@solongate/proxy 0.82.96 → 0.82.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +51 -22
- package/dist/tui/index.js +34 -5
- package/hooks/guard.bundled.mjs +2 -2
- package/hooks/guard.mjs +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11407,7 +11407,26 @@ var init_doctor = __esm({
|
|
|
11407
11407
|
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
11408
11408
|
import TextInput6 from "ink-text-input";
|
|
11409
11409
|
import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
|
|
11410
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
|
|
11411
|
+
import { homedir as homedir11 } from "os";
|
|
11412
|
+
import { join as join13 } from "path";
|
|
11410
11413
|
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
11414
|
+
function guestAllowance() {
|
|
11415
|
+
try {
|
|
11416
|
+
const dir = join13(homedir11(), ".solongate");
|
|
11417
|
+
for (const f of readdirSync2(dir)) {
|
|
11418
|
+
if (!f.startsWith(".policy-cache-") || !f.endsWith(".json")) continue;
|
|
11419
|
+
try {
|
|
11420
|
+
const c2 = JSON.parse(readFileSync11(join13(dir, f), "utf-8"));
|
|
11421
|
+
const q = c2?.security?.guestQuota;
|
|
11422
|
+
if (q && typeof q.limit === "number") return { used: q.used ?? 0, limit: q.limit };
|
|
11423
|
+
} catch {
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
} catch {
|
|
11427
|
+
}
|
|
11428
|
+
return null;
|
|
11429
|
+
}
|
|
11411
11430
|
function SettingsPanel({
|
|
11412
11431
|
active: active2,
|
|
11413
11432
|
focused,
|
|
@@ -11443,6 +11462,7 @@ function SettingsPanel({
|
|
|
11443
11462
|
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
11444
11463
|
return () => clearInterval(step);
|
|
11445
11464
|
}, [diagBusy]);
|
|
11465
|
+
void guestAllowance;
|
|
11446
11466
|
const ver = currentVersion();
|
|
11447
11467
|
useEffect7(() => {
|
|
11448
11468
|
let live2 = true;
|
|
@@ -11965,6 +11985,15 @@ function SettingsPanel({
|
|
|
11965
11985
|
cursor(r),
|
|
11966
11986
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
|
|
11967
11987
|
] });
|
|
11988
|
+
case "allowance": {
|
|
11989
|
+
const left = Math.max(0, r.limit - r.used);
|
|
11990
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
11991
|
+
cursor(r),
|
|
11992
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "allowance".padEnd(11) }),
|
|
11993
|
+
/* @__PURE__ */ jsx8(Text8, { color: left === 0 ? theme.bad : left <= 20 ? theme.warn : theme.ok, bold: true, children: `${r.used}/${r.limit} tool calls` }),
|
|
11994
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: left === 0 ? " used up \u2014 every call is denied \xB7 create an account to carry on" : ` ${left} left on this guest account \xB7 create an account for no limit` })
|
|
11995
|
+
] });
|
|
11996
|
+
}
|
|
11968
11997
|
case "guard":
|
|
11969
11998
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
11970
11999
|
cursor(r),
|
|
@@ -12051,7 +12080,7 @@ function SettingsPanel({
|
|
|
12051
12080
|
] });
|
|
12052
12081
|
}
|
|
12053
12082
|
};
|
|
12054
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
12083
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" || r.kind === "allowance" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
12055
12084
|
const SECTION_DESC = {
|
|
12056
12085
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
12057
12086
|
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
@@ -12409,8 +12438,8 @@ __export(tui_exports, {
|
|
|
12409
12438
|
launchTui: () => launchTui
|
|
12410
12439
|
});
|
|
12411
12440
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
12412
|
-
import { homedir as
|
|
12413
|
-
import { join as
|
|
12441
|
+
import { homedir as homedir12 } from "os";
|
|
12442
|
+
import { join as join14 } from "path";
|
|
12414
12443
|
import { render } from "ink";
|
|
12415
12444
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
12416
12445
|
async function launchTui() {
|
|
@@ -12421,11 +12450,11 @@ async function launchTui() {
|
|
|
12421
12450
|
return;
|
|
12422
12451
|
}
|
|
12423
12452
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
12424
|
-
const debugLog =
|
|
12453
|
+
const debugLog = join14(homedir12(), ".solongate", "dataroom-debug.log");
|
|
12425
12454
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
12426
12455
|
const toFile = (level) => (...args) => {
|
|
12427
12456
|
try {
|
|
12428
|
-
mkdirSync9(
|
|
12457
|
+
mkdirSync9(join14(homedir12(), ".solongate"), { recursive: true });
|
|
12429
12458
|
appendFileSync2(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
12430
12459
|
`);
|
|
12431
12460
|
} catch {
|
|
@@ -12452,7 +12481,7 @@ var init_tui = __esm({
|
|
|
12452
12481
|
});
|
|
12453
12482
|
|
|
12454
12483
|
// src/commands/policy.ts
|
|
12455
|
-
import { readFileSync as
|
|
12484
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
12456
12485
|
async function run2(argv) {
|
|
12457
12486
|
const { positionals, flags } = parse(argv);
|
|
12458
12487
|
const sub = positionals[0];
|
|
@@ -12609,7 +12638,7 @@ function printRules(rules) {
|
|
|
12609
12638
|
}
|
|
12610
12639
|
async function resolveRules(target) {
|
|
12611
12640
|
if (target.endsWith(".json")) {
|
|
12612
|
-
const parsed = JSON.parse(
|
|
12641
|
+
const parsed = JSON.parse(readFileSync12(target, "utf-8"));
|
|
12613
12642
|
return parsed.rules ?? [];
|
|
12614
12643
|
}
|
|
12615
12644
|
const p = await api.policies.get(target);
|
|
@@ -13323,10 +13352,10 @@ __export(logs_server_exports, {
|
|
|
13323
13352
|
runLogsServer: () => runLogsServer
|
|
13324
13353
|
});
|
|
13325
13354
|
import { createServer } from "http";
|
|
13326
|
-
import { readFileSync as
|
|
13327
|
-
import { resolve as resolve5, join as
|
|
13328
|
-
import { homedir as
|
|
13329
|
-
import { readdirSync as
|
|
13355
|
+
import { readFileSync as readFileSync13, statSync as statSync4 } from "fs";
|
|
13356
|
+
import { resolve as resolve5, join as join15, isAbsolute as isAbsolute2 } from "path";
|
|
13357
|
+
import { homedir as homedir13 } from "os";
|
|
13358
|
+
import { readdirSync as readdirSync3 } from "fs";
|
|
13330
13359
|
function allowedOrigins() {
|
|
13331
13360
|
const base = [
|
|
13332
13361
|
"https://dashboard.solongate.com",
|
|
@@ -13342,15 +13371,15 @@ function resolveLocalLogDir(rawPath) {
|
|
|
13342
13371
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
13343
13372
|
if (!dir) return null;
|
|
13344
13373
|
if (isAbsolute2(dir)) return dir;
|
|
13345
|
-
return resolve5(
|
|
13374
|
+
return resolve5(homedir13(), ".solongate", "local-logs");
|
|
13346
13375
|
}
|
|
13347
13376
|
async function findLogDir() {
|
|
13348
|
-
const base = resolve5(
|
|
13377
|
+
const base = resolve5(homedir13(), ".solongate");
|
|
13349
13378
|
try {
|
|
13350
|
-
const files =
|
|
13379
|
+
const files = readdirSync3(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
13351
13380
|
for (const f of files) {
|
|
13352
13381
|
try {
|
|
13353
|
-
const c2 = JSON.parse(
|
|
13382
|
+
const c2 = JSON.parse(readFileSync13(join15(base, f), "utf-8"));
|
|
13354
13383
|
const p = c2?.security?.localLogs?.path;
|
|
13355
13384
|
if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
|
|
13356
13385
|
} catch {
|
|
@@ -13359,7 +13388,7 @@ async function findLogDir() {
|
|
|
13359
13388
|
} catch {
|
|
13360
13389
|
}
|
|
13361
13390
|
try {
|
|
13362
|
-
const cfgRaw =
|
|
13391
|
+
const cfgRaw = readFileSync13(join15(base, "cloud-guard.json"), "utf-8");
|
|
13363
13392
|
const { apiKey, apiUrl } = JSON.parse(cfgRaw);
|
|
13364
13393
|
if (apiKey) {
|
|
13365
13394
|
const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
|
|
@@ -13388,7 +13417,7 @@ function setCors(req, res) {
|
|
|
13388
13417
|
}
|
|
13389
13418
|
function fileInfo(dir) {
|
|
13390
13419
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
13391
|
-
const file =
|
|
13420
|
+
const file = join15(dir, LOG_FILENAME);
|
|
13392
13421
|
try {
|
|
13393
13422
|
const st = statSync4(file);
|
|
13394
13423
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
@@ -13471,7 +13500,7 @@ async function runLogsServer() {
|
|
|
13471
13500
|
return;
|
|
13472
13501
|
}
|
|
13473
13502
|
try {
|
|
13474
|
-
const text =
|
|
13503
|
+
const text = readFileSync13(info.file, "utf-8");
|
|
13475
13504
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
|
|
13476
13505
|
res.end(text);
|
|
13477
13506
|
} catch {
|
|
@@ -13524,9 +13553,9 @@ var init_logs_server = __esm({
|
|
|
13524
13553
|
});
|
|
13525
13554
|
|
|
13526
13555
|
// src/index.ts
|
|
13527
|
-
import { readFileSync as
|
|
13556
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
13528
13557
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13529
|
-
import { dirname as dirname4, join as
|
|
13558
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
13530
13559
|
|
|
13531
13560
|
// src/config.ts
|
|
13532
13561
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -16921,8 +16950,8 @@ if (!IS_HUMAN_CLI) {
|
|
|
16921
16950
|
}
|
|
16922
16951
|
var PKG_VERSION = (() => {
|
|
16923
16952
|
try {
|
|
16924
|
-
const p =
|
|
16925
|
-
return JSON.parse(
|
|
16953
|
+
const p = join16(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
16954
|
+
return JSON.parse(readFileSync14(p, "utf-8")).version || "unknown";
|
|
16926
16955
|
} catch {
|
|
16927
16956
|
return "unknown";
|
|
16928
16957
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -530,8 +530,8 @@ var init_global_install = __esm({
|
|
|
530
530
|
|
|
531
531
|
// src/tui/index.tsx
|
|
532
532
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
533
|
-
import { homedir as
|
|
534
|
-
import { join as
|
|
533
|
+
import { homedir as homedir11 } from "os";
|
|
534
|
+
import { join as join11 } from "path";
|
|
535
535
|
import { render } from "ink";
|
|
536
536
|
|
|
537
537
|
// src/tui/App.tsx
|
|
@@ -4587,7 +4587,26 @@ async function tuiUpdateFlow(onStatus) {
|
|
|
4587
4587
|
}
|
|
4588
4588
|
|
|
4589
4589
|
// src/tui/panels/Settings.tsx
|
|
4590
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync2 } from "fs";
|
|
4591
|
+
import { homedir as homedir10 } from "os";
|
|
4592
|
+
import { join as join10 } from "path";
|
|
4590
4593
|
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4594
|
+
function guestAllowance() {
|
|
4595
|
+
try {
|
|
4596
|
+
const dir = join10(homedir10(), ".solongate");
|
|
4597
|
+
for (const f of readdirSync2(dir)) {
|
|
4598
|
+
if (!f.startsWith(".policy-cache-") || !f.endsWith(".json")) continue;
|
|
4599
|
+
try {
|
|
4600
|
+
const c2 = JSON.parse(readFileSync8(join10(dir, f), "utf-8"));
|
|
4601
|
+
const q = c2?.security?.guestQuota;
|
|
4602
|
+
if (q && typeof q.limit === "number") return { used: q.used ?? 0, limit: q.limit };
|
|
4603
|
+
} catch {
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
} catch {
|
|
4607
|
+
}
|
|
4608
|
+
return null;
|
|
4609
|
+
}
|
|
4591
4610
|
var EVENTS = ["denials", "allowed", "all"];
|
|
4592
4611
|
var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4593
4612
|
var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
|
|
@@ -4639,6 +4658,7 @@ function SettingsPanel({
|
|
|
4639
4658
|
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
4640
4659
|
return () => clearInterval(step);
|
|
4641
4660
|
}, [diagBusy]);
|
|
4661
|
+
void guestAllowance;
|
|
4642
4662
|
const ver = currentVersion();
|
|
4643
4663
|
useEffect7(() => {
|
|
4644
4664
|
let live2 = true;
|
|
@@ -5161,6 +5181,15 @@ function SettingsPanel({
|
|
|
5161
5181
|
cursor(r),
|
|
5162
5182
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
|
|
5163
5183
|
] });
|
|
5184
|
+
case "allowance": {
|
|
5185
|
+
const left = Math.max(0, r.limit - r.used);
|
|
5186
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5187
|
+
cursor(r),
|
|
5188
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "allowance".padEnd(11) }),
|
|
5189
|
+
/* @__PURE__ */ jsx8(Text8, { color: left === 0 ? theme.bad : left <= 20 ? theme.warn : theme.ok, bold: true, children: `${r.used}/${r.limit} tool calls` }),
|
|
5190
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: left === 0 ? " used up \u2014 every call is denied \xB7 create an account to carry on" : ` ${left} left on this guest account \xB7 create an account for no limit` })
|
|
5191
|
+
] });
|
|
5192
|
+
}
|
|
5164
5193
|
case "guard":
|
|
5165
5194
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5166
5195
|
cursor(r),
|
|
@@ -5247,7 +5276,7 @@ function SettingsPanel({
|
|
|
5247
5276
|
] });
|
|
5248
5277
|
}
|
|
5249
5278
|
};
|
|
5250
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
5279
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" || r.kind === "allowance" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
5251
5280
|
const SECTION_DESC = {
|
|
5252
5281
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
5253
5282
|
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
@@ -5552,11 +5581,11 @@ async function launchTui() {
|
|
|
5552
5581
|
return;
|
|
5553
5582
|
}
|
|
5554
5583
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5555
|
-
const debugLog =
|
|
5584
|
+
const debugLog = join11(homedir11(), ".solongate", "dataroom-debug.log");
|
|
5556
5585
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5557
5586
|
const toFile = (level) => (...args) => {
|
|
5558
5587
|
try {
|
|
5559
|
-
mkdirSync7(
|
|
5588
|
+
mkdirSync7(join11(homedir11(), ".solongate"), { recursive: true });
|
|
5560
5589
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5561
5590
|
`);
|
|
5562
5591
|
} catch {
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6536,7 +6536,7 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
|
6536
6536
|
import { homedir } from "node:os";
|
|
6537
6537
|
import { gunzipSync } from "node:zlib";
|
|
6538
6538
|
import { createHash } from "node:crypto";
|
|
6539
|
-
var HOOK_VERSION =
|
|
6539
|
+
var HOOK_VERSION = 69;
|
|
6540
6540
|
function localLogsOnly(security) {
|
|
6541
6541
|
if (security && typeof security === "object") {
|
|
6542
6542
|
const l = security.localLogs;
|
|
@@ -7957,7 +7957,7 @@ function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
|
7957
7957
|
try {
|
|
7958
7958
|
const q = cfg.guestQuota;
|
|
7959
7959
|
if (q && q.exhausted) {
|
|
7960
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on
|
|
7960
|
+
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
7961
7961
|
}
|
|
7962
7962
|
if (cfg.dlpBlock) {
|
|
7963
7963
|
const hit = dlpScan(args, cfg.dlpBlock);
|
package/hooks/guard.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import { createHash } from 'node:crypto';
|
|
|
32
32
|
// the installed hook self-updates when the cloud version is higher (see
|
|
33
33
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
34
34
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
35
|
-
const HOOK_VERSION =
|
|
35
|
+
const HOOK_VERSION = 69;
|
|
36
36
|
|
|
37
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
38
|
// nothing is sent to the cloud audit log.
|
|
@@ -1772,7 +1772,7 @@ function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
|
1772
1772
|
// call through after the allowance is gone.
|
|
1773
1773
|
const q = cfg.guestQuota;
|
|
1774
1774
|
if (q && q.exhausted) {
|
|
1775
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on
|
|
1775
|
+
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
1776
1776
|
}
|
|
1777
1777
|
if (cfg.dlpBlock) {
|
|
1778
1778
|
const hit = dlpScan(args, cfg.dlpBlock);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.98",
|
|
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": {
|