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.
- package/README.md +36 -192
- 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 +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- 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/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- 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 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- 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/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- 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 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- 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 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- 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 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- 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 -105
- 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 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI: `codehere context-source` · v0.8 S2.5
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* list · show every registered provider + connection status
|
|
6
|
-
* connect <provider> · open the browser to begin OAuth · prints URL
|
|
7
|
-
* disconnect <provider> · clear the stored OAuth token
|
|
8
|
-
*
|
|
9
|
-
* Talks to the running daemon at 127.0.0.1:3773. If the daemon isn't running,
|
|
10
|
-
* we print a clear message rather than starting one silently.
|
|
11
|
-
*/
|
|
12
|
-
import { colors, symbols } from '../../../ui.js';
|
|
13
|
-
import { spawn } from 'child_process';
|
|
14
|
-
const DAEMON_BASE = process.env.CODEHERE_DAEMON_URL || 'http://127.0.0.1:3773';
|
|
15
|
-
export async function executeContextSourceCommand(sub, arg, options = {}) {
|
|
16
|
-
switch (sub) {
|
|
17
|
-
case undefined:
|
|
18
|
-
case 'list':
|
|
19
|
-
return list(options.json ?? false);
|
|
20
|
-
case 'connect':
|
|
21
|
-
return connect(arg, options.json ?? false, options.open !== false);
|
|
22
|
-
case 'disconnect':
|
|
23
|
-
return disconnect(arg, options.json ?? false);
|
|
24
|
-
default:
|
|
25
|
-
process.stderr.write(colors.error(`${symbols.error} Unknown subcommand: ${sub}\n`) +
|
|
26
|
-
colors.dim('Try: codehere context-source list | connect <provider> | disconnect <provider>\n'));
|
|
27
|
-
return 2;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async function fetchJson(path, init = {}) {
|
|
31
|
-
try {
|
|
32
|
-
const r = await fetch(DAEMON_BASE + path, init);
|
|
33
|
-
if (!r.ok) {
|
|
34
|
-
const text = await r.text();
|
|
35
|
-
return { ok: false, status: r.status, error: text };
|
|
36
|
-
}
|
|
37
|
-
return { ok: true, status: r.status, data: (await r.json()) };
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
return { ok: false, status: 0, error: err instanceof Error ? err.message : String(err) };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function daemonUnreachableNote(err) {
|
|
44
|
-
return (colors.error(`${symbols.error} Daemon not reachable at ${DAEMON_BASE}.\n`) +
|
|
45
|
-
colors.dim(` Start it with: `) +
|
|
46
|
-
colors.bold('codehere start') +
|
|
47
|
-
'\n' +
|
|
48
|
-
colors.dim(` Detail: ${err}\n`));
|
|
49
|
-
}
|
|
50
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
51
|
-
/* list */
|
|
52
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
53
|
-
async function list(json) {
|
|
54
|
-
const r = await fetchJson('/api/context-sources');
|
|
55
|
-
if (!r.ok) {
|
|
56
|
-
if (json) {
|
|
57
|
-
process.stdout.write(JSON.stringify({ error: 'daemon_unreachable', detail: r.error }, null, 2) + '\n');
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
process.stderr.write(daemonUnreachableNote(r.error ?? ''));
|
|
61
|
-
}
|
|
62
|
-
return 2;
|
|
63
|
-
}
|
|
64
|
-
const items = r.data?.providers ?? [];
|
|
65
|
-
if (json) {
|
|
66
|
-
process.stdout.write(JSON.stringify({ providers: items }, null, 2) + '\n');
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
process.stdout.write('\n' + colors.bold('Context sources') + '\n\n');
|
|
70
|
-
if (!items.length) {
|
|
71
|
-
process.stdout.write(colors.dim(' (no providers registered)\n\n'));
|
|
72
|
-
return 0;
|
|
73
|
-
}
|
|
74
|
-
for (const p of items) {
|
|
75
|
-
const tag = statusTag(p.status);
|
|
76
|
-
const who = p.userLabel ? colors.gray('· ' + p.userLabel) : '';
|
|
77
|
-
process.stdout.write(` ${tag} ${colors.bold(p.label.padEnd(10))} ${colors.dim(p.scheme + '://')} ${who}\n`);
|
|
78
|
-
if (!p.clientIdConfigured) {
|
|
79
|
-
process.stdout.write(' ' + colors.warning(`set CODEHERE_${p.id.toUpperCase()}_CLIENT_ID before connect()`) + '\n');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
process.stdout.write('\n' + colors.dim('Connect: ') + colors.bold('codehere context-source connect <provider>') + '\n\n');
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
function statusTag(s) {
|
|
86
|
-
switch (s) {
|
|
87
|
-
case 'connected':
|
|
88
|
-
return colors.green('● connected ');
|
|
89
|
-
case 'expired':
|
|
90
|
-
return colors.warning('● expired ');
|
|
91
|
-
case 'manual':
|
|
92
|
-
return colors.gray('● manual ');
|
|
93
|
-
case 'disconnected':
|
|
94
|
-
return colors.gray('○ disconnected');
|
|
95
|
-
default:
|
|
96
|
-
// A status this CLI doesn't know yet must never print "undefined"
|
|
97
|
-
// (2026-06-07 audit, Lane C: draw.io/GitHub rows did exactly that).
|
|
98
|
-
return colors.gray(('○ ' + String(s ?? 'unknown')).padEnd(14).slice(0, 14));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
102
|
-
/* connect */
|
|
103
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
104
|
-
async function connect(providerArg, json, openBrowser) {
|
|
105
|
-
if (!providerArg) {
|
|
106
|
-
process.stderr.write(colors.error(`${symbols.error} Need a provider id: codehere context-source connect <id>\n`));
|
|
107
|
-
return 2;
|
|
108
|
-
}
|
|
109
|
-
const r = await fetchJson(`/api/context-sources/${encodeURIComponent(providerArg)}/connect`, { method: 'POST' });
|
|
110
|
-
if (!r.ok) {
|
|
111
|
-
let detail = null;
|
|
112
|
-
try {
|
|
113
|
-
detail = JSON.parse(r.error ?? '{}');
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
/* ignore */
|
|
117
|
-
}
|
|
118
|
-
if (detail?.error === 'provider_not_registered') {
|
|
119
|
-
const envVar = detail.envVar ?? `CODEHERE_${providerArg.toUpperCase()}_CLIENT_ID`;
|
|
120
|
-
if (json) {
|
|
121
|
-
process.stdout.write(JSON.stringify({ error: 'provider_not_registered', envVar, message: detail.message }, null, 2) + '\n');
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
process.stderr.write(colors.error(`${symbols.error} ${providerArg} OAuth app not registered.\n`) +
|
|
125
|
-
colors.dim(` ${detail.message ?? ''}\n`) +
|
|
126
|
-
colors.dim(` Set the env var: `) +
|
|
127
|
-
colors.bold(envVar) +
|
|
128
|
-
'\n');
|
|
129
|
-
}
|
|
130
|
-
return 2;
|
|
131
|
-
}
|
|
132
|
-
if (json) {
|
|
133
|
-
process.stdout.write(JSON.stringify({ error: 'connect_failed', detail: r.error }, null, 2) + '\n');
|
|
134
|
-
}
|
|
135
|
-
else if (r.status === 0) {
|
|
136
|
-
// Only a NETWORK failure means the daemon is unreachable.
|
|
137
|
-
process.stderr.write(daemonUnreachableNote(r.error ?? ''));
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
// The daemon answered — show ITS message, don't blame the connection
|
|
141
|
-
// (2026-06-07 audit, Lane C: a 404 from a live daemon printed
|
|
142
|
-
// "Daemon not reachable").
|
|
143
|
-
let msg = r.error ?? `request failed (${r.status})`;
|
|
144
|
-
try {
|
|
145
|
-
const j = JSON.parse(msg);
|
|
146
|
-
msg = j.error || j.message || msg;
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
/* raw text is fine */
|
|
150
|
-
}
|
|
151
|
-
process.stderr.write(colors.error(`${symbols.error} ${providerArg}: ${msg}\n`));
|
|
152
|
-
}
|
|
153
|
-
return 2;
|
|
154
|
-
}
|
|
155
|
-
const url = r.data?.authorizeUrl;
|
|
156
|
-
if (!url) {
|
|
157
|
-
process.stderr.write(colors.error(`${symbols.error} Daemon did not return an authorize URL.\n`));
|
|
158
|
-
return 1;
|
|
159
|
-
}
|
|
160
|
-
if (json) {
|
|
161
|
-
process.stdout.write(JSON.stringify({ authorizeUrl: url }, null, 2) + '\n');
|
|
162
|
-
return 0;
|
|
163
|
-
}
|
|
164
|
-
process.stdout.write('\n' +
|
|
165
|
-
colors.bold(`Opening ${providerArg} OAuth flow`) +
|
|
166
|
-
'\n\n' +
|
|
167
|
-
colors.dim(' URL: ') +
|
|
168
|
-
url +
|
|
169
|
-
'\n\n' +
|
|
170
|
-
colors.dim(' Codehere will store the token at ~/.codehere/context-sources.json.\n') +
|
|
171
|
-
colors.dim(' You can close the browser tab when it says "connected".\n\n'));
|
|
172
|
-
if (openBrowser) {
|
|
173
|
-
openInBrowser(url);
|
|
174
|
-
}
|
|
175
|
-
return 0;
|
|
176
|
-
}
|
|
177
|
-
function openInBrowser(url) {
|
|
178
|
-
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
179
|
-
try {
|
|
180
|
-
const child = spawn(cmd, [url], { detached: true, stdio: 'ignore' });
|
|
181
|
-
child.unref();
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
// best-effort · the URL is already printed
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
188
|
-
/* disconnect */
|
|
189
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
190
|
-
async function disconnect(providerArg, json) {
|
|
191
|
-
if (!providerArg) {
|
|
192
|
-
process.stderr.write(colors.error(`${symbols.error} Need a provider id: codehere context-source disconnect <id>\n`));
|
|
193
|
-
return 2;
|
|
194
|
-
}
|
|
195
|
-
const r = await fetchJson(`/api/context-sources/${encodeURIComponent(providerArg)}/disconnect`, { method: 'POST' });
|
|
196
|
-
if (!r.ok) {
|
|
197
|
-
if (json) {
|
|
198
|
-
process.stdout.write(JSON.stringify({ error: 'disconnect_failed', detail: r.error }, null, 2) + '\n');
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
process.stderr.write(daemonUnreachableNote(r.error ?? ''));
|
|
202
|
-
}
|
|
203
|
-
return 2;
|
|
204
|
-
}
|
|
205
|
-
if (json) {
|
|
206
|
-
process.stdout.write(JSON.stringify(r.data) + '\n');
|
|
207
|
-
return 0;
|
|
208
|
-
}
|
|
209
|
-
if (r.data?.ok) {
|
|
210
|
-
process.stdout.write(colors.green(`${symbols.success} disconnected ${providerArg}\n`));
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
process.stdout.write(colors.gray(`${symbols.dot} ${providerArg} was not connected\n`));
|
|
214
|
-
}
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
|
-
//# sourceMappingURL=context-source-command.js.map
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Command: hook
|
|
3
|
-
*
|
|
4
|
-
* Manages Claude Code SDK hook integration.
|
|
5
|
-
* Intercepts every Claude Code tool call and runs Layer 0 security scan.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* codehere hook install # Add hooks to ~/.claude/settings.json
|
|
9
|
-
* codehere hook uninstall # Remove hooks
|
|
10
|
-
* codehere hook status # Show current hook config and recent events
|
|
11
|
-
* codehere hook log # Show recent audit log
|
|
12
|
-
* codehere hook log --tail 50 # Last 50 events
|
|
13
|
-
*/
|
|
14
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, copyFileSync, unlinkSync, rmdirSync, } from 'fs';
|
|
15
|
-
import { join, dirname } from 'path';
|
|
16
|
-
import { homedir } from 'os';
|
|
17
|
-
import { fileURLToPath } from 'url';
|
|
18
|
-
import { generateHookConfig, AUDIT_FILE } from '../../../infrastructure/hooks/claude-sdk-hook.js';
|
|
19
|
-
import { colors, success, error, warning, info, newline, sectionHeader } from '../../../ui.js';
|
|
20
|
-
// ── Skills management ─────────────────────────────────────────────────────────
|
|
21
|
-
const SKILLS_TARGET_DIR = join(homedir(), '.claude', 'commands', 'codehere');
|
|
22
|
-
function getSkillsSourceDir() {
|
|
23
|
-
// When running from dist/, the .claude/commands dir is at package root
|
|
24
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
-
const __dirname = dirname(__filename);
|
|
26
|
-
// dist/presentation/cli/commands → ../../../../.claude/commands
|
|
27
|
-
return join(__dirname, '..', '..', '..', '..', '.claude', 'commands');
|
|
28
|
-
}
|
|
29
|
-
function installSkills() {
|
|
30
|
-
const sourceDir = getSkillsSourceDir();
|
|
31
|
-
if (!existsSync(sourceDir))
|
|
32
|
-
return false;
|
|
33
|
-
if (!existsSync(SKILLS_TARGET_DIR)) {
|
|
34
|
-
mkdirSync(SKILLS_TARGET_DIR, { recursive: true });
|
|
35
|
-
}
|
|
36
|
-
const files = readdirSync(sourceDir).filter((f) => f.endsWith('.md'));
|
|
37
|
-
for (const file of files) {
|
|
38
|
-
copyFileSync(join(sourceDir, file), join(SKILLS_TARGET_DIR, file));
|
|
39
|
-
}
|
|
40
|
-
return files.length > 0;
|
|
41
|
-
}
|
|
42
|
-
function uninstallSkills() {
|
|
43
|
-
if (!existsSync(SKILLS_TARGET_DIR))
|
|
44
|
-
return;
|
|
45
|
-
try {
|
|
46
|
-
for (const file of readdirSync(SKILLS_TARGET_DIR)) {
|
|
47
|
-
unlinkSync(join(SKILLS_TARGET_DIR, file));
|
|
48
|
-
}
|
|
49
|
-
rmdirSync(SKILLS_TARGET_DIR);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
/* ignore */
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function skillsInstalled() {
|
|
56
|
-
return existsSync(SKILLS_TARGET_DIR) && readdirSync(SKILLS_TARGET_DIR).some((f) => f.endsWith('.md'));
|
|
57
|
-
}
|
|
58
|
-
const SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
59
|
-
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
60
|
-
function readSettings() {
|
|
61
|
-
if (!existsSync(SETTINGS_PATH))
|
|
62
|
-
return {};
|
|
63
|
-
try {
|
|
64
|
-
return JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return {};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function writeSettings(settings) {
|
|
71
|
-
const dir = join(homedir(), '.claude');
|
|
72
|
-
if (!existsSync(dir))
|
|
73
|
-
mkdirSync(dir, { recursive: true });
|
|
74
|
-
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
75
|
-
}
|
|
76
|
-
function isInstalled(settings) {
|
|
77
|
-
const hooks = (settings.hooks ?? {});
|
|
78
|
-
const preToolUse = (hooks.PreToolUse ?? []);
|
|
79
|
-
return preToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes('codehere scan-tool')));
|
|
80
|
-
}
|
|
81
|
-
// ── command ───────────────────────────────────────────────────────────────────
|
|
82
|
-
export async function executeHookCommand(subcommand, options = {}) {
|
|
83
|
-
const sub = subcommand ?? 'status';
|
|
84
|
-
switch (sub) {
|
|
85
|
-
case 'install': {
|
|
86
|
-
const settings = readSettings();
|
|
87
|
-
if (isInstalled(settings)) {
|
|
88
|
-
console.log(warning('codehere hooks already installed'));
|
|
89
|
-
console.log(colors.dim(` Config: ${SETTINGS_PATH}`));
|
|
90
|
-
console.log(colors.dim(' Run `codehere hook status` to see details'));
|
|
91
|
-
newline();
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const hookConfig = generateHookConfig({
|
|
95
|
-
logOnly: options.logOnly,
|
|
96
|
-
blockMode: options.blockMode,
|
|
97
|
-
});
|
|
98
|
-
// Merge with existing hooks config
|
|
99
|
-
const existingHooks = settings.hooks ?? {};
|
|
100
|
-
const mergedHooks = { ...existingHooks };
|
|
101
|
-
for (const [eventName, handlers] of Object.entries(hookConfig)) {
|
|
102
|
-
const existing = mergedHooks[eventName] ?? [];
|
|
103
|
-
mergedHooks[eventName] = [...existing, ...handlers];
|
|
104
|
-
}
|
|
105
|
-
settings.hooks = mergedHooks;
|
|
106
|
-
writeSettings(settings);
|
|
107
|
-
const skillsOk = installSkills();
|
|
108
|
-
console.log(success('codehere hooks installed'));
|
|
109
|
-
newline();
|
|
110
|
-
console.log(colors.bold('What happens now:'));
|
|
111
|
-
console.log(' Every Bash, Edit, Write, and MultiEdit call your AI makes is');
|
|
112
|
-
console.log(' scanned by codehere before it executes.');
|
|
113
|
-
console.log(' Zero API calls. < 5ms per scan. Runs entirely on your machine.');
|
|
114
|
-
newline();
|
|
115
|
-
console.log(colors.bold('Scan covers:'));
|
|
116
|
-
console.log(' • Prompt injection & jailbreak attempts');
|
|
117
|
-
console.log(' • Credential theft (API keys, SSH keys, env vars)');
|
|
118
|
-
console.log(' • Destructive operations (rm -rf, DB drops, force push)');
|
|
119
|
-
console.log(' • Code injection (curl|bash, reverse shells)');
|
|
120
|
-
console.log(' • Supply chain attacks (typosquatting packages)');
|
|
121
|
-
console.log(' • OWASP Top 10 patterns (SQLi, XSS, hardcoded secrets)');
|
|
122
|
-
newline();
|
|
123
|
-
console.log(colors.bold('Audit log:'));
|
|
124
|
-
console.log(colors.dim(` ${AUDIT_FILE}`));
|
|
125
|
-
newline();
|
|
126
|
-
if (options.logOnly) {
|
|
127
|
-
console.log(warning('Mode: log-only (no blocking). Run without --log-only to enable blocking.'));
|
|
128
|
-
newline();
|
|
129
|
-
}
|
|
130
|
-
console.log(colors.dim(' Config written to: ' + SETTINGS_PATH));
|
|
131
|
-
if (skillsOk) {
|
|
132
|
-
console.log(colors.dim(` Skills installed to: ${SKILLS_TARGET_DIR}`));
|
|
133
|
-
newline();
|
|
134
|
-
console.log(colors.bold('Slash commands now available in your AI assistant:'));
|
|
135
|
-
console.log(colors.dim(' /codehere:trust · scan current project'));
|
|
136
|
-
console.log(colors.dim(' /codehere:report · generate shareable audit report'));
|
|
137
|
-
console.log(colors.dim(' /codehere:audit · pre-merge security check'));
|
|
138
|
-
console.log(colors.dim(' /codehere:hook-status · check hook health'));
|
|
139
|
-
}
|
|
140
|
-
newline();
|
|
141
|
-
console.log(colors.dim(' Restart your AI assistant for changes to take effect.'));
|
|
142
|
-
newline();
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case 'uninstall': {
|
|
146
|
-
const settings = readSettings();
|
|
147
|
-
if (!isInstalled(settings)) {
|
|
148
|
-
console.log(info('codehere hooks are not installed'));
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const hooks = settings.hooks;
|
|
152
|
-
for (const [eventName, handlers] of Object.entries(hooks)) {
|
|
153
|
-
if (Array.isArray(handlers)) {
|
|
154
|
-
hooks[eventName] = handlers.filter((entry) => !entry.hooks?.some((h) => h.command?.includes('codehere scan-tool')));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
writeSettings(settings);
|
|
158
|
-
uninstallSkills();
|
|
159
|
-
console.log(success('codehere hooks and skills removed'));
|
|
160
|
-
console.log(colors.dim('Restart your AI assistant for changes to take effect.'));
|
|
161
|
-
newline();
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
case 'status': {
|
|
165
|
-
const settings = readSettings();
|
|
166
|
-
const installed = isInstalled(settings);
|
|
167
|
-
sectionHeader('codehere Hook Status');
|
|
168
|
-
newline();
|
|
169
|
-
if (installed) {
|
|
170
|
-
console.log(success('Running') + ' Intercepting every tool call before it executes.');
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
console.log(warning('Not installed') + ' Run `codehere hook install` to activate.');
|
|
174
|
-
}
|
|
175
|
-
newline();
|
|
176
|
-
const skills = skillsInstalled();
|
|
177
|
-
if (skills) {
|
|
178
|
-
console.log(info('Skills') + ' /codehere:trust /codehere:report /codehere:audit /codehere:hook-status');
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
console.log(colors.dim('Skills: not installed (run `codehere hook install` to add)'));
|
|
182
|
-
}
|
|
183
|
-
newline();
|
|
184
|
-
console.log(colors.dim('Config: ') + SETTINGS_PATH);
|
|
185
|
-
console.log(colors.dim('Audit log:') + ' ' + AUDIT_FILE);
|
|
186
|
-
newline();
|
|
187
|
-
// Autonomous mode callout
|
|
188
|
-
console.log(colors.bold('Autonomous mode (shift+tab)'));
|
|
189
|
-
console.log(colors.dim(' When Claude Code runs without approval prompts, this hook is your'));
|
|
190
|
-
console.log(colors.dim(' last line of defense. Every tool call is scanned before it executes.'));
|
|
191
|
-
console.log(colors.dim(' After a session: `codehere status --sessions` to review what happened.'));
|
|
192
|
-
newline();
|
|
193
|
-
// Show recent audit stats
|
|
194
|
-
if (existsSync(AUDIT_FILE)) {
|
|
195
|
-
const lines = readFileSync(AUDIT_FILE, 'utf-8').split('\n').filter(Boolean).slice(-1000); // last 1000 events
|
|
196
|
-
if (lines.length > 0) {
|
|
197
|
-
const entries = lines
|
|
198
|
-
.map((l) => {
|
|
199
|
-
try {
|
|
200
|
-
return JSON.parse(l);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
.filter(Boolean);
|
|
207
|
-
const total = entries.length;
|
|
208
|
-
const blocked = entries.filter((e) => e.blocked).length;
|
|
209
|
-
const critical = entries.filter((e) => (e.criticalFindings ?? 0) > 0).length;
|
|
210
|
-
console.log(colors.bold("What it's seen") + colors.dim(' (last 1000 events):'));
|
|
211
|
-
console.log(` ${colors.cyan(String(total))} tool calls scanned`);
|
|
212
|
-
console.log(` ${blocked > 0 ? colors.red(String(blocked)) : colors.green('none')} blocked`);
|
|
213
|
-
console.log(` ${critical > 0 ? colors.red(String(critical)) : colors.green('none')} with critical findings`);
|
|
214
|
-
newline();
|
|
215
|
-
if (blocked > 0) {
|
|
216
|
-
console.log(colors.dim(' Run `codehere status --sessions` to see which sessions had blocks.'));
|
|
217
|
-
newline();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
console.log(colors.dim(" No events logged yet · run Claude Code and it'll start tracking."));
|
|
223
|
-
newline();
|
|
224
|
-
}
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
case 'log': {
|
|
228
|
-
const tail = options.tail ?? 20;
|
|
229
|
-
if (!existsSync(AUDIT_FILE)) {
|
|
230
|
-
console.log(info('No audit log yet'));
|
|
231
|
-
console.log(colors.dim(' Run `codehere hook install` first'));
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const lines = readFileSync(AUDIT_FILE, 'utf-8').split('\n').filter(Boolean).slice(-tail);
|
|
235
|
-
if (lines.length === 0) {
|
|
236
|
-
console.log(info('Audit log is empty'));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
sectionHeader(`Hook Audit Log (last ${lines.length})`);
|
|
240
|
-
newline();
|
|
241
|
-
for (const line of lines) {
|
|
242
|
-
try {
|
|
243
|
-
const entry = JSON.parse(line);
|
|
244
|
-
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
245
|
-
const status = entry.blocked ? colors.red('[BLOCKED]') : colors.green('[ALLOW] ');
|
|
246
|
-
const risk = entry.riskScore > 0 ? colors.yellow(` risk:${entry.riskScore}`) : '';
|
|
247
|
-
const tool = colors.cyan(entry.toolName.padEnd(10));
|
|
248
|
-
const preview = entry.toolInput.substring(0, 60).replace(/\n/g, ' ');
|
|
249
|
-
console.log(` ${ts} ${status} ${tool}${risk} ${colors.dim(preview)}`);
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
// skip malformed line
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
newline();
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
default: {
|
|
259
|
-
console.log(error(`Unknown subcommand: ${sub}`));
|
|
260
|
-
console.log(colors.dim('Usage: codehere hook install|uninstall|status|log'));
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
//# sourceMappingURL=hook-command.js.map
|