@solongate/proxy 0.59.5 → 0.61.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/agents.d.ts +10 -0
- package/dist/api-client/audit.d.ts +22 -0
- package/dist/api-client/client.d.ts +39 -0
- package/dist/api-client/index.d.ts +18 -0
- package/dist/api-client/policies.d.ts +82 -0
- package/dist/api-client/settings.d.ts +22 -0
- package/dist/api-client/stats.d.ts +9 -0
- package/dist/api-client/types.d.ts +224 -0
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/args.d.ts +15 -0
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/dlp.d.ts +1 -0
- package/dist/commands/format.d.ts +22 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +938 -0
- package/dist/commands/policy.d.ts +1 -0
- package/dist/commands/ratelimit.d.ts +1 -0
- package/dist/commands/stats.d.ts +1 -0
- package/dist/index.js +2156 -284
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/components.d.ts +40 -0
- package/dist/tui/hooks.d.ts +13 -0
- package/dist/tui/index.d.ts +1 -0
- package/dist/tui/index.js +999 -0
- package/dist/tui/panels/Agents.d.ts +3 -0
- package/dist/tui/panels/Audit.d.ts +4 -0
- package/dist/tui/panels/Dlp.d.ts +4 -0
- package/dist/tui/panels/Policies.d.ts +4 -0
- package/dist/tui/panels/RateLimit.d.ts +4 -0
- package/dist/tui/panels/Stats.d.ts +3 -0
- package/dist/tui/theme.d.ts +16 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -132,12 +132,12 @@ async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
|
132
132
|
`);
|
|
133
133
|
try {
|
|
134
134
|
const line = JSON.stringify({ ...entry, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
135
|
-
appendFile(AUDIT_LOG_BACKUP_PATH, line, "utf-8").catch((
|
|
136
|
-
process.stderr.write(`[SolonGate] Audit backup write error: ${
|
|
135
|
+
appendFile(AUDIT_LOG_BACKUP_PATH, line, "utf-8").catch((err2) => {
|
|
136
|
+
process.stderr.write(`[SolonGate] Audit backup write error: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
137
137
|
`);
|
|
138
138
|
});
|
|
139
|
-
} catch (
|
|
140
|
-
process.stderr.write(`[SolonGate] Audit backup write error: ${
|
|
139
|
+
} catch (err2) {
|
|
140
|
+
process.stderr.write(`[SolonGate] Audit backup write error: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
141
141
|
`);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -398,11 +398,11 @@ var require_json = __commonJS({
|
|
|
398
398
|
try {
|
|
399
399
|
JSON.parse(str);
|
|
400
400
|
return true;
|
|
401
|
-
} catch (
|
|
402
|
-
if (
|
|
401
|
+
} catch (err2) {
|
|
402
|
+
if (err2 instanceof SyntaxError) {
|
|
403
403
|
return false;
|
|
404
404
|
}
|
|
405
|
-
throw
|
|
405
|
+
throw err2;
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
module.exports = {
|
|
@@ -765,9 +765,9 @@ var require_PlainValue_516d5bc2 = __commonJS({
|
|
|
765
765
|
}
|
|
766
766
|
}
|
|
767
767
|
const offset = col > 1 ? " ".repeat(col - 1) : "";
|
|
768
|
-
const
|
|
768
|
+
const err2 = "^".repeat(errLen);
|
|
769
769
|
return `${src}
|
|
770
|
-
${offset}${
|
|
770
|
+
${offset}${err2}${errEnd}`;
|
|
771
771
|
}
|
|
772
772
|
var Range = class _Range {
|
|
773
773
|
static copy(orig) {
|
|
@@ -2735,7 +2735,7 @@ var require_parse_cst = __commonJS({
|
|
|
2735
2735
|
};
|
|
2736
2736
|
}
|
|
2737
2737
|
};
|
|
2738
|
-
function
|
|
2738
|
+
function parse2(src) {
|
|
2739
2739
|
const cr = [];
|
|
2740
2740
|
if (src.indexOf("\r") !== -1) {
|
|
2741
2741
|
src = src.replace(/\r\n?/g, (match, offset2) => {
|
|
@@ -2766,7 +2766,7 @@ var require_parse_cst = __commonJS({
|
|
|
2766
2766
|
documents.toString = () => documents.join("...\n");
|
|
2767
2767
|
return documents;
|
|
2768
2768
|
}
|
|
2769
|
-
exports.parse =
|
|
2769
|
+
exports.parse = parse2;
|
|
2770
2770
|
}
|
|
2771
2771
|
});
|
|
2772
2772
|
|
|
@@ -3398,12 +3398,12 @@ ${ctx.indent}`;
|
|
|
3398
3398
|
for (const {
|
|
3399
3399
|
format,
|
|
3400
3400
|
test,
|
|
3401
|
-
resolve:
|
|
3401
|
+
resolve: resolve10
|
|
3402
3402
|
} of tags) {
|
|
3403
3403
|
if (test) {
|
|
3404
3404
|
const match = str.match(test);
|
|
3405
3405
|
if (match) {
|
|
3406
|
-
let res =
|
|
3406
|
+
let res = resolve10.apply(null, match);
|
|
3407
3407
|
if (!(res instanceof Scalar)) res = new Scalar(res);
|
|
3408
3408
|
if (format) res.format = format;
|
|
3409
3409
|
return res;
|
|
@@ -3817,15 +3817,15 @@ ${indent}`);
|
|
|
3817
3817
|
}
|
|
3818
3818
|
if (lastItem && lastItem.char !== char) {
|
|
3819
3819
|
const msg = `Expected ${name} to end with ${char}`;
|
|
3820
|
-
let
|
|
3820
|
+
let err2;
|
|
3821
3821
|
if (typeof lastItem.offset === "number") {
|
|
3822
|
-
|
|
3823
|
-
|
|
3822
|
+
err2 = new PlainValue.YAMLSemanticError(cst, msg);
|
|
3823
|
+
err2.offset = lastItem.offset + 1;
|
|
3824
3824
|
} else {
|
|
3825
|
-
|
|
3826
|
-
if (lastItem.range && lastItem.range.end)
|
|
3825
|
+
err2 = new PlainValue.YAMLSemanticError(lastItem, msg);
|
|
3826
|
+
if (lastItem.range && lastItem.range.end) err2.offset = lastItem.range.end - lastItem.range.start;
|
|
3827
3827
|
}
|
|
3828
|
-
errors.push(
|
|
3828
|
+
errors.push(err2);
|
|
3829
3829
|
}
|
|
3830
3830
|
}
|
|
3831
3831
|
function checkFlowCommentSpace(errors, comment) {
|
|
@@ -4364,9 +4364,9 @@ ${ca}` : ca;
|
|
|
4364
4364
|
continue;
|
|
4365
4365
|
}
|
|
4366
4366
|
const msg = `Flow map contains an unexpected ${char}`;
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
doc.errors.push(
|
|
4367
|
+
const err2 = new PlainValue.YAMLSyntaxError(cst, msg);
|
|
4368
|
+
err2.offset = offset;
|
|
4369
|
+
doc.errors.push(err2);
|
|
4370
4370
|
} else if (item.type === PlainValue.Type.BLANK_LINE) {
|
|
4371
4371
|
comments.push({
|
|
4372
4372
|
afterKey: !!key,
|
|
@@ -4482,9 +4482,9 @@ ${ca}` : ca;
|
|
|
4482
4482
|
key = items.pop();
|
|
4483
4483
|
if (key instanceof Pair) {
|
|
4484
4484
|
const msg = "Chaining flow sequence pairs is invalid";
|
|
4485
|
-
const
|
|
4486
|
-
|
|
4487
|
-
doc.errors.push(
|
|
4485
|
+
const err2 = new PlainValue.YAMLSemanticError(cst, msg);
|
|
4486
|
+
err2.offset = offset;
|
|
4487
|
+
doc.errors.push(err2);
|
|
4488
4488
|
}
|
|
4489
4489
|
if (!explicitKey && typeof keyStart === "number") {
|
|
4490
4490
|
const keyEnd = item.range ? item.range.start : item.offset;
|
|
@@ -4506,9 +4506,9 @@ ${ca}` : ca;
|
|
|
4506
4506
|
next = null;
|
|
4507
4507
|
} else if (next === "[" || char !== "]" || i < cst.items.length - 1) {
|
|
4508
4508
|
const msg = `Flow sequence contains an unexpected ${char}`;
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
doc.errors.push(
|
|
4509
|
+
const err2 = new PlainValue.YAMLSyntaxError(cst, msg);
|
|
4510
|
+
err2.offset = offset;
|
|
4511
|
+
doc.errors.push(err2);
|
|
4512
4512
|
}
|
|
4513
4513
|
} else if (item.type === PlainValue.Type.BLANK_LINE) {
|
|
4514
4514
|
comments.push({
|
|
@@ -6045,7 +6045,7 @@ var require_dist = __commonJS({
|
|
|
6045
6045
|
}
|
|
6046
6046
|
return doc;
|
|
6047
6047
|
}
|
|
6048
|
-
function
|
|
6048
|
+
function parse2(src, options) {
|
|
6049
6049
|
const doc = parseDocument(src, options);
|
|
6050
6050
|
doc.warnings.forEach((warning) => warnings.warn(warning));
|
|
6051
6051
|
if (doc.errors.length > 0) throw doc.errors[0];
|
|
@@ -6060,7 +6060,7 @@ var require_dist = __commonJS({
|
|
|
6060
6060
|
createNode,
|
|
6061
6061
|
defaultOptions: Document$1.defaultOptions,
|
|
6062
6062
|
Document,
|
|
6063
|
-
parse,
|
|
6063
|
+
parse: parse2,
|
|
6064
6064
|
parseAllDocuments,
|
|
6065
6065
|
parseCST: parseCst.parse,
|
|
6066
6066
|
parseDocument,
|
|
@@ -6090,7 +6090,7 @@ var require_yaml2 = __commonJS({
|
|
|
6090
6090
|
"YAMLSyntaxError",
|
|
6091
6091
|
"YAMLWarning"
|
|
6092
6092
|
]);
|
|
6093
|
-
function
|
|
6093
|
+
function parse2(str) {
|
|
6094
6094
|
if (typeof str !== "string") {
|
|
6095
6095
|
return { ok: false, result: void 0 };
|
|
6096
6096
|
}
|
|
@@ -6098,11 +6098,11 @@ var require_yaml2 = __commonJS({
|
|
|
6098
6098
|
try {
|
|
6099
6099
|
global.YAML_SILENCE_WARNINGS = true;
|
|
6100
6100
|
return { ok: true, result: yaml.parse(str) };
|
|
6101
|
-
} catch (
|
|
6102
|
-
if (
|
|
6101
|
+
} catch (err2) {
|
|
6102
|
+
if (err2 && errors.has(err2.name)) {
|
|
6103
6103
|
return { ok: false, result: void 0 };
|
|
6104
6104
|
}
|
|
6105
|
-
throw
|
|
6105
|
+
throw err2;
|
|
6106
6106
|
} finally {
|
|
6107
6107
|
global.YAML_SILENCE_WARNINGS = YAML_SILENCE_WARNINGS_CACHED;
|
|
6108
6108
|
}
|
|
@@ -6110,9 +6110,9 @@ var require_yaml2 = __commonJS({
|
|
|
6110
6110
|
module.exports = {
|
|
6111
6111
|
// is_valid is expected to return nothing if input is invalid otherwise
|
|
6112
6112
|
// true/false for it being valid YAML.
|
|
6113
|
-
"yaml.is_valid": (str) => typeof str === "string" ?
|
|
6113
|
+
"yaml.is_valid": (str) => typeof str === "string" ? parse2(str).ok : void 0,
|
|
6114
6114
|
"yaml.marshal": (data) => yaml.stringify(data),
|
|
6115
|
-
"yaml.unmarshal": (str) =>
|
|
6115
|
+
"yaml.unmarshal": (str) => parse2(str).result
|
|
6116
6116
|
};
|
|
6117
6117
|
}
|
|
6118
6118
|
});
|
|
@@ -6554,6 +6554,1861 @@ var init_cli_utils = __esm({
|
|
|
6554
6554
|
}
|
|
6555
6555
|
});
|
|
6556
6556
|
|
|
6557
|
+
// src/api-client/client.ts
|
|
6558
|
+
var client_exports = {};
|
|
6559
|
+
__export(client_exports, {
|
|
6560
|
+
ApiError: () => ApiError,
|
|
6561
|
+
DEFAULT_API_URL: () => DEFAULT_API_URL2,
|
|
6562
|
+
NotAuthenticatedError: () => NotAuthenticatedError,
|
|
6563
|
+
isAuthenticated: () => isAuthenticated,
|
|
6564
|
+
request: () => request,
|
|
6565
|
+
resolveCredentials: () => resolveCredentials
|
|
6566
|
+
});
|
|
6567
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
6568
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
6569
|
+
import { homedir as homedir2 } from "os";
|
|
6570
|
+
function loginCredentialFile() {
|
|
6571
|
+
try {
|
|
6572
|
+
const p = join4(homedir2(), ".solongate", "cloud-guard.json");
|
|
6573
|
+
if (!existsSync3(p)) return {};
|
|
6574
|
+
const c2 = JSON.parse(readFileSync4(p, "utf-8"));
|
|
6575
|
+
return c2 && typeof c2 === "object" ? c2 : {};
|
|
6576
|
+
} catch {
|
|
6577
|
+
return {};
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
function dotenvApiKey() {
|
|
6581
|
+
try {
|
|
6582
|
+
const envPath = resolve3(".env");
|
|
6583
|
+
if (!existsSync3(envPath)) return void 0;
|
|
6584
|
+
for (const line of readFileSync4(envPath, "utf-8").split("\n")) {
|
|
6585
|
+
const trimmed = line.trim();
|
|
6586
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
6587
|
+
const eq = trimmed.indexOf("=");
|
|
6588
|
+
if (eq === -1) continue;
|
|
6589
|
+
const key = trimmed.slice(0, eq).trim();
|
|
6590
|
+
if (key !== "SOLONGATE_API_KEY") continue;
|
|
6591
|
+
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
6592
|
+
}
|
|
6593
|
+
} catch {
|
|
6594
|
+
}
|
|
6595
|
+
return void 0;
|
|
6596
|
+
}
|
|
6597
|
+
function isAuthenticated() {
|
|
6598
|
+
try {
|
|
6599
|
+
resolveCredentials();
|
|
6600
|
+
return true;
|
|
6601
|
+
} catch {
|
|
6602
|
+
return false;
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
function resolveCredentials(apiUrlOverride) {
|
|
6606
|
+
if (cached && !apiUrlOverride) return cached;
|
|
6607
|
+
const file = loginCredentialFile();
|
|
6608
|
+
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
6609
|
+
if (!apiKey) throw new NotAuthenticatedError();
|
|
6610
|
+
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
|
|
6611
|
+
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
6612
|
+
if (!apiUrlOverride) cached = creds;
|
|
6613
|
+
return creds;
|
|
6614
|
+
}
|
|
6615
|
+
function buildUrl(base, path, query) {
|
|
6616
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
6617
|
+
if (query) {
|
|
6618
|
+
for (const [k, v] of Object.entries(query)) {
|
|
6619
|
+
if (v === void 0) continue;
|
|
6620
|
+
url.searchParams.set(k, String(v));
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
return url.toString();
|
|
6624
|
+
}
|
|
6625
|
+
async function request(method, path, opts = {}) {
|
|
6626
|
+
const creds = resolveCredentials(opts.apiUrl);
|
|
6627
|
+
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
6628
|
+
const headers = {
|
|
6629
|
+
Authorization: `Bearer ${creds.apiKey}`
|
|
6630
|
+
};
|
|
6631
|
+
let bodyInit;
|
|
6632
|
+
if (opts.body !== void 0) {
|
|
6633
|
+
headers["Content-Type"] = "application/json";
|
|
6634
|
+
bodyInit = JSON.stringify(opts.body);
|
|
6635
|
+
}
|
|
6636
|
+
let res;
|
|
6637
|
+
try {
|
|
6638
|
+
res = await fetch(url, {
|
|
6639
|
+
method,
|
|
6640
|
+
headers,
|
|
6641
|
+
body: bodyInit,
|
|
6642
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
6643
|
+
});
|
|
6644
|
+
} catch (err2) {
|
|
6645
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
6646
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6647
|
+
}
|
|
6648
|
+
const text = await res.text().catch(() => "");
|
|
6649
|
+
let json = void 0;
|
|
6650
|
+
if (text) {
|
|
6651
|
+
try {
|
|
6652
|
+
json = JSON.parse(text);
|
|
6653
|
+
} catch {
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
if (!res.ok) {
|
|
6657
|
+
const envelope = json?.error;
|
|
6658
|
+
if (envelope && typeof envelope === "object") {
|
|
6659
|
+
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
6660
|
+
}
|
|
6661
|
+
if (typeof envelope === "string") {
|
|
6662
|
+
throw new ApiError(res.status, "ERROR", envelope);
|
|
6663
|
+
}
|
|
6664
|
+
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
|
|
6665
|
+
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
6666
|
+
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
6667
|
+
}
|
|
6668
|
+
return json;
|
|
6669
|
+
}
|
|
6670
|
+
var DEFAULT_API_URL2, ApiError, NotAuthenticatedError, cached;
|
|
6671
|
+
var init_client = __esm({
|
|
6672
|
+
"src/api-client/client.ts"() {
|
|
6673
|
+
"use strict";
|
|
6674
|
+
DEFAULT_API_URL2 = "https://api.solongate.com";
|
|
6675
|
+
ApiError = class extends Error {
|
|
6676
|
+
status;
|
|
6677
|
+
code;
|
|
6678
|
+
constructor(status, code, message) {
|
|
6679
|
+
super(message);
|
|
6680
|
+
this.name = "ApiError";
|
|
6681
|
+
this.status = status;
|
|
6682
|
+
this.code = code;
|
|
6683
|
+
}
|
|
6684
|
+
};
|
|
6685
|
+
NotAuthenticatedError = class extends Error {
|
|
6686
|
+
constructor() {
|
|
6687
|
+
super("Not logged in. Run `solongate login` first.");
|
|
6688
|
+
this.name = "NotAuthenticatedError";
|
|
6689
|
+
}
|
|
6690
|
+
};
|
|
6691
|
+
cached = null;
|
|
6692
|
+
}
|
|
6693
|
+
});
|
|
6694
|
+
|
|
6695
|
+
// src/tui/theme.ts
|
|
6696
|
+
function decisionColor(decision) {
|
|
6697
|
+
const d = (decision || "").toUpperCase();
|
|
6698
|
+
if (d === "ALLOW") return theme.ok;
|
|
6699
|
+
if (d === "DENY" || d === "DENIED") return theme.bad;
|
|
6700
|
+
return theme.dim;
|
|
6701
|
+
}
|
|
6702
|
+
function modeColor(m) {
|
|
6703
|
+
if (m === "block" || m === "active" || m === "on") return theme.ok;
|
|
6704
|
+
if (m === "detect" || m === "idle") return theme.warn;
|
|
6705
|
+
return theme.dim;
|
|
6706
|
+
}
|
|
6707
|
+
function sparkline(values, width2) {
|
|
6708
|
+
let v = values;
|
|
6709
|
+
if (width2 && v.length > width2) v = v.slice(v.length - width2);
|
|
6710
|
+
if (v.length === 0) return "";
|
|
6711
|
+
const max = Math.max(...v, 0);
|
|
6712
|
+
if (max === 0) return BLOCKS[0].repeat(v.length);
|
|
6713
|
+
return v.map((n) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(n / max * (BLOCKS.length - 1)))]).join("");
|
|
6714
|
+
}
|
|
6715
|
+
function truncate2(s, n) {
|
|
6716
|
+
if (!s) return "";
|
|
6717
|
+
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
6718
|
+
}
|
|
6719
|
+
function ago(ts) {
|
|
6720
|
+
const t = typeof ts === "number" ? ts : Date.parse(ts);
|
|
6721
|
+
if (Number.isNaN(t)) return String(ts ?? "");
|
|
6722
|
+
const s = Math.max(0, (Date.now() - t) / 1e3);
|
|
6723
|
+
if (s < 60) return `${Math.floor(s)}s`;
|
|
6724
|
+
if (s < 3600) return `${Math.floor(s / 60)}m`;
|
|
6725
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
6726
|
+
return `${Math.floor(s / 86400)}d`;
|
|
6727
|
+
}
|
|
6728
|
+
var theme, BLOCKS;
|
|
6729
|
+
var init_theme = __esm({
|
|
6730
|
+
"src/tui/theme.ts"() {
|
|
6731
|
+
"use strict";
|
|
6732
|
+
theme = {
|
|
6733
|
+
accent: "cyan",
|
|
6734
|
+
accentBright: "#5a8ce6",
|
|
6735
|
+
ok: "green",
|
|
6736
|
+
warn: "yellow",
|
|
6737
|
+
bad: "red",
|
|
6738
|
+
dim: "gray"
|
|
6739
|
+
};
|
|
6740
|
+
BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
6741
|
+
}
|
|
6742
|
+
});
|
|
6743
|
+
|
|
6744
|
+
// src/tui/components.tsx
|
|
6745
|
+
import { Box, Text } from "ink";
|
|
6746
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6747
|
+
function DataView({
|
|
6748
|
+
loading,
|
|
6749
|
+
error,
|
|
6750
|
+
empty,
|
|
6751
|
+
emptyText,
|
|
6752
|
+
children
|
|
6753
|
+
}) {
|
|
6754
|
+
if (error) return /* @__PURE__ */ jsxs(Text, { color: theme.bad, children: [
|
|
6755
|
+
"\u2717 ",
|
|
6756
|
+
error
|
|
6757
|
+
] });
|
|
6758
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "Loading\u2026" });
|
|
6759
|
+
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
6760
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
6761
|
+
}
|
|
6762
|
+
function Table({ columns, rows }) {
|
|
6763
|
+
const fit = (s, w) => {
|
|
6764
|
+
const v = s ?? "";
|
|
6765
|
+
if (v.length > w) return v.slice(0, Math.max(0, w - 1)) + "\u2026";
|
|
6766
|
+
return v.padEnd(w);
|
|
6767
|
+
};
|
|
6768
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
6769
|
+
/* @__PURE__ */ jsx(Box, { children: columns.map((c2, i) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: fit(c2.header, c2.width) + " " }, i)) }),
|
|
6770
|
+
rows.map((row, ri) => /* @__PURE__ */ jsx(Box, { children: row.map((cell, ci) => /* @__PURE__ */ jsx(Text, { color: cell.color, dimColor: cell.dim, bold: cell.bold, children: fit(cell.value, columns[ci]?.width ?? 10) + " " }, ci)) }, ri))
|
|
6771
|
+
] });
|
|
6772
|
+
}
|
|
6773
|
+
function KeyHints({ hints }) {
|
|
6774
|
+
return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
6775
|
+
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
|
|
6776
|
+
" ",
|
|
6777
|
+
label,
|
|
6778
|
+
i < hints.length - 1 ? " " : ""
|
|
6779
|
+
] }, i)) });
|
|
6780
|
+
}
|
|
6781
|
+
var init_components = __esm({
|
|
6782
|
+
"src/tui/components.tsx"() {
|
|
6783
|
+
"use strict";
|
|
6784
|
+
init_theme();
|
|
6785
|
+
}
|
|
6786
|
+
});
|
|
6787
|
+
|
|
6788
|
+
// src/api-client/types.ts
|
|
6789
|
+
var init_types = __esm({
|
|
6790
|
+
"src/api-client/types.ts"() {
|
|
6791
|
+
"use strict";
|
|
6792
|
+
}
|
|
6793
|
+
});
|
|
6794
|
+
|
|
6795
|
+
// src/api-client/policies.ts
|
|
6796
|
+
var policies_exports = {};
|
|
6797
|
+
__export(policies_exports, {
|
|
6798
|
+
active: () => active,
|
|
6799
|
+
addRule: () => addRule,
|
|
6800
|
+
create: () => create,
|
|
6801
|
+
dryRun: () => dryRun,
|
|
6802
|
+
get: () => get,
|
|
6803
|
+
list: () => list,
|
|
6804
|
+
remove: () => remove,
|
|
6805
|
+
revokeRule: () => revokeRule,
|
|
6806
|
+
rollback: () => rollback,
|
|
6807
|
+
setActive: () => setActive,
|
|
6808
|
+
update: () => update,
|
|
6809
|
+
versions: () => versions
|
|
6810
|
+
});
|
|
6811
|
+
function list() {
|
|
6812
|
+
return request("GET", "/policies");
|
|
6813
|
+
}
|
|
6814
|
+
function get(id, version) {
|
|
6815
|
+
return request("GET", `/policies/${encodeURIComponent(id)}`, {
|
|
6816
|
+
query: version !== void 0 ? { version } : void 0
|
|
6817
|
+
});
|
|
6818
|
+
}
|
|
6819
|
+
function create(policy) {
|
|
6820
|
+
return request("POST", "/policies", { body: policy });
|
|
6821
|
+
}
|
|
6822
|
+
function update(id, policy) {
|
|
6823
|
+
return request("PUT", `/policies/${encodeURIComponent(id)}`, { body: policy });
|
|
6824
|
+
}
|
|
6825
|
+
function remove(id) {
|
|
6826
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}`);
|
|
6827
|
+
}
|
|
6828
|
+
function addRule(id, spec) {
|
|
6829
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rules`, { body: spec });
|
|
6830
|
+
}
|
|
6831
|
+
function revokeRule(id, ruleId) {
|
|
6832
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}/rules/${encodeURIComponent(ruleId)}`);
|
|
6833
|
+
}
|
|
6834
|
+
function versions(id, opts = {}) {
|
|
6835
|
+
return request("GET", `/policies/${encodeURIComponent(id)}/versions`, { query: opts });
|
|
6836
|
+
}
|
|
6837
|
+
function rollback(id, version) {
|
|
6838
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rollback`, { body: { version } });
|
|
6839
|
+
}
|
|
6840
|
+
function active(agentId) {
|
|
6841
|
+
return request("GET", "/policies/active", { query: agentId ? { agent_id: agentId } : void 0 });
|
|
6842
|
+
}
|
|
6843
|
+
function setActive(policyId) {
|
|
6844
|
+
return request("POST", "/policies/active", { body: { policyId: policyId ?? "" } });
|
|
6845
|
+
}
|
|
6846
|
+
function dryRun(body) {
|
|
6847
|
+
return request("POST", "/policies/dry-run", { body });
|
|
6848
|
+
}
|
|
6849
|
+
var init_policies = __esm({
|
|
6850
|
+
"src/api-client/policies.ts"() {
|
|
6851
|
+
"use strict";
|
|
6852
|
+
init_client();
|
|
6853
|
+
}
|
|
6854
|
+
});
|
|
6855
|
+
|
|
6856
|
+
// src/api-client/settings.ts
|
|
6857
|
+
var settings_exports = {};
|
|
6858
|
+
__export(settings_exports, {
|
|
6859
|
+
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
6860
|
+
getGuardStatus: () => getGuardStatus,
|
|
6861
|
+
getRateLimitHistory: () => getRateLimitHistory,
|
|
6862
|
+
getSecurityLayers: () => getSecurityLayers,
|
|
6863
|
+
setSecurityLayers: () => setSecurityLayers
|
|
6864
|
+
});
|
|
6865
|
+
function getSecurityLayers() {
|
|
6866
|
+
return request("GET", "/settings/security-layers");
|
|
6867
|
+
}
|
|
6868
|
+
function setSecurityLayers(layers) {
|
|
6869
|
+
return request("PUT", "/settings/security-layers", { body: { layers } });
|
|
6870
|
+
}
|
|
6871
|
+
function getRateLimitHistory() {
|
|
6872
|
+
return request("GET", "/settings/rate-limit-history");
|
|
6873
|
+
}
|
|
6874
|
+
function clearRateLimitHistory() {
|
|
6875
|
+
return request("DELETE", "/settings/rate-limit-history", { query: { all: 1 } });
|
|
6876
|
+
}
|
|
6877
|
+
function getGuardStatus() {
|
|
6878
|
+
return request("GET", "/settings/guard-status");
|
|
6879
|
+
}
|
|
6880
|
+
var init_settings = __esm({
|
|
6881
|
+
"src/api-client/settings.ts"() {
|
|
6882
|
+
"use strict";
|
|
6883
|
+
init_client();
|
|
6884
|
+
}
|
|
6885
|
+
});
|
|
6886
|
+
|
|
6887
|
+
// src/api-client/stats.ts
|
|
6888
|
+
var stats_exports = {};
|
|
6889
|
+
__export(stats_exports, {
|
|
6890
|
+
drift: () => drift,
|
|
6891
|
+
get: () => get2,
|
|
6892
|
+
securityInsights: () => securityInsights,
|
|
6893
|
+
timeseries: () => timeseries
|
|
6894
|
+
});
|
|
6895
|
+
function get2() {
|
|
6896
|
+
return request("GET", "/stats");
|
|
6897
|
+
}
|
|
6898
|
+
function timeseries(opts = {}) {
|
|
6899
|
+
return request("GET", "/stats/timeseries", { query: opts });
|
|
6900
|
+
}
|
|
6901
|
+
function drift(days) {
|
|
6902
|
+
return request("GET", "/stats/drift", { query: days !== void 0 ? { days } : void 0 });
|
|
6903
|
+
}
|
|
6904
|
+
function securityInsights(days) {
|
|
6905
|
+
return request("GET", "/stats/security-insights", { query: days !== void 0 ? { days } : void 0 });
|
|
6906
|
+
}
|
|
6907
|
+
var init_stats = __esm({
|
|
6908
|
+
"src/api-client/stats.ts"() {
|
|
6909
|
+
"use strict";
|
|
6910
|
+
init_client();
|
|
6911
|
+
}
|
|
6912
|
+
});
|
|
6913
|
+
|
|
6914
|
+
// src/api-client/audit.ts
|
|
6915
|
+
var audit_exports = {};
|
|
6916
|
+
__export(audit_exports, {
|
|
6917
|
+
list: () => list2,
|
|
6918
|
+
whitelist: () => whitelist
|
|
6919
|
+
});
|
|
6920
|
+
function list2(query = {}) {
|
|
6921
|
+
return request("GET", "/audit-logs", { query });
|
|
6922
|
+
}
|
|
6923
|
+
function whitelist(id, scope = "exact") {
|
|
6924
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
6925
|
+
}
|
|
6926
|
+
var init_audit = __esm({
|
|
6927
|
+
"src/api-client/audit.ts"() {
|
|
6928
|
+
"use strict";
|
|
6929
|
+
init_client();
|
|
6930
|
+
}
|
|
6931
|
+
});
|
|
6932
|
+
|
|
6933
|
+
// src/api-client/agents.ts
|
|
6934
|
+
var agents_exports = {};
|
|
6935
|
+
__export(agents_exports, {
|
|
6936
|
+
anomalies: () => anomalies,
|
|
6937
|
+
get: () => get3,
|
|
6938
|
+
live: () => live
|
|
6939
|
+
});
|
|
6940
|
+
function live(opts = {}) {
|
|
6941
|
+
return request("GET", "/agents/live", {
|
|
6942
|
+
query: { limit: opts.limit, include_deactivated: opts.includeDeactivated ? 1 : void 0 }
|
|
6943
|
+
});
|
|
6944
|
+
}
|
|
6945
|
+
function get3(id, scan = false) {
|
|
6946
|
+
return request("GET", `/agents/${encodeURIComponent(id)}`, { query: scan ? { scan: 1 } : void 0 });
|
|
6947
|
+
}
|
|
6948
|
+
function anomalies(id, limit) {
|
|
6949
|
+
return request("GET", `/agents/${encodeURIComponent(id)}/anomalies`, {
|
|
6950
|
+
query: limit !== void 0 ? { limit } : void 0
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
var init_agents = __esm({
|
|
6954
|
+
"src/api-client/agents.ts"() {
|
|
6955
|
+
"use strict";
|
|
6956
|
+
init_client();
|
|
6957
|
+
}
|
|
6958
|
+
});
|
|
6959
|
+
|
|
6960
|
+
// src/api-client/index.ts
|
|
6961
|
+
var api;
|
|
6962
|
+
var init_api_client = __esm({
|
|
6963
|
+
"src/api-client/index.ts"() {
|
|
6964
|
+
"use strict";
|
|
6965
|
+
init_client();
|
|
6966
|
+
init_types();
|
|
6967
|
+
init_policies();
|
|
6968
|
+
init_settings();
|
|
6969
|
+
init_stats();
|
|
6970
|
+
init_audit();
|
|
6971
|
+
init_agents();
|
|
6972
|
+
api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports };
|
|
6973
|
+
}
|
|
6974
|
+
});
|
|
6975
|
+
|
|
6976
|
+
// src/tui/hooks.ts
|
|
6977
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6978
|
+
function useLoader(fn, deps = []) {
|
|
6979
|
+
const [data, setData] = useState(null);
|
|
6980
|
+
const [error, setError] = useState(null);
|
|
6981
|
+
const [loading, setLoading] = useState(true);
|
|
6982
|
+
const [nonce, setNonce] = useState(0);
|
|
6983
|
+
const alive = useRef(true);
|
|
6984
|
+
const fnRef = useRef(fn);
|
|
6985
|
+
fnRef.current = fn;
|
|
6986
|
+
useEffect(() => {
|
|
6987
|
+
alive.current = true;
|
|
6988
|
+
return () => {
|
|
6989
|
+
alive.current = false;
|
|
6990
|
+
};
|
|
6991
|
+
}, []);
|
|
6992
|
+
useEffect(() => {
|
|
6993
|
+
setLoading(true);
|
|
6994
|
+
fnRef.current().then((d) => {
|
|
6995
|
+
if (!alive.current) return;
|
|
6996
|
+
setData(d);
|
|
6997
|
+
setError(null);
|
|
6998
|
+
}).catch((e) => {
|
|
6999
|
+
if (!alive.current) return;
|
|
7000
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
7001
|
+
}).finally(() => {
|
|
7002
|
+
if (alive.current) setLoading(false);
|
|
7003
|
+
});
|
|
7004
|
+
}, [nonce, ...deps]);
|
|
7005
|
+
const reload = useCallback(() => setNonce((n) => n + 1), []);
|
|
7006
|
+
return { data, error, loading, reload };
|
|
7007
|
+
}
|
|
7008
|
+
function usePoll(reload, intervalMs, enabled = true) {
|
|
7009
|
+
useEffect(() => {
|
|
7010
|
+
if (!enabled) return;
|
|
7011
|
+
const t = setInterval(reload, intervalMs);
|
|
7012
|
+
return () => clearInterval(t);
|
|
7013
|
+
}, [reload, intervalMs, enabled]);
|
|
7014
|
+
}
|
|
7015
|
+
var init_hooks = __esm({
|
|
7016
|
+
"src/tui/hooks.ts"() {
|
|
7017
|
+
"use strict";
|
|
7018
|
+
}
|
|
7019
|
+
});
|
|
7020
|
+
|
|
7021
|
+
// src/tui/panels/Policies.tsx
|
|
7022
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7023
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
7024
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7025
|
+
function PoliciesPanel({ focused }) {
|
|
7026
|
+
const list4 = useLoader(() => api.policies.list());
|
|
7027
|
+
const policies = list4.data?.policies ?? [];
|
|
7028
|
+
const [view, setView] = useState2("list");
|
|
7029
|
+
const [pi, setPi] = useState2(0);
|
|
7030
|
+
const [ri, setRi] = useState2(0);
|
|
7031
|
+
const [rules, setRules] = useState2(null);
|
|
7032
|
+
const [mode, setMode] = useState2("denylist");
|
|
7033
|
+
const [status, setStatus] = useState2(null);
|
|
7034
|
+
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
7035
|
+
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
7036
|
+
useEffect2(() => {
|
|
7037
|
+
if (detail.data) {
|
|
7038
|
+
setRules(detail.data.rules);
|
|
7039
|
+
setMode(detail.data.mode ?? "denylist");
|
|
7040
|
+
setRi(0);
|
|
7041
|
+
}
|
|
7042
|
+
}, [detail.data]);
|
|
7043
|
+
const persist = async (nextRules, nextMode) => {
|
|
7044
|
+
if (!detail.data || !selected) return;
|
|
7045
|
+
setStatus("Saving\u2026");
|
|
7046
|
+
try {
|
|
7047
|
+
const res = await api.policies.update(selected.id, {
|
|
7048
|
+
id: selected.id,
|
|
7049
|
+
name: detail.data.name,
|
|
7050
|
+
description: detail.data.description,
|
|
7051
|
+
mode: nextMode,
|
|
7052
|
+
agents: detail.data.agents,
|
|
7053
|
+
rules: nextRules
|
|
7054
|
+
});
|
|
7055
|
+
setRules(res.rules);
|
|
7056
|
+
setStatus("\u2713 Saved v" + res._version);
|
|
7057
|
+
list4.reload();
|
|
7058
|
+
} catch (e) {
|
|
7059
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7060
|
+
}
|
|
7061
|
+
};
|
|
7062
|
+
useInput(
|
|
7063
|
+
(input, key) => {
|
|
7064
|
+
if (view === "list") {
|
|
7065
|
+
if (key.upArrow) setPi((n) => Math.max(0, n - 1));
|
|
7066
|
+
else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
|
|
7067
|
+
else if (key.return || key.rightArrow) {
|
|
7068
|
+
setStatus(null);
|
|
7069
|
+
setView("detail");
|
|
7070
|
+
}
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
7073
|
+
const rs2 = rules ?? [];
|
|
7074
|
+
if (key.leftArrow) {
|
|
7075
|
+
setView("list");
|
|
7076
|
+
setStatus(null);
|
|
7077
|
+
} else if (key.upArrow) setRi((n) => Math.max(0, n - 1));
|
|
7078
|
+
else if (key.downArrow) setRi((n) => Math.min(rs2.length - 1, n + 1));
|
|
7079
|
+
else if (input === " " || input === "e") {
|
|
7080
|
+
if (!rs2[ri]) return;
|
|
7081
|
+
const next = rs2.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r);
|
|
7082
|
+
setRules(next);
|
|
7083
|
+
void persist(next, mode);
|
|
7084
|
+
} else if (input === "d") {
|
|
7085
|
+
const target = rs2[ri];
|
|
7086
|
+
if (!target || !selected) return;
|
|
7087
|
+
setStatus("Deleting\u2026");
|
|
7088
|
+
void api.policies.revokeRule(selected.id, target.id).then(() => {
|
|
7089
|
+
setStatus("\u2713 Deleted");
|
|
7090
|
+
detail.reload();
|
|
7091
|
+
list4.reload();
|
|
7092
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
7093
|
+
} else if (input === "m") {
|
|
7094
|
+
const nm = mode === "denylist" ? "whitelist" : "denylist";
|
|
7095
|
+
setMode(nm);
|
|
7096
|
+
void persist(rs2, nm);
|
|
7097
|
+
}
|
|
7098
|
+
},
|
|
7099
|
+
{ isActive: focused }
|
|
7100
|
+
);
|
|
7101
|
+
if (view === "list") {
|
|
7102
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: list4.loading && !list4.data, error: list4.error, empty: !!list4.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
7103
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
|
|
7104
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
|
|
7105
|
+
(i === pi ? "\u25B8 " : " ") + truncate2(p.name, 26).padEnd(27),
|
|
7106
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7107
|
+
p.mode.padEnd(10),
|
|
7108
|
+
" ",
|
|
7109
|
+
p.rules.length,
|
|
7110
|
+
" rules \xB7 v",
|
|
7111
|
+
p.version
|
|
7112
|
+
] })
|
|
7113
|
+
] }, p.id)) })
|
|
7114
|
+
] }) });
|
|
7115
|
+
}
|
|
7116
|
+
const rs = rules ?? [];
|
|
7117
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: detail.loading && !rules, error: detail.error, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
7118
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7119
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 30) }),
|
|
7120
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " mode: " }),
|
|
7121
|
+
/* @__PURE__ */ jsx2(Text2, { color: mode === "whitelist" ? theme.ok : void 0, children: mode })
|
|
7122
|
+
] }),
|
|
7123
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2191\u2193 rule \xB7 space toggle \xB7 d delete \xB7 m mode \xB7 \u2190 back" }),
|
|
7124
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(
|
|
7125
|
+
Table,
|
|
7126
|
+
{
|
|
7127
|
+
columns: [
|
|
7128
|
+
{ header: "", width: 2 },
|
|
7129
|
+
{ header: "EFFECT", width: 7 },
|
|
7130
|
+
{ header: "PRIO", width: 4 },
|
|
7131
|
+
{ header: "TOOL", width: 20 },
|
|
7132
|
+
{ header: "DESCRIPTION", width: 34 }
|
|
7133
|
+
],
|
|
7134
|
+
rows: rs.map((r, i) => [
|
|
7135
|
+
{ value: i === ri ? "\u25B8" : "", color: theme.accentBright },
|
|
7136
|
+
{ value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
|
|
7137
|
+
{ value: String(r.priority), dim: true },
|
|
7138
|
+
{ value: (r.enabled ? "" : "\u2298 ") + truncate2(r.toolPattern, 18), color: theme.accent, dim: !r.enabled },
|
|
7139
|
+
{ value: truncate2(r.description || "\u2014", 34), dim: !r.enabled }
|
|
7140
|
+
])
|
|
7141
|
+
}
|
|
7142
|
+
) }),
|
|
7143
|
+
rs.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "(no rules)" }) : null,
|
|
7144
|
+
status ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
7145
|
+
] }) });
|
|
7146
|
+
}
|
|
7147
|
+
var init_Policies = __esm({
|
|
7148
|
+
"src/tui/panels/Policies.tsx"() {
|
|
7149
|
+
"use strict";
|
|
7150
|
+
init_api_client();
|
|
7151
|
+
init_components();
|
|
7152
|
+
init_hooks();
|
|
7153
|
+
init_theme();
|
|
7154
|
+
}
|
|
7155
|
+
});
|
|
7156
|
+
|
|
7157
|
+
// src/tui/panels/RateLimit.tsx
|
|
7158
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
7159
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
7160
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7161
|
+
function RateLimitPanel({ focused }) {
|
|
7162
|
+
const layersQ = useLoader(() => api.settings.getSecurityLayers());
|
|
7163
|
+
const historyQ = useLoader(() => api.settings.getRateLimitHistory());
|
|
7164
|
+
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
7165
|
+
const [draft, setDraft] = useState3(null);
|
|
7166
|
+
const [dirty, setDirty] = useState3(false);
|
|
7167
|
+
const [fi, setFi] = useState3(0);
|
|
7168
|
+
const [status, setStatus] = useState3(null);
|
|
7169
|
+
useEffect3(() => {
|
|
7170
|
+
if (layersQ.data && !draft) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
7171
|
+
}, [layersQ.data, draft]);
|
|
7172
|
+
const adjust = (dir, step) => {
|
|
7173
|
+
if (!draft) return;
|
|
7174
|
+
const field = FIELDS[fi];
|
|
7175
|
+
if (field === "mode") {
|
|
7176
|
+
const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
|
|
7177
|
+
setDraft({ ...draft, mode: MODES[idx] });
|
|
7178
|
+
} else {
|
|
7179
|
+
setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
7180
|
+
}
|
|
7181
|
+
setDirty(true);
|
|
7182
|
+
setStatus(null);
|
|
7183
|
+
};
|
|
7184
|
+
const save = async () => {
|
|
7185
|
+
if (!draft || !layersQ.data) return;
|
|
7186
|
+
setStatus("Saving\u2026");
|
|
7187
|
+
try {
|
|
7188
|
+
const next = { ...layersQ.data.layers, rateLimit: draft };
|
|
7189
|
+
const res = await api.settings.setSecurityLayers(next);
|
|
7190
|
+
setDraft({ ...res.layers.rateLimit });
|
|
7191
|
+
setDirty(false);
|
|
7192
|
+
setStatus("\u2713 Saved");
|
|
7193
|
+
historyQ.reload();
|
|
7194
|
+
} catch (e) {
|
|
7195
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7196
|
+
}
|
|
7197
|
+
};
|
|
7198
|
+
useInput2(
|
|
7199
|
+
(input, key) => {
|
|
7200
|
+
const step = key.shift ? 10 : 1;
|
|
7201
|
+
if (key.upArrow) setFi((n) => (n - 1 + FIELDS.length) % FIELDS.length);
|
|
7202
|
+
else if (key.downArrow) setFi((n) => (n + 1) % FIELDS.length);
|
|
7203
|
+
else if (key.leftArrow) adjust(-1, step);
|
|
7204
|
+
else if (key.rightArrow) adjust(1, step);
|
|
7205
|
+
else if (input === "s") void save();
|
|
7206
|
+
},
|
|
7207
|
+
{ isActive: focused }
|
|
7208
|
+
);
|
|
7209
|
+
const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
|
|
7210
|
+
const bursts = (insightsQ.data?.["anomalies"] ?? []).slice(0, 6);
|
|
7211
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7212
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
|
|
7213
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7214
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx3(Text3, { color: modeColor(draft.mode), children: draft.mode }) }),
|
|
7215
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perMinute }) }),
|
|
7216
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
7217
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
7218
|
+
] }),
|
|
7219
|
+
dirty || status ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
7220
|
+
dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
7221
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7222
|
+
] }) : null,
|
|
7223
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7224
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7225
|
+
"Change history ",
|
|
7226
|
+
history.length ? `(${history.length})` : ""
|
|
7227
|
+
] }),
|
|
7228
|
+
history.length ? /* @__PURE__ */ jsx3(
|
|
7229
|
+
Table,
|
|
7230
|
+
{
|
|
7231
|
+
columns: [
|
|
7232
|
+
{ header: "WHEN", width: 8 },
|
|
7233
|
+
{ header: "/MIN", width: 6 },
|
|
7234
|
+
{ header: "/HOUR", width: 7 },
|
|
7235
|
+
{ header: "/DAY", width: 6 }
|
|
7236
|
+
],
|
|
7237
|
+
rows: history.slice(0, 5).map((h, i) => [
|
|
7238
|
+
{ value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
|
|
7239
|
+
{ value: String(h.minute), bold: i === 0 },
|
|
7240
|
+
{ value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
|
|
7241
|
+
{ value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
|
|
7242
|
+
])
|
|
7243
|
+
}
|
|
7244
|
+
) : /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " no changes" })
|
|
7245
|
+
] }),
|
|
7246
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7247
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7248
|
+
"Recent bursts ",
|
|
7249
|
+
insightsQ.loading && !insightsQ.data ? "\u2026" : `(${bursts.length})`
|
|
7250
|
+
] }),
|
|
7251
|
+
bursts.length ? /* @__PURE__ */ jsx3(
|
|
7252
|
+
Table,
|
|
7253
|
+
{
|
|
7254
|
+
columns: [
|
|
7255
|
+
{ header: "RESULT", width: 9 },
|
|
7256
|
+
{ header: "AGENT", width: 16 },
|
|
7257
|
+
{ header: "CALLS/LIMIT", width: 12 },
|
|
7258
|
+
{ header: "WHEN", width: 8 }
|
|
7259
|
+
],
|
|
7260
|
+
rows: bursts.map((b) => [
|
|
7261
|
+
{ value: b.blocked ? "blocked" : "BYPASSED", color: b.blocked ? theme.accent : theme.bad, bold: !b.blocked },
|
|
7262
|
+
{ value: (b.agent || "\u2014").slice(0, 16), dim: true },
|
|
7263
|
+
{ value: `${b.count}/${b.limit}` },
|
|
7264
|
+
{ value: ago(b.minute) + " ago", dim: true }
|
|
7265
|
+
])
|
|
7266
|
+
}
|
|
7267
|
+
) : /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7268
|
+
" ",
|
|
7269
|
+
insightsQ.error ? insightsQ.error : "none in 7d"
|
|
7270
|
+
] })
|
|
7271
|
+
] })
|
|
7272
|
+
] }) : null });
|
|
7273
|
+
}
|
|
7274
|
+
function FieldRow({ label, active: active2, children }) {
|
|
7275
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7276
|
+
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
|
|
7277
|
+
children
|
|
7278
|
+
] });
|
|
7279
|
+
}
|
|
7280
|
+
var MODES, FIELDS;
|
|
7281
|
+
var init_RateLimit = __esm({
|
|
7282
|
+
"src/tui/panels/RateLimit.tsx"() {
|
|
7283
|
+
"use strict";
|
|
7284
|
+
init_api_client();
|
|
7285
|
+
init_components();
|
|
7286
|
+
init_hooks();
|
|
7287
|
+
init_theme();
|
|
7288
|
+
MODES = ["off", "detect", "block"];
|
|
7289
|
+
FIELDS = ["mode", "perMinute", "perHour", "perDay"];
|
|
7290
|
+
}
|
|
7291
|
+
});
|
|
7292
|
+
|
|
7293
|
+
// src/tui/panels/Dlp.tsx
|
|
7294
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
7295
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
7296
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7297
|
+
function DlpPanel({ focused }) {
|
|
7298
|
+
const q = useLoader(() => api.settings.getSecurityLayers());
|
|
7299
|
+
const [dlp, setDlp] = useState4(null);
|
|
7300
|
+
const [available, setAvailable] = useState4([]);
|
|
7301
|
+
const [sel, setSel] = useState4(0);
|
|
7302
|
+
const [status, setStatus] = useState4(null);
|
|
7303
|
+
useEffect4(() => {
|
|
7304
|
+
if (q.data && !dlp) {
|
|
7305
|
+
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
|
|
7306
|
+
setAvailable(q.data.availablePatterns);
|
|
7307
|
+
}
|
|
7308
|
+
}, [q.data, dlp]);
|
|
7309
|
+
const persist = async (nextDlp) => {
|
|
7310
|
+
if (!q.data) return;
|
|
7311
|
+
setDlp(nextDlp);
|
|
7312
|
+
setStatus("Saving\u2026");
|
|
7313
|
+
try {
|
|
7314
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp: nextDlp });
|
|
7315
|
+
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns] });
|
|
7316
|
+
setStatus("\u2713 Saved");
|
|
7317
|
+
} catch (e) {
|
|
7318
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7319
|
+
}
|
|
7320
|
+
};
|
|
7321
|
+
useInput3(
|
|
7322
|
+
(input, key) => {
|
|
7323
|
+
if (!dlp) return;
|
|
7324
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7325
|
+
else if (key.downArrow) setSel((n) => Math.min(available.length - 1, n + 1));
|
|
7326
|
+
else if (input === " " || key.return) {
|
|
7327
|
+
const p = available[sel];
|
|
7328
|
+
if (!p) return;
|
|
7329
|
+
const set = new Set(dlp.patterns);
|
|
7330
|
+
if (set.has(p)) set.delete(p);
|
|
7331
|
+
else set.add(p);
|
|
7332
|
+
void persist({ ...dlp, patterns: [...set] });
|
|
7333
|
+
} else if (input === "m") {
|
|
7334
|
+
const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
|
|
7335
|
+
void persist({ ...dlp, mode: MODES2[idx] });
|
|
7336
|
+
}
|
|
7337
|
+
},
|
|
7338
|
+
{ isActive: focused }
|
|
7339
|
+
);
|
|
7340
|
+
const enabled = new Set(dlp?.patterns ?? []);
|
|
7341
|
+
return /* @__PURE__ */ jsx4(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
7342
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7343
|
+
/* @__PURE__ */ jsx4(Text4, { children: "mode: " }),
|
|
7344
|
+
/* @__PURE__ */ jsx4(Text4, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
7345
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? " \u2191\u2193 move \xB7 space toggle \xB7 m mode" : " press \u2192 to edit" })
|
|
7346
|
+
] }),
|
|
7347
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
7348
|
+
const on = enabled.has(p);
|
|
7349
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: focused && i === sel ? theme.accentBright : void 0, bold: focused && i === sel, children: [
|
|
7350
|
+
(focused && i === sel ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
|
|
7351
|
+
/* @__PURE__ */ jsx4(Text4, { color: on ? theme.ok : theme.dim, children: p })
|
|
7352
|
+
] }, p);
|
|
7353
|
+
}) }),
|
|
7354
|
+
dlp.custom.length ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
7355
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Custom:" }),
|
|
7356
|
+
dlp.custom.map((c2) => /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
7357
|
+
" ",
|
|
7358
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: c2.name }),
|
|
7359
|
+
" ",
|
|
7360
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: c2.re })
|
|
7361
|
+
] }, c2.name))
|
|
7362
|
+
] }) : null,
|
|
7363
|
+
status ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
7364
|
+
] }) : null });
|
|
7365
|
+
}
|
|
7366
|
+
var MODES2;
|
|
7367
|
+
var init_Dlp = __esm({
|
|
7368
|
+
"src/tui/panels/Dlp.tsx"() {
|
|
7369
|
+
"use strict";
|
|
7370
|
+
init_api_client();
|
|
7371
|
+
init_components();
|
|
7372
|
+
init_hooks();
|
|
7373
|
+
init_theme();
|
|
7374
|
+
MODES2 = ["off", "detect", "block"];
|
|
7375
|
+
}
|
|
7376
|
+
});
|
|
7377
|
+
|
|
7378
|
+
// src/tui/panels/Stats.tsx
|
|
7379
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
7380
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
7381
|
+
function StatsPanel({ active: active2 }) {
|
|
7382
|
+
const stats = useLoader(() => api.stats.get());
|
|
7383
|
+
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
7384
|
+
usePoll(() => {
|
|
7385
|
+
stats.reload();
|
|
7386
|
+
ts.reload();
|
|
7387
|
+
}, 5e3, active2);
|
|
7388
|
+
const s = stats.data;
|
|
7389
|
+
const points = ts.data?.timeseries ?? [];
|
|
7390
|
+
return /* @__PURE__ */ jsx5(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
7391
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7392
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
|
|
7393
|
+
s.total_calls,
|
|
7394
|
+
" "
|
|
7395
|
+
] }),
|
|
7396
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "calls " }),
|
|
7397
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.ok, children: [
|
|
7398
|
+
s.allowed,
|
|
7399
|
+
" allowed"
|
|
7400
|
+
] }),
|
|
7401
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " }),
|
|
7402
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.bad, children: [
|
|
7403
|
+
s.denied,
|
|
7404
|
+
" denied"
|
|
7405
|
+
] })
|
|
7406
|
+
] }),
|
|
7407
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
|
|
7408
|
+
s.active_policies,
|
|
7409
|
+
" policies \xB7 ",
|
|
7410
|
+
s.registered_tools,
|
|
7411
|
+
" tools"
|
|
7412
|
+
] }),
|
|
7413
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
7414
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
|
|
7415
|
+
"Last 24h ",
|
|
7416
|
+
ts.data ? `(per ${ts.data.granularity})` : ""
|
|
7417
|
+
] }),
|
|
7418
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7419
|
+
/* @__PURE__ */ jsx5(Text5, { children: "total " }),
|
|
7420
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
|
|
7421
|
+
] }),
|
|
7422
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7423
|
+
/* @__PURE__ */ jsx5(Text5, { children: "allowed " }),
|
|
7424
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
|
|
7425
|
+
] }),
|
|
7426
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7427
|
+
/* @__PURE__ */ jsx5(Text5, { children: "denied " }),
|
|
7428
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
|
|
7429
|
+
] })
|
|
7430
|
+
] }),
|
|
7431
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
7432
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Recent" }),
|
|
7433
|
+
/* @__PURE__ */ jsx5(
|
|
7434
|
+
Table,
|
|
7435
|
+
{
|
|
7436
|
+
columns: [
|
|
7437
|
+
{ header: "DECISION", width: 9 },
|
|
7438
|
+
{ header: "TOOL", width: 20 },
|
|
7439
|
+
{ header: "MS", width: 5 },
|
|
7440
|
+
{ header: "WHEN", width: 6 }
|
|
7441
|
+
],
|
|
7442
|
+
rows: (s.recent_activity ?? []).slice(0, 8).map((a) => [
|
|
7443
|
+
{ value: a.decision, color: decisionColor(a.decision) },
|
|
7444
|
+
{ value: truncate2(a.tool_name, 20), color: theme.accent },
|
|
7445
|
+
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
7446
|
+
{ value: ago(a.created_at), dim: true }
|
|
7447
|
+
])
|
|
7448
|
+
}
|
|
7449
|
+
)
|
|
7450
|
+
] })
|
|
7451
|
+
] }) : null });
|
|
7452
|
+
}
|
|
7453
|
+
var init_Stats = __esm({
|
|
7454
|
+
"src/tui/panels/Stats.tsx"() {
|
|
7455
|
+
"use strict";
|
|
7456
|
+
init_api_client();
|
|
7457
|
+
init_components();
|
|
7458
|
+
init_hooks();
|
|
7459
|
+
init_theme();
|
|
7460
|
+
}
|
|
7461
|
+
});
|
|
7462
|
+
|
|
7463
|
+
// src/tui/panels/Audit.tsx
|
|
7464
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
7465
|
+
import { useState as useState5 } from "react";
|
|
7466
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
7467
|
+
function AuditPanel({ active: active2, focused }) {
|
|
7468
|
+
const [fi, setFi] = useState5(0);
|
|
7469
|
+
const filter = FILTERS[fi];
|
|
7470
|
+
const audit = useLoader(() => api.audit.list({ filter, limit: 14 }), [fi]);
|
|
7471
|
+
usePoll(audit.reload, 5e3, active2);
|
|
7472
|
+
useInput4(
|
|
7473
|
+
(input) => {
|
|
7474
|
+
if (input === "f") setFi((n) => (n + 1) % FILTERS.length);
|
|
7475
|
+
},
|
|
7476
|
+
{ isActive: focused }
|
|
7477
|
+
);
|
|
7478
|
+
const entries = audit.data?.entries ?? [];
|
|
7479
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: audit.loading && !audit.data, error: audit.error, empty: !!audit.data && entries.length === 0, emptyText: "No audit entries.", children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
7480
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
7481
|
+
audit.data?.total ?? 0,
|
|
7482
|
+
" entries \xB7 filter: ",
|
|
7483
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: filter ?? "all" }),
|
|
7484
|
+
" (press f)"
|
|
7485
|
+
] }),
|
|
7486
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
|
|
7487
|
+
Table,
|
|
7488
|
+
{
|
|
7489
|
+
columns: [
|
|
7490
|
+
{ header: "DECISION", width: 9 },
|
|
7491
|
+
{ header: "TOOL", width: 18 },
|
|
7492
|
+
{ header: "AGENT", width: 14 },
|
|
7493
|
+
{ header: "REASON", width: 26 },
|
|
7494
|
+
{ header: "DLP", width: 4 },
|
|
7495
|
+
{ header: "WHEN", width: 6 }
|
|
7496
|
+
],
|
|
7497
|
+
rows: entries.map((e) => [
|
|
7498
|
+
{ value: e.decision, color: decisionColor(e.decision) },
|
|
7499
|
+
{ value: truncate2(e.tool_name, 18), color: theme.accent },
|
|
7500
|
+
{ value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
|
|
7501
|
+
{ value: truncate2(e.reason ?? "\u2014", 26) },
|
|
7502
|
+
{ value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
|
|
7503
|
+
{ value: ago(e.created_at), dim: true }
|
|
7504
|
+
])
|
|
7505
|
+
}
|
|
7506
|
+
) })
|
|
7507
|
+
] }) });
|
|
7508
|
+
}
|
|
7509
|
+
var FILTERS;
|
|
7510
|
+
var init_Audit = __esm({
|
|
7511
|
+
"src/tui/panels/Audit.tsx"() {
|
|
7512
|
+
"use strict";
|
|
7513
|
+
init_api_client();
|
|
7514
|
+
init_components();
|
|
7515
|
+
init_hooks();
|
|
7516
|
+
init_theme();
|
|
7517
|
+
FILTERS = [void 0, "DENY", "ALLOW"];
|
|
7518
|
+
}
|
|
7519
|
+
});
|
|
7520
|
+
|
|
7521
|
+
// src/tui/panels/Agents.tsx
|
|
7522
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
7523
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7524
|
+
function AgentsPanel({ active: active2 }) {
|
|
7525
|
+
const agents = useLoader(() => api.agents.live({ limit: 14 }));
|
|
7526
|
+
usePoll(agents.reload, 4e3, active2);
|
|
7527
|
+
const data = agents.data;
|
|
7528
|
+
const rows = data?.agents ?? [];
|
|
7529
|
+
return /* @__PURE__ */ jsx7(DataView, { loading: agents.loading && !data, error: agents.error, empty: !!data && rows.length === 0, emptyText: "No agent sessions.", children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
7530
|
+
data ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7531
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
|
|
7532
|
+
data.counts.active,
|
|
7533
|
+
" active"
|
|
7534
|
+
] }),
|
|
7535
|
+
" \xB7 ",
|
|
7536
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
7537
|
+
data.counts.idle,
|
|
7538
|
+
" idle"
|
|
7539
|
+
] }),
|
|
7540
|
+
" \xB7 ",
|
|
7541
|
+
data.counts.deactivated,
|
|
7542
|
+
" off"
|
|
7543
|
+
] }) : null,
|
|
7544
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(
|
|
7545
|
+
Table,
|
|
7546
|
+
{
|
|
7547
|
+
columns: [
|
|
7548
|
+
{ header: "STATUS", width: 7 },
|
|
7549
|
+
{ header: "AGENT", width: 20 },
|
|
7550
|
+
{ header: "CALLS", width: 6 },
|
|
7551
|
+
{ header: "DENY", width: 5 },
|
|
7552
|
+
{ header: "DLP", width: 4 },
|
|
7553
|
+
{ header: "TRUST", width: 6 },
|
|
7554
|
+
{ header: "CHARACTER", width: 20 }
|
|
7555
|
+
],
|
|
7556
|
+
rows: rows.map((a) => [
|
|
7557
|
+
{ value: a.status, color: modeColor(a.status) },
|
|
7558
|
+
{ value: truncate2(a.agent_name ?? a.agent_id ?? a.session_id, 20), color: theme.accent },
|
|
7559
|
+
{ value: String(a.total_calls) },
|
|
7560
|
+
{ value: String(a.denied_calls), color: a.denied_calls ? theme.bad : void 0, dim: !a.denied_calls },
|
|
7561
|
+
{ value: String(a.dlp_events), color: a.dlp_events ? theme.bad : void 0, dim: !a.dlp_events },
|
|
7562
|
+
{ value: `${a.trust_score}` },
|
|
7563
|
+
{ value: truncate2(a.character || "\u2014", 20), dim: true }
|
|
7564
|
+
])
|
|
7565
|
+
}
|
|
7566
|
+
) })
|
|
7567
|
+
] }) });
|
|
7568
|
+
}
|
|
7569
|
+
var init_Agents = __esm({
|
|
7570
|
+
"src/tui/panels/Agents.tsx"() {
|
|
7571
|
+
"use strict";
|
|
7572
|
+
init_api_client();
|
|
7573
|
+
init_components();
|
|
7574
|
+
init_hooks();
|
|
7575
|
+
init_theme();
|
|
7576
|
+
}
|
|
7577
|
+
});
|
|
7578
|
+
|
|
7579
|
+
// src/tui/App.tsx
|
|
7580
|
+
import { Box as Box8, Text as Text8, useApp, useInput as useInput5 } from "ink";
|
|
7581
|
+
import { useState as useState6 } from "react";
|
|
7582
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
7583
|
+
function Banner() {
|
|
7584
|
+
const wide = (process.stdout.columns ?? 80) >= 82;
|
|
7585
|
+
if (!wide) {
|
|
7586
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
7587
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
7588
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
|
|
7589
|
+
] });
|
|
7590
|
+
}
|
|
7591
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
7592
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
7593
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
7594
|
+
] });
|
|
7595
|
+
}
|
|
7596
|
+
function App() {
|
|
7597
|
+
const { exit } = useApp();
|
|
7598
|
+
const [section, setSection] = useState6(0);
|
|
7599
|
+
const [focus, setFocus] = useState6("nav");
|
|
7600
|
+
useInput5((input, key) => {
|
|
7601
|
+
if (focus === "nav") {
|
|
7602
|
+
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
7603
|
+
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
7604
|
+
else if (key.rightArrow || key.return || key.tab) setFocus("panel");
|
|
7605
|
+
else if (input === "q") exit();
|
|
7606
|
+
} else {
|
|
7607
|
+
if (key.escape || key.tab) setFocus("nav");
|
|
7608
|
+
}
|
|
7609
|
+
});
|
|
7610
|
+
const current = SECTIONS[section];
|
|
7611
|
+
const Panel = current.Panel;
|
|
7612
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
|
|
7613
|
+
/* @__PURE__ */ jsx8(Banner, {}),
|
|
7614
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
7615
|
+
/* @__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)) }),
|
|
7616
|
+
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minHeight: 16, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: true, focused: focus === "panel" }) })
|
|
7617
|
+
] }),
|
|
7618
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "move"], ["\u2190\u2192/space/s", "edit"], ["esc", "back to menu"]] }) })
|
|
7619
|
+
] });
|
|
7620
|
+
}
|
|
7621
|
+
var SECTIONS, BANNER_HEX;
|
|
7622
|
+
var init_App = __esm({
|
|
7623
|
+
"src/tui/App.tsx"() {
|
|
7624
|
+
"use strict";
|
|
7625
|
+
init_cli_utils();
|
|
7626
|
+
init_components();
|
|
7627
|
+
init_theme();
|
|
7628
|
+
init_Policies();
|
|
7629
|
+
init_RateLimit();
|
|
7630
|
+
init_Dlp();
|
|
7631
|
+
init_Stats();
|
|
7632
|
+
init_Audit();
|
|
7633
|
+
init_Agents();
|
|
7634
|
+
SECTIONS = [
|
|
7635
|
+
{ label: "Policies", Panel: PoliciesPanel },
|
|
7636
|
+
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
7637
|
+
{ label: "DLP", Panel: DlpPanel },
|
|
7638
|
+
{ label: "Stats", Panel: StatsPanel },
|
|
7639
|
+
{ label: "Audit", Panel: AuditPanel },
|
|
7640
|
+
{ label: "Agents", Panel: AgentsPanel }
|
|
7641
|
+
];
|
|
7642
|
+
BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
7643
|
+
}
|
|
7644
|
+
});
|
|
7645
|
+
|
|
7646
|
+
// src/tui/index.tsx
|
|
7647
|
+
var tui_exports = {};
|
|
7648
|
+
__export(tui_exports, {
|
|
7649
|
+
launchTui: () => launchTui
|
|
7650
|
+
});
|
|
7651
|
+
import { render } from "ink";
|
|
7652
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
7653
|
+
async function launchTui() {
|
|
7654
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
7655
|
+
process.stderr.write(
|
|
7656
|
+
"The SolonGate TUI needs an interactive terminal.\nUse the scriptable commands instead, e.g. `solongate stats`, `solongate policy list`.\n"
|
|
7657
|
+
);
|
|
7658
|
+
return;
|
|
7659
|
+
}
|
|
7660
|
+
if (!isAuthenticated()) {
|
|
7661
|
+
process.stderr.write("Not logged in. Run `solongate login` first.\n");
|
|
7662
|
+
return;
|
|
7663
|
+
}
|
|
7664
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
|
|
7665
|
+
await waitUntilExit();
|
|
7666
|
+
}
|
|
7667
|
+
var init_tui = __esm({
|
|
7668
|
+
"src/tui/index.tsx"() {
|
|
7669
|
+
"use strict";
|
|
7670
|
+
init_App();
|
|
7671
|
+
init_client();
|
|
7672
|
+
}
|
|
7673
|
+
});
|
|
7674
|
+
|
|
7675
|
+
// src/commands/format.ts
|
|
7676
|
+
function printJson(value) {
|
|
7677
|
+
out(JSON.stringify(value, null, 2));
|
|
7678
|
+
}
|
|
7679
|
+
function decisionColor2(decision) {
|
|
7680
|
+
const d = decision.toUpperCase();
|
|
7681
|
+
if (d === "ALLOW") return green(d);
|
|
7682
|
+
if (d === "DENY" || d === "DENIED") return red(d);
|
|
7683
|
+
return dim(d);
|
|
7684
|
+
}
|
|
7685
|
+
function table(headers, rows) {
|
|
7686
|
+
const cols = headers.length;
|
|
7687
|
+
const w = new Array(cols).fill(0);
|
|
7688
|
+
for (let i = 0; i < cols; i++) w[i] = width(headers[i] ?? "");
|
|
7689
|
+
for (const row of rows) {
|
|
7690
|
+
for (let i = 0; i < cols; i++) w[i] = Math.max(w[i], width(row[i] ?? ""));
|
|
7691
|
+
}
|
|
7692
|
+
const pad = (s, i) => s + " ".repeat(Math.max(0, w[i] - width(s)));
|
|
7693
|
+
err(" " + headers.map((h, i) => dim(pad(h, i))).join(" "));
|
|
7694
|
+
for (const row of rows) {
|
|
7695
|
+
err(" " + row.map((cell, i) => pad(cell ?? "", i)).join(" "));
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
function truncate3(s, n) {
|
|
7699
|
+
if (s.length <= n) return s;
|
|
7700
|
+
return s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
7701
|
+
}
|
|
7702
|
+
function sparkline2(values) {
|
|
7703
|
+
if (values.length === 0) return "";
|
|
7704
|
+
const max = Math.max(...values, 0);
|
|
7705
|
+
if (max === 0) return BLOCKS2[0].repeat(values.length);
|
|
7706
|
+
return values.map((v) => BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(v / max * (BLOCKS2.length - 1)))]).join("");
|
|
7707
|
+
}
|
|
7708
|
+
var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS2;
|
|
7709
|
+
var init_format = __esm({
|
|
7710
|
+
"src/commands/format.ts"() {
|
|
7711
|
+
"use strict";
|
|
7712
|
+
init_cli_utils();
|
|
7713
|
+
out = (s = "") => void process.stdout.write(s + "\n");
|
|
7714
|
+
err = (s = "") => void process.stderr.write(s + "\n");
|
|
7715
|
+
dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
7716
|
+
bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
7717
|
+
green = (s) => `${c.green}${s}${c.reset}`;
|
|
7718
|
+
red = (s) => `${c.red}${s}${c.reset}`;
|
|
7719
|
+
yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
7720
|
+
cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
7721
|
+
ANSI = /\x1b\[[0-9;]*m/g;
|
|
7722
|
+
width = (s) => s.replace(ANSI, "").length;
|
|
7723
|
+
BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
7724
|
+
}
|
|
7725
|
+
});
|
|
7726
|
+
|
|
7727
|
+
// src/commands/args.ts
|
|
7728
|
+
function parse(argv) {
|
|
7729
|
+
const positionals = [];
|
|
7730
|
+
const flags = {};
|
|
7731
|
+
for (let i = 0; i < argv.length; i++) {
|
|
7732
|
+
const tok = argv[i];
|
|
7733
|
+
if (tok.startsWith("--")) {
|
|
7734
|
+
const body = tok.slice(2);
|
|
7735
|
+
const eq = body.indexOf("=");
|
|
7736
|
+
if (eq !== -1) {
|
|
7737
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
7738
|
+
continue;
|
|
7739
|
+
}
|
|
7740
|
+
const next = argv[i + 1];
|
|
7741
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
7742
|
+
flags[body] = next;
|
|
7743
|
+
i++;
|
|
7744
|
+
} else {
|
|
7745
|
+
flags[body] = true;
|
|
7746
|
+
}
|
|
7747
|
+
} else {
|
|
7748
|
+
positionals.push(tok);
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
return { positionals, flags };
|
|
7752
|
+
}
|
|
7753
|
+
function flagStr(flags, name) {
|
|
7754
|
+
const v = flags[name];
|
|
7755
|
+
return typeof v === "string" ? v : void 0;
|
|
7756
|
+
}
|
|
7757
|
+
function flagNum(flags, name) {
|
|
7758
|
+
const v = flagStr(flags, name);
|
|
7759
|
+
if (v === void 0) return void 0;
|
|
7760
|
+
const n = Number(v);
|
|
7761
|
+
return Number.isFinite(n) ? n : void 0;
|
|
7762
|
+
}
|
|
7763
|
+
function flagBool(flags, name) {
|
|
7764
|
+
return flags[name] === true || flags[name] === "true";
|
|
7765
|
+
}
|
|
7766
|
+
var init_args = __esm({
|
|
7767
|
+
"src/commands/args.ts"() {
|
|
7768
|
+
"use strict";
|
|
7769
|
+
}
|
|
7770
|
+
});
|
|
7771
|
+
|
|
7772
|
+
// src/commands/policy.ts
|
|
7773
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
7774
|
+
async function run(argv) {
|
|
7775
|
+
const { positionals, flags } = parse(argv);
|
|
7776
|
+
const sub = positionals[0];
|
|
7777
|
+
const json = flagBool(flags, "json");
|
|
7778
|
+
switch (sub) {
|
|
7779
|
+
case void 0:
|
|
7780
|
+
case "help":
|
|
7781
|
+
err(USAGE);
|
|
7782
|
+
return sub ? 0 : 1;
|
|
7783
|
+
case "list": {
|
|
7784
|
+
const { policies } = await api.policies.list();
|
|
7785
|
+
if (json) return printJson(policies), 0;
|
|
7786
|
+
if (policies.length === 0) {
|
|
7787
|
+
err(dim(" No policies. Create one at https://dashboard.solongate.com"));
|
|
7788
|
+
return 0;
|
|
7789
|
+
}
|
|
7790
|
+
table(
|
|
7791
|
+
["ID", "NAME", "MODE", "RULES", "VER", "UPDATED BY"],
|
|
7792
|
+
policies.map((p) => [
|
|
7793
|
+
cyan(p.id),
|
|
7794
|
+
truncate3(p.name, 28),
|
|
7795
|
+
p.mode === "whitelist" ? green("whitelist") : "denylist",
|
|
7796
|
+
String(p.rules?.length ?? 0),
|
|
7797
|
+
`v${p.version}`,
|
|
7798
|
+
dim(truncate3(p.created_by || "\u2014", 20))
|
|
7799
|
+
])
|
|
7800
|
+
);
|
|
7801
|
+
return 0;
|
|
7802
|
+
}
|
|
7803
|
+
case "show": {
|
|
7804
|
+
const id = positionals[1];
|
|
7805
|
+
if (!id) return err(" Usage: policy show <id> [--version N]"), 1;
|
|
7806
|
+
const p = await api.policies.get(id, flagNum(flags, "version"));
|
|
7807
|
+
if (json) return printJson(p), 0;
|
|
7808
|
+
err("");
|
|
7809
|
+
err(` ${bold(p.name)} ${dim(`(${p.id})`)} v${p._version}`);
|
|
7810
|
+
if (p.description) err(` ${dim(p.description)}`);
|
|
7811
|
+
err(` mode: ${p.mode === "whitelist" ? green("whitelist") : "denylist"} rules: ${p.rules.length}`);
|
|
7812
|
+
err("");
|
|
7813
|
+
printRules(p.rules);
|
|
7814
|
+
return 0;
|
|
7815
|
+
}
|
|
7816
|
+
case "rules": {
|
|
7817
|
+
const id = positionals[1];
|
|
7818
|
+
if (!id) return err(" Usage: policy rules <id>"), 1;
|
|
7819
|
+
const p = await api.policies.get(id);
|
|
7820
|
+
if (json) return printJson(p.rules), 0;
|
|
7821
|
+
printRules(p.rules);
|
|
7822
|
+
return 0;
|
|
7823
|
+
}
|
|
7824
|
+
case "allow": {
|
|
7825
|
+
const id = positionals[1];
|
|
7826
|
+
if (!id) return err(" Usage: policy allow <id> --tool <pattern> [--command|--path|--url <val>]"), 1;
|
|
7827
|
+
const toolPattern = flagStr(flags, "tool") ?? "*";
|
|
7828
|
+
let kind = "tool";
|
|
7829
|
+
let value;
|
|
7830
|
+
for (const k of ["command", "path", "url"]) {
|
|
7831
|
+
const v = flagStr(flags, k);
|
|
7832
|
+
if (v !== void 0) {
|
|
7833
|
+
kind = k;
|
|
7834
|
+
value = v;
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
const res = await api.policies.addRule(id, { toolPattern, kind, value });
|
|
7838
|
+
if (json) return printJson(res), 0;
|
|
7839
|
+
if (res.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW rule already present."));
|
|
7840
|
+
else err(green(` \u2713 Rule added`) + dim(` (${res.rule?.id}) \u2192 v${res.policy_version}`));
|
|
7841
|
+
return 0;
|
|
7842
|
+
}
|
|
7843
|
+
case "revoke": {
|
|
7844
|
+
const id = positionals[1];
|
|
7845
|
+
const ruleId = positionals[2];
|
|
7846
|
+
if (!id || !ruleId) return err(" Usage: policy revoke <id> <ruleId>"), 1;
|
|
7847
|
+
const res = await api.policies.revokeRule(id, ruleId);
|
|
7848
|
+
if (json) return printJson(res), 0;
|
|
7849
|
+
err(green(` \u2713 Revoked ${ruleId}`) + dim(` \u2192 v${res.policy_version}`));
|
|
7850
|
+
return 0;
|
|
7851
|
+
}
|
|
7852
|
+
case "versions": {
|
|
7853
|
+
const id = positionals[1];
|
|
7854
|
+
if (!id) return err(" Usage: policy versions <id>"), 1;
|
|
7855
|
+
const { versions: versions2 } = await api.policies.versions(id, { limit: flagNum(flags, "limit") });
|
|
7856
|
+
if (json) return printJson(versions2), 0;
|
|
7857
|
+
table(
|
|
7858
|
+
["VER", "RULES", "REASON", "BY", "WHEN"],
|
|
7859
|
+
versions2.map((v) => [
|
|
7860
|
+
`v${v.version}`,
|
|
7861
|
+
String(v.rules_count),
|
|
7862
|
+
truncate3(v.reason || "\u2014", 40),
|
|
7863
|
+
dim(truncate3(v.created_by || "\u2014", 18)),
|
|
7864
|
+
dim(v.created_at)
|
|
7865
|
+
])
|
|
7866
|
+
);
|
|
7867
|
+
return 0;
|
|
7868
|
+
}
|
|
7869
|
+
case "rollback": {
|
|
7870
|
+
const id = positionals[1];
|
|
7871
|
+
const version = Number(positionals[2]);
|
|
7872
|
+
if (!id || !Number.isFinite(version)) return err(" Usage: policy rollback <id> <version>"), 1;
|
|
7873
|
+
const res = await api.policies.rollback(id, version);
|
|
7874
|
+
if (json) return printJson(res), 0;
|
|
7875
|
+
err(green(` \u2713 Rolled back ${res.policy_id} from v${res.rolled_back_from} \u2192 v${res.version}`));
|
|
7876
|
+
return 0;
|
|
7877
|
+
}
|
|
7878
|
+
case "active": {
|
|
7879
|
+
const a = await api.policies.active();
|
|
7880
|
+
if (json) return printJson(a), 0;
|
|
7881
|
+
if (!a.policy) {
|
|
7882
|
+
err(dim(" No active policy resolves for this project."));
|
|
7883
|
+
return 0;
|
|
7884
|
+
}
|
|
7885
|
+
err("");
|
|
7886
|
+
err(` Active: ${bold(a.policy.name)} ${dim(`(${a.policy.id})`)} v${a.version}`);
|
|
7887
|
+
err(` matched by: ${cyan(a.matched_by ?? "\u2014")} self-protection: ${a.self_protection_enabled ? green("on") : dim("off")}`);
|
|
7888
|
+
const rl = a.security?.rateLimit;
|
|
7889
|
+
if (rl) err(` rate limit: ${rl.perMinute}/min ${rl.perHour}/h ${rl.perDay}/day`);
|
|
7890
|
+
if (a.security?.dlpBlock) err(` DLP block: ${a.security.dlpBlock.patterns.length} patterns`);
|
|
7891
|
+
return 0;
|
|
7892
|
+
}
|
|
7893
|
+
case "activate": {
|
|
7894
|
+
if (flagBool(flags, "clear")) {
|
|
7895
|
+
const res2 = await api.policies.setActive(null);
|
|
7896
|
+
if (json) return printJson(res2), 0;
|
|
7897
|
+
return err(green(" \u2713 Cleared active-policy pin.")), 0;
|
|
7898
|
+
}
|
|
7899
|
+
const id = positionals[1];
|
|
7900
|
+
if (!id) return err(" Usage: policy activate <id> | policy activate --clear"), 1;
|
|
7901
|
+
const res = await api.policies.setActive(id);
|
|
7902
|
+
if (json) return printJson(res), 0;
|
|
7903
|
+
err(green(` \u2713 Pinned active policy \u2192 ${res.active}`));
|
|
7904
|
+
return 0;
|
|
7905
|
+
}
|
|
7906
|
+
case "dry-run": {
|
|
7907
|
+
const target = positionals[1];
|
|
7908
|
+
if (!target) return err(" Usage: policy dry-run <id|file.json> [--limit N]"), 1;
|
|
7909
|
+
const rules = await resolveRules(target);
|
|
7910
|
+
const res = await api.policies.dryRun({
|
|
7911
|
+
rules,
|
|
7912
|
+
mode: flagStr(flags, "mode") ?? void 0,
|
|
7913
|
+
limit: flagNum(flags, "limit")
|
|
7914
|
+
});
|
|
7915
|
+
if (json) return printJson(res), 0;
|
|
7916
|
+
err("");
|
|
7917
|
+
err(` Replayed ${bold(String(res.evaluated))} recent calls against ${rules.length} rule(s)`);
|
|
7918
|
+
err(` would allow: ${green(String(res.would_allow))} would deny: ${decisionColor2("DENY")} ${res.would_deny}`);
|
|
7919
|
+
err(` ${green("newly allowed")}: ${res.newly_allowed} ${decisionColor2("DENY")}${dim(" newly blocked")}: ${res.newly_blocked} unchanged: ${res.unchanged}`);
|
|
7920
|
+
if (res.sample_newly_blocked.length) {
|
|
7921
|
+
err(dim("\n Sample newly-blocked:"));
|
|
7922
|
+
for (const s of res.sample_newly_blocked.slice(0, 8)) err(` ${decisionColor2("DENY")} ${s.tool} ${dim(truncate3(s.preview, 50))}`);
|
|
7923
|
+
}
|
|
7924
|
+
return 0;
|
|
7925
|
+
}
|
|
7926
|
+
default:
|
|
7927
|
+
err(` Unknown: policy ${sub}
|
|
7928
|
+
`);
|
|
7929
|
+
err(USAGE);
|
|
7930
|
+
return 1;
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
function printRules(rules) {
|
|
7934
|
+
if (rules.length === 0) return void err(dim(" (no rules)"));
|
|
7935
|
+
table(
|
|
7936
|
+
["EFFECT", "PRIO", "TOOL", "ID", "DESCRIPTION"],
|
|
7937
|
+
rules.map((r) => [
|
|
7938
|
+
r.effect === "ALLOW" ? green("ALLOW") : decisionColor2("DENY"),
|
|
7939
|
+
String(r.priority),
|
|
7940
|
+
cyan(truncate3(r.toolPattern, 24)),
|
|
7941
|
+
dim(truncate3(r.id, 22)),
|
|
7942
|
+
truncate3(r.description || "\u2014", 40)
|
|
7943
|
+
])
|
|
7944
|
+
);
|
|
7945
|
+
}
|
|
7946
|
+
async function resolveRules(target) {
|
|
7947
|
+
if (target.endsWith(".json")) {
|
|
7948
|
+
const parsed = JSON.parse(readFileSync5(target, "utf-8"));
|
|
7949
|
+
return parsed.rules ?? [];
|
|
7950
|
+
}
|
|
7951
|
+
const p = await api.policies.get(target);
|
|
7952
|
+
return p.rules;
|
|
7953
|
+
}
|
|
7954
|
+
var USAGE;
|
|
7955
|
+
var init_policy = __esm({
|
|
7956
|
+
"src/commands/policy.ts"() {
|
|
7957
|
+
"use strict";
|
|
7958
|
+
init_api_client();
|
|
7959
|
+
init_args();
|
|
7960
|
+
init_format();
|
|
7961
|
+
USAGE = `${bold("solongate policy")} \u2014 manage cloud policies
|
|
7962
|
+
|
|
7963
|
+
policy list List all policies
|
|
7964
|
+
policy show <id> [--version N] Show one policy (rules, mode)
|
|
7965
|
+
policy rules <id> List a policy's rules
|
|
7966
|
+
policy allow <id> --tool <p> [--command|--path|--url <val>]
|
|
7967
|
+
Append an ALLOW rule
|
|
7968
|
+
policy revoke <id> <ruleId> Remove a rule
|
|
7969
|
+
policy versions <id> List version history
|
|
7970
|
+
policy rollback <id> <version> Roll back to a version
|
|
7971
|
+
policy active Show the resolved active policy
|
|
7972
|
+
policy activate <id> | --clear Pin / unpin the active policy
|
|
7973
|
+
policy dry-run <id|file.json> [--limit N] [--mode denylist|whitelist]
|
|
7974
|
+
Replay recent traffic against a policy's rules
|
|
7975
|
+
|
|
7976
|
+
Add --json to any read command for machine-readable output.`;
|
|
7977
|
+
}
|
|
7978
|
+
});
|
|
7979
|
+
|
|
7980
|
+
// src/commands/ratelimit.ts
|
|
7981
|
+
async function run2(argv) {
|
|
7982
|
+
const { positionals, flags } = parse(argv);
|
|
7983
|
+
const sub = positionals[0] ?? "show";
|
|
7984
|
+
const json = flagBool(flags, "json");
|
|
7985
|
+
switch (sub) {
|
|
7986
|
+
case "help":
|
|
7987
|
+
return err(USAGE2), 0;
|
|
7988
|
+
case "show": {
|
|
7989
|
+
const [{ layers }, { history }] = await Promise.all([
|
|
7990
|
+
api.settings.getSecurityLayers(),
|
|
7991
|
+
api.settings.getRateLimitHistory()
|
|
7992
|
+
]);
|
|
7993
|
+
if (json) return printJson({ rateLimit: layers.rateLimit, history }), 0;
|
|
7994
|
+
const rl = layers.rateLimit;
|
|
7995
|
+
err("");
|
|
7996
|
+
err(` Rate limit mode: ${modeColor2(rl.mode)}`);
|
|
7997
|
+
err(` ${bold(String(rl.perMinute))} ${dim("/min")} ${bold(String(rl.perHour))} ${dim("/hour")} ${bold(String(rl.perDay))} ${dim("/day")}`);
|
|
7998
|
+
if (history.length) {
|
|
7999
|
+
const spark = sparkline2(history.map((h) => h.minute));
|
|
8000
|
+
err(` history ${cyan(spark)} ${dim(`(${history.length} changes, per-min)`)}`);
|
|
8001
|
+
}
|
|
8002
|
+
return 0;
|
|
8003
|
+
}
|
|
8004
|
+
case "history": {
|
|
8005
|
+
const { history } = await api.settings.getRateLimitHistory();
|
|
8006
|
+
if (json) return printJson(history), 0;
|
|
8007
|
+
if (!history.length) return err(dim(" No rate-limit changes recorded.")), 0;
|
|
8008
|
+
table(
|
|
8009
|
+
["WHEN", "MINUTE", "HOUR", "DAY"],
|
|
8010
|
+
history.map((h) => [dim(new Date(h.ts).toISOString()), String(h.minute), String(h.hour), String(h.day)])
|
|
8011
|
+
);
|
|
8012
|
+
return 0;
|
|
8013
|
+
}
|
|
8014
|
+
case "set": {
|
|
8015
|
+
const minute = flagNum(flags, "minute");
|
|
8016
|
+
const hour = flagNum(flags, "hour");
|
|
8017
|
+
const day = flagNum(flags, "day");
|
|
8018
|
+
const mode = flagStr(flags, "mode");
|
|
8019
|
+
if (minute === void 0 && hour === void 0 && day === void 0 && !mode) {
|
|
8020
|
+
return err(" Usage: ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]"), 1;
|
|
8021
|
+
}
|
|
8022
|
+
const { layers } = await api.settings.getSecurityLayers();
|
|
8023
|
+
const next = {
|
|
8024
|
+
...layers,
|
|
8025
|
+
rateLimit: {
|
|
8026
|
+
mode: mode ?? layers.rateLimit.mode,
|
|
8027
|
+
perMinute: minute ?? layers.rateLimit.perMinute,
|
|
8028
|
+
perHour: hour ?? layers.rateLimit.perHour,
|
|
8029
|
+
perDay: day ?? layers.rateLimit.perDay
|
|
8030
|
+
}
|
|
8031
|
+
};
|
|
8032
|
+
const res = await api.settings.setSecurityLayers(next);
|
|
8033
|
+
if (json) return printJson(res.layers.rateLimit), 0;
|
|
8034
|
+
const r = res.layers.rateLimit;
|
|
8035
|
+
err(green(" \u2713 Rate limit updated") + dim(` ${r.perMinute}/min ${r.perHour}/h ${r.perDay}/day (${r.mode})`));
|
|
8036
|
+
return 0;
|
|
8037
|
+
}
|
|
8038
|
+
default:
|
|
8039
|
+
return err(USAGE2), 1;
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
var USAGE2, modeColor2;
|
|
8043
|
+
var init_ratelimit = __esm({
|
|
8044
|
+
"src/commands/ratelimit.ts"() {
|
|
8045
|
+
"use strict";
|
|
8046
|
+
init_api_client();
|
|
8047
|
+
init_args();
|
|
8048
|
+
init_format();
|
|
8049
|
+
USAGE2 = `${bold("solongate ratelimit")} \u2014 request throttling
|
|
8050
|
+
|
|
8051
|
+
ratelimit show Current limits + change history
|
|
8052
|
+
ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]
|
|
8053
|
+
ratelimit history Recent limit changes
|
|
8054
|
+
|
|
8055
|
+
Add --json for machine-readable output.`;
|
|
8056
|
+
modeColor2 = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
8057
|
+
}
|
|
8058
|
+
});
|
|
8059
|
+
|
|
8060
|
+
// src/commands/dlp.ts
|
|
8061
|
+
async function run3(argv) {
|
|
8062
|
+
const { positionals, flags } = parse(argv);
|
|
8063
|
+
const sub = positionals[0] ?? "show";
|
|
8064
|
+
const json = flagBool(flags, "json");
|
|
8065
|
+
const { layers, availablePatterns } = await api.settings.getSecurityLayers();
|
|
8066
|
+
const save = async (next) => (await api.settings.setSecurityLayers(next)).layers;
|
|
8067
|
+
switch (sub) {
|
|
8068
|
+
case "help":
|
|
8069
|
+
return err(USAGE3), 0;
|
|
8070
|
+
case "show": {
|
|
8071
|
+
if (json) return printJson({ dlp: layers.dlp, availablePatterns }), 0;
|
|
8072
|
+
err("");
|
|
8073
|
+
err(` DLP mode: ${modeColor3(layers.dlp.mode)}`);
|
|
8074
|
+
const enabled = new Set(layers.dlp.patterns);
|
|
8075
|
+
table(
|
|
8076
|
+
["", "PATTERN"],
|
|
8077
|
+
availablePatterns.map((p) => [enabled.has(p) ? green("\u25CF") : dim("\u25CB"), enabled.has(p) ? p : dim(p)])
|
|
8078
|
+
);
|
|
8079
|
+
if (layers.dlp.custom.length) {
|
|
8080
|
+
err(dim("\n Custom:"));
|
|
8081
|
+
for (const c2 of layers.dlp.custom) err(` ${cyan(c2.name)} ${dim(c2.re)}`);
|
|
8082
|
+
}
|
|
8083
|
+
return 0;
|
|
8084
|
+
}
|
|
8085
|
+
case "mode": {
|
|
8086
|
+
const mode = positionals[1];
|
|
8087
|
+
if (!mode || !["off", "detect", "block"].includes(mode)) return err(" Usage: dlp mode <off|detect|block>"), 1;
|
|
8088
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, mode } });
|
|
8089
|
+
if (json) return printJson(saved.dlp), 0;
|
|
8090
|
+
return err(green(` \u2713 DLP mode \u2192 ${saved.dlp.mode}`)), 0;
|
|
8091
|
+
}
|
|
8092
|
+
case "enable":
|
|
8093
|
+
case "disable": {
|
|
8094
|
+
const pattern = positionals.slice(1).join(" ");
|
|
8095
|
+
if (!pattern) return err(` Usage: dlp ${sub} <pattern>`), 1;
|
|
8096
|
+
if (!availablePatterns.includes(pattern)) {
|
|
8097
|
+
err(` Unknown pattern: "${pattern}". Available:`);
|
|
8098
|
+
for (const p of availablePatterns) err(` ${dim("\u2022")} ${p}`);
|
|
8099
|
+
return 1;
|
|
8100
|
+
}
|
|
8101
|
+
const set = new Set(layers.dlp.patterns);
|
|
8102
|
+
if (sub === "enable") set.add(pattern);
|
|
8103
|
+
else set.delete(pattern);
|
|
8104
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, patterns: [...set] } });
|
|
8105
|
+
if (json) return printJson(saved.dlp), 0;
|
|
8106
|
+
return err(green(` \u2713 ${sub}d "${pattern}"`) + dim(` (${saved.dlp.patterns.length} active)`)), 0;
|
|
8107
|
+
}
|
|
8108
|
+
case "add-custom": {
|
|
8109
|
+
const name = flagStr(flags, "name");
|
|
8110
|
+
const re = flagStr(flags, "re");
|
|
8111
|
+
if (!name || !re) return err(" Usage: dlp add-custom --name <name> --re <regex>"), 1;
|
|
8112
|
+
const custom = [...layers.dlp.custom.filter((c2) => c2.name !== name), { name, re }];
|
|
8113
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
8114
|
+
if (json) return printJson(saved.dlp), 0;
|
|
8115
|
+
return err(green(` \u2713 Custom pattern "${name}" added`)), 0;
|
|
8116
|
+
}
|
|
8117
|
+
case "remove-custom": {
|
|
8118
|
+
const name = positionals.slice(1).join(" ");
|
|
8119
|
+
if (!name) return err(" Usage: dlp remove-custom <name>"), 1;
|
|
8120
|
+
const custom = layers.dlp.custom.filter((c2) => c2.name !== name);
|
|
8121
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
8122
|
+
if (json) return printJson(saved.dlp), 0;
|
|
8123
|
+
return err(green(` \u2713 Removed custom pattern "${name}"`)), 0;
|
|
8124
|
+
}
|
|
8125
|
+
default:
|
|
8126
|
+
return err(USAGE3), 1;
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
var USAGE3, modeColor3;
|
|
8130
|
+
var init_dlp = __esm({
|
|
8131
|
+
"src/commands/dlp.ts"() {
|
|
8132
|
+
"use strict";
|
|
8133
|
+
init_api_client();
|
|
8134
|
+
init_args();
|
|
8135
|
+
init_format();
|
|
8136
|
+
USAGE3 = `${bold("solongate dlp")} \u2014 data-loss prevention
|
|
8137
|
+
|
|
8138
|
+
dlp show Current mode + enabled patterns
|
|
8139
|
+
dlp mode <off|detect|block> Set enforcement mode
|
|
8140
|
+
dlp enable <pattern> Enable a built-in pattern
|
|
8141
|
+
dlp disable <pattern> Disable a built-in pattern
|
|
8142
|
+
dlp add-custom --name X --re <regex> Add a custom pattern
|
|
8143
|
+
dlp remove-custom <name> Remove a custom pattern
|
|
8144
|
+
|
|
8145
|
+
Add --json for machine-readable output.`;
|
|
8146
|
+
modeColor3 = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
8147
|
+
}
|
|
8148
|
+
});
|
|
8149
|
+
|
|
8150
|
+
// src/commands/stats.ts
|
|
8151
|
+
async function run4(argv) {
|
|
8152
|
+
const { positionals, flags } = parse(argv);
|
|
8153
|
+
const sub = positionals[0] ?? "overview";
|
|
8154
|
+
const json = flagBool(flags, "json");
|
|
8155
|
+
switch (sub) {
|
|
8156
|
+
case "help":
|
|
8157
|
+
return err(USAGE4), 0;
|
|
8158
|
+
case "overview": {
|
|
8159
|
+
const s = await api.stats.get();
|
|
8160
|
+
if (json) return printJson(s), 0;
|
|
8161
|
+
err("");
|
|
8162
|
+
err(` ${bold(String(s.total_calls))} calls ${green(String(s.allowed))} allowed ${red(String(s.denied))} denied`);
|
|
8163
|
+
err(` ${dim(`${s.active_policies} active policies \xB7 ${s.registered_tools} tools`)}`);
|
|
8164
|
+
if (s.recent_activity.length) {
|
|
8165
|
+
err(dim("\n Recent:"));
|
|
8166
|
+
table(
|
|
8167
|
+
["DECISION", "TOOL", "TRUST", "MS", "WHEN"],
|
|
8168
|
+
s.recent_activity.map((a) => [
|
|
8169
|
+
decisionColor2(a.decision),
|
|
8170
|
+
cyan(truncate3(a.tool_name, 24)),
|
|
8171
|
+
dim(a.trust_level),
|
|
8172
|
+
String(a.evaluation_time_ms ?? "\u2014"),
|
|
8173
|
+
dim(a.created_at)
|
|
8174
|
+
])
|
|
8175
|
+
);
|
|
8176
|
+
}
|
|
8177
|
+
return 0;
|
|
8178
|
+
}
|
|
8179
|
+
case "timeseries": {
|
|
8180
|
+
const period = flagStr(flags, "period") ?? "24h";
|
|
8181
|
+
const ts = await api.stats.timeseries({ period });
|
|
8182
|
+
if (json) return printJson(ts), 0;
|
|
8183
|
+
const pts = ts.timeseries;
|
|
8184
|
+
err("");
|
|
8185
|
+
err(` Timeseries ${dim(`(${ts.period}, per ${ts.granularity})`)}`);
|
|
8186
|
+
err(` total ${cyan(sparkline2(pts.map((p) => p.total)))} ${dim(`max ${Math.max(0, ...pts.map((p) => p.total))}`)}`);
|
|
8187
|
+
err(` allowed ${green(sparkline2(pts.map((p) => p.allowed)))}`);
|
|
8188
|
+
err(` denied ${red(sparkline2(pts.map((p) => p.denied)))}`);
|
|
8189
|
+
return 0;
|
|
8190
|
+
}
|
|
8191
|
+
case "drift": {
|
|
8192
|
+
const d = await api.stats.drift(flagNum(flags, "days"));
|
|
8193
|
+
if (json) return printJson(d), 0;
|
|
8194
|
+
err("");
|
|
8195
|
+
err(` Denial drift ${dim(`(${d.days}d: ${d.total_current} now vs ${d.total_previous} prev)`)}`);
|
|
8196
|
+
if (!d.rules.length) return err(dim(" No denials in window.")), 0;
|
|
8197
|
+
table(
|
|
8198
|
+
["NOW", "PREV", "\u0394", "RULE", "REASON"],
|
|
8199
|
+
d.rules.slice(0, 20).map((r) => [
|
|
8200
|
+
bold(String(r.current)),
|
|
8201
|
+
dim(String(r.previous)),
|
|
8202
|
+
r.is_new ? green("NEW") : r.spike ? red(`+${r.delta}`) : String(r.delta),
|
|
8203
|
+
cyan(truncate3(r.rule_id ?? "\u2014", 22)),
|
|
8204
|
+
truncate3(r.reason ?? r.last_tool ?? "\u2014", 36)
|
|
8205
|
+
])
|
|
8206
|
+
);
|
|
8207
|
+
return 0;
|
|
8208
|
+
}
|
|
8209
|
+
default:
|
|
8210
|
+
return err(USAGE4), 1;
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
var USAGE4;
|
|
8214
|
+
var init_stats2 = __esm({
|
|
8215
|
+
"src/commands/stats.ts"() {
|
|
8216
|
+
"use strict";
|
|
8217
|
+
init_api_client();
|
|
8218
|
+
init_args();
|
|
8219
|
+
init_format();
|
|
8220
|
+
USAGE4 = `${bold("solongate stats")} \u2014 traffic & security stats
|
|
8221
|
+
|
|
8222
|
+
stats Overview (totals, recent activity)
|
|
8223
|
+
stats timeseries [--period 24h|7d|30d|all]
|
|
8224
|
+
stats drift [--days N] Denials rising/falling vs previous window
|
|
8225
|
+
|
|
8226
|
+
Add --json for machine-readable output.`;
|
|
8227
|
+
}
|
|
8228
|
+
});
|
|
8229
|
+
|
|
8230
|
+
// src/commands/audit.ts
|
|
8231
|
+
async function run5(argv) {
|
|
8232
|
+
const { positionals, flags } = parse(argv);
|
|
8233
|
+
const json = flagBool(flags, "json");
|
|
8234
|
+
if (positionals[0] === "help") return err(USAGE5), 0;
|
|
8235
|
+
if (positionals[0] === "whitelist") {
|
|
8236
|
+
const id = positionals[1];
|
|
8237
|
+
if (!id) return err(" Usage: audit whitelist <logId> [--scope exact|tool]"), 1;
|
|
8238
|
+
const scope = flagStr(flags, "scope") ?? "exact";
|
|
8239
|
+
const res2 = await api.audit.whitelist(id, scope);
|
|
8240
|
+
if (json) return printJson(res2), 0;
|
|
8241
|
+
if (res2.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW already present."));
|
|
8242
|
+
else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
|
|
8243
|
+
return 0;
|
|
8244
|
+
}
|
|
8245
|
+
const query = {
|
|
8246
|
+
filter: flagStr(flags, "filter"),
|
|
8247
|
+
tool: flagStr(flags, "tool"),
|
|
8248
|
+
signal: flagStr(flags, "signal"),
|
|
8249
|
+
search: flagStr(flags, "search"),
|
|
8250
|
+
agent_name: flagStr(flags, "agent-name"),
|
|
8251
|
+
limit: flagNum(flags, "limit") ?? 30
|
|
8252
|
+
};
|
|
8253
|
+
const res = await api.audit.list(query);
|
|
8254
|
+
if (json) return printJson(res), 0;
|
|
8255
|
+
err("");
|
|
8256
|
+
err(` ${bold(String(res.total))} matching entries ${dim(`(showing ${res.entries.length})`)}`);
|
|
8257
|
+
if (!res.entries.length) return 0;
|
|
8258
|
+
table(
|
|
8259
|
+
["DECISION", "TOOL", "AGENT", "REASON", "DLP", "WHEN", "ID"],
|
|
8260
|
+
res.entries.map((e) => [
|
|
8261
|
+
decisionColor2(e.decision),
|
|
8262
|
+
cyan(truncate3(e.tool_name, 22)),
|
|
8263
|
+
dim(truncate3(e.agent_name ?? "\u2014", 16)),
|
|
8264
|
+
truncate3(e.reason ?? "\u2014", 30),
|
|
8265
|
+
e.dlp_matches?.length ? red(String(e.dlp_matches.length)) : dim("0"),
|
|
8266
|
+
dim(e.created_at),
|
|
8267
|
+
dim(truncate3(e.id, 10))
|
|
8268
|
+
])
|
|
8269
|
+
);
|
|
8270
|
+
return 0;
|
|
8271
|
+
}
|
|
8272
|
+
var USAGE5;
|
|
8273
|
+
var init_audit2 = __esm({
|
|
8274
|
+
"src/commands/audit.ts"() {
|
|
8275
|
+
"use strict";
|
|
8276
|
+
init_api_client();
|
|
8277
|
+
init_args();
|
|
8278
|
+
init_format();
|
|
8279
|
+
USAGE5 = `${bold("solongate audit")} \u2014 audit log
|
|
8280
|
+
|
|
8281
|
+
audit [--filter ALLOW|DENY] [--tool <substr>] [--signal dlp|ratelimit]
|
|
8282
|
+
[--search <text>] [--agent-name <name>] [--limit N]
|
|
8283
|
+
audit whitelist <logId> [--scope exact|tool]
|
|
8284
|
+
Turn a denied call into an ALLOW rule
|
|
8285
|
+
|
|
8286
|
+
Add --json for machine-readable output.`;
|
|
8287
|
+
}
|
|
8288
|
+
});
|
|
8289
|
+
|
|
8290
|
+
// src/commands/agents.ts
|
|
8291
|
+
async function runAgents(argv) {
|
|
8292
|
+
const { flags } = parse(argv);
|
|
8293
|
+
const json = flagBool(flags, "json");
|
|
8294
|
+
const res = await api.agents.live({ limit: flagNum(flags, "limit"), includeDeactivated: flagBool(flags, "all") });
|
|
8295
|
+
if (json) return printJson(res), 0;
|
|
8296
|
+
err("");
|
|
8297
|
+
err(` Agents ${green(String(res.counts.active))} active ${yellow(String(res.counts.idle))} idle ${dim(String(res.counts.deactivated) + " off")}`);
|
|
8298
|
+
if (!res.agents.length) return err(dim(" No agent sessions.")), 0;
|
|
8299
|
+
table(
|
|
8300
|
+
["STATUS", "AGENT", "CALLS", "DENY", "DLP", "TRUST", "CHARACTER"],
|
|
8301
|
+
res.agents.map((a) => [
|
|
8302
|
+
statusColor(a.status),
|
|
8303
|
+
cyan(truncate3(a.agent_name ?? a.agent_id ?? a.session_id, 20)),
|
|
8304
|
+
String(a.total_calls),
|
|
8305
|
+
a.denied_calls ? red(String(a.denied_calls)) : dim("0"),
|
|
8306
|
+
a.dlp_events ? red(String(a.dlp_events)) : dim("0"),
|
|
8307
|
+
`${a.trust_score}`,
|
|
8308
|
+
dim(truncate3(a.character || "\u2014", 22))
|
|
8309
|
+
])
|
|
8310
|
+
);
|
|
8311
|
+
return 0;
|
|
8312
|
+
}
|
|
8313
|
+
async function runAgent(argv) {
|
|
8314
|
+
const { positionals, flags } = parse(argv);
|
|
8315
|
+
const json = flagBool(flags, "json");
|
|
8316
|
+
const id = positionals[0];
|
|
8317
|
+
if (!id) return err(" Usage: solongate agent <agent_id> [--json]"), 1;
|
|
8318
|
+
const a = await api.agents.get(id);
|
|
8319
|
+
if (json) return printJson(a), 0;
|
|
8320
|
+
err("");
|
|
8321
|
+
err(` ${bold(a.agent_id)} status: ${statusColor(String(a.status))}`);
|
|
8322
|
+
const base = a.baseline;
|
|
8323
|
+
if (base) {
|
|
8324
|
+
err(` ${dim(base.character ?? "")} trust ${bold(String(base.trustScore ?? "\u2014"))}/100 deny-rate ${(Number(base.denyRate ?? 0) * 100).toFixed(0)}%`);
|
|
8325
|
+
}
|
|
8326
|
+
const feed = a.recent_feed ?? [];
|
|
8327
|
+
if (feed.length) {
|
|
8328
|
+
err(dim("\n Recent:"));
|
|
8329
|
+
table(
|
|
8330
|
+
["DECISION", "TOOL", "REASON", "WHEN"],
|
|
8331
|
+
feed.slice(0, 15).map((f) => [
|
|
8332
|
+
f.decision === "ALLOW" ? green("ALLOW") : red(String(f.decision)),
|
|
8333
|
+
cyan(truncate3(String(f.tool), 22)),
|
|
8334
|
+
truncate3(String(f.reason ?? "\u2014"), 30),
|
|
8335
|
+
dim(String(f.created_at))
|
|
8336
|
+
])
|
|
8337
|
+
);
|
|
8338
|
+
}
|
|
8339
|
+
return 0;
|
|
8340
|
+
}
|
|
8341
|
+
var statusColor;
|
|
8342
|
+
var init_agents2 = __esm({
|
|
8343
|
+
"src/commands/agents.ts"() {
|
|
8344
|
+
"use strict";
|
|
8345
|
+
init_api_client();
|
|
8346
|
+
init_args();
|
|
8347
|
+
init_format();
|
|
8348
|
+
statusColor = (s) => s === "active" ? green(s) : s === "idle" ? yellow(s) : dim(s);
|
|
8349
|
+
}
|
|
8350
|
+
});
|
|
8351
|
+
|
|
8352
|
+
// src/commands/index.ts
|
|
8353
|
+
var commands_exports = {};
|
|
8354
|
+
__export(commands_exports, {
|
|
8355
|
+
COMMAND_NAMES: () => COMMAND_NAMES,
|
|
8356
|
+
runCommand: () => runCommand
|
|
8357
|
+
});
|
|
8358
|
+
async function dispatch(command, argv) {
|
|
8359
|
+
switch (command) {
|
|
8360
|
+
case "policy":
|
|
8361
|
+
return run(argv);
|
|
8362
|
+
case "ratelimit":
|
|
8363
|
+
return run2(argv);
|
|
8364
|
+
case "dlp":
|
|
8365
|
+
return run3(argv);
|
|
8366
|
+
case "stats":
|
|
8367
|
+
return run4(argv);
|
|
8368
|
+
case "audit":
|
|
8369
|
+
return run5(argv);
|
|
8370
|
+
case "agents":
|
|
8371
|
+
return runAgents(argv);
|
|
8372
|
+
case "agent":
|
|
8373
|
+
return runAgent(argv);
|
|
8374
|
+
default:
|
|
8375
|
+
err(` Unknown command: ${command}`);
|
|
8376
|
+
return 1;
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
async function runCommand(command, argv) {
|
|
8380
|
+
try {
|
|
8381
|
+
return await dispatch(command, argv);
|
|
8382
|
+
} catch (e) {
|
|
8383
|
+
if (e instanceof NotAuthenticatedError) {
|
|
8384
|
+
err(red(" \u2717 ") + e.message);
|
|
8385
|
+
return 1;
|
|
8386
|
+
}
|
|
8387
|
+
if (e instanceof ApiError) {
|
|
8388
|
+
err(red(" \u2717 ") + `${e.message}` + (e.status ? ` (${e.status})` : ""));
|
|
8389
|
+
return 1;
|
|
8390
|
+
}
|
|
8391
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8392
|
+
err(red(" \u2717 ") + msg);
|
|
8393
|
+
return 1;
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
var COMMAND_NAMES;
|
|
8397
|
+
var init_commands = __esm({
|
|
8398
|
+
"src/commands/index.ts"() {
|
|
8399
|
+
"use strict";
|
|
8400
|
+
init_api_client();
|
|
8401
|
+
init_format();
|
|
8402
|
+
init_policy();
|
|
8403
|
+
init_ratelimit();
|
|
8404
|
+
init_dlp();
|
|
8405
|
+
init_stats2();
|
|
8406
|
+
init_audit2();
|
|
8407
|
+
init_agents2();
|
|
8408
|
+
COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
|
|
8409
|
+
}
|
|
8410
|
+
});
|
|
8411
|
+
|
|
6557
8412
|
// src/global-install.ts
|
|
6558
8413
|
var global_install_exports = {};
|
|
6559
8414
|
__export(global_install_exports, {
|
|
@@ -6566,14 +8421,14 @@ __export(global_install_exports, {
|
|
|
6566
8421
|
runGlobalRestore: () => runGlobalRestore,
|
|
6567
8422
|
unlockProtected: () => unlockProtected
|
|
6568
8423
|
});
|
|
6569
|
-
import { readFileSync as
|
|
6570
|
-
import { resolve as
|
|
6571
|
-
import { homedir as
|
|
8424
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
8425
|
+
import { resolve as resolve4, join as join5, dirname } from "path";
|
|
8426
|
+
import { homedir as homedir3 } from "os";
|
|
6572
8427
|
import { fileURLToPath } from "url";
|
|
6573
8428
|
import { createInterface } from "readline";
|
|
6574
8429
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
6575
8430
|
function lockFile(file) {
|
|
6576
|
-
if (!
|
|
8431
|
+
if (!existsSync4(file)) return;
|
|
6577
8432
|
try {
|
|
6578
8433
|
if (process.platform === "win32") {
|
|
6579
8434
|
try {
|
|
@@ -6599,7 +8454,7 @@ function lockFile(file) {
|
|
|
6599
8454
|
}
|
|
6600
8455
|
}
|
|
6601
8456
|
function unlockFile(file) {
|
|
6602
|
-
if (!
|
|
8457
|
+
if (!existsSync4(file)) return;
|
|
6603
8458
|
try {
|
|
6604
8459
|
if (process.platform === "win32") {
|
|
6605
8460
|
try {
|
|
@@ -6631,10 +8486,10 @@ function unlockFile(file) {
|
|
|
6631
8486
|
function protectedTargets() {
|
|
6632
8487
|
const p = globalPaths();
|
|
6633
8488
|
return [
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
8489
|
+
join5(p.hooksDir, "guard.mjs"),
|
|
8490
|
+
join5(p.hooksDir, "audit.mjs"),
|
|
8491
|
+
join5(p.hooksDir, "stop.mjs"),
|
|
8492
|
+
join5(p.hooksDir, "shield.mjs"),
|
|
6638
8493
|
p.configPath,
|
|
6639
8494
|
p.settingsPath
|
|
6640
8495
|
];
|
|
@@ -6646,26 +8501,26 @@ function unlockProtected() {
|
|
|
6646
8501
|
for (const f of protectedTargets()) unlockFile(f);
|
|
6647
8502
|
}
|
|
6648
8503
|
function globalPaths() {
|
|
6649
|
-
const home =
|
|
6650
|
-
const sgDir =
|
|
6651
|
-
const hooksDir =
|
|
6652
|
-
const claudeDir =
|
|
8504
|
+
const home = homedir3();
|
|
8505
|
+
const sgDir = join5(home, ".solongate");
|
|
8506
|
+
const hooksDir = join5(sgDir, "hooks");
|
|
8507
|
+
const claudeDir = join5(home, ".claude");
|
|
6653
8508
|
return {
|
|
6654
8509
|
home,
|
|
6655
8510
|
sgDir,
|
|
6656
8511
|
hooksDir,
|
|
6657
8512
|
claudeDir,
|
|
6658
|
-
settingsPath:
|
|
6659
|
-
backupPath:
|
|
6660
|
-
configPath:
|
|
8513
|
+
settingsPath: join5(claudeDir, "settings.json"),
|
|
8514
|
+
backupPath: join5(claudeDir, "settings.solongate.bak"),
|
|
8515
|
+
configPath: join5(sgDir, "cloud-guard.json")
|
|
6661
8516
|
};
|
|
6662
8517
|
}
|
|
6663
8518
|
function readHook(filename) {
|
|
6664
|
-
return
|
|
8519
|
+
return readFileSync6(join5(HOOKS_DIR, filename), "utf-8");
|
|
6665
8520
|
}
|
|
6666
8521
|
function readGuard() {
|
|
6667
|
-
const bundled =
|
|
6668
|
-
return
|
|
8522
|
+
const bundled = join5(HOOKS_DIR, "guard.bundled.mjs");
|
|
8523
|
+
return existsSync4(bundled) ? readFileSync6(bundled, "utf-8") : readHook("guard.mjs");
|
|
6669
8524
|
}
|
|
6670
8525
|
function ask(question) {
|
|
6671
8526
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -6678,12 +8533,12 @@ function runGlobalRestore() {
|
|
|
6678
8533
|
const p = globalPaths();
|
|
6679
8534
|
unlockProtected();
|
|
6680
8535
|
removeClaudeShim();
|
|
6681
|
-
if (
|
|
6682
|
-
writeFileSync3(p.settingsPath,
|
|
8536
|
+
if (existsSync4(p.backupPath)) {
|
|
8537
|
+
writeFileSync3(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
|
|
6683
8538
|
console.log(` Restored ${p.settingsPath} from backup.`);
|
|
6684
|
-
} else if (
|
|
8539
|
+
} else if (existsSync4(p.settingsPath)) {
|
|
6685
8540
|
try {
|
|
6686
|
-
const s = JSON.parse(
|
|
8541
|
+
const s = JSON.parse(readFileSync6(p.settingsPath, "utf-8"));
|
|
6687
8542
|
delete s.hooks;
|
|
6688
8543
|
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6689
8544
|
console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
|
|
@@ -6700,12 +8555,12 @@ function escapeRe(s) {
|
|
|
6700
8555
|
function resolveRealClaude() {
|
|
6701
8556
|
try {
|
|
6702
8557
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
6703
|
-
const
|
|
8558
|
+
const out2 = execFileSync2(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
6704
8559
|
if (process.platform === "win32") {
|
|
6705
8560
|
const low = (s) => s.toLowerCase();
|
|
6706
|
-
return
|
|
8561
|
+
return out2.find((l) => low(l).endsWith(".cmd")) || out2.find((l) => low(l).endsWith(".exe")) || out2.find((l) => low(l).endsWith(".bat")) || out2[0] || null;
|
|
6707
8562
|
}
|
|
6708
|
-
return
|
|
8563
|
+
return out2[0] || null;
|
|
6709
8564
|
} catch {
|
|
6710
8565
|
return null;
|
|
6711
8566
|
}
|
|
@@ -6719,11 +8574,11 @@ function shimTargets() {
|
|
|
6719
8574
|
return [];
|
|
6720
8575
|
}
|
|
6721
8576
|
}
|
|
6722
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) =>
|
|
8577
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join5(homedir3(), f)).filter((f) => existsSync4(f));
|
|
6723
8578
|
}
|
|
6724
8579
|
function writeShimBlock(file, block) {
|
|
6725
8580
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
6726
|
-
let content =
|
|
8581
|
+
let content = existsSync4(file) ? readFileSync6(file, "utf-8") : "";
|
|
6727
8582
|
content = content.replace(re, "");
|
|
6728
8583
|
if (block) {
|
|
6729
8584
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
@@ -6774,7 +8629,7 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6774
8629
|
let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
|
|
6775
8630
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
6776
8631
|
try {
|
|
6777
|
-
const cfg = JSON.parse(
|
|
8632
|
+
const cfg = JSON.parse(readFileSync6(p.configPath, "utf-8"));
|
|
6778
8633
|
if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
|
|
6779
8634
|
} catch {
|
|
6780
8635
|
}
|
|
@@ -6790,18 +8645,18 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6790
8645
|
mkdirSync3(p.hooksDir, { recursive: true });
|
|
6791
8646
|
mkdirSync3(p.claudeDir, { recursive: true });
|
|
6792
8647
|
unlockProtected();
|
|
6793
|
-
writeFileSync3(
|
|
6794
|
-
writeFileSync3(
|
|
6795
|
-
writeFileSync3(
|
|
6796
|
-
writeFileSync3(
|
|
8648
|
+
writeFileSync3(join5(p.hooksDir, "guard.mjs"), readGuard());
|
|
8649
|
+
writeFileSync3(join5(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
8650
|
+
writeFileSync3(join5(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
8651
|
+
writeFileSync3(join5(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
6797
8652
|
console.log(` Installed hooks \u2192 ${p.hooksDir}`);
|
|
6798
8653
|
removeClaudeShim();
|
|
6799
8654
|
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
6800
8655
|
console.log(` Wrote ${p.configPath}`);
|
|
6801
8656
|
let existing = {};
|
|
6802
|
-
if (
|
|
6803
|
-
const raw =
|
|
6804
|
-
if (!
|
|
8657
|
+
if (existsSync4(p.settingsPath)) {
|
|
8658
|
+
const raw = readFileSync6(p.settingsPath, "utf-8");
|
|
8659
|
+
if (!existsSync4(p.backupPath)) {
|
|
6805
8660
|
writeFileSync3(p.backupPath, raw);
|
|
6806
8661
|
console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
|
|
6807
8662
|
}
|
|
@@ -6811,9 +8666,9 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6811
8666
|
existing = {};
|
|
6812
8667
|
}
|
|
6813
8668
|
}
|
|
6814
|
-
const guardAbs =
|
|
6815
|
-
const auditAbs =
|
|
6816
|
-
const stopAbs =
|
|
8669
|
+
const guardAbs = join5(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
|
|
8670
|
+
const auditAbs = join5(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
|
|
8671
|
+
const stopAbs = join5(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
|
|
6817
8672
|
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
6818
8673
|
const merged = {
|
|
6819
8674
|
...existing,
|
|
@@ -6838,7 +8693,7 @@ var init_global_install = __esm({
|
|
|
6838
8693
|
"src/global-install.ts"() {
|
|
6839
8694
|
"use strict";
|
|
6840
8695
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
6841
|
-
HOOKS_DIR =
|
|
8696
|
+
HOOKS_DIR = resolve4(__dirname, "..", "hooks");
|
|
6842
8697
|
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
6843
8698
|
SHIM_END = "# <<< SolonGate shield <<<";
|
|
6844
8699
|
}
|
|
@@ -6987,8 +8842,8 @@ var init_login = __esm({
|
|
|
6987
8842
|
init_global_install();
|
|
6988
8843
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6989
8844
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6990
|
-
main().catch((
|
|
6991
|
-
console.log(`Fatal: ${
|
|
8845
|
+
main().catch((err2) => {
|
|
8846
|
+
console.log(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
6992
8847
|
process.exit(1);
|
|
6993
8848
|
});
|
|
6994
8849
|
}
|
|
@@ -7003,21 +8858,21 @@ import { createServer, request as httpRequest } from "http";
|
|
|
7003
8858
|
import { request as httpsRequest } from "https";
|
|
7004
8859
|
import { spawn as spawn2 } from "child_process";
|
|
7005
8860
|
import { URL as URL2 } from "url";
|
|
7006
|
-
import { readFileSync as
|
|
7007
|
-
import { resolve as
|
|
7008
|
-
import { homedir as
|
|
8861
|
+
import { readFileSync as readFileSync7, existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
8862
|
+
import { resolve as resolve5 } from "path";
|
|
8863
|
+
import { homedir as homedir4 } from "os";
|
|
7009
8864
|
function findCacheFile() {
|
|
7010
|
-
const dir =
|
|
8865
|
+
const dir = resolve5(homedir4(), ".solongate");
|
|
7011
8866
|
const envSel = process.env.SOLONGATE_AGENT_ID;
|
|
7012
8867
|
if (envSel) {
|
|
7013
|
-
const f =
|
|
7014
|
-
if (
|
|
8868
|
+
const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
|
|
8869
|
+
if (existsSync5(f)) return f;
|
|
7015
8870
|
}
|
|
7016
8871
|
let best = null, bestTs = -1;
|
|
7017
8872
|
try {
|
|
7018
8873
|
for (const name of readdirSync(dir)) {
|
|
7019
8874
|
if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
|
|
7020
|
-
const full =
|
|
8875
|
+
const full = resolve5(dir, name);
|
|
7021
8876
|
const ts = statSync(full).mtimeMs;
|
|
7022
8877
|
if (ts > bestTs) {
|
|
7023
8878
|
bestTs = ts;
|
|
@@ -7032,8 +8887,8 @@ function findCacheFile() {
|
|
|
7032
8887
|
function loadCfg() {
|
|
7033
8888
|
try {
|
|
7034
8889
|
const f = findCacheFile();
|
|
7035
|
-
if (f &&
|
|
7036
|
-
const c2 = JSON.parse(
|
|
8890
|
+
if (f && existsSync5(f)) {
|
|
8891
|
+
const c2 = JSON.parse(readFileSync7(f, "utf-8"));
|
|
7037
8892
|
const d = c2?.security?.dlpRedact;
|
|
7038
8893
|
const g = c2?.security?.ghost;
|
|
7039
8894
|
const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
|
|
@@ -7146,31 +9001,31 @@ function dlpGlobToRe(glob, flags) {
|
|
|
7146
9001
|
function redactString(s, cfg) {
|
|
7147
9002
|
if (!cfg || typeof s !== "string" || !s) return s;
|
|
7148
9003
|
const allow = new Set(cfg.patterns);
|
|
7149
|
-
let
|
|
7150
|
-
for (const p of DLP_PATTERNS) if (allow.has(p.name))
|
|
9004
|
+
let out2 = s;
|
|
9005
|
+
for (const p of DLP_PATTERNS) if (allow.has(p.name)) out2 = out2.replace(p.re, `[REDACTED: ${p.name}]`);
|
|
7151
9006
|
for (const c2 of cfg.custom) {
|
|
7152
9007
|
try {
|
|
7153
|
-
|
|
9008
|
+
out2 = out2.replace(dlpGlobToRe(c2.re, "g"), `[REDACTED: ${c2.name || "custom"}]`);
|
|
7154
9009
|
} catch {
|
|
7155
9010
|
}
|
|
7156
9011
|
}
|
|
7157
|
-
return
|
|
9012
|
+
return out2;
|
|
7158
9013
|
}
|
|
7159
9014
|
function redactDeep(value, cfg) {
|
|
7160
9015
|
const ghost = cfg && Array.isArray(cfg.ghost) ? cfg.ghost : null;
|
|
7161
9016
|
if (typeof value === "string") {
|
|
7162
|
-
let
|
|
7163
|
-
if (ghost && ghost.length)
|
|
7164
|
-
return
|
|
9017
|
+
let out2 = redactString(value, cfg);
|
|
9018
|
+
if (ghost && ghost.length) out2 = ghostStripLines(out2, ghost);
|
|
9019
|
+
return out2;
|
|
7165
9020
|
}
|
|
7166
9021
|
if (Array.isArray(value)) {
|
|
7167
9022
|
const arr = ghost && ghost.length ? value.filter((v) => !(typeof v === "string" && ghostMatch(v.trim(), ghost))) : value;
|
|
7168
9023
|
return arr.map((v) => redactDeep(v, cfg));
|
|
7169
9024
|
}
|
|
7170
9025
|
if (value && typeof value === "object") {
|
|
7171
|
-
const
|
|
7172
|
-
for (const [k, v] of Object.entries(value))
|
|
7173
|
-
return
|
|
9026
|
+
const out2 = {};
|
|
9027
|
+
for (const [k, v] of Object.entries(value)) out2[k] = redactDeep(v, cfg);
|
|
9028
|
+
return out2;
|
|
7174
9029
|
}
|
|
7175
9030
|
return value;
|
|
7176
9031
|
}
|
|
@@ -7309,9 +9164,9 @@ __export(logs_server_exports, {
|
|
|
7309
9164
|
runLogsServer: () => runLogsServer
|
|
7310
9165
|
});
|
|
7311
9166
|
import { createServer as createServer2 } from "http";
|
|
7312
|
-
import { readFileSync as
|
|
7313
|
-
import { resolve as
|
|
7314
|
-
import { homedir as
|
|
9167
|
+
import { readFileSync as readFileSync8, statSync as statSync2 } from "fs";
|
|
9168
|
+
import { resolve as resolve6, join as join6, isAbsolute } from "path";
|
|
9169
|
+
import { homedir as homedir5 } from "os";
|
|
7315
9170
|
import { readdirSync as readdirSync2 } from "fs";
|
|
7316
9171
|
function allowedOrigins() {
|
|
7317
9172
|
const base = [
|
|
@@ -7328,15 +9183,15 @@ function resolveLocalLogDir(rawPath) {
|
|
|
7328
9183
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
7329
9184
|
if (!dir) return null;
|
|
7330
9185
|
if (isAbsolute(dir)) return dir;
|
|
7331
|
-
return
|
|
9186
|
+
return resolve6(homedir5(), ".solongate", "local-logs");
|
|
7332
9187
|
}
|
|
7333
9188
|
async function findLogDir() {
|
|
7334
|
-
const base =
|
|
9189
|
+
const base = resolve6(homedir5(), ".solongate");
|
|
7335
9190
|
try {
|
|
7336
9191
|
const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
7337
9192
|
for (const f of files) {
|
|
7338
9193
|
try {
|
|
7339
|
-
const c2 = JSON.parse(
|
|
9194
|
+
const c2 = JSON.parse(readFileSync8(join6(base, f), "utf-8"));
|
|
7340
9195
|
const p = c2?.security?.localLogs?.path;
|
|
7341
9196
|
if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
|
|
7342
9197
|
} catch {
|
|
@@ -7345,7 +9200,7 @@ async function findLogDir() {
|
|
|
7345
9200
|
} catch {
|
|
7346
9201
|
}
|
|
7347
9202
|
try {
|
|
7348
|
-
const cfgRaw =
|
|
9203
|
+
const cfgRaw = readFileSync8(join6(base, "cloud-guard.json"), "utf-8");
|
|
7349
9204
|
const { apiKey, apiUrl } = JSON.parse(cfgRaw);
|
|
7350
9205
|
if (apiKey) {
|
|
7351
9206
|
const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
|
|
@@ -7374,7 +9229,7 @@ function setCors(req, res) {
|
|
|
7374
9229
|
}
|
|
7375
9230
|
function fileInfo(dir) {
|
|
7376
9231
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
7377
|
-
const file =
|
|
9232
|
+
const file = join6(dir, LOG_FILENAME);
|
|
7378
9233
|
try {
|
|
7379
9234
|
const st = statSync2(file);
|
|
7380
9235
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
@@ -7435,7 +9290,7 @@ async function runLogsServer() {
|
|
|
7435
9290
|
return;
|
|
7436
9291
|
}
|
|
7437
9292
|
try {
|
|
7438
|
-
const text =
|
|
9293
|
+
const text = readFileSync8(info.file, "utf-8");
|
|
7439
9294
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
|
|
7440
9295
|
res.end(text);
|
|
7441
9296
|
} catch {
|
|
@@ -7465,12 +9320,12 @@ async function runLogsServer() {
|
|
|
7465
9320
|
process.stdout.write(`[SolonGate] Keep this running; the dashboard reads your logs live from here. Ctrl+C to stop.
|
|
7466
9321
|
`);
|
|
7467
9322
|
});
|
|
7468
|
-
server.on("error", (
|
|
7469
|
-
if (
|
|
9323
|
+
server.on("error", (err2) => {
|
|
9324
|
+
if (err2.code === "EADDRINUSE") {
|
|
7470
9325
|
process.stderr.write(`[SolonGate] Port ${port} is already in use. Pass --port <n> or set SOLONGATE_LOGS_PORT.
|
|
7471
9326
|
`);
|
|
7472
9327
|
} else {
|
|
7473
|
-
process.stderr.write(`[SolonGate] Local logs agent error: ${
|
|
9328
|
+
process.stderr.write(`[SolonGate] Local logs agent error: ${err2.message}
|
|
7474
9329
|
`);
|
|
7475
9330
|
}
|
|
7476
9331
|
process.exit(1);
|
|
@@ -7487,8 +9342,8 @@ var init_logs_server = __esm({
|
|
|
7487
9342
|
|
|
7488
9343
|
// src/inject.ts
|
|
7489
9344
|
var inject_exports = {};
|
|
7490
|
-
import { readFileSync as
|
|
7491
|
-
import { resolve as
|
|
9345
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
|
|
9346
|
+
import { resolve as resolve7 } from "path";
|
|
7492
9347
|
import { execSync } from "child_process";
|
|
7493
9348
|
function parseInjectArgs(argv) {
|
|
7494
9349
|
const args = argv.slice(2);
|
|
@@ -7545,9 +9400,9 @@ WHAT IT DOES
|
|
|
7545
9400
|
`);
|
|
7546
9401
|
}
|
|
7547
9402
|
function detectProject() {
|
|
7548
|
-
if (!
|
|
9403
|
+
if (!existsSync6(resolve7("package.json"))) return false;
|
|
7549
9404
|
try {
|
|
7550
|
-
const pkg = JSON.parse(
|
|
9405
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7551
9406
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7552
9407
|
return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
|
|
7553
9408
|
} catch {
|
|
@@ -7556,18 +9411,18 @@ function detectProject() {
|
|
|
7556
9411
|
}
|
|
7557
9412
|
function findTsEntryFile() {
|
|
7558
9413
|
try {
|
|
7559
|
-
const pkg = JSON.parse(
|
|
9414
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7560
9415
|
if (pkg.bin) {
|
|
7561
9416
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
7562
9417
|
if (typeof binPath === "string") {
|
|
7563
9418
|
const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
7564
|
-
if (
|
|
7565
|
-
if (
|
|
9419
|
+
if (existsSync6(resolve7(srcPath))) return resolve7(srcPath);
|
|
9420
|
+
if (existsSync6(resolve7(binPath))) return resolve7(binPath);
|
|
7566
9421
|
}
|
|
7567
9422
|
}
|
|
7568
9423
|
if (pkg.main) {
|
|
7569
9424
|
const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
7570
|
-
if (
|
|
9425
|
+
if (existsSync6(resolve7(srcPath))) return resolve7(srcPath);
|
|
7571
9426
|
}
|
|
7572
9427
|
} catch {
|
|
7573
9428
|
}
|
|
@@ -7580,10 +9435,10 @@ function findTsEntryFile() {
|
|
|
7580
9435
|
"main.ts"
|
|
7581
9436
|
];
|
|
7582
9437
|
for (const c2 of candidates) {
|
|
7583
|
-
const full =
|
|
7584
|
-
if (
|
|
9438
|
+
const full = resolve7(c2);
|
|
9439
|
+
if (existsSync6(full)) {
|
|
7585
9440
|
try {
|
|
7586
|
-
const content =
|
|
9441
|
+
const content = readFileSync9(full, "utf-8");
|
|
7587
9442
|
if (content.includes("McpServer") || content.includes("McpServer")) {
|
|
7588
9443
|
return full;
|
|
7589
9444
|
}
|
|
@@ -7592,18 +9447,18 @@ function findTsEntryFile() {
|
|
|
7592
9447
|
}
|
|
7593
9448
|
}
|
|
7594
9449
|
for (const c2 of candidates) {
|
|
7595
|
-
if (
|
|
9450
|
+
if (existsSync6(resolve7(c2))) return resolve7(c2);
|
|
7596
9451
|
}
|
|
7597
9452
|
return null;
|
|
7598
9453
|
}
|
|
7599
9454
|
function detectPackageManager() {
|
|
7600
|
-
if (
|
|
7601
|
-
if (
|
|
9455
|
+
if (existsSync6(resolve7("pnpm-lock.yaml"))) return "pnpm";
|
|
9456
|
+
if (existsSync6(resolve7("yarn.lock"))) return "yarn";
|
|
7602
9457
|
return "npm";
|
|
7603
9458
|
}
|
|
7604
9459
|
function installSdk() {
|
|
7605
9460
|
try {
|
|
7606
|
-
const pkg = JSON.parse(
|
|
9461
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7607
9462
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7608
9463
|
if (allDeps["@solongate/proxy"]) {
|
|
7609
9464
|
log3(" @solongate/proxy already installed");
|
|
@@ -7617,14 +9472,14 @@ function installSdk() {
|
|
|
7617
9472
|
try {
|
|
7618
9473
|
execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
7619
9474
|
return true;
|
|
7620
|
-
} catch (
|
|
7621
|
-
log3(` Failed to install: ${
|
|
9475
|
+
} catch (err2) {
|
|
9476
|
+
log3(` Failed to install: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
7622
9477
|
log3(" You can install manually: npm install @solongate/proxy");
|
|
7623
9478
|
return false;
|
|
7624
9479
|
}
|
|
7625
9480
|
}
|
|
7626
9481
|
function injectTypeScript(filePath) {
|
|
7627
|
-
const original =
|
|
9482
|
+
const original = readFileSync9(filePath, "utf-8");
|
|
7628
9483
|
const changes = [];
|
|
7629
9484
|
let modified = original;
|
|
7630
9485
|
if (modified.includes("SecureMcpServer")) {
|
|
@@ -7740,8 +9595,8 @@ async function main2() {
|
|
|
7740
9595
|
process.exit(1);
|
|
7741
9596
|
}
|
|
7742
9597
|
log3(" Language: TypeScript");
|
|
7743
|
-
const entryFile = opts.file ?
|
|
7744
|
-
if (!entryFile || !
|
|
9598
|
+
const entryFile = opts.file ? resolve7(opts.file) : findTsEntryFile();
|
|
9599
|
+
if (!entryFile || !existsSync6(entryFile)) {
|
|
7745
9600
|
log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
|
|
7746
9601
|
log3("");
|
|
7747
9602
|
log3(" Specify it manually: --file <path>");
|
|
@@ -7754,7 +9609,7 @@ async function main2() {
|
|
|
7754
9609
|
log3("");
|
|
7755
9610
|
const backupPath = entryFile + ".solongate-backup";
|
|
7756
9611
|
if (opts.restore) {
|
|
7757
|
-
if (!
|
|
9612
|
+
if (!existsSync6(backupPath)) {
|
|
7758
9613
|
log3(" No backup found. Nothing to restore.");
|
|
7759
9614
|
process.exit(1);
|
|
7760
9615
|
}
|
|
@@ -7790,7 +9645,7 @@ async function main2() {
|
|
|
7790
9645
|
log3(" To apply: npx @solongate/proxy inject");
|
|
7791
9646
|
process.exit(0);
|
|
7792
9647
|
}
|
|
7793
|
-
if (!
|
|
9648
|
+
if (!existsSync6(backupPath)) {
|
|
7794
9649
|
copyFileSync(entryFile, backupPath);
|
|
7795
9650
|
log3("");
|
|
7796
9651
|
log3(` Backup: ${backupPath}`);
|
|
@@ -7815,8 +9670,8 @@ var init_inject = __esm({
|
|
|
7815
9670
|
"src/inject.ts"() {
|
|
7816
9671
|
"use strict";
|
|
7817
9672
|
init_cli_utils();
|
|
7818
|
-
main2().catch((
|
|
7819
|
-
log3(`Fatal: ${
|
|
9673
|
+
main2().catch((err2) => {
|
|
9674
|
+
log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
7820
9675
|
process.exit(1);
|
|
7821
9676
|
});
|
|
7822
9677
|
}
|
|
@@ -7824,8 +9679,8 @@ var init_inject = __esm({
|
|
|
7824
9679
|
|
|
7825
9680
|
// src/create.ts
|
|
7826
9681
|
var create_exports = {};
|
|
7827
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as
|
|
7828
|
-
import { resolve as
|
|
9682
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
9683
|
+
import { resolve as resolve8, join as join7 } from "path";
|
|
7829
9684
|
import { execSync as execSync2 } from "child_process";
|
|
7830
9685
|
function withSpinner(message, fn) {
|
|
7831
9686
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -7840,11 +9695,11 @@ function withSpinner(message, fn) {
|
|
|
7840
9695
|
process.stderr.write(`\r ${c.green}\u2713${c.reset} ${message}${" ".repeat(20)}
|
|
7841
9696
|
`);
|
|
7842
9697
|
return result;
|
|
7843
|
-
} catch (
|
|
9698
|
+
} catch (err2) {
|
|
7844
9699
|
clearInterval(id);
|
|
7845
9700
|
process.stderr.write(`\r ${c.red}\u2717${c.reset} ${message} \u2014 failed${" ".repeat(10)}
|
|
7846
9701
|
`);
|
|
7847
|
-
throw
|
|
9702
|
+
throw err2;
|
|
7848
9703
|
}
|
|
7849
9704
|
}
|
|
7850
9705
|
function parseCreateArgs(argv) {
|
|
@@ -7905,7 +9760,7 @@ EXAMPLES
|
|
|
7905
9760
|
}
|
|
7906
9761
|
function createProject(dir, name, _policy) {
|
|
7907
9762
|
writeFileSync5(
|
|
7908
|
-
|
|
9763
|
+
join7(dir, "package.json"),
|
|
7909
9764
|
JSON.stringify(
|
|
7910
9765
|
{
|
|
7911
9766
|
name,
|
|
@@ -7935,7 +9790,7 @@ function createProject(dir, name, _policy) {
|
|
|
7935
9790
|
) + "\n"
|
|
7936
9791
|
);
|
|
7937
9792
|
writeFileSync5(
|
|
7938
|
-
|
|
9793
|
+
join7(dir, "tsconfig.json"),
|
|
7939
9794
|
JSON.stringify(
|
|
7940
9795
|
{
|
|
7941
9796
|
compilerOptions: {
|
|
@@ -7955,9 +9810,9 @@ function createProject(dir, name, _policy) {
|
|
|
7955
9810
|
2
|
|
7956
9811
|
) + "\n"
|
|
7957
9812
|
);
|
|
7958
|
-
mkdirSync4(
|
|
9813
|
+
mkdirSync4(join7(dir, "src"), { recursive: true });
|
|
7959
9814
|
writeFileSync5(
|
|
7960
|
-
|
|
9815
|
+
join7(dir, "src", "index.ts"),
|
|
7961
9816
|
`#!/usr/bin/env node
|
|
7962
9817
|
|
|
7963
9818
|
console.log = (...args: unknown[]) => {
|
|
@@ -7999,7 +9854,7 @@ console.log('Press Ctrl+C to stop.');
|
|
|
7999
9854
|
`
|
|
8000
9855
|
);
|
|
8001
9856
|
writeFileSync5(
|
|
8002
|
-
|
|
9857
|
+
join7(dir, ".mcp.json"),
|
|
8003
9858
|
JSON.stringify(
|
|
8004
9859
|
{
|
|
8005
9860
|
mcpServers: {
|
|
@@ -8017,12 +9872,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
8017
9872
|
) + "\n"
|
|
8018
9873
|
);
|
|
8019
9874
|
writeFileSync5(
|
|
8020
|
-
|
|
9875
|
+
join7(dir, ".env"),
|
|
8021
9876
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
8022
9877
|
`
|
|
8023
9878
|
);
|
|
8024
9879
|
writeFileSync5(
|
|
8025
|
-
|
|
9880
|
+
join7(dir, ".gitignore"),
|
|
8026
9881
|
`node_modules/
|
|
8027
9882
|
dist/
|
|
8028
9883
|
*.solongate-backup
|
|
@@ -8034,9 +9889,9 @@ dist/
|
|
|
8034
9889
|
}
|
|
8035
9890
|
async function main3() {
|
|
8036
9891
|
const opts = parseCreateArgs(process.argv);
|
|
8037
|
-
const dir =
|
|
9892
|
+
const dir = resolve8(opts.name);
|
|
8038
9893
|
printBanner("Create MCP Server");
|
|
8039
|
-
if (
|
|
9894
|
+
if (existsSync7(dir)) {
|
|
8040
9895
|
log3(` ${c.red}Error:${c.reset} Directory "${opts.name}" already exists.`);
|
|
8041
9896
|
process.exit(1);
|
|
8042
9897
|
}
|
|
@@ -8106,8 +9961,8 @@ var init_create = __esm({
|
|
|
8106
9961
|
"src/create.ts"() {
|
|
8107
9962
|
"use strict";
|
|
8108
9963
|
init_cli_utils();
|
|
8109
|
-
main3().catch((
|
|
8110
|
-
log3(`Fatal: ${
|
|
9964
|
+
main3().catch((err2) => {
|
|
9965
|
+
log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
8111
9966
|
process.exit(1);
|
|
8112
9967
|
});
|
|
8113
9968
|
}
|
|
@@ -8115,14 +9970,14 @@ var init_create = __esm({
|
|
|
8115
9970
|
|
|
8116
9971
|
// src/pull-push.ts
|
|
8117
9972
|
var pull_push_exports = {};
|
|
8118
|
-
import { readFileSync as
|
|
8119
|
-
import { resolve as
|
|
9973
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
9974
|
+
import { resolve as resolve9 } from "path";
|
|
8120
9975
|
function loadEnv() {
|
|
8121
9976
|
if (process.env.SOLONGATE_API_KEY) return;
|
|
8122
|
-
const envPath =
|
|
8123
|
-
if (!
|
|
9977
|
+
const envPath = resolve9(".env");
|
|
9978
|
+
if (!existsSync8(envPath)) return;
|
|
8124
9979
|
try {
|
|
8125
|
-
const content =
|
|
9980
|
+
const content = readFileSync10(envPath, "utf-8");
|
|
8126
9981
|
for (const line of content.split("\n")) {
|
|
8127
9982
|
const trimmed = line.trim();
|
|
8128
9983
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -8164,7 +10019,7 @@ function parseCliArgs() {
|
|
|
8164
10019
|
}
|
|
8165
10020
|
}
|
|
8166
10021
|
if (!apiKey) {
|
|
8167
|
-
log5(
|
|
10022
|
+
log5(red2("ERROR: API key not found."));
|
|
8168
10023
|
log5("");
|
|
8169
10024
|
log5("Set it in .env file:");
|
|
8170
10025
|
log5(" SOLONGATE_API_KEY=sg_live_...");
|
|
@@ -8174,10 +10029,10 @@ function parseCliArgs() {
|
|
|
8174
10029
|
process.exit(1);
|
|
8175
10030
|
}
|
|
8176
10031
|
if (!apiKey.startsWith("sg_live_")) {
|
|
8177
|
-
log5(
|
|
10032
|
+
log5(red2("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
8178
10033
|
process.exit(1);
|
|
8179
10034
|
}
|
|
8180
|
-
return { command, apiKey, file:
|
|
10035
|
+
return { command, apiKey, file: resolve9(file), policyId };
|
|
8181
10036
|
}
|
|
8182
10037
|
async function listPolicies(apiKey) {
|
|
8183
10038
|
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
@@ -8187,21 +10042,21 @@ async function listPolicies(apiKey) {
|
|
|
8187
10042
|
const data = await res.json();
|
|
8188
10043
|
return data.policies ?? [];
|
|
8189
10044
|
}
|
|
8190
|
-
async function
|
|
10045
|
+
async function list3(apiKey, policyId) {
|
|
8191
10046
|
const policies = await listPolicies(apiKey);
|
|
8192
10047
|
if (policies.length === 0) {
|
|
8193
|
-
log5(
|
|
8194
|
-
log5(
|
|
10048
|
+
log5(yellow2("No policies found. Create one in the dashboard first."));
|
|
10049
|
+
log5(dim2(" https://dashboard.solongate.com/policies"));
|
|
8195
10050
|
return;
|
|
8196
10051
|
}
|
|
8197
10052
|
if (policyId) {
|
|
8198
10053
|
const match = policies.find((p) => p.id === policyId);
|
|
8199
10054
|
if (!match) {
|
|
8200
|
-
log5(
|
|
10055
|
+
log5(red2(`Policy not found: ${policyId}`));
|
|
8201
10056
|
log5("");
|
|
8202
10057
|
log5("Available policies:");
|
|
8203
10058
|
for (const p of policies) {
|
|
8204
|
-
log5(` ${
|
|
10059
|
+
log5(` ${dim2("\u2022")} ${p.id}`);
|
|
8205
10060
|
}
|
|
8206
10061
|
process.exit(1);
|
|
8207
10062
|
}
|
|
@@ -8210,8 +10065,8 @@ async function list(apiKey, policyId) {
|
|
|
8210
10065
|
return;
|
|
8211
10066
|
}
|
|
8212
10067
|
log5("");
|
|
8213
|
-
log5(
|
|
8214
|
-
log5(
|
|
10068
|
+
log5(bold2(` Policies (${policies.length})`));
|
|
10069
|
+
log5(dim2(" \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"));
|
|
8215
10070
|
log5("");
|
|
8216
10071
|
const fullPolicies = await Promise.all(
|
|
8217
10072
|
policies.map(
|
|
@@ -8221,113 +10076,113 @@ async function list(apiKey, policyId) {
|
|
|
8221
10076
|
for (const { policy, rules } of fullPolicies) {
|
|
8222
10077
|
printPolicySummary(policy, [...rules]);
|
|
8223
10078
|
}
|
|
8224
|
-
log5(
|
|
10079
|
+
log5(dim2(" \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"));
|
|
8225
10080
|
log5("");
|
|
8226
|
-
log5(` ${
|
|
8227
|
-
log5(` ${
|
|
8228
|
-
log5(` ${
|
|
10081
|
+
log5(` ${dim2("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
10082
|
+
log5(` ${dim2("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
10083
|
+
log5(` ${dim2("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
8229
10084
|
log5("");
|
|
8230
10085
|
}
|
|
8231
10086
|
function printPolicySummary(p, rules) {
|
|
8232
10087
|
const ruleCount = rules.length;
|
|
8233
10088
|
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
8234
10089
|
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
8235
|
-
log5(` ${
|
|
8236
|
-
log5(` ${
|
|
8237
|
-
log5(` ${
|
|
10090
|
+
log5(` ${cyan2(p.id)}`);
|
|
10091
|
+
log5(` ${bold2(p.name)} ${dim2(`v${p.version ?? "?"}`)}`);
|
|
10092
|
+
log5(` ${dim2("Rules:")} ${ruleCount} ${green2(`${allowCount} ALLOW`)} ${red2(`${denyCount} DENY`)}`);
|
|
8238
10093
|
if (p.created_at) {
|
|
8239
|
-
log5(` ${
|
|
10094
|
+
log5(` ${dim2("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
8240
10095
|
}
|
|
8241
10096
|
log5("");
|
|
8242
10097
|
}
|
|
8243
10098
|
function printPolicyDetail(policy) {
|
|
8244
10099
|
log5("");
|
|
8245
|
-
log5(
|
|
8246
|
-
log5(` ${
|
|
10100
|
+
log5(bold2(` ${policy.name}`));
|
|
10101
|
+
log5(` ${dim2("ID:")} ${cyan2(policy.id)} ${dim2("Version:")} ${policy.version} ${dim2("Rules:")} ${policy.rules.length}`);
|
|
8247
10102
|
log5("");
|
|
8248
10103
|
if (policy.rules.length === 0) {
|
|
8249
|
-
log5(
|
|
10104
|
+
log5(yellow2(" No rules defined."));
|
|
8250
10105
|
log5("");
|
|
8251
10106
|
return;
|
|
8252
10107
|
}
|
|
8253
|
-
log5(
|
|
10108
|
+
log5(dim2(" \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"));
|
|
8254
10109
|
for (const rule of policy.rules) {
|
|
8255
|
-
const effectColor = rule.effect === "ALLOW" ?
|
|
10110
|
+
const effectColor = rule.effect === "ALLOW" ? green2 : red2;
|
|
8256
10111
|
log5("");
|
|
8257
|
-
log5(` ${effectColor(rule.effect.padEnd(5))} ${
|
|
10112
|
+
log5(` ${effectColor(rule.effect.padEnd(5))} ${bold2(rule.toolPattern)} ${dim2(`P:${rule.priority}`)}`);
|
|
8258
10113
|
if (rule.description) {
|
|
8259
|
-
log5(` ${
|
|
10114
|
+
log5(` ${dim2(rule.description)}`);
|
|
8260
10115
|
}
|
|
8261
|
-
log5(` ${
|
|
10116
|
+
log5(` ${dim2(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
8262
10117
|
if (rule.pathConstraints) {
|
|
8263
10118
|
const pc = rule.pathConstraints;
|
|
8264
10119
|
if (pc.rootDirectory) log5(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
8265
|
-
if (pc.allowed?.length) log5(` ${
|
|
8266
|
-
if (pc.denied?.length) log5(` ${
|
|
10120
|
+
if (pc.allowed?.length) log5(` ${green2("PATHS")} ${pc.allowed.join(", ")}`);
|
|
10121
|
+
if (pc.denied?.length) log5(` ${red2("DENY")} ${pc.denied.join(", ")}`);
|
|
8267
10122
|
}
|
|
8268
10123
|
if (rule.commandConstraints) {
|
|
8269
10124
|
const cc = rule.commandConstraints;
|
|
8270
|
-
if (cc.allowed?.length) log5(` ${
|
|
8271
|
-
if (cc.denied?.length) log5(` ${
|
|
10125
|
+
if (cc.allowed?.length) log5(` ${green2("CMDS")} ${cc.allowed.join(", ")}`);
|
|
10126
|
+
if (cc.denied?.length) log5(` ${red2("DENY")} ${cc.denied.join(", ")}`);
|
|
8272
10127
|
}
|
|
8273
10128
|
if (rule.filenameConstraints) {
|
|
8274
10129
|
const fc = rule.filenameConstraints;
|
|
8275
|
-
if (fc.allowed?.length) log5(` ${
|
|
8276
|
-
if (fc.denied?.length) log5(` ${
|
|
10130
|
+
if (fc.allowed?.length) log5(` ${green2("FILES")} ${fc.allowed.join(", ")}`);
|
|
10131
|
+
if (fc.denied?.length) log5(` ${red2("DENY")} ${fc.denied.join(", ")}`);
|
|
8277
10132
|
}
|
|
8278
10133
|
if (rule.urlConstraints) {
|
|
8279
10134
|
const uc = rule.urlConstraints;
|
|
8280
|
-
if (uc.allowed?.length) log5(` ${
|
|
8281
|
-
if (uc.denied?.length) log5(` ${
|
|
10135
|
+
if (uc.allowed?.length) log5(` ${green2("URLS")} ${uc.allowed.join(", ")}`);
|
|
10136
|
+
if (uc.denied?.length) log5(` ${red2("DENY")} ${uc.denied.join(", ")}`);
|
|
8282
10137
|
}
|
|
8283
10138
|
}
|
|
8284
10139
|
log5("");
|
|
8285
|
-
log5(
|
|
10140
|
+
log5(dim2(" \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"));
|
|
8286
10141
|
log5("");
|
|
8287
10142
|
}
|
|
8288
10143
|
async function pull(apiKey, file, policyId) {
|
|
8289
10144
|
if (!policyId) {
|
|
8290
10145
|
const policies = await listPolicies(apiKey);
|
|
8291
10146
|
if (policies.length === 0) {
|
|
8292
|
-
log5(
|
|
10147
|
+
log5(red2("No policies found. Create one in the dashboard first."));
|
|
8293
10148
|
process.exit(1);
|
|
8294
10149
|
}
|
|
8295
10150
|
if (policies.length === 1) {
|
|
8296
10151
|
policyId = policies[0].id;
|
|
8297
|
-
log5(
|
|
10152
|
+
log5(dim2(`Auto-selecting only policy: ${policyId}`));
|
|
8298
10153
|
} else {
|
|
8299
|
-
log5(
|
|
10154
|
+
log5(yellow2(`Found ${policies.length} policies:`));
|
|
8300
10155
|
log5("");
|
|
8301
10156
|
for (const p of policies) {
|
|
8302
|
-
log5(` ${
|
|
10157
|
+
log5(` ${cyan2(p.id)} ${p.name} ${dim2(`v${p.version ?? "?"}`)}`);
|
|
8303
10158
|
}
|
|
8304
10159
|
log5("");
|
|
8305
10160
|
log5("Use --policy-id <ID> to specify which one to pull.");
|
|
8306
10161
|
process.exit(1);
|
|
8307
10162
|
}
|
|
8308
10163
|
}
|
|
8309
|
-
log5(`Pulling ${
|
|
10164
|
+
log5(`Pulling ${cyan2(policyId)} from dashboard...`);
|
|
8310
10165
|
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
8311
10166
|
const { id: _id, ...policyWithoutId } = policy;
|
|
8312
10167
|
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
8313
10168
|
writeFileSync6(file, json, "utf-8");
|
|
8314
10169
|
log5("");
|
|
8315
|
-
log5(
|
|
8316
|
-
log5(` ${
|
|
8317
|
-
log5(` ${
|
|
8318
|
-
log5(` ${
|
|
10170
|
+
log5(green2(" Saved to: ") + file);
|
|
10171
|
+
log5(` ${dim2("Name:")} ${policy.name}`);
|
|
10172
|
+
log5(` ${dim2("Version:")} ${policy.version}`);
|
|
10173
|
+
log5(` ${dim2("Rules:")} ${policy.rules.length}`);
|
|
8319
10174
|
log5("");
|
|
8320
|
-
log5(
|
|
8321
|
-
log5(
|
|
10175
|
+
log5(dim2("The policy file does not contain an ID."));
|
|
10176
|
+
log5(dim2("Use --policy-id to specify the target when pushing/pulling."));
|
|
8322
10177
|
log5("");
|
|
8323
10178
|
}
|
|
8324
10179
|
async function push(apiKey, file, policyId) {
|
|
8325
|
-
if (!
|
|
8326
|
-
log5(
|
|
10180
|
+
if (!existsSync8(file)) {
|
|
10181
|
+
log5(red2(`ERROR: File not found: ${file}`));
|
|
8327
10182
|
process.exit(1);
|
|
8328
10183
|
}
|
|
8329
10184
|
if (!policyId) {
|
|
8330
|
-
log5(
|
|
10185
|
+
log5(red2("ERROR: --policy-id is required for push."));
|
|
8331
10186
|
log5("");
|
|
8332
10187
|
log5("This determines which cloud policy to update.");
|
|
8333
10188
|
log5("");
|
|
@@ -8339,18 +10194,18 @@ async function push(apiKey, file, policyId) {
|
|
|
8339
10194
|
log5(" solongate-proxy list");
|
|
8340
10195
|
process.exit(1);
|
|
8341
10196
|
}
|
|
8342
|
-
const content =
|
|
10197
|
+
const content = readFileSync10(file, "utf-8");
|
|
8343
10198
|
let policy;
|
|
8344
10199
|
try {
|
|
8345
10200
|
policy = JSON.parse(content);
|
|
8346
10201
|
} catch {
|
|
8347
|
-
log5(
|
|
10202
|
+
log5(red2(`ERROR: Invalid JSON in ${file}`));
|
|
8348
10203
|
process.exit(1);
|
|
8349
10204
|
}
|
|
8350
|
-
log5(`Pushing to ${
|
|
8351
|
-
log5(` ${
|
|
8352
|
-
log5(` ${
|
|
8353
|
-
log5(` ${
|
|
10205
|
+
log5(`Pushing to ${cyan2(policyId)}...`);
|
|
10206
|
+
log5(` ${dim2("File:")} ${file}`);
|
|
10207
|
+
log5(` ${dim2("Name:")} ${policy.name || "Unnamed"}`);
|
|
10208
|
+
log5(` ${dim2("Rules:")} ${(policy.rules || []).length}`);
|
|
8354
10209
|
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
8355
10210
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
8356
10211
|
});
|
|
@@ -8372,14 +10227,14 @@ async function push(apiKey, file, policyId) {
|
|
|
8372
10227
|
});
|
|
8373
10228
|
if (!res.ok) {
|
|
8374
10229
|
const body = await res.text().catch(() => "");
|
|
8375
|
-
log5(
|
|
10230
|
+
log5(red2(`ERROR: Push failed (${res.status}): ${body}`));
|
|
8376
10231
|
process.exit(1);
|
|
8377
10232
|
}
|
|
8378
10233
|
const data = await res.json();
|
|
8379
10234
|
log5("");
|
|
8380
|
-
log5(
|
|
8381
|
-
log5(` ${
|
|
8382
|
-
log5(` ${
|
|
10235
|
+
log5(green2(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
10236
|
+
log5(` ${dim2("Policy ID:")} ${policyId}`);
|
|
10237
|
+
log5(` ${dim2("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
8383
10238
|
log5("");
|
|
8384
10239
|
}
|
|
8385
10240
|
async function main4() {
|
|
@@ -8390,41 +10245,41 @@ async function main4() {
|
|
|
8390
10245
|
} else if (command === "push") {
|
|
8391
10246
|
await push(apiKey, file, policyId);
|
|
8392
10247
|
} else if (command === "list" || command === "ls") {
|
|
8393
|
-
await
|
|
10248
|
+
await list3(apiKey, policyId);
|
|
8394
10249
|
} else {
|
|
8395
|
-
log5(
|
|
10250
|
+
log5(red2(`Unknown command: ${command}`));
|
|
8396
10251
|
log5("");
|
|
8397
|
-
log5(
|
|
10252
|
+
log5(bold2("Usage:"));
|
|
8398
10253
|
log5(" solongate-proxy list List all policies");
|
|
8399
10254
|
log5(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
8400
10255
|
log5(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
8401
10256
|
log5(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
8402
10257
|
log5("");
|
|
8403
|
-
log5(
|
|
10258
|
+
log5(bold2("Flags:"));
|
|
8404
10259
|
log5(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
8405
10260
|
log5(" --file, -f <path> Local file path (default: policy.json)");
|
|
8406
10261
|
log5(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
8407
10262
|
log5("");
|
|
8408
10263
|
process.exit(1);
|
|
8409
10264
|
}
|
|
8410
|
-
} catch (
|
|
8411
|
-
log5(
|
|
10265
|
+
} catch (err2) {
|
|
10266
|
+
log5(red2(`ERROR: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
8412
10267
|
process.exit(1);
|
|
8413
10268
|
}
|
|
8414
10269
|
}
|
|
8415
|
-
var log5,
|
|
10270
|
+
var log5, dim2, bold2, green2, red2, yellow2, cyan2, magenta, API_URL;
|
|
8416
10271
|
var init_pull_push = __esm({
|
|
8417
10272
|
"src/pull-push.ts"() {
|
|
8418
10273
|
"use strict";
|
|
8419
10274
|
init_config();
|
|
8420
10275
|
log5 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
8421
10276
|
`);
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
10277
|
+
dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
10278
|
+
bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
10279
|
+
green2 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
10280
|
+
red2 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
10281
|
+
yellow2 = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
10282
|
+
cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
8428
10283
|
magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
8429
10284
|
API_URL = "https://api.solongate.com";
|
|
8430
10285
|
main4();
|
|
@@ -9329,12 +11184,12 @@ function looksLikeFilename(s) {
|
|
|
9329
11184
|
}
|
|
9330
11185
|
|
|
9331
11186
|
// src/policy-engine/opa/request-adapter.ts
|
|
9332
|
-
function toOpaInput(
|
|
9333
|
-
const args =
|
|
11187
|
+
function toOpaInput(request2) {
|
|
11188
|
+
const args = request2.arguments ?? {};
|
|
9334
11189
|
return {
|
|
9335
|
-
tool_name:
|
|
9336
|
-
permission:
|
|
9337
|
-
trust_level:
|
|
11190
|
+
tool_name: request2.toolName,
|
|
11191
|
+
permission: request2.requiredPermission ?? "",
|
|
11192
|
+
trust_level: request2.context.trustLevel,
|
|
9338
11193
|
arguments: args,
|
|
9339
11194
|
paths: extractPathArguments(args),
|
|
9340
11195
|
commands: extractCommandArguments(args),
|
|
@@ -9407,12 +11262,12 @@ var OpaEvaluator = class {
|
|
|
9407
11262
|
// Evaluates an execution request against the loaded OPA policy.
|
|
9408
11263
|
// Returns a PolicyDecision matching the same interface as the legacy evaluator,
|
|
9409
11264
|
// ensuring full backward compatibility.
|
|
9410
|
-
evaluate(
|
|
11265
|
+
evaluate(request2) {
|
|
9411
11266
|
if (!this.policy || !this.initialized) {
|
|
9412
11267
|
throw new Error("OPA policy not loaded. Call loadBundle() or loadWasm() first.");
|
|
9413
11268
|
}
|
|
9414
11269
|
const startTime = performance.now();
|
|
9415
|
-
const input = toOpaInput(
|
|
11270
|
+
const input = toOpaInput(request2);
|
|
9416
11271
|
const results = this.policy.evaluate(input);
|
|
9417
11272
|
const endTime = performance.now();
|
|
9418
11273
|
const decision = results?.[0]?.result;
|
|
@@ -9482,11 +11337,11 @@ var PolicyEngine = class {
|
|
|
9482
11337
|
// Never throws for denials - denial is a normal outcome, not an error.
|
|
9483
11338
|
// OPA WASM is the only evaluator. If no WASM bundle is loaded, evaluation
|
|
9484
11339
|
// fails CLOSED (default DENY) — there is no legacy fallback.
|
|
9485
|
-
evaluate(
|
|
11340
|
+
evaluate(request2) {
|
|
9486
11341
|
const startTime = performance.now();
|
|
9487
11342
|
let decision;
|
|
9488
11343
|
if (this.opaEvaluator?.isReady()) {
|
|
9489
|
-
decision = this.opaEvaluator.evaluate(
|
|
11344
|
+
decision = this.opaEvaluator.evaluate(request2);
|
|
9490
11345
|
} else {
|
|
9491
11346
|
decision = {
|
|
9492
11347
|
effect: DEFAULT_POLICY_EFFECT,
|
|
@@ -9499,7 +11354,7 @@ var PolicyEngine = class {
|
|
|
9499
11354
|
const elapsed = performance.now() - startTime;
|
|
9500
11355
|
if (elapsed > this.timeoutMs) {
|
|
9501
11356
|
console.warn(
|
|
9502
|
-
`[SolonGate] Policy evaluation took ${elapsed.toFixed(1)}ms (limit: ${this.timeoutMs}ms) for tool "${
|
|
11357
|
+
`[SolonGate] Policy evaluation took ${elapsed.toFixed(1)}ms (limit: ${this.timeoutMs}ms) for tool "${request2.toolName}"`
|
|
9503
11358
|
);
|
|
9504
11359
|
}
|
|
9505
11360
|
return decision;
|
|
@@ -9762,7 +11617,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9762
11617
|
const requestId = randomUUID2();
|
|
9763
11618
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
9764
11619
|
const context = createSecurityContext({ requestId });
|
|
9765
|
-
const
|
|
11620
|
+
const request2 = {
|
|
9766
11621
|
context,
|
|
9767
11622
|
toolName: params.name,
|
|
9768
11623
|
serverName: "default",
|
|
@@ -9779,7 +11634,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9779
11634
|
if (!toolLimit.allowed) {
|
|
9780
11635
|
const result = {
|
|
9781
11636
|
status: "ERROR",
|
|
9782
|
-
request,
|
|
11637
|
+
request: request2,
|
|
9783
11638
|
error: new RateLimitError(params.name, options.rateLimitPerTool),
|
|
9784
11639
|
timestamp
|
|
9785
11640
|
};
|
|
@@ -9796,7 +11651,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9796
11651
|
if (!globalLimit.allowed) {
|
|
9797
11652
|
const result = {
|
|
9798
11653
|
status: "ERROR",
|
|
9799
|
-
request,
|
|
11654
|
+
request: request2,
|
|
9800
11655
|
error: new RateLimitError("*", options.globalRateLimitPerMinute),
|
|
9801
11656
|
timestamp
|
|
9802
11657
|
};
|
|
@@ -9809,7 +11664,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9809
11664
|
if (options.exfiltrationTracker.detectChain(params.name)) {
|
|
9810
11665
|
const result = {
|
|
9811
11666
|
status: "DENIED",
|
|
9812
|
-
request,
|
|
11667
|
+
request: request2,
|
|
9813
11668
|
decision: {
|
|
9814
11669
|
effect: "DENY",
|
|
9815
11670
|
matchedRule: null,
|
|
@@ -9826,11 +11681,11 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9826
11681
|
}
|
|
9827
11682
|
options.exfiltrationTracker.record(params.name);
|
|
9828
11683
|
}
|
|
9829
|
-
const decision = options.policyEngine.evaluate(
|
|
11684
|
+
const decision = options.policyEngine.evaluate(request2);
|
|
9830
11685
|
if (decision.effect === "DENY") {
|
|
9831
11686
|
const result = {
|
|
9832
11687
|
status: "DENIED",
|
|
9833
|
-
request,
|
|
11688
|
+
request: request2,
|
|
9834
11689
|
decision,
|
|
9835
11690
|
timestamp
|
|
9836
11691
|
};
|
|
@@ -9880,7 +11735,7 @@ ${item.text}`;
|
|
|
9880
11735
|
}
|
|
9881
11736
|
const result = {
|
|
9882
11737
|
status: "ALLOWED",
|
|
9883
|
-
request,
|
|
11738
|
+
request: request2,
|
|
9884
11739
|
decision,
|
|
9885
11740
|
toolResult: finalResult,
|
|
9886
11741
|
durationMs,
|
|
@@ -9891,7 +11746,7 @@ ${item.text}`;
|
|
|
9891
11746
|
} catch (error) {
|
|
9892
11747
|
const result = {
|
|
9893
11748
|
status: "ERROR",
|
|
9894
|
-
request,
|
|
11749
|
+
request: request2,
|
|
9895
11750
|
error: error instanceof Error ? new PolicyDeniedError(params.name, error.message) : new PolicyDeniedError(params.name, "Unknown upstream error"),
|
|
9896
11751
|
timestamp
|
|
9897
11752
|
};
|
|
@@ -10156,8 +12011,8 @@ var ServerVerifier = class {
|
|
|
10156
12011
|
/**
|
|
10157
12012
|
* Validates a complete signed request including timestamp, nonce, and signature.
|
|
10158
12013
|
*/
|
|
10159
|
-
validateSignedRequest(
|
|
10160
|
-
const requestTime = new Date(
|
|
12014
|
+
validateSignedRequest(request2) {
|
|
12015
|
+
const requestTime = new Date(request2.timestamp).getTime();
|
|
10161
12016
|
const now = Date.now();
|
|
10162
12017
|
if (isNaN(requestTime)) {
|
|
10163
12018
|
return { valid: false, reason: "Invalid timestamp" };
|
|
@@ -10168,13 +12023,13 @@ var ServerVerifier = class {
|
|
|
10168
12023
|
if (requestTime > now + 3e4) {
|
|
10169
12024
|
return { valid: false, reason: "Request timestamp in the future" };
|
|
10170
12025
|
}
|
|
10171
|
-
if (this.usedNonces.has(
|
|
12026
|
+
if (this.usedNonces.has(request2.nonce)) {
|
|
10172
12027
|
return { valid: false, reason: "Duplicate nonce (replay detected)" };
|
|
10173
12028
|
}
|
|
10174
|
-
if (!this.verifySignature(
|
|
12029
|
+
if (!this.verifySignature(request2.params, request2.capabilityToken, request2.signature)) {
|
|
10175
12030
|
return { valid: false, reason: "Invalid signature" };
|
|
10176
12031
|
}
|
|
10177
|
-
this.usedNonces.add(
|
|
12032
|
+
this.usedNonces.add(request2.nonce);
|
|
10178
12033
|
return { valid: true };
|
|
10179
12034
|
}
|
|
10180
12035
|
};
|
|
@@ -10436,9 +12291,9 @@ var SolonGate = class {
|
|
|
10436
12291
|
throw new LicenseError("Your subscription is inactive. Renew at https://solongate.com");
|
|
10437
12292
|
}
|
|
10438
12293
|
this.licenseValidated = true;
|
|
10439
|
-
} catch (
|
|
10440
|
-
if (
|
|
10441
|
-
console.warn("[SolonGate] License validation failed (network error), allowing through:",
|
|
12294
|
+
} catch (err2) {
|
|
12295
|
+
if (err2 instanceof LicenseError) throw err2;
|
|
12296
|
+
console.warn("[SolonGate] License validation failed (network error), allowing through:", err2 instanceof Error ? err2.message : String(err2));
|
|
10442
12297
|
this.licenseValidated = true;
|
|
10443
12298
|
}
|
|
10444
12299
|
}
|
|
@@ -10487,10 +12342,10 @@ var SolonGate = class {
|
|
|
10487
12342
|
}
|
|
10488
12343
|
const wasmBytes = new Uint8Array(await res.arrayBuffer());
|
|
10489
12344
|
await this.policyEngine.loadWasmBundle(wasmBytes);
|
|
10490
|
-
} catch (
|
|
12345
|
+
} catch (err2) {
|
|
10491
12346
|
console.warn(
|
|
10492
12347
|
"[SolonGate] Failed to load policy WASM bundle:",
|
|
10493
|
-
|
|
12348
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
10494
12349
|
);
|
|
10495
12350
|
}
|
|
10496
12351
|
}
|
|
@@ -10705,8 +12560,8 @@ var PolicySyncManager = class {
|
|
|
10705
12560
|
this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
|
|
10706
12561
|
});
|
|
10707
12562
|
log(`Watching ${filePath} for changes`);
|
|
10708
|
-
} catch (
|
|
10709
|
-
log(`File watch failed: ${
|
|
12563
|
+
} catch (err2) {
|
|
12564
|
+
log(`File watch failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10710
12565
|
}
|
|
10711
12566
|
}
|
|
10712
12567
|
/**
|
|
@@ -10737,12 +12592,12 @@ var PolicySyncManager = class {
|
|
|
10737
12592
|
const result = await this.pushToCloud(newPolicy);
|
|
10738
12593
|
this.cloudVersion = result.version;
|
|
10739
12594
|
log(`Pushed to cloud: v${result.version}`);
|
|
10740
|
-
} catch (
|
|
10741
|
-
log(`Cloud push failed: ${
|
|
12595
|
+
} catch (err2) {
|
|
12596
|
+
log(`Cloud push failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10742
12597
|
}
|
|
10743
12598
|
}
|
|
10744
|
-
} catch (
|
|
10745
|
-
log(`File read error: ${
|
|
12599
|
+
} catch (err2) {
|
|
12600
|
+
log(`File read error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10746
12601
|
}
|
|
10747
12602
|
}
|
|
10748
12603
|
/**
|
|
@@ -10831,8 +12686,8 @@ var PolicySyncManager = class {
|
|
|
10831
12686
|
const { id: _id, ...rest } = policy;
|
|
10832
12687
|
const json = JSON.stringify(rest, null, 2) + "\n";
|
|
10833
12688
|
writeFileSync2(this.localPath, json, "utf-8");
|
|
10834
|
-
} catch (
|
|
10835
|
-
log(`File write error: ${
|
|
12689
|
+
} catch (err2) {
|
|
12690
|
+
log(`File write error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10836
12691
|
}
|
|
10837
12692
|
}
|
|
10838
12693
|
/**
|
|
@@ -10856,7 +12711,7 @@ var Mutex = class {
|
|
|
10856
12711
|
this.locked = true;
|
|
10857
12712
|
return;
|
|
10858
12713
|
}
|
|
10859
|
-
return new Promise((
|
|
12714
|
+
return new Promise((resolve10, reject) => {
|
|
10860
12715
|
const timer = setTimeout(() => {
|
|
10861
12716
|
const idx = this.queue.indexOf(onReady);
|
|
10862
12717
|
if (idx !== -1) this.queue.splice(idx, 1);
|
|
@@ -10864,7 +12719,7 @@ var Mutex = class {
|
|
|
10864
12719
|
}, timeoutMs);
|
|
10865
12720
|
const onReady = () => {
|
|
10866
12721
|
clearTimeout(timer);
|
|
10867
|
-
|
|
12722
|
+
resolve10();
|
|
10868
12723
|
};
|
|
10869
12724
|
this.queue.push(onReady);
|
|
10870
12725
|
});
|
|
@@ -10933,8 +12788,8 @@ var SolonGateProxy = class {
|
|
|
10933
12788
|
return { id: raw.toLowerCase().replace(/\s+/g, "-"), name: raw };
|
|
10934
12789
|
}
|
|
10935
12790
|
/** Extract sub-agent identity from MCP _meta field */
|
|
10936
|
-
extractSubAgent(
|
|
10937
|
-
const meta =
|
|
12791
|
+
extractSubAgent(request2) {
|
|
12792
|
+
const meta = request2?.params?._meta;
|
|
10938
12793
|
if (meta && typeof meta === "object") {
|
|
10939
12794
|
const solonMeta = meta["io.solongate/agent"];
|
|
10940
12795
|
if (solonMeta && typeof solonMeta === "object") {
|
|
@@ -10978,9 +12833,9 @@ var SolonGateProxy = class {
|
|
|
10978
12833
|
process.exit(1);
|
|
10979
12834
|
}
|
|
10980
12835
|
log2("License validated.");
|
|
10981
|
-
} catch (
|
|
12836
|
+
} catch (err2) {
|
|
10982
12837
|
log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
|
|
10983
|
-
log2(`Details: ${
|
|
12838
|
+
log2(`Details: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10984
12839
|
process.exit(1);
|
|
10985
12840
|
}
|
|
10986
12841
|
}
|
|
@@ -10989,8 +12844,8 @@ var SolonGateProxy = class {
|
|
|
10989
12844
|
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl, this.config.policyId);
|
|
10990
12845
|
this.config.policy = cloudPolicy;
|
|
10991
12846
|
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
10992
|
-
} catch (
|
|
10993
|
-
log2(`Cloud policy fetch failed, using local policy: ${
|
|
12847
|
+
} catch (err2) {
|
|
12848
|
+
log2(`Cloud policy fetch failed, using local policy: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10994
12849
|
}
|
|
10995
12850
|
}
|
|
10996
12851
|
}
|
|
@@ -11124,9 +12979,9 @@ var SolonGateProxy = class {
|
|
|
11124
12979
|
});
|
|
11125
12980
|
const MAX_ARGUMENT_SIZE = 1024 * 1024;
|
|
11126
12981
|
const MUTEX_TIMEOUT_MS = 3e4;
|
|
11127
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (
|
|
11128
|
-
const { name, arguments: args } =
|
|
11129
|
-
const subAgent = this.extractSubAgent(
|
|
12982
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
12983
|
+
const { name, arguments: args } = request2.params;
|
|
12984
|
+
const subAgent = this.extractSubAgent(request2) || this.httpSubAgent;
|
|
11130
12985
|
const argsSize = TEXT_ENCODER.encode(JSON.stringify(args ?? {})).length;
|
|
11131
12986
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
11132
12987
|
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
@@ -11210,9 +13065,9 @@ var SolonGateProxy = class {
|
|
|
11210
13065
|
return { resources: [] };
|
|
11211
13066
|
}
|
|
11212
13067
|
});
|
|
11213
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async (
|
|
13068
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request2) => {
|
|
11214
13069
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
11215
|
-
const uri =
|
|
13070
|
+
const uri = request2.params.uri;
|
|
11216
13071
|
log2(`Resource read: ${uri}`);
|
|
11217
13072
|
const resourceResult = await this.client.readResource({ uri });
|
|
11218
13073
|
if (resourceResult.contents) {
|
|
@@ -11247,12 +13102,12 @@ ${content.text}`;
|
|
|
11247
13102
|
return { prompts: [] };
|
|
11248
13103
|
}
|
|
11249
13104
|
});
|
|
11250
|
-
this.server.setRequestHandler(GetPromptRequestSchema, async (
|
|
13105
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request2) => {
|
|
11251
13106
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
11252
|
-
const args =
|
|
11253
|
-
log2(`Prompt get: ${
|
|
13107
|
+
const args = request2.params.arguments;
|
|
13108
|
+
log2(`Prompt get: ${request2.params.name}`);
|
|
11254
13109
|
const promptResult = await this.client.getPrompt({
|
|
11255
|
-
name:
|
|
13110
|
+
name: request2.params.name,
|
|
11256
13111
|
arguments: args
|
|
11257
13112
|
});
|
|
11258
13113
|
if (promptResult.messages) {
|
|
@@ -11261,7 +13116,7 @@ ${content.text}`;
|
|
|
11261
13116
|
const scan = scanResponse(msg.content.text);
|
|
11262
13117
|
if (!scan.safe) {
|
|
11263
13118
|
const threats = scan.threats.map((t) => t.type).join(", ");
|
|
11264
|
-
log2(`WARNING prompt response: ${
|
|
13119
|
+
log2(`WARNING prompt response: ${request2.params.name} \u2014 ${threats}`);
|
|
11265
13120
|
msg.content.text = `${RESPONSE_WARNING_MARKER}
|
|
11266
13121
|
|
|
11267
13122
|
${msg.content.text}`;
|
|
@@ -11367,8 +13222,8 @@ ${msg.content.text}`;
|
|
|
11367
13222
|
const body = await res.text().catch(() => "");
|
|
11368
13223
|
log2(`MCP server registration failed (${res.status}): ${body}`);
|
|
11369
13224
|
}
|
|
11370
|
-
}).catch((
|
|
11371
|
-
log2(`MCP server registration error: ${
|
|
13225
|
+
}).catch((err2) => {
|
|
13226
|
+
log2(`MCP server registration error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
11372
13227
|
});
|
|
11373
13228
|
}
|
|
11374
13229
|
/**
|
|
@@ -11455,7 +13310,7 @@ ${msg.content.text}`;
|
|
|
11455
13310
|
|
|
11456
13311
|
// src/index.ts
|
|
11457
13312
|
init_cli_utils();
|
|
11458
|
-
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "logout", "shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs"]);
|
|
13313
|
+
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "logout", "shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "ui", "dashboard", "tui"]);
|
|
11459
13314
|
var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
|
|
11460
13315
|
if (!IS_HUMAN_CLI) {
|
|
11461
13316
|
console.log = (...args) => {
|
|
@@ -11490,9 +13345,26 @@ function printWelcome() {
|
|
|
11490
13345
|
async function main5() {
|
|
11491
13346
|
const subcommand = process.argv[2];
|
|
11492
13347
|
if (process.argv.length <= 2) {
|
|
13348
|
+
const { isAuthenticated: isAuthenticated2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
13349
|
+
if (process.stdout.isTTY && isAuthenticated2()) {
|
|
13350
|
+
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
13351
|
+
await launchTui2();
|
|
13352
|
+
return;
|
|
13353
|
+
}
|
|
11493
13354
|
printWelcome();
|
|
11494
13355
|
return;
|
|
11495
13356
|
}
|
|
13357
|
+
if (subcommand === "ui" || subcommand === "dashboard" || subcommand === "tui") {
|
|
13358
|
+
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
13359
|
+
await launchTui2();
|
|
13360
|
+
return;
|
|
13361
|
+
}
|
|
13362
|
+
const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"]);
|
|
13363
|
+
if (MGMT_COMMANDS.has(subcommand ?? "")) {
|
|
13364
|
+
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
13365
|
+
const code = await runCommand2(subcommand, process.argv.slice(3));
|
|
13366
|
+
process.exit(code);
|
|
13367
|
+
}
|
|
11496
13368
|
if (subcommand === "login") {
|
|
11497
13369
|
await Promise.resolve().then(() => (init_login(), login_exports));
|
|
11498
13370
|
return;
|
|
@@ -11530,8 +13402,8 @@ async function main5() {
|
|
|
11530
13402
|
const config = parseArgs(process.argv);
|
|
11531
13403
|
const proxy = new SolonGateProxy(config);
|
|
11532
13404
|
await proxy.start();
|
|
11533
|
-
} catch (
|
|
11534
|
-
const message =
|
|
13405
|
+
} catch (err2) {
|
|
13406
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
11535
13407
|
process.stderr.write(`[SolonGate] Fatal: ${message}
|
|
11536
13408
|
`);
|
|
11537
13409
|
process.exit(1);
|