iteration-cli 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +881 -291
- package/dist/cursor-hook-entry.js +162 -53
- package/dist/launcher.js +22 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -648,7 +648,7 @@ var require_has_flag = __commonJS({
|
|
|
648
648
|
var require_supports_color = __commonJS({
|
|
649
649
|
"../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
|
|
650
650
|
"use strict";
|
|
651
|
-
var
|
|
651
|
+
var os9 = __require("os");
|
|
652
652
|
var tty = __require("tty");
|
|
653
653
|
var hasFlag = require_has_flag();
|
|
654
654
|
var { env } = process;
|
|
@@ -696,7 +696,7 @@ var require_supports_color = __commonJS({
|
|
|
696
696
|
return min;
|
|
697
697
|
}
|
|
698
698
|
if (process.platform === "win32") {
|
|
699
|
-
const osRelease =
|
|
699
|
+
const osRelease = os9.release().split(".");
|
|
700
700
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
701
701
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
702
702
|
}
|
|
@@ -933,9 +933,9 @@ var require_src = __commonJS({
|
|
|
933
933
|
|
|
934
934
|
// src/cli.ts
|
|
935
935
|
import { spawnSync } from "node:child_process";
|
|
936
|
-
import
|
|
937
|
-
import
|
|
938
|
-
import { promises as
|
|
936
|
+
import os8 from "node:os";
|
|
937
|
+
import path20 from "node:path";
|
|
938
|
+
import { promises as fs16 } 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, path21) {
|
|
1179
|
+
if (!path21)
|
|
1180
1180
|
return obj;
|
|
1181
|
-
return
|
|
1181
|
+
return path21.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(path21, 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(path21);
|
|
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, path21 = []) => {
|
|
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 }, [...path21, ...issue2.path]));
|
|
1975
1975
|
} else if (issue2.code === "invalid_key") {
|
|
1976
|
-
processError({ issues: issue2.issues }, [...
|
|
1976
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1977
1977
|
} else if (issue2.code === "invalid_element") {
|
|
1978
|
-
processError({ issues: issue2.issues }, [...
|
|
1978
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1979
1979
|
} else {
|
|
1980
|
-
const fullpath = [...
|
|
1980
|
+
const fullpath = [...path21, ...issue2.path];
|
|
1981
1981
|
if (fullpath.length === 0) {
|
|
1982
1982
|
fieldErrors._errors.push(mapper(issue2));
|
|
1983
1983
|
} else {
|
|
@@ -7447,12 +7447,37 @@ function json(params) {
|
|
|
7447
7447
|
return jsonSchema;
|
|
7448
7448
|
}
|
|
7449
7449
|
|
|
7450
|
+
// ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/iso.js
|
|
7451
|
+
var iso_exports = {};
|
|
7452
|
+
__export(iso_exports, {
|
|
7453
|
+
ZodISODate: () => ZodISODate,
|
|
7454
|
+
ZodISODateTime: () => ZodISODateTime,
|
|
7455
|
+
ZodISODuration: () => ZodISODuration,
|
|
7456
|
+
ZodISOTime: () => ZodISOTime,
|
|
7457
|
+
date: () => date2,
|
|
7458
|
+
datetime: () => datetime2,
|
|
7459
|
+
duration: () => duration2,
|
|
7460
|
+
time: () => time2
|
|
7461
|
+
});
|
|
7462
|
+
function datetime2(params) {
|
|
7463
|
+
return _isoDateTime(ZodISODateTime, params);
|
|
7464
|
+
}
|
|
7465
|
+
function date2(params) {
|
|
7466
|
+
return _isoDate(ZodISODate, params);
|
|
7467
|
+
}
|
|
7468
|
+
function time2(params) {
|
|
7469
|
+
return _isoTime(ZodISOTime, params);
|
|
7470
|
+
}
|
|
7471
|
+
function duration2(params) {
|
|
7472
|
+
return _isoDuration(ZodISODuration, params);
|
|
7473
|
+
}
|
|
7474
|
+
|
|
7450
7475
|
// ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/coerce.js
|
|
7451
7476
|
var coerce_exports = {};
|
|
7452
7477
|
__export(coerce_exports, {
|
|
7453
7478
|
bigint: () => bigint2,
|
|
7454
7479
|
boolean: () => boolean3,
|
|
7455
|
-
date: () =>
|
|
7480
|
+
date: () => date3,
|
|
7456
7481
|
number: () => number3,
|
|
7457
7482
|
string: () => string3
|
|
7458
7483
|
});
|
|
@@ -7468,7 +7493,7 @@ function boolean3(params) {
|
|
|
7468
7493
|
function bigint2(params) {
|
|
7469
7494
|
return _coercedBigint(ZodBigInt, params);
|
|
7470
7495
|
}
|
|
7471
|
-
function
|
|
7496
|
+
function date3(params) {
|
|
7472
7497
|
return _coercedDate(ZodDate, params);
|
|
7473
7498
|
}
|
|
7474
7499
|
|
|
@@ -7513,6 +7538,25 @@ function collectorSourceLabel(source) {
|
|
|
7513
7538
|
return COLLECTOR_SOURCE_LABELS[source];
|
|
7514
7539
|
}
|
|
7515
7540
|
var MAX_TRACE_EVENTS_PER_BATCH = 100;
|
|
7541
|
+
var cursorBillingRequestSchema = object({
|
|
7542
|
+
conversationId: uuid2(),
|
|
7543
|
+
reconcileFromMs: number2().int().nonnegative().nullable(),
|
|
7544
|
+
events: array(object({
|
|
7545
|
+
timestamp: number2().int().nonnegative(),
|
|
7546
|
+
kind: _enum([
|
|
7547
|
+
"included",
|
|
7548
|
+
"paid",
|
|
7549
|
+
"byok",
|
|
7550
|
+
"not_charged",
|
|
7551
|
+
"unknown"
|
|
7552
|
+
]),
|
|
7553
|
+
inputTokens: number2().int().nonnegative().nullable(),
|
|
7554
|
+
outputTokens: number2().int().nonnegative().nullable(),
|
|
7555
|
+
cacheReadTokens: number2().int().nonnegative().nullable(),
|
|
7556
|
+
cacheWriteTokens: number2().int().nonnegative().nullable(),
|
|
7557
|
+
paidUsd: number2().finite().nonnegative().nullable()
|
|
7558
|
+
}).strict()).max(5e3)
|
|
7559
|
+
}).strict();
|
|
7516
7560
|
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;
|
|
7517
7561
|
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
7518
7562
|
var unpairedSurrogate = /[\uD800-\uDFFF]/u;
|
|
@@ -7975,10 +8019,10 @@ async function authenticateAndEnrollCollector(endpoint, deviceNameValue, organiz
|
|
|
7975
8019
|
}
|
|
7976
8020
|
|
|
7977
8021
|
// src/auto-connect.ts
|
|
7978
|
-
import { promises as
|
|
8022
|
+
import { promises as fs12 } from "node:fs";
|
|
7979
8023
|
import { createHmac } from "node:crypto";
|
|
7980
8024
|
import os6 from "node:os";
|
|
7981
|
-
import
|
|
8025
|
+
import path12 from "node:path";
|
|
7982
8026
|
|
|
7983
8027
|
// src/background-service.ts
|
|
7984
8028
|
import os4 from "node:os";
|
|
@@ -9330,14 +9374,100 @@ var collectorFetch = (input, init = {}) => {
|
|
|
9330
9374
|
|
|
9331
9375
|
// src/turn-summary.ts
|
|
9332
9376
|
import { createHash as createHash3 } from "node:crypto";
|
|
9333
|
-
import { promises as
|
|
9334
|
-
import
|
|
9377
|
+
import { promises as fs10 } from "node:fs";
|
|
9378
|
+
import path10 from "node:path";
|
|
9379
|
+
|
|
9380
|
+
// src/claude-git-hook.ts
|
|
9381
|
+
import { randomUUID } from "node:crypto";
|
|
9382
|
+
import { promises as fs9 } from "node:fs";
|
|
9383
|
+
import path9 from "node:path";
|
|
9384
|
+
|
|
9385
|
+
// src/git-repository.ts
|
|
9386
|
+
import fs7 from "node:fs";
|
|
9387
|
+
import path6 from "node:path";
|
|
9388
|
+
var repositoryUrls = /* @__PURE__ */ new Map();
|
|
9389
|
+
function missing(error2) {
|
|
9390
|
+
const code = error2.code;
|
|
9391
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
9392
|
+
}
|
|
9393
|
+
function readText(file) {
|
|
9394
|
+
try {
|
|
9395
|
+
return fs7.readFileSync(file, "utf8");
|
|
9396
|
+
} catch (error2) {
|
|
9397
|
+
if (missing(error2)) return null;
|
|
9398
|
+
throw error2;
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
function gitFiles(cwd) {
|
|
9402
|
+
try {
|
|
9403
|
+
if (!fs7.statSync(cwd).isDirectory()) return null;
|
|
9404
|
+
} catch (error2) {
|
|
9405
|
+
if (missing(error2)) return null;
|
|
9406
|
+
throw error2;
|
|
9407
|
+
}
|
|
9408
|
+
for (let directory = cwd; ; directory = path6.dirname(directory)) {
|
|
9409
|
+
const dotGit = path6.join(directory, ".git");
|
|
9410
|
+
let stats;
|
|
9411
|
+
try {
|
|
9412
|
+
stats = fs7.statSync(dotGit);
|
|
9413
|
+
} catch (error2) {
|
|
9414
|
+
if (!missing(error2)) throw error2;
|
|
9415
|
+
if (path6.dirname(directory) === directory) return null;
|
|
9416
|
+
continue;
|
|
9417
|
+
}
|
|
9418
|
+
if (stats.isDirectory())
|
|
9419
|
+
return { directory: dotGit, config: path6.join(dotGit, "config") };
|
|
9420
|
+
const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit) ?? "");
|
|
9421
|
+
if (pointer === null) return null;
|
|
9422
|
+
const gitDirectory = path6.resolve(directory, pointer[1]);
|
|
9423
|
+
const common = readText(path6.join(gitDirectory, "commondir"))?.trim();
|
|
9424
|
+
return {
|
|
9425
|
+
directory: gitDirectory,
|
|
9426
|
+
config: path6.join(
|
|
9427
|
+
common ? path6.resolve(gitDirectory, common) : gitDirectory,
|
|
9428
|
+
"config"
|
|
9429
|
+
)
|
|
9430
|
+
};
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
function originUrl(config2) {
|
|
9434
|
+
let inOrigin = false;
|
|
9435
|
+
for (const line of config2.split(/\r?\n/)) {
|
|
9436
|
+
const section = /^\s*\[\s*([^\]]+?)\s*\]/.exec(line);
|
|
9437
|
+
if (section !== null) {
|
|
9438
|
+
inOrigin = /^remote\s+"origin"$/i.test(section[1]);
|
|
9439
|
+
continue;
|
|
9440
|
+
}
|
|
9441
|
+
const url2 = inOrigin ? /^\s*url\s*=\s*(.+?)\s*$/i.exec(line) : null;
|
|
9442
|
+
if (url2 !== null) return url2[1].replace(/^"(.*)"$/, "$1");
|
|
9443
|
+
}
|
|
9444
|
+
return null;
|
|
9445
|
+
}
|
|
9446
|
+
function withoutCredentials(url2) {
|
|
9447
|
+
return url2.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@]*@/i, "$1");
|
|
9448
|
+
}
|
|
9449
|
+
function gitRepositoryUrl(cwd) {
|
|
9450
|
+
const known = repositoryUrls.get(cwd);
|
|
9451
|
+
if (known !== void 0) return known;
|
|
9452
|
+
const files = gitFiles(cwd);
|
|
9453
|
+
const config2 = files === null ? null : readText(files.config);
|
|
9454
|
+
const origin = config2 === null ? null : originUrl(config2);
|
|
9455
|
+
const url2 = origin === null ? null : withoutCredentials(origin);
|
|
9456
|
+
if (url2 !== null) repositoryUrls.set(cwd, url2);
|
|
9457
|
+
return url2;
|
|
9458
|
+
}
|
|
9459
|
+
function gitBranch(cwd) {
|
|
9460
|
+
const files = gitFiles(cwd);
|
|
9461
|
+
const head = files === null ? null : readText(path6.join(files.directory, "HEAD"));
|
|
9462
|
+
const branch = /^ref: refs\/heads\/(.+)\s*$/.exec(head ?? "")?.[1];
|
|
9463
|
+
return branch ?? null;
|
|
9464
|
+
}
|
|
9335
9465
|
|
|
9336
9466
|
// src/storage.ts
|
|
9337
9467
|
import { once } from "node:events";
|
|
9338
|
-
import { createReadStream, createWriteStream as createWriteStream2, promises as
|
|
9468
|
+
import { createReadStream, createWriteStream as createWriteStream2, promises as fs8 } from "node:fs";
|
|
9339
9469
|
import os5 from "node:os";
|
|
9340
|
-
import
|
|
9470
|
+
import path8 from "node:path";
|
|
9341
9471
|
import { createInterface as createInterface2 } from "node:readline";
|
|
9342
9472
|
|
|
9343
9473
|
// ../../node_modules/.pnpm/@secretlint+core@13.0.6/node_modules/@secretlint/core/module/SecretLintSourceCodeImpl.js
|
|
@@ -10166,7 +10296,7 @@ var registerRule = ({ sourceCode, config: config2, descriptorRule, contextEvents
|
|
|
10166
10296
|
};
|
|
10167
10297
|
|
|
10168
10298
|
// ../../node_modules/.pnpm/@secretlint+secretlint-rule-preset-recommend@13.0.6/node_modules/@secretlint/secretlint-rule-preset-recommend/module/index.js
|
|
10169
|
-
import
|
|
10299
|
+
import path7 from "node:path";
|
|
10170
10300
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
10171
10301
|
var regexpStringMatcher = {};
|
|
10172
10302
|
var lodash_uniq;
|
|
@@ -11148,11 +11278,11 @@ function requireLodash_sortby() {
|
|
|
11148
11278
|
function baseForOwn(object3, iteratee) {
|
|
11149
11279
|
return object3 && baseFor(object3, iteratee, keys);
|
|
11150
11280
|
}
|
|
11151
|
-
function baseGet(object3,
|
|
11152
|
-
|
|
11153
|
-
var index = 0, length =
|
|
11281
|
+
function baseGet(object3, path21) {
|
|
11282
|
+
path21 = isKey(path21, object3) ? [path21] : castPath(path21);
|
|
11283
|
+
var index = 0, length = path21.length;
|
|
11154
11284
|
while (object3 != null && index < length) {
|
|
11155
|
-
object3 = object3[toKey(
|
|
11285
|
+
object3 = object3[toKey(path21[index++])];
|
|
11156
11286
|
}
|
|
11157
11287
|
return index && index == length ? object3 : void 0;
|
|
11158
11288
|
}
|
|
@@ -11279,13 +11409,13 @@ function requireLodash_sortby() {
|
|
|
11279
11409
|
return object3 === source || baseIsMatch(object3, source, matchData);
|
|
11280
11410
|
};
|
|
11281
11411
|
}
|
|
11282
|
-
function baseMatchesProperty(
|
|
11283
|
-
if (isKey(
|
|
11284
|
-
return matchesStrictComparable(toKey(
|
|
11412
|
+
function baseMatchesProperty(path21, srcValue) {
|
|
11413
|
+
if (isKey(path21) && isStrictComparable(srcValue)) {
|
|
11414
|
+
return matchesStrictComparable(toKey(path21), srcValue);
|
|
11285
11415
|
}
|
|
11286
11416
|
return function(object3) {
|
|
11287
|
-
var objValue = get(object3,
|
|
11288
|
-
return objValue === void 0 && objValue === srcValue ? hasIn(object3,
|
|
11417
|
+
var objValue = get(object3, path21);
|
|
11418
|
+
return objValue === void 0 && objValue === srcValue ? hasIn(object3, path21) : baseIsEqual(srcValue, objValue, void 0, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
|
|
11289
11419
|
};
|
|
11290
11420
|
}
|
|
11291
11421
|
function baseOrderBy(collection, iteratees, orders) {
|
|
@@ -11301,9 +11431,9 @@ function requireLodash_sortby() {
|
|
|
11301
11431
|
return compareMultiple(object3, other, orders);
|
|
11302
11432
|
});
|
|
11303
11433
|
}
|
|
11304
|
-
function basePropertyDeep(
|
|
11434
|
+
function basePropertyDeep(path21) {
|
|
11305
11435
|
return function(object3) {
|
|
11306
|
-
return baseGet(object3,
|
|
11436
|
+
return baseGet(object3, path21);
|
|
11307
11437
|
};
|
|
11308
11438
|
}
|
|
11309
11439
|
function baseRest(func, start2) {
|
|
@@ -11558,11 +11688,11 @@ function requireLodash_sortby() {
|
|
|
11558
11688
|
return result;
|
|
11559
11689
|
};
|
|
11560
11690
|
}
|
|
11561
|
-
function hasPath(object3,
|
|
11562
|
-
|
|
11563
|
-
var result, index = -1, length =
|
|
11691
|
+
function hasPath(object3, path21, hasFunc) {
|
|
11692
|
+
path21 = isKey(path21, object3) ? [path21] : castPath(path21);
|
|
11693
|
+
var result, index = -1, length = path21.length;
|
|
11564
11694
|
while (++index < length) {
|
|
11565
|
-
var key = toKey(
|
|
11695
|
+
var key = toKey(path21[index]);
|
|
11566
11696
|
if (!(result = object3 != null && hasFunc(object3, key))) {
|
|
11567
11697
|
break;
|
|
11568
11698
|
}
|
|
@@ -11717,12 +11847,12 @@ function requireLodash_sortby() {
|
|
|
11717
11847
|
function toString(value) {
|
|
11718
11848
|
return value == null ? "" : baseToString(value);
|
|
11719
11849
|
}
|
|
11720
|
-
function get(object3,
|
|
11721
|
-
var result = object3 == null ? void 0 : baseGet(object3,
|
|
11850
|
+
function get(object3, path21, defaultValue) {
|
|
11851
|
+
var result = object3 == null ? void 0 : baseGet(object3, path21);
|
|
11722
11852
|
return result === void 0 ? defaultValue : result;
|
|
11723
11853
|
}
|
|
11724
|
-
function hasIn(object3,
|
|
11725
|
-
return object3 != null && hasPath(object3,
|
|
11854
|
+
function hasIn(object3, path21) {
|
|
11855
|
+
return object3 != null && hasPath(object3, path21, baseHasIn);
|
|
11726
11856
|
}
|
|
11727
11857
|
function keys(object3) {
|
|
11728
11858
|
return isArrayLike(object3) ? arrayLikeKeys(object3) : baseKeys(object3);
|
|
@@ -11730,8 +11860,8 @@ function requireLodash_sortby() {
|
|
|
11730
11860
|
function identity(value) {
|
|
11731
11861
|
return value;
|
|
11732
11862
|
}
|
|
11733
|
-
function property(
|
|
11734
|
-
return isKey(
|
|
11863
|
+
function property(path21) {
|
|
11864
|
+
return isKey(path21) ? baseProperty(toKey(path21)) : basePropertyDeep(path21);
|
|
11735
11865
|
}
|
|
11736
11866
|
module.exports = sortBy;
|
|
11737
11867
|
})(lodash_sortby, lodash_sortby.exports);
|
|
@@ -12162,16 +12292,16 @@ async function reportIfFoundPrivateKeyP12Format({ source, context, t }) {
|
|
|
12162
12292
|
return;
|
|
12163
12293
|
}
|
|
12164
12294
|
try {
|
|
12165
|
-
const
|
|
12166
|
-
const
|
|
12167
|
-
const pfxBytes = new Uint8Array(
|
|
12295
|
+
const fs17 = await import("node:fs");
|
|
12296
|
+
const path21 = await import("node:path");
|
|
12297
|
+
const pfxBytes = new Uint8Array(fs17.readFileSync(source.filePath));
|
|
12168
12298
|
const isGcpServiceAccountP12 = await verifyPkcs12Mac(pfxBytes, GCP_SERVICE_ACCOUNT_P12_PASSWORD);
|
|
12169
12299
|
if (!isGcpServiceAccountP12) {
|
|
12170
12300
|
return;
|
|
12171
12301
|
}
|
|
12172
12302
|
context.report({
|
|
12173
12303
|
message: t("PrivateKeyP12", {
|
|
12174
|
-
FILE_NAME:
|
|
12304
|
+
FILE_NAME: path21.basename(source.filePath)
|
|
12175
12305
|
}),
|
|
12176
12306
|
range: [0, source.content.length]
|
|
12177
12307
|
});
|
|
@@ -12198,7 +12328,7 @@ function reportIfFoundPrivateKeyJSONFormat({ source, context, t }) {
|
|
|
12198
12328
|
}
|
|
12199
12329
|
context.report({
|
|
12200
12330
|
message: t("PrivateKeyJSON", {
|
|
12201
|
-
FILE_NAME: source.filePath ?
|
|
12331
|
+
FILE_NAME: source.filePath ? path7.basename(source.filePath) : ""
|
|
12202
12332
|
}),
|
|
12203
12333
|
range: [0, source.content.length]
|
|
12204
12334
|
});
|
|
@@ -14272,7 +14402,7 @@ function parseOutboxEvent(line) {
|
|
|
14272
14402
|
return event3;
|
|
14273
14403
|
}
|
|
14274
14404
|
function defaultCollectorDirectory(source) {
|
|
14275
|
-
return
|
|
14405
|
+
return path8.join(os5.homedir(), ".config", "iteration", `${source}-collector`);
|
|
14276
14406
|
}
|
|
14277
14407
|
function validateCollectorEndpoint(endpoint) {
|
|
14278
14408
|
let parsed;
|
|
@@ -14298,18 +14428,18 @@ function validateCollectorEndpoint(endpoint) {
|
|
|
14298
14428
|
}
|
|
14299
14429
|
function collectorPaths(directory = defaultCollectorDirectory("codex")) {
|
|
14300
14430
|
return {
|
|
14301
|
-
config:
|
|
14302
|
-
state:
|
|
14303
|
-
outbox:
|
|
14304
|
-
redactionMarker:
|
|
14305
|
-
lock:
|
|
14306
|
-
historyBackfill:
|
|
14307
|
-
health:
|
|
14431
|
+
config: path8.join(directory, "config.json"),
|
|
14432
|
+
state: path8.join(directory, "state.json"),
|
|
14433
|
+
outbox: path8.join(directory, "outbox.jsonl"),
|
|
14434
|
+
redactionMarker: path8.join(directory, "redaction.json"),
|
|
14435
|
+
lock: path8.join(directory, "collector.lock"),
|
|
14436
|
+
historyBackfill: path8.join(directory, "history-backfill.json"),
|
|
14437
|
+
health: path8.join(directory, "health.json")
|
|
14308
14438
|
};
|
|
14309
14439
|
}
|
|
14310
14440
|
async function assertFreshCollectorDirectory(paths, source = "codex") {
|
|
14311
14441
|
const state = await loadState(paths);
|
|
14312
|
-
const configExists = await
|
|
14442
|
+
const configExists = await fs8.access(paths.config).then(() => true).catch((error2) => {
|
|
14313
14443
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14314
14444
|
return false;
|
|
14315
14445
|
throw error2;
|
|
@@ -14326,12 +14456,12 @@ async function assertFreshCollectorDirectory(paths, source = "codex") {
|
|
|
14326
14456
|
}
|
|
14327
14457
|
}
|
|
14328
14458
|
async function ensurePrivateDirectory(directory) {
|
|
14329
|
-
await
|
|
14330
|
-
await
|
|
14459
|
+
await fs8.mkdir(directory, { recursive: true, mode: 448 });
|
|
14460
|
+
await fs8.chmod(directory, 448);
|
|
14331
14461
|
}
|
|
14332
14462
|
async function writeJsonAtomic(filename, value) {
|
|
14333
14463
|
const temporary = `${filename}.${process.pid}.tmp`;
|
|
14334
|
-
const handle = await
|
|
14464
|
+
const handle = await fs8.open(temporary, "w", 384);
|
|
14335
14465
|
try {
|
|
14336
14466
|
await handle.writeFile(`${JSON.stringify(value)}
|
|
14337
14467
|
`, "utf8");
|
|
@@ -14339,16 +14469,16 @@ async function writeJsonAtomic(filename, value) {
|
|
|
14339
14469
|
} finally {
|
|
14340
14470
|
await handle.close();
|
|
14341
14471
|
}
|
|
14342
|
-
await
|
|
14472
|
+
await fs8.rename(temporary, filename);
|
|
14343
14473
|
}
|
|
14344
14474
|
async function saveConfig(config2, paths) {
|
|
14345
|
-
await ensurePrivateDirectory(
|
|
14475
|
+
await ensurePrivateDirectory(path8.dirname(paths.config));
|
|
14346
14476
|
await writeJsonAtomic(paths.config, config2);
|
|
14347
14477
|
}
|
|
14348
14478
|
async function loadHealth(paths) {
|
|
14349
14479
|
let contents;
|
|
14350
14480
|
try {
|
|
14351
|
-
contents = await
|
|
14481
|
+
contents = await fs8.readFile(paths.health, "utf8");
|
|
14352
14482
|
} catch (error2) {
|
|
14353
14483
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
14354
14484
|
return {
|
|
@@ -14390,13 +14520,13 @@ async function healthSnapshot(paths) {
|
|
|
14390
14520
|
}
|
|
14391
14521
|
}
|
|
14392
14522
|
async function saveHealth(health, paths) {
|
|
14393
|
-
await ensurePrivateDirectory(
|
|
14523
|
+
await ensurePrivateDirectory(path8.dirname(paths.health));
|
|
14394
14524
|
await writeJsonAtomic(paths.health, health);
|
|
14395
14525
|
}
|
|
14396
14526
|
async function loadConfig(paths) {
|
|
14397
14527
|
let contents;
|
|
14398
14528
|
try {
|
|
14399
|
-
contents = await
|
|
14529
|
+
contents = await fs8.readFile(paths.config, "utf8");
|
|
14400
14530
|
} catch (error2) {
|
|
14401
14531
|
throw new Error(`Could not read collector config at ${paths.config}.`, {
|
|
14402
14532
|
cause: error2
|
|
@@ -14423,7 +14553,7 @@ async function loadConfig(paths) {
|
|
|
14423
14553
|
async function loadState(paths) {
|
|
14424
14554
|
let contents;
|
|
14425
14555
|
try {
|
|
14426
|
-
contents = await
|
|
14556
|
+
contents = await fs8.readFile(paths.state, "utf8");
|
|
14427
14557
|
} catch (error2) {
|
|
14428
14558
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
14429
14559
|
return {
|
|
@@ -14453,12 +14583,12 @@ async function loadState(paths) {
|
|
|
14453
14583
|
return parsed.data;
|
|
14454
14584
|
}
|
|
14455
14585
|
async function saveState(state, paths) {
|
|
14456
|
-
await ensurePrivateDirectory(
|
|
14586
|
+
await ensurePrivateDirectory(path8.dirname(paths.state));
|
|
14457
14587
|
await writeJsonAtomic(paths.state, state);
|
|
14458
14588
|
}
|
|
14459
14589
|
async function outboxSize(paths) {
|
|
14460
14590
|
try {
|
|
14461
|
-
return (await
|
|
14591
|
+
return (await fs8.stat(paths.outbox)).size;
|
|
14462
14592
|
} catch (error2) {
|
|
14463
14593
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14464
14594
|
return 0;
|
|
@@ -14473,7 +14603,7 @@ async function pendingEventIds(paths) {
|
|
|
14473
14603
|
if (size > MAX_OUTBOX_BYTES)
|
|
14474
14604
|
throw new Error("Trace outbox exceeds its configured capacity.");
|
|
14475
14605
|
try {
|
|
14476
|
-
const input = await
|
|
14606
|
+
const input = await fs8.open(paths.outbox, "r");
|
|
14477
14607
|
const stream = input.createReadStream({
|
|
14478
14608
|
autoClose: true,
|
|
14479
14609
|
encoding: "utf8"
|
|
@@ -14500,8 +14630,8 @@ async function appendEvents(events, paths) {
|
|
|
14500
14630
|
"Trace outbox is full; upload pending data before scanning more sessions."
|
|
14501
14631
|
);
|
|
14502
14632
|
}
|
|
14503
|
-
await ensurePrivateDirectory(
|
|
14504
|
-
const handle = await
|
|
14633
|
+
await ensurePrivateDirectory(path8.dirname(paths.outbox));
|
|
14634
|
+
const handle = await fs8.open(paths.outbox, "a", 384);
|
|
14505
14635
|
try {
|
|
14506
14636
|
await handle.writeFile(contents, "utf8");
|
|
14507
14637
|
await handle.sync();
|
|
@@ -14512,7 +14642,7 @@ async function appendEvents(events, paths) {
|
|
|
14512
14642
|
async function redactPendingOutbox(paths) {
|
|
14513
14643
|
let marker;
|
|
14514
14644
|
try {
|
|
14515
|
-
marker = await
|
|
14645
|
+
marker = await fs8.readFile(paths.redactionMarker, "utf8");
|
|
14516
14646
|
} catch (error2) {
|
|
14517
14647
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14518
14648
|
marker = null;
|
|
@@ -14534,7 +14664,7 @@ async function redactPendingOutbox(paths) {
|
|
|
14534
14664
|
throw new Error("Trace outbox exceeds its configured capacity.");
|
|
14535
14665
|
if (size > 0) {
|
|
14536
14666
|
const temporary = `${paths.outbox}.${process.pid}.redacting`;
|
|
14537
|
-
const output = await
|
|
14667
|
+
const output = await fs8.open(temporary, "w", 384);
|
|
14538
14668
|
try {
|
|
14539
14669
|
const lines = createInterface2({
|
|
14540
14670
|
input: createReadStream(paths.outbox, { encoding: "utf8" }),
|
|
@@ -14548,28 +14678,28 @@ async function redactPendingOutbox(paths) {
|
|
|
14548
14678
|
await output.sync();
|
|
14549
14679
|
} catch (error2) {
|
|
14550
14680
|
await output.close();
|
|
14551
|
-
await
|
|
14681
|
+
await fs8.rm(temporary, { force: true });
|
|
14552
14682
|
throw error2;
|
|
14553
14683
|
}
|
|
14554
14684
|
await output.close();
|
|
14555
14685
|
try {
|
|
14556
|
-
if ((await
|
|
14686
|
+
if ((await fs8.stat(temporary)).size > MAX_OUTBOX_BYTES)
|
|
14557
14687
|
throw new Error(
|
|
14558
14688
|
"Redacted trace outbox exceeds its configured capacity."
|
|
14559
14689
|
);
|
|
14560
|
-
await
|
|
14561
|
-
const directory = await
|
|
14690
|
+
await fs8.rename(temporary, paths.outbox);
|
|
14691
|
+
const directory = await fs8.open(path8.dirname(paths.outbox), "r");
|
|
14562
14692
|
try {
|
|
14563
14693
|
await directory.sync();
|
|
14564
14694
|
} finally {
|
|
14565
14695
|
await directory.close();
|
|
14566
14696
|
}
|
|
14567
14697
|
} catch (error2) {
|
|
14568
|
-
await
|
|
14698
|
+
await fs8.rm(temporary, { force: true });
|
|
14569
14699
|
throw error2;
|
|
14570
14700
|
}
|
|
14571
14701
|
}
|
|
14572
|
-
await ensurePrivateDirectory(
|
|
14702
|
+
await ensurePrivateDirectory(path8.dirname(paths.redactionMarker));
|
|
14573
14703
|
await writeJsonAtomic(paths.redactionMarker, { version: 1 });
|
|
14574
14704
|
}
|
|
14575
14705
|
async function nextBatch(paths, maxBytes) {
|
|
@@ -14580,7 +14710,7 @@ async function nextBatch(paths, maxBytes) {
|
|
|
14580
14710
|
if (size === 0) return [];
|
|
14581
14711
|
if (size > MAX_OUTBOX_BYTES)
|
|
14582
14712
|
throw new Error("Trace outbox exceeds its configured capacity.");
|
|
14583
|
-
const input = await
|
|
14713
|
+
const input = await fs8.open(paths.outbox, "r");
|
|
14584
14714
|
const stream = input.createReadStream({ autoClose: true, encoding: "utf8" });
|
|
14585
14715
|
const lines = createInterface2({ input: stream, crlfDelay: Infinity });
|
|
14586
14716
|
const events = [];
|
|
@@ -14604,7 +14734,7 @@ async function nextBatch(paths, maxBytes) {
|
|
|
14604
14734
|
}
|
|
14605
14735
|
async function acknowledgeEvents(acknowledgedIds, paths) {
|
|
14606
14736
|
const temporary = `${paths.outbox}.${process.pid}.tmp`;
|
|
14607
|
-
const input = await
|
|
14737
|
+
const input = await fs8.open(paths.outbox, "r").catch((error2) => {
|
|
14608
14738
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14609
14739
|
return null;
|
|
14610
14740
|
throw error2;
|
|
@@ -14625,28 +14755,28 @@ async function acknowledgeEvents(acknowledgedIds, paths) {
|
|
|
14625
14755
|
}
|
|
14626
14756
|
output.end();
|
|
14627
14757
|
await once(output, "close");
|
|
14628
|
-
const durable = await
|
|
14758
|
+
const durable = await fs8.open(temporary, "r+");
|
|
14629
14759
|
try {
|
|
14630
14760
|
await durable.sync();
|
|
14631
14761
|
} finally {
|
|
14632
14762
|
await durable.close();
|
|
14633
14763
|
}
|
|
14634
|
-
await
|
|
14764
|
+
await fs8.rename(temporary, paths.outbox);
|
|
14635
14765
|
} catch (error2) {
|
|
14636
14766
|
output.destroy();
|
|
14637
|
-
await
|
|
14767
|
+
await fs8.rm(temporary, { force: true });
|
|
14638
14768
|
throw error2;
|
|
14639
14769
|
}
|
|
14640
14770
|
}
|
|
14641
14771
|
async function prepareSourceInitialState(source, home, includeHistory) {
|
|
14642
|
-
const sourceDirectory =
|
|
14772
|
+
const sourceDirectory = path8.resolve(home);
|
|
14643
14773
|
const files = await sourceFiles(source, sourceDirectory);
|
|
14644
14774
|
const cursors = {};
|
|
14645
14775
|
if (!includeHistory) {
|
|
14646
14776
|
for (const filename of files) {
|
|
14647
14777
|
cursors[filename] = await lastCompleteRecordOffset(
|
|
14648
14778
|
filename,
|
|
14649
|
-
(await
|
|
14779
|
+
(await fs8.stat(filename)).size
|
|
14650
14780
|
);
|
|
14651
14781
|
}
|
|
14652
14782
|
}
|
|
@@ -14662,7 +14792,7 @@ async function ensureStartableState(config2, paths) {
|
|
|
14662
14792
|
const directory = collectorSourceDirectory(config2);
|
|
14663
14793
|
const state = await loadState(paths);
|
|
14664
14794
|
if (state.initialized) {
|
|
14665
|
-
if (state.sourceDirectory !==
|
|
14795
|
+
if (state.sourceDirectory !== path8.resolve(directory)) {
|
|
14666
14796
|
throw new Error(
|
|
14667
14797
|
`Collector state belongs to another ${source} directory; use a separate --data-directory.`
|
|
14668
14798
|
);
|
|
@@ -14685,7 +14815,7 @@ var historyBackfillMarkerSchema = object({ version: number2().int().min(1).max(H
|
|
|
14685
14815
|
async function readHistoryBackfillVersion(paths) {
|
|
14686
14816
|
let marker;
|
|
14687
14817
|
try {
|
|
14688
|
-
marker = await
|
|
14818
|
+
marker = await fs8.readFile(paths.historyBackfill, "utf8");
|
|
14689
14819
|
} catch (error2) {
|
|
14690
14820
|
if (!(error2 instanceof Error && "code" in error2 && error2.code === "ENOENT"))
|
|
14691
14821
|
throw error2;
|
|
@@ -14711,12 +14841,12 @@ async function ensureHistoryBackfill(codexHome, paths) {
|
|
|
14711
14841
|
const version2 = await readHistoryBackfillVersion(paths);
|
|
14712
14842
|
if (version2 !== null && version2 >= HISTORY_BACKFILL_VERSION) return false;
|
|
14713
14843
|
const state = await loadState(paths);
|
|
14714
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
14844
|
+
if (!state.initialized || state.sourceDirectory !== path8.resolve(codexHome)) {
|
|
14715
14845
|
throw new Error(
|
|
14716
14846
|
"Collector state does not match CODEX_HOME; reconnect Codex before uploading."
|
|
14717
14847
|
);
|
|
14718
14848
|
}
|
|
14719
|
-
await rolloutFiles(
|
|
14849
|
+
await rolloutFiles(path8.join(codexHome, "sessions"));
|
|
14720
14850
|
await saveState({ ...state, cursors: {}, skipping: [] }, paths);
|
|
14721
14851
|
await writeJsonAtomic(paths.historyBackfill, {
|
|
14722
14852
|
version: HISTORY_BACKFILL_VERSION
|
|
@@ -14729,12 +14859,12 @@ async function ensureClaudeHistoryBackfill(claudeHome, paths) {
|
|
|
14729
14859
|
if (version2 !== null && version2 >= CLAUDE_HISTORY_BACKFILL_VERSION)
|
|
14730
14860
|
return false;
|
|
14731
14861
|
const state = await loadState(paths);
|
|
14732
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
14862
|
+
if (!state.initialized || state.sourceDirectory !== path8.resolve(claudeHome)) {
|
|
14733
14863
|
throw new Error(
|
|
14734
14864
|
"Collector state does not match the Claude directory; reconnect Claude before uploading."
|
|
14735
14865
|
);
|
|
14736
14866
|
}
|
|
14737
|
-
await claudeFiles(
|
|
14867
|
+
await claudeFiles(path8.join(claudeHome, "projects"));
|
|
14738
14868
|
await saveState({ ...state, cursors: {}, skipping: [] }, paths);
|
|
14739
14869
|
await writeJsonAtomic(paths.historyBackfill, {
|
|
14740
14870
|
version: CLAUDE_HISTORY_BACKFILL_VERSION
|
|
@@ -14745,11 +14875,11 @@ async function ensureCursorHistoryBackfill(cursorHome, paths) {
|
|
|
14745
14875
|
const version2 = await readHistoryBackfillVersion(paths);
|
|
14746
14876
|
if (version2 !== null && version2 >= 1) return false;
|
|
14747
14877
|
const state = await loadState(paths);
|
|
14748
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
14878
|
+
if (!state.initialized || state.sourceDirectory !== path8.resolve(cursorHome))
|
|
14749
14879
|
throw new Error(
|
|
14750
14880
|
"Collector state does not match the Cursor directory; reconnect Cursor before uploading."
|
|
14751
14881
|
);
|
|
14752
|
-
await cursorFiles(
|
|
14882
|
+
await cursorFiles(path8.join(cursorHome, "projects"));
|
|
14753
14883
|
await saveState({ ...state, cursors: {}, skipping: [] }, paths);
|
|
14754
14884
|
await writeJsonAtomic(paths.historyBackfill, { version: 1 });
|
|
14755
14885
|
return true;
|
|
@@ -14758,7 +14888,7 @@ async function historyScanProgress(home, paths, source = "codex") {
|
|
|
14758
14888
|
if (await readHistoryBackfillVersion(paths) === null)
|
|
14759
14889
|
return { kind: "waiting" };
|
|
14760
14890
|
const state = await loadState(paths);
|
|
14761
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
14891
|
+
if (!state.initialized || state.sourceDirectory !== path8.resolve(home)) {
|
|
14762
14892
|
throw new Error(`Collector state does not match the ${source} directory.`);
|
|
14763
14893
|
}
|
|
14764
14894
|
const files = await sourceFiles(source, home);
|
|
@@ -14769,7 +14899,7 @@ async function historyScanProgress(home, paths, source = "codex") {
|
|
|
14769
14899
|
const sizes = await Promise.all(
|
|
14770
14900
|
batch.map(async (filename) => {
|
|
14771
14901
|
try {
|
|
14772
|
-
return (await
|
|
14902
|
+
return (await fs8.stat(filename)).size;
|
|
14773
14903
|
} catch (error2) {
|
|
14774
14904
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14775
14905
|
return null;
|
|
@@ -14793,7 +14923,7 @@ async function historyScanProgress(home, paths, source = "codex") {
|
|
|
14793
14923
|
}
|
|
14794
14924
|
async function resetHistoryCursors(home, paths, source = "codex") {
|
|
14795
14925
|
const state = await loadState(paths);
|
|
14796
|
-
const sourceDirectory =
|
|
14926
|
+
const sourceDirectory = path8.resolve(home);
|
|
14797
14927
|
if (!state.initialized || state.sourceDirectory !== sourceDirectory) {
|
|
14798
14928
|
throw new Error(
|
|
14799
14929
|
`Connect the ${source} source before importing its history.`
|
|
@@ -14804,7 +14934,7 @@ async function resetHistoryCursors(home, paths, source = "codex") {
|
|
|
14804
14934
|
let bytes = 0;
|
|
14805
14935
|
for (const filename of files) {
|
|
14806
14936
|
cursors[filename] = 0;
|
|
14807
|
-
bytes += (await
|
|
14937
|
+
bytes += (await fs8.stat(filename)).size;
|
|
14808
14938
|
if (!Number.isSafeInteger(bytes)) {
|
|
14809
14939
|
throw new Error(`${source} history size exceeds the supported limit.`);
|
|
14810
14940
|
}
|
|
@@ -14831,7 +14961,7 @@ async function historyImportSize(home, source = "codex") {
|
|
|
14831
14961
|
const files = await sourceFiles(source, home);
|
|
14832
14962
|
let bytes = 0;
|
|
14833
14963
|
for (const filename of files) {
|
|
14834
|
-
bytes += (await
|
|
14964
|
+
bytes += (await fs8.stat(filename)).size;
|
|
14835
14965
|
if (!Number.isSafeInteger(bytes)) {
|
|
14836
14966
|
throw new Error(`${source} history size exceeds the supported limit.`);
|
|
14837
14967
|
}
|
|
@@ -14840,7 +14970,7 @@ async function historyImportSize(home, source = "codex") {
|
|
|
14840
14970
|
}
|
|
14841
14971
|
async function lastCompleteRecordOffset(filename, size) {
|
|
14842
14972
|
if (size === 0) return 0;
|
|
14843
|
-
const handle = await
|
|
14973
|
+
const handle = await fs8.open(filename, "r");
|
|
14844
14974
|
try {
|
|
14845
14975
|
const finalByte = Buffer.alloc(1);
|
|
14846
14976
|
await handle.read(finalByte, 0, 1, size - 1);
|
|
@@ -14871,7 +15001,7 @@ async function rolloutFiles(root) {
|
|
|
14871
15001
|
if (directory === void 0) continue;
|
|
14872
15002
|
let entries2;
|
|
14873
15003
|
try {
|
|
14874
|
-
entries2 = await
|
|
15004
|
+
entries2 = await fs8.readdir(directory, { withFileTypes: true });
|
|
14875
15005
|
} catch (error2) {
|
|
14876
15006
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14877
15007
|
continue;
|
|
@@ -14880,7 +15010,7 @@ async function rolloutFiles(root) {
|
|
|
14880
15010
|
});
|
|
14881
15011
|
}
|
|
14882
15012
|
for (const entry of entries2) {
|
|
14883
|
-
const filename =
|
|
15013
|
+
const filename = path8.join(directory, entry.name);
|
|
14884
15014
|
if (entry.isDirectory()) pending.push(filename);
|
|
14885
15015
|
else if (entry.isFile() && /^rollout-.+\.jsonl$/.test(entry.name))
|
|
14886
15016
|
files.push(filename);
|
|
@@ -14892,7 +15022,7 @@ async function rolloutFiles(root) {
|
|
|
14892
15022
|
}
|
|
14893
15023
|
async function directoryEntries(directory) {
|
|
14894
15024
|
try {
|
|
14895
|
-
return await
|
|
15025
|
+
return await fs8.readdir(directory, { withFileTypes: true });
|
|
14896
15026
|
} catch (error2) {
|
|
14897
15027
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14898
15028
|
return [];
|
|
@@ -14905,16 +15035,16 @@ async function claudeFiles(root) {
|
|
|
14905
15035
|
const files = [];
|
|
14906
15036
|
for (const project of await directoryEntries(root)) {
|
|
14907
15037
|
if (!project.isDirectory()) continue;
|
|
14908
|
-
const projectDirectory =
|
|
15038
|
+
const projectDirectory = path8.join(root, project.name);
|
|
14909
15039
|
for (const entry of await directoryEntries(projectDirectory)) {
|
|
14910
|
-
const filename =
|
|
15040
|
+
const filename = path8.join(projectDirectory, entry.name);
|
|
14911
15041
|
if (entry.isFile() && /^[^/]+\.jsonl$/.test(entry.name) && !entry.name.includes(".orphaned-") && !entry.name.includes(".superseded-")) {
|
|
14912
15042
|
files.push(filename);
|
|
14913
15043
|
} else if (entry.isDirectory()) {
|
|
14914
|
-
const subagents =
|
|
15044
|
+
const subagents = path8.join(filename, "subagents");
|
|
14915
15045
|
for (const agent of await directoryEntries(subagents)) {
|
|
14916
15046
|
if (agent.isFile() && /^agent-.+\.jsonl$/.test(agent.name))
|
|
14917
|
-
files.push(
|
|
15047
|
+
files.push(path8.join(subagents, agent.name));
|
|
14918
15048
|
}
|
|
14919
15049
|
}
|
|
14920
15050
|
if (files.length > 1e4)
|
|
@@ -14924,10 +15054,10 @@ async function claudeFiles(root) {
|
|
|
14924
15054
|
return files.sort();
|
|
14925
15055
|
}
|
|
14926
15056
|
function sourceFiles(source, home) {
|
|
14927
|
-
const directory =
|
|
14928
|
-
if (source === "codex") return rolloutFiles(
|
|
14929
|
-
if (source === "claude") return claudeFiles(
|
|
14930
|
-
if (source === "cursor") return cursorFiles(
|
|
15057
|
+
const directory = path8.resolve(home);
|
|
15058
|
+
if (source === "codex") return rolloutFiles(path8.join(directory, "sessions"));
|
|
15059
|
+
if (source === "claude") return claudeFiles(path8.join(directory, "projects"));
|
|
15060
|
+
if (source === "cursor") return cursorFiles(path8.join(directory, "projects"));
|
|
14931
15061
|
const unhandled = source;
|
|
14932
15062
|
throw new Error(`Unsupported collector source: ${unhandled}`);
|
|
14933
15063
|
}
|
|
@@ -14935,15 +15065,15 @@ async function cursorFiles(root) {
|
|
|
14935
15065
|
const files = [];
|
|
14936
15066
|
for (const project of await directoryEntries(root)) {
|
|
14937
15067
|
if (!project.isDirectory()) continue;
|
|
14938
|
-
const transcripts =
|
|
15068
|
+
const transcripts = path8.join(root, project.name, "agent-transcripts");
|
|
14939
15069
|
for (const conversation of await directoryEntries(transcripts)) {
|
|
14940
15070
|
if (!conversation.isDirectory()) continue;
|
|
14941
|
-
const filename =
|
|
15071
|
+
const filename = path8.join(
|
|
14942
15072
|
transcripts,
|
|
14943
15073
|
conversation.name,
|
|
14944
15074
|
`${conversation.name}.jsonl`
|
|
14945
15075
|
);
|
|
14946
|
-
if ((await
|
|
15076
|
+
if ((await fs8.stat(filename).catch((error2) => {
|
|
14947
15077
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
14948
15078
|
return null;
|
|
14949
15079
|
throw error2;
|
|
@@ -14956,6 +15086,114 @@ async function cursorFiles(root) {
|
|
|
14956
15086
|
return files.sort();
|
|
14957
15087
|
}
|
|
14958
15088
|
|
|
15089
|
+
// src/claude-git-hook.ts
|
|
15090
|
+
function claudeGitHookPaths(dataDirectory2) {
|
|
15091
|
+
return {
|
|
15092
|
+
script: path9.join(dataDirectory2, "claude-git", "session-start-hook.sh"),
|
|
15093
|
+
queue: path9.join(dataDirectory2, "claude-git", "git-queue"),
|
|
15094
|
+
failure: path9.join(dataDirectory2, "claude-git", "failure.json")
|
|
15095
|
+
};
|
|
15096
|
+
}
|
|
15097
|
+
var startInput = object({
|
|
15098
|
+
hook_event_name: literal("SessionStart"),
|
|
15099
|
+
session_id: uuid2(),
|
|
15100
|
+
cwd: string2().min(1)
|
|
15101
|
+
});
|
|
15102
|
+
async function captureClaudeGit(dataDirectory2) {
|
|
15103
|
+
let inputText = "";
|
|
15104
|
+
for await (const chunk of process.stdin) {
|
|
15105
|
+
inputText += chunk.toString();
|
|
15106
|
+
if (inputText.length > 64e3)
|
|
15107
|
+
throw new Error("Claude start hook input is too large.");
|
|
15108
|
+
}
|
|
15109
|
+
const input = startInput.parse(JSON.parse(inputText));
|
|
15110
|
+
if (!path9.isAbsolute(input.cwd))
|
|
15111
|
+
throw new Error("Claude start hook cwd is not absolute.");
|
|
15112
|
+
const git = {
|
|
15113
|
+
branch: gitBranch(input.cwd),
|
|
15114
|
+
repository_url: gitRepositoryUrl(input.cwd)
|
|
15115
|
+
};
|
|
15116
|
+
if (git.branch === null && git.repository_url === null) {
|
|
15117
|
+
process.stdout.write("{}\n");
|
|
15118
|
+
return;
|
|
15119
|
+
}
|
|
15120
|
+
const directory = claudeGitHookPaths(dataDirectory2).queue;
|
|
15121
|
+
await ensurePrivateDirectory(directory);
|
|
15122
|
+
const files = (await fs9.readdir(directory)).filter(
|
|
15123
|
+
(name2) => name2.endsWith(".jsonl")
|
|
15124
|
+
);
|
|
15125
|
+
if (files.length >= 1e4) throw new Error("Claude Git hook queue is full.");
|
|
15126
|
+
const name = randomUUID();
|
|
15127
|
+
const temporary = path9.join(directory, `${name}.tmp`);
|
|
15128
|
+
const filename = path9.join(directory, `${name}.jsonl`);
|
|
15129
|
+
try {
|
|
15130
|
+
const handle = await fs9.open(temporary, "wx", 384);
|
|
15131
|
+
try {
|
|
15132
|
+
await handle.writeFile(
|
|
15133
|
+
`${JSON.stringify({ sessionId: input.session_id, cwd: input.cwd, git, capturedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
15134
|
+
`
|
|
15135
|
+
);
|
|
15136
|
+
await handle.sync();
|
|
15137
|
+
} finally {
|
|
15138
|
+
await handle.close();
|
|
15139
|
+
}
|
|
15140
|
+
await fs9.rename(temporary, filename);
|
|
15141
|
+
} catch (error2) {
|
|
15142
|
+
await fs9.rm(temporary, { force: true });
|
|
15143
|
+
throw error2;
|
|
15144
|
+
}
|
|
15145
|
+
process.stdout.write("{}\n");
|
|
15146
|
+
}
|
|
15147
|
+
async function claudeGitHookFiles(dataDirectory2) {
|
|
15148
|
+
const directory = claudeGitHookPaths(dataDirectory2).queue;
|
|
15149
|
+
let names;
|
|
15150
|
+
try {
|
|
15151
|
+
names = await fs9.readdir(directory);
|
|
15152
|
+
} catch (error2) {
|
|
15153
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15154
|
+
return [];
|
|
15155
|
+
throw error2;
|
|
15156
|
+
}
|
|
15157
|
+
if (names.length > 1e4)
|
|
15158
|
+
throw new Error("Claude Git hook queue exceeds the scan limit.");
|
|
15159
|
+
return names.filter((name) => /^[0-9a-f-]+\.jsonl$/i.test(name)).sort().map((name) => path9.join(directory, name));
|
|
15160
|
+
}
|
|
15161
|
+
async function recordClaudeGitHookFailure(dataDirectory2) {
|
|
15162
|
+
const filename = claudeGitHookPaths(dataDirectory2).failure;
|
|
15163
|
+
await ensurePrivateDirectory(path9.dirname(filename));
|
|
15164
|
+
const temporary = `${filename}.${randomUUID()}.tmp`;
|
|
15165
|
+
await fs9.writeFile(temporary, `${JSON.stringify({ atMs: Date.now() })}
|
|
15166
|
+
`, {
|
|
15167
|
+
mode: 384
|
|
15168
|
+
});
|
|
15169
|
+
await fs9.rename(temporary, filename);
|
|
15170
|
+
}
|
|
15171
|
+
async function claudeGitHookFailureAtMs(dataDirectory2) {
|
|
15172
|
+
let contents;
|
|
15173
|
+
try {
|
|
15174
|
+
contents = await fs9.readFile(
|
|
15175
|
+
claudeGitHookPaths(dataDirectory2).failure,
|
|
15176
|
+
"utf8"
|
|
15177
|
+
);
|
|
15178
|
+
} catch (error2) {
|
|
15179
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15180
|
+
return null;
|
|
15181
|
+
throw error2;
|
|
15182
|
+
}
|
|
15183
|
+
let decoded;
|
|
15184
|
+
try {
|
|
15185
|
+
decoded = JSON.parse(contents);
|
|
15186
|
+
} catch (error2) {
|
|
15187
|
+
throw new Error("Claude Git hook failure status is invalid.", {
|
|
15188
|
+
cause: error2
|
|
15189
|
+
});
|
|
15190
|
+
}
|
|
15191
|
+
const parsed = object({ atMs: number2().int().min(0) }).safeParse(decoded);
|
|
15192
|
+
if (!parsed.success)
|
|
15193
|
+
throw new Error("Claude Git hook failure status is invalid.");
|
|
15194
|
+
return parsed.data.atMs;
|
|
15195
|
+
}
|
|
15196
|
+
|
|
14959
15197
|
// src/turn-summary.ts
|
|
14960
15198
|
var REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
14961
15199
|
var REQUEST_RETENTION_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
@@ -14964,13 +15202,13 @@ var REQUEST_TIMEOUT_MS2 = 1e4;
|
|
|
14964
15202
|
var HOOK_TIMEOUT_SECONDS = 1;
|
|
14965
15203
|
var REQUEST_KEY_PATTERN = /^\d{1,10}$/;
|
|
14966
15204
|
function turnSummaryPaths(dataDirectory2) {
|
|
14967
|
-
const directory =
|
|
15205
|
+
const directory = path10.join(dataDirectory2, "turn-summary");
|
|
14968
15206
|
return {
|
|
14969
15207
|
directory,
|
|
14970
|
-
script:
|
|
14971
|
-
requests:
|
|
14972
|
-
lines:
|
|
14973
|
-
shown:
|
|
15208
|
+
script: path10.join(directory, "stop-hook.sh"),
|
|
15209
|
+
requests: path10.join(directory, "requests"),
|
|
15210
|
+
lines: path10.join(directory, "lines"),
|
|
15211
|
+
shown: path10.join(directory, "shown")
|
|
14974
15212
|
};
|
|
14975
15213
|
}
|
|
14976
15214
|
function shellQuote(value) {
|
|
@@ -15006,19 +15244,22 @@ function hookCommand(paths) {
|
|
|
15006
15244
|
function agentHookFile(config2) {
|
|
15007
15245
|
if ("cursorHome" in config2)
|
|
15008
15246
|
throw new Error("Cursor uses its own trace hooks.");
|
|
15009
|
-
return "claudeHome" in config2 ?
|
|
15247
|
+
return "claudeHome" in config2 ? path10.join(config2.claudeHome, "settings.json") : path10.join(config2.codexHome, "hooks.json");
|
|
15010
15248
|
}
|
|
15011
15249
|
var hookEntrySchema = object({ command: string2().optional() }).loose();
|
|
15012
15250
|
var hookGroupSchema = object({ hooks: array(hookEntrySchema).optional() }).loose();
|
|
15013
15251
|
var hookFileSchema = object({
|
|
15014
|
-
hooks: object({
|
|
15252
|
+
hooks: object({
|
|
15253
|
+
Stop: array(hookGroupSchema).optional(),
|
|
15254
|
+
SessionStart: array(hookGroupSchema).optional()
|
|
15255
|
+
}).loose().optional()
|
|
15015
15256
|
}).loose();
|
|
15016
15257
|
async function readHookFile(filename) {
|
|
15017
15258
|
let contents;
|
|
15018
15259
|
let mode = 384;
|
|
15019
15260
|
try {
|
|
15020
|
-
contents = await
|
|
15021
|
-
mode = (await
|
|
15261
|
+
contents = await fs10.readFile(filename, "utf8");
|
|
15262
|
+
mode = (await fs10.stat(filename)).mode & 511;
|
|
15022
15263
|
} catch (error2) {
|
|
15023
15264
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
15024
15265
|
return { file: {}, mode };
|
|
@@ -15044,19 +15285,19 @@ async function readHookFile(filename) {
|
|
|
15044
15285
|
return { file: parsed.data, mode };
|
|
15045
15286
|
}
|
|
15046
15287
|
async function writeHookFile(filename, file, mode) {
|
|
15047
|
-
await
|
|
15048
|
-
const target = await
|
|
15288
|
+
await fs10.mkdir(path10.dirname(filename), { recursive: true });
|
|
15289
|
+
const target = await fs10.realpath(filename).catch((error2) => {
|
|
15049
15290
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15050
15291
|
return filename;
|
|
15051
15292
|
throw error2;
|
|
15052
15293
|
});
|
|
15053
15294
|
const temporary = `${target}.iteration-${process.pid}.tmp`;
|
|
15054
|
-
await
|
|
15295
|
+
await fs10.writeFile(temporary, `${JSON.stringify(file, null, 2)}
|
|
15055
15296
|
`, {
|
|
15056
15297
|
encoding: "utf8",
|
|
15057
15298
|
mode
|
|
15058
15299
|
});
|
|
15059
|
-
await
|
|
15300
|
+
await fs10.rename(temporary, target);
|
|
15060
15301
|
}
|
|
15061
15302
|
function withoutCommand(groups, command) {
|
|
15062
15303
|
return groups.flatMap((group) => {
|
|
@@ -15077,30 +15318,55 @@ async function installTurnSummary(config2) {
|
|
|
15077
15318
|
await Promise.all(
|
|
15078
15319
|
[paths.requests, paths.lines, paths.shown].map(ensurePrivateDirectory)
|
|
15079
15320
|
);
|
|
15080
|
-
await
|
|
15321
|
+
await fs10.writeFile(paths.script, hookScript(paths), {
|
|
15081
15322
|
encoding: "utf8",
|
|
15082
15323
|
mode: 448
|
|
15083
15324
|
});
|
|
15084
|
-
await
|
|
15325
|
+
await fs10.chmod(paths.script, 448);
|
|
15326
|
+
if ("claudeHome" in config2) {
|
|
15327
|
+
const gitPaths = claudeGitHookPaths(config2.dataDirectory);
|
|
15328
|
+
await ensurePrivateDirectory(gitPaths.queue);
|
|
15329
|
+
await fs10.writeFile(
|
|
15330
|
+
gitPaths.script,
|
|
15331
|
+
`#!/bin/sh
|
|
15332
|
+
exec ${shellQuote(await stableNodePath())} ${shellQuote(runtimePaths().launcher)} claude-git-hook ${shellQuote(config2.dataDirectory)}
|
|
15333
|
+
`,
|
|
15334
|
+
{ encoding: "utf8", mode: 448 }
|
|
15335
|
+
);
|
|
15336
|
+
await fs10.chmod(gitPaths.script, 448);
|
|
15337
|
+
}
|
|
15085
15338
|
const filename = agentHookFile(config2);
|
|
15086
15339
|
const { file, mode } = await readHookFile(filename);
|
|
15087
15340
|
const command = hookCommand(paths);
|
|
15341
|
+
const startCommand = `/bin/sh ${shellQuote(claudeGitHookPaths(config2.dataDirectory).script)}`;
|
|
15088
15342
|
const stop = file.hooks?.Stop ?? [];
|
|
15089
|
-
|
|
15343
|
+
const start2 = file.hooks?.SessionStart ?? [];
|
|
15344
|
+
if (hasCommand(stop, command) && (!("claudeHome" in config2) || hasCommand(start2, startCommand)))
|
|
15345
|
+
return;
|
|
15090
15346
|
await writeHookFile(
|
|
15091
15347
|
filename,
|
|
15092
15348
|
{
|
|
15093
15349
|
...file,
|
|
15094
15350
|
hooks: {
|
|
15095
15351
|
...file.hooks,
|
|
15096
|
-
Stop: [
|
|
15352
|
+
Stop: hasCommand(stop, command) ? stop : [
|
|
15097
15353
|
...stop,
|
|
15098
15354
|
{
|
|
15099
15355
|
hooks: [
|
|
15100
15356
|
{ type: "command", command, timeout: HOOK_TIMEOUT_SECONDS }
|
|
15101
15357
|
]
|
|
15102
15358
|
}
|
|
15103
|
-
]
|
|
15359
|
+
],
|
|
15360
|
+
..."claudeHome" in config2 ? {
|
|
15361
|
+
SessionStart: hasCommand(start2, startCommand) ? start2 : [
|
|
15362
|
+
...start2,
|
|
15363
|
+
{
|
|
15364
|
+
hooks: [
|
|
15365
|
+
{ type: "command", command: startCommand, timeout: 3 }
|
|
15366
|
+
]
|
|
15367
|
+
}
|
|
15368
|
+
]
|
|
15369
|
+
} : {}
|
|
15104
15370
|
}
|
|
15105
15371
|
},
|
|
15106
15372
|
mode
|
|
@@ -15111,14 +15377,25 @@ async function uninstallTurnSummary(config2) {
|
|
|
15111
15377
|
const { file, mode } = await readHookFile(filename);
|
|
15112
15378
|
const stop = file.hooks?.Stop ?? [];
|
|
15113
15379
|
const command = hookCommand(turnSummaryPaths(config2.dataDirectory));
|
|
15114
|
-
|
|
15380
|
+
const start2 = file.hooks?.SessionStart ?? [];
|
|
15381
|
+
const startCommand = `/bin/sh ${shellQuote(claudeGitHookPaths(config2.dataDirectory).script)}`;
|
|
15382
|
+
if (!hasCommand(stop, command) && !hasCommand(start2, startCommand)) return;
|
|
15115
15383
|
const remaining = withoutCommand(stop, command);
|
|
15116
|
-
const
|
|
15384
|
+
const remainingStart = withoutCommand(start2, startCommand);
|
|
15385
|
+
const {
|
|
15386
|
+
Stop: _removed,
|
|
15387
|
+
SessionStart: _removedStart,
|
|
15388
|
+
...otherEvents
|
|
15389
|
+
} = file.hooks ?? {};
|
|
15117
15390
|
await writeHookFile(
|
|
15118
15391
|
filename,
|
|
15119
15392
|
{
|
|
15120
15393
|
...file,
|
|
15121
|
-
hooks:
|
|
15394
|
+
hooks: {
|
|
15395
|
+
...otherEvents,
|
|
15396
|
+
...remaining.length > 0 ? { Stop: remaining } : {},
|
|
15397
|
+
...remainingStart.length > 0 ? { SessionStart: remainingStart } : {}
|
|
15398
|
+
}
|
|
15122
15399
|
},
|
|
15123
15400
|
mode
|
|
15124
15401
|
);
|
|
@@ -15171,7 +15448,7 @@ async function fetchSummary(config2, directory, fetcher, signal) {
|
|
|
15171
15448
|
}
|
|
15172
15449
|
async function readRequest(filename) {
|
|
15173
15450
|
try {
|
|
15174
|
-
const [directory] = (await
|
|
15451
|
+
const [directory] = (await fs10.readFile(filename, "utf8")).split("\n");
|
|
15175
15452
|
return directory ?? null;
|
|
15176
15453
|
} catch (error2) {
|
|
15177
15454
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
@@ -15180,25 +15457,25 @@ async function readRequest(filename) {
|
|
|
15180
15457
|
}
|
|
15181
15458
|
}
|
|
15182
15459
|
async function writeLine(paths, key, directory, message) {
|
|
15183
|
-
const filename =
|
|
15460
|
+
const filename = path10.join(paths.lines, key);
|
|
15184
15461
|
if (message === null) {
|
|
15185
|
-
await
|
|
15462
|
+
await fs10.rm(filename, { force: true });
|
|
15186
15463
|
return;
|
|
15187
15464
|
}
|
|
15188
15465
|
const temporary = `${filename}.${process.pid}.tmp`;
|
|
15189
|
-
await
|
|
15466
|
+
await fs10.writeFile(
|
|
15190
15467
|
temporary,
|
|
15191
15468
|
`${directory}
|
|
15192
15469
|
${JSON.stringify({ systemMessage: message })}
|
|
15193
15470
|
`,
|
|
15194
15471
|
{ encoding: "utf8", mode: 384 }
|
|
15195
15472
|
);
|
|
15196
|
-
await
|
|
15473
|
+
await fs10.rename(temporary, filename);
|
|
15197
15474
|
}
|
|
15198
15475
|
async function forget(paths, key) {
|
|
15199
15476
|
await Promise.all(
|
|
15200
15477
|
[paths.requests, paths.lines, paths.shown].map(
|
|
15201
|
-
(directory) =>
|
|
15478
|
+
(directory) => fs10.rm(path10.join(directory, key), { force: true })
|
|
15202
15479
|
)
|
|
15203
15480
|
);
|
|
15204
15481
|
}
|
|
@@ -15206,7 +15483,7 @@ async function refreshTurnSummaries(config2, refreshedAt, nowMs, fetcher = colle
|
|
|
15206
15483
|
const paths = turnSummaryPaths(config2.dataDirectory);
|
|
15207
15484
|
let keys;
|
|
15208
15485
|
try {
|
|
15209
|
-
keys = await
|
|
15486
|
+
keys = await fs10.readdir(paths.requests);
|
|
15210
15487
|
} catch (error2) {
|
|
15211
15488
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15212
15489
|
return 0;
|
|
@@ -15216,8 +15493,8 @@ async function refreshTurnSummaries(config2, refreshedAt, nowMs, fetcher = colle
|
|
|
15216
15493
|
for (const key of keys.sort()) {
|
|
15217
15494
|
signal?.throwIfAborted();
|
|
15218
15495
|
if (!REQUEST_KEY_PATTERN.test(key)) continue;
|
|
15219
|
-
const filename =
|
|
15220
|
-
const stats = await
|
|
15496
|
+
const filename = path10.join(paths.requests, key);
|
|
15497
|
+
const stats = await fs10.stat(filename).catch((error2) => {
|
|
15221
15498
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15222
15499
|
return null;
|
|
15223
15500
|
throw error2;
|
|
@@ -15284,8 +15561,8 @@ async function codexTurnSummaryApproval(config2) {
|
|
|
15284
15561
|
if (handlerIndex < 0) continue;
|
|
15285
15562
|
let configToml;
|
|
15286
15563
|
try {
|
|
15287
|
-
configToml = await
|
|
15288
|
-
|
|
15564
|
+
configToml = await fs10.readFile(
|
|
15565
|
+
path10.join(config2.codexHome, "config.toml"),
|
|
15289
15566
|
"utf8"
|
|
15290
15567
|
);
|
|
15291
15568
|
} catch (error2) {
|
|
@@ -15303,8 +15580,8 @@ async function codexTurnSummaryApproval(config2) {
|
|
|
15303
15580
|
}
|
|
15304
15581
|
|
|
15305
15582
|
// src/cursor-hooks.ts
|
|
15306
|
-
import { promises as
|
|
15307
|
-
import
|
|
15583
|
+
import { promises as fs11 } from "node:fs";
|
|
15584
|
+
import path11 from "node:path";
|
|
15308
15585
|
var CURSOR_HOOKS = [
|
|
15309
15586
|
"sessionStart",
|
|
15310
15587
|
"beforeSubmitPrompt",
|
|
@@ -15320,15 +15597,15 @@ var CURSOR_HOOKS = [
|
|
|
15320
15597
|
"sessionEnd"
|
|
15321
15598
|
];
|
|
15322
15599
|
function cursorHookDirectory(dataDirectory2) {
|
|
15323
|
-
return
|
|
15600
|
+
return path11.join(dataDirectory2, "cursor-hooks");
|
|
15324
15601
|
}
|
|
15325
15602
|
function cursorHookFailurePath(dataDirectory2) {
|
|
15326
|
-
return
|
|
15603
|
+
return path11.join(dataDirectory2, "cursor-hook-failure.json");
|
|
15327
15604
|
}
|
|
15328
15605
|
async function cursorHookFailureAtMs(dataDirectory2) {
|
|
15329
15606
|
let contents;
|
|
15330
15607
|
try {
|
|
15331
|
-
contents = await
|
|
15608
|
+
contents = await fs11.readFile(cursorHookFailurePath(dataDirectory2), "utf8");
|
|
15332
15609
|
} catch (error2) {
|
|
15333
15610
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15334
15611
|
return null;
|
|
@@ -15363,8 +15640,8 @@ async function readHooks(filename) {
|
|
|
15363
15640
|
let contents;
|
|
15364
15641
|
let mode = 384;
|
|
15365
15642
|
try {
|
|
15366
|
-
contents = await
|
|
15367
|
-
mode = (await
|
|
15643
|
+
contents = await fs11.readFile(filename, "utf8");
|
|
15644
|
+
mode = (await fs11.stat(filename)).mode & 511;
|
|
15368
15645
|
} catch (error2) {
|
|
15369
15646
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15370
15647
|
return { file: {}, mode };
|
|
@@ -15386,14 +15663,14 @@ async function readHooks(filename) {
|
|
|
15386
15663
|
return { file: checked.data, mode };
|
|
15387
15664
|
}
|
|
15388
15665
|
async function writeHooks(filename, file, mode) {
|
|
15389
|
-
await ensurePrivateDirectory(
|
|
15390
|
-
const target = await
|
|
15666
|
+
await ensurePrivateDirectory(path11.dirname(filename));
|
|
15667
|
+
const target = await fs11.realpath(filename).catch((error2) => {
|
|
15391
15668
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15392
15669
|
return filename;
|
|
15393
15670
|
throw error2;
|
|
15394
15671
|
});
|
|
15395
15672
|
const temporary = `${target}.iteration-${process.pid}.tmp`;
|
|
15396
|
-
const handle = await
|
|
15673
|
+
const handle = await fs11.open(temporary, "w", mode);
|
|
15397
15674
|
try {
|
|
15398
15675
|
await handle.writeFile(`${JSON.stringify(file, null, 2)}
|
|
15399
15676
|
`, "utf8");
|
|
@@ -15401,14 +15678,14 @@ async function writeHooks(filename, file, mode) {
|
|
|
15401
15678
|
} finally {
|
|
15402
15679
|
await handle.close();
|
|
15403
15680
|
}
|
|
15404
|
-
await
|
|
15681
|
+
await fs11.rename(temporary, target);
|
|
15405
15682
|
}
|
|
15406
15683
|
function entries(value) {
|
|
15407
15684
|
return value ?? [];
|
|
15408
15685
|
}
|
|
15409
15686
|
async function installCursorHooks(cursorHome, dataDirectory2) {
|
|
15410
15687
|
await ensurePrivateDirectory(cursorHookDirectory(dataDirectory2));
|
|
15411
|
-
const filename =
|
|
15688
|
+
const filename = path11.join(cursorHome, "hooks.json");
|
|
15412
15689
|
const { file, mode } = await readHooks(filename);
|
|
15413
15690
|
const hooks = { ...file.hooks };
|
|
15414
15691
|
const nodePath = await stableNodePath();
|
|
@@ -15422,7 +15699,7 @@ async function installCursorHooks(cursorHome, dataDirectory2) {
|
|
|
15422
15699
|
await writeHooks(filename, { ...file, version: 1, hooks }, mode);
|
|
15423
15700
|
}
|
|
15424
15701
|
async function uninstallCursorHooks(cursorHome, dataDirectory2) {
|
|
15425
|
-
const filename =
|
|
15702
|
+
const filename = path11.join(cursorHome, "hooks.json");
|
|
15426
15703
|
const { file, mode } = await readHooks(filename);
|
|
15427
15704
|
const hooks = { ...file.hooks };
|
|
15428
15705
|
let changed = false;
|
|
@@ -15443,7 +15720,7 @@ async function cursorHookFiles(dataDirectory2) {
|
|
|
15443
15720
|
const directory = cursorHookDirectory(dataDirectory2);
|
|
15444
15721
|
let names;
|
|
15445
15722
|
try {
|
|
15446
|
-
names = await
|
|
15723
|
+
names = await fs11.readdir(directory);
|
|
15447
15724
|
} catch (error2) {
|
|
15448
15725
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15449
15726
|
return [];
|
|
@@ -15451,7 +15728,7 @@ async function cursorHookFiles(dataDirectory2) {
|
|
|
15451
15728
|
}
|
|
15452
15729
|
if (names.length > 1e4)
|
|
15453
15730
|
throw new Error("Cursor hook queue exceeds the scan limit.");
|
|
15454
|
-
return names.filter((name) => /^[0-9a-f-]+\.jsonl$/i.test(name)).sort().map((name) =>
|
|
15731
|
+
return names.filter((name) => /^[0-9a-f-]+\.jsonl$/i.test(name)).sort().map((name) => path11.join(directory, name));
|
|
15455
15732
|
}
|
|
15456
15733
|
|
|
15457
15734
|
// src/auto-connect.ts
|
|
@@ -15464,27 +15741,27 @@ var consentSchema = object({
|
|
|
15464
15741
|
sourceHomes: object({
|
|
15465
15742
|
codex: string2(),
|
|
15466
15743
|
claude: string2(),
|
|
15467
|
-
cursor: string2().default(
|
|
15744
|
+
cursor: string2().default(path12.join(os6.homedir(), ".cursor"))
|
|
15468
15745
|
}).strict()
|
|
15469
15746
|
}).strict();
|
|
15470
15747
|
function consentPath() {
|
|
15471
|
-
return
|
|
15748
|
+
return path12.join(os6.homedir(), ".config", "iteration", "auto-connect.json");
|
|
15472
15749
|
}
|
|
15473
15750
|
function sourceHome(source) {
|
|
15474
|
-
return
|
|
15475
|
-
source === "codex" ? process.env.CODEX_HOME ??
|
|
15751
|
+
return path12.resolve(
|
|
15752
|
+
source === "codex" ? process.env.CODEX_HOME ?? path12.join(os6.homedir(), ".codex") : source === "claude" ? process.env.CLAUDE_CONFIG_DIR ?? path12.join(os6.homedir(), ".claude") : path12.join(os6.homedir(), ".cursor")
|
|
15476
15753
|
);
|
|
15477
15754
|
}
|
|
15478
15755
|
async function detectedSources(sourceHomes) {
|
|
15479
15756
|
const detected = [];
|
|
15480
15757
|
for (const source of COLLECTOR_SOURCES) {
|
|
15481
|
-
const directory =
|
|
15758
|
+
const directory = path12.join(
|
|
15482
15759
|
sourceHomes[source],
|
|
15483
15760
|
source === "codex" ? "sessions" : "projects"
|
|
15484
15761
|
);
|
|
15485
15762
|
let stats;
|
|
15486
15763
|
try {
|
|
15487
|
-
stats = await
|
|
15764
|
+
stats = await fs12.stat(directory);
|
|
15488
15765
|
} catch (error2) {
|
|
15489
15766
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15490
15767
|
continue;
|
|
@@ -15504,7 +15781,7 @@ async function detectedSources(sourceHomes) {
|
|
|
15504
15781
|
async function loadMachineConsent() {
|
|
15505
15782
|
let text;
|
|
15506
15783
|
try {
|
|
15507
|
-
text = await
|
|
15784
|
+
text = await fs12.readFile(consentPath(), "utf8");
|
|
15508
15785
|
} catch (error2) {
|
|
15509
15786
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15510
15787
|
return null;
|
|
@@ -15530,9 +15807,9 @@ async function saveMachineConsent(consent) {
|
|
|
15530
15807
|
const parsed = consentSchema.parse(consent);
|
|
15531
15808
|
validateCollectorEndpoint(parsed.endpoint);
|
|
15532
15809
|
const filename = consentPath();
|
|
15533
|
-
await ensurePrivateDirectory(
|
|
15810
|
+
await ensurePrivateDirectory(path12.dirname(filename));
|
|
15534
15811
|
const temporary = `${filename}.${process.pid}.tmp`;
|
|
15535
|
-
const handle = await
|
|
15812
|
+
const handle = await fs12.open(temporary, "w", 384);
|
|
15536
15813
|
try {
|
|
15537
15814
|
await handle.writeFile(`${JSON.stringify(parsed)}
|
|
15538
15815
|
`, "utf8");
|
|
@@ -15540,7 +15817,7 @@ async function saveMachineConsent(consent) {
|
|
|
15540
15817
|
} finally {
|
|
15541
15818
|
await handle.close();
|
|
15542
15819
|
}
|
|
15543
|
-
await
|
|
15820
|
+
await fs12.rename(temporary, filename);
|
|
15544
15821
|
}
|
|
15545
15822
|
async function readCollectorScope(anchor2) {
|
|
15546
15823
|
const endpoint = validateCollectorEndpoint(anchor2.endpoint);
|
|
@@ -15636,7 +15913,7 @@ async function connectNewDetectedSources(anchor2, consent) {
|
|
|
15636
15913
|
if (source === consent.anchorSource) continue;
|
|
15637
15914
|
const paths = collectorPaths(defaultCollectorDirectory(source));
|
|
15638
15915
|
try {
|
|
15639
|
-
await
|
|
15916
|
+
await fs12.access(paths.config);
|
|
15640
15917
|
continue;
|
|
15641
15918
|
} catch (error2) {
|
|
15642
15919
|
if (!(error2 instanceof Error && "code" in error2 && error2.code === "ENOENT"))
|
|
@@ -15679,7 +15956,7 @@ async function connectNewDetectedSources(anchor2, consent) {
|
|
|
15679
15956
|
async function connectedConfig(source) {
|
|
15680
15957
|
const paths = collectorPaths(defaultCollectorDirectory(source));
|
|
15681
15958
|
try {
|
|
15682
|
-
await
|
|
15959
|
+
await fs12.access(paths.config);
|
|
15683
15960
|
} catch (error2) {
|
|
15684
15961
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
15685
15962
|
return null;
|
|
@@ -15689,93 +15966,20 @@ async function connectedConfig(source) {
|
|
|
15689
15966
|
}
|
|
15690
15967
|
|
|
15691
15968
|
// src/history-import.ts
|
|
15692
|
-
import
|
|
15969
|
+
import path19 from "node:path";
|
|
15693
15970
|
|
|
15694
15971
|
// src/service.ts
|
|
15695
|
-
import { promises as
|
|
15696
|
-
import
|
|
15972
|
+
import { promises as fs15 } from "node:fs";
|
|
15973
|
+
import path18 from "node:path";
|
|
15697
15974
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
15698
15975
|
|
|
15699
15976
|
// src/collector.ts
|
|
15700
|
-
import { promises as
|
|
15701
|
-
import
|
|
15977
|
+
import { promises as fs13 } from "node:fs";
|
|
15978
|
+
import path16 from "node:path";
|
|
15702
15979
|
|
|
15703
15980
|
// src/claude-transcript.ts
|
|
15704
15981
|
import { createHash as createHash4 } from "node:crypto";
|
|
15705
|
-
import
|
|
15706
|
-
|
|
15707
|
-
// src/git-repository.ts
|
|
15708
|
-
import fs11 from "node:fs";
|
|
15709
|
-
import path11 from "node:path";
|
|
15710
|
-
var repositoryUrls = /* @__PURE__ */ new Map();
|
|
15711
|
-
function missing(error2) {
|
|
15712
|
-
const code = error2.code;
|
|
15713
|
-
return code === "ENOENT" || code === "ENOTDIR";
|
|
15714
|
-
}
|
|
15715
|
-
function readText(file) {
|
|
15716
|
-
try {
|
|
15717
|
-
return fs11.readFileSync(file, "utf8");
|
|
15718
|
-
} catch (error2) {
|
|
15719
|
-
if (missing(error2)) return null;
|
|
15720
|
-
throw error2;
|
|
15721
|
-
}
|
|
15722
|
-
}
|
|
15723
|
-
function gitConfigFile(cwd) {
|
|
15724
|
-
try {
|
|
15725
|
-
if (!fs11.statSync(cwd).isDirectory()) return null;
|
|
15726
|
-
} catch (error2) {
|
|
15727
|
-
if (missing(error2)) return null;
|
|
15728
|
-
throw error2;
|
|
15729
|
-
}
|
|
15730
|
-
for (let directory = cwd; ; directory = path11.dirname(directory)) {
|
|
15731
|
-
const dotGit = path11.join(directory, ".git");
|
|
15732
|
-
let stats;
|
|
15733
|
-
try {
|
|
15734
|
-
stats = fs11.statSync(dotGit);
|
|
15735
|
-
} catch (error2) {
|
|
15736
|
-
if (!missing(error2)) throw error2;
|
|
15737
|
-
if (path11.dirname(directory) === directory) return null;
|
|
15738
|
-
continue;
|
|
15739
|
-
}
|
|
15740
|
-
if (stats.isDirectory()) return path11.join(dotGit, "config");
|
|
15741
|
-
const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit) ?? "");
|
|
15742
|
-
if (pointer === null) return null;
|
|
15743
|
-
const gitDirectory = path11.resolve(directory, pointer[1]);
|
|
15744
|
-
const common = readText(path11.join(gitDirectory, "commondir"))?.trim();
|
|
15745
|
-
return path11.join(
|
|
15746
|
-
common ? path11.resolve(gitDirectory, common) : gitDirectory,
|
|
15747
|
-
"config"
|
|
15748
|
-
);
|
|
15749
|
-
}
|
|
15750
|
-
}
|
|
15751
|
-
function originUrl(config2) {
|
|
15752
|
-
let inOrigin = false;
|
|
15753
|
-
for (const line of config2.split(/\r?\n/)) {
|
|
15754
|
-
const section = /^\s*\[\s*([^\]]+?)\s*\]/.exec(line);
|
|
15755
|
-
if (section !== null) {
|
|
15756
|
-
inOrigin = /^remote\s+"origin"$/i.test(section[1]);
|
|
15757
|
-
continue;
|
|
15758
|
-
}
|
|
15759
|
-
const url2 = inOrigin ? /^\s*url\s*=\s*(.+?)\s*$/i.exec(line) : null;
|
|
15760
|
-
if (url2 !== null) return url2[1].replace(/^"(.*)"$/, "$1");
|
|
15761
|
-
}
|
|
15762
|
-
return null;
|
|
15763
|
-
}
|
|
15764
|
-
function withoutCredentials(url2) {
|
|
15765
|
-
return url2.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@]*@/i, "$1");
|
|
15766
|
-
}
|
|
15767
|
-
function gitRepositoryUrl(cwd) {
|
|
15768
|
-
const known = repositoryUrls.get(cwd);
|
|
15769
|
-
if (known !== void 0) return known;
|
|
15770
|
-
const configFile = gitConfigFile(cwd);
|
|
15771
|
-
const config2 = configFile === null ? null : readText(configFile);
|
|
15772
|
-
const origin = config2 === null ? null : originUrl(config2);
|
|
15773
|
-
const url2 = origin === null ? null : withoutCredentials(origin);
|
|
15774
|
-
repositoryUrls.set(cwd, url2);
|
|
15775
|
-
return url2;
|
|
15776
|
-
}
|
|
15777
|
-
|
|
15778
|
-
// src/claude-transcript.ts
|
|
15982
|
+
import path13 from "node:path";
|
|
15779
15983
|
var CONTEXT_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
15780
15984
|
"attachment",
|
|
15781
15985
|
"system",
|
|
@@ -15814,9 +16018,9 @@ function childThreadId(parentId, agentId) {
|
|
|
15814
16018
|
var SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15815
16019
|
function claudeSessionIdentity(filename) {
|
|
15816
16020
|
if (!filename.endsWith(".jsonl")) return null;
|
|
15817
|
-
const basename =
|
|
15818
|
-
if (
|
|
15819
|
-
const parent =
|
|
16021
|
+
const basename = path13.basename(filename, ".jsonl");
|
|
16022
|
+
if (path13.basename(path13.dirname(filename)) === "subagents") {
|
|
16023
|
+
const parent = path13.basename(path13.dirname(path13.dirname(filename)));
|
|
15820
16024
|
if (!basename.startsWith("agent-") || !SESSION_UUID.test(parent))
|
|
15821
16025
|
return null;
|
|
15822
16026
|
const agentId = basename.slice("agent-".length);
|
|
@@ -16046,10 +16250,51 @@ function parseClaudeLine(line, filename, byteOffset, repositoryUrl = gitReposito
|
|
|
16046
16250
|
}
|
|
16047
16251
|
return { events: valid, gaps };
|
|
16048
16252
|
}
|
|
16253
|
+
function parseClaudeGitHookLine(line, filename) {
|
|
16254
|
+
const name = path13.basename(filename, ".jsonl");
|
|
16255
|
+
if (!SESSION_UUID.test(name) || path13.basename(path13.dirname(filename)) !== "git-queue")
|
|
16256
|
+
return { events: [], gaps: ["invalid_claude_git_hook_path"] };
|
|
16257
|
+
let decoded;
|
|
16258
|
+
try {
|
|
16259
|
+
decoded = JSON.parse(line);
|
|
16260
|
+
} catch {
|
|
16261
|
+
return { events: [], gaps: ["malformed_claude_git_hook_record"] };
|
|
16262
|
+
}
|
|
16263
|
+
const parsed = object({
|
|
16264
|
+
sessionId: uuid2(),
|
|
16265
|
+
cwd: string2().min(1),
|
|
16266
|
+
capturedAt: iso_exports.datetime(),
|
|
16267
|
+
git: object({
|
|
16268
|
+
branch: string2().nullable(),
|
|
16269
|
+
repository_url: string2().nullable()
|
|
16270
|
+
})
|
|
16271
|
+
}).safeParse(decoded);
|
|
16272
|
+
if (!parsed.success || !path13.isAbsolute(parsed.data.cwd))
|
|
16273
|
+
return { events: [], gaps: ["invalid_claude_git_hook_record"] };
|
|
16274
|
+
const { sessionId, cwd, git, capturedAt } = parsed.data;
|
|
16275
|
+
const candidate = {
|
|
16276
|
+
eventId: `hook:${digest2(filename)}`,
|
|
16277
|
+
sessionId: `claude:${sessionId}`,
|
|
16278
|
+
sequence: 1,
|
|
16279
|
+
callId: null,
|
|
16280
|
+
projectId: null,
|
|
16281
|
+
occurredAt: capturedAt,
|
|
16282
|
+
eventType: "session_meta",
|
|
16283
|
+
payload: { id: sessionId, cwd, git },
|
|
16284
|
+
run: {
|
|
16285
|
+
kind: "identity",
|
|
16286
|
+
id: sessionId,
|
|
16287
|
+
parentId: null,
|
|
16288
|
+
ownStart: true,
|
|
16289
|
+
startOrdinal: null
|
|
16290
|
+
}
|
|
16291
|
+
};
|
|
16292
|
+
return parseTraceEventBatch({ events: [candidate] }).success ? { events: [candidate], gaps: [] } : { events: [], gaps: ["invalid_claude_git_hook_record"] };
|
|
16293
|
+
}
|
|
16049
16294
|
|
|
16050
16295
|
// src/cursor-transcript.ts
|
|
16051
16296
|
import { createHash as createHash5 } from "node:crypto";
|
|
16052
|
-
import
|
|
16297
|
+
import path14 from "node:path";
|
|
16053
16298
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16054
16299
|
function record2(value) {
|
|
16055
16300
|
const parsed = record(string2(), json()).safeParse(value);
|
|
@@ -16077,8 +16322,8 @@ function event2(conversationId, filename, byteOffset, index, eventType, payload,
|
|
|
16077
16322
|
};
|
|
16078
16323
|
}
|
|
16079
16324
|
function parseCursorTranscriptLine(line, filename, byteOffset) {
|
|
16080
|
-
const conversationId =
|
|
16081
|
-
if (!UUID.test(conversationId) ||
|
|
16325
|
+
const conversationId = path14.basename(filename, ".jsonl");
|
|
16326
|
+
if (!UUID.test(conversationId) || path14.basename(path14.dirname(filename)) !== conversationId)
|
|
16082
16327
|
return { events: [], gaps: ["invalid_cursor_transcript_path"] };
|
|
16083
16328
|
let decoded;
|
|
16084
16329
|
try {
|
|
@@ -16219,6 +16464,7 @@ function parseCursorHookLine(line, filename) {
|
|
|
16219
16464
|
const events = [];
|
|
16220
16465
|
if (hook === "beforeSubmitPrompt" || hook === "sessionStart") {
|
|
16221
16466
|
const roots = Array.isArray(input.workspace_roots) ? input.workspace_roots : [];
|
|
16467
|
+
const git = record2(wrapper?.git);
|
|
16222
16468
|
events.push({
|
|
16223
16469
|
...event2(
|
|
16224
16470
|
conversationId,
|
|
@@ -16229,6 +16475,10 @@ function parseCursorHookLine(line, filename) {
|
|
|
16229
16475
|
{
|
|
16230
16476
|
id: conversationId,
|
|
16231
16477
|
cwd: string5(roots[0]),
|
|
16478
|
+
git: {
|
|
16479
|
+
branch: string5(git?.branch),
|
|
16480
|
+
repository_url: string5(git?.repository_url)
|
|
16481
|
+
},
|
|
16232
16482
|
version: string5(input.cursor_version),
|
|
16233
16483
|
model: string5(input.model),
|
|
16234
16484
|
transcript_path: string5(input.transcript_path)
|
|
@@ -16397,7 +16647,7 @@ function validated(events, gaps) {
|
|
|
16397
16647
|
|
|
16398
16648
|
// src/rollout.ts
|
|
16399
16649
|
import { createHash as createHash6 } from "node:crypto";
|
|
16400
|
-
import
|
|
16650
|
+
import path15 from "node:path";
|
|
16401
16651
|
var recordSchema = record(string2(), unknown());
|
|
16402
16652
|
var CONTEXT_RECORDS = [
|
|
16403
16653
|
"session_meta",
|
|
@@ -16450,7 +16700,7 @@ function sourceOrdinal(value) {
|
|
|
16450
16700
|
return parsed.success ? parsed.data : null;
|
|
16451
16701
|
}
|
|
16452
16702
|
function sessionFromPath(filename) {
|
|
16453
|
-
const basename =
|
|
16703
|
+
const basename = path15.basename(filename, ".jsonl");
|
|
16454
16704
|
return basename.startsWith("rollout-") ? basename.slice("rollout-".length) : basename;
|
|
16455
16705
|
}
|
|
16456
16706
|
function callIdFrom(record3) {
|
|
@@ -16720,9 +16970,10 @@ function logGap(filename, offset, reason) {
|
|
|
16720
16970
|
);
|
|
16721
16971
|
}
|
|
16722
16972
|
function parseSourceRecord(source, line, filename, byteOffset) {
|
|
16723
|
-
if (source === "claude")
|
|
16973
|
+
if (source === "claude")
|
|
16974
|
+
return filename.includes(`${path16.sep}git-queue${path16.sep}`) ? parseClaudeGitHookLine(line, filename) : parseClaudeLine(line, filename, byteOffset);
|
|
16724
16975
|
if (source === "cursor")
|
|
16725
|
-
return filename.includes(`${
|
|
16976
|
+
return filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) ? parseCursorHookLine(line, filename) : parseCursorTranscriptLine(line, filename, byteOffset);
|
|
16726
16977
|
if (source === "codex") {
|
|
16727
16978
|
const parsed = parseRolloutLine(line, filename, byteOffset);
|
|
16728
16979
|
return parsed.kind === "event" ? { events: [parsed.event], gaps: [] } : parsed.kind === "gap" ? { events: [], gaps: [parsed.reason] } : { events: [], gaps: [] };
|
|
@@ -16733,7 +16984,7 @@ function parseSourceRecord(source, line, filename, byteOffset) {
|
|
|
16733
16984
|
async function scanSourceOnce(config2, paths, signal) {
|
|
16734
16985
|
signal?.throwIfAborted();
|
|
16735
16986
|
const source = collectorSource(config2);
|
|
16736
|
-
const sourceDirectory =
|
|
16987
|
+
const sourceDirectory = path16.resolve(collectorSourceDirectory(config2));
|
|
16737
16988
|
const state = await loadState(paths);
|
|
16738
16989
|
if (!state.initialized || state.sourceDirectory !== sourceDirectory) {
|
|
16739
16990
|
throw new Error(
|
|
@@ -16743,6 +16994,7 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
16743
16994
|
const cursors = { ...state.cursors };
|
|
16744
16995
|
const skipping = new Set(state.skipping);
|
|
16745
16996
|
const files = [
|
|
16997
|
+
...source === "claude" ? await claudeGitHookFiles(config2.dataDirectory) : [],
|
|
16746
16998
|
...await sourceFiles(source, sourceDirectory),
|
|
16747
16999
|
...source === "cursor" ? await cursorHookFiles(config2.dataDirectory) : []
|
|
16748
17000
|
];
|
|
@@ -16754,12 +17006,12 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
16754
17006
|
let caughtUp = true;
|
|
16755
17007
|
for (const filename of files) {
|
|
16756
17008
|
signal?.throwIfAborted();
|
|
16757
|
-
const recordLimit = source === "cursor" && filename.includes(`${
|
|
17009
|
+
const recordLimit = source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) ? 12 * 1024 * 1024 : MAX_RECORD_BYTES;
|
|
16758
17010
|
if (remainingBudget === 0) {
|
|
16759
17011
|
caughtUp = false;
|
|
16760
17012
|
break;
|
|
16761
17013
|
}
|
|
16762
|
-
const stats = await
|
|
17014
|
+
const stats = await fs13.stat(filename);
|
|
16763
17015
|
let offset = cursors[filename] ?? 0;
|
|
16764
17016
|
if (offset > stats.size) {
|
|
16765
17017
|
logGap(filename, offset, "rollout_file_shrank_resetting_cursor");
|
|
@@ -16775,7 +17027,7 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
16775
17027
|
cursors[filename] = offset;
|
|
16776
17028
|
continue;
|
|
16777
17029
|
}
|
|
16778
|
-
const handle = await
|
|
17030
|
+
const handle = await fs13.open(filename, "r");
|
|
16779
17031
|
let buffer;
|
|
16780
17032
|
let readEnd = offset;
|
|
16781
17033
|
try {
|
|
@@ -16850,12 +17102,12 @@ async function scanSourceOnce(config2, paths, signal) {
|
|
|
16850
17102
|
lineStart = buffer.length;
|
|
16851
17103
|
}
|
|
16852
17104
|
cursors[filename] = offset + lineStart;
|
|
16853
|
-
if (source === "cursor" && filename.includes(`${
|
|
17105
|
+
if ((source === "cursor" && filename.includes(`${path16.sep}cursor-hooks${path16.sep}`) || source === "claude" && filename.includes(`${path16.sep}git-queue${path16.sep}`)) && cursors[filename] === stats.size)
|
|
16854
17106
|
completedHooks.push(filename);
|
|
16855
17107
|
}
|
|
16856
17108
|
await appendEvents(events, paths);
|
|
16857
17109
|
for (const filename of completedHooks) {
|
|
16858
|
-
await
|
|
17110
|
+
await fs13.rm(filename);
|
|
16859
17111
|
delete cursors[filename];
|
|
16860
17112
|
}
|
|
16861
17113
|
await saveState(
|
|
@@ -16953,6 +17205,301 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
|
|
|
16953
17205
|
};
|
|
16954
17206
|
}
|
|
16955
17207
|
|
|
17208
|
+
// src/cursor-billing.ts
|
|
17209
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
17210
|
+
import { promises as fs14 } from "node:fs";
|
|
17211
|
+
import os7 from "node:os";
|
|
17212
|
+
import path17 from "node:path";
|
|
17213
|
+
var CURSOR_USAGE_URL = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents";
|
|
17214
|
+
var PAGE_SIZE = 500;
|
|
17215
|
+
var MAX_PAGES = 1e3;
|
|
17216
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
17217
|
+
var AUDIT_INTERVAL_MS = 7 * DAY_MS;
|
|
17218
|
+
var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17219
|
+
var billingStateSchema = object({
|
|
17220
|
+
accountFingerprint: string2().regex(/^[0-9a-f]{64}$/),
|
|
17221
|
+
lastSyncedAtMs: number2().int().nonnegative(),
|
|
17222
|
+
lastAuditAtMs: number2().int().nonnegative()
|
|
17223
|
+
}).strict();
|
|
17224
|
+
var usageEventSchema = object({
|
|
17225
|
+
conversationId: string2().optional().nullable(),
|
|
17226
|
+
timestamp: union([number2(), string2().regex(/^\d+$/).transform(Number)]).pipe(number2().int().nonnegative().safe()),
|
|
17227
|
+
kind: string2(),
|
|
17228
|
+
usageBasedCosts: string2().optional().nullable(),
|
|
17229
|
+
tokenUsage: object({
|
|
17230
|
+
inputTokens: number2().int().nonnegative(),
|
|
17231
|
+
outputTokens: number2().int().nonnegative(),
|
|
17232
|
+
cacheReadTokens: number2().int().nonnegative(),
|
|
17233
|
+
cacheWriteTokens: number2().int().nonnegative().optional()
|
|
17234
|
+
}).optional().nullable()
|
|
17235
|
+
});
|
|
17236
|
+
var usagePageSchema = object({
|
|
17237
|
+
totalUsageEventsCount: number2().int().nonnegative(),
|
|
17238
|
+
usageEventsDisplay: array(unknown()).max(PAGE_SIZE)
|
|
17239
|
+
});
|
|
17240
|
+
var emptyUsagePageSchema = object({}).strict();
|
|
17241
|
+
function cursorAuthDatabase() {
|
|
17242
|
+
if (process.platform === "darwin")
|
|
17243
|
+
return path17.join(
|
|
17244
|
+
os7.homedir(),
|
|
17245
|
+
"Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
|
17246
|
+
);
|
|
17247
|
+
if (process.platform === "linux")
|
|
17248
|
+
return path17.join(
|
|
17249
|
+
os7.homedir(),
|
|
17250
|
+
".config/Cursor/User/globalStorage/state.vscdb"
|
|
17251
|
+
);
|
|
17252
|
+
return null;
|
|
17253
|
+
}
|
|
17254
|
+
async function readCursorAuth() {
|
|
17255
|
+
const filename = cursorAuthDatabase();
|
|
17256
|
+
if (filename === null) return null;
|
|
17257
|
+
let database;
|
|
17258
|
+
try {
|
|
17259
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
17260
|
+
database = new DatabaseSync(filename, { readOnly: true });
|
|
17261
|
+
} catch (error2) {
|
|
17262
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "SQLITE_CANTOPEN")
|
|
17263
|
+
return null;
|
|
17264
|
+
throw new Error("Could not open Cursor's local account database.", {
|
|
17265
|
+
cause: error2
|
|
17266
|
+
});
|
|
17267
|
+
}
|
|
17268
|
+
try {
|
|
17269
|
+
const row = database.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
17270
|
+
const parsed = string2().min(1).safeParse(row?.value);
|
|
17271
|
+
if (!parsed.success) return null;
|
|
17272
|
+
const email2 = string2().email().safeParse(
|
|
17273
|
+
database.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/cachedEmail")?.value
|
|
17274
|
+
);
|
|
17275
|
+
const identity = email2.success ? `email:${email2.data.toLowerCase()}` : `token:${parsed.data}`;
|
|
17276
|
+
return {
|
|
17277
|
+
token: parsed.data,
|
|
17278
|
+
accountFingerprint: createHash7("sha256").update(identity).digest("hex")
|
|
17279
|
+
};
|
|
17280
|
+
} catch (error2) {
|
|
17281
|
+
throw new Error("Could not read Cursor's local account token.", {
|
|
17282
|
+
cause: error2
|
|
17283
|
+
});
|
|
17284
|
+
} finally {
|
|
17285
|
+
database.close();
|
|
17286
|
+
}
|
|
17287
|
+
}
|
|
17288
|
+
async function loadBillingState(filename) {
|
|
17289
|
+
let contents;
|
|
17290
|
+
try {
|
|
17291
|
+
contents = await fs14.readFile(filename, "utf8");
|
|
17292
|
+
} catch (error2) {
|
|
17293
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
17294
|
+
return null;
|
|
17295
|
+
throw new Error("Could not read Cursor billing sync state.", {
|
|
17296
|
+
cause: error2
|
|
17297
|
+
});
|
|
17298
|
+
}
|
|
17299
|
+
try {
|
|
17300
|
+
return billingStateSchema.parse(JSON.parse(contents));
|
|
17301
|
+
} catch (error2) {
|
|
17302
|
+
throw new Error("Cursor billing sync state is invalid.", { cause: error2 });
|
|
17303
|
+
}
|
|
17304
|
+
}
|
|
17305
|
+
async function saveBillingState(filename, state) {
|
|
17306
|
+
const temporary = `${filename}.${process.pid}.tmp`;
|
|
17307
|
+
const handle = await fs14.open(temporary, "w", 384);
|
|
17308
|
+
try {
|
|
17309
|
+
await handle.writeFile(`${JSON.stringify(state)}
|
|
17310
|
+
`, "utf8");
|
|
17311
|
+
await handle.sync();
|
|
17312
|
+
} finally {
|
|
17313
|
+
await handle.close();
|
|
17314
|
+
}
|
|
17315
|
+
await fs14.rename(temporary, filename);
|
|
17316
|
+
}
|
|
17317
|
+
function planCursorBillingSync(state, accountFingerprint, nowMs) {
|
|
17318
|
+
if (state === null || state.accountFingerprint !== accountFingerprint)
|
|
17319
|
+
return { mode: "history", startDateMs: 0, reconcileFromMs: null };
|
|
17320
|
+
const audit = nowMs - state.lastAuditAtMs >= AUDIT_INTERVAL_MS;
|
|
17321
|
+
const fetchDays = audit ? 36 : 8;
|
|
17322
|
+
const reconcileDays = audit ? 35 : 7;
|
|
17323
|
+
return {
|
|
17324
|
+
mode: audit ? "audit" : "recent",
|
|
17325
|
+
startDateMs: Math.max(
|
|
17326
|
+
0,
|
|
17327
|
+
Math.min(state.lastSyncedAtMs - DAY_MS, nowMs - fetchDays * DAY_MS)
|
|
17328
|
+
),
|
|
17329
|
+
reconcileFromMs: Math.max(
|
|
17330
|
+
0,
|
|
17331
|
+
Math.min(state.lastSyncedAtMs, nowMs - reconcileDays * DAY_MS)
|
|
17332
|
+
)
|
|
17333
|
+
};
|
|
17334
|
+
}
|
|
17335
|
+
function cursorConversationIds(filenames) {
|
|
17336
|
+
return new Set(
|
|
17337
|
+
filenames.map((filename) => path17.basename(filename, ".jsonl")).filter((id) => UUID2.test(id))
|
|
17338
|
+
);
|
|
17339
|
+
}
|
|
17340
|
+
function billingKind(kind) {
|
|
17341
|
+
switch (kind) {
|
|
17342
|
+
case "USAGE_EVENT_KIND_INCLUDED_IN_PRO":
|
|
17343
|
+
case "USAGE_EVENT_KIND_INCLUDED_IN_BUSINESS":
|
|
17344
|
+
return "included";
|
|
17345
|
+
case "USAGE_EVENT_KIND_USAGE_BASED":
|
|
17346
|
+
return "paid";
|
|
17347
|
+
case "USAGE_EVENT_KIND_USER_API_KEY":
|
|
17348
|
+
return "byok";
|
|
17349
|
+
case "USAGE_EVENT_KIND_ERRORED_NOT_CHARGED":
|
|
17350
|
+
case "USAGE_EVENT_KIND_ABORTED_NOT_CHARGED":
|
|
17351
|
+
return "not_charged";
|
|
17352
|
+
default:
|
|
17353
|
+
return "unknown";
|
|
17354
|
+
}
|
|
17355
|
+
}
|
|
17356
|
+
function paidUsd(value) {
|
|
17357
|
+
if (value === null || value === void 0 || value === "-") return null;
|
|
17358
|
+
if (!/^\$\d+(?:\.\d{1,6})?$/.test(value))
|
|
17359
|
+
throw new Error("Cursor returned an invalid usage-based charge.");
|
|
17360
|
+
return Number(value.slice(1));
|
|
17361
|
+
}
|
|
17362
|
+
function normalizeCursorUsageEvent(value, localConversations) {
|
|
17363
|
+
const identity = object({ conversationId: string2().optional().nullable() }).safeParse(value);
|
|
17364
|
+
if (!identity.success)
|
|
17365
|
+
throw new Error("Cursor returned an invalid usage event.");
|
|
17366
|
+
const conversationId = identity.data.conversationId;
|
|
17367
|
+
if (conversationId === null || conversationId === void 0 || !localConversations.has(conversationId))
|
|
17368
|
+
return null;
|
|
17369
|
+
const parsed = usageEventSchema.safeParse(value);
|
|
17370
|
+
if (!parsed.success)
|
|
17371
|
+
throw new Error(
|
|
17372
|
+
"Cursor returned an invalid usage event for a local transcript."
|
|
17373
|
+
);
|
|
17374
|
+
const row = parsed.data;
|
|
17375
|
+
const usage2 = row.tokenUsage;
|
|
17376
|
+
const kind = billingKind(row.kind);
|
|
17377
|
+
return {
|
|
17378
|
+
conversationId,
|
|
17379
|
+
event: {
|
|
17380
|
+
timestamp: row.timestamp,
|
|
17381
|
+
kind,
|
|
17382
|
+
inputTokens: usage2?.inputTokens ?? null,
|
|
17383
|
+
outputTokens: usage2?.outputTokens ?? null,
|
|
17384
|
+
cacheReadTokens: usage2?.cacheReadTokens ?? null,
|
|
17385
|
+
cacheWriteTokens: usage2?.cacheWriteTokens ?? (usage2 === null || usage2 === void 0 ? null : 0),
|
|
17386
|
+
paidUsd: kind === "paid" ? paidUsd(row.usageBasedCosts) : null
|
|
17387
|
+
}
|
|
17388
|
+
};
|
|
17389
|
+
}
|
|
17390
|
+
async function syncCursorBilling(config2, fetcher = collectorFetch, signal) {
|
|
17391
|
+
const auth = await readCursorAuth();
|
|
17392
|
+
if (auth === null)
|
|
17393
|
+
return { sessions: 0, matched: 0, skipped: true, mode: null };
|
|
17394
|
+
const localConversations = cursorConversationIds(
|
|
17395
|
+
await cursorFiles(path17.join(config2.cursorHome, "projects"))
|
|
17396
|
+
);
|
|
17397
|
+
if (localConversations.size === 0)
|
|
17398
|
+
return { sessions: 0, matched: 0, skipped: true, mode: null };
|
|
17399
|
+
const stateFile = path17.join(config2.dataDirectory, "cursor-billing-sync.json");
|
|
17400
|
+
const priorState = await loadBillingState(stateFile);
|
|
17401
|
+
const nowMs = Date.now();
|
|
17402
|
+
const plan = planCursorBillingSync(
|
|
17403
|
+
priorState,
|
|
17404
|
+
auth.accountFingerprint,
|
|
17405
|
+
nowMs
|
|
17406
|
+
);
|
|
17407
|
+
const byConversation = /* @__PURE__ */ new Map();
|
|
17408
|
+
for (let page = 1; page <= MAX_PAGES; page += 1) {
|
|
17409
|
+
signal?.throwIfAborted();
|
|
17410
|
+
const response = await fetch(CURSOR_USAGE_URL, {
|
|
17411
|
+
method: "POST",
|
|
17412
|
+
headers: {
|
|
17413
|
+
Authorization: `Bearer ${auth.token}`,
|
|
17414
|
+
"Content-Type": "application/json",
|
|
17415
|
+
"Connect-Protocol-Version": "1"
|
|
17416
|
+
},
|
|
17417
|
+
body: JSON.stringify({
|
|
17418
|
+
page,
|
|
17419
|
+
pageSize: PAGE_SIZE,
|
|
17420
|
+
startDate: String(plan.startDateMs)
|
|
17421
|
+
}),
|
|
17422
|
+
signal: AbortSignal.any([
|
|
17423
|
+
AbortSignal.timeout(2e4),
|
|
17424
|
+
...signal ? [signal] : []
|
|
17425
|
+
])
|
|
17426
|
+
});
|
|
17427
|
+
if (!response.ok)
|
|
17428
|
+
throw new Error(
|
|
17429
|
+
`Cursor billing lookup failed with HTTP ${response.status}.`
|
|
17430
|
+
);
|
|
17431
|
+
const rawPage = await response.json();
|
|
17432
|
+
if (emptyUsagePageSchema.safeParse(rawPage).success) {
|
|
17433
|
+
if (page !== 1)
|
|
17434
|
+
throw new Error(
|
|
17435
|
+
"Cursor billing history ended before its reported count."
|
|
17436
|
+
);
|
|
17437
|
+
break;
|
|
17438
|
+
}
|
|
17439
|
+
const decoded = usagePageSchema.safeParse(rawPage);
|
|
17440
|
+
if (!decoded.success)
|
|
17441
|
+
throw new Error("Cursor returned an invalid billing page.");
|
|
17442
|
+
for (const value of decoded.data.usageEventsDisplay) {
|
|
17443
|
+
const normalized = normalizeCursorUsageEvent(value, localConversations);
|
|
17444
|
+
if (normalized === null) continue;
|
|
17445
|
+
const events = byConversation.get(normalized.conversationId) ?? [];
|
|
17446
|
+
events.push(normalized.event);
|
|
17447
|
+
if (events.length > 5e3)
|
|
17448
|
+
throw new Error("Cursor conversation exceeds the billing sync limit.");
|
|
17449
|
+
byConversation.set(normalized.conversationId, events);
|
|
17450
|
+
}
|
|
17451
|
+
if (page * PAGE_SIZE >= decoded.data.totalUsageEventsCount) break;
|
|
17452
|
+
if (page === MAX_PAGES)
|
|
17453
|
+
throw new Error("Cursor account exceeds the billing history page limit.");
|
|
17454
|
+
if (decoded.data.usageEventsDisplay.length === 0)
|
|
17455
|
+
throw new Error(
|
|
17456
|
+
"Cursor billing history ended before its reported count."
|
|
17457
|
+
);
|
|
17458
|
+
}
|
|
17459
|
+
const endpoint = new URL(config2.endpoint);
|
|
17460
|
+
endpoint.pathname = "/api/traces/cursor-billing";
|
|
17461
|
+
let matched = 0;
|
|
17462
|
+
for (const [conversationId, events] of byConversation) {
|
|
17463
|
+
signal?.throwIfAborted();
|
|
17464
|
+
const request2 = cursorBillingRequestSchema.parse({
|
|
17465
|
+
conversationId,
|
|
17466
|
+
reconcileFromMs: plan.reconcileFromMs,
|
|
17467
|
+
events
|
|
17468
|
+
});
|
|
17469
|
+
const response = await fetcher(endpoint, {
|
|
17470
|
+
method: "POST",
|
|
17471
|
+
headers: {
|
|
17472
|
+
Authorization: `Bearer ${config2.token}`,
|
|
17473
|
+
"Content-Type": "application/json"
|
|
17474
|
+
},
|
|
17475
|
+
body: JSON.stringify(request2),
|
|
17476
|
+
signal: AbortSignal.any([
|
|
17477
|
+
AbortSignal.timeout(9e4),
|
|
17478
|
+
...signal ? [signal] : []
|
|
17479
|
+
])
|
|
17480
|
+
});
|
|
17481
|
+
if (!response.ok)
|
|
17482
|
+
throw new Error(
|
|
17483
|
+
`Cursor billing sync failed with HTTP ${response.status}.`
|
|
17484
|
+
);
|
|
17485
|
+
const result = object({ matched: number2().int().nonnegative() }).safeParse(await response.json());
|
|
17486
|
+
if (!result.success)
|
|
17487
|
+
throw new Error("Iteration returned an invalid billing sync result.");
|
|
17488
|
+
matched += result.data.matched;
|
|
17489
|
+
}
|
|
17490
|
+
await saveBillingState(stateFile, {
|
|
17491
|
+
accountFingerprint: auth.accountFingerprint,
|
|
17492
|
+
lastSyncedAtMs: nowMs,
|
|
17493
|
+
lastAuditAtMs: plan.mode === "recent" ? priorState.lastAuditAtMs : nowMs
|
|
17494
|
+
});
|
|
17495
|
+
return {
|
|
17496
|
+
sessions: byConversation.size,
|
|
17497
|
+
matched,
|
|
17498
|
+
skipped: false,
|
|
17499
|
+
mode: plan.mode
|
|
17500
|
+
};
|
|
17501
|
+
}
|
|
17502
|
+
|
|
16956
17503
|
// src/service.ts
|
|
16957
17504
|
function errorCode2(error2) {
|
|
16958
17505
|
if (error2 instanceof Error && "code" in error2) return String(error2.code);
|
|
@@ -16994,26 +17541,26 @@ async function persistHealth(health, paths) {
|
|
|
16994
17541
|
}
|
|
16995
17542
|
}
|
|
16996
17543
|
async function acquireCollectorLock(paths) {
|
|
16997
|
-
await ensurePrivateDirectory(
|
|
17544
|
+
await ensurePrivateDirectory(path18.dirname(paths.lock));
|
|
16998
17545
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
16999
17546
|
try {
|
|
17000
|
-
const handle = await
|
|
17547
|
+
const handle = await fs15.open(paths.lock, "wx", 384);
|
|
17001
17548
|
await handle.writeFile(`${process.pid}
|
|
17002
17549
|
`, "utf8");
|
|
17003
17550
|
await handle.sync();
|
|
17004
17551
|
await handle.close();
|
|
17005
17552
|
return async () => {
|
|
17006
|
-
const current = await
|
|
17553
|
+
const current = await fs15.readFile(paths.lock, "utf8").catch(() => "");
|
|
17007
17554
|
if (current.trim() === String(process.pid)) {
|
|
17008
|
-
await
|
|
17555
|
+
await fs15.rm(paths.lock, { force: true });
|
|
17009
17556
|
}
|
|
17010
17557
|
};
|
|
17011
17558
|
} catch (error2) {
|
|
17012
17559
|
if (errorCode2(error2) !== "EEXIST") throw error2;
|
|
17013
|
-
const lockContents = await
|
|
17560
|
+
const lockContents = await fs15.readFile(paths.lock, "utf8").catch(() => "");
|
|
17014
17561
|
const lockPid = Number(lockContents.trim());
|
|
17015
17562
|
if (!Number.isSafeInteger(lockPid) || lockPid < 2) {
|
|
17016
|
-
await
|
|
17563
|
+
await fs15.rm(paths.lock, { force: true });
|
|
17017
17564
|
continue;
|
|
17018
17565
|
}
|
|
17019
17566
|
try {
|
|
@@ -17021,7 +17568,7 @@ async function acquireCollectorLock(paths) {
|
|
|
17021
17568
|
throw new CollectorLockBusyError("Collector is already running.");
|
|
17022
17569
|
} catch (processError) {
|
|
17023
17570
|
if (errorCode2(processError) !== "ESRCH") throw processError;
|
|
17024
|
-
await
|
|
17571
|
+
await fs15.rm(paths.lock, { force: true });
|
|
17025
17572
|
}
|
|
17026
17573
|
}
|
|
17027
17574
|
}
|
|
@@ -17111,11 +17658,13 @@ async function runCollector(directory, expectedSource) {
|
|
|
17111
17658
|
if ("cursorHome" in config2) {
|
|
17112
17659
|
await ensureCursorHistoryBackfill(config2.cursorHome, paths);
|
|
17113
17660
|
await installCursorHooks(config2.cursorHome, config2.dataDirectory);
|
|
17114
|
-
} else if ("claudeHome" in config2)
|
|
17661
|
+
} else if ("claudeHome" in config2) {
|
|
17115
17662
|
await ensureClaudeHistoryBackfill(config2.claudeHome, paths);
|
|
17116
|
-
|
|
17663
|
+
await installTurnSummary(config2);
|
|
17664
|
+
} else await ensureHistoryBackfill(config2.codexHome, paths);
|
|
17117
17665
|
const summariesRefreshedAt = /* @__PURE__ */ new Map();
|
|
17118
17666
|
let lastAutoCheckAt = 0;
|
|
17667
|
+
let lastBillingSyncAt = 0;
|
|
17119
17668
|
const runtimeVersion = process.env.ITERATION_RUNTIME_VERSION;
|
|
17120
17669
|
let lastRuntimeCheckAt = 0;
|
|
17121
17670
|
let trialSettled = runtimeVersion === void 0;
|
|
@@ -17177,6 +17726,7 @@ async function runCollector(directory, expectedSource) {
|
|
|
17177
17726
|
}
|
|
17178
17727
|
}
|
|
17179
17728
|
let acknowledgedAtMs = null;
|
|
17729
|
+
let billingReady = false;
|
|
17180
17730
|
try {
|
|
17181
17731
|
const result = await collectorTick(
|
|
17182
17732
|
config2,
|
|
@@ -17187,6 +17737,7 @@ async function runCollector(directory, expectedSource) {
|
|
|
17187
17737
|
acknowledgedAtMs = Date.now();
|
|
17188
17738
|
}
|
|
17189
17739
|
);
|
|
17740
|
+
billingReady = result.historyCaughtUp && result.outboxBytes === 0;
|
|
17190
17741
|
if (acknowledgedAtMs !== null || health.lastFailure !== null || result.historyCaughtUp && health.historyCaughtUpAtMs === null) {
|
|
17191
17742
|
health = {
|
|
17192
17743
|
lastUploadAtMs: acknowledgedAtMs ?? health.lastUploadAtMs,
|
|
@@ -17262,6 +17813,33 @@ async function runCollector(directory, expectedSource) {
|
|
|
17262
17813
|
})
|
|
17263
17814
|
);
|
|
17264
17815
|
}
|
|
17816
|
+
if ("cursorHome" in config2 && billingReady && Date.now() - lastBillingSyncAt >= 6 * 60 * 60 * 1e3) {
|
|
17817
|
+
try {
|
|
17818
|
+
const billing = await syncCursorBilling(
|
|
17819
|
+
config2,
|
|
17820
|
+
collectorFetch,
|
|
17821
|
+
controller.signal
|
|
17822
|
+
);
|
|
17823
|
+
lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
|
|
17824
|
+
console.info(
|
|
17825
|
+
JSON.stringify({
|
|
17826
|
+
level: "info",
|
|
17827
|
+
operation: "sync_cursor_billing",
|
|
17828
|
+
...billing
|
|
17829
|
+
})
|
|
17830
|
+
);
|
|
17831
|
+
} catch (error2) {
|
|
17832
|
+
if (controller.signal.aborted) break;
|
|
17833
|
+
lastBillingSyncAt = Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3;
|
|
17834
|
+
console.error(
|
|
17835
|
+
JSON.stringify({
|
|
17836
|
+
level: "warning",
|
|
17837
|
+
operation: "sync_cursor_billing",
|
|
17838
|
+
message: error2 instanceof Error ? error2.message : "Cursor billing sync failed."
|
|
17839
|
+
})
|
|
17840
|
+
);
|
|
17841
|
+
}
|
|
17842
|
+
}
|
|
17265
17843
|
try {
|
|
17266
17844
|
await delay3(retryDelayMs, void 0, { signal: controller.signal });
|
|
17267
17845
|
} catch (error2) {
|
|
@@ -17289,7 +17867,7 @@ async function importSourceHistory(config2, paths, confirmed, control = backgrou
|
|
|
17289
17867
|
const source = collectorSource(config2);
|
|
17290
17868
|
const directory = collectorSourceDirectory(config2);
|
|
17291
17869
|
const state = await loadState(paths);
|
|
17292
|
-
if (!state.initialized || state.sourceDirectory !==
|
|
17870
|
+
if (!state.initialized || state.sourceDirectory !== path19.resolve(directory)) {
|
|
17293
17871
|
throw new Error(
|
|
17294
17872
|
`Connect the ${source} source before importing its history.`
|
|
17295
17873
|
);
|
|
@@ -17478,7 +18056,7 @@ function renderHome(status2, nowMs = Date.now(), source = "codex") {
|
|
|
17478
18056
|
} else if (status2.hookFailureAtMs !== null && status2.hookFailureAtMs !== void 0) {
|
|
17479
18057
|
lines.push(
|
|
17480
18058
|
`${name} hook capture needs attention`,
|
|
17481
|
-
`A
|
|
18059
|
+
`A ${name} hook failed ${relativeTime(status2.hookFailureAtMs, nowMs)}. Some data may be missing. See iteration status ${source}.`
|
|
17482
18060
|
);
|
|
17483
18061
|
} else if (!status2.healthAvailable) {
|
|
17484
18062
|
lines.push(
|
|
@@ -17588,7 +18166,7 @@ function renderDetailedStatus(status2, nowMs = Date.now(), source = "codex") {
|
|
|
17588
18166
|
}
|
|
17589
18167
|
if (status2.hookFailureAtMs !== null && status2.hookFailureAtMs !== void 0)
|
|
17590
18168
|
lines.push(
|
|
17591
|
-
`Last
|
|
18169
|
+
`Last ${name} hook capture failure: ${relativeTime(status2.hookFailureAtMs, nowMs)}. Some data may be missing; inspect ${name} hook logs.`
|
|
17592
18170
|
);
|
|
17593
18171
|
if (status2.turnSummaryNeedsApproval) lines.push(CODEX_APPROVAL_STEP);
|
|
17594
18172
|
if (!status2.running) lines.push(`Resume: iteration start ${source}`);
|
|
@@ -17718,7 +18296,7 @@ function usage(command) {
|
|
|
17718
18296
|
}
|
|
17719
18297
|
async function fileExists(filename) {
|
|
17720
18298
|
try {
|
|
17721
|
-
await
|
|
18299
|
+
await fs16.access(filename);
|
|
17722
18300
|
return true;
|
|
17723
18301
|
} catch (error2) {
|
|
17724
18302
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
@@ -17768,7 +18346,7 @@ ${missing2.map(collectorSourceLabel).join(" and ")} found on this computer but n
|
|
|
17768
18346
|
await connectAll({});
|
|
17769
18347
|
}
|
|
17770
18348
|
function dataDirectory(source, values) {
|
|
17771
|
-
return
|
|
18349
|
+
return path20.resolve(
|
|
17772
18350
|
optionalString(values, "data-directory") ?? defaultCollectorDirectory(source)
|
|
17773
18351
|
);
|
|
17774
18352
|
}
|
|
@@ -17789,12 +18367,12 @@ async function initialize(source, values) {
|
|
|
17789
18367
|
requireCollectorPlatform();
|
|
17790
18368
|
const endpoint = optionalString(values, "endpoint") ?? DEFAULT_INGEST_ENDPOINT;
|
|
17791
18369
|
const endpointUrl = validateCollectorEndpoint(endpoint);
|
|
17792
|
-
const deviceName = optionalString(values, "device-name") ??
|
|
18370
|
+
const deviceName = optionalString(values, "device-name") ?? os8.hostname();
|
|
17793
18371
|
const organizationId = optionalString(values, "organization-id");
|
|
17794
18372
|
const directory = dataDirectory(source, values);
|
|
17795
18373
|
const paths = collectorPaths(directory);
|
|
17796
|
-
const selectedHome =
|
|
17797
|
-
source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ??
|
|
18374
|
+
const selectedHome = path20.resolve(
|
|
18375
|
+
source === "codex" ? optionalString(values, "codex-home") ?? process.env.CODEX_HOME ?? path20.join(os8.homedir(), ".codex") : source === "claude" ? optionalString(values, "claude-home") ?? process.env.CLAUDE_CONFIG_DIR ?? path20.join(os8.homedir(), ".claude") : optionalString(values, "cursor-home") ?? path20.join(os8.homedir(), ".cursor")
|
|
17798
18376
|
);
|
|
17799
18377
|
await assertFreshCollectorDirectory(paths, source);
|
|
17800
18378
|
const preparedState = await prepareSourceInitialState(
|
|
@@ -17877,13 +18455,13 @@ async function connectAll(values) {
|
|
|
17877
18455
|
}
|
|
17878
18456
|
const configs = await Promise.all(COLLECTOR_SOURCES.map(connectedConfig));
|
|
17879
18457
|
const sourceHomes = {
|
|
17880
|
-
codex:
|
|
18458
|
+
codex: path20.resolve(
|
|
17881
18459
|
optionalString(values, "codex-home") ?? (configs[0] && "codexHome" in configs[0] ? configs[0].codexHome : sourceHome("codex"))
|
|
17882
18460
|
),
|
|
17883
|
-
claude:
|
|
18461
|
+
claude: path20.resolve(
|
|
17884
18462
|
optionalString(values, "claude-home") ?? (configs[1] && "claudeHome" in configs[1] ? configs[1].claudeHome : sourceHome("claude"))
|
|
17885
18463
|
),
|
|
17886
|
-
cursor:
|
|
18464
|
+
cursor: path20.resolve(
|
|
17887
18465
|
optionalString(values, "cursor-home") ?? (configs[2] && "cursorHome" in configs[2] ? configs[2].cursorHome : sourceHome("cursor"))
|
|
17888
18466
|
)
|
|
17889
18467
|
};
|
|
@@ -18066,7 +18644,7 @@ async function statusSnapshot(source, values) {
|
|
|
18066
18644
|
historyScanProgress(collectorSourceDirectory(config2), paths, source),
|
|
18067
18645
|
healthSnapshot(paths),
|
|
18068
18646
|
codexTurnSummaryApproval(config2),
|
|
18069
|
-
source === "cursor" ? cursorHookFailureAtMs(config2.dataDirectory) : Promise.resolve(null)
|
|
18647
|
+
source === "cursor" ? cursorHookFailureAtMs(config2.dataDirectory) : source === "claude" ? claudeGitHookFailureAtMs(config2.dataDirectory) : Promise.resolve(null)
|
|
18070
18648
|
]);
|
|
18071
18649
|
const pendingEvents = size === 0 ? 0 : ids.size;
|
|
18072
18650
|
const pendingBytes = ids.size === 0 ? 0 : size;
|
|
@@ -18231,6 +18809,18 @@ async function update(setting, values) {
|
|
|
18231
18809
|
`);
|
|
18232
18810
|
}
|
|
18233
18811
|
async function main() {
|
|
18812
|
+
if (process.argv[2] === "claude-git-hook") {
|
|
18813
|
+
const directory = process.argv[3];
|
|
18814
|
+
if (!directory || process.argv.length !== 4)
|
|
18815
|
+
throw new Error("Invalid Claude Git hook command.");
|
|
18816
|
+
try {
|
|
18817
|
+
await captureClaudeGit(directory);
|
|
18818
|
+
} catch (error2) {
|
|
18819
|
+
await recordClaudeGitHookFailure(directory);
|
|
18820
|
+
throw error2;
|
|
18821
|
+
}
|
|
18822
|
+
return;
|
|
18823
|
+
}
|
|
18234
18824
|
const delegated = await delegateToInstalledVersion();
|
|
18235
18825
|
if (delegated !== null) {
|
|
18236
18826
|
process.exitCode = delegated;
|