@solongate/proxy 0.81.72 → 0.81.74

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.
Files changed (3) hide show
  1. package/dist/index.js +148 -102
  2. package/dist/tui/index.js +148 -102
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9273,28 +9273,26 @@ import TextInput3 from "ink-text-input";
9273
9273
  import { useEffect as useEffect5, useState as useState5 } from "react";
9274
9274
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
9275
9275
  function DlpPanel({ focused }) {
9276
+ const { cols, rows } = usePanelSize();
9276
9277
  const q = useLoader(() => api.settings.getSecurityLayers());
9277
9278
  const [dlp, setDlp] = useState5(null);
9278
9279
  const [available, setAvailable] = useState5([]);
9280
+ const [ghostOn, setGhostOn] = useState5(false);
9281
+ const [ghostPats, setGhostPats] = useState5([]);
9279
9282
  const [sel, setSel] = useState5(0);
9280
9283
  const [dirty, setDirty] = useState5(false);
9281
9284
  const [status, setStatus] = useState5(null);
9282
9285
  const [adding, setAdding] = useState5(null);
9283
9286
  const [newName, setNewName] = useState5("");
9284
- const [newRe, setNewRe] = useState5("");
9285
- const [ghostOn, setGhostOn] = useState5(false);
9286
- const spQ = useLoader(() => api.settings.getSelfProtection());
9287
- const [selfProt, setSelfProt] = useState5(null);
9287
+ const [input, setInput] = useState5("");
9288
9288
  useEffect5(() => {
9289
9289
  if (q.data && !dlp) {
9290
9290
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
9291
9291
  setAvailable(q.data.availablePatterns);
9292
9292
  setGhostOn(q.data.layers.ghost.mode === "on");
9293
+ setGhostPats([...q.data.layers.ghost.patterns]);
9293
9294
  }
9294
9295
  }, [q.data, dlp]);
9295
- useEffect5(() => {
9296
- if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
9297
- }, [spQ.data, selfProt]);
9298
9296
  const mutate = (next) => {
9299
9297
  setDlp(next);
9300
9298
  setDirty(true);
@@ -9304,128 +9302,175 @@ function DlpPanel({ focused }) {
9304
9302
  if (!dlp || !q.data) return;
9305
9303
  setStatus("Saving\u2026");
9306
9304
  try {
9307
- const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
9305
+ const ghost = { mode: ghostOn ? "on" : "off", patterns: ghostPats };
9308
9306
  const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
9309
9307
  setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
9310
9308
  setGhostOn(res.layers.ghost.mode === "on");
9309
+ setGhostPats([...res.layers.ghost.patterns]);
9311
9310
  setDirty(false);
9312
9311
  setStatus("\u2713 Saved");
9313
9312
  } catch (e) {
9314
9313
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
9315
9314
  }
9316
9315
  };
9317
- const toggleSelfProt = async () => {
9318
- const next = !(selfProt ?? false);
9319
- setSelfProt(next);
9320
- try {
9321
- await api.settings.setSelfProtection(next);
9322
- setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
9323
- } catch (e) {
9324
- setSelfProt(!next);
9325
- setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
9326
- }
9327
- };
9328
9316
  const discard = () => {
9329
- if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
9317
+ if (!q.data) return;
9318
+ setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
9319
+ setGhostOn(q.data.layers.ghost.mode === "on");
9320
+ setGhostPats([...q.data.layers.ghost.patterns]);
9330
9321
  setDirty(false);
9331
9322
  setStatus("discarded");
9332
9323
  };
9333
- const customCount = dlp?.custom.length ?? 0;
9334
- const total = available.length + customCount;
9324
+ const entries = [
9325
+ ...available.map((name) => ({ kind: "builtin", name })),
9326
+ ...(dlp?.custom ?? []).map((_, i) => ({ kind: "custom", i })),
9327
+ ...ghostPats.map((_, i) => ({ kind: "ghost", i }))
9328
+ ];
9329
+ const selC = Math.min(sel, Math.max(0, entries.length - 1));
9330
+ const cur = entries[selC];
9335
9331
  useInput4(
9336
- (input, key) => {
9332
+ (input2, key) => {
9337
9333
  if (!dlp) return;
9338
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9339
- else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
9340
- else if (input === "m") {
9341
- const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
9342
- mutate({ ...dlp, mode: MODES2[idx] });
9343
- } else if (input === "a") {
9344
- setNewName("");
9345
- setNewRe("");
9346
- setAdding("name");
9347
- } else if (input === " " && sel < available.length) {
9348
- const p = available[sel];
9334
+ if (key.upArrow) setSel(() => Math.max(0, selC - 1));
9335
+ else if (key.downArrow) setSel(() => Math.min(entries.length - 1, selC + 1));
9336
+ else if (input2 === "m") mutate({ ...dlp, mode: MODES2[(MODES2.indexOf(dlp.mode) + 1) % MODES2.length] });
9337
+ else if (input2 === " " && cur?.kind === "builtin") {
9349
9338
  const set = new Set(dlp.patterns);
9350
- if (set.has(p)) set.delete(p);
9351
- else set.add(p);
9339
+ if (set.has(cur.name)) set.delete(cur.name);
9340
+ else set.add(cur.name);
9352
9341
  mutate({ ...dlp, patterns: [...set] });
9353
- } else if (input === "d" && sel >= available.length) {
9354
- const ci = sel - available.length;
9355
- mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
9356
- setSel((n) => Math.max(0, n - 1));
9357
- } else if (input === "g") {
9342
+ } else if (input2 === "a") {
9343
+ setNewName("");
9344
+ setInput("");
9345
+ setAdding("name");
9346
+ } else if (input2 === "r") {
9347
+ setInput("");
9348
+ setAdding("route");
9349
+ } else if (input2 === "d") {
9350
+ if (cur?.kind === "custom") {
9351
+ mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== cur.i) });
9352
+ setSel(() => Math.max(0, selC - 1));
9353
+ } else if (cur?.kind === "ghost") {
9354
+ setGhostPats((ps) => ps.filter((_, i) => i !== cur.i));
9355
+ setDirty(true);
9356
+ setStatus(null);
9357
+ setSel(() => Math.max(0, selC - 1));
9358
+ }
9359
+ } else if (input2 === "g") {
9358
9360
  setGhostOn((v) => !v);
9359
9361
  setDirty(true);
9360
9362
  setStatus(null);
9361
- } else if (input === "P") void toggleSelfProt();
9362
- else if (input === "s") void save();
9363
- else if (input === "x") discard();
9363
+ } else if (input2 === "s") void save();
9364
+ else if (input2 === "x") discard();
9364
9365
  },
9365
9366
  { isActive: focused && !adding }
9366
9367
  );
9367
9368
  const enabled = new Set(dlp?.patterns ?? []);
9369
+ const lines = [];
9370
+ const header = (key, label, extra) => lines.push({
9371
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
9372
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accentBright, bold: true, children: label }),
9373
+ extra ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + extra }) : null
9374
+ ] }, key),
9375
+ entry: -1
9376
+ });
9377
+ const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
9378
+ let ei = 0;
9379
+ header("h:builtin", "built-in secret patterns", "space toggles on/off");
9380
+ available.forEach((p) => {
9381
+ const on = enabled.has(p);
9382
+ const isCur = focused && ei === selC;
9383
+ const idx = ei++;
9384
+ lines.push({
9385
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
9386
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
9387
+ /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
9388
+ /* @__PURE__ */ jsx5(Text5, { color: on ? void 0 : theme.dim, children: p })
9389
+ ] }, "b:" + p),
9390
+ entry: idx
9391
+ });
9392
+ });
9393
+ header("h:custom", "custom patterns", "regex over tool args \xB7 a add \xB7 d remove");
9394
+ note("n:custom-help", " a JS regex, e.g. sk-[a-z0-9]{20} AKIA[0-9A-Z]{16} password=\\S+");
9395
+ if ((dlp?.custom.length ?? 0) === 0) note("n:custom", " (none \u2014 press a to add a name + regex)");
9396
+ (dlp?.custom ?? []).forEach((c2, i) => {
9397
+ const isCur = focused && ei === selC;
9398
+ const idx = ei++;
9399
+ lines.push({
9400
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
9401
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
9402
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
9403
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + truncate2(c2.re, Math.max(10, cols - c2.name.length - 8)) })
9404
+ ] }, "c:" + c2.name + i),
9405
+ entry: idx
9406
+ });
9407
+ });
9408
+ header("h:ghost", "ghost \u2014 hidden paths", `${ghostOn ? "ON" : "off"} (g) \xB7 r add route \xB7 d remove`);
9409
+ note("n:ghost-help", " glob path, * = any chars, e.g. */.env *.pem */secrets/* *secret*");
9410
+ if (ghostPats.length === 0) note("n:ghost", " (none \u2014 g turns ghost on, r adds a path to hide)");
9411
+ ghostPats.forEach((p, i) => {
9412
+ const isCur = focused && ei === selC;
9413
+ const idx = ei++;
9414
+ lines.push({
9415
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
9416
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
9417
+ /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.accent : theme.dim, children: truncate2(p, Math.max(10, cols - 6)) }),
9418
+ !ghostOn ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (ghost off)" }) : null
9419
+ ] }, "g:" + p + i),
9420
+ entry: idx
9421
+ });
9422
+ });
9423
+ const editRows = adding === "re" ? 2 : adding ? 1 : 0;
9424
+ const headerRows = 2 + editRows + (status ? 1 : 0);
9425
+ const budget = Math.max(3, rows - headerRows - 1);
9426
+ const selLine = Math.max(0, lines.findIndex((l) => l.entry === selC));
9427
+ const maxStart = Math.max(0, lines.length - budget);
9428
+ const start = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
9429
+ const win = lines.slice(start, start + budget);
9430
+ const moreAbove = start;
9431
+ const moreBelow = Math.max(0, lines.length - (start + budget));
9368
9432
  return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
9369
- /* @__PURE__ */ jsxs5(Box5, { children: [
9433
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
9370
9434
  /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
9371
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
9435
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
9436
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
9372
9437
  dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
9373
9438
  ] }),
9374
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
9375
- /* @__PURE__ */ jsxs5(Box5, { children: [
9376
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
9377
- /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
9378
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
9379
- /* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
9380
- ] }),
9381
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
9382
- const on = enabled.has(p);
9383
- const active2 = focused && sel === i;
9384
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
9385
- (active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
9386
- /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
9387
- ] }, p);
9388
- }) }),
9389
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
9390
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
9391
- dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
9392
- dlp.custom.map((c2, i) => {
9393
- const active2 = focused && sel === available.length + i;
9394
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
9395
- active2 ? "\u25B8 " : " ",
9396
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
9397
- " ",
9398
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
9399
- ] }, c2.name + i);
9400
- })
9401
- ] }),
9402
- adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
9403
- /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
9404
- adding === "name" ? /* @__PURE__ */ jsx5(
9405
- TextInput3,
9406
- {
9407
- value: newName,
9408
- onChange: setNewName,
9409
- onSubmit: (v) => {
9410
- if (!v.trim()) return setAdding(null);
9411
- setNewName(v.trim());
9412
- setAdding("re");
9413
- }
9414
- }
9415
- ) : /* @__PURE__ */ jsx5(
9416
- TextInput3,
9417
- {
9418
- value: newRe,
9419
- onChange: setNewRe,
9420
- onSubmit: (v) => {
9421
- if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
9422
- setAdding(null);
9439
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add pattern \xB7 r add route \xB7 d remove \xB7 g ghost \xB7 s save${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
9440
+ adding ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
9441
+ /* @__PURE__ */ jsxs5(Box5, { children: [
9442
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `regex for "${newName}" (e.g. sk-[a-z0-9]{20}): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
9443
+ /* @__PURE__ */ jsx5(
9444
+ TextInput3,
9445
+ {
9446
+ value: input,
9447
+ onChange: setInput,
9448
+ onSubmit: (v) => {
9449
+ const t = v.trim();
9450
+ if (adding === "name") {
9451
+ if (!t) return setAdding(null);
9452
+ setNewName(t);
9453
+ setInput("");
9454
+ setAdding("re");
9455
+ } else if (adding === "re") {
9456
+ if (t && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: t }] });
9457
+ setAdding(null);
9458
+ } else {
9459
+ if (t && !ghostPats.includes(t)) {
9460
+ setGhostPats((ps) => [...ps, t]);
9461
+ setDirty(true);
9462
+ setStatus(null);
9463
+ }
9464
+ setAdding(null);
9465
+ }
9466
+ }
9423
9467
  }
9424
- }
9425
- )
9468
+ )
9469
+ ] }),
9470
+ adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: input }) : null
9426
9471
  ] }) : null,
9427
- adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
9428
- status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
9472
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
9473
+ status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
9429
9474
  ] }) : null });
9430
9475
  }
9431
9476
  function RegexTest({ re }) {
@@ -11055,12 +11100,12 @@ function App() {
11055
11100
  update2.kind === "updating" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \u2014 restart (q, then solongate) to apply` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
11056
11101
  ] }),
11057
11102
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
11058
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
11103
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
11059
11104
  const isCur = i === effectiveSection;
11060
11105
  const disabled = locked && s.label !== "Settings";
11061
11106
  return /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
11062
11107
  }) }),
11063
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
11108
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
11064
11109
  ] }, viewNonce),
11065
11110
  /* @__PURE__ */ jsx8(Box8, { children: locked ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2192/enter", "open Settings"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
11066
11111
  ] })
@@ -11110,7 +11155,8 @@ var init_App = __esm({
11110
11155
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
11111
11156
  ["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
11112
11157
  ["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["s", "save"], ["x", "discard"]]],
11113
- ["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
11158
+ ["Rate limit", [["\u2191\u2193", "field"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["s", "save"]]],
11159
+ ["DLP", [["\u2191\u2193", "move"], ["space", "toggle a built-in pattern on/off"], ["m", "cycle mode"], ["a", "add custom pattern (name \u2192 regex)"], ["r", "add a ghost route (path to hide)"], ["d", "remove custom / route"], ["g", "ghost on/off"], ["s", "save \xB7 x discard"]]],
11114
11160
  ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["x / X", "delete entry / ALL (press twice)"], ["c", "clear filters"]]],
11115
11161
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
11116
11162
  ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
package/dist/tui/index.js CHANGED
@@ -2321,28 +2321,26 @@ import { useEffect as useEffect5, useState as useState5 } from "react";
2321
2321
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2322
2322
  var MODES2 = ["off", "detect", "block"];
2323
2323
  function DlpPanel({ focused }) {
2324
+ const { cols, rows } = usePanelSize();
2324
2325
  const q = useLoader(() => api.settings.getSecurityLayers());
2325
2326
  const [dlp, setDlp] = useState5(null);
2326
2327
  const [available, setAvailable] = useState5([]);
2328
+ const [ghostOn, setGhostOn] = useState5(false);
2329
+ const [ghostPats, setGhostPats] = useState5([]);
2327
2330
  const [sel, setSel] = useState5(0);
2328
2331
  const [dirty, setDirty] = useState5(false);
2329
2332
  const [status, setStatus] = useState5(null);
2330
2333
  const [adding, setAdding] = useState5(null);
2331
2334
  const [newName, setNewName] = useState5("");
2332
- const [newRe, setNewRe] = useState5("");
2333
- const [ghostOn, setGhostOn] = useState5(false);
2334
- const spQ = useLoader(() => api.settings.getSelfProtection());
2335
- const [selfProt, setSelfProt] = useState5(null);
2335
+ const [input, setInput] = useState5("");
2336
2336
  useEffect5(() => {
2337
2337
  if (q.data && !dlp) {
2338
2338
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
2339
2339
  setAvailable(q.data.availablePatterns);
2340
2340
  setGhostOn(q.data.layers.ghost.mode === "on");
2341
+ setGhostPats([...q.data.layers.ghost.patterns]);
2341
2342
  }
2342
2343
  }, [q.data, dlp]);
2343
- useEffect5(() => {
2344
- if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
2345
- }, [spQ.data, selfProt]);
2346
2344
  const mutate = (next) => {
2347
2345
  setDlp(next);
2348
2346
  setDirty(true);
@@ -2352,128 +2350,175 @@ function DlpPanel({ focused }) {
2352
2350
  if (!dlp || !q.data) return;
2353
2351
  setStatus("Saving\u2026");
2354
2352
  try {
2355
- const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
2353
+ const ghost = { mode: ghostOn ? "on" : "off", patterns: ghostPats };
2356
2354
  const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
2357
2355
  setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
2358
2356
  setGhostOn(res.layers.ghost.mode === "on");
2357
+ setGhostPats([...res.layers.ghost.patterns]);
2359
2358
  setDirty(false);
2360
2359
  setStatus("\u2713 Saved");
2361
2360
  } catch (e) {
2362
2361
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
2363
2362
  }
2364
2363
  };
2365
- const toggleSelfProt = async () => {
2366
- const next = !(selfProt ?? false);
2367
- setSelfProt(next);
2368
- try {
2369
- await api.settings.setSelfProtection(next);
2370
- setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
2371
- } catch (e) {
2372
- setSelfProt(!next);
2373
- setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
2374
- }
2375
- };
2376
2364
  const discard = () => {
2377
- if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
2365
+ if (!q.data) return;
2366
+ setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
2367
+ setGhostOn(q.data.layers.ghost.mode === "on");
2368
+ setGhostPats([...q.data.layers.ghost.patterns]);
2378
2369
  setDirty(false);
2379
2370
  setStatus("discarded");
2380
2371
  };
2381
- const customCount = dlp?.custom.length ?? 0;
2382
- const total = available.length + customCount;
2372
+ const entries = [
2373
+ ...available.map((name) => ({ kind: "builtin", name })),
2374
+ ...(dlp?.custom ?? []).map((_, i) => ({ kind: "custom", i })),
2375
+ ...ghostPats.map((_, i) => ({ kind: "ghost", i }))
2376
+ ];
2377
+ const selC = Math.min(sel, Math.max(0, entries.length - 1));
2378
+ const cur = entries[selC];
2383
2379
  useInput4(
2384
- (input, key) => {
2380
+ (input2, key) => {
2385
2381
  if (!dlp) return;
2386
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2387
- else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
2388
- else if (input === "m") {
2389
- const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
2390
- mutate({ ...dlp, mode: MODES2[idx] });
2391
- } else if (input === "a") {
2392
- setNewName("");
2393
- setNewRe("");
2394
- setAdding("name");
2395
- } else if (input === " " && sel < available.length) {
2396
- const p = available[sel];
2382
+ if (key.upArrow) setSel(() => Math.max(0, selC - 1));
2383
+ else if (key.downArrow) setSel(() => Math.min(entries.length - 1, selC + 1));
2384
+ else if (input2 === "m") mutate({ ...dlp, mode: MODES2[(MODES2.indexOf(dlp.mode) + 1) % MODES2.length] });
2385
+ else if (input2 === " " && cur?.kind === "builtin") {
2397
2386
  const set = new Set(dlp.patterns);
2398
- if (set.has(p)) set.delete(p);
2399
- else set.add(p);
2387
+ if (set.has(cur.name)) set.delete(cur.name);
2388
+ else set.add(cur.name);
2400
2389
  mutate({ ...dlp, patterns: [...set] });
2401
- } else if (input === "d" && sel >= available.length) {
2402
- const ci = sel - available.length;
2403
- mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
2404
- setSel((n) => Math.max(0, n - 1));
2405
- } else if (input === "g") {
2390
+ } else if (input2 === "a") {
2391
+ setNewName("");
2392
+ setInput("");
2393
+ setAdding("name");
2394
+ } else if (input2 === "r") {
2395
+ setInput("");
2396
+ setAdding("route");
2397
+ } else if (input2 === "d") {
2398
+ if (cur?.kind === "custom") {
2399
+ mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== cur.i) });
2400
+ setSel(() => Math.max(0, selC - 1));
2401
+ } else if (cur?.kind === "ghost") {
2402
+ setGhostPats((ps) => ps.filter((_, i) => i !== cur.i));
2403
+ setDirty(true);
2404
+ setStatus(null);
2405
+ setSel(() => Math.max(0, selC - 1));
2406
+ }
2407
+ } else if (input2 === "g") {
2406
2408
  setGhostOn((v) => !v);
2407
2409
  setDirty(true);
2408
2410
  setStatus(null);
2409
- } else if (input === "P") void toggleSelfProt();
2410
- else if (input === "s") void save();
2411
- else if (input === "x") discard();
2411
+ } else if (input2 === "s") void save();
2412
+ else if (input2 === "x") discard();
2412
2413
  },
2413
2414
  { isActive: focused && !adding }
2414
2415
  );
2415
2416
  const enabled = new Set(dlp?.patterns ?? []);
2417
+ const lines = [];
2418
+ const header = (key, label, extra) => lines.push({
2419
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2420
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accentBright, bold: true, children: label }),
2421
+ extra ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + extra }) : null
2422
+ ] }, key),
2423
+ entry: -1
2424
+ });
2425
+ const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
2426
+ let ei = 0;
2427
+ header("h:builtin", "built-in secret patterns", "space toggles on/off");
2428
+ available.forEach((p) => {
2429
+ const on = enabled.has(p);
2430
+ const isCur = focused && ei === selC;
2431
+ const idx = ei++;
2432
+ lines.push({
2433
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2434
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2435
+ /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
2436
+ /* @__PURE__ */ jsx5(Text5, { color: on ? void 0 : theme.dim, children: p })
2437
+ ] }, "b:" + p),
2438
+ entry: idx
2439
+ });
2440
+ });
2441
+ header("h:custom", "custom patterns", "regex over tool args \xB7 a add \xB7 d remove");
2442
+ note("n:custom-help", " a JS regex, e.g. sk-[a-z0-9]{20} AKIA[0-9A-Z]{16} password=\\S+");
2443
+ if ((dlp?.custom.length ?? 0) === 0) note("n:custom", " (none \u2014 press a to add a name + regex)");
2444
+ (dlp?.custom ?? []).forEach((c2, i) => {
2445
+ const isCur = focused && ei === selC;
2446
+ const idx = ei++;
2447
+ lines.push({
2448
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2449
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2450
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
2451
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + truncate(c2.re, Math.max(10, cols - c2.name.length - 8)) })
2452
+ ] }, "c:" + c2.name + i),
2453
+ entry: idx
2454
+ });
2455
+ });
2456
+ header("h:ghost", "ghost \u2014 hidden paths", `${ghostOn ? "ON" : "off"} (g) \xB7 r add route \xB7 d remove`);
2457
+ note("n:ghost-help", " glob path, * = any chars, e.g. */.env *.pem */secrets/* *secret*");
2458
+ if (ghostPats.length === 0) note("n:ghost", " (none \u2014 g turns ghost on, r adds a path to hide)");
2459
+ ghostPats.forEach((p, i) => {
2460
+ const isCur = focused && ei === selC;
2461
+ const idx = ei++;
2462
+ lines.push({
2463
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2464
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2465
+ /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.accent : theme.dim, children: truncate(p, Math.max(10, cols - 6)) }),
2466
+ !ghostOn ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (ghost off)" }) : null
2467
+ ] }, "g:" + p + i),
2468
+ entry: idx
2469
+ });
2470
+ });
2471
+ const editRows = adding === "re" ? 2 : adding ? 1 : 0;
2472
+ const headerRows = 2 + editRows + (status ? 1 : 0);
2473
+ const budget = Math.max(3, rows - headerRows - 1);
2474
+ const selLine = Math.max(0, lines.findIndex((l) => l.entry === selC));
2475
+ const maxStart = Math.max(0, lines.length - budget);
2476
+ const start = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
2477
+ const win = lines.slice(start, start + budget);
2478
+ const moreAbove = start;
2479
+ const moreBelow = Math.max(0, lines.length - (start + budget));
2416
2480
  return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2417
- /* @__PURE__ */ jsxs5(Box5, { children: [
2481
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2418
2482
  /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
2419
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
2483
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
2484
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
2420
2485
  dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
2421
2486
  ] }),
2422
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
2423
- /* @__PURE__ */ jsxs5(Box5, { children: [
2424
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
2425
- /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
2426
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
2427
- /* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
2428
- ] }),
2429
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
2430
- const on = enabled.has(p);
2431
- const active2 = focused && sel === i;
2432
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
2433
- (active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
2434
- /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
2435
- ] }, p);
2436
- }) }),
2437
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2438
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
2439
- dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
2440
- dlp.custom.map((c2, i) => {
2441
- const active2 = focused && sel === available.length + i;
2442
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
2443
- active2 ? "\u25B8 " : " ",
2444
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
2445
- " ",
2446
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
2447
- ] }, c2.name + i);
2448
- })
2449
- ] }),
2450
- adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2451
- /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
2452
- adding === "name" ? /* @__PURE__ */ jsx5(
2453
- TextInput3,
2454
- {
2455
- value: newName,
2456
- onChange: setNewName,
2457
- onSubmit: (v) => {
2458
- if (!v.trim()) return setAdding(null);
2459
- setNewName(v.trim());
2460
- setAdding("re");
2461
- }
2462
- }
2463
- ) : /* @__PURE__ */ jsx5(
2464
- TextInput3,
2465
- {
2466
- value: newRe,
2467
- onChange: setNewRe,
2468
- onSubmit: (v) => {
2469
- if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
2470
- setAdding(null);
2487
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add pattern \xB7 r add route \xB7 d remove \xB7 g ghost \xB7 s save${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
2488
+ adding ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2489
+ /* @__PURE__ */ jsxs5(Box5, { children: [
2490
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `regex for "${newName}" (e.g. sk-[a-z0-9]{20}): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
2491
+ /* @__PURE__ */ jsx5(
2492
+ TextInput3,
2493
+ {
2494
+ value: input,
2495
+ onChange: setInput,
2496
+ onSubmit: (v) => {
2497
+ const t = v.trim();
2498
+ if (adding === "name") {
2499
+ if (!t) return setAdding(null);
2500
+ setNewName(t);
2501
+ setInput("");
2502
+ setAdding("re");
2503
+ } else if (adding === "re") {
2504
+ if (t && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: t }] });
2505
+ setAdding(null);
2506
+ } else {
2507
+ if (t && !ghostPats.includes(t)) {
2508
+ setGhostPats((ps) => [...ps, t]);
2509
+ setDirty(true);
2510
+ setStatus(null);
2511
+ }
2512
+ setAdding(null);
2513
+ }
2514
+ }
2471
2515
  }
2472
- }
2473
- )
2516
+ )
2517
+ ] }),
2518
+ adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: input }) : null
2474
2519
  ] }) : null,
2475
- adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
2476
- status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
2520
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
2521
+ status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
2477
2522
  ] }) : null });
2478
2523
  }
2479
2524
  var SAMPLES = ["AKIA1234567890ABCD00", "sk-ant-api03-xxxxxxxx", "ghp_16chars0000000000000000000000000000", "password=hunter2", "Bearer eyJhbGciOi"];
@@ -4060,12 +4105,12 @@ function App() {
4060
4105
  update2.kind === "updating" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \u2014 restart (q, then solongate) to apply` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
4061
4106
  ] }),
4062
4107
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
4063
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4108
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4064
4109
  const isCur = i === effectiveSection;
4065
4110
  const disabled = locked && s.label !== "Settings";
4066
4111
  return /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
4067
4112
  }) }),
4068
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4113
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4069
4114
  ] }, viewNonce),
4070
4115
  /* @__PURE__ */ jsx8(Box8, { children: locked ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2192/enter", "open Settings"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
4071
4116
  ] })
@@ -4075,7 +4120,8 @@ var HELP = [
4075
4120
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
4076
4121
  ["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
4077
4122
  ["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["s", "save"], ["x", "discard"]]],
4078
- ["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
4123
+ ["Rate limit", [["\u2191\u2193", "field"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["s", "save"]]],
4124
+ ["DLP", [["\u2191\u2193", "move"], ["space", "toggle a built-in pattern on/off"], ["m", "cycle mode"], ["a", "add custom pattern (name \u2192 regex)"], ["r", "add a ghost route (path to hide)"], ["d", "remove custom / route"], ["g", "ghost on/off"], ["s", "save \xB7 x discard"]]],
4079
4125
  ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["x / X", "delete entry / ALL (press twice)"], ["c", "clear filters"]]],
4080
4126
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
4081
4127
  ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.72",
3
+ "version": "0.81.74",
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": {