UNPKG

@ace-sdk/cli

Version:

ACE CLI - Command-line tool for intelligent pattern learning and playbook management

533 lines 22.1 kB
#!/usr/bin/env node /** * ACE CLI - Standalone command-line tool for pattern learning and playbook management */ import { Command } from 'commander'; import tabtab from 'tabtab'; import { configCommand, configShowCommand, configSetCommand, configResetCommand, configValidateCommand } from './commands/config.js'; import { doctorCommand } from './commands/doctor.js'; import { patternsCommand } from './commands/patterns.js'; import { searchCommand } from './commands/search.js'; import { topCommand } from './commands/top.js'; import { learnCommand } from './commands/learn.js'; import { bootstrapCommand } from './commands/bootstrap.js'; import { statusCommand } from './commands/status.js'; import { domainsCommand } from './commands/domains.js'; import { clearCommand } from './commands/clear.js'; import { cacheCommand, cacheRecallCommand } from './commands/cache.js'; import { deltaCommand } from './commands/delta.js'; import { exportCommand } from './commands/export.js'; import { importCommand } from './commands/import.js'; import { recordStartCommand, recordStopCommand, recordListCommand, recordExportCommand, recordDeleteCommand, recordStatusCommand } from './commands/record.js'; import { summarizeCommand } from './commands/summarize.js'; import { pluginListCommand, pluginTrustCommand, pluginUntrustCommand, pluginEnableCommand, pluginDisableCommand } from './commands/plugin.js'; import { projectsListCommand } from './commands/projects.js'; import { tuneCommand, tuneShowCommand, tuneResetCommand } from './commands/tune.js'; import { loginCommand } from './commands/login.js'; import { logoutCommand } from './commands/logout.js'; import { whoamiCommand } from './commands/whoami.js'; import { orgsCommand } from './commands/orgs.js'; import { switchOrgCommand } from './commands/switch-org.js'; import { refreshCommand } from './commands/refresh.js'; import { tokenCommand } from './commands/token.js'; import { devicesListCommand, devicesRenameCommand, devicesRemoveCommand, devicesLimitCommand } from './commands/devices.js'; import { checkCliUpdate } from './utils/version-checker.js'; import { readFileSync, existsSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // Read version from package.json (avoid hardcoding) const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')); const CLI_VERSION = packageJson.version; // Load .claude/settings.json if present in current directory // This allows per-directory ACE configuration function loadLocalSettings() { const settingsPath = join(process.cwd(), '.claude', 'settings.json'); if (existsSync(settingsPath)) { try { const settings = JSON.parse(readFileSync(settingsPath, 'utf8')); // Handle both flat structure and nested env structure const envVars = settings.env || settings; // Set environment variables from settings (only if not already set) // This allows .claude/settings.json to provide defaults that can be overridden by actual env vars if (envVars.ACE_SERVER_URL && !process.env.ACE_SERVER_URL) { process.env.ACE_SERVER_URL = envVars.ACE_SERVER_URL; } if (envVars.ACE_API_TOKEN && !process.env.ACE_API_TOKEN) { process.env.ACE_API_TOKEN = envVars.ACE_API_TOKEN; } if (envVars.ACE_ORG_ID && !process.env.ACE_ORG_ID) { process.env.ACE_ORG_ID = envVars.ACE_ORG_ID; } if (envVars.ACE_PROJECT_ID && !process.env.ACE_PROJECT_ID) { process.env.ACE_PROJECT_ID = envVars.ACE_PROJECT_ID; } // v2.1.0: Verbosity setting if (envVars.ACE_VERBOSITY && !process.env.ACE_VERBOSITY) { process.env.ACE_VERBOSITY = envVars.ACE_VERBOSITY; } } catch (error) { // Silently ignore errors - if settings file is malformed, just skip it } } } // Load local settings before anything else loadLocalSettings(); const program = new Command(); // Handle tab completion if (process.env.COMP_LINE) { const env = tabtab.parseEnv(process.env); if (env.complete) { // Main command completions if (!env.prev || env.prev === 'ce-ace') { tabtab.log([ 'login', 'logout', 'whoami', 'orgs', 'switch-org', 'refresh', 'token', 'devices', 'status', 'patterns', 'search', 'top', 'learn', 'bootstrap', 'cache', 'delta', 'export', 'import', 'record', 'summarize', 'plugin', 'config', 'doctor', 'projects', 'clear', 'completion', 'tune', 'domains' ]); } // Subcommand completions else if (env.prev === 'record') { tabtab.log(['start', 'stop', 'list', 'export', 'delete', 'status']); } else if (env.prev === 'plugin') { tabtab.log(['list', 'trust', 'untrust', 'enable', 'disable']); } else if (env.prev === 'config') { tabtab.log(['show', 'set', 'reset', 'validate']); } else if (env.prev === 'tune') { tabtab.log(['show', 'reset']); } else if (env.prev === 'devices') { tabtab.log(['list', 'rename', 'remove', 'limit']); } else if (env.prev === 'projects') { tabtab.log(['list']); } else if (env.prev === 'cache') { tabtab.log(['clear']); } else if (env.prev === 'delta') { tabtab.log(['add', 'update', 'remove']); } else if (env.prev === 'completion') { tabtab.log(['install', 'uninstall']); } // Flag completions for specific commands else if (env.prev === 'search') { tabtab.log(['--stdin', '--threshold', '--section', '--json', '--verbose', '--quiet']); } else if (env.prev === 'patterns') { tabtab.log(['--section', '--min-helpful', '--json', '--verbose', '--quiet']); } else if (env.prev === 'bootstrap') { tabtab.log(['--mode', '--thoroughness', '--commit-limit', '--days-back', '--max-files', '--repo-path', '--merge']); } process.exit(0); } } // Check for CLI updates in background (non-blocking, cached for 24h) checkCliUpdate().catch(() => { // Silently ignore errors - update checks shouldn't break the CLI }); // Make global options accessible export let globalOptions = { json: false, verbose: false, quiet: false, trace: false, org: undefined, project: undefined, }; program .name('ce-ace') .description('ACE CLI - Intelligent pattern learning and playbook management') .version(CLI_VERSION) .option('--json', 'Output structured JSON (for automation)') .option('--verbose', 'Show detailed output') .option('--quiet', 'Suppress non-essential output') .option('--trace', 'Log raw HTTP requests and responses (debug mode)') .option('--org <org_id>', 'Organization ID (overrides env and .claude/settings.json)') .option('--project <project_id>', 'Project ID (overrides env and .claude/settings.json)') .hook('preAction', (thisCommand) => { const opts = thisCommand.optsWithGlobals(); globalOptions = { json: opts.json || false, verbose: opts.verbose || false, quiet: opts.quiet || false, trace: opts.trace || false, org: opts.org, project: opts.project, }; }); // Authentication commands (v3.10.0+) program .command('login') .description('Authenticate with ACE server (opens browser)') .option('--no-browser', 'Skip opening browser (for SSH/headless mode)') .action(loginCommand); program .command('logout') .description('Clear authentication credentials') .action(logoutCommand); program .command('whoami') .description('Display current user info') .option('--server', 'Fetch real-time token status from server (validates sliding window)') .action(whoamiCommand); program .command('orgs') .description('List your organizations') .action(orgsCommand); program .command('switch-org <nameOrId>') .description('Switch default organization') .action(switchOrgCommand); program .command('refresh') .description('Refresh organizations from server') .action(refreshCommand); program .command('token') .description('Display current API token') .option('--reveal', 'Show full token (unmasked)') .action(tokenCommand); // Device management commands (v3.6.0+) const devices = program .command('devices') .description('Manage your devices'); devices .command('list') .description('List all authorized devices') .action(devicesListCommand); devices .command('rename <id> <name>') .description('Rename a device') .action(devicesRenameCommand); devices .command('remove <id>') .description('Remove a device (revokes all sessions)') .option('--yes', 'Skip confirmation prompt') .action(devicesRemoveCommand); devices .command('limit') .description('Show device limit') .action(devicesLimitCommand); // Configuration commands const config = program .command('config') .description('Configuration wizard (interactive or non-interactive)') .option('--server-url <url>', 'ACE server URL') .option('--api-token <token>', 'API token') .option('--org-id <org_id>', 'Organization ID (optional)') .option('--project-id <project_id>', 'Project ID') .action(configCommand); config .command('show') .description('Display current configuration') .action(configShowCommand); config .command('set <key> <value>') .description('Set a configuration value') .action(configSetCommand); config .command('reset') .description('Reset configuration to defaults') .option('--yes', 'Skip confirmation prompt') .action(configResetCommand); config .command('validate') .description('Validate token and display organization/project info') .option('--server-url <url>', 'ACE server URL') .option('--api-token <token>', 'API token') .action((_options, command) => { // Commander.js subcommands need optsWithGlobals() to get options // This merges parent and subcommand options, with kebab→camelCase conversion const opts = command.optsWithGlobals(); configValidateCommand({ serverUrl: opts.serverUrl, apiToken: opts.apiToken }); }); // Project discovery (v3.8.0: uses new /api/v1/auth/projects endpoint) const projects = program .command('projects') .description('List accessible projects across organizations'); projects .command('list') .description('List all projects you have access to') .option('--org <org_id>', 'Filter to specific organization') .action(projectsListCommand); // Default action for `ce-ace projects` (without subcommand) projects.action(projectsListCommand); // Diagnostics program .command('doctor') .description('Run diagnostics on ACE configuration and connectivity') .action(doctorCommand); // Tune thresholds and settings (actual server fields - 11 total) const tune = program .command('tune') .description('Tune ACE server configuration (interactive or flags)') .option('--scope <scope>', 'Configuration scope (project)', 'project') .option('--constitution-threshold <value>', 'Retrieval similarity threshold (0.0-1.0)', parseFloat) .option('--search-top-k <value>', 'Max search results (1-100)', parseInt) .option('--dedup-similarity-threshold <value>', 'Deduplication similarity threshold (0.0-1.0)', parseFloat) .option('--dedup-enabled <boolean>', 'Enable/disable deduplication', (v) => v === 'true') .option('--pruning-threshold <value>', 'Pruning threshold to keep patterns (0.0-1.0)', parseFloat) .option('--max-playbook-tokens <value>', 'Maximum playbook tokens (null for unlimited)', parseInt) .option('--token-budget-enforcement <boolean>', 'Enable/disable token budget enforcement', (v) => v === 'true') .option('--max-batch-size <value>', 'Maximum patterns per batch (1-100)', parseInt) .option('--auto-learning-enabled <boolean>', 'Enable/disable automatic learning', (v) => v === 'true') .option('--reflector-enabled <boolean>', 'Enable/disable Reflector agent', (v) => v === 'true') .option('--curator-enabled <boolean>', 'Enable/disable Curator agent', (v) => v === 'true') .action(tuneCommand); tune .command('show') .description('Show current effective configuration') .action(tuneShowCommand); tune .command('reset') .description('Reset configuration to defaults') .option('--scope <scope>', 'Scope to reset (project)', 'project') .action(tuneResetCommand); // Playbook access program .command('patterns') .description('View playbook patterns') .option('--section <section>', 'Filter by section (strategies|snippets|troubleshooting|apis)') .option('--min-helpful <score>', 'Minimum helpful score', parseFloat) .action(patternsCommand); program .command('search [query]') .description('Search for patterns using semantic search') .option('--stdin', 'Read query from stdin') .option('--threshold <value>', 'Similarity threshold (0.0-1.0) - overrides server default', parseFloat) .option('--section <section>', 'Filter by section - overrides server default') .option('--top-k <number>', 'Maximum patterns to return (1-100)', parseInt) .option('--pin-session <sessionId>', 'Pin results to session for later recall') .option('--allowed-domains <domains>', 'Whitelist: Only return patterns from these domains (comma-separated)') .option('--blocked-domains <domains>', 'Blacklist: Exclude patterns from these domains (comma-separated)') .action(searchCommand); program .command('top') .description('Show top-rated patterns') .option('--section <section>', 'Filter by section') .option('--limit <number>', 'Number of patterns to show (server adaptive default if omitted)', parseInt) .option('--min-helpful <score>', 'Minimum helpful score', parseFloat) .action(topCommand); // Learning / Knowledge updates program .command('learn') .description('Submit learning event to update playbook') .option('--transcript <file>', 'Path to transcript file') .option('--stdin', 'Read transcript from stdin') .option('--task <description>', 'Task description') .option('--success', 'Task was successful') .option('--failure', 'Task failed') .option('--output <text>', 'Output/lessons learned') .option('--no-stream', 'Disable SSE streaming (use sync POST)') .option('--timeout <ms>', 'Stream timeout in milliseconds', parseInt, 60000) .option('--verbosity <mode>', 'Output detail level (compact|detailed)', 'compact') // Git context options (v2.2.0+ AI-Trail) .option('--git-context', 'Auto-detect git context from cwd (default: true)') .option('--no-git-context', 'Disable git context detection') .option('--git-commit <hash>', 'Explicit commit hash (overrides auto-detection)') .option('--git-branch <name>', 'Explicit branch name (overrides auto-detection)') .action(learnCommand); // Bootstrap / Data ingestion program .command('bootstrap') .description('Initialize playbook from codebase') .option('--mode <mode>', 'Analysis mode (hybrid|both|local-files|git-history|docs-only)', 'hybrid') .option('--thoroughness <level>', 'Analysis depth (light|medium|deep)', 'medium') .option('--commit-limit <number>', 'Max commits to analyze', parseInt) .option('--days-back <number>', 'Days of history to analyze', parseInt) .option('--max-files <number>', 'Max files to analyze', parseInt) .option('--repo-path <path>', 'Path to repository', process.cwd()) .option('--merge', 'Merge with existing playbook', true) .action(bootstrapCommand); // Maintenance program .command('status') .description('Show playbook statistics') .action(statusCommand); program .command('domains') .description('List available pattern domains for search filtering') .option('--min-patterns <count>', 'Minimum patterns to show domain', (v) => parseInt(v, 10), 1) .action(domainsCommand); program .command('clear') .description('Clear the playbook') .option('--yes', 'Skip confirmation prompt') .action(clearCommand); const cache = program .command('cache') .description('Manage local cache'); cache .command('clear') .description('Clear local cache') .option('--type <type>', 'Cache type (ram|sqlite|all)', 'all') .action(cacheCommand); cache .command('recall') .description('Recall patterns from a pinned session') .requiredOption('--session <sessionId>', 'Session ID to recall') .action(cacheRecallCommand); program .command('delta <operation>') .description('Apply incremental pattern updates (add|update|remove)') .option('--bullets <json>', 'Bullets as JSON string') .option('--file <path>', 'Read bullets from JSON file') .option('--stdin', 'Read bullets from stdin') .action(deltaCommand); program .command('export') .description('Export playbook to JSON') .option('--output <file>', 'Output file (default: stdout)') .action(exportCommand); program .command('import [file]') .description('Import playbook from JSON file or stdin') .option('--file <path>', 'Import from file') .option('--stdin', 'Read playbook from stdin') .action(importCommand); // Session recording const record = program .command('record') .description('Manage session recordings'); record .command('start') .description('Start recording a session') .option('--session <name>', 'Session name/description') .action(recordStartCommand); record .command('stop') .description('Stop the current recording session') .action(recordStopCommand); record .command('list') .description('List all recorded sessions') .action(recordListCommand); record .command('export <sessionId> <output>') .description('Export a session to a file') .action(recordExportCommand); record .command('delete <sessionId>') .description('Delete a session recording') .option('--yes', 'Skip confirmation prompt') .action(recordDeleteCommand); record .command('status') .description('Show current recording status') .action(recordStatusCommand); // Session summarization program .command('summarize [sessionIdOrFile]') .description('Summarize a recorded session using AI') .option('--stdin', 'Read session data from stdin') .option('--output <file>', 'Save summary to file') .option('--format <format>', 'Output format (text|json|markdown)', 'text') .action(summarizeCommand); // Plugin management const plugin = program .command('plugin') .description('Manage plugins'); plugin .command('list') .description('List all available plugins') .action(pluginListCommand); plugin .command('trust <name>') .description('Trust a plugin (required before enabling)') .option('--yes', 'Skip confirmation prompt') .action(pluginTrustCommand); plugin .command('untrust <name>') .description('Untrust and disable a plugin') .action(pluginUntrustCommand); plugin .command('enable <name>') .description('Enable a trusted plugin') .action(pluginEnableCommand); plugin .command('disable <name>') .description('Disable a plugin') .action(pluginDisableCommand); // Shell completion const completion = program .command('completion') .description('Manage shell completion (bash/zsh/fish)'); completion .command('install') .description('Install shell completion for ce-ace') .action(async () => { try { await tabtab.install({ name: 'ce-ace', completer: 'ce-ace' }); console.log('Shell completion installed successfully!'); console.log('Please restart your shell or run: source ~/.bashrc (or ~/.zshrc)'); } catch (error) { console.error('Failed to install completion:', error instanceof Error ? error.message : String(error)); process.exit(1); } }); completion .command('uninstall') .description('Uninstall shell completion for ce-ace') .action(async () => { try { await tabtab.uninstall({ name: 'ce-ace' }); console.log('Shell completion uninstalled successfully!'); } catch (error) { console.error('Failed to uninstall completion:', error instanceof Error ? error.message : String(error)); process.exit(1); } }); // Only run CLI when executed directly (not when imported for testing) const isMainModule = process.argv[1] && (process.argv[1].endsWith('/cli.js') || process.argv[1].endsWith('/ce-ace') || process.argv[1].endsWith('/ace-cli') || process.argv[1].includes('cli/dist/cli.js')); if (isMainModule) { // Handle uncaught errors process.on('unhandledRejection', (reason, _promise) => { if (!globalOptions.json) { console.error('Unhandled rejection:', reason); } else { console.error(JSON.stringify({ error: String(reason), type: 'unhandled_rejection' })); } process.exit(1); }); process.on('uncaughtException', (error) => { if (!globalOptions.json) { console.error('Uncaught exception:', error); } else { console.error(JSON.stringify({ error: error.message, stack: error.stack, type: 'uncaught_exception' })); } process.exit(1); }); // Parse arguments program.parse(process.argv); // Show help if no command provided if (!process.argv.slice(2).length) { program.outputHelp(); process.exit(0); } } //# sourceMappingURL=cli.js.map