codehere 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +36 -192
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
package/dist/index.js CHANGED
@@ -1,36 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * codehere CLI entry · v0.3 PM-for-AI-coding-agents surface.
4
- *
5
- * Command surface trimmed to the v0.3 story (see Decision 10 in memory):
6
- * setup · config · help · health · status · trust · report · hook ·
7
- * serve · start · task · kill · scan-tool · completion
8
- *
9
- * v0.2 commands (ask / fix / plan / explain / refactor / index / etc.) and
10
- * their backing code were removed in the D7 trim. The v0.2 npm releases
11
- * retain that surface for users who depended on it.
3
+ * codehere CLI entry: `check` re-runs a project's tests and reports what actually passed.
4
+ * The earlier orchestrator, daemon, web UI and the other commands are on the legacy/2026-10 branch.
12
5
  */
13
6
  import { Command } from 'commander';
14
- import { config } from 'dotenv';
15
7
  import { join, dirname } from 'path';
16
8
  import { fileURLToPath } from 'url';
17
- import { homedir } from 'os';
18
9
  import { readFileSync } from 'fs';
19
- // ── Global crash safety ───────────────────────────────────────────────────
20
- // The old comment here said these "must never silently kill the daemon", and the
21
- // code delivered the opposite of safety: `process.exitCode = 1` is only honoured
22
- // once the event loop DRAINS, which for a daemon is never. Measured 2026-08-08 —
23
- // after an uncaught exception the process was still running and still serving a
24
- // second later, and would have gone on appending to a hash chain from a state
25
- // Node itself considers undefined. Staying up is not resilience when the thing
26
- // you are protecting is a record of what happened.
27
- //
28
- // One copy-pasteable block, no secrets: version/platform/node/state-dir are what
29
- // a bug report needs, and none of them is sensitive. The error's own message may
30
- // contain a path, which is why the state dir is named rather than dumped.
31
- // Read from package.json rather than the getVersion() imported further down:
32
- // the crash guard installs before most module init, and must not itself throw
33
- // while reporting a throw.
10
+ // A crash must stop the process: `process.exitCode = 1` is only honoured once the event loop drains.
34
11
  function safeVersion() {
35
12
  try {
36
13
  const here = dirname(fileURLToPath(import.meta.url));
@@ -47,8 +24,6 @@ function crashReport(kind, err) {
47
24
  `[codehere] ${kind} — this process is stopping.`,
48
25
  ` version : ${safeVersion()}`,
49
26
  ` node : ${process.version} on ${process.platform} ${process.arch}`,
50
- ` state : ${process.env.CODEHERE_HOME ? '$CODEHERE_HOME' : '~/.codehere'}`,
51
- ` pid : ${process.pid}`,
52
27
  '',
53
28
  (e.stack ?? e.message)
54
29
  .split('\n')
@@ -60,387 +35,64 @@ function crashReport(kind, err) {
60
35
  ].join('\n');
61
36
  }
62
37
  process.on('uncaughtException', (err) => {
63
- // Node's own guidance: after this the process state is undefined. Exiting is
64
- // the honest move — a restarted daemon re-reads the chain from disk and is
65
- // correct again; a surviving one is silently guessing.
66
38
  console.error(crashReport('uncaughtException', err));
67
39
  process.exit(1);
68
40
  });
69
41
  process.on('unhandledRejection', (reason) => {
70
- // Deliberately NOT fatal, unlike the above. The codebase has 17 explicitly
71
- // `void`-marked fire-and-forget promises (re-persist, enrichment) whose
72
- // rejection does not mean the process is corrupt, and killing the daemon on
73
- // one would be a worse regression than the bug being fixed. It is made loud
74
- // and attributed instead, so it cannot hide.
75
- // The residual risk is real and worth naming: a rejected audit-append would be
76
- // logged here and the process would continue. Closing that belongs at the
77
- // write site, not in a global handler.
78
- console.error(crashReport('unhandledRejection (non-fatal, continuing)', reason).replace(' — this process is stopping.', ' — continuing, but something is wrong.'));
42
+ console.error(crashReport('unhandledRejection', reason));
43
+ process.exit(1);
79
44
  });
80
- import { executeSetupCommand } from './presentation/cli/commands/setup-command.js';
81
- import { executeInitCommand } from './presentation/cli/commands/init-command.js';
82
- import { executeMemoryCommand } from './presentation/cli/commands/memory-command.js';
83
- import { executeProtocolsCommand } from './presentation/cli/commands/protocols-command.js';
84
- import { executeConfigCommand } from './presentation/cli/commands/config-command.js';
85
- import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
86
- import { executeStatusCommand } from './presentation/cli/commands/status-command.js';
87
- import { executeHookCommand } from './presentation/cli/commands/hook-command.js';
88
- import { executePermissionCommand } from './presentation/cli/commands/permission-command.js';
89
- import { executeBudgetCommand } from './presentation/cli/commands/budget-command.js';
90
- import { executeTeamCommand } from './presentation/cli/commands/team-command.js';
91
- import { executeNarratorCommand } from './presentation/cli/commands/narrator-command.js';
92
- import { executeTrustCommand } from './presentation/cli/commands/trust-command.js';
93
45
  import { executeCheckCommand } from './presentation/cli/commands/check-command.js';
94
- import { executeScanCommand } from './presentation/cli/commands/scan-command.js';
95
- import { executeReportCommand } from './presentation/cli/commands/report-command.js';
96
- import { executeScanToolCommand } from './presentation/cli/commands/scan-tool-command.js';
97
- import { executeVerifyCommand } from './presentation/cli/commands/verify-command.js';
98
- import { executeSearchCommand } from './presentation/cli/commands/search-command.js';
99
- import { executeAuditCommand } from './presentation/cli/commands/audit-command.js';
100
- import { executeContextSourceCommand } from './presentation/cli/commands/context-source-command.js';
101
- import { executeThreadCommand } from './presentation/cli/commands/thread-command.js';
102
- import { displayError } from './presentation/cli/error-display.js';
103
- import { performHealthCheck, formatHealthStatus, getMetrics } from './monitoring.js';
104
- import { colors, createBanner, createTable, createWelcomeMessage, success, error, info, sectionHeader, formatNumber, newline, isMinimalStyle, printMinimalHeader, } from './ui.js';
105
- // Load .env — agent dir first, then cwd
106
- try {
107
- const __filename = fileURLToPath(import.meta.url);
108
- const __dirname = dirname(__filename);
109
- const agentDir = join(__dirname, '..');
110
- config({ path: join(agentDir, '.env') });
111
- }
112
- catch {
113
- // Fallback to cwd
114
- }
115
- config();
46
+ import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
47
+ import { error } from './ui.js';
116
48
  import { getVersion } from './utils/version.js';
117
49
  import { checkTypo, looksLikeUnknownCommand } from './utils/typo-guard.js';
118
50
  const program = new Command();
119
51
  program
120
52
  .name('codehere')
121
- .description('Your AI coding agents: audited, cost-bounded, cross-vendor. Delegate a task, watch it run, and get a plain-English report with a trust score on every file it changed.')
53
+ .description('Re-run your project\'s tests yourself, so an AI\'s "all tests pass" is checked, not taken on trust.')
122
54
  .version(getVersion())
123
- .configureHelp({ helpWidth: 80, sortSubcommands: true })
124
- // rc.18: many users hit `codehere --help` first and get Commander's flat
125
- // list. The grouped `codehere help` is more useful for discovery. Surface
126
- // it inline so muscle-memory `--help` users find the better page.
127
- // (Pin: no raw ANSI in --help output — existing commands.test.ts asserts
128
- // this, see test/cli/commands.test.ts "no raw ANSI escape codes in --help".)
129
- .addHelpText('after', '\nTip: `codehere help` shows commands grouped by use case (Delegate · Safety & Trust · Configuration).');
130
- // Default action · show welcome, then drop into the interactive REPL on TTY.
131
- // codehere offers TWO surfaces, both first-class: the CLI REPL (here) and the
132
- // web UI (`codehere start`). Users pick whichever fits their workflow.
133
- program
134
- // Variadic so multi-token input is preserved end-to-end. Without this,
135
- // `codehere fix the bug` would silently drop "the bug" and only delegate
136
- // "fix" — and `codehere daemon start` would never see "start", letting the
137
- // typo guard miss the unknown-command shape. See typo-guard.ts.
138
- .argument('[query...]', 'Optional initial task to delegate (otherwise enter interactive REPL)')
139
- .action(async (queryParts) => {
140
- const joined = queryParts && queryParts.length > 0 ? queryParts.join(' ') : '';
141
- // One-shot delegate: `codehere "review src/auth.ts"` runs that task and exits.
142
- // Useful for scripting or quick one-offs without entering the REPL.
143
- if (joined && joined.trim()) {
144
- const trimmed = joined.trim();
145
- // Typo guard — see src/utils/typo-guard.ts for heuristics.
146
- // Returns suggestions when input looks like a typo; null falls through.
147
- const known = program.commands.map((c) => c.name());
148
- const firstToken = trimmed.split(/\s+/)[0];
149
- const suggestions = checkTypo(trimmed, known);
150
- if (suggestions) {
151
- console.error(`codehere: unknown command '${firstToken}'.`);
152
- console.error('');
153
- console.error(' Did you mean:');
154
- for (const s of suggestions)
155
- console.error(` codehere ${s}`);
156
- console.error('');
157
- console.error(` If you meant to ask a question, quote it: codehere "${trimmed}"`);
158
- process.exit(1);
159
- }
160
- // Second guard — `cmd subcmd` shape (e.g. `daemon start`) where the first
161
- // token isn't close enough for typo suggestions but is clearly a command
162
- // attempt rather than a natural-language prompt. Without this, the input
163
- // silently spawns an LLM task on the user's behalf and burns API tokens.
164
- const unknownFirst = looksLikeUnknownCommand(trimmed, known);
165
- if (unknownFirst) {
166
- console.error(`codehere: unknown command '${unknownFirst}'.`);
167
- console.error('');
168
- console.error(' Run `codehere help` to see available commands.');
169
- console.error('');
170
- console.error(' If you meant to delegate this as a task, either:');
171
- console.error(` codehere task ${trimmed}`);
172
- console.error(` codehere "${trimmed}"`);
173
- process.exit(1);
174
- }
175
- const { runOneShot } = await import('./presentation/cli/repl.js');
176
- await runOneShot(trimmed);
177
- return;
178
- }
179
- if (!process.stdin.isTTY) {
180
- console.log(createWelcomeMessage());
181
- // Non-interactive (piped / scripted): the REPL can't run, so leave the
182
- // user with ONE clear next step instead of a dead-end welcome screen.
183
- console.log(colors.dim(' Next: ') + colors.cyan('codehere start') + colors.dim(' open the dashboard'));
184
- console.log(colors.dim(' New here? ') + colors.cyan('codehere setup') + colors.dim(' one-time setup\n'));
185
- return;
186
- }
187
- const { runInteractiveRepl } = await import('./presentation/cli/repl.js');
188
- await runInteractiveRepl();
189
- });
190
- // ── setup ────────────────────────────────────────────────────────────────────
191
- program
192
- .command('setup')
193
- .description('Interactive setup wizard for first-time users')
194
- .option('-f, --force', 'Force reconfiguration even if already set up')
195
- .action(async (options) => {
196
- await executeSetupCommand(options.force || false);
197
- });
198
- // ── init ─────────────────────────────────────────────────────────────────────
199
- program
200
- .command('init')
201
- .description('Scaffold .codehere/ in this repo · CONTEXT, HANDOVER, PROMPTS')
202
- .option('-f, --force', 'Overwrite existing files in .codehere/')
203
- .option('--no-gitignore', 'Skip updating .gitignore')
204
- .action((options) => {
205
- executeInitCommand({ force: options.force, noGitignore: !options.gitignore });
206
- });
207
- // ── memory ───────────────────────────────────────────────────────────────────
208
- // F3 v0.5: project memory operations. `memory init` migrates v0.4.1 layout
209
- // (CONTEXT/HANDOVER/PROMPTS) into the four-layer F3 layout (AGENTS.md +
210
- // memory/ + skills/). `memory repo-map` builds a JSON symbol map. Future:
211
- // memory show, memory graduate.
212
- program
213
- .command('memory')
214
- .description('F3 project memory · init / repo-map / show / graduate / help')
215
- .argument('[action]', 'Action: init, repo-map, show, graduate, help')
216
- .argument('[layer]', 'Layer for `memory show` (working / episodic / semantic / personal / skills / protocols / injection)')
217
- .option('-f, --force', 'Overwrite existing target files (memory init)')
218
- .option('--no-symlinks', 'Delete old paths instead of leaving back-compat symlinks (memory init)')
219
- .option('--dry-run', 'Print planned moves without touching disk (memory init)')
220
- .option('--rebuild', 'Force a fresh build (memory repo-map)')
221
- .option('--add <text>', 'Add a free-text lesson directly (memory graduate)')
222
- .option('--accept <id>', 'Graduate a candidate by id (memory graduate)')
223
- .option('--reject <id>', 'Reject a candidate by id without graduating (memory graduate)')
224
- .action((action, layer, options) => {
225
- executeMemoryCommand(action, {
226
- force: options.force,
227
- noSymlinks: !options.symlinks,
228
- dryRun: options.dryRun,
229
- rebuild: options.rebuild,
230
- layer,
231
- add: options.add,
232
- accept: options.accept,
233
- reject: options.reject,
234
- });
235
- });
236
- // ── protocols ────────────────────────────────────────────────────────────────
237
- // F3 v0.5: protocols layer operations. `protocols export` writes the
238
- // trust-scorer pattern bank to .codehere/protocols/hook_patterns.json so
239
- // auditors / cross-tool consumers can read codehere's safety surface.
240
- program
241
- .command('protocols', { hidden: true }) // hidden from --help (May 2 cut: power-user F3 export, see docs/MEMORY.md)
242
- .description('F3 protocols layer · export (write hook_patterns.json from trust-scorer bank)')
243
- .argument('[action]', 'Action: export, help')
244
- .option('--rebuild', 'Refresh built-in patterns (project patterns are preserved)')
245
- .action((action, options) => {
246
- executeProtocolsCommand(action, { rebuild: options.rebuild });
247
- });
248
- // ── config ───────────────────────────────────────────────────────────────────
249
- program
250
- .command('config')
251
- .description('Manage configuration and preferences')
252
- .argument('[action]', 'Action: show, get, set, reset')
253
- .argument('[key]', 'Preference key (for get/set)')
254
- .argument('[value]', 'Preference value (for set)')
255
- .action(async (action, key, value) => {
256
- await executeConfigCommand(action, key, value);
257
- });
258
- // ── help ─────────────────────────────────────────────────────────────────────
259
- program
260
- .command('help')
261
- .description('Show help for commands')
262
- .argument('[command]', 'Command name to get help for')
263
- .option('--all', 'Show all commands (including advanced)')
264
- .action(async (commandName, options) => {
265
- if (options?.all && !commandName) {
266
- for (const cmd of program.commands) {
267
- cmd._hidden = false;
268
- }
55
+ .configureHelp({ helpWidth: 80, sortSubcommands: true });
56
+ // Bare `codehere` prints help. Anything else that is not a command goes through the typo guard.
57
+ program.allowExcessArguments(true).action(() => {
58
+ const input = program.args.join(' ').trim();
59
+ if (!input) {
269
60
  program.outputHelp();
270
- }
271
- else {
272
- await executeHelpCommand(commandName);
273
- }
274
- });
275
- // ── health ───────────────────────────────────────────────────────────────────
276
- program
277
- .command('health')
278
- .description('Check system health and metrics')
279
- .option('-m, --metrics', 'Show detailed metrics')
280
- .option('--json', 'Machine-readable JSON (for monitoring/CI)')
281
- .action((options) => {
282
- const health = performHealthCheck();
283
- if (options.json) {
284
- // Machine-readable: status + checks (+ metrics when --metrics).
285
- // Stdout is the contract — never mix with the human banner.
286
- // Use process.exitCode (not process.exit) so a piped consumer sees the
287
- // full JSON before the process tears down (rc.16: Codex flagged the old
288
- // process.exit call as a truncation hazard under stdout redirection).
289
- const payload = { status: health.status, checks: health.checks };
290
- if (options.metrics)
291
- payload.metrics = getMetrics();
292
- console.log(JSON.stringify(payload, null, 2));
293
- if (health.status === 'unhealthy')
294
- process.exitCode = 1;
295
61
  return;
296
62
  }
297
- if (isMinimalStyle()) {
298
- printMinimalHeader('health', '');
63
+ const known = program.commands.map((c) => c.name());
64
+ const suggestions = checkTypo(input, known);
65
+ const unknown = suggestions
66
+ ? input.split(/\s+/)[0]
67
+ : (looksLikeUnknownCommand(input, known) ?? input.split(/\s+/)[0]);
68
+ console.error(`codehere: unknown command '${unknown}'.`);
69
+ console.error('');
70
+ if (suggestions) {
71
+ console.error(' Did you mean:');
72
+ for (const s of suggestions)
73
+ console.error(` codehere ${s}`);
299
74
  }
300
75
  else {
301
- newline();
302
- console.log(createBanner('System Health', 'Quick status check'));
303
- newline();
304
- }
305
- console.log(formatHealthStatus(health));
306
- if (options.metrics) {
307
- newline();
308
- sectionHeader('Metrics');
309
- const metrics = getMetrics();
310
- const metricsTable = createTable(['Metric', 'Value'], [
311
- ['Total Operations', formatNumber(metrics.operations.total)],
312
- ['Successful', formatNumber(metrics.operations.successful)],
313
- ['Failed', formatNumber(metrics.operations.failed)],
314
- ['Retries', formatNumber(metrics.operations.retries)],
315
- ['Avg Response Time', `${metrics.performance.avgResponseTime.toFixed(2)}ms`],
316
- ['API Calls', formatNumber(metrics.performance.apiCalls)],
317
- ['Cache Hits', formatNumber(metrics.performance.cacheHits)],
318
- ]);
319
- console.log(metricsTable.toString());
320
- }
321
- newline();
322
- if (health.status === 'unhealthy') {
323
- process.exit(1);
324
- }
325
- });
326
- // ── status ───────────────────────────────────────────────────────────────────
327
- program
328
- .command('status')
329
- .description('Security dashboard — hook activity, blocked calls, cost routing')
330
- .option('--json', 'Machine-readable JSON')
331
- .option('--sessions', 'Show per-session audit trail (multi-agent analysis)')
332
- .option('--session <id>', 'Drill into a specific session (first 8+ chars of session ID)')
333
- .option('--trust', 'Include a quick trust scan of the current directory')
334
- .option('--weekly', 'Show shareable weekly agent-activity recap (last 7 days)')
335
- .option('--export <format>', 'Export raw audit events: jsonl, json, csv, or md')
336
- .option('--since <duration>', 'Limit --export window, e.g. 24h, 7d, 4w')
337
- .option('--env', 'Show environment details')
338
- .option('--track', 'Show command success rates')
339
- .addHelpText('after', `
340
- Examples:
341
- $ codehere status # full dashboard
342
- $ codehere status --weekly # shareable 7-day agent recap
343
- $ codehere status --sessions # show per-session breakdown
344
- $ codehere status --session abc12345 # drill into a specific session
345
- $ codehere status --export jsonl > audit.jsonl # raw audit log export
346
- $ codehere status --export csv --since 7d # 7-day CSV for spreadsheets
347
- $ codehere status --export md --since 24h # 24-hour markdown report
348
- `)
349
- .action(async (options) => {
350
- await executeStatusCommand({
351
- json: Boolean(options.json),
352
- tasks: Boolean(options.tasks),
353
- sessions: Boolean(options.sessions),
354
- session: options.session,
355
- trust: Boolean(options.trust),
356
- weekly: Boolean(options.weekly),
357
- export: options.export,
358
- since: options.since,
359
- env: Boolean(options.env),
360
- track: Boolean(options.track),
361
- });
362
- });
363
- // ── trust ────────────────────────────────────────────────────────────────────
364
- program
365
- .command('trust')
366
- .description('Layer 0 trust score — instant static analysis, zero API calls')
367
- .argument('[target]', 'File, directory, or omit to scan current directory')
368
- .option('--diff [ref]', 'Score a git diff (default: HEAD)')
369
- .option('--badge', 'Output shield badge markdown')
370
- .option('--json', 'Output JSON report')
371
- .option('--verbose', 'Show all findings with line numbers')
372
- .option('--ci', 'CI mode: exit 1 if score < 60')
373
- .action(async (target, options) => {
374
- try {
375
- await executeTrustCommand(target, {
376
- diff: options?.diff === true ? 'HEAD' : options?.diff,
377
- badge: options?.badge,
378
- json: options?.json,
379
- verbose: options?.verbose,
380
- ci: options?.ci,
381
- });
382
- }
383
- catch (err) {
384
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
385
- process.exit(1);
76
+ console.error(' Run `codehere help` to see available commands.');
386
77
  }
78
+ console.error('');
79
+ process.exit(1);
387
80
  });
388
- // ── check ────────────────────────────────────────────────────────────────────
389
81
  program
390
82
  .command('check')
391
83
  .description('Re-run your tests independently — proof of what the AI actually did, not what it claimed')
392
84
  .argument('[target]', 'Repo or directory to check (default: current directory)')
393
85
  .option('--json', 'Machine-readable output')
394
86
  .option('--proof', 'Emit a paste-ready proof block for a PR description or review comment')
87
+ .option('--claim <text>', "The AI's own summary: test files it names that are not in the repo are flagged")
88
+ .option('--base <ref>', 'Look for weakened tests in everything since this branch left <ref> (e.g. origin/main)')
395
89
  .action(async (target, options) => {
396
90
  try {
397
- process.exitCode = await executeCheckCommand(target, { json: options?.json, proof: options?.proof });
398
- }
399
- catch (err) {
400
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
401
- process.exit(1);
402
- }
403
- });
404
- // ── scan ───────────────────────────────────────────────────────────────────────
405
- program
406
- .command('scan')
407
- .description('Find the ONE thing that will get your app hacked — fast pass (gitleaks + semgrep), no API key')
408
- .argument('[target]', 'App directory to scan (default: current directory)')
409
- .option('--json', 'Machine-readable output')
410
- .action(async (target, options) => {
411
- try {
412
- process.exitCode = await executeScanCommand(target, { json: options?.json });
413
- }
414
- catch (err) {
415
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
416
- process.exit(1);
417
- }
418
- });
419
- // ── report ───────────────────────────────────────────────────────────────────
420
- program
421
- .command('report')
422
- .description('Generate a shareable security audit report — narrative, not just a score')
423
- .argument('[target]', 'File or directory to audit (default: current directory)')
424
- .option('--out <file>', 'Write report to a specific file (default: codehere-audit.md for markdown)')
425
- .option('--stdout', 'Print the report to stdout (already the default for --json)')
426
- .option('--json', 'Output machine-readable JSON to stdout (pipe to jq, etc.)')
427
- .option('--simple', 'Plain English output — no jargon, for non-technical stakeholders')
428
- .addHelpText('after', `
429
- Examples:
430
- $ codehere report # writes codehere-audit.md, prints summary
431
- $ codehere report src/ # audit a subdirectory
432
- $ codehere report --out my-audit.md # write to a specific path
433
- $ codehere report --stdout # print to terminal (pipe-friendly)
434
- $ codehere report --json # machine-readable output
435
- $ codehere report --simple # plain English for non-technical stakeholders
436
- `)
437
- .action(async (target, options) => {
438
- try {
439
- await executeReportCommand(target, {
440
- out: options?.out,
441
- stdout: options?.stdout,
91
+ process.exitCode = await executeCheckCommand(target, {
442
92
  json: options?.json,
443
- simple: options?.simple,
93
+ proof: options?.proof,
94
+ claim: options?.claim,
95
+ base: options?.base,
444
96
  });
445
97
  }
446
98
  catch (err) {
@@ -448,838 +100,12 @@ Examples:
448
100
  process.exit(1);
449
101
  }
450
102
  });
451
- // ── verify ───────────────────────────────────────────────────────────────────
452
- // v0.7 verifier-agent (F-03). Catches the easy lies a Final Report makes
453
- // against the structured artifacts recorded during the session.
454
103
  program
455
- .command('verify')
456
- .description("Verify a session's Final Report against the recorded reality (catches optimistic claims)")
457
- .argument('[session-id]', 'Session id (or short prefix) to verify. Omit to list recent sessions.')
458
- .option('--json', 'Output machine-readable JSON instead of formatted findings')
459
- .addHelpText('after', `
460
- Examples:
461
- $ codehere verify # list recent sessions to pick from
462
- $ codehere verify a1b2c3d4 # verify by short id prefix
463
- $ codehere verify a1b2c3d4 --json # machine-readable verdict for CI
464
-
465
- Exit codes:
466
- 0 = verified, partial (warnings only), or no Final Report to verify
467
- 1 = broken (a claim contradicts recorded reality)
468
- 2 = session not found
469
- `)
470
- .action(async (sessionId, options) => {
471
- try {
472
- // Per codex Round 2: set exitCode instead of calling process.exit so a
473
- // piped --json consumer sees the full payload before Node tears down.
474
- // The picker mode can write ~15 sessions worth of JSON · enough to
475
- // exceed pipe buffer + race the process exit. exitCode preserves the
476
- // intent (CI uses the exit code) without forcing an immediate teardown.
477
- process.exitCode = await executeVerifyCommand(sessionId, { json: options.json });
478
- }
479
- catch (err) {
480
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
481
- process.exitCode = 1;
482
- }
483
- });
484
- program
485
- .command('search')
486
- .description('Search your past agent sessions — with provenance (which agent, whether it succeeded)')
487
- .argument('<query>', 'What to look for across your past sessions')
488
- .option('--json', 'Machine-readable JSON')
489
- .option('--limit <n>', 'Max results (default 10)')
490
- .action((query, options) => {
491
- try {
492
- process.exitCode = executeSearchCommand(query, options);
493
- }
494
- catch (err) {
495
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
496
- process.exitCode = 1;
497
- }
498
- });
499
- // ── audit ────────────────────────────────────────────────────────────────────
500
- // F-05 of the demo v12 backlog · expose the hash-chain verifier as a CLI.
501
- program
502
- .command('audit')
503
- .description('Inspect the hash-chained audit log · verify the chain or show one entry')
504
- .argument('[subcommand]', 'verify | show', 'verify')
505
- .argument('[arg]', 'For `show`: the sequence number to print.')
506
- .option('--json', 'Output machine-readable JSON instead of formatted text')
507
- .addHelpText('after', `
508
- Examples:
509
- $ codehere audit # equivalent to: codehere audit verify
510
- $ codehere audit verify # walk the chain, report intact/tampered
511
- $ codehere audit verify --json # machine-readable verdict for CI
512
- $ codehere audit show 42 # print entry with seq=42
513
- $ codehere audit show 42 --json
514
-
515
- Exit codes:
516
- 0 = chain intact OR entry found
517
- 1 = chain BROKEN (one or more break points detected)
518
- 2 = invalid input / no entry found
519
- `)
520
- .action((sub, arg, options) => {
521
- try {
522
- process.exitCode = executeAuditCommand(sub, arg, { json: options.json });
523
- }
524
- catch (err) {
525
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
526
- process.exitCode = 1;
527
- }
528
- });
529
- // ── context-source ───────────────────────────────────────────────────────────
530
- // v0.8 S2.5 · OAuth click-to-connect for Linear, Figma, etc.
531
- program
532
- .command('context-source')
533
- .alias('cs')
534
- .description('Manage context sources (Linear, Figma) · connect via OAuth, list status, disconnect')
535
- .argument('[subcommand]', 'list | connect | disconnect', 'list')
536
- .argument('[provider]', 'For connect/disconnect: linear | figma | ...')
537
- .option('--json', 'Machine-readable output')
538
- .option('--no-open', 'For connect: skip auto-opening the browser (just print the URL)')
539
- .addHelpText('after', `
540
- Examples:
541
- $ codehere context-source # equivalent to: list
542
- $ codehere context-source list
543
- $ codehere context-source connect linear
544
- $ codehere context-source connect figma --no-open
545
- $ codehere context-source disconnect linear
546
-
547
- Tokens persist at ~/.codehere/context-sources.json (0600 mode).
548
- Requires the daemon running: codehere start
549
- Provider client IDs come from env: CODEHERE_LINEAR_CLIENT_ID, CODEHERE_FIGMA_CLIENT_ID
550
- `)
551
- .action(async (sub, provider, options) => {
552
- try {
553
- process.exitCode = await executeContextSourceCommand(sub, provider, {
554
- json: options.json,
555
- open: options.open,
556
- });
557
- }
558
- catch (err) {
559
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
560
- process.exitCode = 1;
561
- }
562
- });
563
- // ── thread ───────────────────────────────────────────────────────────────────
564
- // v0.8 S4 · signed-payload "GitHub for agent sessions" · share/import threads
565
- program
566
- .command('thread')
567
- .description('Share or import a session · Ed25519-signed bundle · local-first')
568
- .argument('<subcommand>', 'export | import | keys | trust | untrust')
569
- .argument('[arg]', 'For export: session-id · For import/trust/untrust: file path or pubkey')
570
- .argument('[label]', 'For trust: an optional human label for the key')
571
- .option('--out <file>', 'For export: write the signed bundle to a file (default: stdout)')
572
- .option('--trust', 'For import: trust the signer key automatically if unknown')
573
- .option('--json', 'Machine-readable output')
574
- .addHelpText('after', `
575
- Examples:
576
- $ codehere thread export a1b2c3d4 --out demo-thread.json
577
- $ codehere thread import ./shared.json --trust
578
- $ codehere thread keys
579
- $ codehere thread trust <pubkey-base64> "Daniel"
580
- $ codehere thread untrust <pubkey-base64>
581
-
582
- Bundles include: spawned · steps · artifacts · finalReport · signature.
583
- No source code · only the synthesized record.
584
- Recipient must trust the signer key (or pass --trust) before replay.
585
- `)
586
- .action((sub, arg, label, options) => {
587
- try {
588
- process.exitCode = executeThreadCommand(sub, arg, label, {
589
- out: options.out,
590
- trust: options.trust,
591
- label,
592
- json: options.json,
593
- });
594
- }
595
- catch (err) {
596
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
597
- process.exitCode = 1;
598
- }
599
- });
600
- // ── hook ─────────────────────────────────────────────────────────────────────
601
- program
602
- .command('hook')
603
- .description('Manage Claude Agent SDK hook integration (intercepts all tool calls)')
604
- .argument('[subcommand]', 'install | uninstall | status | log')
605
- .option('--log-only', 'Log events but never block (audit mode)')
606
- .option('--block-mode <level>', 'Block threshold: critical (default), high, all')
607
- .option('--tail <n>', 'Lines to show in log mode', '20')
608
- .option('--debug', 'Verbose debug output')
609
- .action(async (subcommand, options) => {
610
- try {
611
- await executeHookCommand(subcommand, {
612
- logOnly: options?.logOnly,
613
- blockMode: options?.blockMode,
614
- tail: options?.tail ? parseInt(options.tail, 10) : undefined,
615
- debug: options?.debug,
616
- });
617
- }
618
- catch (err) {
619
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
620
- process.exit(1);
621
- }
622
- });
623
- // ── permission (P18 — per-cwd allow/deny list for the trust-scorer) ─────────
624
- program
625
- .command('permission')
626
- .description("Manage this folder's .codehere/permissions.yaml allow/deny list")
627
- .argument('[subcommand]', 'list | allow | deny | remove')
628
- .argument('[pattern]', 'the substring pattern (for allow/deny/remove)')
629
- .option('--reason <text>', 'Why this pattern is allow-listed (recorded in audit)')
630
- .action(async (subcommand, pattern, options) => {
631
- try {
632
- await executePermissionCommand(subcommand, pattern, { reason: options?.reason });
633
- }
634
- catch (err) {
635
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
636
- process.exit(1);
637
- }
638
- });
639
- // ── narrator (S6 — editable PM narrator prompt) ──────────────────────────────
640
- program
641
- .command('narrator')
642
- .description('Customize the PM narrator prompt template (~/.codehere/narrator-prompt.md)')
643
- .argument('[subcommand]', 'show | edit | reset')
644
- .action(async (subcommand) => {
645
- try {
646
- await executeNarratorCommand(subcommand);
647
- }
648
- catch (err) {
649
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
650
- process.exit(1);
651
- }
652
- });
653
- // ── budget (S3 — daily-spend ceiling enforcement) ────────────────────────────
654
- program
655
- .command('budget')
656
- .description('Set or check the daily spend budget for spawns')
657
- .argument('[subcommand]', 'show | set | off')
658
- .argument('[value]', 'amount in USD (for `set`)')
659
- .action(async (subcommand, value) => {
660
- try {
661
- await executeBudgetCommand(subcommand, value);
662
- }
663
- catch (err) {
664
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
665
- process.exit(1);
666
- }
667
- });
668
- // ── team (local-first team spend ledger) ─────────────────────────────────────
669
- program
670
- .command('team')
671
- .description("Merge each machine's daily spend into one team total (local-first, no cloud)")
672
- .argument('[subcommand]', 'sync | set-dir | budget')
673
- .argument('[value]', 'path (for set-dir) or amount in USD (for budget)')
674
- .option('--strict', 'exit non-zero if any share is tampered or its audit chain is broken (for CI / enterprise gating)')
675
- .action(async (subcommand, value, options) => {
676
- try {
677
- await executeTeamCommand(subcommand, value, { strict: Boolean(options?.strict) });
678
- }
679
- catch (err) {
680
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
681
- process.exit(1);
682
- }
683
- });
684
- // ── serve (MCP server) ───────────────────────────────────────────────────────
685
- program
686
- .command('serve')
687
- .description('Run codehere as a background MCP server (no web UI) — for Claude Desktop / Cursor integration')
688
- .option('-p, --port <number>', 'Port to listen on', '3773')
689
- .option('--host <host>', 'Host to bind to (default: 127.0.0.1)', '127.0.0.1')
690
- .option('--token <token>', 'Require Bearer token for authentication')
691
- .option('--show-config', 'Print the MCP config block to add to Claude Desktop / Cursor settings')
692
- .action(async (options) => {
693
- const port = parseInt(options?.port ?? '3773', 10);
694
- const host = options?.host ?? '127.0.0.1';
695
- const token = options?.token;
696
- if (options?.showConfig) {
697
- const url = `http://${host}:${port}/mcp`;
698
- const authHeader = token ? ` --header "Authorization: Bearer ${token}"` : '';
699
- const configBlock = {
700
- mcpServers: {
701
- codehere: {
702
- url,
703
- ...(token ? { headers: { Authorization: `Bearer ${token}` } } : {}),
704
- },
705
- },
706
- };
707
- newline();
708
- console.log(colors.bold('Connect codehere to your coding agent') + colors.dim(' (codehere must be running)'));
709
- newline();
710
- console.log(colors.bold('Claude Code:'));
711
- console.log(' claude mcp add --transport http codehere ' + url + authHeader);
712
- newline();
713
- console.log(colors.bold('Cursor') + colors.dim(' (.cursor/mcp.json in the project, or ~/.cursor/mcp.json):'));
714
- console.log(JSON.stringify(configBlock, null, 2));
715
- newline();
716
- console.log(colors.bold('Claude Desktop') +
717
- colors.dim(' (~/Library/Application Support/Claude/claude_desktop_config.json):'));
718
- console.log(JSON.stringify(configBlock, null, 2));
719
- newline();
720
- return;
721
- }
722
- try {
723
- const { startMCPServer } = await import('./infrastructure/protocols/mcp-server-impl.js');
724
- const server = startMCPServer({ port, host, token });
725
- // Wait until the port is actually bound before claiming success — otherwise
726
- // a listen() failure (e.g. port in use) crashes with a raw stack trace
727
- // AFTER the "running" banner already printed. The catch below then renders
728
- // a friendly message instead.
729
- await new Promise((resolve, reject) => {
730
- server.once('listening', () => resolve());
731
- server.once('error', reject);
732
- });
733
- newline();
734
- console.log(success(`codehere MCP server running`));
735
- newline();
736
- console.log(colors.bold('Endpoint:') + ` http://${host}:${port}/mcp`);
737
- console.log(colors.bold('Health:') + ` http://${host}:${port}/health`);
738
- newline();
739
- console.log(colors.bold('Tools available:'));
740
- console.log(' codehere_trust — score a file or snippet (0-100, no API)');
741
- console.log(' codehere_scan — scan text for injection/threats');
742
- console.log(' codehere_review — scan a directory, return riskiest files');
743
- console.log(' codehere_audit — read hook audit log');
744
- console.log(" codehere_spend — today's spend by agent and project, vs budget");
745
- console.log(' codehere_team — merged team cost and record across machines');
746
- newline();
747
- console.log(colors.dim(' Run with --show-config to get the MCP client config block'));
748
- console.log(colors.dim(' Press Ctrl+C to stop'));
749
- newline();
750
- process.on('SIGINT', () => {
751
- server.close();
752
- console.log(colors.dim('\ncodehere MCP server stopped'));
753
- process.exit(0);
754
- });
755
- process.on('SIGTERM', () => process.exit(0));
756
- await new Promise((resolve) => {
757
- server.on('close', resolve);
758
- });
759
- }
760
- catch (err) {
761
- console.error(error('Failed to start server:'), err instanceof Error ? err.message : String(err));
762
- process.exit(1);
763
- }
764
- });
765
- // ── start (Mission Control: daemon + web UI) ─────────────────────────────────
766
- program
767
- .command('start')
768
- .description('Start codehere — interactive PM dashboard at localhost:3773 (delegate tasks, watch agents work)')
769
- .option('-p, --port <number>', 'Port to listen on', '3773')
770
- .option('--host <host>', 'Host to bind to', '127.0.0.1')
771
- .option('--no-open', "Don't auto-open the browser")
772
- .option('--token <token>', 'Require a Bearer token for every request (use when binding to a non-localhost host)')
773
- .option('--cloud', 'Enable cloud sync — push session state to codehere.uk/app for remote monitoring')
774
- .action(async (options) => {
775
- const port = parseInt(options?.port ?? '3773', 10);
776
- const host = options?.host ?? '127.0.0.1';
777
- const token = options?.token;
778
- // Auto-open only for a real human at a terminal. A daemon spawned with
779
- // piped stdio (tests, CI, scripts, nohup) must NEVER pop a browser —
780
- // suite runs were opening 2 tabs per run at the test daemons' ports
781
- // (Ega, 2026-06-07: 6 stray tabs from 3 runs). Machine-guard, not a
782
- // convention the next test has to remember.
783
- const shouldOpen = options?.open !== false && process.stdout.isTTY === true;
784
- const cloud = options?.cloud === true;
785
- const url = `http://${host}:${port}/`;
786
- const openBrowser = async () => {
787
- if (!shouldOpen)
788
- return;
789
- const { spawn } = await import('child_process');
790
- const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
791
- try {
792
- spawn(opener, [url], { detached: true, stdio: 'ignore' }).unref();
793
- }
794
- catch {
795
- /* browser open is best-effort */
796
- }
797
- };
798
- try {
799
- // v0.6.0 M1+M7 — migrate sessions.jsonl → ~/.codehere/projects/<slug>/thread.jsonl
800
- // on daemon start. Idempotent: skips projects whose thread.jsonl already exists,
801
- // so this is safe to run on every boot. Opt-out via CODEHERE_DISABLE_THREAD_MIGRATION=1.
802
- // Failures are non-blocking — back-compat to v0.5 sessions.jsonl is preserved.
803
- if (process.env.CODEHERE_DISABLE_THREAD_MIGRATION !== '1') {
804
- try {
805
- const { migrate } = await import('./infrastructure/storage/project-thread-migration.js');
806
- const result = migrate({
807
- sourceJsonl: join(process.env.CODEHERE_HOME || homedir(), '.codehere/sessions.jsonl'),
808
- targetDir: join(process.env.CODEHERE_HOME || homedir(), '.codehere/projects'),
809
- });
810
- if (result.threadsWritten > 0) {
811
- console.log(colors.dim(` v0.6 migration: created ${result.threadsWritten} project thread(s) from ${result.sessionsProcessed} sessions (${result.durationMs}ms)`));
812
- }
813
- }
814
- catch (err) {
815
- console.warn(colors.dim(` v0.6 migration skipped: ${err instanceof Error ? err.message : String(err)}`));
816
- }
817
- }
818
- // v0.6.0 follow-up — backfill missing titles for projects that the
819
- // M9 Title agent never fired on (migration-seeded ones). Runs in the
820
- // background so daemon startup isn't blocked by ~5 LLM calls.
821
- // Idempotent + best-effort. Same env-var gate as M9 itself.
822
- if (process.env.CODEHERE_DISABLE_TITLE_AGENT !== '1') {
823
- void (async () => {
824
- try {
825
- const { backfillThreadTitles } = await import('./infrastructure/storage/project-thread-title-backfill.js');
826
- const r = await backfillThreadTitles();
827
- if (r.titlesGenerated > 0 && process.env.CODEHERE_DEBUG === '1') {
828
- console.log(colors.dim(` v0.6 title backfill: titled ${r.titlesGenerated}/${r.scanned} project(s) in ${r.durationMs}ms`));
829
- }
830
- }
831
- catch {
832
- /* best-effort — silent on failure */
833
- }
834
- })();
835
- }
836
- const localhostOnly = host === '127.0.0.1' || host === 'localhost' || host === '::1';
837
- if (!localhostOnly && !token) {
838
- console.warn(colors.warning(` Exposed on ${host} with no token — anyone who can reach this host can drive your agents. Add --token <secret> to require auth.`));
839
- }
840
- const { startMCPServer } = await import('./infrastructure/protocols/mcp-server-impl.js');
841
- const server = startMCPServer({ port, host, token });
842
- // Wait for the server to either start listening or fail. The default
843
- // Node behavior on listen() failure is an unhandled 'error' event which
844
- // crashes the process with a stack trace. We catch it here so the user
845
- // sees a friendly message instead.
846
- await new Promise((resolve, reject) => {
847
- server.once('listening', () => resolve());
848
- server.once('error', reject);
849
- });
850
- newline();
851
- console.log(success('codehere is running'));
852
- newline();
853
- console.log(colors.bold('Tasks:') + ' ' + url);
854
- console.log(colors.bold('Verify:') + ' ' + `http://${host}:${port}/verify`);
855
- console.log(colors.bold('MCP:') + ' ' + `http://${host}:${port}/mcp`);
856
- if (cloud) {
857
- console.log(colors.bold('Cloud:') + ' ' + 'https://codehere.uk/app');
858
- }
859
- newline();
860
- console.log(colors.dim(' Press Ctrl+C to stop'));
861
- newline();
862
- // Start cloud sync if enabled
863
- if (cloud) {
864
- const { startCloudSync } = await import('./infrastructure/protocols/cloud-sync.js');
865
- startCloudSync();
866
- }
867
- await openBrowser();
868
- const shutdown = () => {
869
- try {
870
- const { SessionManager } = require('./infrastructure/orchestration/session-manager.js');
871
- SessionManager.getInstance().shutdown();
872
- }
873
- catch {
874
- /* graceful degradation — daemon may not have started fully */
875
- }
876
- if (cloud) {
877
- try {
878
- const { stopCloudSync } = require('./infrastructure/protocols/cloud-sync.js');
879
- stopCloudSync();
880
- }
881
- catch {
882
- /* cloud sync may not be loaded */
883
- }
884
- }
885
- server.close();
886
- };
887
- process.on('SIGINT', () => {
888
- shutdown();
889
- console.log(colors.dim('\ncodehere stopped'));
890
- process.exit(0);
891
- });
892
- process.on('SIGTERM', () => {
893
- shutdown();
894
- process.exit(0);
895
- });
896
- await new Promise((resolve) => {
897
- server.on('close', resolve);
898
- });
899
- }
900
- catch (err) {
901
- const code = err?.code;
902
- if (code === 'EADDRINUSE') {
903
- // Port is taken. Probe to find out by what — if it's a running codehere,
904
- // we should hand the user the existing URL; if it's something else,
905
- // tell them to pick a different port.
906
- const { isCodehereOnPort } = await import('./infrastructure/protocols/probe-port.js');
907
- const existing = await isCodehereOnPort(host, port);
908
- newline();
909
- if (existing.isCodehere) {
910
- console.log(success(`codehere is already running at ${url}`));
911
- if (existing.version) {
912
- console.log(colors.dim(` version: ${existing.version}`));
913
- }
914
- newline();
915
- // Honor --no-open here too — found via the v1 stranger walk: a second
916
- // `start --no-open` against a running daemon popped a browser anyway.
917
- if (shouldOpen) {
918
- console.log(colors.dim(' Opening browser...'));
919
- await openBrowser();
920
- }
921
- process.exit(0);
922
- }
923
- console.error(error(`Port ${port} is in use by another process.`));
924
- console.error(colors.dim(` Try: codehere start --port ${port + 1}`));
925
- process.exit(1);
926
- }
927
- console.error(error('Failed to start:'), err instanceof Error ? err.message : String(err));
928
- process.exit(1);
929
- }
930
- });
931
- // ── reply (continue a goal — follow-up turn to an existing session) ──────────
932
- // Multi-turn IS supported (claude --resume etc.) + context carries, but the
933
- // only surface was the web UI; the CLI had no way to continue a goal. Also the
934
- // message API needs the exact UUID while reports show an 8-char prefix — so this
935
- // resolves a prefix → full id (dogfood findings 2026-06-09).
936
- program
937
- .command('reply <id> <text...>')
938
- .description('Continue a goal — send a follow-up turn to an existing session (context carries).')
939
- .option('-p, --port <number>', 'Daemon port', '3773')
940
- .option('--host <host>', 'Daemon host', '127.0.0.1')
941
- .addHelpText('after', `
942
- Examples:
943
- $ codehere reply a5a39692 "now add a clear() method and a test"
944
- The id may be the 8-char prefix that \`codehere task\` printed.`)
945
- .action(async (idArg, textParts, options) => {
946
- const text = textParts.join(' ').trim();
947
- if (!text) {
948
- console.error(error('No message. Usage: codehere reply <id> "your follow-up"'));
949
- process.exit(1);
950
- }
951
- const port = parseInt(options?.port ?? '3773', 10);
952
- const host = options?.host ?? '127.0.0.1';
953
- const base = `http://${host}:${port}`;
954
- const http = await import('http');
955
- // Resolve a prefix id → full session id (reports show 8-char prefixes).
956
- const fullId = await new Promise((resolve) => {
957
- http
958
- .get(`${base}/api/tasks`, (res) => {
959
- let b = '';
960
- res.on('data', (c) => (b += c.toString()));
961
- res.on('end', () => {
962
- try {
963
- const j = JSON.parse(b);
964
- const all = [...(j.spawned ?? []), ...(j.persisted ?? [])];
965
- const m = all.find((s) => s.sessionId === idArg) ?? all.find((s) => s.sessionId.startsWith(idArg));
966
- resolve(m ? m.sessionId : null);
967
- }
968
- catch {
969
- resolve(null);
970
- }
971
- });
972
- })
973
- .on('error', () => resolve(null));
974
- });
975
- if (!fullId) {
976
- console.error(error(`No session matching "${idArg}". See recent ones: codehere search`));
977
- process.exit(1);
978
- }
979
- console.log(colors.dim(` Continuing session ${fullId.slice(0, 8)}… (context carries; the agent keeps the prior turns)`));
980
- const body = JSON.stringify({ text });
981
- const result = await new Promise((resolve) => {
982
- const r = http.request(`${base}/api/task/${fullId}/message`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, (res) => {
983
- let b = '';
984
- res.on('data', (c) => (b += c.toString()));
985
- res.on('end', () => {
986
- try {
987
- resolve(JSON.parse(b));
988
- }
989
- catch {
990
- resolve(null);
991
- }
992
- });
993
- });
994
- r.on('error', () => resolve(null));
995
- r.write(body);
996
- r.end();
997
- });
998
- if (!result) {
999
- console.error(error(`Could not reach the daemon at ${base}. Start it with: codehere start`));
1000
- process.exit(1);
1001
- }
1002
- if (result.error) {
1003
- console.error(error(result.error));
1004
- process.exit(1);
1005
- }
1006
- console.log('\n' + (result.result ?? '(the agent left no text response)') + '\n');
1007
- });
1008
- // ── task (spawn an agent session via CLI) ────────────────────────────────────
1009
- program
1010
- .command('task <prompt...>')
1011
- .description('Assign a task — spawns an agent under codehere. Requires `codehere start` running.')
1012
- .option('-p, --port <number>', 'Daemon port', '3773')
1013
- .option('--host <host>', 'Daemon host', '127.0.0.1')
1014
- .option('--agent <id>', 'Which agent to run (claude-code, codex, aider, gemini, opencode). Default: auto.')
1015
- .option('--watch', 'Watch it work and print the report when it finishes')
1016
- .option('--cwd <path>', 'Repo/directory the agent works in (default: where you run this)')
1017
- .addHelpText('after', `
1018
- Examples:
1019
- $ codehere task "fix the totalValue bug in inventory.js" # assign, get a session id
1020
- $ codehere task "add a health check route" --watch # watch it work + see the report
1021
- $ codehere task "summarize recent changes" --agent codex --watch # pick the agent
1022
-
1023
- Needs a running daemon: start one with \`codehere start\` (or \`codehere serve\`).`)
1024
- .action(async (promptParts, options) => {
1025
- const prompt = promptParts.join(' ').trim();
1026
- if (!prompt) {
1027
- console.error(error('No prompt. Usage: codehere task "fix auth bug in session.ts"'));
1028
- process.exit(1);
1029
- }
1030
- const port = parseInt(options?.port ?? '3773', 10);
1031
- const host = options?.host ?? '127.0.0.1';
1032
- const base = `http://${host}:${port}`;
1033
- const http = await import('http');
1034
- const { resolve: resolvePath } = await import('path');
1035
- const { waitForDeliverable, formatDeliverable } = await import('./presentation/cli/task-deliverable.js');
1036
- // The agent must run in the repo the USER targets — not the daemon's dir.
1037
- // Default to where `codehere task` was invoked; allow an explicit --cwd.
1038
- const cwd = resolvePath(options?.cwd ?? process.cwd());
1039
- const spawnResult = await new Promise((resolve) => {
1040
- const body = JSON.stringify({ prompt, cwd, ...(options?.agent ? { agent: options.agent } : {}) });
1041
- const r = http.request(`${base}/api/task`, {
1042
- method: 'POST',
1043
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
1044
- }, (res) => {
1045
- let buf = '';
1046
- res.on('data', (c) => {
1047
- buf += c.toString();
1048
- });
1049
- res.on('end', () => {
1050
- try {
1051
- resolve(JSON.parse(buf));
1052
- }
1053
- catch {
1054
- resolve(null);
1055
- }
1056
- });
1057
- });
1058
- r.on('error', () => resolve(null));
1059
- r.write(body);
1060
- r.end();
1061
- });
1062
- if (!spawnResult || !spawnResult.sessionId) {
1063
- if (spawnResult?.error)
1064
- console.error(error(spawnResult.error));
1065
- else
1066
- console.error(error(`Could not reach the daemon at ${base}. Start it with: codehere start`));
1067
- process.exit(1);
1068
- }
1069
- const sid = spawnResult.sessionId;
1070
- console.log(success(`Task assigned · session ${sid.slice(0, 8)}`));
1071
- if (!options?.watch) {
1072
- console.log(colors.dim(` Watch in the browser: ${base}/`));
1073
- console.log(colors.dim(` Or in the terminal: codehere task "${prompt}" --watch`));
1074
- console.log(colors.dim(` Stop it: codehere kill ${sid.slice(0, 8)}`));
1075
- return;
1076
- }
1077
- // --watch · render the agent's actual steps as they complete, then print
1078
- // the deliverable (report + files + trust + verifier) when it's done.
1079
- console.log(colors.dim(` Watching… (Ctrl-C to stop watching; the agent keeps running)`));
1080
- const printed = new Set();
1081
- await new Promise((resolve) => {
1082
- const r = http.get(`${base}/api/task/${sid}/events`, (res) => {
1083
- let buf = '';
1084
- res.on('data', (c) => {
1085
- buf += c.toString();
1086
- let i;
1087
- while ((i = buf.indexOf('\n\n')) >= 0) {
1088
- const frame = buf.slice(0, i).trim();
1089
- buf = buf.slice(i + 2);
1090
- if (!frame.startsWith('data:'))
1091
- continue;
1092
- try {
1093
- const event = JSON.parse(frame.slice(5).trim());
1094
- const type = String(event.type || 'message');
1095
- if (type === 'codehere:steps') {
1096
- const steps = (event.data?.steps ?? []);
1097
- for (const s of steps) {
1098
- if (s.state !== 'done' && s.state !== 'failed')
1099
- continue;
1100
- if (printed.has(s.label))
1101
- continue; // one line per step, on first settle
1102
- printed.add(s.label);
1103
- const mark = s.state === 'failed' ? colors.error('✕') : colors.success('✓');
1104
- const text = s.kind === 'say' ? colors.dim(s.label) : s.label;
1105
- console.log(` ${mark} ${text}`);
1106
- }
1107
- }
1108
- else if (type === 'codehere:error') {
1109
- const d = event.data;
1110
- if (d?.message)
1111
- console.log(` ${colors.error('!')} ${d.message}`);
1112
- if (d?.recovery)
1113
- console.log(colors.dim(` ${d.recovery}`));
1114
- }
1115
- else if (type === 'exit') {
1116
- res.destroy();
1117
- resolve();
1118
- return;
1119
- }
1120
- }
1121
- catch {
1122
- /* skip malformed frame */
1123
- }
1124
- }
1125
- });
1126
- res.on('end', () => resolve());
1127
- });
1128
- r.on('error', () => resolve());
1129
- });
1130
- // The Final Report is generated after exit — poll for it, then print.
1131
- // Real agents take ~30-60s to write it, so tell the user we're waiting.
1132
- process.stdout.write(colors.dim(' Writing the report…'));
1133
- const result = await waitForDeliverable(base, sid);
1134
- process.stdout.write('\r' + ' '.repeat(24) + '\r'); // clear the waiting line
1135
- if (result && (result.finalReport || result.failedReport || result.status !== 'completed')) {
1136
- console.log(formatDeliverable(result));
1137
- }
1138
- else if (result) {
1139
- // Completed, but the report is still generating past our wait window.
1140
- console.log(formatDeliverable(result));
1141
- console.log(colors.dim(' (the written report is still being generated — run the command below in a moment)'));
1142
- }
1143
- else {
1144
- console.log(colors.dim('\n Task finished, but the report could not be fetched from the daemon.'));
1145
- }
1146
- console.log(colors.dim(`Next: codehere verify ${sid.slice(0, 8)} (see this report + the fact-check anytime)`));
1147
- });
1148
- // ── kill (stop a running task) ───────────────────────────────────────────────
1149
- program
1150
- .command('kill <sessionId>')
1151
- .description('Kill a running task session.')
1152
- .option('-p, --port <number>', 'Daemon port', '3773')
1153
- .option('--host <host>', 'Daemon host', '127.0.0.1')
1154
- .action(async (sessionId, options) => {
1155
- const port = parseInt(options?.port ?? '3773', 10);
1156
- const host = options?.host ?? '127.0.0.1';
1157
- const http = await import('http');
1158
- await new Promise((resolve) => {
1159
- const r = http.request(`http://${host}:${port}/api/task/${sessionId}/kill`, { method: 'POST' }, (res) => {
1160
- let buf = '';
1161
- res.on('data', (c) => {
1162
- buf += c.toString();
1163
- });
1164
- res.on('end', () => {
1165
- if (res.statusCode === 200)
1166
- console.log(success(`Killed ${sessionId}`));
1167
- else if (res.statusCode === 404) {
1168
- // Plain English + nonzero exit — scripts must be able to trust us
1169
- // (audit 2026-06-07: kill exited 0 and leaked raw JSON on failure).
1170
- console.error(error(`No running task found for "${sessionId}". See active tasks with: codehere status`));
1171
- process.exitCode = 1;
1172
- }
1173
- else {
1174
- console.error(error(`Could not kill ${sessionId} — the daemon said ${res.statusCode}. Details: ${buf}`));
1175
- process.exitCode = 1;
1176
- }
1177
- resolve();
1178
- });
1179
- });
1180
- r.on('error', () => {
1181
- console.error(error(`Could not reach the daemon at http://${host}:${port}. Start it with: codehere start`));
1182
- process.exitCode = 1;
1183
- resolve();
1184
- });
1185
- r.end();
1186
- });
1187
- });
1188
- // ── migrate-threads — v0.6.0 schema migration sessions.jsonl → projects/<slug>/thread.jsonl
1189
- program
1190
- .command('migrate-threads', { hidden: true }) // hidden from --help (v0.6.0 internal — auto-runs on daemon start once wired)
1191
- .description('Migrate sessions.jsonl into per-project thread.jsonl files (v0.6.0 schema)')
1192
- .option('--source <path>', 'Source sessions.jsonl', `${process.env.CODEHERE_HOME || homedir()}/.codehere/sessions.jsonl`)
1193
- .option('--target <dir>', 'Target projects dir', `${process.env.CODEHERE_HOME || homedir()}/.codehere/projects`)
1194
- .option('--dry-run', 'Report what migrate WOULD do, write nothing', false)
1195
- .option('--rollback', 'Remove the projects dir; sessions.jsonl untouched', false)
1196
- .option('--keep-temp-dirs', 'Include /tmp + /var/folders cwds (default skips them)', false)
1197
- .action(async (options) => {
1198
- const { migrate, dryRun, rollback } = await import('./infrastructure/storage/project-thread-migration.js');
1199
- if (options.rollback) {
1200
- const r = rollback(options.target);
1201
- console.log(`Rollback: removed ${r.removedDirs} project dir(s) from ${options.target}`);
1202
- console.log(`Source ${options.source} untouched.`);
1203
- return;
1204
- }
1205
- const fn = options.dryRun ? dryRun : migrate;
1206
- const label = options.dryRun ? 'DRY RUN' : 'MIGRATION';
1207
- const result = fn({
1208
- sourceJsonl: options.source,
1209
- targetDir: options.target,
1210
- skipTempDirs: !options.keepTempDirs,
1211
- });
1212
- console.log(`=== ${label} ===`);
1213
- console.log(` source: ${options.source}`);
1214
- console.log(` target: ${options.target}`);
1215
- console.log(` sessions processed: ${result.sessionsProcessed}`);
1216
- console.log(` threads ${options.dryRun ? 'would write' : 'written'}: ${result.threadsWritten}`);
1217
- console.log(` threads skipped (exist): ${result.threadsSkippedExisting}`);
1218
- console.log(` unknown-cwd sessions: ${result.unknownCwdSessions}`);
1219
- console.log(` abandoned sessions: ${result.abandonedSessions}`);
1220
- console.log(` temp-dir sessions skipped: ${result.tempDirSessionsSkipped}`);
1221
- console.log(` duration: ${result.durationMs}ms`);
1222
- });
1223
- // ── scan-tool (called by Claude Code hooks system, not users directly) ───────
1224
- program
1225
- .command('scan-tool', { hidden: true }) // hidden from --help (May 2 cut: internal hook handler, called by Claude Code via stdin)
1226
- .description('Hook handler: reads Claude Code tool event from stdin, outputs block/allow (used by hook system)')
1227
- .option('--log-only', 'Never block, log only')
1228
- .option('--block-all', 'Block on any finding, not just critical')
1229
- .option('--debug', 'Debug output')
1230
- .action(async (options) => {
1231
- await executeScanToolCommand({
1232
- logOnly: options?.logOnly,
1233
- blockAll: options?.blockAll,
1234
- debug: options?.debug,
1235
- });
1236
- });
1237
- // ── completion (shell completion script generator) ──────────────────────────
1238
- program
1239
- .command('completion', { hidden: true }) // hidden from --help (May 2 cut: shell-completion is npm convention, not product surface)
1240
- .description('Generate shell completion script')
1241
- .argument('<shell>', 'Shell type: bash, zsh, or fish')
1242
- .action(async (shell) => {
1243
- try {
1244
- // The REAL visible command set — completions must never drift from
1245
- // registration again (audit 2026-06-07: the generators shipped a
1246
- // fictional alpha-era command list).
1247
- const cmds = program.commands
1248
- .filter((c) => !c._hidden)
1249
- .map((c) => ({ name: c.name(), description: c.description().split('\n')[0] }));
1250
- switch (shell.toLowerCase()) {
1251
- case 'bash': {
1252
- const { generateBashCompletion } = await import('./infrastructure/completion/bash-completion.js');
1253
- console.log(generateBashCompletion(cmds));
1254
- break;
1255
- }
1256
- case 'zsh': {
1257
- const { generateZshCompletion } = await import('./infrastructure/completion/zsh-completion.js');
1258
- console.log(generateZshCompletion(cmds));
1259
- break;
1260
- }
1261
- case 'fish': {
1262
- const { generateFishCompletion } = await import('./infrastructure/completion/fish-completion.js');
1263
- console.log(generateFishCompletion(cmds));
1264
- break;
1265
- }
1266
- default:
1267
- console.error(error(`Unsupported shell: ${shell}`));
1268
- console.log(info('Supported shells: bash, zsh, fish'));
1269
- console.log(info('Usage: codehere completion <bash|zsh|fish>'));
1270
- process.exit(1);
1271
- }
1272
- }
1273
- catch (err) {
1274
- displayError(err, { operation: 'completion', showSuggestions: true });
1275
- process.exit(1);
1276
- }
104
+ .command('help')
105
+ .description('Show help for commands')
106
+ .argument('[command]', 'Command name to get help for')
107
+ .action(async (commandName) => {
108
+ await executeHelpCommand(commandName);
1277
109
  });
1278
- // ── slash-command routing: `codehere /trust .` → `codehere trust .` ─────────
1279
- const rawArgs = process.argv.slice(2);
1280
- if (rawArgs.length > 0 && rawArgs[0].startsWith('/') && rawArgs[0].length > 1) {
1281
- const stripped = rawArgs[0].slice(1);
1282
- process.argv.splice(2, 1, stripped);
1283
- }
1284
110
  program.parse();
1285
111
  //# sourceMappingURL=index.js.map