#!/usr/bin/env node const { spawnSync } = require('child_process'); const path = require('path'); const { listAvailableLanguages } = require('./lib/install-executor'); const { getComputeSponsorCopy } = require('./lib/compute-sponsor'); const { createSafeItoInvocationEnvironment, getInvocationCommand } = require('./lib/ito-environment'); const COMMANDS = { setup: { script: 'setup.js', description: 'Install or update the Claude plugin with guided scope and hook choices', }, welcome: { script: 'welcome.js', description: 'Show the ECC welcome artwork and community links', }, install: { script: 'install-apply.js', description: 'Install ECC content, including the guided multi-harness wizard', }, plan: { script: 'install-plan.js', description: 'Inspect selective-install manifests and resolved plans', }, catalog: { script: 'catalog.js', description: 'Discover install profiles and component IDs', }, consult: { script: 'consult.js', description: 'Recommend ECC components and profiles from a natural language query', }, 'control-pane': { script: 'control-pane.js', description: 'Run the local ECC2 operator control pane', }, ito: { script: 'ito.js', description: 'Invoke the separately installed canonical Itô compute CLI', }, nasiko: { script: 'nasiko.js', description: 'Install or inspect the optional pinned Nasiko control-plane CLI', }, memory: { script: 'memory.js', description: 'Share durable context across Claude, Codex, Hermes, and other harnesses', }, 'install-plan': { script: 'install-plan.js', description: 'Alias for plan', }, 'list-installed': { script: 'list-installed.js', description: 'Inspect install-state files for the current context', }, doctor: { script: 'doctor.js', description: 'Diagnose missing or drifted ECC-managed files', }, feedback: { script: 'feedback.js', description: 'Open the shortest path to report a problem, feedback, or an idea', }, repair: { script: 'repair.js', description: 'Restore drifted or missing ECC-managed files', }, 'auto-update': { script: 'auto-update.js', description: 'Pull latest ECC changes and reinstall the current managed targets', }, status: { script: 'status.js', description: 'Query the ECC SQLite state store status summary', }, 'platform-audit': { script: 'platform-audit.js', description: 'Audit GitHub queues, discussions, roadmap, release, and security evidence', }, 'security-ioc-scan': { script: 'ci/scan-supply-chain-iocs.js', description: 'Scan dependency and AI-tool persistence surfaces for active supply-chain IOCs', }, sessions: { script: 'sessions-cli.js', description: 'List or inspect ECC sessions from the SQLite state store', }, 'work-items': { script: 'work-items.js', description: 'Track linked Linear, GitHub, handoff, and manual work items', }, 'session-inspect': { script: 'session-inspect.js', description: 'Emit canonical ECC session snapshots from dmux or Claude history targets', }, 'loop-status': { script: 'loop-status.js', description: 'Inspect Claude transcripts for stale loop wakeups and pending tool results', }, uninstall: { script: 'uninstall.js', description: 'Remove ECC-managed files recorded in install-state', }, }; const PRIMARY_COMMANDS = [ 'setup', 'welcome', 'install', 'plan', 'catalog', 'consult', 'control-pane', 'ito', 'nasiko', 'memory', 'list-installed', 'doctor', 'feedback', 'repair', 'auto-update', 'status', 'platform-audit', 'security-ioc-scan', 'sessions', 'work-items', 'session-inspect', 'loop-status', 'uninstall', ]; function showHelp(exitCode = 0) { process.stdout.write(` ECC selective-install CLI Usage: ecc [args...] ecc [install args...] ecc --dry-run [args...] Commands: ${PRIMARY_COMMANDS.map(command => ` ${command.padEnd(15)} ${COMMANDS[command].description}`).join('\n')} Compatibility: ecc-install Legacy install entrypoint retained for existing flows ecc [args...] Without a command, args are routed to "install" ecc help Show help for a specific command Global Flags: --dry-run Preview actions without executing (sets ECC_DRY_RUN=1) Compute: ${getComputeSponsorCopy()} Examples: ecc setup ecc setup --mode claude-plugin --scope user --hooks standard --yes ecc welcome ecc install --guided ecc install --guided --harness claude --harness codex --harness kimi ecc typescript ecc install --profile developer --target claude ecc plan --profile core --target cursor ecc catalog profiles ecc catalog components --family language ecc catalog show framework:nextjs ecc consult "security reviews" ecc control-pane --port 8765 ecc ito login [--no-browser] ecc ito logout ecc ito auth ecc ito find --gpu h200 --count 8 --nodes 1 --gpus-per-node 8 --days 30 --storage-tb 1 --start-window 2099-08-15 --max-rate 3.00 --form-factor bare_metal --contract-type reservation --fabric infiniband --region us-east-1 ecc ito status --json ecc nasiko status --json ecc nasiko install --version v0.1.0 --dry-run --json ecc nasiko install --version v0.1.0 --yes --json ecc ito evals --cluster clu_prod_example --live-sixtytwo --nodes gpu-01,gpu-02 --config-dir /absolute/path/to/qualification-config ecc memory init ecc memory handoff --from codex --target claude --title "Continue migration" --stdin ecc memory search "migration blockers" --target-harness hermes ecc list-installed --json ecc doctor --target cursor ecc feedback ecc repair --dry-run ecc auto-update --dry-run ecc status --json ecc status --exit-code ecc status --markdown --write status.md ecc platform-audit --json --allow-untracked docs/drafts/ ecc security-ioc-scan --home ecc sessions ecc sessions session-active --json ecc work-items upsert linear-ecc-20 --source linear --source-id ECC-20 --title "Review control-plane contract" --status blocked ecc work-items sync-github --repo affaan-m/ECC ecc session-inspect claude:latest ecc loop-status --json ecc uninstall --target antigravity --dry-run `); process.exit(exitCode); } function resolveCommand(argv) { const args = argv.slice(2); if (args.length === 0) { return { mode: 'help' }; } if (args.includes('--dry-run')) { process.env.ECC_DRY_RUN = '1'; } let cmdStart = 0; while (cmdStart < args.length && args[cmdStart] === '--dry-run') { cmdStart++; } if (cmdStart >= args.length) { return { mode: 'help' }; } const firstArg = args[cmdStart]; const restArgs = args.slice(cmdStart + 1); if (firstArg === '--help' || firstArg === '-h') { return { mode: 'help' }; } if (firstArg === 'help') { return { mode: 'help-command', command: restArgs[0] || null, }; } if (COMMANDS[firstArg]) { return { mode: 'command', command: firstArg, args: restArgs, }; } const knownLegacyLanguages = listAvailableLanguages(); const shouldTreatAsImplicitInstall = ( firstArg.startsWith('-') || knownLegacyLanguages.includes(firstArg) ); if (!shouldTreatAsImplicitInstall) { throw new Error(`Unknown command: ${firstArg}`); } return { mode: 'command', command: 'install', args, }; } function runCommand(commandName, args) { const command = COMMANDS[commandName]; if (!command) { throw new Error(`Unknown command: ${commandName}`); } const isItoLogin = commandName === 'ito' && getInvocationCommand(args) === 'login'; const result = spawnSync( process.execPath, [path.join(__dirname, command.script), ...args], { cwd: process.cwd(), env: commandName === 'ito' ? { ...createSafeItoInvocationEnvironment(process.env, args, { includeControls: true, }), } : process.env, stdio: isItoLogin || commandName === 'setup' || commandName === 'install' ? 'inherit' : commandName === 'memory' ? ['inherit', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'], encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, } ); if (result.error) { throw result.error; } if (result.stdout) { process.stdout.write(result.stdout); } if (result.stderr) { process.stderr.write(result.stderr); } if (typeof result.status === 'number') { return result.status; } if (result.signal) { throw new Error(`Command "${commandName}" terminated by signal ${result.signal}`); } return 1; } function main() { try { const resolution = resolveCommand(process.argv); if (resolution.mode === 'help') { showHelp(0); } if (resolution.mode === 'help-command') { if (!resolution.command) { showHelp(0); } if (!COMMANDS[resolution.command]) { throw new Error(`Unknown command: ${resolution.command}`); } process.exitCode = runCommand(resolution.command, ['--help']); return; } process.exitCode = runCommand(resolution.command, resolution.args); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } } main();