@use-aistack/cli 0.13.0 → 0.14.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
@@ -9,7 +9,11 @@ var LOCAL_PRICING_TABLE_VERSION = "local-no-charge";
9
9
  var PROVIDER_VENDOR = {
10
10
  anthropic: "anthropic",
11
11
  openai: "openai",
12
- google: "google"
12
+ google: "google",
13
+ xai: "xai"
14
+ };
15
+ var PRICING_ALIASES = {
16
+ "grok-4.6-build": "grok-4.6"
13
17
  };
14
18
  var LOCAL_PROVIDERS = /* @__PURE__ */ new Set([
15
19
  "ollama",
@@ -105,6 +109,11 @@ var Pricer = class {
105
109
  firstLayerWith(slug, provider) {
106
110
  return this.layers.find((l) => l.has(slug, provider)) ?? null;
107
111
  }
112
+ lookupSlug(slug, provider) {
113
+ if (this.firstLayerWith(slug, provider)) return slug;
114
+ const alias = PRICING_ALIASES[slug];
115
+ return alias && this.firstLayerWith(alias, provider) ? alias : null;
116
+ }
108
117
  /**
109
118
  * Every period that applies to a pricing key, or an empty list when none
110
119
  * can be cited.
@@ -117,14 +126,19 @@ var Pricer = class {
117
126
  periodsFor(modelKey) {
118
127
  const { provider, model } = splitModelKey(modelKey);
119
128
  if (provider === null) {
120
- return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
129
+ const slug = this.lookupSlug(model, null);
130
+ return slug ? this.firstLayerWith(slug, null)?.rowsFor(slug, null) ?? [] : [];
121
131
  }
122
132
  if (LOCAL_PROVIDERS.has(provider)) return [FREE_PERIOD];
123
- const own = this.firstLayerWith(model, provider);
124
- if (own) return own.rowsFor(model, provider);
133
+ const ownSlug = this.lookupSlug(model, provider);
134
+ if (ownSlug) {
135
+ return this.firstLayerWith(ownSlug, provider)?.rowsFor(ownSlug, provider) ?? [];
136
+ }
125
137
  const vendor = PROVIDER_VENDOR[provider];
126
- if (!vendor || this.vendorOf(model) !== vendor) return [];
127
- return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
138
+ const vendorSlug = this.lookupSlug(model, null);
139
+ if (!vendor || !vendorSlug || this.vendorOf(vendorSlug) !== vendor)
140
+ return [];
141
+ return this.firstLayerWith(vendorSlug, null)?.rowsFor(vendorSlug, null) ?? [];
128
142
  }
129
143
  isLocal(modelKey) {
130
144
  const { provider } = splitModelKey(modelKey);
@@ -176,7 +190,7 @@ function parsePriceTable(body) {
176
190
  if (typeof r?.modelSlug !== "string" || r.modelSlug.length === 0 || !num(r.from) || !num(r.input) || !num(r.output) || typeof r.source !== "string") {
177
191
  continue;
178
192
  }
179
- const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "local" ? r.vendor : void 0;
193
+ const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "xai" || r.vendor === "local" ? r.vendor : void 0;
180
194
  rows.push({
181
195
  modelSlug: r.modelSlug,
182
196
  ...typeof r.provider === "string" && r.provider.length > 0 ? { provider: r.provider } : {},
@@ -198,7 +212,8 @@ function parsePriceTable(body) {
198
212
  var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
199
213
  var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-02";
200
214
  var GOOGLE_PRICING_TABLE_VERSION = "google-list-2026-08-09";
201
- var BUNDLED_PRICE_TABLE_ID = "bundled-2026-08-29";
215
+ var XAI_PRICING_TABLE_VERSION = "models.dev@2026-08-29";
216
+ var BUNDLED_PRICE_TABLE_ID = "bundled-2026-09-10";
202
217
  var CACHE_WRITE_5M_MULTIPLIER = 1.25;
203
218
  var CACHE_WRITE_1H_MULTIPLIER = 2;
204
219
  var CACHE_READ_MULTIPLIER = 0.1;
@@ -213,6 +228,11 @@ var GOOGLE_CACHE_MULTIPLIERS = {
213
228
  write1h: 1,
214
229
  read: 0.1
215
230
  };
231
+ var XAI_CACHE_MULTIPLIERS = {
232
+ write5m: 0,
233
+ write1h: 0,
234
+ read: 0.25
235
+ };
216
236
  var anthropic = (periods) => ({
217
237
  vendor: "anthropic",
218
238
  table: PRICING_TABLE_VERSION,
@@ -231,6 +251,12 @@ var google = (periods) => ({
231
251
  periods,
232
252
  cache: GOOGLE_CACHE_MULTIPLIERS
233
253
  });
254
+ var xai = (periods) => ({
255
+ vendor: "xai",
256
+ table: XAI_PRICING_TABLE_VERSION,
257
+ periods,
258
+ cache: XAI_CACHE_MULTIPLIERS
259
+ });
234
260
  var flat = (input, output) => [
235
261
  { from: null, input, output }
236
262
  ];
@@ -288,7 +314,10 @@ var PRICES = {
288
314
  "gemini-3-pro-preview": google(flat(2, 12)),
289
315
  // A real Anthropic model with no row until #123. Measured in #122 as
290
316
  // `claude-opus-4-5-20251101`, which the dated-suffix rule strips to this key.
291
- "claude-opus-4-5": anthropic(flat(5, 25))
317
+ "claude-opus-4-5": anthropic(flat(5, 25)),
318
+ // models.dev's xAI row mirrored into the live table on 2026-08-29. Grok
319
+ // Build's explicit `grok-4.6-build` pricing alias reaches this base rate.
320
+ "grok-4.6": xai(flat(2, 6))
292
321
  };
293
322
  function bundledPriceTable() {
294
323
  const rows = [];
@@ -348,12 +377,12 @@ function apiEquivalentCost(modelKey, t, atMs) {
348
377
  }
349
378
 
350
379
  // src/version.ts
351
- var CLI_VERSION = true ? "0.13.0" : "0.0.0-dev";
380
+ var CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
352
381
 
353
382
  // src/api.ts
354
383
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
355
- async function request(path7, options = {}) {
356
- return fetch(`${BASE_URL}${path7}`, {
384
+ async function request(path9, options = {}) {
385
+ return fetch(`${BASE_URL}${path9}`, {
357
386
  ...options,
358
387
  headers: {
359
388
  "Content-Type": "application/json",
@@ -771,9 +800,9 @@ function canonicalizeRepoUrl(input) {
771
800
  function repoNameFromCanonical(canonical) {
772
801
  return parseRepo(canonical)?.repo ?? "";
773
802
  }
774
- function normalizeUpstreamPath(path7) {
775
- if (!path7) return "";
776
- return path7.split("/").filter(Boolean).join("/");
803
+ function normalizeUpstreamPath(path9) {
804
+ if (!path9) return "";
805
+ return path9.split("/").filter(Boolean).join("/");
777
806
  }
778
807
 
779
808
  // src/git.ts
@@ -819,16 +848,16 @@ function buildRepoLinkResource(canonical) {
819
848
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
820
849
  import { homedir as homedir2 } from "node:os";
821
850
  import { join as join2 } from "node:path";
822
- function readJson(path7) {
851
+ function readJson(path9) {
823
852
  try {
824
- if (!existsSync2(path7)) return null;
825
- return JSON.parse(readFileSync2(path7, "utf-8"));
853
+ if (!existsSync2(path9)) return null;
854
+ return JSON.parse(readFileSync2(path9, "utf-8"));
826
855
  } catch {
827
856
  return null;
828
857
  }
829
858
  }
830
- function hooksFrom(path7, source, out, seen) {
831
- const hooks = readJson(path7)?.hooks;
859
+ function hooksFrom(path9, source, out, seen) {
860
+ const hooks = readJson(path9)?.hooks;
832
861
  if (!hooks || typeof hooks !== "object") return;
833
862
  for (const [event, config] of Object.entries(hooks)) {
834
863
  const stableKey = `hooks:${source}:${event}`;
@@ -917,10 +946,19 @@ function splitImageTag(image) {
917
946
  }
918
947
  function parseMcpPackage(server) {
919
948
  if (server.url) {
949
+ let safeUrl;
950
+ try {
951
+ const parsed = new URL(server.url);
952
+ parsed.username = "";
953
+ parsed.password = "";
954
+ safeUrl = parsed.toString();
955
+ } catch {
956
+ return null;
957
+ }
920
958
  const t = (server.type ?? server.transport ?? "").toLowerCase();
921
959
  return {
922
960
  registry: "url",
923
- id: server.url,
961
+ id: safeUrl,
924
962
  transport: t === "sse" ? "sse" : "http"
925
963
  };
926
964
  }
@@ -968,16 +1006,16 @@ function buildMcpResource(name, group, pkg) {
968
1006
  pkg
969
1007
  };
970
1008
  }
971
- function readText(path7) {
1009
+ function readText(path9) {
972
1010
  try {
973
- if (!existsSync3(path7)) return null;
974
- return readFileSync3(path7, "utf-8");
1011
+ if (!existsSync3(path9)) return null;
1012
+ return readFileSync3(path9, "utf-8");
975
1013
  } catch {
976
1014
  return null;
977
1015
  }
978
1016
  }
979
- function readParsed(path7, parse2) {
980
- const raw = readText(path7);
1017
+ function readParsed(path9, parse2) {
1018
+ const raw = readText(path9);
981
1019
  if (raw === null) return null;
982
1020
  try {
983
1021
  return parse2(raw);
@@ -985,14 +1023,14 @@ function readParsed(path7, parse2) {
985
1023
  return null;
986
1024
  }
987
1025
  }
988
- function readJson2(path7) {
989
- return readParsed(path7, JSON.parse);
1026
+ function readJson2(path9) {
1027
+ return readParsed(path9, JSON.parse);
990
1028
  }
991
- function readYaml(path7) {
992
- return readParsed(path7, parseYaml);
1029
+ function readYaml(path9) {
1030
+ return readParsed(path9, parseYaml);
993
1031
  }
994
- function readToml(path7) {
995
- return readParsed(path7, parseToml);
1032
+ function readToml(path9) {
1033
+ return readParsed(path9, parseToml);
996
1034
  }
997
1035
  function continueListToMap(file) {
998
1036
  if (!file?.mcpServers?.length) return void 0;
@@ -1027,6 +1065,25 @@ function detectMcpServers(cwd, home = homedir3()) {
1027
1065
  out.push(resource);
1028
1066
  }
1029
1067
  };
1068
+ const grokHome = process.env.GROK_HOME ?? join3(home, ".grok");
1069
+ const grokGlobal = readToml(join3(grokHome, "config.toml"));
1070
+ const grokProject = readToml(join3(cwd, ".grok", "config.toml"));
1071
+ const disabled = /* @__PURE__ */ new Set([
1072
+ ...grokGlobal?.disabled_mcp_servers ?? [],
1073
+ ...grokProject?.disabled_mcp_servers ?? []
1074
+ ]);
1075
+ const effectiveGrok = {
1076
+ ...grokGlobal?.mcp_servers ?? {},
1077
+ ...grokProject?.mcp_servers ?? {}
1078
+ };
1079
+ add(
1080
+ Object.fromEntries(
1081
+ Object.entries(effectiveGrok).filter(
1082
+ ([name, config]) => config.enabled !== false && !disabled.has(name)
1083
+ )
1084
+ ),
1085
+ "grok-build"
1086
+ );
1030
1087
  add(readJson2(join3(cwd, ".mcp.json"))?.mcpServers, "claude-code");
1031
1088
  add(readJson2(join3(cwd, "mcp.json"))?.mcpServers, "generic");
1032
1089
  add(
@@ -1112,8 +1169,8 @@ function resolveSource(entry, mpRepoUrl) {
1112
1169
  const src = entry.source;
1113
1170
  if (typeof src === "string") {
1114
1171
  if (!mpRepoUrl) return null;
1115
- const path7 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1116
- return { url: mpRepoUrl, path: path7 || void 0 };
1172
+ const path9 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1173
+ return { url: mpRepoUrl, path: path9 || void 0 };
1117
1174
  }
1118
1175
  if (src && typeof src === "object" && src.url) {
1119
1176
  return { url: src.url, path: src.path, sha: src.sha };
@@ -1150,10 +1207,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
1150
1207
  }
1151
1208
  return out;
1152
1209
  }
1153
- function readJson3(path7) {
1210
+ function readJson3(path9) {
1154
1211
  try {
1155
- if (!existsSync4(path7)) return null;
1156
- return JSON.parse(readFileSync4(path7, "utf-8"));
1212
+ if (!existsSync4(path9)) return null;
1213
+ return JSON.parse(readFileSync4(path9, "utf-8"));
1157
1214
  } catch {
1158
1215
  return null;
1159
1216
  }
@@ -1187,6 +1244,7 @@ var LOCAL_PATTERNS = [
1187
1244
  // Rules
1188
1245
  { path: "CLAUDE.md", type: "rule", group: "claude-code" },
1189
1246
  { path: "AGENTS.md", type: "rule", group: "claude-code" },
1247
+ { path: "GROK.md", type: "rule", group: "grok-build" },
1190
1248
  { path: "GEMINI.md", type: "rule", group: "gemini" },
1191
1249
  { path: ".cursorrules", type: "rule", group: "cursor" },
1192
1250
  { path: ".windsurfrules", type: "rule", group: "windsurf" },
@@ -1217,8 +1275,16 @@ var LOCAL_DIR_PATTERNS = [
1217
1275
  { dir: ".github/instructions", type: "rule", group: "copilot" },
1218
1276
  { dir: ".github/prompts", type: "prompt", group: "copilot" },
1219
1277
  { dir: ".claude/commands", type: "command", group: "claude-code" },
1278
+ { dir: ".claude/skills", type: "skill", group: "claude-code" },
1220
1279
  { dir: ".claude/agents", type: "subagent", group: "claude-code" },
1221
1280
  { dir: ".claude/hooks", type: "hook", group: "claude-code" },
1281
+ { dir: ".grok/skills", type: "skill", group: "grok-build" },
1282
+ { dir: ".grok/commands", type: "command", group: "grok-build" },
1283
+ { dir: ".grok/agents", type: "subagent", group: "grok-build" },
1284
+ { dir: ".grok/hooks", type: "hook", group: "grok-build" },
1285
+ { dir: ".cursor/skills", type: "skill", group: "cursor" },
1286
+ { dir: ".agents/skills", type: "skill", group: "generic" },
1287
+ { dir: ".agents/commands", type: "command", group: "generic" },
1222
1288
  { dir: "prompts", type: "prompt", group: "generic" },
1223
1289
  { dir: ".ai", type: "custom", group: "generic" }
1224
1290
  ];
@@ -1233,8 +1299,8 @@ function loadGitignore(cwd) {
1233
1299
  }
1234
1300
  function readFileSafe(filePath) {
1235
1301
  try {
1236
- const stat6 = statSync(filePath);
1237
- if (stat6.size > MAX_FILE_SIZE) return null;
1302
+ const stat7 = statSync(filePath);
1303
+ if (stat7.size > MAX_FILE_SIZE) return null;
1238
1304
  return readFileSync5(filePath, "utf-8");
1239
1305
  } catch {
1240
1306
  return null;
@@ -1299,7 +1365,9 @@ function scanLocal(cwd) {
1299
1365
  for (const entry of readdirSync2(cwd, { withFileTypes: true }).filter(
1300
1366
  (e) => e.isDirectory()
1301
1367
  )) {
1302
- if (ig.ignores(entry.name + "/")) continue;
1368
+ if ([".grok", ".claude", ".cursor", ".agents"].includes(entry.name))
1369
+ continue;
1370
+ if (ig.ignores(`${entry.name}/`)) continue;
1303
1371
  scanSkillDirs(join5(cwd, entry.name), cwd, ig, results, 1);
1304
1372
  }
1305
1373
  } catch {
@@ -1332,7 +1400,7 @@ function scanSkillDirs(dir, cwd, ig, results, depth) {
1332
1400
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
1333
1401
  if (entry.isDirectory()) {
1334
1402
  const rel = relative(cwd, join5(dir, entry.name));
1335
- if (!ig.ignores(rel + "/")) {
1403
+ if (!ig.ignores(`${rel}/`)) {
1336
1404
  scanSkillDirs(join5(dir, entry.name), cwd, ig, results, depth + 1);
1337
1405
  }
1338
1406
  }
@@ -1345,6 +1413,7 @@ function scanGlobal() {
1345
1413
  const results = [];
1346
1414
  const globalPatterns = [
1347
1415
  { path: ".claude/CLAUDE.md", type: "rule", group: "claude-code" },
1416
+ { path: ".grok/GROK.md", type: "rule", group: "grok-build" },
1348
1417
  { path: ".claude/settings.json", type: "config", group: "claude-code" },
1349
1418
  { path: ".continue/config.json", type: "config", group: "continue" },
1350
1419
  { path: ".continue/config.yaml", type: "config", group: "continue" },
@@ -1376,7 +1445,14 @@ function scanGlobal() {
1376
1445
  { dir: ".claude/commands", type: "command", group: "claude-code" },
1377
1446
  { dir: ".claude/agents", type: "subagent", group: "claude-code" },
1378
1447
  { dir: ".claude/hooks", type: "hook", group: "claude-code" },
1379
- { dir: ".cursor/rules", type: "rule", group: "cursor" }
1448
+ { dir: ".grok/skills", type: "skill", group: "grok-build" },
1449
+ { dir: ".grok/commands", type: "command", group: "grok-build" },
1450
+ { dir: ".grok/agents", type: "subagent", group: "grok-build" },
1451
+ { dir: ".grok/hooks", type: "hook", group: "grok-build" },
1452
+ { dir: ".cursor/rules", type: "rule", group: "cursor" },
1453
+ { dir: ".cursor/skills", type: "skill", group: "cursor" },
1454
+ { dir: ".agents/skills", type: "skill", group: "generic" },
1455
+ { dir: ".agents/commands", type: "command", group: "generic" }
1380
1456
  ];
1381
1457
  for (const { dir, type, group } of globalDirs) {
1382
1458
  const dirPath = join5(home, dir);
@@ -1768,7 +1844,7 @@ function diffResources(current, existing) {
1768
1844
  // src/commands/connect.ts
1769
1845
  import { spawnSync } from "node:child_process";
1770
1846
  import { cpSync, existsSync as existsSync6 } from "node:fs";
1771
- import { homedir as homedir10 } from "node:os";
1847
+ import { homedir as homedir11 } from "node:os";
1772
1848
  import { dirname as dirname3, join as join6 } from "node:path";
1773
1849
  import { fileURLToPath } from "node:url";
1774
1850
  import * as p3 from "@clack/prompts";
@@ -1914,12 +1990,12 @@ function emptyUsage() {
1914
1990
  };
1915
1991
  }
1916
1992
  var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
1917
- function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
1993
+ function addModelUsage(agg, modelKey, counts2, costUSD, messages = 1, at) {
1918
1994
  if (at) {
1919
1995
  noteUsageResponse(agg, {
1920
1996
  tsMs: at.tsMs,
1921
1997
  modelKey,
1922
- counts,
1998
+ counts: counts2,
1923
1999
  costUSD,
1924
2000
  ...at.sidechain ? { sidechain: true } : {}
1925
2001
  });
@@ -1930,13 +2006,13 @@ function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
1930
2006
  agg.byModel.set(modelKey, m);
1931
2007
  }
1932
2008
  m.messages += messages;
1933
- m.input += counts.input;
1934
- m.output += counts.output;
1935
- m.cacheWrite5m += counts.cacheWrite5m;
1936
- m.cacheWrite1h += counts.cacheWrite1h;
1937
- m.cacheWriteUnsplit += counts.cacheWriteUnsplit;
1938
- m.cacheRead += counts.cacheRead;
1939
- if (costUSD === null) m.unpricedTokens += countsTotal(counts);
2009
+ m.input += counts2.input;
2010
+ m.output += counts2.output;
2011
+ m.cacheWrite5m += counts2.cacheWrite5m;
2012
+ m.cacheWrite1h += counts2.cacheWrite1h;
2013
+ m.cacheWriteUnsplit += counts2.cacheWriteUnsplit;
2014
+ m.cacheRead += counts2.cacheRead;
2015
+ if (costUSD === null) m.unpricedTokens += countsTotal(counts2);
1940
2016
  else m.costUSD += costUSD;
1941
2017
  }
1942
2018
  function buildModelRows(agg) {
@@ -2781,13 +2857,13 @@ function playbookHarnesses(reading) {
2781
2857
  return reading.harnesses.filter((harness) => harness.phase !== void 0);
2782
2858
  }
2783
2859
  function startHoursUtc(reading) {
2784
- const counts = /* @__PURE__ */ new Map();
2860
+ const counts2 = /* @__PURE__ */ new Map();
2785
2861
  for (const harness of reading.harnesses) {
2786
2862
  for (const cell of harness.startHours) {
2787
- counts.set(cell.hourUtc, (counts.get(cell.hourUtc) ?? 0) + cell.sessions);
2863
+ counts2.set(cell.hourUtc, (counts2.get(cell.hourUtc) ?? 0) + cell.sessions);
2788
2864
  }
2789
2865
  }
2790
- return counts;
2866
+ return counts2;
2791
2867
  }
2792
2868
  function ownerLocalHour(hourUtc, offsetMinutes) {
2793
2869
  return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
@@ -2795,13 +2871,13 @@ function ownerLocalHour(hourUtc, offsetMinutes) {
2795
2871
  function modalStartHour(reading) {
2796
2872
  const offsetMinutes = reading.utcOffsetMinutes;
2797
2873
  if (offsetMinutes === void 0) return void 0;
2798
- const counts = /* @__PURE__ */ new Map();
2874
+ const counts2 = /* @__PURE__ */ new Map();
2799
2875
  for (const [hourUtc, sessions] of startHoursUtc(reading)) {
2800
2876
  const hour = ownerLocalHour(hourUtc, offsetMinutes);
2801
- counts.set(hour, (counts.get(hour) ?? 0) + sessions);
2877
+ counts2.set(hour, (counts2.get(hour) ?? 0) + sessions);
2802
2878
  }
2803
- if (counts.size === 0) return void 0;
2804
- return [...counts.entries()].sort(
2879
+ if (counts2.size === 0) return void 0;
2880
+ return [...counts2.entries()].sort(
2805
2881
  (a, b) => b[1] - a[1] || a[0] - b[0]
2806
2882
  )[0]?.[0];
2807
2883
  }
@@ -2809,10 +2885,10 @@ function modalStartHour(reading) {
2809
2885
  // ../workflow-rules/src/componentRules.ts
2810
2886
  var COMPONENT_RULES_V2 = "component-rules/v2";
2811
2887
  var gitCoverage = () => 1;
2812
- function harnessShare(input, counts) {
2888
+ function harnessShare(input, counts2) {
2813
2889
  const synced = input.reading.harnesses.length;
2814
2890
  if (synced === 0) return 0;
2815
- return counts(input) / synced;
2891
+ return counts2(input) / synced;
2816
2892
  }
2817
2893
  function topShare(entries) {
2818
2894
  const total = entries.reduce((sum, entry) => sum + entry.value, 0);
@@ -3123,6 +3199,7 @@ var HANDOFF_MARKERS = {
3123
3199
  "mcp__curia__request_review"
3124
3200
  ],
3125
3201
  codex: ["request_user_input"],
3202
+ "grok-build": ["ask_user_question", "request_user_input"],
3126
3203
  opencode: ["question"],
3127
3204
  "pi-mono": []
3128
3205
  };
@@ -3153,7 +3230,11 @@ var SCOUT_TOOLS = [
3153
3230
  "web_search",
3154
3231
  "tool_search",
3155
3232
  "codebase_search",
3156
- "find"
3233
+ "find",
3234
+ "search_tool",
3235
+ "search_files",
3236
+ "list_dir",
3237
+ "read_file"
3157
3238
  ];
3158
3239
  var EDIT_TOOLS = [
3159
3240
  "Edit",
@@ -3164,11 +3245,20 @@ var EDIT_TOOLS = [
3164
3245
  "write",
3165
3246
  "patch",
3166
3247
  "multiedit",
3167
- "apply_patch"
3248
+ "apply_patch",
3249
+ "write_file",
3250
+ "edit_file"
3168
3251
  ];
3169
3252
  var REVIEW_SKILLS = ["code-review", "security-review", "review"];
3170
3253
  var SCOUT_AGENTS = ["Explore", "Plan", "research"];
3171
- var SHELL_TOOLS = ["Bash", "bash", "shell", "local_shell", "exec_command"];
3254
+ var SHELL_TOOLS = [
3255
+ "Bash",
3256
+ "bash",
3257
+ "shell",
3258
+ "local_shell",
3259
+ "exec_command",
3260
+ "run_terminal_command"
3261
+ ];
3172
3262
  var SKILL_TOOLS = ["Skill", "skill"];
3173
3263
  var AGENT_TOOLS = ["Agent", "Task", "task", "agent"];
3174
3264
  var BOOKKEEPING_TOOLS = [
@@ -4320,7 +4410,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4320
4410
  0
4321
4411
  );
4322
4412
  const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
4323
- const routesModels = harness === "claude-code" || harness === "opencode";
4413
+ const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build";
4324
4414
  const asRows = (map) => {
4325
4415
  const safe = /* @__PURE__ */ new Map();
4326
4416
  for (const [model, tokens] of map) {
@@ -4505,8 +4595,8 @@ function ingestClaudeCompaction(agg, rec, ctx, tsMs) {
4505
4595
  ...rec.isSidechain === true ? { sidechain: true } : {}
4506
4596
  });
4507
4597
  }
4508
- function contextOf(counts) {
4509
- return counts.input + counts.cacheWrite5m + counts.cacheWrite1h + counts.cacheWriteUnsplit + counts.cacheRead;
4598
+ function contextOf(counts2) {
4599
+ return counts2.input + counts2.cacheWrite5m + counts2.cacheWrite1h + counts2.cacheWriteUnsplit + counts2.cacheRead;
4510
4600
  }
4511
4601
  function ingestClaudeTurnDuration(agg, rec, ctx, tsMs) {
4512
4602
  if (tsMs === null || asStr(rec.subtype) !== "turn_duration" || asNum(rec.durationMs) <= 0) {
@@ -4534,12 +4624,12 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4534
4624
  const session = sidechain ? `${baseSession}:agent:${agentId ?? "unknown"}` : baseSession;
4535
4625
  const parentSession = sidechain ? baseSession : void 0;
4536
4626
  const usage = asObj(msg.usage);
4537
- const counts = usage ? readCounts(usage) : null;
4627
+ const counts2 = usage ? readCounts(usage) : null;
4538
4628
  const messageId = asStr(msg.id);
4539
4629
  const newTurn = messageId ? !agg.workflowSeenTurns.has(messageId) : true;
4540
4630
  if (messageId) agg.workflowSeenTurns.add(messageId);
4541
4631
  let context = null;
4542
- if (counts && isApiCall(rec)) {
4632
+ if (counts2 && isApiCall(rec)) {
4543
4633
  const held = agg.contextFirstCall.get(session);
4544
4634
  let first = false;
4545
4635
  if (held === void 0) {
@@ -4547,11 +4637,11 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4547
4637
  first = true;
4548
4638
  } else if (held !== null && held === messageId) first = true;
4549
4639
  context = {
4550
- contextTokens: contextOf(counts),
4640
+ contextTokens: contextOf(counts2),
4551
4641
  ...first ? {
4552
4642
  firstCall: {
4553
- harnessTokens: counts.cacheRead,
4554
- instructionsTokens: contextOf(counts) - counts.cacheRead
4643
+ harnessTokens: counts2.cacheRead,
4644
+ instructionsTokens: contextOf(counts2) - counts2.cacheRead
4555
4645
  }
4556
4646
  } : {}
4557
4647
  };
@@ -4565,8 +4655,8 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4565
4655
  ...parentSession ? { parentSession } : {},
4566
4656
  ...messageId ? { responseId: messageId } : {},
4567
4657
  ...asName(msg.model) ? { model: asName(msg.model) } : {},
4568
- ...counts ? { responseTokens: counts.output } : {},
4569
- ...counts ? { routingTokens: countsTotal(counts) } : {},
4658
+ ...counts2 ? { responseTokens: counts2.output } : {},
4659
+ ...counts2 ? { routingTokens: countsTotal(counts2) } : {},
4570
4660
  ...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {},
4571
4661
  ...context ?? {}
4572
4662
  });
@@ -4693,11 +4783,11 @@ function readCounts(usage) {
4693
4783
  function modelKeyFor2(model, speed) {
4694
4784
  return normalizeModel(speed === "fast" ? `${model}#fast` : model);
4695
4785
  }
4696
- function makeEntry(modelKey, counts, tsMs) {
4786
+ function makeEntry(modelKey, counts2, tsMs) {
4697
4787
  return {
4698
4788
  modelKey,
4699
- counts,
4700
- costUSD: apiEquivalentCost(modelKey, counts, tsMs)
4789
+ counts: counts2,
4790
+ costUSD: apiEquivalentCost(modelKey, counts2, tsMs)
4701
4791
  };
4702
4792
  }
4703
4793
  function buildContribution(usage, model, sidechain, tsMs) {
@@ -4742,24 +4832,24 @@ function buildContribution(usage, model, sidechain, tsMs) {
4742
4832
  };
4743
4833
  }
4744
4834
  function applyContribution(agg, c, sign) {
4745
- c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
4835
+ c.entries.forEach(({ modelKey, counts: counts2, costUSD }, i) => {
4746
4836
  let m = agg.byModel.get(modelKey);
4747
4837
  if (!m) {
4748
4838
  m = emptyUsage();
4749
4839
  agg.byModel.set(modelKey, m);
4750
4840
  }
4751
4841
  if (i === 0) m.messages += sign;
4752
- m.input += sign * counts.input;
4753
- m.output += sign * counts.output;
4754
- m.cacheWrite5m += sign * counts.cacheWrite5m;
4755
- m.cacheWrite1h += sign * counts.cacheWrite1h;
4756
- m.cacheWriteUnsplit += sign * counts.cacheWriteUnsplit;
4757
- m.cacheRead += sign * counts.cacheRead;
4758
- if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts);
4842
+ m.input += sign * counts2.input;
4843
+ m.output += sign * counts2.output;
4844
+ m.cacheWrite5m += sign * counts2.cacheWrite5m;
4845
+ m.cacheWrite1h += sign * counts2.cacheWrite1h;
4846
+ m.cacheWriteUnsplit += sign * counts2.cacheWriteUnsplit;
4847
+ m.cacheRead += sign * counts2.cacheRead;
4848
+ if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts2);
4759
4849
  else m.costUSD += sign * costUSD;
4760
4850
  noteUsageResponse(
4761
4851
  agg,
4762
- { tsMs: c.tsMs, modelKey, counts, costUSD, sidechain: c.sidechain },
4852
+ { tsMs: c.tsMs, modelKey, counts: counts2, costUSD, sidechain: c.sidechain },
4763
4853
  sign
4764
4854
  );
4765
4855
  });
@@ -5013,7 +5103,7 @@ function genuineDelta(payload, state, tsMs) {
5013
5103
  if (!last) return null;
5014
5104
  const inputTotal = asNum(last.input_tokens);
5015
5105
  const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
5016
- const counts = {
5106
+ const counts2 = {
5017
5107
  input: inputTotal - cached,
5018
5108
  output: asNum(last.output_tokens),
5019
5109
  cacheWrite5m: 0,
@@ -5021,11 +5111,11 @@ function genuineDelta(payload, state, tsMs) {
5021
5111
  cacheWriteUnsplit: 0,
5022
5112
  cacheRead: cached
5023
5113
  };
5024
- if (countsTotal(counts) === 0) return null;
5114
+ if (countsTotal(counts2) === 0) return null;
5025
5115
  if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
5026
5116
  return null;
5027
5117
  return {
5028
- counts,
5118
+ counts: counts2,
5029
5119
  last,
5030
5120
  contextWindow: info ? asNum(info.model_context_window) : 0
5031
5121
  };
@@ -5092,8 +5182,8 @@ function noteActivity(agg, state, tsMs) {
5092
5182
  function ingestEvent(agg, payload, state, tsMs, firstCall) {
5093
5183
  const delta = genuineDelta(payload, state, tsMs);
5094
5184
  if (!delta) return;
5095
- const { counts, last, contextWindow } = delta;
5096
- const total = countsTotal(counts);
5185
+ const { counts: counts2, last, contextWindow } = delta;
5186
+ const total = countsTotal(counts2);
5097
5187
  if (state.modelKey === null) return;
5098
5188
  if (tsMs === null) agg.untimestampedResponses++;
5099
5189
  agg.distinctResponses++;
@@ -5101,8 +5191,8 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
5101
5191
  addModelUsage(
5102
5192
  agg,
5103
5193
  modelKey,
5104
- counts,
5105
- apiEquivalentCost(modelKey, counts, tsMs),
5194
+ counts2,
5195
+ apiEquivalentCost(modelKey, counts2, tsMs),
5106
5196
  1,
5107
5197
  { tsMs }
5108
5198
  );
@@ -5116,7 +5206,7 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
5116
5206
  projectWorkspace: state.cwd ?? void 0,
5117
5207
  tsMs,
5118
5208
  model: modelKey,
5119
- responseTokens: counts.output,
5209
+ responseTokens: counts2.output,
5120
5210
  routingTokens: total,
5121
5211
  thinkingTokens: asNum(last.reasoning_output_tokens),
5122
5212
  ...state.effort ? { effort: state.effort } : {},
@@ -5125,12 +5215,12 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
5125
5215
  // instructions and tool specs, cached by an earlier session) and
5126
5216
  // the fresh part is the instructions: AGENTS.md, environment,
5127
5217
  // skills and the first prompt. Same method as Claude Code (#358).
5128
- contextTokens: counts.input + counts.cacheRead,
5218
+ contextTokens: counts2.input + counts2.cacheRead,
5129
5219
  ...contextWindow > 0 ? { contextWindow } : {},
5130
5220
  ...firstCall && !state.forked ? {
5131
5221
  firstCall: {
5132
- harnessTokens: counts.cacheRead,
5133
- instructionsTokens: counts.input
5222
+ harnessTokens: counts2.cacheRead,
5223
+ instructionsTokens: counts2.input
5134
5224
  }
5135
5225
  } : {}
5136
5226
  });
@@ -5350,11 +5440,11 @@ function ingestWithRetry(agg, file, opts) {
5350
5440
  }
5351
5441
  }
5352
5442
  function ingestFile2(agg, file, opts) {
5353
- const readFile = opts.readFileImpl ?? readFileSync6;
5443
+ const readFile2 = opts.readFileImpl ?? readFileSync6;
5354
5444
  let text;
5355
5445
  if (file.endsWith(".zst")) {
5356
5446
  if (zstdDecompress === null) throw readError("zstd-unsupported");
5357
- const raw = readFile(file);
5447
+ const raw = readFile2(file);
5358
5448
  try {
5359
5449
  text = zstdDecompress(
5360
5450
  Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
@@ -5363,7 +5453,7 @@ function ingestFile2(agg, file, opts) {
5363
5453
  throw readError("zstd-corrupt");
5364
5454
  }
5365
5455
  } else {
5366
- text = readFile(file).toString("utf8");
5456
+ text = readFile2(file).toString("utf8");
5367
5457
  }
5368
5458
  const records = [];
5369
5459
  let nonEmptyLines = 0;
@@ -5466,6 +5556,510 @@ var codexAdapter = {
5466
5556
  }
5467
5557
  };
5468
5558
 
5559
+ // src/harness/grok/analyzer.ts
5560
+ function createAggregate4() {
5561
+ const workflowLocal = createWorkflowLocalSources();
5562
+ return Object.assign(createAggregate(), {
5563
+ workflow: createHarnessWorkflowReducer("grok-build", workflowLocal),
5564
+ workflowLocal
5565
+ });
5566
+ }
5567
+ var createGrokEventState = (parentSession) => ({
5568
+ tools: /* @__PURE__ */ new Map(),
5569
+ completedTools: /* @__PURE__ */ new Set(),
5570
+ ...parentSession ? { parentSession } : {}
5571
+ });
5572
+ var bump3 = (map, key2) => {
5573
+ map.set(key2, (map.get(key2) ?? 0) + 1);
5574
+ };
5575
+ var toolMetadata = (update) => {
5576
+ const meta = asObj(update._meta);
5577
+ return meta && asObj(meta["x.ai/tool"]);
5578
+ };
5579
+ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
5580
+ const root = asObj(value);
5581
+ const params = root && asObj(root.params);
5582
+ const update = params && asObj(params.update);
5583
+ const session = params && asStr(params.sessionId);
5584
+ const tsMs = timestampMs(
5585
+ asObj(params?._meta)?.agentTimestampMs ?? root?.timestamp
5586
+ );
5587
+ if (!update || !session || tsMs === null) return;
5588
+ const kind = asStr(update.sessionUpdate);
5589
+ if (kind === "tool_call") {
5590
+ const id = asStr(update.toolCallId);
5591
+ const metadata = toolMetadata(update);
5592
+ const name = asName(metadata?.name ?? update.toolName);
5593
+ if (!id || !name || state.tools.has(id)) return;
5594
+ const raw = asObj(update.input);
5595
+ const arg = asStr(raw?.command ?? raw?.query ?? raw?.skill ?? raw?.name);
5596
+ state.tools.set(id, { name, ...arg ? { arg } : {}, tsMs });
5597
+ return;
5598
+ }
5599
+ if (sinceMs !== void 0 && tsMs < sinceMs) return;
5600
+ if (kind === "tool_call_update") {
5601
+ const id = asStr(update.toolCallId);
5602
+ if (!id || asStr(update.status) !== "completed") return;
5603
+ completeTool(agg, state, id, session, projectDir, tsMs);
5604
+ return;
5605
+ }
5606
+ if (kind !== "turn_completed") return;
5607
+ const usage = asObj(update.usage);
5608
+ if (!usage) return;
5609
+ const prompt = asStr(update.prompt_id) ?? `turn:${tsMs}`;
5610
+ for (const [model, raw] of Object.entries(asObj(usage.modelUsage) ?? {})) {
5611
+ const row = asObj(raw);
5612
+ agg.workflow.ingest({
5613
+ type: "response",
5614
+ session,
5615
+ projectWorkspace: projectDir,
5616
+ parentSession: state.parentSession,
5617
+ tsMs,
5618
+ responseId: `${prompt}:${model}`,
5619
+ model,
5620
+ thinkingTokens: asNum(row?.reasoningTokens),
5621
+ responseTokens: asNum(row?.outputTokens),
5622
+ 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 } : {}
5624
+ });
5625
+ }
5626
+ agg.workflow.ingest({
5627
+ type: "turn",
5628
+ session,
5629
+ projectWorkspace: projectDir,
5630
+ parentSession: state.parentSession,
5631
+ tsMs,
5632
+ turnId: prompt,
5633
+ questionBack: asStr(update.stop_reason) === "question"
5634
+ });
5635
+ }
5636
+ function completeTool(agg, state, id, session, projectDir, tsMs) {
5637
+ if (state.completedTools.has(id)) return;
5638
+ const tool = state.tools.get(id);
5639
+ if (!tool) return;
5640
+ state.completedTools.add(id);
5641
+ bump3(agg.toolCalls, tool.name);
5642
+ if (["web_search", "websearch", "search_web"].includes(tool.name))
5643
+ agg.webSearchRequests++;
5644
+ if (["skill", "use_skill"].includes(tool.name) && tool.arg)
5645
+ bump3(agg.skillCalls, tool.arg);
5646
+ const mcp = /^(?:mcp__|mcp:)([^_:]+)[_:](.+)$/.exec(tool.name);
5647
+ if (mcp) {
5648
+ bump3(agg.mcpServerCalls, mcp[1]);
5649
+ bump3(agg.mcpToolCalls, tool.name);
5650
+ } else if (tool.name === "use_tool" && tool.arg?.includes("__")) {
5651
+ const [server] = tool.arg.split("__", 1);
5652
+ if (server) {
5653
+ bump3(agg.mcpServerCalls, server);
5654
+ bump3(agg.mcpToolCalls, tool.arg);
5655
+ }
5656
+ }
5657
+ agg.workflow.ingest({
5658
+ type: "event",
5659
+ session,
5660
+ projectWorkspace: projectDir,
5661
+ parentSession: state.parentSession,
5662
+ tsMs: tool.tsMs || tsMs,
5663
+ tool: tool.name,
5664
+ ...tool.arg ? { arg: tool.arg } : {},
5665
+ batchId: id
5666
+ });
5667
+ }
5668
+ function ingestEvent2(agg, state, value, sessionFallback, projectDir, sinceMs) {
5669
+ const row = asObj(value);
5670
+ if (!row) return;
5671
+ const session = asStr(row.session_id) ?? sessionFallback;
5672
+ const tsMs = timestampMs(row.ts);
5673
+ if (!session || tsMs === null) return;
5674
+ if (asStr(row.type) === "tool_started") {
5675
+ const id = asStr(row.tool_call_id);
5676
+ const name = asName(row.tool_name);
5677
+ if (id && name && !state.tools.has(id)) state.tools.set(id, { name, tsMs });
5678
+ } else if (sinceMs !== void 0 && tsMs < sinceMs) {
5679
+ return;
5680
+ } else if (asStr(row.type) === "tool_completed") {
5681
+ const id = asStr(row.tool_call_id);
5682
+ if (id) completeTool(agg, state, id, session, projectDir, tsMs);
5683
+ } else if (asStr(row.type) === "compaction") {
5684
+ agg.workflow.ingest({
5685
+ type: "compaction",
5686
+ session,
5687
+ projectWorkspace: projectDir,
5688
+ parentSession: state.parentSession,
5689
+ tsMs
5690
+ });
5691
+ }
5692
+ }
5693
+ var timestampMs = (value) => {
5694
+ if (typeof value === "string") {
5695
+ const parsed = Date.parse(value);
5696
+ return Number.isFinite(parsed) ? parsed : null;
5697
+ }
5698
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
5699
+ return value < 1e10 ? value * 1e3 : value;
5700
+ };
5701
+ function counts(value) {
5702
+ const row = asObj(value);
5703
+ if (!row) return null;
5704
+ const totalInput = asNum(row.inputTokens);
5705
+ const cacheRead = asNum(row.cachedReadTokens);
5706
+ const cacheWrite = asNum(row.cacheCreationTokens);
5707
+ if (totalInput < cacheRead + cacheWrite) return null;
5708
+ const result = {
5709
+ input: totalInput - cacheRead - cacheWrite,
5710
+ output: asNum(row.outputTokens),
5711
+ cacheWrite5m: 0,
5712
+ cacheWrite1h: 0,
5713
+ cacheWriteUnsplit: cacheWrite,
5714
+ cacheRead
5715
+ };
5716
+ return countsTotal(result) > 0 ? result : null;
5717
+ }
5718
+ function sidecarContributions(value, projectDir) {
5719
+ const root = asObj(value);
5720
+ const sessionId = root && asStr(root.sessionId);
5721
+ if (!root || !sessionId) return [];
5722
+ const turns = Array.isArray(root.turns) ? root.turns : [];
5723
+ const out = [];
5724
+ for (const raw of turns) {
5725
+ const turn = asObj(raw);
5726
+ const tsMs = turn && timestampMs(turn.endedAt);
5727
+ if (!turn || tsMs === null) continue;
5728
+ const models = [];
5729
+ const perModel = asObj(turn.modelUsage);
5730
+ for (const [model, usage] of Object.entries(perModel ?? {})) {
5731
+ const c = counts(usage);
5732
+ if (c) models.push({ model, counts: c });
5733
+ }
5734
+ if (models.length === 0) {
5735
+ const c = counts(turn);
5736
+ const model = asStr(turn.primaryModelId);
5737
+ if (c && model) models.push({ model, counts: c });
5738
+ }
5739
+ if (models.length > 0)
5740
+ out.push({
5741
+ sessionId,
5742
+ projectDir,
5743
+ tsMs,
5744
+ ...asNum(turn.apiDurationMs) > 0 ? { durationMs: asNum(turn.apiDurationMs) } : {},
5745
+ models
5746
+ });
5747
+ }
5748
+ return out;
5749
+ }
5750
+ function terminalContribution(value, projectDir) {
5751
+ const root = asObj(value);
5752
+ const params = root && asObj(root.params);
5753
+ const update = params && asObj(params.update);
5754
+ const meta = params && asObj(params._meta);
5755
+ if (!update || asStr(update.sessionUpdate) !== "turn_completed") return null;
5756
+ const usage = asObj(update.usage);
5757
+ const sessionId = params && asStr(params.sessionId);
5758
+ const tsMs = timestampMs(meta?.agentTimestampMs ?? root?.timestamp);
5759
+ if (!usage || !sessionId || tsMs === null) return null;
5760
+ const models = [];
5761
+ for (const [model, row] of Object.entries(asObj(usage.modelUsage) ?? {})) {
5762
+ const c = counts(row);
5763
+ if (c) models.push({ model, counts: c });
5764
+ }
5765
+ return models.length === 0 ? null : {
5766
+ sessionId,
5767
+ projectDir,
5768
+ tsMs,
5769
+ ...asNum(update.elapsed_ms) > 0 ? { durationMs: asNum(update.elapsed_ms) } : {},
5770
+ models
5771
+ };
5772
+ }
5773
+ function ingestContribution(agg, row) {
5774
+ agg.records++;
5775
+ agg.assistantRecords++;
5776
+ agg.distinctResponses++;
5777
+ agg.sessions.add(row.sessionId);
5778
+ agg.activeDays.add(new Date(row.tsMs).toISOString().slice(0, 10));
5779
+ agg.projectDirs.add(row.projectDir);
5780
+ agg.firstTs = agg.firstTs === null ? row.tsMs : Math.min(agg.firstTs, row.tsMs);
5781
+ agg.lastTs = agg.lastTs === null ? row.tsMs : Math.max(agg.lastTs, row.tsMs);
5782
+ noteSessionStart(agg, row.sessionId, row.tsMs);
5783
+ noteProjectDay(agg, row.projectDir, row.tsMs);
5784
+ for (const { model, counts: tokenCounts } of row.models) {
5785
+ const key2 = normalizeModel(model);
5786
+ addModelUsage(
5787
+ agg,
5788
+ key2,
5789
+ tokenCounts,
5790
+ apiEquivalentCost(key2, tokenCounts, row.tsMs),
5791
+ 1,
5792
+ {
5793
+ tsMs: row.tsMs
5794
+ }
5795
+ );
5796
+ }
5797
+ }
5798
+
5799
+ // src/harness/grok/scan.ts
5800
+ 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";
5804
+ import readline2 from "node:readline";
5805
+ import { parse as parseToml3 } from "smol-toml";
5806
+ function sessionRoots() {
5807
+ return [
5808
+ path4.join(
5809
+ process.env.GROK_HOME || path4.join(homedir8(), ".grok"),
5810
+ "sessions"
5811
+ )
5812
+ ];
5813
+ }
5814
+ var isGrokEvidenceFile = (name) => name === "usage.json" || name === "updates.jsonl" || name === "events.jsonl";
5815
+ var delays = [0, 100, 300];
5816
+ var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5817
+ async function stableRead(file, jsonl) {
5818
+ for (const delay of delays) {
5819
+ if (delay) await pause(delay);
5820
+ try {
5821
+ const before = await stat4(file);
5822
+ if (!before.isFile()) return { complete: false };
5823
+ if (jsonl) {
5824
+ const lines2 = [];
5825
+ const input = readline2.createInterface({
5826
+ input: createReadStream2(file),
5827
+ crlfDelay: Infinity
5828
+ });
5829
+ for await (const line of input) {
5830
+ if (!line.trim()) continue;
5831
+ try {
5832
+ lines2.push(JSON.parse(line));
5833
+ } catch {
5834
+ lines2.push(null);
5835
+ }
5836
+ }
5837
+ const after = await stat4(file);
5838
+ if (before.size === after.size && before.mtimeMs === after.mtimeMs)
5839
+ return { lines: lines2, complete: true };
5840
+ } else {
5841
+ const raw = await readFile(file, "utf8");
5842
+ const after = await stat4(file);
5843
+ if (before.size === after.size && before.mtimeMs === after.mtimeMs)
5844
+ return { json: JSON.parse(raw), complete: true };
5845
+ }
5846
+ } catch {
5847
+ }
5848
+ }
5849
+ return { complete: false };
5850
+ }
5851
+ async function directories(root) {
5852
+ try {
5853
+ const workspaces = await readdir4(root, { withFileTypes: true });
5854
+ const out = [];
5855
+ for (const workspace of workspaces) {
5856
+ if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
5857
+ const workspacePath = path4.join(root, workspace.name);
5858
+ for (const session of await readdir4(workspacePath, {
5859
+ withFileTypes: true
5860
+ })) {
5861
+ if (session.isDirectory() && !session.isSymbolicLink())
5862
+ out.push(path4.join(workspacePath, session.name));
5863
+ }
5864
+ }
5865
+ return out.sort();
5866
+ } catch (error) {
5867
+ return error.code === "ENOENT" ? [] : null;
5868
+ }
5869
+ }
5870
+ async function modelAliasesForRoot(root) {
5871
+ if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
5872
+ try {
5873
+ const parsed = asObj(
5874
+ parseToml3(await readFile(path4.join(root, "..", "config.toml"), "utf8"))
5875
+ );
5876
+ if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
5877
+ const models = asObj(parsed.model);
5878
+ const aliases = /* @__PURE__ */ new Map();
5879
+ for (const [alias, raw] of Object.entries(models ?? {})) {
5880
+ const entry = asObj(raw);
5881
+ const model = entry && asStr(entry.model);
5882
+ if (!model || entry?.base_url || entry?.model_provider) continue;
5883
+ aliases.set(alias, model);
5884
+ }
5885
+ return aliases;
5886
+ } catch {
5887
+ return /* @__PURE__ */ new Map();
5888
+ }
5889
+ }
5890
+ async function scan3(agg, opts = {}) {
5891
+ const stats = emptyScanStats();
5892
+ const sessionDates = /* @__PURE__ */ new Map();
5893
+ const candidates = /* @__PURE__ */ new Map();
5894
+ let complete = true;
5895
+ for (const root of opts.roots ?? sessionRoots()) {
5896
+ const aliases = await modelAliasesForRoot(root);
5897
+ const dirs = await directories(root);
5898
+ if (dirs === null) {
5899
+ complete = false;
5900
+ continue;
5901
+ }
5902
+ for (const dir of dirs) {
5903
+ let entries;
5904
+ try {
5905
+ entries = await readdir4(dir, { withFileTypes: true });
5906
+ } catch {
5907
+ complete = false;
5908
+ continue;
5909
+ }
5910
+ const files = new Map(
5911
+ entries.filter(
5912
+ (e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
5913
+ ).map((e) => [e.name, path4.join(dir, e.name)])
5914
+ );
5915
+ let projectDir = dir;
5916
+ let sessionFallback = path4.basename(dir);
5917
+ let child = false;
5918
+ let parentSession;
5919
+ const summary = files.get("summary.json");
5920
+ if (summary) {
5921
+ const read2 = await stableRead(summary, false);
5922
+ if (!read2.complete) {
5923
+ complete = false;
5924
+ stats.filesUnreadable++;
5925
+ continue;
5926
+ }
5927
+ const value = asObj(read2.json);
5928
+ const info = value && asObj(value.info);
5929
+ projectDir = asStr(info?.cwd) ?? asStr(value?.cwd) ?? dir;
5930
+ sessionFallback = asStr(value?.sessionId) ?? sessionFallback;
5931
+ parentSession = asStr(value?.parentSessionId) ?? asStr(value?.parent_session_id) ?? asStr(info?.parentSessionId) ?? asStr(info?.parent_session_id) ?? void 0;
5932
+ child = parentSession !== void 0;
5933
+ }
5934
+ let rows = [];
5935
+ let precedence = 0;
5936
+ const usage = files.get("usage.json");
5937
+ if (usage) {
5938
+ stats.filesFound++;
5939
+ const read2 = await stableRead(usage, false);
5940
+ if (!read2.complete) {
5941
+ complete = false;
5942
+ stats.filesUnreadable++;
5943
+ continue;
5944
+ }
5945
+ stats.filesRead++;
5946
+ rows = sidecarContributions(read2.json, projectDir);
5947
+ if (rows.length > 0) precedence = 2;
5948
+ }
5949
+ if (child) {
5950
+ rows = [];
5951
+ precedence = 0;
5952
+ }
5953
+ const eventState = createGrokEventState(parentSession);
5954
+ const updates = files.get("updates.jsonl");
5955
+ if (updates) {
5956
+ const useTerminalUsage = rows.length === 0 && !child;
5957
+ stats.filesFound++;
5958
+ const read2 = await stableRead(updates, true);
5959
+ if (!read2.complete) {
5960
+ complete = false;
5961
+ stats.filesUnreadable++;
5962
+ continue;
5963
+ }
5964
+ stats.filesRead++;
5965
+ for (const value of read2.lines ?? []) {
5966
+ if (value === null) {
5967
+ agg.parseErrors++;
5968
+ continue;
5969
+ }
5970
+ ingestUpdate(agg, eventState, value, projectDir, opts.sinceMs);
5971
+ if (useTerminalUsage) {
5972
+ const row = terminalContribution(value, projectDir);
5973
+ if (row) rows.push(row);
5974
+ }
5975
+ }
5976
+ if (rows.length > 0) precedence = 1;
5977
+ }
5978
+ const events = files.get("events.jsonl");
5979
+ if (events) {
5980
+ stats.filesFound++;
5981
+ const read2 = await stableRead(events, true);
5982
+ if (!read2.complete) {
5983
+ complete = false;
5984
+ stats.filesUnreadable++;
5985
+ continue;
5986
+ }
5987
+ stats.filesRead++;
5988
+ for (const value of read2.lines ?? []) {
5989
+ if (value === null) agg.parseErrors++;
5990
+ else
5991
+ ingestEvent2(
5992
+ agg,
5993
+ eventState,
5994
+ value,
5995
+ sessionFallback,
5996
+ projectDir,
5997
+ opts.sinceMs
5998
+ );
5999
+ }
6000
+ }
6001
+ rows = rows.filter(
6002
+ (row) => opts.sinceMs === void 0 || row.tsMs >= opts.sinceMs
6003
+ );
6004
+ rows = rows.map((row) => ({
6005
+ ...row,
6006
+ models: row.models.map(({ model, counts: counts2 }) => ({
6007
+ model: aliases.get(model) ?? model,
6008
+ counts: counts2
6009
+ }))
6010
+ }));
6011
+ if (rows.length > 0) {
6012
+ const sessionId = rows[0]?.sessionId;
6013
+ const held = candidates.get(sessionId);
6014
+ const total = (values) => values.flatMap((value) => value.models).reduce((sum, model) => sum + countsTotal(model.counts), 0);
6015
+ if (!held || precedence > held.precedence || precedence === held.precedence && total(rows) > total(held.rows))
6016
+ candidates.set(sessionId, { precedence, rows });
6017
+ }
6018
+ opts.onProgress?.(stats.filesFound);
6019
+ }
6020
+ }
6021
+ for (const { rows } of candidates.values()) {
6022
+ for (const row of rows) {
6023
+ ingestContribution(agg, row);
6024
+ const dates = sessionDates.get(row.sessionId) ?? /* @__PURE__ */ new Set();
6025
+ dates.add(new Date(row.tsMs).toISOString().slice(0, 10));
6026
+ sessionDates.set(row.sessionId, dates);
6027
+ }
6028
+ }
6029
+ return { stats, complete, sessionDates };
6030
+ }
6031
+
6032
+ // src/harness/grok/adapter.ts
6033
+ var GROK_HARNESS_NAME = "grok-build";
6034
+ var GROK_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
6035
+ "run_terminal_command",
6036
+ "read_file",
6037
+ "write_file",
6038
+ "search",
6039
+ "web_search"
6040
+ ]);
6041
+ var grokAdapter = {
6042
+ name: GROK_HARNESS_NAME,
6043
+ builtinTools: GROK_BUILTIN_TOOLS,
6044
+ detect: (opts) => hasRecentFile(
6045
+ opts.roots ?? sessionRoots(),
6046
+ isGrokEvidenceFile,
6047
+ opts.sinceMs
6048
+ ),
6049
+ async scan(opts) {
6050
+ const aggregate = createAggregate4();
6051
+ const result = await scan3(aggregate, opts);
6052
+ return {
6053
+ aggregate,
6054
+ stats: result.stats,
6055
+ workflow: aggregate.workflow.finish(),
6056
+ workflowLocal: aggregate.workflowLocal,
6057
+ scanComplete: result.complete,
6058
+ sessionDates: result.sessionDates
6059
+ };
6060
+ }
6061
+ };
6062
+
5469
6063
  // src/harness/opencode/analyzer.ts
5470
6064
  var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
5471
6065
  "apply_patch",
@@ -5485,7 +6079,7 @@ var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
5485
6079
  "websearch",
5486
6080
  "write"
5487
6081
  ]);
5488
- function createAggregate4() {
6082
+ function createAggregate5() {
5489
6083
  const workflowLocal = createWorkflowLocalSources();
5490
6084
  return Object.assign(createAggregate(), {
5491
6085
  workflow: createHarnessWorkflowReducer("opencode", workflowLocal),
@@ -5534,7 +6128,7 @@ function ingestMessageRow(agg, state, row) {
5534
6128
  agg.assistantRecords++;
5535
6129
  agg.distinctResponses++;
5536
6130
  if (tsMs === null) agg.untimestampedResponses++;
5537
- const counts = {
6131
+ const counts2 = {
5538
6132
  input: asNum(row.input),
5539
6133
  output: asNum(row.output),
5540
6134
  cacheWrite5m: 0,
@@ -5542,7 +6136,7 @@ function ingestMessageRow(agg, state, row) {
5542
6136
  cacheWriteUnsplit: asNum(row.cacheWrite),
5543
6137
  cacheRead: asNum(row.cacheRead)
5544
6138
  };
5545
- const total = counts.input + counts.output + counts.cacheWriteUnsplit + counts.cacheRead;
6139
+ const total = counts2.input + counts2.output + counts2.cacheWriteUnsplit + counts2.cacheRead;
5546
6140
  if (session?.parentId) agg.sidechainTokens += total;
5547
6141
  else agg.mainTokens += total;
5548
6142
  const provider = asStr(row.providerId);
@@ -5551,8 +6145,8 @@ function ingestMessageRow(agg, state, row) {
5551
6145
  addModelUsage(
5552
6146
  agg,
5553
6147
  modelKey,
5554
- counts,
5555
- apiEquivalentCost(modelKey, counts, tsMs),
6148
+ counts2,
6149
+ apiEquivalentCost(modelKey, counts2, tsMs),
5556
6150
  1,
5557
6151
  { tsMs, sidechain: Boolean(session?.parentId) }
5558
6152
  );
@@ -5567,7 +6161,7 @@ function ingestMessageRow(agg, state, row) {
5567
6161
  tsMs,
5568
6162
  ...provider && model ? { model: `${provider}:${model}` } : {},
5569
6163
  thinkingTokens: asNum(row.reasoning),
5570
- responseTokens: counts.output,
6164
+ responseTokens: counts2.output,
5571
6165
  routingTokens: total,
5572
6166
  ...completed > tsMs ? { durationSec: (completed - tsMs) / 1e3 } : {}
5573
6167
  });
@@ -5650,14 +6244,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
5650
6244
 
5651
6245
  // src/harness/opencode/scan.ts
5652
6246
  import { readFileSync as readFileSync7 } from "node:fs";
5653
- import { readdir as readdir4, stat as stat4 } from "node:fs/promises";
5654
- import { homedir as homedir8 } from "node:os";
5655
- import path4 from "node:path";
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";
5656
6250
  var OPENCODE_MIGRATION_CEILING = 20260622202450;
5657
6251
  function opencodeDataDirs() {
5658
6252
  const xdg = process.env.XDG_DATA_HOME;
5659
- const base = xdg || path4.join(homedir8(), ".local", "share");
5660
- return [path4.join(base, "opencode")];
6253
+ const base = xdg || path5.join(homedir9(), ".local", "share");
6254
+ return [path5.join(base, "opencode")];
5661
6255
  }
5662
6256
  function isStoreFile(basename2) {
5663
6257
  return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
@@ -5666,8 +6260,8 @@ async function dbFilesIn(root) {
5666
6260
  const override = process.env.OPENCODE_DB;
5667
6261
  if (override) return [override];
5668
6262
  try {
5669
- const entries = await readdir4(root, { withFileTypes: true });
5670
- return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path4.join(root, e.name)).sort();
6263
+ 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();
5671
6265
  } catch {
5672
6266
  return [];
5673
6267
  }
@@ -5687,7 +6281,7 @@ function errorClass2(e) {
5687
6281
  return e instanceof Error ? e.constructor.name : "unknown";
5688
6282
  }
5689
6283
  var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
5690
- async function scan3(agg, opts = {}) {
6284
+ async function scan4(agg, opts = {}) {
5691
6285
  const stats = emptyScanStats();
5692
6286
  const open2 = await loadSqlite();
5693
6287
  const sinceMs = opts.sinceMs ?? 0;
@@ -5704,7 +6298,7 @@ async function scan3(agg, opts = {}) {
5704
6298
  } catch (e) {
5705
6299
  stats.filesUnreadable++;
5706
6300
  stats.unreadableFiles.push({
5707
- path: path4.basename(file),
6301
+ path: path5.basename(file),
5708
6302
  reason: errorClass2(e)
5709
6303
  });
5710
6304
  }
@@ -5871,8 +6465,8 @@ function pickTs(jsonTs, columnTs) {
5871
6465
  }
5872
6466
  function opencodeConfigFile() {
5873
6467
  const xdg = process.env.XDG_CONFIG_HOME;
5874
- const base = xdg || path4.join(homedir8(), ".config");
5875
- return path4.join(base, "opencode", "opencode.json");
6468
+ const base = xdg || path5.join(homedir9(), ".config");
6469
+ return path5.join(base, "opencode", "opencode.json");
5876
6470
  }
5877
6471
  function readConfiguredMcpServers2(agg, state, configFile) {
5878
6472
  const file = configFile ?? opencodeConfigFile();
@@ -5946,7 +6540,7 @@ async function detectOpencode(opts) {
5946
6540
  }
5947
6541
  async function exists3(p8) {
5948
6542
  try {
5949
- await stat4(p8);
6543
+ await stat5(p8);
5950
6544
  return true;
5951
6545
  } catch {
5952
6546
  return false;
@@ -5965,8 +6559,8 @@ var opencodeAdapter = {
5965
6559
  });
5966
6560
  },
5967
6561
  async scan(opts) {
5968
- const aggregate = createAggregate4();
5969
- const stats = await scan3(aggregate, {
6562
+ const aggregate = createAggregate5();
6563
+ const stats = await scan4(aggregate, {
5970
6564
  sinceMs: opts.sinceMs,
5971
6565
  ...opts.onProgress ? { onProgress: opts.onProgress } : {}
5972
6566
  });
@@ -5980,7 +6574,7 @@ var opencodeAdapter = {
5980
6574
  };
5981
6575
 
5982
6576
  // src/harness/pi/analyzer.ts
5983
- function createAggregate5() {
6577
+ function createAggregate6() {
5984
6578
  const workflowLocal = createWorkflowLocalSources();
5985
6579
  return Object.assign(createAggregate(), {
5986
6580
  workflow: createHarnessWorkflowReducer("pi-mono", workflowLocal),
@@ -6049,7 +6643,7 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
6049
6643
  if (outcome !== "duplicate") {
6050
6644
  if (state.sessionId && tsMs !== null) {
6051
6645
  const usage = asObj(message.usage);
6052
- const counts = readCounts2(message.usage);
6646
+ const counts2 = readCounts2(message.usage);
6053
6647
  agg.workflow.ingest({
6054
6648
  type: "response",
6055
6649
  session: state.sessionId,
@@ -6058,8 +6652,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
6058
6652
  tsMs,
6059
6653
  ...state.modelKey ? { model: state.modelKey } : {},
6060
6654
  thinkingTokens: usage ? asNum(usage.reasoning) : 0,
6061
- responseTokens: counts?.output ?? 0,
6062
- routingTokens: counts ? countsTotal(counts) : 0
6655
+ responseTokens: counts2?.output ?? 0,
6656
+ routingTokens: counts2 ? countsTotal(counts2) : 0
6063
6657
  });
6064
6658
  ingestContent(agg, message.content, state.sessionId, state.cwd, tsMs);
6065
6659
  agg.workflow.ingest({
@@ -6086,9 +6680,9 @@ function noteActivity2(agg, state, tsMs) {
6086
6680
  agg.projectDirs.add(state.cwd ?? "(unknown)");
6087
6681
  }
6088
6682
  function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable = true) {
6089
- const counts = readCounts2(usageRaw);
6090
- if (!counts) return "none";
6091
- const total = countsTotal(counts);
6683
+ const counts2 = readCounts2(usageRaw);
6684
+ if (!counts2) return "none";
6685
+ const total = countsTotal(counts2);
6092
6686
  if (total === 0) return "none";
6093
6687
  const id = asStr(rec.id);
6094
6688
  if (id) {
@@ -6107,8 +6701,8 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
6107
6701
  addModelUsage(
6108
6702
  agg,
6109
6703
  key2,
6110
- counts,
6111
- priceable ? apiEquivalentCost(key2, counts, tsMs) : null,
6704
+ counts2,
6705
+ priceable ? apiEquivalentCost(key2, counts2, tsMs) : null,
6112
6706
  1,
6113
6707
  { tsMs }
6114
6708
  );
@@ -6173,18 +6767,18 @@ function readCounts2(usageRaw) {
6173
6767
  }
6174
6768
 
6175
6769
  // src/harness/pi/scan.ts
6176
- import { createReadStream as createReadStream2 } from "node:fs";
6177
- import { readdir as readdir5, realpath as realpath3, stat as stat5 } from "node:fs/promises";
6178
- import { homedir as homedir9 } from "node:os";
6179
- import path5 from "node:path";
6180
- import readline2 from "node:readline";
6770
+ 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";
6774
+ import readline3 from "node:readline";
6181
6775
  var MAX_SESSION_VERSION = 3;
6182
6776
  function piAgentDir() {
6183
- return process.env.PI_CODING_AGENT_DIR || path5.join(homedir9(), ".pi", "agent");
6777
+ return process.env.PI_CODING_AGENT_DIR || path6.join(homedir10(), ".pi", "agent");
6184
6778
  }
6185
- function sessionRoots() {
6779
+ function sessionRoots2() {
6186
6780
  const override = process.env.PI_CODING_AGENT_SESSION_DIR;
6187
- return [override || path5.join(piAgentDir(), "sessions")];
6781
+ return [override || path6.join(piAgentDir(), "sessions")];
6188
6782
  }
6189
6783
  var SESSION_FILE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
6190
6784
  function isSessionFile(basename2) {
@@ -6193,21 +6787,21 @@ function isSessionFile(basename2) {
6193
6787
  async function* walkSessions(dir) {
6194
6788
  let entries;
6195
6789
  try {
6196
- entries = await readdir5(dir, { withFileTypes: true });
6790
+ entries = await readdir6(dir, { withFileTypes: true });
6197
6791
  } catch {
6198
6792
  return;
6199
6793
  }
6200
6794
  for (const e of entries) {
6201
- const full = path5.join(dir, e.name);
6795
+ const full = path6.join(dir, e.name);
6202
6796
  if (e.isDirectory()) yield* walkSessions(full);
6203
6797
  else if (e.isFile() && isSessionFile(e.name)) yield full;
6204
6798
  }
6205
6799
  }
6206
- async function scan4(agg, opts = {}) {
6800
+ async function scan5(agg, opts = {}) {
6207
6801
  const stats = emptyScanStats();
6208
6802
  const visited = /* @__PURE__ */ new Set();
6209
6803
  const fold = createFoldState();
6210
- for (const root of opts.roots ?? sessionRoots()) {
6804
+ for (const root of opts.roots ?? sessionRoots2()) {
6211
6805
  if (!await exists4(root)) continue;
6212
6806
  for await (const file of walkSessions(root)) {
6213
6807
  stats.filesFound++;
@@ -6224,7 +6818,7 @@ async function scan4(agg, opts = {}) {
6224
6818
  visited.add(resolved);
6225
6819
  if (opts.sinceMs !== void 0) {
6226
6820
  try {
6227
- const st = await stat5(file);
6821
+ const st = await stat6(file);
6228
6822
  if (st.mtimeMs < opts.sinceMs) {
6229
6823
  stats.filesSkippedByMtime++;
6230
6824
  continue;
@@ -6246,7 +6840,7 @@ async function scan4(agg, opts = {}) {
6246
6840
  stats.filesUnreadable++;
6247
6841
  stats.filesRead--;
6248
6842
  stats.unreadableFiles.push({
6249
- path: path5.relative(root, file),
6843
+ path: path6.relative(root, file),
6250
6844
  reason: "version-too-new"
6251
6845
  });
6252
6846
  }
@@ -6254,7 +6848,7 @@ async function scan4(agg, opts = {}) {
6254
6848
  stats.filesUnreadable++;
6255
6849
  stats.filesRead--;
6256
6850
  stats.unreadableFiles.push({
6257
- path: path5.relative(root, file),
6851
+ path: path6.relative(root, file),
6258
6852
  reason: "read-error"
6259
6853
  });
6260
6854
  }
@@ -6264,15 +6858,15 @@ async function scan4(agg, opts = {}) {
6264
6858
  }
6265
6859
  async function exists4(p8) {
6266
6860
  try {
6267
- await stat5(p8);
6861
+ await stat6(p8);
6268
6862
  return true;
6269
6863
  } catch {
6270
6864
  return false;
6271
6865
  }
6272
6866
  }
6273
6867
  async function ingestFile3(agg, fold, file, sinceMs) {
6274
- const rl = readline2.createInterface({
6275
- input: createReadStream2(file, { encoding: "utf8" }),
6868
+ const rl = readline3.createInterface({
6869
+ input: createReadStream3(file, { encoding: "utf8" }),
6276
6870
  crlfDelay: Number.POSITIVE_INFINITY
6277
6871
  });
6278
6872
  const state = createFileState2();
@@ -6325,14 +6919,14 @@ var piAdapter = {
6325
6919
  builtinTools: PI_BUILTIN_TOOLS,
6326
6920
  async detect(opts) {
6327
6921
  return hasRecentFile(
6328
- opts.roots ?? sessionRoots(),
6922
+ opts.roots ?? sessionRoots2(),
6329
6923
  isSessionFile,
6330
6924
  opts.sinceMs
6331
6925
  );
6332
6926
  },
6333
6927
  async scan(opts) {
6334
- const aggregate = createAggregate5();
6335
- const stats = await scan4(aggregate, {
6928
+ const aggregate = createAggregate6();
6929
+ const stats = await scan5(aggregate, {
6336
6930
  sinceMs: opts.sinceMs,
6337
6931
  ...opts.onProgress ? { onProgress: opts.onProgress } : {}
6338
6932
  });
@@ -6349,12 +6943,14 @@ var piAdapter = {
6349
6943
  var HARNESS_ADAPTERS = [
6350
6944
  claudeAdapter,
6351
6945
  codexAdapter,
6946
+ grokAdapter,
6352
6947
  opencodeAdapter,
6353
6948
  piAdapter
6354
6949
  ];
6355
6950
  function harnessLabel2(name) {
6356
6951
  if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
6357
6952
  if (name === CODEX_HARNESS_NAME) return "Codex";
6953
+ if (name === GROK_HARNESS_NAME) return "Grok Build";
6358
6954
  if (name === OPENCODE_HARNESS_NAME) return "opencode";
6359
6955
  if (name === PI_HARNESS_NAME) return "Pi";
6360
6956
  return name;
@@ -6388,7 +6984,7 @@ var MCP_ADD_ARGS = [
6388
6984
  "mcp"
6389
6985
  ];
6390
6986
  var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
6391
- var SKILL_DEST = join6(homedir10(), ".claude", "skills", "aistack-sync");
6987
+ var SKILL_DEST = join6(homedir11(), ".claude", "skills", "aistack-sync");
6392
6988
  function runClaude(args) {
6393
6989
  const r = spawnSync("claude", args, { encoding: "utf-8" });
6394
6990
  const notFound = r.error !== void 0 && r.error.code === "ENOENT";
@@ -6621,9 +7217,9 @@ async function createCommand() {
6621
7217
  import { hostname } from "node:os";
6622
7218
  import * as p5 from "@clack/prompts";
6623
7219
  import open from "open";
6624
- function proposedMachineName(read = hostname) {
7220
+ function proposedMachineName(read2 = hostname) {
6625
7221
  try {
6626
- const name = read().trim().replace(/\.local$/i, "");
7222
+ const name = read2().trim().replace(/\.local$/i, "");
6627
7223
  if (!name || name.length > 64) return void 0;
6628
7224
  return name;
6629
7225
  } catch {
@@ -6712,11 +7308,11 @@ import * as p7 from "@clack/prompts";
6712
7308
  // src/autosync/codexHook.ts
6713
7309
  import { createHash } from "node:crypto";
6714
7310
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
6715
- import { homedir as homedir11 } from "node:os";
7311
+ import { homedir as homedir12 } from "node:os";
6716
7312
  import { dirname as dirname5, join as join8 } from "node:path";
6717
7313
  import { parse } from "smol-toml";
6718
7314
  function codexHome2() {
6719
- return process.env.CODEX_HOME || join8(homedir11(), ".codex");
7315
+ return process.env.CODEX_HOME || join8(homedir12(), ".codex");
6720
7316
  }
6721
7317
  function codexHooksFile() {
6722
7318
  return join8(codexHome2(), "hooks.json");
@@ -6742,9 +7338,9 @@ function readHooksJson(file) {
6742
7338
  }
6743
7339
  var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
6744
7340
  function installCodexAutoSyncHook(file = codexHooksFile()) {
6745
- const read = readHooksJson(file);
6746
- if ("error" in read) return { ok: false, message: read.error };
6747
- const settings = read.settings;
7341
+ const read2 = readHooksJson(file);
7342
+ if ("error" in read2) return { ok: false, message: read2.error };
7343
+ const settings = read2.settings;
6748
7344
  const hooks = settings.hooks ?? {};
6749
7345
  const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
6750
7346
  const kept = sessionStart.map((m) => ({
@@ -6764,9 +7360,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
6764
7360
  function removeCodexAutoSyncHook(file = codexHooksFile()) {
6765
7361
  if (!existsSync8(file))
6766
7362
  return { ok: true, message: "no Codex hook to remove" };
6767
- const read = readHooksJson(file);
6768
- if ("error" in read) return { ok: false, message: read.error };
6769
- const settings = read.settings;
7363
+ const read2 = readHooksJson(file);
7364
+ if ("error" in read2) return { ok: false, message: read2.error };
7365
+ const settings = read2.settings;
6770
7366
  const sessionStart = settings.hooks?.SessionStart;
6771
7367
  if (!Array.isArray(sessionStart)) {
6772
7368
  return { ok: true, message: "no Codex hook to remove" };
@@ -6791,18 +7387,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
6791
7387
  return { ok: true, message: `hook removed from ${file}` };
6792
7388
  }
6793
7389
  function codexAutoSyncHookInstalled(file = codexHooksFile()) {
6794
- const read = readHooksJson(file);
6795
- if ("error" in read) return false;
6796
- const sessionStart = read.settings.hooks?.SessionStart;
7390
+ const read2 = readHooksJson(file);
7391
+ if ("error" in read2) return false;
7392
+ const sessionStart = read2.settings.hooks?.SessionStart;
6797
7393
  if (!Array.isArray(sessionStart)) return false;
6798
7394
  return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
6799
7395
  }
6800
7396
  function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
6801
7397
  try {
6802
7398
  const config = parse(readFileSync9(configFile, "utf-8"));
6803
- const read = readHooksJson(hooksFile);
6804
- if ("error" in read) return null;
6805
- const sessionStart = read.settings.hooks?.SessionStart;
7399
+ const read2 = readHooksJson(hooksFile);
7400
+ if ("error" in read2) return null;
7401
+ const sessionStart = read2.settings.hooks?.SessionStart;
6806
7402
  if (!Array.isArray(sessionStart)) return false;
6807
7403
  const matches = [];
6808
7404
  for (const [groupIndex, group] of sessionStart.entries()) {
@@ -6855,19 +7451,131 @@ function canonicalJson2(value) {
6855
7451
  // src/autosync/optin.ts
6856
7452
  import * as p6 from "@clack/prompts";
6857
7453
 
6858
- // src/autosync/hook.ts
6859
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
6860
- import { homedir as homedir12 } from "node:os";
7454
+ // src/autosync/grokHook.ts
7455
+ import {
7456
+ existsSync as existsSync9,
7457
+ mkdirSync as mkdirSync4,
7458
+ readFileSync as readFileSync10,
7459
+ unlinkSync,
7460
+ writeFileSync as writeFileSync4
7461
+ } from "node:fs";
7462
+ import { homedir as homedir13, platform as platform2 } from "node:os";
6861
7463
  import { dirname as dirname6, join as join9 } from "node:path";
6862
- var CLAUDE_SETTINGS_FILE = join9(homedir12(), ".claude", "settings.json");
6863
- var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
7464
+ var GROK_HOOK_FILE = join9(
7465
+ process.env.GROK_HOME || join9(homedir13(), ".grok"),
7466
+ "hooks",
7467
+ "aistack.json"
7468
+ );
7469
+ 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()) {
7471
+ if (os === "win32") {
7472
+ return `Start-Process -WindowStyle Hidden -FilePath "cmd.exe" -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=grok&& (${SYNC_COMMAND}) >NUL 2>&1"'`;
7473
+ }
7474
+ if (os === "darwin") {
7475
+ return `nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' </dev/null >/dev/null 2>&1 &`;
7476
+ }
7477
+ return `setsid nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' >/dev/null 2>&1 &`;
7478
+ }
7479
+ function readHookFile(file) {
7480
+ if (!existsSync9(file)) return { value: {} };
7481
+ try {
7482
+ const value = JSON.parse(readFileSync10(file, "utf-8"));
7483
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7484
+ const candidate = value;
7485
+ if (candidate.hooks !== void 0 && (!candidate.hooks || typeof candidate.hooks !== "object" || Array.isArray(candidate.hooks) || Object.values(candidate.hooks).some(
7486
+ (groups) => !Array.isArray(groups)
7487
+ ))) {
7488
+ return {
7489
+ error: `${file} has a malformed hooks object. Fix it, then retry.`
7490
+ };
7491
+ }
7492
+ return { value: candidate };
7493
+ }
7494
+ } catch {
7495
+ return { error: `${file} is not valid JSON. Fix it, then retry.` };
7496
+ }
7497
+ return { error: `${file} does not hold a JSON object` };
7498
+ }
6864
7499
  function isOurs2(entry) {
6865
7500
  return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
6866
7501
  }
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(
7507
+ (key2) => key2 !== "SessionStart"
7508
+ );
7509
+ const sessionStart = read2.value.hooks?.SessionStart ?? [];
7510
+ const foreignHandlers = sessionStart.flatMap(
7511
+ (group) => (group.hooks ?? []).filter((entry) => !isOurs2(entry))
7512
+ );
7513
+ if (foreignKeys.length > 0 || foreignEvents.length > 0 || foreignHandlers.length > 0) {
7514
+ return {
7515
+ ok: false,
7516
+ message: `${file} contains hooks not owned by AI Stack. Move them to another Grok hook file, then retry.`
7517
+ };
7518
+ }
7519
+ const value = {
7520
+ hooks: {
7521
+ SessionStart: [
7522
+ {
7523
+ hooks: [
7524
+ { type: "command", command: grokHookCommand(os), timeout: 5 }
7525
+ ]
7526
+ }
7527
+ ]
7528
+ }
7529
+ };
7530
+ mkdirSync4(dirname6(file), { recursive: true });
7531
+ writeFileSync4(file, `${JSON.stringify(value, null, 2)}
7532
+ `);
7533
+ return {
7534
+ ok: true,
7535
+ message: `Grok Build SessionStart hook written to ${file}. Start a new Grok session or reload hooks before expecting it to run.`
7536
+ };
7537
+ }
7538
+ function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
7539
+ if (!existsSync9(file))
7540
+ 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(
7545
+ (key2) => key2 === "SessionStart"
7546
+ ) && entries.every(
7547
+ (group) => (group.hooks ?? []).every((entry) => isOurs2(entry))
7548
+ );
7549
+ if (!onlyOurs) {
7550
+ return {
7551
+ ok: false,
7552
+ message: `${file} contains hooks not owned by AI Stack and was not removed.`
7553
+ };
7554
+ }
7555
+ unlinkSync(file);
7556
+ return { ok: true, message: `Grok Build hook removed from ${file}` };
7557
+ }
7558
+ 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))
7563
+ );
7564
+ }
7565
+
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
+ }
6867
7575
  function readClaudeSettings(file) {
6868
- if (!existsSync9(file)) return { settings: {} };
7576
+ if (!existsSync10(file)) return { settings: {} };
6869
7577
  try {
6870
- const raw = JSON.parse(readFileSync10(file, "utf-8"));
7578
+ const raw = JSON.parse(readFileSync11(file, "utf-8"));
6871
7579
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
6872
7580
  return { settings: raw };
6873
7581
  }
@@ -6877,36 +7585,36 @@ function readClaudeSettings(file) {
6877
7585
  }
6878
7586
  }
6879
7587
  function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6880
- const read = readClaudeSettings(file);
6881
- if ("error" in read) return { ok: false, message: read.error };
6882
- const settings = read.settings;
7588
+ const read2 = readClaudeSettings(file);
7589
+ if ("error" in read2) return { ok: false, message: read2.error };
7590
+ const settings = read2.settings;
6883
7591
  const hooks = settings.hooks ?? {};
6884
7592
  const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
6885
7593
  const kept = sessionStart.map((m) => ({
6886
7594
  ...m,
6887
- hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
7595
+ hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
6888
7596
  })).filter((m) => (m.hooks?.length ?? 0) > 0);
6889
7597
  kept.push({
6890
7598
  hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
6891
7599
  });
6892
7600
  settings.hooks = { ...hooks, SessionStart: kept };
6893
- mkdirSync4(dirname6(file), { recursive: true });
6894
- writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
7601
+ mkdirSync5(dirname7(file), { recursive: true });
7602
+ writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
6895
7603
  `);
6896
7604
  return { ok: true, message: `SessionStart hook written to ${file}` };
6897
7605
  }
6898
7606
  function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6899
- if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
6900
- const read = readClaudeSettings(file);
6901
- if ("error" in read) return { ok: false, message: read.error };
6902
- const settings = read.settings;
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;
6903
7611
  const sessionStart = settings.hooks?.SessionStart;
6904
7612
  if (!Array.isArray(sessionStart)) {
6905
7613
  return { ok: true, message: "no hook to remove" };
6906
7614
  }
6907
7615
  const kept = sessionStart.map((m) => ({
6908
7616
  ...m,
6909
- hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
7617
+ hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
6910
7618
  })).filter((m) => (m.hooks?.length ?? 0) > 0);
6911
7619
  const hooks = { ...settings.hooks };
6912
7620
  if (kept.length > 0) {
@@ -6919,21 +7627,21 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6919
7627
  } else {
6920
7628
  delete settings.hooks;
6921
7629
  }
6922
- writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
7630
+ writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
6923
7631
  `);
6924
7632
  return { ok: true, message: `hook removed from ${file}` };
6925
7633
  }
6926
7634
  function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
6927
- const read = readClaudeSettings(file);
6928
- if ("error" in read) return false;
6929
- const sessionStart = read.settings.hooks?.SessionStart;
7635
+ const read2 = readClaudeSettings(file);
7636
+ if ("error" in read2) return false;
7637
+ const sessionStart = read2.settings.hooks?.SessionStart;
6930
7638
  if (!Array.isArray(sessionStart)) return false;
6931
- return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs2(h)));
7639
+ return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs3(h)));
6932
7640
  }
6933
7641
 
6934
7642
  // src/autosync/optin.ts
6935
7643
  var NOT_LINKED = "This machine is not linked to an aistack account, and the auto-sync permission lives on your stack. Run `npx @use-aistack/cli sync` first.";
6936
- var NOTHING_TO_TRIGGER = `No Claude Code or Codex session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
7644
+ var NOTHING_TO_TRIGGER = `No supported session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
6937
7645
  async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
6938
7646
  frequencyHours = normalizeFrequencyHours(frequencyHours);
6939
7647
  const detected = await (deps.detectedImpl ?? detectedAdapters)();
@@ -6966,6 +7674,10 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
6966
7674
  trustLine = codexResult.message;
6967
7675
  }
6968
7676
  }
7677
+ if (names.has(GROK_HARNESS_NAME)) {
7678
+ const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
7679
+ if (!grokResult.ok) return grokResult;
7680
+ }
6969
7681
  saveSettings(
6970
7682
  {
6971
7683
  autoSyncAnswered: true,
@@ -6997,7 +7709,8 @@ async function disableAutoSync(deps = {}) {
6997
7709
  );
6998
7710
  const result = (deps.removeHook ?? removeAutoSyncHook)();
6999
7711
  const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
7000
- const failures = [result, codexResult].filter((r) => !r.ok).map((r) => r.message);
7712
+ const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
7713
+ const failures = [result, codexResult, grokResult].filter((r) => !r.ok).map((r) => r.message);
7001
7714
  const token = (deps.getTokenImpl ?? getToken)();
7002
7715
  if (token !== null) {
7003
7716
  try {
@@ -7020,7 +7733,40 @@ async function disableAutoSync(deps = {}) {
7020
7733
  };
7021
7734
  }
7022
7735
  async function reconcileAutoSync(permission, deps = {}) {
7023
- if (permission?.enabled !== true) return null;
7736
+ if (permission === null) return null;
7737
+ if (permission.enabled !== true) {
7738
+ const settings = getSettings(deps.settingsFile);
7739
+ saveSettings(
7740
+ {
7741
+ autoSyncAnswered: true,
7742
+ autoSync: {
7743
+ enabled: false,
7744
+ frequencyHours: normalizeFrequencyHours(
7745
+ permission.frequencyHours ?? settings.autoSync?.frequencyHours
7746
+ )
7747
+ }
7748
+ },
7749
+ deps.settingsFile
7750
+ );
7751
+ const results = [
7752
+ (deps.removeHook ?? removeAutoSyncHook)(),
7753
+ (deps.removeCodexHook ?? removeCodexAutoSyncHook)(),
7754
+ (deps.removeGrokHook ?? removeGrokAutoSyncHook)()
7755
+ ];
7756
+ const failures2 = results.filter((result) => !result.ok);
7757
+ if (failures2.length > 0) {
7758
+ return {
7759
+ ok: false,
7760
+ message: `Auto-sync is off on this machine, but a trigger could not be removed: ${failures2.map((result) => result.message).join("; ")}. The next interactive sync will retry.`
7761
+ };
7762
+ }
7763
+ const changed = settings.autoSync?.enabled !== false || results.some((result) => !result.message.toLowerCase().startsWith("no "));
7764
+ if (!changed) return null;
7765
+ return {
7766
+ ok: true,
7767
+ message: "Auto-sync is off on this machine. Removed its local triggers."
7768
+ };
7769
+ }
7024
7770
  const detected = await (deps.detectedImpl ?? detectedAdapters)();
7025
7771
  if (detected.length === 0) return null;
7026
7772
  const names = new Set(detected.map((a) => a.name));
@@ -7042,6 +7788,11 @@ async function reconcileAutoSync(permission, deps = {}) {
7042
7788
  deps.codexHookInstalledImpl ?? codexAutoSyncHookInstalled,
7043
7789
  deps.installCodexHook ?? installCodexAutoSyncHook
7044
7790
  );
7791
+ install(
7792
+ GROK_HARNESS_NAME,
7793
+ deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
7794
+ deps.installGrokHook ?? installGrokAutoSyncHook
7795
+ );
7045
7796
  if (failures.length > 0) {
7046
7797
  return {
7047
7798
  ok: false,
@@ -7126,15 +7877,18 @@ async function offerAutoSyncOptIn(deps = {}) {
7126
7877
  // src/autosync/run.ts
7127
7878
  import {
7128
7879
  appendFileSync,
7129
- mkdirSync as mkdirSync5,
7130
- readFileSync as readFileSync11,
7131
- writeFileSync as writeFileSync5
7880
+ closeSync,
7881
+ mkdirSync as mkdirSync7,
7882
+ openSync,
7883
+ readFileSync as readFileSync13,
7884
+ unlinkSync as unlinkSync2,
7885
+ writeFileSync as writeFileSync7
7132
7886
  } from "node:fs";
7133
- import { homedir as homedir13 } from "node:os";
7134
- import { dirname as dirname7, join as join10 } from "node:path";
7887
+ import { homedir as homedir16 } from "node:os";
7888
+ import { dirname as dirname8, join as join11 } from "node:path";
7135
7889
 
7136
7890
  // src/sync/stage.ts
7137
- import { createHash as createHash2 } from "node:crypto";
7891
+ import { createHash as createHash3 } from "node:crypto";
7138
7892
 
7139
7893
  // src/usage/days.ts
7140
7894
  var round6 = (n) => Math.round(n * 1e6) / 1e6;
@@ -7227,7 +7981,13 @@ function mergeUsageDays(perHarness) {
7227
7981
  function buildMeasuredDays(input) {
7228
7982
  const workflowByDate = /* @__PURE__ */ new Map();
7229
7983
  for (const day of input.workflow ?? []) workflowByDate.set(day.date, day);
7230
- const dates = [.../* @__PURE__ */ new Set([...input.usage.keys(), ...workflowByDate.keys()])].filter(
7984
+ const dates = [
7985
+ .../* @__PURE__ */ new Set([
7986
+ ...input.usage.keys(),
7987
+ ...workflowByDate.keys(),
7988
+ ...input.includeDates ?? []
7989
+ ])
7990
+ ].filter(
7231
7991
  (d) => /^\d{4}-\d{2}-\d{2}$/.test(d) && d >= input.from && d <= input.to
7232
7992
  ).sort();
7233
7993
  return dates.map((date) => {
@@ -7284,7 +8044,7 @@ function selectDaysToPublish(input) {
7284
8044
 
7285
8045
  // src/workflow/git.ts
7286
8046
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
7287
- import path6 from "node:path";
8047
+ import path7 from "node:path";
7288
8048
  var TEST_FILE_RULE_VERSION = "test-files/v2";
7289
8049
  var FILE_TYPE_RULE_VERSION = "file-types/v2";
7290
8050
  var COMMIT_SET_RULE_VERSION = "commit-set/v1";
@@ -7567,9 +8327,9 @@ function reduceGitHistories(histories, options) {
7567
8327
  if (included) seenCommits.add(hash);
7568
8328
  continue;
7569
8329
  }
7570
- const stat6 = parseNumstat(field);
7571
- if (!stat6) continue;
7572
- let file = stat6.file;
8330
+ const stat7 = parseNumstat(field);
8331
+ if (!stat7) continue;
8332
+ let file = stat7.file;
7573
8333
  if (file.length === 0) {
7574
8334
  fieldIndex += 2;
7575
8335
  file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
@@ -7577,13 +8337,13 @@ function reduceGitHistories(histories, options) {
7577
8337
  if (!current?.included) continue;
7578
8338
  if (isUnauthoredPath(file)) continue;
7579
8339
  current.authored = true;
7580
- const fileChangedLines = stat6.additions + stat6.removals;
7581
- current.additions += stat6.additions;
7582
- current.removals += stat6.removals;
8340
+ const fileChangedLines = stat7.additions + stat7.removals;
8341
+ current.additions += stat7.additions;
8342
+ current.removals += stat7.removals;
7583
8343
  current.changedLines += fileChangedLines;
7584
8344
  if (isTestFile(file)) current.touchesTest = true;
7585
8345
  if (fileChangedLines <= 0) continue;
7586
- const extension = path6.extname(file).toLowerCase();
8346
+ const extension = path7.extname(file).toLowerCase();
7587
8347
  if (APPROVED_EXTENSIONS.has(extension)) {
7588
8348
  current.extensionLines.set(
7589
8349
  extension,
@@ -7686,6 +8446,47 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
7686
8446
  };
7687
8447
  }
7688
8448
 
8449
+ // 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(),
8456
+ ".config",
8457
+ "aistack",
8458
+ "grok-session-dates.json"
8459
+ );
8460
+ function grokCacheScope(baseUrl, stack, token) {
8461
+ return createHash2("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
8462
+ }
8463
+ function read(file) {
8464
+ if (!existsSync11(file)) return {};
8465
+ try {
8466
+ const value = JSON.parse(readFileSync12(file, "utf8"));
8467
+ return value && typeof value === "object" ? value : {};
8468
+ } catch {
8469
+ return {};
8470
+ }
8471
+ }
8472
+ function loadGrokDateHints(scope, file = defaultFile) {
8473
+ return read(file)[scope] ?? {};
8474
+ }
8475
+ function saveGrokDateHints(scope, hints, file = defaultFile) {
8476
+ const cache = read(file);
8477
+ cache[scope] = hints;
8478
+ mkdirSync6(path8.dirname(file), { recursive: true });
8479
+ writeFileSync6(file, JSON.stringify(cache, null, 2));
8480
+ }
8481
+ function mapToHints(value, floor) {
8482
+ return Object.fromEntries(
8483
+ [...value].map(([id, dates]) => [
8484
+ id,
8485
+ [...dates].filter((d) => d >= floor).sort()
8486
+ ])
8487
+ );
8488
+ }
8489
+
7689
8490
  // src/sync/summary.ts
7690
8491
  function fmtTokens(n) {
7691
8492
  const sig = (v) => {
@@ -8086,7 +8887,7 @@ function buildGateSummary(ctx) {
8086
8887
  // src/sync/stage.ts
8087
8888
  var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
8088
8889
  function stageId(bodyJson) {
8089
- return createHash2("sha256").update(bodyJson).digest("hex").slice(0, 12);
8890
+ return createHash3("sha256").update(bodyJson).digest("hex").slice(0, 12);
8090
8891
  }
8091
8892
  async function stageSync(deps) {
8092
8893
  const now = (deps.now ?? Date.now)();
@@ -8138,6 +8939,9 @@ async function stageSync(deps) {
8138
8939
  const sinceMs = windowStartMs(now, windowDays);
8139
8940
  const daysSinceMs = windowStartMs(now, retentionDays);
8140
8941
  const active2 = await adapters(sinceMs);
8942
+ const historical = await adapters(daysSinceMs);
8943
+ let dayScansComplete = true;
8944
+ let grokCurrentDates = null;
8141
8945
  for (const adapter of active2) {
8142
8946
  progress(`Scanning recent ${adapter.name} usage`);
8143
8947
  const { aggregate, stats } = await adapter.scan({
@@ -8158,12 +8962,15 @@ async function stageSync(deps) {
8158
8962
  })
8159
8963
  );
8160
8964
  }
8161
- for (const adapter of active2) {
8965
+ for (const adapter of historical) {
8162
8966
  progress(`Reading historical ${adapter.name} days`);
8163
- const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
8967
+ const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
8164
8968
  sinceMs: daysSinceMs,
8165
8969
  onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
8166
8970
  });
8971
+ if (scanComplete === false) dayScansComplete = false;
8972
+ if (adapter.name === "grok-build")
8973
+ grokCurrentDates = sessionDates ?? /* @__PURE__ */ new Map();
8167
8974
  workflowScans.push({ aggregate: workflow2, local: workflowLocal });
8168
8975
  usageScans.push(
8169
8976
  buildUsageDays({
@@ -8189,17 +8996,33 @@ async function stageSync(deps) {
8189
8996
  toMs: now
8190
8997
  });
8191
8998
  }
8999
+ const correctionDates = /* @__PURE__ */ new Set();
9000
+ let acknowledgePublish;
9001
+ if (grokCurrentDates && token && config.stack) {
9002
+ const scope = grokCacheScope(deps.baseUrl, config.stack.slug, token);
9003
+ const floor = utcDate2(daysSinceMs);
9004
+ const previous = loadGrokDateHints(scope);
9005
+ const current = mapToHints(grokCurrentDates, floor);
9006
+ for (const dates of Object.values(previous))
9007
+ for (const date of dates) correctionDates.add(date);
9008
+ for (const dates of Object.values(current))
9009
+ for (const date of dates) correctionDates.add(date);
9010
+ if (Object.keys(previous).some((id) => !(id in current)))
9011
+ dayScansComplete = false;
9012
+ acknowledgePublish = () => saveGrokDateHints(scope, current);
9013
+ }
8192
9014
  const localDays = applyDayConsent(
8193
9015
  buildMeasuredDays({
8194
9016
  usage: mergeUsageDays(usageScans),
8195
9017
  ...workflow ? { workflow: workflow.days } : {},
8196
9018
  from: utcDate2(daysSinceMs),
8197
- to: utcDate2(now)
9019
+ to: utcDate2(now),
9020
+ includeDates: correctionDates
8198
9021
  }),
8199
9022
  config
8200
9023
  );
8201
9024
  const days = selectDaysToPublish({
8202
- local: localDays,
9025
+ local: dayScansComplete ? localDays : [],
8203
9026
  manifest,
8204
9027
  todayUtc: utcDate2(now)
8205
9028
  });
@@ -8208,7 +9031,7 @@ async function stageSync(deps) {
8208
9031
  config,
8209
9032
  settings.autoSync,
8210
9033
  deps.trigger,
8211
- active2.length > 0 ? {
9034
+ historical.length > 0 && dayScansComplete ? {
8212
9035
  aggregateVersion: MEASURED_DAYS_V1,
8213
9036
  utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
8214
9037
  days: days.send
@@ -8232,8 +9055,8 @@ async function stageSync(deps) {
8232
9055
  width: process.stdout.columns
8233
9056
  };
8234
9057
  let blockedReason = null;
8235
- if (built.length === 0) {
8236
- blockedReason = `No active harness on this machine - no Claude Code and no Codex transcript from the last ${windowDays} days to read.`;
9058
+ if (historical.length === 0) {
9059
+ blockedReason = `No supported harness transcript from the last ${retentionDays} days to read.`;
8237
9060
  } else if (token === null) {
8238
9061
  blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
8239
9062
  } else if (config.stack === null) {
@@ -8251,25 +9074,47 @@ async function stageSync(deps) {
8251
9074
  stagedAt: now,
8252
9075
  blockedReason,
8253
9076
  days,
8254
- prices
9077
+ prices,
9078
+ ...acknowledgePublish ? { acknowledgePublish } : {}
8255
9079
  };
8256
9080
  }
8257
9081
 
8258
9082
  // src/autosync/run.ts
8259
- var SYNC_LOG_FILE = join10(homedir13(), ".config", "aistack", "sync.log");
9083
+ var SYNC_LOG_FILE = join11(homedir16(), ".config", "aistack", "sync.log");
8260
9084
  var SYNC_LOG_MAX_LINES = 200;
8261
9085
  var FIX_COMMAND = "npx @use-aistack/cli sync";
8262
9086
  var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
8263
9087
  function appendLogLine(file, line) {
8264
- mkdirSync5(dirname7(file), { recursive: true });
9088
+ mkdirSync7(dirname8(file), { recursive: true });
8265
9089
  appendFileSync(file, `${line}
8266
9090
  `);
8267
- const lines2 = readFileSync11(file, "utf-8").split("\n").filter(Boolean);
9091
+ const lines2 = readFileSync13(file, "utf-8").split("\n").filter(Boolean);
8268
9092
  if (lines2.length > SYNC_LOG_MAX_LINES) {
8269
- writeFileSync5(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
9093
+ writeFileSync7(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
8270
9094
  `);
8271
9095
  }
8272
9096
  }
9097
+ function reserveAttempt(file, now, windowMs) {
9098
+ mkdirSync7(dirname8(file), { recursive: true });
9099
+ for (; ; ) {
9100
+ try {
9101
+ const fd = openSync(file, "wx");
9102
+ writeFileSync7(fd, String(now));
9103
+ closeSync(fd);
9104
+ return true;
9105
+ } catch (error) {
9106
+ if (error.code !== "EEXIST") throw error;
9107
+ const held = Number(readFileSync13(file, "utf-8"));
9108
+ if (Number.isFinite(held) && now - held < windowMs) return false;
9109
+ try {
9110
+ unlinkSync2(file);
9111
+ } catch (unlinkError) {
9112
+ if (unlinkError.code !== "ENOENT")
9113
+ throw unlinkError;
9114
+ }
9115
+ }
9116
+ }
9117
+ }
8273
9118
  async function runAutoSync(deps) {
8274
9119
  const now = (deps.now ?? Date.now)();
8275
9120
  const settingsFile = deps.settingsFile;
@@ -8284,9 +9129,13 @@ async function runAutoSync(deps) {
8284
9129
  return;
8285
9130
  }
8286
9131
  const frequencyHours = normalizeFrequencyHours(config.frequencyHours);
9132
+ const windowMs = frequencyHours * 36e5;
8287
9133
  const state = settings.autoSyncState ?? {};
8288
9134
  const lastRunAt = state.lastRunAt ?? 0;
8289
- if (now - lastRunAt < frequencyHours * 36e5) return;
9135
+ if (now - lastRunAt < windowMs) return;
9136
+ const reservationFile = deps.reservationFile ?? `${settingsFile ?? join11(homedir16(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
9137
+ if (!reserveAttempt(reservationFile, now, windowMs)) return;
9138
+ saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
8290
9139
  const stage = deps.stageImpl ?? stageSync;
8291
9140
  const publish = deps.publishImpl ?? syncPublish;
8292
9141
  const loadConfig = deps.loadConfigImpl ?? loadSyncConfig;
@@ -8377,7 +9226,7 @@ async function runAutoSync(deps) {
8377
9226
  logFile,
8378
9227
  `${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
8379
9228
  );
8380
- if (shouldWarn) {
9229
+ if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok") {
8381
9230
  emit(
8382
9231
  JSON.stringify({
8383
9232
  systemMessage: `aistack auto-sync failed ${consecutiveFailures} times in a row (${failure2}). Run \`${FIX_COMMAND}\` in a terminal to fix it, or \`${FIX_COMMAND} --auto off\` to stop these runs.`
@@ -8522,6 +9371,7 @@ async function syncCommand(options = {}) {
8522
9371
  s.start("Publishing");
8523
9372
  try {
8524
9373
  const res = await syncPublish(staged.token, staged.bodyJson);
9374
+ staged.acknowledgePublish?.();
8525
9375
  s.stop("Published");
8526
9376
  const lines2 = [
8527
9377
  `Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
@@ -8752,6 +9602,7 @@ function createSyncServer(deps, send) {
8752
9602
  log7(`consent received, sending stage ${approvedStage.id}`);
8753
9603
  publish(approvedStage.token, approvedStage.bodyJson).then(
8754
9604
  (res) => {
9605
+ approvedStage.acknowledgePublish?.();
8755
9606
  if (staged?.id === approvedStage.id) staged = null;
8756
9607
  const lines2 = [
8757
9608
  `Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,