iteration-cli 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +16 -4
- package/dist/launch-agent.d.ts +1 -0
- package/dist/launch-agent.js +10 -2
- package/dist/storage.d.ts +7 -0
- package/dist/storage.js +51 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { authenticateAndEnrollCollector } from "./clerk-device-auth.js";
|
|
|
8
8
|
import { importCodexHistory } from "./history-import.js";
|
|
9
9
|
import { runCollector } from "./service.js";
|
|
10
10
|
import { installLaunchAgent, launchAgentStatus, uninstallLaunchAgent, } from "./launch-agent.js";
|
|
11
|
-
import { assertFreshCollectorDirectory, collectorPaths, ensureStartableState, loadConfig, outboxSize, pendingEventIds, prepareInitialState, saveConfig, saveState, validateCollectorEndpoint, } from "./storage.js";
|
|
11
|
+
import { assertFreshCollectorDirectory, collectorPaths, ensureStartableState, historyScanProgress, loadConfig, outboxSize, pendingEventIds, prepareInitialState, saveConfig, saveState, validateCollectorEndpoint, } from "./storage.js";
|
|
12
12
|
const DEFAULT_INGEST_ENDPOINT = "https://useiteration.com/api/traces/ingest";
|
|
13
13
|
function argumentsForCli() {
|
|
14
14
|
const parsed = parseArgs({
|
|
@@ -22,6 +22,7 @@ function argumentsForCli() {
|
|
|
22
22
|
"organization-id": { type: "string" },
|
|
23
23
|
"codex-home": { type: "string" },
|
|
24
24
|
"data-directory": { type: "string" },
|
|
25
|
+
"include-history": { type: "boolean" },
|
|
25
26
|
yes: { type: "boolean" },
|
|
26
27
|
},
|
|
27
28
|
});
|
|
@@ -156,7 +157,7 @@ async function importHistory(values) {
|
|
|
156
157
|
return;
|
|
157
158
|
}
|
|
158
159
|
const result = await importCodexHistory(config, paths, true);
|
|
159
|
-
process.stdout.write(`History scan scheduled for ${result.size.files} local session files (${result.size.bytes} bytes). Scanning and uploads run in bounded batches and may take time. Older sessions appear under the date Iteration receives them. iteration status
|
|
160
|
+
process.stdout.write(`History scan scheduled for ${result.size.files} local session files (${result.size.bytes} bytes). Scanning and uploads run in bounded batches and may take time. Older sessions appear under the date Iteration receives them. Use iteration status to see local scan progress and pending uploads.${result.backgroundRunning ? "" : " The collector was not running; run iteration start codex to begin scanning."}\n`);
|
|
160
161
|
}
|
|
161
162
|
async function start(values) {
|
|
162
163
|
if (process.platform !== "darwin") {
|
|
@@ -175,15 +176,22 @@ async function status(values) {
|
|
|
175
176
|
process.stdout.write("Installed: false\nRunning: false\nPending events: 0\n");
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
|
-
await loadConfig(paths);
|
|
179
|
+
const config = await loadConfig(paths);
|
|
179
180
|
const running = await launchAgentStatus();
|
|
180
|
-
const [size, ids] = await Promise.all([
|
|
181
|
+
const [size, ids, scan] = await Promise.all([
|
|
181
182
|
outboxSize(paths),
|
|
182
183
|
pendingEventIds(paths),
|
|
184
|
+
historyScanProgress(config.codexHome, paths),
|
|
183
185
|
]);
|
|
186
|
+
const scanLabel = scan.kind === "waiting"
|
|
187
|
+
? "Local history: waiting for collector start"
|
|
188
|
+
: scan.totalBytes === 0
|
|
189
|
+
? "Local scan: no session data found"
|
|
190
|
+
: `Local scan: ${scan.scannedBytes} of ${scan.totalBytes} bytes processed`;
|
|
184
191
|
process.stdout.write([
|
|
185
192
|
"Installed: true",
|
|
186
193
|
`Running: ${running}`,
|
|
194
|
+
scanLabel,
|
|
187
195
|
`Pending events: ${ids.size}`,
|
|
188
196
|
`Pending bytes: ${size}`,
|
|
189
197
|
].join("\n") + "\n");
|
|
@@ -193,6 +201,10 @@ async function main() {
|
|
|
193
201
|
if (values.help === true || command === "help") {
|
|
194
202
|
return usage(command === "help" ? source : command);
|
|
195
203
|
}
|
|
204
|
+
if (values["include-history"] === true &&
|
|
205
|
+
!(command === "connect" && source === "codex")) {
|
|
206
|
+
throw new Error("Existing sessions are included automatically. Run iteration to connect.");
|
|
207
|
+
}
|
|
196
208
|
if (command === "" || (command === "connect" && source === undefined)) {
|
|
197
209
|
if (Object.keys(values).length > 0) {
|
|
198
210
|
throw new Error("Options require a source. Run iteration connect codex --help for details.");
|
package/dist/launch-agent.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export declare function launchAgentPlist(cliPath?: string, nodePath?: string, lo
|
|
|
2
2
|
export declare function installLaunchAgent(dataDirectory?: string): Promise<string>;
|
|
3
3
|
export declare function uninstallLaunchAgent(): Promise<void>;
|
|
4
4
|
export declare function launchAgentStatus(): Promise<boolean>;
|
|
5
|
+
export declare function launchAgentRunning(output: string): boolean;
|
package/dist/launch-agent.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function installLaunchAgent(dataDirectory) {
|
|
|
79
79
|
const target = launchctlTarget();
|
|
80
80
|
await bootoutIfLoaded(target);
|
|
81
81
|
await execFile("launchctl", ["bootstrap", target, file]);
|
|
82
|
+
await execFile("launchctl", ["kickstart", "-p", `${target}/${LABEL}`]);
|
|
82
83
|
return file;
|
|
83
84
|
}
|
|
84
85
|
export async function uninstallLaunchAgent() {
|
|
@@ -93,10 +94,17 @@ export async function launchAgentStatus() {
|
|
|
93
94
|
if (process.platform !== "darwin")
|
|
94
95
|
return false;
|
|
95
96
|
try {
|
|
96
|
-
await execFile("launchctl", [
|
|
97
|
-
|
|
97
|
+
const { stdout } = await execFile("launchctl", [
|
|
98
|
+
"print",
|
|
99
|
+
`${launchctlTarget()}/${LABEL}`,
|
|
100
|
+
]);
|
|
101
|
+
return launchAgentRunning(stdout);
|
|
98
102
|
}
|
|
99
103
|
catch {
|
|
100
104
|
return false;
|
|
101
105
|
}
|
|
102
106
|
}
|
|
107
|
+
export function launchAgentRunning(output) {
|
|
108
|
+
const state = output.match(/^\s*state = ([^\r\n]+)$/m);
|
|
109
|
+
return state?.[1]?.trim() === "running";
|
|
110
|
+
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export declare function initializeState(codexHome: string, paths: CollectorPaths
|
|
|
35
35
|
export declare function prepareInitialState(codexHome: string, includeHistory: boolean): Promise<CollectorState>;
|
|
36
36
|
export declare function ensureStartableState(config: CollectorConfig, paths: CollectorPaths): Promise<boolean>;
|
|
37
37
|
export declare function ensureHistoryBackfill(codexHome: string, paths: CollectorPaths): Promise<boolean>;
|
|
38
|
+
export declare function historyScanProgress(codexHome: string, paths: CollectorPaths): Promise<Readonly<{
|
|
39
|
+
kind: "waiting";
|
|
40
|
+
} | {
|
|
41
|
+
kind: "scanning";
|
|
42
|
+
scannedBytes: number;
|
|
43
|
+
totalBytes: number;
|
|
44
|
+
}>>;
|
|
38
45
|
export declare function resetHistoryCursors(codexHome: string, paths: CollectorPaths): Promise<Readonly<{
|
|
39
46
|
files: number;
|
|
40
47
|
bytes: number;
|
package/dist/storage.js
CHANGED
|
@@ -322,9 +322,10 @@ export async function initializeState(codexHome, paths, includeHistory) {
|
|
|
322
322
|
export async function prepareInitialState(codexHome, includeHistory) {
|
|
323
323
|
const sourceDirectory = path.resolve(codexHome);
|
|
324
324
|
const sessionsRoot = path.join(sourceDirectory, "sessions");
|
|
325
|
+
const files = await rolloutFiles(sessionsRoot);
|
|
325
326
|
const cursors = {};
|
|
326
327
|
if (!includeHistory) {
|
|
327
|
-
for (const filename of
|
|
328
|
+
for (const filename of files) {
|
|
328
329
|
cursors[filename] = await lastCompleteRecordOffset(filename, (await fs.stat(filename)).size);
|
|
329
330
|
}
|
|
330
331
|
}
|
|
@@ -349,7 +350,7 @@ export async function ensureStartableState(config, paths) {
|
|
|
349
350
|
await initializeState(config.codexHome, paths, true);
|
|
350
351
|
return true;
|
|
351
352
|
}
|
|
352
|
-
|
|
353
|
+
async function hasHistoryBackfillMarker(paths) {
|
|
353
354
|
let marker;
|
|
354
355
|
try {
|
|
355
356
|
marker = await fs.readFile(paths.historyBackfill, "utf8");
|
|
@@ -363,16 +364,63 @@ export async function ensureHistoryBackfill(codexHome, paths) {
|
|
|
363
364
|
if (marker !== '{"version":1}\n') {
|
|
364
365
|
throw new Error("Collector history backfill marker has an invalid shape.");
|
|
365
366
|
}
|
|
366
|
-
return
|
|
367
|
+
return true;
|
|
367
368
|
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
export async function ensureHistoryBackfill(codexHome, paths) {
|
|
372
|
+
if (await hasHistoryBackfillMarker(paths))
|
|
373
|
+
return false;
|
|
368
374
|
const state = await loadState(paths);
|
|
369
375
|
if (!state.initialized || state.sourceDirectory !== path.resolve(codexHome)) {
|
|
370
376
|
throw new Error("Collector state does not match CODEX_HOME; reconnect Codex before uploading.");
|
|
371
377
|
}
|
|
378
|
+
await rolloutFiles(path.join(codexHome, "sessions"));
|
|
372
379
|
await saveState({ ...state, cursors: {}, skipping: [] }, paths);
|
|
373
380
|
await writeJsonAtomic(paths.historyBackfill, { version: 1 });
|
|
374
381
|
return true;
|
|
375
382
|
}
|
|
383
|
+
export async function historyScanProgress(codexHome, paths) {
|
|
384
|
+
if (!(await hasHistoryBackfillMarker(paths)))
|
|
385
|
+
return { kind: "waiting" };
|
|
386
|
+
const state = await loadState(paths);
|
|
387
|
+
if (!state.initialized || state.sourceDirectory !== path.resolve(codexHome)) {
|
|
388
|
+
throw new Error("Collector state does not match CODEX_HOME.");
|
|
389
|
+
}
|
|
390
|
+
const files = await rolloutFiles(path.join(codexHome, "sessions"));
|
|
391
|
+
let scannedBytes = 0;
|
|
392
|
+
let totalBytes = 0;
|
|
393
|
+
for (let start = 0; start < files.length; start += 64) {
|
|
394
|
+
const batch = files.slice(start, start + 64);
|
|
395
|
+
const sizes = await Promise.all(batch.map(async (filename) => {
|
|
396
|
+
try {
|
|
397
|
+
return (await fs.stat(filename)).size;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
if (error instanceof Error &&
|
|
401
|
+
"code" in error &&
|
|
402
|
+
error.code === "ENOENT")
|
|
403
|
+
return null;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
for (const [index, size] of sizes.entries()) {
|
|
408
|
+
if (size === null)
|
|
409
|
+
continue;
|
|
410
|
+
const filename = batch[index];
|
|
411
|
+
if (filename === undefined)
|
|
412
|
+
continue;
|
|
413
|
+
totalBytes += size;
|
|
414
|
+
const cursor = state.cursors[filename] ?? 0;
|
|
415
|
+
scannedBytes += cursor <= size ? cursor : 0;
|
|
416
|
+
if (!Number.isSafeInteger(totalBytes) ||
|
|
417
|
+
!Number.isSafeInteger(scannedBytes)) {
|
|
418
|
+
throw new Error("Codex history size exceeds the supported limit.");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { kind: "scanning", scannedBytes, totalBytes };
|
|
423
|
+
}
|
|
376
424
|
export async function resetHistoryCursors(codexHome, paths) {
|
|
377
425
|
const state = await loadState(paths);
|
|
378
426
|
const sourceDirectory = path.resolve(codexHome);
|