/** * Claude Code Plugin API * * Developer-friendly API for creating Claude Code plugins * Inspired by Conduit's component system */ import { HookSystem, HookEvent, HookContext } from './hook-system' import { SecurityManager, Sandbox, Permission } from './security' // ============================================================================ // TYPES AND INTERFACES // ============================================================================ export interface PluginConfig { name: string version: string description: string author: string license?: string repository?: string permissions?: Permission[] enabled?: boolean } export interface CommandHandler { description: string parameters?: Record handler: (args: any, context: PluginContext) => Promise } export interface ToolExtension { name: string description: string parameters?: any handler: (args: any, context: PluginContext) => Promise } export interface PluginContext { plugin: string session: { id?: string messageId?: string } config: Map sandbox: Sandbox } export type HookHandler = (context: HookContext) => Promise // ============================================================================ // PLUGIN CLASS // ============================================================================ export class Plugin { public readonly name: string public readonly version: string public readonly description: string public readonly author: string public readonly license?: string public readonly repository?: string private permissions: Permission[] private enabled: boolean private commands: Map = new Map() private tools: ToolExtension[] = [] private hooks: Map = new Map() private config: Map = new Map() private security: SecurityManager private hookSystem: HookSystem constructor(config: PluginConfig, security: SecurityManager, hookSystem: HookSystem) { this.name = config.name this.version = config.version this.description = config.description this.author = config.author this.license = config.license this.repository = config.repository this.permissions = config.permissions || [] this.enabled = config.enabled !== false this.security = security this.hookSystem = hookSystem // Create security context this.security.createContext(this.name, this.permissions) } // ============================================================================ // LIFECYCLE HOOKS // ============================================================================ async onLoad?(): Promise async onUnload?(): Promise async onEnable?(): Promise async onDisable?(): Promise // ============================================================================ // COMMAND REGISTRATION // ============================================================================ registerCommand(name: string, handler: CommandHandler): void { this.commands.set(name, handler) } getCommand(name: string): CommandHandler | undefined { return this.commands.get(name) } listCommands(): string[] { return Array.from(this.commands.keys()) } async executeCommand(name: string, args: any, context: PluginContext): Promise { const command = this.commands.get(name) if (!command) { throw new Error(`Command "${name}" not found in plugin "${this.name}"`) } return await command.handler(args, context) } // ============================================================================ // TOOL EXTENSIONS // ============================================================================ registerTool(tool: ToolExtension): void { this.tools.push(tool) } getTools(): ToolExtension[] { return [...this.tools] } // ============================================================================ // HOOK REGISTRATION // ============================================================================ on(event: HookEvent, handler: HookHandler): void { const existing = this.hooks.get(event) || [] existing.push(handler) this.hooks.set(event, existing) } async executeHooks(event: HookEvent, context: HookContext): Promise { const handlers = this.hooks.get(event) || [] for (const handler of handlers) { await handler(context) } } // ============================================================================ // CONFIGURATION // ============================================================================ setConfig(key: string, value: any): void { this.config.set(key, value) } getConfig(key: string): T | undefined { return this.config.get(key) as T } getAllConfig(): Map { return new Map(this.config) } loadConfig(configObj: Record): void { for (const [key, value] of Object.entries(configObj)) { this.config.set(key, value) } } // ============================================================================ // STATE MANAGEMENT // ============================================================================ enable(): void { this.enabled = true } disable(): void { this.enabled = false } isEnabled(): boolean { return this.enabled } // ============================================================================ // SECURITY // ============================================================================ hasPermission(permission: Permission): boolean { return this.permissions.includes(permission) } createSandbox(): Sandbox { const context = this.security.getContext(this.name) if (!context) { throw new Error(`Security context not found for plugin "${this.name}"`) } return new Sandbox(this.security, context) } // ============================================================================ // METADATA // ============================================================================ getMetadata(): PluginConfig { return { name: this.name, version: this.version, description: this.description, author: this.author, license: this.license, repository: this.repository, permissions: this.permissions, enabled: this.enabled, } } toJSON(): Record { return { name: this.name, version: this.version, description: this.description, author: this.author, license: this.license, repository: this.repository, permissions: this.permissions, enabled: this.enabled, commands: this.listCommands(), tools: this.tools.length, hooks: Array.from(this.hooks.keys()), } } } // ============================================================================ // PLUGIN BUILDER - Fluent API for Creating Plugins // ============================================================================ export class PluginBuilder { private config: Partial = {} private commandHandlers: Map = new Map() private toolExtensions: ToolExtension[] = [] private hookHandlers: Map = new Map() private configValues: Map = new Map() name(name: string): this { this.config.name = name return this } version(version: string): this { this.config.version = version return this } description(description: string): this { this.config.description = description return this } author(author: string): this { this.config.author = author return this } license(license: string): this { this.config.license = license return this } repository(repository: string): this { this.config.repository = repository return this } permissions(...permissions: Permission[]): this { this.config.permissions = permissions return this } enabled(enabled: boolean): this { this.config.enabled = enabled return this } command(name: string, handler: CommandHandler): this { this.commandHandlers.set(name, handler) return this } tool(tool: ToolExtension): this { this.toolExtensions.push(tool) return this } hook(event: HookEvent, handler: HookHandler): this { const existing = this.hookHandlers.get(event) || [] existing.push(handler) this.hookHandlers.set(event, existing) return this } config(key: string, value: any): this { this.configValues.set(key, value) return this } onLoad(handler: () => Promise): this { return this.hook('PluginLoad', handler as HookHandler) } onUnload(handler: () => Promise): this { return this.hook('PluginUnload', handler as HookHandler) } onSessionStart(handler: HookHandler): this { return this.hook('SessionStart', handler) } onSessionEnd(handler: HookHandler): this { return this.hook('SessionEnd', handler) } onPreToolUse(handler: HookHandler): this { return this.hook('PreToolUse', handler) } onPostToolUse(handler: HookHandler): this { return this.hook('PostToolUse', handler) } onPreFileEdit(handler: HookHandler): this { return this.hook('PreFileEdit', handler) } onPostFileEdit(handler: HookHandler): this { return this.hook('PostFileEdit', handler) } build(security: SecurityManager, hookSystem: HookSystem): Plugin { if (!this.config.name || !this.config.version || !this.config.author) { throw new Error('Plugin must have name, version, and author') } const plugin = new Plugin( this.config as PluginConfig, security, hookSystem ) // Register commands for (const [name, handler] of this.commandHandlers) { plugin.registerCommand(name, handler) } // Register tools for (const tool of this.toolExtensions) { plugin.registerTool(tool) } // Register hooks for (const [event, handlers] of this.hookHandlers) { for (const handler of handlers) { plugin.on(event, handler) } } // Load config plugin.loadConfig(Object.fromEntries(this.configValues)) return plugin } } // ============================================================================ // HELPER FUNCTIONS // ============================================================================ export function createPlugin( config: PluginConfig, builder?: (plugin: PluginBuilder) => PluginBuilder ): PluginBuilder { const pb = new PluginBuilder() if (config.name) pb.name(config.name) if (config.version) pb.version(config.version) if (config.description) pb.description(config.description) if (config.author) pb.author(config.author) if (config.license) pb.license(config.license) if (config.repository) pb.repository(config.repository) if (config.permissions) pb.permissions(...config.permissions) if (config.enabled !== undefined) pb.enabled(config.enabled) return builder ? builder(pb) : pb } export function definePlugin( config: PluginConfig, definition: (pb: PluginBuilder) => void ): PluginBuilder { const builder = new PluginBuilder() // Set basic config if (config.name) builder.name(config.name) if (config.version) builder.version(config.version) if (config.description) builder.description(config.description) if (config.author) builder.author(config.author) if (config.license) builder.license(config.license) if (config.repository) builder.repository(config.repository) if (config.permissions) builder.permissions(...config.permissions) if (config.enabled !== undefined) builder.enabled(config.enabled) // Apply definition definition(builder) return builder } // ============================================================================ // EXPORTS // ============================================================================ export default Plugin