@use-aistack/cli 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -377,12 +377,13 @@ function apiEquivalentCost(modelKey, t, atMs) {
377
377
  }
378
378
 
379
379
  // src/version.ts
380
- var CLI_VERSION = true ? "0.15.0" : "0.0.0-dev";
380
+ var CLI_VERSION = true ? "0.16.1" : "0.0.0-dev";
381
381
 
382
382
  // src/api.ts
383
383
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
384
- async function request(path13, options = {}) {
385
- return fetch(`${BASE_URL}${path13}`, {
384
+ var STARTUP_READ_TIMEOUT_MS = 5e3;
385
+ async function request(path14, options = {}) {
386
+ return fetch(`${BASE_URL}${path14}`, {
386
387
  ...options,
387
388
  headers: {
388
389
  "Content-Type": "application/json",
@@ -501,6 +502,7 @@ async function syncPublish(token, bodyJson) {
501
502
  }
502
503
  async function fetchDayManifest(baseUrl, token) {
503
504
  const res = await fetch(`${baseUrl}/api/cli/sync-manifest`, {
505
+ signal: AbortSignal.timeout(STARTUP_READ_TIMEOUT_MS),
504
506
  headers: { "Content-Type": "application/json", ...authHeaders(token) }
505
507
  });
506
508
  if (res.status === 404) return null;
@@ -524,6 +526,7 @@ async function fetchDayManifest(baseUrl, token) {
524
526
  }
525
527
  async function fetchPriceTable(baseUrl) {
526
528
  const res = await fetch(`${baseUrl}/api/prices`, {
529
+ signal: AbortSignal.timeout(STARTUP_READ_TIMEOUT_MS),
527
530
  headers: { Accept: "application/json" }
528
531
  });
529
532
  if (res.status === 404) return null;
@@ -800,9 +803,9 @@ function canonicalizeRepoUrl(input) {
800
803
  function repoNameFromCanonical(canonical) {
801
804
  return parseRepo(canonical)?.repo ?? "";
802
805
  }
803
- function normalizeUpstreamPath(path13) {
804
- if (!path13) return "";
805
- return path13.split("/").filter(Boolean).join("/");
806
+ function normalizeUpstreamPath(path14) {
807
+ if (!path14) return "";
808
+ return path14.split("/").filter(Boolean).join("/");
806
809
  }
807
810
 
808
811
  // src/git.ts
@@ -848,16 +851,16 @@ function buildRepoLinkResource(canonical) {
848
851
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
849
852
  import { homedir as homedir2 } from "node:os";
850
853
  import { join as join2 } from "node:path";
851
- function readJson(path13) {
854
+ function readJson(path14) {
852
855
  try {
853
- if (!existsSync2(path13)) return null;
854
- return JSON.parse(readFileSync2(path13, "utf-8"));
856
+ if (!existsSync2(path14)) return null;
857
+ return JSON.parse(readFileSync2(path14, "utf-8"));
855
858
  } catch {
856
859
  return null;
857
860
  }
858
861
  }
859
- function hooksFrom(path13, source, out, seen) {
860
- const hooks = readJson(path13)?.hooks;
862
+ function hooksFrom(path14, source, out, seen) {
863
+ const hooks = readJson(path14)?.hooks;
861
864
  if (!hooks || typeof hooks !== "object") return;
862
865
  for (const [event, config] of Object.entries(hooks)) {
863
866
  const stableKey = `hooks:${source}:${event}`;
@@ -1006,16 +1009,16 @@ function buildMcpResource(name, group, pkg) {
1006
1009
  pkg
1007
1010
  };
1008
1011
  }
1009
- function readText(path13) {
1012
+ function readText(path14) {
1010
1013
  try {
1011
- if (!existsSync3(path13)) return null;
1012
- return readFileSync3(path13, "utf-8");
1014
+ if (!existsSync3(path14)) return null;
1015
+ return readFileSync3(path14, "utf-8");
1013
1016
  } catch {
1014
1017
  return null;
1015
1018
  }
1016
1019
  }
1017
- function readParsed(path13, parse2) {
1018
- const raw = readText(path13);
1020
+ function readParsed(path14, parse2) {
1021
+ const raw = readText(path14);
1019
1022
  if (raw === null) return null;
1020
1023
  try {
1021
1024
  return parse2(raw);
@@ -1023,14 +1026,14 @@ function readParsed(path13, parse2) {
1023
1026
  return null;
1024
1027
  }
1025
1028
  }
1026
- function readJson2(path13) {
1027
- return readParsed(path13, JSON.parse);
1029
+ function readJson2(path14) {
1030
+ return readParsed(path14, JSON.parse);
1028
1031
  }
1029
- function readYaml(path13) {
1030
- return readParsed(path13, parseYaml);
1032
+ function readYaml(path14) {
1033
+ return readParsed(path14, parseYaml);
1031
1034
  }
1032
- function readToml(path13) {
1033
- return readParsed(path13, parseToml);
1035
+ function readToml(path14) {
1036
+ return readParsed(path14, parseToml);
1034
1037
  }
1035
1038
  function continueListToMap(file) {
1036
1039
  if (!file?.mcpServers?.length) return void 0;
@@ -1169,8 +1172,8 @@ function resolveSource(entry, mpRepoUrl) {
1169
1172
  const src = entry.source;
1170
1173
  if (typeof src === "string") {
1171
1174
  if (!mpRepoUrl) return null;
1172
- const path13 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1173
- return { url: mpRepoUrl, path: path13 || void 0 };
1175
+ const path14 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1176
+ return { url: mpRepoUrl, path: path14 || void 0 };
1174
1177
  }
1175
1178
  if (src && typeof src === "object" && src.url) {
1176
1179
  return { url: src.url, path: src.path, sha: src.sha };
@@ -1207,10 +1210,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
1207
1210
  }
1208
1211
  return out;
1209
1212
  }
1210
- function readJson3(path13) {
1213
+ function readJson3(path14) {
1211
1214
  try {
1212
- if (!existsSync4(path13)) return null;
1213
- return JSON.parse(readFileSync4(path13, "utf-8"));
1215
+ if (!existsSync4(path14)) return null;
1216
+ return JSON.parse(readFileSync4(path14, "utf-8"));
1214
1217
  } catch {
1215
1218
  return null;
1216
1219
  }
@@ -1844,7 +1847,7 @@ function diffResources(current, existing) {
1844
1847
  // src/commands/connect.ts
1845
1848
  import { spawnSync } from "node:child_process";
1846
1849
  import { cpSync, existsSync as existsSync6 } from "node:fs";
1847
- import { homedir as homedir13 } from "node:os";
1850
+ import { homedir as homedir14 } from "node:os";
1848
1851
  import { dirname as dirname3, join as join6 } from "node:path";
1849
1852
  import { fileURLToPath } from "node:url";
1850
1853
  import * as p3 from "@clack/prompts";
@@ -3634,13 +3637,13 @@ function canonicalJson(value) {
3634
3637
  function fnv1a64(text) {
3635
3638
  const prime = 0x100000001b3n;
3636
3639
  const mask = 0xffffffffffffffffn;
3637
- let hash = 0xcbf29ce484222325n;
3640
+ let hash2 = 0xcbf29ce484222325n;
3638
3641
  const bytes = new TextEncoder().encode(text);
3639
3642
  for (const byte of bytes) {
3640
- hash ^= BigInt(byte);
3641
- hash = hash * prime & mask;
3643
+ hash2 ^= BigInt(byte);
3644
+ hash2 = hash2 * prime & mask;
3642
3645
  }
3643
- return hash.toString(16).padStart(16, "0");
3646
+ return hash2.toString(16).padStart(16, "0");
3644
3647
  }
3645
3648
  function dayFingerprint(day) {
3646
3649
  return fnv1a64(
@@ -5441,11 +5444,11 @@ function ingestWithRetry(agg, file, opts) {
5441
5444
  }
5442
5445
  }
5443
5446
  function ingestFile2(agg, file, opts) {
5444
- const readFile3 = opts.readFileImpl ?? readFileSync6;
5447
+ const readFile4 = opts.readFileImpl ?? readFileSync6;
5445
5448
  let text;
5446
5449
  if (file.endsWith(".zst")) {
5447
5450
  if (zstdDecompress === null) throw readError("zstd-unsupported");
5448
- const raw = readFile3(file);
5451
+ const raw = readFile4(file);
5449
5452
  try {
5450
5453
  text = zstdDecompress(
5451
5454
  Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
@@ -5454,7 +5457,7 @@ function ingestFile2(agg, file, opts) {
5454
5457
  throw readError("zstd-corrupt");
5455
5458
  }
5456
5459
  } else {
5457
- text = readFile3(file).toString("utf8");
5460
+ text = readFile4(file).toString("utf8");
5458
5461
  }
5459
5462
  const records = [];
5460
5463
  let nonEmptyLines = 0;
@@ -6610,15 +6613,129 @@ function ingestContribution(agg, row) {
6610
6613
 
6611
6614
  // src/harness/grok/scan.ts
6612
6615
  import { createReadStream as createReadStream2 } from "node:fs";
6613
- import { readdir as readdir4, readFile as readFile2, stat as stat5 } from "node:fs/promises";
6614
- import { homedir as homedir10 } from "node:os";
6615
- import path8 from "node:path";
6616
+ import { readdir as readdir5, readFile as readFile3, stat as stat5 } from "node:fs/promises";
6617
+ import { homedir as homedir11 } from "node:os";
6618
+ import path9 from "node:path";
6616
6619
  import readline2 from "node:readline";
6617
6620
  import { parse as parseToml3 } from "smol-toml";
6621
+
6622
+ // src/harness/grok/context.ts
6623
+ import { createHash as createHash2 } from "node:crypto";
6624
+ import { appendFile, mkdir as mkdir2, readdir as readdir4, readFile as readFile2, unlink } from "node:fs/promises";
6625
+ import { homedir as homedir10 } from "node:os";
6626
+ import path8 from "node:path";
6627
+ var DAY_MS = 864e5;
6628
+ var hash = (value) => createHash2("sha256").update(value).digest("hex");
6629
+ function contextCall(value) {
6630
+ const row = asObj(value);
6631
+ if (row?.src !== "shell" || row.msg !== "shell.turn.inference_done")
6632
+ return null;
6633
+ const session = asStr(row.sid);
6634
+ const ts = asStr(row.ts);
6635
+ const ctx = asObj(row.ctx);
6636
+ const tokens = ctx?.prompt_tokens;
6637
+ const loop = ctx?.loop_index;
6638
+ const tsMs = ts ? Date.parse(ts) : NaN;
6639
+ if (!session || !Number.isFinite(tsMs) || typeof tokens !== "number" || !Number.isSafeInteger(tokens) || tokens < 0 || typeof loop !== "number" || !Number.isSafeInteger(loop) || loop < 0)
6640
+ return null;
6641
+ return {
6642
+ id: hash(JSON.stringify([session, tsMs, row.pid ?? null, loop, tokens])),
6643
+ session,
6644
+ tsMs,
6645
+ tokens
6646
+ };
6647
+ }
6648
+ function contextCacheDirectory(root) {
6649
+ return path8.join(
6650
+ homedir10(),
6651
+ ".config",
6652
+ "aistack",
6653
+ "grok-context",
6654
+ hash(path8.resolve(root))
6655
+ );
6656
+ }
6657
+ async function optionalRead(file) {
6658
+ try {
6659
+ return await readFile2(file, "utf8");
6660
+ } catch (error) {
6661
+ if (error.code === "ENOENT") return "";
6662
+ throw error;
6663
+ }
6664
+ }
6665
+ async function retainedContextCalls(root, sessions, cacheDir = contextCacheDirectory(root), now = Date.now()) {
6666
+ const cutoff = now - 400 * DAY_MS;
6667
+ const calls = /* @__PURE__ */ new Map();
6668
+ let files = [];
6669
+ try {
6670
+ files = await readdir4(cacheDir);
6671
+ } catch (error) {
6672
+ if (error.code !== "ENOENT") throw error;
6673
+ }
6674
+ for (const file of files) {
6675
+ if (!/^\d{4}-\d{2}-\d{2}\.jsonl$/.test(file)) continue;
6676
+ if (Date.parse(file.slice(0, 10)) + DAY_MS < cutoff) {
6677
+ await unlink(path8.join(cacheDir, file)).catch(
6678
+ (error) => {
6679
+ if (error.code !== "ENOENT") throw error;
6680
+ }
6681
+ );
6682
+ continue;
6683
+ }
6684
+ const raw2 = await optionalRead(path8.join(cacheDir, file));
6685
+ if (raw2 && !raw2.endsWith("\n"))
6686
+ throw new Error("Incomplete Grok context cache");
6687
+ for (const line of raw2.split("\n").filter(Boolean)) {
6688
+ const call = asObj(JSON.parse(line));
6689
+ if (!call || typeof call.id !== "string" || !/^[a-f0-9]{64}$/.test(call.id) || typeof call.session !== "string" || typeof call.tsMs !== "number" || !Number.isFinite(call.tsMs) || typeof call.tokens !== "number" || !Number.isSafeInteger(call.tokens) || call.tokens < 0)
6690
+ throw new Error("Invalid Grok context cache");
6691
+ if (call.tsMs >= cutoff && call.tsMs <= now)
6692
+ calls.set(call.id, {
6693
+ id: call.id,
6694
+ session: call.session,
6695
+ tsMs: call.tsMs,
6696
+ tokens: call.tokens
6697
+ });
6698
+ }
6699
+ }
6700
+ const raw = await optionalRead(
6701
+ path8.join(root, "..", "logs", "unified.jsonl")
6702
+ );
6703
+ const additions = /* @__PURE__ */ new Map();
6704
+ for (const line of raw.slice(0, raw.lastIndexOf("\n") + 1).split("\n")) {
6705
+ let value;
6706
+ try {
6707
+ value = JSON.parse(line);
6708
+ } catch {
6709
+ continue;
6710
+ }
6711
+ const call = contextCall(value);
6712
+ if (!call || !sessions.has(call.session) || call.tsMs < cutoff || call.tsMs > now || calls.has(call.id))
6713
+ continue;
6714
+ calls.set(call.id, call);
6715
+ const date = new Date(call.tsMs).toISOString().slice(0, 10);
6716
+ const lines2 = additions.get(date) ?? [];
6717
+ lines2.push(JSON.stringify(call));
6718
+ additions.set(date, lines2);
6719
+ }
6720
+ if (additions.size) {
6721
+ await mkdir2(cacheDir, { recursive: true, mode: 448 });
6722
+ for (const [date, lines2] of additions) {
6723
+ await appendFile(
6724
+ path8.join(cacheDir, `${date}.jsonl`),
6725
+ `${lines2.join("\n")}
6726
+ `,
6727
+ { mode: 384 }
6728
+ );
6729
+ }
6730
+ }
6731
+ return [...calls.values()].filter((call) => sessions.has(call.session)).sort((a, b) => a.tsMs - b.tsMs || a.id.localeCompare(b.id));
6732
+ }
6733
+
6734
+ // src/harness/grok/scan.ts
6618
6735
  function sessionRoots() {
6619
6736
  return [
6620
- path8.join(
6621
- process.env.GROK_HOME || path8.join(homedir10(), ".grok"),
6737
+ path9.join(
6738
+ process.env.GROK_HOME || path9.join(homedir11(), ".grok"),
6622
6739
  "sessions"
6623
6740
  )
6624
6741
  ];
@@ -6650,7 +6767,7 @@ async function stableRead(file, jsonl) {
6650
6767
  if (before.size === after.size && before.mtimeMs === after.mtimeMs)
6651
6768
  return { lines: lines2, complete: true };
6652
6769
  } else {
6653
- const raw = await readFile2(file, "utf8");
6770
+ const raw = await readFile3(file, "utf8");
6654
6771
  const after = await stat5(file);
6655
6772
  if (before.size === after.size && before.mtimeMs === after.mtimeMs)
6656
6773
  return { json: JSON.parse(raw), complete: true };
@@ -6662,16 +6779,16 @@ async function stableRead(file, jsonl) {
6662
6779
  }
6663
6780
  async function directories(root) {
6664
6781
  try {
6665
- const workspaces = await readdir4(root, { withFileTypes: true });
6782
+ const workspaces = await readdir5(root, { withFileTypes: true });
6666
6783
  const out = [];
6667
6784
  for (const workspace of workspaces) {
6668
6785
  if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
6669
- const workspacePath = path8.join(root, workspace.name);
6670
- for (const session of await readdir4(workspacePath, {
6786
+ const workspacePath = path9.join(root, workspace.name);
6787
+ for (const session of await readdir5(workspacePath, {
6671
6788
  withFileTypes: true
6672
6789
  })) {
6673
6790
  if (session.isDirectory() && !session.isSymbolicLink())
6674
- out.push(path8.join(workspacePath, session.name));
6791
+ out.push(path9.join(workspacePath, session.name));
6675
6792
  }
6676
6793
  }
6677
6794
  return out.sort();
@@ -6683,7 +6800,7 @@ async function modelAliasesForRoot(root) {
6683
6800
  if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
6684
6801
  try {
6685
6802
  const parsed = asObj(
6686
- parseToml3(await readFile2(path8.join(root, "..", "config.toml"), "utf8"))
6803
+ parseToml3(await readFile3(path9.join(root, "..", "config.toml"), "utf8"))
6687
6804
  );
6688
6805
  if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
6689
6806
  const models = asObj(parsed.model);
@@ -6706,6 +6823,7 @@ async function scan4(agg, opts = {}) {
6706
6823
  let complete = true;
6707
6824
  for (const root of opts.roots ?? sessionRoots()) {
6708
6825
  const aliases = await modelAliasesForRoot(root);
6826
+ const contextSessions = /* @__PURE__ */ new Map();
6709
6827
  const dirs = await directories(root);
6710
6828
  if (dirs === null) {
6711
6829
  complete = false;
@@ -6714,7 +6832,7 @@ async function scan4(agg, opts = {}) {
6714
6832
  for (const dir of dirs) {
6715
6833
  let entries;
6716
6834
  try {
6717
- entries = await readdir4(dir, { withFileTypes: true });
6835
+ entries = await readdir5(dir, { withFileTypes: true });
6718
6836
  } catch {
6719
6837
  complete = false;
6720
6838
  continue;
@@ -6722,10 +6840,10 @@ async function scan4(agg, opts = {}) {
6722
6840
  const files = new Map(
6723
6841
  entries.filter(
6724
6842
  (e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
6725
- ).map((e) => [e.name, path8.join(dir, e.name)])
6843
+ ).map((e) => [e.name, path9.join(dir, e.name)])
6726
6844
  );
6727
6845
  let projectDir = dir;
6728
- let sessionFallback = path8.basename(dir);
6846
+ let sessionFallback = path9.basename(dir);
6729
6847
  let child = false;
6730
6848
  let parentSession;
6731
6849
  const summary = files.get("summary.json");
@@ -6743,6 +6861,7 @@ async function scan4(agg, opts = {}) {
6743
6861
  parentSession = asStr(value?.parentSessionId) ?? asStr(value?.parent_session_id) ?? asStr(info?.parentSessionId) ?? asStr(info?.parent_session_id) ?? void 0;
6744
6862
  child = parentSession !== void 0;
6745
6863
  }
6864
+ contextSessions.set(sessionFallback, { projectDir, parentSession });
6746
6865
  let rows = [];
6747
6866
  let precedence = 0;
6748
6867
  const usage = files.get("usage.json");
@@ -6756,7 +6875,11 @@ async function scan4(agg, opts = {}) {
6756
6875
  }
6757
6876
  stats.filesRead++;
6758
6877
  rows = sidecarContributions(read3.json, projectDir);
6759
- if (rows.length > 0) precedence = 2;
6878
+ if (rows.length > 0) {
6879
+ precedence = 2;
6880
+ for (const row of rows)
6881
+ contextSessions.set(row.sessionId, { projectDir, parentSession });
6882
+ }
6760
6883
  }
6761
6884
  if (child) {
6762
6885
  rows = [];
@@ -6829,6 +6952,32 @@ async function scan4(agg, opts = {}) {
6829
6952
  }
6830
6953
  opts.onProgress?.(stats.filesFound);
6831
6954
  }
6955
+ try {
6956
+ const calls = await retainedContextCalls(
6957
+ root,
6958
+ new Set(contextSessions.keys()),
6959
+ opts.contextCacheDir
6960
+ );
6961
+ for (const call of calls) {
6962
+ if (opts.sinceMs !== void 0 && call.tsMs < opts.sinceMs) continue;
6963
+ const source = contextSessions.get(call.session);
6964
+ agg.workflow.ingest({
6965
+ type: "response",
6966
+ session: call.session,
6967
+ projectWorkspace: source?.projectDir,
6968
+ parentSession: source?.parentSession,
6969
+ tsMs: call.tsMs,
6970
+ responseId: `context:${call.id}`,
6971
+ contextTokens: call.tokens
6972
+ });
6973
+ const dates = sessionDates.get(call.session) ?? /* @__PURE__ */ new Set();
6974
+ dates.add(new Date(call.tsMs).toISOString().slice(0, 10));
6975
+ sessionDates.set(call.session, dates);
6976
+ }
6977
+ } catch {
6978
+ complete = false;
6979
+ stats.filesUnreadable++;
6980
+ }
6832
6981
  }
6833
6982
  for (const { rows } of candidates.values()) {
6834
6983
  for (const row of rows) {
@@ -7056,14 +7205,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
7056
7205
 
7057
7206
  // src/harness/opencode/scan.ts
7058
7207
  import { readFileSync as readFileSync7 } from "node:fs";
7059
- import { readdir as readdir5, stat as stat6 } from "node:fs/promises";
7060
- import { homedir as homedir11 } from "node:os";
7061
- import path9 from "node:path";
7208
+ import { readdir as readdir6, stat as stat6 } from "node:fs/promises";
7209
+ import { homedir as homedir12 } from "node:os";
7210
+ import path10 from "node:path";
7062
7211
  var OPENCODE_MIGRATION_CEILING = 20260622202450;
7063
7212
  function opencodeDataDirs() {
7064
7213
  const xdg = process.env.XDG_DATA_HOME;
7065
- const base = xdg || path9.join(homedir11(), ".local", "share");
7066
- return [path9.join(base, "opencode")];
7214
+ const base = xdg || path10.join(homedir12(), ".local", "share");
7215
+ return [path10.join(base, "opencode")];
7067
7216
  }
7068
7217
  function isStoreFile(basename2) {
7069
7218
  return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
@@ -7072,8 +7221,8 @@ async function dbFilesIn(root) {
7072
7221
  const override = process.env.OPENCODE_DB;
7073
7222
  if (override) return [override];
7074
7223
  try {
7075
- const entries = await readdir5(root, { withFileTypes: true });
7076
- return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path9.join(root, e.name)).sort();
7224
+ const entries = await readdir6(root, { withFileTypes: true });
7225
+ return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path10.join(root, e.name)).sort();
7077
7226
  } catch {
7078
7227
  return [];
7079
7228
  }
@@ -7110,7 +7259,7 @@ async function scan5(agg, opts = {}) {
7110
7259
  } catch (e) {
7111
7260
  stats.filesUnreadable++;
7112
7261
  stats.unreadableFiles.push({
7113
- path: path9.basename(file),
7262
+ path: path10.basename(file),
7114
7263
  reason: errorClass2(e)
7115
7264
  });
7116
7265
  }
@@ -7277,8 +7426,8 @@ function pickTs(jsonTs, columnTs) {
7277
7426
  }
7278
7427
  function opencodeConfigFile() {
7279
7428
  const xdg = process.env.XDG_CONFIG_HOME;
7280
- const base = xdg || path9.join(homedir11(), ".config");
7281
- return path9.join(base, "opencode", "opencode.json");
7429
+ const base = xdg || path10.join(homedir12(), ".config");
7430
+ return path10.join(base, "opencode", "opencode.json");
7282
7431
  }
7283
7432
  function readConfiguredMcpServers2(agg, state, configFile) {
7284
7433
  const file = configFile ?? opencodeConfigFile();
@@ -7580,17 +7729,17 @@ function readCounts2(usageRaw) {
7580
7729
 
7581
7730
  // src/harness/pi/scan.ts
7582
7731
  import { createReadStream as createReadStream3 } from "node:fs";
7583
- import { readdir as readdir6, realpath as realpath3, stat as stat7 } from "node:fs/promises";
7584
- import { homedir as homedir12 } from "node:os";
7585
- import path10 from "node:path";
7732
+ import { readdir as readdir7, realpath as realpath3, stat as stat7 } from "node:fs/promises";
7733
+ import { homedir as homedir13 } from "node:os";
7734
+ import path11 from "node:path";
7586
7735
  import readline3 from "node:readline";
7587
7736
  var MAX_SESSION_VERSION = 3;
7588
7737
  function piAgentDir() {
7589
- return process.env.PI_CODING_AGENT_DIR || path10.join(homedir12(), ".pi", "agent");
7738
+ return process.env.PI_CODING_AGENT_DIR || path11.join(homedir13(), ".pi", "agent");
7590
7739
  }
7591
7740
  function sessionRoots2() {
7592
7741
  const override = process.env.PI_CODING_AGENT_SESSION_DIR;
7593
- return [override || path10.join(piAgentDir(), "sessions")];
7742
+ return [override || path11.join(piAgentDir(), "sessions")];
7594
7743
  }
7595
7744
  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$/;
7596
7745
  function isSessionFile(basename2) {
@@ -7599,12 +7748,12 @@ function isSessionFile(basename2) {
7599
7748
  async function* walkSessions(dir) {
7600
7749
  let entries;
7601
7750
  try {
7602
- entries = await readdir6(dir, { withFileTypes: true });
7751
+ entries = await readdir7(dir, { withFileTypes: true });
7603
7752
  } catch {
7604
7753
  return;
7605
7754
  }
7606
7755
  for (const e of entries) {
7607
- const full = path10.join(dir, e.name);
7756
+ const full = path11.join(dir, e.name);
7608
7757
  if (e.isDirectory()) yield* walkSessions(full);
7609
7758
  else if (e.isFile() && isSessionFile(e.name)) yield full;
7610
7759
  }
@@ -7652,7 +7801,7 @@ async function scan6(agg, opts = {}) {
7652
7801
  stats.filesUnreadable++;
7653
7802
  stats.filesRead--;
7654
7803
  stats.unreadableFiles.push({
7655
- path: path10.relative(root, file),
7804
+ path: path11.relative(root, file),
7656
7805
  reason: "version-too-new"
7657
7806
  });
7658
7807
  }
@@ -7660,7 +7809,7 @@ async function scan6(agg, opts = {}) {
7660
7809
  stats.filesUnreadable++;
7661
7810
  stats.filesRead--;
7662
7811
  stats.unreadableFiles.push({
7663
- path: path10.relative(root, file),
7812
+ path: path11.relative(root, file),
7664
7813
  reason: "read-error"
7665
7814
  });
7666
7815
  }
@@ -7798,7 +7947,7 @@ var MCP_ADD_ARGS = [
7798
7947
  "mcp"
7799
7948
  ];
7800
7949
  var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
7801
- var SKILL_DEST = join6(homedir13(), ".claude", "skills", "aistack-sync");
7950
+ var SKILL_DEST = join6(homedir14(), ".claude", "skills", "aistack-sync");
7802
7951
  function runClaude(args) {
7803
7952
  const r = spawnSync("claude", args, { encoding: "utf-8" });
7804
7953
  const notFound = r.error !== void 0 && r.error.code === "ENOENT";
@@ -8120,13 +8269,13 @@ async function loginCommand(options = {}) {
8120
8269
  import * as p7 from "@clack/prompts";
8121
8270
 
8122
8271
  // src/autosync/codexHook.ts
8123
- import { createHash as createHash2 } from "node:crypto";
8272
+ import { createHash as createHash3 } from "node:crypto";
8124
8273
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
8125
- import { homedir as homedir14 } from "node:os";
8274
+ import { homedir as homedir15 } from "node:os";
8126
8275
  import { dirname as dirname5, join as join8 } from "node:path";
8127
8276
  import { parse } from "smol-toml";
8128
8277
  function codexHome2() {
8129
- return process.env.CODEX_HOME || join8(homedir14(), ".codex");
8278
+ return process.env.CODEX_HOME || join8(homedir15(), ".codex");
8130
8279
  }
8131
8280
  function codexHooksFile() {
8132
8281
  return join8(codexHome2(), "hooks.json");
@@ -8235,7 +8384,7 @@ function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooks
8235
8384
  hooks: [normalizedHandler]
8236
8385
  };
8237
8386
  if (typeof group.matcher === "string") identity.matcher = group.matcher;
8238
- const currentHash = `sha256:${createHash2("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
8387
+ const currentHash = `sha256:${createHash3("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
8239
8388
  const key2 = `${hooksFile}:session_start:${groupIndex}:${handlerIndex}`;
8240
8389
  matches.push(config.hooks?.state?.[key2]?.trusted_hash === currentHash);
8241
8390
  }
@@ -8267,14 +8416,14 @@ import * as p6 from "@clack/prompts";
8267
8416
 
8268
8417
  // src/autosync/cursorHook.ts
8269
8418
  import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
8270
- import { homedir as homedir16, platform as platform2 } from "node:os";
8419
+ import { homedir as homedir17, platform as platform2 } from "node:os";
8271
8420
  import { dirname as dirname7, join as join10 } from "node:path";
8272
8421
 
8273
8422
  // src/autosync/hook.ts
8274
8423
  import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
8275
- import { homedir as homedir15 } from "node:os";
8424
+ import { homedir as homedir16 } from "node:os";
8276
8425
  import { dirname as dirname6, join as join9 } from "node:path";
8277
- var CLAUDE_SETTINGS_FILE = join9(homedir15(), ".claude", "settings.json");
8426
+ var CLAUDE_SETTINGS_FILE = join9(homedir16(), ".claude", "settings.json");
8278
8427
  var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
8279
8428
  function isOurs2(entry) {
8280
8429
  return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
@@ -8347,7 +8496,7 @@ function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
8347
8496
  }
8348
8497
 
8349
8498
  // src/autosync/cursorHook.ts
8350
- var CURSOR_HOOK_FILE = join10(homedir16(), ".cursor", "hooks.json");
8499
+ var CURSOR_HOOK_FILE = join10(homedir17(), ".cursor", "hooks.json");
8351
8500
  function cursorHookCommand(os = platform2()) {
8352
8501
  if (os === "win32") {
8353
8502
  const script = `Start-Process -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=cursor&& (${AUTO_SYNC_HOOK_COMMAND}) <NUL >NUL 2>&1"'`;
@@ -8427,10 +8576,10 @@ import {
8427
8576
  unlinkSync,
8428
8577
  writeFileSync as writeFileSync6
8429
8578
  } from "node:fs";
8430
- import { homedir as homedir17, platform as platform3 } from "node:os";
8579
+ import { homedir as homedir18, platform as platform3 } from "node:os";
8431
8580
  import { dirname as dirname8, join as join11 } from "node:path";
8432
8581
  var GROK_HOOK_FILE = join11(
8433
- process.env.GROK_HOME || join11(homedir17(), ".grok"),
8582
+ process.env.GROK_HOME || join11(homedir18(), ".grok"),
8434
8583
  "hooks",
8435
8584
  "aistack.json"
8436
8585
  );
@@ -8788,11 +8937,11 @@ import {
8788
8937
  unlinkSync as unlinkSync2,
8789
8938
  writeFileSync as writeFileSync8
8790
8939
  } from "node:fs";
8791
- import { homedir as homedir19 } from "node:os";
8940
+ import { homedir as homedir20 } from "node:os";
8792
8941
  import { dirname as dirname9, join as join12 } from "node:path";
8793
8942
 
8794
8943
  // src/sync/stage.ts
8795
- import { createHash as createHash4 } from "node:crypto";
8944
+ import { createHash as createHash5 } from "node:crypto";
8796
8945
 
8797
8946
  // src/usage/days.ts
8798
8947
  var round6 = (n) => Math.round(n * 1e6) / 1e6;
@@ -8907,11 +9056,11 @@ function buildMeasuredDays(input) {
8907
9056
 
8908
9057
  // src/usage/diff.ts
8909
9058
  var MAX_DAY_WINDOW = 400;
8910
- var DAY_MS = 864e5;
9059
+ var DAY_MS2 = 864e5;
8911
9060
  function retentionFloor(todayUtc, days) {
8912
9061
  const span = Math.max(1, Math.min(days, MAX_DAY_WINDOW));
8913
9062
  const todayMs = Date.parse(`${todayUtc}T00:00:00.000Z`);
8914
- return new Date(todayMs - (span - 1) * DAY_MS).toISOString().slice(0, 10);
9063
+ return new Date(todayMs - (span - 1) * DAY_MS2).toISOString().slice(0, 10);
8915
9064
  }
8916
9065
  function selectDaysToPublish(input) {
8917
9066
  const { local, manifest, todayUtc } = input;
@@ -8948,7 +9097,7 @@ function selectDaysToPublish(input) {
8948
9097
 
8949
9098
  // src/workflow/git.ts
8950
9099
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
8951
- import path11 from "node:path";
9100
+ import path12 from "node:path";
8952
9101
  var TEST_FILE_RULE_VERSION = "test-files/v2";
8953
9102
  var FILE_TYPE_RULE_VERSION = "file-types/v2";
8954
9103
  var COMMIT_SET_RULE_VERSION = "commit-set/v1";
@@ -9212,10 +9361,10 @@ function reduceGitHistories(histories, options) {
9212
9361
  const field = fields[fieldIndex] ?? "";
9213
9362
  if (field.replace(/^\n+/, "") === COMMIT_MARKER) {
9214
9363
  finishCommit();
9215
- const hash = fields[++fieldIndex] ?? "";
9364
+ const hash2 = fields[++fieldIndex] ?? "";
9216
9365
  const authoredAt = fields[++fieldIndex] ?? "";
9217
9366
  const authoredMs = Date.parse(authoredAt);
9218
- const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(hash);
9367
+ const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(hash2);
9219
9368
  current = {
9220
9369
  included,
9221
9370
  date: included ? new Date(authoredMs).toISOString().slice(0, 10) : "",
@@ -9228,7 +9377,7 @@ function reduceGitHistories(histories, options) {
9228
9377
  withheldLines: 0,
9229
9378
  extensionLines: /* @__PURE__ */ new Map()
9230
9379
  };
9231
- if (included) seenCommits.add(hash);
9380
+ if (included) seenCommits.add(hash2);
9232
9381
  continue;
9233
9382
  }
9234
9383
  const stat8 = parseNumstat(field);
@@ -9247,7 +9396,7 @@ function reduceGitHistories(histories, options) {
9247
9396
  current.changedLines += fileChangedLines;
9248
9397
  if (isTestFile(file)) current.touchesTest = true;
9249
9398
  if (fileChangedLines <= 0) continue;
9250
- const extension = path11.extname(file).toLowerCase();
9399
+ const extension = path12.extname(file).toLowerCase();
9251
9400
  if (APPROVED_EXTENSIONS.has(extension)) {
9252
9401
  current.extensionLines.set(
9253
9402
  extension,
@@ -9351,18 +9500,18 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
9351
9500
  }
9352
9501
 
9353
9502
  // src/sync/grokDateCache.ts
9354
- import { createHash as createHash3 } from "node:crypto";
9503
+ import { createHash as createHash4 } from "node:crypto";
9355
9504
  import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
9356
- import { homedir as homedir18 } from "node:os";
9357
- import path12 from "node:path";
9358
- var defaultFile = path12.join(
9359
- homedir18(),
9505
+ import { homedir as homedir19 } from "node:os";
9506
+ import path13 from "node:path";
9507
+ var defaultFile = path13.join(
9508
+ homedir19(),
9360
9509
  ".config",
9361
9510
  "aistack",
9362
9511
  "grok-session-dates.json"
9363
9512
  );
9364
9513
  function grokCacheScope(baseUrl, stack, token) {
9365
- return createHash3("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
9514
+ return createHash4("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
9366
9515
  }
9367
9516
  function read2(file) {
9368
9517
  if (!existsSync12(file)) return {};
@@ -9379,7 +9528,7 @@ function loadGrokDateHints(scope, file = defaultFile) {
9379
9528
  function saveGrokDateHints(scope, hints, file = defaultFile) {
9380
9529
  const cache = read2(file);
9381
9530
  cache[scope] = hints;
9382
- mkdirSync7(path12.dirname(file), { recursive: true });
9531
+ mkdirSync7(path13.dirname(file), { recursive: true });
9383
9532
  writeFileSync7(file, JSON.stringify(cache, null, 2));
9384
9533
  }
9385
9534
  function mapToHints(value, floor) {
@@ -9791,7 +9940,7 @@ function buildGateSummary(ctx) {
9791
9940
  // src/sync/stage.ts
9792
9941
  var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
9793
9942
  function stageId(bodyJson) {
9794
- return createHash4("sha256").update(bodyJson).digest("hex").slice(0, 12);
9943
+ return createHash5("sha256").update(bodyJson).digest("hex").slice(0, 12);
9795
9944
  }
9796
9945
  async function stageSync(deps) {
9797
9946
  const now = (deps.now ?? Date.now)();
@@ -9808,7 +9957,7 @@ async function stageSync(deps) {
9808
9957
  id: BUNDLED_PRICE_TABLE_ID,
9809
9958
  origin: "bundled"
9810
9959
  };
9811
- progress("Checking prices and stack settings");
9960
+ progress("Checking prices");
9812
9961
  try {
9813
9962
  const table = await fetchPrices(deps.baseUrl);
9814
9963
  if (table) {
@@ -9820,12 +9969,14 @@ async function stageSync(deps) {
9820
9969
  } catch {
9821
9970
  setActivePricer(null);
9822
9971
  }
9972
+ progress("Checking stack settings");
9823
9973
  const { config, source } = await loadConfig({
9824
9974
  baseUrl: deps.baseUrl,
9825
9975
  ...token ? { token } : {}
9826
9976
  });
9827
9977
  let manifest = null;
9828
9978
  if (token) {
9979
+ progress("Checking previously synced days");
9829
9980
  try {
9830
9981
  manifest = await fetchManifest(deps.baseUrl, token);
9831
9982
  } catch {
@@ -9842,6 +9993,7 @@ async function stageSync(deps) {
9842
9993
  const usageScans = [];
9843
9994
  const sinceMs = windowStartMs(now, windowDays);
9844
9995
  const daysSinceMs = windowStartMs(now, retentionDays);
9996
+ progress("Detecting local harnesses");
9845
9997
  const active2 = await adapters(sinceMs);
9846
9998
  const historical = await adapters(daysSinceMs);
9847
9999
  let dayScansComplete = true;
@@ -9996,7 +10148,7 @@ async function stageSync(deps) {
9996
10148
  }
9997
10149
 
9998
10150
  // src/autosync/run.ts
9999
- var SYNC_LOG_FILE = join12(homedir19(), ".config", "aistack", "sync.log");
10151
+ var SYNC_LOG_FILE = join12(homedir20(), ".config", "aistack", "sync.log");
10000
10152
  var SYNC_LOG_MAX_LINES = 200;
10001
10153
  var FIX_COMMAND = "npx @use-aistack/cli sync";
10002
10154
  var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
@@ -10049,7 +10201,7 @@ async function runAutoSync(deps) {
10049
10201
  const state = settings.autoSyncState ?? {};
10050
10202
  const lastRunAt = state.lastRunAt ?? 0;
10051
10203
  if (now - lastRunAt < windowMs) return;
10052
- const reservationFile = deps.reservationFile ?? `${settingsFile ?? join12(homedir19(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
10204
+ const reservationFile = deps.reservationFile ?? `${settingsFile ?? join12(homedir20(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
10053
10205
  if (!reserveAttempt(reservationFile, now, windowMs)) return;
10054
10206
  saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
10055
10207
  const stage = deps.stageImpl ?? stageSync;