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.
- package/README.md +125 -1
- package/dist/api/last-sync.d.ts +16 -0
- package/dist/api/last-sync.d.ts.map +1 -0
- package/dist/api/last-sync.js +73 -0
- package/dist/api/last-sync.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +85 -58
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze-claude-sessions.d.ts +3 -0
- package/dist/commands/analyze-claude-sessions.d.ts.map +1 -0
- package/dist/commands/analyze-claude-sessions.js +169 -0
- package/dist/commands/analyze-claude-sessions.js.map +1 -0
- package/dist/commands/capture-sessions.d.ts +3 -0
- package/dist/commands/capture-sessions.d.ts.map +1 -0
- package/dist/commands/capture-sessions.js +79 -0
- package/dist/commands/capture-sessions.js.map +1 -0
- package/dist/commands/claude.d.ts +14 -0
- package/dist/commands/claude.d.ts.map +1 -0
- package/dist/commands/claude.js +84 -0
- package/dist/commands/claude.js.map +1 -0
- package/dist/commands/wildcard.d.ts +13 -0
- package/dist/commands/wildcard.d.ts.map +1 -0
- package/dist/commands/wildcard.js +72 -0
- package/dist/commands/wildcard.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/log-request.d.ts +15 -0
- package/dist/log-request.d.ts.map +1 -0
- package/dist/log-request.js +51 -0
- package/dist/log-request.js.map +1 -0
- package/dist/sessions/capture-state.d.ts +37 -0
- package/dist/sessions/capture-state.d.ts.map +1 -0
- package/dist/sessions/capture-state.js +129 -0
- package/dist/sessions/capture-state.js.map +1 -0
- package/dist/sessions/claude-scanner.d.ts +57 -0
- package/dist/sessions/claude-scanner.d.ts.map +1 -0
- package/dist/sessions/claude-scanner.js +176 -0
- package/dist/sessions/claude-scanner.js.map +1 -0
- package/dist/types.d.ts +16 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- 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 @@
|
|
|
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":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/errors.js.map
CHANGED
|
@@ -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;
|
|
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"}
|