@use-aistack/cli 0.13.0 → 0.15.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 +2 -2
- package/dist/index.js +2005 -238
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,11 @@ var LOCAL_PRICING_TABLE_VERSION = "local-no-charge";
|
|
|
9
9
|
var PROVIDER_VENDOR = {
|
|
10
10
|
anthropic: "anthropic",
|
|
11
11
|
openai: "openai",
|
|
12
|
-
google: "google"
|
|
12
|
+
google: "google",
|
|
13
|
+
xai: "xai"
|
|
14
|
+
};
|
|
15
|
+
var PRICING_ALIASES = {
|
|
16
|
+
"grok-4.6-build": "grok-4.6"
|
|
13
17
|
};
|
|
14
18
|
var LOCAL_PROVIDERS = /* @__PURE__ */ new Set([
|
|
15
19
|
"ollama",
|
|
@@ -105,6 +109,11 @@ var Pricer = class {
|
|
|
105
109
|
firstLayerWith(slug, provider) {
|
|
106
110
|
return this.layers.find((l) => l.has(slug, provider)) ?? null;
|
|
107
111
|
}
|
|
112
|
+
lookupSlug(slug, provider) {
|
|
113
|
+
if (this.firstLayerWith(slug, provider)) return slug;
|
|
114
|
+
const alias = PRICING_ALIASES[slug];
|
|
115
|
+
return alias && this.firstLayerWith(alias, provider) ? alias : null;
|
|
116
|
+
}
|
|
108
117
|
/**
|
|
109
118
|
* Every period that applies to a pricing key, or an empty list when none
|
|
110
119
|
* can be cited.
|
|
@@ -117,14 +126,19 @@ var Pricer = class {
|
|
|
117
126
|
periodsFor(modelKey) {
|
|
118
127
|
const { provider, model } = splitModelKey(modelKey);
|
|
119
128
|
if (provider === null) {
|
|
120
|
-
|
|
129
|
+
const slug = this.lookupSlug(model, null);
|
|
130
|
+
return slug ? this.firstLayerWith(slug, null)?.rowsFor(slug, null) ?? [] : [];
|
|
121
131
|
}
|
|
122
132
|
if (LOCAL_PROVIDERS.has(provider)) return [FREE_PERIOD];
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
133
|
+
const ownSlug = this.lookupSlug(model, provider);
|
|
134
|
+
if (ownSlug) {
|
|
135
|
+
return this.firstLayerWith(ownSlug, provider)?.rowsFor(ownSlug, provider) ?? [];
|
|
136
|
+
}
|
|
125
137
|
const vendor = PROVIDER_VENDOR[provider];
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
const vendorSlug = this.lookupSlug(model, null);
|
|
139
|
+
if (!vendor || !vendorSlug || this.vendorOf(vendorSlug) !== vendor)
|
|
140
|
+
return [];
|
|
141
|
+
return this.firstLayerWith(vendorSlug, null)?.rowsFor(vendorSlug, null) ?? [];
|
|
128
142
|
}
|
|
129
143
|
isLocal(modelKey) {
|
|
130
144
|
const { provider } = splitModelKey(modelKey);
|
|
@@ -176,7 +190,7 @@ function parsePriceTable(body) {
|
|
|
176
190
|
if (typeof r?.modelSlug !== "string" || r.modelSlug.length === 0 || !num(r.from) || !num(r.input) || !num(r.output) || typeof r.source !== "string") {
|
|
177
191
|
continue;
|
|
178
192
|
}
|
|
179
|
-
const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "local" ? r.vendor : void 0;
|
|
193
|
+
const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "xai" || r.vendor === "local" ? r.vendor : void 0;
|
|
180
194
|
rows.push({
|
|
181
195
|
modelSlug: r.modelSlug,
|
|
182
196
|
...typeof r.provider === "string" && r.provider.length > 0 ? { provider: r.provider } : {},
|
|
@@ -198,7 +212,8 @@ function parsePriceTable(body) {
|
|
|
198
212
|
var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
|
|
199
213
|
var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-02";
|
|
200
214
|
var GOOGLE_PRICING_TABLE_VERSION = "google-list-2026-08-09";
|
|
201
|
-
var
|
|
215
|
+
var XAI_PRICING_TABLE_VERSION = "models.dev@2026-08-29";
|
|
216
|
+
var BUNDLED_PRICE_TABLE_ID = "bundled-2026-09-10";
|
|
202
217
|
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
203
218
|
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
204
219
|
var CACHE_READ_MULTIPLIER = 0.1;
|
|
@@ -213,6 +228,11 @@ var GOOGLE_CACHE_MULTIPLIERS = {
|
|
|
213
228
|
write1h: 1,
|
|
214
229
|
read: 0.1
|
|
215
230
|
};
|
|
231
|
+
var XAI_CACHE_MULTIPLIERS = {
|
|
232
|
+
write5m: 0,
|
|
233
|
+
write1h: 0,
|
|
234
|
+
read: 0.25
|
|
235
|
+
};
|
|
216
236
|
var anthropic = (periods) => ({
|
|
217
237
|
vendor: "anthropic",
|
|
218
238
|
table: PRICING_TABLE_VERSION,
|
|
@@ -231,6 +251,12 @@ var google = (periods) => ({
|
|
|
231
251
|
periods,
|
|
232
252
|
cache: GOOGLE_CACHE_MULTIPLIERS
|
|
233
253
|
});
|
|
254
|
+
var xai = (periods) => ({
|
|
255
|
+
vendor: "xai",
|
|
256
|
+
table: XAI_PRICING_TABLE_VERSION,
|
|
257
|
+
periods,
|
|
258
|
+
cache: XAI_CACHE_MULTIPLIERS
|
|
259
|
+
});
|
|
234
260
|
var flat = (input, output) => [
|
|
235
261
|
{ from: null, input, output }
|
|
236
262
|
];
|
|
@@ -288,7 +314,10 @@ var PRICES = {
|
|
|
288
314
|
"gemini-3-pro-preview": google(flat(2, 12)),
|
|
289
315
|
// A real Anthropic model with no row until #123. Measured in #122 as
|
|
290
316
|
// `claude-opus-4-5-20251101`, which the dated-suffix rule strips to this key.
|
|
291
|
-
"claude-opus-4-5": anthropic(flat(5, 25))
|
|
317
|
+
"claude-opus-4-5": anthropic(flat(5, 25)),
|
|
318
|
+
// models.dev's xAI row mirrored into the live table on 2026-08-29. Grok
|
|
319
|
+
// Build's explicit `grok-4.6-build` pricing alias reaches this base rate.
|
|
320
|
+
"grok-4.6": xai(flat(2, 6))
|
|
292
321
|
};
|
|
293
322
|
function bundledPriceTable() {
|
|
294
323
|
const rows = [];
|
|
@@ -348,12 +377,12 @@ function apiEquivalentCost(modelKey, t, atMs) {
|
|
|
348
377
|
}
|
|
349
378
|
|
|
350
379
|
// src/version.ts
|
|
351
|
-
var CLI_VERSION = true ? "0.
|
|
380
|
+
var CLI_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
352
381
|
|
|
353
382
|
// src/api.ts
|
|
354
383
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
355
|
-
async function request(
|
|
356
|
-
return fetch(`${BASE_URL}${
|
|
384
|
+
async function request(path13, options = {}) {
|
|
385
|
+
return fetch(`${BASE_URL}${path13}`, {
|
|
357
386
|
...options,
|
|
358
387
|
headers: {
|
|
359
388
|
"Content-Type": "application/json",
|
|
@@ -771,9 +800,9 @@ function canonicalizeRepoUrl(input) {
|
|
|
771
800
|
function repoNameFromCanonical(canonical) {
|
|
772
801
|
return parseRepo(canonical)?.repo ?? "";
|
|
773
802
|
}
|
|
774
|
-
function normalizeUpstreamPath(
|
|
775
|
-
if (!
|
|
776
|
-
return
|
|
803
|
+
function normalizeUpstreamPath(path13) {
|
|
804
|
+
if (!path13) return "";
|
|
805
|
+
return path13.split("/").filter(Boolean).join("/");
|
|
777
806
|
}
|
|
778
807
|
|
|
779
808
|
// src/git.ts
|
|
@@ -819,16 +848,16 @@ function buildRepoLinkResource(canonical) {
|
|
|
819
848
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
820
849
|
import { homedir as homedir2 } from "node:os";
|
|
821
850
|
import { join as join2 } from "node:path";
|
|
822
|
-
function readJson(
|
|
851
|
+
function readJson(path13) {
|
|
823
852
|
try {
|
|
824
|
-
if (!existsSync2(
|
|
825
|
-
return JSON.parse(readFileSync2(
|
|
853
|
+
if (!existsSync2(path13)) return null;
|
|
854
|
+
return JSON.parse(readFileSync2(path13, "utf-8"));
|
|
826
855
|
} catch {
|
|
827
856
|
return null;
|
|
828
857
|
}
|
|
829
858
|
}
|
|
830
|
-
function hooksFrom(
|
|
831
|
-
const hooks = readJson(
|
|
859
|
+
function hooksFrom(path13, source, out, seen) {
|
|
860
|
+
const hooks = readJson(path13)?.hooks;
|
|
832
861
|
if (!hooks || typeof hooks !== "object") return;
|
|
833
862
|
for (const [event, config] of Object.entries(hooks)) {
|
|
834
863
|
const stableKey = `hooks:${source}:${event}`;
|
|
@@ -917,10 +946,19 @@ function splitImageTag(image) {
|
|
|
917
946
|
}
|
|
918
947
|
function parseMcpPackage(server) {
|
|
919
948
|
if (server.url) {
|
|
949
|
+
let safeUrl;
|
|
950
|
+
try {
|
|
951
|
+
const parsed = new URL(server.url);
|
|
952
|
+
parsed.username = "";
|
|
953
|
+
parsed.password = "";
|
|
954
|
+
safeUrl = parsed.toString();
|
|
955
|
+
} catch {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
920
958
|
const t = (server.type ?? server.transport ?? "").toLowerCase();
|
|
921
959
|
return {
|
|
922
960
|
registry: "url",
|
|
923
|
-
id:
|
|
961
|
+
id: safeUrl,
|
|
924
962
|
transport: t === "sse" ? "sse" : "http"
|
|
925
963
|
};
|
|
926
964
|
}
|
|
@@ -968,16 +1006,16 @@ function buildMcpResource(name, group, pkg) {
|
|
|
968
1006
|
pkg
|
|
969
1007
|
};
|
|
970
1008
|
}
|
|
971
|
-
function readText(
|
|
1009
|
+
function readText(path13) {
|
|
972
1010
|
try {
|
|
973
|
-
if (!existsSync3(
|
|
974
|
-
return readFileSync3(
|
|
1011
|
+
if (!existsSync3(path13)) return null;
|
|
1012
|
+
return readFileSync3(path13, "utf-8");
|
|
975
1013
|
} catch {
|
|
976
1014
|
return null;
|
|
977
1015
|
}
|
|
978
1016
|
}
|
|
979
|
-
function readParsed(
|
|
980
|
-
const raw = readText(
|
|
1017
|
+
function readParsed(path13, parse2) {
|
|
1018
|
+
const raw = readText(path13);
|
|
981
1019
|
if (raw === null) return null;
|
|
982
1020
|
try {
|
|
983
1021
|
return parse2(raw);
|
|
@@ -985,14 +1023,14 @@ function readParsed(path7, parse2) {
|
|
|
985
1023
|
return null;
|
|
986
1024
|
}
|
|
987
1025
|
}
|
|
988
|
-
function readJson2(
|
|
989
|
-
return readParsed(
|
|
1026
|
+
function readJson2(path13) {
|
|
1027
|
+
return readParsed(path13, JSON.parse);
|
|
990
1028
|
}
|
|
991
|
-
function readYaml(
|
|
992
|
-
return readParsed(
|
|
1029
|
+
function readYaml(path13) {
|
|
1030
|
+
return readParsed(path13, parseYaml);
|
|
993
1031
|
}
|
|
994
|
-
function readToml(
|
|
995
|
-
return readParsed(
|
|
1032
|
+
function readToml(path13) {
|
|
1033
|
+
return readParsed(path13, parseToml);
|
|
996
1034
|
}
|
|
997
1035
|
function continueListToMap(file) {
|
|
998
1036
|
if (!file?.mcpServers?.length) return void 0;
|
|
@@ -1027,6 +1065,25 @@ function detectMcpServers(cwd, home = homedir3()) {
|
|
|
1027
1065
|
out.push(resource);
|
|
1028
1066
|
}
|
|
1029
1067
|
};
|
|
1068
|
+
const grokHome = process.env.GROK_HOME ?? join3(home, ".grok");
|
|
1069
|
+
const grokGlobal = readToml(join3(grokHome, "config.toml"));
|
|
1070
|
+
const grokProject = readToml(join3(cwd, ".grok", "config.toml"));
|
|
1071
|
+
const disabled = /* @__PURE__ */ new Set([
|
|
1072
|
+
...grokGlobal?.disabled_mcp_servers ?? [],
|
|
1073
|
+
...grokProject?.disabled_mcp_servers ?? []
|
|
1074
|
+
]);
|
|
1075
|
+
const effectiveGrok = {
|
|
1076
|
+
...grokGlobal?.mcp_servers ?? {},
|
|
1077
|
+
...grokProject?.mcp_servers ?? {}
|
|
1078
|
+
};
|
|
1079
|
+
add(
|
|
1080
|
+
Object.fromEntries(
|
|
1081
|
+
Object.entries(effectiveGrok).filter(
|
|
1082
|
+
([name, config]) => config.enabled !== false && !disabled.has(name)
|
|
1083
|
+
)
|
|
1084
|
+
),
|
|
1085
|
+
"grok-build"
|
|
1086
|
+
);
|
|
1030
1087
|
add(readJson2(join3(cwd, ".mcp.json"))?.mcpServers, "claude-code");
|
|
1031
1088
|
add(readJson2(join3(cwd, "mcp.json"))?.mcpServers, "generic");
|
|
1032
1089
|
add(
|
|
@@ -1112,8 +1169,8 @@ function resolveSource(entry, mpRepoUrl) {
|
|
|
1112
1169
|
const src = entry.source;
|
|
1113
1170
|
if (typeof src === "string") {
|
|
1114
1171
|
if (!mpRepoUrl) return null;
|
|
1115
|
-
const
|
|
1116
|
-
return { url: mpRepoUrl, path:
|
|
1172
|
+
const path13 = src.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1173
|
+
return { url: mpRepoUrl, path: path13 || void 0 };
|
|
1117
1174
|
}
|
|
1118
1175
|
if (src && typeof src === "object" && src.url) {
|
|
1119
1176
|
return { url: src.url, path: src.path, sha: src.sha };
|
|
@@ -1150,10 +1207,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
|
|
|
1150
1207
|
}
|
|
1151
1208
|
return out;
|
|
1152
1209
|
}
|
|
1153
|
-
function readJson3(
|
|
1210
|
+
function readJson3(path13) {
|
|
1154
1211
|
try {
|
|
1155
|
-
if (!existsSync4(
|
|
1156
|
-
return JSON.parse(readFileSync4(
|
|
1212
|
+
if (!existsSync4(path13)) return null;
|
|
1213
|
+
return JSON.parse(readFileSync4(path13, "utf-8"));
|
|
1157
1214
|
} catch {
|
|
1158
1215
|
return null;
|
|
1159
1216
|
}
|
|
@@ -1187,6 +1244,7 @@ var LOCAL_PATTERNS = [
|
|
|
1187
1244
|
// Rules
|
|
1188
1245
|
{ path: "CLAUDE.md", type: "rule", group: "claude-code" },
|
|
1189
1246
|
{ path: "AGENTS.md", type: "rule", group: "claude-code" },
|
|
1247
|
+
{ path: "GROK.md", type: "rule", group: "grok-build" },
|
|
1190
1248
|
{ path: "GEMINI.md", type: "rule", group: "gemini" },
|
|
1191
1249
|
{ path: ".cursorrules", type: "rule", group: "cursor" },
|
|
1192
1250
|
{ path: ".windsurfrules", type: "rule", group: "windsurf" },
|
|
@@ -1217,8 +1275,16 @@ var LOCAL_DIR_PATTERNS = [
|
|
|
1217
1275
|
{ dir: ".github/instructions", type: "rule", group: "copilot" },
|
|
1218
1276
|
{ dir: ".github/prompts", type: "prompt", group: "copilot" },
|
|
1219
1277
|
{ dir: ".claude/commands", type: "command", group: "claude-code" },
|
|
1278
|
+
{ dir: ".claude/skills", type: "skill", group: "claude-code" },
|
|
1220
1279
|
{ dir: ".claude/agents", type: "subagent", group: "claude-code" },
|
|
1221
1280
|
{ dir: ".claude/hooks", type: "hook", group: "claude-code" },
|
|
1281
|
+
{ dir: ".grok/skills", type: "skill", group: "grok-build" },
|
|
1282
|
+
{ dir: ".grok/commands", type: "command", group: "grok-build" },
|
|
1283
|
+
{ dir: ".grok/agents", type: "subagent", group: "grok-build" },
|
|
1284
|
+
{ dir: ".grok/hooks", type: "hook", group: "grok-build" },
|
|
1285
|
+
{ dir: ".cursor/skills", type: "skill", group: "cursor" },
|
|
1286
|
+
{ dir: ".agents/skills", type: "skill", group: "generic" },
|
|
1287
|
+
{ dir: ".agents/commands", type: "command", group: "generic" },
|
|
1222
1288
|
{ dir: "prompts", type: "prompt", group: "generic" },
|
|
1223
1289
|
{ dir: ".ai", type: "custom", group: "generic" }
|
|
1224
1290
|
];
|
|
@@ -1233,8 +1299,8 @@ function loadGitignore(cwd) {
|
|
|
1233
1299
|
}
|
|
1234
1300
|
function readFileSafe(filePath) {
|
|
1235
1301
|
try {
|
|
1236
|
-
const
|
|
1237
|
-
if (
|
|
1302
|
+
const stat8 = statSync(filePath);
|
|
1303
|
+
if (stat8.size > MAX_FILE_SIZE) return null;
|
|
1238
1304
|
return readFileSync5(filePath, "utf-8");
|
|
1239
1305
|
} catch {
|
|
1240
1306
|
return null;
|
|
@@ -1299,7 +1365,9 @@ function scanLocal(cwd) {
|
|
|
1299
1365
|
for (const entry of readdirSync2(cwd, { withFileTypes: true }).filter(
|
|
1300
1366
|
(e) => e.isDirectory()
|
|
1301
1367
|
)) {
|
|
1302
|
-
if (
|
|
1368
|
+
if ([".grok", ".claude", ".cursor", ".agents"].includes(entry.name))
|
|
1369
|
+
continue;
|
|
1370
|
+
if (ig.ignores(`${entry.name}/`)) continue;
|
|
1303
1371
|
scanSkillDirs(join5(cwd, entry.name), cwd, ig, results, 1);
|
|
1304
1372
|
}
|
|
1305
1373
|
} catch {
|
|
@@ -1332,7 +1400,7 @@ function scanSkillDirs(dir, cwd, ig, results, depth) {
|
|
|
1332
1400
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
1333
1401
|
if (entry.isDirectory()) {
|
|
1334
1402
|
const rel = relative(cwd, join5(dir, entry.name));
|
|
1335
|
-
if (!ig.ignores(rel
|
|
1403
|
+
if (!ig.ignores(`${rel}/`)) {
|
|
1336
1404
|
scanSkillDirs(join5(dir, entry.name), cwd, ig, results, depth + 1);
|
|
1337
1405
|
}
|
|
1338
1406
|
}
|
|
@@ -1345,6 +1413,7 @@ function scanGlobal() {
|
|
|
1345
1413
|
const results = [];
|
|
1346
1414
|
const globalPatterns = [
|
|
1347
1415
|
{ path: ".claude/CLAUDE.md", type: "rule", group: "claude-code" },
|
|
1416
|
+
{ path: ".grok/GROK.md", type: "rule", group: "grok-build" },
|
|
1348
1417
|
{ path: ".claude/settings.json", type: "config", group: "claude-code" },
|
|
1349
1418
|
{ path: ".continue/config.json", type: "config", group: "continue" },
|
|
1350
1419
|
{ path: ".continue/config.yaml", type: "config", group: "continue" },
|
|
@@ -1376,7 +1445,14 @@ function scanGlobal() {
|
|
|
1376
1445
|
{ dir: ".claude/commands", type: "command", group: "claude-code" },
|
|
1377
1446
|
{ dir: ".claude/agents", type: "subagent", group: "claude-code" },
|
|
1378
1447
|
{ dir: ".claude/hooks", type: "hook", group: "claude-code" },
|
|
1379
|
-
{ dir: ".
|
|
1448
|
+
{ dir: ".grok/skills", type: "skill", group: "grok-build" },
|
|
1449
|
+
{ dir: ".grok/commands", type: "command", group: "grok-build" },
|
|
1450
|
+
{ dir: ".grok/agents", type: "subagent", group: "grok-build" },
|
|
1451
|
+
{ dir: ".grok/hooks", type: "hook", group: "grok-build" },
|
|
1452
|
+
{ dir: ".cursor/rules", type: "rule", group: "cursor" },
|
|
1453
|
+
{ dir: ".cursor/skills", type: "skill", group: "cursor" },
|
|
1454
|
+
{ dir: ".agents/skills", type: "skill", group: "generic" },
|
|
1455
|
+
{ dir: ".agents/commands", type: "command", group: "generic" }
|
|
1380
1456
|
];
|
|
1381
1457
|
for (const { dir, type, group } of globalDirs) {
|
|
1382
1458
|
const dirPath = join5(home, dir);
|
|
@@ -1443,9 +1519,9 @@ function lines(items) {
|
|
|
1443
1519
|
console.log(`${BAR} ${item}`);
|
|
1444
1520
|
}
|
|
1445
1521
|
}
|
|
1446
|
-
function section(label,
|
|
1522
|
+
function section(label, count2) {
|
|
1447
1523
|
console.log(`${BAR}`);
|
|
1448
|
-
const countStr =
|
|
1524
|
+
const countStr = count2 !== void 0 ? ` ${dim(String(count2))}` : "";
|
|
1449
1525
|
console.log(`${BAR} ${bold(label.toUpperCase())}${countStr}`);
|
|
1450
1526
|
}
|
|
1451
1527
|
function divider() {
|
|
@@ -1768,7 +1844,7 @@ function diffResources(current, existing) {
|
|
|
1768
1844
|
// src/commands/connect.ts
|
|
1769
1845
|
import { spawnSync } from "node:child_process";
|
|
1770
1846
|
import { cpSync, existsSync as existsSync6 } from "node:fs";
|
|
1771
|
-
import { homedir as
|
|
1847
|
+
import { homedir as homedir13 } from "node:os";
|
|
1772
1848
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1773
1849
|
import { fileURLToPath } from "node:url";
|
|
1774
1850
|
import * as p3 from "@clack/prompts";
|
|
@@ -1914,12 +1990,12 @@ function emptyUsage() {
|
|
|
1914
1990
|
};
|
|
1915
1991
|
}
|
|
1916
1992
|
var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
|
|
1917
|
-
function addModelUsage(agg, modelKey,
|
|
1993
|
+
function addModelUsage(agg, modelKey, counts2, costUSD, messages = 1, at) {
|
|
1918
1994
|
if (at) {
|
|
1919
1995
|
noteUsageResponse(agg, {
|
|
1920
1996
|
tsMs: at.tsMs,
|
|
1921
1997
|
modelKey,
|
|
1922
|
-
counts,
|
|
1998
|
+
counts: counts2,
|
|
1923
1999
|
costUSD,
|
|
1924
2000
|
...at.sidechain ? { sidechain: true } : {}
|
|
1925
2001
|
});
|
|
@@ -1930,13 +2006,13 @@ function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
|
|
|
1930
2006
|
agg.byModel.set(modelKey, m);
|
|
1931
2007
|
}
|
|
1932
2008
|
m.messages += messages;
|
|
1933
|
-
m.input +=
|
|
1934
|
-
m.output +=
|
|
1935
|
-
m.cacheWrite5m +=
|
|
1936
|
-
m.cacheWrite1h +=
|
|
1937
|
-
m.cacheWriteUnsplit +=
|
|
1938
|
-
m.cacheRead +=
|
|
1939
|
-
if (costUSD === null) m.unpricedTokens += countsTotal(
|
|
2009
|
+
m.input += counts2.input;
|
|
2010
|
+
m.output += counts2.output;
|
|
2011
|
+
m.cacheWrite5m += counts2.cacheWrite5m;
|
|
2012
|
+
m.cacheWrite1h += counts2.cacheWrite1h;
|
|
2013
|
+
m.cacheWriteUnsplit += counts2.cacheWriteUnsplit;
|
|
2014
|
+
m.cacheRead += counts2.cacheRead;
|
|
2015
|
+
if (costUSD === null) m.unpricedTokens += countsTotal(counts2);
|
|
1940
2016
|
else m.costUSD += costUSD;
|
|
1941
2017
|
}
|
|
1942
2018
|
function buildModelRows(agg) {
|
|
@@ -2366,7 +2442,7 @@ function filterAtoms(atoms, sets) {
|
|
|
2366
2442
|
}
|
|
2367
2443
|
merged.set(published, (merged.get(published) ?? 0) + atom.count);
|
|
2368
2444
|
}
|
|
2369
|
-
const allowed = [...merged].map(([name,
|
|
2445
|
+
const allowed = [...merged].map(([name, count2]) => ({ name, count: count2 }));
|
|
2370
2446
|
allowed.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2371
2447
|
keptPrivate.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2372
2448
|
return { allowed, keptPrivate, withheld: keptPrivate.length };
|
|
@@ -2781,13 +2857,13 @@ function playbookHarnesses(reading) {
|
|
|
2781
2857
|
return reading.harnesses.filter((harness) => harness.phase !== void 0);
|
|
2782
2858
|
}
|
|
2783
2859
|
function startHoursUtc(reading) {
|
|
2784
|
-
const
|
|
2860
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2785
2861
|
for (const harness of reading.harnesses) {
|
|
2786
2862
|
for (const cell of harness.startHours) {
|
|
2787
|
-
|
|
2863
|
+
counts2.set(cell.hourUtc, (counts2.get(cell.hourUtc) ?? 0) + cell.sessions);
|
|
2788
2864
|
}
|
|
2789
2865
|
}
|
|
2790
|
-
return
|
|
2866
|
+
return counts2;
|
|
2791
2867
|
}
|
|
2792
2868
|
function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
2793
2869
|
return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
|
|
@@ -2795,13 +2871,13 @@ function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
|
2795
2871
|
function modalStartHour(reading) {
|
|
2796
2872
|
const offsetMinutes = reading.utcOffsetMinutes;
|
|
2797
2873
|
if (offsetMinutes === void 0) return void 0;
|
|
2798
|
-
const
|
|
2874
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2799
2875
|
for (const [hourUtc, sessions] of startHoursUtc(reading)) {
|
|
2800
2876
|
const hour = ownerLocalHour(hourUtc, offsetMinutes);
|
|
2801
|
-
|
|
2877
|
+
counts2.set(hour, (counts2.get(hour) ?? 0) + sessions);
|
|
2802
2878
|
}
|
|
2803
|
-
if (
|
|
2804
|
-
return [...
|
|
2879
|
+
if (counts2.size === 0) return void 0;
|
|
2880
|
+
return [...counts2.entries()].sort(
|
|
2805
2881
|
(a, b) => b[1] - a[1] || a[0] - b[0]
|
|
2806
2882
|
)[0]?.[0];
|
|
2807
2883
|
}
|
|
@@ -2809,10 +2885,10 @@ function modalStartHour(reading) {
|
|
|
2809
2885
|
// ../workflow-rules/src/componentRules.ts
|
|
2810
2886
|
var COMPONENT_RULES_V2 = "component-rules/v2";
|
|
2811
2887
|
var gitCoverage = () => 1;
|
|
2812
|
-
function harnessShare(input,
|
|
2888
|
+
function harnessShare(input, counts2) {
|
|
2813
2889
|
const synced = input.reading.harnesses.length;
|
|
2814
2890
|
if (synced === 0) return 0;
|
|
2815
|
-
return
|
|
2891
|
+
return counts2(input) / synced;
|
|
2816
2892
|
}
|
|
2817
2893
|
function topShare(entries) {
|
|
2818
2894
|
const total = entries.reduce((sum, entry) => sum + entry.value, 0);
|
|
@@ -3123,6 +3199,8 @@ var HANDOFF_MARKERS = {
|
|
|
3123
3199
|
"mcp__curia__request_review"
|
|
3124
3200
|
],
|
|
3125
3201
|
codex: ["request_user_input"],
|
|
3202
|
+
cursor: ["ask_question"],
|
|
3203
|
+
"grok-build": ["ask_user_question", "request_user_input"],
|
|
3126
3204
|
opencode: ["question"],
|
|
3127
3205
|
"pi-mono": []
|
|
3128
3206
|
};
|
|
@@ -3153,7 +3231,11 @@ var SCOUT_TOOLS = [
|
|
|
3153
3231
|
"web_search",
|
|
3154
3232
|
"tool_search",
|
|
3155
3233
|
"codebase_search",
|
|
3156
|
-
"find"
|
|
3234
|
+
"find",
|
|
3235
|
+
"search_tool",
|
|
3236
|
+
"search_files",
|
|
3237
|
+
"list_dir",
|
|
3238
|
+
"read_file"
|
|
3157
3239
|
];
|
|
3158
3240
|
var EDIT_TOOLS = [
|
|
3159
3241
|
"Edit",
|
|
@@ -3164,11 +3246,20 @@ var EDIT_TOOLS = [
|
|
|
3164
3246
|
"write",
|
|
3165
3247
|
"patch",
|
|
3166
3248
|
"multiedit",
|
|
3167
|
-
"apply_patch"
|
|
3249
|
+
"apply_patch",
|
|
3250
|
+
"write_file",
|
|
3251
|
+
"edit_file"
|
|
3168
3252
|
];
|
|
3169
3253
|
var REVIEW_SKILLS = ["code-review", "security-review", "review"];
|
|
3170
3254
|
var SCOUT_AGENTS = ["Explore", "Plan", "research"];
|
|
3171
|
-
var SHELL_TOOLS = [
|
|
3255
|
+
var SHELL_TOOLS = [
|
|
3256
|
+
"Bash",
|
|
3257
|
+
"bash",
|
|
3258
|
+
"shell",
|
|
3259
|
+
"local_shell",
|
|
3260
|
+
"exec_command",
|
|
3261
|
+
"run_terminal_command"
|
|
3262
|
+
];
|
|
3172
3263
|
var SKILL_TOOLS = ["Skill", "skill"];
|
|
3173
3264
|
var AGENT_TOOLS = ["Agent", "Task", "task", "agent"];
|
|
3174
3265
|
var BOOKKEEPING_TOOLS = [
|
|
@@ -3642,7 +3733,7 @@ function sanitizeModelId(id) {
|
|
|
3642
3733
|
var round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
3643
3734
|
var round2 = (n) => Math.round(n * 100) / 100;
|
|
3644
3735
|
var utcDate = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3645
|
-
var toAtoms = (pairs) => pairs.map(([name,
|
|
3736
|
+
var toAtoms = (pairs) => pairs.map(([name, count2]) => ({ name, count: count2 }));
|
|
3646
3737
|
function buildCategory(observed, curated, optIns, denominator) {
|
|
3647
3738
|
const publishable = /* @__PURE__ */ new Set([...curated, ...optIns]);
|
|
3648
3739
|
const {
|
|
@@ -3935,7 +4026,7 @@ var bump2 = (map, key2, amount = 1) => {
|
|
|
3935
4026
|
var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3936
4027
|
function asBuckets(map, field) {
|
|
3937
4028
|
return [...map].map(
|
|
3938
|
-
([bucket,
|
|
4029
|
+
([bucket, count2]) => ({ bucket, [field]: count2 })
|
|
3939
4030
|
).sort((a, b) => a.bucket - b.bucket);
|
|
3940
4031
|
}
|
|
3941
4032
|
var PHASE_RANK = {
|
|
@@ -4320,7 +4411,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4320
4411
|
0
|
|
4321
4412
|
);
|
|
4322
4413
|
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
4323
|
-
const routesModels = harness === "claude-code" || harness === "opencode";
|
|
4414
|
+
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build" || harness === "cursor";
|
|
4324
4415
|
const asRows = (map) => {
|
|
4325
4416
|
const safe = /* @__PURE__ */ new Map();
|
|
4326
4417
|
for (const [model, tokens] of map) {
|
|
@@ -4343,7 +4434,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4343
4434
|
date,
|
|
4344
4435
|
harness,
|
|
4345
4436
|
sessions: day.sessions,
|
|
4346
|
-
startHours: [...day.startHours].map(([hourUtc,
|
|
4437
|
+
startHours: [...day.startHours].map(([hourUtc, count2]) => ({ hourUtc, sessions: count2 })).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
4347
4438
|
...day.phase.sessions > 0 ? {
|
|
4348
4439
|
phase: {
|
|
4349
4440
|
ruleVersion: PHASE_RULES_V1,
|
|
@@ -4471,12 +4562,12 @@ function ingestRecord(agg, raw, ctx) {
|
|
|
4471
4562
|
const sessionId = asStr(rec.sessionId);
|
|
4472
4563
|
if (sessionId) agg.sessions.add(sessionId);
|
|
4473
4564
|
let tsMs = null;
|
|
4474
|
-
const
|
|
4475
|
-
if (
|
|
4476
|
-
const ts = Date.parse(
|
|
4565
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
4566
|
+
if (timestamp2) {
|
|
4567
|
+
const ts = Date.parse(timestamp2);
|
|
4477
4568
|
if (!Number.isNaN(ts)) {
|
|
4478
4569
|
tsMs = ts;
|
|
4479
|
-
agg.activeDays.add(
|
|
4570
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
4480
4571
|
agg.firstTs = agg.firstTs === null ? ts : Math.min(agg.firstTs, ts);
|
|
4481
4572
|
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
4482
4573
|
}
|
|
@@ -4505,8 +4596,8 @@ function ingestClaudeCompaction(agg, rec, ctx, tsMs) {
|
|
|
4505
4596
|
...rec.isSidechain === true ? { sidechain: true } : {}
|
|
4506
4597
|
});
|
|
4507
4598
|
}
|
|
4508
|
-
function contextOf(
|
|
4509
|
-
return
|
|
4599
|
+
function contextOf(counts2) {
|
|
4600
|
+
return counts2.input + counts2.cacheWrite5m + counts2.cacheWrite1h + counts2.cacheWriteUnsplit + counts2.cacheRead;
|
|
4510
4601
|
}
|
|
4511
4602
|
function ingestClaudeTurnDuration(agg, rec, ctx, tsMs) {
|
|
4512
4603
|
if (tsMs === null || asStr(rec.subtype) !== "turn_duration" || asNum(rec.durationMs) <= 0) {
|
|
@@ -4534,12 +4625,12 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4534
4625
|
const session = sidechain ? `${baseSession}:agent:${agentId ?? "unknown"}` : baseSession;
|
|
4535
4626
|
const parentSession = sidechain ? baseSession : void 0;
|
|
4536
4627
|
const usage = asObj(msg.usage);
|
|
4537
|
-
const
|
|
4628
|
+
const counts2 = usage ? readCounts(usage) : null;
|
|
4538
4629
|
const messageId = asStr(msg.id);
|
|
4539
4630
|
const newTurn = messageId ? !agg.workflowSeenTurns.has(messageId) : true;
|
|
4540
4631
|
if (messageId) agg.workflowSeenTurns.add(messageId);
|
|
4541
4632
|
let context = null;
|
|
4542
|
-
if (
|
|
4633
|
+
if (counts2 && isApiCall(rec)) {
|
|
4543
4634
|
const held = agg.contextFirstCall.get(session);
|
|
4544
4635
|
let first = false;
|
|
4545
4636
|
if (held === void 0) {
|
|
@@ -4547,11 +4638,11 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4547
4638
|
first = true;
|
|
4548
4639
|
} else if (held !== null && held === messageId) first = true;
|
|
4549
4640
|
context = {
|
|
4550
|
-
contextTokens: contextOf(
|
|
4641
|
+
contextTokens: contextOf(counts2),
|
|
4551
4642
|
...first ? {
|
|
4552
4643
|
firstCall: {
|
|
4553
|
-
harnessTokens:
|
|
4554
|
-
instructionsTokens: contextOf(
|
|
4644
|
+
harnessTokens: counts2.cacheRead,
|
|
4645
|
+
instructionsTokens: contextOf(counts2) - counts2.cacheRead
|
|
4555
4646
|
}
|
|
4556
4647
|
} : {}
|
|
4557
4648
|
};
|
|
@@ -4565,8 +4656,8 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4565
4656
|
...parentSession ? { parentSession } : {},
|
|
4566
4657
|
...messageId ? { responseId: messageId } : {},
|
|
4567
4658
|
...asName(msg.model) ? { model: asName(msg.model) } : {},
|
|
4568
|
-
...
|
|
4569
|
-
...
|
|
4659
|
+
...counts2 ? { responseTokens: counts2.output } : {},
|
|
4660
|
+
...counts2 ? { routingTokens: countsTotal(counts2) } : {},
|
|
4570
4661
|
...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {},
|
|
4571
4662
|
...context ?? {}
|
|
4572
4663
|
});
|
|
@@ -4693,11 +4784,11 @@ function readCounts(usage) {
|
|
|
4693
4784
|
function modelKeyFor2(model, speed) {
|
|
4694
4785
|
return normalizeModel(speed === "fast" ? `${model}#fast` : model);
|
|
4695
4786
|
}
|
|
4696
|
-
function makeEntry(modelKey,
|
|
4787
|
+
function makeEntry(modelKey, counts2, tsMs) {
|
|
4697
4788
|
return {
|
|
4698
4789
|
modelKey,
|
|
4699
|
-
counts,
|
|
4700
|
-
costUSD: apiEquivalentCost(modelKey,
|
|
4790
|
+
counts: counts2,
|
|
4791
|
+
costUSD: apiEquivalentCost(modelKey, counts2, tsMs)
|
|
4701
4792
|
};
|
|
4702
4793
|
}
|
|
4703
4794
|
function buildContribution(usage, model, sidechain, tsMs) {
|
|
@@ -4742,24 +4833,24 @@ function buildContribution(usage, model, sidechain, tsMs) {
|
|
|
4742
4833
|
};
|
|
4743
4834
|
}
|
|
4744
4835
|
function applyContribution(agg, c, sign) {
|
|
4745
|
-
c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
|
|
4836
|
+
c.entries.forEach(({ modelKey, counts: counts2, costUSD }, i) => {
|
|
4746
4837
|
let m = agg.byModel.get(modelKey);
|
|
4747
4838
|
if (!m) {
|
|
4748
4839
|
m = emptyUsage();
|
|
4749
4840
|
agg.byModel.set(modelKey, m);
|
|
4750
4841
|
}
|
|
4751
4842
|
if (i === 0) m.messages += sign;
|
|
4752
|
-
m.input += sign *
|
|
4753
|
-
m.output += sign *
|
|
4754
|
-
m.cacheWrite5m += sign *
|
|
4755
|
-
m.cacheWrite1h += sign *
|
|
4756
|
-
m.cacheWriteUnsplit += sign *
|
|
4757
|
-
m.cacheRead += sign *
|
|
4758
|
-
if (costUSD === null) m.unpricedTokens += sign * countsTotal(
|
|
4843
|
+
m.input += sign * counts2.input;
|
|
4844
|
+
m.output += sign * counts2.output;
|
|
4845
|
+
m.cacheWrite5m += sign * counts2.cacheWrite5m;
|
|
4846
|
+
m.cacheWrite1h += sign * counts2.cacheWrite1h;
|
|
4847
|
+
m.cacheWriteUnsplit += sign * counts2.cacheWriteUnsplit;
|
|
4848
|
+
m.cacheRead += sign * counts2.cacheRead;
|
|
4849
|
+
if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts2);
|
|
4759
4850
|
else m.costUSD += sign * costUSD;
|
|
4760
4851
|
noteUsageResponse(
|
|
4761
4852
|
agg,
|
|
4762
|
-
{ tsMs: c.tsMs, modelKey, counts, costUSD, sidechain: c.sidechain },
|
|
4853
|
+
{ tsMs: c.tsMs, modelKey, counts: counts2, costUSD, sidechain: c.sidechain },
|
|
4763
4854
|
sign
|
|
4764
4855
|
);
|
|
4765
4856
|
});
|
|
@@ -4769,8 +4860,8 @@ function applyContribution(agg, c, sign) {
|
|
|
4769
4860
|
agg.webFetchRequests += sign * c.webFetch;
|
|
4770
4861
|
agg.fallbackAttempts += sign * c.fallbackAttempts;
|
|
4771
4862
|
agg.untypedMirrors += sign * c.untypedMirrors;
|
|
4772
|
-
for (const [type,
|
|
4773
|
-
bump(agg.mirroredIterationTypes, type, sign *
|
|
4863
|
+
for (const [type, count2] of c.mirroredIterationTypes) {
|
|
4864
|
+
bump(agg.mirroredIterationTypes, type, sign * count2);
|
|
4774
4865
|
}
|
|
4775
4866
|
}
|
|
4776
4867
|
function ingestContentBlocks(agg, content) {
|
|
@@ -5013,7 +5104,7 @@ function genuineDelta(payload, state, tsMs) {
|
|
|
5013
5104
|
if (!last) return null;
|
|
5014
5105
|
const inputTotal = asNum(last.input_tokens);
|
|
5015
5106
|
const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
|
|
5016
|
-
const
|
|
5107
|
+
const counts2 = {
|
|
5017
5108
|
input: inputTotal - cached,
|
|
5018
5109
|
output: asNum(last.output_tokens),
|
|
5019
5110
|
cacheWrite5m: 0,
|
|
@@ -5021,11 +5112,11 @@ function genuineDelta(payload, state, tsMs) {
|
|
|
5021
5112
|
cacheWriteUnsplit: 0,
|
|
5022
5113
|
cacheRead: cached
|
|
5023
5114
|
};
|
|
5024
|
-
if (countsTotal(
|
|
5115
|
+
if (countsTotal(counts2) === 0) return null;
|
|
5025
5116
|
if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
|
|
5026
5117
|
return null;
|
|
5027
5118
|
return {
|
|
5028
|
-
counts,
|
|
5119
|
+
counts: counts2,
|
|
5029
5120
|
last,
|
|
5030
5121
|
contextWindow: info ? asNum(info.model_context_window) : 0
|
|
5031
5122
|
};
|
|
@@ -5036,9 +5127,9 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5036
5127
|
if (!rec) return;
|
|
5037
5128
|
agg.records++;
|
|
5038
5129
|
let tsMs = null;
|
|
5039
|
-
const
|
|
5040
|
-
if (
|
|
5041
|
-
const ts = Date.parse(
|
|
5130
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
5131
|
+
if (timestamp2) {
|
|
5132
|
+
const ts = Date.parse(timestamp2);
|
|
5042
5133
|
if (!Number.isNaN(ts)) tsMs = ts;
|
|
5043
5134
|
}
|
|
5044
5135
|
const inWindow = sinceMs === void 0 || tsMs !== null && tsMs >= sinceMs;
|
|
@@ -5061,8 +5152,8 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5061
5152
|
state.sawResponse = true;
|
|
5062
5153
|
}
|
|
5063
5154
|
if (!inWindow) return;
|
|
5064
|
-
if (tsMs !== null &&
|
|
5065
|
-
agg.activeDays.add(
|
|
5155
|
+
if (tsMs !== null && timestamp2) {
|
|
5156
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
5066
5157
|
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
5067
5158
|
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
5068
5159
|
}
|
|
@@ -5092,8 +5183,8 @@ function noteActivity(agg, state, tsMs) {
|
|
|
5092
5183
|
function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
5093
5184
|
const delta = genuineDelta(payload, state, tsMs);
|
|
5094
5185
|
if (!delta) return;
|
|
5095
|
-
const { counts, last, contextWindow } = delta;
|
|
5096
|
-
const total = countsTotal(
|
|
5186
|
+
const { counts: counts2, last, contextWindow } = delta;
|
|
5187
|
+
const total = countsTotal(counts2);
|
|
5097
5188
|
if (state.modelKey === null) return;
|
|
5098
5189
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
5099
5190
|
agg.distinctResponses++;
|
|
@@ -5101,8 +5192,8 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5101
5192
|
addModelUsage(
|
|
5102
5193
|
agg,
|
|
5103
5194
|
modelKey,
|
|
5104
|
-
|
|
5105
|
-
apiEquivalentCost(modelKey,
|
|
5195
|
+
counts2,
|
|
5196
|
+
apiEquivalentCost(modelKey, counts2, tsMs),
|
|
5106
5197
|
1,
|
|
5107
5198
|
{ tsMs }
|
|
5108
5199
|
);
|
|
@@ -5116,7 +5207,7 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5116
5207
|
projectWorkspace: state.cwd ?? void 0,
|
|
5117
5208
|
tsMs,
|
|
5118
5209
|
model: modelKey,
|
|
5119
|
-
responseTokens:
|
|
5210
|
+
responseTokens: counts2.output,
|
|
5120
5211
|
routingTokens: total,
|
|
5121
5212
|
thinkingTokens: asNum(last.reasoning_output_tokens),
|
|
5122
5213
|
...state.effort ? { effort: state.effort } : {},
|
|
@@ -5125,12 +5216,12 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5125
5216
|
// instructions and tool specs, cached by an earlier session) and
|
|
5126
5217
|
// the fresh part is the instructions: AGENTS.md, environment,
|
|
5127
5218
|
// skills and the first prompt. Same method as Claude Code (#358).
|
|
5128
|
-
contextTokens:
|
|
5219
|
+
contextTokens: counts2.input + counts2.cacheRead,
|
|
5129
5220
|
...contextWindow > 0 ? { contextWindow } : {},
|
|
5130
5221
|
...firstCall && !state.forked ? {
|
|
5131
5222
|
firstCall: {
|
|
5132
|
-
harnessTokens:
|
|
5133
|
-
instructionsTokens:
|
|
5223
|
+
harnessTokens: counts2.cacheRead,
|
|
5224
|
+
instructionsTokens: counts2.input
|
|
5134
5225
|
}
|
|
5135
5226
|
} : {}
|
|
5136
5227
|
});
|
|
@@ -5350,11 +5441,11 @@ function ingestWithRetry(agg, file, opts) {
|
|
|
5350
5441
|
}
|
|
5351
5442
|
}
|
|
5352
5443
|
function ingestFile2(agg, file, opts) {
|
|
5353
|
-
const
|
|
5444
|
+
const readFile3 = opts.readFileImpl ?? readFileSync6;
|
|
5354
5445
|
let text;
|
|
5355
5446
|
if (file.endsWith(".zst")) {
|
|
5356
5447
|
if (zstdDecompress === null) throw readError("zstd-unsupported");
|
|
5357
|
-
const raw =
|
|
5448
|
+
const raw = readFile3(file);
|
|
5358
5449
|
try {
|
|
5359
5450
|
text = zstdDecompress(
|
|
5360
5451
|
Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
|
|
@@ -5363,7 +5454,7 @@ function ingestFile2(agg, file, opts) {
|
|
|
5363
5454
|
throw readError("zstd-corrupt");
|
|
5364
5455
|
}
|
|
5365
5456
|
} else {
|
|
5366
|
-
text =
|
|
5457
|
+
text = readFile3(file).toString("utf8");
|
|
5367
5458
|
}
|
|
5368
5459
|
const records = [];
|
|
5369
5460
|
let nonEmptyLines = 0;
|
|
@@ -5466,6 +5557,1321 @@ var codexAdapter = {
|
|
|
5466
5557
|
}
|
|
5467
5558
|
};
|
|
5468
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
|
+
|
|
6371
|
+
// src/harness/grok/analyzer.ts
|
|
6372
|
+
function createAggregate4() {
|
|
6373
|
+
const workflowLocal = createWorkflowLocalSources();
|
|
6374
|
+
return Object.assign(createAggregate(), {
|
|
6375
|
+
workflow: createHarnessWorkflowReducer("grok-build", workflowLocal),
|
|
6376
|
+
workflowLocal
|
|
6377
|
+
});
|
|
6378
|
+
}
|
|
6379
|
+
var createGrokEventState = (parentSession) => ({
|
|
6380
|
+
tools: /* @__PURE__ */ new Map(),
|
|
6381
|
+
completedTools: /* @__PURE__ */ new Set(),
|
|
6382
|
+
...parentSession ? { parentSession } : {}
|
|
6383
|
+
});
|
|
6384
|
+
var bump3 = (map, key2) => {
|
|
6385
|
+
map.set(key2, (map.get(key2) ?? 0) + 1);
|
|
6386
|
+
};
|
|
6387
|
+
var toolMetadata = (update2) => {
|
|
6388
|
+
const meta = asObj(update2._meta);
|
|
6389
|
+
return meta && asObj(meta["x.ai/tool"]);
|
|
6390
|
+
};
|
|
6391
|
+
function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
6392
|
+
const root = asObj(value);
|
|
6393
|
+
const params = root && asObj(root.params);
|
|
6394
|
+
const update2 = params && asObj(params.update);
|
|
6395
|
+
const session = params && asStr(params.sessionId);
|
|
6396
|
+
const tsMs = timestampMs(
|
|
6397
|
+
asObj(params?._meta)?.agentTimestampMs ?? root?.timestamp
|
|
6398
|
+
);
|
|
6399
|
+
if (!update2 || !session || tsMs === null) return;
|
|
6400
|
+
const kind = asStr(update2.sessionUpdate);
|
|
6401
|
+
if (kind === "tool_call") {
|
|
6402
|
+
const id = asStr(update2.toolCallId);
|
|
6403
|
+
const metadata = toolMetadata(update2);
|
|
6404
|
+
const name = asName(metadata?.name ?? update2.toolName);
|
|
6405
|
+
if (!id || !name || state.tools.has(id)) return;
|
|
6406
|
+
const raw = asObj(update2.input);
|
|
6407
|
+
const arg = asStr(raw?.command ?? raw?.query ?? raw?.skill ?? raw?.name);
|
|
6408
|
+
state.tools.set(id, { name, ...arg ? { arg } : {}, tsMs });
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
if (sinceMs !== void 0 && tsMs < sinceMs) return;
|
|
6412
|
+
if (kind === "tool_call_update") {
|
|
6413
|
+
const id = asStr(update2.toolCallId);
|
|
6414
|
+
if (!id || asStr(update2.status) !== "completed") return;
|
|
6415
|
+
completeTool(agg, state, id, session, projectDir, tsMs);
|
|
6416
|
+
return;
|
|
6417
|
+
}
|
|
6418
|
+
if (kind !== "turn_completed") return;
|
|
6419
|
+
const usage = asObj(update2.usage);
|
|
6420
|
+
if (!usage) return;
|
|
6421
|
+
const prompt = asStr(update2.prompt_id) ?? `turn:${tsMs}`;
|
|
6422
|
+
for (const [model, raw] of Object.entries(asObj(usage.modelUsage) ?? {})) {
|
|
6423
|
+
const row = asObj(raw);
|
|
6424
|
+
agg.workflow.ingest({
|
|
6425
|
+
type: "response",
|
|
6426
|
+
session,
|
|
6427
|
+
projectWorkspace: projectDir,
|
|
6428
|
+
parentSession: state.parentSession,
|
|
6429
|
+
tsMs,
|
|
6430
|
+
responseId: `${prompt}:${model}`,
|
|
6431
|
+
model,
|
|
6432
|
+
thinkingTokens: asNum(row?.reasoningTokens),
|
|
6433
|
+
responseTokens: asNum(row?.outputTokens),
|
|
6434
|
+
routingTokens: asNum(row?.outputTokens),
|
|
6435
|
+
...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(update2.elapsed_ms) > 0 ? { durationSec: asNum(update2.elapsed_ms) / 1e3 } : {}
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6438
|
+
agg.workflow.ingest({
|
|
6439
|
+
type: "turn",
|
|
6440
|
+
session,
|
|
6441
|
+
projectWorkspace: projectDir,
|
|
6442
|
+
parentSession: state.parentSession,
|
|
6443
|
+
tsMs,
|
|
6444
|
+
turnId: prompt,
|
|
6445
|
+
questionBack: asStr(update2.stop_reason) === "question"
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
function completeTool(agg, state, id, session, projectDir, tsMs) {
|
|
6449
|
+
if (state.completedTools.has(id)) return;
|
|
6450
|
+
const tool = state.tools.get(id);
|
|
6451
|
+
if (!tool) return;
|
|
6452
|
+
state.completedTools.add(id);
|
|
6453
|
+
bump3(agg.toolCalls, tool.name);
|
|
6454
|
+
if (["web_search", "websearch", "search_web"].includes(tool.name))
|
|
6455
|
+
agg.webSearchRequests++;
|
|
6456
|
+
if (["skill", "use_skill"].includes(tool.name) && tool.arg)
|
|
6457
|
+
bump3(agg.skillCalls, tool.arg);
|
|
6458
|
+
const mcp = /^(?:mcp__|mcp:)([^_:]+)[_:](.+)$/.exec(tool.name);
|
|
6459
|
+
if (mcp) {
|
|
6460
|
+
bump3(agg.mcpServerCalls, mcp[1]);
|
|
6461
|
+
bump3(agg.mcpToolCalls, tool.name);
|
|
6462
|
+
} else if (tool.name === "use_tool" && tool.arg?.includes("__")) {
|
|
6463
|
+
const [server] = tool.arg.split("__", 1);
|
|
6464
|
+
if (server) {
|
|
6465
|
+
bump3(agg.mcpServerCalls, server);
|
|
6466
|
+
bump3(agg.mcpToolCalls, tool.arg);
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
agg.workflow.ingest({
|
|
6470
|
+
type: "event",
|
|
6471
|
+
session,
|
|
6472
|
+
projectWorkspace: projectDir,
|
|
6473
|
+
parentSession: state.parentSession,
|
|
6474
|
+
tsMs: tool.tsMs || tsMs,
|
|
6475
|
+
tool: tool.name,
|
|
6476
|
+
...tool.arg ? { arg: tool.arg } : {},
|
|
6477
|
+
batchId: id
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
function ingestEvent2(agg, state, value, sessionFallback, projectDir, sinceMs) {
|
|
6481
|
+
const row = asObj(value);
|
|
6482
|
+
if (!row) return;
|
|
6483
|
+
const session = asStr(row.session_id) ?? sessionFallback;
|
|
6484
|
+
const tsMs = timestampMs(row.ts);
|
|
6485
|
+
if (!session || tsMs === null) return;
|
|
6486
|
+
if (asStr(row.type) === "tool_started") {
|
|
6487
|
+
const id = asStr(row.tool_call_id);
|
|
6488
|
+
const name = asName(row.tool_name);
|
|
6489
|
+
if (id && name && !state.tools.has(id)) state.tools.set(id, { name, tsMs });
|
|
6490
|
+
} else if (sinceMs !== void 0 && tsMs < sinceMs) {
|
|
6491
|
+
return;
|
|
6492
|
+
} else if (asStr(row.type) === "tool_completed") {
|
|
6493
|
+
const id = asStr(row.tool_call_id);
|
|
6494
|
+
if (id) completeTool(agg, state, id, session, projectDir, tsMs);
|
|
6495
|
+
} else if (asStr(row.type) === "compaction") {
|
|
6496
|
+
agg.workflow.ingest({
|
|
6497
|
+
type: "compaction",
|
|
6498
|
+
session,
|
|
6499
|
+
projectWorkspace: projectDir,
|
|
6500
|
+
parentSession: state.parentSession,
|
|
6501
|
+
tsMs
|
|
6502
|
+
});
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
var timestampMs = (value) => {
|
|
6506
|
+
if (typeof value === "string") {
|
|
6507
|
+
const parsed = Date.parse(value);
|
|
6508
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
6509
|
+
}
|
|
6510
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
6511
|
+
return value < 1e10 ? value * 1e3 : value;
|
|
6512
|
+
};
|
|
6513
|
+
function counts(value) {
|
|
6514
|
+
const row = asObj(value);
|
|
6515
|
+
if (!row) return null;
|
|
6516
|
+
const totalInput = asNum(row.inputTokens);
|
|
6517
|
+
const cacheRead = asNum(row.cachedReadTokens);
|
|
6518
|
+
const cacheWrite = asNum(row.cacheCreationTokens);
|
|
6519
|
+
if (totalInput < cacheRead + cacheWrite) return null;
|
|
6520
|
+
const result = {
|
|
6521
|
+
input: totalInput - cacheRead - cacheWrite,
|
|
6522
|
+
output: asNum(row.outputTokens),
|
|
6523
|
+
cacheWrite5m: 0,
|
|
6524
|
+
cacheWrite1h: 0,
|
|
6525
|
+
cacheWriteUnsplit: cacheWrite,
|
|
6526
|
+
cacheRead
|
|
6527
|
+
};
|
|
6528
|
+
return countsTotal(result) > 0 ? result : null;
|
|
6529
|
+
}
|
|
6530
|
+
function sidecarContributions(value, projectDir) {
|
|
6531
|
+
const root = asObj(value);
|
|
6532
|
+
const sessionId = root && asStr(root.sessionId);
|
|
6533
|
+
if (!root || !sessionId) return [];
|
|
6534
|
+
const turns = Array.isArray(root.turns) ? root.turns : [];
|
|
6535
|
+
const out = [];
|
|
6536
|
+
for (const raw of turns) {
|
|
6537
|
+
const turn = asObj(raw);
|
|
6538
|
+
const tsMs = turn && timestampMs(turn.endedAt);
|
|
6539
|
+
if (!turn || tsMs === null) continue;
|
|
6540
|
+
const models = [];
|
|
6541
|
+
const perModel = asObj(turn.modelUsage);
|
|
6542
|
+
for (const [model, usage] of Object.entries(perModel ?? {})) {
|
|
6543
|
+
const c = counts(usage);
|
|
6544
|
+
if (c) models.push({ model, counts: c });
|
|
6545
|
+
}
|
|
6546
|
+
if (models.length === 0) {
|
|
6547
|
+
const c = counts(turn);
|
|
6548
|
+
const model = asStr(turn.primaryModelId);
|
|
6549
|
+
if (c && model) models.push({ model, counts: c });
|
|
6550
|
+
}
|
|
6551
|
+
if (models.length > 0)
|
|
6552
|
+
out.push({
|
|
6553
|
+
sessionId,
|
|
6554
|
+
projectDir,
|
|
6555
|
+
tsMs,
|
|
6556
|
+
...asNum(turn.apiDurationMs) > 0 ? { durationMs: asNum(turn.apiDurationMs) } : {},
|
|
6557
|
+
models
|
|
6558
|
+
});
|
|
6559
|
+
}
|
|
6560
|
+
return out;
|
|
6561
|
+
}
|
|
6562
|
+
function terminalContribution(value, projectDir) {
|
|
6563
|
+
const root = asObj(value);
|
|
6564
|
+
const params = root && asObj(root.params);
|
|
6565
|
+
const update2 = params && asObj(params.update);
|
|
6566
|
+
const meta = params && asObj(params._meta);
|
|
6567
|
+
if (!update2 || asStr(update2.sessionUpdate) !== "turn_completed") return null;
|
|
6568
|
+
const usage = asObj(update2.usage);
|
|
6569
|
+
const sessionId = params && asStr(params.sessionId);
|
|
6570
|
+
const tsMs = timestampMs(meta?.agentTimestampMs ?? root?.timestamp);
|
|
6571
|
+
if (!usage || !sessionId || tsMs === null) return null;
|
|
6572
|
+
const models = [];
|
|
6573
|
+
for (const [model, row] of Object.entries(asObj(usage.modelUsage) ?? {})) {
|
|
6574
|
+
const c = counts(row);
|
|
6575
|
+
if (c) models.push({ model, counts: c });
|
|
6576
|
+
}
|
|
6577
|
+
return models.length === 0 ? null : {
|
|
6578
|
+
sessionId,
|
|
6579
|
+
projectDir,
|
|
6580
|
+
tsMs,
|
|
6581
|
+
...asNum(update2.elapsed_ms) > 0 ? { durationMs: asNum(update2.elapsed_ms) } : {},
|
|
6582
|
+
models
|
|
6583
|
+
};
|
|
6584
|
+
}
|
|
6585
|
+
function ingestContribution(agg, row) {
|
|
6586
|
+
agg.records++;
|
|
6587
|
+
agg.assistantRecords++;
|
|
6588
|
+
agg.distinctResponses++;
|
|
6589
|
+
agg.sessions.add(row.sessionId);
|
|
6590
|
+
agg.activeDays.add(new Date(row.tsMs).toISOString().slice(0, 10));
|
|
6591
|
+
agg.projectDirs.add(row.projectDir);
|
|
6592
|
+
agg.firstTs = agg.firstTs === null ? row.tsMs : Math.min(agg.firstTs, row.tsMs);
|
|
6593
|
+
agg.lastTs = agg.lastTs === null ? row.tsMs : Math.max(agg.lastTs, row.tsMs);
|
|
6594
|
+
noteSessionStart(agg, row.sessionId, row.tsMs);
|
|
6595
|
+
noteProjectDay(agg, row.projectDir, row.tsMs);
|
|
6596
|
+
for (const { model, counts: tokenCounts } of row.models) {
|
|
6597
|
+
const key2 = normalizeModel(model);
|
|
6598
|
+
addModelUsage(
|
|
6599
|
+
agg,
|
|
6600
|
+
key2,
|
|
6601
|
+
tokenCounts,
|
|
6602
|
+
apiEquivalentCost(key2, tokenCounts, row.tsMs),
|
|
6603
|
+
1,
|
|
6604
|
+
{
|
|
6605
|
+
tsMs: row.tsMs
|
|
6606
|
+
}
|
|
6607
|
+
);
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
// src/harness/grok/scan.ts
|
|
6612
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
6613
|
+
import { readdir as readdir4, readFile as readFile2, stat as stat5 } from "node:fs/promises";
|
|
6614
|
+
import { homedir as homedir10 } from "node:os";
|
|
6615
|
+
import path8 from "node:path";
|
|
6616
|
+
import readline2 from "node:readline";
|
|
6617
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
6618
|
+
function sessionRoots() {
|
|
6619
|
+
return [
|
|
6620
|
+
path8.join(
|
|
6621
|
+
process.env.GROK_HOME || path8.join(homedir10(), ".grok"),
|
|
6622
|
+
"sessions"
|
|
6623
|
+
)
|
|
6624
|
+
];
|
|
6625
|
+
}
|
|
6626
|
+
var isGrokEvidenceFile = (name) => name === "usage.json" || name === "updates.jsonl" || name === "events.jsonl";
|
|
6627
|
+
var delays = [0, 100, 300];
|
|
6628
|
+
var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6629
|
+
async function stableRead(file, jsonl) {
|
|
6630
|
+
for (const delay of delays) {
|
|
6631
|
+
if (delay) await pause(delay);
|
|
6632
|
+
try {
|
|
6633
|
+
const before = await stat5(file);
|
|
6634
|
+
if (!before.isFile()) return { complete: false };
|
|
6635
|
+
if (jsonl) {
|
|
6636
|
+
const lines2 = [];
|
|
6637
|
+
const input = readline2.createInterface({
|
|
6638
|
+
input: createReadStream2(file),
|
|
6639
|
+
crlfDelay: Infinity
|
|
6640
|
+
});
|
|
6641
|
+
for await (const line of input) {
|
|
6642
|
+
if (!line.trim()) continue;
|
|
6643
|
+
try {
|
|
6644
|
+
lines2.push(JSON.parse(line));
|
|
6645
|
+
} catch {
|
|
6646
|
+
lines2.push(null);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
const after = await stat5(file);
|
|
6650
|
+
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
6651
|
+
return { lines: lines2, complete: true };
|
|
6652
|
+
} else {
|
|
6653
|
+
const raw = await readFile2(file, "utf8");
|
|
6654
|
+
const after = await stat5(file);
|
|
6655
|
+
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
6656
|
+
return { json: JSON.parse(raw), complete: true };
|
|
6657
|
+
}
|
|
6658
|
+
} catch {
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return { complete: false };
|
|
6662
|
+
}
|
|
6663
|
+
async function directories(root) {
|
|
6664
|
+
try {
|
|
6665
|
+
const workspaces = await readdir4(root, { withFileTypes: true });
|
|
6666
|
+
const out = [];
|
|
6667
|
+
for (const workspace of workspaces) {
|
|
6668
|
+
if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
|
|
6669
|
+
const workspacePath = path8.join(root, workspace.name);
|
|
6670
|
+
for (const session of await readdir4(workspacePath, {
|
|
6671
|
+
withFileTypes: true
|
|
6672
|
+
})) {
|
|
6673
|
+
if (session.isDirectory() && !session.isSymbolicLink())
|
|
6674
|
+
out.push(path8.join(workspacePath, session.name));
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
return out.sort();
|
|
6678
|
+
} catch (error) {
|
|
6679
|
+
return error.code === "ENOENT" ? [] : null;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
async function modelAliasesForRoot(root) {
|
|
6683
|
+
if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
|
|
6684
|
+
try {
|
|
6685
|
+
const parsed = asObj(
|
|
6686
|
+
parseToml3(await readFile2(path8.join(root, "..", "config.toml"), "utf8"))
|
|
6687
|
+
);
|
|
6688
|
+
if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
|
|
6689
|
+
const models = asObj(parsed.model);
|
|
6690
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
6691
|
+
for (const [alias, raw] of Object.entries(models ?? {})) {
|
|
6692
|
+
const entry = asObj(raw);
|
|
6693
|
+
const model = entry && asStr(entry.model);
|
|
6694
|
+
if (!model || entry?.base_url || entry?.model_provider) continue;
|
|
6695
|
+
aliases.set(alias, model);
|
|
6696
|
+
}
|
|
6697
|
+
return aliases;
|
|
6698
|
+
} catch {
|
|
6699
|
+
return /* @__PURE__ */ new Map();
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
async function scan4(agg, opts = {}) {
|
|
6703
|
+
const stats = emptyScanStats();
|
|
6704
|
+
const sessionDates = /* @__PURE__ */ new Map();
|
|
6705
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
6706
|
+
let complete = true;
|
|
6707
|
+
for (const root of opts.roots ?? sessionRoots()) {
|
|
6708
|
+
const aliases = await modelAliasesForRoot(root);
|
|
6709
|
+
const dirs = await directories(root);
|
|
6710
|
+
if (dirs === null) {
|
|
6711
|
+
complete = false;
|
|
6712
|
+
continue;
|
|
6713
|
+
}
|
|
6714
|
+
for (const dir of dirs) {
|
|
6715
|
+
let entries;
|
|
6716
|
+
try {
|
|
6717
|
+
entries = await readdir4(dir, { withFileTypes: true });
|
|
6718
|
+
} catch {
|
|
6719
|
+
complete = false;
|
|
6720
|
+
continue;
|
|
6721
|
+
}
|
|
6722
|
+
const files = new Map(
|
|
6723
|
+
entries.filter(
|
|
6724
|
+
(e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
|
|
6725
|
+
).map((e) => [e.name, path8.join(dir, e.name)])
|
|
6726
|
+
);
|
|
6727
|
+
let projectDir = dir;
|
|
6728
|
+
let sessionFallback = path8.basename(dir);
|
|
6729
|
+
let child = false;
|
|
6730
|
+
let parentSession;
|
|
6731
|
+
const summary = files.get("summary.json");
|
|
6732
|
+
if (summary) {
|
|
6733
|
+
const read3 = await stableRead(summary, false);
|
|
6734
|
+
if (!read3.complete) {
|
|
6735
|
+
complete = false;
|
|
6736
|
+
stats.filesUnreadable++;
|
|
6737
|
+
continue;
|
|
6738
|
+
}
|
|
6739
|
+
const value = asObj(read3.json);
|
|
6740
|
+
const info = value && asObj(value.info);
|
|
6741
|
+
projectDir = asStr(info?.cwd) ?? asStr(value?.cwd) ?? dir;
|
|
6742
|
+
sessionFallback = asStr(value?.sessionId) ?? sessionFallback;
|
|
6743
|
+
parentSession = asStr(value?.parentSessionId) ?? asStr(value?.parent_session_id) ?? asStr(info?.parentSessionId) ?? asStr(info?.parent_session_id) ?? void 0;
|
|
6744
|
+
child = parentSession !== void 0;
|
|
6745
|
+
}
|
|
6746
|
+
let rows = [];
|
|
6747
|
+
let precedence = 0;
|
|
6748
|
+
const usage = files.get("usage.json");
|
|
6749
|
+
if (usage) {
|
|
6750
|
+
stats.filesFound++;
|
|
6751
|
+
const read3 = await stableRead(usage, false);
|
|
6752
|
+
if (!read3.complete) {
|
|
6753
|
+
complete = false;
|
|
6754
|
+
stats.filesUnreadable++;
|
|
6755
|
+
continue;
|
|
6756
|
+
}
|
|
6757
|
+
stats.filesRead++;
|
|
6758
|
+
rows = sidecarContributions(read3.json, projectDir);
|
|
6759
|
+
if (rows.length > 0) precedence = 2;
|
|
6760
|
+
}
|
|
6761
|
+
if (child) {
|
|
6762
|
+
rows = [];
|
|
6763
|
+
precedence = 0;
|
|
6764
|
+
}
|
|
6765
|
+
const eventState = createGrokEventState(parentSession);
|
|
6766
|
+
const updates = files.get("updates.jsonl");
|
|
6767
|
+
if (updates) {
|
|
6768
|
+
const useTerminalUsage = rows.length === 0 && !child;
|
|
6769
|
+
stats.filesFound++;
|
|
6770
|
+
const read3 = await stableRead(updates, true);
|
|
6771
|
+
if (!read3.complete) {
|
|
6772
|
+
complete = false;
|
|
6773
|
+
stats.filesUnreadable++;
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
6776
|
+
stats.filesRead++;
|
|
6777
|
+
for (const value of read3.lines ?? []) {
|
|
6778
|
+
if (value === null) {
|
|
6779
|
+
agg.parseErrors++;
|
|
6780
|
+
continue;
|
|
6781
|
+
}
|
|
6782
|
+
ingestUpdate(agg, eventState, value, projectDir, opts.sinceMs);
|
|
6783
|
+
if (useTerminalUsage) {
|
|
6784
|
+
const row = terminalContribution(value, projectDir);
|
|
6785
|
+
if (row) rows.push(row);
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
if (rows.length > 0) precedence = 1;
|
|
6789
|
+
}
|
|
6790
|
+
const events = files.get("events.jsonl");
|
|
6791
|
+
if (events) {
|
|
6792
|
+
stats.filesFound++;
|
|
6793
|
+
const read3 = await stableRead(events, true);
|
|
6794
|
+
if (!read3.complete) {
|
|
6795
|
+
complete = false;
|
|
6796
|
+
stats.filesUnreadable++;
|
|
6797
|
+
continue;
|
|
6798
|
+
}
|
|
6799
|
+
stats.filesRead++;
|
|
6800
|
+
for (const value of read3.lines ?? []) {
|
|
6801
|
+
if (value === null) agg.parseErrors++;
|
|
6802
|
+
else
|
|
6803
|
+
ingestEvent2(
|
|
6804
|
+
agg,
|
|
6805
|
+
eventState,
|
|
6806
|
+
value,
|
|
6807
|
+
sessionFallback,
|
|
6808
|
+
projectDir,
|
|
6809
|
+
opts.sinceMs
|
|
6810
|
+
);
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
rows = rows.filter(
|
|
6814
|
+
(row) => opts.sinceMs === void 0 || row.tsMs >= opts.sinceMs
|
|
6815
|
+
);
|
|
6816
|
+
rows = rows.map((row) => ({
|
|
6817
|
+
...row,
|
|
6818
|
+
models: row.models.map(({ model, counts: counts2 }) => ({
|
|
6819
|
+
model: aliases.get(model) ?? model,
|
|
6820
|
+
counts: counts2
|
|
6821
|
+
}))
|
|
6822
|
+
}));
|
|
6823
|
+
if (rows.length > 0) {
|
|
6824
|
+
const sessionId = rows[0]?.sessionId;
|
|
6825
|
+
const held = candidates.get(sessionId);
|
|
6826
|
+
const total = (values) => values.flatMap((value) => value.models).reduce((sum, model) => sum + countsTotal(model.counts), 0);
|
|
6827
|
+
if (!held || precedence > held.precedence || precedence === held.precedence && total(rows) > total(held.rows))
|
|
6828
|
+
candidates.set(sessionId, { precedence, rows });
|
|
6829
|
+
}
|
|
6830
|
+
opts.onProgress?.(stats.filesFound);
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
for (const { rows } of candidates.values()) {
|
|
6834
|
+
for (const row of rows) {
|
|
6835
|
+
ingestContribution(agg, row);
|
|
6836
|
+
const dates = sessionDates.get(row.sessionId) ?? /* @__PURE__ */ new Set();
|
|
6837
|
+
dates.add(new Date(row.tsMs).toISOString().slice(0, 10));
|
|
6838
|
+
sessionDates.set(row.sessionId, dates);
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
return { stats, complete, sessionDates };
|
|
6842
|
+
}
|
|
6843
|
+
|
|
6844
|
+
// src/harness/grok/adapter.ts
|
|
6845
|
+
var GROK_HARNESS_NAME = "grok-build";
|
|
6846
|
+
var GROK_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
6847
|
+
"run_terminal_command",
|
|
6848
|
+
"read_file",
|
|
6849
|
+
"write_file",
|
|
6850
|
+
"search",
|
|
6851
|
+
"web_search"
|
|
6852
|
+
]);
|
|
6853
|
+
var grokAdapter = {
|
|
6854
|
+
name: GROK_HARNESS_NAME,
|
|
6855
|
+
builtinTools: GROK_BUILTIN_TOOLS,
|
|
6856
|
+
detect: (opts) => hasRecentFile(
|
|
6857
|
+
opts.roots ?? sessionRoots(),
|
|
6858
|
+
isGrokEvidenceFile,
|
|
6859
|
+
opts.sinceMs
|
|
6860
|
+
),
|
|
6861
|
+
async scan(opts) {
|
|
6862
|
+
const aggregate = createAggregate4();
|
|
6863
|
+
const result = await scan4(aggregate, opts);
|
|
6864
|
+
return {
|
|
6865
|
+
aggregate,
|
|
6866
|
+
stats: result.stats,
|
|
6867
|
+
workflow: aggregate.workflow.finish(),
|
|
6868
|
+
workflowLocal: aggregate.workflowLocal,
|
|
6869
|
+
scanComplete: result.complete,
|
|
6870
|
+
sessionDates: result.sessionDates
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
};
|
|
6874
|
+
|
|
5469
6875
|
// src/harness/opencode/analyzer.ts
|
|
5470
6876
|
var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
5471
6877
|
"apply_patch",
|
|
@@ -5485,7 +6891,7 @@ var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
5485
6891
|
"websearch",
|
|
5486
6892
|
"write"
|
|
5487
6893
|
]);
|
|
5488
|
-
function
|
|
6894
|
+
function createAggregate5() {
|
|
5489
6895
|
const workflowLocal = createWorkflowLocalSources();
|
|
5490
6896
|
return Object.assign(createAggregate(), {
|
|
5491
6897
|
workflow: createHarnessWorkflowReducer("opencode", workflowLocal),
|
|
@@ -5534,7 +6940,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5534
6940
|
agg.assistantRecords++;
|
|
5535
6941
|
agg.distinctResponses++;
|
|
5536
6942
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
5537
|
-
const
|
|
6943
|
+
const counts2 = {
|
|
5538
6944
|
input: asNum(row.input),
|
|
5539
6945
|
output: asNum(row.output),
|
|
5540
6946
|
cacheWrite5m: 0,
|
|
@@ -5542,7 +6948,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5542
6948
|
cacheWriteUnsplit: asNum(row.cacheWrite),
|
|
5543
6949
|
cacheRead: asNum(row.cacheRead)
|
|
5544
6950
|
};
|
|
5545
|
-
const total =
|
|
6951
|
+
const total = counts2.input + counts2.output + counts2.cacheWriteUnsplit + counts2.cacheRead;
|
|
5546
6952
|
if (session?.parentId) agg.sidechainTokens += total;
|
|
5547
6953
|
else agg.mainTokens += total;
|
|
5548
6954
|
const provider = asStr(row.providerId);
|
|
@@ -5551,8 +6957,8 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5551
6957
|
addModelUsage(
|
|
5552
6958
|
agg,
|
|
5553
6959
|
modelKey,
|
|
5554
|
-
|
|
5555
|
-
apiEquivalentCost(modelKey,
|
|
6960
|
+
counts2,
|
|
6961
|
+
apiEquivalentCost(modelKey, counts2, tsMs),
|
|
5556
6962
|
1,
|
|
5557
6963
|
{ tsMs, sidechain: Boolean(session?.parentId) }
|
|
5558
6964
|
);
|
|
@@ -5567,7 +6973,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5567
6973
|
tsMs,
|
|
5568
6974
|
...provider && model ? { model: `${provider}:${model}` } : {},
|
|
5569
6975
|
thinkingTokens: asNum(row.reasoning),
|
|
5570
|
-
responseTokens:
|
|
6976
|
+
responseTokens: counts2.output,
|
|
5571
6977
|
routingTokens: total,
|
|
5572
6978
|
...completed > tsMs ? { durationSec: (completed - tsMs) / 1e3 } : {}
|
|
5573
6979
|
});
|
|
@@ -5650,14 +7056,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
|
|
|
5650
7056
|
|
|
5651
7057
|
// src/harness/opencode/scan.ts
|
|
5652
7058
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5653
|
-
import { readdir as
|
|
5654
|
-
import { homedir as
|
|
5655
|
-
import
|
|
7059
|
+
import { readdir as readdir5, stat as stat6 } from "node:fs/promises";
|
|
7060
|
+
import { homedir as homedir11 } from "node:os";
|
|
7061
|
+
import path9 from "node:path";
|
|
5656
7062
|
var OPENCODE_MIGRATION_CEILING = 20260622202450;
|
|
5657
7063
|
function opencodeDataDirs() {
|
|
5658
7064
|
const xdg = process.env.XDG_DATA_HOME;
|
|
5659
|
-
const base = xdg ||
|
|
5660
|
-
return [
|
|
7065
|
+
const base = xdg || path9.join(homedir11(), ".local", "share");
|
|
7066
|
+
return [path9.join(base, "opencode")];
|
|
5661
7067
|
}
|
|
5662
7068
|
function isStoreFile(basename2) {
|
|
5663
7069
|
return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
|
|
@@ -5666,8 +7072,8 @@ async function dbFilesIn(root) {
|
|
|
5666
7072
|
const override = process.env.OPENCODE_DB;
|
|
5667
7073
|
if (override) return [override];
|
|
5668
7074
|
try {
|
|
5669
|
-
const entries = await
|
|
5670
|
-
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) =>
|
|
7075
|
+
const entries = await readdir5(root, { withFileTypes: true });
|
|
7076
|
+
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path9.join(root, e.name)).sort();
|
|
5671
7077
|
} catch {
|
|
5672
7078
|
return [];
|
|
5673
7079
|
}
|
|
@@ -5687,7 +7093,7 @@ function errorClass2(e) {
|
|
|
5687
7093
|
return e instanceof Error ? e.constructor.name : "unknown";
|
|
5688
7094
|
}
|
|
5689
7095
|
var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
|
|
5690
|
-
async function
|
|
7096
|
+
async function scan5(agg, opts = {}) {
|
|
5691
7097
|
const stats = emptyScanStats();
|
|
5692
7098
|
const open2 = await loadSqlite();
|
|
5693
7099
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -5704,7 +7110,7 @@ async function scan3(agg, opts = {}) {
|
|
|
5704
7110
|
} catch (e) {
|
|
5705
7111
|
stats.filesUnreadable++;
|
|
5706
7112
|
stats.unreadableFiles.push({
|
|
5707
|
-
path:
|
|
7113
|
+
path: path9.basename(file),
|
|
5708
7114
|
reason: errorClass2(e)
|
|
5709
7115
|
});
|
|
5710
7116
|
}
|
|
@@ -5871,8 +7277,8 @@ function pickTs(jsonTs, columnTs) {
|
|
|
5871
7277
|
}
|
|
5872
7278
|
function opencodeConfigFile() {
|
|
5873
7279
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
5874
|
-
const base = xdg ||
|
|
5875
|
-
return
|
|
7280
|
+
const base = xdg || path9.join(homedir11(), ".config");
|
|
7281
|
+
return path9.join(base, "opencode", "opencode.json");
|
|
5876
7282
|
}
|
|
5877
7283
|
function readConfiguredMcpServers2(agg, state, configFile) {
|
|
5878
7284
|
const file = configFile ?? opencodeConfigFile();
|
|
@@ -5924,7 +7330,7 @@ async function detectOpencode(opts) {
|
|
|
5924
7330
|
if (open2 === null) return false;
|
|
5925
7331
|
for (const root of opts.roots ?? opencodeDataDirs()) {
|
|
5926
7332
|
for (const file of await dbFilesIn(root)) {
|
|
5927
|
-
if (!await
|
|
7333
|
+
if (!await exists4(file)) continue;
|
|
5928
7334
|
let db = null;
|
|
5929
7335
|
try {
|
|
5930
7336
|
db = open2(file);
|
|
@@ -5944,9 +7350,9 @@ async function detectOpencode(opts) {
|
|
|
5944
7350
|
}
|
|
5945
7351
|
return false;
|
|
5946
7352
|
}
|
|
5947
|
-
async function
|
|
7353
|
+
async function exists4(p8) {
|
|
5948
7354
|
try {
|
|
5949
|
-
await
|
|
7355
|
+
await stat6(p8);
|
|
5950
7356
|
return true;
|
|
5951
7357
|
} catch {
|
|
5952
7358
|
return false;
|
|
@@ -5965,8 +7371,8 @@ var opencodeAdapter = {
|
|
|
5965
7371
|
});
|
|
5966
7372
|
},
|
|
5967
7373
|
async scan(opts) {
|
|
5968
|
-
const aggregate =
|
|
5969
|
-
const stats = await
|
|
7374
|
+
const aggregate = createAggregate5();
|
|
7375
|
+
const stats = await scan5(aggregate, {
|
|
5970
7376
|
sinceMs: opts.sinceMs,
|
|
5971
7377
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
5972
7378
|
});
|
|
@@ -5980,7 +7386,7 @@ var opencodeAdapter = {
|
|
|
5980
7386
|
};
|
|
5981
7387
|
|
|
5982
7388
|
// src/harness/pi/analyzer.ts
|
|
5983
|
-
function
|
|
7389
|
+
function createAggregate6() {
|
|
5984
7390
|
const workflowLocal = createWorkflowLocalSources();
|
|
5985
7391
|
return Object.assign(createAggregate(), {
|
|
5986
7392
|
workflow: createHarnessWorkflowReducer("pi-mono", workflowLocal),
|
|
@@ -6049,7 +7455,7 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
6049
7455
|
if (outcome !== "duplicate") {
|
|
6050
7456
|
if (state.sessionId && tsMs !== null) {
|
|
6051
7457
|
const usage = asObj(message.usage);
|
|
6052
|
-
const
|
|
7458
|
+
const counts2 = readCounts2(message.usage);
|
|
6053
7459
|
agg.workflow.ingest({
|
|
6054
7460
|
type: "response",
|
|
6055
7461
|
session: state.sessionId,
|
|
@@ -6058,8 +7464,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
6058
7464
|
tsMs,
|
|
6059
7465
|
...state.modelKey ? { model: state.modelKey } : {},
|
|
6060
7466
|
thinkingTokens: usage ? asNum(usage.reasoning) : 0,
|
|
6061
|
-
responseTokens:
|
|
6062
|
-
routingTokens:
|
|
7467
|
+
responseTokens: counts2?.output ?? 0,
|
|
7468
|
+
routingTokens: counts2 ? countsTotal(counts2) : 0
|
|
6063
7469
|
});
|
|
6064
7470
|
ingestContent(agg, message.content, state.sessionId, state.cwd, tsMs);
|
|
6065
7471
|
agg.workflow.ingest({
|
|
@@ -6086,9 +7492,9 @@ function noteActivity2(agg, state, tsMs) {
|
|
|
6086
7492
|
agg.projectDirs.add(state.cwd ?? "(unknown)");
|
|
6087
7493
|
}
|
|
6088
7494
|
function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable = true) {
|
|
6089
|
-
const
|
|
6090
|
-
if (!
|
|
6091
|
-
const total = countsTotal(
|
|
7495
|
+
const counts2 = readCounts2(usageRaw);
|
|
7496
|
+
if (!counts2) return "none";
|
|
7497
|
+
const total = countsTotal(counts2);
|
|
6092
7498
|
if (total === 0) return "none";
|
|
6093
7499
|
const id = asStr(rec.id);
|
|
6094
7500
|
if (id) {
|
|
@@ -6107,8 +7513,8 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
|
|
|
6107
7513
|
addModelUsage(
|
|
6108
7514
|
agg,
|
|
6109
7515
|
key2,
|
|
6110
|
-
|
|
6111
|
-
priceable ? apiEquivalentCost(key2,
|
|
7516
|
+
counts2,
|
|
7517
|
+
priceable ? apiEquivalentCost(key2, counts2, tsMs) : null,
|
|
6112
7518
|
1,
|
|
6113
7519
|
{ tsMs }
|
|
6114
7520
|
);
|
|
@@ -6173,18 +7579,18 @@ function readCounts2(usageRaw) {
|
|
|
6173
7579
|
}
|
|
6174
7580
|
|
|
6175
7581
|
// src/harness/pi/scan.ts
|
|
6176
|
-
import { createReadStream as
|
|
6177
|
-
import { readdir as
|
|
6178
|
-
import { homedir as
|
|
6179
|
-
import
|
|
6180
|
-
import
|
|
7582
|
+
import { createReadStream as createReadStream3 } from "node:fs";
|
|
7583
|
+
import { readdir as readdir6, realpath as realpath3, stat as stat7 } from "node:fs/promises";
|
|
7584
|
+
import { homedir as homedir12 } from "node:os";
|
|
7585
|
+
import path10 from "node:path";
|
|
7586
|
+
import readline3 from "node:readline";
|
|
6181
7587
|
var MAX_SESSION_VERSION = 3;
|
|
6182
7588
|
function piAgentDir() {
|
|
6183
|
-
return process.env.PI_CODING_AGENT_DIR ||
|
|
7589
|
+
return process.env.PI_CODING_AGENT_DIR || path10.join(homedir12(), ".pi", "agent");
|
|
6184
7590
|
}
|
|
6185
|
-
function
|
|
7591
|
+
function sessionRoots2() {
|
|
6186
7592
|
const override = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
6187
|
-
return [override ||
|
|
7593
|
+
return [override || path10.join(piAgentDir(), "sessions")];
|
|
6188
7594
|
}
|
|
6189
7595
|
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$/;
|
|
6190
7596
|
function isSessionFile(basename2) {
|
|
@@ -6193,22 +7599,22 @@ function isSessionFile(basename2) {
|
|
|
6193
7599
|
async function* walkSessions(dir) {
|
|
6194
7600
|
let entries;
|
|
6195
7601
|
try {
|
|
6196
|
-
entries = await
|
|
7602
|
+
entries = await readdir6(dir, { withFileTypes: true });
|
|
6197
7603
|
} catch {
|
|
6198
7604
|
return;
|
|
6199
7605
|
}
|
|
6200
7606
|
for (const e of entries) {
|
|
6201
|
-
const full =
|
|
7607
|
+
const full = path10.join(dir, e.name);
|
|
6202
7608
|
if (e.isDirectory()) yield* walkSessions(full);
|
|
6203
7609
|
else if (e.isFile() && isSessionFile(e.name)) yield full;
|
|
6204
7610
|
}
|
|
6205
7611
|
}
|
|
6206
|
-
async function
|
|
7612
|
+
async function scan6(agg, opts = {}) {
|
|
6207
7613
|
const stats = emptyScanStats();
|
|
6208
7614
|
const visited = /* @__PURE__ */ new Set();
|
|
6209
7615
|
const fold = createFoldState();
|
|
6210
|
-
for (const root of opts.roots ??
|
|
6211
|
-
if (!await
|
|
7616
|
+
for (const root of opts.roots ?? sessionRoots2()) {
|
|
7617
|
+
if (!await exists5(root)) continue;
|
|
6212
7618
|
for await (const file of walkSessions(root)) {
|
|
6213
7619
|
stats.filesFound++;
|
|
6214
7620
|
let resolved;
|
|
@@ -6224,7 +7630,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6224
7630
|
visited.add(resolved);
|
|
6225
7631
|
if (opts.sinceMs !== void 0) {
|
|
6226
7632
|
try {
|
|
6227
|
-
const st = await
|
|
7633
|
+
const st = await stat7(file);
|
|
6228
7634
|
if (st.mtimeMs < opts.sinceMs) {
|
|
6229
7635
|
stats.filesSkippedByMtime++;
|
|
6230
7636
|
continue;
|
|
@@ -6246,7 +7652,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6246
7652
|
stats.filesUnreadable++;
|
|
6247
7653
|
stats.filesRead--;
|
|
6248
7654
|
stats.unreadableFiles.push({
|
|
6249
|
-
path:
|
|
7655
|
+
path: path10.relative(root, file),
|
|
6250
7656
|
reason: "version-too-new"
|
|
6251
7657
|
});
|
|
6252
7658
|
}
|
|
@@ -6254,7 +7660,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6254
7660
|
stats.filesUnreadable++;
|
|
6255
7661
|
stats.filesRead--;
|
|
6256
7662
|
stats.unreadableFiles.push({
|
|
6257
|
-
path:
|
|
7663
|
+
path: path10.relative(root, file),
|
|
6258
7664
|
reason: "read-error"
|
|
6259
7665
|
});
|
|
6260
7666
|
}
|
|
@@ -6262,17 +7668,17 @@ async function scan4(agg, opts = {}) {
|
|
|
6262
7668
|
}
|
|
6263
7669
|
return stats;
|
|
6264
7670
|
}
|
|
6265
|
-
async function
|
|
7671
|
+
async function exists5(p8) {
|
|
6266
7672
|
try {
|
|
6267
|
-
await
|
|
7673
|
+
await stat7(p8);
|
|
6268
7674
|
return true;
|
|
6269
7675
|
} catch {
|
|
6270
7676
|
return false;
|
|
6271
7677
|
}
|
|
6272
7678
|
}
|
|
6273
7679
|
async function ingestFile3(agg, fold, file, sinceMs) {
|
|
6274
|
-
const rl =
|
|
6275
|
-
input:
|
|
7680
|
+
const rl = readline3.createInterface({
|
|
7681
|
+
input: createReadStream3(file, { encoding: "utf8" }),
|
|
6276
7682
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
6277
7683
|
});
|
|
6278
7684
|
const state = createFileState2();
|
|
@@ -6325,14 +7731,14 @@ var piAdapter = {
|
|
|
6325
7731
|
builtinTools: PI_BUILTIN_TOOLS,
|
|
6326
7732
|
async detect(opts) {
|
|
6327
7733
|
return hasRecentFile(
|
|
6328
|
-
opts.roots ??
|
|
7734
|
+
opts.roots ?? sessionRoots2(),
|
|
6329
7735
|
isSessionFile,
|
|
6330
7736
|
opts.sinceMs
|
|
6331
7737
|
);
|
|
6332
7738
|
},
|
|
6333
7739
|
async scan(opts) {
|
|
6334
|
-
const aggregate =
|
|
6335
|
-
const stats = await
|
|
7740
|
+
const aggregate = createAggregate6();
|
|
7741
|
+
const stats = await scan6(aggregate, {
|
|
6336
7742
|
sinceMs: opts.sinceMs,
|
|
6337
7743
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6338
7744
|
});
|
|
@@ -6349,12 +7755,16 @@ var piAdapter = {
|
|
|
6349
7755
|
var HARNESS_ADAPTERS = [
|
|
6350
7756
|
claudeAdapter,
|
|
6351
7757
|
codexAdapter,
|
|
7758
|
+
grokAdapter,
|
|
7759
|
+
cursorAdapter,
|
|
6352
7760
|
opencodeAdapter,
|
|
6353
7761
|
piAdapter
|
|
6354
7762
|
];
|
|
6355
7763
|
function harnessLabel2(name) {
|
|
6356
7764
|
if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
|
|
6357
7765
|
if (name === CODEX_HARNESS_NAME) return "Codex";
|
|
7766
|
+
if (name === CURSOR_HARNESS_NAME) return "Cursor";
|
|
7767
|
+
if (name === GROK_HARNESS_NAME) return "Grok Build";
|
|
6358
7768
|
if (name === OPENCODE_HARNESS_NAME) return "opencode";
|
|
6359
7769
|
if (name === PI_HARNESS_NAME) return "Pi";
|
|
6360
7770
|
return name;
|
|
@@ -6388,7 +7798,7 @@ var MCP_ADD_ARGS = [
|
|
|
6388
7798
|
"mcp"
|
|
6389
7799
|
];
|
|
6390
7800
|
var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
|
|
6391
|
-
var SKILL_DEST = join6(
|
|
7801
|
+
var SKILL_DEST = join6(homedir13(), ".claude", "skills", "aistack-sync");
|
|
6392
7802
|
function runClaude(args) {
|
|
6393
7803
|
const r = spawnSync("claude", args, { encoding: "utf-8" });
|
|
6394
7804
|
const notFound = r.error !== void 0 && r.error.code === "ENOENT";
|
|
@@ -6621,9 +8031,9 @@ async function createCommand() {
|
|
|
6621
8031
|
import { hostname } from "node:os";
|
|
6622
8032
|
import * as p5 from "@clack/prompts";
|
|
6623
8033
|
import open from "open";
|
|
6624
|
-
function proposedMachineName(
|
|
8034
|
+
function proposedMachineName(read3 = hostname) {
|
|
6625
8035
|
try {
|
|
6626
|
-
const name =
|
|
8036
|
+
const name = read3().trim().replace(/\.local$/i, "");
|
|
6627
8037
|
if (!name || name.length > 64) return void 0;
|
|
6628
8038
|
return name;
|
|
6629
8039
|
} catch {
|
|
@@ -6710,13 +8120,13 @@ async function loginCommand(options = {}) {
|
|
|
6710
8120
|
import * as p7 from "@clack/prompts";
|
|
6711
8121
|
|
|
6712
8122
|
// src/autosync/codexHook.ts
|
|
6713
|
-
import { createHash } from "node:crypto";
|
|
8123
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6714
8124
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6715
|
-
import { homedir as
|
|
8125
|
+
import { homedir as homedir14 } from "node:os";
|
|
6716
8126
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
6717
8127
|
import { parse } from "smol-toml";
|
|
6718
8128
|
function codexHome2() {
|
|
6719
|
-
return process.env.CODEX_HOME || join8(
|
|
8129
|
+
return process.env.CODEX_HOME || join8(homedir14(), ".codex");
|
|
6720
8130
|
}
|
|
6721
8131
|
function codexHooksFile() {
|
|
6722
8132
|
return join8(codexHome2(), "hooks.json");
|
|
@@ -6742,9 +8152,9 @@ function readHooksJson(file) {
|
|
|
6742
8152
|
}
|
|
6743
8153
|
var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
|
|
6744
8154
|
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
6745
|
-
const
|
|
6746
|
-
if ("error" in
|
|
6747
|
-
const settings =
|
|
8155
|
+
const read3 = readHooksJson(file);
|
|
8156
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8157
|
+
const settings = read3.settings;
|
|
6748
8158
|
const hooks = settings.hooks ?? {};
|
|
6749
8159
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
6750
8160
|
const kept = sessionStart.map((m) => ({
|
|
@@ -6764,9 +8174,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
6764
8174
|
function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
6765
8175
|
if (!existsSync8(file))
|
|
6766
8176
|
return { ok: true, message: "no Codex hook to remove" };
|
|
6767
|
-
const
|
|
6768
|
-
if ("error" in
|
|
6769
|
-
const settings =
|
|
8177
|
+
const read3 = readHooksJson(file);
|
|
8178
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8179
|
+
const settings = read3.settings;
|
|
6770
8180
|
const sessionStart = settings.hooks?.SessionStart;
|
|
6771
8181
|
if (!Array.isArray(sessionStart)) {
|
|
6772
8182
|
return { ok: true, message: "no Codex hook to remove" };
|
|
@@ -6791,18 +8201,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
6791
8201
|
return { ok: true, message: `hook removed from ${file}` };
|
|
6792
8202
|
}
|
|
6793
8203
|
function codexAutoSyncHookInstalled(file = codexHooksFile()) {
|
|
6794
|
-
const
|
|
6795
|
-
if ("error" in
|
|
6796
|
-
const sessionStart =
|
|
8204
|
+
const read3 = readHooksJson(file);
|
|
8205
|
+
if ("error" in read3) return false;
|
|
8206
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
6797
8207
|
if (!Array.isArray(sessionStart)) return false;
|
|
6798
8208
|
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
|
|
6799
8209
|
}
|
|
6800
8210
|
function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
|
|
6801
8211
|
try {
|
|
6802
8212
|
const config = parse(readFileSync9(configFile, "utf-8"));
|
|
6803
|
-
const
|
|
6804
|
-
if ("error" in
|
|
6805
|
-
const sessionStart =
|
|
8213
|
+
const read3 = readHooksJson(hooksFile);
|
|
8214
|
+
if ("error" in read3) return null;
|
|
8215
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
6806
8216
|
if (!Array.isArray(sessionStart)) return false;
|
|
6807
8217
|
const matches = [];
|
|
6808
8218
|
for (const [groupIndex, group] of sessionStart.entries()) {
|
|
@@ -6825,7 +8235,7 @@ function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooks
|
|
|
6825
8235
|
hooks: [normalizedHandler]
|
|
6826
8236
|
};
|
|
6827
8237
|
if (typeof group.matcher === "string") identity.matcher = group.matcher;
|
|
6828
|
-
const currentHash = `sha256:${
|
|
8238
|
+
const currentHash = `sha256:${createHash2("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
|
|
6829
8239
|
const key2 = `${hooksFile}:session_start:${groupIndex}:${handlerIndex}`;
|
|
6830
8240
|
matches.push(config.hooks?.state?.[key2]?.trusted_hash === currentHash);
|
|
6831
8241
|
}
|
|
@@ -6855,11 +8265,16 @@ function canonicalJson2(value) {
|
|
|
6855
8265
|
// src/autosync/optin.ts
|
|
6856
8266
|
import * as p6 from "@clack/prompts";
|
|
6857
8267
|
|
|
8268
|
+
// src/autosync/cursorHook.ts
|
|
8269
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8270
|
+
import { homedir as homedir16, platform as platform2 } from "node:os";
|
|
8271
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
8272
|
+
|
|
6858
8273
|
// src/autosync/hook.ts
|
|
6859
8274
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6860
|
-
import { homedir as
|
|
8275
|
+
import { homedir as homedir15 } from "node:os";
|
|
6861
8276
|
import { dirname as dirname6, join as join9 } from "node:path";
|
|
6862
|
-
var CLAUDE_SETTINGS_FILE = join9(
|
|
8277
|
+
var CLAUDE_SETTINGS_FILE = join9(homedir15(), ".claude", "settings.json");
|
|
6863
8278
|
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
6864
8279
|
function isOurs2(entry) {
|
|
6865
8280
|
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
@@ -6877,9 +8292,9 @@ function readClaudeSettings(file) {
|
|
|
6877
8292
|
}
|
|
6878
8293
|
}
|
|
6879
8294
|
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
6880
|
-
const
|
|
6881
|
-
if ("error" in
|
|
6882
|
-
const settings =
|
|
8295
|
+
const read3 = readClaudeSettings(file);
|
|
8296
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8297
|
+
const settings = read3.settings;
|
|
6883
8298
|
const hooks = settings.hooks ?? {};
|
|
6884
8299
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
6885
8300
|
const kept = sessionStart.map((m) => ({
|
|
@@ -6897,9 +8312,9 @@ function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
6897
8312
|
}
|
|
6898
8313
|
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
6899
8314
|
if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
|
|
6900
|
-
const
|
|
6901
|
-
if ("error" in
|
|
6902
|
-
const settings =
|
|
8315
|
+
const read3 = readClaudeSettings(file);
|
|
8316
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8317
|
+
const settings = read3.settings;
|
|
6903
8318
|
const sessionStart = settings.hooks?.SessionStart;
|
|
6904
8319
|
if (!Array.isArray(sessionStart)) {
|
|
6905
8320
|
return { ok: true, message: "no hook to remove" };
|
|
@@ -6924,16 +8339,201 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
6924
8339
|
return { ok: true, message: `hook removed from ${file}` };
|
|
6925
8340
|
}
|
|
6926
8341
|
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
6927
|
-
const
|
|
6928
|
-
if ("error" in
|
|
6929
|
-
const sessionStart =
|
|
8342
|
+
const read3 = readClaudeSettings(file);
|
|
8343
|
+
if ("error" in read3) return false;
|
|
8344
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
6930
8345
|
if (!Array.isArray(sessionStart)) return false;
|
|
6931
8346
|
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs2(h)));
|
|
6932
8347
|
}
|
|
6933
8348
|
|
|
8349
|
+
// src/autosync/cursorHook.ts
|
|
8350
|
+
var CURSOR_HOOK_FILE = join10(homedir16(), ".cursor", "hooks.json");
|
|
8351
|
+
function cursorHookCommand(os = platform2()) {
|
|
8352
|
+
if (os === "win32") {
|
|
8353
|
+
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"'`;
|
|
8354
|
+
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script, "utf16le").toString("base64")}`;
|
|
8355
|
+
}
|
|
8356
|
+
const detach = os === "darwin" ? "nohup" : "setsid nohup";
|
|
8357
|
+
return `${detach} sh -c 'export AISTACK_HOOK_SOURCE=cursor; ${AUTO_SYNC_HOOK_COMMAND}' </dev/null >/dev/null 2>&1 &`;
|
|
8358
|
+
}
|
|
8359
|
+
function read(file) {
|
|
8360
|
+
if (!existsSync10(file)) return {};
|
|
8361
|
+
const value = JSON.parse(readFileSync11(file, "utf8"));
|
|
8362
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8363
|
+
throw new Error("expected a JSON object");
|
|
8364
|
+
const config = value;
|
|
8365
|
+
if (config.version !== void 0 && config.version !== 1)
|
|
8366
|
+
throw new Error("unsupported hooks version");
|
|
8367
|
+
if (config.hooks !== void 0 && (!config.hooks || typeof config.hooks !== "object" || Array.isArray(config.hooks) || Object.values(config.hooks).some(
|
|
8368
|
+
(entries) => !Array.isArray(entries) || entries.some(
|
|
8369
|
+
(entry) => !entry || typeof entry !== "object" || Array.isArray(entry)
|
|
8370
|
+
)
|
|
8371
|
+
)))
|
|
8372
|
+
throw new Error("malformed hooks object");
|
|
8373
|
+
return config;
|
|
8374
|
+
}
|
|
8375
|
+
function isOurs3(entry) {
|
|
8376
|
+
return ["linux", "darwin", "win32"].some(
|
|
8377
|
+
(os) => entry.command === cursorHookCommand(os)
|
|
8378
|
+
);
|
|
8379
|
+
}
|
|
8380
|
+
function update(file, install, os) {
|
|
8381
|
+
try {
|
|
8382
|
+
const config = read(file);
|
|
8383
|
+
const existing = config.hooks?.stop ?? [];
|
|
8384
|
+
const kept = existing.filter((entry) => !isOurs3(entry));
|
|
8385
|
+
if (!install && kept.length === existing.length)
|
|
8386
|
+
return { ok: true, message: "no Cursor hook to remove" };
|
|
8387
|
+
if (install) kept.push({ command: cursorHookCommand(os) });
|
|
8388
|
+
const hooks = { ...config.hooks };
|
|
8389
|
+
if (kept.length) hooks.stop = kept;
|
|
8390
|
+
else delete hooks.stop;
|
|
8391
|
+
if (Object.keys(hooks).length) config.hooks = hooks;
|
|
8392
|
+
else delete config.hooks;
|
|
8393
|
+
if (install) config.version = 1;
|
|
8394
|
+
mkdirSync5(dirname7(file), { recursive: true });
|
|
8395
|
+
writeFileSync5(file, `${JSON.stringify(config, null, 2)}
|
|
8396
|
+
`);
|
|
8397
|
+
return {
|
|
8398
|
+
ok: true,
|
|
8399
|
+
message: `Cursor stop hook ${install ? "written to" : "removed from"} ${file}`
|
|
8400
|
+
};
|
|
8401
|
+
} catch (error) {
|
|
8402
|
+
return {
|
|
8403
|
+
ok: false,
|
|
8404
|
+
message: `Could not ${install ? "install" : "remove"} Cursor hook in ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
8405
|
+
};
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8408
|
+
function installCursorAutoSyncHook(file = CURSOR_HOOK_FILE, os = platform2()) {
|
|
8409
|
+
return update(file, true, os);
|
|
8410
|
+
}
|
|
8411
|
+
function removeCursorAutoSyncHook(file = CURSOR_HOOK_FILE) {
|
|
8412
|
+
return update(file, false, platform2());
|
|
8413
|
+
}
|
|
8414
|
+
function cursorAutoSyncHookInstalled(file = CURSOR_HOOK_FILE) {
|
|
8415
|
+
try {
|
|
8416
|
+
return (read(file).hooks?.stop ?? []).some(isOurs3);
|
|
8417
|
+
} catch {
|
|
8418
|
+
return false;
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8421
|
+
|
|
8422
|
+
// src/autosync/grokHook.ts
|
|
8423
|
+
import {
|
|
8424
|
+
existsSync as existsSync11,
|
|
8425
|
+
mkdirSync as mkdirSync6,
|
|
8426
|
+
readFileSync as readFileSync12,
|
|
8427
|
+
unlinkSync,
|
|
8428
|
+
writeFileSync as writeFileSync6
|
|
8429
|
+
} from "node:fs";
|
|
8430
|
+
import { homedir as homedir17, platform as platform3 } from "node:os";
|
|
8431
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
8432
|
+
var GROK_HOOK_FILE = join11(
|
|
8433
|
+
process.env.GROK_HOME || join11(homedir17(), ".grok"),
|
|
8434
|
+
"hooks",
|
|
8435
|
+
"aistack.json"
|
|
8436
|
+
);
|
|
8437
|
+
var SYNC_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
8438
|
+
function grokHookCommand(os = platform3()) {
|
|
8439
|
+
if (os === "win32") {
|
|
8440
|
+
return `Start-Process -WindowStyle Hidden -FilePath "cmd.exe" -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=grok&& (${SYNC_COMMAND}) >NUL 2>&1"'`;
|
|
8441
|
+
}
|
|
8442
|
+
if (os === "darwin") {
|
|
8443
|
+
return `nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' </dev/null >/dev/null 2>&1 &`;
|
|
8444
|
+
}
|
|
8445
|
+
return `setsid nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' >/dev/null 2>&1 &`;
|
|
8446
|
+
}
|
|
8447
|
+
function readHookFile(file) {
|
|
8448
|
+
if (!existsSync11(file)) return { value: {} };
|
|
8449
|
+
try {
|
|
8450
|
+
const value = JSON.parse(readFileSync12(file, "utf-8"));
|
|
8451
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
8452
|
+
const candidate = value;
|
|
8453
|
+
if (candidate.hooks !== void 0 && (!candidate.hooks || typeof candidate.hooks !== "object" || Array.isArray(candidate.hooks) || Object.values(candidate.hooks).some(
|
|
8454
|
+
(groups) => !Array.isArray(groups)
|
|
8455
|
+
))) {
|
|
8456
|
+
return {
|
|
8457
|
+
error: `${file} has a malformed hooks object. Fix it, then retry.`
|
|
8458
|
+
};
|
|
8459
|
+
}
|
|
8460
|
+
return { value: candidate };
|
|
8461
|
+
}
|
|
8462
|
+
} catch {
|
|
8463
|
+
return { error: `${file} is not valid JSON. Fix it, then retry.` };
|
|
8464
|
+
}
|
|
8465
|
+
return { error: `${file} does not hold a JSON object` };
|
|
8466
|
+
}
|
|
8467
|
+
function isOurs4(entry) {
|
|
8468
|
+
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
8469
|
+
}
|
|
8470
|
+
function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform3()) {
|
|
8471
|
+
const read3 = readHookFile(file);
|
|
8472
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8473
|
+
const foreignKeys = Object.keys(read3.value).filter((key2) => key2 !== "hooks");
|
|
8474
|
+
const foreignEvents = Object.keys(read3.value.hooks ?? {}).filter(
|
|
8475
|
+
(key2) => key2 !== "SessionStart"
|
|
8476
|
+
);
|
|
8477
|
+
const sessionStart = read3.value.hooks?.SessionStart ?? [];
|
|
8478
|
+
const foreignHandlers = sessionStart.flatMap(
|
|
8479
|
+
(group) => (group.hooks ?? []).filter((entry) => !isOurs4(entry))
|
|
8480
|
+
);
|
|
8481
|
+
if (foreignKeys.length > 0 || foreignEvents.length > 0 || foreignHandlers.length > 0) {
|
|
8482
|
+
return {
|
|
8483
|
+
ok: false,
|
|
8484
|
+
message: `${file} contains hooks not owned by AI Stack. Move them to another Grok hook file, then retry.`
|
|
8485
|
+
};
|
|
8486
|
+
}
|
|
8487
|
+
const value = {
|
|
8488
|
+
hooks: {
|
|
8489
|
+
SessionStart: [
|
|
8490
|
+
{
|
|
8491
|
+
hooks: [
|
|
8492
|
+
{ type: "command", command: grokHookCommand(os), timeout: 5 }
|
|
8493
|
+
]
|
|
8494
|
+
}
|
|
8495
|
+
]
|
|
8496
|
+
}
|
|
8497
|
+
};
|
|
8498
|
+
mkdirSync6(dirname8(file), { recursive: true });
|
|
8499
|
+
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
8500
|
+
`);
|
|
8501
|
+
return {
|
|
8502
|
+
ok: true,
|
|
8503
|
+
message: `Grok Build SessionStart hook written to ${file}. Start a new Grok session or reload hooks before expecting it to run.`
|
|
8504
|
+
};
|
|
8505
|
+
}
|
|
8506
|
+
function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
|
|
8507
|
+
if (!existsSync11(file))
|
|
8508
|
+
return { ok: true, message: "no Grok Build hook to remove" };
|
|
8509
|
+
const read3 = readHookFile(file);
|
|
8510
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8511
|
+
const entries = read3.value.hooks?.SessionStart ?? [];
|
|
8512
|
+
const onlyOurs = Object.keys(read3.value).every((key2) => key2 === "hooks") && Object.keys(read3.value.hooks ?? {}).every(
|
|
8513
|
+
(key2) => key2 === "SessionStart"
|
|
8514
|
+
) && entries.every(
|
|
8515
|
+
(group) => (group.hooks ?? []).every((entry) => isOurs4(entry))
|
|
8516
|
+
);
|
|
8517
|
+
if (!onlyOurs) {
|
|
8518
|
+
return {
|
|
8519
|
+
ok: false,
|
|
8520
|
+
message: `${file} contains hooks not owned by AI Stack and was not removed.`
|
|
8521
|
+
};
|
|
8522
|
+
}
|
|
8523
|
+
unlinkSync(file);
|
|
8524
|
+
return { ok: true, message: `Grok Build hook removed from ${file}` };
|
|
8525
|
+
}
|
|
8526
|
+
function grokAutoSyncHookInstalled(file = GROK_HOOK_FILE) {
|
|
8527
|
+
const read3 = readHookFile(file);
|
|
8528
|
+
if ("error" in read3) return false;
|
|
8529
|
+
return (read3.value.hooks?.SessionStart ?? []).some(
|
|
8530
|
+
(group) => (group.hooks ?? []).some((entry) => isOurs4(entry))
|
|
8531
|
+
);
|
|
8532
|
+
}
|
|
8533
|
+
|
|
6934
8534
|
// src/autosync/optin.ts
|
|
6935
8535
|
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.";
|
|
6936
|
-
var NOTHING_TO_TRIGGER = `No
|
|
8536
|
+
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.`;
|
|
6937
8537
|
async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
|
|
6938
8538
|
frequencyHours = normalizeFrequencyHours(frequencyHours);
|
|
6939
8539
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
@@ -6966,6 +8566,14 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
6966
8566
|
trustLine = codexResult.message;
|
|
6967
8567
|
}
|
|
6968
8568
|
}
|
|
8569
|
+
if (names.has(GROK_HARNESS_NAME)) {
|
|
8570
|
+
const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
|
|
8571
|
+
if (!grokResult.ok) return grokResult;
|
|
8572
|
+
}
|
|
8573
|
+
if (names.has(CURSOR_HARNESS_NAME)) {
|
|
8574
|
+
const result = (deps.installCursorHook ?? installCursorAutoSyncHook)();
|
|
8575
|
+
if (!result.ok) return result;
|
|
8576
|
+
}
|
|
6969
8577
|
saveSettings(
|
|
6970
8578
|
{
|
|
6971
8579
|
autoSyncAnswered: true,
|
|
@@ -6976,7 +8584,7 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
6976
8584
|
return {
|
|
6977
8585
|
ok: true,
|
|
6978
8586
|
message: [
|
|
6979
|
-
`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`,
|
|
8587
|
+
`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`,
|
|
6980
8588
|
...trustLine ? [trustLine] : []
|
|
6981
8589
|
].join("\n")
|
|
6982
8590
|
};
|
|
@@ -6997,7 +8605,9 @@ async function disableAutoSync(deps = {}) {
|
|
|
6997
8605
|
);
|
|
6998
8606
|
const result = (deps.removeHook ?? removeAutoSyncHook)();
|
|
6999
8607
|
const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
|
|
7000
|
-
const
|
|
8608
|
+
const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
|
|
8609
|
+
const cursorResult = (deps.removeCursorHook ?? removeCursorAutoSyncHook)();
|
|
8610
|
+
const failures = [result, codexResult, grokResult, cursorResult].filter((r) => !r.ok).map((r) => r.message);
|
|
7001
8611
|
const token = (deps.getTokenImpl ?? getToken)();
|
|
7002
8612
|
if (token !== null) {
|
|
7003
8613
|
try {
|
|
@@ -7020,7 +8630,41 @@ async function disableAutoSync(deps = {}) {
|
|
|
7020
8630
|
};
|
|
7021
8631
|
}
|
|
7022
8632
|
async function reconcileAutoSync(permission, deps = {}) {
|
|
7023
|
-
if (permission
|
|
8633
|
+
if (permission === null) return null;
|
|
8634
|
+
if (permission.enabled !== true) {
|
|
8635
|
+
const settings = getSettings(deps.settingsFile);
|
|
8636
|
+
saveSettings(
|
|
8637
|
+
{
|
|
8638
|
+
autoSyncAnswered: true,
|
|
8639
|
+
autoSync: {
|
|
8640
|
+
enabled: false,
|
|
8641
|
+
frequencyHours: normalizeFrequencyHours(
|
|
8642
|
+
permission.frequencyHours ?? settings.autoSync?.frequencyHours
|
|
8643
|
+
)
|
|
8644
|
+
}
|
|
8645
|
+
},
|
|
8646
|
+
deps.settingsFile
|
|
8647
|
+
);
|
|
8648
|
+
const results = [
|
|
8649
|
+
(deps.removeHook ?? removeAutoSyncHook)(),
|
|
8650
|
+
(deps.removeCodexHook ?? removeCodexAutoSyncHook)(),
|
|
8651
|
+
(deps.removeGrokHook ?? removeGrokAutoSyncHook)(),
|
|
8652
|
+
(deps.removeCursorHook ?? removeCursorAutoSyncHook)()
|
|
8653
|
+
];
|
|
8654
|
+
const failures2 = results.filter((result) => !result.ok);
|
|
8655
|
+
if (failures2.length > 0) {
|
|
8656
|
+
return {
|
|
8657
|
+
ok: false,
|
|
8658
|
+
message: `Auto-sync is off on this machine, but a trigger could not be removed: ${failures2.map((result) => result.message).join("; ")}. The next interactive sync will retry.`
|
|
8659
|
+
};
|
|
8660
|
+
}
|
|
8661
|
+
const changed = settings.autoSync?.enabled !== false || results.some((result) => !result.message.toLowerCase().startsWith("no "));
|
|
8662
|
+
if (!changed) return null;
|
|
8663
|
+
return {
|
|
8664
|
+
ok: true,
|
|
8665
|
+
message: "Auto-sync is off on this machine. Removed its local triggers."
|
|
8666
|
+
};
|
|
8667
|
+
}
|
|
7024
8668
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
7025
8669
|
if (detected.length === 0) return null;
|
|
7026
8670
|
const names = new Set(detected.map((a) => a.name));
|
|
@@ -7042,6 +8686,16 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7042
8686
|
deps.codexHookInstalledImpl ?? codexAutoSyncHookInstalled,
|
|
7043
8687
|
deps.installCodexHook ?? installCodexAutoSyncHook
|
|
7044
8688
|
);
|
|
8689
|
+
install(
|
|
8690
|
+
GROK_HARNESS_NAME,
|
|
8691
|
+
deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
|
|
8692
|
+
deps.installGrokHook ?? installGrokAutoSyncHook
|
|
8693
|
+
);
|
|
8694
|
+
install(
|
|
8695
|
+
CURSOR_HARNESS_NAME,
|
|
8696
|
+
deps.cursorHookInstalledImpl ?? cursorAutoSyncHookInstalled,
|
|
8697
|
+
deps.installCursorHook ?? installCursorAutoSyncHook
|
|
8698
|
+
);
|
|
7045
8699
|
if (failures.length > 0) {
|
|
7046
8700
|
return {
|
|
7047
8701
|
ok: false,
|
|
@@ -7060,7 +8714,7 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7060
8714
|
if (installed.length === 0 && mirrored) return null;
|
|
7061
8715
|
return {
|
|
7062
8716
|
ok: true,
|
|
7063
|
-
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
|
|
8717
|
+
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"}.`
|
|
7064
8718
|
};
|
|
7065
8719
|
}
|
|
7066
8720
|
async function settleAutoSync(permission, deps = {}) {
|
|
@@ -7078,13 +8732,14 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7078
8732
|
return false;
|
|
7079
8733
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
7080
8734
|
if (detected.length === 0) return false;
|
|
8735
|
+
const names = new Set(detected.map((adapter) => adapter.name));
|
|
7081
8736
|
const answer = await p6.select({
|
|
7082
8737
|
message: "Keep this stack fresh automatically every 6 hours?",
|
|
7083
8738
|
options: [
|
|
7084
8739
|
{
|
|
7085
8740
|
value: "enable",
|
|
7086
8741
|
label: "Enable",
|
|
7087
|
-
hint: `a silent sync at most every 6 hours when a ${harnessListLabel(detected)} session starts`
|
|
8742
|
+
hint: `a silent sync at most every 6 hours when a ${harnessListLabel(detected)} ${names.has(CURSOR_HARNESS_NAME) ? "session is active" : "session starts"}`
|
|
7088
8743
|
},
|
|
7089
8744
|
{
|
|
7090
8745
|
value: "later",
|
|
@@ -7126,15 +8781,18 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7126
8781
|
// src/autosync/run.ts
|
|
7127
8782
|
import {
|
|
7128
8783
|
appendFileSync,
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
8784
|
+
closeSync,
|
|
8785
|
+
mkdirSync as mkdirSync8,
|
|
8786
|
+
openSync,
|
|
8787
|
+
readFileSync as readFileSync14,
|
|
8788
|
+
unlinkSync as unlinkSync2,
|
|
8789
|
+
writeFileSync as writeFileSync8
|
|
7132
8790
|
} from "node:fs";
|
|
7133
|
-
import { homedir as
|
|
7134
|
-
import { dirname as
|
|
8791
|
+
import { homedir as homedir19 } from "node:os";
|
|
8792
|
+
import { dirname as dirname9, join as join12 } from "node:path";
|
|
7135
8793
|
|
|
7136
8794
|
// src/sync/stage.ts
|
|
7137
|
-
import { createHash as
|
|
8795
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7138
8796
|
|
|
7139
8797
|
// src/usage/days.ts
|
|
7140
8798
|
var round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
@@ -7227,7 +8885,13 @@ function mergeUsageDays(perHarness) {
|
|
|
7227
8885
|
function buildMeasuredDays(input) {
|
|
7228
8886
|
const workflowByDate = /* @__PURE__ */ new Map();
|
|
7229
8887
|
for (const day of input.workflow ?? []) workflowByDate.set(day.date, day);
|
|
7230
|
-
const dates = [
|
|
8888
|
+
const dates = [
|
|
8889
|
+
.../* @__PURE__ */ new Set([
|
|
8890
|
+
...input.usage.keys(),
|
|
8891
|
+
...workflowByDate.keys(),
|
|
8892
|
+
...input.includeDates ?? []
|
|
8893
|
+
])
|
|
8894
|
+
].filter(
|
|
7231
8895
|
(d) => /^\d{4}-\d{2}-\d{2}$/.test(d) && d >= input.from && d <= input.to
|
|
7232
8896
|
).sort();
|
|
7233
8897
|
return dates.map((date) => {
|
|
@@ -7284,7 +8948,7 @@ function selectDaysToPublish(input) {
|
|
|
7284
8948
|
|
|
7285
8949
|
// src/workflow/git.ts
|
|
7286
8950
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
7287
|
-
import
|
|
8951
|
+
import path11 from "node:path";
|
|
7288
8952
|
var TEST_FILE_RULE_VERSION = "test-files/v2";
|
|
7289
8953
|
var FILE_TYPE_RULE_VERSION = "file-types/v2";
|
|
7290
8954
|
var COMMIT_SET_RULE_VERSION = "commit-set/v1";
|
|
@@ -7567,9 +9231,9 @@ function reduceGitHistories(histories, options) {
|
|
|
7567
9231
|
if (included) seenCommits.add(hash);
|
|
7568
9232
|
continue;
|
|
7569
9233
|
}
|
|
7570
|
-
const
|
|
7571
|
-
if (!
|
|
7572
|
-
let file =
|
|
9234
|
+
const stat8 = parseNumstat(field);
|
|
9235
|
+
if (!stat8) continue;
|
|
9236
|
+
let file = stat8.file;
|
|
7573
9237
|
if (file.length === 0) {
|
|
7574
9238
|
fieldIndex += 2;
|
|
7575
9239
|
file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
|
|
@@ -7577,13 +9241,13 @@ function reduceGitHistories(histories, options) {
|
|
|
7577
9241
|
if (!current?.included) continue;
|
|
7578
9242
|
if (isUnauthoredPath(file)) continue;
|
|
7579
9243
|
current.authored = true;
|
|
7580
|
-
const fileChangedLines =
|
|
7581
|
-
current.additions +=
|
|
7582
|
-
current.removals +=
|
|
9244
|
+
const fileChangedLines = stat8.additions + stat8.removals;
|
|
9245
|
+
current.additions += stat8.additions;
|
|
9246
|
+
current.removals += stat8.removals;
|
|
7583
9247
|
current.changedLines += fileChangedLines;
|
|
7584
9248
|
if (isTestFile(file)) current.touchesTest = true;
|
|
7585
9249
|
if (fileChangedLines <= 0) continue;
|
|
7586
|
-
const extension =
|
|
9250
|
+
const extension = path11.extname(file).toLowerCase();
|
|
7587
9251
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
7588
9252
|
current.extensionLines.set(
|
|
7589
9253
|
extension,
|
|
@@ -7686,6 +9350,47 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
|
|
|
7686
9350
|
};
|
|
7687
9351
|
}
|
|
7688
9352
|
|
|
9353
|
+
// src/sync/grokDateCache.ts
|
|
9354
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9355
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9356
|
+
import { homedir as homedir18 } from "node:os";
|
|
9357
|
+
import path12 from "node:path";
|
|
9358
|
+
var defaultFile = path12.join(
|
|
9359
|
+
homedir18(),
|
|
9360
|
+
".config",
|
|
9361
|
+
"aistack",
|
|
9362
|
+
"grok-session-dates.json"
|
|
9363
|
+
);
|
|
9364
|
+
function grokCacheScope(baseUrl, stack, token) {
|
|
9365
|
+
return createHash3("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
|
|
9366
|
+
}
|
|
9367
|
+
function read2(file) {
|
|
9368
|
+
if (!existsSync12(file)) return {};
|
|
9369
|
+
try {
|
|
9370
|
+
const value = JSON.parse(readFileSync13(file, "utf8"));
|
|
9371
|
+
return value && typeof value === "object" ? value : {};
|
|
9372
|
+
} catch {
|
|
9373
|
+
return {};
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
function loadGrokDateHints(scope, file = defaultFile) {
|
|
9377
|
+
return read2(file)[scope] ?? {};
|
|
9378
|
+
}
|
|
9379
|
+
function saveGrokDateHints(scope, hints, file = defaultFile) {
|
|
9380
|
+
const cache = read2(file);
|
|
9381
|
+
cache[scope] = hints;
|
|
9382
|
+
mkdirSync7(path12.dirname(file), { recursive: true });
|
|
9383
|
+
writeFileSync7(file, JSON.stringify(cache, null, 2));
|
|
9384
|
+
}
|
|
9385
|
+
function mapToHints(value, floor) {
|
|
9386
|
+
return Object.fromEntries(
|
|
9387
|
+
[...value].map(([id, dates]) => [
|
|
9388
|
+
id,
|
|
9389
|
+
[...dates].filter((d) => d >= floor).sort()
|
|
9390
|
+
])
|
|
9391
|
+
);
|
|
9392
|
+
}
|
|
9393
|
+
|
|
7689
9394
|
// src/sync/summary.ts
|
|
7690
9395
|
function fmtTokens(n) {
|
|
7691
9396
|
const sig = (v) => {
|
|
@@ -8086,7 +9791,7 @@ function buildGateSummary(ctx) {
|
|
|
8086
9791
|
// src/sync/stage.ts
|
|
8087
9792
|
var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
8088
9793
|
function stageId(bodyJson) {
|
|
8089
|
-
return
|
|
9794
|
+
return createHash4("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
8090
9795
|
}
|
|
8091
9796
|
async function stageSync(deps) {
|
|
8092
9797
|
const now = (deps.now ?? Date.now)();
|
|
@@ -8138,10 +9843,14 @@ async function stageSync(deps) {
|
|
|
8138
9843
|
const sinceMs = windowStartMs(now, windowDays);
|
|
8139
9844
|
const daysSinceMs = windowStartMs(now, retentionDays);
|
|
8140
9845
|
const active2 = await adapters(sinceMs);
|
|
9846
|
+
const historical = await adapters(daysSinceMs);
|
|
9847
|
+
let dayScansComplete = true;
|
|
9848
|
+
const sessionDatesByHarness = /* @__PURE__ */ new Map();
|
|
8141
9849
|
for (const adapter of active2) {
|
|
8142
9850
|
progress(`Scanning recent ${adapter.name} usage`);
|
|
8143
9851
|
const { aggregate, stats } = await adapter.scan({
|
|
8144
9852
|
sinceMs,
|
|
9853
|
+
publishWorkflow: false,
|
|
8145
9854
|
onProgress: (files) => progress(`Scanning recent ${adapter.name} usage \xB7 ${files} files`)
|
|
8146
9855
|
});
|
|
8147
9856
|
scanStats[adapter.name] = stats;
|
|
@@ -8158,12 +9867,16 @@ async function stageSync(deps) {
|
|
|
8158
9867
|
})
|
|
8159
9868
|
);
|
|
8160
9869
|
}
|
|
8161
|
-
for (const adapter of
|
|
9870
|
+
for (const adapter of historical) {
|
|
8162
9871
|
progress(`Reading historical ${adapter.name} days`);
|
|
8163
|
-
const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
|
|
9872
|
+
const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
|
|
8164
9873
|
sinceMs: daysSinceMs,
|
|
9874
|
+
publishWorkflow: config.publishWorkflow,
|
|
8165
9875
|
onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
|
|
8166
9876
|
});
|
|
9877
|
+
if (scanComplete === false) dayScansComplete = false;
|
|
9878
|
+
if (adapter.name === "grok-build" || adapter.name === "cursor")
|
|
9879
|
+
sessionDatesByHarness.set(adapter.name, sessionDates ?? /* @__PURE__ */ new Map());
|
|
8167
9880
|
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
8168
9881
|
usageScans.push(
|
|
8169
9882
|
buildUsageDays({
|
|
@@ -8189,17 +9902,43 @@ async function stageSync(deps) {
|
|
|
8189
9902
|
toMs: now
|
|
8190
9903
|
});
|
|
8191
9904
|
}
|
|
9905
|
+
const correctionDates = /* @__PURE__ */ new Set();
|
|
9906
|
+
let acknowledgePublish;
|
|
9907
|
+
const acknowledgements = [];
|
|
9908
|
+
for (const [harness, currentDates] of sessionDatesByHarness) {
|
|
9909
|
+
if (!token || !config.stack) continue;
|
|
9910
|
+
const scope = grokCacheScope(
|
|
9911
|
+
harness === "grok-build" ? deps.baseUrl : `${deps.baseUrl}\0${harness}`,
|
|
9912
|
+
config.stack.slug,
|
|
9913
|
+
token
|
|
9914
|
+
);
|
|
9915
|
+
const floor = utcDate2(daysSinceMs);
|
|
9916
|
+
const previous = loadGrokDateHints(scope);
|
|
9917
|
+
const current = mapToHints(currentDates, floor);
|
|
9918
|
+
for (const dates of Object.values(previous))
|
|
9919
|
+
for (const date of dates) correctionDates.add(date);
|
|
9920
|
+
for (const dates of Object.values(current))
|
|
9921
|
+
for (const date of dates) correctionDates.add(date);
|
|
9922
|
+
if (Object.keys(previous).some((id) => !(id in current)))
|
|
9923
|
+
dayScansComplete = false;
|
|
9924
|
+
acknowledgements.push(() => saveGrokDateHints(scope, current));
|
|
9925
|
+
}
|
|
9926
|
+
if (acknowledgements.length)
|
|
9927
|
+
acknowledgePublish = () => {
|
|
9928
|
+
for (const acknowledge of acknowledgements) acknowledge();
|
|
9929
|
+
};
|
|
8192
9930
|
const localDays = applyDayConsent(
|
|
8193
9931
|
buildMeasuredDays({
|
|
8194
9932
|
usage: mergeUsageDays(usageScans),
|
|
8195
9933
|
...workflow ? { workflow: workflow.days } : {},
|
|
8196
9934
|
from: utcDate2(daysSinceMs),
|
|
8197
|
-
to: utcDate2(now)
|
|
9935
|
+
to: utcDate2(now),
|
|
9936
|
+
includeDates: correctionDates
|
|
8198
9937
|
}),
|
|
8199
9938
|
config
|
|
8200
9939
|
);
|
|
8201
9940
|
const days = selectDaysToPublish({
|
|
8202
|
-
local: localDays,
|
|
9941
|
+
local: dayScansComplete ? localDays : [],
|
|
8203
9942
|
manifest,
|
|
8204
9943
|
todayUtc: utcDate2(now)
|
|
8205
9944
|
});
|
|
@@ -8208,7 +9947,7 @@ async function stageSync(deps) {
|
|
|
8208
9947
|
config,
|
|
8209
9948
|
settings.autoSync,
|
|
8210
9949
|
deps.trigger,
|
|
8211
|
-
|
|
9950
|
+
historical.length > 0 && dayScansComplete ? {
|
|
8212
9951
|
aggregateVersion: MEASURED_DAYS_V1,
|
|
8213
9952
|
utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
|
|
8214
9953
|
days: days.send
|
|
@@ -8232,8 +9971,8 @@ async function stageSync(deps) {
|
|
|
8232
9971
|
width: process.stdout.columns
|
|
8233
9972
|
};
|
|
8234
9973
|
let blockedReason = null;
|
|
8235
|
-
if (
|
|
8236
|
-
blockedReason = `No
|
|
9974
|
+
if (historical.length === 0) {
|
|
9975
|
+
blockedReason = `No supported harness transcript from the last ${retentionDays} days to read.`;
|
|
8237
9976
|
} else if (token === null) {
|
|
8238
9977
|
blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
|
|
8239
9978
|
} else if (config.stack === null) {
|
|
@@ -8251,25 +9990,47 @@ async function stageSync(deps) {
|
|
|
8251
9990
|
stagedAt: now,
|
|
8252
9991
|
blockedReason,
|
|
8253
9992
|
days,
|
|
8254
|
-
prices
|
|
9993
|
+
prices,
|
|
9994
|
+
...acknowledgePublish ? { acknowledgePublish } : {}
|
|
8255
9995
|
};
|
|
8256
9996
|
}
|
|
8257
9997
|
|
|
8258
9998
|
// src/autosync/run.ts
|
|
8259
|
-
var SYNC_LOG_FILE =
|
|
9999
|
+
var SYNC_LOG_FILE = join12(homedir19(), ".config", "aistack", "sync.log");
|
|
8260
10000
|
var SYNC_LOG_MAX_LINES = 200;
|
|
8261
10001
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
8262
10002
|
var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
|
|
8263
10003
|
function appendLogLine(file, line) {
|
|
8264
|
-
|
|
10004
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
8265
10005
|
appendFileSync(file, `${line}
|
|
8266
10006
|
`);
|
|
8267
|
-
const lines2 =
|
|
10007
|
+
const lines2 = readFileSync14(file, "utf-8").split("\n").filter(Boolean);
|
|
8268
10008
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
8269
|
-
|
|
10009
|
+
writeFileSync8(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
|
|
8270
10010
|
`);
|
|
8271
10011
|
}
|
|
8272
10012
|
}
|
|
10013
|
+
function reserveAttempt(file, now, windowMs) {
|
|
10014
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
10015
|
+
for (; ; ) {
|
|
10016
|
+
try {
|
|
10017
|
+
const fd = openSync(file, "wx");
|
|
10018
|
+
writeFileSync8(fd, String(now));
|
|
10019
|
+
closeSync(fd);
|
|
10020
|
+
return true;
|
|
10021
|
+
} catch (error) {
|
|
10022
|
+
if (error.code !== "EEXIST") throw error;
|
|
10023
|
+
const held = Number(readFileSync14(file, "utf-8"));
|
|
10024
|
+
if (Number.isFinite(held) && now - held < windowMs) return false;
|
|
10025
|
+
try {
|
|
10026
|
+
unlinkSync2(file);
|
|
10027
|
+
} catch (unlinkError) {
|
|
10028
|
+
if (unlinkError.code !== "ENOENT")
|
|
10029
|
+
throw unlinkError;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
8273
10034
|
async function runAutoSync(deps) {
|
|
8274
10035
|
const now = (deps.now ?? Date.now)();
|
|
8275
10036
|
const settingsFile = deps.settingsFile;
|
|
@@ -8284,9 +10045,13 @@ async function runAutoSync(deps) {
|
|
|
8284
10045
|
return;
|
|
8285
10046
|
}
|
|
8286
10047
|
const frequencyHours = normalizeFrequencyHours(config.frequencyHours);
|
|
10048
|
+
const windowMs = frequencyHours * 36e5;
|
|
8287
10049
|
const state = settings.autoSyncState ?? {};
|
|
8288
10050
|
const lastRunAt = state.lastRunAt ?? 0;
|
|
8289
|
-
if (now - lastRunAt <
|
|
10051
|
+
if (now - lastRunAt < windowMs) return;
|
|
10052
|
+
const reservationFile = deps.reservationFile ?? `${settingsFile ?? join12(homedir19(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
|
|
10053
|
+
if (!reserveAttempt(reservationFile, now, windowMs)) return;
|
|
10054
|
+
saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
|
|
8290
10055
|
const stage = deps.stageImpl ?? stageSync;
|
|
8291
10056
|
const publish = deps.publishImpl ?? syncPublish;
|
|
8292
10057
|
const loadConfig = deps.loadConfigImpl ?? loadSyncConfig;
|
|
@@ -8377,7 +10142,7 @@ async function runAutoSync(deps) {
|
|
|
8377
10142
|
logFile,
|
|
8378
10143
|
`${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
|
|
8379
10144
|
);
|
|
8380
|
-
if (shouldWarn) {
|
|
10145
|
+
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok" && process.env.AISTACK_HOOK_SOURCE !== "cursor") {
|
|
8381
10146
|
emit(
|
|
8382
10147
|
JSON.stringify({
|
|
8383
10148
|
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.`
|
|
@@ -8522,6 +10287,7 @@ async function syncCommand(options = {}) {
|
|
|
8522
10287
|
s.start("Publishing");
|
|
8523
10288
|
try {
|
|
8524
10289
|
const res = await syncPublish(staged.token, staged.bodyJson);
|
|
10290
|
+
staged.acknowledgePublish?.();
|
|
8525
10291
|
s.stop("Published");
|
|
8526
10292
|
const lines2 = [
|
|
8527
10293
|
`Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
|
|
@@ -8752,6 +10518,7 @@ function createSyncServer(deps, send) {
|
|
|
8752
10518
|
log7(`consent received, sending stage ${approvedStage.id}`);
|
|
8753
10519
|
publish(approvedStage.token, approvedStage.bodyJson).then(
|
|
8754
10520
|
(res) => {
|
|
10521
|
+
approvedStage.acknowledgePublish?.();
|
|
8755
10522
|
if (staged?.id === approvedStage.id) staged = null;
|
|
8756
10523
|
const lines2 = [
|
|
8757
10524
|
`Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,
|
|
@@ -8869,7 +10636,7 @@ program.command("mcp").description(
|
|
|
8869
10636
|
});
|
|
8870
10637
|
program.command("sync").description("Scan, preview, and publish measured usage (rolling 30 days)").option(
|
|
8871
10638
|
"--auto [state]",
|
|
8872
|
-
"silent background sync; 'on' asks your stack for the permission and installs
|
|
10639
|
+
"silent background sync; 'on' asks your stack for the permission and installs harness hooks, 'off' revokes both"
|
|
8873
10640
|
).option(
|
|
8874
10641
|
"--every <hours>",
|
|
8875
10642
|
"with --auto on: hours between auto-syncs (default 6)"
|