@solongate/proxy 0.82.33 → 0.82.35

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
@@ -8800,6 +8800,7 @@ var init_Live = __esm({
8800
8800
 
8801
8801
  // src/tui/panels/DryRun.tsx
8802
8802
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
8803
+ import TextInput2 from "ink-text-input";
8803
8804
  import { useEffect as useEffect3, useState as useState3 } from "react";
8804
8805
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
8805
8806
  function requestDryRun(policyId) {
@@ -8809,21 +8810,24 @@ function DryRunPanel({ focused }) {
8809
8810
  const list6 = useLoader(() => api.policies.list());
8810
8811
  const policies = list6.data?.policies ?? [];
8811
8812
  const [pi, setPi] = useState3(0);
8812
- const [si, setSi] = useState3(3);
8813
+ const [limit, setLimit] = useState3(DEFAULT_LIMIT);
8814
+ const [editLogs, setEditLogs] = useState3(null);
8815
+ const statsQ = useLoader(() => api.stats.get());
8816
+ const totalLogs = statsQ.data?.total_calls ?? null;
8813
8817
  const [res, setRes] = useState3(null);
8814
8818
  const [running, setRunning] = useState3(false);
8815
8819
  const [err2, setErr] = useState3(null);
8816
8820
  const [off, setOff] = useState3(0);
8817
8821
  const { cols, rows } = usePanelSize();
8818
8822
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
8819
- function run10(policyIdx = pi, sampleIdx = si) {
8823
+ function run10(policyIdx = pi, lim = limit) {
8820
8824
  const p = policies[policyIdx];
8821
8825
  if (!p) return;
8822
8826
  setRunning(true);
8823
8827
  setErr(null);
8824
8828
  setRes(null);
8825
8829
  setOff(0);
8826
- api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: SAMPLES[sampleIdx] }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
8830
+ api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: lim }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
8827
8831
  }
8828
8832
  useEffect3(() => {
8829
8833
  if (policies.length === 0 || res || running || err2) return;
@@ -8834,22 +8838,28 @@ function DryRunPanel({ focused }) {
8834
8838
  pendingPolicyId = null;
8835
8839
  }
8836
8840
  setPi(idx);
8837
- run10(idx, si);
8841
+ run10(idx, limit);
8838
8842
  }, [policies.length]);
8839
8843
  useInput2((input, key) => {
8840
- if (!focused) return;
8844
+ if (!focused || editLogs !== null) return;
8841
8845
  if (key.upArrow) setOff((n) => Math.max(0, n - 1));
8842
8846
  else if (key.downArrow) setOff((n) => n + 1);
8843
- else if (input === "p") {
8847
+ else if (input === "p" || input === "P") {
8844
8848
  const n = policies.length ? (pi + 1) % policies.length : 0;
8845
8849
  setPi(n);
8846
- run10(n, si);
8847
- } else if (input === "n") {
8848
- const n = (si + 1) % SAMPLES.length;
8849
- setSi(n);
8850
- run10(pi, n);
8851
- } else if (input === "r") run10(pi, si);
8850
+ run10(n, limit);
8851
+ } else if (input === "n" || input === "N") {
8852
+ setEditLogs(String(limit));
8853
+ } else if (input === "r" || input === "R") run10(pi, limit);
8852
8854
  });
8855
+ function commitLogs(raw) {
8856
+ const cap = totalLogs && totalLogs > 0 ? totalLogs : 5e3;
8857
+ const parsed = parseInt(raw.replace(/[^0-9]/g, ""), 10);
8858
+ const next = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, cap) : limit;
8859
+ setEditLogs(null);
8860
+ setLimit(next);
8861
+ run10(pi, next);
8862
+ }
8853
8863
  const w = Math.max(40, cols);
8854
8864
  const lines = [];
8855
8865
  const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
@@ -8918,38 +8928,38 @@ function DryRunPanel({ focused }) {
8918
8928
  }
8919
8929
  }
8920
8930
  }
8921
- const headerRows = 3;
8931
+ const headerRows = 5;
8922
8932
  const budget = Math.max(3, rows - headerRows);
8923
8933
  const maxOff = Math.max(0, lines.length - budget);
8924
8934
  const start = Math.min(off, maxOff);
8925
8935
  const win = lines.slice(start, start + budget);
8926
8936
  const above = start;
8927
8937
  const below = Math.max(0, lines.length - (start + budget));
8928
- const sampleLabel = `${SAMPLES[si]}${res?.sampled != null ? ` (ran ${res.sampled})` : ""}`;
8929
8938
  return /* @__PURE__ */ jsx3(DataView, { loading: list6.loading && !list6.data, error: list6.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
8930
- /* @__PURE__ */ jsxs3(Box3, { children: [
8931
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
8932
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
8933
- " ",
8934
- truncate2(selected?.name ?? "no policy", 26)
8935
- ] }),
8936
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
8937
- " mode: ",
8938
- selected?.mode ?? "-"
8939
- ] }),
8940
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
8941
- " sample: ",
8942
- sampleLabel
8943
- ] })
8939
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
8940
+ /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
8941
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " policy " }),
8942
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate2(selected?.name ?? "none", 30) + " \u203A" }),
8943
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
8944
+ ] }),
8945
+ editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
8946
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
8947
+ /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "how many recent logs to replay: " }),
8948
+ /* @__PURE__ */ jsx3(TextInput2, { value: editLogs, onChange: setEditLogs, onSubmit: commitLogs }),
8949
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: totalLogs != null ? ` (you have ${totalLogs})` : "" })
8950
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
8951
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
8952
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(limit) + " \u203A" }),
8953
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n to type a number${totalLogs != null ? ` you have ${totalLogs}` : ""}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
8944
8954
  ] }),
8945
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n sample \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
8955
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n logs \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
8946
8956
  running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
8947
8957
  err2 ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err2 }) : null,
8948
- !running && !err2 && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet \u2014 press r to run" }) : null,
8958
+ !running && !err2 && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet, press r to run" }) : null,
8949
8959
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
8950
8960
  ] }) });
8951
8961
  }
8952
- var SAMPLES, pendingPolicyId, pct;
8962
+ var DEFAULT_LIMIT, pendingPolicyId, pct;
8953
8963
  var init_DryRun = __esm({
8954
8964
  "src/tui/panels/DryRun.tsx"() {
8955
8965
  "use strict";
@@ -8957,15 +8967,40 @@ var init_DryRun = __esm({
8957
8967
  init_components();
8958
8968
  init_hooks();
8959
8969
  init_theme();
8960
- SAMPLES = [100, 250, 500, 1e3, 2500, 5e3];
8970
+ DEFAULT_LIMIT = 1e3;
8961
8971
  pendingPolicyId = null;
8962
8972
  pct = (n, d) => d ? (n / d * 100).toFixed(1) + "%" : "0%";
8963
8973
  }
8964
8974
  });
8965
8975
 
8976
+ // src/tui/nav.ts
8977
+ function requestSection(label) {
8978
+ pending = label;
8979
+ for (const fn of subs) fn();
8980
+ }
8981
+ function takePendingSection() {
8982
+ const p = pending;
8983
+ pending = null;
8984
+ return p;
8985
+ }
8986
+ function onSectionRequest(fn) {
8987
+ subs.add(fn);
8988
+ return () => {
8989
+ subs.delete(fn);
8990
+ };
8991
+ }
8992
+ var pending, subs;
8993
+ var init_nav = __esm({
8994
+ "src/tui/nav.ts"() {
8995
+ "use strict";
8996
+ pending = null;
8997
+ subs = /* @__PURE__ */ new Set();
8998
+ }
8999
+ });
9000
+
8966
9001
  // src/tui/panels/Policies.tsx
8967
9002
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
8968
- import TextInput2 from "ink-text-input";
9003
+ import TextInput3 from "ink-text-input";
8969
9004
  import { useEffect as useEffect4, useState as useState4 } from "react";
8970
9005
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
8971
9006
  function ruleSummary(r) {
@@ -9042,8 +9077,20 @@ function PoliciesPanel({ focused }) {
9042
9077
  setDirty(false);
9043
9078
  setStatus("discarded");
9044
9079
  };
9080
+ const [creating, setCreating] = useState4(null);
9081
+ function commitNewPolicy(name) {
9082
+ const n = name.trim();
9083
+ setCreating(null);
9084
+ if (!n) return;
9085
+ setStatus("Creating\u2026");
9086
+ void api.policies.create({ id: `policy-${Date.now()}`, name: n, rules: [], mode: "denylist" }).then(() => {
9087
+ setStatus(`\u2713 created "${n}" - open it and press n to add rules`);
9088
+ list6.reload();
9089
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9090
+ }
9045
9091
  useInput3(
9046
9092
  (input, key) => {
9093
+ if (creating !== null) return;
9047
9094
  if (key.ctrl && input === "r" && !editing) {
9048
9095
  list6.reload();
9049
9096
  activeQ.reload();
@@ -9065,6 +9112,9 @@ function PoliciesPanel({ focused }) {
9065
9112
  activeQ.reload();
9066
9113
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9067
9114
  }
9115
+ } else if (input === "n" || input === "N") {
9116
+ setStatus(null);
9117
+ setCreating("");
9068
9118
  } else if (input === "x") {
9069
9119
  setStatus("Deactivating\u2026");
9070
9120
  void api.policies.setActive(null).then(() => {
@@ -9104,7 +9154,7 @@ function PoliciesPanel({ focused }) {
9104
9154
  } else if (input === "D") {
9105
9155
  if (selected) {
9106
9156
  requestDryRun(selected.id);
9107
- setStatus('Sent to Dry Run - open the "Dry Run" section in the left nav for the full report.');
9157
+ requestSection("Dry Run");
9108
9158
  }
9109
9159
  } else if (input === "s") void save();
9110
9160
  else if (input === "x") discard();
@@ -9132,7 +9182,7 @@ function PoliciesPanel({ focused }) {
9132
9182
  );
9133
9183
  if (view === "list") {
9134
9184
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
9135
- const lHead = 3 + (status ? 1 : 0);
9185
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
9136
9186
  const lBudget = Math.max(3, panelRows - lHead);
9137
9187
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
9138
9188
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -9149,7 +9199,11 @@ function PoliciesPanel({ focused }) {
9149
9199
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
9150
9200
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
9151
9201
  ] }),
9152
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
9202
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new policy \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
9203
+ creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
9204
+ /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
9205
+ /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
9206
+ ] }) : null,
9153
9207
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
9154
9208
  const i = lStart + wi;
9155
9209
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -9229,7 +9283,7 @@ function PoliciesPanel({ focused }) {
9229
9283
  return /* @__PURE__ */ jsxs4(Box4, { children: [
9230
9284
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
9231
9285
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
9232
- TextInput2,
9286
+ TextInput3,
9233
9287
  {
9234
9288
  value: editVal,
9235
9289
  onChange: setEditVal,
@@ -9257,6 +9311,7 @@ var init_Policies = __esm({
9257
9311
  "use strict";
9258
9312
  init_api_client();
9259
9313
  init_DryRun();
9314
+ init_nav();
9260
9315
  init_components();
9261
9316
  init_hooks();
9262
9317
  init_theme();
@@ -9466,7 +9521,7 @@ var init_RateLimit = __esm({
9466
9521
 
9467
9522
  // src/tui/panels/Dlp.tsx
9468
9523
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
9469
- import TextInput3 from "ink-text-input";
9524
+ import TextInput4 from "ink-text-input";
9470
9525
  import { useEffect as useEffect6, useState as useState6 } from "react";
9471
9526
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
9472
9527
  function DlpPanel({ focused }) {
@@ -9646,7 +9701,7 @@ function DlpPanel({ focused }) {
9646
9701
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
9647
9702
  /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `pattern for "${newName}" (glob, * = any chars, e.g. sk-* AKIA*): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
9648
9703
  /* @__PURE__ */ jsx6(
9649
- TextInput3,
9704
+ TextInput4,
9650
9705
  {
9651
9706
  value: input,
9652
9707
  onChange: setInput,
@@ -9690,7 +9745,7 @@ var init_Dlp = __esm({
9690
9745
 
9691
9746
  // src/tui/panels/Audit.tsx
9692
9747
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
9693
- import TextInput4 from "ink-text-input";
9748
+ import TextInput5 from "ink-text-input";
9694
9749
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
9695
9750
  import { homedir as homedir8 } from "os";
9696
9751
  import { join as join10 } from "path";
@@ -10129,7 +10184,7 @@ function AuditPanel({ active: active2, focused }) {
10129
10184
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
10130
10185
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
10131
10186
  /* @__PURE__ */ jsx7(
10132
- TextInput4,
10187
+ TextInput5,
10133
10188
  {
10134
10189
  value: sessSearch,
10135
10190
  onChange: (v) => {
@@ -10207,7 +10262,7 @@ function AuditPanel({ active: active2, focused }) {
10207
10262
  ": "
10208
10263
  ] }),
10209
10264
  /* @__PURE__ */ jsx7(
10210
- TextInput4,
10265
+ TextInput5,
10211
10266
  {
10212
10267
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
10213
10268
  onChange: (v) => {
@@ -10631,7 +10686,7 @@ var init_global_install = __esm({
10631
10686
 
10632
10687
  // src/tui/panels/Settings.tsx
10633
10688
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
10634
- import TextInput5 from "ink-text-input";
10689
+ import TextInput6 from "ink-text-input";
10635
10690
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
10636
10691
  import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
10637
10692
  function SettingsPanel({
@@ -11055,7 +11110,7 @@ function SettingsPanel({
11055
11110
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
11056
11111
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
11057
11112
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
11058
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
11113
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11059
11114
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
11060
11115
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
11061
11116
  fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(editor.target, cols - 16) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
@@ -11235,7 +11290,7 @@ function SettingsPanel({
11235
11290
  /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: theme.dim, children: focused ? `\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove \xB7 d delete \xB7 n add${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to configure" }),
11236
11291
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
11237
11292
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
11238
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
11293
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11239
11294
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
11240
11295
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
11241
11296
  ] }) });
@@ -11308,6 +11363,18 @@ function App() {
11308
11363
  const [viewNonce, setViewNonce] = useState9(0);
11309
11364
  const [section, setSection] = useState9(accounts.length === 0 ? SETTINGS_SECTION : 0);
11310
11365
  const [focus, setFocus] = useState9("nav");
11366
+ useEffect8(
11367
+ () => onSectionRequest(() => {
11368
+ const label = takePendingSection();
11369
+ if (!label) return;
11370
+ const idx = SECTIONS.findIndex((s) => s.label === label);
11371
+ if (idx >= 0) {
11372
+ setSection(idx);
11373
+ setFocus("panel");
11374
+ }
11375
+ }),
11376
+ []
11377
+ );
11311
11378
  const [help, setHelp] = useState9(false);
11312
11379
  const [update2, setUpdate] = useState9({ kind: "idle" });
11313
11380
  useEffect8(() => {
@@ -11445,6 +11512,7 @@ var init_App = __esm({
11445
11512
  init_DryRun();
11446
11513
  init_Settings();
11447
11514
  init_hooks();
11515
+ init_nav();
11448
11516
  init_client();
11449
11517
  init_api_client();
11450
11518
  init_self_update();
package/dist/tui/index.js CHANGED
@@ -1904,14 +1904,15 @@ function LivePanel({ active: active2 }) {
1904
1904
 
1905
1905
  // src/tui/panels/Policies.tsx
1906
1906
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
1907
- import TextInput2 from "ink-text-input";
1907
+ import TextInput3 from "ink-text-input";
1908
1908
  import { useEffect as useEffect4, useState as useState4 } from "react";
1909
1909
 
1910
1910
  // src/tui/panels/DryRun.tsx
1911
1911
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1912
+ import TextInput2 from "ink-text-input";
1912
1913
  import { useEffect as useEffect3, useState as useState3 } from "react";
1913
1914
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1914
- var SAMPLES = [100, 250, 500, 1e3, 2500, 5e3];
1915
+ var DEFAULT_LIMIT = 1e3;
1915
1916
  var pendingPolicyId = null;
1916
1917
  function requestDryRun(policyId) {
1917
1918
  pendingPolicyId = policyId;
@@ -1921,21 +1922,24 @@ function DryRunPanel({ focused }) {
1921
1922
  const list5 = useLoader(() => api.policies.list());
1922
1923
  const policies = list5.data?.policies ?? [];
1923
1924
  const [pi, setPi] = useState3(0);
1924
- const [si, setSi] = useState3(3);
1925
+ const [limit, setLimit] = useState3(DEFAULT_LIMIT);
1926
+ const [editLogs, setEditLogs] = useState3(null);
1927
+ const statsQ = useLoader(() => api.stats.get());
1928
+ const totalLogs = statsQ.data?.total_calls ?? null;
1925
1929
  const [res, setRes] = useState3(null);
1926
1930
  const [running, setRunning] = useState3(false);
1927
1931
  const [err, setErr] = useState3(null);
1928
1932
  const [off, setOff] = useState3(0);
1929
1933
  const { cols, rows } = usePanelSize();
1930
1934
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1931
- function run(policyIdx = pi, sampleIdx = si) {
1935
+ function run(policyIdx = pi, lim = limit) {
1932
1936
  const p = policies[policyIdx];
1933
1937
  if (!p) return;
1934
1938
  setRunning(true);
1935
1939
  setErr(null);
1936
1940
  setRes(null);
1937
1941
  setOff(0);
1938
- api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: SAMPLES[sampleIdx] }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
1942
+ api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: lim }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
1939
1943
  }
1940
1944
  useEffect3(() => {
1941
1945
  if (policies.length === 0 || res || running || err) return;
@@ -1946,22 +1950,28 @@ function DryRunPanel({ focused }) {
1946
1950
  pendingPolicyId = null;
1947
1951
  }
1948
1952
  setPi(idx);
1949
- run(idx, si);
1953
+ run(idx, limit);
1950
1954
  }, [policies.length]);
1951
1955
  useInput2((input, key) => {
1952
- if (!focused) return;
1956
+ if (!focused || editLogs !== null) return;
1953
1957
  if (key.upArrow) setOff((n) => Math.max(0, n - 1));
1954
1958
  else if (key.downArrow) setOff((n) => n + 1);
1955
- else if (input === "p") {
1959
+ else if (input === "p" || input === "P") {
1956
1960
  const n = policies.length ? (pi + 1) % policies.length : 0;
1957
1961
  setPi(n);
1958
- run(n, si);
1959
- } else if (input === "n") {
1960
- const n = (si + 1) % SAMPLES.length;
1961
- setSi(n);
1962
- run(pi, n);
1963
- } else if (input === "r") run(pi, si);
1962
+ run(n, limit);
1963
+ } else if (input === "n" || input === "N") {
1964
+ setEditLogs(String(limit));
1965
+ } else if (input === "r" || input === "R") run(pi, limit);
1964
1966
  });
1967
+ function commitLogs(raw) {
1968
+ const cap = totalLogs && totalLogs > 0 ? totalLogs : 5e3;
1969
+ const parsed = parseInt(raw.replace(/[^0-9]/g, ""), 10);
1970
+ const next = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, cap) : limit;
1971
+ setEditLogs(null);
1972
+ setLimit(next);
1973
+ run(pi, next);
1974
+ }
1965
1975
  const w = Math.max(40, cols);
1966
1976
  const lines = [];
1967
1977
  const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
@@ -2030,38 +2040,57 @@ function DryRunPanel({ focused }) {
2030
2040
  }
2031
2041
  }
2032
2042
  }
2033
- const headerRows = 3;
2043
+ const headerRows = 5;
2034
2044
  const budget = Math.max(3, rows - headerRows);
2035
2045
  const maxOff = Math.max(0, lines.length - budget);
2036
2046
  const start = Math.min(off, maxOff);
2037
2047
  const win = lines.slice(start, start + budget);
2038
2048
  const above = start;
2039
2049
  const below = Math.max(0, lines.length - (start + budget));
2040
- const sampleLabel = `${SAMPLES[si]}${res?.sampled != null ? ` (ran ${res.sampled})` : ""}`;
2041
2050
  return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2042
- /* @__PURE__ */ jsxs3(Box3, { children: [
2043
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
2044
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2045
- " ",
2046
- truncate(selected?.name ?? "no policy", 26)
2047
- ] }),
2048
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2049
- " mode: ",
2050
- selected?.mode ?? "-"
2051
- ] }),
2052
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2053
- " sample: ",
2054
- sampleLabel
2055
- ] })
2051
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
2052
+ /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2053
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " policy " }),
2054
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate(selected?.name ?? "none", 30) + " \u203A" }),
2055
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
2056
2056
  ] }),
2057
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n sample \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
2057
+ editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
2058
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
2059
+ /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "how many recent logs to replay: " }),
2060
+ /* @__PURE__ */ jsx3(TextInput2, { value: editLogs, onChange: setEditLogs, onSubmit: commitLogs }),
2061
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: totalLogs != null ? ` (you have ${totalLogs})` : "" })
2062
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2063
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
2064
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(limit) + " \u203A" }),
2065
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n to type a number${totalLogs != null ? ` you have ${totalLogs}` : ""}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
2066
+ ] }),
2067
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n logs \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
2058
2068
  running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
2059
2069
  err ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err }) : null,
2060
- !running && !err && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet \u2014 press r to run" }) : null,
2070
+ !running && !err && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet, press r to run" }) : null,
2061
2071
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
2062
2072
  ] }) });
2063
2073
  }
2064
2074
 
2075
+ // src/tui/nav.ts
2076
+ var pending = null;
2077
+ var subs = /* @__PURE__ */ new Set();
2078
+ function requestSection(label) {
2079
+ pending = label;
2080
+ for (const fn of subs) fn();
2081
+ }
2082
+ function takePendingSection() {
2083
+ const p = pending;
2084
+ pending = null;
2085
+ return p;
2086
+ }
2087
+ function onSectionRequest(fn) {
2088
+ subs.add(fn);
2089
+ return () => {
2090
+ subs.delete(fn);
2091
+ };
2092
+ }
2093
+
2065
2094
  // src/tui/panels/Policies.tsx
2066
2095
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2067
2096
  var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
@@ -2159,8 +2188,20 @@ function PoliciesPanel({ focused }) {
2159
2188
  setDirty(false);
2160
2189
  setStatus("discarded");
2161
2190
  };
2191
+ const [creating, setCreating] = useState4(null);
2192
+ function commitNewPolicy(name) {
2193
+ const n = name.trim();
2194
+ setCreating(null);
2195
+ if (!n) return;
2196
+ setStatus("Creating\u2026");
2197
+ void api.policies.create({ id: `policy-${Date.now()}`, name: n, rules: [], mode: "denylist" }).then(() => {
2198
+ setStatus(`\u2713 created "${n}" - open it and press n to add rules`);
2199
+ list5.reload();
2200
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2201
+ }
2162
2202
  useInput3(
2163
2203
  (input, key) => {
2204
+ if (creating !== null) return;
2164
2205
  if (key.ctrl && input === "r" && !editing) {
2165
2206
  list5.reload();
2166
2207
  activeQ.reload();
@@ -2182,6 +2223,9 @@ function PoliciesPanel({ focused }) {
2182
2223
  activeQ.reload();
2183
2224
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2184
2225
  }
2226
+ } else if (input === "n" || input === "N") {
2227
+ setStatus(null);
2228
+ setCreating("");
2185
2229
  } else if (input === "x") {
2186
2230
  setStatus("Deactivating\u2026");
2187
2231
  void api.policies.setActive(null).then(() => {
@@ -2221,7 +2265,7 @@ function PoliciesPanel({ focused }) {
2221
2265
  } else if (input === "D") {
2222
2266
  if (selected) {
2223
2267
  requestDryRun(selected.id);
2224
- setStatus('Sent to Dry Run - open the "Dry Run" section in the left nav for the full report.');
2268
+ requestSection("Dry Run");
2225
2269
  }
2226
2270
  } else if (input === "s") void save();
2227
2271
  else if (input === "x") discard();
@@ -2249,7 +2293,7 @@ function PoliciesPanel({ focused }) {
2249
2293
  );
2250
2294
  if (view === "list") {
2251
2295
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
2252
- const lHead = 3 + (status ? 1 : 0);
2296
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
2253
2297
  const lBudget = Math.max(3, panelRows - lHead);
2254
2298
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
2255
2299
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -2266,7 +2310,11 @@ function PoliciesPanel({ focused }) {
2266
2310
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2267
2311
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2268
2312
  ] }),
2269
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2313
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new policy \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2314
+ creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
2315
+ /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
2316
+ /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
2317
+ ] }) : null,
2270
2318
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
2271
2319
  const i = lStart + wi;
2272
2320
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -2346,7 +2394,7 @@ function PoliciesPanel({ focused }) {
2346
2394
  return /* @__PURE__ */ jsxs4(Box4, { children: [
2347
2395
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2348
2396
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
2349
- TextInput2,
2397
+ TextInput3,
2350
2398
  {
2351
2399
  value: editVal,
2352
2400
  onChange: setEditVal,
@@ -2541,7 +2589,7 @@ function FieldRow({ label, active: active2, children }) {
2541
2589
 
2542
2590
  // src/tui/panels/Dlp.tsx
2543
2591
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2544
- import TextInput3 from "ink-text-input";
2592
+ import TextInput4 from "ink-text-input";
2545
2593
  import { useEffect as useEffect6, useState as useState6 } from "react";
2546
2594
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2547
2595
  var MODES2 = ["off", "detect", "block"];
@@ -2722,7 +2770,7 @@ function DlpPanel({ focused }) {
2722
2770
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
2723
2771
  /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `pattern for "${newName}" (glob, * = any chars, e.g. sk-* AKIA*): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
2724
2772
  /* @__PURE__ */ jsx6(
2725
- TextInput3,
2773
+ TextInput4,
2726
2774
  {
2727
2775
  value: input,
2728
2776
  onChange: setInput,
@@ -2755,7 +2803,7 @@ function DlpPanel({ focused }) {
2755
2803
 
2756
2804
  // src/tui/panels/Audit.tsx
2757
2805
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2758
- import TextInput4 from "ink-text-input";
2806
+ import TextInput5 from "ink-text-input";
2759
2807
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2760
2808
  import { homedir as homedir5 } from "os";
2761
2809
  import { join as join5 } from "path";
@@ -3277,7 +3325,7 @@ function AuditPanel({ active: active2, focused }) {
3277
3325
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
3278
3326
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
3279
3327
  /* @__PURE__ */ jsx7(
3280
- TextInput4,
3328
+ TextInput5,
3281
3329
  {
3282
3330
  value: sessSearch,
3283
3331
  onChange: (v) => {
@@ -3355,7 +3403,7 @@ function AuditPanel({ active: active2, focused }) {
3355
3403
  ": "
3356
3404
  ] }),
3357
3405
  /* @__PURE__ */ jsx7(
3358
- TextInput4,
3406
+ TextInput5,
3359
3407
  {
3360
3408
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
3361
3409
  onChange: (v) => {
@@ -3379,7 +3427,7 @@ function AuditPanel({ active: active2, focused }) {
3379
3427
 
3380
3428
  // src/tui/panels/Settings.tsx
3381
3429
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3382
- import TextInput5 from "ink-text-input";
3430
+ import TextInput6 from "ink-text-input";
3383
3431
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
3384
3432
 
3385
3433
  // src/global-install.ts
@@ -4302,7 +4350,7 @@ function SettingsPanel({
4302
4350
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
4303
4351
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
4304
4352
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
4305
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4353
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4306
4354
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
4307
4355
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
4308
4356
  fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(editor.target, cols - 16) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
@@ -4482,7 +4530,7 @@ function SettingsPanel({
4482
4530
  /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: theme.dim, children: focused ? `\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove \xB7 d delete \xB7 n add${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to configure" }),
4483
4531
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
4484
4532
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
4485
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4533
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4486
4534
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
4487
4535
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
4488
4536
  ] }) });
@@ -4533,6 +4581,18 @@ function App() {
4533
4581
  const [viewNonce, setViewNonce] = useState9(0);
4534
4582
  const [section, setSection] = useState9(accounts.length === 0 ? SETTINGS_SECTION : 0);
4535
4583
  const [focus, setFocus] = useState9("nav");
4584
+ useEffect8(
4585
+ () => onSectionRequest(() => {
4586
+ const label = takePendingSection();
4587
+ if (!label) return;
4588
+ const idx = SECTIONS.findIndex((s) => s.label === label);
4589
+ if (idx >= 0) {
4590
+ setSection(idx);
4591
+ setFocus("panel");
4592
+ }
4593
+ }),
4594
+ []
4595
+ );
4536
4596
  const [help, setHelp] = useState9(false);
4537
4597
  const [update2, setUpdate] = useState9({ kind: "idle" });
4538
4598
  useEffect8(() => {
@@ -0,0 +1,6 @@
1
+ /** Ask the shell to switch to (and focus) the section with this label. */
2
+ export declare function requestSection(label: string): void;
3
+ /** Consume the pending request (returns null when there is none). */
4
+ export declare function takePendingSection(): string | null;
5
+ /** Subscribe to requests. Returns an unsubscribe function. */
6
+ export declare function onSectionRequest(fn: () => void): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.33",
3
+ "version": "0.82.35",
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": {