@solongate/proxy 0.81.27 → 0.81.29

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
@@ -8877,6 +8877,9 @@ var init_Dlp = __esm({
8877
8877
  // src/tui/panels/Audit.tsx
8878
8878
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
8879
8879
  import TextInput4 from "ink-text-input";
8880
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
8881
+ import { homedir as homedir6 } from "os";
8882
+ import { join as join8 } from "path";
8880
8883
  import { useState as useState6 } from "react";
8881
8884
  import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8882
8885
  function loadLocalRows() {
@@ -8917,13 +8920,14 @@ function AuditPanel({ active: active2, focused }) {
8917
8920
  const [confirm, setConfirm] = useState6(null);
8918
8921
  const [msg, setMsg] = useState6(null);
8919
8922
  const [showHelp, setShowHelp] = useState6(false);
8923
+ const [frozen, setFrozen] = useState6(false);
8920
8924
  const toTop = () => setSel(0);
8921
8925
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
8922
8926
  const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
8923
8927
  usePoll(() => {
8924
8928
  statsQ.reloadQuiet();
8925
8929
  tsQ.reloadQuiet();
8926
- }, 15e3, active2 && source === "cloud");
8930
+ }, 15e3, active2 && source === "cloud" && !frozen);
8927
8931
  const query = {
8928
8932
  filter: DECISIONS[di],
8929
8933
  signal: SIGNALS[gi],
@@ -8938,29 +8942,28 @@ function AuditPanel({ active: active2, focused }) {
8938
8942
  () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
8939
8943
  [source, di, gi, tool, agent, search, sessFilter, page]
8940
8944
  );
8941
- usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
8945
+ usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0 && !frozen);
8942
8946
  const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
8943
- usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
8947
+ usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0 && !frozen);
8948
+ const q = search.trim().toLowerCase();
8949
+ const localFiltered = (localQ.data ?? []).filter((r) => {
8950
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
8951
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
8952
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
8953
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
8954
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
8955
+ if (sessFilter && r.session !== sessFilter) return false;
8956
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
8957
+ return true;
8958
+ });
8944
8959
  let pageRows = [];
8945
8960
  let total = 0;
8946
8961
  if (source === "cloud") {
8947
8962
  pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
8948
8963
  total = cloudQ.data?.total ?? 0;
8949
8964
  } else {
8950
- const all = localQ.data ?? [];
8951
- const q = search.trim().toLowerCase();
8952
- const filtered = all.filter((r) => {
8953
- if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
8954
- if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
8955
- if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
8956
- if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
8957
- if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
8958
- if (sessFilter && r.session !== sessFilter) return false;
8959
- if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
8960
- return true;
8961
- });
8962
- total = filtered.length;
8963
- pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
8965
+ total = localFiltered.length;
8966
+ pageRows = localFiltered.slice(page * PAGE, page * PAGE + PAGE);
8964
8967
  }
8965
8968
  const pages = Math.max(1, Math.ceil(total / PAGE));
8966
8969
  const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
@@ -8968,7 +8971,7 @@ function AuditPanel({ active: active2, focused }) {
8968
8971
  () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
8969
8972
  [source]
8970
8973
  );
8971
- usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
8974
+ usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions" && !frozen);
8972
8975
  let sessions = [];
8973
8976
  if (source === "cloud") {
8974
8977
  sessions = (agentsQ.data?.agents ?? []).map((a) => ({
@@ -9027,8 +9030,30 @@ function AuditPanel({ active: active2, focused }) {
9027
9030
  toTop();
9028
9031
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
9029
9032
  };
9033
+ const doExport = (kind) => {
9034
+ setMsg({ text: "exporting\u2026", level: "ok" });
9035
+ const run12 = async () => {
9036
+ const dir = join8(homedir6(), ".solongate");
9037
+ const file = join8(dir, `audit-export-${source}.jsonl`);
9038
+ let rows2;
9039
+ if (kind === "page") rows2 = pageRows;
9040
+ else if (source === "cloud") {
9041
+ const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
9042
+ rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
9043
+ } else rows2 = localFiltered;
9044
+ mkdirSync4(dir, { recursive: true });
9045
+ writeFileSync5(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
9046
+ return { n: rows2.length, file };
9047
+ };
9048
+ run12().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
9049
+ };
9030
9050
  useInput5(
9031
9051
  (input, key) => {
9052
+ if (input === " ") {
9053
+ setFrozen((f) => !f);
9054
+ return;
9055
+ }
9056
+ if (frozen) return;
9032
9057
  if (showHelp) {
9033
9058
  setShowHelp(false);
9034
9059
  return;
@@ -9115,7 +9140,7 @@ function AuditPanel({ active: active2, focused }) {
9115
9140
  if (!current) return;
9116
9141
  if (confirm?.kind !== "one" || confirm.key !== current.id) {
9117
9142
  setConfirm({ kind: "one", key: current.id });
9118
- setMsg({ text: `delete ${current.decision} ${current.tool} (${ago(current.at)} ago)? press x again`, level: "bad" });
9143
+ setMsg({ text: `x = delete ONLY the selected entry: ${current.decision} ${current.tool} (${ago(current.at)} ago) \u2014 press x again`, level: "bad" });
9119
9144
  return;
9120
9145
  }
9121
9146
  setConfirm(null);
@@ -9123,12 +9148,14 @@ function AuditPanel({ active: active2, focused }) {
9123
9148
  } else if (input === "X") {
9124
9149
  if (confirm?.kind !== "all") {
9125
9150
  setConfirm({ kind: "all", key: "all" });
9126
- setMsg({ text: `DELETE ALL ${source} logs (${total} entries)? press X again`, level: "bad" });
9151
+ setMsg({ text: `\u26A0 X = delete ALL ${source} logs \u2014 every one of the ${total} matched entries! press X again`, level: "bad" });
9127
9152
  return;
9128
9153
  }
9129
9154
  setConfirm(null);
9130
9155
  doDelete("all");
9131
- } else if (input === "t") setEditing("tool");
9156
+ } else if (input === "e") doExport("page");
9157
+ else if (input === "E") doExport("all");
9158
+ else if (input === "t") setEditing("tool");
9132
9159
  else if (input === "n") setEditing("agent");
9133
9160
  else if (input === "/") setEditing("search");
9134
9161
  else if (input === "c") {
@@ -9179,6 +9206,7 @@ function AuditPanel({ active: active2, focused }) {
9179
9206
  LOCAL_LOG
9180
9207
  ] })
9181
9208
  ] });
9209
+ const copyBanner = frozen ? /* @__PURE__ */ jsx6(Text6, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, wrap: "truncate", children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : null;
9182
9210
  const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
9183
9211
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
9184
9212
  /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
@@ -9204,11 +9232,12 @@ function AuditPanel({ active: active2, focused }) {
9204
9232
  const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
9205
9233
  const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
9206
9234
  const reasonShown = reasonLines.slice(0, 4);
9207
- const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
9235
+ const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1 - (frozen ? 1 : 0));
9208
9236
  const maxScroll = Math.max(0, argLines.length - argBudget);
9209
9237
  const off = Math.min(detailScroll, maxScroll);
9210
9238
  const argWin = argLines.slice(off, off + argBudget);
9211
9239
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9240
+ copyBanner,
9212
9241
  /* @__PURE__ */ jsxs6(Box6, { children: [
9213
9242
  /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
9214
9243
  /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
@@ -9267,8 +9296,9 @@ function AuditPanel({ active: active2, focused }) {
9267
9296
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
9268
9297
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
9269
9298
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
9270
- return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9299
+ return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9271
9300
  strip,
9301
+ copyBanner,
9272
9302
  /* @__PURE__ */ jsxs6(Box6, { children: [
9273
9303
  srcChip,
9274
9304
  /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
@@ -9330,15 +9360,16 @@ function AuditPanel({ active: active2, focused }) {
9330
9360
  ] }) : null
9331
9361
  ] }) });
9332
9362
  }
9333
- const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0);
9363
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0) + (frozen ? 1 : 0);
9334
9364
  const listRows = Math.max(4, rows - headerRows);
9335
9365
  const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
9336
9366
  const maxStart = Math.max(0, pageRows.length - listRows);
9337
9367
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
9338
9368
  const windowed = pageRows.slice(start, start + listRows);
9339
9369
  const reasonW = Math.min(60, Math.max(12, cols - 67));
9340
- return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9370
+ return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9341
9371
  strip,
9372
+ copyBanner,
9342
9373
  /* @__PURE__ */ jsxs6(Box6, { children: [
9343
9374
  srcChip,
9344
9375
  /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
@@ -9451,8 +9482,10 @@ var init_Audit = __esm({
9451
9482
  ["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
9452
9483
  ["t / n", "tool / agent filter (type, enter done)"],
9453
9484
  ["/", "free-text search"],
9454
- ["x", "delete the selected entry (press x twice)"],
9455
- ["X", "delete ALL logs of the current source (press X twice)"],
9485
+ ["x", "delete ONLY the selected entry (press x twice)"],
9486
+ ["X", "delete ALL matched logs of the source (press X twice)"],
9487
+ ["e", "export this page \u2192 ~/.solongate/audit-export-<src>.jsonl"],
9488
+ ["E", "export ALL matched rows (cloud: up to 10k)"],
9456
9489
  ["c", "clear every filter (incl. session)"]
9457
9490
  ]
9458
9491
  ],
@@ -9471,6 +9504,7 @@ var init_Audit = __esm({
9471
9504
  [
9472
9505
  ["v", "switch logs \u2194 sessions"],
9473
9506
  ["s", "switch source cloud \u2194 local file"],
9507
+ ["space", "copy mode: freeze screen for mouse selection"],
9474
9508
  ["?", "this help \xB7 any key closes"],
9475
9509
  ["esc", "back to the menu"]
9476
9510
  ]
@@ -10540,8 +10574,8 @@ var init_agents2 = __esm({
10540
10574
 
10541
10575
  // src/commands/doctor.ts
10542
10576
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
10543
- import { homedir as homedir6 } from "os";
10544
- import { join as join8 } from "path";
10577
+ import { homedir as homedir7 } from "os";
10578
+ import { join as join9 } from "path";
10545
10579
  async function run6(argv) {
10546
10580
  const { flags } = parse(argv);
10547
10581
  const json = flagBool(flags, "json");
@@ -10601,14 +10635,14 @@ var init_doctor = __esm({
10601
10635
  init_api_client();
10602
10636
  init_format();
10603
10637
  init_args();
10604
- LOCAL_LOG2 = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
10638
+ LOCAL_LOG2 = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
10605
10639
  }
10606
10640
  });
10607
10641
 
10608
10642
  // src/commands/watch.ts
10609
10643
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync3 } from "fs";
10610
- import { homedir as homedir7 } from "os";
10611
- import { join as join9 } from "path";
10644
+ import { homedir as homedir8 } from "os";
10645
+ import { join as join10 } from "path";
10612
10646
  function tailLocal(file, maxBytes = 131072) {
10613
10647
  try {
10614
10648
  const size = statSync3(file).size;
@@ -10715,7 +10749,7 @@ var init_watch = __esm({
10715
10749
  init_cli_utils();
10716
10750
  init_args();
10717
10751
  init_format();
10718
- LOCAL_LOG3 = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
10752
+ LOCAL_LOG3 = join10(homedir8(), ".solongate", "local-logs", "solongate-audit.jsonl");
10719
10753
  trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
10720
10754
  time = (ms) => new Date(ms).toTimeString().slice(0, 8);
10721
10755
  }
@@ -11017,9 +11051,9 @@ __export(global_install_exports, {
11017
11051
  runGlobalRestore: () => runGlobalRestore,
11018
11052
  unlockProtected: () => unlockProtected
11019
11053
  });
11020
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
11021
- import { resolve as resolve4, join as join10, dirname } from "path";
11022
- import { homedir as homedir8 } from "os";
11054
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
11055
+ import { resolve as resolve4, join as join11, dirname } from "path";
11056
+ import { homedir as homedir9 } from "os";
11023
11057
  import { fileURLToPath } from "url";
11024
11058
  import { createInterface } from "readline";
11025
11059
  import { execFileSync as execFileSync2 } from "child_process";
@@ -11082,10 +11116,10 @@ function unlockFile(file) {
11082
11116
  function protectedTargets() {
11083
11117
  const p = globalPaths();
11084
11118
  return [
11085
- join10(p.hooksDir, "guard.mjs"),
11086
- join10(p.hooksDir, "audit.mjs"),
11087
- join10(p.hooksDir, "stop.mjs"),
11088
- join10(p.hooksDir, "shield.mjs"),
11119
+ join11(p.hooksDir, "guard.mjs"),
11120
+ join11(p.hooksDir, "audit.mjs"),
11121
+ join11(p.hooksDir, "stop.mjs"),
11122
+ join11(p.hooksDir, "shield.mjs"),
11089
11123
  p.configPath,
11090
11124
  p.settingsPath
11091
11125
  ];
@@ -11097,25 +11131,25 @@ function unlockProtected() {
11097
11131
  for (const f of protectedTargets()) unlockFile(f);
11098
11132
  }
11099
11133
  function globalPaths() {
11100
- const home = homedir8();
11101
- const sgDir = join10(home, ".solongate");
11102
- const hooksDir = join10(sgDir, "hooks");
11103
- const claudeDir = join10(home, ".claude");
11134
+ const home = homedir9();
11135
+ const sgDir = join11(home, ".solongate");
11136
+ const hooksDir = join11(sgDir, "hooks");
11137
+ const claudeDir = join11(home, ".claude");
11104
11138
  return {
11105
11139
  home,
11106
11140
  sgDir,
11107
11141
  hooksDir,
11108
11142
  claudeDir,
11109
- settingsPath: join10(claudeDir, "settings.json"),
11110
- backupPath: join10(claudeDir, "settings.solongate.bak"),
11111
- configPath: join10(sgDir, "cloud-guard.json")
11143
+ settingsPath: join11(claudeDir, "settings.json"),
11144
+ backupPath: join11(claudeDir, "settings.solongate.bak"),
11145
+ configPath: join11(sgDir, "cloud-guard.json")
11112
11146
  };
11113
11147
  }
11114
11148
  function readHook(filename) {
11115
- return readFileSync8(join10(HOOKS_DIR, filename), "utf-8");
11149
+ return readFileSync8(join11(HOOKS_DIR, filename), "utf-8");
11116
11150
  }
11117
11151
  function readGuard() {
11118
- const bundled = join10(HOOKS_DIR, "guard.bundled.mjs");
11152
+ const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
11119
11153
  return existsSync6(bundled) ? readFileSync8(bundled, "utf-8") : readHook("guard.mjs");
11120
11154
  }
11121
11155
  function ask(question) {
@@ -11130,13 +11164,13 @@ function runGlobalRestore() {
11130
11164
  unlockProtected();
11131
11165
  removeClaudeShim();
11132
11166
  if (existsSync6(p.backupPath)) {
11133
- writeFileSync5(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11167
+ writeFileSync6(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11134
11168
  console.log(` Restored ${p.settingsPath} from backup.`);
11135
11169
  } else if (existsSync6(p.settingsPath)) {
11136
11170
  try {
11137
11171
  const s = JSON.parse(readFileSync8(p.settingsPath, "utf-8"));
11138
11172
  delete s.hooks;
11139
- writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11173
+ writeFileSync6(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11140
11174
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
11141
11175
  } catch {
11142
11176
  }
@@ -11170,7 +11204,7 @@ function shimTargets() {
11170
11204
  return [];
11171
11205
  }
11172
11206
  }
11173
- return [".bashrc", ".zshrc", ".profile"].map((f) => join10(homedir8(), f)).filter((f) => existsSync6(f));
11207
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join11(homedir9(), f)).filter((f) => existsSync6(f));
11174
11208
  }
11175
11209
  function writeShimBlock(file, block2) {
11176
11210
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
@@ -11180,8 +11214,8 @@ function writeShimBlock(file, block2) {
11180
11214
  if (content.length && !content.endsWith("\n")) content += "\n";
11181
11215
  content += block2 + "\n";
11182
11216
  }
11183
- mkdirSync4(dirname(file), { recursive: true });
11184
- writeFileSync5(file, content);
11217
+ mkdirSync5(dirname(file), { recursive: true });
11218
+ writeFileSync6(file, content);
11185
11219
  }
11186
11220
  function installClaudeShim(shieldPath) {
11187
11221
  const real = resolveRealClaude();
@@ -11232,22 +11266,22 @@ async function runGlobalInstall(opts = {}) {
11232
11266
  process.exit(1);
11233
11267
  }
11234
11268
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
11235
- mkdirSync4(p.hooksDir, { recursive: true });
11236
- mkdirSync4(p.claudeDir, { recursive: true });
11269
+ mkdirSync5(p.hooksDir, { recursive: true });
11270
+ mkdirSync5(p.claudeDir, { recursive: true });
11237
11271
  unlockProtected();
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"));
11272
+ writeFileSync6(join11(p.hooksDir, "guard.mjs"), readGuard());
11273
+ writeFileSync6(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11274
+ writeFileSync6(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11275
+ writeFileSync6(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11242
11276
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11243
- installClaudeShim(join10(p.hooksDir, "shield.mjs"));
11244
- writeFileSync5(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11277
+ installClaudeShim(join11(p.hooksDir, "shield.mjs"));
11278
+ writeFileSync6(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11245
11279
  console.log(` Wrote ${p.configPath}`);
11246
11280
  let existing = {};
11247
11281
  if (existsSync6(p.settingsPath)) {
11248
11282
  const raw = readFileSync8(p.settingsPath, "utf-8");
11249
11283
  if (!existsSync6(p.backupPath)) {
11250
- writeFileSync5(p.backupPath, raw);
11284
+ writeFileSync6(p.backupPath, raw);
11251
11285
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
11252
11286
  }
11253
11287
  try {
@@ -11256,9 +11290,9 @@ async function runGlobalInstall(opts = {}) {
11256
11290
  existing = {};
11257
11291
  }
11258
11292
  }
11259
- const guardAbs = join10(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11260
- const auditAbs = join10(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11261
- const stopAbs = join10(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11293
+ const guardAbs = join11(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11294
+ const auditAbs = join11(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11295
+ const stopAbs = join11(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11262
11296
  const nodeBin = process.execPath.replace(/\\/g, "/");
11263
11297
  const call = process.platform === "win32" ? "& " : "";
11264
11298
  const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
@@ -11270,7 +11304,7 @@ async function runGlobalInstall(opts = {}) {
11270
11304
  Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
11271
11305
  }
11272
11306
  };
11273
- writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11307
+ writeFileSync6(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11274
11308
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
11275
11309
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
11276
11310
  lockProtected();
@@ -11452,9 +11486,9 @@ import { spawn as spawn3 } from "child_process";
11452
11486
  import { URL as URL2 } from "url";
11453
11487
  import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11454
11488
  import { resolve as resolve5 } from "path";
11455
- import { homedir as homedir9 } from "os";
11489
+ import { homedir as homedir10 } from "os";
11456
11490
  function findCacheFile() {
11457
- const dir = resolve5(homedir9(), ".solongate");
11491
+ const dir = resolve5(homedir10(), ".solongate");
11458
11492
  const envSel = process.env.SOLONGATE_AGENT_ID;
11459
11493
  if (envSel) {
11460
11494
  const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
@@ -11769,8 +11803,8 @@ __export(logs_server_exports, {
11769
11803
  });
11770
11804
  import { createServer as createServer2 } from "http";
11771
11805
  import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
11772
- import { resolve as resolve6, join as join11, isAbsolute } from "path";
11773
- import { homedir as homedir10 } from "os";
11806
+ import { resolve as resolve6, join as join12, isAbsolute } from "path";
11807
+ import { homedir as homedir11 } from "os";
11774
11808
  import { readdirSync as readdirSync2 } from "fs";
11775
11809
  function allowedOrigins() {
11776
11810
  const base = [
@@ -11787,15 +11821,15 @@ function resolveLocalLogDir(rawPath) {
11787
11821
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
11788
11822
  if (!dir) return null;
11789
11823
  if (isAbsolute(dir)) return dir;
11790
- return resolve6(homedir10(), ".solongate", "local-logs");
11824
+ return resolve6(homedir11(), ".solongate", "local-logs");
11791
11825
  }
11792
11826
  async function findLogDir() {
11793
- const base = resolve6(homedir10(), ".solongate");
11827
+ const base = resolve6(homedir11(), ".solongate");
11794
11828
  try {
11795
11829
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
11796
11830
  for (const f of files) {
11797
11831
  try {
11798
- const c2 = JSON.parse(readFileSync10(join11(base, f), "utf-8"));
11832
+ const c2 = JSON.parse(readFileSync10(join12(base, f), "utf-8"));
11799
11833
  const p = c2?.security?.localLogs?.path;
11800
11834
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
11801
11835
  } catch {
@@ -11804,7 +11838,7 @@ async function findLogDir() {
11804
11838
  } catch {
11805
11839
  }
11806
11840
  try {
11807
- const cfgRaw = readFileSync10(join11(base, "cloud-guard.json"), "utf-8");
11841
+ const cfgRaw = readFileSync10(join12(base, "cloud-guard.json"), "utf-8");
11808
11842
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
11809
11843
  if (apiKey) {
11810
11844
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -11833,7 +11867,7 @@ function setCors(req, res) {
11833
11867
  }
11834
11868
  function fileInfo(dir) {
11835
11869
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
11836
- const file = join11(dir, LOG_FILENAME);
11870
+ const file = join12(dir, LOG_FILENAME);
11837
11871
  try {
11838
11872
  const st = statSync5(file);
11839
11873
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -11946,7 +11980,7 @@ var init_logs_server = __esm({
11946
11980
 
11947
11981
  // src/inject.ts
11948
11982
  var inject_exports = {};
11949
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync8, copyFileSync } from "fs";
11983
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync8, copyFileSync } from "fs";
11950
11984
  import { resolve as resolve7 } from "path";
11951
11985
  import { execSync } from "child_process";
11952
11986
  function parseInjectArgs(argv) {
@@ -12254,7 +12288,7 @@ async function main2() {
12254
12288
  log3("");
12255
12289
  log3(` Backup: ${backupPath}`);
12256
12290
  }
12257
- writeFileSync6(entryFile, result.modified);
12291
+ writeFileSync7(entryFile, result.modified);
12258
12292
  log3("");
12259
12293
  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");
12260
12294
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -12283,8 +12317,8 @@ var init_inject = __esm({
12283
12317
 
12284
12318
  // src/create.ts
12285
12319
  var create_exports = {};
12286
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7, existsSync as existsSync9 } from "fs";
12287
- import { resolve as resolve8, join as join12 } from "path";
12320
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, existsSync as existsSync9 } from "fs";
12321
+ import { resolve as resolve8, join as join13 } from "path";
12288
12322
  import { execSync as execSync2 } from "child_process";
12289
12323
  function withSpinner(message, fn) {
12290
12324
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -12363,8 +12397,8 @@ EXAMPLES
12363
12397
  `);
12364
12398
  }
12365
12399
  function createProject(dir, name, _policy) {
12366
- writeFileSync7(
12367
- join12(dir, "package.json"),
12400
+ writeFileSync8(
12401
+ join13(dir, "package.json"),
12368
12402
  JSON.stringify(
12369
12403
  {
12370
12404
  name,
@@ -12393,8 +12427,8 @@ function createProject(dir, name, _policy) {
12393
12427
  2
12394
12428
  ) + "\n"
12395
12429
  );
12396
- writeFileSync7(
12397
- join12(dir, "tsconfig.json"),
12430
+ writeFileSync8(
12431
+ join13(dir, "tsconfig.json"),
12398
12432
  JSON.stringify(
12399
12433
  {
12400
12434
  compilerOptions: {
@@ -12414,9 +12448,9 @@ function createProject(dir, name, _policy) {
12414
12448
  2
12415
12449
  ) + "\n"
12416
12450
  );
12417
- mkdirSync5(join12(dir, "src"), { recursive: true });
12418
- writeFileSync7(
12419
- join12(dir, "src", "index.ts"),
12451
+ mkdirSync6(join13(dir, "src"), { recursive: true });
12452
+ writeFileSync8(
12453
+ join13(dir, "src", "index.ts"),
12420
12454
  `#!/usr/bin/env node
12421
12455
 
12422
12456
  console.log = (...args: unknown[]) => {
@@ -12457,8 +12491,8 @@ console.log('');
12457
12491
  console.log('Press Ctrl+C to stop.');
12458
12492
  `
12459
12493
  );
12460
- writeFileSync7(
12461
- join12(dir, ".mcp.json"),
12494
+ writeFileSync8(
12495
+ join13(dir, ".mcp.json"),
12462
12496
  JSON.stringify(
12463
12497
  {
12464
12498
  mcpServers: {
@@ -12475,13 +12509,13 @@ console.log('Press Ctrl+C to stop.');
12475
12509
  2
12476
12510
  ) + "\n"
12477
12511
  );
12478
- writeFileSync7(
12479
- join12(dir, ".env"),
12512
+ writeFileSync8(
12513
+ join13(dir, ".env"),
12480
12514
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12481
12515
  `
12482
12516
  );
12483
- writeFileSync7(
12484
- join12(dir, ".gitignore"),
12517
+ writeFileSync8(
12518
+ join13(dir, ".gitignore"),
12485
12519
  `node_modules/
12486
12520
  dist/
12487
12521
  *.solongate-backup
@@ -12500,7 +12534,7 @@ async function main3() {
12500
12534
  process.exit(1);
12501
12535
  }
12502
12536
  withSpinner(`Setting up ${opts.name}...`, () => {
12503
- mkdirSync5(dir, { recursive: true });
12537
+ mkdirSync6(dir, { recursive: true });
12504
12538
  createProject(dir, opts.name, opts.policy);
12505
12539
  });
12506
12540
  if (!opts.noInstall) {
@@ -12574,7 +12608,7 @@ var init_create = __esm({
12574
12608
 
12575
12609
  // src/pull-push.ts
12576
12610
  var pull_push_exports = {};
12577
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync10 } from "fs";
12611
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync10 } from "fs";
12578
12612
  import { resolve as resolve9 } from "path";
12579
12613
  function loadEnv() {
12580
12614
  if (process.env.SOLONGATE_API_KEY) return;
@@ -12769,7 +12803,7 @@ async function pull(apiKey, file, policyId) {
12769
12803
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
12770
12804
  const { id: _id, ...policyWithoutId } = policy;
12771
12805
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
12772
- writeFileSync8(file, json, "utf-8");
12806
+ writeFileSync9(file, json, "utf-8");
12773
12807
  log5("");
12774
12808
  log5(green2(" Saved to: ") + file);
12775
12809
  log5(` ${dim2("Name:")} ${policy.name}`);
package/dist/tui/index.js CHANGED
@@ -2214,6 +2214,9 @@ function RegexTest({ re }) {
2214
2214
  // src/tui/panels/Audit.tsx
2215
2215
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2216
2216
  import TextInput4 from "ink-text-input";
2217
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
2218
+ import { homedir as homedir5 } from "os";
2219
+ import { join as join5 } from "path";
2217
2220
  import { useState as useState6 } from "react";
2218
2221
  import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2219
2222
  var DECISIONS = [void 0, "DENY", "ALLOW"];
@@ -2266,8 +2269,10 @@ var AUDIT_HELP = [
2266
2269
  ["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
2267
2270
  ["t / n", "tool / agent filter (type, enter done)"],
2268
2271
  ["/", "free-text search"],
2269
- ["x", "delete the selected entry (press x twice)"],
2270
- ["X", "delete ALL logs of the current source (press X twice)"],
2272
+ ["x", "delete ONLY the selected entry (press x twice)"],
2273
+ ["X", "delete ALL matched logs of the source (press X twice)"],
2274
+ ["e", "export this page \u2192 ~/.solongate/audit-export-<src>.jsonl"],
2275
+ ["E", "export ALL matched rows (cloud: up to 10k)"],
2271
2276
  ["c", "clear every filter (incl. session)"]
2272
2277
  ]
2273
2278
  ],
@@ -2286,6 +2291,7 @@ var AUDIT_HELP = [
2286
2291
  [
2287
2292
  ["v", "switch logs \u2194 sessions"],
2288
2293
  ["s", "switch source cloud \u2194 local file"],
2294
+ ["space", "copy mode: freeze screen for mouse selection"],
2289
2295
  ["?", "this help \xB7 any key closes"],
2290
2296
  ["esc", "back to the menu"]
2291
2297
  ]
@@ -2318,13 +2324,14 @@ function AuditPanel({ active: active2, focused }) {
2318
2324
  const [confirm, setConfirm] = useState6(null);
2319
2325
  const [msg, setMsg] = useState6(null);
2320
2326
  const [showHelp, setShowHelp] = useState6(false);
2327
+ const [frozen, setFrozen] = useState6(false);
2321
2328
  const toTop = () => setSel(0);
2322
2329
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
2323
2330
  const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
2324
2331
  usePoll(() => {
2325
2332
  statsQ.reloadQuiet();
2326
2333
  tsQ.reloadQuiet();
2327
- }, 15e3, active2 && source === "cloud");
2334
+ }, 15e3, active2 && source === "cloud" && !frozen);
2328
2335
  const query = {
2329
2336
  filter: DECISIONS[di],
2330
2337
  signal: SIGNALS[gi],
@@ -2339,29 +2346,28 @@ function AuditPanel({ active: active2, focused }) {
2339
2346
  () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
2340
2347
  [source, di, gi, tool, agent, search, sessFilter, page]
2341
2348
  );
2342
- usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
2349
+ usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0 && !frozen);
2343
2350
  const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
2344
- usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
2351
+ usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0 && !frozen);
2352
+ const q = search.trim().toLowerCase();
2353
+ const localFiltered = (localQ.data ?? []).filter((r) => {
2354
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
2355
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
2356
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
2357
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
2358
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
2359
+ if (sessFilter && r.session !== sessFilter) return false;
2360
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
2361
+ return true;
2362
+ });
2345
2363
  let pageRows = [];
2346
2364
  let total = 0;
2347
2365
  if (source === "cloud") {
2348
2366
  pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
2349
2367
  total = cloudQ.data?.total ?? 0;
2350
2368
  } else {
2351
- const all = localQ.data ?? [];
2352
- const q = search.trim().toLowerCase();
2353
- const filtered = all.filter((r) => {
2354
- if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
2355
- if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
2356
- if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
2357
- if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
2358
- if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
2359
- if (sessFilter && r.session !== sessFilter) return false;
2360
- if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
2361
- return true;
2362
- });
2363
- total = filtered.length;
2364
- pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
2369
+ total = localFiltered.length;
2370
+ pageRows = localFiltered.slice(page * PAGE, page * PAGE + PAGE);
2365
2371
  }
2366
2372
  const pages = Math.max(1, Math.ceil(total / PAGE));
2367
2373
  const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
@@ -2369,7 +2375,7 @@ function AuditPanel({ active: active2, focused }) {
2369
2375
  () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
2370
2376
  [source]
2371
2377
  );
2372
- usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
2378
+ usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions" && !frozen);
2373
2379
  let sessions = [];
2374
2380
  if (source === "cloud") {
2375
2381
  sessions = (agentsQ.data?.agents ?? []).map((a) => ({
@@ -2428,8 +2434,30 @@ function AuditPanel({ active: active2, focused }) {
2428
2434
  toTop();
2429
2435
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
2430
2436
  };
2437
+ const doExport = (kind) => {
2438
+ setMsg({ text: "exporting\u2026", level: "ok" });
2439
+ const run = async () => {
2440
+ const dir = join5(homedir5(), ".solongate");
2441
+ const file = join5(dir, `audit-export-${source}.jsonl`);
2442
+ let rows2;
2443
+ if (kind === "page") rows2 = pageRows;
2444
+ else if (source === "cloud") {
2445
+ const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
2446
+ rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
2447
+ } else rows2 = localFiltered;
2448
+ mkdirSync2(dir, { recursive: true });
2449
+ writeFileSync3(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
2450
+ return { n: rows2.length, file };
2451
+ };
2452
+ run().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
2453
+ };
2431
2454
  useInput5(
2432
2455
  (input, key) => {
2456
+ if (input === " ") {
2457
+ setFrozen((f) => !f);
2458
+ return;
2459
+ }
2460
+ if (frozen) return;
2433
2461
  if (showHelp) {
2434
2462
  setShowHelp(false);
2435
2463
  return;
@@ -2516,7 +2544,7 @@ function AuditPanel({ active: active2, focused }) {
2516
2544
  if (!current) return;
2517
2545
  if (confirm?.kind !== "one" || confirm.key !== current.id) {
2518
2546
  setConfirm({ kind: "one", key: current.id });
2519
- setMsg({ text: `delete ${current.decision} ${current.tool} (${ago(current.at)} ago)? press x again`, level: "bad" });
2547
+ setMsg({ text: `x = delete ONLY the selected entry: ${current.decision} ${current.tool} (${ago(current.at)} ago) \u2014 press x again`, level: "bad" });
2520
2548
  return;
2521
2549
  }
2522
2550
  setConfirm(null);
@@ -2524,12 +2552,14 @@ function AuditPanel({ active: active2, focused }) {
2524
2552
  } else if (input === "X") {
2525
2553
  if (confirm?.kind !== "all") {
2526
2554
  setConfirm({ kind: "all", key: "all" });
2527
- setMsg({ text: `DELETE ALL ${source} logs (${total} entries)? press X again`, level: "bad" });
2555
+ setMsg({ text: `\u26A0 X = delete ALL ${source} logs \u2014 every one of the ${total} matched entries! press X again`, level: "bad" });
2528
2556
  return;
2529
2557
  }
2530
2558
  setConfirm(null);
2531
2559
  doDelete("all");
2532
- } else if (input === "t") setEditing("tool");
2560
+ } else if (input === "e") doExport("page");
2561
+ else if (input === "E") doExport("all");
2562
+ else if (input === "t") setEditing("tool");
2533
2563
  else if (input === "n") setEditing("agent");
2534
2564
  else if (input === "/") setEditing("search");
2535
2565
  else if (input === "c") {
@@ -2580,6 +2610,7 @@ function AuditPanel({ active: active2, focused }) {
2580
2610
  LOCAL_LOG
2581
2611
  ] })
2582
2612
  ] });
2613
+ const copyBanner = frozen ? /* @__PURE__ */ jsx6(Text6, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, wrap: "truncate", children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : null;
2583
2614
  const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
2584
2615
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
2585
2616
  /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
@@ -2605,11 +2636,12 @@ function AuditPanel({ active: active2, focused }) {
2605
2636
  const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
2606
2637
  const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
2607
2638
  const reasonShown = reasonLines.slice(0, 4);
2608
- const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
2639
+ const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1 - (frozen ? 1 : 0));
2609
2640
  const maxScroll = Math.max(0, argLines.length - argBudget);
2610
2641
  const off = Math.min(detailScroll, maxScroll);
2611
2642
  const argWin = argLines.slice(off, off + argBudget);
2612
2643
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2644
+ copyBanner,
2613
2645
  /* @__PURE__ */ jsxs6(Box6, { children: [
2614
2646
  /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2615
2647
  /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
@@ -2668,8 +2700,9 @@ function AuditPanel({ active: active2, focused }) {
2668
2700
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
2669
2701
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
2670
2702
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
2671
- return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2703
+ return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2672
2704
  strip,
2705
+ copyBanner,
2673
2706
  /* @__PURE__ */ jsxs6(Box6, { children: [
2674
2707
  srcChip,
2675
2708
  /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
@@ -2731,15 +2764,16 @@ function AuditPanel({ active: active2, focused }) {
2731
2764
  ] }) : null
2732
2765
  ] }) });
2733
2766
  }
2734
- const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0);
2767
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0) + (frozen ? 1 : 0);
2735
2768
  const listRows = Math.max(4, rows - headerRows);
2736
2769
  const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
2737
2770
  const maxStart = Math.max(0, pageRows.length - listRows);
2738
2771
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2739
2772
  const windowed = pageRows.slice(start, start + listRows);
2740
2773
  const reasonW = Math.min(60, Math.max(12, cols - 67));
2741
- return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2774
+ return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2742
2775
  strip,
2776
+ copyBanner,
2743
2777
  /* @__PURE__ */ jsxs6(Box6, { children: [
2744
2778
  srcChip,
2745
2779
  /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.27",
3
+ "version": "0.81.29",
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": {