@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.
- package/dist/agent-lens.js +328 -96
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
- package/web/404.html +2 -2
- package/web/assets/AboutView-B6r8WJKS.js +1 -0
- package/web/assets/{Dashboard-BkRLAY2N.js → Dashboard-C0axgFBI.js} +1 -1
- package/web/assets/{FileView-BbPK6hx9.js → FileView-D3OB1c3e.js} +1 -1
- package/web/assets/{FilesView-D0gm4_XO.js → FilesView-DYSJHOfO.js} +1 -1
- package/web/assets/{SecurityView-92YGEb3-.js → SecurityView-Dl5rRtT4.js} +1 -1
- package/web/assets/{SessionView-Dy0ELjaS.js → SessionView-77dXiwzp.js} +8 -8
- package/web/assets/SkillView-Bkwf24pB.js +1 -0
- package/web/assets/{SkillsView-FtrADGqR.js → SkillsView-CAGiD59V.js} +1 -1
- package/web/assets/{WorkflowView-D45UzOuV.js → WorkflowView-CR2GzUdU.js} +1 -1
- package/web/assets/{index-Bgxu3c84.js → index-BnVRCFL6.js} +1 -1
- package/web/assets/{index-DYY0_SmQ.js → index-BxbXFMQG.js} +13 -13
- package/web/assets/index-CeW4iDDs.css +1 -0
- package/web/assets/{jsonish-Dd3X-MG6.js → jsonish-B3QCD4cU.js} +1 -1
- package/web/assets/{severity-CoO1IKlD.js → severity-DTzxtVJn.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/SkillView-Te5jcVBw.js +0 -1
- package/web/assets/index-DBaa9asK.css +0 -1
package/dist/agent-lens.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { existsSync as existsSync10,
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath as
|
|
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
|
|
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(
|
|
125
|
+
res.push(parse2(angledMatch));
|
|
126
126
|
}
|
|
127
127
|
let squareMatch;
|
|
128
128
|
while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
|
|
129
|
-
res.push(
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1586
|
-
ensureDir(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1648
|
-
const versionsDir =
|
|
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(
|
|
1654
|
-
ensureDir(
|
|
1781
|
+
ensureDir(join4(archive, "projects"));
|
|
1782
|
+
ensureDir(join4(archive, "settings"));
|
|
1655
1783
|
const files = [];
|
|
1656
|
-
const projectsDir =
|
|
1784
|
+
const projectsDir = join4(configDir, "projects");
|
|
1657
1785
|
if (existsSync2(projectsDir))
|
|
1658
1786
|
walkCollectable(projectsDir, files);
|
|
1659
|
-
const historySrc =
|
|
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,
|
|
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 =
|
|
1803
|
+
const s = join4(configDir, f);
|
|
1676
1804
|
if (!existsSync2(s))
|
|
1677
1805
|
continue;
|
|
1678
|
-
const a =
|
|
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(
|
|
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(
|
|
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
|
|
1714
|
-
import { dirname as
|
|
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(
|
|
1855
|
+
mkdirSync2(dirname4(lockPath), { recursive: true });
|
|
1728
1856
|
if (existsSync3(lockPath)) {
|
|
1729
1857
|
try {
|
|
1730
|
-
const held = JSON.parse(
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
1920
|
-
writeFileSync2(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
|
2084
|
-
import { dirname as
|
|
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
|
|
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 =
|
|
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(
|
|
2256
|
-
const versionsRoot =
|
|
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(
|
|
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
|
|
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(
|
|
3450
|
+
if (existsSync4(join7(cur, ".git")))
|
|
3323
3451
|
return cur;
|
|
3324
|
-
const next =
|
|
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 =
|
|
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 (
|
|
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
|
|
3865
|
-
import { join as
|
|
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 =
|
|
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(
|
|
4077
|
+
walk(join8(sourceArchiveDir, "projects"), subdir, false, matchName, out);
|
|
3950
4078
|
try {
|
|
3951
|
-
for (const ts of readdirSync3(
|
|
4079
|
+
for (const ts of readdirSync3(join8(sourceArchiveDir, ".versions"), { withFileTypes: true }))
|
|
3952
4080
|
if (ts.isDirectory())
|
|
3953
|
-
walk(
|
|
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 =
|
|
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 ?
|
|
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(
|
|
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(
|
|
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,
|
|
4271
|
-
ingestSubagentMeta(db,
|
|
4272
|
-
ingestToolResults(db,
|
|
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
|
|
4305
|
-
import { fileURLToPath as
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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, {
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
|
5947
|
-
const
|
|
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
|
});
|