@solongate/proxy 0.80.0 → 0.81.1

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/login.js CHANGED
@@ -217,12 +217,14 @@ async function runGlobalInstall(opts = {}) {
217
217
  const auditAbs = join(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
218
218
  const stopAbs = join(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
219
219
  const nodeBin = process.execPath.replace(/\\/g, "/");
220
+ const call = process.platform === "win32" ? "& " : "";
221
+ const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
220
222
  const merged = {
221
223
  ...existing,
222
224
  hooks: {
223
- PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: `"${nodeBin}" "${guardAbs}" claude-code "Claude Code"` }] }],
224
- PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: `"${nodeBin}" "${auditAbs}" claude-code "Claude Code"` }] }],
225
- Stop: [{ matcher: "", hooks: [{ type: "command", command: `"${nodeBin}" "${stopAbs}" claude-code "Claude Code"` }] }]
225
+ PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(guardAbs) }] }],
226
+ PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(auditAbs) }] }],
227
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
226
228
  }
227
229
  };
228
230
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
@@ -0,0 +1,6 @@
1
+ export interface TuiConfig {
2
+ notifications: boolean;
3
+ accent?: string;
4
+ pollMs?: number;
5
+ }
6
+ export declare function loadConfig(): TuiConfig;
package/dist/tui/index.js CHANGED
@@ -8,8 +8,8 @@ var __export = (target, all) => {
8
8
  import { render } from "ink";
9
9
 
10
10
  // src/tui/App.tsx
11
- import { Box as Box8, Text as Text8, useApp, useInput as useInput6 } from "ink";
12
- import { useState as useState7 } from "react";
11
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput7 } from "ink";
12
+ import { useState as useState8 } from "react";
13
13
 
14
14
  // src/cli-utils.ts
15
15
  var c = {
@@ -44,10 +44,33 @@ var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
44
44
  // src/tui/components.tsx
45
45
  import { Box, Text } from "ink";
46
46
 
47
+ // src/tui/config.ts
48
+ import { readFileSync } from "fs";
49
+ import { homedir } from "os";
50
+ import { join } from "path";
51
+ var cached = null;
52
+ function loadConfig() {
53
+ if (cached) return cached;
54
+ const defaults = { notifications: true };
55
+ try {
56
+ const raw = readFileSync(join(homedir(), ".solongate", "tui-config.json"), "utf-8");
57
+ const j = JSON.parse(raw);
58
+ cached = {
59
+ notifications: j.notifications !== false,
60
+ accent: typeof j.accent === "string" ? j.accent : void 0,
61
+ pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
62
+ };
63
+ } catch {
64
+ cached = defaults;
65
+ }
66
+ return cached;
67
+ }
68
+
47
69
  // src/tui/theme.ts
70
+ var accent = loadConfig().accent;
48
71
  var theme = {
49
- accent: "cyan",
50
- accentBright: "#5a8ce6",
72
+ accent: accent || "cyan",
73
+ accentBright: accent || "#5a8ce6",
51
74
  ok: "green",
52
75
  warn: "yellow",
53
76
  bad: "red",
@@ -128,15 +151,15 @@ function KeyHints({ hints }) {
128
151
  import { Box as Box2, Text as Text2, useInput } from "ink";
129
152
  import TextInput from "ink-text-input";
130
153
  import { spawn } from "child_process";
131
- import { closeSync, openSync, readSync, statSync } from "fs";
132
- import { homedir as homedir2 } from "os";
133
- import { join as join2 } from "path";
154
+ import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
155
+ import { homedir as homedir3 } from "os";
156
+ import { join as join3 } from "path";
134
157
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
135
158
 
136
159
  // src/api-client/client.ts
137
- import { readFileSync, existsSync } from "fs";
138
- import { resolve, join } from "path";
139
- import { homedir } from "os";
160
+ import { readFileSync as readFileSync2, existsSync } from "fs";
161
+ import { resolve, join as join2 } from "path";
162
+ import { homedir as homedir2 } from "os";
140
163
  var DEFAULT_API_URL = "https://api.solongate.com";
141
164
  var ApiError = class extends Error {
142
165
  status;
@@ -156,9 +179,9 @@ var NotAuthenticatedError = class extends Error {
156
179
  };
157
180
  function loginCredentialFile() {
158
181
  try {
159
- const p = join(homedir(), ".solongate", "cloud-guard.json");
182
+ const p = join2(homedir2(), ".solongate", "cloud-guard.json");
160
183
  if (!existsSync(p)) return {};
161
- const c2 = JSON.parse(readFileSync(p, "utf-8"));
184
+ const c2 = JSON.parse(readFileSync2(p, "utf-8"));
162
185
  return c2 && typeof c2 === "object" ? c2 : {};
163
186
  } catch {
164
187
  return {};
@@ -168,7 +191,7 @@ function dotenvApiKey() {
168
191
  try {
169
192
  const envPath = resolve(".env");
170
193
  if (!existsSync(envPath)) return void 0;
171
- for (const line of readFileSync(envPath, "utf-8").split("\n")) {
194
+ for (const line of readFileSync2(envPath, "utf-8").split("\n")) {
172
195
  const trimmed = line.trim();
173
196
  if (!trimmed || trimmed.startsWith("#")) continue;
174
197
  const eq = trimmed.indexOf("=");
@@ -181,7 +204,7 @@ function dotenvApiKey() {
181
204
  }
182
205
  return void 0;
183
206
  }
184
- var cached = null;
207
+ var cached2 = null;
185
208
  function isAuthenticated() {
186
209
  try {
187
210
  resolveCredentials();
@@ -191,13 +214,13 @@ function isAuthenticated() {
191
214
  }
192
215
  }
193
216
  function resolveCredentials(apiUrlOverride) {
194
- if (cached && !apiUrlOverride) return cached;
217
+ if (cached2 && !apiUrlOverride) return cached2;
195
218
  const file = loginCredentialFile();
196
219
  const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
197
220
  if (!apiKey) throw new NotAuthenticatedError();
198
221
  const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL;
199
222
  const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
200
- if (!apiUrlOverride) cached = creds;
223
+ if (!apiUrlOverride) cached2 = creds;
201
224
  return creds;
202
225
  }
203
226
  function buildUrl(base, path, query) {
@@ -508,11 +531,12 @@ function usePoll(reload, intervalMs, enabled = true) {
508
531
 
509
532
  // src/tui/panels/Live.tsx
510
533
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
534
+ var CONFIG = loadConfig();
511
535
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
512
536
  var BG = "#12234f";
513
537
  var DIM_FLOOR = "#233457";
514
- var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
515
- var RING = join2(process.cwd(), ".solongate", ".eval-ring.jsonl");
538
+ var LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
539
+ var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
516
540
  var hhmmss = (ts) => {
517
541
  const d = new Date(ts);
518
542
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -836,6 +860,7 @@ function LivePanel({ active: active2 }) {
836
860
  (id, title, msg, level = "warn") => {
837
861
  pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
838
862
  setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
863
+ if (!CONFIG.notifications) return;
839
864
  try {
840
865
  process.stdout.write("\x07");
841
866
  } catch {
@@ -1063,7 +1088,16 @@ function LivePanel({ active: active2 }) {
1063
1088
  else if (input === "d") toggleSignal("deny");
1064
1089
  else if (input === "x") toggleSignal("dlp");
1065
1090
  else if (input === "r") toggleSignal("ratelimit");
1066
- else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
1091
+ else if (input === "e") {
1092
+ const file = join3(homedir3(), ".solongate", "live-export.jsonl");
1093
+ try {
1094
+ mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
1095
+ writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1096
+ setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1097
+ } catch (err) {
1098
+ setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
1099
+ }
1100
+ } else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
1067
1101
  else if (key.upArrow) setSel((n) => Math.max(0, n - 1));
1068
1102
  else if (key.pageDown) setSel((n) => Math.min(visibleDesc.length - 1, n + streamBodyRows));
1069
1103
  else if (key.pageUp) setSel((n) => Math.max(0, n - streamBodyRows));
@@ -1317,7 +1351,7 @@ function LivePanel({ active: active2 }) {
1317
1351
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1318
1352
  import TextInput2 from "ink-text-input";
1319
1353
  import { useEffect as useEffect3, useState as useState3 } from "react";
1320
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1354
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1321
1355
  var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
1322
1356
  var setConstr = (r, g, side, val) => {
1323
1357
  const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
@@ -1368,6 +1402,11 @@ function PoliciesPanel({ focused }) {
1368
1402
  () => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
1369
1403
  [view, selected?.id]
1370
1404
  );
1405
+ const diffVer = versionsQ.data?.versions[vi]?.version;
1406
+ const diffQ = useLoader(
1407
+ () => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
1408
+ [view, selected?.id, diffVer]
1409
+ );
1371
1410
  useEffect3(() => {
1372
1411
  if (detail.data) {
1373
1412
  setRules(detail.data.rules);
@@ -1571,6 +1610,23 @@ function PoliciesPanel({ focused }) {
1571
1610
  ])
1572
1611
  }
1573
1612
  ) }),
1613
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1614
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
1615
+ (() => {
1616
+ const old = diffQ.data?.rules ?? [];
1617
+ const cur = rules;
1618
+ const oldIds = new Set(old.map((r) => r.id));
1619
+ const curIds = new Set(cur.map((r) => r.id));
1620
+ const added = cur.filter((r) => !oldIds.has(r.id));
1621
+ const removed = old.filter((r) => !curIds.has(r.id));
1622
+ if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
1623
+ if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
1624
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1625
+ added.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.ok, children: ` + ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "a" + r.id)),
1626
+ removed.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.bad, children: ` - ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "r" + r.id))
1627
+ ] });
1628
+ })()
1629
+ ] }),
1574
1630
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1575
1631
  ] }) });
1576
1632
  }
@@ -1687,6 +1743,20 @@ function RateLimitPanel({ focused }) {
1687
1743
  /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
1688
1744
  /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
1689
1745
  ] }),
1746
+ draft.perMinute > 0 ? (() => {
1747
+ const now = minuteSeries.slice(-1)[0] ?? 0;
1748
+ const pct = Math.min(100, Math.round(now / draft.perMinute * 100));
1749
+ const width = 30;
1750
+ const filled = Math.min(width, Math.round(now / draft.perMinute * width));
1751
+ const col = pct >= 100 ? theme.bad : pct >= 80 ? theme.warn : theme.ok;
1752
+ return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1753
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load " }),
1754
+ /* @__PURE__ */ jsx4(Text4, { color: col, children: "\u2588".repeat(filled) }),
1755
+ /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
1756
+ /* @__PURE__ */ jsx4(Text4, { color: col, children: ` ${now}/${draft.perMinute} ` }),
1757
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: `(${pct}% this min)` })
1758
+ ] });
1759
+ })() : null,
1690
1760
  dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1691
1761
  dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
1692
1762
  status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
@@ -1899,9 +1969,27 @@ function DlpPanel({ focused }) {
1899
1969
  }
1900
1970
  )
1901
1971
  ] }) : null,
1972
+ adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
1902
1973
  status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
1903
1974
  ] }) : null });
1904
1975
  }
1976
+ var SAMPLES = ["AKIA1234567890ABCD00", "sk-ant-api03-xxxxxxxx", "ghp_16chars0000000000000000000000000000", "password=hunter2", "Bearer eyJhbGciOi"];
1977
+ function RegexTest({ re }) {
1978
+ if (!re.trim()) return /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " regex: type a pattern\u2026" });
1979
+ let rx = null;
1980
+ let err = "";
1981
+ try {
1982
+ rx = new RegExp(re);
1983
+ } catch (e) {
1984
+ err = e instanceof Error ? e.message : String(e);
1985
+ }
1986
+ if (!rx) return /* @__PURE__ */ jsx5(Text5, { color: theme.bad, children: ` \u2717 invalid: ${err.slice(0, 50)}` });
1987
+ const hits = SAMPLES.filter((s) => rx.test(s));
1988
+ return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
1989
+ /* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: " \u2713 valid" }),
1990
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: hits.length ? ` matches ${hits.length}/${SAMPLES.length} samples: ${hits.map((h) => h.slice(0, 8)).join(", ")}` : " (no sample match \u2014 will only catch your custom secrets)" })
1991
+ ] });
1992
+ }
1905
1993
 
1906
1994
  // src/tui/panels/Stats.tsx
1907
1995
  import { Box as Box6, Text as Text6 } from "ink";
@@ -2176,22 +2264,109 @@ function Detail({ label, value, color }) {
2176
2264
  ] });
2177
2265
  }
2178
2266
 
2267
+ // src/tui/panels/Agents.tsx
2268
+ import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
2269
+ import { useState as useState7 } from "react";
2270
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2271
+ var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
2272
+ var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
2273
+ function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
2274
+ const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
2275
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2276
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
2277
+ /* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
2278
+ /* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
2279
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
2280
+ ] });
2281
+ }
2282
+ function AgentsPanel({ focused }) {
2283
+ const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
2284
+ usePoll(live2.reload, 6e3, focused ? false : true);
2285
+ const [sel, setSel] = useState7(0);
2286
+ const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
2287
+ const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
2288
+ const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
2289
+ useInput6(
2290
+ (_input, key) => {
2291
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2292
+ else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
2293
+ },
2294
+ { isActive: focused }
2295
+ );
2296
+ const d = detail.data;
2297
+ const base = d?.baseline;
2298
+ const radar = base?.radar;
2299
+ const anomalies2 = d?.anomalies ?? [];
2300
+ const tmap = d?.trust_map;
2301
+ const tools = tmap?.tools ?? [];
2302
+ const resources = tmap?.resources ?? [];
2303
+ const listW = 30;
2304
+ return /* @__PURE__ */ jsx8(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs8(Box8, { children: [
2305
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
2306
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
2307
+ agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
2308
+ /* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
2309
+ a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
2310
+ " "
2311
+ ] }),
2312
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
2313
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
2314
+ ] }, a.session_id))
2315
+ ] }),
2316
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
2317
+ /* @__PURE__ */ jsxs8(Box8, { children: [
2318
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
2319
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
2320
+ ] }),
2321
+ base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
2322
+ radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2323
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
2324
+ /* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
2325
+ /* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
2326
+ /* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
2327
+ /* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
2328
+ /* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
2329
+ ] }) : null,
2330
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2331
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2332
+ "Anomalies ",
2333
+ anomalies2.length ? `(${anomalies2.length})` : ""
2334
+ ] }),
2335
+ anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2336
+ /* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
2337
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
2338
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
2339
+ ] }, i))
2340
+ ] }),
2341
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2342
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
2343
+ tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2344
+ /* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
2345
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
2346
+ ] }, i)),
2347
+ resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: r.anomalous ? theme.bad : theme.dim, children: ` ${r.type === "domain" ? "\u{1F310}" : "\u{1F4C1}"} ${truncate(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
2348
+ ] })
2349
+ ] }) })
2350
+ ] }) });
2351
+ }
2352
+
2179
2353
  // src/tui/App.tsx
2180
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2354
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2181
2355
  function LiveHint() {
2182
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2183
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2184
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2185
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2186
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2356
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2357
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2358
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2359
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2360
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
2187
2361
  "press ",
2188
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
2362
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
2189
2363
  " to go live"
2190
2364
  ] }) })
2191
2365
  ] });
2192
2366
  }
2193
2367
  var SECTIONS = [
2194
2368
  { label: "Live", Panel: LiveHint },
2369
+ { label: "Agents", Panel: AgentsPanel },
2195
2370
  { label: "Policies", Panel: PoliciesPanel },
2196
2371
  { label: "Rate Limit", Panel: RateLimitPanel },
2197
2372
  { label: "DLP", Panel: DlpPanel },
@@ -2202,22 +2377,22 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
2202
2377
  function Banner() {
2203
2378
  const wide = (process.stdout.columns ?? 80) >= 82;
2204
2379
  if (!wide) {
2205
- return /* @__PURE__ */ jsxs8(Box8, { children: [
2206
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2207
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
2380
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
2381
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2382
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
2208
2383
  ] });
2209
2384
  }
2210
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2211
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2212
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2385
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2386
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2387
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2213
2388
  ] });
2214
2389
  }
2215
2390
  function App() {
2216
2391
  const { exit } = useApp();
2217
- const [section, setSection] = useState7(0);
2218
- const [focus, setFocus] = useState7("nav");
2219
- const [help, setHelp] = useState7(false);
2220
- useInput6((input, key) => {
2392
+ const [section, setSection] = useState8(0);
2393
+ const [focus, setFocus] = useState8("nav");
2394
+ const [help, setHelp] = useState8(false);
2395
+ useInput7((input, key) => {
2221
2396
  if (help) {
2222
2397
  setHelp(false);
2223
2398
  return;
@@ -2239,18 +2414,18 @@ function App() {
2239
2414
  const cols = process.stdout.columns ?? 100;
2240
2415
  const rows = process.stdout.rows ?? 30;
2241
2416
  const current = SECTIONS[section];
2242
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
2417
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
2243
2418
  if (current.label === "Live" && focus === "panel") {
2244
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
2419
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
2245
2420
  }
2246
2421
  const Panel = current.Panel;
2247
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2248
- /* @__PURE__ */ jsx8(Banner, {}),
2249
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
2250
- /* @__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)) }),
2251
- /* @__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" }) })
2422
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2423
+ /* @__PURE__ */ jsx9(Banner, {}),
2424
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
2425
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
2426
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
2252
2427
  ] }),
2253
- /* @__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"]] }) })
2428
+ /* @__PURE__ */ jsx9(Box9, { children: focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
2254
2429
  ] });
2255
2430
  }
2256
2431
  var HELP = [
@@ -2260,21 +2435,21 @@ var HELP = [
2260
2435
  ["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"]]]
2261
2436
  ];
2262
2437
  function HelpOverlay({ cols, rows }) {
2263
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2264
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2265
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
2266
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
2267
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
2268
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2269
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
2438
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2439
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2440
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
2441
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
2442
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
2443
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2444
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
2270
2445
  ] }, k))
2271
2446
  ] }, group)) }),
2272
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
2447
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
2273
2448
  ] });
2274
2449
  }
2275
2450
 
2276
2451
  // src/tui/index.tsx
2277
- import { jsx as jsx9 } from "react/jsx-runtime";
2452
+ import { jsx as jsx10 } from "react/jsx-runtime";
2278
2453
  async function launchTui() {
2279
2454
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2280
2455
  process.stderr.write(
@@ -2288,7 +2463,7 @@ async function launchTui() {
2288
2463
  }
2289
2464
  process.stdout.write("\x1B[?1049h\x1B[H");
2290
2465
  try {
2291
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
2466
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
2292
2467
  await waitUntilExit();
2293
2468
  } finally {
2294
2469
  process.stdout.write("\x1B[?1049l");
@@ -0,0 +1,4 @@
1
+ export declare function AgentsPanel({ focused }: {
2
+ active: boolean;
3
+ focused: boolean;
4
+ }): JSX.Element;
@@ -1,7 +1,6 @@
1
- /** Shared colors + small helpers for the Ink TUI. */
2
1
  export declare const theme: {
3
- readonly accent: "cyan";
4
- readonly accentBright: "#5a8ce6";
2
+ readonly accent: string;
3
+ readonly accentBright: string;
5
4
  readonly ok: "green";
6
5
  readonly warn: "yellow";
7
6
  readonly bad: "red";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.80.0",
3
+ "version": "0.81.1",
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": {