@solongate/proxy 0.81.35 → 0.81.37

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.
@@ -3,6 +3,17 @@ export interface Credentials {
3
3
  apiKey: string;
4
4
  apiUrl: string;
5
5
  }
6
+ export interface SavedAccount {
7
+ apiKey: string;
8
+ apiUrl: string;
9
+ project?: string;
10
+ user?: string;
11
+ addedAt?: number;
12
+ }
13
+ export declare function listAccounts(): SavedAccount[];
14
+ /** Upsert an account (dedupe by apiKey). Called from `login`. */
15
+ export declare function saveAccount(acc: SavedAccount): void;
16
+ export declare function setViewCredentials(creds: Credentials | null): void;
6
17
  /**
7
18
  * Thrown for any non-2xx response. `code`/`message` come from the API's
8
19
  * `{ error: { code, message } }` envelope when present; `status` is the HTTP
@@ -5,10 +5,11 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/api-client/client.ts
8
- import { readFileSync, existsSync } from "fs";
8
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
9
9
  import { resolve, join } from "path";
10
10
  import { homedir } from "os";
11
11
  var DEFAULT_API_URL = "https://api.solongate.com";
12
+ var viewOverride = null;
12
13
  var ApiError = class extends Error {
13
14
  status;
14
15
  code;
@@ -62,6 +63,7 @@ function isAuthenticated() {
62
63
  }
63
64
  }
64
65
  function resolveCredentials(apiUrlOverride) {
66
+ if (viewOverride && !apiUrlOverride) return viewOverride;
65
67
  if (cached && !apiUrlOverride) return cached;
66
68
  const file = loginCredentialFile();
67
69
  const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
package/dist/index.js CHANGED
@@ -6561,12 +6561,49 @@ __export(client_exports, {
6561
6561
  DEFAULT_API_URL: () => DEFAULT_API_URL2,
6562
6562
  NotAuthenticatedError: () => NotAuthenticatedError,
6563
6563
  isAuthenticated: () => isAuthenticated,
6564
+ listAccounts: () => listAccounts,
6564
6565
  request: () => request,
6565
- resolveCredentials: () => resolveCredentials
6566
+ resolveCredentials: () => resolveCredentials,
6567
+ saveAccount: () => saveAccount,
6568
+ setViewCredentials: () => setViewCredentials
6566
6569
  });
6567
- import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
6570
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
6568
6571
  import { resolve as resolve3, join as join4 } from "path";
6569
6572
  import { homedir as homedir2 } from "os";
6573
+ function listAccounts() {
6574
+ let list6 = [];
6575
+ try {
6576
+ const raw = JSON.parse(readFileSync4(accountsFile(), "utf-8"));
6577
+ if (Array.isArray(raw)) list6 = raw.filter((a) => a && typeof a.apiKey === "string");
6578
+ } catch {
6579
+ }
6580
+ const active2 = loginCredentialFile();
6581
+ if (active2.apiKey && !list6.some((a) => a.apiKey === active2.apiKey)) {
6582
+ list6.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL2 });
6583
+ }
6584
+ return list6;
6585
+ }
6586
+ function saveAccount(acc) {
6587
+ try {
6588
+ const list6 = (() => {
6589
+ try {
6590
+ const raw = JSON.parse(readFileSync4(accountsFile(), "utf-8"));
6591
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
6592
+ } catch {
6593
+ return [];
6594
+ }
6595
+ })();
6596
+ const next = list6.filter((a) => a.apiKey !== acc.apiKey);
6597
+ next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
6598
+ mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
6599
+ writeFileSync3(accountsFile(), JSON.stringify(next, null, 2));
6600
+ } catch {
6601
+ }
6602
+ }
6603
+ function setViewCredentials(creds) {
6604
+ viewOverride = creds;
6605
+ cached = null;
6606
+ }
6570
6607
  function loginCredentialFile() {
6571
6608
  try {
6572
6609
  const p = join4(homedir2(), ".solongate", "cloud-guard.json");
@@ -6603,6 +6640,7 @@ function isAuthenticated() {
6603
6640
  }
6604
6641
  }
6605
6642
  function resolveCredentials(apiUrlOverride) {
6643
+ if (viewOverride && !apiUrlOverride) return viewOverride;
6606
6644
  if (cached && !apiUrlOverride) return cached;
6607
6645
  const file = loginCredentialFile();
6608
6646
  const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
@@ -6677,11 +6715,13 @@ async function request(method, path, opts = {}) {
6677
6715
  }
6678
6716
  return json;
6679
6717
  }
6680
- var DEFAULT_API_URL2, ApiError, NotAuthenticatedError, cached;
6718
+ var DEFAULT_API_URL2, accountsFile, viewOverride, ApiError, NotAuthenticatedError, cached;
6681
6719
  var init_client = __esm({
6682
6720
  "src/api-client/client.ts"() {
6683
6721
  "use strict";
6684
6722
  DEFAULT_API_URL2 = "https://api.solongate.com";
6723
+ accountsFile = () => join4(homedir2(), ".solongate", "accounts.json");
6724
+ viewOverride = null;
6685
6725
  ApiError = class extends Error {
6686
6726
  status;
6687
6727
  code;
@@ -6781,8 +6821,8 @@ var init_theme = __esm({
6781
6821
  init_config2();
6782
6822
  accent = loadConfig().accent;
6783
6823
  theme = {
6784
- accent: accent || "cyan",
6785
- accentBright: accent || "#5a8ce6",
6824
+ accent: accent || "white",
6825
+ accentBright: accent || "white",
6786
6826
  ok: "green",
6787
6827
  warn: "yellow",
6788
6828
  bad: "red",
@@ -6836,7 +6876,7 @@ var init_components = __esm({
6836
6876
  });
6837
6877
 
6838
6878
  // src/tui/local-log.ts
6839
- import { closeSync, openSync, readFileSync as readFileSync6, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
6879
+ import { closeSync, openSync, readFileSync as readFileSync6, readSync, statSync, writeFileSync as writeFileSync4 } from "fs";
6840
6880
  import { homedir as homedir4 } from "os";
6841
6881
  import { join as join6 } from "path";
6842
6882
  function tailLines(file, maxBytes = 131072) {
@@ -6872,7 +6912,7 @@ function deleteLocalEntry(at, tool, session) {
6872
6912
  }
6873
6913
  return true;
6874
6914
  });
6875
- if (removed) writeFileSync3(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
6915
+ if (removed) writeFileSync4(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
6876
6916
  return removed;
6877
6917
  } catch {
6878
6918
  return 0;
@@ -6881,7 +6921,7 @@ function deleteLocalEntry(at, tool, session) {
6881
6921
  function clearLocalLog() {
6882
6922
  try {
6883
6923
  const n = readFileSync6(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6884
- writeFileSync3(LOCAL_LOG, "");
6924
+ writeFileSync4(LOCAL_LOG, "");
6885
6925
  return n;
6886
6926
  } catch {
6887
6927
  return 0;
@@ -7310,7 +7350,7 @@ var init_hooks = __esm({
7310
7350
  // src/tui/panels/Live.tsx
7311
7351
  import { Box as Box2, Text as Text2, useInput } from "ink";
7312
7352
  import TextInput from "ink-text-input";
7313
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
7353
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
7314
7354
  import { homedir as homedir5 } from "os";
7315
7355
  import { join as join7 } from "path";
7316
7356
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
@@ -7401,7 +7441,7 @@ function StreamLine({ e, loc, selected }) {
7401
7441
  hhmmss(e.at),
7402
7442
  " "
7403
7443
  ] }),
7404
- /* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "#4f6db8", children: loc ? "LOC" : "CLD" }),
7444
+ /* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
7405
7445
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
7406
7446
  /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7407
7447
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
@@ -7861,8 +7901,8 @@ function LivePanel({ active: active2 }) {
7861
7901
  else if (input === "e") {
7862
7902
  const file = join7(homedir5(), ".solongate", "live-export.jsonl");
7863
7903
  try {
7864
- mkdirSync3(join7(homedir5(), ".solongate"), { recursive: true });
7865
- writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7904
+ mkdirSync4(join7(homedir5(), ".solongate"), { recursive: true });
7905
+ writeFileSync5(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7866
7906
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
7867
7907
  } catch (err2) {
7868
7908
  setActionMsg({ text: "\u2717 export failed: " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 6e3 });
@@ -7908,9 +7948,9 @@ function LivePanel({ active: active2 }) {
7908
7948
  ] });
7909
7949
  }
7910
7950
  const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7911
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
7912
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7913
- frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
7951
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "white", bold: true, children: " SOLONGATE LIVE " }),
7952
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7953
+ frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2713 clean " })
7914
7954
  ] });
7915
7955
  if (showHelp) {
7916
7956
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
@@ -7937,10 +7977,10 @@ function LivePanel({ active: active2 }) {
7937
7977
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7938
7978
  titleBar,
7939
7979
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7940
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: " ENTRY " }),
7980
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " ENTRY " }),
7941
7981
  /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
7942
7982
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, bold: true, children: " " + e.tool }),
7943
- /* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "#4f6db8", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
7983
+ /* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "white", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
7944
7984
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: " DLP!" }) : null,
7945
7985
  e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: " BURST" }) : null,
7946
7986
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
@@ -7966,9 +8006,9 @@ function LivePanel({ active: active2 }) {
7966
8006
  /* @__PURE__ */ jsx2(PaneTitle, { label: "FULL CONTENT", extra: `${lines.length} lines${maxScroll2 ? ` \xB7 \u25BC${maxScroll2 - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
7967
8007
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: l || " " }, off + i)) }),
7968
8008
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7969
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " ENTRY " }),
7970
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${e.id} ` }),
7971
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
8009
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " ENTRY " }),
8010
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
8011
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
7972
8012
  ] })
7973
8013
  ] });
7974
8014
  }
@@ -7998,12 +8038,12 @@ function LivePanel({ active: active2 }) {
7998
8038
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7999
8039
  titleBar,
8000
8040
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
8001
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
8041
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
8002
8042
  (() => {
8003
8043
  const st = STATUS_STYLE[sessStatus(detail.lastAt, nowMs)];
8004
8044
  return /* @__PURE__ */ jsx2(Text2, { color: st.color, bold: true, children: ` ${st.dot} ${st.label}` });
8005
8045
  })(),
8006
- /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
8046
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "white", children: " " + detail.source.toUpperCase() }),
8007
8047
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
8008
8048
  detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
8009
8049
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
@@ -8039,9 +8079,9 @@ function LivePanel({ active: active2 }) {
8039
8079
  tl.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: start + i === dSel }, e.id))
8040
8080
  ] }),
8041
8081
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
8042
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
8043
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
8044
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full entry \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
8082
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " SESSION " }),
8083
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` ${detail.id} ` }),
8084
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2191\u2193 select \xB7 enter full entry \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
8045
8085
  ] })
8046
8086
  ] });
8047
8087
  }
@@ -8087,7 +8127,7 @@ function LivePanel({ active: active2 }) {
8087
8127
  ] }),
8088
8128
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
8089
8129
  /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `now ${latNow}ms \xB7 med ${latMed}ms \xB7 amber >${Math.round(latHotAt)}ms`, width: rightW }),
8090
- /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0", hotColor: "#ffb454" })
8130
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "white", hotColor: "#ffb454" })
8091
8131
  ] })
8092
8132
  ] }),
8093
8133
  /* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
@@ -8171,9 +8211,9 @@ function LivePanel({ active: active2 }) {
8171
8211
  windowed.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
8172
8212
  ] }),
8173
8213
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
8174
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
8175
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
8176
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 space copy \xB7 ? all keys \xB7 esc menu \xB7 q quit ` })
8214
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " LIVE " }),
8215
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
8216
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: ` \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 space copy \xB7 ? all keys \xB7 esc menu \xB7 q quit ` })
8177
8217
  ] })
8178
8218
  ] });
8179
8219
  }
@@ -8880,7 +8920,7 @@ var init_Dlp = __esm({
8880
8920
  // src/tui/panels/Audit.tsx
8881
8921
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
8882
8922
  import TextInput4 from "ink-text-input";
8883
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
8923
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
8884
8924
  import { homedir as homedir6 } from "os";
8885
8925
  import { join as join8 } from "path";
8886
8926
  import { useState as useState6 } from "react";
@@ -9044,8 +9084,8 @@ function AuditPanel({ active: active2, focused }) {
9044
9084
  const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
9045
9085
  rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
9046
9086
  } else rows2 = localFiltered;
9047
- mkdirSync4(dir, { recursive: true });
9048
- writeFileSync5(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
9087
+ mkdirSync5(dir, { recursive: true });
9088
+ writeFileSync6(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
9049
9089
  return { n: rows2.length, file };
9050
9090
  };
9051
9091
  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" }));
@@ -9781,6 +9821,17 @@ function App() {
9781
9821
  const [section, setSection] = useState8(0);
9782
9822
  const [focus, setFocus] = useState8("nav");
9783
9823
  const [help, setHelp] = useState8(false);
9824
+ const [accounts] = useState8(() => listAccounts());
9825
+ const [acctIdx, setAcctIdx] = useState8(0);
9826
+ const cur = accounts[acctIdx];
9827
+ const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "\u2014";
9828
+ const switchAccount = () => {
9829
+ if (accounts.length < 2) return;
9830
+ const next = (acctIdx + 1) % accounts.length;
9831
+ const a = accounts[next];
9832
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
9833
+ setAcctIdx(next);
9834
+ };
9784
9835
  useInput7((input, key) => {
9785
9836
  if (help) {
9786
9837
  setHelp(false);
@@ -9794,6 +9845,7 @@ function App() {
9794
9845
  if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
9795
9846
  else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
9796
9847
  else if (key.rightArrow || key.return || key.tab) setFocus("panel");
9848
+ else if (input === "a") switchAccount();
9797
9849
  else if (input === "q") exit();
9798
9850
  } else {
9799
9851
  if (key.escape || key.tab) setFocus("nav");
@@ -9804,16 +9856,21 @@ function App() {
9804
9856
  const current = SECTIONS[section];
9805
9857
  if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
9806
9858
  if (current.label === "Live" && focus === "panel") {
9807
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
9859
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, acctIdx) });
9808
9860
  }
9809
9861
  const Panel = current.Panel;
9810
9862
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9811
9863
  /* @__PURE__ */ jsx8(Banner, { cols }),
9864
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9865
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
9866
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: acctLabel }),
9867
+ accounts.length > 1 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch)` }) : null
9868
+ ] }),
9812
9869
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
9813
9870
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
9814
9871
  /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
9815
- ] }),
9816
- /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
9872
+ ] }, acctIdx),
9873
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
9817
9874
  ] });
9818
9875
  }
9819
9876
  function HelpOverlay({ cols, rows }) {
@@ -9843,6 +9900,7 @@ var init_App = __esm({
9843
9900
  init_Audit();
9844
9901
  init_Settings();
9845
9902
  init_hooks();
9903
+ init_client();
9846
9904
  SECTIONS = [
9847
9905
  { label: "Live", Panel: LiveHint },
9848
9906
  { label: "Policies", Panel: PoliciesPanel },
@@ -9858,7 +9916,8 @@ var init_App = __esm({
9858
9916
  ["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"]]],
9859
9917
  ["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"]]],
9860
9918
  ["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"]]],
9861
- ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
9919
+ ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
9920
+ ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
9862
9921
  ];
9863
9922
  }
9864
9923
  });
@@ -11063,7 +11122,7 @@ __export(global_install_exports, {
11063
11122
  runGlobalRestore: () => runGlobalRestore,
11064
11123
  unlockProtected: () => unlockProtected
11065
11124
  });
11066
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
11125
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync6, mkdirSync as mkdirSync6 } from "fs";
11067
11126
  import { resolve as resolve4, join as join11, dirname } from "path";
11068
11127
  import { homedir as homedir9 } from "os";
11069
11128
  import { fileURLToPath } from "url";
@@ -11176,13 +11235,13 @@ function runGlobalRestore() {
11176
11235
  unlockProtected();
11177
11236
  removeClaudeShim();
11178
11237
  if (existsSync6(p.backupPath)) {
11179
- writeFileSync6(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11238
+ writeFileSync7(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11180
11239
  console.log(` Restored ${p.settingsPath} from backup.`);
11181
11240
  } else if (existsSync6(p.settingsPath)) {
11182
11241
  try {
11183
11242
  const s = JSON.parse(readFileSync8(p.settingsPath, "utf-8"));
11184
11243
  delete s.hooks;
11185
- writeFileSync6(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11244
+ writeFileSync7(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11186
11245
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
11187
11246
  } catch {
11188
11247
  }
@@ -11226,8 +11285,8 @@ function writeShimBlock(file, block2) {
11226
11285
  if (content.length && !content.endsWith("\n")) content += "\n";
11227
11286
  content += block2 + "\n";
11228
11287
  }
11229
- mkdirSync5(dirname(file), { recursive: true });
11230
- writeFileSync6(file, content);
11288
+ mkdirSync6(dirname(file), { recursive: true });
11289
+ writeFileSync7(file, content);
11231
11290
  }
11232
11291
  function installClaudeShim(shieldPath) {
11233
11292
  const real = resolveRealClaude();
@@ -11278,22 +11337,22 @@ async function runGlobalInstall(opts = {}) {
11278
11337
  process.exit(1);
11279
11338
  }
11280
11339
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
11281
- mkdirSync5(p.hooksDir, { recursive: true });
11282
- mkdirSync5(p.claudeDir, { recursive: true });
11340
+ mkdirSync6(p.hooksDir, { recursive: true });
11341
+ mkdirSync6(p.claudeDir, { recursive: true });
11283
11342
  unlockProtected();
11284
- writeFileSync6(join11(p.hooksDir, "guard.mjs"), readGuard());
11285
- writeFileSync6(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11286
- writeFileSync6(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11287
- writeFileSync6(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11343
+ writeFileSync7(join11(p.hooksDir, "guard.mjs"), readGuard());
11344
+ writeFileSync7(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11345
+ writeFileSync7(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11346
+ writeFileSync7(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11288
11347
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11289
11348
  installClaudeShim(join11(p.hooksDir, "shield.mjs"));
11290
- writeFileSync6(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11349
+ writeFileSync7(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11291
11350
  console.log(` Wrote ${p.configPath}`);
11292
11351
  let existing = {};
11293
11352
  if (existsSync6(p.settingsPath)) {
11294
11353
  const raw = readFileSync8(p.settingsPath, "utf-8");
11295
11354
  if (!existsSync6(p.backupPath)) {
11296
- writeFileSync6(p.backupPath, raw);
11355
+ writeFileSync7(p.backupPath, raw);
11297
11356
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
11298
11357
  }
11299
11358
  try {
@@ -11316,7 +11375,7 @@ async function runGlobalInstall(opts = {}) {
11316
11375
  Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
11317
11376
  }
11318
11377
  };
11319
- writeFileSync6(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11378
+ writeFileSync7(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11320
11379
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
11321
11380
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
11322
11381
  lockProtected();
@@ -11434,6 +11493,7 @@ async function main() {
11434
11493
  if (data?.project?.name) {
11435
11494
  console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
11436
11495
  }
11496
+ saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email });
11437
11497
  break;
11438
11498
  }
11439
11499
  if (data?.status === "expired" || data?.status === "not_found") {
@@ -11478,6 +11538,7 @@ var init_login = __esm({
11478
11538
  "use strict";
11479
11539
  init_cli_utils();
11480
11540
  init_global_install();
11541
+ init_client();
11481
11542
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
11482
11543
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11483
11544
  main().catch((err2) => {
@@ -11992,7 +12053,7 @@ var init_logs_server = __esm({
11992
12053
 
11993
12054
  // src/inject.ts
11994
12055
  var inject_exports = {};
11995
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync8, copyFileSync } from "fs";
12056
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync8, copyFileSync } from "fs";
11996
12057
  import { resolve as resolve7 } from "path";
11997
12058
  import { execSync } from "child_process";
11998
12059
  function parseInjectArgs(argv) {
@@ -12300,7 +12361,7 @@ async function main2() {
12300
12361
  log3("");
12301
12362
  log3(` Backup: ${backupPath}`);
12302
12363
  }
12303
- writeFileSync7(entryFile, result.modified);
12364
+ writeFileSync8(entryFile, result.modified);
12304
12365
  log3("");
12305
12366
  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");
12306
12367
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -12329,7 +12390,7 @@ var init_inject = __esm({
12329
12390
 
12330
12391
  // src/create.ts
12331
12392
  var create_exports = {};
12332
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, existsSync as existsSync9 } from "fs";
12393
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12333
12394
  import { resolve as resolve8, join as join13 } from "path";
12334
12395
  import { execSync as execSync2 } from "child_process";
12335
12396
  function withSpinner(message, fn) {
@@ -12409,7 +12470,7 @@ EXAMPLES
12409
12470
  `);
12410
12471
  }
12411
12472
  function createProject(dir, name, _policy) {
12412
- writeFileSync8(
12473
+ writeFileSync9(
12413
12474
  join13(dir, "package.json"),
12414
12475
  JSON.stringify(
12415
12476
  {
@@ -12439,7 +12500,7 @@ function createProject(dir, name, _policy) {
12439
12500
  2
12440
12501
  ) + "\n"
12441
12502
  );
12442
- writeFileSync8(
12503
+ writeFileSync9(
12443
12504
  join13(dir, "tsconfig.json"),
12444
12505
  JSON.stringify(
12445
12506
  {
@@ -12460,8 +12521,8 @@ function createProject(dir, name, _policy) {
12460
12521
  2
12461
12522
  ) + "\n"
12462
12523
  );
12463
- mkdirSync6(join13(dir, "src"), { recursive: true });
12464
- writeFileSync8(
12524
+ mkdirSync7(join13(dir, "src"), { recursive: true });
12525
+ writeFileSync9(
12465
12526
  join13(dir, "src", "index.ts"),
12466
12527
  `#!/usr/bin/env node
12467
12528
 
@@ -12503,7 +12564,7 @@ console.log('');
12503
12564
  console.log('Press Ctrl+C to stop.');
12504
12565
  `
12505
12566
  );
12506
- writeFileSync8(
12567
+ writeFileSync9(
12507
12568
  join13(dir, ".mcp.json"),
12508
12569
  JSON.stringify(
12509
12570
  {
@@ -12521,12 +12582,12 @@ console.log('Press Ctrl+C to stop.');
12521
12582
  2
12522
12583
  ) + "\n"
12523
12584
  );
12524
- writeFileSync8(
12585
+ writeFileSync9(
12525
12586
  join13(dir, ".env"),
12526
12587
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12527
12588
  `
12528
12589
  );
12529
- writeFileSync8(
12590
+ writeFileSync9(
12530
12591
  join13(dir, ".gitignore"),
12531
12592
  `node_modules/
12532
12593
  dist/
@@ -12546,7 +12607,7 @@ async function main3() {
12546
12607
  process.exit(1);
12547
12608
  }
12548
12609
  withSpinner(`Setting up ${opts.name}...`, () => {
12549
- mkdirSync6(dir, { recursive: true });
12610
+ mkdirSync7(dir, { recursive: true });
12550
12611
  createProject(dir, opts.name, opts.policy);
12551
12612
  });
12552
12613
  if (!opts.noInstall) {
@@ -12620,7 +12681,7 @@ var init_create = __esm({
12620
12681
 
12621
12682
  // src/pull-push.ts
12622
12683
  var pull_push_exports = {};
12623
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync10 } from "fs";
12684
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, existsSync as existsSync10 } from "fs";
12624
12685
  import { resolve as resolve9 } from "path";
12625
12686
  function loadEnv() {
12626
12687
  if (process.env.SOLONGATE_API_KEY) return;
@@ -12815,7 +12876,7 @@ async function pull(apiKey, file, policyId) {
12815
12876
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
12816
12877
  const { id: _id, ...policyWithoutId } = policy;
12817
12878
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
12818
- writeFileSync9(file, json, "utf-8");
12879
+ writeFileSync10(file, json, "utf-8");
12819
12880
  log5("");
12820
12881
  log5(green2(" Saved to: ") + file);
12821
12882
  log5(` ${dim2("Name:")} ${policy.name}`);
package/dist/login.js CHANGED
@@ -263,6 +263,29 @@ async function runGlobalInstall(opts = {}) {
263
263
  }
264
264
  }
265
265
 
266
+ // src/api-client/client.ts
267
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
268
+ import { resolve as resolve2, join as join2 } from "path";
269
+ import { homedir as homedir2 } from "os";
270
+ var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
271
+ function saveAccount(acc) {
272
+ try {
273
+ const list = (() => {
274
+ try {
275
+ const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
276
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
277
+ } catch {
278
+ return [];
279
+ }
280
+ })();
281
+ const next = list.filter((a) => a.apiKey !== acc.apiKey);
282
+ next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
283
+ mkdirSync2(join2(homedir2(), ".solongate"), { recursive: true });
284
+ writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
285
+ } catch {
286
+ }
287
+ }
288
+
266
289
  // src/login.ts
267
290
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
268
291
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -360,6 +383,7 @@ async function main() {
360
383
  if (data?.project?.name) {
361
384
  console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
362
385
  }
386
+ saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email });
363
387
  break;
364
388
  }
365
389
  if (data?.status === "expired" || data?.status === "not_found") {
package/dist/tui/index.js CHANGED
@@ -69,8 +69,8 @@ function loadConfig() {
69
69
  // src/tui/theme.ts
70
70
  var accent = loadConfig().accent;
71
71
  var theme = {
72
- accent: accent || "cyan",
73
- accentBright: accent || "#5a8ce6",
72
+ accent: accent || "white",
73
+ accentBright: accent || "white",
74
74
  ok: "green",
75
75
  warn: "yellow",
76
76
  bad: "red",
@@ -160,7 +160,7 @@ function KeyHints({ hints }) {
160
160
  // src/tui/panels/Live.tsx
161
161
  import { Box as Box2, Text as Text2, useInput } from "ink";
162
162
  import TextInput from "ink-text-input";
163
- import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
163
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
164
164
  import { homedir as homedir4 } from "os";
165
165
  import { join as join4 } from "path";
166
166
 
@@ -245,10 +245,29 @@ function parseLocalLines(lines) {
245
245
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
246
246
 
247
247
  // src/api-client/client.ts
248
- import { readFileSync as readFileSync3, existsSync } from "fs";
248
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
249
249
  import { resolve, join as join3 } from "path";
250
250
  import { homedir as homedir3 } from "os";
251
251
  var DEFAULT_API_URL = "https://api.solongate.com";
252
+ var accountsFile = () => join3(homedir3(), ".solongate", "accounts.json");
253
+ function listAccounts() {
254
+ let list5 = [];
255
+ try {
256
+ const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
257
+ if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
258
+ } catch {
259
+ }
260
+ const active2 = loginCredentialFile();
261
+ if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
262
+ list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
263
+ }
264
+ return list5;
265
+ }
266
+ var viewOverride = null;
267
+ function setViewCredentials(creds) {
268
+ viewOverride = creds;
269
+ cached2 = null;
270
+ }
252
271
  var ApiError = class extends Error {
253
272
  status;
254
273
  code;
@@ -302,6 +321,7 @@ function isAuthenticated() {
302
321
  }
303
322
  }
304
323
  function resolveCredentials(apiUrlOverride) {
324
+ if (viewOverride && !apiUrlOverride) return viewOverride;
305
325
  if (cached2 && !apiUrlOverride) return cached2;
306
326
  const file = loginCredentialFile();
307
327
  const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
@@ -794,7 +814,7 @@ function StreamLine({ e, loc, selected }) {
794
814
  hhmmss(e.at),
795
815
  " "
796
816
  ] }),
797
- /* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "#4f6db8", children: loc ? "LOC" : "CLD" }),
817
+ /* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
798
818
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
799
819
  /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
800
820
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
@@ -1300,8 +1320,8 @@ function LivePanel({ active: active2 }) {
1300
1320
  else if (input === "e") {
1301
1321
  const file = join4(homedir4(), ".solongate", "live-export.jsonl");
1302
1322
  try {
1303
- mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
1304
- writeFileSync2(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1323
+ mkdirSync2(join4(homedir4(), ".solongate"), { recursive: true });
1324
+ writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1305
1325
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1306
1326
  } catch (err) {
1307
1327
  setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
@@ -1347,9 +1367,9 @@ function LivePanel({ active: active2 }) {
1347
1367
  ] });
1348
1368
  }
1349
1369
  const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1350
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
1351
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
1352
- frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
1370
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "white", bold: true, children: " SOLONGATE LIVE " }),
1371
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
1372
+ frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2713 clean " })
1353
1373
  ] });
1354
1374
  if (showHelp) {
1355
1375
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
@@ -1376,10 +1396,10 @@ function LivePanel({ active: active2 }) {
1376
1396
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
1377
1397
  titleBar,
1378
1398
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1379
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: " ENTRY " }),
1399
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " ENTRY " }),
1380
1400
  /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
1381
1401
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, bold: true, children: " " + e.tool }),
1382
- /* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "#4f6db8", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
1402
+ /* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "white", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
1383
1403
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: " DLP!" }) : null,
1384
1404
  e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: " BURST" }) : null,
1385
1405
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
@@ -1405,9 +1425,9 @@ function LivePanel({ active: active2 }) {
1405
1425
  /* @__PURE__ */ jsx2(PaneTitle, { label: "FULL CONTENT", extra: `${lines.length} lines${maxScroll2 ? ` \xB7 \u25BC${maxScroll2 - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
1406
1426
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: l || " " }, off + i)) }),
1407
1427
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1408
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " ENTRY " }),
1409
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${e.id} ` }),
1410
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
1428
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " ENTRY " }),
1429
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
1430
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
1411
1431
  ] })
1412
1432
  ] });
1413
1433
  }
@@ -1437,12 +1457,12 @@ function LivePanel({ active: active2 }) {
1437
1457
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
1438
1458
  titleBar,
1439
1459
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1440
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
1460
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
1441
1461
  (() => {
1442
1462
  const st = STATUS_STYLE[sessStatus(detail.lastAt, nowMs)];
1443
1463
  return /* @__PURE__ */ jsx2(Text2, { color: st.color, bold: true, children: ` ${st.dot} ${st.label}` });
1444
1464
  })(),
1445
- /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
1465
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "white", children: " " + detail.source.toUpperCase() }),
1446
1466
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
1447
1467
  detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
1448
1468
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
@@ -1478,9 +1498,9 @@ function LivePanel({ active: active2 }) {
1478
1498
  tl.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: start + i === dSel }, e.id))
1479
1499
  ] }),
1480
1500
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1481
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
1482
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
1483
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full entry \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
1501
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " SESSION " }),
1502
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` ${detail.id} ` }),
1503
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: " \u2191\u2193 select \xB7 enter full entry \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
1484
1504
  ] })
1485
1505
  ] });
1486
1506
  }
@@ -1526,7 +1546,7 @@ function LivePanel({ active: active2 }) {
1526
1546
  ] }),
1527
1547
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
1528
1548
  /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `now ${latNow}ms \xB7 med ${latMed}ms \xB7 amber >${Math.round(latHotAt)}ms`, width: rightW }),
1529
- /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0", hotColor: "#ffb454" })
1549
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "white", hotColor: "#ffb454" })
1530
1550
  ] })
1531
1551
  ] }),
1532
1552
  /* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
@@ -1610,9 +1630,9 @@ function LivePanel({ active: active2 }) {
1610
1630
  windowed.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
1611
1631
  ] }),
1612
1632
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1613
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
1614
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1615
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 space copy \xB7 ? all keys \xB7 esc menu \xB7 q quit ` })
1633
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " LIVE " }),
1634
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "white", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1635
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", children: ` \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 space copy \xB7 ? all keys \xB7 esc menu \xB7 q quit ` })
1616
1636
  ] })
1617
1637
  ] });
1618
1638
  }
@@ -2217,7 +2237,7 @@ function RegexTest({ re }) {
2217
2237
  // src/tui/panels/Audit.tsx
2218
2238
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2219
2239
  import TextInput4 from "ink-text-input";
2220
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
2240
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2221
2241
  import { homedir as homedir5 } from "os";
2222
2242
  import { join as join5 } from "path";
2223
2243
  import { useState as useState6 } from "react";
@@ -2451,8 +2471,8 @@ function AuditPanel({ active: active2, focused }) {
2451
2471
  const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
2452
2472
  rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
2453
2473
  } else rows2 = localFiltered;
2454
- mkdirSync2(dir, { recursive: true });
2455
- writeFileSync3(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
2474
+ mkdirSync3(dir, { recursive: true });
2475
+ writeFileSync4(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
2456
2476
  return { n: rows2.length, file };
2457
2477
  };
2458
2478
  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" }));
@@ -3104,6 +3124,17 @@ function App() {
3104
3124
  const [section, setSection] = useState8(0);
3105
3125
  const [focus, setFocus] = useState8("nav");
3106
3126
  const [help, setHelp] = useState8(false);
3127
+ const [accounts] = useState8(() => listAccounts());
3128
+ const [acctIdx, setAcctIdx] = useState8(0);
3129
+ const cur = accounts[acctIdx];
3130
+ const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "\u2014";
3131
+ const switchAccount = () => {
3132
+ if (accounts.length < 2) return;
3133
+ const next = (acctIdx + 1) % accounts.length;
3134
+ const a = accounts[next];
3135
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3136
+ setAcctIdx(next);
3137
+ };
3107
3138
  useInput7((input, key) => {
3108
3139
  if (help) {
3109
3140
  setHelp(false);
@@ -3117,6 +3148,7 @@ function App() {
3117
3148
  if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
3118
3149
  else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
3119
3150
  else if (key.rightArrow || key.return || key.tab) setFocus("panel");
3151
+ else if (input === "a") switchAccount();
3120
3152
  else if (input === "q") exit();
3121
3153
  } else {
3122
3154
  if (key.escape || key.tab) setFocus("nav");
@@ -3127,16 +3159,21 @@ function App() {
3127
3159
  const current = SECTIONS[section];
3128
3160
  if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
3129
3161
  if (current.label === "Live" && focus === "panel") {
3130
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
3162
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, acctIdx) });
3131
3163
  }
3132
3164
  const Panel = current.Panel;
3133
3165
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3134
3166
  /* @__PURE__ */ jsx8(Banner, { cols }),
3167
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3168
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
3169
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: acctLabel }),
3170
+ accounts.length > 1 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch)` }) : null
3171
+ ] }),
3135
3172
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3136
3173
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
3137
3174
  /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
3138
- ] }),
3139
- /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
3175
+ ] }, acctIdx),
3176
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
3140
3177
  ] });
3141
3178
  }
3142
3179
  var HELP = [
@@ -3145,7 +3182,8 @@ var HELP = [
3145
3182
  ["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"]]],
3146
3183
  ["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"]]],
3147
3184
  ["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"]]],
3148
- ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
3185
+ ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
3186
+ ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
3149
3187
  ];
3150
3188
  function HelpOverlay({ cols, rows }) {
3151
3189
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.35",
3
+ "version": "0.81.37",
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": {