codehere 0.8.0 → 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.
- package/README.md +37 -184
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +329 -0
- package/dist/index.js +74 -1004
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
- package/dist/presentation/cli/commands/check-command.js +251 -0
- package/dist/presentation/cli/commands/help-command.js +22 -286
- package/dist/ui.js +19 -438
- package/dist/utils/version.js +0 -12
- package/package.json +18 -27
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/generate-final-report.js +0 -134
- package/dist/application/use-cases/generate-narration.js +0 -128
- package/dist/application/use-cases/generate-thread-title.js +0 -178
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/thread-export.js +0 -155
- package/dist/application/use-cases/verify-claim.js +0 -274
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/entities/task.js +0 -61
- package/dist/domain/services/codebase-detector.js +0 -91
- package/dist/error-handler.js +0 -251
- package/dist/infrastructure/completion/bash-completion.js +0 -25
- package/dist/infrastructure/completion/fish-completion.js +0 -22
- package/dist/infrastructure/completion/zsh-completion.js +0 -30
- package/dist/infrastructure/config/config-manager.js +0 -173
- package/dist/infrastructure/config/product-context.js +0 -67
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -163
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -119
- package/dist/infrastructure/context/registry-bootstrap.js +0 -99
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -113
- package/dist/infrastructure/cost/cost-router.js +0 -104
- package/dist/infrastructure/cost/spend-aggregator.js +0 -147
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
- package/dist/infrastructure/hooks/hook-mode.js +0 -71
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
- package/dist/infrastructure/orchestration/context-handoff.js +0 -144
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -77
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
- package/dist/infrastructure/orchestration/graphify-context.js +0 -254
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1091
- package/dist/infrastructure/orchestration/session-persistence.js +0 -258
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/protocols/verify-page.js +0 -983
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/security/permissions-config.js +0 -234
- package/dist/infrastructure/security/trust-scorer.js +0 -657
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
- package/dist/infrastructure/storage/project-thread-reader.js +0 -172
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/infrastructure/storage/task-repository.js +0 -206
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -126
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -198
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -578
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -116
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -387
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
- package/dist/presentation/cli/commands/setup-command.js +0 -307
- package/dist/presentation/cli/commands/status-command.js +0 -661
- package/dist/presentation/cli/commands/thread-command.js +0 -288
- package/dist/presentation/cli/commands/trust-command.js +0 -306
- package/dist/presentation/cli/commands/verify-command.js +0 -233
- package/dist/presentation/cli/error-display.js +0 -336
- package/dist/presentation/cli/repl.js +0 -797
- package/dist/presentation/cli/task-deliverable.js +0 -127
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -166
package/dist/index.js
CHANGED
|
@@ -1,360 +1,98 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* codehere CLI entry
|
|
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 {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
// A crash must stop the process: `process.exitCode = 1` is only honoured once the event loop drains.
|
|
11
|
+
function safeVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
return JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8')).version ?? 'unknown';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 'unknown';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function crashReport(kind, err) {
|
|
21
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
22
|
+
return [
|
|
23
|
+
'',
|
|
24
|
+
`[codehere] ${kind} — this process is stopping.`,
|
|
25
|
+
` version : ${safeVersion()}`,
|
|
26
|
+
` node : ${process.version} on ${process.platform} ${process.arch}`,
|
|
27
|
+
'',
|
|
28
|
+
(e.stack ?? e.message)
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map((l) => ` ${l}`)
|
|
31
|
+
.join('\n'),
|
|
32
|
+
'',
|
|
33
|
+
' Report at https://github.com/muhammadegaa/codehere/issues with the block above.',
|
|
34
|
+
'',
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
23
37
|
process.on('uncaughtException', (err) => {
|
|
24
|
-
console.error('
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
console.error(crashReport('uncaughtException', err));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
process.on('unhandledRejection', (reason) => {
|
|
42
|
+
console.error(crashReport('unhandledRejection', reason));
|
|
43
|
+
process.exit(1);
|
|
27
44
|
});
|
|
28
|
-
import {
|
|
29
|
-
import { executeInitCommand } from './presentation/cli/commands/init-command.js';
|
|
30
|
-
import { executeMemoryCommand } from './presentation/cli/commands/memory-command.js';
|
|
31
|
-
import { executeProtocolsCommand } from './presentation/cli/commands/protocols-command.js';
|
|
32
|
-
import { executeConfigCommand } from './presentation/cli/commands/config-command.js';
|
|
45
|
+
import { executeCheckCommand } from './presentation/cli/commands/check-command.js';
|
|
33
46
|
import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
|
|
34
|
-
import {
|
|
35
|
-
import { executeHookCommand } from './presentation/cli/commands/hook-command.js';
|
|
36
|
-
import { executePermissionCommand } from './presentation/cli/commands/permission-command.js';
|
|
37
|
-
import { executeBudgetCommand } from './presentation/cli/commands/budget-command.js';
|
|
38
|
-
import { executeNarratorCommand } from './presentation/cli/commands/narrator-command.js';
|
|
39
|
-
import { executeTrustCommand } from './presentation/cli/commands/trust-command.js';
|
|
40
|
-
import { executeReportCommand } from './presentation/cli/commands/report-command.js';
|
|
41
|
-
import { executeScanToolCommand } from './presentation/cli/commands/scan-tool-command.js';
|
|
42
|
-
import { executeVerifyCommand } from './presentation/cli/commands/verify-command.js';
|
|
43
|
-
import { executeAuditCommand } from './presentation/cli/commands/audit-command.js';
|
|
44
|
-
import { executeContextSourceCommand } from './presentation/cli/commands/context-source-command.js';
|
|
45
|
-
import { executeThreadCommand } from './presentation/cli/commands/thread-command.js';
|
|
46
|
-
import { displayError } from './presentation/cli/error-display.js';
|
|
47
|
-
import { performHealthCheck, formatHealthStatus, getMetrics } from './monitoring.js';
|
|
48
|
-
import { colors, createBanner, createTable, createWelcomeMessage, success, error, info, sectionHeader, formatNumber, newline, isMinimalStyle, printMinimalHeader, } from './ui.js';
|
|
49
|
-
// Load .env — agent dir first, then cwd
|
|
50
|
-
try {
|
|
51
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
52
|
-
const __dirname = dirname(__filename);
|
|
53
|
-
const agentDir = join(__dirname, '..');
|
|
54
|
-
config({ path: join(agentDir, '.env') });
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// Fallback to cwd
|
|
58
|
-
}
|
|
59
|
-
config();
|
|
47
|
+
import { error } from './ui.js';
|
|
60
48
|
import { getVersion } from './utils/version.js';
|
|
61
49
|
import { checkTypo, looksLikeUnknownCommand } from './utils/typo-guard.js';
|
|
62
50
|
const program = new Command();
|
|
63
51
|
program
|
|
64
52
|
.name('codehere')
|
|
65
|
-
.description('
|
|
53
|
+
.description('Re-run your project\'s tests yourself, so an AI\'s "all tests pass" is checked, not taken on trust.')
|
|
66
54
|
.version(getVersion())
|
|
67
|
-
.configureHelp({ helpWidth: 80, sortSubcommands: true })
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// this, see test/cli/commands.test.ts "no raw ANSI escape codes in --help".)
|
|
73
|
-
.addHelpText('after', '\nTip: `codehere help` shows commands grouped by use case (Delegate · Safety & Trust · Configuration).');
|
|
74
|
-
// Default action · show welcome, then drop into the interactive REPL on TTY.
|
|
75
|
-
// codehere offers TWO surfaces, both first-class: the CLI REPL (here) and the
|
|
76
|
-
// web UI (`codehere start`). Users pick whichever fits their workflow.
|
|
77
|
-
program
|
|
78
|
-
// Variadic so multi-token input is preserved end-to-end. Without this,
|
|
79
|
-
// `codehere fix the bug` would silently drop "the bug" and only delegate
|
|
80
|
-
// "fix" — and `codehere daemon start` would never see "start", letting the
|
|
81
|
-
// typo guard miss the unknown-command shape. See typo-guard.ts.
|
|
82
|
-
.argument('[query...]', 'Optional initial task to delegate (otherwise enter interactive REPL)')
|
|
83
|
-
.action(async (queryParts) => {
|
|
84
|
-
const joined = queryParts && queryParts.length > 0 ? queryParts.join(' ') : '';
|
|
85
|
-
// One-shot delegate: `codehere "review src/auth.ts"` runs that task and exits.
|
|
86
|
-
// Useful for scripting or quick one-offs without entering the REPL.
|
|
87
|
-
if (joined && joined.trim()) {
|
|
88
|
-
const trimmed = joined.trim();
|
|
89
|
-
// Typo guard — see src/utils/typo-guard.ts for heuristics.
|
|
90
|
-
// Returns suggestions when input looks like a typo; null falls through.
|
|
91
|
-
const known = program.commands.map((c) => c.name());
|
|
92
|
-
const firstToken = trimmed.split(/\s+/)[0];
|
|
93
|
-
const suggestions = checkTypo(trimmed, known);
|
|
94
|
-
if (suggestions) {
|
|
95
|
-
console.error(`codehere: unknown command '${firstToken}'.`);
|
|
96
|
-
console.error('');
|
|
97
|
-
console.error(' Did you mean:');
|
|
98
|
-
for (const s of suggestions)
|
|
99
|
-
console.error(` codehere ${s}`);
|
|
100
|
-
console.error('');
|
|
101
|
-
console.error(` If you meant to ask a question, quote it: codehere "${trimmed}"`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
// Second guard — `cmd subcmd` shape (e.g. `daemon start`) where the first
|
|
105
|
-
// token isn't close enough for typo suggestions but is clearly a command
|
|
106
|
-
// attempt rather than a natural-language prompt. Without this, the input
|
|
107
|
-
// silently spawns an LLM task on the user's behalf and burns API tokens.
|
|
108
|
-
const unknownFirst = looksLikeUnknownCommand(trimmed, known);
|
|
109
|
-
if (unknownFirst) {
|
|
110
|
-
console.error(`codehere: unknown command '${unknownFirst}'.`);
|
|
111
|
-
console.error('');
|
|
112
|
-
console.error(' Run `codehere help` to see available commands.');
|
|
113
|
-
console.error('');
|
|
114
|
-
console.error(' If you meant to delegate this as a task, either:');
|
|
115
|
-
console.error(` codehere task ${trimmed}`);
|
|
116
|
-
console.error(` codehere "${trimmed}"`);
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
const { runOneShot } = await import('./presentation/cli/repl.js');
|
|
120
|
-
await runOneShot(trimmed);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (!process.stdin.isTTY) {
|
|
124
|
-
console.log(createWelcomeMessage());
|
|
125
|
-
// Non-interactive (piped / scripted): the REPL can't run, so leave the
|
|
126
|
-
// user with ONE clear next step instead of a dead-end welcome screen.
|
|
127
|
-
console.log(colors.dim(' Next: ') + colors.cyan('codehere start') + colors.dim(' open the dashboard'));
|
|
128
|
-
console.log(colors.dim(' New here? ') + colors.cyan('codehere setup') + colors.dim(' one-time setup\n'));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const { runInteractiveRepl } = await import('./presentation/cli/repl.js');
|
|
132
|
-
await runInteractiveRepl();
|
|
133
|
-
});
|
|
134
|
-
// ── setup ────────────────────────────────────────────────────────────────────
|
|
135
|
-
program
|
|
136
|
-
.command('setup')
|
|
137
|
-
.description('Interactive setup wizard for first-time users')
|
|
138
|
-
.option('-f, --force', 'Force reconfiguration even if already set up')
|
|
139
|
-
.action(async (options) => {
|
|
140
|
-
await executeSetupCommand(options.force || false);
|
|
141
|
-
});
|
|
142
|
-
// ── init ─────────────────────────────────────────────────────────────────────
|
|
143
|
-
program
|
|
144
|
-
.command('init')
|
|
145
|
-
.description('Scaffold .codehere/ in this repo · CONTEXT, HANDOVER, PROMPTS')
|
|
146
|
-
.option('-f, --force', 'Overwrite existing files in .codehere/')
|
|
147
|
-
.option('--no-gitignore', 'Skip updating .gitignore')
|
|
148
|
-
.action((options) => {
|
|
149
|
-
executeInitCommand({ force: options.force, noGitignore: !options.gitignore });
|
|
150
|
-
});
|
|
151
|
-
// ── memory ───────────────────────────────────────────────────────────────────
|
|
152
|
-
// F3 v0.5: project memory operations. `memory init` migrates v0.4.1 layout
|
|
153
|
-
// (CONTEXT/HANDOVER/PROMPTS) into the four-layer F3 layout (AGENTS.md +
|
|
154
|
-
// memory/ + skills/). `memory repo-map` builds a JSON symbol map. Future:
|
|
155
|
-
// memory show, memory graduate.
|
|
156
|
-
program
|
|
157
|
-
.command('memory')
|
|
158
|
-
.description('F3 project memory · init / repo-map / show / graduate / help')
|
|
159
|
-
.argument('[action]', 'Action: init, repo-map, show, graduate, help')
|
|
160
|
-
.argument('[layer]', 'Layer for `memory show` (working / episodic / semantic / personal / skills / protocols / injection)')
|
|
161
|
-
.option('-f, --force', 'Overwrite existing target files (memory init)')
|
|
162
|
-
.option('--no-symlinks', 'Delete old paths instead of leaving back-compat symlinks (memory init)')
|
|
163
|
-
.option('--dry-run', 'Print planned moves without touching disk (memory init)')
|
|
164
|
-
.option('--rebuild', 'Force a fresh build (memory repo-map)')
|
|
165
|
-
.option('--add <text>', 'Add a free-text lesson directly (memory graduate)')
|
|
166
|
-
.option('--accept <id>', 'Graduate a candidate by id (memory graduate)')
|
|
167
|
-
.option('--reject <id>', 'Reject a candidate by id without graduating (memory graduate)')
|
|
168
|
-
.action((action, layer, options) => {
|
|
169
|
-
executeMemoryCommand(action, {
|
|
170
|
-
force: options.force,
|
|
171
|
-
noSymlinks: !options.symlinks,
|
|
172
|
-
dryRun: options.dryRun,
|
|
173
|
-
rebuild: options.rebuild,
|
|
174
|
-
layer,
|
|
175
|
-
add: options.add,
|
|
176
|
-
accept: options.accept,
|
|
177
|
-
reject: options.reject,
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
// ── protocols ────────────────────────────────────────────────────────────────
|
|
181
|
-
// F3 v0.5: protocols layer operations. `protocols export` writes the
|
|
182
|
-
// trust-scorer pattern bank to .codehere/protocols/hook_patterns.json so
|
|
183
|
-
// auditors / cross-tool consumers can read codehere's safety surface.
|
|
184
|
-
program
|
|
185
|
-
.command('protocols', { hidden: true }) // hidden from --help (May 2 cut: power-user F3 export, see docs/MEMORY.md)
|
|
186
|
-
.description('F3 protocols layer · export (write hook_patterns.json from trust-scorer bank)')
|
|
187
|
-
.argument('[action]', 'Action: export, help')
|
|
188
|
-
.option('--rebuild', 'Refresh built-in patterns (project patterns are preserved)')
|
|
189
|
-
.action((action, options) => {
|
|
190
|
-
executeProtocolsCommand(action, { rebuild: options.rebuild });
|
|
191
|
-
});
|
|
192
|
-
// ── config ───────────────────────────────────────────────────────────────────
|
|
193
|
-
program
|
|
194
|
-
.command('config')
|
|
195
|
-
.description('Manage configuration and preferences')
|
|
196
|
-
.argument('[action]', 'Action: show, get, set, reset')
|
|
197
|
-
.argument('[key]', 'Preference key (for get/set)')
|
|
198
|
-
.argument('[value]', 'Preference value (for set)')
|
|
199
|
-
.action(async (action, key, value) => {
|
|
200
|
-
await executeConfigCommand(action, key, value);
|
|
201
|
-
});
|
|
202
|
-
// ── help ─────────────────────────────────────────────────────────────────────
|
|
203
|
-
program
|
|
204
|
-
.command('help')
|
|
205
|
-
.description('Show help for commands')
|
|
206
|
-
.argument('[command]', 'Command name to get help for')
|
|
207
|
-
.option('--all', 'Show all commands (including advanced)')
|
|
208
|
-
.action(async (commandName, options) => {
|
|
209
|
-
if (options?.all && !commandName) {
|
|
210
|
-
for (const cmd of program.commands) {
|
|
211
|
-
cmd._hidden = false;
|
|
212
|
-
}
|
|
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) {
|
|
213
60
|
program.outputHelp();
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
await executeHelpCommand(commandName);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
// ── health ───────────────────────────────────────────────────────────────────
|
|
220
|
-
program
|
|
221
|
-
.command('health')
|
|
222
|
-
.description('Check system health and metrics')
|
|
223
|
-
.option('-m, --metrics', 'Show detailed metrics')
|
|
224
|
-
.option('--json', 'Machine-readable JSON (for monitoring/CI)')
|
|
225
|
-
.action((options) => {
|
|
226
|
-
const health = performHealthCheck();
|
|
227
|
-
if (options.json) {
|
|
228
|
-
// Machine-readable: status + checks (+ metrics when --metrics).
|
|
229
|
-
// Stdout is the contract — never mix with the human banner.
|
|
230
|
-
// Use process.exitCode (not process.exit) so a piped consumer sees the
|
|
231
|
-
// full JSON before the process tears down (rc.16: Codex flagged the old
|
|
232
|
-
// process.exit call as a truncation hazard under stdout redirection).
|
|
233
|
-
const payload = { status: health.status, checks: health.checks };
|
|
234
|
-
if (options.metrics)
|
|
235
|
-
payload.metrics = getMetrics();
|
|
236
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
237
|
-
if (health.status === 'unhealthy')
|
|
238
|
-
process.exitCode = 1;
|
|
239
61
|
return;
|
|
240
62
|
}
|
|
241
|
-
|
|
242
|
-
|
|
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}`);
|
|
243
74
|
}
|
|
244
75
|
else {
|
|
245
|
-
|
|
246
|
-
console.log(createBanner('System Health', 'Quick status check'));
|
|
247
|
-
newline();
|
|
248
|
-
}
|
|
249
|
-
console.log(formatHealthStatus(health));
|
|
250
|
-
if (options.metrics) {
|
|
251
|
-
newline();
|
|
252
|
-
sectionHeader('Metrics');
|
|
253
|
-
const metrics = getMetrics();
|
|
254
|
-
const metricsTable = createTable(['Metric', 'Value'], [
|
|
255
|
-
['Total Operations', formatNumber(metrics.operations.total)],
|
|
256
|
-
['Successful', formatNumber(metrics.operations.successful)],
|
|
257
|
-
['Failed', formatNumber(metrics.operations.failed)],
|
|
258
|
-
['Retries', formatNumber(metrics.operations.retries)],
|
|
259
|
-
['Avg Response Time', `${metrics.performance.avgResponseTime.toFixed(2)}ms`],
|
|
260
|
-
['API Calls', formatNumber(metrics.performance.apiCalls)],
|
|
261
|
-
['Cache Hits', formatNumber(metrics.performance.cacheHits)],
|
|
262
|
-
]);
|
|
263
|
-
console.log(metricsTable.toString());
|
|
264
|
-
}
|
|
265
|
-
newline();
|
|
266
|
-
if (health.status === 'unhealthy') {
|
|
267
|
-
process.exit(1);
|
|
76
|
+
console.error(' Run `codehere help` to see available commands.');
|
|
268
77
|
}
|
|
78
|
+
console.error('');
|
|
79
|
+
process.exit(1);
|
|
269
80
|
});
|
|
270
|
-
// ── status ───────────────────────────────────────────────────────────────────
|
|
271
81
|
program
|
|
272
|
-
.command('
|
|
273
|
-
.description('
|
|
274
|
-
.
|
|
275
|
-
.option('--
|
|
276
|
-
.option('--
|
|
277
|
-
.option('--
|
|
278
|
-
.option('--
|
|
279
|
-
.option('--weekly', 'Show shareable weekly agent-activity recap (last 7 days)')
|
|
280
|
-
.option('--export <format>', 'Export raw audit events: jsonl, json, csv, or md')
|
|
281
|
-
.option('--since <duration>', 'Limit --export window, e.g. 24h, 7d, 4w')
|
|
282
|
-
.option('--env', 'Show environment details')
|
|
283
|
-
.option('--track', 'Show command success rates')
|
|
284
|
-
.addHelpText('after', `
|
|
285
|
-
Examples:
|
|
286
|
-
$ codehere status # full dashboard
|
|
287
|
-
$ codehere status --weekly # shareable 7-day agent recap
|
|
288
|
-
$ codehere status --sessions # show per-session breakdown
|
|
289
|
-
$ codehere status --session abc12345 # drill into a specific session
|
|
290
|
-
$ codehere status --export jsonl > audit.jsonl # raw audit log export
|
|
291
|
-
$ codehere status --export csv --since 7d # 7-day CSV for spreadsheets
|
|
292
|
-
$ codehere status --export md --since 24h # 24-hour markdown report
|
|
293
|
-
`)
|
|
294
|
-
.action(async (options) => {
|
|
295
|
-
await executeStatusCommand({
|
|
296
|
-
json: Boolean(options.json),
|
|
297
|
-
tasks: Boolean(options.tasks),
|
|
298
|
-
sessions: Boolean(options.sessions),
|
|
299
|
-
session: options.session,
|
|
300
|
-
trust: Boolean(options.trust),
|
|
301
|
-
weekly: Boolean(options.weekly),
|
|
302
|
-
export: options.export,
|
|
303
|
-
since: options.since,
|
|
304
|
-
env: Boolean(options.env),
|
|
305
|
-
track: Boolean(options.track),
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
// ── trust ────────────────────────────────────────────────────────────────────
|
|
309
|
-
program
|
|
310
|
-
.command('trust')
|
|
311
|
-
.description('Layer 0 trust score — instant static analysis, zero API calls')
|
|
312
|
-
.argument('[target]', 'File, directory, or omit to scan current directory')
|
|
313
|
-
.option('--diff [ref]', 'Score a git diff (default: HEAD)')
|
|
314
|
-
.option('--badge', 'Output shield badge markdown')
|
|
315
|
-
.option('--json', 'Output JSON report')
|
|
316
|
-
.option('--verbose', 'Show all findings with line numbers')
|
|
317
|
-
.option('--ci', 'CI mode: exit 1 if score < 60')
|
|
318
|
-
.action(async (target, options) => {
|
|
319
|
-
try {
|
|
320
|
-
await executeTrustCommand(target, {
|
|
321
|
-
diff: options?.diff === true ? 'HEAD' : options?.diff,
|
|
322
|
-
badge: options?.badge,
|
|
323
|
-
json: options?.json,
|
|
324
|
-
verbose: options?.verbose,
|
|
325
|
-
ci: options?.ci,
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
catch (err) {
|
|
329
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
330
|
-
process.exit(1);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
// ── report ───────────────────────────────────────────────────────────────────
|
|
334
|
-
program
|
|
335
|
-
.command('report')
|
|
336
|
-
.description('Generate a shareable security audit report — narrative, not just a score')
|
|
337
|
-
.argument('[target]', 'File or directory to audit (default: current directory)')
|
|
338
|
-
.option('--out <file>', 'Write report to a specific file (default: codehere-audit.md for markdown)')
|
|
339
|
-
.option('--stdout', 'Print the report to stdout (already the default for --json)')
|
|
340
|
-
.option('--json', 'Output machine-readable JSON to stdout (pipe to jq, etc.)')
|
|
341
|
-
.option('--simple', 'Plain English output — no jargon, for non-technical stakeholders')
|
|
342
|
-
.addHelpText('after', `
|
|
343
|
-
Examples:
|
|
344
|
-
$ codehere report # writes codehere-audit.md, prints summary
|
|
345
|
-
$ codehere report src/ # audit a subdirectory
|
|
346
|
-
$ codehere report --out my-audit.md # write to a specific path
|
|
347
|
-
$ codehere report --stdout # print to terminal (pipe-friendly)
|
|
348
|
-
$ codehere report --json # machine-readable output
|
|
349
|
-
$ codehere report --simple # plain English for non-technical stakeholders
|
|
350
|
-
`)
|
|
82
|
+
.command('check')
|
|
83
|
+
.description('Re-run your tests independently — proof of what the AI actually did, not what it claimed')
|
|
84
|
+
.argument('[target]', 'Repo or directory to check (default: current directory)')
|
|
85
|
+
.option('--json', 'Machine-readable output')
|
|
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)')
|
|
351
89
|
.action(async (target, options) => {
|
|
352
90
|
try {
|
|
353
|
-
await
|
|
354
|
-
out: options?.out,
|
|
355
|
-
stdout: options?.stdout,
|
|
91
|
+
process.exitCode = await executeCheckCommand(target, {
|
|
356
92
|
json: options?.json,
|
|
357
|
-
|
|
93
|
+
proof: options?.proof,
|
|
94
|
+
claim: options?.claim,
|
|
95
|
+
base: options?.base,
|
|
358
96
|
});
|
|
359
97
|
}
|
|
360
98
|
catch (err) {
|
|
@@ -362,680 +100,12 @@ Examples:
|
|
|
362
100
|
process.exit(1);
|
|
363
101
|
}
|
|
364
102
|
});
|
|
365
|
-
// ── verify ───────────────────────────────────────────────────────────────────
|
|
366
|
-
// v0.7 verifier-agent (F-03). Catches the easy lies a Final Report makes
|
|
367
|
-
// against the structured artifacts recorded during the session.
|
|
368
|
-
program
|
|
369
|
-
.command('verify')
|
|
370
|
-
.description("Verify a session's Final Report against the recorded reality (catches optimistic claims)")
|
|
371
|
-
.argument('[session-id]', 'Session id (or short prefix) to verify. Omit to list recent sessions.')
|
|
372
|
-
.option('--json', 'Output machine-readable JSON instead of formatted findings')
|
|
373
|
-
.addHelpText('after', `
|
|
374
|
-
Examples:
|
|
375
|
-
$ codehere verify # list recent sessions to pick from
|
|
376
|
-
$ codehere verify a1b2c3d4 # verify by short id prefix
|
|
377
|
-
$ codehere verify a1b2c3d4 --json # machine-readable verdict for CI
|
|
378
|
-
|
|
379
|
-
Exit codes:
|
|
380
|
-
0 = verified, partial (warnings only), or no Final Report to verify
|
|
381
|
-
1 = broken (a claim contradicts recorded reality)
|
|
382
|
-
2 = session not found
|
|
383
|
-
`)
|
|
384
|
-
.action(async (sessionId, options) => {
|
|
385
|
-
try {
|
|
386
|
-
// Per codex Round 2: set exitCode instead of calling process.exit so a
|
|
387
|
-
// piped --json consumer sees the full payload before Node tears down.
|
|
388
|
-
// The picker mode can write ~15 sessions worth of JSON · enough to
|
|
389
|
-
// exceed pipe buffer + race the process exit. exitCode preserves the
|
|
390
|
-
// intent (CI uses the exit code) without forcing an immediate teardown.
|
|
391
|
-
process.exitCode = await executeVerifyCommand(sessionId, { json: options.json });
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
395
|
-
process.exitCode = 1;
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
// ── audit ────────────────────────────────────────────────────────────────────
|
|
399
|
-
// F-05 of the demo v12 backlog · expose the hash-chain verifier as a CLI.
|
|
400
103
|
program
|
|
401
|
-
.command('
|
|
402
|
-
.description('
|
|
403
|
-
.argument('[
|
|
404
|
-
.
|
|
405
|
-
|
|
406
|
-
.addHelpText('after', `
|
|
407
|
-
Examples:
|
|
408
|
-
$ codehere audit # equivalent to: codehere audit verify
|
|
409
|
-
$ codehere audit verify # walk the chain, report intact/tampered
|
|
410
|
-
$ codehere audit verify --json # machine-readable verdict for CI
|
|
411
|
-
$ codehere audit show 42 # print entry with seq=42
|
|
412
|
-
$ codehere audit show 42 --json
|
|
413
|
-
|
|
414
|
-
Exit codes:
|
|
415
|
-
0 = chain intact OR entry found
|
|
416
|
-
1 = chain BROKEN (one or more break points detected)
|
|
417
|
-
2 = invalid input / no entry found
|
|
418
|
-
`)
|
|
419
|
-
.action((sub, arg, options) => {
|
|
420
|
-
try {
|
|
421
|
-
process.exitCode = executeAuditCommand(sub, arg, { json: options.json });
|
|
422
|
-
}
|
|
423
|
-
catch (err) {
|
|
424
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
425
|
-
process.exitCode = 1;
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
// ── context-source ───────────────────────────────────────────────────────────
|
|
429
|
-
// v0.8 S2.5 · OAuth click-to-connect for Linear, Figma, etc.
|
|
430
|
-
program
|
|
431
|
-
.command('context-source')
|
|
432
|
-
.alias('cs')
|
|
433
|
-
.description('Manage context sources (Linear, Figma) · connect via OAuth, list status, disconnect')
|
|
434
|
-
.argument('[subcommand]', 'list | connect | disconnect', 'list')
|
|
435
|
-
.argument('[provider]', 'For connect/disconnect: linear | figma | ...')
|
|
436
|
-
.option('--json', 'Machine-readable output')
|
|
437
|
-
.option('--no-open', 'For connect: skip auto-opening the browser (just print the URL)')
|
|
438
|
-
.addHelpText('after', `
|
|
439
|
-
Examples:
|
|
440
|
-
$ codehere context-source # equivalent to: list
|
|
441
|
-
$ codehere context-source list
|
|
442
|
-
$ codehere context-source connect linear
|
|
443
|
-
$ codehere context-source connect figma --no-open
|
|
444
|
-
$ codehere context-source disconnect linear
|
|
445
|
-
|
|
446
|
-
Tokens persist at ~/.codehere/context-sources.json (0600 mode).
|
|
447
|
-
Requires the daemon running: codehere start
|
|
448
|
-
Provider client IDs come from env: CODEHERE_LINEAR_CLIENT_ID, CODEHERE_FIGMA_CLIENT_ID
|
|
449
|
-
`)
|
|
450
|
-
.action(async (sub, provider, options) => {
|
|
451
|
-
try {
|
|
452
|
-
process.exitCode = await executeContextSourceCommand(sub, provider, {
|
|
453
|
-
json: options.json,
|
|
454
|
-
open: options.open,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
catch (err) {
|
|
458
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
459
|
-
process.exitCode = 1;
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
// ── thread ───────────────────────────────────────────────────────────────────
|
|
463
|
-
// v0.8 S4 · signed-payload "GitHub for agent sessions" · share/import threads
|
|
464
|
-
program
|
|
465
|
-
.command('thread')
|
|
466
|
-
.description('Share or import a session · Ed25519-signed bundle · local-first')
|
|
467
|
-
.argument('<subcommand>', 'export | import | keys | trust | untrust')
|
|
468
|
-
.argument('[arg]', 'For export: session-id · For import/trust/untrust: file path or pubkey')
|
|
469
|
-
.argument('[label]', 'For trust: an optional human label for the key')
|
|
470
|
-
.option('--out <file>', 'For export: write the signed bundle to a file (default: stdout)')
|
|
471
|
-
.option('--trust', 'For import: trust the signer key automatically if unknown')
|
|
472
|
-
.option('--json', 'Machine-readable output')
|
|
473
|
-
.addHelpText('after', `
|
|
474
|
-
Examples:
|
|
475
|
-
$ codehere thread export a1b2c3d4 --out demo-thread.json
|
|
476
|
-
$ codehere thread import ./shared.json --trust
|
|
477
|
-
$ codehere thread keys
|
|
478
|
-
$ codehere thread trust <pubkey-base64> "Daniel"
|
|
479
|
-
$ codehere thread untrust <pubkey-base64>
|
|
480
|
-
|
|
481
|
-
Bundles include: spawned · steps · artifacts · finalReport · signature.
|
|
482
|
-
No source code · only the synthesized record.
|
|
483
|
-
Recipient must trust the signer key (or pass --trust) before replay.
|
|
484
|
-
`)
|
|
485
|
-
.action((sub, arg, label, options) => {
|
|
486
|
-
try {
|
|
487
|
-
process.exitCode = executeThreadCommand(sub, arg, label, {
|
|
488
|
-
out: options.out,
|
|
489
|
-
trust: options.trust,
|
|
490
|
-
label,
|
|
491
|
-
json: options.json,
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
catch (err) {
|
|
495
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
496
|
-
process.exitCode = 1;
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
// ── hook ─────────────────────────────────────────────────────────────────────
|
|
500
|
-
program
|
|
501
|
-
.command('hook')
|
|
502
|
-
.description('Manage Claude Agent SDK hook integration (intercepts all tool calls)')
|
|
503
|
-
.argument('[subcommand]', 'install | uninstall | status | log')
|
|
504
|
-
.option('--log-only', 'Log events but never block (audit mode)')
|
|
505
|
-
.option('--block-mode <level>', 'Block threshold: critical (default), high, all')
|
|
506
|
-
.option('--tail <n>', 'Lines to show in log mode', '20')
|
|
507
|
-
.option('--debug', 'Verbose debug output')
|
|
508
|
-
.action(async (subcommand, options) => {
|
|
509
|
-
try {
|
|
510
|
-
await executeHookCommand(subcommand, {
|
|
511
|
-
logOnly: options?.logOnly,
|
|
512
|
-
blockMode: options?.blockMode,
|
|
513
|
-
tail: options?.tail ? parseInt(options.tail, 10) : undefined,
|
|
514
|
-
debug: options?.debug,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
catch (err) {
|
|
518
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
519
|
-
process.exit(1);
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
// ── permission (P18 — per-cwd allow/deny list for the trust-scorer) ─────────
|
|
523
|
-
program
|
|
524
|
-
.command('permission')
|
|
525
|
-
.description("Manage this folder's .codehere/permissions.yaml allow/deny list")
|
|
526
|
-
.argument('[subcommand]', 'list | allow | deny | remove')
|
|
527
|
-
.argument('[pattern]', 'the substring pattern (for allow/deny/remove)')
|
|
528
|
-
.option('--reason <text>', 'Why this pattern is allow-listed (recorded in audit)')
|
|
529
|
-
.action(async (subcommand, pattern, options) => {
|
|
530
|
-
try {
|
|
531
|
-
await executePermissionCommand(subcommand, pattern, { reason: options?.reason });
|
|
532
|
-
}
|
|
533
|
-
catch (err) {
|
|
534
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
535
|
-
process.exit(1);
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
// ── narrator (S6 — editable PM narrator prompt) ──────────────────────────────
|
|
539
|
-
program
|
|
540
|
-
.command('narrator')
|
|
541
|
-
.description('Customize the PM narrator prompt template (~/.codehere/narrator-prompt.md)')
|
|
542
|
-
.argument('[subcommand]', 'show | edit | reset')
|
|
543
|
-
.action(async (subcommand) => {
|
|
544
|
-
try {
|
|
545
|
-
await executeNarratorCommand(subcommand);
|
|
546
|
-
}
|
|
547
|
-
catch (err) {
|
|
548
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
549
|
-
process.exit(1);
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
// ── budget (S3 — daily-spend ceiling enforcement) ────────────────────────────
|
|
553
|
-
program
|
|
554
|
-
.command('budget')
|
|
555
|
-
.description('Set or check the daily spend budget for spawns')
|
|
556
|
-
.argument('[subcommand]', 'show | set | off')
|
|
557
|
-
.argument('[value]', 'amount in USD (for `set`)')
|
|
558
|
-
.action(async (subcommand, value) => {
|
|
559
|
-
try {
|
|
560
|
-
await executeBudgetCommand(subcommand, value);
|
|
561
|
-
}
|
|
562
|
-
catch (err) {
|
|
563
|
-
console.error(error('Error:'), err instanceof Error ? err.message : String(err));
|
|
564
|
-
process.exit(1);
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
// ── serve (MCP server) ───────────────────────────────────────────────────────
|
|
568
|
-
program
|
|
569
|
-
.command('serve')
|
|
570
|
-
.description('Run codehere as a background MCP server (no web UI) — for Claude Desktop / Cursor integration')
|
|
571
|
-
.option('-p, --port <number>', 'Port to listen on', '3773')
|
|
572
|
-
.option('--host <host>', 'Host to bind to (default: 127.0.0.1)', '127.0.0.1')
|
|
573
|
-
.option('--token <token>', 'Require Bearer token for authentication')
|
|
574
|
-
.option('--show-config', 'Print the MCP config block to add to Claude Desktop / Cursor settings')
|
|
575
|
-
.action(async (options) => {
|
|
576
|
-
const port = parseInt(options?.port ?? '3773', 10);
|
|
577
|
-
const host = options?.host ?? '127.0.0.1';
|
|
578
|
-
const token = options?.token;
|
|
579
|
-
if (options?.showConfig) {
|
|
580
|
-
const configBlock = {
|
|
581
|
-
mcpServers: {
|
|
582
|
-
codehere: {
|
|
583
|
-
url: `http://${host}:${port}/mcp`,
|
|
584
|
-
...(token ? { headers: { Authorization: `Bearer ${token}` } } : {}),
|
|
585
|
-
},
|
|
586
|
-
},
|
|
587
|
-
};
|
|
588
|
-
console.log('\nAdd to Claude Desktop or Cursor MCP settings:\n');
|
|
589
|
-
console.log(JSON.stringify(configBlock, null, 2));
|
|
590
|
-
newline();
|
|
591
|
-
console.log(colors.dim('Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json'));
|
|
592
|
-
console.log(colors.dim('Cursor: Settings > MCP Servers'));
|
|
593
|
-
newline();
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
try {
|
|
597
|
-
const { startMCPServer } = await import('./infrastructure/protocols/mcp-server-impl.js');
|
|
598
|
-
const server = startMCPServer({ port, host, token });
|
|
599
|
-
newline();
|
|
600
|
-
console.log(success(`codehere MCP server running`));
|
|
601
|
-
newline();
|
|
602
|
-
console.log(colors.bold('Endpoint:') + ` http://${host}:${port}/mcp`);
|
|
603
|
-
console.log(colors.bold('Health:') + ` http://${host}:${port}/health`);
|
|
604
|
-
newline();
|
|
605
|
-
console.log(colors.bold('Tools available:'));
|
|
606
|
-
console.log(' codehere_trust — score a file or snippet (0-100, no API)');
|
|
607
|
-
console.log(' codehere_scan — scan text for injection/threats');
|
|
608
|
-
console.log(' codehere_review — scan a directory, return riskiest files');
|
|
609
|
-
console.log(' codehere_audit — read hook audit log');
|
|
610
|
-
newline();
|
|
611
|
-
console.log(colors.dim(' Run with --show-config to get the MCP client config block'));
|
|
612
|
-
console.log(colors.dim(' Press Ctrl+C to stop'));
|
|
613
|
-
newline();
|
|
614
|
-
process.on('SIGINT', () => {
|
|
615
|
-
server.close();
|
|
616
|
-
console.log(colors.dim('\ncodehere MCP server stopped'));
|
|
617
|
-
process.exit(0);
|
|
618
|
-
});
|
|
619
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
620
|
-
await new Promise((resolve) => {
|
|
621
|
-
server.on('close', resolve);
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
catch (err) {
|
|
625
|
-
console.error(error('Failed to start server:'), err instanceof Error ? err.message : String(err));
|
|
626
|
-
process.exit(1);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
// ── start (Mission Control: daemon + web UI) ─────────────────────────────────
|
|
630
|
-
program
|
|
631
|
-
.command('start')
|
|
632
|
-
.description('Start codehere — interactive PM dashboard at localhost:3773 (delegate tasks, watch agents work)')
|
|
633
|
-
.option('-p, --port <number>', 'Port to listen on', '3773')
|
|
634
|
-
.option('--host <host>', 'Host to bind to', '127.0.0.1')
|
|
635
|
-
.option('--no-open', "Don't auto-open the browser")
|
|
636
|
-
.option('--cloud', 'Enable cloud sync — push session state to codehere.uk/app for remote monitoring')
|
|
637
|
-
.action(async (options) => {
|
|
638
|
-
const port = parseInt(options?.port ?? '3773', 10);
|
|
639
|
-
const host = options?.host ?? '127.0.0.1';
|
|
640
|
-
const shouldOpen = options?.open !== false;
|
|
641
|
-
const cloud = options?.cloud === true;
|
|
642
|
-
const url = `http://${host}:${port}/`;
|
|
643
|
-
const openBrowser = async () => {
|
|
644
|
-
if (!shouldOpen)
|
|
645
|
-
return;
|
|
646
|
-
const { spawn } = await import('child_process');
|
|
647
|
-
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
648
|
-
try {
|
|
649
|
-
spawn(opener, [url], { detached: true, stdio: 'ignore' }).unref();
|
|
650
|
-
}
|
|
651
|
-
catch {
|
|
652
|
-
/* browser open is best-effort */
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
try {
|
|
656
|
-
// v0.6.0 M1+M7 — migrate sessions.jsonl → ~/.codehere/projects/<slug>/thread.jsonl
|
|
657
|
-
// on daemon start. Idempotent: skips projects whose thread.jsonl already exists,
|
|
658
|
-
// so this is safe to run on every boot. Opt-out via CODEHERE_DISABLE_THREAD_MIGRATION=1.
|
|
659
|
-
// Failures are non-blocking — back-compat to v0.5 sessions.jsonl is preserved.
|
|
660
|
-
if (process.env.CODEHERE_DISABLE_THREAD_MIGRATION !== '1') {
|
|
661
|
-
try {
|
|
662
|
-
const { migrate } = await import('./infrastructure/storage/project-thread-migration.js');
|
|
663
|
-
const result = migrate({
|
|
664
|
-
sourceJsonl: join(process.env.CODEHERE_HOME || homedir(), '.codehere/sessions.jsonl'),
|
|
665
|
-
targetDir: join(process.env.CODEHERE_HOME || homedir(), '.codehere/projects'),
|
|
666
|
-
});
|
|
667
|
-
if (result.threadsWritten > 0) {
|
|
668
|
-
console.log(colors.dim(` v0.6 migration: created ${result.threadsWritten} project thread(s) from ${result.sessionsProcessed} sessions (${result.durationMs}ms)`));
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
catch (err) {
|
|
672
|
-
console.warn(colors.dim(` v0.6 migration skipped: ${err instanceof Error ? err.message : String(err)}`));
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
// v0.6.0 follow-up — backfill missing titles for projects that the
|
|
676
|
-
// M9 Title agent never fired on (migration-seeded ones). Runs in the
|
|
677
|
-
// background so daemon startup isn't blocked by ~5 LLM calls.
|
|
678
|
-
// Idempotent + best-effort. Same env-var gate as M9 itself.
|
|
679
|
-
if (process.env.CODEHERE_DISABLE_TITLE_AGENT !== '1') {
|
|
680
|
-
void (async () => {
|
|
681
|
-
try {
|
|
682
|
-
const { backfillThreadTitles } = await import('./infrastructure/storage/project-thread-title-backfill.js');
|
|
683
|
-
const r = await backfillThreadTitles();
|
|
684
|
-
if (r.titlesGenerated > 0 && process.env.CODEHERE_DEBUG === '1') {
|
|
685
|
-
console.log(colors.dim(` v0.6 title backfill: titled ${r.titlesGenerated}/${r.scanned} project(s) in ${r.durationMs}ms`));
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch {
|
|
689
|
-
/* best-effort — silent on failure */
|
|
690
|
-
}
|
|
691
|
-
})();
|
|
692
|
-
}
|
|
693
|
-
const { startMCPServer } = await import('./infrastructure/protocols/mcp-server-impl.js');
|
|
694
|
-
const server = startMCPServer({ port, host });
|
|
695
|
-
// Wait for the server to either start listening or fail. The default
|
|
696
|
-
// Node behavior on listen() failure is an unhandled 'error' event which
|
|
697
|
-
// crashes the process with a stack trace. We catch it here so the user
|
|
698
|
-
// sees a friendly message instead.
|
|
699
|
-
await new Promise((resolve, reject) => {
|
|
700
|
-
server.once('listening', () => resolve());
|
|
701
|
-
server.once('error', reject);
|
|
702
|
-
});
|
|
703
|
-
newline();
|
|
704
|
-
console.log(success('codehere is running'));
|
|
705
|
-
newline();
|
|
706
|
-
console.log(colors.bold('Tasks:') + ' ' + url);
|
|
707
|
-
console.log(colors.bold('History:') + ' ' + `http://${host}:${port}/history`);
|
|
708
|
-
console.log(colors.bold('MCP:') + ' ' + `http://${host}:${port}/mcp`);
|
|
709
|
-
if (cloud) {
|
|
710
|
-
console.log(colors.bold('Cloud:') + ' ' + 'https://codehere.uk/app');
|
|
711
|
-
}
|
|
712
|
-
newline();
|
|
713
|
-
console.log(colors.dim(' Press Ctrl+C to stop'));
|
|
714
|
-
newline();
|
|
715
|
-
// Start cloud sync if enabled
|
|
716
|
-
if (cloud) {
|
|
717
|
-
const { startCloudSync } = await import('./infrastructure/protocols/cloud-sync.js');
|
|
718
|
-
startCloudSync();
|
|
719
|
-
}
|
|
720
|
-
await openBrowser();
|
|
721
|
-
const shutdown = () => {
|
|
722
|
-
try {
|
|
723
|
-
const { SessionManager } = require('./infrastructure/orchestration/session-manager.js');
|
|
724
|
-
SessionManager.getInstance().shutdown();
|
|
725
|
-
}
|
|
726
|
-
catch {
|
|
727
|
-
/* graceful degradation — daemon may not have started fully */
|
|
728
|
-
}
|
|
729
|
-
if (cloud) {
|
|
730
|
-
try {
|
|
731
|
-
const { stopCloudSync } = require('./infrastructure/protocols/cloud-sync.js');
|
|
732
|
-
stopCloudSync();
|
|
733
|
-
}
|
|
734
|
-
catch {
|
|
735
|
-
/* cloud sync may not be loaded */
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
server.close();
|
|
739
|
-
};
|
|
740
|
-
process.on('SIGINT', () => {
|
|
741
|
-
shutdown();
|
|
742
|
-
console.log(colors.dim('\ncodehere stopped'));
|
|
743
|
-
process.exit(0);
|
|
744
|
-
});
|
|
745
|
-
process.on('SIGTERM', () => {
|
|
746
|
-
shutdown();
|
|
747
|
-
process.exit(0);
|
|
748
|
-
});
|
|
749
|
-
await new Promise((resolve) => {
|
|
750
|
-
server.on('close', resolve);
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
catch (err) {
|
|
754
|
-
const code = err?.code;
|
|
755
|
-
if (code === 'EADDRINUSE') {
|
|
756
|
-
// Port is taken. Probe to find out by what — if it's a running codehere,
|
|
757
|
-
// we should hand the user the existing URL; if it's something else,
|
|
758
|
-
// tell them to pick a different port.
|
|
759
|
-
const { isCodehereOnPort } = await import('./infrastructure/protocols/probe-port.js');
|
|
760
|
-
const existing = await isCodehereOnPort(host, port);
|
|
761
|
-
newline();
|
|
762
|
-
if (existing.isCodehere) {
|
|
763
|
-
console.log(success(`codehere is already running at ${url}`));
|
|
764
|
-
if (existing.version) {
|
|
765
|
-
console.log(colors.dim(` version: ${existing.version}`));
|
|
766
|
-
}
|
|
767
|
-
newline();
|
|
768
|
-
// Honor --no-open here too — found via the v1 stranger walk: a second
|
|
769
|
-
// `start --no-open` against a running daemon popped a browser anyway.
|
|
770
|
-
if (shouldOpen) {
|
|
771
|
-
console.log(colors.dim(' Opening browser...'));
|
|
772
|
-
await openBrowser();
|
|
773
|
-
}
|
|
774
|
-
process.exit(0);
|
|
775
|
-
}
|
|
776
|
-
console.error(error(`Port ${port} is in use by another process.`));
|
|
777
|
-
console.error(colors.dim(` Try: codehere start --port ${port + 1}`));
|
|
778
|
-
process.exit(1);
|
|
779
|
-
}
|
|
780
|
-
console.error(error('Failed to start:'), err instanceof Error ? err.message : String(err));
|
|
781
|
-
process.exit(1);
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
// ── task (spawn an agent session via CLI) ────────────────────────────────────
|
|
785
|
-
program
|
|
786
|
-
.command('task <prompt...>')
|
|
787
|
-
.description('Assign a task — spawns an agent under codehere. Requires `codehere start` running.')
|
|
788
|
-
.option('-p, --port <number>', 'Daemon port', '3773')
|
|
789
|
-
.option('--host <host>', 'Daemon host', '127.0.0.1')
|
|
790
|
-
.option('--agent <id>', 'Which agent to run (claude-code, codex, aider, gemini, opencode). Default: auto.')
|
|
791
|
-
.option('--watch', 'Watch it work and print the report when it finishes')
|
|
792
|
-
.addHelpText('after', `
|
|
793
|
-
Examples:
|
|
794
|
-
$ codehere task "fix the totalValue bug in inventory.js" # assign, get a session id
|
|
795
|
-
$ codehere task "add a health check route" --watch # watch it work + see the report
|
|
796
|
-
$ codehere task "summarize recent changes" --agent codex --watch # pick the agent
|
|
797
|
-
|
|
798
|
-
Needs a running daemon: start one with \`codehere start\` (or \`codehere serve\`).`)
|
|
799
|
-
.action(async (promptParts, options) => {
|
|
800
|
-
const prompt = promptParts.join(' ').trim();
|
|
801
|
-
if (!prompt) {
|
|
802
|
-
console.error(error('No prompt. Usage: codehere task "fix auth bug in session.ts"'));
|
|
803
|
-
process.exit(1);
|
|
804
|
-
}
|
|
805
|
-
const port = parseInt(options?.port ?? '3773', 10);
|
|
806
|
-
const host = options?.host ?? '127.0.0.1';
|
|
807
|
-
const base = `http://${host}:${port}`;
|
|
808
|
-
const http = await import('http');
|
|
809
|
-
const { waitForDeliverable, formatDeliverable } = await import('./presentation/cli/task-deliverable.js');
|
|
810
|
-
const spawnResult = await new Promise((resolve) => {
|
|
811
|
-
const body = JSON.stringify({ prompt, ...(options?.agent ? { agent: options.agent } : {}) });
|
|
812
|
-
const r = http.request(`${base}/api/task`, {
|
|
813
|
-
method: 'POST',
|
|
814
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
815
|
-
}, (res) => {
|
|
816
|
-
let buf = '';
|
|
817
|
-
res.on('data', (c) => {
|
|
818
|
-
buf += c.toString();
|
|
819
|
-
});
|
|
820
|
-
res.on('end', () => {
|
|
821
|
-
try {
|
|
822
|
-
resolve(JSON.parse(buf));
|
|
823
|
-
}
|
|
824
|
-
catch {
|
|
825
|
-
resolve(null);
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
r.on('error', () => resolve(null));
|
|
830
|
-
r.write(body);
|
|
831
|
-
r.end();
|
|
832
|
-
});
|
|
833
|
-
if (!spawnResult || !spawnResult.sessionId) {
|
|
834
|
-
if (spawnResult?.error)
|
|
835
|
-
console.error(error(spawnResult.error));
|
|
836
|
-
else
|
|
837
|
-
console.error(error(`Could not reach the daemon at ${base}. Start it with: codehere start`));
|
|
838
|
-
process.exit(1);
|
|
839
|
-
}
|
|
840
|
-
const sid = spawnResult.sessionId;
|
|
841
|
-
console.log(success(`Task assigned · session ${sid.slice(0, 8)}`));
|
|
842
|
-
if (!options?.watch) {
|
|
843
|
-
console.log(colors.dim(` Watch in the browser: ${base}/`));
|
|
844
|
-
console.log(colors.dim(` Or in the terminal: codehere task "${prompt}" --watch`));
|
|
845
|
-
console.log(colors.dim(` Stop it: codehere kill ${sid.slice(0, 8)}`));
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
// --watch · render the agent's actual steps as they complete, then print
|
|
849
|
-
// the deliverable (report + files + trust + verifier) when it's done.
|
|
850
|
-
console.log(colors.dim(` Watching… (Ctrl-C to stop watching; the agent keeps running)`));
|
|
851
|
-
const printed = new Set();
|
|
852
|
-
await new Promise((resolve) => {
|
|
853
|
-
const r = http.get(`${base}/api/task/${sid}/events`, (res) => {
|
|
854
|
-
let buf = '';
|
|
855
|
-
res.on('data', (c) => {
|
|
856
|
-
buf += c.toString();
|
|
857
|
-
let i;
|
|
858
|
-
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
859
|
-
const frame = buf.slice(0, i).trim();
|
|
860
|
-
buf = buf.slice(i + 2);
|
|
861
|
-
if (!frame.startsWith('data:'))
|
|
862
|
-
continue;
|
|
863
|
-
try {
|
|
864
|
-
const event = JSON.parse(frame.slice(5).trim());
|
|
865
|
-
const type = String(event.type || 'message');
|
|
866
|
-
if (type === 'codehere:steps') {
|
|
867
|
-
const steps = (event.data?.steps ?? []);
|
|
868
|
-
for (const s of steps) {
|
|
869
|
-
if (s.state !== 'done' && s.state !== 'failed')
|
|
870
|
-
continue;
|
|
871
|
-
if (printed.has(s.label))
|
|
872
|
-
continue; // one line per step, on first settle
|
|
873
|
-
printed.add(s.label);
|
|
874
|
-
const mark = s.state === 'failed' ? colors.error('✗') : colors.success('✓');
|
|
875
|
-
const text = s.kind === 'say' ? colors.dim(s.label) : s.label;
|
|
876
|
-
console.log(` ${mark} ${text}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
else if (type === 'codehere:error') {
|
|
880
|
-
const d = event.data;
|
|
881
|
-
if (d?.message)
|
|
882
|
-
console.log(` ${colors.error('!')} ${d.message}`);
|
|
883
|
-
if (d?.recovery)
|
|
884
|
-
console.log(colors.dim(` ${d.recovery}`));
|
|
885
|
-
}
|
|
886
|
-
else if (type === 'exit') {
|
|
887
|
-
res.destroy();
|
|
888
|
-
resolve();
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
catch {
|
|
893
|
-
/* skip malformed frame */
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
res.on('end', () => resolve());
|
|
898
|
-
});
|
|
899
|
-
r.on('error', () => resolve());
|
|
900
|
-
});
|
|
901
|
-
// The Final Report is generated after exit — poll for it, then print.
|
|
902
|
-
// Real agents take ~30-60s to write it, so tell the user we're waiting.
|
|
903
|
-
process.stdout.write(colors.dim(' Writing the report…'));
|
|
904
|
-
const result = await waitForDeliverable(base, sid);
|
|
905
|
-
process.stdout.write('\r' + ' '.repeat(24) + '\r'); // clear the waiting line
|
|
906
|
-
if (result && (result.finalReport || result.failedReport || result.status !== 'completed')) {
|
|
907
|
-
console.log(formatDeliverable(result));
|
|
908
|
-
}
|
|
909
|
-
else if (result) {
|
|
910
|
-
// Completed, but the report is still generating past our wait window.
|
|
911
|
-
console.log(formatDeliverable(result));
|
|
912
|
-
console.log(colors.dim(' (the written report is still being generated — run the command below in a moment)'));
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
console.log(colors.dim('\n Task finished, but the report could not be fetched from the daemon.'));
|
|
916
|
-
}
|
|
917
|
-
console.log(colors.dim(`Next: codehere verify ${sid.slice(0, 8)} (see this report + the fact-check anytime)`));
|
|
918
|
-
});
|
|
919
|
-
// ── kill (stop a running task) ───────────────────────────────────────────────
|
|
920
|
-
program
|
|
921
|
-
.command('kill <sessionId>')
|
|
922
|
-
.description('Kill a running task session.')
|
|
923
|
-
.option('-p, --port <number>', 'Daemon port', '3773')
|
|
924
|
-
.option('--host <host>', 'Daemon host', '127.0.0.1')
|
|
925
|
-
.action(async (sessionId, options) => {
|
|
926
|
-
const port = parseInt(options?.port ?? '3773', 10);
|
|
927
|
-
const host = options?.host ?? '127.0.0.1';
|
|
928
|
-
const http = await import('http');
|
|
929
|
-
await new Promise((resolve) => {
|
|
930
|
-
const r = http.request(`http://${host}:${port}/api/task/${sessionId}/kill`, { method: 'POST' }, (res) => {
|
|
931
|
-
let buf = '';
|
|
932
|
-
res.on('data', (c) => {
|
|
933
|
-
buf += c.toString();
|
|
934
|
-
});
|
|
935
|
-
res.on('end', () => {
|
|
936
|
-
if (res.statusCode === 200)
|
|
937
|
-
console.log(success(`Killed ${sessionId}`));
|
|
938
|
-
else
|
|
939
|
-
console.error(error(`Failed (${res.statusCode}): ${buf}`));
|
|
940
|
-
resolve();
|
|
941
|
-
});
|
|
942
|
-
});
|
|
943
|
-
r.on('error', (err) => {
|
|
944
|
-
console.error(error(err.message));
|
|
945
|
-
resolve();
|
|
946
|
-
});
|
|
947
|
-
r.end();
|
|
948
|
-
});
|
|
949
|
-
});
|
|
950
|
-
// ── migrate-threads — v0.6.0 schema migration sessions.jsonl → projects/<slug>/thread.jsonl
|
|
951
|
-
program
|
|
952
|
-
.command('migrate-threads', { hidden: true }) // hidden from --help (v0.6.0 internal — auto-runs on daemon start once wired)
|
|
953
|
-
.description('Migrate sessions.jsonl into per-project thread.jsonl files (v0.6.0 schema)')
|
|
954
|
-
.option('--source <path>', 'Source sessions.jsonl', `${process.env.CODEHERE_HOME || homedir()}/.codehere/sessions.jsonl`)
|
|
955
|
-
.option('--target <dir>', 'Target projects dir', `${process.env.CODEHERE_HOME || homedir()}/.codehere/projects`)
|
|
956
|
-
.option('--dry-run', 'Report what migrate WOULD do, write nothing', false)
|
|
957
|
-
.option('--rollback', 'Remove the projects dir; sessions.jsonl untouched', false)
|
|
958
|
-
.option('--keep-temp-dirs', 'Include /tmp + /var/folders cwds (default skips them)', false)
|
|
959
|
-
.action(async (options) => {
|
|
960
|
-
const { migrate, dryRun, rollback } = await import('./infrastructure/storage/project-thread-migration.js');
|
|
961
|
-
if (options.rollback) {
|
|
962
|
-
const r = rollback(options.target);
|
|
963
|
-
console.log(`Rollback: removed ${r.removedDirs} project dir(s) from ${options.target}`);
|
|
964
|
-
console.log(`Source ${options.source} untouched.`);
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
const fn = options.dryRun ? dryRun : migrate;
|
|
968
|
-
const label = options.dryRun ? 'DRY RUN' : 'MIGRATION';
|
|
969
|
-
const result = fn({
|
|
970
|
-
sourceJsonl: options.source,
|
|
971
|
-
targetDir: options.target,
|
|
972
|
-
skipTempDirs: !options.keepTempDirs,
|
|
973
|
-
});
|
|
974
|
-
console.log(`=== ${label} ===`);
|
|
975
|
-
console.log(` source: ${options.source}`);
|
|
976
|
-
console.log(` target: ${options.target}`);
|
|
977
|
-
console.log(` sessions processed: ${result.sessionsProcessed}`);
|
|
978
|
-
console.log(` threads ${options.dryRun ? 'would write' : 'written'}: ${result.threadsWritten}`);
|
|
979
|
-
console.log(` threads skipped (exist): ${result.threadsSkippedExisting}`);
|
|
980
|
-
console.log(` unknown-cwd sessions: ${result.unknownCwdSessions}`);
|
|
981
|
-
console.log(` abandoned sessions: ${result.abandonedSessions}`);
|
|
982
|
-
console.log(` temp-dir sessions skipped: ${result.tempDirSessionsSkipped}`);
|
|
983
|
-
console.log(` duration: ${result.durationMs}ms`);
|
|
984
|
-
});
|
|
985
|
-
// ── scan-tool (called by Claude Code hooks system, not users directly) ───────
|
|
986
|
-
program
|
|
987
|
-
.command('scan-tool', { hidden: true }) // hidden from --help (May 2 cut: internal hook handler, called by Claude Code via stdin)
|
|
988
|
-
.description('Hook handler: reads Claude Code tool event from stdin, outputs block/allow (used by hook system)')
|
|
989
|
-
.option('--log-only', 'Never block, log only')
|
|
990
|
-
.option('--block-all', 'Block on any finding, not just critical')
|
|
991
|
-
.option('--debug', 'Debug output')
|
|
992
|
-
.action(async (options) => {
|
|
993
|
-
await executeScanToolCommand({
|
|
994
|
-
logOnly: options?.logOnly,
|
|
995
|
-
blockAll: options?.blockAll,
|
|
996
|
-
debug: options?.debug,
|
|
997
|
-
});
|
|
998
|
-
});
|
|
999
|
-
// ── completion (shell completion script generator) ──────────────────────────
|
|
1000
|
-
program
|
|
1001
|
-
.command('completion', { hidden: true }) // hidden from --help (May 2 cut: shell-completion is npm convention, not product surface)
|
|
1002
|
-
.description('Generate shell completion script')
|
|
1003
|
-
.argument('<shell>', 'Shell type: bash, zsh, or fish')
|
|
1004
|
-
.action(async (shell) => {
|
|
1005
|
-
try {
|
|
1006
|
-
switch (shell.toLowerCase()) {
|
|
1007
|
-
case 'bash': {
|
|
1008
|
-
const { generateBashCompletion } = await import('./infrastructure/completion/bash-completion.js');
|
|
1009
|
-
console.log(generateBashCompletion());
|
|
1010
|
-
break;
|
|
1011
|
-
}
|
|
1012
|
-
case 'zsh': {
|
|
1013
|
-
const { generateZshCompletion } = await import('./infrastructure/completion/zsh-completion.js');
|
|
1014
|
-
console.log(generateZshCompletion());
|
|
1015
|
-
break;
|
|
1016
|
-
}
|
|
1017
|
-
case 'fish': {
|
|
1018
|
-
const { generateFishCompletion } = await import('./infrastructure/completion/fish-completion.js');
|
|
1019
|
-
console.log(generateFishCompletion());
|
|
1020
|
-
break;
|
|
1021
|
-
}
|
|
1022
|
-
default:
|
|
1023
|
-
console.error(error(`Unsupported shell: ${shell}`));
|
|
1024
|
-
console.log(info('Supported shells: bash, zsh, fish'));
|
|
1025
|
-
console.log(info('Usage: codehere completion <bash|zsh|fish>'));
|
|
1026
|
-
process.exit(1);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
catch (err) {
|
|
1030
|
-
displayError(err, { operation: 'completion', showSuggestions: true });
|
|
1031
|
-
process.exit(1);
|
|
1032
|
-
}
|
|
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);
|
|
1033
109
|
});
|
|
1034
|
-
// ── slash-command routing: `codehere /trust .` → `codehere trust .` ─────────
|
|
1035
|
-
const rawArgs = process.argv.slice(2);
|
|
1036
|
-
if (rawArgs.length > 0 && rawArgs[0].startsWith('/') && rawArgs[0].length > 1) {
|
|
1037
|
-
const stripped = rawArgs[0].slice(1);
|
|
1038
|
-
process.argv.splice(2, 1, stripped);
|
|
1039
|
-
}
|
|
1040
110
|
program.parse();
|
|
1041
111
|
//# sourceMappingURL=index.js.map
|