index.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #!/usr/bin/env node
  2. import { Command } from 'commander'
  3. import { readCliVersion } from './lib/version.js'
  4. import { checkForUpdate } from './lib/update-check.js'
  5. const program = new Command()
  6. program
  7. .name('oac')
  8. .description('OpenAgents Control — install, manage, and update AI agents and context files')
  9. .version(readCliVersion(), '-v, --version', 'Print version and exit')
  10. .addHelpText('after', `
  11. Examples:
  12. $ oac init Set up OAC in the current project
  13. $ oac update Update OAC files (skips files you modified)
  14. $ oac update --dry-run Preview what would be updated
  15. $ oac doctor Check your setup and report issues
  16. $ oac add agent:openagent Add a specific agent from the registry
  17. $ oac apply cursor Generate Cursor IDE rules file
  18. $ oac clean --dry-run Preview what oac clean would remove
  19. Docs: https://github.com/darrenhinde/OpenAgentsControl#readme
  20. `)
  21. // Restore terminal state on Ctrl-C or kill signal
  22. // Exit codes follow Unix convention: 128 + signal number
  23. process.on('SIGINT', () => process.exit(130)) // 128 + 2 (SIGINT)
  24. process.on('SIGTERM', () => process.exit(143)) // 128 + 15 (SIGTERM)
  25. // Lazy-load command modules in parallel — keeps startup < 100ms
  26. async function main(): Promise<void> {
  27. // Fast path: --version only — --help needs all commands registered first
  28. const args = process.argv.slice(2)
  29. const isFastPath =
  30. args.includes('--version') || args.includes('-v')
  31. if (isFastPath) {
  32. await program.parseAsync(process.argv)
  33. return
  34. }
  35. const [
  36. { registerInitCommand },
  37. { registerUpdateCommand },
  38. { registerAddCommand },
  39. { registerApplyCommand },
  40. { registerDoctorCommand },
  41. { registerListCommand },
  42. { registerStatusCommand },
  43. { registerCleanCommand },
  44. ] = await Promise.all([
  45. import('./commands/init.js'),
  46. import('./commands/update.js'),
  47. import('./commands/add.js'),
  48. import('./commands/apply.js'),
  49. import('./commands/doctor.js'),
  50. import('./commands/list.js'),
  51. import('./commands/status.js'),
  52. import('./commands/clean.js'),
  53. ])
  54. registerInitCommand(program)
  55. registerUpdateCommand(program)
  56. registerAddCommand(program) // also registers `remove`
  57. registerApplyCommand(program)
  58. registerDoctorCommand(program)
  59. registerListCommand(program)
  60. registerStatusCommand(program)
  61. registerCleanCommand(program)
  62. // Unknown commands: print a helpful error and exit 1
  63. program.on('command:*', (operands: string[]) => {
  64. console.error(`error: unknown command '${operands[0]}'\n`)
  65. console.error(`Run 'oac --help' to see available commands.`)
  66. process.exitCode = 1
  67. })
  68. // Print help when no command is given — must happen before update check
  69. // so we don't fire a background fetch that gets abandoned on process.exit()
  70. if (args.length === 0) {
  71. program.help() // exits the process
  72. }
  73. await program.parseAsync(process.argv)
  74. // Non-blocking update check — runs after command completes, max once per 24h
  75. // void: intentionally not awaited — failure must never affect exit code
  76. // Note: skipped on --version fast path (returns before reaching this line)
  77. void checkForUpdate()
  78. }
  79. main().catch((err: unknown) => {
  80. console.error('Fatal error:', err instanceof Error ? err.message : String(err))
  81. process.exitCode = 1
  82. })