coolhand-cli 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +125 -1
  2. package/dist/api/last-sync.d.ts +16 -0
  3. package/dist/api/last-sync.d.ts.map +1 -0
  4. package/dist/api/last-sync.js +73 -0
  5. package/dist/api/last-sync.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +85 -58
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/analyze-claude-sessions.d.ts +3 -0
  10. package/dist/commands/analyze-claude-sessions.d.ts.map +1 -0
  11. package/dist/commands/analyze-claude-sessions.js +169 -0
  12. package/dist/commands/analyze-claude-sessions.js.map +1 -0
  13. package/dist/commands/capture-sessions.d.ts +3 -0
  14. package/dist/commands/capture-sessions.d.ts.map +1 -0
  15. package/dist/commands/capture-sessions.js +79 -0
  16. package/dist/commands/capture-sessions.js.map +1 -0
  17. package/dist/commands/claude.d.ts +14 -0
  18. package/dist/commands/claude.d.ts.map +1 -0
  19. package/dist/commands/claude.js +84 -0
  20. package/dist/commands/claude.js.map +1 -0
  21. package/dist/commands/wildcard.d.ts +13 -0
  22. package/dist/commands/wildcard.d.ts.map +1 -0
  23. package/dist/commands/wildcard.js +72 -0
  24. package/dist/commands/wildcard.js.map +1 -0
  25. package/dist/errors.d.ts +1 -1
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/errors.js.map +1 -1
  28. package/dist/log-request.d.ts +15 -0
  29. package/dist/log-request.d.ts.map +1 -0
  30. package/dist/log-request.js +51 -0
  31. package/dist/log-request.js.map +1 -0
  32. package/dist/sessions/capture-state.d.ts +37 -0
  33. package/dist/sessions/capture-state.d.ts.map +1 -0
  34. package/dist/sessions/capture-state.js +129 -0
  35. package/dist/sessions/capture-state.js.map +1 -0
  36. package/dist/sessions/claude-scanner.d.ts +57 -0
  37. package/dist/sessions/claude-scanner.d.ts.map +1 -0
  38. package/dist/sessions/claude-scanner.js +176 -0
  39. package/dist/sessions/claude-scanner.js.map +1 -0
  40. package/dist/types.d.ts +16 -12
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/version.d.ts +2 -2
  44. package/dist/version.js +1 -1
  45. package/package.json +11 -4
@@ -0,0 +1,169 @@
1
+ import { CliError, ExitCode } from '../errors.js';
2
+ import { logger, redact } from '../logger.js';
3
+ import { scanSessions, sessionIdOf } from '../sessions/claude-scanner.js';
4
+ import { loadCaptureState, getTurnsSubmitted, recordSubmission, saveCaptureState, V1_MIGRATION_SENTINEL, } from '../sessions/capture-state.js';
5
+ import { fetchLastSync } from '../api/last-sync.js';
6
+ import { loadConfig, getClient } from '../config.js';
7
+ import { logRequest } from '../log-request.js';
8
+ /** Errors that apply to every session (auth/config), so the run should abort, not keep retrying. */
9
+ const FATAL_CODES = new Set(['NOT_CONFIGURED', 'CLIENT_NOT_FOUND', 'INVALID_BASE_URL']);
10
+ /**
11
+ * Reference cutoff for the mtime pre-filter: local `lastSyncAt` → server `last_sync` → epoch.
12
+ *
13
+ * Local `lastSyncAt` is preferred: it is only advanced when a run completes with zero failures,
14
+ * so it accurately reflects the point past which all sessions have been submitted. Using server
15
+ * time instead would advance the cutoff past sessions that failed to submit on a prior run,
16
+ * causing the mtime filter to skip them permanently. Server time is also on a different clock
17
+ * from local file mtimes, so clock skew can silently exclude freshly-written files.
18
+ */
19
+ function resolveReferenceTime(serverTime, state) {
20
+ if (state.lastSyncAt) {
21
+ const local = new Date(state.lastSyncAt);
22
+ if (!Number.isNaN(local.getTime())) {
23
+ return local;
24
+ }
25
+ }
26
+ if (serverTime) {
27
+ return serverTime;
28
+ }
29
+ return new Date(0);
30
+ }
31
+ export async function run(opts) {
32
+ try {
33
+ // (1) Resolve the client and load local state up front — both feed the reference time and the
34
+ // per-session turn-count comparison.
35
+ const cfg = await loadConfig();
36
+ const stateClientId = getClient(cfg, opts.clientId)?.client_id ?? opts.clientId ?? '_default';
37
+ const state = await loadCaptureState();
38
+ // (2) Work out the reference timestamp. serverTime is only used when local state is absent or
39
+ // invalid (resolveReferenceTime prefers lastSyncAt), so skip the round-trip when unneeded.
40
+ const localSyncAt = state.lastSyncAt ? new Date(state.lastSyncAt) : null;
41
+ const needsServerTime = !localSyncAt || Number.isNaN(localSyncAt.getTime());
42
+ const serverTime = needsServerTime ? await fetchLastSync({ clientId: opts.clientId }) : null;
43
+ const referenceTime = resolveReferenceTime(serverTime, state);
44
+ // (3) Stamp the cutoff before scanning. Any transcript written after this point will have an
45
+ // mtime >= runStartedAt and will be picked up by the next run's filter — closing the window
46
+ // where a turn added during the submit loop (between scan and lastSyncAt stamp) would be
47
+ // silently skipped because its mtime fell before a later timestamp.
48
+ const runStartedAt = new Date();
49
+ const { envelopes, sessionCount } = await scanSessions({ sinceTime: referenceTime });
50
+ // (4) Classify each examined session as new / updated / unchanged by comparing its current turn
51
+ // count against what we last submitted. The turn-count guard — not the mtime filter — is what
52
+ // makes re-uploads correct.
53
+ const toSubmit = [];
54
+ let newCount = 0;
55
+ let updatedCount = 0;
56
+ let unchangedCount = 0;
57
+ for (const envelope of envelopes) {
58
+ const sessionId = sessionIdOf(envelope);
59
+ const already = getTurnsSubmitted(state, stateClientId, sessionId);
60
+ if (already === 0) {
61
+ newCount += 1;
62
+ toSubmit.push(envelope);
63
+ }
64
+ else if (already === V1_MIGRATION_SENTINEL) {
65
+ // Session was submitted under v1 but its turn count was not recorded. Record the actual
66
+ // count now so future runs can detect growth, without re-submitting (server already has it).
67
+ recordSubmission(state, stateClientId, sessionId, envelope.turnCount);
68
+ unchangedCount += 1;
69
+ }
70
+ else if (envelope.turnCount > already) {
71
+ updatedCount += 1;
72
+ toSubmit.push(envelope);
73
+ }
74
+ else {
75
+ unchangedCount += 1;
76
+ }
77
+ }
78
+ if (opts.dryRun) {
79
+ if (opts.json) {
80
+ logger.json({
81
+ ok: true,
82
+ dryRun: true,
83
+ scanned: sessionCount,
84
+ new: newCount,
85
+ updated: updatedCount,
86
+ unchanged: unchangedCount,
87
+ toSubmit: toSubmit.length,
88
+ });
89
+ }
90
+ else {
91
+ logger.info(`Dry run: ${newCount} new session(s), ${updatedCount} updated, ` +
92
+ `${unchangedCount} unchanged (${sessionCount} scanned). Nothing sent.`);
93
+ }
94
+ return ExitCode.OK;
95
+ }
96
+ // (5) Submit new + updated sessions, recording the full current turn count on success, then stamp
97
+ // lastSyncAt and persist — even if a fatal error aborts the run partway.
98
+ let submittedNew = 0;
99
+ let submittedUpdated = 0;
100
+ let failed = 0;
101
+ try {
102
+ for (const envelope of toSubmit) {
103
+ const sessionId = sessionIdOf(envelope);
104
+ const prior = getTurnsSubmitted(state, stateClientId, sessionId);
105
+ try {
106
+ await logRequest(envelope, { clientId: opts.clientId });
107
+ recordSubmission(state, stateClientId, sessionId, envelope.turnCount);
108
+ if (prior === 0) {
109
+ submittedNew += 1;
110
+ }
111
+ else {
112
+ submittedUpdated += 1;
113
+ }
114
+ }
115
+ catch (err) {
116
+ if (err instanceof CliError && FATAL_CODES.has(err.code)) {
117
+ throw err;
118
+ }
119
+ failed += 1;
120
+ logger.warn(`Failed to submit session: ${redact(err.message)}`);
121
+ }
122
+ }
123
+ // Advance the local cutoff ONLY when nothing failed. A failed-but-grown session keeps its
124
+ // older mtime; if we moved the cutoff past it, the next run's mtime filter would skip it and
125
+ // the growth would be lost. Use runStartedAt (captured before scanSessions) so any transcript
126
+ // written during the submit loop has mtime >= runStartedAt and is caught by the next run.
127
+ if (failed === 0) {
128
+ state.lastSyncAt = runStartedAt.toISOString();
129
+ }
130
+ }
131
+ finally {
132
+ try {
133
+ await saveCaptureState(state);
134
+ }
135
+ catch (err) {
136
+ logger.warn(`Failed to save capture state: ${redact(err.message)}`);
137
+ }
138
+ }
139
+ if (opts.json) {
140
+ logger.json({
141
+ ok: failed === 0,
142
+ scanned: sessionCount,
143
+ new: submittedNew,
144
+ updated: submittedUpdated,
145
+ unchanged: unchangedCount,
146
+ failed,
147
+ });
148
+ }
149
+ else {
150
+ const failureNote = failed > 0 ? `, ${failed} failed` : '';
151
+ logger.info(`Submitted ${submittedNew} new, ${submittedUpdated} updated, ` +
152
+ `${unchangedCount} unchanged (${sessionCount} scanned)${failureNote}.`);
153
+ }
154
+ return failed === 0 ? ExitCode.OK : ExitCode.USER_ERROR;
155
+ }
156
+ catch (err) {
157
+ if (err instanceof CliError) {
158
+ if (opts.json) {
159
+ logger.json({ ok: false, error: err.code, message: redact(err.message) });
160
+ }
161
+ else {
162
+ logger.info(`Error: ${redact(err.message)} [${err.code}]`);
163
+ }
164
+ return err.exitCode;
165
+ }
166
+ throw err;
167
+ }
168
+ }
169
+ //# sourceMappingURL=analyze-claude-sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-claude-sessions.js","sourceRoot":"","sources":["../../src/commands/analyze-claude-sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CaptureSessionsOptions } from '../types.js';
2
+ export declare function run(opts: CaptureSessionsOptions): Promise<number>;
3
+ //# sourceMappingURL=capture-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-sessions.d.ts","sourceRoot":"","sources":["../../src/commands/capture-sessions.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAK1D,wBAAsB,GAAG,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEvE"}
@@ -0,0 +1,79 @@
1
+ import { CliError, ExitCode } from '../errors.js';
2
+ import { logger, redact } from '../logger.js';
3
+ import { scanSessions, sessionIdOf } from '../sessions/claude-scanner.js';
4
+ import { loadCaptureState, isSubmitted, markSubmitted, saveCaptureState, } from '../sessions/capture-state.js';
5
+ import { loadConfig, getClient } from '../config.js';
6
+ import { logRequest } from '../log-request.js';
7
+ /** Errors that apply to every session (auth/config), so the run should abort, not keep retrying. */
8
+ const FATAL_CODES = new Set(['NOT_CONFIGURED', 'CLIENT_NOT_FOUND', 'INVALID_BASE_URL']);
9
+ export async function run(opts) {
10
+ try {
11
+ const { envelopes, sessionCount } = await scanSessions();
12
+ // Scope the "already submitted" list to the client we're submitting to, so the same sessions
13
+ // can be sent to a different client if needed.
14
+ const cfg = await loadConfig();
15
+ const stateClientId = getClient(cfg, opts.clientId)?.client_id ?? opts.clientId ?? '_default';
16
+ const state = await loadCaptureState();
17
+ const pending = envelopes.filter((e) => !isSubmitted(state, stateClientId, sessionIdOf(e)));
18
+ const skipped = envelopes.length - pending.length;
19
+ if (opts.dryRun) {
20
+ if (opts.json) {
21
+ logger.json({ ok: true, dryRun: true, sessions: sessionCount, toSubmit: pending.length, skipped });
22
+ }
23
+ else {
24
+ logger.info(`Dry run: found ${sessionCount} session(s), ${pending.length} new to submit, ` +
25
+ `${skipped} already submitted. Nothing sent.`);
26
+ }
27
+ return ExitCode.OK;
28
+ }
29
+ let submitted = 0;
30
+ let failed = 0;
31
+ try {
32
+ for (const envelope of pending) {
33
+ try {
34
+ await logRequest(envelope, { clientId: opts.clientId });
35
+ markSubmitted(state, stateClientId, sessionIdOf(envelope));
36
+ submitted += 1;
37
+ }
38
+ catch (err) {
39
+ if (err instanceof CliError && FATAL_CODES.has(err.code)) {
40
+ throw err;
41
+ }
42
+ failed += 1;
43
+ logger.warn(`Failed to submit session: ${redact(err.message)}`);
44
+ }
45
+ }
46
+ }
47
+ finally {
48
+ // Persist whatever we recorded, even if a fatal error aborts the run partway.
49
+ try {
50
+ await saveCaptureState(state);
51
+ }
52
+ catch (err) {
53
+ logger.warn(`Failed to save capture state: ${redact(err.message)}`);
54
+ }
55
+ }
56
+ if (opts.json) {
57
+ logger.json({ ok: failed === 0, sessions: sessionCount, submitted, skipped, failed });
58
+ }
59
+ else {
60
+ const failureNote = failed > 0 ? `, ${failed} failed` : '';
61
+ const skippedNote = skipped > 0 ? `, ${skipped} already submitted` : '';
62
+ logger.info(`Submitted ${submitted} session(s) of ${sessionCount} scanned${skippedNote}${failureNote}.`);
63
+ }
64
+ return failed === 0 ? ExitCode.OK : ExitCode.USER_ERROR;
65
+ }
66
+ catch (err) {
67
+ if (err instanceof CliError) {
68
+ if (opts.json) {
69
+ logger.json({ ok: false, error: err.code, message: redact(err.message) });
70
+ }
71
+ else {
72
+ logger.info(`Error: ${redact(err.message)} [${err.code}]`);
73
+ }
74
+ return err.exitCode;
75
+ }
76
+ throw err;
77
+ }
78
+ }
79
+ //# sourceMappingURL=capture-sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-sessions.js","sourceRoot":"","sources":["../../src/commands/capture-sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/C,oGAAoG;AACpG,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAExF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAA4B;IACpD,IAAI,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,YAAY,EAAE,CAAC;QAEzD,6FAA6F;QAC7F,+CAA+C;QAC/C,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;QAC/B,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,UAAU,CAAC;QAC9F,MAAM,KAAK,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAEvC,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5F,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAElD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;YACrG,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CACT,kBAAkB,YAAY,gBAAgB,OAAO,CAAC,MAAM,kBAAkB;oBAC5E,GAAG,OAAO,mCAAmC,CAChD,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,EAAE,CAAC;QACrB,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,CAAC;YACH,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,MAAM,UAAU,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACxD,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;oBAC3D,SAAS,IAAI,CAAC,CAAC;gBACjB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,GAAG,YAAY,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;wBACzD,MAAM,GAAG,CAAC;oBACZ,CAAC;oBACD,MAAM,IAAI,CAAC,CAAC;oBACZ,MAAM,CAAC,IAAI,CAAC,6BAA6B,MAAM,CAAE,GAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC7E,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,8EAA8E;YAC9E,IAAI,CAAC;gBACH,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,iCAAiC,MAAM,CAAE,GAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACjF,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,KAAK,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACxF,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,MAAM,WAAW,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,IAAI,CACT,aAAa,SAAS,kBAAkB,YAAY,WAAW,WAAW,GAAG,WAAW,GAAG,CAC5F,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,GAAG,CAAC,QAAQ,CAAC;QACtB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { spawn } from 'child_process';
2
+ import { type ClaudeOptions } from '../types.js';
3
+ interface ClaudeDeps {
4
+ spawnFn?: typeof spawn;
5
+ resolveProxyCli?: () => string;
6
+ }
7
+ /**
8
+ * `coolhand claude [args...]` — run the Claude CLI behind the Coolhand proxy with
9
+ * the stored API key filled in. Shells out to `coolhand-proxy wrap -- claude ...`
10
+ * so the proxy's battle-tested wrap behavior is reused, not duplicated.
11
+ */
12
+ export declare function run(opts: ClaudeOptions, deps?: ClaudeDeps): Promise<number>;
13
+ export {};
14
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/commands/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAMtC,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,UAAU,UAAU;IAClB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAEvB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;CAChC;AA8BD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CrF"}
@@ -0,0 +1,84 @@
1
+ import { spawn } from 'child_process';
2
+ import { createRequire } from 'module';
3
+ import { realpathSync } from 'fs';
4
+ import { ExitCode, CliError } from '../errors.js';
5
+ import { logger, redact } from '../logger.js';
6
+ import { loadConfig, getClient } from '../config.js';
7
+ import { DEFAULT_BASE_URL } from '../types.js';
8
+ /**
9
+ * Locate the coolhand-proxy CLI (`dist/cli.js`, which hosts the `wrap` command).
10
+ * coolhand-proxy ships dist/ and declares no `exports` restriction, so deep
11
+ * subpath resolution works. We resolve a file path and later run it with `node`
12
+ * rather than via the `.bin` shim, which avoids the Windows `.bin` ENOENT gotcha.
13
+ */
14
+ function defaultResolveProxyCli() {
15
+ // Anchor resolution at the real path of the running CLI entry (dist/bin.js),
16
+ // following any global-install symlink, so we resolve coolhand-proxy from this
17
+ // package's node_modules. Avoids `import.meta` (keeps the ts-jest build happy).
18
+ const anchor = realpathSync(process.argv[1] ?? process.cwd());
19
+ const require = createRequire(anchor);
20
+ return require.resolve('coolhand-proxy/dist/cli.js');
21
+ }
22
+ /**
23
+ * Build the full ingest endpoint the proxy should post to for a given client.
24
+ * The proxy's `--api-endpoint` wants the full path; the CLI stores only the
25
+ * site base_url, so append the ingest path. Returns undefined for the default
26
+ * base_url (the proxy already targets the right endpoint).
27
+ */
28
+ function endpointForBaseUrl(baseUrl) {
29
+ if (!baseUrl || baseUrl === DEFAULT_BASE_URL) {
30
+ return undefined;
31
+ }
32
+ return `${baseUrl.replace(/\/+$/, '')}/api/v2/llm_request_logs`;
33
+ }
34
+ /**
35
+ * `coolhand claude [args...]` — run the Claude CLI behind the Coolhand proxy with
36
+ * the stored API key filled in. Shells out to `coolhand-proxy wrap -- claude ...`
37
+ * so the proxy's battle-tested wrap behavior is reused, not duplicated.
38
+ */
39
+ export async function run(opts, deps = {}) {
40
+ const spawnFn = deps.spawnFn ?? spawn;
41
+ const resolveProxyCli = deps.resolveProxyCli ?? defaultResolveProxyCli;
42
+ try {
43
+ const cfg = await loadConfig();
44
+ const entry = getClient(cfg, opts.clientId);
45
+ if (!entry) {
46
+ throw new CliError('NOT_CONFIGURED', 'No Coolhand account configured. Run `coolhand login` first.');
47
+ }
48
+ let proxyCli;
49
+ try {
50
+ proxyCli = resolveProxyCli();
51
+ }
52
+ catch {
53
+ logger.info('Error: could not locate the coolhand-proxy dependency. Try reinstalling coolhand-cli.');
54
+ return ExitCode.INTERNAL;
55
+ }
56
+ const wrapArgs = ['wrap', '--silent'];
57
+ const endpoint = endpointForBaseUrl(entry.base_url);
58
+ if (endpoint) {
59
+ wrapArgs.push('--api-endpoint', endpoint);
60
+ }
61
+ wrapArgs.push('--', 'claude', ...opts.args);
62
+ return await new Promise((resolve) => {
63
+ const child = spawnFn(process.execPath, [proxyCli, ...wrapArgs], {
64
+ stdio: 'inherit',
65
+ env: { ...process.env, COOLHAND_API_KEY: entry.api_key },
66
+ });
67
+ child.on('error', (err) => {
68
+ logger.info(`Error: failed to start coolhand-proxy: ${redact(err.message)}`);
69
+ resolve(ExitCode.INTERNAL);
70
+ });
71
+ child.on('close', (code) => {
72
+ resolve(code ?? ExitCode.INTERNAL);
73
+ });
74
+ });
75
+ }
76
+ catch (err) {
77
+ if (err instanceof CliError) {
78
+ logger.info(`Error: ${redact(err.message)} [${err.code}]`);
79
+ return err.exitCode;
80
+ }
81
+ throw err;
82
+ }
83
+ }
84
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/commands/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAsB,MAAM,aAAa,CAAC;AAQnE;;;;;GAKG;AACH,SAAS,sBAAsB;IAC7B,6EAA6E;IAC7E,+EAA+E;IAC/E,gFAAgF;IAChF,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,gBAAgB,EAAE,CAAC;QAC7C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,0BAA0B,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAmB,EAAE,OAAmB,EAAE;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACtC,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,sBAAsB,CAAC;IAEvE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,QAAQ,CAAC,gBAAgB,EAAE,6DAA6D,CAAC,CAAC;QACtG,CAAC;QAED,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,eAAe,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,uFAAuF,CAAC,CAAC;YACrG,OAAO,QAAQ,CAAC,QAAQ,CAAC;QAC3B,CAAC;QAED,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QAC5C,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5C,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE;gBAC/D,KAAK,EAAE,SAAS;gBAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,CAAC,OAAO,EAAE;aACzD,CAAC,CAAC;YACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;gBAC/B,MAAM,CAAC,IAAI,CAAC,0CAA0C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC7E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;gBACxC,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;YAC3D,OAAO,GAAG,CAAC,QAAQ,CAAC;QACtB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type ComplaintBoxOptions } from '../types.js';
2
+ /**
3
+ * Records an agent's free-form "I am blocked, this capability does not exist"
4
+ * complaint as feedback (tagged creator_type: "agent"), then prints a terminal
5
+ * de-loop message and exits 0 so the agent stops and moves on. The de-loop ALWAYS
6
+ * fires — even when the feedback could not be recorded — because the missing
7
+ * capability is real regardless of server state (and a logged-out agent in a
8
+ * sandbox is exactly who this command is for). When recording fails it says so
9
+ * plainly and logs a warning, so the failure still surfaces without trapping the
10
+ * agent in the retry loop the command exists to break.
11
+ */
12
+ export declare function run(opts: ComplaintBoxOptions): Promise<number>;
13
+ //# sourceMappingURL=wildcard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wildcard.d.ts","sourceRoot":"","sources":["../../src/commands/wildcard.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAoBzE;;;;;;;;;GASG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2CpE"}
@@ -0,0 +1,72 @@
1
+ import { Coolhand } from 'coolhand-node';
2
+ import { ExitCode } from '../errors.js';
3
+ import { logger, redact } from '../logger.js';
4
+ import { loadConfig, getClient } from '../config.js';
5
+ import { DEFAULT_BASE_URL } from '../types.js';
6
+ // Terminal guidance that breaks the agent out of its retry loop. The missing
7
+ // capability is real regardless of whether we could reach the Coolhand server, so
8
+ // the de-loop is the agent's decision to make, not the server's.
9
+ const STOP_GUIDANCE = 'Do not retry this action or attempt alternative ways to perform it; continue with the rest of your task.';
10
+ // Printed when the blocker was successfully recorded server-side.
11
+ const DELOOP_RECORDED = 'This capability is not available in this environment. Your feedback has been recorded. ' + STOP_GUIDANCE;
12
+ // Printed when the blocker could NOT be recorded (e.g. not logged in, or the server
13
+ // did not confirm). We still de-loop the agent, but stay honest: we never claim a
14
+ // recording happened, and a warning is logged separately so the failure surfaces.
15
+ const DELOOP_UNRECORDED = 'This capability is not available in this environment. Your feedback could not be recorded ' +
16
+ '(not logged in, or the server did not confirm — run `coolhand login` to enable reporting). ' +
17
+ STOP_GUIDANCE;
18
+ /**
19
+ * Records an agent's free-form "I am blocked, this capability does not exist"
20
+ * complaint as feedback (tagged creator_type: "agent"), then prints a terminal
21
+ * de-loop message and exits 0 so the agent stops and moves on. The de-loop ALWAYS
22
+ * fires — even when the feedback could not be recorded — because the missing
23
+ * capability is real regardless of server state (and a logged-out agent in a
24
+ * sandbox is exactly who this command is for). When recording fails it says so
25
+ * plainly and logs a warning, so the failure still surfaces without trapping the
26
+ * agent in the retry loop the command exists to break.
27
+ */
28
+ export async function run(opts) {
29
+ let recorded = false;
30
+ try {
31
+ const cfg = await loadConfig();
32
+ const client = getClient(cfg, opts.clientId);
33
+ const apiKey = client?.api_key ?? process.env.COOLHAND_API_KEY;
34
+ const baseUrl = client?.base_url ?? DEFAULT_BASE_URL;
35
+ if (!apiKey) {
36
+ logger.warn('Not logged in; blocker feedback was not recorded. Run `coolhand login` to enable reporting.');
37
+ }
38
+ else {
39
+ const coolhand = new Coolhand({ apiKey, baseUrl, silent: true });
40
+ // The SDK resolves to null (it does not throw) when the write fails, so a
41
+ // truthy response is the only proof the blocker was actually recorded.
42
+ const feedback = {
43
+ explanation: opts.complaint,
44
+ creator_unique_id: opts.agentName,
45
+ creator_type: 'agent',
46
+ ...(opts.thinking ? { original_output: opts.thinking } : {}),
47
+ ...(opts.logId !== undefined ? { llm_request_log_id: opts.logId } : {}),
48
+ };
49
+ const result = await coolhand.createFeedback(feedback);
50
+ recorded = result !== null && result !== undefined;
51
+ if (!recorded) {
52
+ logger.warn('Blocker feedback was not recorded: the server did not confirm the write.');
53
+ }
54
+ }
55
+ }
56
+ catch (err) {
57
+ logger.warn(`Could not record blocker feedback: ${redact(err.message)}`);
58
+ }
59
+ // Always de-loop: the capability genuinely does not exist, so the agent must stop
60
+ // whether or not we could notify the server. Stay honest about the recording status
61
+ // (warnings above already surfaced any failure) rather than gating the stop-signal
62
+ // on a confirmed write and leaving a logged-out agent stuck.
63
+ const message = recorded ? DELOOP_RECORDED : DELOOP_UNRECORDED;
64
+ if (opts.json === true) {
65
+ logger.json({ ok: true, recorded, message });
66
+ }
67
+ else {
68
+ logger.info(message);
69
+ }
70
+ return ExitCode.OK;
71
+ }
72
+ //# sourceMappingURL=wildcard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wildcard.js","sourceRoot":"","sources":["../../src/commands/wildcard.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -4,7 +4,7 @@ export declare enum ExitCode {
4
4
  INTERNAL = 2,
5
5
  ABORTED = 130
6
6
  }
7
- export type ErrorCode = 'TIMEOUT' | 'STATE_MISMATCH' | 'INVALID_CALLBACK' | 'BROWSER_FAILED' | 'CONFIG_WRITE_FAILED' | 'CONFIG_READ_FAILED' | 'NOT_CONFIGURED' | 'INVALID_REDIRECT_URI' | 'INVALID_BASE_URL' | 'INVALID_ARGS' | 'WRITE_ENV_FAILED' | 'CLIENT_NOT_FOUND' | 'NO_PRIVATE_KEY' | 'MCP_ERROR';
7
+ export type ErrorCode = 'TIMEOUT' | 'STATE_MISMATCH' | 'INVALID_CALLBACK' | 'BROWSER_FAILED' | 'CONFIG_WRITE_FAILED' | 'CONFIG_READ_FAILED' | 'NOT_CONFIGURED' | 'INVALID_REDIRECT_URI' | 'INVALID_BASE_URL' | 'INVALID_ARGS' | 'WRITE_ENV_FAILED' | 'CLIENT_NOT_FOUND' | 'NO_PRIVATE_KEY' | 'MCP_ERROR' | 'INGEST_ERROR';
8
8
  export declare class CliError extends Error {
9
9
  readonly code: ErrorCode;
10
10
  readonly exitCode: number;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,EAAE,IAAI;IACN,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,OAAO,MAAM;CACd;AAED,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,gBAAgB,GAChB,kBAAkB,GAClB,gBAAgB,GAChB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,sBAAsB,GACtB,kBAAkB,GAClB,cAAc,GACd,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,GAChB,WAAW,CAAC;AAEhB,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,IAAI,EAAE,SAAS,CAAC;IAChC,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA4B;CAMrF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,EAAE,IAAI;IACN,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,OAAO,MAAM;CACd;AAED,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,gBAAgB,GAChB,kBAAkB,GAClB,gBAAgB,GAChB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,sBAAsB,GACtB,kBAAkB,GAClB,cAAc,GACd,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,GAChB,WAAW,GACX,cAAc,CAAC;AAEnB,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,IAAI,EAAE,SAAS,CAAC;IAChC,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA4B;CAMrF"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,QAKX;AALD,WAAY,QAAQ;IAClB,mCAAM,CAAA;IACN,mDAAc,CAAA;IACd,+CAAY,CAAA;IACZ,+CAAa,CAAA;AACf,CAAC,EALW,QAAQ,KAAR,QAAQ,QAKnB;AAkBD,MAAM,OAAO,QAAS,SAAQ,KAAK;IAIjC,YAAY,IAAe,EAAE,OAAe,EAAE,WAAmB,QAAQ,CAAC,UAAU;QAClF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,QAKX;AALD,WAAY,QAAQ;IAClB,mCAAM,CAAA;IACN,mDAAc,CAAA;IACd,+CAAY,CAAA;IACZ,+CAAa,CAAA;AACf,CAAC,EALW,QAAQ,KAAR,QAAQ,QAKnB;AAmBD,MAAM,OAAO,QAAS,SAAQ,KAAK;IAIjC,YAAY,IAAe,EAAE,OAAe,EAAE,WAAmB,QAAQ,CAAC,UAAU;QAClF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Submit one captured request/response envelope to the Coolhand ingest endpoint.
3
+ *
4
+ * Transport is delegated to coolhand-node's `Coolhand#logRequest`, which posts to the same
5
+ * `/api/v2/llm_request_logs` endpoint with the client's public `api_key`. The server
6
+ * deduplicates by the envelope's `response_body.id`, so re-submitting the same turn is harmless.
7
+ *
8
+ * The SDK swallows submission failures and returns `null`; we translate that (and a base_url
9
+ * rejection from the SDK constructor) back into a `CliError` so `analyze-claude-sessions` keeps its
10
+ * per-session success/failure accounting intact.
11
+ */
12
+ export declare function logRequest(rawRequest: unknown, opts?: {
13
+ clientId?: string;
14
+ }): Promise<unknown>;
15
+ //# sourceMappingURL=log-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-request.d.ts","sourceRoot":"","sources":["../src/log-request.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,OAAO,EACnB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,OAAO,CAAC,CAwClB"}
@@ -0,0 +1,51 @@
1
+ import { Coolhand } from 'coolhand-node';
2
+ import { CliError } from './errors.js';
3
+ import { loadConfig, getClient } from './config.js';
4
+ import { DEFAULT_BASE_URL } from './types.js';
5
+ /** Identifies submissions from this tool in the server's collector field. */
6
+ const COLLECTOR = 'coolhand-cli/claude-code';
7
+ /**
8
+ * Submit one captured request/response envelope to the Coolhand ingest endpoint.
9
+ *
10
+ * Transport is delegated to coolhand-node's `Coolhand#logRequest`, which posts to the same
11
+ * `/api/v2/llm_request_logs` endpoint with the client's public `api_key`. The server
12
+ * deduplicates by the envelope's `response_body.id`, so re-submitting the same turn is harmless.
13
+ *
14
+ * The SDK swallows submission failures and returns `null`; we translate that (and a base_url
15
+ * rejection from the SDK constructor) back into a `CliError` so `analyze-claude-sessions` keeps its
16
+ * per-session success/failure accounting intact.
17
+ */
18
+ export async function logRequest(rawRequest, opts = {}) {
19
+ const cfg = await loadConfig();
20
+ const client = getClient(cfg, opts.clientId);
21
+ if (opts.clientId && !client) {
22
+ throw new CliError('CLIENT_NOT_FOUND', `No client "${opts.clientId}" is configured.`);
23
+ }
24
+ const apiKey = client?.api_key ?? process.env.COOLHAND_API_KEY;
25
+ if (!apiKey) {
26
+ throw new CliError('NOT_CONFIGURED', 'Not logged in. Run `coolhand login` to authenticate.');
27
+ }
28
+ const baseUrl = client?.base_url ?? DEFAULT_BASE_URL;
29
+ let coolhand;
30
+ try {
31
+ // The SDK validates baseUrl in its constructor (https required; http only for localhost).
32
+ coolhand = new Coolhand({ apiKey, baseUrl, silent: true });
33
+ }
34
+ catch (err) {
35
+ throw new CliError('INVALID_BASE_URL', `Invalid base_url for client: ${baseUrl} (${err.message})`);
36
+ }
37
+ let result;
38
+ try {
39
+ result = await coolhand.logRequest(rawRequest, { collector: COLLECTOR });
40
+ }
41
+ catch (err) {
42
+ throw new CliError('INGEST_ERROR', `Log submission failed: ${err.message}`);
43
+ }
44
+ // The SDK returns null when the POST fails (it reports the error itself). On Node >= 20 (our
45
+ // engine floor) fetch is always defined, so a null result here unambiguously means failure.
46
+ if (result === null) {
47
+ throw new CliError('INGEST_ERROR', 'Log submission failed.');
48
+ }
49
+ return result;
50
+ }
51
+ //# sourceMappingURL=log-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-request.js","sourceRoot":"","sources":["../src/log-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAyB,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,SAAS,GAAG,0BAA0B,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAmB,EACnB,OAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,cAAc,IAAI,CAAC,QAAQ,kBAAkB,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC/D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,QAAQ,CAChB,gBAAgB,EAChB,sDAAsD,CACvD,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,QAAQ,IAAI,gBAAgB,CAAC;IAErD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,0FAA0F;QAC1F,QAAQ,GAAG,IAAI,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,gCAAgC,OAAO,KAAM,GAAa,CAAC,OAAO,GAAG,CAAC,CAAC;IAChH,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,UAA8B,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,0BAA2B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,6FAA6F;IAC7F,4FAA4F;IAC5F,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,wBAAwB,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Local record of how much of each Claude Code session has already been submitted, so re-running
3
+ * `analyze-claude-sessions` does not resend a session unchanged — but DOES resend one whose transcript has
4
+ * grown with new turns since last time.
5
+ *
6
+ * The server cannot deduplicate these logs itself (its dedup runs before a log is classified, and
7
+ * matched logs are never re-checked), so the tool keeps this record and decides what to (re)send.
8
+ * Keyed per client id, since the same session may legitimately be submitted to more than one client.
9
+ *
10
+ * We store a turn COUNT per session (not a yes/no flag): a chat transcript keeps growing, so a
11
+ * boolean "already submitted?" silently drops later turns. Comparing the current turn count against
12
+ * `turnsSubmitted` is what lets a grown session be re-submitted.
13
+ */
14
+ export interface SubmittedSession {
15
+ /** Number of assistant turns submitted the last time this session was sent. */
16
+ turnsSubmitted: number;
17
+ }
18
+ export interface CaptureState {
19
+ version: number;
20
+ /** ISO timestamp of the last sync; used as a cheap mtime cutoff to skip unchanged files. */
21
+ lastSyncAt?: string;
22
+ submitted: Record<string, Record<string, SubmittedSession>>;
23
+ }
24
+ export declare function captureStatePath(): string;
25
+ /**
26
+ * Sentinel stored for sessions migrated from v1 (where only a boolean "submitted?" was tracked).
27
+ * `run()` treats this as "already submitted, turn count unknown" — it records the actual count
28
+ * without re-submitting, so subsequent runs can detect growth correctly.
29
+ */
30
+ export declare const V1_MIGRATION_SENTINEL = -1;
31
+ export declare function loadCaptureState(): Promise<CaptureState>;
32
+ /** Turns already submitted for this session/client, or 0 if it has never been submitted. */
33
+ export declare function getTurnsSubmitted(state: CaptureState, clientId: string, sessionId: string): number;
34
+ /** Record that `turns` turns of this session have now been submitted for this client. */
35
+ export declare function recordSubmission(state: CaptureState, clientId: string, sessionId: string, turns: number): void;
36
+ export declare function saveCaptureState(state: CaptureState): Promise<void>;
37
+ //# sourceMappingURL=capture-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-state.d.ts","sourceRoot":"","sources":["../../src/sessions/capture-state.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAC7D;AAQD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAMD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AA6CxC,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,CAAC,CAwB9D;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAElG;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,IAAI,CAGN;AAWD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBzE"}