@solongate/proxy 0.83.35 → 0.83.37

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.
@@ -1437,6 +1437,106 @@ async function run7(argv) {
1437
1437
  return bad ? 1 : 0;
1438
1438
  }
1439
1439
 
1440
+ // src/commands/trace.ts
1441
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1442
+ import { homedir as homedir5 } from "os";
1443
+ import { join as join5, resolve as resolve3 } from "path";
1444
+ var USAGE7 = usage("solongate trace", "what the guard saw here", [
1445
+ ["trace", "the last evaluations in this directory"],
1446
+ ["trace --limit N", "how many to show (default 20)"],
1447
+ ["trace --json", "the raw records"]
1448
+ ]);
1449
+ function readAllRings() {
1450
+ const root = join5(homedir5(), ".solongate", "projects");
1451
+ let dirs;
1452
+ try {
1453
+ dirs = readdirSync3(root);
1454
+ } catch {
1455
+ return [];
1456
+ }
1457
+ const out2 = [];
1458
+ for (const d of dirs) {
1459
+ let text;
1460
+ try {
1461
+ text = readFileSync6(join5(root, d, ".eval-ring.jsonl"), "utf-8");
1462
+ } catch {
1463
+ continue;
1464
+ }
1465
+ for (const line of text.split("\n")) {
1466
+ const t = line.trim();
1467
+ if (!t) continue;
1468
+ try {
1469
+ out2.push(JSON.parse(t));
1470
+ } catch {
1471
+ }
1472
+ }
1473
+ }
1474
+ return out2.sort((a, b) => (b.ts ?? 0) - (a.ts ?? 0));
1475
+ }
1476
+ var sameDir = (a, b) => {
1477
+ const clean = (s) => {
1478
+ const t = String(s ?? "").replaceAll("\\", "/");
1479
+ return t.length > 1 ? t.replace(/\/+$/, "") : t;
1480
+ };
1481
+ return !!a && clean(a) === clean(b);
1482
+ };
1483
+ var countCell = (n) => {
1484
+ if (n === void 0 || n === null) return dim("-");
1485
+ if (n === 0) return red("0");
1486
+ return String(n);
1487
+ };
1488
+ async function run8(argv) {
1489
+ const { positionals, flags } = parse(argv);
1490
+ const sub = positionals[0] ?? "";
1491
+ if (sub === "help") return err(USAGE7), 0;
1492
+ if (sub) return unknownSub("trace", sub, USAGE7);
1493
+ const limit = flagNum(flags, "limit") || 20;
1494
+ const here = resolve3(process.cwd());
1495
+ const all = readAllRings();
1496
+ let records = all.filter((r) => sameDir(r.cwd, here));
1497
+ if (!records.length) {
1498
+ err("");
1499
+ err(" No calls recorded for this directory.");
1500
+ err(dim(` ${here}`));
1501
+ if (all.length) {
1502
+ err("");
1503
+ err(dim(" The guard has recorded calls in:"));
1504
+ const seen = /* @__PURE__ */ new Set();
1505
+ for (const r of all) {
1506
+ if (!r.cwd || seen.has(r.cwd) || seen.size >= 8) continue;
1507
+ seen.add(r.cwd);
1508
+ err(` ${dim("\u2022")} ${r.cwd}`);
1509
+ }
1510
+ if (!seen.size) err(dim(" (older records carry no directory; make one more call and retry)"));
1511
+ }
1512
+ return 1;
1513
+ }
1514
+ records = records.slice(0, limit);
1515
+ if (flagBool(flags, "json")) return printJson(records), 0;
1516
+ err("");
1517
+ err(` ${records.length} local evaluation(s) \xB7 ${dim(here)}`);
1518
+ table(
1519
+ ["WHEN", "CLIENT", "TOOL", "PERM", "PATHS", "CMDS", "URLS", "ARGS", "MS"],
1520
+ records.map((r) => [
1521
+ r.ts ? new Date(r.ts).toTimeString().slice(0, 8) : "",
1522
+ r.client ?? "",
1523
+ r.tool ?? "",
1524
+ dim(r.perm ?? ""),
1525
+ countCell(r.paths),
1526
+ countCell(r.cmds),
1527
+ countCell(r.urls),
1528
+ // An empty list is not the same as an absent one: it means the guard
1529
+ // parsed the payload and found no arguments in it at all.
1530
+ r.args === void 0 ? dim("-") : r.args.length ? r.args.join(",") : red("(none)"),
1531
+ dim(`${Math.round(r.ms ?? 0)}ms`)
1532
+ ])
1533
+ );
1534
+ err("");
1535
+ err(dim(" PATHS/CMDS/URLS is what the guard extracted from the call. A path rule"));
1536
+ err(dim(" cannot fire on a call whose PATHS is 0, whatever the rule says."));
1537
+ return 0;
1538
+ }
1539
+
1440
1540
  // src/commands/watch.ts
1441
1541
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync4 } from "fs";
1442
1542
  var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
@@ -1464,7 +1564,7 @@ function print(r, json) {
1464
1564
  `${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
1465
1565
  );
1466
1566
  }
1467
- async function run8(argv) {
1567
+ async function run9(argv) {
1468
1568
  const { flags } = parse(argv);
1469
1569
  const json = flagBool(flags, "json");
1470
1570
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -1542,19 +1642,19 @@ async function run8(argv) {
1542
1642
  }
1543
1643
 
1544
1644
  // src/commands/alerts.ts
1545
- var USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1645
+ var USAGE8 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1546
1646
  ["alerts list"],
1547
1647
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
1548
1648
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
1549
1649
  ["alerts remove <id>"]
1550
1650
  ]);
1551
- async function run9(argv) {
1651
+ async function run10(argv) {
1552
1652
  const { positionals, flags } = parse(argv);
1553
1653
  const sub = positionals[0] ?? "list";
1554
1654
  const json = flagBool(flags, "json");
1555
1655
  switch (sub) {
1556
1656
  case "help":
1557
- return err(USAGE7), 0;
1657
+ return err(USAGE8), 0;
1558
1658
  case "list": {
1559
1659
  const { rules } = await api.settings.getAlerts();
1560
1660
  if (json) return printJson(rules), 0;
@@ -1596,23 +1696,23 @@ async function run9(argv) {
1596
1696
  return err(green(` \u2713 Removed ${id}`)), 0;
1597
1697
  }
1598
1698
  default:
1599
- return unknownSub("alerts", sub, USAGE7);
1699
+ return unknownSub("alerts", sub, USAGE8);
1600
1700
  }
1601
1701
  }
1602
1702
 
1603
1703
  // src/commands/webhooks.ts
1604
- var USAGE8 = usage("solongate webhooks", "event webhooks", [
1704
+ var USAGE9 = usage("solongate webhooks", "event webhooks", [
1605
1705
  ["webhooks list"],
1606
1706
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
1607
1707
  ["webhooks remove <id>"]
1608
1708
  ]);
1609
- async function run10(argv) {
1709
+ async function run11(argv) {
1610
1710
  const { positionals, flags } = parse(argv);
1611
1711
  const sub = positionals[0] ?? "list";
1612
1712
  const json = flagBool(flags, "json");
1613
1713
  switch (sub) {
1614
1714
  case "help":
1615
- return err(USAGE8), 0;
1715
+ return err(USAGE9), 0;
1616
1716
  case "list": {
1617
1717
  const { webhooks } = await api.settings.getWebhooks();
1618
1718
  if (json) return printJson(webhooks), 0;
@@ -1638,7 +1738,7 @@ async function run10(argv) {
1638
1738
  return err(green(` \u2713 Removed ${id}`)), 0;
1639
1739
  }
1640
1740
  default:
1641
- return unknownSub("webhooks", sub, USAGE8);
1741
+ return unknownSub("webhooks", sub, USAGE9);
1642
1742
  }
1643
1743
  }
1644
1744
 
@@ -1663,12 +1763,14 @@ async function dispatch(command, argv) {
1663
1763
  return runAgent(argv);
1664
1764
  case "doctor":
1665
1765
  return run7(argv);
1666
- case "watch":
1766
+ case "trace":
1667
1767
  return run8(argv);
1668
- case "alerts":
1768
+ case "watch":
1669
1769
  return run9(argv);
1670
- case "webhooks":
1770
+ case "alerts":
1671
1771
  return run10(argv);
1772
+ case "webhooks":
1773
+ return run11(argv);
1672
1774
  default:
1673
1775
  err(` Unknown command: ${command}`);
1674
1776
  return 1;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
package/dist/index.js CHANGED
@@ -3043,12 +3043,12 @@ ${ctx.indent}`;
3043
3043
  for (const {
3044
3044
  format,
3045
3045
  test,
3046
- resolve: resolve7
3046
+ resolve: resolve8
3047
3047
  } of tags) {
3048
3048
  if (test) {
3049
3049
  const match = str.match(test);
3050
3050
  if (match) {
3051
- let res = resolve7.apply(null, match);
3051
+ let res = resolve8.apply(null, match);
3052
3052
  if (!(res instanceof Scalar)) res = new Scalar(res);
3053
3053
  if (format) res.format = format;
3054
3054
  return res;
@@ -7303,7 +7303,7 @@ async function runUpdateCommand() {
7303
7303
  return 0;
7304
7304
  }
7305
7305
  function runGlobalInstall2(version) {
7306
- return new Promise((resolve7) => {
7306
+ return new Promise((resolve8) => {
7307
7307
  try {
7308
7308
  mkdirSync4(join5(homedir3(), ".solongate"), { recursive: true });
7309
7309
  execFile(
@@ -7319,11 +7319,11 @@ ${output}
7319
7319
  `, { flag: "a" });
7320
7320
  } catch {
7321
7321
  }
7322
- resolve7({ ok: !err2, needsAdmin: !!err2 && NEEDS_ADMIN_RE.test(output) });
7322
+ resolve8({ ok: !err2, needsAdmin: !!err2 && NEEDS_ADMIN_RE.test(output) });
7323
7323
  }
7324
7324
  );
7325
7325
  } catch {
7326
- resolve7({ ok: false, needsAdmin: false });
7326
+ resolve8({ ok: false, needsAdmin: false });
7327
7327
  }
7328
7328
  });
7329
7329
  }
@@ -7364,13 +7364,13 @@ function ownNodeModules() {
7364
7364
  return null;
7365
7365
  }
7366
7366
  function globalInstallCheck() {
7367
- prefixCheck ??= new Promise((resolve7) => {
7367
+ prefixCheck ??= new Promise((resolve8) => {
7368
7368
  try {
7369
7369
  const dir = ownNodeModules();
7370
- if (!dir) return resolve7({ writable: null, dir: null });
7371
- access(dir, FS.W_OK, (e) => resolve7({ writable: !e, dir }));
7370
+ if (!dir) return resolve8({ writable: null, dir: null });
7371
+ access(dir, FS.W_OK, (e) => resolve8({ writable: !e, dir }));
7372
7372
  } catch {
7373
- resolve7({ writable: null, dir: null });
7373
+ resolve8({ writable: null, dir: null });
7374
7374
  }
7375
7375
  });
7376
7376
  return prefixCheck;
@@ -9755,7 +9755,7 @@ function DryRunPanel({ focused }) {
9755
9755
  const [off, setOff] = useState3(0);
9756
9756
  const { cols, rows } = usePanelSize();
9757
9757
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
9758
- function run11(policyIdx = pi, lim = limit) {
9758
+ function run12(policyIdx = pi, lim = limit) {
9759
9759
  const p = policies[policyIdx];
9760
9760
  if (!p) return;
9761
9761
  setRunning(true);
@@ -9773,7 +9773,7 @@ function DryRunPanel({ focused }) {
9773
9773
  pendingPolicyId = null;
9774
9774
  }
9775
9775
  setPi(idx);
9776
- run11(idx, limit);
9776
+ run12(idx, limit);
9777
9777
  }, [policies.length]);
9778
9778
  useInput2((input, key) => {
9779
9779
  if (!focused || editLogs !== null) return;
@@ -9782,10 +9782,10 @@ function DryRunPanel({ focused }) {
9782
9782
  else if (input === "p" || input === "P") {
9783
9783
  const n = policies.length ? (pi + 1) % policies.length : 0;
9784
9784
  setPi(n);
9785
- run11(n, limit);
9785
+ run12(n, limit);
9786
9786
  } else if (input === "n" || input === "N") {
9787
9787
  setEditLogs(String(limit));
9788
- } else if (input === "r" || input === "R") run11(pi, limit);
9788
+ } else if (input === "r" || input === "R") run12(pi, limit);
9789
9789
  });
9790
9790
  function commitLogs(raw) {
9791
9791
  const cap = Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT);
@@ -9793,7 +9793,7 @@ function DryRunPanel({ focused }) {
9793
9793
  const next = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, cap) : limit;
9794
9794
  setEditLogs(null);
9795
9795
  setLimit(next);
9796
- run11(pi, next);
9796
+ run12(pi, next);
9797
9797
  }
9798
9798
  const w = Math.max(40, cols);
9799
9799
  const lines = [];
@@ -11090,7 +11090,7 @@ function AuditPanel({ active: active2, focused }) {
11090
11090
  const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
11091
11091
  const doDelete = (kind) => {
11092
11092
  setMsg({ text: "deleting\u2026", level: "ok" });
11093
- const run11 = async () => {
11093
+ const run12 = async () => {
11094
11094
  if (source === "cloud") {
11095
11095
  if (kind === "one") {
11096
11096
  if (!current) throw new Error("nothing selected");
@@ -11106,14 +11106,14 @@ function AuditPanel({ active: active2, focused }) {
11106
11106
  localQ.reload();
11107
11107
  }
11108
11108
  };
11109
- run11().then(() => {
11109
+ run12().then(() => {
11110
11110
  setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
11111
11111
  toTop();
11112
11112
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11113
11113
  };
11114
11114
  const doExport = (kind) => {
11115
11115
  setMsg({ text: "exporting\u2026", level: "ok" });
11116
- const run11 = async () => {
11116
+ const run12 = async () => {
11117
11117
  const dir = join11(homedir9(), ".solongate");
11118
11118
  const file = join11(dir, `audit-export-${source}.jsonl`);
11119
11119
  let rows2;
@@ -11126,7 +11126,7 @@ function AuditPanel({ active: active2, focused }) {
11126
11126
  writeFileSync9(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
11127
11127
  return { n: rows2.length, file };
11128
11128
  };
11129
- run11().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11129
+ run12().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11130
11130
  };
11131
11131
  useInput6(
11132
11132
  (input, key) => {
@@ -12013,7 +12013,7 @@ function SettingsPanel({
12013
12013
  guardQ.reload();
12014
12014
  selfQ.reload();
12015
12015
  };
12016
- const run11 = (label, fn, reload) => {
12016
+ const run12 = (label, fn, reload) => {
12017
12017
  if (busy) return;
12018
12018
  setBusy(true);
12019
12019
  setMsg({ text: label + "\u2026", level: "ok" });
@@ -12095,8 +12095,8 @@ function SettingsPanel({
12095
12095
  const body = { signal: editor.signal, threshold: editor.threshold, windowSeconds: editor.windowSeconds, enabled: editor.enabled, ...channels };
12096
12096
  const id = editor.id;
12097
12097
  setEditor(null);
12098
- if (id) run11("alert updated", () => api.settings.updateAlert(id, body), alertQ.reload);
12099
- else run11(`${editor.channel} alert added`, () => api.settings.createAlert({ name: "SolonGate alert", ...body }), alertQ.reload);
12098
+ if (id) run12("alert updated", () => api.settings.updateAlert(id, body), alertQ.reload);
12099
+ else run12(`${editor.channel} alert added`, () => api.settings.createAlert({ name: "SolonGate alert", ...body }), alertQ.reload);
12100
12100
  };
12101
12101
  const activate = (r) => {
12102
12102
  if (r.kind === "acct") {
@@ -12170,7 +12170,7 @@ function SettingsPanel({
12170
12170
  })();
12171
12171
  } else if (r.kind === "self") {
12172
12172
  if (!selfProt) return;
12173
- run11(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
12173
+ run12(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
12174
12174
  } else if (r.kind === "cli-update") {
12175
12175
  if (updBusy) return;
12176
12176
  setUpdBusy(true);
@@ -12217,7 +12217,7 @@ function SettingsPanel({
12217
12217
  setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
12218
12218
  return;
12219
12219
  }
12220
- run11(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
12220
+ run12(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
12221
12221
  } else if (r.kind === "ll-path") {
12222
12222
  setInput(local?.path ?? "");
12223
12223
  setEditing("path");
@@ -12228,7 +12228,7 @@ function SettingsPanel({
12228
12228
  next.running ? { text: `\u2713 dashboard link running on 127.0.0.1:${next.port} \u2014 keeps running after you close the dataroom`, level: "ok" } : { text: "\u2713 dashboard link stopped (disabled until you start it again)", level: "ok" }
12229
12229
  );
12230
12230
  } else if (r.kind === "wh") {
12231
- run11(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
12231
+ run12(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
12232
12232
  } else if (r.kind === "wh-add") {
12233
12233
  setInput("");
12234
12234
  setEditing("wh-url");
@@ -12247,7 +12247,7 @@ function SettingsPanel({
12247
12247
  setEditing(null);
12248
12248
  if (which === "path") {
12249
12249
  const enabled = (local?.enabled ?? false) && v.length > 0;
12250
- run11(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
12250
+ run12(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
12251
12251
  } else if (which === "wh-url") {
12252
12252
  const url = v.replace(/^["'<]+|["'>]+$/g, "").trim();
12253
12253
  if (!url) return;
@@ -12255,7 +12255,7 @@ function SettingsPanel({
12255
12255
  setMsg({ text: "\u2717 webhook url must start with http:// or https://", level: "bad" });
12256
12256
  return;
12257
12257
  }
12258
- run11("webhook added", () => api.settings.createWebhook({ url, events: "denials" }), whQ.reload);
12258
+ run12("webhook added", () => api.settings.createWebhook({ url, events: "denials" }), whQ.reload);
12259
12259
  } else if (which === "alert-target" && editor) {
12260
12260
  setEditor({ ...editor, target: v, field: 1 });
12261
12261
  }
@@ -12310,7 +12310,7 @@ function SettingsPanel({
12310
12310
  setMsg(ok ? { text: `\u2713 ${acctLabel(cur.acc)} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
12311
12311
  refreshAccounts();
12312
12312
  } else if (cur.kind === "wh" || cur.kind === "alert" || cur.kind === "self" || cur.kind === "auto-update" || cur.kind === "ll-enabled" || cur.kind === "ll-server") {
12313
- if (cur.kind === "alert") run11(cur.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(cur.rule.id, !cur.rule.enabled), alertQ.reload);
12313
+ if (cur.kind === "alert") run12(cur.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(cur.rule.id, !cur.rule.enabled), alertQ.reload);
12314
12314
  else activate(cur);
12315
12315
  }
12316
12316
  } else if (inp === "x" && cur.kind === "acct") {
@@ -12341,13 +12341,13 @@ function SettingsPanel({
12341
12341
  refreshAccounts();
12342
12342
  onAccountsChanged?.();
12343
12343
  } else if (inp === "t" && cur.kind === "wh") {
12344
- run11("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
12344
+ run12("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
12345
12345
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
12346
12346
  }), whQ.reload);
12347
12347
  } else if (inp === "e" && cur.kind === "ll-path") activate(cur);
12348
12348
  else if (inp === "e" && cur.kind === "wh") {
12349
12349
  const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
12350
- run11(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
12350
+ run12(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
12351
12351
  } else if (inp === "e" && cur.kind === "alert") {
12352
12352
  const ch = alertChannel(cur.rule);
12353
12353
  if (ch) openAlertEditor(ch, cur.rule);
@@ -12384,10 +12384,10 @@ function SettingsPanel({
12384
12384
  setConfirmDel(null);
12385
12385
  if (cur.kind === "wh") {
12386
12386
  const id = cur.wh.id;
12387
- run11("webhook deleted", () => api.settings.deleteWebhook(id).then(() => setHidden((h) => new Set(h).add("wh:" + id))), whQ.reload);
12387
+ run12("webhook deleted", () => api.settings.deleteWebhook(id).then(() => setHidden((h) => new Set(h).add("wh:" + id))), whQ.reload);
12388
12388
  } else {
12389
12389
  const id = cur.rule.id;
12390
- run11("alert deleted", () => api.settings.deleteAlert(id).then(() => setHidden((h) => new Set(h).add("alert:" + id))), alertQ.reload);
12390
+ run12("alert deleted", () => api.settings.deleteAlert(id).then(() => setHidden((h) => new Set(h).add("alert:" + id))), alertQ.reload);
12391
12391
  }
12392
12392
  } else if (inp === "r") reloadAll();
12393
12393
  },
@@ -12775,9 +12775,9 @@ function App() {
12775
12775
  const [update2, setUpdate] = useState9({ kind: "idle" });
12776
12776
  useEffect8(() => {
12777
12777
  let alive = true;
12778
- const run11 = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
12779
- run11();
12780
- const t = setInterval(run11, 30 * 6e4);
12778
+ const run12 = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
12779
+ run12();
12780
+ const t = setInterval(run12, 30 * 6e4);
12781
12781
  return () => {
12782
12782
  alive = false;
12783
12783
  clearInterval(t);
@@ -13645,6 +13645,114 @@ var init_agents2 = __esm({
13645
13645
  }
13646
13646
  });
13647
13647
 
13648
+ // src/commands/trace.ts
13649
+ import { readdirSync as readdirSync3, readFileSync as readFileSync12 } from "fs";
13650
+ import { homedir as homedir12 } from "os";
13651
+ import { join as join14, resolve as resolve6 } from "path";
13652
+ function readAllRings() {
13653
+ const root = join14(homedir12(), ".solongate", "projects");
13654
+ let dirs;
13655
+ try {
13656
+ dirs = readdirSync3(root);
13657
+ } catch {
13658
+ return [];
13659
+ }
13660
+ const out2 = [];
13661
+ for (const d of dirs) {
13662
+ let text;
13663
+ try {
13664
+ text = readFileSync12(join14(root, d, ".eval-ring.jsonl"), "utf-8");
13665
+ } catch {
13666
+ continue;
13667
+ }
13668
+ for (const line of text.split("\n")) {
13669
+ const t = line.trim();
13670
+ if (!t) continue;
13671
+ try {
13672
+ out2.push(JSON.parse(t));
13673
+ } catch {
13674
+ }
13675
+ }
13676
+ }
13677
+ return out2.sort((a, b) => (b.ts ?? 0) - (a.ts ?? 0));
13678
+ }
13679
+ async function run8(argv) {
13680
+ const { positionals, flags } = parse(argv);
13681
+ const sub = positionals[0] ?? "";
13682
+ if (sub === "help") return err(USAGE7), 0;
13683
+ if (sub) return unknownSub("trace", sub, USAGE7);
13684
+ const limit = flagNum(flags, "limit") || 20;
13685
+ const here = resolve6(process.cwd());
13686
+ const all = readAllRings();
13687
+ let records = all.filter((r) => sameDir(r.cwd, here));
13688
+ if (!records.length) {
13689
+ err("");
13690
+ err(" No calls recorded for this directory.");
13691
+ err(dim(` ${here}`));
13692
+ if (all.length) {
13693
+ err("");
13694
+ err(dim(" The guard has recorded calls in:"));
13695
+ const seen = /* @__PURE__ */ new Set();
13696
+ for (const r of all) {
13697
+ if (!r.cwd || seen.has(r.cwd) || seen.size >= 8) continue;
13698
+ seen.add(r.cwd);
13699
+ err(` ${dim("\u2022")} ${r.cwd}`);
13700
+ }
13701
+ if (!seen.size) err(dim(" (older records carry no directory; make one more call and retry)"));
13702
+ }
13703
+ return 1;
13704
+ }
13705
+ records = records.slice(0, limit);
13706
+ if (flagBool(flags, "json")) return printJson(records), 0;
13707
+ err("");
13708
+ err(` ${records.length} local evaluation(s) \xB7 ${dim(here)}`);
13709
+ table(
13710
+ ["WHEN", "CLIENT", "TOOL", "PERM", "PATHS", "CMDS", "URLS", "ARGS", "MS"],
13711
+ records.map((r) => [
13712
+ r.ts ? new Date(r.ts).toTimeString().slice(0, 8) : "",
13713
+ r.client ?? "",
13714
+ r.tool ?? "",
13715
+ dim(r.perm ?? ""),
13716
+ countCell(r.paths),
13717
+ countCell(r.cmds),
13718
+ countCell(r.urls),
13719
+ // An empty list is not the same as an absent one: it means the guard
13720
+ // parsed the payload and found no arguments in it at all.
13721
+ r.args === void 0 ? dim("-") : r.args.length ? r.args.join(",") : red("(none)"),
13722
+ dim(`${Math.round(r.ms ?? 0)}ms`)
13723
+ ])
13724
+ );
13725
+ err("");
13726
+ err(dim(" PATHS/CMDS/URLS is what the guard extracted from the call. A path rule"));
13727
+ err(dim(" cannot fire on a call whose PATHS is 0, whatever the rule says."));
13728
+ return 0;
13729
+ }
13730
+ var USAGE7, sameDir, countCell;
13731
+ var init_trace = __esm({
13732
+ "src/commands/trace.ts"() {
13733
+ "use strict";
13734
+ init_args();
13735
+ init_format();
13736
+ USAGE7 = usage("solongate trace", "what the guard saw here", [
13737
+ ["trace", "the last evaluations in this directory"],
13738
+ ["trace --limit N", "how many to show (default 20)"],
13739
+ ["trace --json", "the raw records"]
13740
+ ]);
13741
+ sameDir = (a, b) => {
13742
+ const clean = (s) => {
13743
+ const t = String(s ?? "").replaceAll("\\", "/");
13744
+ return t.length > 1 ? t.replace(/\/+$/, "") : t;
13745
+ };
13746
+ return !!a && clean(a) === clean(b);
13747
+ };
13748
+ countCell = (n) => {
13749
+ if (n === void 0 || n === null) return dim("-");
13750
+ if (n === 0) return red("0");
13751
+ return String(n);
13752
+ };
13753
+ }
13754
+ });
13755
+
13648
13756
  // src/commands/watch.ts
13649
13757
  import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync4, readSync as readSync2, statSync as statSync4 } from "fs";
13650
13758
  function tailLocal(file, maxBytes = 131072) {
@@ -13670,7 +13778,7 @@ function print(r, json) {
13670
13778
  `${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
13671
13779
  );
13672
13780
  }
13673
- async function run8(argv) {
13781
+ async function run9(argv) {
13674
13782
  const { flags } = parse(argv);
13675
13783
  const json = flagBool(flags, "json");
13676
13784
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -13761,13 +13869,13 @@ var init_watch = __esm({
13761
13869
  });
13762
13870
 
13763
13871
  // src/commands/alerts.ts
13764
- async function run9(argv) {
13872
+ async function run10(argv) {
13765
13873
  const { positionals, flags } = parse(argv);
13766
13874
  const sub = positionals[0] ?? "list";
13767
13875
  const json = flagBool(flags, "json");
13768
13876
  switch (sub) {
13769
13877
  case "help":
13770
- return err(USAGE7), 0;
13878
+ return err(USAGE8), 0;
13771
13879
  case "list": {
13772
13880
  const { rules } = await api.settings.getAlerts();
13773
13881
  if (json) return printJson(rules), 0;
@@ -13809,17 +13917,17 @@ async function run9(argv) {
13809
13917
  return err(green(` \u2713 Removed ${id}`)), 0;
13810
13918
  }
13811
13919
  default:
13812
- return unknownSub("alerts", sub, USAGE7);
13920
+ return unknownSub("alerts", sub, USAGE8);
13813
13921
  }
13814
13922
  }
13815
- var USAGE7;
13923
+ var USAGE8;
13816
13924
  var init_alerts = __esm({
13817
13925
  "src/commands/alerts.ts"() {
13818
13926
  "use strict";
13819
13927
  init_api_client();
13820
13928
  init_args();
13821
13929
  init_format();
13822
- USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13930
+ USAGE8 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13823
13931
  ["alerts list"],
13824
13932
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
13825
13933
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
@@ -13829,13 +13937,13 @@ var init_alerts = __esm({
13829
13937
  });
13830
13938
 
13831
13939
  // src/commands/webhooks.ts
13832
- async function run10(argv) {
13940
+ async function run11(argv) {
13833
13941
  const { positionals, flags } = parse(argv);
13834
13942
  const sub = positionals[0] ?? "list";
13835
13943
  const json = flagBool(flags, "json");
13836
13944
  switch (sub) {
13837
13945
  case "help":
13838
- return err(USAGE8), 0;
13946
+ return err(USAGE9), 0;
13839
13947
  case "list": {
13840
13948
  const { webhooks } = await api.settings.getWebhooks();
13841
13949
  if (json) return printJson(webhooks), 0;
@@ -13861,17 +13969,17 @@ async function run10(argv) {
13861
13969
  return err(green(` \u2713 Removed ${id}`)), 0;
13862
13970
  }
13863
13971
  default:
13864
- return unknownSub("webhooks", sub, USAGE8);
13972
+ return unknownSub("webhooks", sub, USAGE9);
13865
13973
  }
13866
13974
  }
13867
- var USAGE8;
13975
+ var USAGE9;
13868
13976
  var init_webhooks = __esm({
13869
13977
  "src/commands/webhooks.ts"() {
13870
13978
  "use strict";
13871
13979
  init_api_client();
13872
13980
  init_args();
13873
13981
  init_format();
13874
- USAGE8 = usage("solongate webhooks", "event webhooks", [
13982
+ USAGE9 = usage("solongate webhooks", "event webhooks", [
13875
13983
  ["webhooks list"],
13876
13984
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
13877
13985
  ["webhooks remove <id>"]
@@ -13905,12 +14013,14 @@ async function dispatch(command, argv) {
13905
14013
  return runAgent(argv);
13906
14014
  case "doctor":
13907
14015
  return run(argv);
13908
- case "watch":
14016
+ case "trace":
13909
14017
  return run8(argv);
13910
- case "alerts":
14018
+ case "watch":
13911
14019
  return run9(argv);
13912
- case "webhooks":
14020
+ case "alerts":
13913
14021
  return run10(argv);
14022
+ case "webhooks":
14023
+ return run11(argv);
13914
14024
  default:
13915
14025
  err(` Unknown command: ${command}`);
13916
14026
  return 1;
@@ -13947,6 +14057,7 @@ var init_commands = __esm({
13947
14057
  init_audit2();
13948
14058
  init_agents2();
13949
14059
  init_doctor();
14060
+ init_trace();
13950
14061
  init_watch();
13951
14062
  init_alerts();
13952
14063
  init_webhooks();
@@ -13960,10 +14071,10 @@ __export(logs_server_exports, {
13960
14071
  runLogsServer: () => runLogsServer
13961
14072
  });
13962
14073
  import { createServer } from "http";
13963
- import { readFileSync as readFileSync12, statSync as statSync5 } from "fs";
13964
- import { resolve as resolve6, join as join14, isAbsolute as isAbsolute2 } from "path";
13965
- import { homedir as homedir12 } from "os";
13966
- import { readdirSync as readdirSync3 } from "fs";
14074
+ import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
14075
+ import { resolve as resolve7, join as join15, isAbsolute as isAbsolute2 } from "path";
14076
+ import { homedir as homedir13 } from "os";
14077
+ import { readdirSync as readdirSync4 } from "fs";
13967
14078
  function allowedOrigins() {
13968
14079
  const base = [
13969
14080
  "https://dashboard.solongate.com",
@@ -13979,15 +14090,15 @@ function resolveLocalLogDir(rawPath) {
13979
14090
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
13980
14091
  if (!dir) return null;
13981
14092
  if (isAbsolute2(dir)) return dir;
13982
- return resolve6(homedir12(), ".solongate", "local-logs");
14093
+ return resolve7(homedir13(), ".solongate", "local-logs");
13983
14094
  }
13984
14095
  async function findLogDir() {
13985
- const base = resolve6(homedir12(), ".solongate");
14096
+ const base = resolve7(homedir13(), ".solongate");
13986
14097
  try {
13987
- const files = readdirSync3(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
14098
+ const files = readdirSync4(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
13988
14099
  for (const f of files) {
13989
14100
  try {
13990
- const c2 = JSON.parse(readFileSync12(join14(base, f), "utf-8"));
14101
+ const c2 = JSON.parse(readFileSync13(join15(base, f), "utf-8"));
13991
14102
  const p = c2?.security?.localLogs?.path;
13992
14103
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
13993
14104
  } catch {
@@ -13996,7 +14107,7 @@ async function findLogDir() {
13996
14107
  } catch {
13997
14108
  }
13998
14109
  try {
13999
- const cfgRaw = readFileSync12(join14(base, "cloud-guard.json"), "utf-8");
14110
+ const cfgRaw = readFileSync13(join15(base, "cloud-guard.json"), "utf-8");
14000
14111
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
14001
14112
  if (apiKey) {
14002
14113
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -14025,7 +14136,7 @@ function setCors(req, res) {
14025
14136
  }
14026
14137
  function fileInfo(dir) {
14027
14138
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
14028
- const file = join14(dir, LOG_FILENAME);
14139
+ const file = join15(dir, LOG_FILENAME);
14029
14140
  try {
14030
14141
  const st = statSync5(file);
14031
14142
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -14108,7 +14219,7 @@ async function runLogsServer() {
14108
14219
  return;
14109
14220
  }
14110
14221
  try {
14111
- const text = readFileSync12(info.file, "utf-8");
14222
+ const text = readFileSync13(info.file, "utf-8");
14112
14223
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
14113
14224
  res.end(text);
14114
14225
  } catch {
@@ -14161,9 +14272,9 @@ var init_logs_server = __esm({
14161
14272
  });
14162
14273
 
14163
14274
  // src/index.ts
14164
- import { readFileSync as readFileSync13 } from "fs";
14275
+ import { readFileSync as readFileSync14 } from "fs";
14165
14276
  import { fileURLToPath as fileURLToPath4 } from "url";
14166
- import { dirname as dirname4, join as join15 } from "path";
14277
+ import { dirname as dirname4, join as join16 } from "path";
14167
14278
 
14168
14279
  // src/config.ts
14169
14280
  import { readFileSync, existsSync } from "fs";
@@ -16939,7 +17050,7 @@ var Mutex = class {
16939
17050
  this.locked = true;
16940
17051
  return;
16941
17052
  }
16942
- return new Promise((resolve7, reject) => {
17053
+ return new Promise((resolve8, reject) => {
16943
17054
  const timer = setTimeout(() => {
16944
17055
  const idx = this.queue.indexOf(onReady);
16945
17056
  if (idx !== -1) this.queue.splice(idx, 1);
@@ -16947,7 +17058,7 @@ var Mutex = class {
16947
17058
  }, timeoutMs);
16948
17059
  const onReady = () => {
16949
17060
  clearTimeout(timer);
16950
- resolve7();
17061
+ resolve8();
16951
17062
  };
16952
17063
  this.queue.push(onReady);
16953
17064
  });
@@ -17539,7 +17650,7 @@ ${msg.content.text}`;
17539
17650
 
17540
17651
  // src/index.ts
17541
17652
  init_cli_utils();
17542
- var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["update", "repair", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks", "dataroom"]);
17653
+ var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["update", "repair", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "trace", "watch", "alerts", "webhooks", "dataroom"]);
17543
17654
  var CLI_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
17544
17655
  var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "") || CLI_INFO_ARGS.has(process.argv[2] ?? "");
17545
17656
  if (!IS_HUMAN_CLI) {
@@ -17558,8 +17669,8 @@ if (!IS_HUMAN_CLI) {
17558
17669
  }
17559
17670
  var PKG_VERSION = (() => {
17560
17671
  try {
17561
- const p = join15(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
17562
- return JSON.parse(readFileSync13(p, "utf-8")).version || "unknown";
17672
+ const p = join16(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
17673
+ return JSON.parse(readFileSync14(p, "utf-8")).version || "unknown";
17563
17674
  } catch {
17564
17675
  return "unknown";
17565
17676
  }
@@ -17604,6 +17715,7 @@ function printHelp() {
17604
17715
  cmd("update auto on|off", "background auto-update (default off \u2014 on macOS npm -g often needs sudo)");
17605
17716
  cmd("repair", "restore the guard + hook + settings files if they were deleted or disarmed");
17606
17717
  cmd("doctor", "health check: login, policy, guard, local logs");
17718
+ cmd("trace [--limit N]", "what the guard saw in this directory, allows included");
17607
17719
  cmd("doctor --json", "the same health check as machine-readable JSON");
17608
17720
  cmd("logs-server start", "start the local audit-log service for the dashboard (background)");
17609
17721
  cmd("logs-server stop", "stop AND disable it (only this makes it stay down)");
@@ -17733,7 +17845,7 @@ async function main() {
17733
17845
  await launchTui2();
17734
17846
  return;
17735
17847
  }
17736
- const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks"]);
17848
+ const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "trace", "watch", "alerts", "webhooks"]);
17737
17849
  if (MGMT_COMMANDS.has(subcommand ?? "")) {
17738
17850
  const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
17739
17851
  const code = await runCommand2(subcommand, process.argv.slice(3));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.35",
3
+ "version": "0.83.37",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,12 +61,12 @@
61
61
  "node": ">=20.0.0"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@solongate/guard-linux-x64": "0.83.35",
65
- "@solongate/guard-linux-arm64": "0.83.35",
66
- "@solongate/guard-darwin-x64": "0.83.35",
67
- "@solongate/guard-darwin-arm64": "0.83.35",
68
- "@solongate/guard-win32-x64": "0.83.35",
69
- "@solongate/guard-win32-arm64": "0.83.35"
64
+ "@solongate/guard-linux-x64": "0.83.37",
65
+ "@solongate/guard-linux-arm64": "0.83.37",
66
+ "@solongate/guard-darwin-x64": "0.83.37",
67
+ "@solongate/guard-darwin-arm64": "0.83.37",
68
+ "@solongate/guard-win32-x64": "0.83.37",
69
+ "@solongate/guard-win32-arm64": "0.83.37"
70
70
  },
71
71
  "dependencies": {
72
72
  "@modelcontextprotocol/sdk": "^1.26.0",