- 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>
429 lines
12 KiB
TypeScript
429 lines
12 KiB
TypeScript
/**
|
|
* 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<string, any>
|
|
handler: (args: any, context: PluginContext) => Promise<any>
|
|
}
|
|
|
|
export interface ToolExtension {
|
|
name: string
|
|
description: string
|
|
parameters?: any
|
|
handler: (args: any, context: PluginContext) => Promise<any>
|
|
}
|
|
|
|
export interface PluginContext {
|
|
plugin: string
|
|
session: {
|
|
id?: string
|
|
messageId?: string
|
|
}
|
|
config: Map<string, any>
|
|
sandbox: Sandbox
|
|
}
|
|
|
|
export type HookHandler = (context: HookContext) => Promise<void | any>
|
|
|
|
// ============================================================================
|
|
// 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<string, CommandHandler> = new Map()
|
|
private tools: ToolExtension[] = []
|
|
private hooks: Map<HookEvent, HookHandler[]> = new Map()
|
|
private config: Map<string, any> = 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<void>
|
|
async onUnload?(): Promise<void>
|
|
async onEnable?(): Promise<void>
|
|
async onDisable?(): Promise<void>
|
|
|
|
// ============================================================================
|
|
// 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<any> {
|
|
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<void> {
|
|
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<T>(key: string): T | undefined {
|
|
return this.config.get(key) as T
|
|
}
|
|
|
|
getAllConfig(): Map<string, any> {
|
|
return new Map(this.config)
|
|
}
|
|
|
|
loadConfig(configObj: Record<string, any>): 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<string, any> {
|
|
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<PluginConfig> = {}
|
|
private commandHandlers: Map<string, CommandHandler> = new Map()
|
|
private toolExtensions: ToolExtension[] = []
|
|
private hookHandlers: Map<HookEvent, HookHandler[]> = new Map()
|
|
private configValues: Map<string, any> = 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<void>): this {
|
|
return this.hook('PluginLoad', handler as HookHandler)
|
|
}
|
|
|
|
onUnload(handler: () => Promise<void>): 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
|