@use-aistack/cli 0.14.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 +1219 -303
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -377,12 +377,12 @@ function apiEquivalentCost(modelKey, t, atMs) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
// src/version.ts
|
|
380
|
-
var CLI_VERSION = true ? "0.
|
|
380
|
+
var CLI_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
381
381
|
|
|
382
382
|
// src/api.ts
|
|
383
383
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
384
|
-
async function request(
|
|
385
|
-
return fetch(`${BASE_URL}${
|
|
384
|
+
async function request(path13, options = {}) {
|
|
385
|
+
return fetch(`${BASE_URL}${path13}`, {
|
|
386
386
|
...options,
|
|
387
387
|
headers: {
|
|
388
388
|
"Content-Type": "application/json",
|
|
@@ -800,9 +800,9 @@ function canonicalizeRepoUrl(input) {
|
|
|
800
800
|
function repoNameFromCanonical(canonical) {
|
|
801
801
|
return parseRepo(canonical)?.repo ?? "";
|
|
802
802
|
}
|
|
803
|
-
function normalizeUpstreamPath(
|
|
804
|
-
if (!
|
|
805
|
-
return
|
|
803
|
+
function normalizeUpstreamPath(path13) {
|
|
804
|
+
if (!path13) return "";
|
|
805
|
+
return path13.split("/").filter(Boolean).join("/");
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
// src/git.ts
|
|
@@ -848,16 +848,16 @@ function buildRepoLinkResource(canonical) {
|
|
|
848
848
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
849
849
|
import { homedir as homedir2 } from "node:os";
|
|
850
850
|
import { join as join2 } from "node:path";
|
|
851
|
-
function readJson(
|
|
851
|
+
function readJson(path13) {
|
|
852
852
|
try {
|
|
853
|
-
if (!existsSync2(
|
|
854
|
-
return JSON.parse(readFileSync2(
|
|
853
|
+
if (!existsSync2(path13)) return null;
|
|
854
|
+
return JSON.parse(readFileSync2(path13, "utf-8"));
|
|
855
855
|
} catch {
|
|
856
856
|
return null;
|
|
857
857
|
}
|
|
858
858
|
}
|
|
859
|
-
function hooksFrom(
|
|
860
|
-
const hooks = readJson(
|
|
859
|
+
function hooksFrom(path13, source, out, seen) {
|
|
860
|
+
const hooks = readJson(path13)?.hooks;
|
|
861
861
|
if (!hooks || typeof hooks !== "object") return;
|
|
862
862
|
for (const [event, config] of Object.entries(hooks)) {
|
|
863
863
|
const stableKey = `hooks:${source}:${event}`;
|
|
@@ -1006,16 +1006,16 @@ function buildMcpResource(name, group, pkg) {
|
|
|
1006
1006
|
pkg
|
|
1007
1007
|
};
|
|
1008
1008
|
}
|
|
1009
|
-
function readText(
|
|
1009
|
+
function readText(path13) {
|
|
1010
1010
|
try {
|
|
1011
|
-
if (!existsSync3(
|
|
1012
|
-
return readFileSync3(
|
|
1011
|
+
if (!existsSync3(path13)) return null;
|
|
1012
|
+
return readFileSync3(path13, "utf-8");
|
|
1013
1013
|
} catch {
|
|
1014
1014
|
return null;
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
|
-
function readParsed(
|
|
1018
|
-
const raw = readText(
|
|
1017
|
+
function readParsed(path13, parse2) {
|
|
1018
|
+
const raw = readText(path13);
|
|
1019
1019
|
if (raw === null) return null;
|
|
1020
1020
|
try {
|
|
1021
1021
|
return parse2(raw);
|
|
@@ -1023,14 +1023,14 @@ function readParsed(path9, parse2) {
|
|
|
1023
1023
|
return null;
|
|
1024
1024
|
}
|
|
1025
1025
|
}
|
|
1026
|
-
function readJson2(
|
|
1027
|
-
return readParsed(
|
|
1026
|
+
function readJson2(path13) {
|
|
1027
|
+
return readParsed(path13, JSON.parse);
|
|
1028
1028
|
}
|
|
1029
|
-
function readYaml(
|
|
1030
|
-
return readParsed(
|
|
1029
|
+
function readYaml(path13) {
|
|
1030
|
+
return readParsed(path13, parseYaml);
|
|
1031
1031
|
}
|
|
1032
|
-
function readToml(
|
|
1033
|
-
return readParsed(
|
|
1032
|
+
function readToml(path13) {
|
|
1033
|
+
return readParsed(path13, parseToml);
|
|
1034
1034
|
}
|
|
1035
1035
|
function continueListToMap(file) {
|
|
1036
1036
|
if (!file?.mcpServers?.length) return void 0;
|
|
@@ -1169,8 +1169,8 @@ function resolveSource(entry, mpRepoUrl) {
|
|
|
1169
1169
|
const src = entry.source;
|
|
1170
1170
|
if (typeof src === "string") {
|
|
1171
1171
|
if (!mpRepoUrl) return null;
|
|
1172
|
-
const
|
|
1173
|
-
return { url: mpRepoUrl, path:
|
|
1172
|
+
const path13 = src.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1173
|
+
return { url: mpRepoUrl, path: path13 || void 0 };
|
|
1174
1174
|
}
|
|
1175
1175
|
if (src && typeof src === "object" && src.url) {
|
|
1176
1176
|
return { url: src.url, path: src.path, sha: src.sha };
|
|
@@ -1207,10 +1207,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
return out;
|
|
1209
1209
|
}
|
|
1210
|
-
function readJson3(
|
|
1210
|
+
function readJson3(path13) {
|
|
1211
1211
|
try {
|
|
1212
|
-
if (!existsSync4(
|
|
1213
|
-
return JSON.parse(readFileSync4(
|
|
1212
|
+
if (!existsSync4(path13)) return null;
|
|
1213
|
+
return JSON.parse(readFileSync4(path13, "utf-8"));
|
|
1214
1214
|
} catch {
|
|
1215
1215
|
return null;
|
|
1216
1216
|
}
|
|
@@ -1299,8 +1299,8 @@ function loadGitignore(cwd) {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
function readFileSafe(filePath) {
|
|
1301
1301
|
try {
|
|
1302
|
-
const
|
|
1303
|
-
if (
|
|
1302
|
+
const stat8 = statSync(filePath);
|
|
1303
|
+
if (stat8.size > MAX_FILE_SIZE) return null;
|
|
1304
1304
|
return readFileSync5(filePath, "utf-8");
|
|
1305
1305
|
} catch {
|
|
1306
1306
|
return null;
|
|
@@ -1519,9 +1519,9 @@ function lines(items) {
|
|
|
1519
1519
|
console.log(`${BAR} ${item}`);
|
|
1520
1520
|
}
|
|
1521
1521
|
}
|
|
1522
|
-
function section(label,
|
|
1522
|
+
function section(label, count2) {
|
|
1523
1523
|
console.log(`${BAR}`);
|
|
1524
|
-
const countStr =
|
|
1524
|
+
const countStr = count2 !== void 0 ? ` ${dim(String(count2))}` : "";
|
|
1525
1525
|
console.log(`${BAR} ${bold(label.toUpperCase())}${countStr}`);
|
|
1526
1526
|
}
|
|
1527
1527
|
function divider() {
|
|
@@ -1844,7 +1844,7 @@ function diffResources(current, existing) {
|
|
|
1844
1844
|
// src/commands/connect.ts
|
|
1845
1845
|
import { spawnSync } from "node:child_process";
|
|
1846
1846
|
import { cpSync, existsSync as existsSync6 } from "node:fs";
|
|
1847
|
-
import { homedir as
|
|
1847
|
+
import { homedir as homedir13 } from "node:os";
|
|
1848
1848
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1849
1849
|
import { fileURLToPath } from "node:url";
|
|
1850
1850
|
import * as p3 from "@clack/prompts";
|
|
@@ -2442,7 +2442,7 @@ function filterAtoms(atoms, sets) {
|
|
|
2442
2442
|
}
|
|
2443
2443
|
merged.set(published, (merged.get(published) ?? 0) + atom.count);
|
|
2444
2444
|
}
|
|
2445
|
-
const allowed = [...merged].map(([name,
|
|
2445
|
+
const allowed = [...merged].map(([name, count2]) => ({ name, count: count2 }));
|
|
2446
2446
|
allowed.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2447
2447
|
keptPrivate.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2448
2448
|
return { allowed, keptPrivate, withheld: keptPrivate.length };
|
|
@@ -3199,6 +3199,7 @@ var HANDOFF_MARKERS = {
|
|
|
3199
3199
|
"mcp__curia__request_review"
|
|
3200
3200
|
],
|
|
3201
3201
|
codex: ["request_user_input"],
|
|
3202
|
+
cursor: ["ask_question"],
|
|
3202
3203
|
"grok-build": ["ask_user_question", "request_user_input"],
|
|
3203
3204
|
opencode: ["question"],
|
|
3204
3205
|
"pi-mono": []
|
|
@@ -3732,7 +3733,7 @@ function sanitizeModelId(id) {
|
|
|
3732
3733
|
var round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
3733
3734
|
var round2 = (n) => Math.round(n * 100) / 100;
|
|
3734
3735
|
var utcDate = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3735
|
-
var toAtoms = (pairs) => pairs.map(([name,
|
|
3736
|
+
var toAtoms = (pairs) => pairs.map(([name, count2]) => ({ name, count: count2 }));
|
|
3736
3737
|
function buildCategory(observed, curated, optIns, denominator) {
|
|
3737
3738
|
const publishable = /* @__PURE__ */ new Set([...curated, ...optIns]);
|
|
3738
3739
|
const {
|
|
@@ -4025,7 +4026,7 @@ var bump2 = (map, key2, amount = 1) => {
|
|
|
4025
4026
|
var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
4026
4027
|
function asBuckets(map, field) {
|
|
4027
4028
|
return [...map].map(
|
|
4028
|
-
([bucket,
|
|
4029
|
+
([bucket, count2]) => ({ bucket, [field]: count2 })
|
|
4029
4030
|
).sort((a, b) => a.bucket - b.bucket);
|
|
4030
4031
|
}
|
|
4031
4032
|
var PHASE_RANK = {
|
|
@@ -4410,7 +4411,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4410
4411
|
0
|
|
4411
4412
|
);
|
|
4412
4413
|
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
4413
|
-
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build";
|
|
4414
|
+
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build" || harness === "cursor";
|
|
4414
4415
|
const asRows = (map) => {
|
|
4415
4416
|
const safe = /* @__PURE__ */ new Map();
|
|
4416
4417
|
for (const [model, tokens] of map) {
|
|
@@ -4433,7 +4434,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4433
4434
|
date,
|
|
4434
4435
|
harness,
|
|
4435
4436
|
sessions: day.sessions,
|
|
4436
|
-
startHours: [...day.startHours].map(([hourUtc,
|
|
4437
|
+
startHours: [...day.startHours].map(([hourUtc, count2]) => ({ hourUtc, sessions: count2 })).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
4437
4438
|
...day.phase.sessions > 0 ? {
|
|
4438
4439
|
phase: {
|
|
4439
4440
|
ruleVersion: PHASE_RULES_V1,
|
|
@@ -4561,12 +4562,12 @@ function ingestRecord(agg, raw, ctx) {
|
|
|
4561
4562
|
const sessionId = asStr(rec.sessionId);
|
|
4562
4563
|
if (sessionId) agg.sessions.add(sessionId);
|
|
4563
4564
|
let tsMs = null;
|
|
4564
|
-
const
|
|
4565
|
-
if (
|
|
4566
|
-
const ts = Date.parse(
|
|
4565
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
4566
|
+
if (timestamp2) {
|
|
4567
|
+
const ts = Date.parse(timestamp2);
|
|
4567
4568
|
if (!Number.isNaN(ts)) {
|
|
4568
4569
|
tsMs = ts;
|
|
4569
|
-
agg.activeDays.add(
|
|
4570
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
4570
4571
|
agg.firstTs = agg.firstTs === null ? ts : Math.min(agg.firstTs, ts);
|
|
4571
4572
|
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
4572
4573
|
}
|
|
@@ -4859,8 +4860,8 @@ function applyContribution(agg, c, sign) {
|
|
|
4859
4860
|
agg.webFetchRequests += sign * c.webFetch;
|
|
4860
4861
|
agg.fallbackAttempts += sign * c.fallbackAttempts;
|
|
4861
4862
|
agg.untypedMirrors += sign * c.untypedMirrors;
|
|
4862
|
-
for (const [type,
|
|
4863
|
-
bump(agg.mirroredIterationTypes, type, sign *
|
|
4863
|
+
for (const [type, count2] of c.mirroredIterationTypes) {
|
|
4864
|
+
bump(agg.mirroredIterationTypes, type, sign * count2);
|
|
4864
4865
|
}
|
|
4865
4866
|
}
|
|
4866
4867
|
function ingestContentBlocks(agg, content) {
|
|
@@ -5126,9 +5127,9 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5126
5127
|
if (!rec) return;
|
|
5127
5128
|
agg.records++;
|
|
5128
5129
|
let tsMs = null;
|
|
5129
|
-
const
|
|
5130
|
-
if (
|
|
5131
|
-
const ts = Date.parse(
|
|
5130
|
+
const timestamp2 = asStr(rec.timestamp);
|
|
5131
|
+
if (timestamp2) {
|
|
5132
|
+
const ts = Date.parse(timestamp2);
|
|
5132
5133
|
if (!Number.isNaN(ts)) tsMs = ts;
|
|
5133
5134
|
}
|
|
5134
5135
|
const inWindow = sinceMs === void 0 || tsMs !== null && tsMs >= sinceMs;
|
|
@@ -5151,8 +5152,8 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
5151
5152
|
state.sawResponse = true;
|
|
5152
5153
|
}
|
|
5153
5154
|
if (!inWindow) return;
|
|
5154
|
-
if (tsMs !== null &&
|
|
5155
|
-
agg.activeDays.add(
|
|
5155
|
+
if (tsMs !== null && timestamp2) {
|
|
5156
|
+
agg.activeDays.add(timestamp2.slice(0, 10));
|
|
5156
5157
|
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
5157
5158
|
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
5158
5159
|
}
|
|
@@ -5440,11 +5441,11 @@ function ingestWithRetry(agg, file, opts) {
|
|
|
5440
5441
|
}
|
|
5441
5442
|
}
|
|
5442
5443
|
function ingestFile2(agg, file, opts) {
|
|
5443
|
-
const
|
|
5444
|
+
const readFile3 = opts.readFileImpl ?? readFileSync6;
|
|
5444
5445
|
let text;
|
|
5445
5446
|
if (file.endsWith(".zst")) {
|
|
5446
5447
|
if (zstdDecompress === null) throw readError("zstd-unsupported");
|
|
5447
|
-
const raw =
|
|
5448
|
+
const raw = readFile3(file);
|
|
5448
5449
|
try {
|
|
5449
5450
|
text = zstdDecompress(
|
|
5450
5451
|
Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
|
|
@@ -5453,7 +5454,7 @@ function ingestFile2(agg, file, opts) {
|
|
|
5453
5454
|
throw readError("zstd-corrupt");
|
|
5454
5455
|
}
|
|
5455
5456
|
} else {
|
|
5456
|
-
text =
|
|
5457
|
+
text = readFile3(file).toString("utf8");
|
|
5457
5458
|
}
|
|
5458
5459
|
const records = [];
|
|
5459
5460
|
let nonEmptyLines = 0;
|
|
@@ -5556,6 +5557,817 @@ var codexAdapter = {
|
|
|
5556
5557
|
}
|
|
5557
5558
|
};
|
|
5558
5559
|
|
|
5560
|
+
// src/harness/cursor/cache.ts
|
|
5561
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5562
|
+
import { homedir as homedir9 } from "node:os";
|
|
5563
|
+
import path5 from "node:path";
|
|
5564
|
+
|
|
5565
|
+
// src/harness/cursor/account.ts
|
|
5566
|
+
import { createHash } from "node:crypto";
|
|
5567
|
+
|
|
5568
|
+
// src/harness/cursor/evidence.ts
|
|
5569
|
+
function timestamp(value) {
|
|
5570
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? /^\d+$/.test(value) ? Number(value) : Date.parse(value) : NaN;
|
|
5571
|
+
return Number.isFinite(n) && n > 0 && n < 864e13 ? n : null;
|
|
5572
|
+
}
|
|
5573
|
+
function count(value) {
|
|
5574
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
5575
|
+
}
|
|
5576
|
+
function tokenEvidence(value) {
|
|
5577
|
+
const raw = asObj(value) ?? {};
|
|
5578
|
+
const direct = asObj(raw.tokenCount);
|
|
5579
|
+
const usage = asObj(raw.usage);
|
|
5580
|
+
for (const [obj, fields] of [
|
|
5581
|
+
[
|
|
5582
|
+
direct,
|
|
5583
|
+
["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]
|
|
5584
|
+
],
|
|
5585
|
+
[
|
|
5586
|
+
usage,
|
|
5587
|
+
[
|
|
5588
|
+
"input_tokens",
|
|
5589
|
+
"output_tokens",
|
|
5590
|
+
"cache_read_input_tokens",
|
|
5591
|
+
"cache_creation_input_tokens"
|
|
5592
|
+
]
|
|
5593
|
+
]
|
|
5594
|
+
]) {
|
|
5595
|
+
for (const field of fields)
|
|
5596
|
+
if (obj?.[field] != null && count(obj[field]) === void 0)
|
|
5597
|
+
throw new Error("Invalid Cursor token evidence");
|
|
5598
|
+
}
|
|
5599
|
+
const out = {};
|
|
5600
|
+
out.input = count(direct?.inputTokens) ?? count(usage?.input_tokens);
|
|
5601
|
+
out.output = count(direct?.outputTokens) ?? count(usage?.output_tokens);
|
|
5602
|
+
out.cacheRead = count(direct?.cacheReadTokens) ?? count(usage?.cache_read_input_tokens);
|
|
5603
|
+
out.cacheWrite = count(direct?.cacheWriteTokens) ?? count(usage?.cache_creation_input_tokens);
|
|
5604
|
+
if (out.input !== void 0) out.inputSource = "reported";
|
|
5605
|
+
if (out.output !== void 0) out.outputSource = "reported";
|
|
5606
|
+
if (out.input === void 0) {
|
|
5607
|
+
const context = count(asObj(raw.contextWindowStatusAtCreation)?.tokensUsed);
|
|
5608
|
+
if (context !== void 0) {
|
|
5609
|
+
out.input = context;
|
|
5610
|
+
out.inputSource = "context";
|
|
5611
|
+
} else if (typeof raw.promptDryRunInfo === "string") {
|
|
5612
|
+
try {
|
|
5613
|
+
const dry = asObj(JSON.parse(raw.promptDryRunInfo));
|
|
5614
|
+
const estimate = count(asObj(dry?.fullConversationTokenCount)?.numTokens) ?? count(asObj(dry?.userMessageTokenCount)?.numTokens);
|
|
5615
|
+
if (estimate !== void 0) {
|
|
5616
|
+
out.input = estimate;
|
|
5617
|
+
out.inputSource = "dry-run";
|
|
5618
|
+
}
|
|
5619
|
+
} catch {
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
return out;
|
|
5624
|
+
}
|
|
5625
|
+
var directTimes = /* @__PURE__ */ new Set([
|
|
5626
|
+
"composer-created-at",
|
|
5627
|
+
"composer-timing",
|
|
5628
|
+
"store-turn-timing"
|
|
5629
|
+
]);
|
|
5630
|
+
function messageTimes(session, api = []) {
|
|
5631
|
+
const times = session.messages.map(
|
|
5632
|
+
(m) => directTimes.has(m.timestampSource ?? "") ? timestamp(m.timestamp) : null
|
|
5633
|
+
);
|
|
5634
|
+
const anchors = [];
|
|
5635
|
+
for (const [index, time] of times.entries())
|
|
5636
|
+
if (time !== null) anchors.push({ index, time });
|
|
5637
|
+
const start = session.createdAtSource !== "epoch-unknown" ? timestamp(session.timestamp) : null;
|
|
5638
|
+
const end = session.lastUpdatedAtSource !== "epoch-unknown" ? timestamp(session.metadata?.lastModified) : null;
|
|
5639
|
+
const apiTimes = api.filter((e) => e.session === session.id).map((e) => e.tsMs);
|
|
5640
|
+
if (!anchors.some((a) => a.index === 0)) {
|
|
5641
|
+
const time = start ?? (apiTimes.length ? Math.min(...apiTimes) : null);
|
|
5642
|
+
if (time !== null) anchors.unshift({ index: -1, time });
|
|
5643
|
+
}
|
|
5644
|
+
const last = session.messages.length;
|
|
5645
|
+
if (last && !anchors.some((a) => a.index === last - 1)) {
|
|
5646
|
+
const time = end ?? (apiTimes.length ? Math.max(...apiTimes) : null);
|
|
5647
|
+
if (time !== null) anchors.push({ index: last, time });
|
|
5648
|
+
}
|
|
5649
|
+
for (let i = 0; i < times.length; i++) {
|
|
5650
|
+
if (times[i] !== null) continue;
|
|
5651
|
+
const before = anchors.filter((a) => a.index < i).at(-1);
|
|
5652
|
+
const after = anchors.find((a) => a.index > i);
|
|
5653
|
+
if (before && after && after.time >= before.time)
|
|
5654
|
+
times[i] = Math.round(
|
|
5655
|
+
before.time + (after.time - before.time) * (i - before.index) / (after.index - before.index)
|
|
5656
|
+
);
|
|
5657
|
+
else times[i] = before?.time ?? after?.time ?? null;
|
|
5658
|
+
}
|
|
5659
|
+
return times;
|
|
5660
|
+
}
|
|
5661
|
+
function localContributions(local, api = []) {
|
|
5662
|
+
const { session, tokens } = local;
|
|
5663
|
+
const times = messageTimes(session, api);
|
|
5664
|
+
const out = [];
|
|
5665
|
+
let pending;
|
|
5666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5667
|
+
for (const [i, message] of session.messages.entries()) {
|
|
5668
|
+
if (message.id && seen.has(message.id)) continue;
|
|
5669
|
+
if (message.id) seen.add(message.id);
|
|
5670
|
+
if (message.role === "user") {
|
|
5671
|
+
pending = message;
|
|
5672
|
+
continue;
|
|
5673
|
+
}
|
|
5674
|
+
const own = tokens.get(message.id ?? "") ?? {};
|
|
5675
|
+
const user = tokens.get(pending?.id ?? "") ?? {};
|
|
5676
|
+
const input = own.input !== void 0 ? own : user;
|
|
5677
|
+
const buckets = {
|
|
5678
|
+
...own,
|
|
5679
|
+
input: input.input ?? (pending ? Math.ceil(pending.content.length / 4) : void 0),
|
|
5680
|
+
inputSource: input.inputSource ?? (pending ? "text" : void 0),
|
|
5681
|
+
output: own.output ?? Math.ceil(message.content.length / 4),
|
|
5682
|
+
outputSource: own.outputSource ?? "text"
|
|
5683
|
+
};
|
|
5684
|
+
pending = void 0;
|
|
5685
|
+
const tsMs = times[i];
|
|
5686
|
+
if (tsMs === null) continue;
|
|
5687
|
+
out.push({
|
|
5688
|
+
session: session.id,
|
|
5689
|
+
...message.id ? { id: message.id } : {},
|
|
5690
|
+
...message.identityOrigin === "composer-native" && message.id ? { nativeId: message.id } : {},
|
|
5691
|
+
tsMs,
|
|
5692
|
+
model: asStr(message.model) ?? "unknown",
|
|
5693
|
+
buckets,
|
|
5694
|
+
source: "local",
|
|
5695
|
+
...message.isSidechain ? { sidechain: true } : {}
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
return out;
|
|
5699
|
+
}
|
|
5700
|
+
function reconcile(local, api) {
|
|
5701
|
+
const grain = (c) => `${c.session}\0${new Date(c.tsMs).toISOString().slice(0, 10)}`;
|
|
5702
|
+
const reported = api.filter(
|
|
5703
|
+
(row) => Object.values(row.buckets).some((value) => typeof value === "number")
|
|
5704
|
+
);
|
|
5705
|
+
const grains = new Set(reported.map(grain));
|
|
5706
|
+
return [...local.filter((c) => !grains.has(grain(c))), ...reported];
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
// src/harness/cursor/local.ts
|
|
5710
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
5711
|
+
import { homedir as homedir8 } from "node:os";
|
|
5712
|
+
import path4 from "node:path";
|
|
5713
|
+
function dataPath(env = process.env, home = homedir8(), platform4 = process.platform) {
|
|
5714
|
+
if (env.CURSOR_DATA_PATH) return path4.resolve(env.CURSOR_DATA_PATH);
|
|
5715
|
+
const base = platform4 === "darwin" ? path4.join(home, "Library", "Application Support") : platform4 === "win32" ? env.APPDATA || path4.join(home, "AppData", "Roaming") : env.XDG_CONFIG_HOME || path4.join(home, ".config");
|
|
5716
|
+
return path4.join(base, "Cursor", "User", "workspaceStorage");
|
|
5717
|
+
}
|
|
5718
|
+
var storeRoot = () => process.env.CURSOR_STORE_ROOT || path4.join(homedir8(), ".cursor");
|
|
5719
|
+
var globalDb = (root) => path4.join(path4.dirname(root), "globalStorage", "state.vscdb");
|
|
5720
|
+
async function exists3(file) {
|
|
5721
|
+
try {
|
|
5722
|
+
await stat4(file);
|
|
5723
|
+
return true;
|
|
5724
|
+
} catch (error) {
|
|
5725
|
+
return error.code !== "ENOENT";
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
async function hasLocalSource(root = dataPath()) {
|
|
5729
|
+
return (await Promise.all(
|
|
5730
|
+
[
|
|
5731
|
+
root,
|
|
5732
|
+
globalDb(root),
|
|
5733
|
+
path4.join(storeRoot(), "projects"),
|
|
5734
|
+
path4.join(storeRoot(), "chats"),
|
|
5735
|
+
path4.join(storeRoot(), "acp-sessions")
|
|
5736
|
+
].map(exists3)
|
|
5737
|
+
)).some(Boolean);
|
|
5738
|
+
}
|
|
5739
|
+
async function readTokenEvidence(root, session) {
|
|
5740
|
+
const out = /* @__PURE__ */ new Map();
|
|
5741
|
+
if (!session.messages.some((m) => m.identityOrigin?.startsWith("composer")))
|
|
5742
|
+
return out;
|
|
5743
|
+
const file = globalDb(root);
|
|
5744
|
+
if (!await exists3(file)) return out;
|
|
5745
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
5746
|
+
const db = new DatabaseSync(file, { readOnly: true });
|
|
5747
|
+
try {
|
|
5748
|
+
db.exec("BEGIN");
|
|
5749
|
+
const table = db.prepare(
|
|
5750
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'cursorDiskKV'"
|
|
5751
|
+
).get();
|
|
5752
|
+
if (!table) return out;
|
|
5753
|
+
const query = db.prepare(`SELECT json_object(
|
|
5754
|
+
'tokenCount', json_extract(value, '$.tokenCount'),
|
|
5755
|
+
'usage', json_extract(value, '$.usage'),
|
|
5756
|
+
'contextWindowStatusAtCreation', json_extract(value, '$.contextWindowStatusAtCreation'),
|
|
5757
|
+
'promptDryRunInfo', json_extract(value, '$.promptDryRunInfo')) AS evidence
|
|
5758
|
+
FROM cursorDiskKV WHERE key = ?`);
|
|
5759
|
+
for (const message of session.messages) {
|
|
5760
|
+
if (!message.id || message.identityOrigin !== "composer-native") continue;
|
|
5761
|
+
const row = query.get(`bubbleId:${session.id}:${message.id}`);
|
|
5762
|
+
if (typeof row?.evidence === "string")
|
|
5763
|
+
out.set(message.id, tokenEvidence(JSON.parse(row.evidence)));
|
|
5764
|
+
}
|
|
5765
|
+
const header = db.prepare(
|
|
5766
|
+
"SELECT json_extract(value, '$.conversation') AS conversation FROM cursorDiskKV WHERE key = ?"
|
|
5767
|
+
).get(`composerData:${session.id}`);
|
|
5768
|
+
if (typeof header?.conversation === "string") {
|
|
5769
|
+
const conversation = JSON.parse(header.conversation);
|
|
5770
|
+
if (Array.isArray(conversation))
|
|
5771
|
+
for (const [index, raw] of conversation.entries()) {
|
|
5772
|
+
const obj = asObj(raw);
|
|
5773
|
+
const id = asStr(obj?.bubbleId) ?? asStr(obj?.id) ?? `msg:${index}`;
|
|
5774
|
+
if (!out.has(id)) out.set(id, tokenEvidence(raw));
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
return out;
|
|
5778
|
+
} finally {
|
|
5779
|
+
db.close();
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
async function sourceStamp(root) {
|
|
5783
|
+
const values = await Promise.all(
|
|
5784
|
+
[globalDb(root), `${globalDb(root)}-wal`].map(async (file) => {
|
|
5785
|
+
try {
|
|
5786
|
+
const info = await stat4(file);
|
|
5787
|
+
return `${info.size}:${info.mtimeMs}`;
|
|
5788
|
+
} catch {
|
|
5789
|
+
return "unavailable";
|
|
5790
|
+
}
|
|
5791
|
+
})
|
|
5792
|
+
);
|
|
5793
|
+
return values.join("/");
|
|
5794
|
+
}
|
|
5795
|
+
async function readLocal(root = dataPath(), onProgress) {
|
|
5796
|
+
const stats = emptyScanStats();
|
|
5797
|
+
const out = { sessions: [], complete: true, stats };
|
|
5798
|
+
if (!await hasLocalSource(root)) return out;
|
|
5799
|
+
const before = await sourceStamp(root);
|
|
5800
|
+
let context;
|
|
5801
|
+
try {
|
|
5802
|
+
const reader = await import("cursor-history");
|
|
5803
|
+
const options = {
|
|
5804
|
+
dataPath: root,
|
|
5805
|
+
sqliteDriver: "node:sqlite",
|
|
5806
|
+
onDiagnostic: () => {
|
|
5807
|
+
out.complete = false;
|
|
5808
|
+
},
|
|
5809
|
+
signal: AbortSignal.timeout(12e4)
|
|
5810
|
+
};
|
|
5811
|
+
context = reader.createSessionReadContext(options);
|
|
5812
|
+
const config = { ...options, readContext: context };
|
|
5813
|
+
let offset = 0;
|
|
5814
|
+
while (true) {
|
|
5815
|
+
const page = await reader.listSessionSummaries({
|
|
5816
|
+
...config,
|
|
5817
|
+
offset,
|
|
5818
|
+
limit: 100
|
|
5819
|
+
});
|
|
5820
|
+
for (const summary of page.data) {
|
|
5821
|
+
stats.filesFound++;
|
|
5822
|
+
if (summary.resolutionState === "ambiguous") {
|
|
5823
|
+
out.complete = false;
|
|
5824
|
+
}
|
|
5825
|
+
try {
|
|
5826
|
+
const session = await reader.getSession(summary.id, config);
|
|
5827
|
+
if (session.resolutionState !== "complete" || session.messages.some((m) => m.metadata?.corrupted))
|
|
5828
|
+
out.complete = false;
|
|
5829
|
+
const tokens = await readTokenEvidence(root, session);
|
|
5830
|
+
out.sessions.push({ session, tokens });
|
|
5831
|
+
stats.filesRead++;
|
|
5832
|
+
onProgress?.(stats.filesRead);
|
|
5833
|
+
} catch {
|
|
5834
|
+
out.complete = false;
|
|
5835
|
+
stats.filesUnreadable++;
|
|
5836
|
+
} finally {
|
|
5837
|
+
context.releaseSession(summary.id);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
if (!page.pagination.hasMore) break;
|
|
5841
|
+
if (page.data.length === 0 || offset >= 1e5) {
|
|
5842
|
+
out.complete = false;
|
|
5843
|
+
break;
|
|
5844
|
+
}
|
|
5845
|
+
offset += page.data.length;
|
|
5846
|
+
}
|
|
5847
|
+
} catch {
|
|
5848
|
+
out.complete = false;
|
|
5849
|
+
stats.filesUnreadable++;
|
|
5850
|
+
} finally {
|
|
5851
|
+
try {
|
|
5852
|
+
await context?.dispose();
|
|
5853
|
+
} catch {
|
|
5854
|
+
out.complete = false;
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
if (before !== await sourceStamp(root)) out.complete = false;
|
|
5858
|
+
return out;
|
|
5859
|
+
}
|
|
5860
|
+
|
|
5861
|
+
// src/harness/cursor/account.ts
|
|
5862
|
+
var digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
5863
|
+
async function existingAccount(root) {
|
|
5864
|
+
let db;
|
|
5865
|
+
try {
|
|
5866
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
5867
|
+
db = new DatabaseSync(globalDb(root), { readOnly: true });
|
|
5868
|
+
const row = db.prepare("SELECT value FROM ItemTable WHERE key = ?").get("cursorAuth/accessToken");
|
|
5869
|
+
if (typeof row?.value !== "string") return null;
|
|
5870
|
+
const token = row.value.trim().replace(/^"|"$/g, "");
|
|
5871
|
+
const claims = asObj(
|
|
5872
|
+
JSON.parse(
|
|
5873
|
+
Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8")
|
|
5874
|
+
)
|
|
5875
|
+
);
|
|
5876
|
+
const subject = asStr(claims?.sub);
|
|
5877
|
+
if (!subject || claims?.type === "api_key_token" || !/^[\w|:-]+$/.test(subject) || !/^[A-Za-z0-9_.-]+$/.test(token))
|
|
5878
|
+
return null;
|
|
5879
|
+
const user = subject.split("|").at(-1);
|
|
5880
|
+
if (!user) return null;
|
|
5881
|
+
return { scope: digest(subject), cookie: `${user}%3A%3A${token}` };
|
|
5882
|
+
} catch {
|
|
5883
|
+
return null;
|
|
5884
|
+
} finally {
|
|
5885
|
+
db?.close();
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
function apiContribution(value) {
|
|
5889
|
+
const raw = asObj(value);
|
|
5890
|
+
if (!raw) throw new Error("Invalid Cursor event");
|
|
5891
|
+
const session = asStr(raw.conversationId);
|
|
5892
|
+
if (!session || raw.cloudAgentId || session.startsWith("bc-")) return null;
|
|
5893
|
+
const tsMs = timestamp(raw.timestamp);
|
|
5894
|
+
const usage = raw.tokenUsage == null ? {} : asObj(raw.tokenUsage);
|
|
5895
|
+
if (tsMs === null || !usage) throw new Error("Invalid Cursor event");
|
|
5896
|
+
const buckets = {};
|
|
5897
|
+
for (const [key2, field] of [
|
|
5898
|
+
["input", "inputTokens"],
|
|
5899
|
+
["output", "outputTokens"],
|
|
5900
|
+
["cacheRead", "cacheReadTokens"],
|
|
5901
|
+
["cacheWrite", "cacheWriteTokens"]
|
|
5902
|
+
]) {
|
|
5903
|
+
if (usage[field] === void 0 || usage[field] === null) continue;
|
|
5904
|
+
const value2 = count(usage[field]);
|
|
5905
|
+
if (value2 === void 0) throw new Error("Invalid Cursor token bucket");
|
|
5906
|
+
buckets[key2] = value2;
|
|
5907
|
+
}
|
|
5908
|
+
const id = asStr(raw.id) ?? asStr(raw.eventId);
|
|
5909
|
+
return {
|
|
5910
|
+
session,
|
|
5911
|
+
...id ? { id } : {},
|
|
5912
|
+
tsMs,
|
|
5913
|
+
model: asStr(raw.model) ?? "unknown",
|
|
5914
|
+
buckets,
|
|
5915
|
+
source: "api"
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
async function fetchWindow(account, from, to, fetchImpl = fetch) {
|
|
5919
|
+
const out = [];
|
|
5920
|
+
const pages = /* @__PURE__ */ new Set();
|
|
5921
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
5922
|
+
let total;
|
|
5923
|
+
let retrieved = 0;
|
|
5924
|
+
const pageSize = 100;
|
|
5925
|
+
for (let page = 1; page <= 100; page++) {
|
|
5926
|
+
const response = await fetchImpl(
|
|
5927
|
+
"https://cursor.com/api/dashboard/get-filtered-usage-events",
|
|
5928
|
+
{
|
|
5929
|
+
method: "POST",
|
|
5930
|
+
redirect: "error",
|
|
5931
|
+
signal: AbortSignal.timeout(15e3),
|
|
5932
|
+
headers: {
|
|
5933
|
+
"Content-Type": "application/json",
|
|
5934
|
+
Origin: "https://cursor.com",
|
|
5935
|
+
Cookie: `WorkosCursorSessionToken=${account.cookie}`
|
|
5936
|
+
},
|
|
5937
|
+
body: JSON.stringify({
|
|
5938
|
+
startDate: from,
|
|
5939
|
+
endDate: to - 1,
|
|
5940
|
+
page,
|
|
5941
|
+
pageSize
|
|
5942
|
+
})
|
|
5943
|
+
}
|
|
5944
|
+
);
|
|
5945
|
+
if (!response.ok) throw new Error("Cursor usage unavailable");
|
|
5946
|
+
const payload = asObj(await response.json());
|
|
5947
|
+
const events = payload?.usageEventsDisplay;
|
|
5948
|
+
if (!Array.isArray(events) || events.length > pageSize)
|
|
5949
|
+
throw new Error("Invalid Cursor page");
|
|
5950
|
+
if (payload?.totalUsageEventsCount !== void 0) {
|
|
5951
|
+
const reported = count(payload.totalUsageEventsCount);
|
|
5952
|
+
if (reported === void 0 || !Number.isInteger(reported) || total !== void 0 && total !== reported)
|
|
5953
|
+
throw new Error("Cursor page count changed");
|
|
5954
|
+
total = reported;
|
|
5955
|
+
}
|
|
5956
|
+
if (events.length) {
|
|
5957
|
+
const key2 = digest(JSON.stringify(events));
|
|
5958
|
+
if (pages.has(key2)) throw new Error("Repeated Cursor page");
|
|
5959
|
+
pages.add(key2);
|
|
5960
|
+
}
|
|
5961
|
+
retrieved += events.length;
|
|
5962
|
+
for (const value of events) {
|
|
5963
|
+
const event = apiContribution(value);
|
|
5964
|
+
if (!event) continue;
|
|
5965
|
+
if (event.tsMs < from || event.tsMs >= to)
|
|
5966
|
+
throw new Error("Cursor event outside query");
|
|
5967
|
+
if (event.id && eventIds.has(event.id)) continue;
|
|
5968
|
+
if (event.id) eventIds.add(event.id);
|
|
5969
|
+
out.push(event);
|
|
5970
|
+
}
|
|
5971
|
+
if (total !== void 0 && retrieved > total)
|
|
5972
|
+
throw new Error("Invalid Cursor page count");
|
|
5973
|
+
if (total !== void 0 && retrieved === total || total === void 0 && events.length < pageSize)
|
|
5974
|
+
return out;
|
|
5975
|
+
if (!events.length) throw new Error("Incomplete Cursor pages");
|
|
5976
|
+
}
|
|
5977
|
+
throw new Error("Cursor page limit");
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5980
|
+
// src/harness/cursor/cache.ts
|
|
5981
|
+
var emptyCache = () => ({
|
|
5982
|
+
version: 1,
|
|
5983
|
+
account: null,
|
|
5984
|
+
windows: {},
|
|
5985
|
+
local: [],
|
|
5986
|
+
sessions: []
|
|
5987
|
+
});
|
|
5988
|
+
var cacheFile = (root, store) => path5.join(
|
|
5989
|
+
homedir9(),
|
|
5990
|
+
".config",
|
|
5991
|
+
"aistack",
|
|
5992
|
+
"cursor",
|
|
5993
|
+
`${digest(`${root}\0${store}`)}.json`
|
|
5994
|
+
);
|
|
5995
|
+
function validContribution(value) {
|
|
5996
|
+
const row = asObj(value);
|
|
5997
|
+
const buckets = asObj(row?.buckets);
|
|
5998
|
+
return !!row && typeof row.session === "string" && typeof row.model === "string" && timestamp(row.tsMs) !== null && (row.id === void 0 || typeof row.id === "string") && (row.source === "api" || row.source === "local") && !!buckets && ["input", "output", "cacheRead", "cacheWrite"].every(
|
|
5999
|
+
(k) => buckets[k] === void 0 || count(buckets[k]) !== void 0
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
6002
|
+
async function loadCache(file) {
|
|
6003
|
+
try {
|
|
6004
|
+
const raw = asObj(JSON.parse(await readFile(file, "utf8")));
|
|
6005
|
+
const windows = asObj(raw?.windows);
|
|
6006
|
+
if (raw?.version !== 1 || !(raw.account === null || typeof raw.account === "string") || !Array.isArray(raw.sessions) || !raw.sessions.every((id) => typeof id === "string") || !Array.isArray(raw.local) || !raw.local.every(validContribution) || !windows)
|
|
6007
|
+
throw new Error("Invalid cache");
|
|
6008
|
+
for (const value of Object.values(windows)) {
|
|
6009
|
+
const w = asObj(value);
|
|
6010
|
+
if (!w || timestamp(w.from) === null || timestamp(w.to) === null || timestamp(w.fetchedAt) === null || !Array.isArray(w.events) || !w.events.every(validContribution))
|
|
6011
|
+
throw new Error("Invalid window");
|
|
6012
|
+
}
|
|
6013
|
+
return { value: raw, complete: true };
|
|
6014
|
+
} catch (error) {
|
|
6015
|
+
return {
|
|
6016
|
+
value: emptyCache(),
|
|
6017
|
+
complete: error.code === "ENOENT"
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
async function saveCache(file, value) {
|
|
6022
|
+
await mkdir(path5.dirname(file), { recursive: true, mode: 448 });
|
|
6023
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
6024
|
+
await writeFile(temporary, JSON.stringify(value), { mode: 384 });
|
|
6025
|
+
await rename(temporary, file);
|
|
6026
|
+
}
|
|
6027
|
+
var WINDOW_MS = 30 * 864e5;
|
|
6028
|
+
async function refreshAccount(input) {
|
|
6029
|
+
const { cache, account, now, sessionIds } = input;
|
|
6030
|
+
if (!account) return cache;
|
|
6031
|
+
const changed = cache.account !== account.scope;
|
|
6032
|
+
const next = {
|
|
6033
|
+
...cache,
|
|
6034
|
+
account: account.scope,
|
|
6035
|
+
windows: changed ? {} : { ...cache.windows }
|
|
6036
|
+
};
|
|
6037
|
+
for (let from = Math.floor(input.sinceMs / WINDOW_MS) * WINDOW_MS; from <= now; from += WINDOW_MS) {
|
|
6038
|
+
const to = Math.min(from + WINDOW_MS, now + 1);
|
|
6039
|
+
const key2 = String(from);
|
|
6040
|
+
const previous = next.windows[key2];
|
|
6041
|
+
if (previous && now - previous.fetchedAt < 3e5) continue;
|
|
6042
|
+
try {
|
|
6043
|
+
const events = (await fetchWindow(account, from, to, input.fetchImpl)).filter((e) => sessionIds.has(e.session));
|
|
6044
|
+
if (previous?.events.length && events.length === 0 && to <= now) continue;
|
|
6045
|
+
next.windows[key2] = { from, to, fetchedAt: now, events };
|
|
6046
|
+
} catch {
|
|
6047
|
+
break;
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
return next;
|
|
6051
|
+
}
|
|
6052
|
+
function cachedEvents(cache, sessionIds) {
|
|
6053
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6054
|
+
return Object.values(cache.windows).sort((a, b) => b.fetchedAt - a.fetchedAt || b.from - a.from).flatMap((w) => w.events).filter((e) => {
|
|
6055
|
+
if (!sessionIds.has(e.session) || e.id && seen.has(e.id)) return false;
|
|
6056
|
+
if (e.id) seen.add(e.id);
|
|
6057
|
+
return true;
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
// src/harness/cursor/scan.ts
|
|
6062
|
+
import path7 from "node:path";
|
|
6063
|
+
|
|
6064
|
+
// src/harness/cursor/workflow.ts
|
|
6065
|
+
import path6 from "node:path";
|
|
6066
|
+
var TOOLS = {
|
|
6067
|
+
read_file: "Read",
|
|
6068
|
+
read_file_v2: "Read",
|
|
6069
|
+
list_dir: "Glob",
|
|
6070
|
+
glob_file_search: "Glob",
|
|
6071
|
+
grep: "Grep",
|
|
6072
|
+
search: "Grep",
|
|
6073
|
+
codebase_search: "Grep",
|
|
6074
|
+
edit_file: "Edit",
|
|
6075
|
+
edit_file_v2: "Edit",
|
|
6076
|
+
search_replace: "Edit",
|
|
6077
|
+
write: "Write",
|
|
6078
|
+
write_file: "Write",
|
|
6079
|
+
delete_file: "Edit",
|
|
6080
|
+
run_terminal_cmd: "Bash",
|
|
6081
|
+
run_terminal_command: "Bash",
|
|
6082
|
+
execute_command: "Bash",
|
|
6083
|
+
web_search: "WebSearch",
|
|
6084
|
+
web_fetch: "WebFetch",
|
|
6085
|
+
ask_question: "ask_question",
|
|
6086
|
+
todo_write: "TodoWrite",
|
|
6087
|
+
task: "Task",
|
|
6088
|
+
skill: "Skill"
|
|
6089
|
+
};
|
|
6090
|
+
var CURSOR_BUILTIN_TOOLS = new Set(
|
|
6091
|
+
Object.keys(TOOLS)
|
|
6092
|
+
);
|
|
6093
|
+
function projectHistory(options) {
|
|
6094
|
+
const { locals, api, usage, aggregate, workflow, sinceMs, now } = options;
|
|
6095
|
+
const nativeOwners = /* @__PURE__ */ new Map();
|
|
6096
|
+
for (const { session } of locals)
|
|
6097
|
+
for (const message of session.messages)
|
|
6098
|
+
if (message.id && message.identityOrigin === "composer-native" && !message.isSidechain && !nativeOwners.has(message.id))
|
|
6099
|
+
nativeOwners.set(message.id, session.id);
|
|
6100
|
+
const seenMessages = /* @__PURE__ */ new Set();
|
|
6101
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
6102
|
+
const configuredServers = /* @__PURE__ */ new Map();
|
|
6103
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
6104
|
+
const inWindow = (t) => t !== null && t >= sinceMs && t <= now;
|
|
6105
|
+
for (const { session } of locals) {
|
|
6106
|
+
const projectWorkspace = session.canonicalWorkspacePath && path6.isAbsolute(session.canonicalWorkspacePath) ? session.canonicalWorkspacePath : void 0;
|
|
6107
|
+
const times = messageTimes(session, api);
|
|
6108
|
+
const scopeOf = (message) => {
|
|
6109
|
+
const parent = message.parentMessageId && nativeOwners.get(message.parentMessageId);
|
|
6110
|
+
return message.isSidechain ? {
|
|
6111
|
+
session: `${session.id}:sidechain`,
|
|
6112
|
+
sidechain: true,
|
|
6113
|
+
...parent ? { parentSession: parent } : {}
|
|
6114
|
+
} : { session: session.id };
|
|
6115
|
+
};
|
|
6116
|
+
for (const [index, message] of session.messages.entries()) {
|
|
6117
|
+
const identity = message.identityOrigin === "composer-native" && message.id ? `native:${message.id}` : `${session.id}:${message.id ?? index}`;
|
|
6118
|
+
if (seenMessages.has(identity)) continue;
|
|
6119
|
+
seenMessages.add(identity);
|
|
6120
|
+
const tsMs = times[index];
|
|
6121
|
+
const scope = scopeOf(message);
|
|
6122
|
+
if (message.id) scopes.set(`${session.id}:${message.id}`, scope);
|
|
6123
|
+
if (tsMs !== null && !inWindow(tsMs)) continue;
|
|
6124
|
+
const common = { ...scope, projectWorkspace, tsMs: tsMs ?? 0 };
|
|
6125
|
+
if (message.role === "user") {
|
|
6126
|
+
if (inWindow(tsMs))
|
|
6127
|
+
workflow?.ingest({
|
|
6128
|
+
...common,
|
|
6129
|
+
type: "response",
|
|
6130
|
+
responseId: `anchor:${message.id ?? index}`
|
|
6131
|
+
});
|
|
6132
|
+
continue;
|
|
6133
|
+
}
|
|
6134
|
+
let questionBack = false;
|
|
6135
|
+
for (const [toolIndex, call] of (message.toolCalls ?? []).entries()) {
|
|
6136
|
+
const id = call.identityOrigin === "source-native" && call.id ? `native:${call.id}` : `${identity}:${call.id ?? toolIndex}`;
|
|
6137
|
+
if (seenTools.has(id)) continue;
|
|
6138
|
+
seenTools.add(id);
|
|
6139
|
+
bump(aggregate.toolCalls, call.name);
|
|
6140
|
+
const tool = TOOLS[call.name] ?? call.name;
|
|
6141
|
+
const arg = asStr(call.params?.command) ?? asStr(call.params?.cmd) ?? asStr(call.params?.skill) ?? asStr(call.params?.subagent_type) ?? "";
|
|
6142
|
+
if (tool === "WebSearch") aggregate.webSearchRequests++;
|
|
6143
|
+
if (tool === "WebFetch") aggregate.webFetchRequests++;
|
|
6144
|
+
if (tool === "Skill" && arg) bump(aggregate.skillCalls, arg);
|
|
6145
|
+
if (tool === "Task") bump(aggregate.subagentCalls, arg || "(unknown)");
|
|
6146
|
+
const mcp = /^mcp__(.+?)__(.+)$/.exec(call.name);
|
|
6147
|
+
let server = mcp?.[1];
|
|
6148
|
+
if (!server && call.name.startsWith("mcp_")) {
|
|
6149
|
+
const directory = projectWorkspace ?? process.cwd();
|
|
6150
|
+
let names = configuredServers.get(directory);
|
|
6151
|
+
if (!names) {
|
|
6152
|
+
names = detectMcpServers(directory).filter((r) => r.group === "cursor").map((r) => r.name).sort((a, b) => b.length - a.length);
|
|
6153
|
+
configuredServers.set(directory, names);
|
|
6154
|
+
}
|
|
6155
|
+
server = names.find((name) => call.name.startsWith(`mcp_${name}_`));
|
|
6156
|
+
}
|
|
6157
|
+
if (server) {
|
|
6158
|
+
bump(aggregate.mcpServerCalls, server);
|
|
6159
|
+
bump(aggregate.mcpToolCalls, call.name);
|
|
6160
|
+
}
|
|
6161
|
+
questionBack ||= tool === "ask_question";
|
|
6162
|
+
if (inWindow(tsMs))
|
|
6163
|
+
workflow?.ingest({
|
|
6164
|
+
...common,
|
|
6165
|
+
type: "event",
|
|
6166
|
+
tool,
|
|
6167
|
+
arg,
|
|
6168
|
+
batchId: id
|
|
6169
|
+
});
|
|
6170
|
+
}
|
|
6171
|
+
if (inWindow(tsMs))
|
|
6172
|
+
workflow?.ingest({
|
|
6173
|
+
...common,
|
|
6174
|
+
type: "turn",
|
|
6175
|
+
turnId: message.id ?? `assistant:${index}`,
|
|
6176
|
+
questionBack
|
|
6177
|
+
});
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
if (!workflow) return;
|
|
6181
|
+
const projects = new Map(
|
|
6182
|
+
locals.map(({ session }) => [session.id, session.canonicalWorkspacePath])
|
|
6183
|
+
);
|
|
6184
|
+
for (const [index, row] of usage.entries()) {
|
|
6185
|
+
if (!inWindow(row.tsMs)) continue;
|
|
6186
|
+
const project = projects.get(row.session);
|
|
6187
|
+
const scope = row.id && row.source === "local" ? scopes.get(`${row.session}:${row.id}`) : void 0;
|
|
6188
|
+
workflow.ingest({
|
|
6189
|
+
...scope ?? {
|
|
6190
|
+
session: row.sidechain ? `${row.session}:sidechain` : row.session,
|
|
6191
|
+
sidechain: row.sidechain
|
|
6192
|
+
},
|
|
6193
|
+
...project && path6.isAbsolute(project) ? { projectWorkspace: project } : {},
|
|
6194
|
+
type: "response",
|
|
6195
|
+
tsMs: row.tsMs,
|
|
6196
|
+
responseId: `${row.source}:${row.id ?? index}`,
|
|
6197
|
+
model: normalizeModel(row.model),
|
|
6198
|
+
routingTokens: (row.buckets.input ?? 0) + (row.buckets.output ?? 0) + (row.buckets.cacheRead ?? 0) + (row.buckets.cacheWrite ?? 0)
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6203
|
+
// src/harness/cursor/scan.ts
|
|
6204
|
+
async function scan3(options) {
|
|
6205
|
+
const root = options.root ?? dataPath();
|
|
6206
|
+
const now = options.now ?? Date.now();
|
|
6207
|
+
const file = options.cachePath ?? cacheFile(root, storeRoot());
|
|
6208
|
+
const local = await (options.readLocalImpl ?? readLocal)(
|
|
6209
|
+
root,
|
|
6210
|
+
options.onProgress
|
|
6211
|
+
);
|
|
6212
|
+
const loaded = await loadCache(file);
|
|
6213
|
+
let complete = local.complete && loaded.complete;
|
|
6214
|
+
const ids = new Set(local.sessions.map((s) => s.session.id));
|
|
6215
|
+
if ([
|
|
6216
|
+
...loaded.value.local,
|
|
6217
|
+
...Object.values(loaded.value.windows).flatMap((w) => w.events)
|
|
6218
|
+
].some((row) => row.tsMs >= options.sinceMs && !ids.has(row.session)))
|
|
6219
|
+
complete = false;
|
|
6220
|
+
const account = await (options.accountImpl ?? existingAccount)(root);
|
|
6221
|
+
const cache = await refreshAccount({
|
|
6222
|
+
cache: loaded.value,
|
|
6223
|
+
account,
|
|
6224
|
+
sinceMs: options.sinceMs,
|
|
6225
|
+
now,
|
|
6226
|
+
sessionIds: ids,
|
|
6227
|
+
fetchImpl: options.fetchImpl
|
|
6228
|
+
});
|
|
6229
|
+
const api = cachedEvents(cache, ids);
|
|
6230
|
+
const native = /* @__PURE__ */ new Set();
|
|
6231
|
+
const contributions = local.sessions.flatMap((s) => localContributions(s, api)).filter((row) => {
|
|
6232
|
+
if (!row.nativeId) return true;
|
|
6233
|
+
if (native.has(row.nativeId)) return false;
|
|
6234
|
+
native.add(row.nativeId);
|
|
6235
|
+
return true;
|
|
6236
|
+
});
|
|
6237
|
+
const dated = new Set(contributions.map((row) => row.session));
|
|
6238
|
+
if (loaded.value.local.some(
|
|
6239
|
+
(row) => row.tsMs >= options.sinceMs && !dated.has(row.session)
|
|
6240
|
+
))
|
|
6241
|
+
complete = false;
|
|
6242
|
+
const previousDates = /* @__PURE__ */ new Map();
|
|
6243
|
+
for (const row of [
|
|
6244
|
+
...loaded.value.local,
|
|
6245
|
+
...cachedEvents(loaded.value, ids)
|
|
6246
|
+
]) {
|
|
6247
|
+
const dates = previousDates.get(row.session) ?? /* @__PURE__ */ new Set();
|
|
6248
|
+
dates.add(utcDateOf(row.tsMs));
|
|
6249
|
+
previousDates.set(row.session, dates);
|
|
6250
|
+
}
|
|
6251
|
+
if (complete) {
|
|
6252
|
+
cache.local = contributions;
|
|
6253
|
+
cache.sessions = [...ids].sort();
|
|
6254
|
+
try {
|
|
6255
|
+
await saveCache(file, cache);
|
|
6256
|
+
} catch {
|
|
6257
|
+
complete = false;
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
const aggregate = createAggregate();
|
|
6261
|
+
const workflowLocal = createWorkflowLocalSources();
|
|
6262
|
+
const workflow = createHarnessWorkflowReducer("cursor", workflowLocal);
|
|
6263
|
+
const sessions = new Map(local.sessions.map((s) => [s.session.id, s]));
|
|
6264
|
+
const sessionDates = previousDates;
|
|
6265
|
+
for (const { session } of local.sessions) {
|
|
6266
|
+
for (const message of session.messages)
|
|
6267
|
+
if (message.model) {
|
|
6268
|
+
const model = normalizeModel(message.model);
|
|
6269
|
+
if (!aggregate.byModel.has(model))
|
|
6270
|
+
aggregate.byModel.set(model, emptyUsage());
|
|
6271
|
+
}
|
|
6272
|
+
const project = session.canonicalWorkspacePath;
|
|
6273
|
+
if (project && path7.isAbsolute(project)) aggregate.projectDirs.add(project);
|
|
6274
|
+
if (session.metadata?.cursorVersion)
|
|
6275
|
+
aggregate.ccVersions.add(session.metadata.cursorVersion);
|
|
6276
|
+
const times = messageTimes(session, api).filter(
|
|
6277
|
+
(t) => t !== null && t >= options.sinceMs && t <= now
|
|
6278
|
+
);
|
|
6279
|
+
if (times.length) {
|
|
6280
|
+
aggregate.sessions.add(session.id);
|
|
6281
|
+
noteSessionStart(aggregate, session.id, Math.min(...times));
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6284
|
+
const usage = reconcile(contributions, api);
|
|
6285
|
+
projectHistory({
|
|
6286
|
+
locals: local.sessions,
|
|
6287
|
+
api,
|
|
6288
|
+
usage,
|
|
6289
|
+
aggregate,
|
|
6290
|
+
workflow: options.publishWorkflow === false ? void 0 : workflow,
|
|
6291
|
+
sinceMs: options.sinceMs,
|
|
6292
|
+
now
|
|
6293
|
+
});
|
|
6294
|
+
for (const contribution of usage) {
|
|
6295
|
+
const { tsMs, buckets, session, sidechain } = contribution;
|
|
6296
|
+
const dates = sessionDates.get(session) ?? /* @__PURE__ */ new Set();
|
|
6297
|
+
dates.add(utcDateOf(tsMs));
|
|
6298
|
+
sessionDates.set(session, dates);
|
|
6299
|
+
if (tsMs < options.sinceMs || tsMs > now) continue;
|
|
6300
|
+
const model = normalizeModel(contribution.model);
|
|
6301
|
+
const counts2 = {
|
|
6302
|
+
input: buckets.input ?? 0,
|
|
6303
|
+
output: buckets.output ?? 0,
|
|
6304
|
+
cacheRead: buckets.cacheRead ?? 0,
|
|
6305
|
+
cacheWrite5m: 0,
|
|
6306
|
+
cacheWrite1h: 0,
|
|
6307
|
+
cacheWriteUnsplit: buckets.cacheWrite ?? 0
|
|
6308
|
+
};
|
|
6309
|
+
addModelUsage(
|
|
6310
|
+
aggregate,
|
|
6311
|
+
model,
|
|
6312
|
+
counts2,
|
|
6313
|
+
apiEquivalentCost(model, counts2, tsMs),
|
|
6314
|
+
1,
|
|
6315
|
+
{ tsMs, sidechain }
|
|
6316
|
+
);
|
|
6317
|
+
aggregate.records++;
|
|
6318
|
+
aggregate.assistantRecords++;
|
|
6319
|
+
aggregate.distinctResponses++;
|
|
6320
|
+
aggregate.sessions.add(session);
|
|
6321
|
+
aggregate.activeDays.add(utcDateOf(tsMs));
|
|
6322
|
+
aggregate.firstTs = Math.min(aggregate.firstTs ?? tsMs, tsMs);
|
|
6323
|
+
aggregate.lastTs = Math.max(aggregate.lastTs ?? tsMs, tsMs);
|
|
6324
|
+
if (sidechain) aggregate.sidechainTokens += countsTotal(counts2);
|
|
6325
|
+
else aggregate.mainTokens += countsTotal(counts2);
|
|
6326
|
+
noteSessionStart(aggregate, session, tsMs);
|
|
6327
|
+
const project = sessions.get(session)?.session.canonicalWorkspacePath;
|
|
6328
|
+
if (project && path7.isAbsolute(project))
|
|
6329
|
+
noteProjectDay(aggregate, project, tsMs);
|
|
6330
|
+
}
|
|
6331
|
+
aggregate.files = local.stats.filesRead;
|
|
6332
|
+
return {
|
|
6333
|
+
aggregate,
|
|
6334
|
+
stats: local.stats,
|
|
6335
|
+
workflow: workflow.finish(),
|
|
6336
|
+
workflowLocal,
|
|
6337
|
+
scanComplete: complete,
|
|
6338
|
+
sessionDates
|
|
6339
|
+
};
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
// src/harness/cursor/adapter.ts
|
|
6343
|
+
var CURSOR_HARNESS_NAME = "cursor";
|
|
6344
|
+
var cursorAdapter = {
|
|
6345
|
+
name: CURSOR_HARNESS_NAME,
|
|
6346
|
+
builtinTools: CURSOR_BUILTIN_TOOLS,
|
|
6347
|
+
async detect(options) {
|
|
6348
|
+
const roots = options.roots ?? [dataPath()];
|
|
6349
|
+
for (const root of roots) {
|
|
6350
|
+
const held = await loadCache(cacheFile(root, storeRoot()));
|
|
6351
|
+
if (!held.complete || [
|
|
6352
|
+
...held.value.local,
|
|
6353
|
+
...cachedEvents(held.value, new Set(held.value.sessions))
|
|
6354
|
+
].some((row) => row.tsMs >= options.sinceMs))
|
|
6355
|
+
return true;
|
|
6356
|
+
const local = await readLocal(root);
|
|
6357
|
+
if (!local.complete || local.sessions.some(
|
|
6358
|
+
({ session }) => messageTimes(session).some((t) => t !== null && t >= options.sinceMs)
|
|
6359
|
+
))
|
|
6360
|
+
return true;
|
|
6361
|
+
if (local.sessions.some(
|
|
6362
|
+
({ session }) => session.messages.length > 0 && messageTimes(session).every((t) => t === null)
|
|
6363
|
+
))
|
|
6364
|
+
return true;
|
|
6365
|
+
}
|
|
6366
|
+
return false;
|
|
6367
|
+
},
|
|
6368
|
+
scan: scan3
|
|
6369
|
+
};
|
|
6370
|
+
|
|
5559
6371
|
// src/harness/grok/analyzer.ts
|
|
5560
6372
|
function createAggregate4() {
|
|
5561
6373
|
const workflowLocal = createWorkflowLocalSources();
|
|
@@ -5572,41 +6384,41 @@ var createGrokEventState = (parentSession) => ({
|
|
|
5572
6384
|
var bump3 = (map, key2) => {
|
|
5573
6385
|
map.set(key2, (map.get(key2) ?? 0) + 1);
|
|
5574
6386
|
};
|
|
5575
|
-
var toolMetadata = (
|
|
5576
|
-
const meta = asObj(
|
|
6387
|
+
var toolMetadata = (update2) => {
|
|
6388
|
+
const meta = asObj(update2._meta);
|
|
5577
6389
|
return meta && asObj(meta["x.ai/tool"]);
|
|
5578
6390
|
};
|
|
5579
6391
|
function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
5580
6392
|
const root = asObj(value);
|
|
5581
6393
|
const params = root && asObj(root.params);
|
|
5582
|
-
const
|
|
6394
|
+
const update2 = params && asObj(params.update);
|
|
5583
6395
|
const session = params && asStr(params.sessionId);
|
|
5584
6396
|
const tsMs = timestampMs(
|
|
5585
6397
|
asObj(params?._meta)?.agentTimestampMs ?? root?.timestamp
|
|
5586
6398
|
);
|
|
5587
|
-
if (!
|
|
5588
|
-
const kind = asStr(
|
|
6399
|
+
if (!update2 || !session || tsMs === null) return;
|
|
6400
|
+
const kind = asStr(update2.sessionUpdate);
|
|
5589
6401
|
if (kind === "tool_call") {
|
|
5590
|
-
const id = asStr(
|
|
5591
|
-
const metadata = toolMetadata(
|
|
5592
|
-
const name = asName(metadata?.name ??
|
|
6402
|
+
const id = asStr(update2.toolCallId);
|
|
6403
|
+
const metadata = toolMetadata(update2);
|
|
6404
|
+
const name = asName(metadata?.name ?? update2.toolName);
|
|
5593
6405
|
if (!id || !name || state.tools.has(id)) return;
|
|
5594
|
-
const raw = asObj(
|
|
6406
|
+
const raw = asObj(update2.input);
|
|
5595
6407
|
const arg = asStr(raw?.command ?? raw?.query ?? raw?.skill ?? raw?.name);
|
|
5596
6408
|
state.tools.set(id, { name, ...arg ? { arg } : {}, tsMs });
|
|
5597
6409
|
return;
|
|
5598
6410
|
}
|
|
5599
6411
|
if (sinceMs !== void 0 && tsMs < sinceMs) return;
|
|
5600
6412
|
if (kind === "tool_call_update") {
|
|
5601
|
-
const id = asStr(
|
|
5602
|
-
if (!id || asStr(
|
|
6413
|
+
const id = asStr(update2.toolCallId);
|
|
6414
|
+
if (!id || asStr(update2.status) !== "completed") return;
|
|
5603
6415
|
completeTool(agg, state, id, session, projectDir, tsMs);
|
|
5604
6416
|
return;
|
|
5605
6417
|
}
|
|
5606
6418
|
if (kind !== "turn_completed") return;
|
|
5607
|
-
const usage = asObj(
|
|
6419
|
+
const usage = asObj(update2.usage);
|
|
5608
6420
|
if (!usage) return;
|
|
5609
|
-
const prompt = asStr(
|
|
6421
|
+
const prompt = asStr(update2.prompt_id) ?? `turn:${tsMs}`;
|
|
5610
6422
|
for (const [model, raw] of Object.entries(asObj(usage.modelUsage) ?? {})) {
|
|
5611
6423
|
const row = asObj(raw);
|
|
5612
6424
|
agg.workflow.ingest({
|
|
@@ -5620,7 +6432,7 @@ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
|
5620
6432
|
thinkingTokens: asNum(row?.reasoningTokens),
|
|
5621
6433
|
responseTokens: asNum(row?.outputTokens),
|
|
5622
6434
|
routingTokens: asNum(row?.outputTokens),
|
|
5623
|
-
...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(
|
|
6435
|
+
...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(update2.elapsed_ms) > 0 ? { durationSec: asNum(update2.elapsed_ms) / 1e3 } : {}
|
|
5624
6436
|
});
|
|
5625
6437
|
}
|
|
5626
6438
|
agg.workflow.ingest({
|
|
@@ -5630,7 +6442,7 @@ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
|
|
|
5630
6442
|
parentSession: state.parentSession,
|
|
5631
6443
|
tsMs,
|
|
5632
6444
|
turnId: prompt,
|
|
5633
|
-
questionBack: asStr(
|
|
6445
|
+
questionBack: asStr(update2.stop_reason) === "question"
|
|
5634
6446
|
});
|
|
5635
6447
|
}
|
|
5636
6448
|
function completeTool(agg, state, id, session, projectDir, tsMs) {
|
|
@@ -5750,10 +6562,10 @@ function sidecarContributions(value, projectDir) {
|
|
|
5750
6562
|
function terminalContribution(value, projectDir) {
|
|
5751
6563
|
const root = asObj(value);
|
|
5752
6564
|
const params = root && asObj(root.params);
|
|
5753
|
-
const
|
|
6565
|
+
const update2 = params && asObj(params.update);
|
|
5754
6566
|
const meta = params && asObj(params._meta);
|
|
5755
|
-
if (!
|
|
5756
|
-
const usage = asObj(
|
|
6567
|
+
if (!update2 || asStr(update2.sessionUpdate) !== "turn_completed") return null;
|
|
6568
|
+
const usage = asObj(update2.usage);
|
|
5757
6569
|
const sessionId = params && asStr(params.sessionId);
|
|
5758
6570
|
const tsMs = timestampMs(meta?.agentTimestampMs ?? root?.timestamp);
|
|
5759
6571
|
if (!usage || !sessionId || tsMs === null) return null;
|
|
@@ -5766,7 +6578,7 @@ function terminalContribution(value, projectDir) {
|
|
|
5766
6578
|
sessionId,
|
|
5767
6579
|
projectDir,
|
|
5768
6580
|
tsMs,
|
|
5769
|
-
...asNum(
|
|
6581
|
+
...asNum(update2.elapsed_ms) > 0 ? { durationMs: asNum(update2.elapsed_ms) } : {},
|
|
5770
6582
|
models
|
|
5771
6583
|
};
|
|
5772
6584
|
}
|
|
@@ -5798,15 +6610,15 @@ function ingestContribution(agg, row) {
|
|
|
5798
6610
|
|
|
5799
6611
|
// src/harness/grok/scan.ts
|
|
5800
6612
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
5801
|
-
import { readdir as readdir4, readFile, stat as
|
|
5802
|
-
import { homedir as
|
|
5803
|
-
import
|
|
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";
|
|
5804
6616
|
import readline2 from "node:readline";
|
|
5805
6617
|
import { parse as parseToml3 } from "smol-toml";
|
|
5806
6618
|
function sessionRoots() {
|
|
5807
6619
|
return [
|
|
5808
|
-
|
|
5809
|
-
process.env.GROK_HOME ||
|
|
6620
|
+
path8.join(
|
|
6621
|
+
process.env.GROK_HOME || path8.join(homedir10(), ".grok"),
|
|
5810
6622
|
"sessions"
|
|
5811
6623
|
)
|
|
5812
6624
|
];
|
|
@@ -5818,7 +6630,7 @@ async function stableRead(file, jsonl) {
|
|
|
5818
6630
|
for (const delay of delays) {
|
|
5819
6631
|
if (delay) await pause(delay);
|
|
5820
6632
|
try {
|
|
5821
|
-
const before = await
|
|
6633
|
+
const before = await stat5(file);
|
|
5822
6634
|
if (!before.isFile()) return { complete: false };
|
|
5823
6635
|
if (jsonl) {
|
|
5824
6636
|
const lines2 = [];
|
|
@@ -5834,12 +6646,12 @@ async function stableRead(file, jsonl) {
|
|
|
5834
6646
|
lines2.push(null);
|
|
5835
6647
|
}
|
|
5836
6648
|
}
|
|
5837
|
-
const after = await
|
|
6649
|
+
const after = await stat5(file);
|
|
5838
6650
|
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
5839
6651
|
return { lines: lines2, complete: true };
|
|
5840
6652
|
} else {
|
|
5841
|
-
const raw = await
|
|
5842
|
-
const after = await
|
|
6653
|
+
const raw = await readFile2(file, "utf8");
|
|
6654
|
+
const after = await stat5(file);
|
|
5843
6655
|
if (before.size === after.size && before.mtimeMs === after.mtimeMs)
|
|
5844
6656
|
return { json: JSON.parse(raw), complete: true };
|
|
5845
6657
|
}
|
|
@@ -5854,12 +6666,12 @@ async function directories(root) {
|
|
|
5854
6666
|
const out = [];
|
|
5855
6667
|
for (const workspace of workspaces) {
|
|
5856
6668
|
if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
|
|
5857
|
-
const workspacePath =
|
|
6669
|
+
const workspacePath = path8.join(root, workspace.name);
|
|
5858
6670
|
for (const session of await readdir4(workspacePath, {
|
|
5859
6671
|
withFileTypes: true
|
|
5860
6672
|
})) {
|
|
5861
6673
|
if (session.isDirectory() && !session.isSymbolicLink())
|
|
5862
|
-
out.push(
|
|
6674
|
+
out.push(path8.join(workspacePath, session.name));
|
|
5863
6675
|
}
|
|
5864
6676
|
}
|
|
5865
6677
|
return out.sort();
|
|
@@ -5871,7 +6683,7 @@ async function modelAliasesForRoot(root) {
|
|
|
5871
6683
|
if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
|
|
5872
6684
|
try {
|
|
5873
6685
|
const parsed = asObj(
|
|
5874
|
-
parseToml3(await
|
|
6686
|
+
parseToml3(await readFile2(path8.join(root, "..", "config.toml"), "utf8"))
|
|
5875
6687
|
);
|
|
5876
6688
|
if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
|
|
5877
6689
|
const models = asObj(parsed.model);
|
|
@@ -5887,7 +6699,7 @@ async function modelAliasesForRoot(root) {
|
|
|
5887
6699
|
return /* @__PURE__ */ new Map();
|
|
5888
6700
|
}
|
|
5889
6701
|
}
|
|
5890
|
-
async function
|
|
6702
|
+
async function scan4(agg, opts = {}) {
|
|
5891
6703
|
const stats = emptyScanStats();
|
|
5892
6704
|
const sessionDates = /* @__PURE__ */ new Map();
|
|
5893
6705
|
const candidates = /* @__PURE__ */ new Map();
|
|
@@ -5910,21 +6722,21 @@ async function scan3(agg, opts = {}) {
|
|
|
5910
6722
|
const files = new Map(
|
|
5911
6723
|
entries.filter(
|
|
5912
6724
|
(e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
|
|
5913
|
-
).map((e) => [e.name,
|
|
6725
|
+
).map((e) => [e.name, path8.join(dir, e.name)])
|
|
5914
6726
|
);
|
|
5915
6727
|
let projectDir = dir;
|
|
5916
|
-
let sessionFallback =
|
|
6728
|
+
let sessionFallback = path8.basename(dir);
|
|
5917
6729
|
let child = false;
|
|
5918
6730
|
let parentSession;
|
|
5919
6731
|
const summary = files.get("summary.json");
|
|
5920
6732
|
if (summary) {
|
|
5921
|
-
const
|
|
5922
|
-
if (!
|
|
6733
|
+
const read3 = await stableRead(summary, false);
|
|
6734
|
+
if (!read3.complete) {
|
|
5923
6735
|
complete = false;
|
|
5924
6736
|
stats.filesUnreadable++;
|
|
5925
6737
|
continue;
|
|
5926
6738
|
}
|
|
5927
|
-
const value = asObj(
|
|
6739
|
+
const value = asObj(read3.json);
|
|
5928
6740
|
const info = value && asObj(value.info);
|
|
5929
6741
|
projectDir = asStr(info?.cwd) ?? asStr(value?.cwd) ?? dir;
|
|
5930
6742
|
sessionFallback = asStr(value?.sessionId) ?? sessionFallback;
|
|
@@ -5936,14 +6748,14 @@ async function scan3(agg, opts = {}) {
|
|
|
5936
6748
|
const usage = files.get("usage.json");
|
|
5937
6749
|
if (usage) {
|
|
5938
6750
|
stats.filesFound++;
|
|
5939
|
-
const
|
|
5940
|
-
if (!
|
|
6751
|
+
const read3 = await stableRead(usage, false);
|
|
6752
|
+
if (!read3.complete) {
|
|
5941
6753
|
complete = false;
|
|
5942
6754
|
stats.filesUnreadable++;
|
|
5943
6755
|
continue;
|
|
5944
6756
|
}
|
|
5945
6757
|
stats.filesRead++;
|
|
5946
|
-
rows = sidecarContributions(
|
|
6758
|
+
rows = sidecarContributions(read3.json, projectDir);
|
|
5947
6759
|
if (rows.length > 0) precedence = 2;
|
|
5948
6760
|
}
|
|
5949
6761
|
if (child) {
|
|
@@ -5955,14 +6767,14 @@ async function scan3(agg, opts = {}) {
|
|
|
5955
6767
|
if (updates) {
|
|
5956
6768
|
const useTerminalUsage = rows.length === 0 && !child;
|
|
5957
6769
|
stats.filesFound++;
|
|
5958
|
-
const
|
|
5959
|
-
if (!
|
|
6770
|
+
const read3 = await stableRead(updates, true);
|
|
6771
|
+
if (!read3.complete) {
|
|
5960
6772
|
complete = false;
|
|
5961
6773
|
stats.filesUnreadable++;
|
|
5962
6774
|
continue;
|
|
5963
6775
|
}
|
|
5964
6776
|
stats.filesRead++;
|
|
5965
|
-
for (const value of
|
|
6777
|
+
for (const value of read3.lines ?? []) {
|
|
5966
6778
|
if (value === null) {
|
|
5967
6779
|
agg.parseErrors++;
|
|
5968
6780
|
continue;
|
|
@@ -5978,14 +6790,14 @@ async function scan3(agg, opts = {}) {
|
|
|
5978
6790
|
const events = files.get("events.jsonl");
|
|
5979
6791
|
if (events) {
|
|
5980
6792
|
stats.filesFound++;
|
|
5981
|
-
const
|
|
5982
|
-
if (!
|
|
6793
|
+
const read3 = await stableRead(events, true);
|
|
6794
|
+
if (!read3.complete) {
|
|
5983
6795
|
complete = false;
|
|
5984
6796
|
stats.filesUnreadable++;
|
|
5985
6797
|
continue;
|
|
5986
6798
|
}
|
|
5987
6799
|
stats.filesRead++;
|
|
5988
|
-
for (const value of
|
|
6800
|
+
for (const value of read3.lines ?? []) {
|
|
5989
6801
|
if (value === null) agg.parseErrors++;
|
|
5990
6802
|
else
|
|
5991
6803
|
ingestEvent2(
|
|
@@ -6048,7 +6860,7 @@ var grokAdapter = {
|
|
|
6048
6860
|
),
|
|
6049
6861
|
async scan(opts) {
|
|
6050
6862
|
const aggregate = createAggregate4();
|
|
6051
|
-
const result = await
|
|
6863
|
+
const result = await scan4(aggregate, opts);
|
|
6052
6864
|
return {
|
|
6053
6865
|
aggregate,
|
|
6054
6866
|
stats: result.stats,
|
|
@@ -6244,14 +7056,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
|
|
|
6244
7056
|
|
|
6245
7057
|
// src/harness/opencode/scan.ts
|
|
6246
7058
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
6247
|
-
import { readdir as readdir5, stat as
|
|
6248
|
-
import { homedir as
|
|
6249
|
-
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";
|
|
6250
7062
|
var OPENCODE_MIGRATION_CEILING = 20260622202450;
|
|
6251
7063
|
function opencodeDataDirs() {
|
|
6252
7064
|
const xdg = process.env.XDG_DATA_HOME;
|
|
6253
|
-
const base = xdg ||
|
|
6254
|
-
return [
|
|
7065
|
+
const base = xdg || path9.join(homedir11(), ".local", "share");
|
|
7066
|
+
return [path9.join(base, "opencode")];
|
|
6255
7067
|
}
|
|
6256
7068
|
function isStoreFile(basename2) {
|
|
6257
7069
|
return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
|
|
@@ -6261,7 +7073,7 @@ async function dbFilesIn(root) {
|
|
|
6261
7073
|
if (override) return [override];
|
|
6262
7074
|
try {
|
|
6263
7075
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
6264
|
-
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) =>
|
|
7076
|
+
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path9.join(root, e.name)).sort();
|
|
6265
7077
|
} catch {
|
|
6266
7078
|
return [];
|
|
6267
7079
|
}
|
|
@@ -6281,7 +7093,7 @@ function errorClass2(e) {
|
|
|
6281
7093
|
return e instanceof Error ? e.constructor.name : "unknown";
|
|
6282
7094
|
}
|
|
6283
7095
|
var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
|
|
6284
|
-
async function
|
|
7096
|
+
async function scan5(agg, opts = {}) {
|
|
6285
7097
|
const stats = emptyScanStats();
|
|
6286
7098
|
const open2 = await loadSqlite();
|
|
6287
7099
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -6298,7 +7110,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6298
7110
|
} catch (e) {
|
|
6299
7111
|
stats.filesUnreadable++;
|
|
6300
7112
|
stats.unreadableFiles.push({
|
|
6301
|
-
path:
|
|
7113
|
+
path: path9.basename(file),
|
|
6302
7114
|
reason: errorClass2(e)
|
|
6303
7115
|
});
|
|
6304
7116
|
}
|
|
@@ -6465,8 +7277,8 @@ function pickTs(jsonTs, columnTs) {
|
|
|
6465
7277
|
}
|
|
6466
7278
|
function opencodeConfigFile() {
|
|
6467
7279
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
6468
|
-
const base = xdg ||
|
|
6469
|
-
return
|
|
7280
|
+
const base = xdg || path9.join(homedir11(), ".config");
|
|
7281
|
+
return path9.join(base, "opencode", "opencode.json");
|
|
6470
7282
|
}
|
|
6471
7283
|
function readConfiguredMcpServers2(agg, state, configFile) {
|
|
6472
7284
|
const file = configFile ?? opencodeConfigFile();
|
|
@@ -6518,7 +7330,7 @@ async function detectOpencode(opts) {
|
|
|
6518
7330
|
if (open2 === null) return false;
|
|
6519
7331
|
for (const root of opts.roots ?? opencodeDataDirs()) {
|
|
6520
7332
|
for (const file of await dbFilesIn(root)) {
|
|
6521
|
-
if (!await
|
|
7333
|
+
if (!await exists4(file)) continue;
|
|
6522
7334
|
let db = null;
|
|
6523
7335
|
try {
|
|
6524
7336
|
db = open2(file);
|
|
@@ -6538,9 +7350,9 @@ async function detectOpencode(opts) {
|
|
|
6538
7350
|
}
|
|
6539
7351
|
return false;
|
|
6540
7352
|
}
|
|
6541
|
-
async function
|
|
7353
|
+
async function exists4(p8) {
|
|
6542
7354
|
try {
|
|
6543
|
-
await
|
|
7355
|
+
await stat6(p8);
|
|
6544
7356
|
return true;
|
|
6545
7357
|
} catch {
|
|
6546
7358
|
return false;
|
|
@@ -6560,7 +7372,7 @@ var opencodeAdapter = {
|
|
|
6560
7372
|
},
|
|
6561
7373
|
async scan(opts) {
|
|
6562
7374
|
const aggregate = createAggregate5();
|
|
6563
|
-
const stats = await
|
|
7375
|
+
const stats = await scan5(aggregate, {
|
|
6564
7376
|
sinceMs: opts.sinceMs,
|
|
6565
7377
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6566
7378
|
});
|
|
@@ -6768,17 +7580,17 @@ function readCounts2(usageRaw) {
|
|
|
6768
7580
|
|
|
6769
7581
|
// src/harness/pi/scan.ts
|
|
6770
7582
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
6771
|
-
import { readdir as readdir6, realpath as realpath3, stat as
|
|
6772
|
-
import { homedir as
|
|
6773
|
-
import
|
|
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";
|
|
6774
7586
|
import readline3 from "node:readline";
|
|
6775
7587
|
var MAX_SESSION_VERSION = 3;
|
|
6776
7588
|
function piAgentDir() {
|
|
6777
|
-
return process.env.PI_CODING_AGENT_DIR ||
|
|
7589
|
+
return process.env.PI_CODING_AGENT_DIR || path10.join(homedir12(), ".pi", "agent");
|
|
6778
7590
|
}
|
|
6779
7591
|
function sessionRoots2() {
|
|
6780
7592
|
const override = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
6781
|
-
return [override ||
|
|
7593
|
+
return [override || path10.join(piAgentDir(), "sessions")];
|
|
6782
7594
|
}
|
|
6783
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$/;
|
|
6784
7596
|
function isSessionFile(basename2) {
|
|
@@ -6792,17 +7604,17 @@ async function* walkSessions(dir) {
|
|
|
6792
7604
|
return;
|
|
6793
7605
|
}
|
|
6794
7606
|
for (const e of entries) {
|
|
6795
|
-
const full =
|
|
7607
|
+
const full = path10.join(dir, e.name);
|
|
6796
7608
|
if (e.isDirectory()) yield* walkSessions(full);
|
|
6797
7609
|
else if (e.isFile() && isSessionFile(e.name)) yield full;
|
|
6798
7610
|
}
|
|
6799
7611
|
}
|
|
6800
|
-
async function
|
|
7612
|
+
async function scan6(agg, opts = {}) {
|
|
6801
7613
|
const stats = emptyScanStats();
|
|
6802
7614
|
const visited = /* @__PURE__ */ new Set();
|
|
6803
7615
|
const fold = createFoldState();
|
|
6804
7616
|
for (const root of opts.roots ?? sessionRoots2()) {
|
|
6805
|
-
if (!await
|
|
7617
|
+
if (!await exists5(root)) continue;
|
|
6806
7618
|
for await (const file of walkSessions(root)) {
|
|
6807
7619
|
stats.filesFound++;
|
|
6808
7620
|
let resolved;
|
|
@@ -6818,7 +7630,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6818
7630
|
visited.add(resolved);
|
|
6819
7631
|
if (opts.sinceMs !== void 0) {
|
|
6820
7632
|
try {
|
|
6821
|
-
const st = await
|
|
7633
|
+
const st = await stat7(file);
|
|
6822
7634
|
if (st.mtimeMs < opts.sinceMs) {
|
|
6823
7635
|
stats.filesSkippedByMtime++;
|
|
6824
7636
|
continue;
|
|
@@ -6840,7 +7652,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6840
7652
|
stats.filesUnreadable++;
|
|
6841
7653
|
stats.filesRead--;
|
|
6842
7654
|
stats.unreadableFiles.push({
|
|
6843
|
-
path:
|
|
7655
|
+
path: path10.relative(root, file),
|
|
6844
7656
|
reason: "version-too-new"
|
|
6845
7657
|
});
|
|
6846
7658
|
}
|
|
@@ -6848,7 +7660,7 @@ async function scan5(agg, opts = {}) {
|
|
|
6848
7660
|
stats.filesUnreadable++;
|
|
6849
7661
|
stats.filesRead--;
|
|
6850
7662
|
stats.unreadableFiles.push({
|
|
6851
|
-
path:
|
|
7663
|
+
path: path10.relative(root, file),
|
|
6852
7664
|
reason: "read-error"
|
|
6853
7665
|
});
|
|
6854
7666
|
}
|
|
@@ -6856,9 +7668,9 @@ async function scan5(agg, opts = {}) {
|
|
|
6856
7668
|
}
|
|
6857
7669
|
return stats;
|
|
6858
7670
|
}
|
|
6859
|
-
async function
|
|
7671
|
+
async function exists5(p8) {
|
|
6860
7672
|
try {
|
|
6861
|
-
await
|
|
7673
|
+
await stat7(p8);
|
|
6862
7674
|
return true;
|
|
6863
7675
|
} catch {
|
|
6864
7676
|
return false;
|
|
@@ -6926,7 +7738,7 @@ var piAdapter = {
|
|
|
6926
7738
|
},
|
|
6927
7739
|
async scan(opts) {
|
|
6928
7740
|
const aggregate = createAggregate6();
|
|
6929
|
-
const stats = await
|
|
7741
|
+
const stats = await scan6(aggregate, {
|
|
6930
7742
|
sinceMs: opts.sinceMs,
|
|
6931
7743
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6932
7744
|
});
|
|
@@ -6944,12 +7756,14 @@ var HARNESS_ADAPTERS = [
|
|
|
6944
7756
|
claudeAdapter,
|
|
6945
7757
|
codexAdapter,
|
|
6946
7758
|
grokAdapter,
|
|
7759
|
+
cursorAdapter,
|
|
6947
7760
|
opencodeAdapter,
|
|
6948
7761
|
piAdapter
|
|
6949
7762
|
];
|
|
6950
7763
|
function harnessLabel2(name) {
|
|
6951
7764
|
if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
|
|
6952
7765
|
if (name === CODEX_HARNESS_NAME) return "Codex";
|
|
7766
|
+
if (name === CURSOR_HARNESS_NAME) return "Cursor";
|
|
6953
7767
|
if (name === GROK_HARNESS_NAME) return "Grok Build";
|
|
6954
7768
|
if (name === OPENCODE_HARNESS_NAME) return "opencode";
|
|
6955
7769
|
if (name === PI_HARNESS_NAME) return "Pi";
|
|
@@ -6984,7 +7798,7 @@ var MCP_ADD_ARGS = [
|
|
|
6984
7798
|
"mcp"
|
|
6985
7799
|
];
|
|
6986
7800
|
var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
|
|
6987
|
-
var SKILL_DEST = join6(
|
|
7801
|
+
var SKILL_DEST = join6(homedir13(), ".claude", "skills", "aistack-sync");
|
|
6988
7802
|
function runClaude(args) {
|
|
6989
7803
|
const r = spawnSync("claude", args, { encoding: "utf-8" });
|
|
6990
7804
|
const notFound = r.error !== void 0 && r.error.code === "ENOENT";
|
|
@@ -7217,9 +8031,9 @@ async function createCommand() {
|
|
|
7217
8031
|
import { hostname } from "node:os";
|
|
7218
8032
|
import * as p5 from "@clack/prompts";
|
|
7219
8033
|
import open from "open";
|
|
7220
|
-
function proposedMachineName(
|
|
8034
|
+
function proposedMachineName(read3 = hostname) {
|
|
7221
8035
|
try {
|
|
7222
|
-
const name =
|
|
8036
|
+
const name = read3().trim().replace(/\.local$/i, "");
|
|
7223
8037
|
if (!name || name.length > 64) return void 0;
|
|
7224
8038
|
return name;
|
|
7225
8039
|
} catch {
|
|
@@ -7306,13 +8120,13 @@ async function loginCommand(options = {}) {
|
|
|
7306
8120
|
import * as p7 from "@clack/prompts";
|
|
7307
8121
|
|
|
7308
8122
|
// src/autosync/codexHook.ts
|
|
7309
|
-
import { createHash } from "node:crypto";
|
|
8123
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
7310
8124
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7311
|
-
import { homedir as
|
|
8125
|
+
import { homedir as homedir14 } from "node:os";
|
|
7312
8126
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
7313
8127
|
import { parse } from "smol-toml";
|
|
7314
8128
|
function codexHome2() {
|
|
7315
|
-
return process.env.CODEX_HOME || join8(
|
|
8129
|
+
return process.env.CODEX_HOME || join8(homedir14(), ".codex");
|
|
7316
8130
|
}
|
|
7317
8131
|
function codexHooksFile() {
|
|
7318
8132
|
return join8(codexHome2(), "hooks.json");
|
|
@@ -7338,9 +8152,9 @@ function readHooksJson(file) {
|
|
|
7338
8152
|
}
|
|
7339
8153
|
var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
|
|
7340
8154
|
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
7341
|
-
const
|
|
7342
|
-
if ("error" in
|
|
7343
|
-
const settings =
|
|
8155
|
+
const read3 = readHooksJson(file);
|
|
8156
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8157
|
+
const settings = read3.settings;
|
|
7344
8158
|
const hooks = settings.hooks ?? {};
|
|
7345
8159
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
7346
8160
|
const kept = sessionStart.map((m) => ({
|
|
@@ -7360,9 +8174,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
7360
8174
|
function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
7361
8175
|
if (!existsSync8(file))
|
|
7362
8176
|
return { ok: true, message: "no Codex hook to remove" };
|
|
7363
|
-
const
|
|
7364
|
-
if ("error" in
|
|
7365
|
-
const settings =
|
|
8177
|
+
const read3 = readHooksJson(file);
|
|
8178
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8179
|
+
const settings = read3.settings;
|
|
7366
8180
|
const sessionStart = settings.hooks?.SessionStart;
|
|
7367
8181
|
if (!Array.isArray(sessionStart)) {
|
|
7368
8182
|
return { ok: true, message: "no Codex hook to remove" };
|
|
@@ -7387,18 +8201,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
7387
8201
|
return { ok: true, message: `hook removed from ${file}` };
|
|
7388
8202
|
}
|
|
7389
8203
|
function codexAutoSyncHookInstalled(file = codexHooksFile()) {
|
|
7390
|
-
const
|
|
7391
|
-
if ("error" in
|
|
7392
|
-
const sessionStart =
|
|
8204
|
+
const read3 = readHooksJson(file);
|
|
8205
|
+
if ("error" in read3) return false;
|
|
8206
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
7393
8207
|
if (!Array.isArray(sessionStart)) return false;
|
|
7394
8208
|
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
|
|
7395
8209
|
}
|
|
7396
8210
|
function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
|
|
7397
8211
|
try {
|
|
7398
8212
|
const config = parse(readFileSync9(configFile, "utf-8"));
|
|
7399
|
-
const
|
|
7400
|
-
if ("error" in
|
|
7401
|
-
const sessionStart =
|
|
8213
|
+
const read3 = readHooksJson(hooksFile);
|
|
8214
|
+
if ("error" in read3) return null;
|
|
8215
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
7402
8216
|
if (!Array.isArray(sessionStart)) return false;
|
|
7403
8217
|
const matches = [];
|
|
7404
8218
|
for (const [groupIndex, group] of sessionStart.entries()) {
|
|
@@ -7421,7 +8235,7 @@ function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooks
|
|
|
7421
8235
|
hooks: [normalizedHandler]
|
|
7422
8236
|
};
|
|
7423
8237
|
if (typeof group.matcher === "string") identity.matcher = group.matcher;
|
|
7424
|
-
const currentHash = `sha256:${
|
|
8238
|
+
const currentHash = `sha256:${createHash2("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
|
|
7425
8239
|
const key2 = `${hooksFile}:session_start:${groupIndex}:${handlerIndex}`;
|
|
7426
8240
|
matches.push(config.hooks?.state?.[key2]?.trusted_hash === currentHash);
|
|
7427
8241
|
}
|
|
@@ -7451,23 +8265,177 @@ function canonicalJson2(value) {
|
|
|
7451
8265
|
// src/autosync/optin.ts
|
|
7452
8266
|
import * as p6 from "@clack/prompts";
|
|
7453
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
|
+
|
|
8273
|
+
// src/autosync/hook.ts
|
|
8274
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8275
|
+
import { homedir as homedir15 } from "node:os";
|
|
8276
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8277
|
+
var CLAUDE_SETTINGS_FILE = join9(homedir15(), ".claude", "settings.json");
|
|
8278
|
+
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
8279
|
+
function isOurs2(entry) {
|
|
8280
|
+
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
8281
|
+
}
|
|
8282
|
+
function readClaudeSettings(file) {
|
|
8283
|
+
if (!existsSync9(file)) return { settings: {} };
|
|
8284
|
+
try {
|
|
8285
|
+
const raw = JSON.parse(readFileSync10(file, "utf-8"));
|
|
8286
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
8287
|
+
return { settings: raw };
|
|
8288
|
+
}
|
|
8289
|
+
return { error: `${file} does not hold a JSON object` };
|
|
8290
|
+
} catch {
|
|
8291
|
+
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
8295
|
+
const read3 = readClaudeSettings(file);
|
|
8296
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8297
|
+
const settings = read3.settings;
|
|
8298
|
+
const hooks = settings.hooks ?? {};
|
|
8299
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
8300
|
+
const kept = sessionStart.map((m) => ({
|
|
8301
|
+
...m,
|
|
8302
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
8303
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
8304
|
+
kept.push({
|
|
8305
|
+
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
8306
|
+
});
|
|
8307
|
+
settings.hooks = { ...hooks, SessionStart: kept };
|
|
8308
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
8309
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
8310
|
+
`);
|
|
8311
|
+
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
8312
|
+
}
|
|
8313
|
+
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
8314
|
+
if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
|
|
8315
|
+
const read3 = readClaudeSettings(file);
|
|
8316
|
+
if ("error" in read3) return { ok: false, message: read3.error };
|
|
8317
|
+
const settings = read3.settings;
|
|
8318
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
8319
|
+
if (!Array.isArray(sessionStart)) {
|
|
8320
|
+
return { ok: true, message: "no hook to remove" };
|
|
8321
|
+
}
|
|
8322
|
+
const kept = sessionStart.map((m) => ({
|
|
8323
|
+
...m,
|
|
8324
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
8325
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
8326
|
+
const hooks = { ...settings.hooks };
|
|
8327
|
+
if (kept.length > 0) {
|
|
8328
|
+
hooks.SessionStart = kept;
|
|
8329
|
+
} else {
|
|
8330
|
+
delete hooks.SessionStart;
|
|
8331
|
+
}
|
|
8332
|
+
if (Object.keys(hooks).length > 0) {
|
|
8333
|
+
settings.hooks = hooks;
|
|
8334
|
+
} else {
|
|
8335
|
+
delete settings.hooks;
|
|
8336
|
+
}
|
|
8337
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
8338
|
+
`);
|
|
8339
|
+
return { ok: true, message: `hook removed from ${file}` };
|
|
8340
|
+
}
|
|
8341
|
+
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
8342
|
+
const read3 = readClaudeSettings(file);
|
|
8343
|
+
if ("error" in read3) return false;
|
|
8344
|
+
const sessionStart = read3.settings.hooks?.SessionStart;
|
|
8345
|
+
if (!Array.isArray(sessionStart)) return false;
|
|
8346
|
+
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs2(h)));
|
|
8347
|
+
}
|
|
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
|
+
|
|
7454
8422
|
// src/autosync/grokHook.ts
|
|
7455
8423
|
import {
|
|
7456
|
-
existsSync as
|
|
7457
|
-
mkdirSync as
|
|
7458
|
-
readFileSync as
|
|
8424
|
+
existsSync as existsSync11,
|
|
8425
|
+
mkdirSync as mkdirSync6,
|
|
8426
|
+
readFileSync as readFileSync12,
|
|
7459
8427
|
unlinkSync,
|
|
7460
|
-
writeFileSync as
|
|
8428
|
+
writeFileSync as writeFileSync6
|
|
7461
8429
|
} from "node:fs";
|
|
7462
|
-
import { homedir as
|
|
7463
|
-
import { dirname as
|
|
7464
|
-
var GROK_HOOK_FILE =
|
|
7465
|
-
process.env.GROK_HOME ||
|
|
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"),
|
|
7466
8434
|
"hooks",
|
|
7467
8435
|
"aistack.json"
|
|
7468
8436
|
);
|
|
7469
8437
|
var SYNC_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
7470
|
-
function grokHookCommand(os =
|
|
8438
|
+
function grokHookCommand(os = platform3()) {
|
|
7471
8439
|
if (os === "win32") {
|
|
7472
8440
|
return `Start-Process -WindowStyle Hidden -FilePath "cmd.exe" -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=grok&& (${SYNC_COMMAND}) >NUL 2>&1"'`;
|
|
7473
8441
|
}
|
|
@@ -7477,9 +8445,9 @@ function grokHookCommand(os = platform2()) {
|
|
|
7477
8445
|
return `setsid nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' >/dev/null 2>&1 &`;
|
|
7478
8446
|
}
|
|
7479
8447
|
function readHookFile(file) {
|
|
7480
|
-
if (!
|
|
8448
|
+
if (!existsSync11(file)) return { value: {} };
|
|
7481
8449
|
try {
|
|
7482
|
-
const value = JSON.parse(
|
|
8450
|
+
const value = JSON.parse(readFileSync12(file, "utf-8"));
|
|
7483
8451
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
7484
8452
|
const candidate = value;
|
|
7485
8453
|
if (candidate.hooks !== void 0 && (!candidate.hooks || typeof candidate.hooks !== "object" || Array.isArray(candidate.hooks) || Object.values(candidate.hooks).some(
|
|
@@ -7496,19 +8464,19 @@ function readHookFile(file) {
|
|
|
7496
8464
|
}
|
|
7497
8465
|
return { error: `${file} does not hold a JSON object` };
|
|
7498
8466
|
}
|
|
7499
|
-
function
|
|
8467
|
+
function isOurs4(entry) {
|
|
7500
8468
|
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
7501
8469
|
}
|
|
7502
|
-
function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os =
|
|
7503
|
-
const
|
|
7504
|
-
if ("error" in
|
|
7505
|
-
const foreignKeys = Object.keys(
|
|
7506
|
-
const foreignEvents = Object.keys(
|
|
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(
|
|
7507
8475
|
(key2) => key2 !== "SessionStart"
|
|
7508
8476
|
);
|
|
7509
|
-
const sessionStart =
|
|
8477
|
+
const sessionStart = read3.value.hooks?.SessionStart ?? [];
|
|
7510
8478
|
const foreignHandlers = sessionStart.flatMap(
|
|
7511
|
-
(group) => (group.hooks ?? []).filter((entry) => !
|
|
8479
|
+
(group) => (group.hooks ?? []).filter((entry) => !isOurs4(entry))
|
|
7512
8480
|
);
|
|
7513
8481
|
if (foreignKeys.length > 0 || foreignEvents.length > 0 || foreignHandlers.length > 0) {
|
|
7514
8482
|
return {
|
|
@@ -7527,8 +8495,8 @@ function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform2()) {
|
|
|
7527
8495
|
]
|
|
7528
8496
|
}
|
|
7529
8497
|
};
|
|
7530
|
-
|
|
7531
|
-
|
|
8498
|
+
mkdirSync6(dirname8(file), { recursive: true });
|
|
8499
|
+
writeFileSync6(file, `${JSON.stringify(value, null, 2)}
|
|
7532
8500
|
`);
|
|
7533
8501
|
return {
|
|
7534
8502
|
ok: true,
|
|
@@ -7536,15 +8504,15 @@ function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform2()) {
|
|
|
7536
8504
|
};
|
|
7537
8505
|
}
|
|
7538
8506
|
function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
|
|
7539
|
-
if (!
|
|
8507
|
+
if (!existsSync11(file))
|
|
7540
8508
|
return { ok: true, message: "no Grok Build hook to remove" };
|
|
7541
|
-
const
|
|
7542
|
-
if ("error" in
|
|
7543
|
-
const entries =
|
|
7544
|
-
const onlyOurs = Object.keys(
|
|
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(
|
|
7545
8513
|
(key2) => key2 === "SessionStart"
|
|
7546
8514
|
) && entries.every(
|
|
7547
|
-
(group) => (group.hooks ?? []).every((entry) =>
|
|
8515
|
+
(group) => (group.hooks ?? []).every((entry) => isOurs4(entry))
|
|
7548
8516
|
);
|
|
7549
8517
|
if (!onlyOurs) {
|
|
7550
8518
|
return {
|
|
@@ -7556,89 +8524,13 @@ function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
|
|
|
7556
8524
|
return { ok: true, message: `Grok Build hook removed from ${file}` };
|
|
7557
8525
|
}
|
|
7558
8526
|
function grokAutoSyncHookInstalled(file = GROK_HOOK_FILE) {
|
|
7559
|
-
const
|
|
7560
|
-
if ("error" in
|
|
7561
|
-
return (
|
|
7562
|
-
(group) => (group.hooks ?? []).some((entry) =>
|
|
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))
|
|
7563
8531
|
);
|
|
7564
8532
|
}
|
|
7565
8533
|
|
|
7566
|
-
// src/autosync/hook.ts
|
|
7567
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7568
|
-
import { homedir as homedir14 } from "node:os";
|
|
7569
|
-
import { dirname as dirname7, join as join10 } from "node:path";
|
|
7570
|
-
var CLAUDE_SETTINGS_FILE = join10(homedir14(), ".claude", "settings.json");
|
|
7571
|
-
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
7572
|
-
function isOurs3(entry) {
|
|
7573
|
-
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
7574
|
-
}
|
|
7575
|
-
function readClaudeSettings(file) {
|
|
7576
|
-
if (!existsSync10(file)) return { settings: {} };
|
|
7577
|
-
try {
|
|
7578
|
-
const raw = JSON.parse(readFileSync11(file, "utf-8"));
|
|
7579
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7580
|
-
return { settings: raw };
|
|
7581
|
-
}
|
|
7582
|
-
return { error: `${file} does not hold a JSON object` };
|
|
7583
|
-
} catch {
|
|
7584
|
-
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
7585
|
-
}
|
|
7586
|
-
}
|
|
7587
|
-
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
7588
|
-
const read2 = readClaudeSettings(file);
|
|
7589
|
-
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7590
|
-
const settings = read2.settings;
|
|
7591
|
-
const hooks = settings.hooks ?? {};
|
|
7592
|
-
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
7593
|
-
const kept = sessionStart.map((m) => ({
|
|
7594
|
-
...m,
|
|
7595
|
-
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
7596
|
-
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
7597
|
-
kept.push({
|
|
7598
|
-
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
7599
|
-
});
|
|
7600
|
-
settings.hooks = { ...hooks, SessionStart: kept };
|
|
7601
|
-
mkdirSync5(dirname7(file), { recursive: true });
|
|
7602
|
-
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
7603
|
-
`);
|
|
7604
|
-
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
7605
|
-
}
|
|
7606
|
-
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
7607
|
-
if (!existsSync10(file)) return { ok: true, message: "no hook to remove" };
|
|
7608
|
-
const read2 = readClaudeSettings(file);
|
|
7609
|
-
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7610
|
-
const settings = read2.settings;
|
|
7611
|
-
const sessionStart = settings.hooks?.SessionStart;
|
|
7612
|
-
if (!Array.isArray(sessionStart)) {
|
|
7613
|
-
return { ok: true, message: "no hook to remove" };
|
|
7614
|
-
}
|
|
7615
|
-
const kept = sessionStart.map((m) => ({
|
|
7616
|
-
...m,
|
|
7617
|
-
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
7618
|
-
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
7619
|
-
const hooks = { ...settings.hooks };
|
|
7620
|
-
if (kept.length > 0) {
|
|
7621
|
-
hooks.SessionStart = kept;
|
|
7622
|
-
} else {
|
|
7623
|
-
delete hooks.SessionStart;
|
|
7624
|
-
}
|
|
7625
|
-
if (Object.keys(hooks).length > 0) {
|
|
7626
|
-
settings.hooks = hooks;
|
|
7627
|
-
} else {
|
|
7628
|
-
delete settings.hooks;
|
|
7629
|
-
}
|
|
7630
|
-
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
7631
|
-
`);
|
|
7632
|
-
return { ok: true, message: `hook removed from ${file}` };
|
|
7633
|
-
}
|
|
7634
|
-
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
7635
|
-
const read2 = readClaudeSettings(file);
|
|
7636
|
-
if ("error" in read2) return false;
|
|
7637
|
-
const sessionStart = read2.settings.hooks?.SessionStart;
|
|
7638
|
-
if (!Array.isArray(sessionStart)) return false;
|
|
7639
|
-
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs3(h)));
|
|
7640
|
-
}
|
|
7641
|
-
|
|
7642
8534
|
// src/autosync/optin.ts
|
|
7643
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.";
|
|
7644
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.`;
|
|
@@ -7678,6 +8570,10 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
7678
8570
|
const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
|
|
7679
8571
|
if (!grokResult.ok) return grokResult;
|
|
7680
8572
|
}
|
|
8573
|
+
if (names.has(CURSOR_HARNESS_NAME)) {
|
|
8574
|
+
const result = (deps.installCursorHook ?? installCursorAutoSyncHook)();
|
|
8575
|
+
if (!result.ok) return result;
|
|
8576
|
+
}
|
|
7681
8577
|
saveSettings(
|
|
7682
8578
|
{
|
|
7683
8579
|
autoSyncAnswered: true,
|
|
@@ -7688,7 +8584,7 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
7688
8584
|
return {
|
|
7689
8585
|
ok: true,
|
|
7690
8586
|
message: [
|
|
7691
|
-
`Auto-sync is on. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
|
|
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`,
|
|
7692
8588
|
...trustLine ? [trustLine] : []
|
|
7693
8589
|
].join("\n")
|
|
7694
8590
|
};
|
|
@@ -7710,7 +8606,8 @@ async function disableAutoSync(deps = {}) {
|
|
|
7710
8606
|
const result = (deps.removeHook ?? removeAutoSyncHook)();
|
|
7711
8607
|
const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
|
|
7712
8608
|
const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
|
|
7713
|
-
const
|
|
8609
|
+
const cursorResult = (deps.removeCursorHook ?? removeCursorAutoSyncHook)();
|
|
8610
|
+
const failures = [result, codexResult, grokResult, cursorResult].filter((r) => !r.ok).map((r) => r.message);
|
|
7714
8611
|
const token = (deps.getTokenImpl ?? getToken)();
|
|
7715
8612
|
if (token !== null) {
|
|
7716
8613
|
try {
|
|
@@ -7751,7 +8648,8 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7751
8648
|
const results = [
|
|
7752
8649
|
(deps.removeHook ?? removeAutoSyncHook)(),
|
|
7753
8650
|
(deps.removeCodexHook ?? removeCodexAutoSyncHook)(),
|
|
7754
|
-
(deps.removeGrokHook ?? removeGrokAutoSyncHook)()
|
|
8651
|
+
(deps.removeGrokHook ?? removeGrokAutoSyncHook)(),
|
|
8652
|
+
(deps.removeCursorHook ?? removeCursorAutoSyncHook)()
|
|
7755
8653
|
];
|
|
7756
8654
|
const failures2 = results.filter((result) => !result.ok);
|
|
7757
8655
|
if (failures2.length > 0) {
|
|
@@ -7793,6 +8691,11 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7793
8691
|
deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
|
|
7794
8692
|
deps.installGrokHook ?? installGrokAutoSyncHook
|
|
7795
8693
|
);
|
|
8694
|
+
install(
|
|
8695
|
+
CURSOR_HARNESS_NAME,
|
|
8696
|
+
deps.cursorHookInstalledImpl ?? cursorAutoSyncHookInstalled,
|
|
8697
|
+
deps.installCursorHook ?? installCursorAutoSyncHook
|
|
8698
|
+
);
|
|
7796
8699
|
if (failures.length > 0) {
|
|
7797
8700
|
return {
|
|
7798
8701
|
ok: false,
|
|
@@ -7811,7 +8714,7 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7811
8714
|
if (installed.length === 0 && mirrored) return null;
|
|
7812
8715
|
return {
|
|
7813
8716
|
ok: true,
|
|
7814
|
-
message: installed.length > 0 ? `Auto-sync is on for this stack. Installed the ${installed.join(" and ")} trigger on this machine; it runs about every ${frequencyHours}h
|
|
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"}.`
|
|
7815
8718
|
};
|
|
7816
8719
|
}
|
|
7817
8720
|
async function settleAutoSync(permission, deps = {}) {
|
|
@@ -7829,13 +8732,14 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7829
8732
|
return false;
|
|
7830
8733
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
7831
8734
|
if (detected.length === 0) return false;
|
|
8735
|
+
const names = new Set(detected.map((adapter) => adapter.name));
|
|
7832
8736
|
const answer = await p6.select({
|
|
7833
8737
|
message: "Keep this stack fresh automatically every 6 hours?",
|
|
7834
8738
|
options: [
|
|
7835
8739
|
{
|
|
7836
8740
|
value: "enable",
|
|
7837
8741
|
label: "Enable",
|
|
7838
|
-
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"}`
|
|
7839
8743
|
},
|
|
7840
8744
|
{
|
|
7841
8745
|
value: "later",
|
|
@@ -7878,17 +8782,17 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7878
8782
|
import {
|
|
7879
8783
|
appendFileSync,
|
|
7880
8784
|
closeSync,
|
|
7881
|
-
mkdirSync as
|
|
8785
|
+
mkdirSync as mkdirSync8,
|
|
7882
8786
|
openSync,
|
|
7883
|
-
readFileSync as
|
|
8787
|
+
readFileSync as readFileSync14,
|
|
7884
8788
|
unlinkSync as unlinkSync2,
|
|
7885
|
-
writeFileSync as
|
|
8789
|
+
writeFileSync as writeFileSync8
|
|
7886
8790
|
} from "node:fs";
|
|
7887
|
-
import { homedir as
|
|
7888
|
-
import { dirname as
|
|
8791
|
+
import { homedir as homedir19 } from "node:os";
|
|
8792
|
+
import { dirname as dirname9, join as join12 } from "node:path";
|
|
7889
8793
|
|
|
7890
8794
|
// src/sync/stage.ts
|
|
7891
|
-
import { createHash as
|
|
8795
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7892
8796
|
|
|
7893
8797
|
// src/usage/days.ts
|
|
7894
8798
|
var round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
@@ -8044,7 +8948,7 @@ function selectDaysToPublish(input) {
|
|
|
8044
8948
|
|
|
8045
8949
|
// src/workflow/git.ts
|
|
8046
8950
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
8047
|
-
import
|
|
8951
|
+
import path11 from "node:path";
|
|
8048
8952
|
var TEST_FILE_RULE_VERSION = "test-files/v2";
|
|
8049
8953
|
var FILE_TYPE_RULE_VERSION = "file-types/v2";
|
|
8050
8954
|
var COMMIT_SET_RULE_VERSION = "commit-set/v1";
|
|
@@ -8327,9 +9231,9 @@ function reduceGitHistories(histories, options) {
|
|
|
8327
9231
|
if (included) seenCommits.add(hash);
|
|
8328
9232
|
continue;
|
|
8329
9233
|
}
|
|
8330
|
-
const
|
|
8331
|
-
if (!
|
|
8332
|
-
let file =
|
|
9234
|
+
const stat8 = parseNumstat(field);
|
|
9235
|
+
if (!stat8) continue;
|
|
9236
|
+
let file = stat8.file;
|
|
8333
9237
|
if (file.length === 0) {
|
|
8334
9238
|
fieldIndex += 2;
|
|
8335
9239
|
file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
|
|
@@ -8337,13 +9241,13 @@ function reduceGitHistories(histories, options) {
|
|
|
8337
9241
|
if (!current?.included) continue;
|
|
8338
9242
|
if (isUnauthoredPath(file)) continue;
|
|
8339
9243
|
current.authored = true;
|
|
8340
|
-
const fileChangedLines =
|
|
8341
|
-
current.additions +=
|
|
8342
|
-
current.removals +=
|
|
9244
|
+
const fileChangedLines = stat8.additions + stat8.removals;
|
|
9245
|
+
current.additions += stat8.additions;
|
|
9246
|
+
current.removals += stat8.removals;
|
|
8343
9247
|
current.changedLines += fileChangedLines;
|
|
8344
9248
|
if (isTestFile(file)) current.touchesTest = true;
|
|
8345
9249
|
if (fileChangedLines <= 0) continue;
|
|
8346
|
-
const extension =
|
|
9250
|
+
const extension = path11.extname(file).toLowerCase();
|
|
8347
9251
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
8348
9252
|
current.extensionLines.set(
|
|
8349
9253
|
extension,
|
|
@@ -8447,36 +9351,36 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
|
|
|
8447
9351
|
}
|
|
8448
9352
|
|
|
8449
9353
|
// src/sync/grokDateCache.ts
|
|
8450
|
-
import { createHash as
|
|
8451
|
-
import { existsSync as
|
|
8452
|
-
import { homedir as
|
|
8453
|
-
import
|
|
8454
|
-
var defaultFile =
|
|
8455
|
-
|
|
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(),
|
|
8456
9360
|
".config",
|
|
8457
9361
|
"aistack",
|
|
8458
9362
|
"grok-session-dates.json"
|
|
8459
9363
|
);
|
|
8460
9364
|
function grokCacheScope(baseUrl, stack, token) {
|
|
8461
|
-
return
|
|
9365
|
+
return createHash3("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
|
|
8462
9366
|
}
|
|
8463
|
-
function
|
|
8464
|
-
if (!
|
|
9367
|
+
function read2(file) {
|
|
9368
|
+
if (!existsSync12(file)) return {};
|
|
8465
9369
|
try {
|
|
8466
|
-
const value = JSON.parse(
|
|
9370
|
+
const value = JSON.parse(readFileSync13(file, "utf8"));
|
|
8467
9371
|
return value && typeof value === "object" ? value : {};
|
|
8468
9372
|
} catch {
|
|
8469
9373
|
return {};
|
|
8470
9374
|
}
|
|
8471
9375
|
}
|
|
8472
9376
|
function loadGrokDateHints(scope, file = defaultFile) {
|
|
8473
|
-
return
|
|
9377
|
+
return read2(file)[scope] ?? {};
|
|
8474
9378
|
}
|
|
8475
9379
|
function saveGrokDateHints(scope, hints, file = defaultFile) {
|
|
8476
|
-
const cache =
|
|
9380
|
+
const cache = read2(file);
|
|
8477
9381
|
cache[scope] = hints;
|
|
8478
|
-
|
|
8479
|
-
|
|
9382
|
+
mkdirSync7(path12.dirname(file), { recursive: true });
|
|
9383
|
+
writeFileSync7(file, JSON.stringify(cache, null, 2));
|
|
8480
9384
|
}
|
|
8481
9385
|
function mapToHints(value, floor) {
|
|
8482
9386
|
return Object.fromEntries(
|
|
@@ -8887,7 +9791,7 @@ function buildGateSummary(ctx) {
|
|
|
8887
9791
|
// src/sync/stage.ts
|
|
8888
9792
|
var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
8889
9793
|
function stageId(bodyJson) {
|
|
8890
|
-
return
|
|
9794
|
+
return createHash4("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
8891
9795
|
}
|
|
8892
9796
|
async function stageSync(deps) {
|
|
8893
9797
|
const now = (deps.now ?? Date.now)();
|
|
@@ -8941,11 +9845,12 @@ async function stageSync(deps) {
|
|
|
8941
9845
|
const active2 = await adapters(sinceMs);
|
|
8942
9846
|
const historical = await adapters(daysSinceMs);
|
|
8943
9847
|
let dayScansComplete = true;
|
|
8944
|
-
|
|
9848
|
+
const sessionDatesByHarness = /* @__PURE__ */ new Map();
|
|
8945
9849
|
for (const adapter of active2) {
|
|
8946
9850
|
progress(`Scanning recent ${adapter.name} usage`);
|
|
8947
9851
|
const { aggregate, stats } = await adapter.scan({
|
|
8948
9852
|
sinceMs,
|
|
9853
|
+
publishWorkflow: false,
|
|
8949
9854
|
onProgress: (files) => progress(`Scanning recent ${adapter.name} usage \xB7 ${files} files`)
|
|
8950
9855
|
});
|
|
8951
9856
|
scanStats[adapter.name] = stats;
|
|
@@ -8966,11 +9871,12 @@ async function stageSync(deps) {
|
|
|
8966
9871
|
progress(`Reading historical ${adapter.name} days`);
|
|
8967
9872
|
const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
|
|
8968
9873
|
sinceMs: daysSinceMs,
|
|
9874
|
+
publishWorkflow: config.publishWorkflow,
|
|
8969
9875
|
onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
|
|
8970
9876
|
});
|
|
8971
9877
|
if (scanComplete === false) dayScansComplete = false;
|
|
8972
|
-
if (adapter.name === "grok-build")
|
|
8973
|
-
|
|
9878
|
+
if (adapter.name === "grok-build" || adapter.name === "cursor")
|
|
9879
|
+
sessionDatesByHarness.set(adapter.name, sessionDates ?? /* @__PURE__ */ new Map());
|
|
8974
9880
|
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
8975
9881
|
usageScans.push(
|
|
8976
9882
|
buildUsageDays({
|
|
@@ -8998,19 +9904,29 @@ async function stageSync(deps) {
|
|
|
8998
9904
|
}
|
|
8999
9905
|
const correctionDates = /* @__PURE__ */ new Set();
|
|
9000
9906
|
let acknowledgePublish;
|
|
9001
|
-
|
|
9002
|
-
|
|
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
|
+
);
|
|
9003
9915
|
const floor = utcDate2(daysSinceMs);
|
|
9004
9916
|
const previous = loadGrokDateHints(scope);
|
|
9005
|
-
const current = mapToHints(
|
|
9917
|
+
const current = mapToHints(currentDates, floor);
|
|
9006
9918
|
for (const dates of Object.values(previous))
|
|
9007
9919
|
for (const date of dates) correctionDates.add(date);
|
|
9008
9920
|
for (const dates of Object.values(current))
|
|
9009
9921
|
for (const date of dates) correctionDates.add(date);
|
|
9010
9922
|
if (Object.keys(previous).some((id) => !(id in current)))
|
|
9011
9923
|
dayScansComplete = false;
|
|
9012
|
-
|
|
9924
|
+
acknowledgements.push(() => saveGrokDateHints(scope, current));
|
|
9013
9925
|
}
|
|
9926
|
+
if (acknowledgements.length)
|
|
9927
|
+
acknowledgePublish = () => {
|
|
9928
|
+
for (const acknowledge of acknowledgements) acknowledge();
|
|
9929
|
+
};
|
|
9014
9930
|
const localDays = applyDayConsent(
|
|
9015
9931
|
buildMeasuredDays({
|
|
9016
9932
|
usage: mergeUsageDays(usageScans),
|
|
@@ -9080,31 +9996,31 @@ async function stageSync(deps) {
|
|
|
9080
9996
|
}
|
|
9081
9997
|
|
|
9082
9998
|
// src/autosync/run.ts
|
|
9083
|
-
var SYNC_LOG_FILE =
|
|
9999
|
+
var SYNC_LOG_FILE = join12(homedir19(), ".config", "aistack", "sync.log");
|
|
9084
10000
|
var SYNC_LOG_MAX_LINES = 200;
|
|
9085
10001
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
9086
10002
|
var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
|
|
9087
10003
|
function appendLogLine(file, line) {
|
|
9088
|
-
|
|
10004
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
9089
10005
|
appendFileSync(file, `${line}
|
|
9090
10006
|
`);
|
|
9091
|
-
const lines2 =
|
|
10007
|
+
const lines2 = readFileSync14(file, "utf-8").split("\n").filter(Boolean);
|
|
9092
10008
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
9093
|
-
|
|
10009
|
+
writeFileSync8(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
|
|
9094
10010
|
`);
|
|
9095
10011
|
}
|
|
9096
10012
|
}
|
|
9097
10013
|
function reserveAttempt(file, now, windowMs) {
|
|
9098
|
-
|
|
10014
|
+
mkdirSync8(dirname9(file), { recursive: true });
|
|
9099
10015
|
for (; ; ) {
|
|
9100
10016
|
try {
|
|
9101
10017
|
const fd = openSync(file, "wx");
|
|
9102
|
-
|
|
10018
|
+
writeFileSync8(fd, String(now));
|
|
9103
10019
|
closeSync(fd);
|
|
9104
10020
|
return true;
|
|
9105
10021
|
} catch (error) {
|
|
9106
10022
|
if (error.code !== "EEXIST") throw error;
|
|
9107
|
-
const held = Number(
|
|
10023
|
+
const held = Number(readFileSync14(file, "utf-8"));
|
|
9108
10024
|
if (Number.isFinite(held) && now - held < windowMs) return false;
|
|
9109
10025
|
try {
|
|
9110
10026
|
unlinkSync2(file);
|
|
@@ -9133,7 +10049,7 @@ async function runAutoSync(deps) {
|
|
|
9133
10049
|
const state = settings.autoSyncState ?? {};
|
|
9134
10050
|
const lastRunAt = state.lastRunAt ?? 0;
|
|
9135
10051
|
if (now - lastRunAt < windowMs) return;
|
|
9136
|
-
const reservationFile = deps.reservationFile ?? `${settingsFile ??
|
|
10052
|
+
const reservationFile = deps.reservationFile ?? `${settingsFile ?? join12(homedir19(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
|
|
9137
10053
|
if (!reserveAttempt(reservationFile, now, windowMs)) return;
|
|
9138
10054
|
saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
|
|
9139
10055
|
const stage = deps.stageImpl ?? stageSync;
|
|
@@ -9226,7 +10142,7 @@ async function runAutoSync(deps) {
|
|
|
9226
10142
|
logFile,
|
|
9227
10143
|
`${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
|
|
9228
10144
|
);
|
|
9229
|
-
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok") {
|
|
10145
|
+
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok" && process.env.AISTACK_HOOK_SOURCE !== "cursor") {
|
|
9230
10146
|
emit(
|
|
9231
10147
|
JSON.stringify({
|
|
9232
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.`
|
|
@@ -9720,7 +10636,7 @@ program.command("mcp").description(
|
|
|
9720
10636
|
});
|
|
9721
10637
|
program.command("sync").description("Scan, preview, and publish measured usage (rolling 30 days)").option(
|
|
9722
10638
|
"--auto [state]",
|
|
9723
|
-
"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"
|
|
9724
10640
|
).option(
|
|
9725
10641
|
"--every <hours>",
|
|
9726
10642
|
"with --auto on: hours between auto-syncs (default 6)"
|