UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

316 lines (315 loc) 15.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const claude_cli_1 = require("../../lib/claude-cli"); const marketplace_1 = require("../../lib/marketplace"); const plugin_utils_1 = require("../../lib/plugin-utils"); const shell_config_1 = require("../../lib/shell-config"); /** * Install a single plugin (marketplace must already be added and updated) */ function installPlugin(plugin, cli, toolbox) { return __awaiter(this, void 0, void 0, function* () { const { print: { error, info, spin }, } = toolbox; // Step 1: Install or update plugin. // // `claude plugin install` is a no-op for an already-installed plugin: it reports // "already installed" and leaves the active version pinned to whatever was installed // before. To actually pull a newer release from the (freshly updated) marketplace // cache, an existing install must be bumped with `claude plugin update`. const fullPluginName = `${plugin.pluginName}@${plugin.marketplaceName}`; const pluginSpinner = spin(`Installing/updating ${plugin.pluginName}`); let installResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin install ${fullPluginName}`); let pluginAction = 'installed'; if (installResult.output.includes('already installed')) { // Plugin was already present — follow up with an update to reach the latest version. installResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin update ${fullPluginName}`); pluginAction = installResult.output.includes('already') ? 'up to date' : 'updated'; } if (installResult.success || installResult.output.includes('already')) { pluginSpinner.succeed(`${plugin.pluginName} ${pluginAction}`); } else { pluginSpinner.fail(`Failed to install ${plugin.pluginName}`); error(installResult.output); info(''); info('Manual installation:'); info(` /plugin marketplace add ${plugin.marketplaceRepo}`); info(` /plugin install ${fullPluginName}`); info(` /plugin update ${fullPluginName}`); return { action: 'failed', contents: plugin_utils_1.EMPTY_PLUGIN_CONTENTS, postInstall: null, success: false }; } // Step 2: Read plugin contents const pluginContents = (0, plugin_utils_1.readPluginContents)(plugin.marketplaceName, plugin.pluginName); // Warn if plugin seems empty (but not for LSP plugins which don't have skills/commands) const isLspPlugin = plugin.pluginName.endsWith('-lsp'); if (!isLspPlugin && pluginContents.skills.length === 0 && pluginContents.commands.length === 0) { info(''); info(`Warning: No skills or commands found for ${plugin.pluginName}. Plugin may not be installed correctly.`); } // Step 3: Setup permissions if (pluginContents.permissions.length > 0) { const permSpinner = spin(`Configuring permissions for ${plugin.pluginName}`); const permResult = (0, plugin_utils_1.setupPermissions)(pluginContents.permissions, error); if (permResult.success) { if (permResult.added.length > 0) { permSpinner.succeed(`Permissions configured for ${plugin.pluginName} (${permResult.added.length} added)`); } else { permSpinner.succeed(`Permissions already configured for ${plugin.pluginName}`); } } else { permSpinner.fail(`Failed to configure permissions for ${plugin.pluginName}`); info(''); info('Add manually to ~/.claude/settings.json:'); info(JSON.stringify({ permissions: { allow: pluginContents.permissions } }, null, 2)); } } // Step 4: Process post-installation requirements (for LSP plugins, etc.) const postInstallResult = (0, plugin_utils_1.processPostInstall)(plugin.pluginName, toolbox); return { action: pluginAction, contents: pluginContents, postInstall: postInstallResult, success: true }; }); } /** * Prepare marketplaces (add and update cache) for a list of plugins * Returns the set of successfully prepared marketplace names */ function prepareMarketplaces(plugins, cli, toolbox) { const { print: { error, spin }, } = toolbox; // Get unique marketplaces from plugins const marketplaceMap = new Map(); for (const plugin of plugins) { if (!marketplaceMap.has(plugin.marketplaceName)) { marketplaceMap.set(plugin.marketplaceName, plugin); } } const preparedMarketplaces = new Set(); for (const [marketplaceName, plugin] of marketplaceMap) { // Check if marketplace already exists const marketplaceExists = (0, claude_cli_1.checkMarketplaceExists)(marketplaceName); // Add marketplace only if it doesn't exist yet if (!marketplaceExists) { const addSpinner = spin(`Adding marketplace ${marketplaceName}`); const addResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin marketplace add ${plugin.marketplaceRepo}`); if (addResult.success || addResult.output.includes('already')) { addSpinner.succeed(`Marketplace ${marketplaceName} added`); } else { addSpinner.fail(`Failed to add marketplace ${marketplaceName}`); error(addResult.output); continue; } } // Always update marketplace cache to get latest plugin versions const updateSpinner = spin(`Updating ${marketplaceName} cache`); const updateResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin marketplace update ${marketplaceName}`); if (updateResult.success) { updateSpinner.succeed(`${marketplaceName} cache updated`); } else { updateSpinner.stopAndPersist({ symbol: '⚠', text: `Could not update ${marketplaceName} cache, using cached version`, }); } preparedMarketplaces.add(marketplaceName); } return preparedMarketplaces; } /** * Install/update Claude Code Plugins */ const PluginsCommand = { alias: ['p'], description: 'Install Claude Code plugins', name: 'plugins', run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const { helper, parameters, print: { error, info, spin, success, warning }, system, } = toolbox; // Start timer const timer = system.startTimer(); // Check if claude CLI is available (before network requests) const cli = (0, claude_cli_1.findClaudeCli)(); if (!cli) { error('Claude CLI not found. Please install Claude Code first.'); info(''); info('Installation: https://docs.anthropic.com/en/docs/claude-code'); process.exit(1); } // Fetch available plugins from GitHub let availablePlugins; try { availablePlugins = yield (0, marketplace_1.fetchAvailablePlugins)(spin); } catch (err) { error(`Failed to fetch plugins: ${err.message}`); info(''); info('Check your internet connection or try again later.'); process.exit(1); } if (availablePlugins.length === 0) { error('No plugins found in the repository.'); process.exit(1); } // Get plugin names from parameters (if provided) const requestedPlugins = ((_a = parameters.array) === null || _a === void 0 ? void 0 : _a.filter((p) => typeof p === 'string')) || []; // Determine which plugins to install let pluginsToInstall; const notFoundPlugins = []; if (requestedPlugins.length > 0) { // Find specific plugins by name pluginsToInstall = []; for (const name of requestedPlugins) { const plugin = availablePlugins.find((p) => p.pluginName === name); if (plugin) { pluginsToInstall.push(plugin); } else { notFoundPlugins.push(name); } } // Warn about not found plugins but continue with the rest if (notFoundPlugins.length > 0) { warning(`Plugin${notFoundPlugins.length > 1 ? 's' : ''} not found: ${notFoundPlugins.join(', ')}`); } // Check if there are any plugins to install if (pluginsToInstall.length === 0) { error('No valid plugins to install.'); info(''); (0, marketplace_1.printAvailablePlugins)(availablePlugins, info); process.exit(1); } info(`Installing ${pluginsToInstall.length} plugin${pluginsToInstall.length > 1 ? 's' : ''}: ${pluginsToInstall.map((p) => p.pluginName).join(', ')}`); } else { // Install all plugins from primary marketplace (lenne-tech) plus default external plugins const primaryMarketplace = marketplace_1.MARKETPLACES[0].name; const primaryPlugins = availablePlugins.filter((p) => p.marketplaceName === primaryMarketplace); // Add default external plugins const externalPlugins = []; for (const defaultPlugin of marketplace_1.DEFAULT_EXTERNAL_PLUGINS) { const plugin = availablePlugins.find((p) => p.pluginName === defaultPlugin.pluginName && p.marketplaceName === defaultPlugin.marketplaceName); if (plugin) { externalPlugins.push(plugin); } } pluginsToInstall = [...primaryPlugins, ...externalPlugins]; if (externalPlugins.length > 0) { info(`Installing ${primaryPlugins.length} plugins from ${primaryMarketplace}`); info(` + ${externalPlugins.length} default plugins: ${externalPlugins.map((p) => p.pluginName).join(', ')}`); } else { info(`Installing all plugins (${pluginsToInstall.length})...`); } } info(''); // Prepare marketplaces (add and update cache once per marketplace) const preparedMarketplaces = prepareMarketplaces(pluginsToInstall, cli, toolbox); info(''); // Install plugins (only from successfully prepared marketplaces) const results = []; for (const plugin of pluginsToInstall) { // Skip plugins from marketplaces that failed to prepare if (!preparedMarketplaces.has(plugin.marketplaceName)) { results.push({ action: 'failed', contents: plugin_utils_1.EMPTY_PLUGIN_CONTENTS, plugin, postInstall: null, success: false, }); continue; } const result = yield installPlugin(plugin, cli, toolbox); results.push(Object.assign(Object.assign({}, result), { plugin })); if (pluginsToInstall.length > 1 && results.length < pluginsToInstall.length) { info(''); // Add spacing between plugins } } // Summary const successCount = results.filter((r) => r.success).length; const failCount = results.filter((r) => !r.success).length; const totalIssues = failCount + notFoundPlugins.length; info(''); if (totalIssues === 0) { success(`${successCount} plugin${successCount > 1 ? 's' : ''} processed in ${helper.msToMinutesAndSeconds(timer())}m.`); } else { warning(`${successCount} succeeded, ${totalIssues} issue${totalIssues > 1 ? 's' : ''} in ${helper.msToMinutesAndSeconds(timer())}m.`); } // Print summaries for successful installations const successfulResults = results.filter((r) => r.success); if (successfulResults.length > 0) { info(''); info('Installed:'); for (const result of successfulResults) { (0, plugin_utils_1.printPluginSummary)(result.plugin.pluginName, result.contents, info); } } // Print failed installations const failedResults = results.filter((r) => !r.success); if (failedResults.length > 0) { info(''); warning('Failed to install:'); for (const result of failedResults) { info(` ${result.plugin.pluginName}`); } } // Print not found plugins if (notFoundPlugins.length > 0) { info(''); warning('Not found:'); for (const name of notFoundPlugins) { info(` ${name}`); } info(''); (0, marketplace_1.printAvailablePlugins)(availablePlugins, info); } // Collect missing environment variables from all successful installations const allMissingEnvVars = new Map(); for (const result of successfulResults) { if ((_b = result.postInstall) === null || _b === void 0 ? void 0 : _b.envVarsMissing) { for (const envVar of result.postInstall.envVarsMissing) { if (!allMissingEnvVars.has(envVar.name)) { allMissingEnvVars.set(envVar.name, envVar); } } } } // Handle missing environment variables const envVarsResult = yield (0, plugin_utils_1.handleMissingEnvVars)(allMissingEnvVars, toolbox); // Show next steps if (successCount > 0) { info(''); info('Next:'); if (envVarsResult.needed && !envVarsResult.configured) { info(' 1. Set required environment variables (see above)'); info(' 2. Restart Claude Code to activate the plugins'); } else if (envVarsResult.configured || allMissingEnvVars.size > 0) { const shellConfig = (0, shell_config_1.getPreferredShellConfig)(); const sourceCmd = shellConfig ? `source ${shellConfig.path}` : 'source your shell config'; info(` 1. Restart your terminal or run: ${sourceCmd}`); info(' 2. Restart Claude Code to activate the plugins'); } else { info(' Restart Claude Code to activate the plugins'); } } info(''); if (!toolbox.parameters.options.fromGluegunMenu) { process.exit(totalIssues > 0 ? 1 : 0); } return `${successCount} plugin${successCount !== 1 ? 's' : ''} installed${totalIssues > 0 ? `, ${totalIssues} failed` : ''}`; }), }; exports.default = PluginsCommand;