@solongate/proxy 0.81.71 → 0.81.73

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
@@ -6946,11 +6946,12 @@ function PaneTitle({ label, extra, width: width2 }) {
6946
6946
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
6947
6947
  ] });
6948
6948
  }
6949
- function StreamLine({ e, loc, selected }) {
6949
+ function StreamLine({ e, loc, selected, date }) {
6950
6950
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
6951
6951
  /* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
6952
6952
  /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
6953
6953
  "[",
6954
+ date ? ymd(e.at) + " " : "",
6954
6955
  hhmmss(e.at),
6955
6956
  " "
6956
6957
  ] }),
@@ -7001,7 +7002,7 @@ function KeyHints({ hints }) {
7001
7002
  i < hints.length - 1 ? " " : ""
7002
7003
  ] }, i)) });
7003
7004
  }
7004
- var hhmmss;
7005
+ var hhmmss, ymd;
7005
7006
  var init_components = __esm({
7006
7007
  "src/tui/components.tsx"() {
7007
7008
  "use strict";
@@ -7010,6 +7011,12 @@ var init_components = __esm({
7010
7011
  const d = new Date(ts);
7011
7012
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
7012
7013
  };
7014
+ ymd = (ts) => {
7015
+ const d = new Date(ts);
7016
+ if (Number.isNaN(d.getTime())) return "----------";
7017
+ const p = (n) => String(n).padStart(2, "0");
7018
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
7019
+ };
7013
7020
  }
7014
7021
  });
7015
7022
 
@@ -9266,28 +9273,26 @@ import TextInput3 from "ink-text-input";
9266
9273
  import { useEffect as useEffect5, useState as useState5 } from "react";
9267
9274
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
9268
9275
  function DlpPanel({ focused }) {
9276
+ const { cols, rows } = usePanelSize();
9269
9277
  const q = useLoader(() => api.settings.getSecurityLayers());
9270
9278
  const [dlp, setDlp] = useState5(null);
9271
9279
  const [available, setAvailable] = useState5([]);
9280
+ const [ghostOn, setGhostOn] = useState5(false);
9281
+ const [ghostPats, setGhostPats] = useState5([]);
9272
9282
  const [sel, setSel] = useState5(0);
9273
9283
  const [dirty, setDirty] = useState5(false);
9274
9284
  const [status, setStatus] = useState5(null);
9275
9285
  const [adding, setAdding] = useState5(null);
9276
9286
  const [newName, setNewName] = useState5("");
9277
- const [newRe, setNewRe] = useState5("");
9278
- const [ghostOn, setGhostOn] = useState5(false);
9279
- const spQ = useLoader(() => api.settings.getSelfProtection());
9280
- const [selfProt, setSelfProt] = useState5(null);
9287
+ const [input, setInput] = useState5("");
9281
9288
  useEffect5(() => {
9282
9289
  if (q.data && !dlp) {
9283
9290
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
9284
9291
  setAvailable(q.data.availablePatterns);
9285
9292
  setGhostOn(q.data.layers.ghost.mode === "on");
9293
+ setGhostPats([...q.data.layers.ghost.patterns]);
9286
9294
  }
9287
9295
  }, [q.data, dlp]);
9288
- useEffect5(() => {
9289
- if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
9290
- }, [spQ.data, selfProt]);
9291
9296
  const mutate = (next) => {
9292
9297
  setDlp(next);
9293
9298
  setDirty(true);
@@ -9297,128 +9302,184 @@ function DlpPanel({ focused }) {
9297
9302
  if (!dlp || !q.data) return;
9298
9303
  setStatus("Saving\u2026");
9299
9304
  try {
9300
- const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
9305
+ const ghost = { mode: ghostOn ? "on" : "off", patterns: ghostPats };
9301
9306
  const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
9302
9307
  setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
9303
9308
  setGhostOn(res.layers.ghost.mode === "on");
9309
+ setGhostPats([...res.layers.ghost.patterns]);
9304
9310
  setDirty(false);
9305
9311
  setStatus("\u2713 Saved");
9306
9312
  } catch (e) {
9307
9313
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
9308
9314
  }
9309
9315
  };
9310
- const toggleSelfProt = async () => {
9311
- const next = !(selfProt ?? false);
9312
- setSelfProt(next);
9313
- try {
9314
- await api.settings.setSelfProtection(next);
9315
- setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
9316
- } catch (e) {
9317
- setSelfProt(!next);
9318
- setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
9319
- }
9320
- };
9321
9316
  const discard = () => {
9322
- 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]);
9323
9321
  setDirty(false);
9324
9322
  setStatus("discarded");
9325
9323
  };
9326
- const customCount = dlp?.custom.length ?? 0;
9327
- 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];
9328
9331
  useInput4(
9329
- (input, key) => {
9332
+ (input2, key) => {
9330
9333
  if (!dlp) return;
9331
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9332
- else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
9333
- else if (input === "m") {
9334
- const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
9335
- mutate({ ...dlp, mode: MODES2[idx] });
9336
- } else if (input === "a") {
9337
- setNewName("");
9338
- setNewRe("");
9339
- setAdding("name");
9340
- } else if (input === " " && sel < available.length) {
9341
- 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") {
9342
9338
  const set = new Set(dlp.patterns);
9343
- if (set.has(p)) set.delete(p);
9344
- else set.add(p);
9339
+ if (set.has(cur.name)) set.delete(cur.name);
9340
+ else set.add(cur.name);
9345
9341
  mutate({ ...dlp, patterns: [...set] });
9346
- } else if (input === "d" && sel >= available.length) {
9347
- const ci = sel - available.length;
9348
- mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
9349
- setSel((n) => Math.max(0, n - 1));
9350
- } 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") {
9351
9360
  setGhostOn((v) => !v);
9352
9361
  setDirty(true);
9353
9362
  setStatus(null);
9354
- } else if (input === "P") void toggleSelfProt();
9355
- else if (input === "s") void save();
9356
- else if (input === "x") discard();
9363
+ } else if (input2 === "s") void save();
9364
+ else if (input2 === "x") discard();
9357
9365
  },
9358
9366
  { isActive: focused && !adding }
9359
9367
  );
9360
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");
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", "a add \xB7 d remove");
9394
+ if ((dlp?.custom.length ?? 0) === 0) note("n:custom", " (none \u2014 press a to add a name + regex)");
9395
+ (dlp?.custom ?? []).forEach((c2, i) => {
9396
+ const isCur = focused && ei === selC;
9397
+ const idx = ei++;
9398
+ lines.push({
9399
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
9400
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
9401
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
9402
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + truncate2(c2.re, Math.max(10, cols - c2.name.length - 8)) })
9403
+ ] }, "c:" + c2.name + i),
9404
+ entry: idx
9405
+ });
9406
+ });
9407
+ header("h:ghost", "ghost \u2014 hidden paths", `${ghostOn ? "ON" : "off"} (g) \xB7 r add route \xB7 d remove`);
9408
+ if (ghostPats.length === 0) note("n:ghost", " (none \u2014 g turns ghost on, r adds a path to hide, e.g. **/.env)");
9409
+ ghostPats.forEach((p, i) => {
9410
+ const isCur = focused && ei === selC;
9411
+ const idx = ei++;
9412
+ lines.push({
9413
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
9414
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
9415
+ /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.accent : theme.dim, children: truncate2(p, Math.max(10, cols - 6)) }),
9416
+ !ghostOn ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (ghost off)" }) : null
9417
+ ] }, "g:" + p + i),
9418
+ entry: idx
9419
+ });
9420
+ });
9421
+ const editRows = adding === "re" ? 2 : adding ? 1 : 0;
9422
+ const headerRows = 3 + editRows + (status ? 1 : 0);
9423
+ const budget = Math.max(3, rows - headerRows - 1);
9424
+ const selLine = Math.max(0, lines.findIndex((l) => l.entry === selC));
9425
+ const maxStart = Math.max(0, lines.length - budget);
9426
+ const start = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
9427
+ const win = lines.slice(start, start + budget);
9428
+ const moreAbove = start;
9429
+ const moreBelow = Math.max(0, lines.length - (start + budget));
9361
9430
  return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
9362
- /* @__PURE__ */ jsxs5(Box5, { children: [
9431
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
9363
9432
  /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
9364
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
9433
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
9434
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
9365
9435
  dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
9366
9436
  ] }),
9367
- /* @__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" }),
9368
- /* @__PURE__ */ jsxs5(Box5, { children: [
9369
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
9370
- /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
9371
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
9372
- /* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
9437
+ /* @__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" : "press \u2192 to edit" }),
9438
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, wrap: "truncate", children: [
9439
+ "legend ",
9440
+ /* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: "\u25CF" }),
9441
+ " on \xB7 ",
9442
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "\u25CB" }),
9443
+ " off \xB7 ",
9444
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accentBright, children: "\u25B8" }),
9445
+ " cursor",
9446
+ moreAbove ? /* @__PURE__ */ jsx5(Text5, { children: ` \u25B2${moreAbove}` }) : null,
9447
+ moreBelow ? /* @__PURE__ */ jsx5(Text5, { children: ` \u25BC${moreBelow}` }) : null
9373
9448
  ] }),
9374
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
9375
- const on = enabled.has(p);
9376
- const active2 = focused && sel === i;
9377
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
9378
- (active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
9379
- /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
9380
- ] }, p);
9381
- }) }),
9382
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
9383
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
9384
- dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
9385
- dlp.custom.map((c2, i) => {
9386
- const active2 = focused && sel === available.length + i;
9387
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
9388
- active2 ? "\u25B8 " : " ",
9389
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
9390
- " ",
9391
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
9392
- ] }, c2.name + i);
9393
- })
9394
- ] }),
9395
- adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
9396
- /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
9397
- adding === "name" ? /* @__PURE__ */ jsx5(
9398
- TextInput3,
9399
- {
9400
- value: newName,
9401
- onChange: setNewName,
9402
- onSubmit: (v) => {
9403
- if (!v.trim()) return setAdding(null);
9404
- setNewName(v.trim());
9405
- setAdding("re");
9406
- }
9407
- }
9408
- ) : /* @__PURE__ */ jsx5(
9409
- TextInput3,
9410
- {
9411
- value: newRe,
9412
- onChange: setNewRe,
9413
- onSubmit: (v) => {
9414
- if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
9415
- setAdding(null);
9449
+ adding ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
9450
+ /* @__PURE__ */ jsxs5(Box5, { children: [
9451
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `regex for "${newName}": ` : "ghost route (glob path to hide, e.g. **/.env): " }),
9452
+ /* @__PURE__ */ jsx5(
9453
+ TextInput3,
9454
+ {
9455
+ value: input,
9456
+ onChange: setInput,
9457
+ onSubmit: (v) => {
9458
+ const t = v.trim();
9459
+ if (adding === "name") {
9460
+ if (!t) return setAdding(null);
9461
+ setNewName(t);
9462
+ setInput("");
9463
+ setAdding("re");
9464
+ } else if (adding === "re") {
9465
+ if (t && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: t }] });
9466
+ setAdding(null);
9467
+ } else {
9468
+ if (t && !ghostPats.includes(t)) {
9469
+ setGhostPats((ps) => [...ps, t]);
9470
+ setDirty(true);
9471
+ setStatus(null);
9472
+ }
9473
+ setAdding(null);
9474
+ }
9475
+ }
9416
9476
  }
9417
- }
9418
- )
9477
+ )
9478
+ ] }),
9479
+ adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: input }) : null
9419
9480
  ] }) : null,
9420
- adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
9421
- status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
9481
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
9482
+ status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
9422
9483
  ] }) : null });
9423
9484
  }
9424
9485
  function RegexTest({ re }) {
@@ -9973,7 +10034,7 @@ function AuditPanel({ active: active2, focused }) {
9973
10034
  )
9974
10035
  ] }) : null,
9975
10036
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
9976
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused }, e.id)) }),
10037
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
9977
10038
  pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9978
10039
  "(no entries",
9979
10040
  source === "local" ? " in the local file" : "",
@@ -11048,12 +11109,12 @@ function App() {
11048
11109
  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
11049
11110
  ] }),
11050
11111
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
11051
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
11112
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
11052
11113
  const isCur = i === effectiveSection;
11053
11114
  const disabled = locked && s.label !== "Settings";
11054
11115
  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);
11055
11116
  }) }),
11056
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
11117
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
11057
11118
  ] }, viewNonce),
11058
11119
  /* @__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"]] }) })
11059
11120
  ] })
@@ -11103,7 +11164,8 @@ var init_App = __esm({
11103
11164
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
11104
11165
  ["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)"]]],
11105
11166
  ["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"]]],
11106
- ["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"]]],
11167
+ ["Rate limit", [["\u2191\u2193", "field"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["s", "save"]]],
11168
+ ["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"]]],
11107
11169
  ["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"]]],
11108
11170
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
11109
11171
  ["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"]]]
@@ -1,6 +1,8 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /** HH:MM:SS for a timestamp (ms or ISO); `--:--:--` when unparseable. */
3
3
  export declare const hhmmss: (ts: string | number) => string;
4
+ /** YYYY-MM-DD for a timestamp; `----------` when unparseable. */
5
+ export declare const ymd: (ts: string | number) => string;
4
6
  /** Section divider title used across the Live/Audit consoles. */
5
7
  export declare function PaneTitle({ label, extra, width }: {
6
8
  label: string;
@@ -25,10 +27,11 @@ export interface StreamRow {
25
27
  evalMs?: number | null;
26
28
  rule?: string | null;
27
29
  }
28
- export declare function StreamLine({ e, loc, selected }: {
30
+ export declare function StreamLine({ e, loc, selected, date }: {
29
31
  e: StreamRow;
30
32
  loc: boolean;
31
33
  selected?: boolean;
34
+ date?: boolean;
32
35
  }): JSX.Element;
33
36
  export declare function Panel({ title, subtitle, children }: {
34
37
  title: string;
package/dist/tui/index.js CHANGED
@@ -129,6 +129,12 @@ var hhmmss = (ts) => {
129
129
  const d = new Date(ts);
130
130
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
131
131
  };
132
+ var ymd = (ts) => {
133
+ const d = new Date(ts);
134
+ if (Number.isNaN(d.getTime())) return "----------";
135
+ const p = (n) => String(n).padStart(2, "0");
136
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
137
+ };
132
138
  function PaneTitle({ label, extra, width }) {
133
139
  const tail = extra ? ` ${extra} ` : " ";
134
140
  const used = 2 + 1 + label.length + tail.length;
@@ -141,11 +147,12 @@ function PaneTitle({ label, extra, width }) {
141
147
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
142
148
  ] });
143
149
  }
144
- function StreamLine({ e, loc, selected }) {
150
+ function StreamLine({ e, loc, selected, date }) {
145
151
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
146
152
  /* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
147
153
  /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
148
154
  "[",
155
+ date ? ymd(e.at) + " " : "",
149
156
  hhmmss(e.at),
150
157
  " "
151
158
  ] }),
@@ -2314,28 +2321,26 @@ import { useEffect as useEffect5, useState as useState5 } from "react";
2314
2321
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2315
2322
  var MODES2 = ["off", "detect", "block"];
2316
2323
  function DlpPanel({ focused }) {
2324
+ const { cols, rows } = usePanelSize();
2317
2325
  const q = useLoader(() => api.settings.getSecurityLayers());
2318
2326
  const [dlp, setDlp] = useState5(null);
2319
2327
  const [available, setAvailable] = useState5([]);
2328
+ const [ghostOn, setGhostOn] = useState5(false);
2329
+ const [ghostPats, setGhostPats] = useState5([]);
2320
2330
  const [sel, setSel] = useState5(0);
2321
2331
  const [dirty, setDirty] = useState5(false);
2322
2332
  const [status, setStatus] = useState5(null);
2323
2333
  const [adding, setAdding] = useState5(null);
2324
2334
  const [newName, setNewName] = useState5("");
2325
- const [newRe, setNewRe] = useState5("");
2326
- const [ghostOn, setGhostOn] = useState5(false);
2327
- const spQ = useLoader(() => api.settings.getSelfProtection());
2328
- const [selfProt, setSelfProt] = useState5(null);
2335
+ const [input, setInput] = useState5("");
2329
2336
  useEffect5(() => {
2330
2337
  if (q.data && !dlp) {
2331
2338
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
2332
2339
  setAvailable(q.data.availablePatterns);
2333
2340
  setGhostOn(q.data.layers.ghost.mode === "on");
2341
+ setGhostPats([...q.data.layers.ghost.patterns]);
2334
2342
  }
2335
2343
  }, [q.data, dlp]);
2336
- useEffect5(() => {
2337
- if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
2338
- }, [spQ.data, selfProt]);
2339
2344
  const mutate = (next) => {
2340
2345
  setDlp(next);
2341
2346
  setDirty(true);
@@ -2345,128 +2350,184 @@ function DlpPanel({ focused }) {
2345
2350
  if (!dlp || !q.data) return;
2346
2351
  setStatus("Saving\u2026");
2347
2352
  try {
2348
- const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
2353
+ const ghost = { mode: ghostOn ? "on" : "off", patterns: ghostPats };
2349
2354
  const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
2350
2355
  setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
2351
2356
  setGhostOn(res.layers.ghost.mode === "on");
2357
+ setGhostPats([...res.layers.ghost.patterns]);
2352
2358
  setDirty(false);
2353
2359
  setStatus("\u2713 Saved");
2354
2360
  } catch (e) {
2355
2361
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
2356
2362
  }
2357
2363
  };
2358
- const toggleSelfProt = async () => {
2359
- const next = !(selfProt ?? false);
2360
- setSelfProt(next);
2361
- try {
2362
- await api.settings.setSelfProtection(next);
2363
- setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
2364
- } catch (e) {
2365
- setSelfProt(!next);
2366
- setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
2367
- }
2368
- };
2369
2364
  const discard = () => {
2370
- 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]);
2371
2369
  setDirty(false);
2372
2370
  setStatus("discarded");
2373
2371
  };
2374
- const customCount = dlp?.custom.length ?? 0;
2375
- 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];
2376
2379
  useInput4(
2377
- (input, key) => {
2380
+ (input2, key) => {
2378
2381
  if (!dlp) return;
2379
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2380
- else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
2381
- else if (input === "m") {
2382
- const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
2383
- mutate({ ...dlp, mode: MODES2[idx] });
2384
- } else if (input === "a") {
2385
- setNewName("");
2386
- setNewRe("");
2387
- setAdding("name");
2388
- } else if (input === " " && sel < available.length) {
2389
- 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") {
2390
2386
  const set = new Set(dlp.patterns);
2391
- if (set.has(p)) set.delete(p);
2392
- else set.add(p);
2387
+ if (set.has(cur.name)) set.delete(cur.name);
2388
+ else set.add(cur.name);
2393
2389
  mutate({ ...dlp, patterns: [...set] });
2394
- } else if (input === "d" && sel >= available.length) {
2395
- const ci = sel - available.length;
2396
- mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
2397
- setSel((n) => Math.max(0, n - 1));
2398
- } 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") {
2399
2408
  setGhostOn((v) => !v);
2400
2409
  setDirty(true);
2401
2410
  setStatus(null);
2402
- } else if (input === "P") void toggleSelfProt();
2403
- else if (input === "s") void save();
2404
- else if (input === "x") discard();
2411
+ } else if (input2 === "s") void save();
2412
+ else if (input2 === "x") discard();
2405
2413
  },
2406
2414
  { isActive: focused && !adding }
2407
2415
  );
2408
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");
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", "a add \xB7 d remove");
2442
+ if ((dlp?.custom.length ?? 0) === 0) note("n:custom", " (none \u2014 press a to add a name + regex)");
2443
+ (dlp?.custom ?? []).forEach((c2, i) => {
2444
+ const isCur = focused && ei === selC;
2445
+ const idx = ei++;
2446
+ lines.push({
2447
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2448
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2449
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
2450
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + truncate(c2.re, Math.max(10, cols - c2.name.length - 8)) })
2451
+ ] }, "c:" + c2.name + i),
2452
+ entry: idx
2453
+ });
2454
+ });
2455
+ header("h:ghost", "ghost \u2014 hidden paths", `${ghostOn ? "ON" : "off"} (g) \xB7 r add route \xB7 d remove`);
2456
+ if (ghostPats.length === 0) note("n:ghost", " (none \u2014 g turns ghost on, r adds a path to hide, e.g. **/.env)");
2457
+ ghostPats.forEach((p, i) => {
2458
+ const isCur = focused && ei === selC;
2459
+ const idx = ei++;
2460
+ lines.push({
2461
+ node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2462
+ /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2463
+ /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.accent : theme.dim, children: truncate(p, Math.max(10, cols - 6)) }),
2464
+ !ghostOn ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (ghost off)" }) : null
2465
+ ] }, "g:" + p + i),
2466
+ entry: idx
2467
+ });
2468
+ });
2469
+ const editRows = adding === "re" ? 2 : adding ? 1 : 0;
2470
+ const headerRows = 3 + editRows + (status ? 1 : 0);
2471
+ const budget = Math.max(3, rows - headerRows - 1);
2472
+ const selLine = Math.max(0, lines.findIndex((l) => l.entry === selC));
2473
+ const maxStart = Math.max(0, lines.length - budget);
2474
+ const start = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
2475
+ const win = lines.slice(start, start + budget);
2476
+ const moreAbove = start;
2477
+ const moreBelow = Math.max(0, lines.length - (start + budget));
2409
2478
  return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2410
- /* @__PURE__ */ jsxs5(Box5, { children: [
2479
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2411
2480
  /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
2412
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
2481
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
2482
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
2413
2483
  dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
2414
2484
  ] }),
2415
- /* @__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" }),
2416
- /* @__PURE__ */ jsxs5(Box5, { children: [
2417
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
2418
- /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
2419
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
2420
- /* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
2421
- ] }),
2422
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
2423
- const on = enabled.has(p);
2424
- const active2 = focused && sel === i;
2425
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
2426
- (active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
2427
- /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
2428
- ] }, p);
2429
- }) }),
2430
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2431
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
2432
- dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
2433
- dlp.custom.map((c2, i) => {
2434
- const active2 = focused && sel === available.length + i;
2435
- return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
2436
- active2 ? "\u25B8 " : " ",
2437
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
2438
- " ",
2439
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
2440
- ] }, c2.name + i);
2441
- })
2485
+ /* @__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" : "press \u2192 to edit" }),
2486
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, wrap: "truncate", children: [
2487
+ "legend ",
2488
+ /* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: "\u25CF" }),
2489
+ " on \xB7 ",
2490
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "\u25CB" }),
2491
+ " off \xB7 ",
2492
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accentBright, children: "\u25B8" }),
2493
+ " cursor",
2494
+ moreAbove ? /* @__PURE__ */ jsx5(Text5, { children: ` \u25B2${moreAbove}` }) : null,
2495
+ moreBelow ? /* @__PURE__ */ jsx5(Text5, { children: ` \u25BC${moreBelow}` }) : null
2442
2496
  ] }),
2443
- adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2444
- /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
2445
- adding === "name" ? /* @__PURE__ */ jsx5(
2446
- TextInput3,
2447
- {
2448
- value: newName,
2449
- onChange: setNewName,
2450
- onSubmit: (v) => {
2451
- if (!v.trim()) return setAdding(null);
2452
- setNewName(v.trim());
2453
- setAdding("re");
2454
- }
2455
- }
2456
- ) : /* @__PURE__ */ jsx5(
2457
- TextInput3,
2458
- {
2459
- value: newRe,
2460
- onChange: setNewRe,
2461
- onSubmit: (v) => {
2462
- if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
2463
- setAdding(null);
2497
+ adding ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2498
+ /* @__PURE__ */ jsxs5(Box5, { children: [
2499
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `regex for "${newName}": ` : "ghost route (glob path to hide, e.g. **/.env): " }),
2500
+ /* @__PURE__ */ jsx5(
2501
+ TextInput3,
2502
+ {
2503
+ value: input,
2504
+ onChange: setInput,
2505
+ onSubmit: (v) => {
2506
+ const t = v.trim();
2507
+ if (adding === "name") {
2508
+ if (!t) return setAdding(null);
2509
+ setNewName(t);
2510
+ setInput("");
2511
+ setAdding("re");
2512
+ } else if (adding === "re") {
2513
+ if (t && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: t }] });
2514
+ setAdding(null);
2515
+ } else {
2516
+ if (t && !ghostPats.includes(t)) {
2517
+ setGhostPats((ps) => [...ps, t]);
2518
+ setDirty(true);
2519
+ setStatus(null);
2520
+ }
2521
+ setAdding(null);
2522
+ }
2523
+ }
2464
2524
  }
2465
- }
2466
- )
2525
+ )
2526
+ ] }),
2527
+ adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: input }) : null
2467
2528
  ] }) : null,
2468
- adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
2469
- status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
2529
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
2530
+ status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
2470
2531
  ] }) : null });
2471
2532
  }
2472
2533
  var SAMPLES = ["AKIA1234567890ABCD00", "sk-ant-api03-xxxxxxxx", "ghp_16chars0000000000000000000000000000", "password=hunter2", "Bearer eyJhbGciOi"];
@@ -3093,7 +3154,7 @@ function AuditPanel({ active: active2, focused }) {
3093
3154
  )
3094
3155
  ] }) : null,
3095
3156
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
3096
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused }, e.id)) }),
3157
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
3097
3158
  pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3098
3159
  "(no entries",
3099
3160
  source === "local" ? " in the local file" : "",
@@ -4053,12 +4114,12 @@ function App() {
4053
4114
  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
4054
4115
  ] }),
4055
4116
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
4056
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4117
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4057
4118
  const isCur = i === effectiveSection;
4058
4119
  const disabled = locked && s.label !== "Settings";
4059
4120
  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);
4060
4121
  }) }),
4061
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4122
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4062
4123
  ] }, viewNonce),
4063
4124
  /* @__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"]] }) })
4064
4125
  ] })
@@ -4068,7 +4129,8 @@ var HELP = [
4068
4129
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
4069
4130
  ["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)"]]],
4070
4131
  ["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"]]],
4071
- ["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"]]],
4132
+ ["Rate limit", [["\u2191\u2193", "field"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["s", "save"]]],
4133
+ ["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"]]],
4072
4134
  ["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"]]],
4073
4135
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
4074
4136
  ["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.71",
3
+ "version": "0.81.73",
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": {