@solongate/proxy 0.81.25 → 0.81.27

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.
@@ -12,6 +12,19 @@ export interface AuditQuery {
12
12
  signal?: 'dlp' | 'ratelimit';
13
13
  }
14
14
  export declare function list(query?: AuditQuery): Promise<AuditList>;
15
+ export declare function remove(ids: string[]): Promise<{
16
+ deleted: {
17
+ logs: number;
18
+ agents: number;
19
+ };
20
+ }>;
21
+ /** Deletes EVERY audit log of the project (agents/sessions are kept). */
22
+ export declare function removeAll(): Promise<{
23
+ deleted: {
24
+ logs: number;
25
+ agents: number;
26
+ };
27
+ }>;
15
28
  export declare function whitelist(id: string, scope?: 'exact' | 'tool'): Promise<{
16
29
  ok: true;
17
30
  deduped: boolean;
@@ -291,11 +291,19 @@ var audit_exports = {};
291
291
  __export(audit_exports, {
292
292
  block: () => block,
293
293
  list: () => list2,
294
+ remove: () => remove2,
295
+ removeAll: () => removeAll,
294
296
  whitelist: () => whitelist
295
297
  });
296
298
  function list2(query = {}) {
297
299
  return request("GET", "/audit-logs", { query });
298
300
  }
301
+ function remove2(ids) {
302
+ return request("DELETE", "/audit-logs", { body: { ids } });
303
+ }
304
+ function removeAll() {
305
+ return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
306
+ }
299
307
  function whitelist(id, scope = "exact") {
300
308
  return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
301
309
  }
package/dist/index.js CHANGED
@@ -6845,7 +6845,7 @@ var init_components = __esm({
6845
6845
  });
6846
6846
 
6847
6847
  // src/tui/local-log.ts
6848
- import { closeSync, openSync, readSync, statSync } from "fs";
6848
+ import { closeSync, openSync, readFileSync as readFileSync6, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
6849
6849
  import { homedir as homedir4 } from "os";
6850
6850
  import { join as join6 } from "path";
6851
6851
  function tailLines(file, maxBytes = 131072) {
@@ -6863,6 +6863,39 @@ function tailLines(file, maxBytes = 131072) {
6863
6863
  return [];
6864
6864
  }
6865
6865
  }
6866
+ function deleteLocalEntry(at, tool, session) {
6867
+ try {
6868
+ const lines = readFileSync6(LOCAL_LOG, "utf-8").split("\n");
6869
+ let removed = 0;
6870
+ const kept = lines.filter((line) => {
6871
+ if (!line.trim()) return false;
6872
+ if (removed) return true;
6873
+ try {
6874
+ const j = JSON.parse(line);
6875
+ const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
6876
+ if (hit) {
6877
+ removed++;
6878
+ return false;
6879
+ }
6880
+ } catch {
6881
+ }
6882
+ return true;
6883
+ });
6884
+ if (removed) writeFileSync3(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
6885
+ return removed;
6886
+ } catch {
6887
+ return 0;
6888
+ }
6889
+ }
6890
+ function clearLocalLog() {
6891
+ try {
6892
+ const n = readFileSync6(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6893
+ writeFileSync3(LOCAL_LOG, "");
6894
+ return n;
6895
+ } catch {
6896
+ return 0;
6897
+ }
6898
+ }
6866
6899
  function parseLocalLines(lines) {
6867
6900
  const out2 = [];
6868
6901
  for (const line of lines) {
@@ -7062,11 +7095,19 @@ var audit_exports = {};
7062
7095
  __export(audit_exports, {
7063
7096
  block: () => block,
7064
7097
  list: () => list2,
7098
+ remove: () => remove2,
7099
+ removeAll: () => removeAll,
7065
7100
  whitelist: () => whitelist
7066
7101
  });
7067
7102
  function list2(query = {}) {
7068
7103
  return request("GET", "/audit-logs", { query });
7069
7104
  }
7105
+ function remove2(ids) {
7106
+ return request("DELETE", "/audit-logs", { body: { ids } });
7107
+ }
7108
+ function removeAll() {
7109
+ return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
7110
+ }
7070
7111
  function whitelist(id, scope = "exact") {
7071
7112
  return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
7072
7113
  }
@@ -7267,7 +7308,7 @@ var init_hooks = __esm({
7267
7308
  // src/tui/panels/Live.tsx
7268
7309
  import { Box as Box2, Text as Text2, useInput } from "ink";
7269
7310
  import TextInput from "ink-text-input";
7270
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
7311
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
7271
7312
  import { homedir as homedir5 } from "os";
7272
7313
  import { join as join7 } from "path";
7273
7314
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
@@ -7818,7 +7859,7 @@ function LivePanel({ active: active2 }) {
7818
7859
  const file = join7(homedir5(), ".solongate", "live-export.jsonl");
7819
7860
  try {
7820
7861
  mkdirSync3(join7(homedir5(), ".solongate"), { recursive: true });
7821
- writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7862
+ writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7822
7863
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
7823
7864
  } catch (err2) {
7824
7865
  setActionMsg({ text: "\u2717 export failed: " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 6e3 });
@@ -8873,6 +8914,9 @@ function AuditPanel({ active: active2, focused }) {
8873
8914
  const [si, setSi] = useState6(0);
8874
8915
  const [sessSearch, setSessSearch] = useState6("");
8875
8916
  const [sessSel, setSessSel] = useState6(0);
8917
+ const [confirm, setConfirm] = useState6(null);
8918
+ const [msg, setMsg] = useState6(null);
8919
+ const [showHelp, setShowHelp] = useState6(false);
8876
8920
  const toTop = () => setSel(0);
8877
8921
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
8878
8922
  const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
@@ -8958,8 +9002,41 @@ function AuditPanel({ active: active2, focused }) {
8958
9002
  () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
8959
9003
  [view, current?.session, source]
8960
9004
  );
9005
+ const logsLoading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
9006
+ const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
9007
+ const doDelete = (kind) => {
9008
+ setMsg({ text: "deleting\u2026", level: "ok" });
9009
+ const run12 = async () => {
9010
+ if (source === "cloud") {
9011
+ if (kind === "one") {
9012
+ if (!current) throw new Error("nothing selected");
9013
+ await api.audit.remove([current.id]);
9014
+ } else {
9015
+ await api.audit.removeAll();
9016
+ }
9017
+ cloudQ.reload();
9018
+ statsQ.reloadQuiet();
9019
+ } else {
9020
+ const n = kind === "one" && current ? deleteLocalEntry(current.at, current.tool, current.session) : kind === "all" ? clearLocalLog() : 0;
9021
+ if (!n) throw new Error("entry not found in the local file");
9022
+ localQ.reload();
9023
+ }
9024
+ };
9025
+ run12().then(() => {
9026
+ setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
9027
+ toTop();
9028
+ }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
9029
+ };
8961
9030
  useInput5(
8962
9031
  (input, key) => {
9032
+ if (showHelp) {
9033
+ setShowHelp(false);
9034
+ return;
9035
+ }
9036
+ if (input === "?") {
9037
+ setShowHelp(true);
9038
+ return;
9039
+ }
8963
9040
  if (view === "detail") {
8964
9041
  if (key.leftArrow || key.escape) setView("logs");
8965
9042
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
@@ -8968,6 +9045,11 @@ function AuditPanel({ active: active2, focused }) {
8968
9045
  else if (key.pageDown) setDetailScroll((n) => n + 10);
8969
9046
  return;
8970
9047
  }
9048
+ if (view === "logs" ? logsLoading : sessLoading) return;
9049
+ if (confirm && input !== "x" && input !== "X") {
9050
+ setConfirm(null);
9051
+ setMsg(null);
9052
+ }
8971
9053
  if (input === "s") {
8972
9054
  setSource((s) => s === "cloud" ? "local" : "cloud");
8973
9055
  setPage(0);
@@ -9029,6 +9111,23 @@ function AuditPanel({ active: active2, focused }) {
9029
9111
  setGi((n) => (n + 1) % SIGNALS.length);
9030
9112
  setPage(0);
9031
9113
  toTop();
9114
+ } else if (input === "x") {
9115
+ if (!current) return;
9116
+ if (confirm?.kind !== "one" || confirm.key !== current.id) {
9117
+ setConfirm({ kind: "one", key: current.id });
9118
+ setMsg({ text: `delete ${current.decision} ${current.tool} (${ago(current.at)} ago)? press x again`, level: "bad" });
9119
+ return;
9120
+ }
9121
+ setConfirm(null);
9122
+ doDelete("one");
9123
+ } else if (input === "X") {
9124
+ if (confirm?.kind !== "all") {
9125
+ setConfirm({ kind: "all", key: "all" });
9126
+ setMsg({ text: `DELETE ALL ${source} logs (${total} entries)? press X again`, level: "bad" });
9127
+ return;
9128
+ }
9129
+ setConfirm(null);
9130
+ doDelete("all");
9032
9131
  } else if (input === "t") setEditing("tool");
9033
9132
  else if (input === "n") setEditing("agent");
9034
9133
  else if (input === "/") setEditing("search");
@@ -9085,6 +9184,19 @@ function AuditPanel({ active: active2, focused }) {
9085
9184
  /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
9086
9185
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
9087
9186
  ] });
9187
+ if (showHelp) {
9188
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9189
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accentBright, children: "AUDIT \u2014 all keys" }),
9190
+ AUDIT_HELP.map(([group, keys]) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
9191
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: group }),
9192
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
9193
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, children: (" " + k).padEnd(20) }),
9194
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: desc })
9195
+ ] }, k))
9196
+ ] }, group)),
9197
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "press any key to close" }) })
9198
+ ] });
9199
+ }
9088
9200
  if (view === "detail" && current) {
9089
9201
  const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
9090
9202
  const bodyW = Math.max(20, cols - 2);
@@ -9155,8 +9267,7 @@ function AuditPanel({ active: active2, focused }) {
9155
9267
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
9156
9268
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
9157
9269
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
9158
- const loading2 = source === "cloud" ? agentsQ.loading : localQ.loading;
9159
- return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9270
+ return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9160
9271
  strip,
9161
9272
  /* @__PURE__ */ jsxs6(Box6, { children: [
9162
9273
  srcChip,
@@ -9165,7 +9276,7 @@ function AuditPanel({ active: active2, focused }) {
9165
9276
  chip("status", SESS_STATUS[si] ?? "all", si !== 0),
9166
9277
  chip("search", sessSearch || "\xB7", !!sessSearch)
9167
9278
  ] }),
9168
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 f status \xB7 / search \xB7 s source \xB7 v logs \xB7 c clear" : "press \u2192 to browse" }),
9279
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ? all keys" : "press \u2192 to browse" }),
9169
9280
  editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9170
9281
  /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
9171
9282
  /* @__PURE__ */ jsx6(
@@ -9219,15 +9330,14 @@ function AuditPanel({ active: active2, focused }) {
9219
9330
  ] }) : null
9220
9331
  ] }) });
9221
9332
  }
9222
- const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
9333
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0);
9223
9334
  const listRows = Math.max(4, rows - headerRows);
9224
9335
  const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
9225
9336
  const maxStart = Math.max(0, pageRows.length - listRows);
9226
9337
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
9227
9338
  const windowed = pageRows.slice(start, start + listRows);
9228
9339
  const reasonW = Math.min(60, Math.max(12, cols - 67));
9229
- const loading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
9230
- return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9340
+ return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9231
9341
  strip,
9232
9342
  /* @__PURE__ */ jsxs6(Box6, { children: [
9233
9343
  srcChip,
@@ -9240,7 +9350,8 @@ function AuditPanel({ active: active2, focused }) {
9240
9350
  chip("search", search || "\xB7", !!search),
9241
9351
  sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
9242
9352
  ] }),
9243
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
9353
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ? all keys" : "press \u2192 to browse" }),
9354
+ msg ? /* @__PURE__ */ jsx6(Text6, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : null,
9244
9355
  editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9245
9356
  /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
9246
9357
  editing,
@@ -9299,7 +9410,7 @@ function Detail({ label, value, color }) {
9299
9410
  /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
9300
9411
  ] });
9301
9412
  }
9302
- var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, cloudRow, sessStatus2, STATUS_DOT;
9413
+ var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, cloudRow, AUDIT_HELP, sessStatus2, STATUS_DOT;
9303
9414
  var init_Audit = __esm({
9304
9415
  "src/tui/panels/Audit.tsx"() {
9305
9416
  "use strict";
@@ -9329,6 +9440,43 @@ var init_Audit = __esm({
9329
9440
  burst: !!e.rate_limit_burst,
9330
9441
  args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
9331
9442
  });
9443
+ AUDIT_HELP = [
9444
+ [
9445
+ "Logs",
9446
+ [
9447
+ ["\u2191\u2193 / PgUp PgDn", "select a row (window follows)"],
9448
+ ["enter", "open the FULL entry (reason + arguments)"],
9449
+ ["\u2190 \u2192", "previous / next page (500 per page, jumps to top)"],
9450
+ ["f", "decision filter: all \u2192 DENY \u2192 ALLOW"],
9451
+ ["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
9452
+ ["t / n", "tool / agent filter (type, enter done)"],
9453
+ ["/", "free-text search"],
9454
+ ["x", "delete the selected entry (press x twice)"],
9455
+ ["X", "delete ALL logs of the current source (press X twice)"],
9456
+ ["c", "clear every filter (incl. session)"]
9457
+ ]
9458
+ ],
9459
+ [
9460
+ "Sessions",
9461
+ [
9462
+ ["\u2191\u2193", "select a session"],
9463
+ ["enter", "open that session's logs"],
9464
+ ["f", "status filter: all \u2192 active \u2192 idle \u2192 ended"],
9465
+ ["/", "search agent / session id"],
9466
+ ["c", "clear session filters"]
9467
+ ]
9468
+ ],
9469
+ [
9470
+ "Anywhere in Audit",
9471
+ [
9472
+ ["v", "switch logs \u2194 sessions"],
9473
+ ["s", "switch source cloud \u2194 local file"],
9474
+ ["?", "this help \xB7 any key closes"],
9475
+ ["esc", "back to the menu"]
9476
+ ]
9477
+ ],
9478
+ ["Entry detail", [["\u2191\u2193 / PgUp PgDn", "scroll the arguments"], ["\u2190 / esc", "back to the list"]]]
9479
+ ];
9332
9480
  sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
9333
9481
  STATUS_DOT = {
9334
9482
  active: { ch: "\u25CF", color: theme.ok },
@@ -9663,7 +9811,7 @@ var init_App = __esm({
9663
9811
  ["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)"]]],
9664
9812
  ["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"]]],
9665
9813
  ["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"]]],
9666
- ["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"], ["c", "clear filters"]]],
9814
+ ["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"]]],
9667
9815
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
9668
9816
  ];
9669
9817
  }
@@ -9801,7 +9949,7 @@ var init_args = __esm({
9801
9949
  });
9802
9950
 
9803
9951
  // src/commands/policy.ts
9804
- import { readFileSync as readFileSync6 } from "fs";
9952
+ import { readFileSync as readFileSync7 } from "fs";
9805
9953
  async function run(argv) {
9806
9954
  const { positionals, flags } = parse(argv);
9807
9955
  const sub = positionals[0];
@@ -9976,7 +10124,7 @@ function printRules(rules) {
9976
10124
  }
9977
10125
  async function resolveRules(target) {
9978
10126
  if (target.endsWith(".json")) {
9979
- const parsed = JSON.parse(readFileSync6(target, "utf-8"));
10127
+ const parsed = JSON.parse(readFileSync7(target, "utf-8"));
9980
10128
  return parsed.rules ?? [];
9981
10129
  }
9982
10130
  const p = await api.policies.get(target);
@@ -10869,7 +11017,7 @@ __export(global_install_exports, {
10869
11017
  runGlobalRestore: () => runGlobalRestore,
10870
11018
  unlockProtected: () => unlockProtected
10871
11019
  });
10872
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
11020
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
10873
11021
  import { resolve as resolve4, join as join10, dirname } from "path";
10874
11022
  import { homedir as homedir8 } from "os";
10875
11023
  import { fileURLToPath } from "url";
@@ -10964,11 +11112,11 @@ function globalPaths() {
10964
11112
  };
10965
11113
  }
10966
11114
  function readHook(filename) {
10967
- return readFileSync7(join10(HOOKS_DIR, filename), "utf-8");
11115
+ return readFileSync8(join10(HOOKS_DIR, filename), "utf-8");
10968
11116
  }
10969
11117
  function readGuard() {
10970
11118
  const bundled = join10(HOOKS_DIR, "guard.bundled.mjs");
10971
- return existsSync6(bundled) ? readFileSync7(bundled, "utf-8") : readHook("guard.mjs");
11119
+ return existsSync6(bundled) ? readFileSync8(bundled, "utf-8") : readHook("guard.mjs");
10972
11120
  }
10973
11121
  function ask(question) {
10974
11122
  const rl = createInterface({ input: process.stdin, output: process.stderr });
@@ -10982,13 +11130,13 @@ function runGlobalRestore() {
10982
11130
  unlockProtected();
10983
11131
  removeClaudeShim();
10984
11132
  if (existsSync6(p.backupPath)) {
10985
- writeFileSync4(p.settingsPath, readFileSync7(p.backupPath, "utf-8"));
11133
+ writeFileSync5(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
10986
11134
  console.log(` Restored ${p.settingsPath} from backup.`);
10987
11135
  } else if (existsSync6(p.settingsPath)) {
10988
11136
  try {
10989
- const s = JSON.parse(readFileSync7(p.settingsPath, "utf-8"));
11137
+ const s = JSON.parse(readFileSync8(p.settingsPath, "utf-8"));
10990
11138
  delete s.hooks;
10991
- writeFileSync4(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11139
+ writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
10992
11140
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
10993
11141
  } catch {
10994
11142
  }
@@ -11026,14 +11174,14 @@ function shimTargets() {
11026
11174
  }
11027
11175
  function writeShimBlock(file, block2) {
11028
11176
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
11029
- let content = existsSync6(file) ? readFileSync7(file, "utf-8") : "";
11177
+ let content = existsSync6(file) ? readFileSync8(file, "utf-8") : "";
11030
11178
  content = content.replace(re, "");
11031
11179
  if (block2) {
11032
11180
  if (content.length && !content.endsWith("\n")) content += "\n";
11033
11181
  content += block2 + "\n";
11034
11182
  }
11035
11183
  mkdirSync4(dirname(file), { recursive: true });
11036
- writeFileSync4(file, content);
11184
+ writeFileSync5(file, content);
11037
11185
  }
11038
11186
  function installClaudeShim(shieldPath) {
11039
11187
  const real = resolveRealClaude();
@@ -11071,7 +11219,7 @@ async function runGlobalInstall(opts = {}) {
11071
11219
  let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
11072
11220
  if (!apiKey || apiKey === "sg_live_your_key_here") {
11073
11221
  try {
11074
- const cfg = JSON.parse(readFileSync7(p.configPath, "utf-8"));
11222
+ const cfg = JSON.parse(readFileSync8(p.configPath, "utf-8"));
11075
11223
  if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
11076
11224
  } catch {
11077
11225
  }
@@ -11087,19 +11235,19 @@ async function runGlobalInstall(opts = {}) {
11087
11235
  mkdirSync4(p.hooksDir, { recursive: true });
11088
11236
  mkdirSync4(p.claudeDir, { recursive: true });
11089
11237
  unlockProtected();
11090
- writeFileSync4(join10(p.hooksDir, "guard.mjs"), readGuard());
11091
- writeFileSync4(join10(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11092
- writeFileSync4(join10(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11093
- writeFileSync4(join10(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11238
+ writeFileSync5(join10(p.hooksDir, "guard.mjs"), readGuard());
11239
+ writeFileSync5(join10(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11240
+ writeFileSync5(join10(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11241
+ writeFileSync5(join10(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11094
11242
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11095
11243
  installClaudeShim(join10(p.hooksDir, "shield.mjs"));
11096
- writeFileSync4(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11244
+ writeFileSync5(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11097
11245
  console.log(` Wrote ${p.configPath}`);
11098
11246
  let existing = {};
11099
11247
  if (existsSync6(p.settingsPath)) {
11100
- const raw = readFileSync7(p.settingsPath, "utf-8");
11248
+ const raw = readFileSync8(p.settingsPath, "utf-8");
11101
11249
  if (!existsSync6(p.backupPath)) {
11102
- writeFileSync4(p.backupPath, raw);
11250
+ writeFileSync5(p.backupPath, raw);
11103
11251
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
11104
11252
  }
11105
11253
  try {
@@ -11122,7 +11270,7 @@ async function runGlobalInstall(opts = {}) {
11122
11270
  Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
11123
11271
  }
11124
11272
  };
11125
- writeFileSync4(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11273
+ writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11126
11274
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
11127
11275
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
11128
11276
  lockProtected();
@@ -11302,7 +11450,7 @@ import { createServer, request as httpRequest } from "http";
11302
11450
  import { request as httpsRequest } from "https";
11303
11451
  import { spawn as spawn3 } from "child_process";
11304
11452
  import { URL as URL2 } from "url";
11305
- import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11453
+ import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11306
11454
  import { resolve as resolve5 } from "path";
11307
11455
  import { homedir as homedir9 } from "os";
11308
11456
  function findCacheFile() {
@@ -11332,7 +11480,7 @@ function loadCfg() {
11332
11480
  try {
11333
11481
  const f = findCacheFile();
11334
11482
  if (f && existsSync7(f)) {
11335
- const c2 = JSON.parse(readFileSync8(f, "utf-8"));
11483
+ const c2 = JSON.parse(readFileSync9(f, "utf-8"));
11336
11484
  const d = c2?.security?.dlpRedact;
11337
11485
  const g = c2?.security?.ghost;
11338
11486
  const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
@@ -11620,7 +11768,7 @@ __export(logs_server_exports, {
11620
11768
  runLogsServer: () => runLogsServer
11621
11769
  });
11622
11770
  import { createServer as createServer2 } from "http";
11623
- import { readFileSync as readFileSync9, statSync as statSync5 } from "fs";
11771
+ import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
11624
11772
  import { resolve as resolve6, join as join11, isAbsolute } from "path";
11625
11773
  import { homedir as homedir10 } from "os";
11626
11774
  import { readdirSync as readdirSync2 } from "fs";
@@ -11647,7 +11795,7 @@ async function findLogDir() {
11647
11795
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
11648
11796
  for (const f of files) {
11649
11797
  try {
11650
- const c2 = JSON.parse(readFileSync9(join11(base, f), "utf-8"));
11798
+ const c2 = JSON.parse(readFileSync10(join11(base, f), "utf-8"));
11651
11799
  const p = c2?.security?.localLogs?.path;
11652
11800
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
11653
11801
  } catch {
@@ -11656,7 +11804,7 @@ async function findLogDir() {
11656
11804
  } catch {
11657
11805
  }
11658
11806
  try {
11659
- const cfgRaw = readFileSync9(join11(base, "cloud-guard.json"), "utf-8");
11807
+ const cfgRaw = readFileSync10(join11(base, "cloud-guard.json"), "utf-8");
11660
11808
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
11661
11809
  if (apiKey) {
11662
11810
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -11746,7 +11894,7 @@ async function runLogsServer() {
11746
11894
  return;
11747
11895
  }
11748
11896
  try {
11749
- const text = readFileSync9(info.file, "utf-8");
11897
+ const text = readFileSync10(info.file, "utf-8");
11750
11898
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
11751
11899
  res.end(text);
11752
11900
  } catch {
@@ -11798,7 +11946,7 @@ var init_logs_server = __esm({
11798
11946
 
11799
11947
  // src/inject.ts
11800
11948
  var inject_exports = {};
11801
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync8, copyFileSync } from "fs";
11949
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync8, copyFileSync } from "fs";
11802
11950
  import { resolve as resolve7 } from "path";
11803
11951
  import { execSync } from "child_process";
11804
11952
  function parseInjectArgs(argv) {
@@ -11858,7 +12006,7 @@ WHAT IT DOES
11858
12006
  function detectProject() {
11859
12007
  if (!existsSync8(resolve7("package.json"))) return false;
11860
12008
  try {
11861
- const pkg = JSON.parse(readFileSync10(resolve7("package.json"), "utf-8"));
12009
+ const pkg = JSON.parse(readFileSync11(resolve7("package.json"), "utf-8"));
11862
12010
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
11863
12011
  return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
11864
12012
  } catch {
@@ -11867,7 +12015,7 @@ function detectProject() {
11867
12015
  }
11868
12016
  function findTsEntryFile() {
11869
12017
  try {
11870
- const pkg = JSON.parse(readFileSync10(resolve7("package.json"), "utf-8"));
12018
+ const pkg = JSON.parse(readFileSync11(resolve7("package.json"), "utf-8"));
11871
12019
  if (pkg.bin) {
11872
12020
  const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
11873
12021
  if (typeof binPath === "string") {
@@ -11894,7 +12042,7 @@ function findTsEntryFile() {
11894
12042
  const full = resolve7(c2);
11895
12043
  if (existsSync8(full)) {
11896
12044
  try {
11897
- const content = readFileSync10(full, "utf-8");
12045
+ const content = readFileSync11(full, "utf-8");
11898
12046
  if (content.includes("McpServer") || content.includes("McpServer")) {
11899
12047
  return full;
11900
12048
  }
@@ -11914,7 +12062,7 @@ function detectPackageManager() {
11914
12062
  }
11915
12063
  function installSdk() {
11916
12064
  try {
11917
- const pkg = JSON.parse(readFileSync10(resolve7("package.json"), "utf-8"));
12065
+ const pkg = JSON.parse(readFileSync11(resolve7("package.json"), "utf-8"));
11918
12066
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
11919
12067
  if (allDeps["@solongate/proxy"]) {
11920
12068
  log3(" @solongate/proxy already installed");
@@ -11935,7 +12083,7 @@ function installSdk() {
11935
12083
  }
11936
12084
  }
11937
12085
  function injectTypeScript(filePath) {
11938
- const original = readFileSync10(filePath, "utf-8");
12086
+ const original = readFileSync11(filePath, "utf-8");
11939
12087
  const changes = [];
11940
12088
  let modified = original;
11941
12089
  if (modified.includes("SecureMcpServer")) {
@@ -12106,7 +12254,7 @@ async function main2() {
12106
12254
  log3("");
12107
12255
  log3(` Backup: ${backupPath}`);
12108
12256
  }
12109
- writeFileSync5(entryFile, result.modified);
12257
+ writeFileSync6(entryFile, result.modified);
12110
12258
  log3("");
12111
12259
  log3(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
12112
12260
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -12135,7 +12283,7 @@ var init_inject = __esm({
12135
12283
 
12136
12284
  // src/create.ts
12137
12285
  var create_exports = {};
12138
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
12286
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7, existsSync as existsSync9 } from "fs";
12139
12287
  import { resolve as resolve8, join as join12 } from "path";
12140
12288
  import { execSync as execSync2 } from "child_process";
12141
12289
  function withSpinner(message, fn) {
@@ -12215,7 +12363,7 @@ EXAMPLES
12215
12363
  `);
12216
12364
  }
12217
12365
  function createProject(dir, name, _policy) {
12218
- writeFileSync6(
12366
+ writeFileSync7(
12219
12367
  join12(dir, "package.json"),
12220
12368
  JSON.stringify(
12221
12369
  {
@@ -12245,7 +12393,7 @@ function createProject(dir, name, _policy) {
12245
12393
  2
12246
12394
  ) + "\n"
12247
12395
  );
12248
- writeFileSync6(
12396
+ writeFileSync7(
12249
12397
  join12(dir, "tsconfig.json"),
12250
12398
  JSON.stringify(
12251
12399
  {
@@ -12267,7 +12415,7 @@ function createProject(dir, name, _policy) {
12267
12415
  ) + "\n"
12268
12416
  );
12269
12417
  mkdirSync5(join12(dir, "src"), { recursive: true });
12270
- writeFileSync6(
12418
+ writeFileSync7(
12271
12419
  join12(dir, "src", "index.ts"),
12272
12420
  `#!/usr/bin/env node
12273
12421
 
@@ -12309,7 +12457,7 @@ console.log('');
12309
12457
  console.log('Press Ctrl+C to stop.');
12310
12458
  `
12311
12459
  );
12312
- writeFileSync6(
12460
+ writeFileSync7(
12313
12461
  join12(dir, ".mcp.json"),
12314
12462
  JSON.stringify(
12315
12463
  {
@@ -12327,12 +12475,12 @@ console.log('Press Ctrl+C to stop.');
12327
12475
  2
12328
12476
  ) + "\n"
12329
12477
  );
12330
- writeFileSync6(
12478
+ writeFileSync7(
12331
12479
  join12(dir, ".env"),
12332
12480
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12333
12481
  `
12334
12482
  );
12335
- writeFileSync6(
12483
+ writeFileSync7(
12336
12484
  join12(dir, ".gitignore"),
12337
12485
  `node_modules/
12338
12486
  dist/
@@ -12426,14 +12574,14 @@ var init_create = __esm({
12426
12574
 
12427
12575
  // src/pull-push.ts
12428
12576
  var pull_push_exports = {};
12429
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
12577
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync10 } from "fs";
12430
12578
  import { resolve as resolve9 } from "path";
12431
12579
  function loadEnv() {
12432
12580
  if (process.env.SOLONGATE_API_KEY) return;
12433
12581
  const envPath = resolve9(".env");
12434
12582
  if (!existsSync10(envPath)) return;
12435
12583
  try {
12436
- const content = readFileSync11(envPath, "utf-8");
12584
+ const content = readFileSync12(envPath, "utf-8");
12437
12585
  for (const line of content.split("\n")) {
12438
12586
  const trimmed = line.trim();
12439
12587
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -12621,7 +12769,7 @@ async function pull(apiKey, file, policyId) {
12621
12769
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
12622
12770
  const { id: _id, ...policyWithoutId } = policy;
12623
12771
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
12624
- writeFileSync7(file, json, "utf-8");
12772
+ writeFileSync8(file, json, "utf-8");
12625
12773
  log5("");
12626
12774
  log5(green2(" Saved to: ") + file);
12627
12775
  log5(` ${dim2("Name:")} ${policy.name}`);
@@ -12650,7 +12798,7 @@ async function push(apiKey, file, policyId) {
12650
12798
  log5(" solongate-proxy list");
12651
12799
  process.exit(1);
12652
12800
  }
12653
- const content = readFileSync11(file, "utf-8");
12801
+ const content = readFileSync12(file, "utf-8");
12654
12802
  let policy;
12655
12803
  try {
12656
12804
  policy = JSON.parse(content);
package/dist/tui/index.js CHANGED
@@ -169,12 +169,12 @@ function KeyHints({ hints }) {
169
169
  // src/tui/panels/Live.tsx
170
170
  import { Box as Box2, Text as Text2, useInput } from "ink";
171
171
  import TextInput from "ink-text-input";
172
- import { mkdirSync, writeFileSync } from "fs";
172
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
173
173
  import { homedir as homedir4 } from "os";
174
174
  import { join as join4 } from "path";
175
175
 
176
176
  // src/tui/local-log.ts
177
- import { closeSync, openSync, readSync, statSync } from "fs";
177
+ import { closeSync, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
178
178
  import { homedir as homedir2 } from "os";
179
179
  import { join as join2 } from "path";
180
180
  var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
@@ -193,6 +193,39 @@ function tailLines(file, maxBytes = 131072) {
193
193
  return [];
194
194
  }
195
195
  }
196
+ function deleteLocalEntry(at, tool, session) {
197
+ try {
198
+ const lines = readFileSync2(LOCAL_LOG, "utf-8").split("\n");
199
+ let removed = 0;
200
+ const kept = lines.filter((line) => {
201
+ if (!line.trim()) return false;
202
+ if (removed) return true;
203
+ try {
204
+ const j = JSON.parse(line);
205
+ const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
206
+ if (hit) {
207
+ removed++;
208
+ return false;
209
+ }
210
+ } catch {
211
+ }
212
+ return true;
213
+ });
214
+ if (removed) writeFileSync(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
215
+ return removed;
216
+ } catch {
217
+ return 0;
218
+ }
219
+ }
220
+ function clearLocalLog() {
221
+ try {
222
+ const n = readFileSync2(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
223
+ writeFileSync(LOCAL_LOG, "");
224
+ return n;
225
+ } catch {
226
+ return 0;
227
+ }
228
+ }
196
229
  function parseLocalLines(lines) {
197
230
  const out = [];
198
231
  for (const line of lines) {
@@ -210,7 +243,7 @@ function parseLocalLines(lines) {
210
243
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
211
244
 
212
245
  // src/api-client/client.ts
213
- import { readFileSync as readFileSync2, existsSync } from "fs";
246
+ import { readFileSync as readFileSync3, existsSync } from "fs";
214
247
  import { resolve, join as join3 } from "path";
215
248
  import { homedir as homedir3 } from "os";
216
249
  var DEFAULT_API_URL = "https://api.solongate.com";
@@ -234,7 +267,7 @@ function loginCredentialFile() {
234
267
  try {
235
268
  const p = join3(homedir3(), ".solongate", "cloud-guard.json");
236
269
  if (!existsSync(p)) return {};
237
- const c2 = JSON.parse(readFileSync2(p, "utf-8"));
270
+ const c2 = JSON.parse(readFileSync3(p, "utf-8"));
238
271
  return c2 && typeof c2 === "object" ? c2 : {};
239
272
  } catch {
240
273
  return {};
@@ -244,7 +277,7 @@ function dotenvApiKey() {
244
277
  try {
245
278
  const envPath = resolve(".env");
246
279
  if (!existsSync(envPath)) return void 0;
247
- for (const line of readFileSync2(envPath, "utf-8").split("\n")) {
280
+ for (const line of readFileSync3(envPath, "utf-8").split("\n")) {
248
281
  const trimmed = line.trim();
249
282
  if (!trimmed || trimmed.startsWith("#")) continue;
250
283
  const eq = trimmed.indexOf("=");
@@ -496,11 +529,19 @@ var audit_exports = {};
496
529
  __export(audit_exports, {
497
530
  block: () => block,
498
531
  list: () => list2,
532
+ remove: () => remove2,
533
+ removeAll: () => removeAll,
499
534
  whitelist: () => whitelist
500
535
  });
501
536
  function list2(query = {}) {
502
537
  return request("GET", "/audit-logs", { query });
503
538
  }
539
+ function remove2(ids) {
540
+ return request("DELETE", "/audit-logs", { body: { ids } });
541
+ }
542
+ function removeAll() {
543
+ return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
544
+ }
504
545
  function whitelist(id, scope = "exact") {
505
546
  return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
506
547
  }
@@ -1257,7 +1298,7 @@ function LivePanel({ active: active2 }) {
1257
1298
  const file = join4(homedir4(), ".solongate", "live-export.jsonl");
1258
1299
  try {
1259
1300
  mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
1260
- writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1301
+ writeFileSync2(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1261
1302
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1262
1303
  } catch (err) {
1263
1304
  setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
@@ -2214,6 +2255,43 @@ function loadLocalRows() {
2214
2255
  args: j.arguments ? JSON.stringify(j.arguments) : null
2215
2256
  })).sort((a, b) => b.at - a.at);
2216
2257
  }
2258
+ var AUDIT_HELP = [
2259
+ [
2260
+ "Logs",
2261
+ [
2262
+ ["\u2191\u2193 / PgUp PgDn", "select a row (window follows)"],
2263
+ ["enter", "open the FULL entry (reason + arguments)"],
2264
+ ["\u2190 \u2192", "previous / next page (500 per page, jumps to top)"],
2265
+ ["f", "decision filter: all \u2192 DENY \u2192 ALLOW"],
2266
+ ["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
2267
+ ["t / n", "tool / agent filter (type, enter done)"],
2268
+ ["/", "free-text search"],
2269
+ ["x", "delete the selected entry (press x twice)"],
2270
+ ["X", "delete ALL logs of the current source (press X twice)"],
2271
+ ["c", "clear every filter (incl. session)"]
2272
+ ]
2273
+ ],
2274
+ [
2275
+ "Sessions",
2276
+ [
2277
+ ["\u2191\u2193", "select a session"],
2278
+ ["enter", "open that session's logs"],
2279
+ ["f", "status filter: all \u2192 active \u2192 idle \u2192 ended"],
2280
+ ["/", "search agent / session id"],
2281
+ ["c", "clear session filters"]
2282
+ ]
2283
+ ],
2284
+ [
2285
+ "Anywhere in Audit",
2286
+ [
2287
+ ["v", "switch logs \u2194 sessions"],
2288
+ ["s", "switch source cloud \u2194 local file"],
2289
+ ["?", "this help \xB7 any key closes"],
2290
+ ["esc", "back to the menu"]
2291
+ ]
2292
+ ],
2293
+ ["Entry detail", [["\u2191\u2193 / PgUp PgDn", "scroll the arguments"], ["\u2190 / esc", "back to the list"]]]
2294
+ ];
2217
2295
  var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
2218
2296
  var STATUS_DOT = {
2219
2297
  active: { ch: "\u25CF", color: theme.ok },
@@ -2237,6 +2315,9 @@ function AuditPanel({ active: active2, focused }) {
2237
2315
  const [si, setSi] = useState6(0);
2238
2316
  const [sessSearch, setSessSearch] = useState6("");
2239
2317
  const [sessSel, setSessSel] = useState6(0);
2318
+ const [confirm, setConfirm] = useState6(null);
2319
+ const [msg, setMsg] = useState6(null);
2320
+ const [showHelp, setShowHelp] = useState6(false);
2240
2321
  const toTop = () => setSel(0);
2241
2322
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
2242
2323
  const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
@@ -2322,8 +2403,41 @@ function AuditPanel({ active: active2, focused }) {
2322
2403
  () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
2323
2404
  [view, current?.session, source]
2324
2405
  );
2406
+ const logsLoading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
2407
+ const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
2408
+ const doDelete = (kind) => {
2409
+ setMsg({ text: "deleting\u2026", level: "ok" });
2410
+ const run = async () => {
2411
+ if (source === "cloud") {
2412
+ if (kind === "one") {
2413
+ if (!current) throw new Error("nothing selected");
2414
+ await api.audit.remove([current.id]);
2415
+ } else {
2416
+ await api.audit.removeAll();
2417
+ }
2418
+ cloudQ.reload();
2419
+ statsQ.reloadQuiet();
2420
+ } else {
2421
+ const n = kind === "one" && current ? deleteLocalEntry(current.at, current.tool, current.session) : kind === "all" ? clearLocalLog() : 0;
2422
+ if (!n) throw new Error("entry not found in the local file");
2423
+ localQ.reload();
2424
+ }
2425
+ };
2426
+ run().then(() => {
2427
+ setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
2428
+ toTop();
2429
+ }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
2430
+ };
2325
2431
  useInput5(
2326
2432
  (input, key) => {
2433
+ if (showHelp) {
2434
+ setShowHelp(false);
2435
+ return;
2436
+ }
2437
+ if (input === "?") {
2438
+ setShowHelp(true);
2439
+ return;
2440
+ }
2327
2441
  if (view === "detail") {
2328
2442
  if (key.leftArrow || key.escape) setView("logs");
2329
2443
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
@@ -2332,6 +2446,11 @@ function AuditPanel({ active: active2, focused }) {
2332
2446
  else if (key.pageDown) setDetailScroll((n) => n + 10);
2333
2447
  return;
2334
2448
  }
2449
+ if (view === "logs" ? logsLoading : sessLoading) return;
2450
+ if (confirm && input !== "x" && input !== "X") {
2451
+ setConfirm(null);
2452
+ setMsg(null);
2453
+ }
2335
2454
  if (input === "s") {
2336
2455
  setSource((s) => s === "cloud" ? "local" : "cloud");
2337
2456
  setPage(0);
@@ -2393,6 +2512,23 @@ function AuditPanel({ active: active2, focused }) {
2393
2512
  setGi((n) => (n + 1) % SIGNALS.length);
2394
2513
  setPage(0);
2395
2514
  toTop();
2515
+ } else if (input === "x") {
2516
+ if (!current) return;
2517
+ if (confirm?.kind !== "one" || confirm.key !== current.id) {
2518
+ setConfirm({ kind: "one", key: current.id });
2519
+ setMsg({ text: `delete ${current.decision} ${current.tool} (${ago(current.at)} ago)? press x again`, level: "bad" });
2520
+ return;
2521
+ }
2522
+ setConfirm(null);
2523
+ doDelete("one");
2524
+ } else if (input === "X") {
2525
+ if (confirm?.kind !== "all") {
2526
+ setConfirm({ kind: "all", key: "all" });
2527
+ setMsg({ text: `DELETE ALL ${source} logs (${total} entries)? press X again`, level: "bad" });
2528
+ return;
2529
+ }
2530
+ setConfirm(null);
2531
+ doDelete("all");
2396
2532
  } else if (input === "t") setEditing("tool");
2397
2533
  else if (input === "n") setEditing("agent");
2398
2534
  else if (input === "/") setEditing("search");
@@ -2449,6 +2585,19 @@ function AuditPanel({ active: active2, focused }) {
2449
2585
  /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
2450
2586
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
2451
2587
  ] });
2588
+ if (showHelp) {
2589
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2590
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accentBright, children: "AUDIT \u2014 all keys" }),
2591
+ AUDIT_HELP.map(([group, keys]) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
2592
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: group }),
2593
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2594
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, children: (" " + k).padEnd(20) }),
2595
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: desc })
2596
+ ] }, k))
2597
+ ] }, group)),
2598
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "press any key to close" }) })
2599
+ ] });
2600
+ }
2452
2601
  if (view === "detail" && current) {
2453
2602
  const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
2454
2603
  const bodyW = Math.max(20, cols - 2);
@@ -2519,8 +2668,7 @@ function AuditPanel({ active: active2, focused }) {
2519
2668
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
2520
2669
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
2521
2670
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
2522
- const loading2 = source === "cloud" ? agentsQ.loading : localQ.loading;
2523
- return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2671
+ return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2524
2672
  strip,
2525
2673
  /* @__PURE__ */ jsxs6(Box6, { children: [
2526
2674
  srcChip,
@@ -2529,7 +2677,7 @@ function AuditPanel({ active: active2, focused }) {
2529
2677
  chip("status", SESS_STATUS[si] ?? "all", si !== 0),
2530
2678
  chip("search", sessSearch || "\xB7", !!sessSearch)
2531
2679
  ] }),
2532
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 f status \xB7 / search \xB7 s source \xB7 v logs \xB7 c clear" : "press \u2192 to browse" }),
2680
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ? all keys" : "press \u2192 to browse" }),
2533
2681
  editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2534
2682
  /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
2535
2683
  /* @__PURE__ */ jsx6(
@@ -2583,15 +2731,14 @@ function AuditPanel({ active: active2, focused }) {
2583
2731
  ] }) : null
2584
2732
  ] }) });
2585
2733
  }
2586
- const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
2734
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0);
2587
2735
  const listRows = Math.max(4, rows - headerRows);
2588
2736
  const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
2589
2737
  const maxStart = Math.max(0, pageRows.length - listRows);
2590
2738
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2591
2739
  const windowed = pageRows.slice(start, start + listRows);
2592
2740
  const reasonW = Math.min(60, Math.max(12, cols - 67));
2593
- const loading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
2594
- return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2741
+ return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2595
2742
  strip,
2596
2743
  /* @__PURE__ */ jsxs6(Box6, { children: [
2597
2744
  srcChip,
@@ -2604,7 +2751,8 @@ function AuditPanel({ active: active2, focused }) {
2604
2751
  chip("search", search || "\xB7", !!search),
2605
2752
  sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
2606
2753
  ] }),
2607
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
2754
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ? all keys" : "press \u2192 to browse" }),
2755
+ msg ? /* @__PURE__ */ jsx6(Text6, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
2608
2756
  editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2609
2757
  /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
2610
2758
  editing,
@@ -2950,7 +3098,7 @@ var HELP = [
2950
3098
  ["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)"]]],
2951
3099
  ["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"]]],
2952
3100
  ["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"]]],
2953
- ["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"], ["c", "clear filters"]]],
3101
+ ["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"]]],
2954
3102
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
2955
3103
  ];
2956
3104
  function HelpOverlay({ cols, rows }) {
@@ -17,6 +17,10 @@ export interface LocalLogLine {
17
17
  matched_rule_id?: string;
18
18
  rate_limit_burst?: boolean;
19
19
  }
20
+ /** Delete ONE matching line (ts + tool [+ session]) from the local log file. */
21
+ export declare function deleteLocalEntry(at: number, tool: string, session?: string | null): number;
22
+ /** Empty the local log file. Returns how many lines were removed. */
23
+ export declare function clearLocalLog(): number;
20
24
  export declare function parseLocalLines(lines: string[]): Array<LocalLogLine & {
21
25
  at: number;
22
26
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.25",
3
+ "version": "0.81.27",
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": {