@swestash/agent-lens 0.9.6 → 0.10.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
  };
@@ -1377,6 +1377,12 @@ import { join as join2 } from "path";
1377
1377
  var DEFAULT_PORT = 4477;
1378
1378
  var DEFAULT_HOST = "127.0.0.1";
1379
1379
  var DEFAULT_DB_NAME = "agent-lens.db";
1380
+ var DEFAULT_VERSIONS_KEEP_DAYS = 90;
1381
+ function resolveRetention() {
1382
+ const raw = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS;
1383
+ const parsed = raw ? Number(raw) : NaN;
1384
+ return Number.isFinite(parsed) && parsed >= 0 ? { keepDays: parsed, origin: "env" } : { keepDays: DEFAULT_VERSIONS_KEEP_DAYS, origin: "default" };
1385
+ }
1380
1386
  function readConfigFile() {
1381
1387
  const repoRoot = findRepoRoot();
1382
1388
  const dataDir = resolveDataDir(repoRoot);
@@ -1436,6 +1442,59 @@ function resolveServerConfig(overrides = {}, cfg = readConfigFile()) {
1436
1442
  return { port, host, portOrigin, hostOrigin };
1437
1443
  }
1438
1444
 
1445
+ // ../core/dist/version.js
1446
+ import { spawnSync } from "child_process";
1447
+ import { readFileSync as readFileSync3 } from "fs";
1448
+ import { dirname as dirname2, join as join3, parse } from "path";
1449
+ import { fileURLToPath as fileURLToPath2 } from "url";
1450
+ var moduleDir2 = dirname2(fileURLToPath2(import.meta.url));
1451
+ function isPlaceholder(v) {
1452
+ return v.startsWith("0.0.0");
1453
+ }
1454
+ function nearestPackageVersion(dir) {
1455
+ const { root } = parse(dir);
1456
+ for (let d = dir; ; d = dirname2(d)) {
1457
+ try {
1458
+ const pkg = JSON.parse(readFileSync3(join3(d, "package.json"), "utf8"));
1459
+ if (typeof pkg.version === "string" && pkg.version)
1460
+ return pkg.version;
1461
+ } catch {
1462
+ }
1463
+ if (d === root)
1464
+ return null;
1465
+ }
1466
+ }
1467
+ function gitDescribe(cwd) {
1468
+ try {
1469
+ const r = spawnSync("git", ["describe", "--tags", "--always", "--dirty"], {
1470
+ cwd,
1471
+ encoding: "utf8",
1472
+ stdio: ["ignore", "pipe", "ignore"]
1473
+ });
1474
+ if (r.status !== 0)
1475
+ return null;
1476
+ const out = r.stdout?.trim();
1477
+ return out ? out : null;
1478
+ } catch {
1479
+ return null;
1480
+ }
1481
+ }
1482
+ function computeVersion(fromDir = moduleDir2) {
1483
+ const pkg = nearestPackageVersion(fromDir);
1484
+ if (pkg && !isPlaceholder(pkg))
1485
+ return { version: pkg, source: "npm" };
1486
+ const described = gitDescribe(fromDir);
1487
+ if (described)
1488
+ return { version: described, source: "git" };
1489
+ return { version: "unknown", source: "unknown" };
1490
+ }
1491
+ var cached = null;
1492
+ function resolveVersion() {
1493
+ if (!cached)
1494
+ cached = computeVersion();
1495
+ return cached;
1496
+ }
1497
+
1439
1498
  // ../core/dist/sources.js
1440
1499
  function loadSources() {
1441
1500
  if (process.env.CLAUDE_DIR) {
@@ -1490,7 +1549,7 @@ function encodeProjectPath(p) {
1490
1549
 
1491
1550
  // ../core/dist/collect.js
1492
1551
  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";
1552
+ import { basename, dirname as dirname3, join as join4, relative, sep } from "path";
1494
1553
  import { platform as platform2 } from "os";
1495
1554
  var POSIX = platform2() !== "win32";
1496
1555
  var DIR_MODE = 448;
@@ -1516,7 +1575,7 @@ function syncMeta(dst, src) {
1516
1575
  }
1517
1576
  }
1518
1577
  function copyWhole(src, dst, srcStat) {
1519
- ensureDir(dirname2(dst));
1578
+ ensureDir(dirname3(dst));
1520
1579
  copyFileSync(src, dst);
1521
1580
  syncMeta(dst, srcStat);
1522
1581
  }
@@ -1570,27 +1629,27 @@ function filesEqual(a, b) {
1570
1629
  return prefixEquals(a, b, sa.size);
1571
1630
  }
1572
1631
  function latestSnapshot(archive, relParts) {
1573
- const versRoot = join3(archive, ".versions");
1632
+ const versRoot = join4(archive, ".versions");
1574
1633
  if (!existsSync2(versRoot))
1575
1634
  return null;
1576
1635
  const stamps = readdirSync(versRoot).sort();
1577
1636
  for (let i = stamps.length - 1; i >= 0; i--) {
1578
- const p = join3(versRoot, stamps[i], ...relParts);
1637
+ const p = join4(versRoot, stamps[i], ...relParts);
1579
1638
  if (existsSync2(p))
1580
1639
  return p;
1581
1640
  }
1582
1641
  return null;
1583
1642
  }
1584
1643
  function snapshot(absPath, relParts, versionsDir, stats) {
1585
- const dst = join3(versionsDir, ...relParts);
1586
- ensureDir(dirname2(dst));
1644
+ const dst = join4(versionsDir, ...relParts);
1645
+ ensureDir(dirname3(dst));
1587
1646
  copyFileSync(absPath, dst);
1588
1647
  syncMeta(dst, statSync(absPath));
1589
1648
  stats.snapshots++;
1590
1649
  }
1591
1650
  function walkCollectable(dir, out, inWorkflows = false, inToolResults = false) {
1592
1651
  for (const e of readdirSync(dir, { withFileTypes: true })) {
1593
- const p = join3(dir, e.name);
1652
+ const p = join4(dir, e.name);
1594
1653
  if (e.isDirectory()) {
1595
1654
  walkCollectable(p, out, inWorkflows || e.name === "workflows", inToolResults || e.name === "tool-results");
1596
1655
  } else if (e.isFile() && (e.name.endsWith(".jsonl") || inWorkflows || inToolResults || e.name.endsWith(".meta.json"))) {
@@ -1631,7 +1690,7 @@ function removeCredentials(dir) {
1631
1690
  if (!existsSync2(dir))
1632
1691
  return;
1633
1692
  for (const e of readdirSync(dir, { withFileTypes: true })) {
1634
- const p = join3(dir, e.name);
1693
+ const p = join4(dir, e.name);
1635
1694
  if (e.isDirectory())
1636
1695
  removeCredentials(p);
1637
1696
  else if (e.name === ".credentials.json") {
@@ -1644,19 +1703,19 @@ function removeCredentials(dir) {
1644
1703
  }
1645
1704
  function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1646
1705
  const { label, configDir } = source;
1647
- const archive = join3(archiveBase, label);
1648
- const versionsDir = join3(archive, ".versions", runTs);
1706
+ const archive = join4(archiveBase, label);
1707
+ const versionsDir = join4(archive, ".versions", runTs);
1649
1708
  if (!existsSync2(configDir)) {
1650
1709
  log(`agent-lens: [${label}] source not found: ${configDir} (skipping)`);
1651
1710
  return;
1652
1711
  }
1653
- ensureDir(join3(archive, "projects"));
1654
- ensureDir(join3(archive, "settings"));
1712
+ ensureDir(join4(archive, "projects"));
1713
+ ensureDir(join4(archive, "settings"));
1655
1714
  const files = [];
1656
- const projectsDir = join3(configDir, "projects");
1715
+ const projectsDir = join4(configDir, "projects");
1657
1716
  if (existsSync2(projectsDir))
1658
1717
  walkCollectable(projectsDir, files);
1659
- const historySrc = join3(configDir, "history.jsonl");
1718
+ const historySrc = join4(configDir, "history.jsonl");
1660
1719
  if (existsSync2(historySrc))
1661
1720
  files.push(historySrc);
1662
1721
  const before = { scanned: stats.scanned, snapshots: stats.snapshots };
@@ -1669,16 +1728,16 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1669
1728
  if (excludedDirs.some((enc) => relPosix.startsWith(`projects/${enc}/`)))
1670
1729
  continue;
1671
1730
  stats.scanned++;
1672
- syncFile(src, join3(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(".jsonl"));
1731
+ syncFile(src, join4(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(".jsonl"));
1673
1732
  }
1674
1733
  for (const f of ["settings.json", "settings.local.json"]) {
1675
- const s = join3(configDir, f);
1734
+ const s = join4(configDir, f);
1676
1735
  if (!existsSync2(s))
1677
1736
  continue;
1678
- const a = join3(archive, "settings", f);
1737
+ const a = join4(archive, "settings", f);
1679
1738
  if (existsSync2(a) && !filesEqual(s, a))
1680
1739
  snapshot(a, ["settings", f], versionsDir, stats);
1681
- ensureDir(dirname2(a));
1740
+ ensureDir(dirname3(a));
1682
1741
  copyFileSync(s, a);
1683
1742
  syncMeta(a, statSync(s));
1684
1743
  }
@@ -1688,7 +1747,7 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1688
1747
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} run=${runTs} source=${label} scanned=${scanned} snapshots=${snapshots} archive=${archive}`;
1689
1748
  log(`agent-lens: ${line}`);
1690
1749
  try {
1691
- appendFileSync(join3(archiveBase, ".collect.log"), line + "\n");
1750
+ appendFileSync(join4(archiveBase, ".collect.log"), line + "\n");
1692
1751
  } catch {
1693
1752
  }
1694
1753
  }
@@ -1710,8 +1769,8 @@ function collectAll(opts = {}) {
1710
1769
  }
1711
1770
 
1712
1771
  // ../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";
1772
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
1773
+ import { dirname as dirname4 } from "path";
1715
1774
  import { hostname } from "os";
1716
1775
  function isAlive(pid) {
1717
1776
  if (!pid)
@@ -1724,10 +1783,10 @@ function isAlive(pid) {
1724
1783
  }
1725
1784
  }
1726
1785
  function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1727
- mkdirSync2(dirname3(lockPath), { recursive: true });
1786
+ mkdirSync2(dirname4(lockPath), { recursive: true });
1728
1787
  if (existsSync3(lockPath)) {
1729
1788
  try {
1730
- const held = JSON.parse(readFileSync3(lockPath, "utf8"));
1789
+ const held = JSON.parse(readFileSync4(lockPath, "utf8"));
1731
1790
  const fresh = typeof held.ts === "number" && Date.now() - held.ts < staleMs;
1732
1791
  if (fresh && isAlive(held.pid ?? 0))
1733
1792
  return null;
@@ -1741,7 +1800,7 @@ function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1741
1800
  return;
1742
1801
  released = true;
1743
1802
  try {
1744
- const cur = JSON.parse(readFileSync3(lockPath, "utf8"));
1803
+ const cur = JSON.parse(readFileSync4(lockPath, "utf8"));
1745
1804
  if (cur.pid === process.pid)
1746
1805
  unlinkSync2(lockPath);
1747
1806
  } catch {
@@ -1752,10 +1811,10 @@ function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1752
1811
  }
1753
1812
 
1754
1813
  // ../core/dist/service.js
1755
- import { spawnSync } from "child_process";
1814
+ import { spawnSync as spawnSync2 } from "child_process";
1756
1815
  import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1757
1816
  import { homedir as homedir2, platform as platform3, userInfo } from "os";
1758
- import { join as join4 } from "path";
1817
+ import { join as join5 } from "path";
1759
1818
  var DEFAULT_HOURS = [9, 13, 17, 21];
1760
1819
  function currentUser() {
1761
1820
  try {
@@ -1897,15 +1956,15 @@ ${envDict} <key>RunAtLoad</key><true/>
1897
1956
  `;
1898
1957
  }
1899
1958
  function sh(cmd, args, quiet = false) {
1900
- const r = spawnSync(cmd, args, { stdio: quiet ? "ignore" : "inherit", encoding: "utf8" });
1959
+ const r = spawnSync2(cmd, args, { stdio: quiet ? "ignore" : "inherit", encoding: "utf8" });
1901
1960
  return r.status === 0;
1902
1961
  }
1903
1962
  function have(cmd) {
1904
- const r = spawnSync(cmd, ["--version"], { stdio: "ignore" });
1963
+ const r = spawnSync2(cmd, ["--version"], { stdio: "ignore" });
1905
1964
  return r.status === 0 || r.error === void 0;
1906
1965
  }
1907
1966
  function systemdUnitDir() {
1908
- return join4(process.env.XDG_CONFIG_HOME || join4(homedir2(), ".config"), "systemd", "user");
1967
+ return join5(process.env.XDG_CONFIG_HOME || join5(homedir2(), ".config"), "systemd", "user");
1909
1968
  }
1910
1969
  function linuxInstall(targets, node, cli2, hours) {
1911
1970
  if (!have("systemctl")) {
@@ -1916,11 +1975,11 @@ function linuxInstall(targets, node, cli2, hours) {
1916
1975
  const dir = systemdUnitDir();
1917
1976
  mkdirSync3(dir, { recursive: true });
1918
1977
  if (targets.includes("collector")) {
1919
- writeFileSync2(join4(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2));
1920
- writeFileSync2(join4(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));
1978
+ writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2));
1979
+ writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));
1921
1980
  }
1922
1981
  if (targets.includes("server")) {
1923
- writeFileSync2(join4(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, serverEnv()));
1982
+ writeFileSync2(join5(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, serverEnv()));
1924
1983
  }
1925
1984
  sh("systemctl", ["--user", "daemon-reload"]);
1926
1985
  if (targets.includes("collector")) {
@@ -1945,12 +2004,12 @@ function linuxUninstall(targets) {
1945
2004
  if (targets.includes("collector")) {
1946
2005
  sh("systemctl", ["--user", "disable", "--now", SYSTEMD_COLLECTOR_TIMER], true);
1947
2006
  for (const u of [SYSTEMD_COLLECTOR_TIMER, SYSTEMD_COLLECTOR_SERVICE])
1948
- rmSync(join4(dir, u), { force: true });
2007
+ rmSync(join5(dir, u), { force: true });
1949
2008
  sh("systemctl", ["--user", "reset-failed", SYSTEMD_COLLECTOR_SERVICE], true);
1950
2009
  }
1951
2010
  if (targets.includes("server")) {
1952
2011
  sh("systemctl", ["--user", "disable", "--now", SYSTEMD_SERVER_SERVICE], true);
1953
- rmSync(join4(dir, SYSTEMD_SERVER_SERVICE), { force: true });
2012
+ rmSync(join5(dir, SYSTEMD_SERVER_SERVICE), { force: true });
1954
2013
  sh("systemctl", ["--user", "reset-failed", SYSTEMD_SERVER_SERVICE], true);
1955
2014
  }
1956
2015
  sh("systemctl", ["--user", "daemon-reload"]);
@@ -1965,7 +2024,7 @@ function linuxStatus(targets) {
1965
2024
  sh("systemctl", ["--user", "status", SYSTEMD_SERVER_SERVICE, "--no-pager"]);
1966
2025
  }
1967
2026
  function launchdPlistPath(label) {
1968
- return join4(homedir2(), "Library", "LaunchAgents", `${label}.plist`);
2027
+ return join5(homedir2(), "Library", "LaunchAgents", `${label}.plist`);
1969
2028
  }
1970
2029
  function macBootstrap(plist) {
1971
2030
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
@@ -1979,18 +2038,18 @@ function macBootout(label, plist) {
1979
2038
  rmSync(plist, { force: true });
1980
2039
  }
1981
2040
  function macInstall(targets, node, cli2, hours) {
1982
- const agents = join4(homedir2(), "Library", "LaunchAgents");
2041
+ const agents = join5(homedir2(), "Library", "LaunchAgents");
1983
2042
  mkdirSync3(agents, { recursive: true });
1984
2043
  const dataDir = resolveDataDir(findRepoRoot());
1985
2044
  if (targets.includes("collector")) {
1986
2045
  const plist = launchdPlistPath(LAUNCHD_COLLECTOR_LABEL);
1987
- writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join4(dataDir, "schedule.log")));
2046
+ writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join5(dataDir, "schedule.log")));
1988
2047
  macBootstrap(plist);
1989
2048
  console.log(`agent-lens service: installed collector agent ${LAUNCHD_COLLECTOR_LABEL} (hours ${hours.join(",")})`);
1990
2049
  }
1991
2050
  if (targets.includes("server")) {
1992
2051
  const plist = launchdPlistPath(LAUNCHD_SERVER_LABEL);
1993
- writeFileSync2(plist, launchdServerPlist(node, cli2, join4(dataDir, "server.log"), serverEnv()));
2052
+ writeFileSync2(plist, launchdServerPlist(node, cli2, join5(dataDir, "server.log"), serverEnv()));
1994
2053
  macBootstrap(plist);
1995
2054
  console.log(`agent-lens service: installed server agent ${LAUNCHD_SERVER_LABEL}`);
1996
2055
  }
@@ -2080,8 +2139,8 @@ function runService(action, params) {
2080
2139
  }
2081
2140
 
2082
2141
  // ../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";
2142
+ import { readFileSync as readFileSync6, statSync as statSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
2143
+ import { dirname as dirname6, join as join9 } from "path";
2085
2144
 
2086
2145
  // ../ingest/dist/db.js
2087
2146
  import Database from "better-sqlite3";
@@ -2138,7 +2197,7 @@ function resetSchema(db) {
2138
2197
 
2139
2198
  // ../ingest/dist/adapters/claude-code.js
2140
2199
  import { readdirSync as readdirSync2 } from "fs";
2141
- import { basename as basename2, join as join5 } from "path";
2200
+ import { basename as basename2, join as join6 } from "path";
2142
2201
  function flattenText(content) {
2143
2202
  if (content == null)
2144
2203
  return null;
@@ -2221,7 +2280,7 @@ function recurseJsonl(dir, out) {
2221
2280
  return;
2222
2281
  }
2223
2282
  for (const e of entries) {
2224
- const p = join5(dir, e.name);
2283
+ const p = join6(dir, e.name);
2225
2284
  if (e.isDirectory())
2226
2285
  recurseJsonl(p, out);
2227
2286
  else if (e.isFile() && e.name.endsWith(".jsonl"))
@@ -2252,12 +2311,12 @@ var ClaudeCodeAdapter = class {
2252
2311
  });
2253
2312
  }
2254
2313
  };
2255
- addFrom(join5(archiveRoot, "projects"), false);
2256
- const versionsRoot = join5(archiveRoot, ".versions");
2314
+ addFrom(join6(archiveRoot, "projects"), false);
2315
+ const versionsRoot = join6(archiveRoot, ".versions");
2257
2316
  try {
2258
2317
  for (const ts of readdirSync2(versionsRoot, { withFileTypes: true })) {
2259
2318
  if (ts.isDirectory())
2260
- addFrom(join5(versionsRoot, ts.name, "projects"), true);
2319
+ addFrom(join6(versionsRoot, ts.name, "projects"), true);
2261
2320
  }
2262
2321
  } catch {
2263
2322
  }
@@ -3311,7 +3370,7 @@ function deriveFileChanges(db, dirty) {
3311
3370
  import { existsSync as existsSync4 } from "fs";
3312
3371
  import { homedir as homedir3 } from "os";
3313
3372
  import { createHash as createHash4 } from "crypto";
3314
- import { dirname as dirname4, join as join6 } from "path";
3373
+ import { dirname as dirname5, join as join7 } from "path";
3315
3374
  function projectId(agentId, path3) {
3316
3375
  return createHash4("sha1").update(`${agentId}\0${path3}`).digest("hex").slice(0, 16);
3317
3376
  }
@@ -3319,9 +3378,9 @@ var strip = (p) => p.length > 1 ? p.replace(/\/$/, "") : p;
3319
3378
  function nearestGitRoot(path3, home) {
3320
3379
  let cur = strip(path3);
3321
3380
  while (cur !== home && cur !== "/" && cur.length > 0) {
3322
- if (existsSync4(join6(cur, ".git")))
3381
+ if (existsSync4(join7(cur, ".git")))
3323
3382
  return cur;
3324
- const next = dirname4(cur);
3383
+ const next = dirname5(cur);
3325
3384
  if (next === cur)
3326
3385
  break;
3327
3386
  cur = next;
@@ -3348,12 +3407,12 @@ function canonicalizeProjects(db, opts = {}) {
3348
3407
  if (git) {
3349
3408
  out = git;
3350
3409
  } 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)) {
3410
+ for (let cur = dirname5(p); cur !== "/" && cur !== home && cur.length > 0; cur = dirname5(cur)) {
3352
3411
  if (byPath.has(cur)) {
3353
3412
  out = canon(cur);
3354
3413
  break;
3355
3414
  }
3356
- if (dirname4(cur) === cur)
3415
+ if (dirname5(cur) === cur)
3357
3416
  break;
3358
3417
  }
3359
3418
  }
@@ -3861,8 +3920,8 @@ function ingestFile(db, stmts, adapter, file, lines, meta, now2, stats) {
3861
3920
  import { basename as basename3 } from "path";
3862
3921
 
3863
3922
  // ../ingest/dist/sidecar.js
3864
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3865
- import { join as join7 } from "path";
3923
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3924
+ import { join as join8 } from "path";
3866
3925
 
3867
3926
  // ../ingest/dist/redact.js
3868
3927
  import { createHash as createHash6, randomBytes } from "crypto";
@@ -3937,7 +3996,7 @@ function walk(dir, subdir, inSubdir, matchName, out) {
3937
3996
  return;
3938
3997
  }
3939
3998
  for (const e of entries) {
3940
- const p = join7(dir, e.name);
3999
+ const p = join8(dir, e.name);
3941
4000
  if (e.isDirectory())
3942
4001
  walk(p, subdir, inSubdir || e.name === subdir, matchName, out);
3943
4002
  else if (e.isFile() && inSubdir && matchName(e.name))
@@ -3946,11 +4005,11 @@ function walk(dir, subdir, inSubdir, matchName, out) {
3946
4005
  }
3947
4006
  function discover(sourceArchiveDir, subdir, matchName) {
3948
4007
  const out = [];
3949
- walk(join7(sourceArchiveDir, "projects"), subdir, false, matchName, out);
4008
+ walk(join8(sourceArchiveDir, "projects"), subdir, false, matchName, out);
3950
4009
  try {
3951
- for (const ts of readdirSync3(join7(sourceArchiveDir, ".versions"), { withFileTypes: true }))
4010
+ for (const ts of readdirSync3(join8(sourceArchiveDir, ".versions"), { withFileTypes: true }))
3952
4011
  if (ts.isDirectory())
3953
- walk(join7(sourceArchiveDir, ts.name, "projects"), subdir, false, matchName, out);
4012
+ walk(join8(sourceArchiveDir, ts.name, "projects"), subdir, false, matchName, out);
3954
4013
  } catch {
3955
4014
  }
3956
4015
  return out;
@@ -3977,7 +4036,7 @@ function ingestSidecars(db, sourceArchiveDir, excludedDirs, now2, stats, full, c
3977
4036
  }
3978
4037
  let buf;
3979
4038
  try {
3980
- buf = readFileSync4(path3);
4039
+ buf = readFileSync5(path3);
3981
4040
  } catch {
3982
4041
  stats.malformed++;
3983
4042
  continue;
@@ -4161,7 +4220,7 @@ function shouldReingest(path3, size, mtimeMs, prev) {
4161
4220
  if (prev && prev.size === size && prev.mtime_ms === mtimeMs)
4162
4221
  return { action: "skip-stat" };
4163
4222
  const small = size <= STREAM_THRESHOLD;
4164
- const buf = small ? readFileSync5(path3) : null;
4223
+ const buf = small ? readFileSync6(path3) : null;
4165
4224
  const hash2 = small ? sha256(buf) : sha256File(path3);
4166
4225
  if (prev && prev.sha256 === hash2)
4167
4226
  return { action: "skip-hash", hash: hash2 };
@@ -4204,7 +4263,7 @@ function runIngest(argv = process.argv.slice(2)) {
4204
4263
  console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);
4205
4264
  process.exit(1);
4206
4265
  }
4207
- mkdirSync4(dirname5(dbPath), { recursive: true });
4266
+ mkdirSync4(dirname6(dbPath), { recursive: true });
4208
4267
  const db = args.full ? openRaw(dbPath) : openDb(dbPath);
4209
4268
  if (args.full) {
4210
4269
  resetSchema(db);
@@ -4237,7 +4296,7 @@ function runIngest(argv = process.argv.slice(2)) {
4237
4296
  }
4238
4297
  stmts.insAgent.run(adapter.agentId, adapter.agentName);
4239
4298
  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);
4299
+ let files = adapter.discover(join9(archiveRoot, source.label), source.label);
4241
4300
  if (excludedDirs.length)
4242
4301
  files = files.filter((f) => !isExcludedArchivePath(f.path, excludedDirs));
4243
4302
  files.sort((a, b) => Number(a.isVersion) - Number(b.isVersion));
@@ -4267,9 +4326,9 @@ function runIngest(argv = process.argv.slice(2)) {
4267
4326
  ingestFile(db, stmts, adapter, file, lines, { size: st.size, mtimeMs, hash: decision.hash }, now2, stats);
4268
4327
  dirty.add(file.sessionId);
4269
4328
  }
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);
4329
+ ingestWorkflowResults(db, join9(archiveRoot, source.label), source.label, excludedDirs, now2, wfStats, args.full);
4330
+ ingestSubagentMeta(db, join9(archiveRoot, source.label), source.label, excludedDirs, now2, metaStats, args.full);
4331
+ ingestToolResults(db, join9(archiveRoot, source.label), excludedDirs, now2, trStats, args.full);
4273
4332
  }
4274
4333
  const expanded = rebuildDerived(db, args.full ? null : dirty);
4275
4334
  canonicalizeProjects(db);
@@ -4301,8 +4360,8 @@ function runMetrics(argv = process.argv.slice(2)) {
4301
4360
 
4302
4361
  // ../server/dist/run.js
4303
4362
  import { existsSync as existsSync8 } from "fs";
4304
- import { dirname as dirname6 } from "path";
4305
- import { fileURLToPath as fileURLToPath2 } from "url";
4363
+ import { dirname as dirname7 } from "path";
4364
+ import { fileURLToPath as fileURLToPath3 } from "url";
4306
4365
 
4307
4366
  // ../server/dist/db.js
4308
4367
  import Database2 from "better-sqlite3";
@@ -5384,7 +5443,7 @@ function dashboardBreakdowns(db, f) {
5384
5443
 
5385
5444
  // ../server/dist/refresh.js
5386
5445
  import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
5387
- import { join as join9 } from "path";
5446
+ import { join as join10 } from "path";
5388
5447
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
5389
5448
  function originAllowed(origin) {
5390
5449
  if (!origin)
@@ -5405,7 +5464,7 @@ function writeBlocked(headers) {
5405
5464
  }
5406
5465
  function runRefresh() {
5407
5466
  const dataDir = resolveDataDir(findRepoRoot());
5408
- const lock = acquireLock(join9(dataDir, ".agent-lens.lock"));
5467
+ const lock = acquireLock(join10(dataDir, ".agent-lens.lock"));
5409
5468
  if (!lock)
5410
5469
  return null;
5411
5470
  try {
@@ -5487,6 +5546,61 @@ function listMutes(db) {
5487
5546
  return db.prepare("SELECT rule_id, scope, scope_id, note, muted_at FROM muted_rules ORDER BY muted_at DESC").all();
5488
5547
  }
5489
5548
 
5549
+ // ../server/dist/about.js
5550
+ import { statSync as statSync4 } from "fs";
5551
+ function storage(db, dbPath) {
5552
+ const row = tableExists(db, "ingest_state") ? queryGet(db, "SELECT COUNT(*) files, SUM(size) bytes, MAX(ingested_at) last FROM ingest_state") : void 0;
5553
+ let db_bytes = null;
5554
+ try {
5555
+ db_bytes = statSync4(dbPath).size;
5556
+ } catch {
5557
+ db_bytes = null;
5558
+ }
5559
+ return {
5560
+ db_bytes,
5561
+ archive_bytes: row?.bytes ?? 0,
5562
+ // SUM over zero rows is NULL, not 0
5563
+ archive_files: row?.files ?? 0,
5564
+ last_ingested: row?.last ?? null
5565
+ };
5566
+ }
5567
+ function engineVersion(db, table, sql, expected) {
5568
+ const in_data = tableExists(db, table) ? queryAll(db, sql).map((r) => r.v).filter((v) => Number.isFinite(v)) : [];
5569
+ return { expected, in_data, stale: in_data.some((v) => v < expected) };
5570
+ }
5571
+ function about(db, ctx) {
5572
+ const version2 = resolveVersion();
5573
+ const schema = schemaStatus(db);
5574
+ const dataDir = resolveDataDir(ctx.repoRoot);
5575
+ const retention = resolveRetention();
5576
+ return {
5577
+ versions: {
5578
+ app: version2.version,
5579
+ app_source: version2.source,
5580
+ schema: schema.db_version,
5581
+ schema_expected: schema.expected,
5582
+ schema_stale: schema.stale,
5583
+ detector: engineVersion(db, "findings", "SELECT DISTINCT detector_version v FROM findings ORDER BY v", DETECTOR_VERSION),
5584
+ classifier: engineVersion(db, "classifications", "SELECT DISTINCT classifier_version v FROM classifications ORDER BY v", CLASSIFIER_VERSION)
5585
+ },
5586
+ paths: {
5587
+ config_file: resolveConfigFile(ctx.repoRoot, dataDir),
5588
+ data_dir: { path: dataDir, origin: process.env.AGENT_LENS_DATA ? "env" : "default" },
5589
+ // archive and triage_db are "fixed": ADR-021 makes them non-relocatable on their own, so they
5590
+ // are derived here with the same core helpers the CLI uses rather than read from config.
5591
+ archive: { path: resolveArchiveDir(ctx.repoRoot), origin: "fixed" },
5592
+ db: { path: ctx.db.path, origin: ctx.db.origin },
5593
+ triage_db: { path: triageDbFor(ctx.db.path), origin: "fixed" }
5594
+ },
5595
+ server: { host: ctx.host, port: ctx.port, loopback_only: ctx.loopbackOnly },
5596
+ retention: { versions_keep_days: retention.keepDays, origin: retention.origin },
5597
+ sources: loadSources().map((s) => ({ label: s.label, agent: s.agent, config_dir: s.configDir })),
5598
+ // storage() already carries last_ingested from the same MAX(ingested_at) it aggregates over —
5599
+ // reusing it avoids a second query AND keeps the tableExists guard that lastIngested() lacks.
5600
+ storage: storage(db, ctx.db.path)
5601
+ };
5602
+ }
5603
+
5490
5604
  // ../server/dist/prefs.js
5491
5605
  var PREFS_SCHEMA_SQL = (
5492
5606
  /* sql */
@@ -5548,8 +5662,20 @@ async function createApp(db, opts = {}) {
5548
5662
  };
5549
5663
  app.get("/api/health", async () => {
5550
5664
  const s = schemaStatus(db);
5551
- return { ok: true, last_ingested: lastIngested(db), schema_version: s.db_version, schema_stale: s.stale };
5665
+ const v = resolveVersion();
5666
+ return {
5667
+ ok: true,
5668
+ last_ingested: lastIngested(db),
5669
+ schema_version: s.db_version,
5670
+ schema_stale: s.stale,
5671
+ version: v.version,
5672
+ version_source: v.source
5673
+ };
5552
5674
  });
5675
+ if (opts.about) {
5676
+ const ctx = opts.about;
5677
+ app.get("/api/about", async () => about(db, ctx));
5678
+ }
5553
5679
  app.post("/api/refresh", async (req, reply) => {
5554
5680
  if (writeBlocked(req.headers)) {
5555
5681
  return reply.code(403).send({ error: { code: "FORBIDDEN_ORIGIN", message: "cross-origin refresh blocked" } });
@@ -5756,9 +5882,9 @@ async function createApp(db, opts = {}) {
5756
5882
 
5757
5883
  // ../server/dist/run.js
5758
5884
  async function startServer(overrides = {}) {
5759
- const here = dirname6(fileURLToPath2(import.meta.url));
5885
+ const here = dirname7(fileURLToPath3(import.meta.url));
5760
5886
  const repoRoot = findRepoRoot(here);
5761
- const { path: dbPath } = resolveDbPath(overrides.db);
5887
+ const { path: dbPath, origin: dbOrigin } = resolveDbPath(overrides.db);
5762
5888
  const { host, port } = resolveServerConfig({ port: overrides.port, host: overrides.host });
5763
5889
  const webDist = resolveWebDist(here, repoRoot);
5764
5890
  if (host !== "127.0.0.1" && host !== "localhost" && !process.env.AGENT_LENS_ALLOW_NONLOCAL) {
@@ -5772,7 +5898,12 @@ async function startServer(overrides = {}) {
5772
5898
  const triageDbPath = triageDbFor(dbPath);
5773
5899
  const db = openReadonly(dbPath);
5774
5900
  const enforceLoopbackHost = host === "127.0.0.1" || host === "localhost";
5775
- const app = await createApp(db, { webDist, triageDbPath, enforceLoopbackHost });
5901
+ const app = await createApp(db, {
5902
+ webDist,
5903
+ triageDbPath,
5904
+ enforceLoopbackHost,
5905
+ about: { db: { path: dbPath, origin: dbOrigin }, host, port, loopbackOnly: enforceLoopbackHost, repoRoot }
5906
+ });
5776
5907
  try {
5777
5908
  await app.listen({ host, port });
5778
5909
  console.log(`agent-lens-server: http://${host}:${port} (db: ${dbPath})`);
@@ -5786,11 +5917,11 @@ async function startServer(overrides = {}) {
5786
5917
 
5787
5918
  // src/watch.ts
5788
5919
  import { existsSync as existsSync9 } from "fs";
5789
- import { join as join10 } from "path";
5920
+ import { join as join11 } from "path";
5790
5921
  import chokidar from "chokidar";
5791
5922
  async function runWatch(opts = {}) {
5792
5923
  const sources = loadSources();
5793
- const lockPath = join10(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5924
+ const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5794
5925
  let running = false;
5795
5926
  let pending = false;
5796
5927
  const cycle = () => {
@@ -5823,7 +5954,7 @@ async function runWatch(opts = {}) {
5823
5954
  }
5824
5955
  };
5825
5956
  cycle();
5826
- const watchPaths = sources.map((s) => join10(s.configDir, "projects")).filter((p) => existsSync9(p));
5957
+ const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) => existsSync9(p));
5827
5958
  if (watchPaths.length === 0) {
5828
5959
  console.error("agent-lens watch: no source 'projects' directories found \u2014 nothing to watch");
5829
5960
  }
@@ -5850,15 +5981,10 @@ async function runWatch(opts = {}) {
5850
5981
 
5851
5982
  // src/index.ts
5852
5983
  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
- }
5984
+ return resolveVersion().version;
5859
5985
  }
5860
5986
  function withLock(fn) {
5861
- const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5987
+ const lockPath = join12(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
5862
5988
  const lock = acquireLock(lockPath);
5863
5989
  if (!lock) {
5864
5990
  console.error("agent-lens: another collect/ingest run is in progress \u2014 skipping");
@@ -5926,7 +6052,7 @@ cli.command(
5926
6052
  ).option("--times <hours>", "Collector cadence: comma-separated hours 0-23 (default: 9,13,17,21)").action((action, target, opts) => {
5927
6053
  const targets = parseTargets(target);
5928
6054
  const hours = action === "install" ? parseHours(opts.times) : void 0;
5929
- runService(action, { cliEntry: fileURLToPath3(import.meta.url), hours, targets });
6055
+ runService(action, { cliEntry: fileURLToPath4(import.meta.url), hours, targets });
5930
6056
  });
5931
6057
  cli.command("config", "Show the effective configuration (sources, paths, server) and where each value came from").action(() => {
5932
6058
  const repoRoot = findRepoRoot();
@@ -5943,8 +6069,7 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
5943
6069
  const { path: dbPath, origin: dbOrigin } = resolveDbPath();
5944
6070
  const archive = resolveArchiveDir(repoRoot);
5945
6071
  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";
6072
+ const { keepDays, origin: keepOrigin } = resolveRetention();
5948
6073
  console.log("agent-lens config (precedence: flag > env > config file > default)\n");
5949
6074
  console.log("Paths");
5950
6075
  console.log(` config file ${configFile ?? "(none \u2014 using built-in default source)"}`);