iteration-cli 0.11.1 → 0.13.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 +8 -2
- package/dist/cli.js +754 -73
- package/dist/cursor-hook-entry.js +53 -0
- package/dist/launcher.js +53 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# Iteration CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Connect a computer with the command shown on your Iteration workspace's overview:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx iteration-cli connect --workspace <workspace ID>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`--workspace` connects to that workspace without a prompt. The collectors keep running from their own copy of the CLI, so the command does not need a global install. To keep the `iteration` command for status and later changes, install it globally:
|
|
4
10
|
|
|
5
11
|
```sh
|
|
6
12
|
npm install --global iteration-cli
|
|
7
13
|
iteration connect
|
|
8
14
|
```
|
|
9
15
|
|
|
10
|
-
The collectors run on macOS or Linux with Node.js 22 or later. Cursor's local Agents window collector is tested on macOS. Linux needs systemd. The CLI automatically enables background collection after logout and installs a user service that starts at boot and restarts on failure. If server policy does not let the user enable this, an administrator must allow background services for the account. The setting applies to the whole Linux account and remains enabled after `iteration disconnect`; the CLI does not turn off other user services. `iteration connect` finds supported agents with local session directories, asks once before uploading
|
|
16
|
+
The collectors run on macOS or Linux with Node.js 22 or later. Cursor's local Agents window collector is tested on macOS. Linux needs systemd. The CLI automatically enables background collection after logout and installs a user service that starts at boot and restarts on failure. If server policy does not let the user enable this, an administrator must allow background services for the account. The setting applies to the whole Linux account and remains enabled after `iteration disconnect`; the CLI does not turn off other user services. `iteration connect` finds supported agents with local session directories, shows how many saved sessions each one has, asks once before uploading them and future activity, and connects them to one workspace. Saved sessions import newest first, and each collector reports its import progress to Iteration until it completes. A connected collector checks for newly detected supported agents and connects them under that approval. Each source has its own background process, local checkpoint, and upload queue. Run `iteration` to see connected sources, `iteration status cursor` for Cursor details, and `iteration help` for other commands. Source-specific connect commands remain available for advanced setups.
|
|
11
17
|
|
|
12
18
|
Cursor's **This Mac** Agents sessions are read from `~/.cursor/projects/*/agent-transcripts/<conversation>/<conversation>.jsonl`. Connecting Cursor also installs user-level hooks in `~/.cursor/hooks.json` automatically. They capture tool output, thought blocks, model and usage fields, subagent transcripts, and other lifecycle records that the main transcript omits; the collector queues those records locally before upload. Existing hooks are preserved, and `iteration disconnect cursor` removes only Iteration's entries. No Cursor approval or settings step is needed. A custom Cursor directory can be selected with `iteration connect cursor --cursor-home DIR`. Local history predating connection has transcript messages and tool calls, but tool results that Cursor never wrote to its transcript cannot be recovered. Cloud Agents are outside this local collector.
|
|
13
19
|
|
package/dist/cli.js
CHANGED
|
@@ -934,8 +934,8 @@ var require_src = __commonJS({
|
|
|
934
934
|
// src/cli.ts
|
|
935
935
|
import { spawnSync } from "node:child_process";
|
|
936
936
|
import os8 from "node:os";
|
|
937
|
-
import
|
|
938
|
-
import { promises as
|
|
937
|
+
import path21 from "node:path";
|
|
938
|
+
import { promises as fs17 } from "node:fs";
|
|
939
939
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
940
940
|
import { parseArgs } from "node:util";
|
|
941
941
|
|
|
@@ -1175,10 +1175,10 @@ function mergeDefs(...defs) {
|
|
|
1175
1175
|
function cloneDef(schema) {
|
|
1176
1176
|
return mergeDefs(schema._zod.def);
|
|
1177
1177
|
}
|
|
1178
|
-
function getElementAtPath(obj,
|
|
1179
|
-
if (!
|
|
1178
|
+
function getElementAtPath(obj, path22) {
|
|
1179
|
+
if (!path22)
|
|
1180
1180
|
return obj;
|
|
1181
|
-
return
|
|
1181
|
+
return path22.reduce((acc, key) => acc?.[key], obj);
|
|
1182
1182
|
}
|
|
1183
1183
|
function promiseAllObject(promisesObj) {
|
|
1184
1184
|
const keys = Object.keys(promisesObj);
|
|
@@ -1518,11 +1518,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1518
1518
|
}
|
|
1519
1519
|
return false;
|
|
1520
1520
|
}
|
|
1521
|
-
function prefixIssues(
|
|
1521
|
+
function prefixIssues(path22, issues) {
|
|
1522
1522
|
return issues.map((iss) => {
|
|
1523
1523
|
var _a3;
|
|
1524
1524
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1525
|
-
iss.path.unshift(
|
|
1525
|
+
iss.path.unshift(path22);
|
|
1526
1526
|
return iss;
|
|
1527
1527
|
});
|
|
1528
1528
|
}
|
|
@@ -1968,16 +1968,16 @@ function flattenError(error2, mapper = (issue2) => issue2.message) {
|
|
|
1968
1968
|
}
|
|
1969
1969
|
function formatError(error2, mapper = (issue2) => issue2.message) {
|
|
1970
1970
|
const fieldErrors = { _errors: [] };
|
|
1971
|
-
const processError = (error3,
|
|
1971
|
+
const processError = (error3, path22 = []) => {
|
|
1972
1972
|
for (const issue2 of error3.issues) {
|
|
1973
1973
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1974
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1974
|
+
issue2.errors.map((issues) => processError({ issues }, [...path22, ...issue2.path]));
|
|
1975
1975
|
} else if (issue2.code === "invalid_key") {
|
|
1976
|
-
processError({ issues: issue2.issues }, [...
|
|
1976
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1977
1977
|
} else if (issue2.code === "invalid_element") {
|
|
1978
|
-
processError({ issues: issue2.issues }, [...
|
|
1978
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1979
1979
|
} else {
|
|
1980
|
-
const fullpath = [...
|
|
1980
|
+
const fullpath = [...path22, ...issue2.path];
|
|
1981
1981
|
if (fullpath.length === 0) {
|
|
1982
1982
|
fieldErrors._errors.push(mapper(issue2));
|
|
1983
1983
|
} else {
|
|
@@ -7557,6 +7557,34 @@ var cursorBillingRequestSchema = object({
|
|
|
7557
7557
|
paidUsd: number2().finite().nonnegative().nullable()
|
|
7558
7558
|
}).strict()).max(5e3)
|
|
7559
7559
|
}).strict();
|
|
7560
|
+
function codexCreditSign(value) {
|
|
7561
|
+
const mantissa = value.split(/[eE]/, 1)[0];
|
|
7562
|
+
if (!/[1-9]/.test(mantissa))
|
|
7563
|
+
return 0;
|
|
7564
|
+
return mantissa.startsWith("-") ? -1 : 1;
|
|
7565
|
+
}
|
|
7566
|
+
var codexBillingRequestSchema = object({
|
|
7567
|
+
accountFingerprint: string2().regex(/^[0-9a-f]{64}$/),
|
|
7568
|
+
reconcile: boolean2(),
|
|
7569
|
+
tasks: array(object({
|
|
7570
|
+
threadId: uuid2(),
|
|
7571
|
+
createdAt: iso_exports.datetime().nullable(),
|
|
7572
|
+
descendantThreadIds: array(uuid2()).max(900),
|
|
7573
|
+
dataStatus: _enum([
|
|
7574
|
+
"available",
|
|
7575
|
+
"partial",
|
|
7576
|
+
"unavailable",
|
|
7577
|
+
"not_queried"
|
|
7578
|
+
]),
|
|
7579
|
+
usageSource: string2().max(100),
|
|
7580
|
+
balanceUsageCredits: string2().regex(/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d{1,3})?$/).max(128).nullable()
|
|
7581
|
+
}).strict()).max(100),
|
|
7582
|
+
authTurns: array(object({
|
|
7583
|
+
threadId: uuid2(),
|
|
7584
|
+
turnId: uuid2(),
|
|
7585
|
+
mode: _enum(["chatgpt", "api_key", "conflicting"])
|
|
7586
|
+
}).strict()).max(5e3)
|
|
7587
|
+
}).strict();
|
|
7560
7588
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7561
7589
|
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
7562
7590
|
var unpairedSurrogate = /[\uD800-\uDFFF]/u;
|
|
@@ -7605,6 +7633,12 @@ var acknowledgementSchema = object({
|
|
|
7605
7633
|
}).strict();
|
|
7606
7634
|
var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
7607
7635
|
var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
|
|
7636
|
+
var sessionCountSchema = number2().int().min(0).max(1e6);
|
|
7637
|
+
var collectorHistoryProgressSchema = object({
|
|
7638
|
+
sessions: sessionCountSchema,
|
|
7639
|
+
readSessions: sessionCountSchema,
|
|
7640
|
+
complete: boolean2()
|
|
7641
|
+
}).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.");
|
|
7608
7642
|
var collectorEnrollmentRequestSchema = object({
|
|
7609
7643
|
deviceName: string2().trim().min(1).max(160),
|
|
7610
7644
|
organizationId: string2().regex(UUID_PATTERN).optional(),
|
|
@@ -7905,7 +7939,7 @@ async function clerkDeviceSignIn(configuration) {
|
|
|
7905
7939
|
);
|
|
7906
7940
|
const verificationUrl = challenge.verification_uri_complete ?? challenge.verification_uri;
|
|
7907
7941
|
process.stdout.write(
|
|
7908
|
-
"Sign in to Iteration
|
|
7942
|
+
"Sign in to Iteration in your browser and confirm this code.\nCode: " + challenge.user_code + "\nLink: " + verificationUrl + "\n"
|
|
7909
7943
|
);
|
|
7910
7944
|
await openBrowser(verificationUrl);
|
|
7911
7945
|
return pollForAccessToken(
|
|
@@ -7953,7 +7987,7 @@ async function chooseOrganization(organizations, hasMore) {
|
|
|
7953
7987
|
);
|
|
7954
7988
|
}
|
|
7955
7989
|
if (process.stdin.isTTY !== true) {
|
|
7956
|
-
throw new Error("Run iteration interactively or pass --
|
|
7990
|
+
throw new Error("Run iteration interactively or pass --workspace.");
|
|
7957
7991
|
}
|
|
7958
7992
|
const prompt = createInterface({
|
|
7959
7993
|
input: process.stdin,
|
|
@@ -11278,11 +11312,11 @@ function requireLodash_sortby() {
|
|
|
11278
11312
|
function baseForOwn(object3, iteratee) {
|
|
11279
11313
|
return object3 && baseFor(object3, iteratee, keys);
|
|
11280
11314
|
}
|
|
11281
|
-
function baseGet(object3,
|
|
11282
|
-
|
|
11283
|
-
var index = 0, length =
|
|
11315
|
+
function baseGet(object3, path22) {
|
|
11316
|
+
path22 = isKey(path22, object3) ? [path22] : castPath(path22);
|
|
11317
|
+
var index = 0, length = path22.length;
|
|
11284
11318
|
while (object3 != null && index < length) {
|
|
11285
|
-
object3 = object3[toKey(
|
|
11319
|
+
object3 = object3[toKey(path22[index++])];
|
|
11286
11320
|
}
|
|
11287
11321
|
return index && index == length ? object3 : void 0;
|
|
11288
11322
|
}
|
|
@@ -11409,13 +11443,13 @@ function requireLodash_sortby() {
|
|
|
11409
11443
|
return object3 === source || baseIsMatch(object3, source, matchData);
|
|
11410
11444
|
};
|
|
11411
11445
|
}
|
|
11412
|
-
function baseMatchesProperty(
|
|
11413
|
-
if (isKey(
|
|
11414
|
-
return matchesStrictComparable(toKey(
|
|
11446
|
+
function baseMatchesProperty(path22, srcValue) {
|
|
11447
|
+
if (isKey(path22) && isStrictComparable(srcValue)) {
|
|
11448
|
+
return matchesStrictComparable(toKey(path22), srcValue);
|
|
11415
11449
|
}
|
|
11416
11450
|
return function(object3) {
|
|
11417
|
-
var objValue = get(object3,
|
|
11418
|
-
return objValue === void 0 && objValue === srcValue ? hasIn(object3,
|
|
11451
|
+
var objValue = get(object3, path22);
|
|
11452
|
+
return objValue === void 0 && objValue === srcValue ? hasIn(object3, path22) : baseIsEqual(srcValue, objValue, void 0, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
|
|
11419
11453
|
};
|
|
11420
11454
|
}
|
|
11421
11455
|
function baseOrderBy(collection, iteratees, orders) {
|
|
@@ -11431,9 +11465,9 @@ function requireLodash_sortby() {
|
|
|
11431
11465
|
return compareMultiple(object3, other, orders);
|
|
11432
11466
|
});
|
|
11433
11467
|
}
|
|
11434
|
-
function basePropertyDeep(
|
|
11468
|
+
function basePropertyDeep(path22) {
|
|
11435
11469
|
return function(object3) {
|
|
11436
|
-
return baseGet(object3,
|
|
11470
|
+
return baseGet(object3, path22);
|
|
11437
11471
|
};
|
|
11438
11472
|
}
|
|
11439
11473
|
function baseRest(func, start2) {
|
|
@@ -11688,11 +11722,11 @@ function requireLodash_sortby() {
|
|
|
11688
11722
|
return result;
|
|
11689
11723
|
};
|
|
11690
11724
|
}
|
|
11691
|
-
function hasPath(object3,
|
|
11692
|
-
|
|
11693
|
-
var result, index = -1, length =
|
|
11725
|
+
function hasPath(object3, path22, hasFunc) {
|
|
11726
|
+
path22 = isKey(path22, object3) ? [path22] : castPath(path22);
|
|
11727
|
+
var result, index = -1, length = path22.length;
|
|
11694
11728
|
while (++index < length) {
|
|
11695
|
-
var key = toKey(
|
|
11729
|
+
var key = toKey(path22[index]);
|
|
11696
11730
|
if (!(result = object3 != null && hasFunc(object3, key))) {
|
|
11697
11731
|
break;
|
|
11698
11732
|
}
|
|
@@ -11847,12 +11881,12 @@ function requireLodash_sortby() {
|
|
|
11847
11881
|
function toString(value) {
|
|
11848
11882
|
return value == null ? "" : baseToString(value);
|
|
11849
11883
|
}
|
|
11850
|
-
function get(object3,
|
|
11851
|
-
var result = object3 == null ? void 0 : baseGet(object3,
|
|
11884
|
+
function get(object3, path22, defaultValue) {
|
|
11885
|
+
var result = object3 == null ? void 0 : baseGet(object3, path22);
|
|
11852
11886
|
return result === void 0 ? defaultValue : result;
|
|
11853
11887
|
}
|
|
11854
|
-
function hasIn(object3,
|
|
11855
|
-
return object3 != null && hasPath(object3,
|
|
11888
|
+
function hasIn(object3, path22) {
|
|
11889
|
+
return object3 != null && hasPath(object3, path22, baseHasIn);
|
|
11856
11890
|
}
|
|
11857
11891
|
function keys(object3) {
|
|
11858
11892
|
return isArrayLike(object3) ? arrayLikeKeys(object3) : baseKeys(object3);
|
|
@@ -11860,8 +11894,8 @@ function requireLodash_sortby() {
|
|
|
11860
11894
|
function identity(value) {
|
|
11861
11895
|
return value;
|
|
11862
11896
|
}
|
|
11863
|
-
function property(
|
|
11864
|
-
return isKey(
|
|
11897
|
+
function property(path22) {
|
|
11898
|
+
return isKey(path22) ? baseProperty(toKey(path22)) : basePropertyDeep(path22);
|
|
11865
11899
|
}
|
|
11866
11900
|
module.exports = sortBy;
|
|
11867
11901
|
})(lodash_sortby, lodash_sortby.exports);
|
|
@@ -12292,16 +12326,16 @@ async function reportIfFoundPrivateKeyP12Format({ source, context, t }) {
|
|
|
12292
12326
|
return;
|
|
12293
12327
|
}
|
|
12294
12328
|
try {
|
|
12295
|
-
const
|
|
12296
|
-
const
|
|
12297
|
-
const pfxBytes = new Uint8Array(
|
|
12329
|
+
const fs18 = await import("node:fs");
|
|
12330
|
+
const path22 = await import("node:path");
|
|
12331
|
+
const pfxBytes = new Uint8Array(fs18.readFileSync(source.filePath));
|
|
12298
12332
|
const isGcpServiceAccountP12 = await verifyPkcs12Mac(pfxBytes, GCP_SERVICE_ACCOUNT_P12_PASSWORD);
|
|
12299
12333
|
if (!isGcpServiceAccountP12) {
|
|
12300
12334
|
return;
|
|
12301
12335
|
}
|
|
12302
12336
|
context.report({
|
|
12303
12337
|
message: t("PrivateKeyP12", {
|
|
12304
|
-
FILE_NAME:
|
|
12338
|
+
FILE_NAME: path22.basename(source.filePath)
|
|
12305
12339
|
}),
|
|
12306
12340
|
range: [0, source.content.length]
|
|
12307
12341
|
});
|
|
@@ -15053,6 +15087,38 @@ async function claudeFiles(root) {
|
|
|
15053
15087
|
}
|
|
15054
15088
|
return files.sort();
|
|
15055
15089
|
}
|
|
15090
|
+
function isSessionFile(filename) {
|
|
15091
|
+
return !filename.includes(`${path8.sep}subagents${path8.sep}`);
|
|
15092
|
+
}
|
|
15093
|
+
async function countSavedSessions(source, home) {
|
|
15094
|
+
return (await sourceFiles(source, home)).filter(isSessionFile).length;
|
|
15095
|
+
}
|
|
15096
|
+
async function sourceFilesNewestFirst(source, home) {
|
|
15097
|
+
const files = await sourceFiles(source, home);
|
|
15098
|
+
const listed = [];
|
|
15099
|
+
for (let start2 = 0; start2 < files.length; start2 += 64) {
|
|
15100
|
+
const batch = files.slice(start2, start2 + 64);
|
|
15101
|
+
const stats = await Promise.all(batch.map((filename) => fs8.stat(filename)));
|
|
15102
|
+
for (const [index, stat] of stats.entries()) {
|
|
15103
|
+
const filename = batch[index];
|
|
15104
|
+
if (filename === void 0) continue;
|
|
15105
|
+
listed.push({
|
|
15106
|
+
filename,
|
|
15107
|
+
size: stat.size,
|
|
15108
|
+
modifiedMs: stat.mtimeMs,
|
|
15109
|
+
session: isSessionFile(filename)
|
|
15110
|
+
});
|
|
15111
|
+
}
|
|
15112
|
+
}
|
|
15113
|
+
listed.sort(
|
|
15114
|
+
(left, right) => right.modifiedMs - left.modifiedMs || left.filename.localeCompare(right.filename)
|
|
15115
|
+
);
|
|
15116
|
+
return listed.map(({ filename, size, session }) => ({
|
|
15117
|
+
filename,
|
|
15118
|
+
size,
|
|
15119
|
+
session
|
|
15120
|
+
}));
|
|
15121
|
+
}
|
|
15056
15122
|
function sourceFiles(source, home) {
|
|
15057
15123
|
const directory = path8.resolve(home);
|
|
15058
15124
|
if (source === "codex") return rolloutFiles(path8.join(directory, "sessions"));
|
|
@@ -15966,11 +16032,11 @@ async function connectedConfig(source) {
|
|
|
15966
16032
|
}
|
|
15967
16033
|
|
|
15968
16034
|
// src/history-import.ts
|
|
15969
|
-
import
|
|
16035
|
+
import path20 from "node:path";
|
|
15970
16036
|
|
|
15971
16037
|
// src/service.ts
|
|
15972
|
-
import { promises as
|
|
15973
|
-
import
|
|
16038
|
+
import { promises as fs16 } from "node:fs";
|
|
16039
|
+
import path19 from "node:path";
|
|
15974
16040
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
15975
16041
|
|
|
15976
16042
|
// src/collector.ts
|
|
@@ -16993,9 +17059,13 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
16993
17059
|
}
|
|
16994
17060
|
const cursors = { ...state.cursors };
|
|
16995
17061
|
const skipping = new Set(state.skipping);
|
|
17062
|
+
const sessionFiles = await sourceFilesNewestFirst(source, sourceDirectory);
|
|
17063
|
+
const listedSizes = new Map(
|
|
17064
|
+
sessionFiles.map((file) => [file.filename, file.size])
|
|
17065
|
+
);
|
|
16996
17066
|
const files = [
|
|
16997
17067
|
...source === "claude" ? await claudeGitHookFiles(config2.dataDirectory) : [],
|
|
16998
|
-
...
|
|
17068
|
+
...sessionFiles.map((file) => file.filename),
|
|
16999
17069
|
...source === "cursor" ? await cursorHookFiles(config2.dataDirectory) : []
|
|
17000
17070
|
];
|
|
17001
17071
|
const completedHooks = [];
|
|
@@ -17011,15 +17081,15 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
17011
17081
|
caughtUp = false;
|
|
17012
17082
|
break;
|
|
17013
17083
|
}
|
|
17014
|
-
const
|
|
17084
|
+
const size = listedSizes.get(filename) ?? (await fs13.stat(filename)).size;
|
|
17015
17085
|
let offset = cursors[filename] ?? 0;
|
|
17016
|
-
if (offset >
|
|
17086
|
+
if (offset > size) {
|
|
17017
17087
|
logGap(filename, offset, "rollout_file_shrank_resetting_cursor");
|
|
17018
17088
|
offset = 0;
|
|
17019
17089
|
skipping.delete(filename);
|
|
17020
17090
|
}
|
|
17021
17091
|
const bytesToRead = Math.min(
|
|
17022
|
-
|
|
17092
|
+
size - offset,
|
|
17023
17093
|
MAX_SCAN_BYTES_PER_FILE,
|
|
17024
17094
|
remainingBudget
|
|
17025
17095
|
);
|
|
@@ -17039,7 +17109,7 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
17039
17109
|
} finally {
|
|
17040
17110
|
await handle.close();
|
|
17041
17111
|
}
|
|
17042
|
-
if (readEnd <
|
|
17112
|
+
if (readEnd < size) caughtUp = false;
|
|
17043
17113
|
if (skipping.has(filename)) {
|
|
17044
17114
|
const newline = buffer.indexOf(10);
|
|
17045
17115
|
if (newline < 0) {
|
|
@@ -17102,7 +17172,7 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
17102
17172
|
lineStart = buffer.length;
|
|
17103
17173
|
}
|
|
17104
17174
|
cursors[filename] = offset + lineStart;
|
|
17105
|
-
if ((source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) || source === "claude" && filename.includes(`${path16.sep}git-queue${path16.sep}`)) && cursors[filename] ===
|
|
17175
|
+
if ((source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) || source === "claude" && filename.includes(`${path16.sep}git-queue${path16.sep}`)) && cursors[filename] === size)
|
|
17106
17176
|
completedHooks.push(filename);
|
|
17107
17177
|
}
|
|
17108
17178
|
await appendEvents(events, paths);
|
|
@@ -17119,7 +17189,17 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
17119
17189
|
},
|
|
17120
17190
|
paths
|
|
17121
17191
|
);
|
|
17122
|
-
|
|
17192
|
+
const sessions = sessionFiles.filter((file) => file.session);
|
|
17193
|
+
return {
|
|
17194
|
+
discovered: events.length,
|
|
17195
|
+
caughtUp,
|
|
17196
|
+
history: {
|
|
17197
|
+
sessions: sessions.length,
|
|
17198
|
+
readSessions: sessions.filter(
|
|
17199
|
+
(file) => (cursors[file.filename] ?? 0) >= file.size
|
|
17200
|
+
).length
|
|
17201
|
+
}
|
|
17202
|
+
};
|
|
17123
17203
|
}
|
|
17124
17204
|
async function uploadPendingOnce(config2, paths, fetcher = collectorFetch, signal, onAcknowledged) {
|
|
17125
17205
|
let uploaded = 0;
|
|
@@ -17189,7 +17269,7 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
|
|
|
17189
17269
|
signal,
|
|
17190
17270
|
onAcknowledged
|
|
17191
17271
|
);
|
|
17192
|
-
const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? { discovered: 0, caughtUp: false } : await scanSourceOnce(config2, paths, signal);
|
|
17272
|
+
const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? { discovered: 0, caughtUp: false, history: null } : await scanSourceOnce(config2, paths, signal);
|
|
17193
17273
|
uploaded += await uploadPendingOnce(
|
|
17194
17274
|
config2,
|
|
17195
17275
|
paths,
|
|
@@ -17201,10 +17281,66 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
|
|
|
17201
17281
|
discovered: scan.discovered,
|
|
17202
17282
|
uploaded,
|
|
17203
17283
|
outboxBytes: await outboxSize(paths),
|
|
17204
|
-
historyCaughtUp: scan.caughtUp
|
|
17284
|
+
historyCaughtUp: scan.caughtUp,
|
|
17285
|
+
history: scan.history
|
|
17205
17286
|
};
|
|
17206
17287
|
}
|
|
17207
17288
|
|
|
17289
|
+
// src/history-progress.ts
|
|
17290
|
+
var REQUEST_TIMEOUT_MS3 = 15e3;
|
|
17291
|
+
var REPORT_INTERVAL_MS = 1e4;
|
|
17292
|
+
var HistoryProgressError = class extends Error {
|
|
17293
|
+
constructor(message, options) {
|
|
17294
|
+
super(message, options);
|
|
17295
|
+
this.name = "HistoryProgressError";
|
|
17296
|
+
}
|
|
17297
|
+
};
|
|
17298
|
+
function unchanged(left, right) {
|
|
17299
|
+
return right !== null && left.sessions === right.sessions && left.readSessions === right.readSessions && left.complete === right.complete;
|
|
17300
|
+
}
|
|
17301
|
+
var HistoryProgressReporter = class {
|
|
17302
|
+
#sent = null;
|
|
17303
|
+
#sentAtMs = Number.NEGATIVE_INFINITY;
|
|
17304
|
+
#completed = false;
|
|
17305
|
+
async report(config2, progress, fetcher, signal, nowMs) {
|
|
17306
|
+
if (this.#completed || unchanged(progress, this.#sent)) return;
|
|
17307
|
+
if (!progress.complete && nowMs - this.#sentAtMs < REPORT_INTERVAL_MS)
|
|
17308
|
+
return;
|
|
17309
|
+
let response;
|
|
17310
|
+
try {
|
|
17311
|
+
response = await fetcher(
|
|
17312
|
+
new URL("/api/traces/collector-progress", config2.endpoint),
|
|
17313
|
+
{
|
|
17314
|
+
method: "POST",
|
|
17315
|
+
headers: {
|
|
17316
|
+
authorization: `Bearer ${config2.token}`,
|
|
17317
|
+
"content-type": "application/json"
|
|
17318
|
+
},
|
|
17319
|
+
body: JSON.stringify(progress),
|
|
17320
|
+
signal: AbortSignal.any([
|
|
17321
|
+
signal,
|
|
17322
|
+
AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
|
|
17323
|
+
])
|
|
17324
|
+
}
|
|
17325
|
+
);
|
|
17326
|
+
} catch (error2) {
|
|
17327
|
+
if (signal.aborted) throw error2;
|
|
17328
|
+
throw new HistoryProgressError(
|
|
17329
|
+
"History progress could not reach Iteration.",
|
|
17330
|
+
{ cause: error2 }
|
|
17331
|
+
);
|
|
17332
|
+
}
|
|
17333
|
+
if (response.status !== 204) {
|
|
17334
|
+
throw new HistoryProgressError(
|
|
17335
|
+
`History progress failed with HTTP ${response.status}.`
|
|
17336
|
+
);
|
|
17337
|
+
}
|
|
17338
|
+
this.#sent = progress;
|
|
17339
|
+
this.#sentAtMs = nowMs;
|
|
17340
|
+
this.#completed = progress.complete;
|
|
17341
|
+
}
|
|
17342
|
+
};
|
|
17343
|
+
|
|
17208
17344
|
// src/cursor-billing.ts
|
|
17209
17345
|
import { createHash as createHash7 } from "node:crypto";
|
|
17210
17346
|
import { promises as fs14 } from "node:fs";
|
|
@@ -17500,6 +17636,485 @@ async function syncCursorBilling(config2, fetcher = collectorFetch, signal) {
|
|
|
17500
17636
|
};
|
|
17501
17637
|
}
|
|
17502
17638
|
|
|
17639
|
+
// src/codex-billing.ts
|
|
17640
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
17641
|
+
import { promises as fs15 } from "node:fs";
|
|
17642
|
+
import path18 from "node:path";
|
|
17643
|
+
var TASK_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage/thread_usage/query_v2";
|
|
17644
|
+
var UUID3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17645
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
17646
|
+
var TASK_BATCH_SIZE = 50;
|
|
17647
|
+
var MAX_HEADER_BYTES = 3 * 1024 * 1024;
|
|
17648
|
+
var stateSchema2 = object({
|
|
17649
|
+
accountFingerprint: string2().regex(/^[0-9a-f]{64}$/),
|
|
17650
|
+
lastSyncedAtMs: number2().int().nonnegative(),
|
|
17651
|
+
lastAuditAtMs: number2().int().nonnegative(),
|
|
17652
|
+
unavailableThreadIds: array(uuid2())
|
|
17653
|
+
});
|
|
17654
|
+
var authSchema = object({
|
|
17655
|
+
auth_mode: string2().optional(),
|
|
17656
|
+
tokens: object({
|
|
17657
|
+
access_token: string2().min(1),
|
|
17658
|
+
account_id: string2().min(1)
|
|
17659
|
+
}).optional()
|
|
17660
|
+
});
|
|
17661
|
+
var taskUsageSchema = object({
|
|
17662
|
+
threads: array(
|
|
17663
|
+
object({
|
|
17664
|
+
thread_id: uuid2(),
|
|
17665
|
+
data_status: _enum(["available", "partial", "unavailable"]),
|
|
17666
|
+
usage_source: string2(),
|
|
17667
|
+
balance_usage_credits: string2().nullable().optional()
|
|
17668
|
+
})
|
|
17669
|
+
).max(TASK_BATCH_SIZE)
|
|
17670
|
+
});
|
|
17671
|
+
var sessionMetaSchema = object({
|
|
17672
|
+
type: literal("session_meta"),
|
|
17673
|
+
payload: object({
|
|
17674
|
+
id: uuid2(),
|
|
17675
|
+
timestamp: string2().optional(),
|
|
17676
|
+
creator_account_id: string2().nullable().optional(),
|
|
17677
|
+
model_provider: string2().nullable().optional(),
|
|
17678
|
+
parent_thread_id: string2().nullable().optional(),
|
|
17679
|
+
source: unknown().optional()
|
|
17680
|
+
})
|
|
17681
|
+
});
|
|
17682
|
+
var subagentSourceSchema = object({
|
|
17683
|
+
subagent: object({
|
|
17684
|
+
thread_spawn: object({ parent_thread_id: string2() }).optional()
|
|
17685
|
+
}).optional()
|
|
17686
|
+
});
|
|
17687
|
+
var syncResultSchema = object({ matched: number2().int().nonnegative() });
|
|
17688
|
+
function fingerprint(value) {
|
|
17689
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
17690
|
+
}
|
|
17691
|
+
async function readAuth(codexHome) {
|
|
17692
|
+
let contents;
|
|
17693
|
+
try {
|
|
17694
|
+
contents = await fs15.readFile(path18.join(codexHome, "auth.json"), "utf8");
|
|
17695
|
+
} catch (error2) {
|
|
17696
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
17697
|
+
return {
|
|
17698
|
+
accessToken: null,
|
|
17699
|
+
accountId: null,
|
|
17700
|
+
accountFingerprint: fingerprint(
|
|
17701
|
+
"codex-local-auth-logs:no-chatgpt-token"
|
|
17702
|
+
)
|
|
17703
|
+
};
|
|
17704
|
+
throw new Error("Could not read Codex authentication.", { cause: error2 });
|
|
17705
|
+
}
|
|
17706
|
+
let decoded;
|
|
17707
|
+
try {
|
|
17708
|
+
decoded = JSON.parse(contents);
|
|
17709
|
+
} catch (error2) {
|
|
17710
|
+
throw new Error("Codex authentication is not valid JSON.", {
|
|
17711
|
+
cause: error2
|
|
17712
|
+
});
|
|
17713
|
+
}
|
|
17714
|
+
const parsed = authSchema.safeParse(decoded);
|
|
17715
|
+
if (!parsed.success)
|
|
17716
|
+
throw new Error("Codex authentication has an unexpected shape.");
|
|
17717
|
+
const accountId = parsed.data.tokens?.account_id ?? null;
|
|
17718
|
+
return {
|
|
17719
|
+
accessToken: parsed.data.auth_mode === "chatgpt" ? parsed.data.tokens?.access_token ?? null : null,
|
|
17720
|
+
accountId,
|
|
17721
|
+
accountFingerprint: fingerprint(
|
|
17722
|
+
`${accountId ?? "codex-local-auth-logs"}:${parsed.data.auth_mode === "chatgpt" && parsed.data.tokens?.access_token ? "chatgpt" : "no-chatgpt-token"}`
|
|
17723
|
+
)
|
|
17724
|
+
};
|
|
17725
|
+
}
|
|
17726
|
+
async function readState(filename) {
|
|
17727
|
+
let contents;
|
|
17728
|
+
try {
|
|
17729
|
+
contents = await fs15.readFile(filename, "utf8");
|
|
17730
|
+
} catch (error2) {
|
|
17731
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
17732
|
+
return null;
|
|
17733
|
+
throw new Error("Could not read Codex billing sync state.", {
|
|
17734
|
+
cause: error2
|
|
17735
|
+
});
|
|
17736
|
+
}
|
|
17737
|
+
let decoded;
|
|
17738
|
+
try {
|
|
17739
|
+
decoded = JSON.parse(contents);
|
|
17740
|
+
} catch (error2) {
|
|
17741
|
+
throw new Error("Codex billing sync state is not valid JSON.", {
|
|
17742
|
+
cause: error2
|
|
17743
|
+
});
|
|
17744
|
+
}
|
|
17745
|
+
const parsed = stateSchema2.safeParse(decoded);
|
|
17746
|
+
if (!parsed.success) throw new Error("Codex billing sync state is invalid.");
|
|
17747
|
+
return parsed.data;
|
|
17748
|
+
}
|
|
17749
|
+
async function saveState2(filename, state) {
|
|
17750
|
+
const temporary = `${filename}.${process.pid}.tmp`;
|
|
17751
|
+
const handle = await fs15.open(temporary, "w", 384);
|
|
17752
|
+
try {
|
|
17753
|
+
await handle.writeFile(`${JSON.stringify(state)}
|
|
17754
|
+
`);
|
|
17755
|
+
await handle.sync();
|
|
17756
|
+
} finally {
|
|
17757
|
+
await handle.close();
|
|
17758
|
+
}
|
|
17759
|
+
await fs15.rename(temporary, filename);
|
|
17760
|
+
}
|
|
17761
|
+
async function localThread(filename) {
|
|
17762
|
+
const stat = await fs15.stat(filename);
|
|
17763
|
+
const handle = await fs15.open(filename, "r");
|
|
17764
|
+
let header = null;
|
|
17765
|
+
try {
|
|
17766
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
17767
|
+
const chunks = [];
|
|
17768
|
+
let offset = 0;
|
|
17769
|
+
while (offset < Math.min(stat.size, MAX_HEADER_BYTES)) {
|
|
17770
|
+
const read = await handle.read(
|
|
17771
|
+
buffer,
|
|
17772
|
+
0,
|
|
17773
|
+
Math.min(buffer.length, MAX_HEADER_BYTES - offset),
|
|
17774
|
+
offset
|
|
17775
|
+
);
|
|
17776
|
+
if (read.bytesRead === 0) break;
|
|
17777
|
+
const newline = buffer.subarray(0, read.bytesRead).indexOf(10);
|
|
17778
|
+
if (newline >= 0) {
|
|
17779
|
+
chunks.push(Buffer.from(buffer.subarray(0, newline)));
|
|
17780
|
+
header = Buffer.concat(chunks);
|
|
17781
|
+
break;
|
|
17782
|
+
}
|
|
17783
|
+
chunks.push(Buffer.from(buffer.subarray(0, read.bytesRead)));
|
|
17784
|
+
offset += read.bytesRead;
|
|
17785
|
+
}
|
|
17786
|
+
} finally {
|
|
17787
|
+
await handle.close();
|
|
17788
|
+
}
|
|
17789
|
+
if (header === null) return null;
|
|
17790
|
+
let raw;
|
|
17791
|
+
try {
|
|
17792
|
+
raw = JSON.parse(header.toString("utf8"));
|
|
17793
|
+
} catch {
|
|
17794
|
+
return null;
|
|
17795
|
+
}
|
|
17796
|
+
const record3 = sessionMetaSchema.safeParse(raw);
|
|
17797
|
+
if (!record3.success) return null;
|
|
17798
|
+
const payload = record3.data.payload;
|
|
17799
|
+
if (!filename.endsWith(`${payload.id}.jsonl`)) return null;
|
|
17800
|
+
const source = subagentSourceSchema.safeParse(payload.source);
|
|
17801
|
+
const parentThreadId = payload.parent_thread_id ?? (source.success ? source.data.subagent?.thread_spawn?.parent_thread_id : null) ?? null;
|
|
17802
|
+
if (parentThreadId !== null && !UUID3.test(parentThreadId)) return null;
|
|
17803
|
+
return {
|
|
17804
|
+
threadId: payload.id,
|
|
17805
|
+
parentThreadId,
|
|
17806
|
+
createdAt: payload.timestamp && !Number.isNaN(Date.parse(payload.timestamp)) ? new Date(payload.timestamp).toISOString() : null,
|
|
17807
|
+
accountId: payload.creator_account_id ?? null,
|
|
17808
|
+
modelProvider: payload.model_provider ?? null,
|
|
17809
|
+
mtimeMs: stat.mtimeMs
|
|
17810
|
+
};
|
|
17811
|
+
}
|
|
17812
|
+
function groupCodexTasks(threads, accountId) {
|
|
17813
|
+
const byId = new Map(
|
|
17814
|
+
threads.filter(
|
|
17815
|
+
(thread) => thread.accountId === null || thread.accountId === accountId
|
|
17816
|
+
).map((thread) => [thread.threadId, thread])
|
|
17817
|
+
);
|
|
17818
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
17819
|
+
for (const thread of byId.values()) {
|
|
17820
|
+
if (thread.parentThreadId === null)
|
|
17821
|
+
tasks.set(thread.threadId, {
|
|
17822
|
+
threadId: thread.threadId,
|
|
17823
|
+
createdAt: thread.createdAt,
|
|
17824
|
+
descendantThreadIds: [],
|
|
17825
|
+
mtimeMs: thread.mtimeMs
|
|
17826
|
+
});
|
|
17827
|
+
}
|
|
17828
|
+
for (const thread of byId.values()) {
|
|
17829
|
+
if (thread.parentThreadId === null) continue;
|
|
17830
|
+
let ancestor = thread.parentThreadId;
|
|
17831
|
+
const seen = /* @__PURE__ */ new Set([thread.threadId]);
|
|
17832
|
+
while (ancestor !== null && !seen.has(ancestor)) {
|
|
17833
|
+
seen.add(ancestor);
|
|
17834
|
+
const parent = byId.get(ancestor);
|
|
17835
|
+
if (parent === void 0) break;
|
|
17836
|
+
if (parent.parentThreadId === null) {
|
|
17837
|
+
const task = tasks.get(parent.threadId);
|
|
17838
|
+
if (task !== void 0) {
|
|
17839
|
+
task.descendantThreadIds.push(thread.threadId);
|
|
17840
|
+
task.mtimeMs = Math.max(task.mtimeMs, thread.mtimeMs);
|
|
17841
|
+
}
|
|
17842
|
+
break;
|
|
17843
|
+
}
|
|
17844
|
+
ancestor = parent.parentThreadId;
|
|
17845
|
+
}
|
|
17846
|
+
}
|
|
17847
|
+
return [...tasks.values()].map((task) => ({
|
|
17848
|
+
...task,
|
|
17849
|
+
descendantThreadIds: task.descendantThreadIds.sort()
|
|
17850
|
+
}));
|
|
17851
|
+
}
|
|
17852
|
+
function parseCodexAuthLog(threadId, body) {
|
|
17853
|
+
if (threadId === null || !UUID3.test(threadId) || body === null) return null;
|
|
17854
|
+
const turn = /try_run_sampling_request\{turn_id=([0-9a-f-]{36})\b/i.exec(
|
|
17855
|
+
body
|
|
17856
|
+
);
|
|
17857
|
+
const auth = /\bauth_mode=Some\(([^)]+)\)/.exec(body);
|
|
17858
|
+
if (turn === null || auth === null || !UUID3.test(turn[1])) return null;
|
|
17859
|
+
const mode = auth[1] === "Chatgpt" ? "chatgpt" : auth[1] === "ApiKey" || auth[1] === "BedrockApiKey" || auth[1] === "BedrockAccessKeys" ? "api_key" : "conflicting";
|
|
17860
|
+
return { threadId, turnId: turn[1], mode };
|
|
17861
|
+
}
|
|
17862
|
+
async function localAuthTurns(codexHome, modelProviders) {
|
|
17863
|
+
const filename = path18.join(codexHome, "logs_2.sqlite");
|
|
17864
|
+
try {
|
|
17865
|
+
await fs15.access(filename);
|
|
17866
|
+
} catch (error2) {
|
|
17867
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
17868
|
+
return [];
|
|
17869
|
+
throw new Error("Could not access Codex's local logs.", { cause: error2 });
|
|
17870
|
+
}
|
|
17871
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
17872
|
+
let database;
|
|
17873
|
+
try {
|
|
17874
|
+
database = new DatabaseSync(filename, { readOnly: true });
|
|
17875
|
+
} catch (error2) {
|
|
17876
|
+
throw new Error("Could not open Codex's local logs.", { cause: error2 });
|
|
17877
|
+
}
|
|
17878
|
+
const turns = /* @__PURE__ */ new Map();
|
|
17879
|
+
const logRowSchema = object({
|
|
17880
|
+
thread_id: string2().nullable(),
|
|
17881
|
+
feedback_log_body: string2().nullable()
|
|
17882
|
+
});
|
|
17883
|
+
try {
|
|
17884
|
+
const rows = database.prepare(
|
|
17885
|
+
"SELECT thread_id, feedback_log_body FROM logs WHERE target = 'feedback_tags' AND feedback_log_body LIKE '%auth_mode=Some(%'"
|
|
17886
|
+
).iterate();
|
|
17887
|
+
for (const raw of rows) {
|
|
17888
|
+
const row = logRowSchema.safeParse(raw);
|
|
17889
|
+
if (!row.success)
|
|
17890
|
+
throw new Error("Codex's local log row has an invalid shape.");
|
|
17891
|
+
if (row.data.thread_id === null || !modelProviders.has(row.data.thread_id))
|
|
17892
|
+
continue;
|
|
17893
|
+
const parsed = parseCodexAuthLog(
|
|
17894
|
+
row.data.thread_id,
|
|
17895
|
+
row.data.feedback_log_body
|
|
17896
|
+
);
|
|
17897
|
+
if (parsed === null) continue;
|
|
17898
|
+
const mode = parsed.mode === "chatgpt" && modelProviders.get(parsed.threadId) !== "openai" ? "conflicting" : parsed.mode;
|
|
17899
|
+
const key = `${parsed.threadId}:${parsed.turnId}`;
|
|
17900
|
+
const prior = turns.get(key);
|
|
17901
|
+
turns.set(key, {
|
|
17902
|
+
...parsed,
|
|
17903
|
+
mode: prior === void 0 || prior.mode === mode ? mode : "conflicting"
|
|
17904
|
+
});
|
|
17905
|
+
}
|
|
17906
|
+
} finally {
|
|
17907
|
+
database.close();
|
|
17908
|
+
}
|
|
17909
|
+
return [...turns.values()];
|
|
17910
|
+
}
|
|
17911
|
+
async function queryTaskUsage(tasks, accessToken, accountId, signal) {
|
|
17912
|
+
const response = await fetch(TASK_USAGE_URL, {
|
|
17913
|
+
method: "POST",
|
|
17914
|
+
headers: {
|
|
17915
|
+
Authorization: `Bearer ${accessToken}`,
|
|
17916
|
+
"ChatGPT-Account-Id": accountId,
|
|
17917
|
+
"Content-Type": "application/json",
|
|
17918
|
+
"User-Agent": "codex-cli"
|
|
17919
|
+
},
|
|
17920
|
+
body: JSON.stringify({
|
|
17921
|
+
threads: tasks.map((task) => ({
|
|
17922
|
+
thread_id: task.threadId,
|
|
17923
|
+
created_at: task.createdAt,
|
|
17924
|
+
descendant_thread_ids: task.descendantThreadIds
|
|
17925
|
+
}))
|
|
17926
|
+
}),
|
|
17927
|
+
signal: AbortSignal.any([
|
|
17928
|
+
AbortSignal.timeout(2e4),
|
|
17929
|
+
...signal ? [signal] : []
|
|
17930
|
+
])
|
|
17931
|
+
});
|
|
17932
|
+
if (!response.ok)
|
|
17933
|
+
throw new Error(
|
|
17934
|
+
`Codex task usage lookup failed with HTTP ${response.status}.`
|
|
17935
|
+
);
|
|
17936
|
+
const parsed = taskUsageSchema.safeParse(await response.json());
|
|
17937
|
+
if (!parsed.success) throw new Error("Codex returned invalid task usage.");
|
|
17938
|
+
const requested = new Set(tasks.map((task) => task.threadId));
|
|
17939
|
+
const byId = new Map(tasks.map((task) => [task.threadId, task]));
|
|
17940
|
+
const result = /* @__PURE__ */ new Map();
|
|
17941
|
+
for (const row of parsed.data.threads) {
|
|
17942
|
+
if (!requested.has(row.thread_id) || result.has(row.thread_id))
|
|
17943
|
+
throw new Error("Codex returned an unexpected task usage ID.");
|
|
17944
|
+
const task = byId.get(row.thread_id);
|
|
17945
|
+
result.set(row.thread_id, {
|
|
17946
|
+
threadId: row.thread_id,
|
|
17947
|
+
createdAt: task.createdAt,
|
|
17948
|
+
descendantThreadIds: [...task.descendantThreadIds],
|
|
17949
|
+
dataStatus: row.data_status,
|
|
17950
|
+
usageSource: row.usage_source,
|
|
17951
|
+
balanceUsageCredits: row.balance_usage_credits ?? null
|
|
17952
|
+
});
|
|
17953
|
+
}
|
|
17954
|
+
return result;
|
|
17955
|
+
}
|
|
17956
|
+
async function syncCodexBilling(config2, fetcher = collectorFetch, signal) {
|
|
17957
|
+
const auth = await readAuth(config2.codexHome);
|
|
17958
|
+
const stateFile = path18.join(config2.dataDirectory, "codex-billing-sync.json");
|
|
17959
|
+
const prior = await readState(stateFile);
|
|
17960
|
+
const nowMs = Date.now();
|
|
17961
|
+
const history = prior === null || prior.accountFingerprint !== auth.accountFingerprint;
|
|
17962
|
+
const audit = !history && nowMs - prior.lastAuditAtMs >= 7 * DAY_MS2;
|
|
17963
|
+
const cutoff = history ? 0 : Math.min(
|
|
17964
|
+
prior.lastSyncedAtMs - DAY_MS2,
|
|
17965
|
+
nowMs - (audit ? 35 : 7) * DAY_MS2
|
|
17966
|
+
);
|
|
17967
|
+
const filenames = await sourceFiles("codex", config2.codexHome);
|
|
17968
|
+
const threads = [];
|
|
17969
|
+
for (const filename of filenames) {
|
|
17970
|
+
signal?.throwIfAborted();
|
|
17971
|
+
const thread = await localThread(filename);
|
|
17972
|
+
if (thread !== null) threads.push(thread);
|
|
17973
|
+
}
|
|
17974
|
+
const allTasks = groupCodexTasks(threads, auth.accountId);
|
|
17975
|
+
const retryIds = new Set(audit ? prior?.unavailableThreadIds ?? [] : []);
|
|
17976
|
+
const selected = allTasks.filter(
|
|
17977
|
+
(task) => task.mtimeMs >= cutoff || retryIds.has(task.threadId)
|
|
17978
|
+
);
|
|
17979
|
+
if (selected.length === 0)
|
|
17980
|
+
return {
|
|
17981
|
+
tasks: 0,
|
|
17982
|
+
included: 0,
|
|
17983
|
+
creditTasks: 0,
|
|
17984
|
+
apiTurns: 0,
|
|
17985
|
+
skipped: false
|
|
17986
|
+
};
|
|
17987
|
+
const selectedIds = new Set(
|
|
17988
|
+
selected.flatMap((task) => [task.threadId, ...task.descendantThreadIds])
|
|
17989
|
+
);
|
|
17990
|
+
const modelProviders = new Map(
|
|
17991
|
+
threads.filter((thread) => selectedIds.has(thread.threadId)).map((thread) => [thread.threadId, thread.modelProvider])
|
|
17992
|
+
);
|
|
17993
|
+
const authTurns = await localAuthTurns(config2.codexHome, modelProviders);
|
|
17994
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
17995
|
+
for (const turn of authTurns) {
|
|
17996
|
+
const turns = byThread.get(turn.threadId) ?? [];
|
|
17997
|
+
turns.push(turn);
|
|
17998
|
+
byThread.set(turn.threadId, turns);
|
|
17999
|
+
}
|
|
18000
|
+
const localTaskIds = new Set(allTasks.map((task) => task.threadId));
|
|
18001
|
+
const unavailable = new Set(
|
|
18002
|
+
history ? [] : (prior?.unavailableThreadIds ?? []).filter(
|
|
18003
|
+
(id) => localTaskIds.has(id)
|
|
18004
|
+
)
|
|
18005
|
+
);
|
|
18006
|
+
let included = 0;
|
|
18007
|
+
let creditTasks = 0;
|
|
18008
|
+
let apiTurns = 0;
|
|
18009
|
+
const endpoint = new URL(config2.endpoint);
|
|
18010
|
+
endpoint.pathname = "/api/traces/codex-billing";
|
|
18011
|
+
const batches = [];
|
|
18012
|
+
let current = [];
|
|
18013
|
+
let currentIds = 0;
|
|
18014
|
+
let currentTurns = 0;
|
|
18015
|
+
for (const task of selected) {
|
|
18016
|
+
const size = 1 + task.descendantThreadIds.length;
|
|
18017
|
+
const turns = [task.threadId, ...task.descendantThreadIds].reduce(
|
|
18018
|
+
(total, threadId) => total + (byThread.get(threadId)?.length ?? 0),
|
|
18019
|
+
0
|
|
18020
|
+
);
|
|
18021
|
+
if (size > 1e3)
|
|
18022
|
+
throw new Error("Codex task exceeds the billing thread limit.");
|
|
18023
|
+
if (current.length >= TASK_BATCH_SIZE || currentIds + size > 1e3 || currentTurns + turns > 5e3) {
|
|
18024
|
+
if (current.length > 0) batches.push(current);
|
|
18025
|
+
current = [];
|
|
18026
|
+
currentIds = 0;
|
|
18027
|
+
currentTurns = 0;
|
|
18028
|
+
}
|
|
18029
|
+
current.push(task);
|
|
18030
|
+
currentIds += size;
|
|
18031
|
+
currentTurns += turns;
|
|
18032
|
+
if (turns > 5e3) {
|
|
18033
|
+
batches.push(current);
|
|
18034
|
+
current = [];
|
|
18035
|
+
currentIds = 0;
|
|
18036
|
+
currentTurns = 0;
|
|
18037
|
+
}
|
|
18038
|
+
}
|
|
18039
|
+
if (current.length > 0) batches.push(current);
|
|
18040
|
+
for (const batch of batches) {
|
|
18041
|
+
signal?.throwIfAborted();
|
|
18042
|
+
const responseTasks = auth.accessToken === null || auth.accountId === null ? /* @__PURE__ */ new Map() : await queryTaskUsage(batch, auth.accessToken, auth.accountId, signal);
|
|
18043
|
+
const tasks = batch.map((task) => {
|
|
18044
|
+
const response = responseTasks.get(task.threadId);
|
|
18045
|
+
return response ?? {
|
|
18046
|
+
threadId: task.threadId,
|
|
18047
|
+
createdAt: task.createdAt,
|
|
18048
|
+
descendantThreadIds: [...task.descendantThreadIds],
|
|
18049
|
+
dataStatus: auth.accessToken === null || auth.accountId === null ? "not_queried" : "unavailable",
|
|
18050
|
+
usageSource: "",
|
|
18051
|
+
balanceUsageCredits: null
|
|
18052
|
+
};
|
|
18053
|
+
});
|
|
18054
|
+
const batchTurns = batch.flatMap(
|
|
18055
|
+
(task) => [task.threadId, ...task.descendantThreadIds].flatMap(
|
|
18056
|
+
(threadId) => byThread.get(threadId) ?? []
|
|
18057
|
+
)
|
|
18058
|
+
);
|
|
18059
|
+
let serverIncomplete = false;
|
|
18060
|
+
for (let offset = 0; offset < Math.max(1, batchTurns.length); offset += 5e3) {
|
|
18061
|
+
const request2 = codexBillingRequestSchema.parse({
|
|
18062
|
+
accountFingerprint: auth.accountFingerprint,
|
|
18063
|
+
reconcile: offset === 0,
|
|
18064
|
+
tasks,
|
|
18065
|
+
authTurns: batchTurns.slice(offset, offset + 5e3)
|
|
18066
|
+
});
|
|
18067
|
+
const response = await fetcher(endpoint, {
|
|
18068
|
+
method: "POST",
|
|
18069
|
+
headers: {
|
|
18070
|
+
Authorization: `Bearer ${config2.token}`,
|
|
18071
|
+
"Content-Type": "application/json"
|
|
18072
|
+
},
|
|
18073
|
+
body: JSON.stringify(request2),
|
|
18074
|
+
signal: AbortSignal.any([
|
|
18075
|
+
AbortSignal.timeout(9e4),
|
|
18076
|
+
...signal ? [signal] : []
|
|
18077
|
+
])
|
|
18078
|
+
});
|
|
18079
|
+
if (!response.ok)
|
|
18080
|
+
throw new Error(
|
|
18081
|
+
`Codex billing sync failed with HTTP ${response.status}.`
|
|
18082
|
+
);
|
|
18083
|
+
const result = syncResultSchema.safeParse(await response.json());
|
|
18084
|
+
if (!result.success || result.data.matched > tasks.length)
|
|
18085
|
+
throw new Error("Iteration returned an invalid Codex billing result.");
|
|
18086
|
+
if (result.data.matched < tasks.length) serverIncomplete = true;
|
|
18087
|
+
}
|
|
18088
|
+
for (const task of tasks) {
|
|
18089
|
+
if (task.dataStatus === "available" && task.balanceUsageCredits !== null && (task.usageSource === "included_plan" || task.usageSource === "plan_and_credits"))
|
|
18090
|
+
unavailable.delete(task.threadId);
|
|
18091
|
+
else if (task.dataStatus !== "not_queried")
|
|
18092
|
+
unavailable.add(task.threadId);
|
|
18093
|
+
if (serverIncomplete) unavailable.add(task.threadId);
|
|
18094
|
+
if (task.dataStatus === "available" && task.balanceUsageCredits !== null && (task.usageSource === "included_plan" || task.usageSource === "plan_and_credits")) {
|
|
18095
|
+
if (codexCreditSign(task.balanceUsageCredits) === 0 && task.usageSource === "included_plan")
|
|
18096
|
+
included += 1;
|
|
18097
|
+
else if (codexCreditSign(task.balanceUsageCredits) > 0)
|
|
18098
|
+
creditTasks += 1;
|
|
18099
|
+
}
|
|
18100
|
+
}
|
|
18101
|
+
apiTurns += batchTurns.filter((turn) => turn.mode === "api_key").length;
|
|
18102
|
+
}
|
|
18103
|
+
await saveState2(stateFile, {
|
|
18104
|
+
accountFingerprint: auth.accountFingerprint,
|
|
18105
|
+
lastSyncedAtMs: nowMs,
|
|
18106
|
+
lastAuditAtMs: history || audit ? nowMs : prior.lastAuditAtMs,
|
|
18107
|
+
unavailableThreadIds: [...unavailable]
|
|
18108
|
+
});
|
|
18109
|
+
return {
|
|
18110
|
+
tasks: selected.length,
|
|
18111
|
+
included,
|
|
18112
|
+
creditTasks,
|
|
18113
|
+
apiTurns,
|
|
18114
|
+
skipped: false
|
|
18115
|
+
};
|
|
18116
|
+
}
|
|
18117
|
+
|
|
17503
18118
|
// src/service.ts
|
|
17504
18119
|
function errorCode2(error2) {
|
|
17505
18120
|
if (error2 instanceof Error && "code" in error2) return String(error2.code);
|
|
@@ -17541,26 +18156,26 @@ async function persistHealth(health, paths) {
|
|
|
17541
18156
|
}
|
|
17542
18157
|
}
|
|
17543
18158
|
async function acquireCollectorLock(paths) {
|
|
17544
|
-
await ensurePrivateDirectory(
|
|
18159
|
+
await ensurePrivateDirectory(path19.dirname(paths.lock));
|
|
17545
18160
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
17546
18161
|
try {
|
|
17547
|
-
const handle = await
|
|
18162
|
+
const handle = await fs16.open(paths.lock, "wx", 384);
|
|
17548
18163
|
await handle.writeFile(`${process.pid}
|
|
17549
18164
|
`, "utf8");
|
|
17550
18165
|
await handle.sync();
|
|
17551
18166
|
await handle.close();
|
|
17552
18167
|
return async () => {
|
|
17553
|
-
const current = await
|
|
18168
|
+
const current = await fs16.readFile(paths.lock, "utf8").catch(() => "");
|
|
17554
18169
|
if (current.trim() === String(process.pid)) {
|
|
17555
|
-
await
|
|
18170
|
+
await fs16.rm(paths.lock, { force: true });
|
|
17556
18171
|
}
|
|
17557
18172
|
};
|
|
17558
18173
|
} catch (error2) {
|
|
17559
18174
|
if (errorCode2(error2) !== "EEXIST") throw error2;
|
|
17560
|
-
const lockContents = await
|
|
18175
|
+
const lockContents = await fs16.readFile(paths.lock, "utf8").catch(() => "");
|
|
17561
18176
|
const lockPid = Number(lockContents.trim());
|
|
17562
18177
|
if (!Number.isSafeInteger(lockPid) || lockPid < 2) {
|
|
17563
|
-
await
|
|
18178
|
+
await fs16.rm(paths.lock, { force: true });
|
|
17564
18179
|
continue;
|
|
17565
18180
|
}
|
|
17566
18181
|
try {
|
|
@@ -17568,7 +18183,7 @@ async function acquireCollectorLock(paths) {
|
|
|
17568
18183
|
throw new CollectorLockBusyError("Collector is already running.");
|
|
17569
18184
|
} catch (processError) {
|
|
17570
18185
|
if (errorCode2(processError) !== "ESRCH") throw processError;
|
|
17571
|
-
await
|
|
18186
|
+
await fs16.rm(paths.lock, { force: true });
|
|
17572
18187
|
}
|
|
17573
18188
|
}
|
|
17574
18189
|
}
|
|
@@ -17665,6 +18280,7 @@ async function runCollector(directory, expectedSource) {
|
|
|
17665
18280
|
const summariesRefreshedAt = /* @__PURE__ */ new Map();
|
|
17666
18281
|
let lastAutoCheckAt = 0;
|
|
17667
18282
|
let lastBillingSyncAt = 0;
|
|
18283
|
+
const historyProgress = new HistoryProgressReporter();
|
|
17668
18284
|
const runtimeVersion = process.env.ITERATION_RUNTIME_VERSION;
|
|
17669
18285
|
let lastRuntimeCheckAt = 0;
|
|
17670
18286
|
let trialSettled = runtimeVersion === void 0;
|
|
@@ -17727,6 +18343,7 @@ async function runCollector(directory, expectedSource) {
|
|
|
17727
18343
|
}
|
|
17728
18344
|
let acknowledgedAtMs = null;
|
|
17729
18345
|
let billingReady = false;
|
|
18346
|
+
let history = null;
|
|
17730
18347
|
try {
|
|
17731
18348
|
const result = await collectorTick(
|
|
17732
18349
|
config2,
|
|
@@ -17738,6 +18355,12 @@ async function runCollector(directory, expectedSource) {
|
|
|
17738
18355
|
}
|
|
17739
18356
|
);
|
|
17740
18357
|
billingReady = result.historyCaughtUp && result.outboxBytes === 0;
|
|
18358
|
+
if (result.history !== null) {
|
|
18359
|
+
history = {
|
|
18360
|
+
...result.history,
|
|
18361
|
+
complete: billingReady && result.history.readSessions === result.history.sessions
|
|
18362
|
+
};
|
|
18363
|
+
}
|
|
17741
18364
|
if (acknowledgedAtMs !== null || health.lastFailure !== null || result.historyCaughtUp && health.historyCaughtUpAtMs === null) {
|
|
17742
18365
|
health = {
|
|
17743
18366
|
lastUploadAtMs: acknowledgedAtMs ?? health.lastUploadAtMs,
|
|
@@ -17790,6 +18413,30 @@ async function runCollector(directory, expectedSource) {
|
|
|
17790
18413
|
await persistHealth(health, paths);
|
|
17791
18414
|
retryDelayMs = Math.min(retryDelayMs * 2, 6e4);
|
|
17792
18415
|
}
|
|
18416
|
+
if (history !== null) {
|
|
18417
|
+
try {
|
|
18418
|
+
await historyProgress.report(
|
|
18419
|
+
config2,
|
|
18420
|
+
history,
|
|
18421
|
+
collectorFetch,
|
|
18422
|
+
controller.signal,
|
|
18423
|
+
Date.now()
|
|
18424
|
+
);
|
|
18425
|
+
} catch (error2) {
|
|
18426
|
+
if (controller.signal.aborted) break;
|
|
18427
|
+
console.error(
|
|
18428
|
+
JSON.stringify({
|
|
18429
|
+
level: "warning",
|
|
18430
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18431
|
+
source,
|
|
18432
|
+
operation: "report_history_progress",
|
|
18433
|
+
errorName: error2 instanceof Error ? error2.name : "UnknownError",
|
|
18434
|
+
causes: errorCauses(error2),
|
|
18435
|
+
message: error2 instanceof Error ? error2.message : "Unknown history progress failure."
|
|
18436
|
+
})
|
|
18437
|
+
);
|
|
18438
|
+
}
|
|
18439
|
+
}
|
|
17793
18440
|
try {
|
|
17794
18441
|
if (source !== "cursor")
|
|
17795
18442
|
await refreshTurnSummaries(
|
|
@@ -17840,6 +18487,33 @@ async function runCollector(directory, expectedSource) {
|
|
|
17840
18487
|
);
|
|
17841
18488
|
}
|
|
17842
18489
|
}
|
|
18490
|
+
if ("codexHome" in config2 && billingReady && Date.now() - lastBillingSyncAt >= 6 * 60 * 60 * 1e3) {
|
|
18491
|
+
try {
|
|
18492
|
+
const billing = await syncCodexBilling(
|
|
18493
|
+
config2,
|
|
18494
|
+
collectorFetch,
|
|
18495
|
+
controller.signal
|
|
18496
|
+
);
|
|
18497
|
+
lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
|
|
18498
|
+
console.info(
|
|
18499
|
+
JSON.stringify({
|
|
18500
|
+
level: "info",
|
|
18501
|
+
operation: "sync_codex_billing",
|
|
18502
|
+
...billing
|
|
18503
|
+
})
|
|
18504
|
+
);
|
|
18505
|
+
} catch (error2) {
|
|
18506
|
+
if (controller.signal.aborted) break;
|
|
18507
|
+
lastBillingSyncAt = Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3;
|
|
18508
|
+
console.error(
|
|
18509
|
+
JSON.stringify({
|
|
18510
|
+
level: "warning",
|
|
18511
|
+
operation: "sync_codex_billing",
|
|
18512
|
+
message: error2 instanceof Error ? error2.message : "Codex billing sync failed."
|
|
18513
|
+
})
|
|
18514
|
+
);
|
|
18515
|
+
}
|
|
18516
|
+
}
|
|
17843
18517
|
try {
|
|
17844
18518
|
await delay3(retryDelayMs, void 0, { signal: controller.signal });
|
|
17845
18519
|
} catch (error2) {
|
|
@@ -17867,7 +18541,7 @@ async function importSourceHistory(config2, paths, confirmed, control = backgrou
|
|
|
17867
18541
|
const source = collectorSource(config2);
|
|
17868
18542
|
const directory = collectorSourceDirectory(config2);
|
|
17869
18543
|
const state = await loadState(paths);
|
|
17870
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
18544
|
+
if (!state.initialized || state.sourceDirectory !== path20.resolve(directory)) {
|
|
17871
18545
|
throw new Error(
|
|
17872
18546
|
`Connect the ${source} source before importing its history.`
|
|
17873
18547
|
);
|
|
@@ -18219,7 +18893,7 @@ function argumentsForCli() {
|
|
|
18219
18893
|
help: { type: "boolean" },
|
|
18220
18894
|
endpoint: { type: "string" },
|
|
18221
18895
|
"device-name": { type: "string" },
|
|
18222
|
-
|
|
18896
|
+
workspace: { type: "string" },
|
|
18223
18897
|
"codex-home": { type: "string" },
|
|
18224
18898
|
"claude-home": { type: "string" },
|
|
18225
18899
|
"cursor-home": { type: "string" },
|
|
@@ -18263,7 +18937,7 @@ function usage(command) {
|
|
|
18263
18937
|
"",
|
|
18264
18938
|
"Options for advanced setups:",
|
|
18265
18939
|
" --device-name NAME Name this computer in Iteration",
|
|
18266
|
-
" --
|
|
18940
|
+
" --workspace ID Connect to this workspace without a prompt",
|
|
18267
18941
|
" --codex-home DIR Use a different Codex data directory",
|
|
18268
18942
|
" --claude-home DIR Use a different Claude data directory",
|
|
18269
18943
|
" --cursor-home DIR Use a different Cursor data directory",
|
|
@@ -18296,7 +18970,7 @@ function usage(command) {
|
|
|
18296
18970
|
}
|
|
18297
18971
|
async function fileExists(filename) {
|
|
18298
18972
|
try {
|
|
18299
|
-
await
|
|
18973
|
+
await fs17.access(filename);
|
|
18300
18974
|
return true;
|
|
18301
18975
|
} catch (error2) {
|
|
18302
18976
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
@@ -18346,7 +19020,7 @@ ${missing2.map(collectorSourceLabel).join(" and ")} found on this computer but n
|
|
|
18346
19020
|
await connectAll({});
|
|
18347
19021
|
}
|
|
18348
19022
|
function dataDirectory(source, values) {
|
|
18349
|
-
return
|
|
19023
|
+
return path21.resolve(
|
|
18350
19024
|
optionalString(values, "data-directory") ?? defaultCollectorDirectory(source)
|
|
18351
19025
|
);
|
|
18352
19026
|
}
|
|
@@ -18368,11 +19042,11 @@ async function initialize(source, values) {
|
|
|
18368
19042
|
const endpoint = optionalString(values, "endpoint") ?? DEFAULT_INGEST_ENDPOINT;
|
|
18369
19043
|
const endpointUrl = validateCollectorEndpoint(endpoint);
|
|
18370
19044
|
const deviceName = optionalString(values, "device-name") ?? os8.hostname();
|
|
18371
|
-
const organizationId = optionalString(values, "
|
|
19045
|
+
const organizationId = optionalString(values, "workspace");
|
|
18372
19046
|
const directory = dataDirectory(source, values);
|
|
18373
19047
|
const paths = collectorPaths(directory);
|
|
18374
|
-
const selectedHome =
|
|
18375
|
-
source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ??
|
|
19048
|
+
const selectedHome = path21.resolve(
|
|
19049
|
+
source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ?? path21.join(os8.homedir(), ".codex") : source === "claude" ? optionalString(values, "claude-home") ?? process.env.CLAUDE_CONFIG_DIR ?? path21.join(os8.homedir(), ".claude") : optionalString(values, "cursor-home") ?? path21.join(os8.homedir(), ".cursor")
|
|
18376
19050
|
);
|
|
18377
19051
|
await assertFreshCollectorDirectory(paths, source);
|
|
18378
19052
|
const preparedState = await prepareSourceInitialState(
|
|
@@ -18415,13 +19089,15 @@ async function initialize(source, values) {
|
|
|
18415
19089
|
}
|
|
18416
19090
|
const notice = await turnSummaryNotice(config2);
|
|
18417
19091
|
process.stdout.write(
|
|
18418
|
-
`${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}. Existing sessions are importing and new activity is collected automatically.
|
|
19092
|
+
`${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}. Existing sessions are importing, newest first, and new activity is collected automatically. Iteration shows the import as it runs.${notice}
|
|
18419
19093
|
`
|
|
18420
19094
|
);
|
|
18421
19095
|
return config2;
|
|
18422
19096
|
}
|
|
18423
19097
|
async function approveAllSources(sources, workspaceName, yes) {
|
|
18424
|
-
const names = sources.map(
|
|
19098
|
+
const names = sources.map(
|
|
19099
|
+
({ source, sessions }) => `${collectorSourceLabel(source)} (${sessions.toLocaleString("en-US")} saved ${sessions === 1 ? "session" : "sessions"})`
|
|
19100
|
+
).join(" and ");
|
|
18425
19101
|
const safeName = safeWorkspaceName(workspaceName);
|
|
18426
19102
|
process.stdout.write(
|
|
18427
19103
|
`Detected ${names || "no saved agent sessions"} on this computer.
|
|
@@ -18455,13 +19131,13 @@ async function connectAll(values) {
|
|
|
18455
19131
|
}
|
|
18456
19132
|
const configs = await Promise.all(COLLECTOR_SOURCES.map(connectedConfig));
|
|
18457
19133
|
const sourceHomes = {
|
|
18458
|
-
codex:
|
|
19134
|
+
codex: path21.resolve(
|
|
18459
19135
|
optionalString(values, "codex-home") ?? (configs[0] && "codexHome" in configs[0] ? configs[0].codexHome : sourceHome("codex"))
|
|
18460
19136
|
),
|
|
18461
|
-
claude:
|
|
19137
|
+
claude: path21.resolve(
|
|
18462
19138
|
optionalString(values, "claude-home") ?? (configs[1] && "claudeHome" in configs[1] ? configs[1].claudeHome : sourceHome("claude"))
|
|
18463
19139
|
),
|
|
18464
|
-
cursor:
|
|
19140
|
+
cursor: path21.resolve(
|
|
18465
19141
|
optionalString(values, "cursor-home") ?? (configs[2] && "cursorHome" in configs[2] ? configs[2].cursorHome : sourceHome("cursor"))
|
|
18466
19142
|
)
|
|
18467
19143
|
};
|
|
@@ -18498,14 +19174,19 @@ async function connectAll(values) {
|
|
|
18498
19174
|
);
|
|
18499
19175
|
}
|
|
18500
19176
|
}
|
|
18501
|
-
const requestedOrganizationId = optionalString(values, "
|
|
19177
|
+
const requestedOrganizationId = optionalString(values, "workspace");
|
|
18502
19178
|
if (requestedOrganizationId && existingScopes[0] && requestedOrganizationId !== existingScopes[0].scope.organizationId) {
|
|
18503
19179
|
throw new Error("The existing collector belongs to a different workspace.");
|
|
18504
19180
|
}
|
|
18505
19181
|
const previousConsent = await loadMachineConsent();
|
|
18506
19182
|
if (previousConsent === null || anchorExisting === null || previousConsent.endpoint !== endpoint || previousConsent.anchorSource !== anchorSource || previousConsent.anchorDataDirectory !== anchorExisting.dataDirectory || previousConsent.organizationId !== existingScopes[0]?.scope.organizationId || previousConsent.sourceHomes.codex !== sourceHomes.codex || previousConsent.sourceHomes.claude !== sourceHomes.claude || previousConsent.sourceHomes.cursor !== sourceHomes.cursor) {
|
|
18507
19183
|
await approveAllSources(
|
|
18508
|
-
|
|
19184
|
+
await Promise.all(
|
|
19185
|
+
detected.map(async (source) => ({
|
|
19186
|
+
source,
|
|
19187
|
+
sessions: await countSavedSessions(source, sourceHomes[source])
|
|
19188
|
+
}))
|
|
19189
|
+
),
|
|
18509
19190
|
existingScopes[0]?.scope.organizationName ?? null,
|
|
18510
19191
|
values.yes === true
|
|
18511
19192
|
);
|
|
@@ -7285,6 +7285,31 @@ function superRefine(fn, params) {
|
|
|
7285
7285
|
return _superRefine(fn, params);
|
|
7286
7286
|
}
|
|
7287
7287
|
|
|
7288
|
+
// ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/iso.js
|
|
7289
|
+
var iso_exports = {};
|
|
7290
|
+
__export(iso_exports, {
|
|
7291
|
+
ZodISODate: () => ZodISODate,
|
|
7292
|
+
ZodISODateTime: () => ZodISODateTime,
|
|
7293
|
+
ZodISODuration: () => ZodISODuration,
|
|
7294
|
+
ZodISOTime: () => ZodISOTime,
|
|
7295
|
+
date: () => date2,
|
|
7296
|
+
datetime: () => datetime2,
|
|
7297
|
+
duration: () => duration2,
|
|
7298
|
+
time: () => time2
|
|
7299
|
+
});
|
|
7300
|
+
function datetime2(params) {
|
|
7301
|
+
return _isoDateTime(ZodISODateTime, params);
|
|
7302
|
+
}
|
|
7303
|
+
function date2(params) {
|
|
7304
|
+
return _isoDate(ZodISODate, params);
|
|
7305
|
+
}
|
|
7306
|
+
function time2(params) {
|
|
7307
|
+
return _isoTime(ZodISOTime, params);
|
|
7308
|
+
}
|
|
7309
|
+
function duration2(params) {
|
|
7310
|
+
return _isoDuration(ZodISODuration, params);
|
|
7311
|
+
}
|
|
7312
|
+
|
|
7288
7313
|
// src/cursor-hooks.ts
|
|
7289
7314
|
import path2 from "node:path";
|
|
7290
7315
|
|
|
@@ -7340,6 +7365,28 @@ var cursorBillingRequestSchema = object({
|
|
|
7340
7365
|
paidUsd: number2().finite().nonnegative().nullable()
|
|
7341
7366
|
}).strict()).max(5e3)
|
|
7342
7367
|
}).strict();
|
|
7368
|
+
var codexBillingRequestSchema = object({
|
|
7369
|
+
accountFingerprint: string2().regex(/^[0-9a-f]{64}$/),
|
|
7370
|
+
reconcile: boolean2(),
|
|
7371
|
+
tasks: array(object({
|
|
7372
|
+
threadId: uuid2(),
|
|
7373
|
+
createdAt: iso_exports.datetime().nullable(),
|
|
7374
|
+
descendantThreadIds: array(uuid2()).max(900),
|
|
7375
|
+
dataStatus: _enum([
|
|
7376
|
+
"available",
|
|
7377
|
+
"partial",
|
|
7378
|
+
"unavailable",
|
|
7379
|
+
"not_queried"
|
|
7380
|
+
]),
|
|
7381
|
+
usageSource: string2().max(100),
|
|
7382
|
+
balanceUsageCredits: string2().regex(/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d{1,3})?$/).max(128).nullable()
|
|
7383
|
+
}).strict()).max(100),
|
|
7384
|
+
authTurns: array(object({
|
|
7385
|
+
threadId: uuid2(),
|
|
7386
|
+
turnId: uuid2(),
|
|
7387
|
+
mode: _enum(["chatgpt", "api_key", "conflicting"])
|
|
7388
|
+
}).strict()).max(5e3)
|
|
7389
|
+
}).strict();
|
|
7343
7390
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7344
7391
|
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
7345
7392
|
var unpairedSurrogate = /[\uD800-\uDFFF]/u;
|
|
@@ -7388,6 +7435,12 @@ var acknowledgementSchema = object({
|
|
|
7388
7435
|
}).strict();
|
|
7389
7436
|
var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
7390
7437
|
var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
|
|
7438
|
+
var sessionCountSchema = number2().int().min(0).max(1e6);
|
|
7439
|
+
var collectorHistoryProgressSchema = object({
|
|
7440
|
+
sessions: sessionCountSchema,
|
|
7441
|
+
readSessions: sessionCountSchema,
|
|
7442
|
+
complete: boolean2()
|
|
7443
|
+
}).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.");
|
|
7391
7444
|
var collectorEnrollmentRequestSchema = object({
|
|
7392
7445
|
deviceName: string2().trim().min(1).max(160),
|
|
7393
7446
|
organizationId: string2().regex(UUID_PATTERN).optional(),
|
package/dist/launcher.js
CHANGED
|
@@ -6364,6 +6364,31 @@ function superRefine(fn, params) {
|
|
|
6364
6364
|
return _superRefine(fn, params);
|
|
6365
6365
|
}
|
|
6366
6366
|
|
|
6367
|
+
// ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/iso.js
|
|
6368
|
+
var iso_exports = {};
|
|
6369
|
+
__export(iso_exports, {
|
|
6370
|
+
ZodISODate: () => ZodISODate,
|
|
6371
|
+
ZodISODateTime: () => ZodISODateTime,
|
|
6372
|
+
ZodISODuration: () => ZodISODuration,
|
|
6373
|
+
ZodISOTime: () => ZodISOTime,
|
|
6374
|
+
date: () => date2,
|
|
6375
|
+
datetime: () => datetime2,
|
|
6376
|
+
duration: () => duration2,
|
|
6377
|
+
time: () => time2
|
|
6378
|
+
});
|
|
6379
|
+
function datetime2(params) {
|
|
6380
|
+
return _isoDateTime(ZodISODateTime, params);
|
|
6381
|
+
}
|
|
6382
|
+
function date2(params) {
|
|
6383
|
+
return _isoDate(ZodISODate, params);
|
|
6384
|
+
}
|
|
6385
|
+
function time2(params) {
|
|
6386
|
+
return _isoTime(ZodISOTime, params);
|
|
6387
|
+
}
|
|
6388
|
+
function duration2(params) {
|
|
6389
|
+
return _isoDuration(ZodISODuration, params);
|
|
6390
|
+
}
|
|
6391
|
+
|
|
6367
6392
|
// ../trace-contracts/dist/index.js
|
|
6368
6393
|
var TRACE_EVENT_TYPES = [
|
|
6369
6394
|
"user_message",
|
|
@@ -6416,6 +6441,28 @@ var cursorBillingRequestSchema = object({
|
|
|
6416
6441
|
paidUsd: number2().finite().nonnegative().nullable()
|
|
6417
6442
|
}).strict()).max(5e3)
|
|
6418
6443
|
}).strict();
|
|
6444
|
+
var codexBillingRequestSchema = object({
|
|
6445
|
+
accountFingerprint: string2().regex(/^[0-9a-f]{64}$/),
|
|
6446
|
+
reconcile: boolean2(),
|
|
6447
|
+
tasks: array(object({
|
|
6448
|
+
threadId: uuid2(),
|
|
6449
|
+
createdAt: iso_exports.datetime().nullable(),
|
|
6450
|
+
descendantThreadIds: array(uuid2()).max(900),
|
|
6451
|
+
dataStatus: _enum([
|
|
6452
|
+
"available",
|
|
6453
|
+
"partial",
|
|
6454
|
+
"unavailable",
|
|
6455
|
+
"not_queried"
|
|
6456
|
+
]),
|
|
6457
|
+
usageSource: string2().max(100),
|
|
6458
|
+
balanceUsageCredits: string2().regex(/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d{1,3})?$/).max(128).nullable()
|
|
6459
|
+
}).strict()).max(100),
|
|
6460
|
+
authTurns: array(object({
|
|
6461
|
+
threadId: uuid2(),
|
|
6462
|
+
turnId: uuid2(),
|
|
6463
|
+
mode: _enum(["chatgpt", "api_key", "conflicting"])
|
|
6464
|
+
}).strict()).max(5e3)
|
|
6465
|
+
}).strict();
|
|
6419
6466
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
6420
6467
|
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
6421
6468
|
var unpairedSurrogate = /[\uD800-\uDFFF]/u;
|
|
@@ -6464,6 +6511,12 @@ var acknowledgementSchema = object({
|
|
|
6464
6511
|
}).strict();
|
|
6465
6512
|
var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
6466
6513
|
var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
|
|
6514
|
+
var sessionCountSchema = number2().int().min(0).max(1e6);
|
|
6515
|
+
var collectorHistoryProgressSchema = object({
|
|
6516
|
+
sessions: sessionCountSchema,
|
|
6517
|
+
readSessions: sessionCountSchema,
|
|
6518
|
+
complete: boolean2()
|
|
6519
|
+
}).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.");
|
|
6467
6520
|
var collectorEnrollmentRequestSchema = object({
|
|
6468
6521
|
deviceName: string2().trim().min(1).max(160),
|
|
6469
6522
|
organizationId: string2().regex(UUID_PATTERN).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iteration-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@secretlint/secretlint-rule-preset-recommend": "13.0.6",
|
|
23
23
|
"esbuild": "0.28.2",
|
|
24
24
|
"zod": "4.6.4",
|
|
25
|
-
"iteration-trace-contracts": "0.
|
|
25
|
+
"iteration-trace-contracts": "0.10.0"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json --noEmit && node build.mjs"
|