@lenne.tech/cli
Version:
lenne.Tech CLI: lt
316 lines (315 loc) • 15.7 kB
JavaScript
;
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;