@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/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.14.0" : "0.0.0-dev";
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(path9, options = {}) {
385
- return fetch(`${BASE_URL}${path9}`, {
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(path9) {
804
- if (!path9) return "";
805
- return path9.split("/").filter(Boolean).join("/");
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(path9) {
851
+ function readJson(path13) {
852
852
  try {
853
- if (!existsSync2(path9)) return null;
854
- return JSON.parse(readFileSync2(path9, "utf-8"));
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(path9, source, out, seen) {
860
- const hooks = readJson(path9)?.hooks;
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(path9) {
1009
+ function readText(path13) {
1010
1010
  try {
1011
- if (!existsSync3(path9)) return null;
1012
- return readFileSync3(path9, "utf-8");
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(path9, parse2) {
1018
- const raw = readText(path9);
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(path9) {
1027
- return readParsed(path9, JSON.parse);
1026
+ function readJson2(path13) {
1027
+ return readParsed(path13, JSON.parse);
1028
1028
  }
1029
- function readYaml(path9) {
1030
- return readParsed(path9, parseYaml);
1029
+ function readYaml(path13) {
1030
+ return readParsed(path13, parseYaml);
1031
1031
  }
1032
- function readToml(path9) {
1033
- return readParsed(path9, parseToml);
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 path9 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1173
- return { url: mpRepoUrl, path: path9 || void 0 };
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(path9) {
1210
+ function readJson3(path13) {
1211
1211
  try {
1212
- if (!existsSync4(path9)) return null;
1213
- return JSON.parse(readFileSync4(path9, "utf-8"));
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 stat7 = statSync(filePath);
1303
- if (stat7.size > MAX_FILE_SIZE) return null;
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, count) {
1522
+ function section(label, count2) {
1523
1523
  console.log(`${BAR}`);
1524
- const countStr = count !== void 0 ? ` ${dim(String(count))}` : "";
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 homedir11 } from "node:os";
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, count]) => ({ name, count }));
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, count]) => ({ name, count }));
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, count]) => ({ bucket, [field]: count })
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, count]) => ({ hourUtc, sessions: count })).sort((a, b) => a.hourUtc - b.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 timestamp = asStr(rec.timestamp);
4565
- if (timestamp) {
4566
- const ts = Date.parse(timestamp);
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(timestamp.slice(0, 10));
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, count] of c.mirroredIterationTypes) {
4863
- bump(agg.mirroredIterationTypes, type, sign * count);
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 timestamp = asStr(rec.timestamp);
5130
- if (timestamp) {
5131
- const ts = Date.parse(timestamp);
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 && timestamp) {
5155
- agg.activeDays.add(timestamp.slice(0, 10));
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 readFile2 = opts.readFileImpl ?? readFileSync6;
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 = readFile2(file);
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 = readFile2(file).toString("utf8");
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 = (update) => {
5576
- const meta = asObj(update._meta);
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 update = params && asObj(params.update);
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 (!update || !session || tsMs === null) return;
5588
- const kind = asStr(update.sessionUpdate);
6399
+ if (!update2 || !session || tsMs === null) return;
6400
+ const kind = asStr(update2.sessionUpdate);
5589
6401
  if (kind === "tool_call") {
5590
- const id = asStr(update.toolCallId);
5591
- const metadata = toolMetadata(update);
5592
- const name = asName(metadata?.name ?? update.toolName);
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(update.input);
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(update.toolCallId);
5602
- if (!id || asStr(update.status) !== "completed") return;
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(update.usage);
6419
+ const usage = asObj(update2.usage);
5608
6420
  if (!usage) return;
5609
- const prompt = asStr(update.prompt_id) ?? `turn:${tsMs}`;
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(update.elapsed_ms) > 0 ? { durationSec: asNum(update.elapsed_ms) / 1e3 } : {}
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(update.stop_reason) === "question"
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 update = params && asObj(params.update);
6565
+ const update2 = params && asObj(params.update);
5754
6566
  const meta = params && asObj(params._meta);
5755
- if (!update || asStr(update.sessionUpdate) !== "turn_completed") return null;
5756
- const usage = asObj(update.usage);
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(update.elapsed_ms) > 0 ? { durationMs: asNum(update.elapsed_ms) } : {},
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 stat4 } from "node:fs/promises";
5802
- import { homedir as homedir8 } from "node:os";
5803
- import path4 from "node:path";
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
- path4.join(
5809
- process.env.GROK_HOME || path4.join(homedir8(), ".grok"),
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 stat4(file);
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 stat4(file);
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 readFile(file, "utf8");
5842
- const after = await stat4(file);
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 = path4.join(root, workspace.name);
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(path4.join(workspacePath, session.name));
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 readFile(path4.join(root, "..", "config.toml"), "utf8"))
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 scan3(agg, opts = {}) {
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, path4.join(dir, e.name)])
6725
+ ).map((e) => [e.name, path8.join(dir, e.name)])
5914
6726
  );
5915
6727
  let projectDir = dir;
5916
- let sessionFallback = path4.basename(dir);
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 read2 = await stableRead(summary, false);
5922
- if (!read2.complete) {
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(read2.json);
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 read2 = await stableRead(usage, false);
5940
- if (!read2.complete) {
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(read2.json, projectDir);
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 read2 = await stableRead(updates, true);
5959
- if (!read2.complete) {
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 read2.lines ?? []) {
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 read2 = await stableRead(events, true);
5982
- if (!read2.complete) {
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 read2.lines ?? []) {
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 scan3(aggregate, opts);
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 stat5 } from "node:fs/promises";
6248
- import { homedir as homedir9 } from "node:os";
6249
- import path5 from "node:path";
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 || path5.join(homedir9(), ".local", "share");
6254
- return [path5.join(base, "opencode")];
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) => path5.join(root, e.name)).sort();
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 scan4(agg, opts = {}) {
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: path5.basename(file),
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 || path5.join(homedir9(), ".config");
6469
- return path5.join(base, "opencode", "opencode.json");
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 exists3(file)) continue;
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 exists3(p8) {
7353
+ async function exists4(p8) {
6542
7354
  try {
6543
- await stat5(p8);
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 scan4(aggregate, {
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 stat6 } from "node:fs/promises";
6772
- import { homedir as homedir10 } from "node:os";
6773
- import path6 from "node:path";
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 || path6.join(homedir10(), ".pi", "agent");
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 || path6.join(piAgentDir(), "sessions")];
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 = path6.join(dir, e.name);
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 scan5(agg, opts = {}) {
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 exists4(root)) continue;
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 stat6(file);
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: path6.relative(root, file),
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: path6.relative(root, file),
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 exists4(p8) {
7671
+ async function exists5(p8) {
6860
7672
  try {
6861
- await stat6(p8);
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 scan5(aggregate, {
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(homedir11(), ".claude", "skills", "aistack-sync");
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(read2 = hostname) {
8034
+ function proposedMachineName(read3 = hostname) {
7221
8035
  try {
7222
- const name = read2().trim().replace(/\.local$/i, "");
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 homedir12 } from "node:os";
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(homedir12(), ".codex");
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 read2 = readHooksJson(file);
7342
- if ("error" in read2) return { ok: false, message: read2.error };
7343
- const settings = read2.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 read2 = readHooksJson(file);
7364
- if ("error" in read2) return { ok: false, message: read2.error };
7365
- const settings = read2.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 read2 = readHooksJson(file);
7391
- if ("error" in read2) return false;
7392
- const sessionStart = read2.settings.hooks?.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 read2 = readHooksJson(hooksFile);
7400
- if ("error" in read2) return null;
7401
- const sessionStart = read2.settings.hooks?.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:${createHash("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
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 existsSync9,
7457
- mkdirSync as mkdirSync4,
7458
- readFileSync as readFileSync10,
8424
+ existsSync as existsSync11,
8425
+ mkdirSync as mkdirSync6,
8426
+ readFileSync as readFileSync12,
7459
8427
  unlinkSync,
7460
- writeFileSync as writeFileSync4
8428
+ writeFileSync as writeFileSync6
7461
8429
  } from "node:fs";
7462
- import { homedir as homedir13, platform as platform2 } from "node:os";
7463
- import { dirname as dirname6, join as join9 } from "node:path";
7464
- var GROK_HOOK_FILE = join9(
7465
- process.env.GROK_HOME || join9(homedir13(), ".grok"),
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 = platform2()) {
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 (!existsSync9(file)) return { value: {} };
8448
+ if (!existsSync11(file)) return { value: {} };
7481
8449
  try {
7482
- const value = JSON.parse(readFileSync10(file, "utf-8"));
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 isOurs2(entry) {
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 = platform2()) {
7503
- const read2 = readHookFile(file);
7504
- if ("error" in read2) return { ok: false, message: read2.error };
7505
- const foreignKeys = Object.keys(read2.value).filter((key2) => key2 !== "hooks");
7506
- const foreignEvents = Object.keys(read2.value.hooks ?? {}).filter(
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 = read2.value.hooks?.SessionStart ?? [];
8477
+ const sessionStart = read3.value.hooks?.SessionStart ?? [];
7510
8478
  const foreignHandlers = sessionStart.flatMap(
7511
- (group) => (group.hooks ?? []).filter((entry) => !isOurs2(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
- mkdirSync4(dirname6(file), { recursive: true });
7531
- writeFileSync4(file, `${JSON.stringify(value, null, 2)}
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 (!existsSync9(file))
8507
+ if (!existsSync11(file))
7540
8508
  return { ok: true, message: "no Grok Build hook to remove" };
7541
- const read2 = readHookFile(file);
7542
- if ("error" in read2) return { ok: false, message: read2.error };
7543
- const entries = read2.value.hooks?.SessionStart ?? [];
7544
- const onlyOurs = Object.keys(read2.value).every((key2) => key2 === "hooks") && Object.keys(read2.value.hooks ?? {}).every(
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) => isOurs2(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 read2 = readHookFile(file);
7560
- if ("error" in read2) return false;
7561
- return (read2.value.hooks?.SessionStart ?? []).some(
7562
- (group) => (group.hooks ?? []).some((entry) => isOurs2(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 failures = [result, codexResult, grokResult].filter((r) => !r.ok).map((r) => r.message);
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 when a session starts.` : `Auto-sync is on for this stack. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts.`
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 mkdirSync7,
8785
+ mkdirSync as mkdirSync8,
7882
8786
  openSync,
7883
- readFileSync as readFileSync13,
8787
+ readFileSync as readFileSync14,
7884
8788
  unlinkSync as unlinkSync2,
7885
- writeFileSync as writeFileSync7
8789
+ writeFileSync as writeFileSync8
7886
8790
  } from "node:fs";
7887
- import { homedir as homedir16 } from "node:os";
7888
- import { dirname as dirname8, join as join11 } from "node:path";
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 createHash3 } from "node:crypto";
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 path7 from "node:path";
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 stat7 = parseNumstat(field);
8331
- if (!stat7) continue;
8332
- let file = stat7.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 = stat7.additions + stat7.removals;
8341
- current.additions += stat7.additions;
8342
- current.removals += stat7.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 = path7.extname(file).toLowerCase();
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 createHash2 } from "node:crypto";
8451
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
8452
- import { homedir as homedir15 } from "node:os";
8453
- import path8 from "node:path";
8454
- var defaultFile = path8.join(
8455
- homedir15(),
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 createHash2("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
9365
+ return createHash3("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
8462
9366
  }
8463
- function read(file) {
8464
- if (!existsSync11(file)) return {};
9367
+ function read2(file) {
9368
+ if (!existsSync12(file)) return {};
8465
9369
  try {
8466
- const value = JSON.parse(readFileSync12(file, "utf8"));
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 read(file)[scope] ?? {};
9377
+ return read2(file)[scope] ?? {};
8474
9378
  }
8475
9379
  function saveGrokDateHints(scope, hints, file = defaultFile) {
8476
- const cache = read(file);
9380
+ const cache = read2(file);
8477
9381
  cache[scope] = hints;
8478
- mkdirSync6(path8.dirname(file), { recursive: true });
8479
- writeFileSync6(file, JSON.stringify(cache, null, 2));
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 createHash3("sha256").update(bodyJson).digest("hex").slice(0, 12);
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
- let grokCurrentDates = null;
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
- grokCurrentDates = sessionDates ?? /* @__PURE__ */ new Map();
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
- if (grokCurrentDates && token && config.stack) {
9002
- const scope = grokCacheScope(deps.baseUrl, config.stack.slug, token);
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(grokCurrentDates, floor);
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
- acknowledgePublish = () => saveGrokDateHints(scope, current);
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 = join11(homedir16(), ".config", "aistack", "sync.log");
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
- mkdirSync7(dirname8(file), { recursive: true });
10004
+ mkdirSync8(dirname9(file), { recursive: true });
9089
10005
  appendFileSync(file, `${line}
9090
10006
  `);
9091
- const lines2 = readFileSync13(file, "utf-8").split("\n").filter(Boolean);
10007
+ const lines2 = readFileSync14(file, "utf-8").split("\n").filter(Boolean);
9092
10008
  if (lines2.length > SYNC_LOG_MAX_LINES) {
9093
- writeFileSync7(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
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
- mkdirSync7(dirname8(file), { recursive: true });
10014
+ mkdirSync8(dirname9(file), { recursive: true });
9099
10015
  for (; ; ) {
9100
10016
  try {
9101
10017
  const fd = openSync(file, "wx");
9102
- writeFileSync7(fd, String(now));
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(readFileSync13(file, "utf-8"));
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 ?? join11(homedir16(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
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 the SessionStart hooks, 'off' revokes both"
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)"