iteration-cli 0.7.3 → 0.8.1
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 +6 -2
- package/dist/auto-connect.d.ts +1 -0
- package/dist/auto-connect.js +18 -4
- package/dist/clerk-device-auth.js +1 -1
- package/dist/cli.js +63 -33
- package/dist/collector.js +32 -7
- package/dist/cursor-hook-entry.d.ts +1 -0
- package/dist/cursor-hook-entry.js +157 -0
- package/dist/cursor-hooks.d.ts +7 -0
- package/dist/cursor-hooks.js +163 -0
- package/dist/cursor-transcript.d.ts +8 -0
- package/dist/cursor-transcript.js +294 -0
- package/dist/redact-trace.d.ts +2 -0
- package/dist/redact-trace.js +182 -0
- package/dist/service.js +9 -3
- package/dist/status-view.d.ts +1 -0
- package/dist/status-view.js +6 -0
- package/dist/storage.d.ts +7 -0
- package/dist/storage.js +125 -3
- package/dist/turn-summary.js +3 -1
- package/package.json +4 -2
package/dist/status-view.d.ts
CHANGED
package/dist/status-view.js
CHANGED
|
@@ -149,6 +149,10 @@ export function renderHome(status, nowMs = Date.now(), source = "codex") {
|
|
|
149
149
|
lines.push(`${pendingDescription(status)}.`);
|
|
150
150
|
lines.push(`Resume with iteration start ${source}.`);
|
|
151
151
|
}
|
|
152
|
+
else if (status.hookFailureAtMs !== null &&
|
|
153
|
+
status.hookFailureAtMs !== undefined) {
|
|
154
|
+
lines.push(`${name} hook capture needs attention`, `A Cursor hook failed ${relativeTime(status.hookFailureAtMs, nowMs)}. Some data may be missing. See iteration status cursor.`);
|
|
155
|
+
}
|
|
152
156
|
else if (!status.healthAvailable) {
|
|
153
157
|
lines.push(`${name} is running; local status needs attention`, "Upload history could not be read. Collection continues in the background.");
|
|
154
158
|
if (status.pendingEvents > 0)
|
|
@@ -242,6 +246,8 @@ export function renderDetailedStatus(status, nowMs = Date.now(), source = "codex
|
|
|
242
246
|
: "Local collector error";
|
|
243
247
|
lines.push(`Last issue: ${label} (${relativeTime(failure.atMs, nowMs)})`, `Collector log: ${status.logPath}`);
|
|
244
248
|
}
|
|
249
|
+
if (status.hookFailureAtMs !== null && status.hookFailureAtMs !== undefined)
|
|
250
|
+
lines.push(`Last Cursor hook capture failure: ${relativeTime(status.hookFailureAtMs, nowMs)}. Some data may be missing; inspect Cursor hook logs.`);
|
|
245
251
|
if (status.turnSummaryNeedsApproval)
|
|
246
252
|
lines.push(CODEX_APPROVAL_STEP);
|
|
247
253
|
if (!status.running)
|
package/dist/storage.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export type CollectorConfig = (CollectorConnection & Readonly<{
|
|
|
11
11
|
}>) | (CollectorConnection & Readonly<{
|
|
12
12
|
source: "claude";
|
|
13
13
|
claudeHome: string;
|
|
14
|
+
}>) | (CollectorConnection & Readonly<{
|
|
15
|
+
source: "cursor";
|
|
16
|
+
cursorHome: string;
|
|
14
17
|
}>);
|
|
15
18
|
export declare function collectorSource(config: CollectorConfig): CollectorSource;
|
|
16
19
|
export declare function collectorSourceDirectory(config: CollectorConfig): string;
|
|
@@ -41,6 +44,7 @@ export type CollectorPaths = Readonly<{
|
|
|
41
44
|
config: string;
|
|
42
45
|
state: string;
|
|
43
46
|
outbox: string;
|
|
47
|
+
redactionMarker: string;
|
|
44
48
|
lock: string;
|
|
45
49
|
historyBackfill: string;
|
|
46
50
|
health: string;
|
|
@@ -60,6 +64,7 @@ export declare function saveState(state: CollectorState, paths: CollectorPaths):
|
|
|
60
64
|
export declare function outboxSize(paths: CollectorPaths): Promise<number>;
|
|
61
65
|
export declare function pendingEventIds(paths: CollectorPaths): Promise<Set<string>>;
|
|
62
66
|
export declare function appendEvents(events: readonly TraceEvent[], paths: CollectorPaths): Promise<void>;
|
|
67
|
+
export declare function redactPendingOutbox(paths: CollectorPaths): Promise<void>;
|
|
63
68
|
export declare function nextBatch(paths: CollectorPaths, maxBytes: number): Promise<readonly TraceEvent[]>;
|
|
64
69
|
export declare function acknowledgeEvents(acknowledgedIds: ReadonlySet<string>, paths: CollectorPaths): Promise<void>;
|
|
65
70
|
export declare function initializeState(source: CollectorSource, home: string, paths: CollectorPaths, includeHistory: boolean): Promise<void>;
|
|
@@ -67,6 +72,7 @@ export declare function prepareSourceInitialState(source: CollectorSource, home:
|
|
|
67
72
|
export declare function ensureStartableState(config: CollectorConfig, paths: CollectorPaths): Promise<boolean>;
|
|
68
73
|
export declare function ensureHistoryBackfill(codexHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
69
74
|
export declare function ensureClaudeHistoryBackfill(claudeHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
75
|
+
export declare function ensureCursorHistoryBackfill(cursorHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
70
76
|
export declare function historyScanProgress(home: string, paths: CollectorPaths, source?: CollectorSource): Promise<Readonly<{
|
|
71
77
|
kind: "waiting";
|
|
72
78
|
} | {
|
|
@@ -84,4 +90,5 @@ export declare function historyImportSize(home: string, source?: CollectorSource
|
|
|
84
90
|
}>>;
|
|
85
91
|
export declare function claudeFiles(root: string): Promise<string[]>;
|
|
86
92
|
export declare function sourceFiles(source: CollectorSource, home: string): Promise<string[]>;
|
|
93
|
+
export declare function cursorFiles(root: string): Promise<string[]>;
|
|
87
94
|
export {};
|
package/dist/storage.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { once } from "node:events";
|
|
2
|
-
import { createWriteStream, promises as fs } from "node:fs";
|
|
2
|
+
import { createReadStream, createWriteStream, promises as fs } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { MAX_TRACE_EVENTS_PER_BATCH, parseTraceEventBatch, } from "iteration-trace-contracts";
|
|
7
7
|
import * as z from "zod";
|
|
8
|
+
import { redactTraceEvent } from "./redact-trace.js";
|
|
8
9
|
const MAX_OUTBOX_BYTES = 64 * 1024 * 1024;
|
|
9
10
|
export function collectorSource(config) {
|
|
10
|
-
return "
|
|
11
|
+
return "cursorHome" in config
|
|
12
|
+
? "cursor"
|
|
13
|
+
: "claudeHome" in config
|
|
14
|
+
? "claude"
|
|
15
|
+
: "codex";
|
|
11
16
|
}
|
|
12
17
|
export function collectorSourceDirectory(config) {
|
|
13
|
-
return "
|
|
18
|
+
return "cursorHome" in config
|
|
19
|
+
? config.cursorHome
|
|
20
|
+
: "claudeHome" in config
|
|
21
|
+
? config.claudeHome
|
|
22
|
+
: config.codexHome;
|
|
14
23
|
}
|
|
15
24
|
const connectionSchema = {
|
|
16
25
|
endpoint: z.string(),
|
|
@@ -28,6 +37,13 @@ const configSchema = z.union([
|
|
|
28
37
|
claudeHome: z.string(),
|
|
29
38
|
})
|
|
30
39
|
.strict(),
|
|
40
|
+
z
|
|
41
|
+
.object({
|
|
42
|
+
...connectionSchema,
|
|
43
|
+
source: z.literal("cursor"),
|
|
44
|
+
cursorHome: z.string(),
|
|
45
|
+
})
|
|
46
|
+
.strict(),
|
|
31
47
|
]);
|
|
32
48
|
const stateSchema = z
|
|
33
49
|
.object({
|
|
@@ -51,6 +67,7 @@ const healthSchema = z
|
|
|
51
67
|
.nullable(),
|
|
52
68
|
})
|
|
53
69
|
.strict();
|
|
70
|
+
const redactionMarkerSchema = z.object({ version: z.literal(1) }).strict();
|
|
54
71
|
function parseOutboxEvent(line) {
|
|
55
72
|
let decoded;
|
|
56
73
|
try {
|
|
@@ -101,6 +118,7 @@ export function collectorPaths(directory = defaultCollectorDirectory("codex")) {
|
|
|
101
118
|
config: path.join(directory, "config.json"),
|
|
102
119
|
state: path.join(directory, "state.json"),
|
|
103
120
|
outbox: path.join(directory, "outbox.jsonl"),
|
|
121
|
+
redactionMarker: path.join(directory, "redaction.json"),
|
|
104
122
|
lock: path.join(directory, "collector.lock"),
|
|
105
123
|
historyBackfill: path.join(directory, "history-backfill.json"),
|
|
106
124
|
health: path.join(directory, "health.json"),
|
|
@@ -315,6 +333,72 @@ export async function appendEvents(events, paths) {
|
|
|
315
333
|
await handle.close();
|
|
316
334
|
}
|
|
317
335
|
}
|
|
336
|
+
export async function redactPendingOutbox(paths) {
|
|
337
|
+
let marker;
|
|
338
|
+
try {
|
|
339
|
+
marker = await fs.readFile(paths.redactionMarker, "utf8");
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
343
|
+
marker = null;
|
|
344
|
+
else
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
if (marker !== null) {
|
|
348
|
+
let decoded;
|
|
349
|
+
try {
|
|
350
|
+
decoded = JSON.parse(marker);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
throw new Error("Collector redaction marker is not valid JSON.");
|
|
354
|
+
}
|
|
355
|
+
if (!redactionMarkerSchema.safeParse(decoded).success)
|
|
356
|
+
throw new Error("Collector redaction marker has an invalid shape.");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const size = await outboxSize(paths);
|
|
360
|
+
if (size > MAX_OUTBOX_BYTES)
|
|
361
|
+
throw new Error("Trace outbox exceeds its configured capacity.");
|
|
362
|
+
if (size > 0) {
|
|
363
|
+
const temporary = `${paths.outbox}.${process.pid}.redacting`;
|
|
364
|
+
const output = await fs.open(temporary, "w", 0o600);
|
|
365
|
+
try {
|
|
366
|
+
const lines = createInterface({
|
|
367
|
+
input: createReadStream(paths.outbox, { encoding: "utf8" }),
|
|
368
|
+
crlfDelay: Infinity,
|
|
369
|
+
});
|
|
370
|
+
for await (const line of lines) {
|
|
371
|
+
const event = await redactTraceEvent(parseOutboxEvent(line));
|
|
372
|
+
await output.writeFile(`${JSON.stringify(event)}\n`, "utf8");
|
|
373
|
+
}
|
|
374
|
+
await output.sync();
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
await output.close();
|
|
378
|
+
await fs.rm(temporary, { force: true });
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
await output.close();
|
|
382
|
+
try {
|
|
383
|
+
if ((await fs.stat(temporary)).size > MAX_OUTBOX_BYTES)
|
|
384
|
+
throw new Error("Redacted trace outbox exceeds its configured capacity.");
|
|
385
|
+
await fs.rename(temporary, paths.outbox);
|
|
386
|
+
const directory = await fs.open(path.dirname(paths.outbox), "r");
|
|
387
|
+
try {
|
|
388
|
+
await directory.sync();
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
await directory.close();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
await fs.rm(temporary, { force: true });
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
await ensurePrivateDirectory(path.dirname(paths.redactionMarker));
|
|
400
|
+
await writeJsonAtomic(paths.redactionMarker, { version: 1 });
|
|
401
|
+
}
|
|
318
402
|
export async function nextBatch(paths, maxBytes) {
|
|
319
403
|
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
320
404
|
throw new Error("Maximum upload batch size must be a positive integer.");
|
|
@@ -492,6 +576,18 @@ export async function ensureClaudeHistoryBackfill(claudeHome, paths) {
|
|
|
492
576
|
await writeJsonAtomic(paths.historyBackfill, { version: 1 });
|
|
493
577
|
return true;
|
|
494
578
|
}
|
|
579
|
+
export async function ensureCursorHistoryBackfill(cursorHome, paths) {
|
|
580
|
+
const version = await readHistoryBackfillVersion(paths);
|
|
581
|
+
if (version !== null && version >= 1)
|
|
582
|
+
return false;
|
|
583
|
+
const state = await loadState(paths);
|
|
584
|
+
if (!state.initialized || state.sourceDirectory !== path.resolve(cursorHome))
|
|
585
|
+
throw new Error("Collector state does not match the Cursor directory; reconnect Cursor before uploading.");
|
|
586
|
+
await cursorFiles(path.join(cursorHome, "projects"));
|
|
587
|
+
await saveState({ ...state, cursors: {}, skipping: [] }, paths);
|
|
588
|
+
await writeJsonAtomic(paths.historyBackfill, { version: 1 });
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
495
591
|
export async function historyScanProgress(home, paths, source = "codex") {
|
|
496
592
|
if ((await readHistoryBackfillVersion(paths)) === null)
|
|
497
593
|
return { kind: "waiting" };
|
|
@@ -677,6 +773,32 @@ export function sourceFiles(source, home) {
|
|
|
677
773
|
return rolloutFiles(path.join(directory, "sessions"));
|
|
678
774
|
if (source === "claude")
|
|
679
775
|
return claudeFiles(path.join(directory, "projects"));
|
|
776
|
+
if (source === "cursor")
|
|
777
|
+
return cursorFiles(path.join(directory, "projects"));
|
|
680
778
|
const unhandled = source;
|
|
681
779
|
throw new Error(`Unsupported collector source: ${unhandled}`);
|
|
682
780
|
}
|
|
781
|
+
export async function cursorFiles(root) {
|
|
782
|
+
const files = [];
|
|
783
|
+
for (const project of await directoryEntries(root)) {
|
|
784
|
+
if (!project.isDirectory())
|
|
785
|
+
continue;
|
|
786
|
+
const transcripts = path.join(root, project.name, "agent-transcripts");
|
|
787
|
+
for (const conversation of await directoryEntries(transcripts)) {
|
|
788
|
+
if (!conversation.isDirectory())
|
|
789
|
+
continue;
|
|
790
|
+
const filename = path.join(transcripts, conversation.name, `${conversation.name}.jsonl`);
|
|
791
|
+
if ((await fs.stat(filename).catch((error) => {
|
|
792
|
+
if (error instanceof Error &&
|
|
793
|
+
"code" in error &&
|
|
794
|
+
error.code === "ENOENT")
|
|
795
|
+
return null;
|
|
796
|
+
throw error;
|
|
797
|
+
}))?.isFile())
|
|
798
|
+
files.push(filename);
|
|
799
|
+
if (files.length > 10_000)
|
|
800
|
+
throw new Error("Cursor session file count exceeds the scan limit.");
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return files.sort();
|
|
804
|
+
}
|
package/dist/turn-summary.js
CHANGED
|
@@ -55,6 +55,8 @@ function hookCommand(paths) {
|
|
|
55
55
|
return `/bin/sh ${shellQuote(paths.script)}`;
|
|
56
56
|
}
|
|
57
57
|
function agentHookFile(config) {
|
|
58
|
+
if ("cursorHome" in config)
|
|
59
|
+
throw new Error("Cursor uses its own trace hooks.");
|
|
58
60
|
return "claudeHome" in config
|
|
59
61
|
? path.join(config.claudeHome, "settings.json")
|
|
60
62
|
: path.join(config.codexHome, "hooks.json");
|
|
@@ -337,7 +339,7 @@ function codexTrustedHash(configToml, key) {
|
|
|
337
339
|
return trustedHash;
|
|
338
340
|
}
|
|
339
341
|
export async function codexTurnSummaryApproval(config) {
|
|
340
|
-
if ("claudeHome" in config)
|
|
342
|
+
if ("claudeHome" in config || "cursorHome" in config)
|
|
341
343
|
return "approved";
|
|
342
344
|
const filename = agentHookFile(config);
|
|
343
345
|
const { file } = await readHookFile(filename);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iteration-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
"iteration": "./dist/cli.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@secretlint/core": "13.0.6",
|
|
21
|
+
"@secretlint/secretlint-rule-preset-recommend": "13.0.6",
|
|
20
22
|
"zod": "4.6.4",
|
|
21
|
-
"iteration-trace-contracts": "0.
|
|
23
|
+
"iteration-trace-contracts": "0.7.0"
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json"
|