@solongate/proxy 0.82.34 → 0.82.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.
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 = Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT);
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}`));
@@ -8932,10 +8942,15 @@ function DryRunPanel({ focused }) {
8932
8942
  /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate2(selected?.name ?? "none", 30) + " \u203A" }),
8933
8943
  /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
8934
8944
  ] }),
8935
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
8945
+ editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
8936
8946
  /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
8937
- /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(SAMPLES[si]) + " \u203A" }),
8938
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n${res?.sampled != null ? ` replayed ${res.sampled} of your most recent 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: ` (max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)})` })
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 max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
8939
8954
  ] }),
8940
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" }),
8941
8956
  running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
@@ -8944,7 +8959,7 @@ function DryRunPanel({ focused }) {
8944
8959
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
8945
8960
  ] }) });
8946
8961
  }
8947
- var SAMPLES, pendingPolicyId, pct;
8962
+ var DEFAULT_LIMIT, MAX_LIMIT, pendingPolicyId, pct;
8948
8963
  var init_DryRun = __esm({
8949
8964
  "src/tui/panels/DryRun.tsx"() {
8950
8965
  "use strict";
@@ -8952,7 +8967,8 @@ var init_DryRun = __esm({
8952
8967
  init_components();
8953
8968
  init_hooks();
8954
8969
  init_theme();
8955
- SAMPLES = [100, 250, 500, 1e3, 2500, 5e3];
8970
+ DEFAULT_LIMIT = 1e3;
8971
+ MAX_LIMIT = 5e3;
8956
8972
  pendingPolicyId = null;
8957
8973
  pct = (n, d) => d ? (n / d * 100).toFixed(1) + "%" : "0%";
8958
8974
  }
@@ -8985,7 +9001,7 @@ var init_nav = __esm({
8985
9001
 
8986
9002
  // src/tui/panels/Policies.tsx
8987
9003
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
8988
- import TextInput2 from "ink-text-input";
9004
+ import TextInput3 from "ink-text-input";
8989
9005
  import { useEffect as useEffect4, useState as useState4 } from "react";
8990
9006
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
8991
9007
  function ruleSummary(r) {
@@ -9048,7 +9064,7 @@ function PoliciesPanel({ focused }) {
9048
9064
  });
9049
9065
  setRules(res.rules);
9050
9066
  setDirty(false);
9051
- setStatus("\u2713 Saved v" + res._version);
9067
+ setStatus("\u2713 Saved");
9052
9068
  list6.reload();
9053
9069
  } catch (e) {
9054
9070
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
@@ -9062,8 +9078,20 @@ function PoliciesPanel({ focused }) {
9062
9078
  setDirty(false);
9063
9079
  setStatus("discarded");
9064
9080
  };
9081
+ const [creating, setCreating] = useState4(null);
9082
+ function commitNewPolicy(name) {
9083
+ const n = name.trim();
9084
+ setCreating(null);
9085
+ if (!n) return;
9086
+ setStatus("Creating\u2026");
9087
+ void api.policies.create({ id: `policy-${Date.now()}`, name: n, rules: [], mode: "denylist" }).then(() => {
9088
+ setStatus(`\u2713 created "${n}" - open it and press n to add rules`);
9089
+ list6.reload();
9090
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9091
+ }
9065
9092
  useInput3(
9066
9093
  (input, key) => {
9094
+ if (creating !== null) return;
9067
9095
  if (key.ctrl && input === "r" && !editing) {
9068
9096
  list6.reload();
9069
9097
  activeQ.reload();
@@ -9085,6 +9113,9 @@ function PoliciesPanel({ focused }) {
9085
9113
  activeQ.reload();
9086
9114
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9087
9115
  }
9116
+ } else if (input === "n" || input === "N") {
9117
+ setStatus(null);
9118
+ setCreating("");
9088
9119
  } else if (input === "x") {
9089
9120
  setStatus("Deactivating\u2026");
9090
9121
  void api.policies.setActive(null).then(() => {
@@ -9152,7 +9183,7 @@ function PoliciesPanel({ focused }) {
9152
9183
  );
9153
9184
  if (view === "list") {
9154
9185
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
9155
- const lHead = 3 + (status ? 1 : 0);
9186
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
9156
9187
  const lBudget = Math.max(3, panelRows - lHead);
9157
9188
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
9158
9189
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -9169,7 +9200,11 @@ function PoliciesPanel({ focused }) {
9169
9200
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
9170
9201
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
9171
9202
  ] }),
9172
- /* @__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" }),
9203
+ /* @__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" }),
9204
+ creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
9205
+ /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
9206
+ /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
9207
+ ] }) : null,
9173
9208
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
9174
9209
  const i = lStart + wi;
9175
9210
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -9249,7 +9284,7 @@ function PoliciesPanel({ focused }) {
9249
9284
  return /* @__PURE__ */ jsxs4(Box4, { children: [
9250
9285
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
9251
9286
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
9252
- TextInput2,
9287
+ TextInput3,
9253
9288
  {
9254
9289
  value: editVal,
9255
9290
  onChange: setEditVal,
@@ -9487,7 +9522,7 @@ var init_RateLimit = __esm({
9487
9522
 
9488
9523
  // src/tui/panels/Dlp.tsx
9489
9524
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
9490
- import TextInput3 from "ink-text-input";
9525
+ import TextInput4 from "ink-text-input";
9491
9526
  import { useEffect as useEffect6, useState as useState6 } from "react";
9492
9527
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
9493
9528
  function DlpPanel({ focused }) {
@@ -9667,7 +9702,7 @@ function DlpPanel({ focused }) {
9667
9702
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
9668
9703
  /* @__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): " }),
9669
9704
  /* @__PURE__ */ jsx6(
9670
- TextInput3,
9705
+ TextInput4,
9671
9706
  {
9672
9707
  value: input,
9673
9708
  onChange: setInput,
@@ -9711,7 +9746,7 @@ var init_Dlp = __esm({
9711
9746
 
9712
9747
  // src/tui/panels/Audit.tsx
9713
9748
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
9714
- import TextInput4 from "ink-text-input";
9749
+ import TextInput5 from "ink-text-input";
9715
9750
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
9716
9751
  import { homedir as homedir8 } from "os";
9717
9752
  import { join as join10 } from "path";
@@ -10150,7 +10185,7 @@ function AuditPanel({ active: active2, focused }) {
10150
10185
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
10151
10186
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
10152
10187
  /* @__PURE__ */ jsx7(
10153
- TextInput4,
10188
+ TextInput5,
10154
10189
  {
10155
10190
  value: sessSearch,
10156
10191
  onChange: (v) => {
@@ -10228,7 +10263,7 @@ function AuditPanel({ active: active2, focused }) {
10228
10263
  ": "
10229
10264
  ] }),
10230
10265
  /* @__PURE__ */ jsx7(
10231
- TextInput4,
10266
+ TextInput5,
10232
10267
  {
10233
10268
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
10234
10269
  onChange: (v) => {
@@ -10652,7 +10687,7 @@ var init_global_install = __esm({
10652
10687
 
10653
10688
  // src/tui/panels/Settings.tsx
10654
10689
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
10655
- import TextInput5 from "ink-text-input";
10690
+ import TextInput6 from "ink-text-input";
10656
10691
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
10657
10692
  import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
10658
10693
  function SettingsPanel({
@@ -11076,7 +11111,7 @@ function SettingsPanel({
11076
11111
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
11077
11112
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
11078
11113
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
11079
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
11114
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11080
11115
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
11081
11116
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
11082
11117
  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"),
@@ -11256,7 +11291,7 @@ function SettingsPanel({
11256
11291
  /* @__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" }),
11257
11292
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
11258
11293
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
11259
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
11294
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11260
11295
  ] }) : 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: " " }),
11261
11296
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
11262
11297
  ] }) });
package/dist/tui/index.js CHANGED
@@ -1904,14 +1904,16 @@ 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;
1916
+ var MAX_LIMIT = 5e3;
1915
1917
  var pendingPolicyId = null;
1916
1918
  function requestDryRun(policyId) {
1917
1919
  pendingPolicyId = policyId;
@@ -1921,21 +1923,24 @@ function DryRunPanel({ focused }) {
1921
1923
  const list5 = useLoader(() => api.policies.list());
1922
1924
  const policies = list5.data?.policies ?? [];
1923
1925
  const [pi, setPi] = useState3(0);
1924
- const [si, setSi] = useState3(3);
1926
+ const [limit, setLimit] = useState3(DEFAULT_LIMIT);
1927
+ const [editLogs, setEditLogs] = useState3(null);
1928
+ const statsQ = useLoader(() => api.stats.get());
1929
+ const totalLogs = statsQ.data?.total_calls ?? null;
1925
1930
  const [res, setRes] = useState3(null);
1926
1931
  const [running, setRunning] = useState3(false);
1927
1932
  const [err, setErr] = useState3(null);
1928
1933
  const [off, setOff] = useState3(0);
1929
1934
  const { cols, rows } = usePanelSize();
1930
1935
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1931
- function run(policyIdx = pi, sampleIdx = si) {
1936
+ function run(policyIdx = pi, lim = limit) {
1932
1937
  const p = policies[policyIdx];
1933
1938
  if (!p) return;
1934
1939
  setRunning(true);
1935
1940
  setErr(null);
1936
1941
  setRes(null);
1937
1942
  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));
1943
+ 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
1944
  }
1940
1945
  useEffect3(() => {
1941
1946
  if (policies.length === 0 || res || running || err) return;
@@ -1946,22 +1951,28 @@ function DryRunPanel({ focused }) {
1946
1951
  pendingPolicyId = null;
1947
1952
  }
1948
1953
  setPi(idx);
1949
- run(idx, si);
1954
+ run(idx, limit);
1950
1955
  }, [policies.length]);
1951
1956
  useInput2((input, key) => {
1952
- if (!focused) return;
1957
+ if (!focused || editLogs !== null) return;
1953
1958
  if (key.upArrow) setOff((n) => Math.max(0, n - 1));
1954
1959
  else if (key.downArrow) setOff((n) => n + 1);
1955
- else if (input === "p") {
1960
+ else if (input === "p" || input === "P") {
1956
1961
  const n = policies.length ? (pi + 1) % policies.length : 0;
1957
1962
  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);
1963
+ run(n, limit);
1964
+ } else if (input === "n" || input === "N") {
1965
+ setEditLogs(String(limit));
1966
+ } else if (input === "r" || input === "R") run(pi, limit);
1964
1967
  });
1968
+ function commitLogs(raw) {
1969
+ const cap = Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT);
1970
+ const parsed = parseInt(raw.replace(/[^0-9]/g, ""), 10);
1971
+ const next = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, cap) : limit;
1972
+ setEditLogs(null);
1973
+ setLimit(next);
1974
+ run(pi, next);
1975
+ }
1965
1976
  const w = Math.max(40, cols);
1966
1977
  const lines = [];
1967
1978
  const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
@@ -2044,10 +2055,15 @@ function DryRunPanel({ focused }) {
2044
2055
  /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate(selected?.name ?? "none", 30) + " \u203A" }),
2045
2056
  /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
2046
2057
  ] }),
2047
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2058
+ editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
2048
2059
  /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
2049
- /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(SAMPLES[si]) + " \u203A" }),
2050
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n${res?.sampled != null ? ` replayed ${res.sampled} of your most recent logs` : ""}` })
2060
+ /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "how many recent logs to replay: " }),
2061
+ /* @__PURE__ */ jsx3(TextInput2, { value: editLogs, onChange: setEditLogs, onSubmit: commitLogs }),
2062
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` (max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)})` })
2063
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2064
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
2065
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(limit) + " \u203A" }),
2066
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n to type a number max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
2051
2067
  ] }),
2052
2068
  /* @__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" }),
2053
2069
  running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
@@ -2159,7 +2175,7 @@ function PoliciesPanel({ focused }) {
2159
2175
  });
2160
2176
  setRules(res.rules);
2161
2177
  setDirty(false);
2162
- setStatus("\u2713 Saved v" + res._version);
2178
+ setStatus("\u2713 Saved");
2163
2179
  list5.reload();
2164
2180
  } catch (e) {
2165
2181
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
@@ -2173,8 +2189,20 @@ function PoliciesPanel({ focused }) {
2173
2189
  setDirty(false);
2174
2190
  setStatus("discarded");
2175
2191
  };
2192
+ const [creating, setCreating] = useState4(null);
2193
+ function commitNewPolicy(name) {
2194
+ const n = name.trim();
2195
+ setCreating(null);
2196
+ if (!n) return;
2197
+ setStatus("Creating\u2026");
2198
+ void api.policies.create({ id: `policy-${Date.now()}`, name: n, rules: [], mode: "denylist" }).then(() => {
2199
+ setStatus(`\u2713 created "${n}" - open it and press n to add rules`);
2200
+ list5.reload();
2201
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2202
+ }
2176
2203
  useInput3(
2177
2204
  (input, key) => {
2205
+ if (creating !== null) return;
2178
2206
  if (key.ctrl && input === "r" && !editing) {
2179
2207
  list5.reload();
2180
2208
  activeQ.reload();
@@ -2196,6 +2224,9 @@ function PoliciesPanel({ focused }) {
2196
2224
  activeQ.reload();
2197
2225
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2198
2226
  }
2227
+ } else if (input === "n" || input === "N") {
2228
+ setStatus(null);
2229
+ setCreating("");
2199
2230
  } else if (input === "x") {
2200
2231
  setStatus("Deactivating\u2026");
2201
2232
  void api.policies.setActive(null).then(() => {
@@ -2263,7 +2294,7 @@ function PoliciesPanel({ focused }) {
2263
2294
  );
2264
2295
  if (view === "list") {
2265
2296
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
2266
- const lHead = 3 + (status ? 1 : 0);
2297
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
2267
2298
  const lBudget = Math.max(3, panelRows - lHead);
2268
2299
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
2269
2300
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -2280,7 +2311,11 @@ function PoliciesPanel({ focused }) {
2280
2311
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2281
2312
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2282
2313
  ] }),
2283
- /* @__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" }),
2314
+ /* @__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" }),
2315
+ creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
2316
+ /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
2317
+ /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
2318
+ ] }) : null,
2284
2319
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
2285
2320
  const i = lStart + wi;
2286
2321
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -2360,7 +2395,7 @@ function PoliciesPanel({ focused }) {
2360
2395
  return /* @__PURE__ */ jsxs4(Box4, { children: [
2361
2396
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2362
2397
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
2363
- TextInput2,
2398
+ TextInput3,
2364
2399
  {
2365
2400
  value: editVal,
2366
2401
  onChange: setEditVal,
@@ -2555,7 +2590,7 @@ function FieldRow({ label, active: active2, children }) {
2555
2590
 
2556
2591
  // src/tui/panels/Dlp.tsx
2557
2592
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2558
- import TextInput3 from "ink-text-input";
2593
+ import TextInput4 from "ink-text-input";
2559
2594
  import { useEffect as useEffect6, useState as useState6 } from "react";
2560
2595
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2561
2596
  var MODES2 = ["off", "detect", "block"];
@@ -2736,7 +2771,7 @@ function DlpPanel({ focused }) {
2736
2771
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
2737
2772
  /* @__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): " }),
2738
2773
  /* @__PURE__ */ jsx6(
2739
- TextInput3,
2774
+ TextInput4,
2740
2775
  {
2741
2776
  value: input,
2742
2777
  onChange: setInput,
@@ -2769,7 +2804,7 @@ function DlpPanel({ focused }) {
2769
2804
 
2770
2805
  // src/tui/panels/Audit.tsx
2771
2806
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2772
- import TextInput4 from "ink-text-input";
2807
+ import TextInput5 from "ink-text-input";
2773
2808
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2774
2809
  import { homedir as homedir5 } from "os";
2775
2810
  import { join as join5 } from "path";
@@ -3291,7 +3326,7 @@ function AuditPanel({ active: active2, focused }) {
3291
3326
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
3292
3327
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
3293
3328
  /* @__PURE__ */ jsx7(
3294
- TextInput4,
3329
+ TextInput5,
3295
3330
  {
3296
3331
  value: sessSearch,
3297
3332
  onChange: (v) => {
@@ -3369,7 +3404,7 @@ function AuditPanel({ active: active2, focused }) {
3369
3404
  ": "
3370
3405
  ] }),
3371
3406
  /* @__PURE__ */ jsx7(
3372
- TextInput4,
3407
+ TextInput5,
3373
3408
  {
3374
3409
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
3375
3410
  onChange: (v) => {
@@ -3393,7 +3428,7 @@ function AuditPanel({ active: active2, focused }) {
3393
3428
 
3394
3429
  // src/tui/panels/Settings.tsx
3395
3430
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3396
- import TextInput5 from "ink-text-input";
3431
+ import TextInput6 from "ink-text-input";
3397
3432
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
3398
3433
 
3399
3434
  // src/global-install.ts
@@ -4316,7 +4351,7 @@ function SettingsPanel({
4316
4351
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
4317
4352
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
4318
4353
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
4319
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4354
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4320
4355
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
4321
4356
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
4322
4357
  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"),
@@ -4496,7 +4531,7 @@ function SettingsPanel({
4496
4531
  /* @__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" }),
4497
4532
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
4498
4533
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
4499
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4534
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4500
4535
  ] }) : 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: " " }),
4501
4536
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
4502
4537
  ] }) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.34",
3
+ "version": "0.82.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": {