@solongate/proxy 0.78.0 → 0.80.0
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/api-client/index.d.ts +4 -0
- package/dist/api-client/keys.d.ts +17 -0
- package/dist/api-client/mcp.d.ts +10 -0
- package/dist/api-client/settings.d.ts +52 -0
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +426 -3
- package/dist/commands/keys.d.ts +1 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/watch.d.ts +1 -0
- package/dist/commands/webhooks.d.ts +1 -0
- package/dist/index.js +730 -90
- package/dist/tui/index.js +221 -12
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -325,10 +325,18 @@ function dryRun(body) {
|
|
|
325
325
|
var settings_exports = {};
|
|
326
326
|
__export(settings_exports, {
|
|
327
327
|
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
328
|
+
createAlert: () => createAlert,
|
|
329
|
+
createWebhook: () => createWebhook,
|
|
330
|
+
deleteAlert: () => deleteAlert,
|
|
331
|
+
deleteWebhook: () => deleteWebhook,
|
|
332
|
+
getAlerts: () => getAlerts,
|
|
328
333
|
getGuardStatus: () => getGuardStatus,
|
|
329
334
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
330
335
|
getSecurityLayers: () => getSecurityLayers,
|
|
331
|
-
|
|
336
|
+
getSelfProtection: () => getSelfProtection,
|
|
337
|
+
getWebhooks: () => getWebhooks,
|
|
338
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
339
|
+
setSelfProtection: () => setSelfProtection
|
|
332
340
|
});
|
|
333
341
|
function getSecurityLayers() {
|
|
334
342
|
return request("GET", "/settings/security-layers");
|
|
@@ -345,6 +353,30 @@ function clearRateLimitHistory() {
|
|
|
345
353
|
function getGuardStatus() {
|
|
346
354
|
return request("GET", "/settings/guard-status");
|
|
347
355
|
}
|
|
356
|
+
function getSelfProtection() {
|
|
357
|
+
return request("GET", "/settings/self-protection");
|
|
358
|
+
}
|
|
359
|
+
function setSelfProtection(enabled) {
|
|
360
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
361
|
+
}
|
|
362
|
+
function getAlerts() {
|
|
363
|
+
return request("GET", "/settings/denial-alerts");
|
|
364
|
+
}
|
|
365
|
+
function createAlert(body) {
|
|
366
|
+
return request("POST", "/settings/denial-alerts", { body });
|
|
367
|
+
}
|
|
368
|
+
function deleteAlert(id) {
|
|
369
|
+
return request("DELETE", "/settings/denial-alerts", { query: { id } });
|
|
370
|
+
}
|
|
371
|
+
function getWebhooks() {
|
|
372
|
+
return request("GET", "/settings/denial-webhook");
|
|
373
|
+
}
|
|
374
|
+
function createWebhook(body) {
|
|
375
|
+
return request("POST", "/settings/denial-webhook", { body });
|
|
376
|
+
}
|
|
377
|
+
function deleteWebhook(id) {
|
|
378
|
+
return request("DELETE", "/settings/denial-webhook", { query: { id } });
|
|
379
|
+
}
|
|
348
380
|
|
|
349
381
|
// src/api-client/stats.ts
|
|
350
382
|
var stats_exports = {};
|
|
@@ -405,8 +437,34 @@ function anomalies(id, limit) {
|
|
|
405
437
|
});
|
|
406
438
|
}
|
|
407
439
|
|
|
440
|
+
// src/api-client/keys.ts
|
|
441
|
+
var keys_exports = {};
|
|
442
|
+
__export(keys_exports, {
|
|
443
|
+
create: () => create2,
|
|
444
|
+
list: () => list3,
|
|
445
|
+
revoke: () => revoke
|
|
446
|
+
});
|
|
447
|
+
function list3() {
|
|
448
|
+
return request("GET", "/keys");
|
|
449
|
+
}
|
|
450
|
+
function create2(name, isLive = true) {
|
|
451
|
+
return request("POST", "/keys", { body: { name, is_live: isLive } });
|
|
452
|
+
}
|
|
453
|
+
function revoke(id) {
|
|
454
|
+
return request("DELETE", `/keys/${encodeURIComponent(id)}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/api-client/mcp.ts
|
|
458
|
+
var mcp_exports = {};
|
|
459
|
+
__export(mcp_exports, {
|
|
460
|
+
list: () => list4
|
|
461
|
+
});
|
|
462
|
+
function list4() {
|
|
463
|
+
return request("GET", "/mcp-servers");
|
|
464
|
+
}
|
|
465
|
+
|
|
408
466
|
// src/api-client/index.ts
|
|
409
|
-
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports };
|
|
467
|
+
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
|
|
410
468
|
|
|
411
469
|
// src/tui/hooks.ts
|
|
412
470
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -1291,12 +1349,13 @@ function ruleSummary(r) {
|
|
|
1291
1349
|
return bits.join(" ");
|
|
1292
1350
|
}
|
|
1293
1351
|
function PoliciesPanel({ focused }) {
|
|
1294
|
-
const
|
|
1295
|
-
const policies =
|
|
1352
|
+
const list5 = useLoader(() => api.policies.list());
|
|
1353
|
+
const policies = list5.data?.policies ?? [];
|
|
1296
1354
|
const [view, setView] = useState3("list");
|
|
1297
1355
|
const [pi, setPi] = useState3(0);
|
|
1298
1356
|
const [ri, setRi] = useState3(0);
|
|
1299
1357
|
const [fi, setFi] = useState3(0);
|
|
1358
|
+
const [vi, setVi] = useState3(0);
|
|
1300
1359
|
const [rules, setRules] = useState3([]);
|
|
1301
1360
|
const [mode, setMode] = useState3("denylist");
|
|
1302
1361
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -1305,6 +1364,10 @@ function PoliciesPanel({ focused }) {
|
|
|
1305
1364
|
const [status, setStatus] = useState3(null);
|
|
1306
1365
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
1307
1366
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
1367
|
+
const versionsQ = useLoader(
|
|
1368
|
+
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
1369
|
+
[view, selected?.id]
|
|
1370
|
+
);
|
|
1308
1371
|
useEffect3(() => {
|
|
1309
1372
|
if (detail.data) {
|
|
1310
1373
|
setRules(detail.data.rules);
|
|
@@ -1334,7 +1397,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1334
1397
|
setRules(res.rules);
|
|
1335
1398
|
setDirty(false);
|
|
1336
1399
|
setStatus("\u2713 Saved v" + res._version);
|
|
1337
|
-
|
|
1400
|
+
list5.reload();
|
|
1338
1401
|
} catch (e) {
|
|
1339
1402
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1340
1403
|
}
|
|
@@ -1379,10 +1442,41 @@ function PoliciesPanel({ focused }) {
|
|
|
1379
1442
|
}
|
|
1380
1443
|
} else if (input === "m") {
|
|
1381
1444
|
mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
|
|
1445
|
+
} else if (input === "n") {
|
|
1446
|
+
const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
|
|
1447
|
+
mutate([nr, ...rules]);
|
|
1448
|
+
setRi(0);
|
|
1449
|
+
setFi(0);
|
|
1450
|
+
setView("rule");
|
|
1451
|
+
} else if (input === "D") {
|
|
1452
|
+
setStatus("Dry-running\u2026");
|
|
1453
|
+
void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1454
|
+
} else if (input === "v") {
|
|
1455
|
+
setVi(0);
|
|
1456
|
+
setView("versions");
|
|
1382
1457
|
} else if (input === "s") void save();
|
|
1383
1458
|
else if (input === "x") discard();
|
|
1384
1459
|
return;
|
|
1385
1460
|
}
|
|
1461
|
+
if (view === "versions") {
|
|
1462
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1463
|
+
if (key.leftArrow) return void setView("rules");
|
|
1464
|
+
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
1465
|
+
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
1466
|
+
else if (key.return) {
|
|
1467
|
+
const v = vers[vi];
|
|
1468
|
+
if (v && selected) {
|
|
1469
|
+
setStatus("Rolling back\u2026");
|
|
1470
|
+
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
1471
|
+
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
1472
|
+
detail.reload();
|
|
1473
|
+
list5.reload();
|
|
1474
|
+
setView("rules");
|
|
1475
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1386
1480
|
const rule2 = rules[ri];
|
|
1387
1481
|
if (!rule2) return setView("rules");
|
|
1388
1482
|
const field = FIELDS[fi];
|
|
@@ -1404,7 +1498,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1404
1498
|
{ isActive: focused && !editing }
|
|
1405
1499
|
);
|
|
1406
1500
|
if (view === "list") {
|
|
1407
|
-
return /* @__PURE__ */ jsx3(DataView, { loading:
|
|
1501
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1408
1502
|
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
|
|
1409
1503
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs3(Text3, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
|
|
1410
1504
|
(i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
|
|
@@ -1427,7 +1521,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1427
1521
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
1428
1522
|
dirtyTag
|
|
1429
1523
|
] }),
|
|
1430
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193
|
|
1524
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 v versions \xB7 s save \xB7 \u2190 back" }),
|
|
1431
1525
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1432
1526
|
Table,
|
|
1433
1527
|
{
|
|
@@ -1453,6 +1547,33 @@ function PoliciesPanel({ focused }) {
|
|
|
1453
1547
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1454
1548
|
] }) });
|
|
1455
1549
|
}
|
|
1550
|
+
if (view === "versions") {
|
|
1551
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1552
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1553
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
|
|
1554
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
1555
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1556
|
+
Table,
|
|
1557
|
+
{
|
|
1558
|
+
columns: [
|
|
1559
|
+
{ header: "", width: 2 },
|
|
1560
|
+
{ header: "VER", width: 5 },
|
|
1561
|
+
{ header: "RULES", width: 5 },
|
|
1562
|
+
{ header: "REASON", width: 34 },
|
|
1563
|
+
{ header: "WHEN", width: 16 }
|
|
1564
|
+
],
|
|
1565
|
+
rows: vers.map((v, i) => [
|
|
1566
|
+
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
1567
|
+
{ value: `v${v.version}`, bold: i === vi },
|
|
1568
|
+
{ value: String(v.rules_count), dim: true },
|
|
1569
|
+
{ value: truncate(v.reason || "\u2014", 34) },
|
|
1570
|
+
{ value: truncate(v.created_at, 16), dim: true }
|
|
1571
|
+
])
|
|
1572
|
+
}
|
|
1573
|
+
) }),
|
|
1574
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1575
|
+
] }) });
|
|
1576
|
+
}
|
|
1456
1577
|
const rule = rules[ri];
|
|
1457
1578
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1458
1579
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -1636,12 +1757,19 @@ function DlpPanel({ focused }) {
|
|
|
1636
1757
|
const [adding, setAdding] = useState5(null);
|
|
1637
1758
|
const [newName, setNewName] = useState5("");
|
|
1638
1759
|
const [newRe, setNewRe] = useState5("");
|
|
1760
|
+
const [ghostOn, setGhostOn] = useState5(false);
|
|
1761
|
+
const spQ = useLoader(() => api.settings.getSelfProtection());
|
|
1762
|
+
const [selfProt, setSelfProt] = useState5(null);
|
|
1639
1763
|
useEffect5(() => {
|
|
1640
1764
|
if (q.data && !dlp) {
|
|
1641
1765
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1642
1766
|
setAvailable(q.data.availablePatterns);
|
|
1767
|
+
setGhostOn(q.data.layers.ghost.mode === "on");
|
|
1643
1768
|
}
|
|
1644
1769
|
}, [q.data, dlp]);
|
|
1770
|
+
useEffect5(() => {
|
|
1771
|
+
if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
|
|
1772
|
+
}, [spQ.data, selfProt]);
|
|
1645
1773
|
const mutate = (next) => {
|
|
1646
1774
|
setDlp(next);
|
|
1647
1775
|
setDirty(true);
|
|
@@ -1651,14 +1779,27 @@ function DlpPanel({ focused }) {
|
|
|
1651
1779
|
if (!dlp || !q.data) return;
|
|
1652
1780
|
setStatus("Saving\u2026");
|
|
1653
1781
|
try {
|
|
1654
|
-
const
|
|
1782
|
+
const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
|
|
1783
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
|
|
1655
1784
|
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
|
|
1785
|
+
setGhostOn(res.layers.ghost.mode === "on");
|
|
1656
1786
|
setDirty(false);
|
|
1657
1787
|
setStatus("\u2713 Saved");
|
|
1658
1788
|
} catch (e) {
|
|
1659
1789
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1660
1790
|
}
|
|
1661
1791
|
};
|
|
1792
|
+
const toggleSelfProt = async () => {
|
|
1793
|
+
const next = !(selfProt ?? false);
|
|
1794
|
+
setSelfProt(next);
|
|
1795
|
+
try {
|
|
1796
|
+
await api.settings.setSelfProtection(next);
|
|
1797
|
+
setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
|
|
1798
|
+
} catch (e) {
|
|
1799
|
+
setSelfProt(!next);
|
|
1800
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1662
1803
|
const discard = () => {
|
|
1663
1804
|
if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1664
1805
|
setDirty(false);
|
|
@@ -1688,7 +1829,12 @@ function DlpPanel({ focused }) {
|
|
|
1688
1829
|
const ci = sel - available.length;
|
|
1689
1830
|
mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
|
|
1690
1831
|
setSel((n) => Math.max(0, n - 1));
|
|
1691
|
-
} else if (input === "
|
|
1832
|
+
} else if (input === "g") {
|
|
1833
|
+
setGhostOn((v) => !v);
|
|
1834
|
+
setDirty(true);
|
|
1835
|
+
setStatus(null);
|
|
1836
|
+
} else if (input === "P") void toggleSelfProt();
|
|
1837
|
+
else if (input === "s") void save();
|
|
1692
1838
|
else if (input === "x") discard();
|
|
1693
1839
|
},
|
|
1694
1840
|
{ isActive: focused && !adding }
|
|
@@ -1700,7 +1846,13 @@ function DlpPanel({ focused }) {
|
|
|
1700
1846
|
/* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
1701
1847
|
dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
|
|
1702
1848
|
] }),
|
|
1703
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193
|
|
1849
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
|
|
1850
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1851
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
|
|
1852
|
+
/* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
|
|
1853
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
|
|
1854
|
+
/* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
|
|
1855
|
+
] }),
|
|
1704
1856
|
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
1705
1857
|
const on = enabled.has(p);
|
|
1706
1858
|
const active2 = focused && sel === i;
|
|
@@ -1757,12 +1909,15 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
1757
1909
|
function StatsPanel({ active: active2 }) {
|
|
1758
1910
|
const stats = useLoader(() => api.stats.get());
|
|
1759
1911
|
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
1912
|
+
const drift2 = useLoader(() => api.stats.drift(7));
|
|
1760
1913
|
usePoll(() => {
|
|
1761
1914
|
stats.reload();
|
|
1762
1915
|
ts.reload();
|
|
1763
1916
|
}, 5e3, active2);
|
|
1917
|
+
usePoll(drift2.reload, 3e4, active2);
|
|
1764
1918
|
const s = stats.data;
|
|
1765
1919
|
const points = ts.data?.timeseries ?? [];
|
|
1920
|
+
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
1766
1921
|
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1767
1922
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1768
1923
|
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
@@ -1815,7 +1970,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
1815
1970
|
{ header: "MS", width: 5 },
|
|
1816
1971
|
{ header: "WHEN", width: 6 }
|
|
1817
1972
|
],
|
|
1818
|
-
rows: (s.recent_activity ?? []).slice(0,
|
|
1973
|
+
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
1819
1974
|
{ value: a.decision, color: decisionColor(a.decision) },
|
|
1820
1975
|
{ value: truncate(a.tool_name, 20), color: theme.accent },
|
|
1821
1976
|
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
@@ -1823,6 +1978,31 @@ function StatsPanel({ active: active2 }) {
|
|
|
1823
1978
|
])
|
|
1824
1979
|
}
|
|
1825
1980
|
)
|
|
1981
|
+
] }),
|
|
1982
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1983
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
1984
|
+
"Denial drift ",
|
|
1985
|
+
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
1986
|
+
] }),
|
|
1987
|
+
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
1988
|
+
Table,
|
|
1989
|
+
{
|
|
1990
|
+
columns: [
|
|
1991
|
+
{ header: "NOW", width: 5 },
|
|
1992
|
+
{ header: "PREV", width: 5 },
|
|
1993
|
+
{ header: "\u0394", width: 6 },
|
|
1994
|
+
{ header: "RULE", width: 22 },
|
|
1995
|
+
{ header: "REASON", width: 26 }
|
|
1996
|
+
],
|
|
1997
|
+
rows: driftRules.map((r) => [
|
|
1998
|
+
{ value: String(r.current), bold: true },
|
|
1999
|
+
{ value: String(r.previous), dim: true },
|
|
2000
|
+
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
2001
|
+
{ value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
2002
|
+
{ value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
2003
|
+
])
|
|
2004
|
+
}
|
|
2005
|
+
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
1826
2006
|
] })
|
|
1827
2007
|
] }) : null });
|
|
1828
2008
|
}
|
|
@@ -2036,7 +2216,16 @@ function App() {
|
|
|
2036
2216
|
const { exit } = useApp();
|
|
2037
2217
|
const [section, setSection] = useState7(0);
|
|
2038
2218
|
const [focus, setFocus] = useState7("nav");
|
|
2219
|
+
const [help, setHelp] = useState7(false);
|
|
2039
2220
|
useInput6((input, key) => {
|
|
2221
|
+
if (help) {
|
|
2222
|
+
setHelp(false);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
if (input === "?" && focus === "nav") {
|
|
2226
|
+
setHelp(true);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2040
2229
|
if (focus === "nav") {
|
|
2041
2230
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
2042
2231
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
@@ -2050,6 +2239,7 @@ function App() {
|
|
|
2050
2239
|
const cols = process.stdout.columns ?? 100;
|
|
2051
2240
|
const rows = process.stdout.rows ?? 30;
|
|
2052
2241
|
const current = SECTIONS[section];
|
|
2242
|
+
if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
|
|
2053
2243
|
if (current.label === "Live" && focus === "panel") {
|
|
2054
2244
|
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
|
|
2055
2245
|
}
|
|
@@ -2060,7 +2250,26 @@ function App() {
|
|
|
2060
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)) }),
|
|
2061
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" }) })
|
|
2062
2252
|
] }),
|
|
2063
|
-
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
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"]] }) })
|
|
2254
|
+
] });
|
|
2255
|
+
}
|
|
2256
|
+
var HELP = [
|
|
2257
|
+
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2258
|
+
["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
2259
|
+
["Policies", [["\u2191\u2193", "browse / select"], ["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"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
|
|
2260
|
+
["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
|
+
];
|
|
2262
|
+
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 })
|
|
2270
|
+
] }, k))
|
|
2271
|
+
] }, group)) }),
|
|
2272
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
|
|
2064
2273
|
] });
|
|
2065
2274
|
}
|
|
2066
2275
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0",
|
|
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": {
|