Files
admin b723e2bd7d Reorganize: Move all skills to skills/ folder
- Created skills/ directory
- Moved 272 skills to skills/ subfolder
- Kept agents/ at root level
- Kept installation scripts and docs at root level

Repository structure:
- skills/           - All 272 skills from skills.sh
- agents/           - Agent definitions
- *.sh, *.ps1       - Installation scripts
- README.md, etc.   - Documentation

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-23 18:05:17 +00:00

386 lines
12 KiB
JavaScript

#!/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<void> {
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<void> {
await this.pluginManager.initialize()
await this.hookSystem.initialize()
}
// ============================================================================
// COMMANDS
// ============================================================================
private async discover(query?: string): Promise<void> {
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<void> {
if (!marketplace) {
throw new Error('Usage: claude-plugin install <marketplace> [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<void> {
if (!repo) {
throw new Error('Usage: claude-plugin install-github <user/repo>')
}
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<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin uninstall <plugin-name> [marketplace]')
}
console.log(`🗑️ Uninstalling ${pluginName}...`)
await this.pluginManager.uninstallPlugin(pluginName, marketplace)
console.log(`✓ Successfully uninstalled ${pluginName}`)
}
private async enable(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin enable <plugin-name> [marketplace]')
}
await this.pluginManager.enablePlugin(pluginName, marketplace)
console.log(`✓ Enabled ${pluginName}`)
}
private async disable(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin disable <plugin-name> [marketplace]')
}
await this.pluginManager.disablePlugin(pluginName, marketplace)
console.log(`✓ Disabled ${pluginName}`)
}
private async update(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin update <plugin-name> [marketplace]')
}
console.log(`🔄 Updating ${pluginName}...`)
await this.pluginManager.updatePlugin(pluginName, marketplace)
console.log(`✓ Updated ${pluginName}`)
}
private async info(pluginName: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin info <plugin-name>')
}
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<void> {
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<void> {
if (!name || !url) {
throw new Error('Usage: claude-plugin add-marketplace <name> <github-url>')
}
// 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<void> {
if (!pluginPath) {
throw new Error('Usage: claude-plugin validate <plugin-path>')
}
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 <marketplace> Install a plugin from a marketplace
[plugin-name]
install-github <repo> Install a plugin directly from GitHub (user/repo)
uninstall <plugin-name> Uninstall a plugin
[marketplace]
enable <plugin-name> Enable a plugin
[marketplace]
disable <plugin-name> Disable a plugin
[marketplace]
update <plugin-name> Update a plugin to the latest version
[marketplace]
info <plugin-name> Show detailed information about a plugin
hooks [event] List registered hooks (optional: specific event)
add-marketplace <name> Add a new plugin marketplace
<github-url>
validate <path> 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)
})