#!/usr/bin/env node /** * Claude Code Plugin CLI * * Command-line interface for managing Claude Code plugins * Inspired by Conduit's CLI interface */ import { PluginManager } from './plugin-manager' import { HookSystem } from './hook-system' import { SecurityManager } from './security' import { resolve } from 'path' // ============================================================================ // CLI CLASS // ============================================================================ class PluginCLI { private pluginManager: PluginManager private hookSystem: HookSystem private security: SecurityManager constructor() { const claudeDir = process.env.CLAUDE_DIR || resolve(process.env.HOME || '', '.claude') this.pluginManager = new PluginManager(claudeDir) this.hookSystem = new HookSystem(claudeDir) this.security = new SecurityManager() } async run(args: string[]): Promise { const [command, ...rest] = args try { await this.initialize() switch (command) { case 'discover': case 'list': await this.discover(rest[0]) break case 'install': await this.install(rest[0], rest[1]) break case 'install-github': await this.installFromGitHub(rest[0]) break case 'uninstall': await this.uninstall(rest[0], rest[1]) break case 'enable': await this.enable(rest[0], rest[1]) break case 'disable': await this.disable(rest[0], rest[1]) break case 'update': await this.update(rest[0], rest[1]) break case 'info': await this.info(rest[0]) break case 'hooks': await this.listHooks(rest[0]) break case 'add-marketplace': await this.addMarketplace(rest[0], rest[1]) break case 'validate': await this.validate(rest[0]) break default: this.showHelp() } } catch (error) { console.error(`āŒ Error: ${error instanceof Error ? error.message : String(error)}`) process.exit(1) } } private async initialize(): Promise { await this.pluginManager.initialize() await this.hookSystem.initialize() } // ============================================================================ // COMMANDS // ============================================================================ private async discover(query?: string): Promise { console.log('šŸ” Discovering plugins...\n') const plugins = await this.pluginManager.discoverPlugins(query) if (plugins.length === 0) { console.log('No plugins found.') return } console.log(`Found ${plugins.length} plugin(s):\n`) for (const plugin of plugins) { console.log(`šŸ“¦ ${plugin.name}`) console.log(` Description: ${plugin.description}`) console.log(` Version: ${plugin.version}`) console.log(` Author: ${plugin.author}`) console.log(` Source: ${plugin.source}`) console.log('') } } private async install(marketplace: string, pluginName?: string): Promise { if (!marketplace) { throw new Error('Usage: claude-plugin install [plugin-name]') } if (!pluginName) { // Discover plugins in marketplace const plugins = await this.pluginManager.discoverPlugins() const marketplacePlugins = plugins.filter(p => p.source === marketplace) if (marketplacePlugins.length === 0) { console.log(`No plugins found in marketplace "${marketplace}"`) return } console.log(`\nšŸ“¦ Available plugins in "${marketplace}":\n`) marketplacePlugins.forEach(p => { console.log(` • ${p.name} - ${p.description}`) }) return } console.log(`šŸ“¦ Installing ${pluginName} from ${marketplace}...`) const plugin = await this.pluginManager.installPlugin(marketplace, pluginName) console.log(`\nāœ“ Successfully installed ${plugin.metadata.name} v${plugin.version}`) console.log(` Location: ${plugin.installPath}`) console.log(` Permissions: ${plugin.metadata.claude.permissions.join(', ')}`) } private async installFromGitHub(repo: string): Promise { if (!repo) { throw new Error('Usage: claude-plugin install-github ') } console.log(`šŸ“¦ Installing plugin from GitHub: ${repo}...`) const plugin = await this.pluginManager.installFromGitHub(repo) console.log(`\nāœ“ Successfully installed ${plugin.metadata.name} v${plugin.version}`) console.log(` Location: ${plugin.installPath}`) console.log(` Permissions: ${plugin.metadata.claude.permissions.join(', ')}`) } private async uninstall(pluginName: string, marketplace?: string): Promise { if (!pluginName) { throw new Error('Usage: claude-plugin uninstall [marketplace]') } console.log(`šŸ—‘ļø Uninstalling ${pluginName}...`) await this.pluginManager.uninstallPlugin(pluginName, marketplace) console.log(`āœ“ Successfully uninstalled ${pluginName}`) } private async enable(pluginName: string, marketplace?: string): Promise { if (!pluginName) { throw new Error('Usage: claude-plugin enable [marketplace]') } await this.pluginManager.enablePlugin(pluginName, marketplace) console.log(`āœ“ Enabled ${pluginName}`) } private async disable(pluginName: string, marketplace?: string): Promise { if (!pluginName) { throw new Error('Usage: claude-plugin disable [marketplace]') } await this.pluginManager.disablePlugin(pluginName, marketplace) console.log(`āœ“ Disabled ${pluginName}`) } private async update(pluginName: string, marketplace?: string): Promise { if (!pluginName) { throw new Error('Usage: claude-plugin update [marketplace]') } console.log(`šŸ”„ Updating ${pluginName}...`) await this.pluginManager.updatePlugin(pluginName, marketplace) console.log(`āœ“ Updated ${pluginName}`) } private async info(pluginName: string): Promise { if (!pluginName) { throw new Error('Usage: claude-plugin info ') } const installed = await this.pluginManager.loadInstalledPlugins() for (const [key, plugins] of Object.entries(installed)) { if (key.includes(pluginName)) { const plugin = plugins[0] console.log(`\nšŸ“¦ ${plugin.metadata.name}`) console.log(`Version: ${plugin.version}`) console.log(`Description: ${plugin.metadata.description}`) console.log(`Author: ${plugin.metadata.author}`) console.log(`License: ${plugin.metadata.license || 'Not specified'}`) console.log(`Repository: ${plugin.metadata.repository || 'Not specified'}`) console.log(`\nInstalled:`) console.log(` Path: ${plugin.installPath}`) console.log(` Date: ${plugin.installedAt}`) console.log(` Scope: ${plugin.scope}`) console.log(` Enabled: ${plugin.enabled ? 'Yes' : 'No'}`) console.log(`\nPermissions:`) plugin.metadata.claude.permissions.forEach(perm => { console.log(` • ${perm}`) }) if (plugin.metadata.claude.commands?.length) { console.log(`\nCommands (${plugin.metadata.claude.commands.length}):`) plugin.metadata.claude.commands.forEach(cmd => { console.log(` • ${cmd.name} - ${cmd.description}`) }) } if (plugin.metadata.claude.hooks?.length) { console.log(`\nHooks (${plugin.metadata.claude.hooks.length}):`) plugin.metadata.claude.hooks.forEach(hook => { console.log(` • ${hook.event} - ${hook.handler}`) }) } return } } console.log(`Plugin "${pluginName}" is not installed.`) } private async listHooks(event?: string): Promise { if (event) { const hooks = await this.hookSystem.listHooksByEvent(event as any) console.log(`\nšŸŖ Registered hooks for "${event}":\n`) if (hooks.length === 0) { console.log(' No hooks registered') } hooks.forEach((hook, i) => { console.log(` ${i + 1}. ${hook.type}`) if (hook.command) console.log(` Command: ${hook.command}`) if (hook.module) console.log(` Module: ${hook.module}`) if (hook.priority !== undefined) console.log(` Priority: ${hook.priority}`) console.log(` Enabled: ${hook.enabled !== false}`) }) } else { const hooks = this.hookSystem.getRegisteredHooks() console.log('\nšŸŖ All registered hooks:\n') for (const [evt, hookList] of hooks.entries()) { console.log(`${evt}: ${hookList.length} hook(s)`) } } } private async addMarketplace(name: string, url: string): Promise { if (!name || !url) { throw new Error('Usage: claude-plugin add-marketplace ') } // Parse GitHub URL const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/) if (!match) { throw new Error('Invalid GitHub URL') } const [, owner, repo] = match await this.pluginManager.addMarketplace(name, { type: 'github', repo: `${owner}/${repo}`, }) console.log(`āœ“ Added marketplace "${name}"`) } private async validate(pluginPath: string): Promise { if (!pluginPath) { throw new Error('Usage: claude-plugin validate ') } console.log(`šŸ” Validating plugin at ${pluginPath}...`) // Check for plugin.json const pluginJsonPath = resolve(pluginPath, '.claude-plugin', 'plugin.json') console.log(` āœ“ Checking plugin.json...`) // Validate structure console.log(` āœ“ Validating structure...`) // Calculate integrity const integrity = await this.security.calculateDirectoryIntegrity(pluginPath) console.log(` āœ“ Integrity: ${integrity.slice(0, 16)}...`) console.log('\nāœ“ Plugin is valid!') } // ============================================================================ // HELP // ============================================================================ private showHelp(): void { console.log(` Claude Code Plugin Manager Commands: discover [query] List available plugins (optional search query) install Install a plugin from a marketplace [plugin-name] install-github Install a plugin directly from GitHub (user/repo) uninstall Uninstall a plugin [marketplace] enable Enable a plugin [marketplace] disable Disable a plugin [marketplace] update Update a plugin to the latest version [marketplace] info Show detailed information about a plugin hooks [event] List registered hooks (optional: specific event) add-marketplace Add a new plugin marketplace validate Validate a plugin Examples: claude-plugin discover claude-plugin discover git claude-plugin install claude-plugins-official hookify claude-plugin install-github username/my-plugin claude-plugin uninstall hookify claude-plugin info hookify claude-plugin hooks PreFileEdit For more information, visit: https://github.com/anthropics/claude-code `) } } // ============================================================================ // MAIN // ============================================================================ async function main() { const cli = new PluginCLI() await cli.run(process.argv.slice(2)) } main().catch((error) => { console.error('Fatal error:', error) process.exit(1) })