@solongate/proxy 0.82.34 → 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}`));
@@ -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: 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}` : ""}` })
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, pendingPolicyId, pct;
8948
8963
  var init_DryRun = __esm({
8949
8964
  "src/tui/panels/DryRun.tsx"() {
8950
8965
  "use strict";
@@ -8952,7 +8967,7 @@ 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;
8956
8971
  pendingPolicyId = null;
8957
8972
  pct = (n, d) => d ? (n / d * 100).toFixed(1) + "%" : "0%";
8958
8973
  }
@@ -8985,7 +9000,7 @@ var init_nav = __esm({
8985
9000
 
8986
9001
  // src/tui/panels/Policies.tsx
8987
9002
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
8988
- import TextInput2 from "ink-text-input";
9003
+ import TextInput3 from "ink-text-input";
8989
9004
  import { useEffect as useEffect4, useState as useState4 } from "react";
8990
9005
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
8991
9006
  function ruleSummary(r) {
@@ -9062,8 +9077,20 @@ function PoliciesPanel({ focused }) {
9062
9077
  setDirty(false);
9063
9078
  setStatus("discarded");
9064
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
+ }
9065
9091
  useInput3(
9066
9092
  (input, key) => {
9093
+ if (creating !== null) return;
9067
9094
  if (key.ctrl && input === "r" && !editing) {
9068
9095
  list6.reload();
9069
9096
  activeQ.reload();
@@ -9085,6 +9112,9 @@ function PoliciesPanel({ focused }) {
9085
9112
  activeQ.reload();
9086
9113
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9087
9114
  }
9115
+ } else if (input === "n" || input === "N") {
9116
+ setStatus(null);
9117
+ setCreating("");
9088
9118
  } else if (input === "x") {
9089
9119
  setStatus("Deactivating\u2026");
9090
9120
  void api.policies.setActive(null).then(() => {
@@ -9152,7 +9182,7 @@ function PoliciesPanel({ focused }) {
9152
9182
  );
9153
9183
  if (view === "list") {
9154
9184
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
9155
- const lHead = 3 + (status ? 1 : 0);
9185
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
9156
9186
  const lBudget = Math.max(3, panelRows - lHead);
9157
9187
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
9158
9188
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -9169,7 +9199,11 @@ function PoliciesPanel({ focused }) {
9169
9199
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
9170
9200
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
9171
9201
  ] }),
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" }),
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,
9173
9207
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
9174
9208
  const i = lStart + wi;
9175
9209
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -9249,7 +9283,7 @@ function PoliciesPanel({ focused }) {
9249
9283
  return /* @__PURE__ */ jsxs4(Box4, { children: [
9250
9284
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
9251
9285
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
9252
- TextInput2,
9286
+ TextInput3,
9253
9287
  {
9254
9288
  value: editVal,
9255
9289
  onChange: setEditVal,
@@ -9487,7 +9521,7 @@ var init_RateLimit = __esm({
9487
9521
 
9488
9522
  // src/tui/panels/Dlp.tsx
9489
9523
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
9490
- import TextInput3 from "ink-text-input";
9524
+ import TextInput4 from "ink-text-input";
9491
9525
  import { useEffect as useEffect6, useState as useState6 } from "react";
9492
9526
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
9493
9527
  function DlpPanel({ focused }) {
@@ -9667,7 +9701,7 @@ function DlpPanel({ focused }) {
9667
9701
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
9668
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): " }),
9669
9703
  /* @__PURE__ */ jsx6(
9670
- TextInput3,
9704
+ TextInput4,
9671
9705
  {
9672
9706
  value: input,
9673
9707
  onChange: setInput,
@@ -9711,7 +9745,7 @@ var init_Dlp = __esm({
9711
9745
 
9712
9746
  // src/tui/panels/Audit.tsx
9713
9747
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
9714
- import TextInput4 from "ink-text-input";
9748
+ import TextInput5 from "ink-text-input";
9715
9749
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
9716
9750
  import { homedir as homedir8 } from "os";
9717
9751
  import { join as join10 } from "path";
@@ -10150,7 +10184,7 @@ function AuditPanel({ active: active2, focused }) {
10150
10184
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
10151
10185
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
10152
10186
  /* @__PURE__ */ jsx7(
10153
- TextInput4,
10187
+ TextInput5,
10154
10188
  {
10155
10189
  value: sessSearch,
10156
10190
  onChange: (v) => {
@@ -10228,7 +10262,7 @@ function AuditPanel({ active: active2, focused }) {
10228
10262
  ": "
10229
10263
  ] }),
10230
10264
  /* @__PURE__ */ jsx7(
10231
- TextInput4,
10265
+ TextInput5,
10232
10266
  {
10233
10267
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
10234
10268
  onChange: (v) => {
@@ -10652,7 +10686,7 @@ var init_global_install = __esm({
10652
10686
 
10653
10687
  // src/tui/panels/Settings.tsx
10654
10688
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
10655
- import TextInput5 from "ink-text-input";
10689
+ import TextInput6 from "ink-text-input";
10656
10690
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
10657
10691
  import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
10658
10692
  function SettingsPanel({
@@ -11076,7 +11110,7 @@ function SettingsPanel({
11076
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" }),
11077
11111
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
11078
11112
  /* @__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 })
11113
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11080
11114
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
11081
11115
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
11082
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"),
@@ -11256,7 +11290,7 @@ function SettingsPanel({
11256
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" }),
11257
11291
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
11258
11292
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
11259
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
11293
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
11260
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: " " }),
11261
11295
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
11262
11296
  ] }) });
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}`));
@@ -2044,10 +2054,15 @@ function DryRunPanel({ focused }) {
2044
2054
  /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate(selected?.name ?? "none", 30) + " \u203A" }),
2045
2055
  /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
2046
2056
  ] }),
2047
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2057
+ editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
2048
2058
  /* @__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` : ""}` })
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}` : ""}` })
2051
2066
  ] }),
2052
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" }),
2053
2068
  running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
@@ -2173,8 +2188,20 @@ function PoliciesPanel({ focused }) {
2173
2188
  setDirty(false);
2174
2189
  setStatus("discarded");
2175
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
+ }
2176
2202
  useInput3(
2177
2203
  (input, key) => {
2204
+ if (creating !== null) return;
2178
2205
  if (key.ctrl && input === "r" && !editing) {
2179
2206
  list5.reload();
2180
2207
  activeQ.reload();
@@ -2196,6 +2223,9 @@ function PoliciesPanel({ focused }) {
2196
2223
  activeQ.reload();
2197
2224
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2198
2225
  }
2226
+ } else if (input === "n" || input === "N") {
2227
+ setStatus(null);
2228
+ setCreating("");
2199
2229
  } else if (input === "x") {
2200
2230
  setStatus("Deactivating\u2026");
2201
2231
  void api.policies.setActive(null).then(() => {
@@ -2263,7 +2293,7 @@ function PoliciesPanel({ focused }) {
2263
2293
  );
2264
2294
  if (view === "list") {
2265
2295
  const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
2266
- const lHead = 3 + (status ? 1 : 0);
2296
+ const lHead = 3 + (status ? 1 : 0) + (creating !== null ? 1 : 0);
2267
2297
  const lBudget = Math.max(3, panelRows - lHead);
2268
2298
  const lStart = Math.min(Math.max(0, pi - Math.floor(lBudget / 2)), Math.max(0, policies.length - lBudget));
2269
2299
  const lWin = policies.slice(lStart, lStart + lBudget);
@@ -2280,7 +2310,11 @@ function PoliciesPanel({ focused }) {
2280
2310
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2281
2311
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2282
2312
  ] }),
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" }),
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,
2284
2318
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
2285
2319
  const i = lStart + wi;
2286
2320
  return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
@@ -2360,7 +2394,7 @@ function PoliciesPanel({ focused }) {
2360
2394
  return /* @__PURE__ */ jsxs4(Box4, { children: [
2361
2395
  /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2362
2396
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
2363
- TextInput2,
2397
+ TextInput3,
2364
2398
  {
2365
2399
  value: editVal,
2366
2400
  onChange: setEditVal,
@@ -2555,7 +2589,7 @@ function FieldRow({ label, active: active2, children }) {
2555
2589
 
2556
2590
  // src/tui/panels/Dlp.tsx
2557
2591
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2558
- import TextInput3 from "ink-text-input";
2592
+ import TextInput4 from "ink-text-input";
2559
2593
  import { useEffect as useEffect6, useState as useState6 } from "react";
2560
2594
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2561
2595
  var MODES2 = ["off", "detect", "block"];
@@ -2736,7 +2770,7 @@ function DlpPanel({ focused }) {
2736
2770
  adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
2737
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): " }),
2738
2772
  /* @__PURE__ */ jsx6(
2739
- TextInput3,
2773
+ TextInput4,
2740
2774
  {
2741
2775
  value: input,
2742
2776
  onChange: setInput,
@@ -2769,7 +2803,7 @@ function DlpPanel({ focused }) {
2769
2803
 
2770
2804
  // src/tui/panels/Audit.tsx
2771
2805
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2772
- import TextInput4 from "ink-text-input";
2806
+ import TextInput5 from "ink-text-input";
2773
2807
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2774
2808
  import { homedir as homedir5 } from "os";
2775
2809
  import { join as join5 } from "path";
@@ -3291,7 +3325,7 @@ function AuditPanel({ active: active2, focused }) {
3291
3325
  editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
3292
3326
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
3293
3327
  /* @__PURE__ */ jsx7(
3294
- TextInput4,
3328
+ TextInput5,
3295
3329
  {
3296
3330
  value: sessSearch,
3297
3331
  onChange: (v) => {
@@ -3369,7 +3403,7 @@ function AuditPanel({ active: active2, focused }) {
3369
3403
  ": "
3370
3404
  ] }),
3371
3405
  /* @__PURE__ */ jsx7(
3372
- TextInput4,
3406
+ TextInput5,
3373
3407
  {
3374
3408
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
3375
3409
  onChange: (v) => {
@@ -3393,7 +3427,7 @@ function AuditPanel({ active: active2, focused }) {
3393
3427
 
3394
3428
  // src/tui/panels/Settings.tsx
3395
3429
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3396
- import TextInput5 from "ink-text-input";
3430
+ import TextInput6 from "ink-text-input";
3397
3431
  import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
3398
3432
 
3399
3433
  // src/global-install.ts
@@ -4316,7 +4350,7 @@ function SettingsPanel({
4316
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" }),
4317
4351
  editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
4318
4352
  /* @__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 })
4353
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4320
4354
  ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
4321
4355
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
4322
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"),
@@ -4496,7 +4530,7 @@ function SettingsPanel({
4496
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" }),
4497
4531
  editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
4498
4532
  /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
4499
- /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4533
+ /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
4500
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: " " }),
4501
4535
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
4502
4536
  ] }) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.34",
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": {