iteration-cli 0.4.3 → 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 +7 -2
- package/dist/claude-transcript.d.ts +6 -0
- package/dist/claude-transcript.js +255 -0
- package/dist/clerk-device-auth.d.ts +2 -1
- package/dist/clerk-device-auth.js +6 -4
- package/dist/cli.js +135 -77
- package/dist/collector.d.ts +11 -2
- package/dist/collector.js +86 -38
- package/dist/history-import.d.ts +1 -1
- package/dist/history-import.js +18 -15
- package/dist/launch-agent.d.ts +5 -4
- package/dist/launch-agent.js +26 -23
- package/dist/rollout.js +122 -135
- package/dist/service.d.ts +2 -1
- package/dist/service.js +64 -7
- package/dist/status-view.d.ts +30 -0
- package/dist/status-view.js +246 -0
- package/dist/storage.d.ts +43 -10
- package/dist/storage.js +215 -51
- package/package.json +2 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { collectorSourceLabel, } from "iteration-trace-contracts";
|
|
2
|
+
function sourceName(source) {
|
|
3
|
+
return collectorSourceLabel(source);
|
|
4
|
+
}
|
|
5
|
+
function safeWorkspaceName(value) {
|
|
6
|
+
const cleaned = value
|
|
7
|
+
?.split("")
|
|
8
|
+
.map((character) => {
|
|
9
|
+
const code = character.charCodeAt(0);
|
|
10
|
+
return code < 32 || (code >= 127 && code <= 159) ? " " : character;
|
|
11
|
+
})
|
|
12
|
+
.join("")
|
|
13
|
+
.replace(/\s+/g, " ")
|
|
14
|
+
.trim();
|
|
15
|
+
return cleaned === undefined || cleaned === "" ? null : cleaned;
|
|
16
|
+
}
|
|
17
|
+
function readableBytes(bytes) {
|
|
18
|
+
if (bytes < 1_000)
|
|
19
|
+
return `${bytes} B`;
|
|
20
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
21
|
+
let amount = bytes;
|
|
22
|
+
let index = -1;
|
|
23
|
+
do {
|
|
24
|
+
amount /= 1_000;
|
|
25
|
+
index += 1;
|
|
26
|
+
} while (amount >= 1_000 && index < units.length - 1);
|
|
27
|
+
const precision = amount < 10 ? 1 : 0;
|
|
28
|
+
return `${amount.toFixed(precision).replace(/\.0$/, "")} ${units[index]}`;
|
|
29
|
+
}
|
|
30
|
+
function relativeTime(atMs, nowMs) {
|
|
31
|
+
const elapsedSeconds = Math.max(0, Math.floor((nowMs - atMs) / 1_000));
|
|
32
|
+
if (elapsedSeconds < 5)
|
|
33
|
+
return "just now";
|
|
34
|
+
if (elapsedSeconds < 60)
|
|
35
|
+
return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ago`;
|
|
36
|
+
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
|
37
|
+
if (elapsedMinutes < 60)
|
|
38
|
+
return `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ago`;
|
|
39
|
+
const elapsedHours = Math.floor(elapsedMinutes / 60);
|
|
40
|
+
if (elapsedHours < 24)
|
|
41
|
+
return `${elapsedHours} hour${elapsedHours === 1 ? "" : "s"} ago`;
|
|
42
|
+
const elapsedDays = Math.floor(elapsedHours / 24);
|
|
43
|
+
return `${elapsedDays} day${elapsedDays === 1 ? "" : "s"} ago`;
|
|
44
|
+
}
|
|
45
|
+
function pendingDescription(status) {
|
|
46
|
+
return `${status.pendingEvents} event${status.pendingEvents === 1 ? "" : "s"} (${readableBytes(status.pendingBytes)}) waiting on this Mac`;
|
|
47
|
+
}
|
|
48
|
+
function progressDescription(scan, source) {
|
|
49
|
+
if (scan.kind === "waiting")
|
|
50
|
+
return "Local history is preparing to scan.";
|
|
51
|
+
if (scan.totalBytes === 0)
|
|
52
|
+
return `No local ${sourceName(source)} sessions found yet.`;
|
|
53
|
+
if (scan.scannedBytes >= scan.totalBytes)
|
|
54
|
+
return `Local history scanned (${readableBytes(scan.totalBytes)}).`;
|
|
55
|
+
const remainingBytes = scan.totalBytes - scan.scannedBytes;
|
|
56
|
+
if (remainingBytes < 1_000_000)
|
|
57
|
+
return "Local scan is nearly caught up (less than 1 MB remains).";
|
|
58
|
+
const percentage = Math.min(99, Math.floor((scan.scannedBytes / scan.totalBytes) * 100));
|
|
59
|
+
return `About ${percentage}% of local history scanned (${readableBytes(scan.scannedBytes)} of ${readableBytes(scan.totalBytes)}).`;
|
|
60
|
+
}
|
|
61
|
+
function historyDescription(status, source) {
|
|
62
|
+
if (status.health.historyCaughtUpAtMs === null) {
|
|
63
|
+
return progressDescription(status.scan, source);
|
|
64
|
+
}
|
|
65
|
+
if (status.scan.kind === "waiting")
|
|
66
|
+
return "History is preparing to scan.";
|
|
67
|
+
const remainingBytes = status.scan.totalBytes - status.scan.scannedBytes;
|
|
68
|
+
if (remainingBytes <= 0)
|
|
69
|
+
return "History imported; local files are caught up.";
|
|
70
|
+
if (remainingBytes < 1_000_000)
|
|
71
|
+
return "History imported; scanning current activity.";
|
|
72
|
+
return `History imported; ${readableBytes(remainingBytes)} of new activity waiting to scan.`;
|
|
73
|
+
}
|
|
74
|
+
function failureDescription(status, source) {
|
|
75
|
+
const name = sourceName(source);
|
|
76
|
+
const failure = status.health.lastFailure;
|
|
77
|
+
if (failure === null) {
|
|
78
|
+
throw new Error("A collector failure is required to describe it.");
|
|
79
|
+
}
|
|
80
|
+
if (failure.kind === "http") {
|
|
81
|
+
const code = failure.status === null ? "HTTP error" : `HTTP ${failure.status}`;
|
|
82
|
+
if (failure.status === 401 || failure.status === 403) {
|
|
83
|
+
return {
|
|
84
|
+
heading: `${name} upload access needs attention`,
|
|
85
|
+
explanation: `Iteration denied this collector (${code}). Queued data stays on this Mac. Contact Iteration support to reconnect it safely.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (failure.status !== null &&
|
|
89
|
+
failure.status < 500 &&
|
|
90
|
+
failure.status !== 429) {
|
|
91
|
+
return {
|
|
92
|
+
heading: `${name} uploads need attention`,
|
|
93
|
+
explanation: `Iteration rejected an event batch (${code}). Check the collector log for details.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
heading: `${name} uploads are delayed`,
|
|
98
|
+
explanation: `Iteration could not accept an upload (${code}). Retrying automatically.`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (failure.kind === "network") {
|
|
102
|
+
return {
|
|
103
|
+
heading: `${name} uploads are delayed`,
|
|
104
|
+
explanation: "Could not reach Iteration. Retrying automatically.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (failure.kind === "response") {
|
|
108
|
+
return {
|
|
109
|
+
heading: `${name} uploads need attention`,
|
|
110
|
+
explanation: "Iteration returned an unexpected upload response. Retrying automatically.",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
heading: `${name} collector needs attention`,
|
|
115
|
+
explanation: `A local error interrupted collection. See iteration status ${source} for details.`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function renderHome(status, nowMs = Date.now(), source = "codex") {
|
|
119
|
+
const name = sourceName(source);
|
|
120
|
+
if (status.kind === "not_connected") {
|
|
121
|
+
return [
|
|
122
|
+
"Iteration",
|
|
123
|
+
"",
|
|
124
|
+
`${name} is not connected.`,
|
|
125
|
+
`Run iteration connect ${source} to import your existing sessions.`,
|
|
126
|
+
].join("\n");
|
|
127
|
+
}
|
|
128
|
+
if (status.kind === "needs_repair") {
|
|
129
|
+
return [
|
|
130
|
+
"Iteration",
|
|
131
|
+
"",
|
|
132
|
+
`${name} connection needs repair`,
|
|
133
|
+
"Local collector data exists, but its connection details are missing.",
|
|
134
|
+
status.pendingBytes > 0
|
|
135
|
+
? `${readableBytes(status.pendingBytes)} of queued data remains on this Mac.`
|
|
136
|
+
: "Existing local progress remains on this Mac.",
|
|
137
|
+
`Run iteration status ${source} for recovery details.`,
|
|
138
|
+
].join("\n");
|
|
139
|
+
}
|
|
140
|
+
const workspaceName = safeWorkspaceName(status.workspaceName);
|
|
141
|
+
const lines = [
|
|
142
|
+
workspaceName === null ? "Iteration" : `Iteration · ${workspaceName}`,
|
|
143
|
+
"",
|
|
144
|
+
];
|
|
145
|
+
if (!status.running) {
|
|
146
|
+
lines.push(`${name} is stopped`, "New sessions are not being collected.");
|
|
147
|
+
if (status.pendingEvents > 0)
|
|
148
|
+
lines.push(`${pendingDescription(status)}.`);
|
|
149
|
+
lines.push(`Resume with iteration start ${source}.`);
|
|
150
|
+
}
|
|
151
|
+
else if (!status.healthAvailable) {
|
|
152
|
+
lines.push(`${name} is running; local status needs attention`, "Upload history could not be read. Collection continues in the background.");
|
|
153
|
+
if (status.pendingEvents > 0)
|
|
154
|
+
lines.push(`${pendingDescription(status)}.`);
|
|
155
|
+
lines.push(`Details: iteration status ${source}`);
|
|
156
|
+
}
|
|
157
|
+
else if (status.health.lastFailure !== null) {
|
|
158
|
+
const failure = failureDescription(status, source);
|
|
159
|
+
lines.push(failure.heading, failure.explanation);
|
|
160
|
+
if (status.pendingEvents > 0)
|
|
161
|
+
lines.push(`${pendingDescription(status)}.`);
|
|
162
|
+
if (status.health.lastUploadAtMs !== null) {
|
|
163
|
+
lines.push(`Last successful upload ${relativeTime(status.health.lastUploadAtMs, nowMs)}.`);
|
|
164
|
+
}
|
|
165
|
+
lines.push(`Details: iteration status ${source}`);
|
|
166
|
+
}
|
|
167
|
+
else if (status.scan.kind === "waiting") {
|
|
168
|
+
lines.push(`${name} is preparing your history`, progressDescription(status.scan, source));
|
|
169
|
+
}
|
|
170
|
+
else if (status.scan.kind === "scanning" &&
|
|
171
|
+
status.scan.totalBytes > 0 &&
|
|
172
|
+
status.scan.scannedBytes < status.scan.totalBytes &&
|
|
173
|
+
status.health.historyCaughtUpAtMs === null) {
|
|
174
|
+
lines.push(`${name} is importing existing sessions`, historyDescription(status, source), "New activity is collected automatically.");
|
|
175
|
+
}
|
|
176
|
+
else if (status.scan.kind === "scanning" &&
|
|
177
|
+
status.scan.totalBytes - status.scan.scannedBytes >= 1_000_000) {
|
|
178
|
+
lines.push(`${name} is catching up with new activity`, historyDescription(status, source));
|
|
179
|
+
if (status.pendingEvents > 0)
|
|
180
|
+
lines.push(`${pendingDescription(status)}.`);
|
|
181
|
+
}
|
|
182
|
+
else if (status.pendingEvents > 0) {
|
|
183
|
+
lines.push(`${name} is uploading captured sessions`, `${pendingDescription(status)}.`);
|
|
184
|
+
}
|
|
185
|
+
else if (status.scan.totalBytes === 0) {
|
|
186
|
+
lines.push(`${name} is connected`, `Waiting for ${name} sessions on this Mac.`);
|
|
187
|
+
}
|
|
188
|
+
else if (status.scan.kind === "scanning" &&
|
|
189
|
+
status.scan.scannedBytes < status.scan.totalBytes) {
|
|
190
|
+
lines.push(`${name} is collecting new activity`, "Local history is imported; current activity is being scanned.");
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
lines.push(`${name} is collecting`, "Local scan is caught up and no uploads are waiting.", "New activity is collected automatically.");
|
|
194
|
+
}
|
|
195
|
+
if (status.running &&
|
|
196
|
+
status.health.lastFailure === null &&
|
|
197
|
+
status.health.lastUploadAtMs !== null) {
|
|
198
|
+
lines.push(`Last successful upload ${relativeTime(status.health.lastUploadAtMs, nowMs)}.`);
|
|
199
|
+
}
|
|
200
|
+
lines.push("", `Explore traces ${status.tracesUrl}`);
|
|
201
|
+
return lines.join("\n");
|
|
202
|
+
}
|
|
203
|
+
export function renderDetailedStatus(status, nowMs = Date.now(), source = "codex") {
|
|
204
|
+
const name = sourceName(source);
|
|
205
|
+
if (status.kind === "not_connected") {
|
|
206
|
+
return `${name} is not connected. Run iteration connect ${source} to connect.`;
|
|
207
|
+
}
|
|
208
|
+
if (status.kind === "needs_repair") {
|
|
209
|
+
return [
|
|
210
|
+
`${name} connection needs repair`,
|
|
211
|
+
"Local collector data exists without its connection details.",
|
|
212
|
+
`Queued data: ${readableBytes(status.pendingBytes)}`,
|
|
213
|
+
`Restore the missing config from backup: ${status.configPath}`,
|
|
214
|
+
"If you do not have it, contact Iteration support before reconnecting; existing data has been kept.",
|
|
215
|
+
].join("\n");
|
|
216
|
+
}
|
|
217
|
+
const workspaceName = safeWorkspaceName(status.workspaceName);
|
|
218
|
+
const lines = [
|
|
219
|
+
`${name} collector`,
|
|
220
|
+
...(workspaceName === null ? [] : [`Workspace: ${workspaceName}`]),
|
|
221
|
+
`Background process: ${status.running ? "Running" : "Stopped"}`,
|
|
222
|
+
`History: ${historyDescription(status, source).replace(/^History /, "")}`,
|
|
223
|
+
`Uploads: ${status.pendingEvents === 0 ? "None waiting" : pendingDescription(status)}`,
|
|
224
|
+
];
|
|
225
|
+
if (!status.healthAvailable) {
|
|
226
|
+
lines.push("Local status: unavailable; check collector file permissions", `Collector log: ${status.logPath}`);
|
|
227
|
+
}
|
|
228
|
+
if (status.health.lastUploadAtMs !== null) {
|
|
229
|
+
lines.push(`Last successful upload: ${relativeTime(status.health.lastUploadAtMs, nowMs)}`);
|
|
230
|
+
}
|
|
231
|
+
if (status.health.lastFailure !== null) {
|
|
232
|
+
const failure = status.health.lastFailure;
|
|
233
|
+
const label = failure.kind === "http" && failure.status !== null
|
|
234
|
+
? `HTTP ${failure.status}`
|
|
235
|
+
: failure.kind === "network"
|
|
236
|
+
? "Network unavailable"
|
|
237
|
+
: failure.kind === "response"
|
|
238
|
+
? "Unexpected upload response"
|
|
239
|
+
: "Local collector error";
|
|
240
|
+
lines.push(`Last issue: ${label} (${relativeTime(failure.atMs, nowMs)})`, `Collector log: ${status.logPath}`);
|
|
241
|
+
}
|
|
242
|
+
if (!status.running)
|
|
243
|
+
lines.push(`Resume: iteration start ${source}`);
|
|
244
|
+
lines.push(`Explore traces: ${status.tracesUrl}`);
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,28 +1,58 @@
|
|
|
1
|
-
import { type TraceEvent } from "iteration-trace-contracts";
|
|
2
|
-
|
|
1
|
+
import { type CollectorSource, type TraceEvent } from "iteration-trace-contracts";
|
|
2
|
+
type CollectorConnection = Readonly<{
|
|
3
3
|
endpoint: string;
|
|
4
4
|
token: string;
|
|
5
|
-
codexHome: string;
|
|
6
5
|
dataDirectory: string;
|
|
6
|
+
organizationName?: string;
|
|
7
7
|
}>;
|
|
8
|
+
export type CollectorConfig = (CollectorConnection & Readonly<{
|
|
9
|
+
codexHome: string;
|
|
10
|
+
}>) | (CollectorConnection & Readonly<{
|
|
11
|
+
source: "claude";
|
|
12
|
+
claudeHome: string;
|
|
13
|
+
}>);
|
|
14
|
+
export declare function collectorSource(config: CollectorConfig): CollectorSource;
|
|
15
|
+
export declare function collectorSourceDirectory(config: CollectorConfig): string;
|
|
8
16
|
export type CollectorState = Readonly<{
|
|
9
17
|
cursors: Readonly<Record<string, number>>;
|
|
10
18
|
skipping: readonly string[];
|
|
11
19
|
sourceDirectory: string | null;
|
|
12
20
|
initialized: boolean;
|
|
13
21
|
}>;
|
|
22
|
+
export type CollectorFailure = Readonly<{
|
|
23
|
+
atMs: number;
|
|
24
|
+
kind: "http" | "network" | "response" | "collector";
|
|
25
|
+
status: number | null;
|
|
26
|
+
}>;
|
|
27
|
+
export type CollectorHealth = Readonly<{
|
|
28
|
+
lastUploadAtMs: number | null;
|
|
29
|
+
historyCaughtUpAtMs: number | null;
|
|
30
|
+
lastFailure: CollectorFailure | null;
|
|
31
|
+
}>;
|
|
32
|
+
export type HealthSnapshot = Readonly<{
|
|
33
|
+
kind: "available";
|
|
34
|
+
health: CollectorHealth;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "unavailable";
|
|
37
|
+
health: CollectorHealth;
|
|
38
|
+
}>;
|
|
14
39
|
export type CollectorPaths = Readonly<{
|
|
15
40
|
config: string;
|
|
16
41
|
state: string;
|
|
17
42
|
outbox: string;
|
|
18
43
|
lock: string;
|
|
19
44
|
historyBackfill: string;
|
|
45
|
+
health: string;
|
|
20
46
|
}>;
|
|
47
|
+
export declare function defaultCollectorDirectory(source: CollectorSource): string;
|
|
21
48
|
export declare function validateCollectorEndpoint(endpoint: string): URL;
|
|
22
49
|
export declare function collectorPaths(directory?: string): CollectorPaths;
|
|
23
|
-
export declare function assertFreshCollectorDirectory(paths: CollectorPaths): Promise<void>;
|
|
50
|
+
export declare function assertFreshCollectorDirectory(paths: CollectorPaths, source?: CollectorSource): Promise<void>;
|
|
24
51
|
export declare function ensurePrivateDirectory(directory: string): Promise<void>;
|
|
25
52
|
export declare function saveConfig(config: CollectorConfig, paths: CollectorPaths): Promise<void>;
|
|
53
|
+
export declare function loadHealth(paths: CollectorPaths): Promise<CollectorHealth>;
|
|
54
|
+
export declare function healthSnapshot(paths: CollectorPaths): Promise<HealthSnapshot>;
|
|
55
|
+
export declare function saveHealth(health: CollectorHealth, paths: CollectorPaths): Promise<void>;
|
|
26
56
|
export declare function loadConfig(paths: CollectorPaths): Promise<CollectorConfig>;
|
|
27
57
|
export declare function loadState(paths: CollectorPaths): Promise<CollectorState>;
|
|
28
58
|
export declare function saveState(state: CollectorState, paths: CollectorPaths): Promise<void>;
|
|
@@ -31,23 +61,26 @@ export declare function pendingEventIds(paths: CollectorPaths): Promise<Set<stri
|
|
|
31
61
|
export declare function appendEvents(events: readonly TraceEvent[], paths: CollectorPaths): Promise<void>;
|
|
32
62
|
export declare function nextBatch(paths: CollectorPaths, maxBytes: number): Promise<readonly TraceEvent[]>;
|
|
33
63
|
export declare function acknowledgeEvents(acknowledgedIds: ReadonlySet<string>, paths: CollectorPaths): Promise<void>;
|
|
34
|
-
export declare function initializeState(
|
|
35
|
-
export declare function
|
|
64
|
+
export declare function initializeState(source: CollectorSource, home: string, paths: CollectorPaths, includeHistory: boolean): Promise<void>;
|
|
65
|
+
export declare function prepareSourceInitialState(source: CollectorSource, home: string, includeHistory: boolean): Promise<CollectorState>;
|
|
36
66
|
export declare function ensureStartableState(config: CollectorConfig, paths: CollectorPaths): Promise<boolean>;
|
|
37
67
|
export declare function ensureHistoryBackfill(codexHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
38
|
-
export declare function
|
|
68
|
+
export declare function ensureClaudeHistoryBackfill(claudeHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
69
|
+
export declare function historyScanProgress(home: string, paths: CollectorPaths, source?: CollectorSource): Promise<Readonly<{
|
|
39
70
|
kind: "waiting";
|
|
40
71
|
} | {
|
|
41
72
|
kind: "scanning";
|
|
42
73
|
scannedBytes: number;
|
|
43
74
|
totalBytes: number;
|
|
44
75
|
}>>;
|
|
45
|
-
export declare function resetHistoryCursors(
|
|
76
|
+
export declare function resetHistoryCursors(home: string, paths: CollectorPaths, source?: CollectorSource): Promise<Readonly<{
|
|
46
77
|
files: number;
|
|
47
78
|
bytes: number;
|
|
48
79
|
}>>;
|
|
49
|
-
export declare function historyImportSize(
|
|
80
|
+
export declare function historyImportSize(home: string, source?: CollectorSource): Promise<Readonly<{
|
|
50
81
|
files: number;
|
|
51
82
|
bytes: number;
|
|
52
83
|
}>>;
|
|
53
|
-
export declare function
|
|
84
|
+
export declare function claudeFiles(root: string): Promise<string[]>;
|
|
85
|
+
export declare function sourceFiles(source: CollectorSource, home: string): Promise<string[]>;
|
|
86
|
+
export {};
|