@swestash/agent-lens 0.9.6 → 0.11.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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
5
- import { dirname as dirname7, join as join11 } from "path";
6
- import { fileURLToPath as fileURLToPath3 } from "url";
4
+ import { existsSync as existsSync10, writeFileSync as writeFileSync3 } from "fs";
5
+ import { join as join12 } from "path";
6
+ import { fileURLToPath as fileURLToPath4 } from "url";
7
7
 
8
8
  // ../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs
9
9
  import { EventEmitter } from "events";
@@ -107,7 +107,7 @@ var findAllBrackets = (v) => {
107
107
  const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
108
108
  const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
109
109
  const res = [];
110
- const parse = (match) => {
110
+ const parse2 = (match) => {
111
111
  let variadic = false;
112
112
  let value = match[1];
113
113
  if (value.startsWith("...")) {
@@ -122,11 +122,11 @@ var findAllBrackets = (v) => {
122
122
  };
123
123
  let angledMatch;
124
124
  while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {
125
- res.push(parse(angledMatch));
125
+ res.push(parse2(angledMatch));
126
126
  }
127
127
  let squareMatch;
128
128
  while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
129
- res.push(parse(squareMatch));
129
+ res.push(parse2(squareMatch));
130
130
  }
131
131
  return res;
132
132
  };
@@ -1242,6 +1242,15 @@ function classifyToolError(resultSummary) {
1242
1242
  }
1243
1243
 
1244
1244
  // ../core/dist/pricing.js
1245
+ var SYNTHETIC_MODEL = "<synthetic>";
1246
+ function unpricedModels(models) {
1247
+ const out = /* @__PURE__ */ new Set();
1248
+ for (const m of models) {
1249
+ if (m && m !== SYNTHETIC_MODEL && !rateForModel(m))
1250
+ out.add(m);
1251
+ }
1252
+ return [...out].sort();
1253
+ }
1245
1254
  var PRICE_TABLE = {
1246
1255
  // Opus 4.0 / 4.1 launched at $15/$75; Opus 4.5 cut the Opus tier to $5/$25 and 4.6/4.7/4.8 kept it.
1247
1256
  // The bare 'claude-opus-4' fallback stays at the legacy rate for 4.0/4.1; 4.5+ override below.
@@ -1250,17 +1259,65 @@ var PRICE_TABLE = {
1250
1259
  "claude-opus-4-6": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
1251
1260
  "claude-opus-4-7": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
1252
1261
  "claude-opus-4-8": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
1262
+ "claude-opus-5": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
1253
1263
  "claude-fable-5": { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1 },
1254
1264
  "claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
1265
+ // Sonnet 5 list price. An introductory $2/$10 rate runs through 2026-08-31; we bill the list
1266
+ // price so the estimate doesn't silently change under a date, matching the "list price" label.
1267
+ "claude-sonnet-5": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
1255
1268
  "claude-haiku-4": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
1256
1269
  "claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
1257
1270
  "claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
1258
1271
  };
1272
+ var CACHE_WRITE_MULTIPLIER = 1.25;
1273
+ var CACHE_READ_MULTIPLIER = 0.1;
1274
+ function toRate(v) {
1275
+ if (v === null || typeof v !== "object" || Array.isArray(v))
1276
+ return null;
1277
+ const o = v;
1278
+ const num = (x) => typeof x === "number" && Number.isFinite(x) && x >= 0 ? x : null;
1279
+ const input = num(o.input);
1280
+ const output = num(o.output);
1281
+ if (input === null || output === null)
1282
+ return null;
1283
+ if (o.cacheWrite !== void 0 && num(o.cacheWrite) === null)
1284
+ return null;
1285
+ if (o.cacheRead !== void 0 && num(o.cacheRead) === null)
1286
+ return null;
1287
+ return {
1288
+ input,
1289
+ output,
1290
+ cacheWrite: num(o.cacheWrite) ?? input * CACHE_WRITE_MULTIPLIER,
1291
+ cacheRead: num(o.cacheRead) ?? input * CACHE_READ_MULTIPLIER
1292
+ };
1293
+ }
1294
+ function mergePricing(base, overrides) {
1295
+ const table = { ...base };
1296
+ const invalid = [];
1297
+ const applied = [];
1298
+ if (overrides === null || typeof overrides !== "object" || Array.isArray(overrides)) {
1299
+ return { table, invalid, applied };
1300
+ }
1301
+ for (const [prefix, value] of Object.entries(overrides)) {
1302
+ const rate = toRate(value);
1303
+ if (!prefix.trim() || !rate)
1304
+ invalid.push(prefix);
1305
+ else {
1306
+ table[prefix] = rate;
1307
+ applied.push(prefix);
1308
+ }
1309
+ }
1310
+ return { table, invalid: invalid.sort(), applied: applied.sort() };
1311
+ }
1312
+ var activeTable = PRICE_TABLE;
1313
+ function usePricing(table) {
1314
+ activeTable = table;
1315
+ }
1259
1316
  function rateForModel(model) {
1260
1317
  if (!model)
1261
1318
  return null;
1262
1319
  let best = null;
1263
- for (const [prefix, rate] of Object.entries(PRICE_TABLE)) {
1320
+ for (const [prefix, rate] of Object.entries(activeTable)) {
1264
1321
  if (model.startsWith(prefix) && (!best || prefix.length > best.len)) {
1265
1322
  best = { len: prefix.length, rate };
1266
1323
  }
@@ -1377,6 +1434,24 @@ import { join as join2 } from "path";
1377
1434
  var DEFAULT_PORT = 4477;
1378
1435
  var DEFAULT_HOST = "127.0.0.1";
1379
1436
  var DEFAULT_DB_NAME = "agent-lens.db";
1437
+ var DEFAULT_VERSIONS_KEEP_DAYS = 90;
1438
+ function resolveRetention() {
1439
+ const raw = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS;
1440
+ const parsed = raw ? Number(raw) : NaN;
1441
+ return Number.isFinite(parsed) && parsed >= 0 ? { keepDays: parsed, origin: "env" } : { keepDays: DEFAULT_VERSIONS_KEEP_DAYS, origin: "default" };
1442
+ }
1443
+ function resolvePricing(cfg = readConfigFile()) {
1444
+ const { table, invalid, applied } = mergePricing(PRICE_TABLE, cfg?.pricing);
1445
+ return { table, origin: applied.length ? "file" : "default", applied, invalid };
1446
+ }
1447
+ function installPricing(cfg) {
1448
+ const resolved = cfg === void 0 ? resolvePricing() : resolvePricing(cfg);
1449
+ usePricing(resolved.table);
1450
+ if (resolved.invalid.length) {
1451
+ console.error(`agent-lens: ignoring ${resolved.invalid.length} malformed pricing override(s): ${resolved.invalid.join(", ")} (each entry needs numeric input + output; built-in rates kept)`);
1452
+ }
1453
+ return resolved;
1454
+ }
1380
1455
  function readConfigFile() {
1381
1456
  const repoRoot = findRepoRoot();
1382
1457
  const dataDir = resolveDataDir(repoRoot);
@@ -1436,6 +1511,59 @@ function resolveServerConfig(overrides = {}, cfg = readConfigFile()) {
1436
1511
  return { port, host, portOrigin, hostOrigin };
1437
1512
  }
1438
1513
 
1514
+ // ../core/dist/version.js
1515
+ import { spawnSync } from "child_process";
1516
+ import { readFileSync as readFileSync3 } from "fs";
1517
+ import { dirname as dirname2, join as join3, parse } from "path";
1518
+ import { fileURLToPath as fileURLToPath2 } from "url";
1519
+ var moduleDir2 = dirname2(fileURLToPath2(import.meta.url));
1520
+ function isPlaceholder(v) {
1521
+ return v.startsWith("0.0.0");
1522
+ }
1523
+ function nearestPackageVersion(dir) {
1524
+ const { root } = parse(dir);
1525
+ for (let d = dir; ; d = dirname2(d)) {
1526
+ try {
1527
+ const pkg = JSON.parse(readFileSync3(join3(d, "package.json"), "utf8"));
1528
+ if (typeof pkg.version === "string" && pkg.version)
1529
+ return pkg.version;
1530
+ } catch {
1531
+ }
1532
+ if (d === root)
1533
+ return null;
1534
+ }
1535
+ }
1536
+ function gitDescribe(cwd) {
1537
+ try {
1538
+ const r = spawnSync("git", ["describe", "--tags", "--always", "--dirty"], {
1539
+ cwd,
1540
+ encoding: "utf8",
1541
+ stdio: ["ignore", "pipe", "ignore"]
1542
+ });
1543
+ if (r.status !== 0)
1544
+ return null;
1545
+ const out = r.stdout?.trim();
1546
+ return out ? out : null;
1547
+ } catch {
1548
+ return null;
1549
+ }
1550
+ }
1551
+ function computeVersion(fromDir = moduleDir2) {
1552
+ const pkg = nearestPackageVersion(fromDir);
1553
+ if (pkg && !isPlaceholder(pkg))
1554
+ return { version: pkg, source: "npm" };
1555
+ const described = gitDescribe(fromDir);
1556
+ if (described)
1557
+ return { version: described, source: "git" };
1558
+ return { version: "unknown", source: "unknown" };
1559
+ }
1560
+ var cached = null;
1561
+ function resolveVersion() {
1562
+ if (!cached)
1563
+ cached = computeVersion();
1564
+ return cached;
1565
+ }
1566
+
1439
1567
  // ../core/dist/sources.js
1440
1568
  function loadSources() {
1441
1569
  if (process.env.CLAUDE_DIR) {
@@ -1490,7 +1618,7 @@ function encodeProjectPath(p) {
1490
1618
 
1491
1619
  // ../core/dist/collect.js
1492
1620
  import { appendFileSync, closeSync, copyFileSync, existsSync as existsSync2, mkdirSync, openSync, readSync, readdirSync, statSync, unlinkSync, utimesSync, writeSync, chmodSync } from "fs";
1493
- import { basename, dirname as dirname2, join as join3, relative, sep } from "path";
1621
+ import { basename, dirname as dirname3, join as join4, relative, sep } from "path";
1494
1622
  import { platform as platform2 } from "os";
1495
1623
  var POSIX = platform2() !== "win32";
1496
1624
  var DIR_MODE = 448;
@@ -1516,7 +1644,7 @@ function syncMeta(dst, src) {
1516
1644
  }
1517
1645
  }
1518
1646
  function copyWhole(src, dst, srcStat) {
1519
- ensureDir(dirname2(dst));
1647
+ ensureDir(dirname3(dst));
1520
1648
  copyFileSync(src, dst);
1521
1649
  syncMeta(dst, srcStat);
1522
1650
  }
@@ -1570,27 +1698,27 @@ function filesEqual(a, b) {
1570
1698
  return prefixEquals(a, b, sa.size);
1571
1699
  }
1572
1700
  function latestSnapshot(archive, relParts) {
1573
- const versRoot = join3(archive, ".versions");
1701
+ const versRoot = join4(archive, ".versions");
1574
1702
  if (!existsSync2(versRoot))
1575
1703
  return null;
1576
1704
  const stamps = readdirSync(versRoot).sort();
1577
1705
  for (let i = stamps.length - 1; i >= 0; i--) {
1578
- const p = join3(versRoot, stamps[i], ...relParts);
1706
+ const p = join4(versRoot, stamps[i], ...relParts);
1579
1707
  if (existsSync2(p))
1580
1708
  return p;
1581
1709
  }
1582
1710
  return null;
1583
1711
  }
1584
1712
  function snapshot(absPath, relParts, versionsDir, stats) {
1585
- const dst = join3(versionsDir, ...relParts);
1586
- ensureDir(dirname2(dst));
1713
+ const dst = join4(versionsDir, ...relParts);
1714
+ ensureDir(dirname3(dst));
1587
1715
  copyFileSync(absPath, dst);
1588
1716
  syncMeta(dst, statSync(absPath));
1589
1717
  stats.snapshots++;
1590
1718
  }
1591
1719
  function walkCollectable(dir, out, inWorkflows = false, inToolResults = false) {
1592
1720
  for (const e of readdirSync(dir, { withFileTypes: true })) {
1593
- const p = join3(dir, e.name);
1721
+ const p = join4(dir, e.name);
1594
1722
  if (e.isDirectory()) {
1595
1723
  walkCollectable(p, out, inWorkflows || e.name === "workflows", inToolResults || e.name === "tool-results");
1596
1724
  } else if (e.isFile() && (e.name.endsWith(".jsonl") || inWorkflows || inToolResults || e.name.endsWith(".meta.json"))) {
@@ -1631,7 +1759,7 @@ function removeCredentials(dir) {
1631
1759
  if (!existsSync2(dir))
1632
1760
  return;
1633
1761
  for (const e of readdirSync(dir, { withFileTypes: true })) {
1634
- const p = join3(dir, e.name);
1762
+ const p = join4(dir, e.name);
1635
1763
  if (e.isDirectory())
1636
1764
  removeCredentials(p);
1637
1765
  else if (e.name === ".credentials.json") {
@@ -1644,19 +1772,19 @@ function removeCredentials(dir) {
1644
1772
  }
1645
1773
  function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1646
1774
  const { label, configDir } = source;
1647
- const archive = join3(archiveBase, label);
1648
- const versionsDir = join3(archive, ".versions", runTs);
1775
+ const archive = join4(archiveBase, label);
1776
+ const versionsDir = join4(archive, ".versions", runTs);
1649
1777
  if (!existsSync2(configDir)) {
1650
1778
  log(`agent-lens: [${label}] source not found: ${configDir} (skipping)`);
1651
1779
  return;
1652
1780
  }
1653
- ensureDir(join3(archive, "projects"));
1654
- ensureDir(join3(archive, "settings"));
1781
+ ensureDir(join4(archive, "projects"));
1782
+ ensureDir(join4(archive, "settings"));
1655
1783
  const files = [];
1656
- const projectsDir = join3(configDir, "projects");
1784
+ const projectsDir = join4(configDir, "projects");
1657
1785
  if (existsSync2(projectsDir))
1658
1786
  walkCollectable(projectsDir, files);
1659
- const historySrc = join3(configDir, "history.jsonl");
1787
+ const historySrc = join4(configDir, "history.jsonl");
1660
1788
  if (existsSync2(historySrc))
1661
1789
  files.push(historySrc);
1662
1790
  const before = { scanned: stats.scanned, snapshots: stats.snapshots };
@@ -1669,16 +1797,16 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1669
1797
  if (excludedDirs.some((enc) => relPosix.startsWith(`projects/${enc}/`)))
1670
1798
  continue;
1671
1799
  stats.scanned++;
1672
- syncFile(src, join3(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(".jsonl"));
1800
+ syncFile(src, join4(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(".jsonl"));
1673
1801
  }
1674
1802
  for (const f of ["settings.json", "settings.local.json"]) {
1675
- const s = join3(configDir, f);
1803
+ const s = join4(configDir, f);
1676
1804
  if (!existsSync2(s))
1677
1805
  continue;
1678
- const a = join3(archive, "settings", f);
1806
+ const a = join4(archive, "settings", f);
1679
1807
  if (existsSync2(a) && !filesEqual(s, a))
1680
1808
  snapshot(a, ["settings", f], versionsDir, stats);
1681
- ensureDir(dirname2(a));
1809
+ ensureDir(dirname3(a));
1682
1810
  copyFileSync(s, a);
1683
1811
  syncMeta(a, statSync(s));
1684
1812
  }
@@ -1688,7 +1816,7 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1688
1816
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} run=${runTs} source=${label} scanned=${scanned} snapshots=${snapshots} archive=${archive}`;
1689
1817
  log(`agent-lens: ${line}`);
1690
1818
  try {
1691
- appendFileSync(join3(archiveBase, ".collect.log"), line + "\n");
1819
+ appendFileSync(join4(archiveBase, ".collect.log"), line + "\n");
1692
1820
  } catch {
1693
1821
  }
1694
1822
  }
@@ -1710,8 +1838,8 @@ function collectAll(opts = {}) {
1710
1838
  }
1711
1839
 
1712
1840
  // ../core/dist/lock.js
1713
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync } from "fs";
1714
- import { dirname as dirname3 } from "path";
1841
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
1842
+ import { dirname as dirname4 } from "path";
1715
1843
  import { hostname } from "os";
1716
1844
  function isAlive(pid) {
1717
1845
  if (!pid)
@@ -1724,10 +1852,10 @@ function isAlive(pid) {
1724
1852
  }
1725
1853
  }
1726
1854
  function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1727
- mkdirSync2(dirname3(lockPath), { recursive: true });
1855
+ mkdirSync2(dirname4(lockPath), { recursive: true });
1728
1856
  if (existsSync3(lockPath)) {
1729
1857
  try {
1730
- const held = JSON.parse(readFileSync3(lockPath, "utf8"));
1858
+ const held = JSON.parse(readFileSync4(lockPath, "utf8"));
1731
1859
  const fresh = typeof held.ts === "number" && Date.now() - held.ts < staleMs;
1732
1860
  if (fresh && isAlive(held.pid ?? 0))
1733
1861
  return null;
@@ -1741,7 +1869,7 @@ function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1741
1869
  return;
1742
1870
  released = true;
1743
1871
  try {
1744
- const cur = JSON.parse(readFileSync3(lockPath, "utf8"));
1872
+ const cur = JSON.parse(readFileSync4(lockPath, "utf8"));
1745
1873
  if (cur.pid === process.pid)
1746
1874
  unlinkSync2(lockPath);
1747
1875
  } catch {
@@ -1752,10 +1880,10 @@ function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1752
1880
  }
1753
1881
 
1754
1882
  // ../core/dist/service.js
1755
- import { spawnSync } from "child_process";
1883
+ import { spawnSync as spawnSync2 } from "child_process";
1756
1884
  import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1757
1885
  import { homedir as homedir2, platform as platform3, userInfo } from "os";
1758
- import { join as join4 } from "path";
1886
+ import { join as join5 } from "path";
1759
1887
  var DEFAULT_HOURS = [9, 13, 17, 21];
1760
1888
  function currentUser() {
1761
1889
  try {
@@ -1897,15 +2025,15 @@ ${envDict} <key>RunAtLoad</key><true/>
1897
2025
  `;
1898
2026
  }
1899
2027
  function sh(cmd, args, quiet = false) {
1900
- const r = spawnSync(cmd, args, { stdio: quiet ? "ignore" : "inherit", encoding: "utf8" });
2028
+ const r = spawnSync2(cmd, args, { stdio: quiet ? "ignore" : "inherit", encoding: "utf8" });
1901
2029
  return r.status === 0;
1902
2030
  }
1903
2031
  function have(cmd) {
1904
- const r = spawnSync(cmd, ["--version"], { stdio: "ignore" });
2032
+ const r = spawnSync2(cmd, ["--version"], { stdio: "ignore" });
1905
2033
  return r.status === 0 || r.error === void 0;
1906
2034
  }
1907
2035
  function systemdUnitDir() {
1908
- return join4(process.env.XDG_CONFIG_HOME || join4(homedir2(), ".config"), "systemd", "user");
2036
+ return join5(process.env.XDG_CONFIG_HOME || join5(homedir2(), ".config"), "systemd", "user");
1909
2037
  }
1910
2038
  function linuxInstall(targets, node, cli2, hours) {
1911
2039
  if (!have("systemctl")) {
@@ -1916,11 +2044,11 @@ function linuxInstall(targets, node, cli2, hours) {
1916
2044
  const dir = systemdUnitDir();
1917
2045
  mkdirSync3(dir, { recursive: true });
1918
2046
  if (targets.includes("collector")) {
1919
- writeFileSync2(join4(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2));
1920
- writeFileSync2(join4(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));
2047
+ writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2));
2048
+ writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));
1921
2049
  }
1922
2050
  if (targets.includes("server")) {
1923
- writeFileSync2(join4(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, serverEnv()));
2051
+ writeFileSync2(join5(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, serverEnv()));
1924
2052
  }
1925
2053
  sh("systemctl", ["--user", "daemon-reload"]);
1926
2054
  if (targets.includes("collector")) {
@@ -1945,12 +2073,12 @@ function linuxUninstall(targets) {
1945
2073
  if (targets.includes("collector")) {
1946
2074
  sh("systemctl", ["--user", "disable", "--now", SYSTEMD_COLLECTOR_TIMER], true);
1947
2075
  for (const u of [SYSTEMD_COLLECTOR_TIMER, SYSTEMD_COLLECTOR_SERVICE])
1948
- rmSync(join4(dir, u), { force: true });
2076
+ rmSync(join5(dir, u), { force: true });
1949
2077
  sh("systemctl", ["--user", "reset-failed", SYSTEMD_COLLECTOR_SERVICE], true);
1950
2078
  }
1951
2079
  if (targets.includes("server")) {
1952
2080
  sh("systemctl", ["--user", "disable", "--now", SYSTEMD_SERVER_SERVICE], true);
1953
- rmSync(join4(dir, SYSTEMD_SERVER_SERVICE), { force: true });
2081
+ rmSync(join5(dir, SYSTEMD_SERVER_SERVICE), { force: true });
1954
2082
  sh("systemctl", ["--user", "reset-failed", SYSTEMD_SERVER_SERVICE], true);
1955
2083
  }
1956
2084
  sh("systemctl", ["--user", "daemon-reload"]);
@@ -1965,7 +2093,7 @@ function linuxStatus(targets) {
1965
2093
  sh("systemctl", ["--user", "status", SYSTEMD_SERVER_SERVICE, "--no-pager"]);
1966
2094
  }
1967
2095
  function launchdPlistPath(label) {
1968
- return join4(homedir2(), "Library", "LaunchAgents", `${label}.plist`);
2096
+ return join5(homedir2(), "Library", "LaunchAgents", `${label}.plist`);
1969
2097
  }
1970
2098
  function macBootstrap(plist) {
1971
2099
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
@@ -1979,18 +2107,18 @@ function macBootout(label, plist) {
1979
2107
  rmSync(plist, { force: true });
1980
2108
  }
1981
2109
  function macInstall(targets, node, cli2, hours) {
1982
- const agents = join4(homedir2(), "Library", "LaunchAgents");
2110
+ const agents = join5(homedir2(), "Library", "LaunchAgents");
1983
2111
  mkdirSync3(agents, { recursive: true });
1984
2112
  const dataDir = resolveDataDir(findRepoRoot());
1985
2113
  if (targets.includes("collector")) {
1986
2114
  const plist = launchdPlistPath(LAUNCHD_COLLECTOR_LABEL);
1987
- writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join4(dataDir, "schedule.log")));
2115
+ writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join5(dataDir, "schedule.log")));
1988
2116
  macBootstrap(plist);
1989
2117
  console.log(`agent-lens service: installed collector agent ${LAUNCHD_COLLECTOR_LABEL} (hours ${hours.join(",")})`);
1990
2118
  }
1991
2119
  if (targets.includes("server")) {
1992
2120
  const plist = launchdPlistPath(LAUNCHD_SERVER_LABEL);
1993
- writeFileSync2(plist, launchdServerPlist(node, cli2, join4(dataDir, "server.log"), serverEnv()));
2121
+ writeFileSync2(plist, launchdServerPlist(node, cli2, join5(dataDir, "server.log"), serverEnv()));
1994
2122
  macBootstrap(plist);
1995
2123
  console.log(`agent-lens service: installed server agent ${LAUNCHD_SERVER_LABEL}`);
1996
2124
  }
@@ -2080,8 +2208,8 @@ function runService(action, params) {
2080
2208
  }
2081
2209
 
2082
2210
  // ../ingest/dist/run.js
2083
- import { readFileSync as readFileSync5, statSync as statSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
2084
- import { dirname as dirname5, join as join8 } from "path";
2211
+ import { readFileSync as readFileSync6, statSync as statSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
2212
+ import { dirname as dirname6, join as join9 } from "path";
2085
2213
 
2086
2214
  // ../ingest/dist/db.js
2087
2215
  import Database from "better-sqlite3";
@@ -2138,7 +2266,7 @@ function resetSchema(db) {
2138
2266
 
2139
2267
  // ../ingest/dist/adapters/claude-code.js
2140
2268
  import { readdirSync as readdirSync2 } from "fs";
2141
- import { basename as basename2, join as join5 } from "path";
2269
+ import { basename as basename2, join as join6 } from "path";
2142
2270
  function flattenText(content) {
2143
2271
  if (content == null)
2144
2272
  return null;
@@ -2221,7 +2349,7 @@ function recurseJsonl(dir, out) {
2221
2349
  return;
2222
2350
  }
2223
2351
  for (const e of entries) {
2224
- const p = join5(dir, e.name);
2352
+ const p = join6(dir, e.name);
2225
2353
  if (e.isDirectory())
2226
2354
  recurseJsonl(p, out);
2227
2355
  else if (e.isFile() && e.name.endsWith(".jsonl"))
@@ -2252,12 +2380,12 @@ var ClaudeCodeAdapter = class {
2252
2380
  });
2253
2381
  }
2254
2382
  };
2255
- addFrom(join5(archiveRoot, "projects"), false);
2256
- const versionsRoot = join5(archiveRoot, ".versions");
2383
+ addFrom(join6(archiveRoot, "projects"), false);
2384
+ const versionsRoot = join6(archiveRoot, ".versions");
2257
2385
  try {
2258
2386
  for (const ts of readdirSync2(versionsRoot, { withFileTypes: true })) {
2259
2387
  if (ts.isDirectory())
2260
- addFrom(join5(versionsRoot, ts.name, "projects"), true);
2388
+ addFrom(join6(versionsRoot, ts.name, "projects"), true);
2261
2389
  }
2262
2390
  } catch {
2263
2391
  }
@@ -3311,7 +3439,7 @@ function deriveFileChanges(db, dirty) {
3311
3439
  import { existsSync as existsSync4 } from "fs";
3312
3440
  import { homedir as homedir3 } from "os";
3313
3441
  import { createHash as createHash4 } from "crypto";
3314
- import { dirname as dirname4, join as join6 } from "path";
3442
+ import { dirname as dirname5, join as join7 } from "path";
3315
3443
  function projectId(agentId, path3) {
3316
3444
  return createHash4("sha1").update(`${agentId}\0${path3}`).digest("hex").slice(0, 16);
3317
3445
  }
@@ -3319,9 +3447,9 @@ var strip = (p) => p.length > 1 ? p.replace(/\/$/, "") : p;
3319
3447
  function nearestGitRoot(path3, home) {
3320
3448
  let cur = strip(path3);
3321
3449
  while (cur !== home && cur !== "/" && cur.length > 0) {
3322
- if (existsSync4(join6(cur, ".git")))
3450
+ if (existsSync4(join7(cur, ".git")))
3323
3451
  return cur;
3324
- const next = dirname4(cur);
3452
+ const next = dirname5(cur);
3325
3453
  if (next === cur)
3326
3454
  break;
3327
3455
  cur = next;
@@ -3348,12 +3476,12 @@ function canonicalizeProjects(db, opts = {}) {
3348
3476
  if (git) {
3349
3477
  out = git;
3350
3478
  } else if (existsSync4(p) || !withMains.has(byPath.get(p)?.id ?? "")) {
3351
- for (let cur = dirname4(p); cur !== "/" && cur !== home && cur.length > 0; cur = dirname4(cur)) {
3479
+ for (let cur = dirname5(p); cur !== "/" && cur !== home && cur.length > 0; cur = dirname5(cur)) {
3352
3480
  if (byPath.has(cur)) {
3353
3481
  out = canon(cur);
3354
3482
  break;
3355
3483
  }
3356
- if (dirname4(cur) === cur)
3484
+ if (dirname5(cur) === cur)
3357
3485
  break;
3358
3486
  }
3359
3487
  }
@@ -3861,8 +3989,8 @@ function ingestFile(db, stmts, adapter, file, lines, meta, now2, stats) {
3861
3989
  import { basename as basename3 } from "path";
3862
3990
 
3863
3991
  // ../ingest/dist/sidecar.js
3864
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3865
- import { join as join7 } from "path";
3992
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3993
+ import { join as join8 } from "path";
3866
3994
 
3867
3995
  // ../ingest/dist/redact.js
3868
3996
  import { createHash as createHash6, randomBytes } from "crypto";
@@ -3937,7 +4065,7 @@ function walk(dir, subdir, inSubdir, matchName, out) {
3937
4065
  return;
3938
4066
  }
3939
4067
  for (const e of entries) {
3940
- const p = join7(dir, e.name);
4068
+ const p = join8(dir, e.name);
3941
4069
  if (e.isDirectory())
3942
4070
  walk(p, subdir, inSubdir || e.name === subdir, matchName, out);
3943
4071
  else if (e.isFile() && inSubdir && matchName(e.name))
@@ -3946,11 +4074,11 @@ function walk(dir, subdir, inSubdir, matchName, out) {
3946
4074
  }
3947
4075
  function discover(sourceArchiveDir, subdir, matchName) {
3948
4076
  const out = [];
3949
- walk(join7(sourceArchiveDir, "projects"), subdir, false, matchName, out);
4077
+ walk(join8(sourceArchiveDir, "projects"), subdir, false, matchName, out);
3950
4078
  try {
3951
- for (const ts of readdirSync3(join7(sourceArchiveDir, ".versions"), { withFileTypes: true }))
4079
+ for (const ts of readdirSync3(join8(sourceArchiveDir, ".versions"), { withFileTypes: true }))
3952
4080
  if (ts.isDirectory())
3953
- walk(join7(sourceArchiveDir, ts.name, "projects"), subdir, false, matchName, out);
4081
+ walk(join8(sourceArchiveDir, ts.name, "projects"), subdir, false, matchName, out);
3954
4082
  } catch {
3955
4083
  }
3956
4084
  return out;
@@ -3977,7 +4105,7 @@ function ingestSidecars(db, sourceArchiveDir, excludedDirs, now2, stats, full, c
3977
4105
  }
3978
4106
  let buf;
3979
4107
  try {
3980
- buf = readFileSync4(path3);
4108
+ buf = readFileSync5(path3);
3981
4109
  } catch {
3982
4110
  stats.malformed++;
3983
4111
  continue;
@@ -4161,7 +4289,7 @@ function shouldReingest(path3, size, mtimeMs, prev) {
4161
4289
  if (prev && prev.size === size && prev.mtime_ms === mtimeMs)
4162
4290
  return { action: "skip-stat" };
4163
4291
  const small = size <= STREAM_THRESHOLD;
4164
- const buf = small ? readFileSync5(path3) : null;
4292
+ const buf = small ? readFileSync6(path3) : null;
4165
4293
  const hash2 = small ? sha256(buf) : sha256File(path3);
4166
4294
  if (prev && prev.sha256 === hash2)
4167
4295
  return { action: "skip-hash", hash: hash2 };
@@ -4176,10 +4304,16 @@ function printIngestReport(db, dbPath, r) {
4176
4304
  const usageRows = db.prepare("SELECT model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens FROM token_usage").all();
4177
4305
  let cost = 0;
4178
4306
  let totalTokens = 0;
4307
+ const unpriced2 = /* @__PURE__ */ new Map();
4179
4308
  for (const u of usageRows) {
4180
4309
  cost += costForUsage(u.model, u);
4181
4310
  totalTokens += u.input_tokens + u.output_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens;
4311
+ if (u.model && u.model !== SYNTHETIC_MODEL && !rateForModel(u.model)) {
4312
+ unpriced2.set(u.model, (unpriced2.get(u.model) ?? 0) + 1);
4313
+ }
4182
4314
  }
4315
+ const unpricedLine = unpriced2.size ? `
4316
+ unpriced=${unpriced2.size} model(s): ` + [...unpriced2].map(([m, n]) => `${m} (${n} records)`).join(", ") + ` \u2014 cost above excludes them; add rates under "pricing" in agent-lens.config.json` : "";
4183
4317
  const wfRuns = count("SELECT COUNT(*) n FROM workflow_results");
4184
4318
  const metaRows = count("SELECT COUNT(*) n FROM session_meta");
4185
4319
  const trRows = count("SELECT COUNT(*) n FROM tool_results");
@@ -4194,17 +4328,18 @@ function printIngestReport(db, dbPath, r) {
4194
4328
  tool_results=${trRows} (upserted=${r.trStats.upserted} skipped=${r.trStats.skipped} malformed=${r.trStats.malformed})
4195
4329
  findings=${r.detected.count} (${sevLine})
4196
4330
  file_changes=${r.fileChanges.count}
4197
- tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}`);
4331
+ tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}` + unpricedLine);
4198
4332
  }
4199
4333
  function runIngest(argv = process.argv.slice(2)) {
4200
4334
  const args = parseIngestArgs(argv);
4201
4335
  const archiveRoot = args.archive || resolveArchiveDir();
4202
4336
  const { path: dbPath } = resolveDbPath(args.db);
4337
+ installPricing();
4203
4338
  if (!existsSync5(archiveRoot)) {
4204
4339
  console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);
4205
4340
  process.exit(1);
4206
4341
  }
4207
- mkdirSync4(dirname5(dbPath), { recursive: true });
4342
+ mkdirSync4(dirname6(dbPath), { recursive: true });
4208
4343
  const db = args.full ? openRaw(dbPath) : openDb(dbPath);
4209
4344
  if (args.full) {
4210
4345
  resetSchema(db);
@@ -4237,7 +4372,7 @@ function runIngest(argv = process.argv.slice(2)) {
4237
4372
  }
4238
4373
  stmts.insAgent.run(adapter.agentId, adapter.agentName);
4239
4374
  stmts.insSource.run({ id: source.label, label: source.label, agent_id: adapter.agentId, config_dir: source.configDir });
4240
- let files = adapter.discover(join8(archiveRoot, source.label), source.label);
4375
+ let files = adapter.discover(join9(archiveRoot, source.label), source.label);
4241
4376
  if (excludedDirs.length)
4242
4377
  files = files.filter((f) => !isExcludedArchivePath(f.path, excludedDirs));
4243
4378
  files.sort((a, b) => Number(a.isVersion) - Number(b.isVersion));
@@ -4267,9 +4402,9 @@ function runIngest(argv = process.argv.slice(2)) {
4267
4402
  ingestFile(db, stmts, adapter, file, lines, { size: st.size, mtimeMs, hash: decision.hash }, now2, stats);
4268
4403
  dirty.add(file.sessionId);
4269
4404
  }
4270
- ingestWorkflowResults(db, join8(archiveRoot, source.label), source.label, excludedDirs, now2, wfStats, args.full);
4271
- ingestSubagentMeta(db, join8(archiveRoot, source.label), source.label, excludedDirs, now2, metaStats, args.full);
4272
- ingestToolResults(db, join8(archiveRoot, source.label), excludedDirs, now2, trStats, args.full);
4405
+ ingestWorkflowResults(db, join9(archiveRoot, source.label), source.label, excludedDirs, now2, wfStats, args.full);
4406
+ ingestSubagentMeta(db, join9(archiveRoot, source.label), source.label, excludedDirs, now2, metaStats, args.full);
4407
+ ingestToolResults(db, join9(archiveRoot, source.label), excludedDirs, now2, trStats, args.full);
4273
4408
  }
4274
4409
  const expanded = rebuildDerived(db, args.full ? null : dirty);
4275
4410
  canonicalizeProjects(db);
@@ -4301,8 +4436,8 @@ function runMetrics(argv = process.argv.slice(2)) {
4301
4436
 
4302
4437
  // ../server/dist/run.js
4303
4438
  import { existsSync as existsSync8 } from "fs";
4304
- import { dirname as dirname6 } from "path";
4305
- import { fileURLToPath as fileURLToPath2 } from "url";
4439
+ import { dirname as dirname7 } from "path";
4440
+ import { fileURLToPath as fileURLToPath3 } from "url";
4306
4441
 
4307
4442
  // ../server/dist/db.js
4308
4443
  import Database2 from "better-sqlite3";
@@ -4373,6 +4508,9 @@ function splitOf(rows) {
4373
4508
  function tokensOf(split) {
4374
4509
  return split.input + split.output + split.cache_creation + split.cache_read;
4375
4510
  }
4511
+ function unpricedOf(rows) {
4512
+ return unpricedModels(rows.filter((u) => u.i || u.o || u.cw || u.cr).map((u) => u.model));
4513
+ }
4376
4514
  function costOf(rows) {
4377
4515
  let cost = 0;
4378
4516
  for (const u of rows) {
@@ -4535,7 +4673,14 @@ function attachSessionCost(db, rows) {
4535
4673
  return rows.map(({ ai_title, slug, ...r }) => {
4536
4674
  const u = usageBySession.get(r.id) ?? [];
4537
4675
  const split = splitOf(u);
4538
- return { ...r, tokens: tokensOf(split), token_split: split, cost: costOf(u), title: ai_title || slug || null };
4676
+ return {
4677
+ ...r,
4678
+ tokens: tokensOf(split),
4679
+ token_split: split,
4680
+ cost: costOf(u),
4681
+ unpriced_models: unpricedOf(u),
4682
+ title: ai_title || slug || null
4683
+ };
4539
4684
  });
4540
4685
  }
4541
4686
  function loadToolCalls(db, id) {
@@ -4680,6 +4825,7 @@ function withSessionTotals(db, session, id, toolRows) {
4680
4825
  tokens: tokensOf(split),
4681
4826
  token_split: split,
4682
4827
  cost: costOf(usage),
4828
+ unpriced_models: unpricedOf(usage),
4683
4829
  title: session.ai_title || session.slug || null,
4684
4830
  tool_call_count: toolRows.length,
4685
4831
  tool_error_count,
@@ -5191,12 +5337,12 @@ function dashboardOverview(db, f) {
5191
5337
  GROUP BY t.model`, ...w.params);
5192
5338
  const tokens = zeroSplit();
5193
5339
  let cost = 0;
5194
- const unpriced = [];
5340
+ const unpriced2 = [];
5195
5341
  for (const u of usage) {
5196
5342
  addUsage(tokens, u);
5197
5343
  cost += costForUsage(u.model, usageForCost(u));
5198
5344
  if (u.model && !rateForModel(u.model) && (u.i || u.o || u.cw || u.cr))
5199
- unpriced.push(u.model);
5345
+ unpriced2.push(u.model);
5200
5346
  }
5201
5347
  const totalTokens = tokens.input + tokens.output + tokens.cache_creation + tokens.cache_read;
5202
5348
  const durs = queryAll(db, `SELECT tn.duration_ms d FROM turns tn JOIN sessions s ON s.id = tn.session_id
@@ -5216,7 +5362,7 @@ function dashboardOverview(db, f) {
5216
5362
  total_tokens: totalTokens,
5217
5363
  cache_read_ratio: totalTokens ? tokens.cache_read / totalTokens : 0,
5218
5364
  cost: Number(cost.toFixed(4)),
5219
- unpriced_models: [...new Set(unpriced)].sort(),
5365
+ unpriced_models: [...new Set(unpriced2)].sort(),
5220
5366
  turn_duration_ms: { p50: pct(durs, 50), p95: pct(durs, 95), count: durs.length },
5221
5367
  session_duration_ms: { p50: pct(sessDurs, 50), p95: pct(sessDurs, 95), count: sessDurs.length },
5222
5368
  workflows: workflowAgg(db, f)
@@ -5384,7 +5530,7 @@ function dashboardBreakdowns(db, f) {
5384
5530
 
5385
5531
  // ../server/dist/refresh.js
5386
5532
  import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
5387
- import { join as join9 } from "path";
5533
+ import { join as join10 } from "path";
5388
5534
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
5389
5535
  function originAllowed(origin) {
5390
5536
  if (!origin)
@@ -5405,7 +5551,7 @@ function writeBlocked(headers) {
5405
5551
  }
5406
5552
  function runRefresh() {
5407
5553
  const dataDir = resolveDataDir(findRepoRoot());
5408
- const lock = acquireLock(join9(dataDir, ".agent-lens.lock"));
5554
+ const lock = acquireLock(join10(dataDir, ".agent-lens.lock"));
5409
5555
  if (!lock)
5410
5556
  return null;
5411
5557
  try {
@@ -5487,6 +5633,75 @@ function listMutes(db) {
5487
5633
  return db.prepare("SELECT rule_id, scope, scope_id, note, muted_at FROM muted_rules ORDER BY muted_at DESC").all();
5488
5634
  }
5489
5635
 
5636
+ // ../server/dist/about.js
5637
+ import { statSync as statSync4 } from "fs";
5638
+ function storage(db, dbPath) {
5639
+ const row = tableExists(db, "ingest_state") ? queryGet(db, "SELECT COUNT(*) files, SUM(size) bytes, MAX(ingested_at) last FROM ingest_state") : void 0;
5640
+ let db_bytes = null;
5641
+ try {
5642
+ db_bytes = statSync4(dbPath).size;
5643
+ } catch {
5644
+ db_bytes = null;
5645
+ }
5646
+ return {
5647
+ db_bytes,
5648
+ archive_bytes: row?.bytes ?? 0,
5649
+ // SUM over zero rows is NULL, not 0
5650
+ archive_files: row?.files ?? 0,
5651
+ last_ingested: row?.last ?? null
5652
+ };
5653
+ }
5654
+ function unpriced(db) {
5655
+ if (!tableExists(db, "token_usage"))
5656
+ return [];
5657
+ const rows = queryAll(db, `SELECT DISTINCT model FROM token_usage
5658
+ WHERE input_tokens + output_tokens + cache_creation_input_tokens + cache_read_input_tokens > 0`);
5659
+ return unpricedModels(rows.map((r) => r.model));
5660
+ }
5661
+ function engineVersion(db, table, sql, expected) {
5662
+ const in_data = tableExists(db, table) ? queryAll(db, sql).map((r) => r.v).filter((v) => Number.isFinite(v)) : [];
5663
+ return { expected, in_data, stale: in_data.some((v) => v < expected) };
5664
+ }
5665
+ function about(db, ctx) {
5666
+ const version2 = resolveVersion();
5667
+ const schema = schemaStatus(db);
5668
+ const dataDir = resolveDataDir(ctx.repoRoot);
5669
+ const retention = resolveRetention();
5670
+ return {
5671
+ versions: {
5672
+ app: version2.version,
5673
+ app_source: version2.source,
5674
+ schema: schema.db_version,
5675
+ schema_expected: schema.expected,
5676
+ schema_stale: schema.stale,
5677
+ detector: engineVersion(db, "findings", "SELECT DISTINCT detector_version v FROM findings ORDER BY v", DETECTOR_VERSION),
5678
+ classifier: engineVersion(db, "classifications", "SELECT DISTINCT classifier_version v FROM classifications ORDER BY v", CLASSIFIER_VERSION)
5679
+ },
5680
+ paths: {
5681
+ config_file: resolveConfigFile(ctx.repoRoot, dataDir),
5682
+ data_dir: { path: dataDir, origin: process.env.AGENT_LENS_DATA ? "env" : "default" },
5683
+ // archive and triage_db are "fixed": ADR-021 makes them non-relocatable on their own, so they
5684
+ // are derived here with the same core helpers the CLI uses rather than read from config.
5685
+ archive: { path: resolveArchiveDir(ctx.repoRoot), origin: "fixed" },
5686
+ db: { path: ctx.db.path, origin: ctx.db.origin },
5687
+ triage_db: { path: triageDbFor(ctx.db.path), origin: "fixed" }
5688
+ },
5689
+ server: { host: ctx.host, port: ctx.port, loopback_only: ctx.loopbackOnly },
5690
+ retention: { versions_keep_days: retention.keepDays, origin: retention.origin },
5691
+ pricing: {
5692
+ origin: ctx.pricing.origin,
5693
+ models: Object.keys(ctx.pricing.table).length,
5694
+ applied: ctx.pricing.applied,
5695
+ invalid: ctx.pricing.invalid,
5696
+ unpriced: unpriced(db)
5697
+ },
5698
+ sources: loadSources().map((s) => ({ label: s.label, agent: s.agent, config_dir: s.configDir })),
5699
+ // storage() already carries last_ingested from the same MAX(ingested_at) it aggregates over —
5700
+ // reusing it avoids a second query AND keeps the tableExists guard that lastIngested() lacks.
5701
+ storage: storage(db, ctx.db.path)
5702
+ };
5703
+ }
5704
+
5490
5705
  // ../server/dist/prefs.js
5491
5706
  var PREFS_SCHEMA_SQL = (
5492
5707
  /* sql */
@@ -5548,8 +5763,20 @@ async function createApp(db, opts = {}) {
5548
5763
  };
5549
5764
  app.get("/api/health", async () => {
5550
5765
  const s = schemaStatus(db);
5551
- return { ok: true, last_ingested: lastIngested(db), schema_version: s.db_version, schema_stale: s.stale };
5766
+ const v = resolveVersion();
5767
+ return {
5768
+ ok: true,
5769
+ last_ingested: lastIngested(db),
5770
+ schema_version: s.db_version,
5771
+ schema_stale: s.stale,
5772
+ version: v.version,
5773
+ version_source: v.source
5774
+ };
5552
5775
  });
5776
+ if (opts.about) {
5777
+ const ctx = opts.about;
5778
+ app.get("/api/about", async () => about(db, ctx));
5779
+ }
5553
5780
  app.post("/api/refresh", async (req, reply) => {
5554
5781
  if (writeBlocked(req.headers)) {
5555
5782
  return reply.code(403).send({ error: { code: "FORBIDDEN_ORIGIN", message: "cross-origin refresh blocked" } });
@@ -5756,10 +5983,11 @@ async function createApp(db, opts = {}) {
5756
5983
 
5757
5984
  // ../server/dist/run.js
5758
5985
  async function startServer(overrides = {}) {
5759
- const here = dirname6(fileURLToPath2(import.meta.url));
5986
+ const here = dirname7(fileURLToPath3(import.meta.url));
5760
5987
  const repoRoot = findRepoRoot(here);
5761
- const { path: dbPath } = resolveDbPath(overrides.db);
5988
+ const { path: dbPath, origin: dbOrigin } = resolveDbPath(overrides.db);
5762
5989
  const { host, port } = resolveServerConfig({ port: overrides.port, host: overrides.host });
5990
+ const pricing = installPricing();
5763
5991
  const webDist = resolveWebDist(here, repoRoot);
5764
5992
  if (host !== "127.0.0.1" && host !== "localhost" && !process.env.AGENT_LENS_ALLOW_NONLOCAL) {
5765
5993
  console.error(`agent-lens-server: refusing to bind non-loopback host '${host}' (privacy). Set AGENT_LENS_ALLOW_NONLOCAL=1 to override.`);
@@ -5772,7 +6000,12 @@ async function startServer(overrides = {}) {
5772
6000
  const triageDbPath = triageDbFor(dbPath);
5773
6001
  const db = openReadonly(dbPath);
5774
6002
  const enforceLoopbackHost = host === "127.0.0.1" || host === "localhost";
5775
- const app = await createApp(db, { webDist, triageDbPath, enforceLoopbackHost });
6003
+ const app = await createApp(db, {
6004
+ webDist,
6005
+ triageDbPath,
6006
+ enforceLoopbackHost,
6007
+ about: { db: { path: dbPath, origin: dbOrigin }, host, port, loopbackOnly: enforceLoopbackHost, repoRoot, pricing }
6008
+ });
5776
6009
  try {
5777
6010
  await app.listen({ host, port });
5778
6011
  console.log(`agent-lens-server: http://${host}:${port} (db: ${dbPath})`);
@@ -5786,11 +6019,11 @@ async function startServer(overrides = {}) {
5786
6019
 
5787
6020
  // src/watch.ts
5788
6021
  import { existsSync as existsSync9 } from "fs";
5789
- import { join as join10 } from "path";
6022
+ import { join as join11 } from "path";
5790
6023
  import chokidar from "chokidar";
5791
6024
  async function runWatch(opts = {}) {
5792
6025
  const sources = loadSources();
5793
- const lockPath = join10(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
6026
+ const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5794
6027
  let running = false;
5795
6028
  let pending = false;
5796
6029
  const cycle = () => {
@@ -5823,7 +6056,7 @@ async function runWatch(opts = {}) {
5823
6056
  }
5824
6057
  };
5825
6058
  cycle();
5826
- const watchPaths = sources.map((s) => join10(s.configDir, "projects")).filter((p) => existsSync9(p));
6059
+ const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) => existsSync9(p));
5827
6060
  if (watchPaths.length === 0) {
5828
6061
  console.error("agent-lens watch: no source 'projects' directories found \u2014 nothing to watch");
5829
6062
  }
@@ -5850,15 +6083,10 @@ async function runWatch(opts = {}) {
5850
6083
 
5851
6084
  // src/index.ts
5852
6085
  function version() {
5853
- try {
5854
- const pkg = JSON.parse(readFileSync6(join11(dirname7(fileURLToPath3(import.meta.url)), "../package.json"), "utf8"));
5855
- return pkg.version ?? "0.0.0";
5856
- } catch {
5857
- return "0.0.0";
5858
- }
6086
+ return resolveVersion().version;
5859
6087
  }
5860
6088
  function withLock(fn) {
5861
- const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
6089
+ const lockPath = join12(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5862
6090
  const lock = acquireLock(lockPath);
5863
6091
  if (!lock) {
5864
6092
  console.error("agent-lens: another collect/ingest run is in progress \u2014 skipping");
@@ -5926,7 +6154,7 @@ cli.command(
5926
6154
  ).option("--times <hours>", "Collector cadence: comma-separated hours 0-23 (default: 9,13,17,21)").action((action, target, opts) => {
5927
6155
  const targets = parseTargets(target);
5928
6156
  const hours = action === "install" ? parseHours(opts.times) : void 0;
5929
- runService(action, { cliEntry: fileURLToPath3(import.meta.url), hours, targets });
6157
+ runService(action, { cliEntry: fileURLToPath4(import.meta.url), hours, targets });
5930
6158
  });
5931
6159
  cli.command("config", "Show the effective configuration (sources, paths, server) and where each value came from").action(() => {
5932
6160
  const repoRoot = findRepoRoot();
@@ -5943,8 +6171,8 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
5943
6171
  const { path: dbPath, origin: dbOrigin } = resolveDbPath();
5944
6172
  const archive = resolveArchiveDir(repoRoot);
5945
6173
  const triageDb = triageDbFor(dbPath);
5946
- const keepDays = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS || "90";
5947
- const keepOrigin = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS ? "env" : "default";
6174
+ const { keepDays, origin: keepOrigin } = resolveRetention();
6175
+ const pricing = resolvePricing();
5948
6176
  console.log("agent-lens config (precedence: flag > env > config file > default)\n");
5949
6177
  console.log("Paths");
5950
6178
  console.log(` config file ${configFile ?? "(none \u2014 using built-in default source)"}`);
@@ -5958,6 +6186,10 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
5958
6186
  console.log(` non-local bind ${process.env.AGENT_LENS_ALLOW_NONLOCAL ? "allowed (AGENT_LENS_ALLOW_NONLOCAL)" : "blocked (loopback only)"}`);
5959
6187
  console.log("\nRetention");
5960
6188
  console.log(` .versions keep ${keepDays} day(s) [${keepOrigin}]`);
6189
+ console.log("\nPricing");
6190
+ console.log(` models priced ${Object.keys(pricing.table).length} [${pricing.origin}]`);
6191
+ if (pricing.applied.length) console.log(` overrides ${pricing.applied.join(", ")}`);
6192
+ if (pricing.invalid.length) console.log(` ignored (bad) ${pricing.invalid.join(", ")}`);
5961
6193
  console.log("\nSources");
5962
6194
  for (const s of loadSources()) console.log(` ${s.label.padEnd(14)} ${s.configDir} (${s.agent})`);
5963
6195
  });