@swestash/agent-lens 0.13.0 → 0.13.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/README.md CHANGED
@@ -43,6 +43,23 @@ agent-lens service install # periodic collect+ingest AND the always-on U
43
43
  agent-lens watch # a resident process that collects+ingests on file change
44
44
  ```
45
45
 
46
+ No configuration is required to start: with no config file, Agent Lens collects the single default
47
+ source `~/.claude`.
48
+
49
+ ### Where your data lives
50
+
51
+ Installed from npm, the archive and SQLite store go to a per-user data directory:
52
+
53
+ | Platform | Data directory |
54
+ | --- | --- |
55
+ | Linux | `$XDG_DATA_HOME/agent-lens`, else `~/.local/share/agent-lens` |
56
+ | macOS | `~/Library/Application Support/agent-lens` |
57
+ | Windows | `%LOCALAPPDATA%\agent-lens` |
58
+
59
+ Set `AGENT_LENS_DATA` to put it elsewhere — that is the only way to relocate the archive, which the
60
+ SQLite store and triage sidecar follow. `agent-lens config` prints the resolved paths and where each
61
+ value came from.
62
+
46
63
  ### Commands
47
64
 
48
65
  | Command | What it does |
@@ -51,8 +68,10 @@ agent-lens watch # a resident process that collects+ingests on
51
68
  | `agent-lens ingest` | Normalize the archive into the SQLite store. |
52
69
  | `agent-lens serve` | Serve the web UI + read-only API at `http://127.0.0.1:4477`. |
53
70
  | `agent-lens watch` | Foreground resident: collect + ingest on file change. |
54
- | `agent-lens metrics` | Print store metrics to the terminal. |
71
+ | `agent-lens metrics` | Re-run classification, security detection, error typing, and file-change indexing over an already-ingested store. |
72
+ | `agent-lens export <sessionId>` | Write a session to a shareable Markdown file — redacted by default (`--level secrets\|structure`, `--no-redact` to opt out). |
55
73
  | `agent-lens service <install\|uninstall\|status> [collector\|server\|all]` | Install/manage OS services (systemd / launchd / Windows Task Scheduler). |
74
+ | `agent-lens config` | Print the effective configuration — sources, paths, server binding — and where each value came from. |
56
75
 
57
76
  ## Privacy
58
77
 
@@ -1264,6 +1264,14 @@ function classifyToolError(resultSummary) {
1264
1264
  return { type: "other", kind: errorKind("other") };
1265
1265
  }
1266
1266
 
1267
+ // ../core/dist/user-error.js
1268
+ var UserError = class extends Error {
1269
+ constructor(message) {
1270
+ super(message);
1271
+ this.name = "UserError";
1272
+ }
1273
+ };
1274
+
1267
1275
  // ../core/dist/pricing.js
1268
1276
  var SYNTHETIC_MODEL = "<synthetic>";
1269
1277
  function unpricedModels(models) {
@@ -1495,7 +1503,7 @@ function resolveDbPath(override, cfg = readConfigFile()) {
1495
1503
  function validatePort(value, source) {
1496
1504
  const n = typeof value === "number" ? value : Number(String(value).trim());
1497
1505
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
1498
- throw new Error(`agent-lens: invalid port from ${source}: '${value}' (expected an integer 1-65535)`);
1506
+ throw new UserError(`invalid port from ${source}: '${value}' (expected an integer 1-65535)`);
1499
1507
  }
1500
1508
  return n;
1501
1509
  }
@@ -1608,12 +1616,12 @@ function loadSources() {
1608
1616
  if (!s || !s.label || !s.configDir)
1609
1617
  continue;
1610
1618
  if (seen.has(s.label))
1611
- throw new Error(`duplicate source label: ${s.label}`);
1619
+ throw new UserError(`duplicate source label: ${s.label}`);
1612
1620
  seen.add(s.label);
1613
1621
  out.push({ label: s.label, agent: s.agent || "claude-code", configDir: expandPath(s.configDir) });
1614
1622
  }
1615
1623
  if (!out.length)
1616
- throw new Error("no valid sources configured");
1624
+ throw new UserError("no valid sources configured");
1617
1625
  return out;
1618
1626
  }
1619
1627
  function loadExcludes() {
@@ -1854,7 +1862,7 @@ function collectAll(opts = {}) {
1854
1862
  ensureDir(archiveBase);
1855
1863
  const stats = { sources: 0, scanned: 0, copied: 0, appended: 0, diverged: 0, compacted: 0, snapshots: 0 };
1856
1864
  if (!sources.length)
1857
- throw new Error("no sources configured");
1865
+ throw new UserError("no sources configured");
1858
1866
  for (const source of sources) {
1859
1867
  stats.sources++;
1860
1868
  collectSource(source, archiveBase, excludedDirs, runTs, stats, log);
@@ -1908,7 +1916,7 @@ function acquireLock(lockPath, staleMs = 6 * 60 * 60 * 1e3) {
1908
1916
  import { spawnSync as spawnSync2 } from "node:child_process";
1909
1917
  import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1910
1918
  import { homedir as homedir2, platform as platform3, userInfo } from "node:os";
1911
- import { join as join5 } from "node:path";
1919
+ import { join as join5, resolve as resolve2 } from "node:path";
1912
1920
  var DEFAULT_HOURS = [9, 13, 17, 21];
1913
1921
  function currentUser() {
1914
1922
  try {
@@ -1929,14 +1937,14 @@ function parseTargets(spec) {
1929
1937
  return ["collector", "server"];
1930
1938
  if (spec === "collector" || spec === "server")
1931
1939
  return [spec];
1932
- throw new Error(`invalid target '${spec}' (expected: collector | server | all)`);
1940
+ throw new UserError(`invalid target '${spec}' (expected: collector | server | all)`);
1933
1941
  }
1934
1942
  function parseHours(spec) {
1935
1943
  if (!spec)
1936
1944
  return DEFAULT_HOURS;
1937
1945
  const hours = spec.split(",").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 0 && n <= 23);
1938
1946
  if (!hours.length)
1939
- throw new Error(`invalid hours '${spec}' (expected comma-separated 0-23, e.g. 9,13,17,21)`);
1947
+ throw new UserError(`invalid hours '${spec}' (expected comma-separated 0-23, e.g. 9,13,17,21)`);
1940
1948
  return [...new Set(hours)].sort((a, b) => a - b);
1941
1949
  }
1942
1950
  function serverEnv() {
@@ -1948,10 +1956,26 @@ function serverEnv() {
1948
1956
  env.AGENT_LENS_HOST = host;
1949
1957
  return env;
1950
1958
  }
1959
+ function pathEnv() {
1960
+ const env = {};
1961
+ if (process.env.AGENT_LENS_DATA)
1962
+ env.AGENT_LENS_DATA = resolveDataDir();
1963
+ if (process.env.AGENT_LENS_CONFIG)
1964
+ env.AGENT_LENS_CONFIG = resolve2(process.env.AGENT_LENS_CONFIG);
1965
+ return env;
1966
+ }
1967
+ function reportBakedEnv(env) {
1968
+ for (const [k, v] of Object.entries(env))
1969
+ console.log(`agent-lens service: ${k}=${v}`);
1970
+ }
1951
1971
  function onCalendarHours(hours) {
1952
1972
  return hours.map((h) => String(h).padStart(2, "0")).join(",") + ":00";
1953
1973
  }
1954
- function systemdCollectorService(node, cli2) {
1974
+ function systemdEnvLines(env) {
1975
+ return Object.entries(env).map(([k, v]) => `Environment=${k}=${v}
1976
+ `).join("");
1977
+ }
1978
+ function systemdCollectorService(node, cli2, env = {}) {
1955
1979
  return `[Unit]
1956
1980
  Description=Agent Lens \u2014 collect + ingest Claude Code session traces
1957
1981
 
@@ -1959,7 +1983,7 @@ Description=Agent Lens \u2014 collect + ingest Claude Code session traces
1959
1983
  Type=oneshot
1960
1984
  # Absolute node + CLI paths: systemd user services don't inherit the interactive PATH.
1961
1985
  ExecStart=${node} ${cli2} collect --then-ingest
1962
- Nice=10
1986
+ ${systemdEnvLines(env)}Nice=10
1963
1987
  IOSchedulingClass=idle
1964
1988
  `;
1965
1989
  }
@@ -1978,8 +2002,7 @@ WantedBy=timers.target
1978
2002
  `;
1979
2003
  }
1980
2004
  function systemdServerService(node, cli2, env = {}) {
1981
- const envLines = Object.entries(env).map(([k, v]) => `Environment=${k}=${v}
1982
- `).join("");
2005
+ const envLines = systemdEnvLines(env);
1983
2006
  return `[Unit]
1984
2007
  Description=Agent Lens \u2014 local web UI + read-only API server
1985
2008
  After=network.target
@@ -2000,7 +2023,17 @@ Nice=10
2000
2023
  WantedBy=default.target
2001
2024
  `;
2002
2025
  }
2003
- function launchdCollectorPlist(node, cli2, hours, logPath) {
2026
+ function launchdEnvDict(env) {
2027
+ if (!Object.keys(env).length)
2028
+ return "";
2029
+ const entries = Object.entries(env).map(([k, v]) => ` <key>${k}</key><string>${v}</string>`).join("\n");
2030
+ return ` <key>EnvironmentVariables</key>
2031
+ <dict>
2032
+ ${entries}
2033
+ </dict>
2034
+ `;
2035
+ }
2036
+ function launchdCollectorPlist(node, cli2, hours, logPath, env = {}) {
2004
2037
  const cal = hours.map((h) => ` <dict><key>Hour</key><integer>${h}</integer><key>Minute</key><integer>0</integer></dict>`).join("\n");
2005
2038
  return `<?xml version="1.0" encoding="UTF-8"?>
2006
2039
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -2018,18 +2051,14 @@ function launchdCollectorPlist(node, cli2, hours, logPath) {
2018
2051
  <array>
2019
2052
  ${cal}
2020
2053
  </array>
2021
- <key>StandardOutPath</key><string>${logPath}</string>
2054
+ ${launchdEnvDict(env)} <key>StandardOutPath</key><string>${logPath}</string>
2022
2055
  <key>StandardErrorPath</key><string>${logPath}</string>
2023
2056
  </dict>
2024
2057
  </plist>
2025
2058
  `;
2026
2059
  }
2027
2060
  function launchdServerPlist(node, cli2, logPath, env = {}) {
2028
- const envDict = Object.entries(env).length ? ` <key>EnvironmentVariables</key>
2029
- <dict>
2030
- ${Object.entries(env).map(([k, v]) => ` <key>${k}</key><string>${v}</string>`).join("\n")}
2031
- </dict>
2032
- ` : "";
2061
+ const envDict = launchdEnvDict(env);
2033
2062
  return `<?xml version="1.0" encoding="UTF-8"?>
2034
2063
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2035
2064
  <plist version="1.0">
@@ -2068,22 +2097,25 @@ function linuxInstall(targets, node, cli2, hours) {
2068
2097
  }
2069
2098
  const dir = systemdUnitDir();
2070
2099
  mkdirSync3(dir, { recursive: true });
2100
+ const paths = pathEnv();
2071
2101
  if (targets.includes("collector")) {
2072
- writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2));
2102
+ writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli2, paths));
2073
2103
  writeFileSync2(join5(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));
2074
2104
  }
2075
2105
  if (targets.includes("server")) {
2076
- writeFileSync2(join5(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, serverEnv()));
2106
+ writeFileSync2(join5(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli2, { ...paths, ...serverEnv() }));
2077
2107
  }
2078
2108
  sh("systemctl", ["--user", "daemon-reload"]);
2079
2109
  if (targets.includes("collector")) {
2080
2110
  sh("systemctl", ["--user", "enable", "--now", SYSTEMD_COLLECTOR_TIMER]);
2081
2111
  console.log(`agent-lens service: installed collector timer (${onCalendarHours(hours)})`);
2112
+ reportBakedEnv(paths);
2082
2113
  sh("systemctl", ["--user", "list-timers", SYSTEMD_COLLECTOR_TIMER, "--no-pager"]);
2083
2114
  }
2084
2115
  if (targets.includes("server")) {
2085
2116
  sh("systemctl", ["--user", "enable", "--now", SYSTEMD_SERVER_SERVICE]);
2086
2117
  console.log("agent-lens service: installed server daemon");
2118
+ reportBakedEnv({ ...paths, ...serverEnv() });
2087
2119
  sh("systemctl", ["--user", "status", SYSTEMD_SERVER_SERVICE, "--no-pager"]);
2088
2120
  }
2089
2121
  const user = currentUser();
@@ -2135,17 +2167,21 @@ function macInstall(targets, node, cli2, hours) {
2135
2167
  const agents = join5(homedir2(), "Library", "LaunchAgents");
2136
2168
  mkdirSync3(agents, { recursive: true });
2137
2169
  const dataDir = resolveDataDir(findRepoRoot());
2170
+ const paths = pathEnv();
2138
2171
  if (targets.includes("collector")) {
2139
2172
  const plist = launchdPlistPath(LAUNCHD_COLLECTOR_LABEL);
2140
- writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join5(dataDir, "schedule.log")));
2173
+ writeFileSync2(plist, launchdCollectorPlist(node, cli2, hours, join5(dataDir, "schedule.log"), paths));
2141
2174
  macBootstrap(plist);
2142
2175
  console.log(`agent-lens service: installed collector agent ${LAUNCHD_COLLECTOR_LABEL} (hours ${hours.join(",")})`);
2176
+ reportBakedEnv(paths);
2143
2177
  }
2144
2178
  if (targets.includes("server")) {
2145
2179
  const plist = launchdPlistPath(LAUNCHD_SERVER_LABEL);
2146
- writeFileSync2(plist, launchdServerPlist(node, cli2, join5(dataDir, "server.log"), serverEnv()));
2180
+ const env = { ...paths, ...serverEnv() };
2181
+ writeFileSync2(plist, launchdServerPlist(node, cli2, join5(dataDir, "server.log"), env));
2147
2182
  macBootstrap(plist);
2148
2183
  console.log(`agent-lens service: installed server agent ${LAUNCHD_SERVER_LABEL}`);
2184
+ reportBakedEnv(env);
2149
2185
  }
2150
2186
  }
2151
2187
  function macUninstall(targets) {
@@ -2166,6 +2202,13 @@ function winCollectorTaskName(hour) {
2166
2202
  return `${WINDOWS_COLLECTOR_PREFIX}_${String(hour).padStart(2, "0")}`;
2167
2203
  }
2168
2204
  function windowsInstall(targets, node, cli2, hours) {
2205
+ const unbakeable = Object.entries({ ...pathEnv(), ...serverEnv() });
2206
+ if (unbakeable.length) {
2207
+ console.log("agent-lens service: Task Scheduler cannot carry environment variables. Set these");
2208
+ console.log("agent-lens service: for your user account so the task sees them, then reinstall:");
2209
+ for (const [k, v] of unbakeable)
2210
+ console.log(`agent-lens service: setx ${k} "${v}"`);
2211
+ }
2169
2212
  if (targets.includes("collector")) {
2170
2213
  const tr = `"${node}" "${cli2}" collect --then-ingest`;
2171
2214
  for (const h of hours) {
@@ -6127,6 +6170,13 @@ async function runWatch(opts = {}) {
6127
6170
  function version() {
6128
6171
  return resolveVersion().version;
6129
6172
  }
6173
+ function die(e) {
6174
+ if (e instanceof UserError) {
6175
+ console.error(`agent-lens: ${e.message}`);
6176
+ process.exit(1);
6177
+ }
6178
+ throw e;
6179
+ }
6130
6180
  function withLock(fn) {
6131
6181
  const lockPath = join12(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
6132
6182
  const lock = acquireLock(lockPath);
@@ -6158,12 +6208,7 @@ cli.command("ingest", "Build/update the SQLite projection from the archive").opt
6158
6208
  withLock(() => runIngest(argv));
6159
6209
  });
6160
6210
  cli.command("serve", "Serve the web UI + read-only API on 127.0.0.1").option("--port <n>", "HTTP port (overrides AGENT_LENS_PORT / config server.port; default 4477)").option("--host <host>", "Bind host (overrides AGENT_LENS_HOST / config server.host; loopback only unless AGENT_LENS_ALLOW_NONLOCAL=1)").option("--db <path>", "SQLite DB path (overrides AGENT_LENS_DB / config db)").action(async (opts) => {
6161
- try {
6162
- await startServer({ port: opts.port, host: opts.host, db: opts.db });
6163
- } catch (e) {
6164
- console.error(e instanceof Error ? e.message : String(e));
6165
- process.exit(1);
6166
- }
6211
+ await startServer({ port: opts.port, host: opts.host, db: opts.db });
6167
6212
  });
6168
6213
  cli.command("watch", "Watch sources and collect + ingest on change (resident process)").option("--interval <sec>", "Also run on a fixed interval (seconds)").option("--poll", "Use polling \u2014 for network filesystems where fs events don't fire").action(async (opts) => {
6169
6214
  await runWatch({ intervalSec: opts.interval ? Number(opts.interval) : void 0, poll: !!opts.poll });
@@ -6202,14 +6247,7 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
6202
6247
  const repoRoot = findRepoRoot();
6203
6248
  const dataDir = resolveDataDir(repoRoot);
6204
6249
  const configFile = resolveConfigFile(repoRoot, dataDir);
6205
- let server;
6206
- try {
6207
- server = resolveServerConfig();
6208
- } catch (e) {
6209
- console.error(e instanceof Error ? e.message : String(e));
6210
- process.exit(1);
6211
- }
6212
- const { port, host, portOrigin, hostOrigin } = server;
6250
+ const { port, host, portOrigin, hostOrigin } = resolveServerConfig();
6213
6251
  const { path: dbPath, origin: dbOrigin } = resolveDbPath();
6214
6252
  const archive = resolveArchiveDir(repoRoot);
6215
6253
  const triageDb = triageDbFor(dbPath);
@@ -6237,5 +6275,10 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
6237
6275
  });
6238
6276
  cli.help();
6239
6277
  cli.version(version());
6240
- cli.parse();
6278
+ process.on("unhandledRejection", die);
6279
+ try {
6280
+ cli.parse();
6281
+ } catch (e) {
6282
+ die(e);
6283
+ }
6241
6284
  //# sourceMappingURL=agent-lens.js.map