@use-aistack/cli 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.js +1380 -318
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -377,12 +377,12 @@ function apiEquivalentCost(modelKey, t, atMs) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
// src/version.ts
|
|
380
|
-
var CLI_VERSION = true ? "0.
|
|
380
|
+
var CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
|
|
381
381
|
|
|
382
382
|
// src/api.ts
|
|
383
383
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
384
|
-
async function request(
|
|
385
|
-
return fetch(`${BASE_URL}${
|
|
384
|
+
async function request(path14, options = {}) {
|
|
385
|
+
return fetch(`${BASE_URL}${path14}`, {
|
|
386
386
|
...options,
|
|
387
387
|
headers: {
|
|
388
388
|
"Content-Type": "application/json",
|
|
@@ -800,9 +800,9 @@ function canonicalizeRepoUrl(input) {
|
|
|
800
800
|
function repoNameFromCanonical(canonical) {
|
|
801
801
|
return parseRepo(canonical)?.repo ?? "";
|
|
802
802
|
}
|
|
803
|
-
function normalizeUpstreamPath(
|
|
804
|
-
if (!
|
|
805
|
-
return
|
|
803
|
+
function normalizeUpstreamPath(path14) {
|
|
804
|
+
if (!path14) return "";
|
|
805
|
+
return path14.split("/").filter(Boolean).join("/");
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
// src/git.ts
|
|
@@ -848,16 +848,16 @@ function buildRepoLinkResource(canonical) {
|
|
|
848
848
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
849
849
|
import { homedir as homedir2 } from "node:os";
|
|
850
850
|
import { join as join2 } from "node:path";
|
|
851
|
-
function readJson(
|
|
851
|
+
function readJson(path14) {
|
|
852
852
|
try {
|
|
853
|
-
if (!existsSync2(
|
|
854
|
-
return JSON.parse(readFileSync2(
|
|
853
|
+
if (!existsSync2(path14)) return null;
|
|
854
|
+
return JSON.parse(readFileSync2(path14, "utf-8"));
|
|
855
855
|
} catch {
|
|
856
856
|
return null;
|
|
857
857
|
}
|
|
858
858
|
}
|
|
859
|
-
function hooksFrom(
|
|
860
|
-
const hooks = readJson(
|
|
859
|
+
function hooksFrom(path14, source, out, seen) {
|
|
860
|
+
const hooks = readJson(path14)?.hooks;
|
|
861
861
|
if (!hooks || typeof hooks !== "object") return;
|
|
862
862
|
for (const [event, config] of Object.entries(hooks)) {
|
|
863
863
|
const stableKey = `hooks:${source}:${event}`;
|
|
@@ -1006,16 +1006,16 @@ function buildMcpResource(name, group, pkg) {
|
|
|
1006
1006
|
pkg
|
|
1007
1007
|
};
|
|
1008
1008
|
}
|
|
1009
|
-
function readText(
|
|
1009
|
+
function readText(path14) {
|
|
1010
1010
|
try {
|
|
1011
|
-
if (!existsSync3(
|
|
1012
|
-
return readFileSync3(
|
|
1011
|
+
if (!existsSync3(path14)) return null;
|
|
1012
|
+
return readFileSync3(path14, "utf-8");
|
|
1013
1013
|
} catch {
|
|
1014
1014
|
return null;
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
|
-
function readParsed(
|
|
1018
|
-
const raw = readText(
|
|
1017
|
+
function readParsed(path14, parse2) {
|
|
1018
|
+
const raw = readText(path14);
|
|
1019
1019
|
if (raw === null) return null;
|
|
1020
1020
|
try {
|
|
1021
1021
|
return parse2(raw);
|
|
@@ -1023,14 +1023,14 @@ function readParsed(path9, parse2) {
|
|
|
1023
1023
|
return null;
|
|
1024
1024
|
}
|
|
1025
1025
|
}
|
|
1026
|
-
function readJson2(
|
|
1027
|
-
return readParsed(
|
|
1026
|
+
function readJson2(path14) {
|
|
1027
|
+
return readParsed(path14, JSON.parse);
|
|
1028
1028
|
}
|
|
1029
|
-
function readYaml(
|
|
1030
|
-
return readParsed(
|
|
1029
|
+
function readYaml(path14) {
|
|
1030
|
+
return readParsed(path14, parseYaml);
|
|
1031
1031
|
}
|
|
1032
|
-
function readToml(
|
|
1033
|
-
return readParsed(
|
|
1032
|
+
function readToml(path14) {
|
|
1033
|
+
return readParsed(path14, parseToml);
|
|
1034
1034
|
}
|
|
1035
1035
|
function continueListToMap(file) {
|
|
1036
1036
|
if (!file?.mcpServers?.length) return void 0;
|
|
@@ -1169,8 +1169,8 @@ function resolveSource(entry, mpRepoUrl) {
|
|
|
1169
1169
|
const src = entry.source;
|
|
1170
1170
|
if (typeof src === "string") {
|
|
1171
1171
|
if (!mpRepoUrl) return null;
|
|
1172
|
-
const
|
|
1173
|
-
return { url: mpRepoUrl, path:
|
|
1172
|
+
const path14 = src.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1173
|
+
return { url: mpRepoUrl, path: path14 || void 0 };
|
|
1174
1174
|
}
|
|
1175
1175
|
if (src && typeof src === "object" && src.url) {
|
|
1176
1176
|
return { url: src.url, path: src.path, sha: src.sha };
|
|
@@ -1207,10 +1207,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
return out;
|
|
1209
1209
|
}
|
|
1210
|
-
function readJson3(
|
|
1210
|
+
function readJson3(path14) {
|
|
1211
1211
|
try {
|
|
1212
|
-
if (!existsSync4(
|
|
1213
|
-
return JSON.parse(readFileSync4(
|
|
1212
|
+
if (!existsSync4(path14)) return null;
|
|
1213
|
+
return JSON.parse(readFileSync4(path14, "utf-8"));
|
|
1214
1214
|
} catch {
|
|
1215
1215
|
return null;
|
|
1216
1216
|
}
|
|
@@ -1299,8 +1299,8 @@ function loadGitignore(cwd) {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
function readFileSafe(filePath) {
|
|
1301
1301
|
try {
|
|
1302
|
-
const
|
|
1303
|
-
if (
|
|
1302
|
+
const stat8 = statSync(filePath);
|
|
1303
|
+
if (stat8.size > MAX_FILE_SIZE) return null;
|
|
1304
1304
|
return readFileSync5(filePath, "utf-8");
|
|
1305
1305
|
} catch {
|
|
1306
1306
|
return null;
|
|
@@ -1519,9 +1519,9 @@ function lines(items) {
|
|
|
1519
1519
|
console.log(`${BAR} ${item}`);
|
|
1520
1520
|
}
|
|
1521
1521
|
}
|
|
1522
|
-
function section(label,
|
|
1522
|
+
function section(label, count2) {
|
|
1523
1523
|
console.log(`${BAR}`);
|
|
1524
|
-
const countStr =
|
|
1524
|
+
const countStr = count2 !== void 0 ? ` ${dim(String(count2))}` : "";
|
|
1525
1525
|
console.log(`${BAR} ${bold(label.toUpperCase())}${countStr}`);
|
|
1526
1526
|
}
|
|
1527
1527
|
function divider() {
|
|
@@ -1844,7 +1844,7 @@ function diffResources(current, existing) {
|
|
|
1844
1844
|
// src/commands/connect.ts
|
|
1845
1845
|
import { spawnSync } from "node:child_process";
|
|
1846
1846
|
import { cpSync, existsSync as existsSync6 } from "node:fs";
|
|
1847
|
-
import { homedir as
|
|
1847
|
+
import { homedir as homedir14 } from "node:os";
|
|
1848
1848
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1849
1849
|
import { fileURLToPath } from "node:url";
|
|
1850
1850
|
import * as p3 from "@clack/prompts";
|
|
@@ -2442,7 +2442,7 @@ function filterAtoms(atoms, sets) {
|
|
|
2442
2442
|
}
|
|
2443
2443
|
merged.set(published, (merged.get(published) ?? 0) + atom.count);
|
|
2444
2444
|
}
|
|
2445
|
-
const allowed = [...merged].map(([name,
|
|
2445
|
+
const allowed = [...merged].map(([name, count2]) => ({ name, count: count2 }));
|
|
2446
2446
|
allowed.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2447
2447
|
keptPrivate.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2448
2448
|
return { allowed, keptPrivate, withheld: keptPrivate.length };
|
|
@@ -3199,6 +3199,7 @@ var HANDOFF_MARKERS = {
|
|
|
3199
3199
|
"mcp__curia__request_review"
|
|
3200
3200
|
],
|
|
3201
3201
|
codex: ["request_user_input"],
|
|
3202
|
+
cursor: ["ask_question"],
|
|
3202
3203
|
"grok-build": ["ask_user_question", "request_user_input"],
|
|
3203
3204
|
opencode: ["question"],
|
|
3204
3205
|
"pi-mono": []
|
|
@@ -3633,13 +3634,13 @@ function canonicalJson(value) {
|
|
|
3633
3634
|
function fnv1a64(text) {
|
|
3634
3635
|
const prime = 0x100000001b3n;
|
|
3635
3636
|
const mask = 0xffffffffffffffffn;
|
|
3636
|
-
let
|
|
3637
|
+
let hash2 = 0xcbf29ce484222325n;
|
|
3637
3638
|
const bytes = new TextEncoder().encode(text);
|
|
3638
3639
|
for (const byte of bytes) {
|
|
3639
|
-
|
|
3640
|
-
|
|
3640
|
+
hash2 ^= BigInt(byte);
|
|
3641
|
+
hash2 = hash2 * prime & mask;
|
|
3641
3642
|
}
|
|
3642
|
-
return
|
|
3643
|
+
return hash2.toString(16).padStart(16, "0");
|
|
3643
3644
|
}
|
|
3644
3645
|
function dayFingerprint(day) {
|
|
3645
3646
|
return fnv1a64(
|
|
@@ -3732,7 +3733,7 @@ function sanitizeModelId(id) {
|
|
|
3732
3733
|
var round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
3733
3734
|
var round2 = (n) => Math.round(n * 100) / 100;
|
|
3734
3735
|
var utcDate = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3735
|
-
var toAtoms = (pairs) => pairs.map(([name,
|
|
3736
|
+
var toAtoms = (pairs) => pairs.map(([name, count2]) => ({ name, count: count2 }));
|
|
3736
3737
|
function buildCategory(observed, curated, optIns, denominator) {
|
|
3737
3738
|
const publishable = /* @__PURE__ */ new Set([...curated, ...optIns]);
|
|
3738
3739
|
const {
|
|
@@ -4025,7 +4026,7 @@ var bump2 = (map, key2, amount = 1) => {
|
|
|
4025
4026
|
var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
4026
4027
|
function asBuckets(map, field) {
|
|
4027
4028
|
return [...map].map(
|
|
4028
|
-
([bucket,
|
|
4029
|
+
([bucket, count2]) => ({ bucket, [field]: count2 })
|
|
4029
4030
|
).sort((a, b) => a.bucket - b.bucket);
|
|
4030
4031
|
}
|
|
4031
4032
|
var PHASE_RANK = {
|
|
@@ -4410,7 +4411,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4410
4411
|
0
|
|
4411
4412
|
);
|
|
4412
4413
|
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
4413
|
-
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build";
|
|
4414
|
+
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build" || harness === "cursor";
|
|
4414
4415
|
const asRows = (map) => {
|
|
4415
4416
|
const safe = /* @__PURE__ */ new Map();
|
|
4416
4417
|
for (const [model, tokens] of map) {
|
|
@@ -4433,7 +4434,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4433
4434
|
date,
|
|
4434
4435
|
harness,
|
|
4435
4436
|
sessions: day.sessions,
|
|
4436
|
-
startHours: [...day.startHours].map(([hourUtc,
|
|
4437
|
+
startHours: [...day.startHours].map(([hourUtc, count2]) => ({ hourUtc, sessions: count2 })).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
4437
4438
|
...day.phase.sessions > 0 ? {
|
|
4438
4439
|
phase: {
|
|
4439
4440
|
ruleVersion: PHASE_RULES_V1,
|
|
@@ -4561,12 +4562,12 @@ function ingestRecord(agg, raw, ctx) {
|
|
|
4561
4562
|
const sessionId = asStr(rec.sessionId);
|
|
4562
4563
|
if (sessionId) agg.sessions.add(sessionId);
|
|
4563
4564
|
let tsMs = null;
|
|
4564
|
-
const
|
|
4565
|
-
if (
|
|
4566
|
-
const ts = Date.parse(
|
|
4565
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
4566
|
+
if (timestamp2) {
|
|
4567
|
+
const ts = Date.parse(timestamp2);
|
|
4567
4568
|
if (!Number.isNaN(ts)) {
|
|
4568
4569
|
tsMs = ts;
|
|
4569
|
-
agg.activeDays.add(
|
|
4570
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
4570
4571
|
agg.firstTs = agg.firstTs === null ? ts : Math.min(agg.firstTs, ts);
|
|
4571
4572
|
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
4572
4573
|
}
|
|
@@ -4859,8 +4860,8 @@ function applyContribution(agg, c, sign) {
|
|
|
4859
4860
|
agg.webFetchRequests += sign * c.webFetch;
|
|
4860
4861
|
agg.fallbackAttempts += sign * c.fallbackAttempts;
|
|
4861
4862
|
agg.untypedMirrors += sign * c.untypedMirrors;
|
|
4862
|
-
for (const [type,
|
|
4863
|
-
bump(agg.mirroredIterationTypes, type, sign *
|
|
4863
|
+
for (const [type, count2] of c.mirroredIterationTypes) {
|
|
4864
|
+
bump(agg.mirroredIterationTypes, type, sign * count2);
|
|
4864
4865
|
}
|
|
4865
4866
|
}
|
|
4866
4867
|
function ingestContentBlocks(agg, content) {
|
|
@@ -5126,9 +5127,9 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5126
5127
|
if (!rec) return;
|
|
5127
5128
|
agg.records++;
|
|
5128
5129
|
let tsMs = null;
|
|
5129
|
-
const
|
|
5130
|
-
if (
|
|
5131
|
-
const ts = Date.parse(
|
|
5130
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
5131
|
+
if (timestamp2) {
|
|
5132
|
+
const ts = Date.parse(timestamp2);
|
|
5132
5133
|
if (!Number.isNaN(ts)) tsMs = ts;
|
|
5133
5134
|
}
|
|
5134
5135
|
const inWindow = sinceMs === void 0 || tsMs !== null && tsMs >= sinceMs;
|
|
@@ -5151,8 +5152,8 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5151
5152
|
state.sawResponse = true;
|
|
5152
5153
|
}
|
|
5153
5154
|
if (!inWindow) return;
|
|
5154
|
-
if (tsMs !== null &&
|
|
5155
|
-
agg.activeDays.add(
|
|
5155
|
+
if (tsMs !== null && timestamp2) {
|
|
5156
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
5156
5157
|
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
5157
5158
|
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
5158
5159
|
}
|
|
@@ -5440,11 +5441,11 @@ function ingestWithRetry(agg, file, opts) {
|
|
|
5440
5441
|
}
|
|
5441
5442
|
}
|
|
5442
5443
|
function ingestFile2(agg, file, opts) {
|
|
5443
|
-
const
|
|
5444
|
+
const readFile4 = opts.readFileImpl ?? readFileSync6;
|
|
5444
5445
|
let text;
|
|
5445
5446
|
if (file.endsWith(".zst")) {
|
|
5446
5447
|
if (zstdDecompress === null) throw readError("zstd-unsupported");
|
|
5447
|
-
const raw =
|
|
5448
|
+
const raw = readFile4(file);
|
|
5448
5449
|
try {
|
|
5449
5450
|
text = zstdDecompress(
|
|
5450
5451
|
Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
|
|
@@ -5453,7 +5454,7 @@ function ingestFile2(agg, file, opts) {
|
|
|
5453
5454
|
throw readError("zstd-corrupt");
|
|
5454
5455
|
}
|
|
5455
5456
|
} else {
|
|
5456
|
-
text =
|
|
5457
|
+
text = readFile4(file).toString("utf8");
|
|
5457
5458
|
}
|
|
5458
5459
|
const records = [];
|
|
5459
5460
|
let nonEmptyLines = 0;
|
|
@@ -5556,6 +5557,817 @@ var codexAdapter = {
|
|
|
5556
5557
|
}
|
|
5557
5558
|
};
|
|
5558
5559
|
|
|
5560
|
+
// src/harness/cursor/cache.ts
|
|
5561
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5562
|
+
import { homedir as homedir9 } from "node:os";
|
|
5563
|
+
import path5 from "node:path";
|
|
5564
|
+
|
|
5565
|
+
// src/harness/cursor/account.ts
|
|
5566
|
+
import { createHash } from "node:crypto";
|
|
5567
|
+
|
|
5568
|
+
// src/harness/cursor/evidence.ts
|
|
5569
|
+
function timestamp(value) {
|
|
5570
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? /^\d+$/.test(value) ? Number(value) : Date.parse(value) : NaN;
|
|
5571
|
+
return Number.isFinite(n) && n > 0 && n < 864e13 ? n : null;
|
|
5572
|
+
}
|
|
5573
|
+
function count(value) {
|
|
5574
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
5575
|
+
}
|
|
5576
|
+
function tokenEvidence(value) {
|
|
5577
|
+
const raw = asObj(value) ?? {};
|
|
5578
|
+
const direct = asObj(raw.tokenCount);
|
|
5579
|
+
const usage = asObj(raw.usage);
|
|
5580
|
+
for (const [obj, fields] of [
|
|
5581
|
+
[
|
|
5582
|
+
direct,
|
|
5583
|
+
["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]
|
|
5584
|
+
],
|
|
5585
|
+
[
|
|
5586
|
+
usage,
|
|
5587
|
+
[
|
|
5588
|
+
"input_tokens",
|
|
5589
|
+
"output_tokens",
|
|
5590
|
+
"cache_read_input_tokens",
|
|
5591
|
+
"cache_creation_input_tokens"
|
|
5592
|
+
]
|
|
5593
|
+
]
|
|
5594
|
+
]) {
|
|
5595
|
+
for (const field of fields)
|
|
5596
|
+
if (obj?.[field] != null && count(obj[field]) === void 0)
|
|
5597
|
+
throw new Error("Invalid Cursor token evidence");
|
|
5598
|
+
}
|
|
5599
|
+
const out = {};
|
|
5600
|
+
out.input = count(direct?.inputTokens) ?? count(usage?.input_tokens);
|
|
5601
|
+
out.output = count(direct?.outputTokens) ?? count(usage?.output_tokens);
|
|
5602
|
+
out.cacheRead = count(direct?.cacheReadTokens) ?? count(usage?.cache_read_input_tokens);
|
|
5603
|
+
out.cacheWrite = count(direct?.cacheWriteTokens) ?? count(usage?.cache_creation_input_tokens);
|
|
5604
|
+
if (out.input !== void 0) out.inputSource = "reported";
|
|
5605
|
+
if (out.output !== void 0) out.outputSource = "reported";
|
|
5606
|
+
if (out.input === void 0) {
|
|
5607
|
+
const context = count(asObj(raw.contextWindowStatusAtCreation)?.tokensUsed);
|
|
5608
|
+
if (context !== void 0) {
|
|
5609
|
+
out.input = context;
|
|
5610
|
+
out.inputSource = "context";
|
|
5611
|
+
} else if (typeof raw.promptDryRunInfo === "string") {
|
|
5612
|
+
try {
|
|
5613
|
+
const dry = asObj(JSON.parse(raw.promptDryRunInfo));
|
|
5614
|
+
const estimate = count(asObj(dry?.fullConversationTokenCount)?.numTokens) ?? count(asObj(dry?.userMessageTokenCount)?.numTokens);
|
|
5615
|
+
if (estimate !== void 0) {
|
|
5616
|
+
out.input = estimate;
|
|
5617
|
+
out.inputSource = "dry-run";
|
|
5618
|
+
}
|
|
5619
|
+
} catch {
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
return out;
|
|
5624
|
+
}
|
|
5625
|
+
var directTimes = /* @__PURE__ */ new Set([
|
|
5626
|
+
"composer-created-at",
|
|
5627
|
+
"composer-timing",
|
|
5628
|
+
"store-turn-timing"
|
|
5629
|
+
]);
|
|
5630
|
+
function messageTimes(session, api = []) {
|
|
5631
|
+
const times = session.messages.map(
|
|
5632
|
+
(m) => directTimes.has(m.timestampSource ?? "") ? timestamp(m.timestamp) : null
|
|
5633
|
+
);
|
|
5634
|
+
const anchors = [];
|
|
5635
|
+
for (const [index, time] of times.entries())
|
|
5636
|
+
if (time !== null) anchors.push({ index, time });
|
|
5637
|
+
const start = session.createdAtSource !== "epoch-unknown" ? timestamp(session.timestamp) : null;
|
|
5638
|
+
const end = session.lastUpdatedAtSource !== "epoch-unknown" ? timestamp(session.metadata?.lastModified) : null;
|
|
5639
|
+
const apiTimes = api.filter((e) => e.session === session.id).map((e) => e.tsMs);
|
|
5640
|
+
if (!anchors.some((a) => a.index === 0)) {
|
|
5641
|
+
const time = start ?? (apiTimes.length ? Math.min(...apiTimes) : null);
|
|
5642
|
+
if (time !== null) anchors.unshift({ index: -1, time });
|
|
5643
|
+
}
|
|
5644
|
+
const last = session.messages.length;
|
|
5645
|
+
if (last && !anchors.some((a) => a.index === last - 1)) {
|
|
5646
|
+
const time = end ?? (apiTimes.length ? Math.max(...apiTimes) : null);
|
|
5647
|
+
if (time !== null) anchors.push({ index: last, time });
|
|
5648
|
+
}
|
|
5649
|
+
for (let i = 0; i < times.length; i++) {
|
|
5650
|
+
if (times[i] !== null) continue;
|
|
5651
|
+
const before = anchors.filter((a) => a.index < i).at(-1);
|
|
5652
|
+
const after = anchors.find((a) => a.index > i);
|
|
5653
|
+
if (before && after && after.time >= before.time)
|
|
5654
|
+
times[i] = Math.round(
|
|
5655
|
+
before.time + (after.time - before.time) * (i - before.index) / (after.index - before.index)
|
|
5656
|
+
);
|
|
5657
|
+
else times[i] = before?.time ?? after?.time ?? null;
|
|
5658
|
+
}
|
|
5659
|
+
return times;
|
|
5660
|
+
}
|
|
5661
|
+
function localContributions(local, api = []) {
|
|
5662
|
+
const { session, tokens } = local;
|
|
5663
|
+
const times = messageTimes(session, api);
|
|
5664
|
+
const out = [];
|
|
5665
|
+
let pending;
|
|
5666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5667
|
+
for (const [i, message] of session.messages.entries()) {
|
|
5668
|
+
if (message.id && seen.has(message.id)) continue;
|
|
5669
|
+
if (message.id) seen.add(message.id);
|
|
5670
|
+
if (message.role === "user") {
|
|
5671
|
+
pending = message;
|
|
5672
|
+
continue;
|
|
5673
|
+
}
|
|
5674
|
+
const own = tokens.get(message.id ?? "") ?? {};
|
|
5675
|
+
const user = tokens.get(pending?.id ?? "") ?? {};
|
|
5676
|
+
const input = own.input !== void 0 ? own : user;
|
|
5677
|
+
const buckets = {
|
|
5678
|
+
...own,
|
|
5679
|
+
input: input.input ?? (pending ? Math.ceil(pending.content.length / 4) : void 0),
|
|
5680
|
+
inputSource: input.inputSource ?? (pending ? "text" : void 0),
|
|
5681
|
+
output: own.output ?? Math.ceil(message.content.length / 4),
|
|
5682
|
+
outputSource: own.outputSource ?? "text"
|
|
5683
|
+
};
|
|
5684
|
+
pending = void 0;
|
|
5685
|
+
const tsMs = times[i];
|
|
5686
|
+
if (tsMs === null) continue;
|
|
5687
|
+
out.push({
|
|
5688
|
+
session: session.id,
|
|
5689
|
+
...message.id ? { id: message.id } : {},
|
|
5690
|
+
...message.identityOrigin === "composer-native" && message.id ? { nativeId: message.id } : {},
|
|
5691
|
+
tsMs,
|
|
5692
|
+
model: asStr(message.model) ?? "unknown",
|
|
5693
|
+
buckets,
|
|
5694
|
+
source: "local",
|
|
5695
|
+
...message.isSidechain ? { sidechain: true } : {}
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
return out;
|
|
5699
|
+
}
|
|
5700
|
+
function reconcile(local, api) {
|
|
5701
|
+
const grain = (c) => `${c.session}\0${new Date(c.tsMs).toISOString().slice(0, 10)}`;
|
|
5702
|
+
const reported = api.filter(
|
|
5703
|
+
(row) => Object.values(row.buckets).some((value) => typeof value === "number")
|
|
5704
|
+
);
|
|
5705
|
+
const grains = new Set(reported.map(grain));
|
|
5706
|
+
return [...local.filter((c) => !grains.has(grain(c))), ...reported];
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
// src/harness/cursor/local.ts
|
|
5710
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
5711
|
+
import { homedir as homedir8 } from "node:os";
|
|
5712
|
+
import path4 from "node:path";
|
|
5713
|
+
function dataPath(env = process.env, home = homedir8(), platform4 = process.platform) {
|
|
5714
|
+
if (env.CURSOR_DATA_PATH) return path4.resolve(env.CURSOR_DATA_PATH);
|
|
5715
|
+
const base = platform4 === "darwin" ? path4.join(home, "Library", "Application Support") : platform4 === "win32" ? env.APPDATA || path4.join(home, "AppData", "Roaming") : env.XDG_CONFIG_HOME || path4.join(home, ".config");
|
|
5716
|
+
return path4.join(base, "Cursor", "User", "workspaceStorage");
|
|
5717
|
+
}
|
|
5718
|
+
var storeRoot = () => process.env.CURSOR_STORE_ROOT || path4.join(homedir8(), ".cursor");
|
|
5719
|
+
var globalDb = (root) => path4.join(path4.dirname(root), "globalStorage", "state.vscdb");
|
|
5720
|
+
async function exists3(file) {
|
|
5721
|
+
try {
|
|
5722
|
+
await stat4(file);
|
|
5723
|
+
return true;
|
|
5724
|
+
} catch (error) {
|
|
5725
|
+
return error.code !== "ENOENT";
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
async function hasLocalSource(root = dataPath()) {
|
|
5729
|
+
return (await Promise.all(
|
|
5730
|
+
[
|
|
5731
|
+
root,
|
|
5732
|
+
globalDb(root),
|
|
5733
|
+
path4.join(storeRoot(), "projects"),
|
|
5734
|
+
path4.join(storeRoot(), "chats"),
|
|
5735
|
+
path4.join(storeRoot(), "acp-sessions")
|
|
5736
|
+
].map(exists3)
|
|
5737
|
+
)).some(Boolean);
|
|
5738
|
+
}
|
|
5739
|
+
async function readTokenEvidence(root, session) {
|
|
5740
|
+
const out = /* @__PURE__ */ new Map();
|
|
5741
|
+
if (!session.messages.some((m) => m.identityOrigin?.startsWith("composer")))
|
|
5742
|
+
return out;
|
|
5743
|
+
const file = globalDb(root);
|
|
5744
|
+
if (!await exists3(file)) return out;
|
|
5745
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
5746
|
+
const db = new DatabaseSync(file, { readOnly: true });
|
|
5747
|
+
try {
|
|
5748
|
+
db.exec("BEGIN");
|
|
5749
|
+
const table = db.prepare(
|
|
5750
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'cursorDiskKV'"
|
|
5751
|
+
).get();
|
|
5752
|
+
if (!table) return out;
|
|
5753
|
+
const query = db.prepare(`SELECT json_object(
|
|
5754
|
+
'tokenCount', json_extract(value, '$.tokenCount'),
|
|
5755
|
+
'usage', json_extract(value, '$.usage'),
|
|
5756
|
+
'contextWindowStatusAtCreation', json_extract(value, '$.contextWindowStatusAtCreation'),
|
|
5757
|
+
'promptDryRunInfo', json_extract(value, '$.promptDryRunInfo')) AS evidence
|
|
5758
|
+
FROM cursorDiskKV WHERE key = ?`);
|
|
5759
|
+
for (const message of session.messages) {
|
|
5760
|
+
if (!message.id || message.identityOrigin !== "composer-native") continue;
|
|
5761
|
+
const row = query.get(`bubbleId:${session.id}:${message.id}`);
|
|
5762
|
+
if (typeof row?.evidence === "string")
|
|
5763
|
+
out.set(message.id, tokenEvidence(JSON.parse(row.evidence)));
|
|
5764
|
+
}
|
|
5765
|
+
const header = db.prepare(
|
|
5766
|
+
"SELECT json_extract(value, '$.conversation') AS conversation FROM cursorDiskKV WHERE key = ?"
|
|
5767
|
+
).get(`composerData:${session.id}`);
|
|
5768
|
+
if (typeof header?.conversation === "string") {
|
|
5769
|
+
const conversation = JSON.parse(header.conversation);
|
|
5770
|
+
if (Array.isArray(conversation))
|
|
5771
|
+
for (const [index, raw] of conversation.entries()) {
|
|
5772
|
+
const obj = asObj(raw);
|
|
5773
|
+
const id = asStr(obj?.bubbleId) ?? asStr(obj?.id) ?? `msg:${index}`;
|
|
5774
|
+
if (!out.has(id)) out.set(id, tokenEvidence(raw));
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
return out;
|
|
5778
|
+
} finally {
|
|
5779
|
+
db.close();
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
async function sourceStamp(root) {
|
|
5783
|
+
const values = await Promise.all(
|
|
5784
|
+
[globalDb(root), `${globalDb(root)}-wal`].map(async (file) => {
|
|
5785
|
+
try {
|
|
5786
|
+
const info = await stat4(file);
|
|
5787
|
+
return `${info.size}:${info.mtimeMs}`;
|
|
5788
|
+
} catch {
|
|
5789
|
+
return "unavailable";
|
|
5790
|
+
}
|
|
5791
|
+
})
|
|
5792
|
+
);
|
|
5793
|
+
return values.join("/");
|
|
5794
|
+
}
|
|
5795
|
+
async function readLocal(root = dataPath(), onProgress) {
|
|
5796
|
+
const stats = emptyScanStats();
|
|
5797
|
+
const out = { sessions: [], complete: true, stats };
|
|
5798
|
+
if (!await hasLocalSource(root)) return out;
|
|
5799
|
+
const before = await sourceStamp(root);
|
|
5800
|
+
let context;
|
|
5801
|
+
try {
|
|
5802
|
+
const reader = await import("cursor-history");
|
|
5803
|
+
const options = {
|
|
5804
|
+
dataPath: root,
|
|
5805
|
+
sqliteDriver: "node:sqlite",
|
|
5806
|
+
onDiagnostic: () => {
|
|
5807
|
+
out.complete = false;
|
|
5808
|
+
},
|
|
5809
|
+
signal: AbortSignal.timeout(12e4)
|
|
5810
|
+
};
|
|
5811
|
+
context = reader.createSessionReadContext(options);
|
|
5812
|
+
const config = { ...options, readContext: context };
|
|
5813
|
+
let offset = 0;
|
|
5814
|
+
while (true) {
|
|
5815
|
+
const page = await reader.listSessionSummaries({
|
|
5816
|
+
...config,
|
|
5817
|
+
offset,
|
|
5818
|
+
limit: 100
|
|
5819
|
+
});
|
|
5820
|
+
for (const summary of page.data) {
|
|
5821
|
+
stats.filesFound++;
|
|
5822
|
+
if (summary.resolutionState === "ambiguous") {
|
|
5823
|
+
out.complete = false;
|
|
5824
|
+
}
|
|
5825
|
+
try {
|
|
5826
|
+
const session = await reader.getSession(summary.id, config);
|
|
5827
|
+
if (session.resolutionState !== "complete" || session.messages.some((m) => m.metadata?.corrupted))
|
|
5828
|
+
out.complete = false;
|
|
5829
|
+
const tokens = await readTokenEvidence(root, session);
|
|
5830
|
+
out.sessions.push({ session, tokens });
|
|
5831
|
+
stats.filesRead++;
|
|
5832
|
+
onProgress?.(stats.filesRead);
|
|
5833
|
+
} catch {
|
|
5834
|
+
out.complete = false;
|
|
5835
|
+
stats.filesUnreadable++;
|
|
5836
|
+
} finally {
|
|
5837
|
+
context.releaseSession(summary.id);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
if (!page.pagination.hasMore) break;
|
|
5841
|
+
if (page.data.length === 0 || offset >= 1e5) {
|
|
5842
|
+
out.complete = false;
|
|
5843
|
+
break;
|
|
5844
|
+
}
|
|
5845
|
+
offset += page.data.length;
|
|
5846
|
+
}
|
|
5847
|
+
} catch {
|
|
5848
|
+
out.complete = false;
|
|
5849
|
+
stats.filesUnreadable++;
|
|
5850
|
+
} finally {
|
|
5851
|
+
try {
|
|
5852
|
+
await context?.dispose();
|
|
5853
|
+
} catch {
|
|
5854
|
+
out.complete = false;
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
if (before !== await sourceStamp(root)) out.complete = false;
|
|
5858
|
+
return out;
|
|
5859
|
+
}
|
|
5860
|
+
|
|
5861
|
+
// src/harness/cursor/account.ts
|
|
5862
|
+
var digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
5863
|
+
async function existingAccount(root) {
|
|
5864
|
+
let db;
|
|
5865
|
+
try {
|
|
5866
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
5867
|
+
db = new DatabaseSync(globalDb(root), { readOnly: true });
|
|
5868
|
+
const row = db.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
5869
|
+
if (typeof row?.value !== "string") return null;
|
|
5870
|
+
const token = row.value.trim().replace(/^"|"$/g, "");
|
|
5871
|
+
const claims = asObj(
|
|
5872
|
+
JSON.parse(
|
|
5873
|
+
Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8")
|
|
5874
|
+
)
|
|
5875
|
+
);
|
|
5876
|
+
const subject = asStr(claims?.sub);
|
|
5877
|
+
if (!subject || claims?.type === "api_key_token" || !/^[\w|:-]+$/.test(subject) || !/^[A-Za-z0-9_.-]+$/.test(token))
|
|
5878
|
+
return null;
|
|
5879
|
+
const user = subject.split("|").at(-1);
|
|
5880
|
+
if (!user) return null;
|
|
5881
|
+
return { scope: digest(subject), cookie: `${user}%3A%3A${token}` };
|
|
5882
|
+
} catch {
|
|
5883
|
+
return null;
|
|
5884
|
+
} finally {
|
|
5885
|
+
db?.close();
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
function apiContribution(value) {
|
|
5889
|
+
const raw = asObj(value);
|
|
5890
|
+
if (!raw) throw new Error("Invalid Cursor event");
|
|
5891
|
+
const session = asStr(raw.conversationId);
|
|
5892
|
+
if (!session || raw.cloudAgentId || session.startsWith("bc-")) return null;
|
|
5893
|
+
const tsMs = timestamp(raw.timestamp);
|
|
5894
|
+
const usage = raw.tokenUsage == null ? {} : asObj(raw.tokenUsage);
|
|
5895
|
+
if (tsMs === null || !usage) throw new Error("Invalid Cursor event");
|
|
5896
|
+
const buckets = {};
|
|
5897
|
+
for (const [key2, field] of [
|
|
5898
|
+
["input", "inputTokens"],
|
|
5899
|
+
["output", "outputTokens"],
|
|
5900
|
+
["cacheRead", "cacheReadTokens"],
|
|
5901
|
+
["cacheWrite", "cacheWriteTokens"]
|
|
5902
|
+
]) {
|
|
5903
|
+
if (usage[field] === void 0 || usage[field] === null) continue;
|
|
5904
|
+
const value2 = count(usage[field]);
|
|
5905
|
+
if (value2 === void 0) throw new Error("Invalid Cursor token bucket");
|
|
5906
|
+
buckets[key2] = value2;
|
|
5907
|
+
}
|
|
5908
|
+
const id = asStr(raw.id) ?? asStr(raw.eventId);
|
|
5909
|
+
return {
|
|
5910
|
+
session,
|
|
5911
|
+
...id ? { id } : {},
|
|
5912
|
+
tsMs,
|
|
5913
|
+
model: asStr(raw.model) ?? "unknown",
|
|
5914
|
+
buckets,
|
|
5915
|
+
source: "api"
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
async function fetchWindow(account, from, to, fetchImpl = fetch) {
|
|
5919
|
+
const out = [];
|
|
5920
|
+
const pages = /* @__PURE__ */ new Set();
|
|
5921
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
5922
|
+
let total;
|
|
5923
|
+
let retrieved = 0;
|
|
5924
|
+
const pageSize = 100;
|
|
5925
|
+
for (let page = 1; page <= 100; page++) {
|
|
5926
|
+
const response = await fetchImpl(
|
|
5927
|
+
"https://cursor.com/api/dashboard/get-filtered-usage-events",
|
|
5928
|
+
{
|
|
5929
|
+
method: "POST",
|
|
5930
|
+
redirect: "error",
|
|
5931
|
+
signal: AbortSignal.timeout(15e3),
|
|
5932
|
+
headers: {
|
|
5933
|
+
"Content-Type": "application/json",
|
|
5934
|
+
Origin: "https://cursor.com",
|
|
5935
|
+
Cookie: `WorkosCursorSessionToken=${account.cookie}`
|
|
5936
|
+
},
|
|
5937
|
+
body: JSON.stringify({
|
|
5938
|
+
startDate: from,
|
|
5939
|
+
endDate: to - 1,
|
|
5940
|
+
page,
|
|
5941
|
+
pageSize
|
|
5942
|
+
})
|
|
5943
|
+
}
|
|
5944
|
+
);
|
|
5945
|
+
if (!response.ok) throw new Error("Cursor usage unavailable");
|
|
5946
|
+
const payload = asObj(await response.json());
|
|
5947
|
+
const events = payload?.usageEventsDisplay;
|
|
5948
|
+
if (!Array.isArray(events) || events.length > pageSize)
|
|
5949
|
+
throw new Error("Invalid Cursor page");
|
|
5950
|
+
if (payload?.totalUsageEventsCount !== void 0) {
|
|
5951
|
+
const reported = count(payload.totalUsageEventsCount);
|
|
5952
|
+
if (reported === void 0 || !Number.isInteger(reported) || total !== void 0 && total !== reported)
|
|
5953
|
+
throw new Error("Cursor page count changed");
|
|
5954
|
+
total = reported;
|
|
5955
|
+
}
|
|
5956
|
+
if (events.length) {
|
|
5957
|
+
const key2 = digest(JSON.stringify(events));
|
|
5958
|
+
if (pages.has(key2)) throw new Error("Repeated Cursor page");
|
|
5959
|
+
pages.add(key2);
|
|
5960
|
+
}
|
|
5961
|
+
retrieved += events.length;
|
|
5962
|
+
for (const value of events) {
|
|
5963
|
+
const event = apiContribution(value);
|
|
5964
|
+
if (!event) continue;
|
|
5965
|
+
if (event.tsMs < from || event.tsMs >= to)
|
|
5966
|
+
throw new Error("Cursor event outside query");
|
|
5967
|
+
if (event.id && eventIds.has(event.id)) continue;
|
|
5968
|
+
if (event.id) eventIds.add(event.id);
|
|
5969
|
+
out.push(event);
|
|
5970
|
+
}
|
|
5971
|
+
if (total !== void 0 && retrieved > total)
|
|
5972
|
+
throw new Error("Invalid Cursor page count");
|
|
5973
|
+
if (total !== void 0 && retrieved === total || total === void 0 && events.length < pageSize)
|
|
5974
|
+
return out;
|
|
5975
|
+
if (!events.length) throw new Error("Incomplete Cursor pages");
|
|
5976
|
+
}
|
|
5977
|
+
throw new Error("Cursor page limit");
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5980
|
+
// src/harness/cursor/cache.ts
|
|
5981
|
+
var emptyCache = () => ({
|
|
5982
|
+
version: 1,
|
|
5983
|
+
account: null,
|
|
5984
|
+
windows: {},
|
|
5985
|
+
local: [],
|
|
5986
|
+
sessions: []
|
|
5987
|
+
});
|
|
5988
|
+
var cacheFile = (root, store) => path5.join(
|
|
5989
|
+
homedir9(),
|
|
5990
|
+
".config",
|
|
5991
|
+
"aistack",
|
|
5992
|
+
"cursor",
|
|
5993
|
+
`${digest(`${root}\0${store}`)}.json`
|
|
5994
|
+
);
|
|
5995
|
+
function validContribution(value) {
|
|
5996
|
+
const row = asObj(value);
|
|
5997
|
+
const buckets = asObj(row?.buckets);
|
|
5998
|
+
return !!row && typeof row.session === "string" && typeof row.model === "string" && timestamp(row.tsMs) !== null && (row.id === void 0 || typeof row.id === "string") && (row.source === "api" || row.source === "local") && !!buckets && ["input", "output", "cacheRead", "cacheWrite"].every(
|
|
5999
|
+
(k) => buckets[k] === void 0 || count(buckets[k]) !== void 0
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
6002
|
+
async function loadCache(file) {
|
|
6003
|
+
try {
|
|
6004
|
+
const raw = asObj(JSON.parse(await readFile(file, "utf8")));
|
|
6005
|
+
const windows = asObj(raw?.windows);
|
|
6006
|
+
if (raw?.version !== 1 || !(raw.account === null || typeof raw.account === "string") || !Array.isArray(raw.sessions) || !raw.sessions.every((id) => typeof id === "string") || !Array.isArray(raw.local) || !raw.local.every(validContribution) || !windows)
|
|
6007
|
+
throw new Error("Invalid cache");
|
|
6008
|
+
for (const value of Object.values(windows)) {
|
|
6009
|
+
const w = asObj(value);
|
|
6010
|
+
if (!w || timestamp(w.from) === null || timestamp(w.to) === null || timestamp(w.fetchedAt) === null || !Array.isArray(w.events) || !w.events.every(validContribution))
|
|
6011
|
+
throw new Error("Invalid window");
|
|
6012
|
+
}
|
|
6013
|
+
return { value: raw, complete: true };
|
|
6014
|
+
} catch (error) {
|
|
6015
|
+
return {
|
|
6016
|
+
value: emptyCache(),
|
|
6017
|
+
complete: error.code === "ENOENT"
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
async function saveCache(file, value) {
|
|
6022
|
+
await mkdir(path5.dirname(file), { recursive: true, mode: 448 });
|
|
6023
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
6024
|
+
await writeFile(temporary, JSON.stringify(value), { mode: 384 });
|
|
6025
|
+
await rename(temporary, file);
|
|
6026
|
+
}
|
|
6027
|
+
var WINDOW_MS = 30 * 864e5;
|
|
6028
|
+
async function refreshAccount(input) {
|
|
6029
|
+
const { cache, account, now, sessionIds } = input;
|
|
6030
|
+
if (!account) return cache;
|
|
6031
|
+
const changed = cache.account !== account.scope;
|
|
6032
|
+
const next = {
|
|
6033
|
+
...cache,
|
|
6034
|
+
account: account.scope,
|
|
6035
|
+
windows: changed ? {} : { ...cache.windows }
|
|
6036
|
+
};
|
|
6037
|
+
for (let from = Math.floor(input.sinceMs / WINDOW_MS) * WINDOW_MS; from <= now; from += WINDOW_MS) {
|
|
6038
|
+
const to = Math.min(from + WINDOW_MS, now + 1);
|
|
6039
|
+
const key2 = String(from);
|
|
6040
|
+
const previous = next.windows[key2];
|
|
6041
|
+
if (previous && now - previous.fetchedAt < 3e5) continue;
|
|
6042
|
+
try {
|
|
6043
|
+
const events = (await fetchWindow(account, from, to, input.fetchImpl)).filter((e) => sessionIds.has(e.session));
|
|
6044
|
+
if (previous?.events.length && events.length === 0 && to <= now) continue;
|
|
6045
|
+
next.windows[key2] = { from, to, fetchedAt: now, events };
|
|
6046
|
+
} catch {
|
|
6047
|
+
break;
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
return next;
|
|
6051
|
+
}
|
|
6052
|
+
function cachedEvents(cache, sessionIds) {
|
|
6053
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6054
|
+
return Object.values(cache.windows).sort((a, b) => b.fetchedAt - a.fetchedAt || b.from - a.from).flatMap((w) => w.events).filter((e) => {
|
|
6055
|
+
if (!sessionIds.has(e.session) || e.id && seen.has(e.id)) return false;
|
|
6056
|
+
if (e.id) seen.add(e.id);
|
|
6057
|
+
return true;
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
// src/harness/cursor/scan.ts
|
|
6062
|
+
import path7 from "node:path";
|
|
6063
|
+
|
|
6064
|
+
// src/harness/cursor/workflow.ts
|
|
6065
|
+
import path6 from "node:path";
|
|
6066
|
+
var TOOLS = {
|
|
6067
|
+
read_file: "Read",
|
|
6068
|
+
read_file_v2: "Read",
|
|
6069
|
+
list_dir: "Glob",
|
|
6070
|
+
glob_file_search: "Glob",
|
|
6071
|
+
grep: "Grep",
|
|
6072
|
+
search: "Grep",
|
|
6073
|
+
codebase_search: "Grep",
|
|
6074
|
+
edit_file: "Edit",
|
|
6075
|
+
edit_file_v2: "Edit",
|
|
6076
|
+
search_replace: "Edit",
|
|
6077
|
+
write: "Write",
|
|
6078
|
+
write_file: "Write",
|
|
6079
|
+
delete_file: "Edit",
|
|
6080
|
+
run_terminal_cmd: "Bash",
|
|
6081
|
+
run_terminal_command: "Bash",
|
|
6082
|
+
execute_command: "Bash",
|
|
6083
|
+
web_search: "WebSearch",
|
|
6084
|
+
web_fetch: "WebFetch",
|
|
6085
|
+
ask_question: "ask_question",
|
|
6086
|
+
todo_write: "TodoWrite",
|
|
6087
|
+
task: "Task",
|
|
6088
|
+
skill: "Skill"
|
|
6089
|
+
};
|
|
6090
|
+
var CURSOR_BUILTIN_TOOLS = new Set(
|
|
6091
|
+
Object.keys(TOOLS)
|
|
6092
|
+
);
|
|
6093
|
+
function projectHistory(options) {
|
|
6094
|
+
const { locals, api, usage, aggregate, workflow, sinceMs, now } = options;
|
|
6095
|
+
const nativeOwners = /* @__PURE__ */ new Map();
|
|
6096
|
+
for (const { session } of locals)
|
|
6097
|
+
for (const message of session.messages)
|
|
6098
|
+
if (message.id && message.identityOrigin === "composer-native" && !message.isSidechain && !nativeOwners.has(message.id))
|
|
6099
|
+
nativeOwners.set(message.id, session.id);
|
|
6100
|
+
const seenMessages = /* @__PURE__ */ new Set();
|
|
6101
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
6102
|
+
const configuredServers = /* @__PURE__ */ new Map();
|
|
6103
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
6104
|
+
const inWindow = (t) => t !== null && t >= sinceMs && t <= now;
|
|
6105
|
+
for (const { session } of locals) {
|
|
6106
|
+
const projectWorkspace = session.canonicalWorkspacePath && path6.isAbsolute(session.canonicalWorkspacePath) ? session.canonicalWorkspacePath : void 0;
|
|
6107
|
+
const times = messageTimes(session, api);
|
|
6108
|
+
const scopeOf = (message) => {
|
|
6109
|
+
const parent = message.parentMessageId && nativeOwners.get(message.parentMessageId);
|
|
6110
|
+
return message.isSidechain ? {
|
|
6111
|
+
session: `${session.id}:sidechain`,
|
|
6112
|
+
sidechain: true,
|
|
6113
|
+
...parent ? { parentSession: parent } : {}
|
|
6114
|
+
} : { session: session.id };
|
|
6115
|
+
};
|
|
6116
|
+
for (const [index, message] of session.messages.entries()) {
|
|
6117
|
+
const identity = message.identityOrigin === "composer-native" && message.id ? `native:${message.id}` : `${session.id}:${message.id ?? index}`;
|
|
6118
|
+
if (seenMessages.has(identity)) continue;
|
|
6119
|
+
seenMessages.add(identity);
|
|
6120
|
+
const tsMs = times[index];
|
|
6121
|
+
const scope = scopeOf(message);
|
|
6122
|
+
if (message.id) scopes.set(`${session.id}:${message.id}`, scope);
|
|
6123
|
+
if (tsMs !== null && !inWindow(tsMs)) continue;
|
|
6124
|
+
const common = { ...scope, projectWorkspace, tsMs: tsMs ?? 0 };
|
|
6125
|
+
if (message.role === "user") {
|
|
6126
|
+
if (inWindow(tsMs))
|
|
6127
|
+
workflow?.ingest({
|
|
6128
|
+
...common,
|
|
6129
|
+
type: "response",
|
|
6130
|
+
responseId: `anchor:${message.id ?? index}`
|
|
6131
|
+
});
|
|
6132
|
+
continue;
|
|
6133
|
+
}
|
|
6134
|
+
let questionBack = false;
|
|
6135
|
+
for (const [toolIndex, call] of (message.toolCalls ?? []).entries()) {
|
|
6136
|
+
const id = call.identityOrigin === "source-native" && call.id ? `native:${call.id}` : `${identity}:${call.id ?? toolIndex}`;
|
|
6137
|
+
if (seenTools.has(id)) continue;
|
|
6138
|
+
seenTools.add(id);
|
|
6139
|
+
bump(aggregate.toolCalls, call.name);
|
|
6140
|
+
const tool = TOOLS[call.name] ?? call.name;
|
|
6141
|
+
const arg = asStr(call.params?.command) ?? asStr(call.params?.cmd) ?? asStr(call.params?.skill) ?? asStr(call.params?.subagent_type) ?? "";
|
|
6142
|
+
if (tool === "WebSearch") aggregate.webSearchRequests++;
|
|
6143
|
+
if (tool === "WebFetch") aggregate.webFetchRequests++;
|
|
6144
|
+
if (tool === "Skill" && arg) bump(aggregate.skillCalls, arg);
|
|
6145
|
+
if (tool === "Task") bump(aggregate.subagentCalls, arg || "(unknown)");
|
|
6146
|
+
const mcp = /^mcp__(.+?)__(.+)$/.exec(call.name);
|
|
6147
|
+
let server = mcp?.[1];
|
|
6148
|
+
if (!server && call.name.startsWith("mcp_")) {
|
|
6149
|
+
const directory = projectWorkspace ?? process.cwd();
|
|
6150
|
+
let names = configuredServers.get(directory);
|
|
6151
|
+
if (!names) {
|
|
6152
|
+
names = detectMcpServers(directory).filter((r) => r.group === "cursor").map((r) => r.name).sort((a, b) => b.length - a.length);
|
|
6153
|
+
configuredServers.set(directory, names);
|
|
6154
|
+
}
|
|
6155
|
+
server = names.find((name) => call.name.startsWith(`mcp_${name}_`));
|
|
6156
|
+
}
|
|
6157
|
+
if (server) {
|
|
6158
|
+
bump(aggregate.mcpServerCalls, server);
|
|
6159
|
+
bump(aggregate.mcpToolCalls, call.name);
|
|
6160
|
+
}
|
|
6161
|
+
questionBack ||= tool === "ask_question";
|
|
6162
|
+
if (inWindow(tsMs))
|
|
6163
|
+
workflow?.ingest({
|
|
6164
|
+
...common,
|
|
6165
|
+
type: "event",
|
|
6166
|
+
tool,
|
|
6167
|
+
arg,
|
|
6168
|
+
batchId: id
|
|
6169
|
+
});
|
|
6170
|
+
}
|
|
6171
|
+
if (inWindow(tsMs))
|
|
6172
|
+
workflow?.ingest({
|
|
6173
|
+
...common,
|
|
6174
|
+
type: "turn",
|
|
6175
|
+
turnId: message.id ?? `assistant:${index}`,
|
|
6176
|
+
questionBack
|
|
6177
|
+
});
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
if (!workflow) return;
|
|
6181
|
+
const projects = new Map(
|
|
6182
|
+
locals.map(({ session }) => [session.id, session.canonicalWorkspacePath])
|
|
6183
|
+
);
|
|
6184
|
+
for (const [index, row] of usage.entries()) {
|
|
6185
|
+
if (!inWindow(row.tsMs)) continue;
|
|
6186
|
+
const project = projects.get(row.session);
|
|
6187
|
+
const scope = row.id && row.source === "local" ? scopes.get(`${row.session}:${row.id}`) : void 0;
|
|
6188
|
+
workflow.ingest({
|
|
6189
|
+
...scope ?? {
|
|
6190
|
+
session: row.sidechain ? `${row.session}:sidechain` : row.session,
|
|
6191
|
+
sidechain: row.sidechain
|
|
6192
|
+
},
|
|
6193
|
+
...project && path6.isAbsolute(project) ? { projectWorkspace: project } : {},
|
|
6194
|
+
type: "response",
|
|
6195
|
+
tsMs: row.tsMs,
|
|
6196
|
+
responseId: `${row.source}:${row.id ?? index}`,
|
|
6197
|
+
model: normalizeModel(row.model),
|
|
6198
|
+
routingTokens: (row.buckets.input ?? 0) + (row.buckets.output ?? 0) + (row.buckets.cacheRead ?? 0) + (row.buckets.cacheWrite ?? 0)
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6203
|
+
// src/harness/cursor/scan.ts
|
|
6204
|
+
async function scan3(options) {
|
|
6205
|
+
const root = options.root ?? dataPath();
|
|
6206
|
+
const now = options.now ?? Date.now();
|
|
6207
|
+
const file = options.cachePath ?? cacheFile(root, storeRoot());
|
|
6208
|
+
const local = await (options.readLocalImpl ?? readLocal)(
|
|
6209
|
+
root,
|
|
6210
|
+
options.onProgress
|
|
6211
|
+
);
|
|
6212
|
+
const loaded = await loadCache(file);
|
|
6213
|
+
let complete = local.complete && loaded.complete;
|
|
6214
|
+
const ids = new Set(local.sessions.map((s) => s.session.id));
|
|
6215
|
+
if ([
|
|
6216
|
+
...loaded.value.local,
|
|
6217
|
+
...Object.values(loaded.value.windows).flatMap((w) => w.events)
|
|
6218
|
+
].some((row) => row.tsMs >= options.sinceMs && !ids.has(row.session)))
|
|
6219
|
+
complete = false;
|
|
6220
|
+
const account = await (options.accountImpl ?? existingAccount)(root);
|
|
6221
|
+
const cache = await refreshAccount({
|
|
6222
|
+
cache: loaded.value,
|
|
6223
|
+
account,
|
|
6224
|
+
sinceMs: options.sinceMs,
|
|
6225
|
+
now,
|
|
6226
|
+
sessionIds: ids,
|
|
6227
|
+
fetchImpl: options.fetchImpl
|
|
6228
|
+
});
|
|
6229
|
+
const api = cachedEvents(cache, ids);
|
|
6230
|
+
const native = /* @__PURE__ */ new Set();
|
|
6231
|
+
const contributions = local.sessions.flatMap((s) => localContributions(s, api)).filter((row) => {
|
|
6232
|
+
if (!row.nativeId) return true;
|
|
6233
|
+
if (native.has(row.nativeId)) return false;
|
|
6234
|
+
native.add(row.nativeId);
|
|
6235
|
+
return true;
|
|
6236
|
+
});
|
|
6237
|
+
const dated = new Set(contributions.map((row) => row.session));
|
|
6238
|
+
if (loaded.value.local.some(
|
|
6239
|
+
(row) => row.tsMs >= options.sinceMs && !dated.has(row.session)
|
|
6240
|
+
))
|
|
6241
|
+
complete = false;
|
|
6242
|
+
const previousDates = /* @__PURE__ */ new Map();
|
|
6243
|
+
for (const row of [
|
|
6244
|
+
...loaded.value.local,
|
|
6245
|
+
...cachedEvents(loaded.value, ids)
|
|
6246
|
+
]) {
|
|
6247
|
+
const dates = previousDates.get(row.session) ?? /* @__PURE__ */ new Set();
|
|
6248
|
+
dates.add(utcDateOf(row.tsMs));
|
|
6249
|
+
previousDates.set(row.session, dates);
|
|
6250
|
+
}
|
|
6251
|
+
if (complete) {
|
|
6252
|
+
cache.local = contributions;
|
|
6253
|
+
cache.sessions = [...ids].sort();
|
|
6254
|
+
try {
|
|
6255
|
+
await saveCache(file, cache);
|
|
6256
|
+
} catch {
|
|
6257
|
+
complete = false;
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
const aggregate = createAggregate();
|
|
6261
|
+
const workflowLocal = createWorkflowLocalSources();
|
|
6262
|
+
const workflow = createHarnessWorkflowReducer("cursor", workflowLocal);
|
|
6263
|
+
const sessions = new Map(local.sessions.map((s) => [s.session.id, s]));
|
|
6264
|
+
const sessionDates = previousDates;
|
|
6265
|
+
for (const { session } of local.sessions) {
|
|
6266
|
+
for (const message of session.messages)
|
|
6267
|
+
if (message.model) {
|
|
6268
|
+
const model = normalizeModel(message.model);
|
|
6269
|
+
if (!aggregate.byModel.has(model))
|
|
6270
|
+
aggregate.byModel.set(model, emptyUsage());
|
|
6271
|
+
}
|
|
6272
|
+
const project = session.canonicalWorkspacePath;
|
|
6273
|
+
if (project && path7.isAbsolute(project)) aggregate.projectDirs.add(project);
|
|
6274
|
+
if (session.metadata?.cursorVersion)
|
|
6275
|
+
aggregate.ccVersions.add(session.metadata.cursorVersion);
|
|
6276
|
+
const times = messageTimes(session, api).filter(
|
|
6277
|
+
(t) => t !== null && t >= options.sinceMs && t <= now
|
|
6278
|
+
);
|
|
6279
|
+
if (times.length) {
|
|
6280
|
+
aggregate.sessions.add(session.id);
|
|
6281
|
+
noteSessionStart(aggregate, session.id, Math.min(...times));
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6284
|
+
const usage = reconcile(contributions, api);
|
|
6285
|
+
projectHistory({
|
|
6286
|
+
locals: local.sessions,
|
|
6287
|
+
api,
|
|
6288
|
+
usage,
|
|
6289
|
+
aggregate,
|
|
6290
|
+
workflow: options.publishWorkflow === false ? void 0 : workflow,
|
|
6291
|
+
sinceMs: options.sinceMs,
|
|
6292
|
+
now
|
|
6293
|
+
});
|
|
6294
|
+
for (const contribution of usage) {
|
|
6295
|
+
const { tsMs, buckets, session, sidechain } = contribution;
|
|
6296
|
+
const dates = sessionDates.get(session) ?? /* @__PURE__ */ new Set();
|
|
6297
|
+
dates.add(utcDateOf(tsMs));
|
|
6298
|
+
sessionDates.set(session, dates);
|
|
6299
|
+
if (tsMs < options.sinceMs || tsMs > now) continue;
|
|
6300
|
+
const model = normalizeModel(contribution.model);
|
|
6301
|
+
const counts2 = {
|
|
6302
|
+
input: buckets.input ?? 0,
|
|
6303
|
+
output: buckets.output ?? 0,
|
|
6304
|
+
cacheRead: buckets.cacheRead ?? 0,
|
|
6305
|
+
cacheWrite5m: 0,
|
|
6306
|
+
cacheWrite1h: 0,
|
|
6307
|
+
cacheWriteUnsplit: buckets.cacheWrite ?? 0
|
|
6308
|
+
};
|
|
6309
|
+
addModelUsage(
|
|
6310
|
+
aggregate,
|
|
6311
|
+
model,
|
|
6312
|
+
counts2,
|
|
6313
|
+
apiEquivalentCost(model, counts2, tsMs),
|
|
6314
|
+
1,
|
|
6315
|
+
{ tsMs, sidechain }
|
|
6316
|
+
);
|
|
6317
|
+
aggregate.records++;
|
|
6318
|
+
aggregate.assistantRecords++;
|
|
6319
|
+
aggregate.distinctResponses++;
|
|
6320
|
+
aggregate.sessions.add(session);
|
|
6321
|
+
aggregate.activeDays.add(utcDateOf(tsMs));
|
|
6322
|
+
aggregate.firstTs = Math.min(aggregate.firstTs ?? tsMs, tsMs);
|
|
6323
|
+
aggregate.lastTs = Math.max(aggregate.lastTs ?? tsMs, tsMs);
|
|
6324
|
+
if (sidechain) aggregate.sidechainTokens += countsTotal(counts2);
|
|
6325
|
+
else aggregate.mainTokens += countsTotal(counts2);
|
|
6326
|
+
noteSessionStart(aggregate, session, tsMs);
|
|
6327
|
+
const project = sessions.get(session)?.session.canonicalWorkspacePath;
|
|
6328
|
+
if (project && path7.isAbsolute(project))
|
|
6329
|
+
noteProjectDay(aggregate, project, tsMs);
|
|
6330
|
+
}
|
|
6331
|
+
aggregate.files = local.stats.filesRead;
|
|
6332
|
+
return {
|
|
6333
|
+
aggregate,
|
|
6334
|
+
stats: local.stats,
|
|
6335
|
+
workflow: workflow.finish(),
|
|
6336
|
+
workflowLocal,
|
|
6337
|
+
scanComplete: complete,
|
|
6338
|
+
sessionDates
|
|
6339
|
+
};
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
// src/harness/cursor/adapter.ts
|
|
6343
|
+
var CURSOR_HARNESS_NAME = "cursor";
|
|
6344
|
+
var cursorAdapter = {
|
|
6345
|
+
name: CURSOR_HARNESS_NAME,
|
|
6346
|
+
builtinTools: CURSOR_BUILTIN_TOOLS,
|
|
6347
|
+
async detect(options) {
|
|
6348
|
+
const roots = options.roots ?? [dataPath()];
|
|
6349
|
+
for (const root of roots) {
|
|
6350
|
+
const held = await loadCache(cacheFile(root, storeRoot()));
|
|
6351
|
+
if (!held.complete || [
|
|
6352
|
+
...held.value.local,
|
|
6353
|
+
...cachedEvents(held.value, new Set(held.value.sessions))
|
|
6354
|
+
].some((row) => row.tsMs >= options.sinceMs))
|
|
6355
|
+
return true;
|
|
6356
|
+
const local = await readLocal(root);
|
|
6357
|
+
if (!local.complete || local.sessions.some(
|
|
6358
|
+
({ session }) => messageTimes(session).some((t) => t !== null && t >= options.sinceMs)
|
|
6359
|
+
))
|
|
6360
|
+
return true;
|
|
6361
|
+
if (local.sessions.some(
|
|
6362
|
+
({ session }) => session.messages.length > 0 && messageTimes(session).every((t) => t === null)
|
|
6363
|
+
))
|
|
6364
|
+
return true;
|
|
6365
|
+
}
|
|
6366
|
+
return false;
|
|
6367
|
+
},
|
|
6368
|
+
scan: scan3
|
|
6369
|
+
};
|
|
6370
|
+
|
|
5559
6371
|
// src/harness/grok/analyzer.ts
|
|
5560
6372
|
function createAggregate4() {
|
|
5561
6373
|
const workflowLocal = createWorkflowLocalSources();
|
|
@@ -5572,41 +6384,41 @@ var createGrokEventState = (parentSession) => ({
|
|
|
5572
6384
|
var bump3 = (map, key2) => {
|
|
5573
6385
|
map.set(key2, (map.get(key2) ?? 0) + 1);
|
|
5574
6386
|
};
|
|
5575
|
-
var toolMetadata = (
|
|
5576
|
-
const meta = asObj(
|
|
6387
|
+
var toolMetadata = (update2) => {
|
|
6388
|
+
const meta = asObj(update2._meta);
|
|
5577
6389
|
return meta && asObj(meta["x.ai/tool"]);
|
|
5578
6390
|
};
|
|
5579
6391
|
function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
5580
6392
|
const root = asObj(value);
|
|
5581
6393
|
const params = root && asObj(root.params);
|
|
5582
|
-
const
|
|
6394
|
+
const update2 = params && asObj(params.update);
|
|
5583
6395
|
const session = params && asStr(params.sessionId);
|
|
5584
6396
|
const tsMs = timestampMs(
|
|
5585
6397
|
asObj(params?._meta)?.agentTimestampMs ?? root?.timestamp
|
|
5586
6398
|
);
|
|
5587
|
-
if (!
|
|
5588
|
-
const kind = asStr(
|
|
6399
|
+
if (!update2 || !session || tsMs === null) return;
|
|
6400
|
+
const kind = asStr(update2.sessionUpdate);
|
|
5589
6401
|
if (kind === "tool_call") {
|
|
5590
|
-
const id = asStr(
|
|
5591
|
-
const metadata = toolMetadata(
|
|
5592
|
-
const name = asName(metadata?.name ??
|
|
6402
|
+
const id = asStr(update2.toolCallId);
|
|
6403
|
+
const metadata = toolMetadata(update2);
|
|
6404
|
+
const name = asName(metadata?.name ?? update2.toolName);
|
|
5593
6405
|
if (!id || !name || state.tools.has(id)) return;
|
|
5594
|
-
const raw = asObj(
|
|
6406
|
+
const raw = asObj(update2.input);
|
|
5595
6407
|
const arg = asStr(raw?.command ?? raw?.query ?? raw?.skill ?? raw?.name);
|
|
5596
6408
|
state.tools.set(id, { name, ...arg ? { arg } : {}, tsMs });
|
|
5597
6409
|
return;
|
|
5598
6410
|
}
|
|
5599
6411
|
if (sinceMs !== void 0 && tsMs < sinceMs) return;
|
|
5600
6412
|
if (kind === "tool_call_update") {
|
|
5601
|
-
const id = asStr(
|
|
5602
|
-
if (!id || asStr(
|
|
6413
|
+
const id = asStr(update2.toolCallId);
|
|
6414
|
+
if (!id || asStr(update2.status) !== "completed") return;
|
|
5603
6415
|
completeTool(agg, state, id, session, projectDir, tsMs);
|
|
5604
6416
|
return;
|
|
5605
6417
|
}
|
|
5606
6418
|
if (kind !== "turn_completed") return;
|
|
5607
|
-
const usage = asObj(
|
|
6419
|
+
const usage = asObj(update2.usage);
|
|
5608
6420
|
if (!usage) return;
|
|
5609
|
-
const prompt = asStr(
|
|
6421
|
+
const prompt = asStr(update2.prompt_id) ?? `turn:${tsMs}`;
|
|
5610
6422
|
for (const [model, raw] of Object.entries(asObj(usage.modelUsage) ?? {})) {
|
|
5611
6423
|
const row = asObj(raw);
|
|
5612
6424
|
agg.workflow.ingest({
|
|
@@ -5620,7 +6432,7 @@ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
|
5620
6432
|
thinkingTokens: asNum(row?.reasoningTokens),
|
|
5621
6433
|
responseTokens: asNum(row?.outputTokens),
|
|
5622
6434
|
routingTokens: asNum(row?.outputTokens),
|
|
5623
|
-
...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(
|
|
6435
|
+
...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(update2.elapsed_ms) > 0 ? { durationSec: asNum(update2.elapsed_ms) / 1e3 } : {}
|
|
5624
6436
|
});
|
|
5625
6437
|
}
|
|
5626
6438
|
agg.workflow.ingest({
|
|
@@ -5630,7 +6442,7 @@ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
|
5630
6442
|
parentSession: state.parentSession,
|
|
5631
6443
|
tsMs,
|
|
5632
6444
|
turnId: prompt,
|
|
5633
|
-
questionBack: asStr(
|
|
6445
|
+
questionBack: asStr(update2.stop_reason) === "question"
|
|
5634
6446
|
});
|
|
5635
6447
|
}
|
|
5636
6448
|
function completeTool(agg, state, id, session, projectDir, tsMs) {
|
|
@@ -5750,10 +6562,10 @@ function sidecarContributions(value, projectDir) {
|
|
|
5750
6562
|
function terminalContribution(value, projectDir) {
|
|
5751
6563
|
const root = asObj(value);
|
|
5752
6564
|
const params = root && asObj(root.params);
|
|
5753
|
-
const
|
|
6565
|
+
const update2 = params && asObj(params.update);
|
|
5754
6566
|
const meta = params && asObj(params._meta);
|
|
5755
|
-
if (!
|
|
5756
|
-
const usage = asObj(
|
|
6567
|
+
if (!update2 || asStr(update2.sessionUpdate) !== "turn_completed") return null;
|
|
6568
|
+
const usage = asObj(update2.usage);
|
|
5757
6569
|
const sessionId = params && asStr(params.sessionId);
|
|
5758
6570
|
const tsMs = timestampMs(meta?.agentTimestampMs ?? root?.timestamp);
|
|
5759
6571
|
if (!usage || !sessionId || tsMs === null) return null;
|
|
@@ -5766,7 +6578,7 @@ function terminalContribution(value, projectDir) {
|
|
|
5766
6578
|
sessionId,
|
|
5767
6579
|
projectDir,
|
|
5768
6580
|
tsMs,
|
|
5769
|
-
...asNum(
|
|
6581
|
+
...asNum(update2.elapsed_ms) > 0 ? { durationMs: asNum(update2.elapsed_ms) } : {},
|
|
5770
6582
|
models
|
|
5771
6583
|
};
|
|
5772
6584
|
}
|
|
@@ -5798,15 +6610,129 @@ function ingestContribution(agg, row) {
|
|
|
5798
6610
|
|
|
5799
6611
|
// src/harness/grok/scan.ts
|
|
5800
6612
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
5801
|
-
import { readdir as
|
|
5802
|
-
import { homedir as
|
|
5803
|
-
import
|
|
6613
|
+
import { readdir as readdir5, readFile as readFile3, stat as stat5 } from "node:fs/promises";
|
|
6614
|
+
import { homedir as homedir11 } from "node:os";
|
|
6615
|
+
import path9 from "node:path";
|
|
5804
6616
|
import readline2 from "node:readline";
|
|
5805
6617
|
import { parse as parseToml3 } from "smol-toml";
|
|
6618
|
+
|
|
6619
|
+
// src/harness/grok/context.ts
|
|
6620
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6621
|
+
import { appendFile, mkdir as mkdir2, readdir as readdir4, readFile as readFile2, unlink } from "node:fs/promises";
|
|
6622
|
+
import { homedir as homedir10 } from "node:os";
|
|
6623
|
+
import path8 from "node:path";
|
|
6624
|
+
var DAY_MS = 864e5;
|
|
6625
|
+
var hash = (value) => createHash2("sha256").update(value).digest("hex");
|
|
6626
|
+
function contextCall(value) {
|
|
6627
|
+
const row = asObj(value);
|
|
6628
|
+
if (row?.src !== "shell" || row.msg !== "shell.turn.inference_done")
|
|
6629
|
+
return null;
|
|
6630
|
+
const session = asStr(row.sid);
|
|
6631
|
+
const ts = asStr(row.ts);
|
|
6632
|
+
const ctx = asObj(row.ctx);
|
|
6633
|
+
const tokens = ctx?.prompt_tokens;
|
|
6634
|
+
const loop = ctx?.loop_index;
|
|
6635
|
+
const tsMs = ts ? Date.parse(ts) : NaN;
|
|
6636
|
+
if (!session || !Number.isFinite(tsMs) || typeof tokens !== "number" || !Number.isSafeInteger(tokens) || tokens < 0 || typeof loop !== "number" || !Number.isSafeInteger(loop) || loop < 0)
|
|
6637
|
+
return null;
|
|
6638
|
+
return {
|
|
6639
|
+
id: hash(JSON.stringify([session, tsMs, row.pid ?? null, loop, tokens])),
|
|
6640
|
+
session,
|
|
6641
|
+
tsMs,
|
|
6642
|
+
tokens
|
|
6643
|
+
};
|
|
6644
|
+
}
|
|
6645
|
+
function contextCacheDirectory(root) {
|
|
6646
|
+
return path8.join(
|
|
6647
|
+
homedir10(),
|
|
6648
|
+
".config",
|
|
6649
|
+
"aistack",
|
|
6650
|
+
"grok-context",
|
|
6651
|
+
hash(path8.resolve(root))
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
async function optionalRead(file) {
|
|
6655
|
+
try {
|
|
6656
|
+
return await readFile2(file, "utf8");
|
|
6657
|
+
} catch (error) {
|
|
6658
|
+
if (error.code === "ENOENT") return "";
|
|
6659
|
+
throw error;
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
async function retainedContextCalls(root, sessions, cacheDir = contextCacheDirectory(root), now = Date.now()) {
|
|
6663
|
+
const cutoff = now - 400 * DAY_MS;
|
|
6664
|
+
const calls = /* @__PURE__ */ new Map();
|
|
6665
|
+
let files = [];
|
|
6666
|
+
try {
|
|
6667
|
+
files = await readdir4(cacheDir);
|
|
6668
|
+
} catch (error) {
|
|
6669
|
+
if (error.code !== "ENOENT") throw error;
|
|
6670
|
+
}
|
|
6671
|
+
for (const file of files) {
|
|
6672
|
+
if (!/^\d{4}-\d{2}-\d{2}\.jsonl$/.test(file)) continue;
|
|
6673
|
+
if (Date.parse(file.slice(0, 10)) + DAY_MS < cutoff) {
|
|
6674
|
+
await unlink(path8.join(cacheDir, file)).catch(
|
|
6675
|
+
(error) => {
|
|
6676
|
+
if (error.code !== "ENOENT") throw error;
|
|
6677
|
+
}
|
|
6678
|
+
);
|
|
6679
|
+
continue;
|
|
6680
|
+
}
|
|
6681
|
+
const raw2 = await optionalRead(path8.join(cacheDir, file));
|
|
6682
|
+
if (raw2 && !raw2.endsWith("\n"))
|
|
6683
|
+
throw new Error("Incomplete Grok context cache");
|
|
6684
|
+
for (const line of raw2.split("\n").filter(Boolean)) {
|
|
6685
|
+
const call = asObj(JSON.parse(line));
|
|
6686
|
+
if (!call || typeof call.id !== "string" || !/^[a-f0-9]{64}$/.test(call.id) || typeof call.session !== "string" || typeof call.tsMs !== "number" || !Number.isFinite(call.tsMs) || typeof call.tokens !== "number" || !Number.isSafeInteger(call.tokens) || call.tokens < 0)
|
|
6687
|
+
throw new Error("Invalid Grok context cache");
|
|
6688
|
+
if (call.tsMs >= cutoff && call.tsMs <= now)
|
|
6689
|
+
calls.set(call.id, {
|
|
6690
|
+
id: call.id,
|
|
6691
|
+
session: call.session,
|
|
6692
|
+
tsMs: call.tsMs,
|
|
6693
|
+
tokens: call.tokens
|
|
6694
|
+
});
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
const raw = await optionalRead(
|
|
6698
|
+
path8.join(root, "..", "logs", "unified.jsonl")
|
|
6699
|
+
);
|
|
6700
|
+
const additions = /* @__PURE__ */ new Map();
|
|
6701
|
+
for (const line of raw.slice(0, raw.lastIndexOf("\n") + 1).split("\n")) {
|
|
6702
|
+
let value;
|
|
6703
|
+
try {
|
|
6704
|
+
value = JSON.parse(line);
|
|
6705
|
+
} catch {
|
|
6706
|
+
continue;
|
|
6707
|
+
}
|
|
6708
|
+
const call = contextCall(value);
|
|
6709
|
+
if (!call || !sessions.has(call.session) || call.tsMs < cutoff || call.tsMs > now || calls.has(call.id))
|
|
6710
|
+
continue;
|
|
6711
|
+
calls.set(call.id, call);
|
|
6712
|
+
const date = new Date(call.tsMs).toISOString().slice(0, 10);
|
|
6713
|
+
const lines2 = additions.get(date) ?? [];
|
|
6714
|
+
lines2.push(JSON.stringify(call));
|
|
6715
|
+
additions.set(date, lines2);
|
|
6716
|
+
}
|
|
6717
|
+
if (additions.size) {
|
|
6718
|
+
await mkdir2(cacheDir, { recursive: true, mode: 448 });
|
|
6719
|
+
for (const [date, lines2] of additions) {
|
|
6720
|
+
await appendFile(
|
|
6721
|
+
path8.join(cacheDir, `${date}.jsonl`),
|
|
6722
|
+
`${lines2.join("\n")}
|
|
6723
|
+
`,
|
|
6724
|
+
{ mode: 384 }
|
|
6725
|
+
);
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
return [...calls.values()].filter((call) => sessions.has(call.session)).sort((a, b) => a.tsMs - b.tsMs || a.id.localeCompare(b.id));
|
|
6729
|
+
}
|
|
6730
|
+
|
|
6731
|
+
// src/harness/grok/scan.ts
|
|
5806
6732
|
function sessionRoots() {
|
|
5807
6733
|
return [
|
|
5808
|
-
|
|
5809
|
-
process.env.GROK_HOME ||
|
|
6734
|
+
path9.join(
|
|
6735
|
+
process.env.GROK_HOME || path9.join(homedir11(), ".grok"),
|
|
5810
6736
|
"sessions"
|
|
5811
6737
|
)
|
|
5812
6738
|
];
|
|
@@ -5818,7 +6744,7 @@ async function stableRead(file, jsonl) {
|
|
|
5818
6744
|
for (const delay of delays) {
|
|
5819
6745
|
if (delay) await pause(delay);
|
|
5820
6746
|
try {
|
|
5821
|
-
const before = await
|
|
6747
|
+
const before = await stat5(file);
|
|
5822
6748
|
if (!before.isFile()) return { complete: false };
|
|
5823
6749
|
if (jsonl) {
|
|
5824
6750
|
const lines2 = [];
|
|
@@ -5834,12 +6760,12 @@ async function stableRead(file, jsonl) {
|
|
|
5834
6760
|
lines2.push(null);
|
|
5835
6761
|
}
|
|
5836
6762
|
}
|
|
5837
|
-
const after = await
|
|
6763
|
+
const after = await stat5(file);
|
|
5838
6764
|
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
5839
6765
|
return { lines: lines2, complete: true };
|
|
5840
6766
|
} else {
|
|
5841
|
-
const raw = await
|
|
5842
|
-
const after = await
|
|
6767
|
+
const raw = await readFile3(file, "utf8");
|
|
6768
|
+
const after = await stat5(file);
|
|
5843
6769
|
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
5844
6770
|
return { json: JSON.parse(raw), complete: true };
|
|
5845
6771
|
}
|
|
@@ -5850,16 +6776,16 @@ async function stableRead(file, jsonl) {
|
|
|
5850
6776
|
}
|
|
5851
6777
|
async function directories(root) {
|
|
5852
6778
|
try {
|
|
5853
|
-
const workspaces = await
|
|
6779
|
+
const workspaces = await readdir5(root, { withFileTypes: true });
|
|
5854
6780
|
const out = [];
|
|
5855
6781
|
for (const workspace of workspaces) {
|
|
5856
6782
|
if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
|
|
5857
|
-
const workspacePath =
|
|
5858
|
-
for (const session of await
|
|
6783
|
+
const workspacePath = path9.join(root, workspace.name);
|
|
6784
|
+
for (const session of await readdir5(workspacePath, {
|
|
5859
6785
|
withFileTypes: true
|
|
5860
6786
|
})) {
|
|
5861
6787
|
if (session.isDirectory() && !session.isSymbolicLink())
|
|
5862
|
-
out.push(
|
|
6788
|
+
out.push(path9.join(workspacePath, session.name));
|
|
5863
6789
|
}
|
|
5864
6790
|
}
|
|
5865
6791
|
return out.sort();
|
|
@@ -5871,7 +6797,7 @@ async function modelAliasesForRoot(root) {
|
|
|
5871
6797
|
if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
|
|
5872
6798
|
try {
|
|
5873
6799
|
const parsed = asObj(
|
|
5874
|
-
parseToml3(await
|
|
6800
|
+
parseToml3(await readFile3(path9.join(root, "..", "config.toml"), "utf8"))
|
|
5875
6801
|
);
|
|
5876
6802
|
if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
|
|
5877
6803
|
const models = asObj(parsed.model);
|
|
@@ -5887,13 +6813,14 @@ async function modelAliasesForRoot(root) {
|
|
|
5887
6813
|
return /* @__PURE__ */ new Map();
|
|
5888
6814
|
}
|
|
5889
6815
|
}
|
|
5890
|
-
async function
|
|
6816
|
+
async function scan4(agg, opts = {}) {
|
|
5891
6817
|
const stats = emptyScanStats();
|
|
5892
6818
|
const sessionDates = /* @__PURE__ */ new Map();
|
|
5893
6819
|
const candidates = /* @__PURE__ */ new Map();
|
|
5894
6820
|
let complete = true;
|
|
5895
6821
|
for (const root of opts.roots ?? sessionRoots()) {
|
|
5896
6822
|
const aliases = await modelAliasesForRoot(root);
|
|
6823
|
+
const contextSessions = /* @__PURE__ */ new Map();
|
|
5897
6824
|
const dirs = await directories(root);
|
|
5898
6825
|
if (dirs === null) {
|
|
5899
6826
|
complete = false;
|
|
@@ -5902,7 +6829,7 @@ async function scan3(agg, opts = {}) {
|
|
|
5902
6829
|
for (const dir of dirs) {
|
|
5903
6830
|
let entries;
|
|
5904
6831
|
try {
|
|
5905
|
-
entries = await
|
|
6832
|
+
entries = await readdir5(dir, { withFileTypes: true });
|
|
5906
6833
|
} catch {
|
|
5907
6834
|
complete = false;
|
|
5908
6835
|
continue;
|
|
@@ -5910,41 +6837,46 @@ async function scan3(agg, opts = {}) {
|
|
|
5910
6837
|
const files = new Map(
|
|
5911
6838
|
entries.filter(
|
|
5912
6839
|
(e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
|
|
5913
|
-
).map((e) => [e.name,
|
|
6840
|
+
).map((e) => [e.name, path9.join(dir, e.name)])
|
|
5914
6841
|
);
|
|
5915
6842
|
let projectDir = dir;
|
|
5916
|
-
let sessionFallback =
|
|
6843
|
+
let sessionFallback = path9.basename(dir);
|
|
5917
6844
|
let child = false;
|
|
5918
6845
|
let parentSession;
|
|
5919
6846
|
const summary = files.get("summary.json");
|
|
5920
6847
|
if (summary) {
|
|
5921
|
-
const
|
|
5922
|
-
if (!
|
|
6848
|
+
const read3 = await stableRead(summary, false);
|
|
6849
|
+
if (!read3.complete) {
|
|
5923
6850
|
complete = false;
|
|
5924
6851
|
stats.filesUnreadable++;
|
|
5925
6852
|
continue;
|
|
5926
6853
|
}
|
|
5927
|
-
const value = asObj(
|
|
6854
|
+
const value = asObj(read3.json);
|
|
5928
6855
|
const info = value && asObj(value.info);
|
|
5929
6856
|
projectDir = asStr(info?.cwd) ?? asStr(value?.cwd) ?? dir;
|
|
5930
6857
|
sessionFallback = asStr(value?.sessionId) ?? sessionFallback;
|
|
5931
6858
|
parentSession = asStr(value?.parentSessionId) ?? asStr(value?.parent_session_id) ?? asStr(info?.parentSessionId) ?? asStr(info?.parent_session_id) ?? void 0;
|
|
5932
6859
|
child = parentSession !== void 0;
|
|
5933
6860
|
}
|
|
6861
|
+
contextSessions.set(sessionFallback, { projectDir, parentSession });
|
|
5934
6862
|
let rows = [];
|
|
5935
6863
|
let precedence = 0;
|
|
5936
6864
|
const usage = files.get("usage.json");
|
|
5937
6865
|
if (usage) {
|
|
5938
6866
|
stats.filesFound++;
|
|
5939
|
-
const
|
|
5940
|
-
if (!
|
|
6867
|
+
const read3 = await stableRead(usage, false);
|
|
6868
|
+
if (!read3.complete) {
|
|
5941
6869
|
complete = false;
|
|
5942
6870
|
stats.filesUnreadable++;
|
|
5943
6871
|
continue;
|
|
5944
6872
|
}
|
|
5945
6873
|
stats.filesRead++;
|
|
5946
|
-
rows = sidecarContributions(
|
|
5947
|
-
if (rows.length > 0)
|
|
6874
|
+
rows = sidecarContributions(read3.json, projectDir);
|
|
6875
|
+
if (rows.length > 0) {
|
|
6876
|
+
precedence = 2;
|
|
6877
|
+
for (const row of rows)
|
|
6878
|
+
contextSessions.set(row.sessionId, { projectDir, parentSession });
|
|
6879
|
+
}
|
|
5948
6880
|
}
|
|
5949
6881
|
if (child) {
|
|
5950
6882
|
rows = [];
|
|
@@ -5955,14 +6887,14 @@ async function scan3(agg, opts = {}) {
|
|
|
5955
6887
|
if (updates) {
|
|
5956
6888
|
const useTerminalUsage = rows.length === 0 && !child;
|
|
5957
6889
|
stats.filesFound++;
|
|
5958
|
-
const
|
|
5959
|
-
if (!
|
|
6890
|
+
const read3 = await stableRead(updates, true);
|
|
6891
|
+
if (!read3.complete) {
|
|
5960
6892
|
complete = false;
|
|
5961
6893
|
stats.filesUnreadable++;
|
|
5962
6894
|
continue;
|
|
5963
6895
|
}
|
|
5964
6896
|
stats.filesRead++;
|
|
5965
|
-
for (const value of
|
|
6897
|
+
for (const value of read3.lines ?? []) {
|
|
5966
6898
|
if (value === null) {
|
|
5967
6899
|
agg.parseErrors++;
|
|
5968
6900
|
continue;
|
|
@@ -5978,14 +6910,14 @@ async function scan3(agg, opts = {}) {
|
|
|
5978
6910
|
const events = files.get("events.jsonl");
|
|
5979
6911
|
if (events) {
|
|
5980
6912
|
stats.filesFound++;
|
|
5981
|
-
const
|
|
5982
|
-
if (!
|
|
6913
|
+
const read3 = await stableRead(events, true);
|
|
6914
|
+
if (!read3.complete) {
|
|
5983
6915
|
complete = false;
|
|
5984
6916
|
stats.filesUnreadable++;
|
|
5985
6917
|
continue;
|
|
5986
6918
|
}
|
|
5987
6919
|
stats.filesRead++;
|
|
5988
|
-
for (const value of
|
|
6920
|
+
for (const value of read3.lines ?? []) {
|
|
5989
6921
|
if (value === null) agg.parseErrors++;
|
|
5990
6922
|
else
|
|
5991
6923
|
ingestEvent2(
|
|
@@ -6017,6 +6949,32 @@ async function scan3(agg, opts = {}) {
|
|
|
6017
6949
|
}
|
|
6018
6950
|
opts.onProgress?.(stats.filesFound);
|
|
6019
6951
|
}
|
|
6952
|
+
try {
|
|
6953
|
+
const calls = await retainedContextCalls(
|
|
6954
|
+
root,
|
|
6955
|
+
new Set(contextSessions.keys()),
|
|
6956
|
+
opts.contextCacheDir
|
|
6957
|
+
);
|
|
6958
|
+
for (const call of calls) {
|
|
6959
|
+
if (opts.sinceMs !== void 0 && call.tsMs < opts.sinceMs) continue;
|
|
6960
|
+
const source = contextSessions.get(call.session);
|
|
6961
|
+
agg.workflow.ingest({
|
|
6962
|
+
type: "response",
|
|
6963
|
+
session: call.session,
|
|
6964
|
+
projectWorkspace: source?.projectDir,
|
|
6965
|
+
parentSession: source?.parentSession,
|
|
6966
|
+
tsMs: call.tsMs,
|
|
6967
|
+
responseId: `context:${call.id}`,
|
|
6968
|
+
contextTokens: call.tokens
|
|
6969
|
+
});
|
|
6970
|
+
const dates = sessionDates.get(call.session) ?? /* @__PURE__ */ new Set();
|
|
6971
|
+
dates.add(new Date(call.tsMs).toISOString().slice(0, 10));
|
|
6972
|
+
sessionDates.set(call.session, dates);
|
|
6973
|
+
}
|
|
6974
|
+
} catch {
|
|
6975
|
+
complete = false;
|
|
6976
|
+
stats.filesUnreadable++;
|
|
6977
|
+
}
|
|
6020
6978
|
}
|
|
6021
6979
|
for (const { rows } of candidates.values()) {
|
|
6022
6980
|
for (const row of rows) {
|
|
@@ -6048,7 +7006,7 @@ var grokAdapter = {
|
|
|
6048
7006
|
),
|
|
6049
7007
|
async scan(opts) {
|
|
6050
7008
|
const aggregate = createAggregate4();
|
|
6051
|
-
const result = await
|
|
7009
|
+
const result = await scan4(aggregate, opts);
|
|
6052
7010
|
return {
|
|
6053
7011
|
aggregate,
|
|
6054
7012
|
stats: result.stats,
|
|
@@ -6244,14 +7202,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
|
|
|
6244
7202
|
|
|
6245
7203
|
// src/harness/opencode/scan.ts
|
|
6246
7204
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
6247
|
-
import { readdir as
|
|
6248
|
-
import { homedir as
|
|
6249
|
-
import
|
|
7205
|
+
import { readdir as readdir6, stat as stat6 } from "node:fs/promises";
|
|
7206
|
+
import { homedir as homedir12 } from "node:os";
|
|
7207
|
+
import path10 from "node:path";
|
|
6250
7208
|
var OPENCODE_MIGRATION_CEILING = 20260622202450;
|
|
6251
7209
|
function opencodeDataDirs() {
|
|
6252
7210
|
const xdg = process.env.XDG_DATA_HOME;
|
|
6253
|
-
const base = xdg ||
|
|
6254
|
-
return [
|
|
7211
|
+
const base = xdg || path10.join(homedir12(), ".local", "share");
|
|
7212
|
+
return [path10.join(base, "opencode")];
|
|
6255
7213
|
}
|
|
6256
7214
|
function isStoreFile(basename2) {
|
|
6257
7215
|
return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
|
|
@@ -6260,8 +7218,8 @@ async function dbFilesIn(root) {
|
|
|
6260
7218
|
const override = process.env.OPENCODE_DB;
|
|
6261
7219
|
if (override) return [override];
|
|
6262
7220
|
try {
|
|
6263
|
-
const entries = await
|
|
6264
|
-
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) =>
|
|
7221
|
+
const entries = await readdir6(root, { withFileTypes: true });
|
|
7222
|
+
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path10.join(root, e.name)).sort();
|
|
6265
7223
|
} catch {
|
|
6266
7224
|
return [];
|
|
6267
7225
|
}
|
|
@@ -6281,7 +7239,7 @@ function errorClass2(e) {
|
|
|
6281
7239
|
return e instanceof Error ? e.constructor.name : "unknown";
|
|
6282
7240
|
}
|
|
6283
7241
|
var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
|
|
6284
|
-
async function
|
|
7242
|
+
async function scan5(agg, opts = {}) {
|
|
6285
7243
|
const stats = emptyScanStats();
|
|
6286
7244
|
const open2 = await loadSqlite();
|
|
6287
7245
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -6298,7 +7256,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6298
7256
|
} catch (e) {
|
|
6299
7257
|
stats.filesUnreadable++;
|
|
6300
7258
|
stats.unreadableFiles.push({
|
|
6301
|
-
path:
|
|
7259
|
+
path: path10.basename(file),
|
|
6302
7260
|
reason: errorClass2(e)
|
|
6303
7261
|
});
|
|
6304
7262
|
}
|
|
@@ -6465,8 +7423,8 @@ function pickTs(jsonTs, columnTs) {
|
|
|
6465
7423
|
}
|
|
6466
7424
|
function opencodeConfigFile() {
|
|
6467
7425
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
6468
|
-
const base = xdg ||
|
|
6469
|
-
return
|
|
7426
|
+
const base = xdg || path10.join(homedir12(), ".config");
|
|
7427
|
+
return path10.join(base, "opencode", "opencode.json");
|
|
6470
7428
|
}
|
|
6471
7429
|
function readConfiguredMcpServers2(agg, state, configFile) {
|
|
6472
7430
|
const file = configFile ?? opencodeConfigFile();
|
|
@@ -6518,7 +7476,7 @@ async function detectOpencode(opts) {
|
|
|
6518
7476
|
if (open2 === null) return false;
|
|
6519
7477
|
for (const root of opts.roots ?? opencodeDataDirs()) {
|
|
6520
7478
|
for (const file of await dbFilesIn(root)) {
|
|
6521
|
-
if (!await
|
|
7479
|
+
if (!await exists4(file)) continue;
|
|
6522
7480
|
let db = null;
|
|
6523
7481
|
try {
|
|
6524
7482
|
db = open2(file);
|
|
@@ -6538,9 +7496,9 @@ async function detectOpencode(opts) {
|
|
|
6538
7496
|
}
|
|
6539
7497
|
return false;
|
|
6540
7498
|
}
|
|
6541
|
-
async function
|
|
7499
|
+
async function exists4(p8) {
|
|
6542
7500
|
try {
|
|
6543
|
-
await
|
|
7501
|
+
await stat6(p8);
|
|
6544
7502
|
return true;
|
|
6545
7503
|
} catch {
|
|
6546
7504
|
return false;
|
|
@@ -6560,7 +7518,7 @@ var opencodeAdapter = {
|
|
|
6560
7518
|
},
|
|
6561
7519
|
async scan(opts) {
|
|
6562
7520
|
const aggregate = createAggregate5();
|
|
6563
|
-
const stats = await
|
|
7521
|
+
const stats = await scan5(aggregate, {
|
|
6564
7522
|
sinceMs: opts.sinceMs,
|
|
6565
7523
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6566
7524
|
});
|
|
@@ -6768,17 +7726,17 @@ function readCounts2(usageRaw) {
|
|
|
6768
7726
|
|
|
6769
7727
|
// src/harness/pi/scan.ts
|
|
6770
7728
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
6771
|
-
import { readdir as
|
|
6772
|
-
import { homedir as
|
|
6773
|
-
import
|
|
7729
|
+
import { readdir as readdir7, realpath as realpath3, stat as stat7 } from "node:fs/promises";
|
|
7730
|
+
import { homedir as homedir13 } from "node:os";
|
|
7731
|
+
import path11 from "node:path";
|
|
6774
7732
|
import readline3 from "node:readline";
|
|
6775
7733
|
var MAX_SESSION_VERSION = 3;
|
|
6776
7734
|
function piAgentDir() {
|
|
6777
|
-
return process.env.PI_CODING_AGENT_DIR ||
|
|
7735
|
+
return process.env.PI_CODING_AGENT_DIR || path11.join(homedir13(), ".pi", "agent");
|
|
6778
7736
|
}
|
|
6779
7737
|
function sessionRoots2() {
|
|
6780
7738
|
const override = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
6781
|
-
return [override ||
|
|
7739
|
+
return [override || path11.join(piAgentDir(), "sessions")];
|
|
6782
7740
|
}
|
|
6783
7741
|
var SESSION_FILE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
|
|
6784
7742
|
function isSessionFile(basename2) {
|
|
@@ -6787,22 +7745,22 @@ function isSessionFile(basename2) {
|
|
|
6787
7745
|
async function* walkSessions(dir) {
|
|
6788
7746
|
let entries;
|
|
6789
7747
|
try {
|
|
6790
|
-
entries = await
|
|
7748
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
6791
7749
|
} catch {
|
|
6792
7750
|
return;
|
|
6793
7751
|
}
|
|
6794
7752
|
for (const e of entries) {
|
|
6795
|
-
const full =
|
|
7753
|
+
const full = path11.join(dir, e.name);
|
|
6796
7754
|
if (e.isDirectory()) yield* walkSessions(full);
|
|
6797
7755
|
else if (e.isFile() && isSessionFile(e.name)) yield full;
|
|
6798
7756
|
}
|
|
6799
7757
|
}
|
|
6800
|
-
async function
|
|
7758
|
+
async function scan6(agg, opts = {}) {
|
|
6801
7759
|
const stats = emptyScanStats();
|
|
6802
7760
|
const visited = /* @__PURE__ */ new Set();
|
|
6803
7761
|
const fold = createFoldState();
|
|
6804
7762
|
for (const root of opts.roots ?? sessionRoots2()) {
|
|
6805
|
-
if (!await
|
|
7763
|
+
if (!await exists5(root)) continue;
|
|
6806
7764
|
for await (const file of walkSessions(root)) {
|
|
6807
7765
|
stats.filesFound++;
|
|
6808
7766
|
let resolved;
|
|
@@ -6818,7 +7776,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6818
7776
|
visited.add(resolved);
|
|
6819
7777
|
if (opts.sinceMs !== void 0) {
|
|
6820
7778
|
try {
|
|
6821
|
-
const st = await
|
|
7779
|
+
const st = await stat7(file);
|
|
6822
7780
|
if (st.mtimeMs < opts.sinceMs) {
|
|
6823
7781
|
stats.filesSkippedByMtime++;
|
|
6824
7782
|
continue;
|
|
@@ -6840,7 +7798,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6840
7798
|
stats.filesUnreadable++;
|
|
6841
7799
|
stats.filesRead--;
|
|
6842
7800
|
stats.unreadableFiles.push({
|
|
6843
|
-
path:
|
|
7801
|
+
path: path11.relative(root, file),
|
|
6844
7802
|
reason: "version-too-new"
|
|
6845
7803
|
});
|
|
6846
7804
|
}
|
|
@@ -6848,7 +7806,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6848
7806
|
stats.filesUnreadable++;
|
|
6849
7807
|
stats.filesRead--;
|
|
6850
7808
|
stats.unreadableFiles.push({
|
|
6851
|
-
path:
|
|
7809
|
+
path: path11.relative(root, file),
|
|
6852
7810
|
reason: "read-error"
|
|
6853
7811
|
});
|
|
6854
7812
|
}
|
|
@@ -6856,9 +7814,9 @@ async function scan5(agg, opts = {}) {
|
|
|
6856
7814
|
}
|
|
6857
7815
|
return stats;
|
|
6858
7816
|
}
|
|
6859
|
-
async function
|
|
7817
|
+
async function exists5(p8) {
|
|
6860
7818
|
try {
|
|
6861
|
-
await
|
|
7819
|
+
await stat7(p8);
|
|
6862
7820
|
return true;
|
|
6863
7821
|
} catch {
|
|
6864
7822
|
return false;
|
|
@@ -6926,7 +7884,7 @@ var piAdapter = {
|
|
|
6926
7884
|
},
|
|
6927
7885
|
async scan(opts) {
|
|
6928
7886
|
const aggregate = createAggregate6();
|
|
6929
|
-
const stats = await
|
|
7887
|
+
const stats = await scan6(aggregate, {
|
|
6930
7888
|
sinceMs: opts.sinceMs,
|
|
6931
7889
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6932
7890
|
});
|
|
@@ -6944,12 +7902,14 @@ var HARNESS_ADAPTERS = [
|
|
|
6944
7902
|
claudeAdapter,
|
|
6945
7903
|
codexAdapter,
|
|
6946
7904
|
grokAdapter,
|
|
7905
|
+
cursorAdapter,
|
|
6947
7906
|
opencodeAdapter,
|
|
6948
7907
|
piAdapter
|
|
6949
7908
|
];
|
|
6950
7909
|
function harnessLabel2(name) {
|
|
6951
7910
|
if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
|
|
6952
7911
|
if (name === CODEX_HARNESS_NAME) return "Codex";
|
|
7912
|
+
if (name === CURSOR_HARNESS_NAME) return "Cursor";
|
|
6953
7913
|
if (name === GROK_HARNESS_NAME) return "Grok Build";
|
|
6954
7914
|
if (name === OPENCODE_HARNESS_NAME) return "opencode";
|
|
6955
7915
|
if (name === PI_HARNESS_NAME) return "Pi";
|
|
@@ -6984,7 +7944,7 @@ var MCP_ADD_ARGS = [
|
|
|
6984
7944
|
"mcp"
|
|
6985
7945
|
];
|
|
6986
7946
|
var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
|
|
6987
|
-
var SKILL_DEST = join6(
|
|
7947
|
+
var SKILL_DEST = join6(homedir14(), ".claude", "skills", "aistack-sync");
|
|
6988
7948
|
function runClaude(args) {
|
|
6989
7949
|
const r = spawnSync("claude", args, { encoding: "utf-8" });
|
|
6990
7950
|
const notFound = r.error !== void 0 && r.error.code === "ENOENT";
|
|
@@ -7217,9 +8177,9 @@ async function createCommand() {
|
|
|
7217
8177
|
import { hostname } from "node:os";
|
|
7218
8178
|
import * as p5 from "@clack/prompts";
|
|
7219
8179
|
import open from "open";
|
|
7220
|
-
function proposedMachineName(
|
|
8180
|
+
function proposedMachineName(read3 = hostname) {
|
|
7221
8181
|
try {
|
|
7222
|
-
const name =
|
|
8182
|
+
const name = read3().trim().replace(/\.local$/i, "");
|
|
7223
8183
|
if (!name || name.length > 64) return void 0;
|
|
7224
8184
|
return name;
|
|
7225
8185
|
} catch {
|
|
@@ -7306,13 +8266,13 @@ async function loginCommand(options = {}) {
|
|
|
7306
8266
|
import * as p7 from "@clack/prompts";
|
|
7307
8267
|
|
|
7308
8268
|
// src/autosync/codexHook.ts
|
|
7309
|
-
import { createHash } from "node:crypto";
|
|
8269
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7310
8270
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7311
|
-
import { homedir as
|
|
8271
|
+
import { homedir as homedir15 } from "node:os";
|
|
7312
8272
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
7313
8273
|
import { parse } from "smol-toml";
|
|
7314
8274
|
function codexHome2() {
|
|
7315
|
-
return process.env.CODEX_HOME || join8(
|
|
8275
|
+
return process.env.CODEX_HOME || join8(homedir15(), ".codex");
|
|
7316
8276
|
}
|
|
7317
8277
|
function codexHooksFile() {
|
|
7318
8278
|
return join8(codexHome2(), "hooks.json");
|
|
@@ -7338,9 +8298,9 @@ function readHooksJson(file) {
|
|
|
7338
8298
|
}
|
|
7339
8299
|
var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
|
|
7340
8300
|
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
7341
|
-
const
|
|
7342
|
-
if ("error" in
|
|
7343
|
-
const settings =
|
|
8301
|
+
const read3 = readHooksJson(file);
|
|
8302
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8303
|
+
const settings = read3.settings;
|
|
7344
8304
|
const hooks = settings.hooks ?? {};
|
|
7345
8305
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
7346
8306
|
const kept = sessionStart.map((m) => ({
|
|
@@ -7360,9 +8320,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
7360
8320
|
function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
7361
8321
|
if (!existsSync8(file))
|
|
7362
8322
|
return { ok: true, message: "no Codex hook to remove" };
|
|
7363
|
-
const
|
|
7364
|
-
if ("error" in
|
|
7365
|
-
const settings =
|
|
8323
|
+
const read3 = readHooksJson(file);
|
|
8324
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8325
|
+
const settings = read3.settings;
|
|
7366
8326
|
const sessionStart = settings.hooks?.SessionStart;
|
|
7367
8327
|
if (!Array.isArray(sessionStart)) {
|
|
7368
8328
|
return { ok: true, message: "no Codex hook to remove" };
|
|
@@ -7387,18 +8347,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
7387
8347
|
return { ok: true, message: `hook removed from ${file}` };
|
|
7388
8348
|
}
|
|
7389
8349
|
function codexAutoSyncHookInstalled(file = codexHooksFile()) {
|
|
7390
|
-
const
|
|
7391
|
-
if ("error" in
|
|
7392
|
-
const sessionStart =
|
|
8350
|
+
const read3 = readHooksJson(file);
|
|
8351
|
+
if ("error" in read3) return false;
|
|
8352
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
7393
8353
|
if (!Array.isArray(sessionStart)) return false;
|
|
7394
8354
|
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
|
|
7395
8355
|
}
|
|
7396
8356
|
function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
|
|
7397
8357
|
try {
|
|
7398
8358
|
const config = parse(readFileSync9(configFile, "utf-8"));
|
|
7399
|
-
const
|
|
7400
|
-
if ("error" in
|
|
7401
|
-
const sessionStart =
|
|
8359
|
+
const read3 = readHooksJson(hooksFile);
|
|
8360
|
+
if ("error" in read3) return null;
|
|
8361
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
7402
8362
|
if (!Array.isArray(sessionStart)) return false;
|
|
7403
8363
|
const matches = [];
|
|
7404
8364
|
for (const [groupIndex, group] of sessionStart.entries()) {
|
|
@@ -7421,7 +8381,7 @@ function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooks
|
|
|
7421
8381
|
hooks: [normalizedHandler]
|
|
7422
8382
|
};
|
|
7423
8383
|
if (typeof group.matcher === "string") identity.matcher = group.matcher;
|
|
7424
|
-
const currentHash = `sha256:${
|
|
8384
|
+
const currentHash = `sha256:${createHash3("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
|
|
7425
8385
|
const key2 = `${hooksFile}:session_start:${groupIndex}:${handlerIndex}`;
|
|
7426
8386
|
matches.push(config.hooks?.state?.[key2]?.trusted_hash === currentHash);
|
|
7427
8387
|
}
|
|
@@ -7451,23 +8411,177 @@ function canonicalJson2(value) {
|
|
|
7451
8411
|
// src/autosync/optin.ts
|
|
7452
8412
|
import * as p6 from "@clack/prompts";
|
|
7453
8413
|
|
|
8414
|
+
// src/autosync/cursorHook.ts
|
|
8415
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8416
|
+
import { homedir as homedir17, platform as platform2 } from "node:os";
|
|
8417
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
8418
|
+
|
|
8419
|
+
// src/autosync/hook.ts
|
|
8420
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8421
|
+
import { homedir as homedir16 } from "node:os";
|
|
8422
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8423
|
+
var CLAUDE_SETTINGS_FILE = join9(homedir16(), ".claude", "settings.json");
|
|
8424
|
+
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
8425
|
+
function isOurs2(entry) {
|
|
8426
|
+
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
8427
|
+
}
|
|
8428
|
+
function readClaudeSettings(file) {
|
|
8429
|
+
if (!existsSync9(file)) return { settings: {} };
|
|
8430
|
+
try {
|
|
8431
|
+
const raw = JSON.parse(readFileSync10(file, "utf-8"));
|
|
8432
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
8433
|
+
return { settings: raw };
|
|
8434
|
+
}
|
|
8435
|
+
return { error: `${file} does not hold a JSON object` };
|
|
8436
|
+
} catch {
|
|
8437
|
+
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
8438
|
+
}
|
|
8439
|
+
}
|
|
8440
|
+
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
8441
|
+
const read3 = readClaudeSettings(file);
|
|
8442
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8443
|
+
const settings = read3.settings;
|
|
8444
|
+
const hooks = settings.hooks ?? {};
|
|
8445
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
8446
|
+
const kept = sessionStart.map((m) => ({
|
|
8447
|
+
...m,
|
|
8448
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
8449
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
8450
|
+
kept.push({
|
|
8451
|
+
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
8452
|
+
});
|
|
8453
|
+
settings.hooks = { ...hooks, SessionStart: kept };
|
|
8454
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
8455
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
8456
|
+
`);
|
|
8457
|
+
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
8458
|
+
}
|
|
8459
|
+
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
8460
|
+
if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
|
|
8461
|
+
const read3 = readClaudeSettings(file);
|
|
8462
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8463
|
+
const settings = read3.settings;
|
|
8464
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
8465
|
+
if (!Array.isArray(sessionStart)) {
|
|
8466
|
+
return { ok: true, message: "no hook to remove" };
|
|
8467
|
+
}
|
|
8468
|
+
const kept = sessionStart.map((m) => ({
|
|
8469
|
+
...m,
|
|
8470
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
8471
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
8472
|
+
const hooks = { ...settings.hooks };
|
|
8473
|
+
if (kept.length > 0) {
|
|
8474
|
+
hooks.SessionStart = kept;
|
|
8475
|
+
} else {
|
|
8476
|
+
delete hooks.SessionStart;
|
|
8477
|
+
}
|
|
8478
|
+
if (Object.keys(hooks).length > 0) {
|
|
8479
|
+
settings.hooks = hooks;
|
|
8480
|
+
} else {
|
|
8481
|
+
delete settings.hooks;
|
|
8482
|
+
}
|
|
8483
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
8484
|
+
`);
|
|
8485
|
+
return { ok: true, message: `hook removed from ${file}` };
|
|
8486
|
+
}
|
|
8487
|
+
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
8488
|
+
const read3 = readClaudeSettings(file);
|
|
8489
|
+
if ("error" in read3) return false;
|
|
8490
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
8491
|
+
if (!Array.isArray(sessionStart)) return false;
|
|
8492
|
+
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs2(h)));
|
|
8493
|
+
}
|
|
8494
|
+
|
|
8495
|
+
// src/autosync/cursorHook.ts
|
|
8496
|
+
var CURSOR_HOOK_FILE = join10(homedir17(), ".cursor", "hooks.json");
|
|
8497
|
+
function cursorHookCommand(os = platform2()) {
|
|
8498
|
+
if (os === "win32") {
|
|
8499
|
+
const script = `Start-Process -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=cursor&& (${AUTO_SYNC_HOOK_COMMAND}) <NUL >NUL 2>&1"'`;
|
|
8500
|
+
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script, "utf16le").toString("base64")}`;
|
|
8501
|
+
}
|
|
8502
|
+
const detach = os === "darwin" ? "nohup" : "setsid nohup";
|
|
8503
|
+
return `${detach} sh -c 'export AISTACK_HOOK_SOURCE=cursor; ${AUTO_SYNC_HOOK_COMMAND}' </dev/null >/dev/null 2>&1 &`;
|
|
8504
|
+
}
|
|
8505
|
+
function read(file) {
|
|
8506
|
+
if (!existsSync10(file)) return {};
|
|
8507
|
+
const value = JSON.parse(readFileSync11(file, "utf8"));
|
|
8508
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8509
|
+
throw new Error("expected a JSON object");
|
|
8510
|
+
const config = value;
|
|
8511
|
+
if (config.version !== void 0 && config.version !== 1)
|
|
8512
|
+
throw new Error("unsupported hooks version");
|
|
8513
|
+
if (config.hooks !== void 0 && (!config.hooks || typeof config.hooks !== "object" || Array.isArray(config.hooks) || Object.values(config.hooks).some(
|
|
8514
|
+
(entries) => !Array.isArray(entries) || entries.some(
|
|
8515
|
+
(entry) => !entry || typeof entry !== "object" || Array.isArray(entry)
|
|
8516
|
+
)
|
|
8517
|
+
)))
|
|
8518
|
+
throw new Error("malformed hooks object");
|
|
8519
|
+
return config;
|
|
8520
|
+
}
|
|
8521
|
+
function isOurs3(entry) {
|
|
8522
|
+
return ["linux", "darwin", "win32"].some(
|
|
8523
|
+
(os) => entry.command === cursorHookCommand(os)
|
|
8524
|
+
);
|
|
8525
|
+
}
|
|
8526
|
+
function update(file, install, os) {
|
|
8527
|
+
try {
|
|
8528
|
+
const config = read(file);
|
|
8529
|
+
const existing = config.hooks?.stop ?? [];
|
|
8530
|
+
const kept = existing.filter((entry) => !isOurs3(entry));
|
|
8531
|
+
if (!install && kept.length === existing.length)
|
|
8532
|
+
return { ok: true, message: "no Cursor hook to remove" };
|
|
8533
|
+
if (install) kept.push({ command: cursorHookCommand(os) });
|
|
8534
|
+
const hooks = { ...config.hooks };
|
|
8535
|
+
if (kept.length) hooks.stop = kept;
|
|
8536
|
+
else delete hooks.stop;
|
|
8537
|
+
if (Object.keys(hooks).length) config.hooks = hooks;
|
|
8538
|
+
else delete config.hooks;
|
|
8539
|
+
if (install) config.version = 1;
|
|
8540
|
+
mkdirSync5(dirname7(file), { recursive: true });
|
|
8541
|
+
writeFileSync5(file, `${JSON.stringify(config, null, 2)}
|
|
8542
|
+
`);
|
|
8543
|
+
return {
|
|
8544
|
+
ok: true,
|
|
8545
|
+
message: `Cursor stop hook ${install ? "written to" : "removed from"} ${file}`
|
|
8546
|
+
};
|
|
8547
|
+
} catch (error) {
|
|
8548
|
+
return {
|
|
8549
|
+
ok: false,
|
|
8550
|
+
message: `Could not ${install ? "install" : "remove"} Cursor hook in ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
8551
|
+
};
|
|
8552
|
+
}
|
|
8553
|
+
}
|
|
8554
|
+
function installCursorAutoSyncHook(file = CURSOR_HOOK_FILE, os = platform2()) {
|
|
8555
|
+
return update(file, true, os);
|
|
8556
|
+
}
|
|
8557
|
+
function removeCursorAutoSyncHook(file = CURSOR_HOOK_FILE) {
|
|
8558
|
+
return update(file, false, platform2());
|
|
8559
|
+
}
|
|
8560
|
+
function cursorAutoSyncHookInstalled(file = CURSOR_HOOK_FILE) {
|
|
8561
|
+
try {
|
|
8562
|
+
return (read(file).hooks?.stop ?? []).some(isOurs3);
|
|
8563
|
+
} catch {
|
|
8564
|
+
return false;
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8567
|
+
|
|
7454
8568
|
// src/autosync/grokHook.ts
|
|
7455
8569
|
import {
|
|
7456
|
-
existsSync as
|
|
7457
|
-
mkdirSync as
|
|
7458
|
-
readFileSync as
|
|
8570
|
+
existsSync as existsSync11,
|
|
8571
|
+
mkdirSync as mkdirSync6,
|
|
8572
|
+
readFileSync as readFileSync12,
|
|
7459
8573
|
unlinkSync,
|
|
7460
|
-
writeFileSync as
|
|
8574
|
+
writeFileSync as writeFileSync6
|
|
7461
8575
|
} from "node:fs";
|
|
7462
|
-
import { homedir as
|
|
7463
|
-
import { dirname as
|
|
7464
|
-
var GROK_HOOK_FILE =
|
|
7465
|
-
process.env.GROK_HOME ||
|
|
8576
|
+
import { homedir as homedir18, platform as platform3 } from "node:os";
|
|
8577
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
8578
|
+
var GROK_HOOK_FILE = join11(
|
|
8579
|
+
process.env.GROK_HOME || join11(homedir18(), ".grok"),
|
|
7466
8580
|
"hooks",
|
|
7467
8581
|
"aistack.json"
|
|
7468
8582
|
);
|
|
7469
8583
|
var SYNC_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
7470
|
-
function grokHookCommand(os =
|
|
8584
|
+
function grokHookCommand(os = platform3()) {
|
|
7471
8585
|
if (os === "win32") {
|
|
7472
8586
|
return `Start-Process -WindowStyle Hidden -FilePath "cmd.exe" -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=grok&& (${SYNC_COMMAND}) >NUL 2>&1"'`;
|
|
7473
8587
|
}
|
|
@@ -7477,9 +8591,9 @@ function grokHookCommand(os = platform2()) {
|
|
|
7477
8591
|
return `setsid nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' >/dev/null 2>&1 &`;
|
|
7478
8592
|
}
|
|
7479
8593
|
function readHookFile(file) {
|
|
7480
|
-
if (!
|
|
8594
|
+
if (!existsSync11(file)) return { value: {} };
|
|
7481
8595
|
try {
|
|
7482
|
-
const value = JSON.parse(
|
|
8596
|
+
const value = JSON.parse(readFileSync12(file, "utf-8"));
|
|
7483
8597
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
7484
8598
|
const candidate = value;
|
|
7485
8599
|
if (candidate.hooks !== void 0 && (!candidate.hooks || typeof candidate.hooks !== "object" || Array.isArray(candidate.hooks) || Object.values(candidate.hooks).some(
|
|
@@ -7496,19 +8610,19 @@ function readHookFile(file) {
|
|
|
7496
8610
|
}
|
|
7497
8611
|
return { error: `${file} does not hold a JSON object` };
|
|
7498
8612
|
}
|
|
7499
|
-
function
|
|
8613
|
+
function isOurs4(entry) {
|
|
7500
8614
|
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
7501
8615
|
}
|
|
7502
|
-
function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os =
|
|
7503
|
-
const
|
|
7504
|
-
if ("error" in
|
|
7505
|
-
const foreignKeys = Object.keys(
|
|
7506
|
-
const foreignEvents = Object.keys(
|
|
8616
|
+
function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform3()) {
|
|
8617
|
+
const read3 = readHookFile(file);
|
|
8618
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8619
|
+
const foreignKeys = Object.keys(read3.value).filter((key2) => key2 !== "hooks");
|
|
8620
|
+
const foreignEvents = Object.keys(read3.value.hooks ?? {}).filter(
|
|
7507
8621
|
(key2) => key2 !== "SessionStart"
|
|
7508
8622
|
);
|
|
7509
|
-
const sessionStart =
|
|
8623
|
+
const sessionStart = read3.value.hooks?.SessionStart ?? [];
|
|
7510
8624
|
const foreignHandlers = sessionStart.flatMap(
|
|
7511
|
-
(group) => (group.hooks ?? []).filter((entry) => !
|
|
8625
|
+
(group) => (group.hooks ?? []).filter((entry) => !isOurs4(entry))
|
|
7512
8626
|
);
|
|
7513
8627
|
if (foreignKeys.length > 0 || foreignEvents.length > 0 || foreignHandlers.length > 0) {
|
|
7514
8628
|
return {
|
|
@@ -7527,8 +8641,8 @@ function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform2()) {
|
|
|
7527
8641
|
]
|
|
7528
8642
|
}
|
|
7529
8643
|
};
|
|
7530
|
-
|
|
7531
|
-
|
|
8644
|
+
mkdirSync6(dirname8(file), { recursive: true });
|
|
8645
|
+
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
7532
8646
|
`);
|
|
7533
8647
|
return {
|
|
7534
8648
|
ok: true,
|
|
@@ -7536,15 +8650,15 @@ function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform2()) {
|
|
|
7536
8650
|
};
|
|
7537
8651
|
}
|
|
7538
8652
|
function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
|
|
7539
|
-
if (!
|
|
8653
|
+
if (!existsSync11(file))
|
|
7540
8654
|
return { ok: true, message: "no Grok Build hook to remove" };
|
|
7541
|
-
const
|
|
7542
|
-
if ("error" in
|
|
7543
|
-
const entries =
|
|
7544
|
-
const onlyOurs = Object.keys(
|
|
8655
|
+
const read3 = readHookFile(file);
|
|
8656
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8657
|
+
const entries = read3.value.hooks?.SessionStart ?? [];
|
|
8658
|
+
const onlyOurs = Object.keys(read3.value).every((key2) => key2 === "hooks") && Object.keys(read3.value.hooks ?? {}).every(
|
|
7545
8659
|
(key2) => key2 === "SessionStart"
|
|
7546
8660
|
) && entries.every(
|
|
7547
|
-
(group) => (group.hooks ?? []).every((entry) =>
|
|
8661
|
+
(group) => (group.hooks ?? []).every((entry) => isOurs4(entry))
|
|
7548
8662
|
);
|
|
7549
8663
|
if (!onlyOurs) {
|
|
7550
8664
|
return {
|
|
@@ -7556,89 +8670,13 @@ function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
|
|
|
7556
8670
|
return { ok: true, message: `Grok Build hook removed from ${file}` };
|
|
7557
8671
|
}
|
|
7558
8672
|
function grokAutoSyncHookInstalled(file = GROK_HOOK_FILE) {
|
|
7559
|
-
const
|
|
7560
|
-
if ("error" in
|
|
7561
|
-
return (
|
|
7562
|
-
(group) => (group.hooks ?? []).some((entry) =>
|
|
8673
|
+
const read3 = readHookFile(file);
|
|
8674
|
+
if ("error" in read3) return false;
|
|
8675
|
+
return (read3.value.hooks?.SessionStart ?? []).some(
|
|
8676
|
+
(group) => (group.hooks ?? []).some((entry) => isOurs4(entry))
|
|
7563
8677
|
);
|
|
7564
8678
|
}
|
|
7565
8679
|
|
|
7566
|
-
// src/autosync/hook.ts
|
|
7567
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7568
|
-
import { homedir as homedir14 } from "node:os";
|
|
7569
|
-
import { dirname as dirname7, join as join10 } from "node:path";
|
|
7570
|
-
var CLAUDE_SETTINGS_FILE = join10(homedir14(), ".claude", "settings.json");
|
|
7571
|
-
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
7572
|
-
function isOurs3(entry) {
|
|
7573
|
-
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
7574
|
-
}
|
|
7575
|
-
function readClaudeSettings(file) {
|
|
7576
|
-
if (!existsSync10(file)) return { settings: {} };
|
|
7577
|
-
try {
|
|
7578
|
-
const raw = JSON.parse(readFileSync11(file, "utf-8"));
|
|
7579
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7580
|
-
return { settings: raw };
|
|
7581
|
-
}
|
|
7582
|
-
return { error: `${file} does not hold a JSON object` };
|
|
7583
|
-
} catch {
|
|
7584
|
-
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
7585
|
-
}
|
|
7586
|
-
}
|
|
7587
|
-
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
7588
|
-
const read2 = readClaudeSettings(file);
|
|
7589
|
-
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7590
|
-
const settings = read2.settings;
|
|
7591
|
-
const hooks = settings.hooks ?? {};
|
|
7592
|
-
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
7593
|
-
const kept = sessionStart.map((m) => ({
|
|
7594
|
-
...m,
|
|
7595
|
-
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
7596
|
-
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
7597
|
-
kept.push({
|
|
7598
|
-
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
7599
|
-
});
|
|
7600
|
-
settings.hooks = { ...hooks, SessionStart: kept };
|
|
7601
|
-
mkdirSync5(dirname7(file), { recursive: true });
|
|
7602
|
-
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
7603
|
-
`);
|
|
7604
|
-
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
7605
|
-
}
|
|
7606
|
-
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
7607
|
-
if (!existsSync10(file)) return { ok: true, message: "no hook to remove" };
|
|
7608
|
-
const read2 = readClaudeSettings(file);
|
|
7609
|
-
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7610
|
-
const settings = read2.settings;
|
|
7611
|
-
const sessionStart = settings.hooks?.SessionStart;
|
|
7612
|
-
if (!Array.isArray(sessionStart)) {
|
|
7613
|
-
return { ok: true, message: "no hook to remove" };
|
|
7614
|
-
}
|
|
7615
|
-
const kept = sessionStart.map((m) => ({
|
|
7616
|
-
...m,
|
|
7617
|
-
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
7618
|
-
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
7619
|
-
const hooks = { ...settings.hooks };
|
|
7620
|
-
if (kept.length > 0) {
|
|
7621
|
-
hooks.SessionStart = kept;
|
|
7622
|
-
} else {
|
|
7623
|
-
delete hooks.SessionStart;
|
|
7624
|
-
}
|
|
7625
|
-
if (Object.keys(hooks).length > 0) {
|
|
7626
|
-
settings.hooks = hooks;
|
|
7627
|
-
} else {
|
|
7628
|
-
delete settings.hooks;
|
|
7629
|
-
}
|
|
7630
|
-
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
7631
|
-
`);
|
|
7632
|
-
return { ok: true, message: `hook removed from ${file}` };
|
|
7633
|
-
}
|
|
7634
|
-
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
7635
|
-
const read2 = readClaudeSettings(file);
|
|
7636
|
-
if ("error" in read2) return false;
|
|
7637
|
-
const sessionStart = read2.settings.hooks?.SessionStart;
|
|
7638
|
-
if (!Array.isArray(sessionStart)) return false;
|
|
7639
|
-
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs3(h)));
|
|
7640
|
-
}
|
|
7641
|
-
|
|
7642
8680
|
// src/autosync/optin.ts
|
|
7643
8681
|
var NOT_LINKED = "This machine is not linked to an aistack account, and the auto-sync permission lives on your stack. Run `npx @use-aistack/cli sync` first.";
|
|
7644
8682
|
var NOTHING_TO_TRIGGER = `No supported session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
|
|
@@ -7678,6 +8716,10 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
7678
8716
|
const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
|
|
7679
8717
|
if (!grokResult.ok) return grokResult;
|
|
7680
8718
|
}
|
|
8719
|
+
if (names.has(CURSOR_HARNESS_NAME)) {
|
|
8720
|
+
const result = (deps.installCursorHook ?? installCursorAutoSyncHook)();
|
|
8721
|
+
if (!result.ok) return result;
|
|
8722
|
+
}
|
|
7681
8723
|
saveSettings(
|
|
7682
8724
|
{
|
|
7683
8725
|
autoSyncAnswered: true,
|
|
@@ -7688,7 +8730,7 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
7688
8730
|
return {
|
|
7689
8731
|
ok: true,
|
|
7690
8732
|
message: [
|
|
7691
|
-
`Auto-sync is on. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
|
|
8733
|
+
`Auto-sync is on. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} ${names.has(CURSOR_HARNESS_NAME) ? "session is active" : "session starts"}. Turn it off any time: npx @use-aistack/cli sync --auto off`,
|
|
7692
8734
|
...trustLine ? [trustLine] : []
|
|
7693
8735
|
].join("\n")
|
|
7694
8736
|
};
|
|
@@ -7710,7 +8752,8 @@ async function disableAutoSync(deps = {}) {
|
|
|
7710
8752
|
const result = (deps.removeHook ?? removeAutoSyncHook)();
|
|
7711
8753
|
const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
|
|
7712
8754
|
const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
|
|
7713
|
-
const
|
|
8755
|
+
const cursorResult = (deps.removeCursorHook ?? removeCursorAutoSyncHook)();
|
|
8756
|
+
const failures = [result, codexResult, grokResult, cursorResult].filter((r) => !r.ok).map((r) => r.message);
|
|
7714
8757
|
const token = (deps.getTokenImpl ?? getToken)();
|
|
7715
8758
|
if (token !== null) {
|
|
7716
8759
|
try {
|
|
@@ -7751,7 +8794,8 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7751
8794
|
const results = [
|
|
7752
8795
|
(deps.removeHook ?? removeAutoSyncHook)(),
|
|
7753
8796
|
(deps.removeCodexHook ?? removeCodexAutoSyncHook)(),
|
|
7754
|
-
(deps.removeGrokHook ?? removeGrokAutoSyncHook)()
|
|
8797
|
+
(deps.removeGrokHook ?? removeGrokAutoSyncHook)(),
|
|
8798
|
+
(deps.removeCursorHook ?? removeCursorAutoSyncHook)()
|
|
7755
8799
|
];
|
|
7756
8800
|
const failures2 = results.filter((result) => !result.ok);
|
|
7757
8801
|
if (failures2.length > 0) {
|
|
@@ -7793,6 +8837,11 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7793
8837
|
deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
|
|
7794
8838
|
deps.installGrokHook ?? installGrokAutoSyncHook
|
|
7795
8839
|
);
|
|
8840
|
+
install(
|
|
8841
|
+
CURSOR_HARNESS_NAME,
|
|
8842
|
+
deps.cursorHookInstalledImpl ?? cursorAutoSyncHookInstalled,
|
|
8843
|
+
deps.installCursorHook ?? installCursorAutoSyncHook
|
|
8844
|
+
);
|
|
7796
8845
|
if (failures.length > 0) {
|
|
7797
8846
|
return {
|
|
7798
8847
|
ok: false,
|
|
@@ -7811,7 +8860,7 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7811
8860
|
if (installed.length === 0 && mirrored) return null;
|
|
7812
8861
|
return {
|
|
7813
8862
|
ok: true,
|
|
7814
|
-
message: installed.length > 0 ? `Auto-sync is on for this stack. Installed the ${installed.join(" and ")} trigger on this machine; it runs about every ${frequencyHours}h
|
|
8863
|
+
message: installed.length > 0 ? `Auto-sync is on for this stack. Installed the ${installed.join(" and ")} trigger on this machine; it runs about every ${frequencyHours}h during session activity.` : `Auto-sync is on for this stack. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} ${names.has(CURSOR_HARNESS_NAME) ? "session is active" : "session starts"}.`
|
|
7815
8864
|
};
|
|
7816
8865
|
}
|
|
7817
8866
|
async function settleAutoSync(permission, deps = {}) {
|
|
@@ -7829,13 +8878,14 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7829
8878
|
return false;
|
|
7830
8879
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
7831
8880
|
if (detected.length === 0) return false;
|
|
8881
|
+
const names = new Set(detected.map((adapter) => adapter.name));
|
|
7832
8882
|
const answer = await p6.select({
|
|
7833
8883
|
message: "Keep this stack fresh automatically every 6 hours?",
|
|
7834
8884
|
options: [
|
|
7835
8885
|
{
|
|
7836
8886
|
value: "enable",
|
|
7837
8887
|
label: "Enable",
|
|
7838
|
-
hint: `a silent sync at most every 6 hours when a ${harnessListLabel(detected)} session starts`
|
|
8888
|
+
hint: `a silent sync at most every 6 hours when a ${harnessListLabel(detected)} ${names.has(CURSOR_HARNESS_NAME) ? "session is active" : "session starts"}`
|
|
7839
8889
|
},
|
|
7840
8890
|
{
|
|
7841
8891
|
value: "later",
|
|
@@ -7878,17 +8928,17 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7878
8928
|
import {
|
|
7879
8929
|
appendFileSync,
|
|
7880
8930
|
closeSync,
|
|
7881
|
-
mkdirSync as
|
|
8931
|
+
mkdirSync as mkdirSync8,
|
|
7882
8932
|
openSync,
|
|
7883
|
-
readFileSync as
|
|
8933
|
+
readFileSync as readFileSync14,
|
|
7884
8934
|
unlinkSync as unlinkSync2,
|
|
7885
|
-
writeFileSync as
|
|
8935
|
+
writeFileSync as writeFileSync8
|
|
7886
8936
|
} from "node:fs";
|
|
7887
|
-
import { homedir as
|
|
7888
|
-
import { dirname as
|
|
8937
|
+
import { homedir as homedir20 } from "node:os";
|
|
8938
|
+
import { dirname as dirname9, join as join12 } from "node:path";
|
|
7889
8939
|
|
|
7890
8940
|
// src/sync/stage.ts
|
|
7891
|
-
import { createHash as
|
|
8941
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
7892
8942
|
|
|
7893
8943
|
// src/usage/days.ts
|
|
7894
8944
|
var round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
@@ -8003,11 +9053,11 @@ function buildMeasuredDays(input) {
|
|
|
8003
9053
|
|
|
8004
9054
|
// src/usage/diff.ts
|
|
8005
9055
|
var MAX_DAY_WINDOW = 400;
|
|
8006
|
-
var
|
|
9056
|
+
var DAY_MS2 = 864e5;
|
|
8007
9057
|
function retentionFloor(todayUtc, days) {
|
|
8008
9058
|
const span = Math.max(1, Math.min(days, MAX_DAY_WINDOW));
|
|
8009
9059
|
const todayMs = Date.parse(`${todayUtc}T00:00:00.000Z`);
|
|
8010
|
-
return new Date(todayMs - (span - 1) *
|
|
9060
|
+
return new Date(todayMs - (span - 1) * DAY_MS2).toISOString().slice(0, 10);
|
|
8011
9061
|
}
|
|
8012
9062
|
function selectDaysToPublish(input) {
|
|
8013
9063
|
const { local, manifest, todayUtc } = input;
|
|
@@ -8044,7 +9094,7 @@ function selectDaysToPublish(input) {
|
|
|
8044
9094
|
|
|
8045
9095
|
// src/workflow/git.ts
|
|
8046
9096
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
8047
|
-
import
|
|
9097
|
+
import path12 from "node:path";
|
|
8048
9098
|
var TEST_FILE_RULE_VERSION = "test-files/v2";
|
|
8049
9099
|
var FILE_TYPE_RULE_VERSION = "file-types/v2";
|
|
8050
9100
|
var COMMIT_SET_RULE_VERSION = "commit-set/v1";
|
|
@@ -8308,10 +9358,10 @@ function reduceGitHistories(histories, options) {
|
|
|
8308
9358
|
const field = fields[fieldIndex] ?? "";
|
|
8309
9359
|
if (field.replace(/^\n+/, "") === COMMIT_MARKER) {
|
|
8310
9360
|
finishCommit();
|
|
8311
|
-
const
|
|
9361
|
+
const hash2 = fields[++fieldIndex] ?? "";
|
|
8312
9362
|
const authoredAt = fields[++fieldIndex] ?? "";
|
|
8313
9363
|
const authoredMs = Date.parse(authoredAt);
|
|
8314
|
-
const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(
|
|
9364
|
+
const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(hash2);
|
|
8315
9365
|
current = {
|
|
8316
9366
|
included,
|
|
8317
9367
|
date: included ? new Date(authoredMs).toISOString().slice(0, 10) : "",
|
|
@@ -8324,12 +9374,12 @@ function reduceGitHistories(histories, options) {
|
|
|
8324
9374
|
withheldLines: 0,
|
|
8325
9375
|
extensionLines: /* @__PURE__ */ new Map()
|
|
8326
9376
|
};
|
|
8327
|
-
if (included) seenCommits.add(
|
|
9377
|
+
if (included) seenCommits.add(hash2);
|
|
8328
9378
|
continue;
|
|
8329
9379
|
}
|
|
8330
|
-
const
|
|
8331
|
-
if (!
|
|
8332
|
-
let file =
|
|
9380
|
+
const stat8 = parseNumstat(field);
|
|
9381
|
+
if (!stat8) continue;
|
|
9382
|
+
let file = stat8.file;
|
|
8333
9383
|
if (file.length === 0) {
|
|
8334
9384
|
fieldIndex += 2;
|
|
8335
9385
|
file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
|
|
@@ -8337,13 +9387,13 @@ function reduceGitHistories(histories, options) {
|
|
|
8337
9387
|
if (!current?.included) continue;
|
|
8338
9388
|
if (isUnauthoredPath(file)) continue;
|
|
8339
9389
|
current.authored = true;
|
|
8340
|
-
const fileChangedLines =
|
|
8341
|
-
current.additions +=
|
|
8342
|
-
current.removals +=
|
|
9390
|
+
const fileChangedLines = stat8.additions + stat8.removals;
|
|
9391
|
+
current.additions += stat8.additions;
|
|
9392
|
+
current.removals += stat8.removals;
|
|
8343
9393
|
current.changedLines += fileChangedLines;
|
|
8344
9394
|
if (isTestFile(file)) current.touchesTest = true;
|
|
8345
9395
|
if (fileChangedLines <= 0) continue;
|
|
8346
|
-
const extension =
|
|
9396
|
+
const extension = path12.extname(file).toLowerCase();
|
|
8347
9397
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
8348
9398
|
current.extensionLines.set(
|
|
8349
9399
|
extension,
|
|
@@ -8447,36 +9497,36 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
|
|
|
8447
9497
|
}
|
|
8448
9498
|
|
|
8449
9499
|
// src/sync/grokDateCache.ts
|
|
8450
|
-
import { createHash as
|
|
8451
|
-
import { existsSync as
|
|
8452
|
-
import { homedir as
|
|
8453
|
-
import
|
|
8454
|
-
var defaultFile =
|
|
8455
|
-
|
|
9500
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9501
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9502
|
+
import { homedir as homedir19 } from "node:os";
|
|
9503
|
+
import path13 from "node:path";
|
|
9504
|
+
var defaultFile = path13.join(
|
|
9505
|
+
homedir19(),
|
|
8456
9506
|
".config",
|
|
8457
9507
|
"aistack",
|
|
8458
9508
|
"grok-session-dates.json"
|
|
8459
9509
|
);
|
|
8460
9510
|
function grokCacheScope(baseUrl, stack, token) {
|
|
8461
|
-
return
|
|
9511
|
+
return createHash4("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
|
|
8462
9512
|
}
|
|
8463
|
-
function
|
|
8464
|
-
if (!
|
|
9513
|
+
function read2(file) {
|
|
9514
|
+
if (!existsSync12(file)) return {};
|
|
8465
9515
|
try {
|
|
8466
|
-
const value = JSON.parse(
|
|
9516
|
+
const value = JSON.parse(readFileSync13(file, "utf8"));
|
|
8467
9517
|
return value && typeof value === "object" ? value : {};
|
|
8468
9518
|
} catch {
|
|
8469
9519
|
return {};
|
|
8470
9520
|
}
|
|
8471
9521
|
}
|
|
8472
9522
|
function loadGrokDateHints(scope, file = defaultFile) {
|
|
8473
|
-
return
|
|
9523
|
+
return read2(file)[scope] ?? {};
|
|
8474
9524
|
}
|
|
8475
9525
|
function saveGrokDateHints(scope, hints, file = defaultFile) {
|
|
8476
|
-
const cache =
|
|
9526
|
+
const cache = read2(file);
|
|
8477
9527
|
cache[scope] = hints;
|
|
8478
|
-
|
|
8479
|
-
|
|
9528
|
+
mkdirSync7(path13.dirname(file), { recursive: true });
|
|
9529
|
+
writeFileSync7(file, JSON.stringify(cache, null, 2));
|
|
8480
9530
|
}
|
|
8481
9531
|
function mapToHints(value, floor) {
|
|
8482
9532
|
return Object.fromEntries(
|
|
@@ -8887,7 +9937,7 @@ function buildGateSummary(ctx) {
|
|
|
8887
9937
|
// src/sync/stage.ts
|
|
8888
9938
|
var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
8889
9939
|
function stageId(bodyJson) {
|
|
8890
|
-
return
|
|
9940
|
+
return createHash5("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
8891
9941
|
}
|
|
8892
9942
|
async function stageSync(deps) {
|
|
8893
9943
|
const now = (deps.now ?? Date.now)();
|
|
@@ -8941,11 +9991,12 @@ async function stageSync(deps) {
|
|
|
8941
9991
|
const active2 = await adapters(sinceMs);
|
|
8942
9992
|
const historical = await adapters(daysSinceMs);
|
|
8943
9993
|
let dayScansComplete = true;
|
|
8944
|
-
|
|
9994
|
+
const sessionDatesByHarness = /* @__PURE__ */ new Map();
|
|
8945
9995
|
for (const adapter of active2) {
|
|
8946
9996
|
progress(`Scanning recent ${adapter.name} usage`);
|
|
8947
9997
|
const { aggregate, stats } = await adapter.scan({
|
|
8948
9998
|
sinceMs,
|
|
9999
|
+
publishWorkflow: false,
|
|
8949
10000
|
onProgress: (files) => progress(`Scanning recent ${adapter.name} usage \xB7 ${files} files`)
|
|
8950
10001
|
});
|
|
8951
10002
|
scanStats[adapter.name] = stats;
|
|
@@ -8966,11 +10017,12 @@ async function stageSync(deps) {
|
|
|
8966
10017
|
progress(`Reading historical ${adapter.name} days`);
|
|
8967
10018
|
const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
|
|
8968
10019
|
sinceMs: daysSinceMs,
|
|
10020
|
+
publishWorkflow: config.publishWorkflow,
|
|
8969
10021
|
onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
|
|
8970
10022
|
});
|
|
8971
10023
|
if (scanComplete === false) dayScansComplete = false;
|
|
8972
|
-
if (adapter.name === "grok-build")
|
|
8973
|
-
|
|
10024
|
+
if (adapter.name === "grok-build" || adapter.name === "cursor")
|
|
10025
|
+
sessionDatesByHarness.set(adapter.name, sessionDates ?? /* @__PURE__ */ new Map());
|
|
8974
10026
|
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
8975
10027
|
usageScans.push(
|
|
8976
10028
|
buildUsageDays({
|
|
@@ -8998,19 +10050,29 @@ async function stageSync(deps) {
|
|
|
8998
10050
|
}
|
|
8999
10051
|
const correctionDates = /* @__PURE__ */ new Set();
|
|
9000
10052
|
let acknowledgePublish;
|
|
9001
|
-
|
|
9002
|
-
|
|
10053
|
+
const acknowledgements = [];
|
|
10054
|
+
for (const [harness, currentDates] of sessionDatesByHarness) {
|
|
10055
|
+
if (!token || !config.stack) continue;
|
|
10056
|
+
const scope = grokCacheScope(
|
|
10057
|
+
harness === "grok-build" ? deps.baseUrl : `${deps.baseUrl}\0${harness}`,
|
|
10058
|
+
config.stack.slug,
|
|
10059
|
+
token
|
|
10060
|
+
);
|
|
9003
10061
|
const floor = utcDate2(daysSinceMs);
|
|
9004
10062
|
const previous = loadGrokDateHints(scope);
|
|
9005
|
-
const current = mapToHints(
|
|
10063
|
+
const current = mapToHints(currentDates, floor);
|
|
9006
10064
|
for (const dates of Object.values(previous))
|
|
9007
10065
|
for (const date of dates) correctionDates.add(date);
|
|
9008
10066
|
for (const dates of Object.values(current))
|
|
9009
10067
|
for (const date of dates) correctionDates.add(date);
|
|
9010
10068
|
if (Object.keys(previous).some((id) => !(id in current)))
|
|
9011
10069
|
dayScansComplete = false;
|
|
9012
|
-
|
|
10070
|
+
acknowledgements.push(() => saveGrokDateHints(scope, current));
|
|
9013
10071
|
}
|
|
10072
|
+
if (acknowledgements.length)
|
|
10073
|
+
acknowledgePublish = () => {
|
|
10074
|
+
for (const acknowledge of acknowledgements) acknowledge();
|
|
10075
|
+
};
|
|
9014
10076
|
const localDays = applyDayConsent(
|
|
9015
10077
|
buildMeasuredDays({
|
|
9016
10078
|
usage: mergeUsageDays(usageScans),
|
|
@@ -9080,31 +10142,31 @@ async function stageSync(deps) {
|
|
|
9080
10142
|
}
|
|
9081
10143
|
|
|
9082
10144
|
// src/autosync/run.ts
|
|
9083
|
-
var SYNC_LOG_FILE =
|
|
10145
|
+
var SYNC_LOG_FILE = join12(homedir20(), ".config", "aistack", "sync.log");
|
|
9084
10146
|
var SYNC_LOG_MAX_LINES = 200;
|
|
9085
10147
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
9086
10148
|
var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
|
|
9087
10149
|
function appendLogLine(file, line) {
|
|
9088
|
-
|
|
10150
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
9089
10151
|
appendFileSync(file, `${line}
|
|
9090
10152
|
`);
|
|
9091
|
-
const lines2 =
|
|
10153
|
+
const lines2 = readFileSync14(file, "utf-8").split("\n").filter(Boolean);
|
|
9092
10154
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
9093
|
-
|
|
10155
|
+
writeFileSync8(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
|
|
9094
10156
|
`);
|
|
9095
10157
|
}
|
|
9096
10158
|
}
|
|
9097
10159
|
function reserveAttempt(file, now, windowMs) {
|
|
9098
|
-
|
|
10160
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
9099
10161
|
for (; ; ) {
|
|
9100
10162
|
try {
|
|
9101
10163
|
const fd = openSync(file, "wx");
|
|
9102
|
-
|
|
10164
|
+
writeFileSync8(fd, String(now));
|
|
9103
10165
|
closeSync(fd);
|
|
9104
10166
|
return true;
|
|
9105
10167
|
} catch (error) {
|
|
9106
10168
|
if (error.code !== "EEXIST") throw error;
|
|
9107
|
-
const held = Number(
|
|
10169
|
+
const held = Number(readFileSync14(file, "utf-8"));
|
|
9108
10170
|
if (Number.isFinite(held) && now - held < windowMs) return false;
|
|
9109
10171
|
try {
|
|
9110
10172
|
unlinkSync2(file);
|
|
@@ -9133,7 +10195,7 @@ async function runAutoSync(deps) {
|
|
|
9133
10195
|
const state = settings.autoSyncState ?? {};
|
|
9134
10196
|
const lastRunAt = state.lastRunAt ?? 0;
|
|
9135
10197
|
if (now - lastRunAt < windowMs) return;
|
|
9136
|
-
const reservationFile = deps.reservationFile ?? `${settingsFile ??
|
|
10198
|
+
const reservationFile = deps.reservationFile ?? `${settingsFile ?? join12(homedir20(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
|
|
9137
10199
|
if (!reserveAttempt(reservationFile, now, windowMs)) return;
|
|
9138
10200
|
saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
|
|
9139
10201
|
const stage = deps.stageImpl ?? stageSync;
|
|
@@ -9226,7 +10288,7 @@ async function runAutoSync(deps) {
|
|
|
9226
10288
|
logFile,
|
|
9227
10289
|
`${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
|
|
9228
10290
|
);
|
|
9229
|
-
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok") {
|
|
10291
|
+
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok" && process.env.AISTACK_HOOK_SOURCE !== "cursor") {
|
|
9230
10292
|
emit(
|
|
9231
10293
|
JSON.stringify({
|
|
9232
10294
|
systemMessage: `aistack auto-sync failed ${consecutiveFailures} times in a row (${failure2}). Run \`${FIX_COMMAND}\` in a terminal to fix it, or \`${FIX_COMMAND} --auto off\` to stop these runs.`
|
|
@@ -9720,7 +10782,7 @@ program.command("mcp").description(
|
|
|
9720
10782
|
});
|
|
9721
10783
|
program.command("sync").description("Scan, preview, and publish measured usage (rolling 30 days)").option(
|
|
9722
10784
|
"--auto [state]",
|
|
9723
|
-
"silent background sync; 'on' asks your stack for the permission and installs
|
|
10785
|
+
"silent background sync; 'on' asks your stack for the permission and installs harness hooks, 'off' revokes both"
|
|
9724
10786
|
).option(
|
|
9725
10787
|
"--every <hours>",
|
|
9726
10788
|
"with --auto on: hours between auto-syncs (default 6)"
|