@solongate/proxy 0.74.0 → 0.75.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/index.js CHANGED
@@ -7030,6 +7030,7 @@ var init_hooks = __esm({
7030
7030
 
7031
7031
  // src/tui/panels/Live.tsx
7032
7032
  import { Box as Box2, Text as Text2, useInput } from "ink";
7033
+ import TextInput from "ink-text-input";
7033
7034
  import { closeSync, openSync, readSync, statSync } from "fs";
7034
7035
  import { homedir as homedir3 } from "os";
7035
7036
  import { join as join5 } from "path";
@@ -7135,7 +7136,6 @@ function StreamLine({ e, loc }) {
7135
7136
  }
7136
7137
  function LivePanel({ active: active2 }) {
7137
7138
  const [s, setS] = useState2(null);
7138
- const [tsData, setTsData] = useState2(null);
7139
7139
  const [lat, setLat] = useState2([]);
7140
7140
  const [cloudBuf, setCloudBuf] = useState2([]);
7141
7141
  const [localBuf, setLocalBuf] = useState2([]);
@@ -7143,6 +7143,8 @@ function LivePanel({ active: active2 }) {
7143
7143
  const [ring, setRing] = useState2(null);
7144
7144
  const [log6, setLog] = useState2([]);
7145
7145
  const [filter, setFilter] = useState2("all");
7146
+ const [search, setSearch] = useState2("");
7147
+ const [editingSearch, setEditingSearch] = useState2(false);
7146
7148
  const [scroll, setScroll] = useState2(0);
7147
7149
  const [mode, setMode] = useState2("stream");
7148
7150
  const [pickIdx, setPickIdx] = useState2(0);
@@ -7262,38 +7264,26 @@ function LivePanel({ active: active2 }) {
7262
7264
  onApiError(e);
7263
7265
  }
7264
7266
  }, [onApiError]);
7265
- const pollSlow = useCallback2(async () => {
7266
- if (paused()) return;
7267
- try {
7268
- const td = await api.stats.timeseries({ period: "24h" });
7269
- if (!frozenRef.current) setTsData(td);
7270
- } catch (e) {
7271
- onApiError(e);
7272
- }
7273
- }, [onApiError]);
7274
7267
  useEffect2(() => {
7275
7268
  if (!active2) return;
7276
7269
  void pollFeed();
7277
7270
  void pollMedium();
7278
- void pollSlow();
7279
- const t1 = setInterval(() => void pollFeed(), 4e3);
7280
- const t2 = setInterval(() => void pollMedium(), 1e4);
7281
- const t3 = setInterval(() => void pollSlow(), 3e4);
7271
+ const t1 = setInterval(() => void pollFeed(), 3e3);
7272
+ const t2 = setInterval(() => void pollMedium(), 8e3);
7282
7273
  return () => {
7283
7274
  clearInterval(t1);
7284
7275
  clearInterval(t2);
7285
- clearInterval(t3);
7286
7276
  };
7287
- }, [active2, pollFeed, pollMedium, pollSlow]);
7277
+ }, [active2, pollFeed, pollMedium]);
7288
7278
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7289
7279
  const insights = useLoader(() => api.stats.securityInsights(7));
7290
7280
  const guard = useLoader(() => api.settings.getGuardStatus());
7291
7281
  usePoll(() => {
7292
7282
  if (!paused()) sessions.reload();
7293
- }, 1e4, active2);
7283
+ }, 8e3, active2);
7294
7284
  usePoll(() => {
7295
7285
  if (!paused()) insights.reload();
7296
- }, 3e4, active2);
7286
+ }, 2e4, active2);
7297
7287
  usePoll(() => {
7298
7288
  if (!paused()) guard.reload();
7299
7289
  }, 6e4, active2);
@@ -7329,21 +7319,17 @@ function LivePanel({ active: active2 }) {
7329
7319
  }
7330
7320
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
7331
7321
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
7332
- const points = tsData?.timeseries ?? [];
7333
7322
  const nowSlot = Math.floor(nowMs / 1e4);
7334
- const slotCounts = new Array(60).fill(0);
7335
- const slotHot = new Array(60).fill(false);
7323
+ const traffic = new Array(60).fill(0);
7324
+ const trafficHot = new Array(60).fill(false);
7336
7325
  for (const e of mergedAll) {
7337
7326
  const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
7338
7327
  if (idx >= 0 && idx < 60) {
7339
- slotCounts[idx]++;
7340
- if (e.decision !== "ALLOW") slotHot[idx] = true;
7328
+ traffic[idx]++;
7329
+ if (e.decision !== "ALLOW") trafficHot[idx] = true;
7341
7330
  }
7342
7331
  }
7343
- const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
7344
- const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
7345
- const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
7346
- const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
7332
+ const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
7347
7333
  const rl = ins.layers?.rateLimit;
7348
7334
  const dl = ins.layers?.dlp;
7349
7335
  const gh = ins.layers?.ghost;
@@ -7388,7 +7374,9 @@ function LivePanel({ active: active2 }) {
7388
7374
  const localSessIds = new Set(localSess.keys());
7389
7375
  if (ring?.session) localSessIds.add(ring.session);
7390
7376
  const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
7391
- const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
7377
+ const q = search.trim().toLowerCase();
7378
+ const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
7379
+ const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
7392
7380
  const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7393
7381
  const chartH = rows >= 36 ? 6 : 4;
7394
7382
  const colH = rows >= 32 ? 7 : 5;
@@ -7408,12 +7396,21 @@ function LivePanel({ active: active2 }) {
7408
7396
  const key = `${e.at}:${e.tool}:${e.decision}`;
7409
7397
  if (seen.has(key)) continue;
7410
7398
  seen.add(key);
7411
- all.push(e);
7399
+ if (matches(e)) all.push(e);
7412
7400
  }
7413
7401
  return all.sort((a, b) => b.at - a.at);
7414
7402
  })() : [];
7415
7403
  useInput(
7416
7404
  (input, key) => {
7405
+ if (input === " ") {
7406
+ setFrozen((f) => !f);
7407
+ return;
7408
+ }
7409
+ if (frozen) return;
7410
+ if (input === "/" && mode !== "pick") {
7411
+ setEditingSearch(true);
7412
+ return;
7413
+ }
7417
7414
  if (mode === "detail") {
7418
7415
  const maxD = Math.max(0, detailEntries.length - (rows - 10));
7419
7416
  if (key.leftArrow) {
@@ -7437,11 +7434,6 @@ function LivePanel({ active: active2 }) {
7437
7434
  } else if (input === "s" || key.leftArrow) setMode("stream");
7438
7435
  return;
7439
7436
  }
7440
- if (input === " ") {
7441
- setFrozen((f) => !f);
7442
- return;
7443
- }
7444
- if (frozen) return;
7445
7437
  if (input === "f") {
7446
7438
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
7447
7439
  setScroll(0);
@@ -7453,8 +7445,28 @@ function LivePanel({ active: active2 }) {
7453
7445
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
7454
7446
  else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
7455
7447
  },
7456
- { isActive: active2 }
7448
+ { isActive: active2 && !editingSearch }
7457
7449
  );
7450
+ const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7451
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
7452
+ /* @__PURE__ */ jsx2(
7453
+ TextInput,
7454
+ {
7455
+ value: search,
7456
+ onChange: (v) => {
7457
+ setSearch(v);
7458
+ setScroll(0);
7459
+ setDetailScroll(0);
7460
+ },
7461
+ onSubmit: () => setEditingSearch(false)
7462
+ }
7463
+ ),
7464
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
7465
+ ] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7466
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
7467
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
7468
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
7469
+ ] }) : null;
7458
7470
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7459
7471
  void sessionDot;
7460
7472
  if (s === null && localOn === null) {
@@ -7522,7 +7534,8 @@ function LivePanel({ active: active2 }) {
7522
7534
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
7523
7535
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7524
7536
  ] }),
7525
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
7537
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
7538
+ searchRow,
7526
7539
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
7527
7540
  tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
7528
7541
  tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
@@ -7631,11 +7644,12 @@ function LivePanel({ active: active2 }) {
7631
7644
  PaneTitle,
7632
7645
  {
7633
7646
  label: "TOOL STREAM",
7634
- extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
7647
+ extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
7635
7648
  width: innerW
7636
7649
  }
7637
7650
  ),
7638
7651
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
7652
+ searchRow,
7639
7653
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7640
7654
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
7641
7655
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
@@ -7649,7 +7663,7 @@ function LivePanel({ active: active2 }) {
7649
7663
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7650
7664
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
7651
7665
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
7652
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
7666
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
7653
7667
  ] })
7654
7668
  ] });
7655
7669
  }
@@ -7681,7 +7695,7 @@ var init_Live = __esm({
7681
7695
 
7682
7696
  // src/tui/panels/Policies.tsx
7683
7697
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
7684
- import TextInput from "ink-text-input";
7698
+ import TextInput2 from "ink-text-input";
7685
7699
  import { useEffect as useEffect3, useState as useState3 } from "react";
7686
7700
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
7687
7701
  function ruleSummary(r) {
@@ -7870,7 +7884,7 @@ function PoliciesPanel({ focused }) {
7870
7884
  return /* @__PURE__ */ jsxs3(Box3, { children: [
7871
7885
  /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
7872
7886
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
7873
- TextInput,
7887
+ TextInput2,
7874
7888
  {
7875
7889
  value: editVal,
7876
7890
  onChange: setEditVal,
@@ -8066,7 +8080,7 @@ var init_RateLimit = __esm({
8066
8080
 
8067
8081
  // src/tui/panels/Dlp.tsx
8068
8082
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
8069
- import TextInput2 from "ink-text-input";
8083
+ import TextInput3 from "ink-text-input";
8070
8084
  import { useEffect as useEffect5, useState as useState5 } from "react";
8071
8085
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
8072
8086
  function DlpPanel({ focused }) {
@@ -8168,7 +8182,7 @@ function DlpPanel({ focused }) {
8168
8182
  adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
8169
8183
  /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
8170
8184
  adding === "name" ? /* @__PURE__ */ jsx5(
8171
- TextInput2,
8185
+ TextInput3,
8172
8186
  {
8173
8187
  value: newName,
8174
8188
  onChange: setNewName,
@@ -8179,7 +8193,7 @@ function DlpPanel({ focused }) {
8179
8193
  }
8180
8194
  }
8181
8195
  ) : /* @__PURE__ */ jsx5(
8182
- TextInput2,
8196
+ TextInput3,
8183
8197
  {
8184
8198
  value: newRe,
8185
8199
  onChange: setNewRe,
@@ -8292,7 +8306,7 @@ var init_Stats = __esm({
8292
8306
 
8293
8307
  // src/tui/panels/Audit.tsx
8294
8308
  import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
8295
- import TextInput3 from "ink-text-input";
8309
+ import TextInput4 from "ink-text-input";
8296
8310
  import { useState as useState6 } from "react";
8297
8311
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8298
8312
  function AuditPanel({ active: active2, focused }) {
@@ -8409,7 +8423,7 @@ function AuditPanel({ active: active2, focused }) {
8409
8423
  ": "
8410
8424
  ] }),
8411
8425
  /* @__PURE__ */ jsx7(
8412
- TextInput3,
8426
+ TextInput4,
8413
8427
  {
8414
8428
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
8415
8429
  onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
package/dist/tui/index.js CHANGED
@@ -126,6 +126,7 @@ function KeyHints({ hints }) {
126
126
 
127
127
  // src/tui/panels/Live.tsx
128
128
  import { Box as Box2, Text as Text2, useInput } from "ink";
129
+ import TextInput from "ink-text-input";
129
130
  import { closeSync, openSync, readSync, statSync } from "fs";
130
131
  import { homedir as homedir2 } from "os";
131
132
  import { join as join2 } from "path";
@@ -560,7 +561,6 @@ function StreamLine({ e, loc }) {
560
561
  var FILTERS = ["all", "local", "cloud"];
561
562
  function LivePanel({ active: active2 }) {
562
563
  const [s, setS] = useState2(null);
563
- const [tsData, setTsData] = useState2(null);
564
564
  const [lat, setLat] = useState2([]);
565
565
  const [cloudBuf, setCloudBuf] = useState2([]);
566
566
  const [localBuf, setLocalBuf] = useState2([]);
@@ -568,6 +568,8 @@ function LivePanel({ active: active2 }) {
568
568
  const [ring, setRing] = useState2(null);
569
569
  const [log, setLog] = useState2([]);
570
570
  const [filter, setFilter] = useState2("all");
571
+ const [search, setSearch] = useState2("");
572
+ const [editingSearch, setEditingSearch] = useState2(false);
571
573
  const [scroll, setScroll] = useState2(0);
572
574
  const [mode, setMode] = useState2("stream");
573
575
  const [pickIdx, setPickIdx] = useState2(0);
@@ -687,38 +689,26 @@ function LivePanel({ active: active2 }) {
687
689
  onApiError(e);
688
690
  }
689
691
  }, [onApiError]);
690
- const pollSlow = useCallback2(async () => {
691
- if (paused()) return;
692
- try {
693
- const td = await api.stats.timeseries({ period: "24h" });
694
- if (!frozenRef.current) setTsData(td);
695
- } catch (e) {
696
- onApiError(e);
697
- }
698
- }, [onApiError]);
699
692
  useEffect2(() => {
700
693
  if (!active2) return;
701
694
  void pollFeed();
702
695
  void pollMedium();
703
- void pollSlow();
704
- const t1 = setInterval(() => void pollFeed(), 4e3);
705
- const t2 = setInterval(() => void pollMedium(), 1e4);
706
- const t3 = setInterval(() => void pollSlow(), 3e4);
696
+ const t1 = setInterval(() => void pollFeed(), 3e3);
697
+ const t2 = setInterval(() => void pollMedium(), 8e3);
707
698
  return () => {
708
699
  clearInterval(t1);
709
700
  clearInterval(t2);
710
- clearInterval(t3);
711
701
  };
712
- }, [active2, pollFeed, pollMedium, pollSlow]);
702
+ }, [active2, pollFeed, pollMedium]);
713
703
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
714
704
  const insights = useLoader(() => api.stats.securityInsights(7));
715
705
  const guard = useLoader(() => api.settings.getGuardStatus());
716
706
  usePoll(() => {
717
707
  if (!paused()) sessions.reload();
718
- }, 1e4, active2);
708
+ }, 8e3, active2);
719
709
  usePoll(() => {
720
710
  if (!paused()) insights.reload();
721
- }, 3e4, active2);
711
+ }, 2e4, active2);
722
712
  usePoll(() => {
723
713
  if (!paused()) guard.reload();
724
714
  }, 6e4, active2);
@@ -754,21 +744,17 @@ function LivePanel({ active: active2 }) {
754
744
  }
755
745
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
756
746
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
757
- const points = tsData?.timeseries ?? [];
758
747
  const nowSlot = Math.floor(nowMs / 1e4);
759
- const slotCounts = new Array(60).fill(0);
760
- const slotHot = new Array(60).fill(false);
748
+ const traffic = new Array(60).fill(0);
749
+ const trafficHot = new Array(60).fill(false);
761
750
  for (const e of mergedAll) {
762
751
  const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
763
752
  if (idx >= 0 && idx < 60) {
764
- slotCounts[idx]++;
765
- if (e.decision !== "ALLOW") slotHot[idx] = true;
753
+ traffic[idx]++;
754
+ if (e.decision !== "ALLOW") trafficHot[idx] = true;
766
755
  }
767
756
  }
768
- const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
769
- const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
770
- const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
771
- const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
757
+ const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
772
758
  const rl = ins.layers?.rateLimit;
773
759
  const dl = ins.layers?.dlp;
774
760
  const gh = ins.layers?.ghost;
@@ -813,7 +799,9 @@ function LivePanel({ active: active2 }) {
813
799
  const localSessIds = new Set(localSess.keys());
814
800
  if (ring?.session) localSessIds.add(ring.session);
815
801
  const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
816
- const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
802
+ const q = search.trim().toLowerCase();
803
+ const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
804
+ const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
817
805
  const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
818
806
  const chartH = rows >= 36 ? 6 : 4;
819
807
  const colH = rows >= 32 ? 7 : 5;
@@ -833,12 +821,21 @@ function LivePanel({ active: active2 }) {
833
821
  const key = `${e.at}:${e.tool}:${e.decision}`;
834
822
  if (seen.has(key)) continue;
835
823
  seen.add(key);
836
- all.push(e);
824
+ if (matches(e)) all.push(e);
837
825
  }
838
826
  return all.sort((a, b) => b.at - a.at);
839
827
  })() : [];
840
828
  useInput(
841
829
  (input, key) => {
830
+ if (input === " ") {
831
+ setFrozen((f) => !f);
832
+ return;
833
+ }
834
+ if (frozen) return;
835
+ if (input === "/" && mode !== "pick") {
836
+ setEditingSearch(true);
837
+ return;
838
+ }
842
839
  if (mode === "detail") {
843
840
  const maxD = Math.max(0, detailEntries.length - (rows - 10));
844
841
  if (key.leftArrow) {
@@ -862,11 +859,6 @@ function LivePanel({ active: active2 }) {
862
859
  } else if (input === "s" || key.leftArrow) setMode("stream");
863
860
  return;
864
861
  }
865
- if (input === " ") {
866
- setFrozen((f) => !f);
867
- return;
868
- }
869
- if (frozen) return;
870
862
  if (input === "f") {
871
863
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
872
864
  setScroll(0);
@@ -878,8 +870,28 @@ function LivePanel({ active: active2 }) {
878
870
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
879
871
  else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
880
872
  },
881
- { isActive: active2 }
873
+ { isActive: active2 && !editingSearch }
882
874
  );
875
+ const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
876
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
877
+ /* @__PURE__ */ jsx2(
878
+ TextInput,
879
+ {
880
+ value: search,
881
+ onChange: (v) => {
882
+ setSearch(v);
883
+ setScroll(0);
884
+ setDetailScroll(0);
885
+ },
886
+ onSubmit: () => setEditingSearch(false)
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
890
+ ] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
891
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
892
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
893
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
894
+ ] }) : null;
883
895
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
884
896
  void sessionDot;
885
897
  if (s === null && localOn === null) {
@@ -947,7 +959,8 @@ function LivePanel({ active: active2 }) {
947
959
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
948
960
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
949
961
  ] }),
950
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
962
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
963
+ searchRow,
951
964
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
952
965
  tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
953
966
  tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
@@ -1056,11 +1069,12 @@ function LivePanel({ active: active2 }) {
1056
1069
  PaneTitle,
1057
1070
  {
1058
1071
  label: "TOOL STREAM",
1059
- extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
1072
+ extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
1060
1073
  width: innerW
1061
1074
  }
1062
1075
  ),
1063
1076
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
1077
+ searchRow,
1064
1078
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1065
1079
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
1066
1080
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
@@ -1074,14 +1088,14 @@ function LivePanel({ active: active2 }) {
1074
1088
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1075
1089
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
1076
1090
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1077
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
1091
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
1078
1092
  ] })
1079
1093
  ] });
1080
1094
  }
1081
1095
 
1082
1096
  // src/tui/panels/Policies.tsx
1083
1097
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1084
- import TextInput from "ink-text-input";
1098
+ import TextInput2 from "ink-text-input";
1085
1099
  import { useEffect as useEffect3, useState as useState3 } from "react";
1086
1100
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1087
1101
  var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
@@ -1291,7 +1305,7 @@ function PoliciesPanel({ focused }) {
1291
1305
  return /* @__PURE__ */ jsxs3(Box3, { children: [
1292
1306
  /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
1293
1307
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
1294
- TextInput,
1308
+ TextInput2,
1295
1309
  {
1296
1310
  value: editVal,
1297
1311
  onChange: setEditVal,
@@ -1446,7 +1460,7 @@ function FieldRow({ label, active: active2, children }) {
1446
1460
 
1447
1461
  // src/tui/panels/Dlp.tsx
1448
1462
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
1449
- import TextInput2 from "ink-text-input";
1463
+ import TextInput3 from "ink-text-input";
1450
1464
  import { useEffect as useEffect5, useState as useState5 } from "react";
1451
1465
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1452
1466
  var MODES2 = ["off", "detect", "block"];
@@ -1549,7 +1563,7 @@ function DlpPanel({ focused }) {
1549
1563
  adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1550
1564
  /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
1551
1565
  adding === "name" ? /* @__PURE__ */ jsx5(
1552
- TextInput2,
1566
+ TextInput3,
1553
1567
  {
1554
1568
  value: newName,
1555
1569
  onChange: setNewName,
@@ -1560,7 +1574,7 @@ function DlpPanel({ focused }) {
1560
1574
  }
1561
1575
  }
1562
1576
  ) : /* @__PURE__ */ jsx5(
1563
- TextInput2,
1577
+ TextInput3,
1564
1578
  {
1565
1579
  value: newRe,
1566
1580
  onChange: setNewRe,
@@ -1653,7 +1667,7 @@ function StatsPanel({ active: active2 }) {
1653
1667
 
1654
1668
  // src/tui/panels/Audit.tsx
1655
1669
  import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
1656
- import TextInput3 from "ink-text-input";
1670
+ import TextInput4 from "ink-text-input";
1657
1671
  import { useState as useState6 } from "react";
1658
1672
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1659
1673
  var DECISIONS = [void 0, "DENY", "ALLOW"];
@@ -1772,7 +1786,7 @@ function AuditPanel({ active: active2, focused }) {
1772
1786
  ": "
1773
1787
  ] }),
1774
1788
  /* @__PURE__ */ jsx7(
1775
- TextInput3,
1789
+ TextInput4,
1776
1790
  {
1777
1791
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
1778
1792
  onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.74.0",
3
+ "version": "0.75.0",
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": {