@solongate/proxy 0.83.35 → 0.83.36

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,80 @@ async function run7(argv) {
1437
1437
  return bad ? 1 : 0;
1438
1438
  }
1439
1439
 
1440
+ // src/commands/trace.ts
1441
+ import { 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 projectKey(dir) {
1450
+ let h = 2166136261;
1451
+ const s = String(dir || "");
1452
+ for (let i = 0; i < s.length; i++) {
1453
+ h ^= s.charCodeAt(i);
1454
+ h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
1455
+ }
1456
+ return h.toString(16);
1457
+ }
1458
+ var countCell = (n) => {
1459
+ if (n === void 0 || n === null) return dim("-");
1460
+ if (n === 0) return red("0");
1461
+ return String(n);
1462
+ };
1463
+ async function run8(argv) {
1464
+ const { positionals, flags } = parse(argv);
1465
+ const sub = positionals[0] ?? "";
1466
+ if (sub === "help") return err(USAGE7), 0;
1467
+ if (sub) return unknownSub("trace", sub, USAGE7);
1468
+ const limit = flagNum(flags, "limit") || 20;
1469
+ const dir = join5(homedir5(), ".solongate", "projects", projectKey(resolve3(process.cwd())));
1470
+ let records;
1471
+ try {
1472
+ records = readFileSync6(join5(dir, ".eval-ring.jsonl"), "utf-8").split("\n").map((l) => l.trim()).filter(Boolean).map((l) => {
1473
+ try {
1474
+ return JSON.parse(l);
1475
+ } catch {
1476
+ return null;
1477
+ }
1478
+ }).filter((r) => r !== null).sort((a, b) => (b.ts ?? 0) - (a.ts ?? 0));
1479
+ } catch {
1480
+ err("");
1481
+ err(" No local records for this directory.");
1482
+ err(dim(` ${dir}`));
1483
+ err("");
1484
+ err(dim(" The guard writes one per evaluation, in the directory the call was"));
1485
+ err(dim(" made in. Run this from the project you are testing."));
1486
+ return 1;
1487
+ }
1488
+ records = records.slice(0, limit);
1489
+ if (flagBool(flags, "json")) return printJson(records), 0;
1490
+ err("");
1491
+ err(` ${records.length} local evaluation(s) \xB7 ${dim(dir)}`);
1492
+ table(
1493
+ ["WHEN", "CLIENT", "TOOL", "PERM", "PATHS", "CMDS", "URLS", "ARGS", "MS"],
1494
+ records.map((r) => [
1495
+ r.ts ? new Date(r.ts).toTimeString().slice(0, 8) : "",
1496
+ r.client ?? "",
1497
+ r.tool ?? "",
1498
+ dim(r.perm ?? ""),
1499
+ countCell(r.paths),
1500
+ countCell(r.cmds),
1501
+ countCell(r.urls),
1502
+ // An empty list is not the same as an absent one: it means the guard
1503
+ // parsed the payload and found no arguments in it at all.
1504
+ r.args === void 0 ? dim("-") : r.args.length ? r.args.join(",") : red("(none)"),
1505
+ dim(`${Math.round(r.ms ?? 0)}ms`)
1506
+ ])
1507
+ );
1508
+ err("");
1509
+ err(dim(" PATHS/CMDS/URLS is what the guard extracted from the call. A path rule"));
1510
+ err(dim(" cannot fire on a call whose PATHS is 0, whatever the rule says."));
1511
+ return 0;
1512
+ }
1513
+
1440
1514
  // src/commands/watch.ts
1441
1515
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync4 } from "fs";
1442
1516
  var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
@@ -1464,7 +1538,7 @@ function print(r, json) {
1464
1538
  `${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
1539
  );
1466
1540
  }
1467
- async function run8(argv) {
1541
+ async function run9(argv) {
1468
1542
  const { flags } = parse(argv);
1469
1543
  const json = flagBool(flags, "json");
1470
1544
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -1542,19 +1616,19 @@ async function run8(argv) {
1542
1616
  }
1543
1617
 
1544
1618
  // src/commands/alerts.ts
1545
- var USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1619
+ var USAGE8 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1546
1620
  ["alerts list"],
1547
1621
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
1548
1622
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
1549
1623
  ["alerts remove <id>"]
1550
1624
  ]);
1551
- async function run9(argv) {
1625
+ async function run10(argv) {
1552
1626
  const { positionals, flags } = parse(argv);
1553
1627
  const sub = positionals[0] ?? "list";
1554
1628
  const json = flagBool(flags, "json");
1555
1629
  switch (sub) {
1556
1630
  case "help":
1557
- return err(USAGE7), 0;
1631
+ return err(USAGE8), 0;
1558
1632
  case "list": {
1559
1633
  const { rules } = await api.settings.getAlerts();
1560
1634
  if (json) return printJson(rules), 0;
@@ -1596,23 +1670,23 @@ async function run9(argv) {
1596
1670
  return err(green(` \u2713 Removed ${id}`)), 0;
1597
1671
  }
1598
1672
  default:
1599
- return unknownSub("alerts", sub, USAGE7);
1673
+ return unknownSub("alerts", sub, USAGE8);
1600
1674
  }
1601
1675
  }
1602
1676
 
1603
1677
  // src/commands/webhooks.ts
1604
- var USAGE8 = usage("solongate webhooks", "event webhooks", [
1678
+ var USAGE9 = usage("solongate webhooks", "event webhooks", [
1605
1679
  ["webhooks list"],
1606
1680
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
1607
1681
  ["webhooks remove <id>"]
1608
1682
  ]);
1609
- async function run10(argv) {
1683
+ async function run11(argv) {
1610
1684
  const { positionals, flags } = parse(argv);
1611
1685
  const sub = positionals[0] ?? "list";
1612
1686
  const json = flagBool(flags, "json");
1613
1687
  switch (sub) {
1614
1688
  case "help":
1615
- return err(USAGE8), 0;
1689
+ return err(USAGE9), 0;
1616
1690
  case "list": {
1617
1691
  const { webhooks } = await api.settings.getWebhooks();
1618
1692
  if (json) return printJson(webhooks), 0;
@@ -1638,7 +1712,7 @@ async function run10(argv) {
1638
1712
  return err(green(` \u2713 Removed ${id}`)), 0;
1639
1713
  }
1640
1714
  default:
1641
- return unknownSub("webhooks", sub, USAGE8);
1715
+ return unknownSub("webhooks", sub, USAGE9);
1642
1716
  }
1643
1717
  }
1644
1718
 
@@ -1663,12 +1737,14 @@ async function dispatch(command, argv) {
1663
1737
  return runAgent(argv);
1664
1738
  case "doctor":
1665
1739
  return run7(argv);
1666
- case "watch":
1740
+ case "trace":
1667
1741
  return run8(argv);
1668
- case "alerts":
1742
+ case "watch":
1669
1743
  return run9(argv);
1670
- case "webhooks":
1744
+ case "alerts":
1671
1745
  return run10(argv);
1746
+ case "webhooks":
1747
+ return run11(argv);
1672
1748
  default:
1673
1749
  err(` Unknown command: ${command}`);
1674
1750
  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,88 @@ var init_agents2 = __esm({
13645
13645
  }
13646
13646
  });
13647
13647
 
13648
+ // src/commands/trace.ts
13649
+ import { readFileSync as readFileSync12 } from "fs";
13650
+ import { homedir as homedir12 } from "os";
13651
+ import { join as join14, resolve as resolve6 } from "path";
13652
+ function projectKey2(dir) {
13653
+ let h = 2166136261;
13654
+ const s = String(dir || "");
13655
+ for (let i = 0; i < s.length; i++) {
13656
+ h ^= s.charCodeAt(i);
13657
+ h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
13658
+ }
13659
+ return h.toString(16);
13660
+ }
13661
+ async function run8(argv) {
13662
+ const { positionals, flags } = parse(argv);
13663
+ const sub = positionals[0] ?? "";
13664
+ if (sub === "help") return err(USAGE7), 0;
13665
+ if (sub) return unknownSub("trace", sub, USAGE7);
13666
+ const limit = flagNum(flags, "limit") || 20;
13667
+ const dir = join14(homedir12(), ".solongate", "projects", projectKey2(resolve6(process.cwd())));
13668
+ let records;
13669
+ try {
13670
+ records = readFileSync12(join14(dir, ".eval-ring.jsonl"), "utf-8").split("\n").map((l) => l.trim()).filter(Boolean).map((l) => {
13671
+ try {
13672
+ return JSON.parse(l);
13673
+ } catch {
13674
+ return null;
13675
+ }
13676
+ }).filter((r) => r !== null).sort((a, b) => (b.ts ?? 0) - (a.ts ?? 0));
13677
+ } catch {
13678
+ err("");
13679
+ err(" No local records for this directory.");
13680
+ err(dim(` ${dir}`));
13681
+ err("");
13682
+ err(dim(" The guard writes one per evaluation, in the directory the call was"));
13683
+ err(dim(" made in. Run this from the project you are testing."));
13684
+ return 1;
13685
+ }
13686
+ records = records.slice(0, limit);
13687
+ if (flagBool(flags, "json")) return printJson(records), 0;
13688
+ err("");
13689
+ err(` ${records.length} local evaluation(s) \xB7 ${dim(dir)}`);
13690
+ table(
13691
+ ["WHEN", "CLIENT", "TOOL", "PERM", "PATHS", "CMDS", "URLS", "ARGS", "MS"],
13692
+ records.map((r) => [
13693
+ r.ts ? new Date(r.ts).toTimeString().slice(0, 8) : "",
13694
+ r.client ?? "",
13695
+ r.tool ?? "",
13696
+ dim(r.perm ?? ""),
13697
+ countCell(r.paths),
13698
+ countCell(r.cmds),
13699
+ countCell(r.urls),
13700
+ // An empty list is not the same as an absent one: it means the guard
13701
+ // parsed the payload and found no arguments in it at all.
13702
+ r.args === void 0 ? dim("-") : r.args.length ? r.args.join(",") : red("(none)"),
13703
+ dim(`${Math.round(r.ms ?? 0)}ms`)
13704
+ ])
13705
+ );
13706
+ err("");
13707
+ err(dim(" PATHS/CMDS/URLS is what the guard extracted from the call. A path rule"));
13708
+ err(dim(" cannot fire on a call whose PATHS is 0, whatever the rule says."));
13709
+ return 0;
13710
+ }
13711
+ var USAGE7, countCell;
13712
+ var init_trace = __esm({
13713
+ "src/commands/trace.ts"() {
13714
+ "use strict";
13715
+ init_args();
13716
+ init_format();
13717
+ USAGE7 = usage("solongate trace", "what the guard saw here", [
13718
+ ["trace", "the last evaluations in this directory"],
13719
+ ["trace --limit N", "how many to show (default 20)"],
13720
+ ["trace --json", "the raw records"]
13721
+ ]);
13722
+ countCell = (n) => {
13723
+ if (n === void 0 || n === null) return dim("-");
13724
+ if (n === 0) return red("0");
13725
+ return String(n);
13726
+ };
13727
+ }
13728
+ });
13729
+
13648
13730
  // src/commands/watch.ts
13649
13731
  import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync4, readSync as readSync2, statSync as statSync4 } from "fs";
13650
13732
  function tailLocal(file, maxBytes = 131072) {
@@ -13670,7 +13752,7 @@ function print(r, json) {
13670
13752
  `${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
13753
  );
13672
13754
  }
13673
- async function run8(argv) {
13755
+ async function run9(argv) {
13674
13756
  const { flags } = parse(argv);
13675
13757
  const json = flagBool(flags, "json");
13676
13758
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -13761,13 +13843,13 @@ var init_watch = __esm({
13761
13843
  });
13762
13844
 
13763
13845
  // src/commands/alerts.ts
13764
- async function run9(argv) {
13846
+ async function run10(argv) {
13765
13847
  const { positionals, flags } = parse(argv);
13766
13848
  const sub = positionals[0] ?? "list";
13767
13849
  const json = flagBool(flags, "json");
13768
13850
  switch (sub) {
13769
13851
  case "help":
13770
- return err(USAGE7), 0;
13852
+ return err(USAGE8), 0;
13771
13853
  case "list": {
13772
13854
  const { rules } = await api.settings.getAlerts();
13773
13855
  if (json) return printJson(rules), 0;
@@ -13809,17 +13891,17 @@ async function run9(argv) {
13809
13891
  return err(green(` \u2713 Removed ${id}`)), 0;
13810
13892
  }
13811
13893
  default:
13812
- return unknownSub("alerts", sub, USAGE7);
13894
+ return unknownSub("alerts", sub, USAGE8);
13813
13895
  }
13814
13896
  }
13815
- var USAGE7;
13897
+ var USAGE8;
13816
13898
  var init_alerts = __esm({
13817
13899
  "src/commands/alerts.ts"() {
13818
13900
  "use strict";
13819
13901
  init_api_client();
13820
13902
  init_args();
13821
13903
  init_format();
13822
- USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13904
+ USAGE8 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13823
13905
  ["alerts list"],
13824
13906
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
13825
13907
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
@@ -13829,13 +13911,13 @@ var init_alerts = __esm({
13829
13911
  });
13830
13912
 
13831
13913
  // src/commands/webhooks.ts
13832
- async function run10(argv) {
13914
+ async function run11(argv) {
13833
13915
  const { positionals, flags } = parse(argv);
13834
13916
  const sub = positionals[0] ?? "list";
13835
13917
  const json = flagBool(flags, "json");
13836
13918
  switch (sub) {
13837
13919
  case "help":
13838
- return err(USAGE8), 0;
13920
+ return err(USAGE9), 0;
13839
13921
  case "list": {
13840
13922
  const { webhooks } = await api.settings.getWebhooks();
13841
13923
  if (json) return printJson(webhooks), 0;
@@ -13861,17 +13943,17 @@ async function run10(argv) {
13861
13943
  return err(green(` \u2713 Removed ${id}`)), 0;
13862
13944
  }
13863
13945
  default:
13864
- return unknownSub("webhooks", sub, USAGE8);
13946
+ return unknownSub("webhooks", sub, USAGE9);
13865
13947
  }
13866
13948
  }
13867
- var USAGE8;
13949
+ var USAGE9;
13868
13950
  var init_webhooks = __esm({
13869
13951
  "src/commands/webhooks.ts"() {
13870
13952
  "use strict";
13871
13953
  init_api_client();
13872
13954
  init_args();
13873
13955
  init_format();
13874
- USAGE8 = usage("solongate webhooks", "event webhooks", [
13956
+ USAGE9 = usage("solongate webhooks", "event webhooks", [
13875
13957
  ["webhooks list"],
13876
13958
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
13877
13959
  ["webhooks remove <id>"]
@@ -13905,12 +13987,14 @@ async function dispatch(command, argv) {
13905
13987
  return runAgent(argv);
13906
13988
  case "doctor":
13907
13989
  return run(argv);
13908
- case "watch":
13990
+ case "trace":
13909
13991
  return run8(argv);
13910
- case "alerts":
13992
+ case "watch":
13911
13993
  return run9(argv);
13912
- case "webhooks":
13994
+ case "alerts":
13913
13995
  return run10(argv);
13996
+ case "webhooks":
13997
+ return run11(argv);
13914
13998
  default:
13915
13999
  err(` Unknown command: ${command}`);
13916
14000
  return 1;
@@ -13947,6 +14031,7 @@ var init_commands = __esm({
13947
14031
  init_audit2();
13948
14032
  init_agents2();
13949
14033
  init_doctor();
14034
+ init_trace();
13950
14035
  init_watch();
13951
14036
  init_alerts();
13952
14037
  init_webhooks();
@@ -13960,9 +14045,9 @@ __export(logs_server_exports, {
13960
14045
  runLogsServer: () => runLogsServer
13961
14046
  });
13962
14047
  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";
14048
+ import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
14049
+ import { resolve as resolve7, join as join15, isAbsolute as isAbsolute2 } from "path";
14050
+ import { homedir as homedir13 } from "os";
13966
14051
  import { readdirSync as readdirSync3 } from "fs";
13967
14052
  function allowedOrigins() {
13968
14053
  const base = [
@@ -13979,15 +14064,15 @@ function resolveLocalLogDir(rawPath) {
13979
14064
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
13980
14065
  if (!dir) return null;
13981
14066
  if (isAbsolute2(dir)) return dir;
13982
- return resolve6(homedir12(), ".solongate", "local-logs");
14067
+ return resolve7(homedir13(), ".solongate", "local-logs");
13983
14068
  }
13984
14069
  async function findLogDir() {
13985
- const base = resolve6(homedir12(), ".solongate");
14070
+ const base = resolve7(homedir13(), ".solongate");
13986
14071
  try {
13987
14072
  const files = readdirSync3(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
13988
14073
  for (const f of files) {
13989
14074
  try {
13990
- const c2 = JSON.parse(readFileSync12(join14(base, f), "utf-8"));
14075
+ const c2 = JSON.parse(readFileSync13(join15(base, f), "utf-8"));
13991
14076
  const p = c2?.security?.localLogs?.path;
13992
14077
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
13993
14078
  } catch {
@@ -13996,7 +14081,7 @@ async function findLogDir() {
13996
14081
  } catch {
13997
14082
  }
13998
14083
  try {
13999
- const cfgRaw = readFileSync12(join14(base, "cloud-guard.json"), "utf-8");
14084
+ const cfgRaw = readFileSync13(join15(base, "cloud-guard.json"), "utf-8");
14000
14085
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
14001
14086
  if (apiKey) {
14002
14087
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -14025,7 +14110,7 @@ function setCors(req, res) {
14025
14110
  }
14026
14111
  function fileInfo(dir) {
14027
14112
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
14028
- const file = join14(dir, LOG_FILENAME);
14113
+ const file = join15(dir, LOG_FILENAME);
14029
14114
  try {
14030
14115
  const st = statSync5(file);
14031
14116
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -14108,7 +14193,7 @@ async function runLogsServer() {
14108
14193
  return;
14109
14194
  }
14110
14195
  try {
14111
- const text = readFileSync12(info.file, "utf-8");
14196
+ const text = readFileSync13(info.file, "utf-8");
14112
14197
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
14113
14198
  res.end(text);
14114
14199
  } catch {
@@ -14161,9 +14246,9 @@ var init_logs_server = __esm({
14161
14246
  });
14162
14247
 
14163
14248
  // src/index.ts
14164
- import { readFileSync as readFileSync13 } from "fs";
14249
+ import { readFileSync as readFileSync14 } from "fs";
14165
14250
  import { fileURLToPath as fileURLToPath4 } from "url";
14166
- import { dirname as dirname4, join as join15 } from "path";
14251
+ import { dirname as dirname4, join as join16 } from "path";
14167
14252
 
14168
14253
  // src/config.ts
14169
14254
  import { readFileSync, existsSync } from "fs";
@@ -16939,7 +17024,7 @@ var Mutex = class {
16939
17024
  this.locked = true;
16940
17025
  return;
16941
17026
  }
16942
- return new Promise((resolve7, reject) => {
17027
+ return new Promise((resolve8, reject) => {
16943
17028
  const timer = setTimeout(() => {
16944
17029
  const idx = this.queue.indexOf(onReady);
16945
17030
  if (idx !== -1) this.queue.splice(idx, 1);
@@ -16947,7 +17032,7 @@ var Mutex = class {
16947
17032
  }, timeoutMs);
16948
17033
  const onReady = () => {
16949
17034
  clearTimeout(timer);
16950
- resolve7();
17035
+ resolve8();
16951
17036
  };
16952
17037
  this.queue.push(onReady);
16953
17038
  });
@@ -17539,7 +17624,7 @@ ${msg.content.text}`;
17539
17624
 
17540
17625
  // src/index.ts
17541
17626
  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"]);
17627
+ 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
17628
  var CLI_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
17544
17629
  var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "") || CLI_INFO_ARGS.has(process.argv[2] ?? "");
17545
17630
  if (!IS_HUMAN_CLI) {
@@ -17558,8 +17643,8 @@ if (!IS_HUMAN_CLI) {
17558
17643
  }
17559
17644
  var PKG_VERSION = (() => {
17560
17645
  try {
17561
- const p = join15(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
17562
- return JSON.parse(readFileSync13(p, "utf-8")).version || "unknown";
17646
+ const p = join16(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
17647
+ return JSON.parse(readFileSync14(p, "utf-8")).version || "unknown";
17563
17648
  } catch {
17564
17649
  return "unknown";
17565
17650
  }
@@ -17604,6 +17689,7 @@ function printHelp() {
17604
17689
  cmd("update auto on|off", "background auto-update (default off \u2014 on macOS npm -g often needs sudo)");
17605
17690
  cmd("repair", "restore the guard + hook + settings files if they were deleted or disarmed");
17606
17691
  cmd("doctor", "health check: login, policy, guard, local logs");
17692
+ cmd("trace [--limit N]", "what the guard saw in this directory, allows included");
17607
17693
  cmd("doctor --json", "the same health check as machine-readable JSON");
17608
17694
  cmd("logs-server start", "start the local audit-log service for the dashboard (background)");
17609
17695
  cmd("logs-server stop", "stop AND disable it (only this makes it stay down)");
@@ -17733,7 +17819,7 @@ async function main() {
17733
17819
  await launchTui2();
17734
17820
  return;
17735
17821
  }
17736
- const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks"]);
17822
+ const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "trace", "watch", "alerts", "webhooks"]);
17737
17823
  if (MGMT_COMMANDS.has(subcommand ?? "")) {
17738
17824
  const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
17739
17825
  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.36",
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.36",
65
+ "@solongate/guard-linux-arm64": "0.83.36",
66
+ "@solongate/guard-darwin-x64": "0.83.36",
67
+ "@solongate/guard-darwin-arm64": "0.83.36",
68
+ "@solongate/guard-win32-x64": "0.83.36",
69
+ "@solongate/guard-win32-arm64": "0.83.36"
70
70
  },
71
71
  "dependencies": {
72
72
  "@modelcontextprotocol/sdk": "^1.26.0",