@solongate/proxy 0.59.4 → 0.60.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/audit/index.js +0 -1
- 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 +2059 -348
- package/dist/inject.js +4 -4
- package/dist/lib.js +3 -31
- package/dist/proxy.d.ts +0 -7
- package/dist/pull-push.js +2 -1
- 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 +881 -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/hooks/audit.mjs +23 -4
- package/hooks/guard.bundled.mjs +46 -35
- package/hooks/guard.mjs +16 -2
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -41,8 +41,8 @@ function loginCredential() {
|
|
|
41
41
|
try {
|
|
42
42
|
const p = join(homedir(), ".solongate", "cloud-guard.json");
|
|
43
43
|
if (!existsSync(p)) return {};
|
|
44
|
-
const
|
|
45
|
-
return
|
|
44
|
+
const c2 = JSON.parse(readFileSync(p, "utf-8"));
|
|
45
|
+
return c2 && typeof c2 === "object" ? c2 : {};
|
|
46
46
|
} catch {
|
|
47
47
|
return {};
|
|
48
48
|
}
|
|
@@ -78,6 +78,7 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
|
78
78
|
return {
|
|
79
79
|
id: String(data.id ?? "cloud"),
|
|
80
80
|
name: String(data.name ?? "Cloud Policy"),
|
|
81
|
+
description: String(data.description ?? ""),
|
|
81
82
|
version: Number(data._version ?? 1),
|
|
82
83
|
rules: data.rules ?? [],
|
|
83
84
|
createdAt: String(data._created_at ?? ""),
|
|
@@ -131,12 +132,12 @@ async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
|
131
132
|
`);
|
|
132
133
|
try {
|
|
133
134
|
const line = JSON.stringify({ ...entry, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
134
|
-
appendFile(AUDIT_LOG_BACKUP_PATH, line, "utf-8").catch((
|
|
135
|
-
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)}
|
|
136
137
|
`);
|
|
137
138
|
});
|
|
138
|
-
} catch (
|
|
139
|
-
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)}
|
|
140
141
|
`);
|
|
141
142
|
}
|
|
142
143
|
}
|
|
@@ -197,21 +198,6 @@ function parseArgs(argv) {
|
|
|
197
198
|
let separatorIndex = args.indexOf("--");
|
|
198
199
|
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
199
200
|
let upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
200
|
-
const flagsWithValue = /* @__PURE__ */ new Set([
|
|
201
|
-
"--policy",
|
|
202
|
-
"--name",
|
|
203
|
-
"--rate-limit",
|
|
204
|
-
"--global-rate-limit",
|
|
205
|
-
"--config",
|
|
206
|
-
"--api-key",
|
|
207
|
-
"--api-url",
|
|
208
|
-
"--upstream-url",
|
|
209
|
-
"--upstream-transport",
|
|
210
|
-
"--port",
|
|
211
|
-
"--policy-id",
|
|
212
|
-
"--id",
|
|
213
|
-
"--agent-name"
|
|
214
|
-
]);
|
|
215
201
|
for (let i = 0; i < flags.length; i++) {
|
|
216
202
|
if (!flags[i].startsWith("--")) {
|
|
217
203
|
if (upstreamArgs.length === 0) {
|
|
@@ -412,11 +398,11 @@ var require_json = __commonJS({
|
|
|
412
398
|
try {
|
|
413
399
|
JSON.parse(str);
|
|
414
400
|
return true;
|
|
415
|
-
} catch (
|
|
416
|
-
if (
|
|
401
|
+
} catch (err2) {
|
|
402
|
+
if (err2 instanceof SyntaxError) {
|
|
417
403
|
return false;
|
|
418
404
|
}
|
|
419
|
-
throw
|
|
405
|
+
throw err2;
|
|
420
406
|
}
|
|
421
407
|
}
|
|
422
408
|
module.exports = {
|
|
@@ -779,9 +765,9 @@ var require_PlainValue_516d5bc2 = __commonJS({
|
|
|
779
765
|
}
|
|
780
766
|
}
|
|
781
767
|
const offset = col > 1 ? " ".repeat(col - 1) : "";
|
|
782
|
-
const
|
|
768
|
+
const err2 = "^".repeat(errLen);
|
|
783
769
|
return `${src}
|
|
784
|
-
${offset}${
|
|
770
|
+
${offset}${err2}${errEnd}`;
|
|
785
771
|
}
|
|
786
772
|
var Range = class _Range {
|
|
787
773
|
static copy(orig) {
|
|
@@ -1447,9 +1433,9 @@ var require_parse_cst = __commonJS({
|
|
|
1447
1433
|
offset = this.node.range.end;
|
|
1448
1434
|
} else {
|
|
1449
1435
|
if (inlineComment) {
|
|
1450
|
-
const
|
|
1451
|
-
this.props.push(
|
|
1452
|
-
offset =
|
|
1436
|
+
const c2 = comments[0];
|
|
1437
|
+
this.props.push(c2);
|
|
1438
|
+
offset = c2.end;
|
|
1453
1439
|
} else {
|
|
1454
1440
|
offset = PlainValue.Node.endOfLine(src, start + 1);
|
|
1455
1441
|
}
|
|
@@ -2749,7 +2735,7 @@ var require_parse_cst = __commonJS({
|
|
|
2749
2735
|
};
|
|
2750
2736
|
}
|
|
2751
2737
|
};
|
|
2752
|
-
function
|
|
2738
|
+
function parse2(src) {
|
|
2753
2739
|
const cr = [];
|
|
2754
2740
|
if (src.indexOf("\r") !== -1) {
|
|
2755
2741
|
src = src.replace(/\r\n?/g, (match, offset2) => {
|
|
@@ -2780,7 +2766,7 @@ var require_parse_cst = __commonJS({
|
|
|
2780
2766
|
documents.toString = () => documents.join("...\n");
|
|
2781
2767
|
return documents;
|
|
2782
2768
|
}
|
|
2783
|
-
exports.parse =
|
|
2769
|
+
exports.parse = parse2;
|
|
2784
2770
|
}
|
|
2785
2771
|
});
|
|
2786
2772
|
|
|
@@ -3190,8 +3176,8 @@ ${ctx.indent}`;
|
|
|
3190
3176
|
} else if (node instanceof Collection) {
|
|
3191
3177
|
let count = 0;
|
|
3192
3178
|
for (const item of node.items) {
|
|
3193
|
-
const
|
|
3194
|
-
if (
|
|
3179
|
+
const c2 = getAliasCount(item, anchors);
|
|
3180
|
+
if (c2 > count) count = c2;
|
|
3195
3181
|
}
|
|
3196
3182
|
return count;
|
|
3197
3183
|
} else if (node instanceof Pair) {
|
|
@@ -3412,12 +3398,12 @@ ${ctx.indent}`;
|
|
|
3412
3398
|
for (const {
|
|
3413
3399
|
format,
|
|
3414
3400
|
test,
|
|
3415
|
-
resolve:
|
|
3401
|
+
resolve: resolve10
|
|
3416
3402
|
} of tags) {
|
|
3417
3403
|
if (test) {
|
|
3418
3404
|
const match = str.match(test);
|
|
3419
3405
|
if (match) {
|
|
3420
|
-
let res =
|
|
3406
|
+
let res = resolve10.apply(null, match);
|
|
3421
3407
|
if (!(res instanceof Scalar)) res = new Scalar(res);
|
|
3422
3408
|
if (format) res.format = format;
|
|
3423
3409
|
return res;
|
|
@@ -3831,15 +3817,15 @@ ${indent}`);
|
|
|
3831
3817
|
}
|
|
3832
3818
|
if (lastItem && lastItem.char !== char) {
|
|
3833
3819
|
const msg = `Expected ${name} to end with ${char}`;
|
|
3834
|
-
let
|
|
3820
|
+
let err2;
|
|
3835
3821
|
if (typeof lastItem.offset === "number") {
|
|
3836
|
-
|
|
3837
|
-
|
|
3822
|
+
err2 = new PlainValue.YAMLSemanticError(cst, msg);
|
|
3823
|
+
err2.offset = lastItem.offset + 1;
|
|
3838
3824
|
} else {
|
|
3839
|
-
|
|
3840
|
-
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;
|
|
3841
3827
|
}
|
|
3842
|
-
errors.push(
|
|
3828
|
+
errors.push(err2);
|
|
3843
3829
|
}
|
|
3844
3830
|
}
|
|
3845
3831
|
function checkFlowCommentSpace(errors, comment) {
|
|
@@ -4378,9 +4364,9 @@ ${ca}` : ca;
|
|
|
4378
4364
|
continue;
|
|
4379
4365
|
}
|
|
4380
4366
|
const msg = `Flow map contains an unexpected ${char}`;
|
|
4381
|
-
const
|
|
4382
|
-
|
|
4383
|
-
doc.errors.push(
|
|
4367
|
+
const err2 = new PlainValue.YAMLSyntaxError(cst, msg);
|
|
4368
|
+
err2.offset = offset;
|
|
4369
|
+
doc.errors.push(err2);
|
|
4384
4370
|
} else if (item.type === PlainValue.Type.BLANK_LINE) {
|
|
4385
4371
|
comments.push({
|
|
4386
4372
|
afterKey: !!key,
|
|
@@ -4496,9 +4482,9 @@ ${ca}` : ca;
|
|
|
4496
4482
|
key = items.pop();
|
|
4497
4483
|
if (key instanceof Pair) {
|
|
4498
4484
|
const msg = "Chaining flow sequence pairs is invalid";
|
|
4499
|
-
const
|
|
4500
|
-
|
|
4501
|
-
doc.errors.push(
|
|
4485
|
+
const err2 = new PlainValue.YAMLSemanticError(cst, msg);
|
|
4486
|
+
err2.offset = offset;
|
|
4487
|
+
doc.errors.push(err2);
|
|
4502
4488
|
}
|
|
4503
4489
|
if (!explicitKey && typeof keyStart === "number") {
|
|
4504
4490
|
const keyEnd = item.range ? item.range.start : item.offset;
|
|
@@ -4520,9 +4506,9 @@ ${ca}` : ca;
|
|
|
4520
4506
|
next = null;
|
|
4521
4507
|
} else if (next === "[" || char !== "]" || i < cst.items.length - 1) {
|
|
4522
4508
|
const msg = `Flow sequence contains an unexpected ${char}`;
|
|
4523
|
-
const
|
|
4524
|
-
|
|
4525
|
-
doc.errors.push(
|
|
4509
|
+
const err2 = new PlainValue.YAMLSyntaxError(cst, msg);
|
|
4510
|
+
err2.offset = offset;
|
|
4511
|
+
doc.errors.push(err2);
|
|
4526
4512
|
}
|
|
4527
4513
|
} else if (item.type === PlainValue.Type.BLANK_LINE) {
|
|
4528
4514
|
comments.push({
|
|
@@ -6059,7 +6045,7 @@ var require_dist = __commonJS({
|
|
|
6059
6045
|
}
|
|
6060
6046
|
return doc;
|
|
6061
6047
|
}
|
|
6062
|
-
function
|
|
6048
|
+
function parse2(src, options) {
|
|
6063
6049
|
const doc = parseDocument(src, options);
|
|
6064
6050
|
doc.warnings.forEach((warning) => warnings.warn(warning));
|
|
6065
6051
|
if (doc.errors.length > 0) throw doc.errors[0];
|
|
@@ -6074,7 +6060,7 @@ var require_dist = __commonJS({
|
|
|
6074
6060
|
createNode,
|
|
6075
6061
|
defaultOptions: Document$1.defaultOptions,
|
|
6076
6062
|
Document,
|
|
6077
|
-
parse,
|
|
6063
|
+
parse: parse2,
|
|
6078
6064
|
parseAllDocuments,
|
|
6079
6065
|
parseCST: parseCst.parse,
|
|
6080
6066
|
parseDocument,
|
|
@@ -6104,7 +6090,7 @@ var require_yaml2 = __commonJS({
|
|
|
6104
6090
|
"YAMLSyntaxError",
|
|
6105
6091
|
"YAMLWarning"
|
|
6106
6092
|
]);
|
|
6107
|
-
function
|
|
6093
|
+
function parse2(str) {
|
|
6108
6094
|
if (typeof str !== "string") {
|
|
6109
6095
|
return { ok: false, result: void 0 };
|
|
6110
6096
|
}
|
|
@@ -6112,11 +6098,11 @@ var require_yaml2 = __commonJS({
|
|
|
6112
6098
|
try {
|
|
6113
6099
|
global.YAML_SILENCE_WARNINGS = true;
|
|
6114
6100
|
return { ok: true, result: yaml.parse(str) };
|
|
6115
|
-
} catch (
|
|
6116
|
-
if (
|
|
6101
|
+
} catch (err2) {
|
|
6102
|
+
if (err2 && errors.has(err2.name)) {
|
|
6117
6103
|
return { ok: false, result: void 0 };
|
|
6118
6104
|
}
|
|
6119
|
-
throw
|
|
6105
|
+
throw err2;
|
|
6120
6106
|
} finally {
|
|
6121
6107
|
global.YAML_SILENCE_WARNINGS = YAML_SILENCE_WARNINGS_CACHED;
|
|
6122
6108
|
}
|
|
@@ -6124,9 +6110,9 @@ var require_yaml2 = __commonJS({
|
|
|
6124
6110
|
module.exports = {
|
|
6125
6111
|
// is_valid is expected to return nothing if input is invalid otherwise
|
|
6126
6112
|
// true/false for it being valid YAML.
|
|
6127
|
-
"yaml.is_valid": (str) => typeof str === "string" ?
|
|
6113
|
+
"yaml.is_valid": (str) => typeof str === "string" ? parse2(str).ok : void 0,
|
|
6128
6114
|
"yaml.marshal": (data) => yaml.stringify(data),
|
|
6129
|
-
"yaml.unmarshal": (str) =>
|
|
6115
|
+
"yaml.unmarshal": (str) => parse2(str).result
|
|
6130
6116
|
};
|
|
6131
6117
|
}
|
|
6132
6118
|
});
|
|
@@ -6568,6 +6554,1743 @@ var init_cli_utils = __esm({
|
|
|
6568
6554
|
}
|
|
6569
6555
|
});
|
|
6570
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 { useState as useState2 } from "react";
|
|
7024
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7025
|
+
function PoliciesPanel({ focused }) {
|
|
7026
|
+
const [sel, setSel] = useState2(0);
|
|
7027
|
+
const list4 = useLoader(() => api.policies.list());
|
|
7028
|
+
const policies = list4.data?.policies ?? [];
|
|
7029
|
+
const selected = policies[Math.min(sel, Math.max(0, policies.length - 1))];
|
|
7030
|
+
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
7031
|
+
useInput(
|
|
7032
|
+
(_input, key) => {
|
|
7033
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7034
|
+
if (key.downArrow) setSel((n) => Math.min(policies.length - 1, n + 1));
|
|
7035
|
+
},
|
|
7036
|
+
{ isActive: focused }
|
|
7037
|
+
);
|
|
7038
|
+
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: [
|
|
7039
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: focused ? "\u2191\u2193 select policy" : "press \u2192 to browse" }),
|
|
7040
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === sel ? theme.accentBright : void 0, bold: i === sel, children: [
|
|
7041
|
+
(i === sel ? "\u25B8 " : " ") + truncate2(p.name, 26).padEnd(27),
|
|
7042
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7043
|
+
p.mode.padEnd(10),
|
|
7044
|
+
" ",
|
|
7045
|
+
p.rules.length,
|
|
7046
|
+
" rules \xB7 v",
|
|
7047
|
+
p.version
|
|
7048
|
+
] })
|
|
7049
|
+
] }, p.id)) }),
|
|
7050
|
+
selected ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
7051
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7052
|
+
"Rules of ",
|
|
7053
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: selected.id })
|
|
7054
|
+
] }),
|
|
7055
|
+
/* @__PURE__ */ jsx2(
|
|
7056
|
+
Table,
|
|
7057
|
+
{
|
|
7058
|
+
columns: [
|
|
7059
|
+
{ header: "EFFECT", width: 7 },
|
|
7060
|
+
{ header: "PRIO", width: 4 },
|
|
7061
|
+
{ header: "TOOL", width: 22 },
|
|
7062
|
+
{ header: "DESCRIPTION", width: 34 }
|
|
7063
|
+
],
|
|
7064
|
+
rows: (detail.data?.rules ?? []).slice(0, 10).map((r) => [
|
|
7065
|
+
{ value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad },
|
|
7066
|
+
{ value: String(r.priority), dim: true },
|
|
7067
|
+
{ value: truncate2(r.toolPattern, 22), color: theme.accent },
|
|
7068
|
+
{ value: truncate2(r.description || "\u2014", 34) }
|
|
7069
|
+
])
|
|
7070
|
+
}
|
|
7071
|
+
)
|
|
7072
|
+
] }) : null
|
|
7073
|
+
] }) });
|
|
7074
|
+
}
|
|
7075
|
+
var init_Policies = __esm({
|
|
7076
|
+
"src/tui/panels/Policies.tsx"() {
|
|
7077
|
+
"use strict";
|
|
7078
|
+
init_api_client();
|
|
7079
|
+
init_components();
|
|
7080
|
+
init_hooks();
|
|
7081
|
+
init_theme();
|
|
7082
|
+
}
|
|
7083
|
+
});
|
|
7084
|
+
|
|
7085
|
+
// src/tui/panels/RateLimit.tsx
|
|
7086
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
7087
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
7088
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7089
|
+
function RateLimitPanel({ focused }) {
|
|
7090
|
+
const layersQ = useLoader(() => api.settings.getSecurityLayers());
|
|
7091
|
+
const historyQ = useLoader(() => api.settings.getRateLimitHistory());
|
|
7092
|
+
const [draft, setDraft] = useState3(null);
|
|
7093
|
+
const [dirty, setDirty] = useState3(false);
|
|
7094
|
+
const [fi, setFi] = useState3(0);
|
|
7095
|
+
const [status, setStatus] = useState3(null);
|
|
7096
|
+
useEffect2(() => {
|
|
7097
|
+
if (layersQ.data && !draft) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
7098
|
+
}, [layersQ.data, draft]);
|
|
7099
|
+
const adjust = (dir) => {
|
|
7100
|
+
if (!draft) return;
|
|
7101
|
+
const field = FIELDS[fi];
|
|
7102
|
+
if (field === "mode") {
|
|
7103
|
+
const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
|
|
7104
|
+
setDraft({ ...draft, mode: MODES[idx] });
|
|
7105
|
+
} else {
|
|
7106
|
+
const step = 10;
|
|
7107
|
+
setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
7108
|
+
}
|
|
7109
|
+
setDirty(true);
|
|
7110
|
+
setStatus(null);
|
|
7111
|
+
};
|
|
7112
|
+
const save = async () => {
|
|
7113
|
+
if (!draft || !layersQ.data) return;
|
|
7114
|
+
setStatus("Saving\u2026");
|
|
7115
|
+
try {
|
|
7116
|
+
const next = { ...layersQ.data.layers, rateLimit: draft };
|
|
7117
|
+
const res = await api.settings.setSecurityLayers(next);
|
|
7118
|
+
setDraft({ ...res.layers.rateLimit });
|
|
7119
|
+
setDirty(false);
|
|
7120
|
+
setStatus("\u2713 Saved");
|
|
7121
|
+
historyQ.reload();
|
|
7122
|
+
} catch (e) {
|
|
7123
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7124
|
+
}
|
|
7125
|
+
};
|
|
7126
|
+
useInput2(
|
|
7127
|
+
(input, key) => {
|
|
7128
|
+
if (key.upArrow) setFi((n) => (n - 1 + FIELDS.length) % FIELDS.length);
|
|
7129
|
+
else if (key.downArrow) setFi((n) => (n + 1) % FIELDS.length);
|
|
7130
|
+
else if (key.leftArrow) adjust(-1);
|
|
7131
|
+
else if (key.rightArrow) adjust(1);
|
|
7132
|
+
else if (input === "s") void save();
|
|
7133
|
+
},
|
|
7134
|
+
{ isActive: focused }
|
|
7135
|
+
);
|
|
7136
|
+
const history = historyQ.data?.history ?? [];
|
|
7137
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7138
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field \u2190\u2192 change s save" : "press \u2192 to edit" }),
|
|
7139
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7140
|
+
/* @__PURE__ */ jsx3(Row, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx3(Text3, { color: modeColor(draft.mode), children: draft.mode }) }),
|
|
7141
|
+
/* @__PURE__ */ jsx3(Row, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perMinute }) }),
|
|
7142
|
+
/* @__PURE__ */ jsx3(Row, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
7143
|
+
/* @__PURE__ */ jsx3(Row, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
7144
|
+
] }),
|
|
7145
|
+
history.length ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
7146
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "history " }),
|
|
7147
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: sparkline(history.map((h) => h.minute), 40) }),
|
|
7148
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " " + history.length + " changes" })
|
|
7149
|
+
] }) : null,
|
|
7150
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
7151
|
+
dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
7152
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7153
|
+
] })
|
|
7154
|
+
] }) : null });
|
|
7155
|
+
}
|
|
7156
|
+
function Row({ label, active: active2, children }) {
|
|
7157
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7158
|
+
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
|
|
7159
|
+
children
|
|
7160
|
+
] });
|
|
7161
|
+
}
|
|
7162
|
+
var MODES, FIELDS;
|
|
7163
|
+
var init_RateLimit = __esm({
|
|
7164
|
+
"src/tui/panels/RateLimit.tsx"() {
|
|
7165
|
+
"use strict";
|
|
7166
|
+
init_api_client();
|
|
7167
|
+
init_components();
|
|
7168
|
+
init_hooks();
|
|
7169
|
+
init_theme();
|
|
7170
|
+
MODES = ["off", "detect", "block"];
|
|
7171
|
+
FIELDS = ["mode", "perMinute", "perHour", "perDay"];
|
|
7172
|
+
}
|
|
7173
|
+
});
|
|
7174
|
+
|
|
7175
|
+
// src/tui/panels/Dlp.tsx
|
|
7176
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
7177
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
7178
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7179
|
+
function DlpPanel({ focused }) {
|
|
7180
|
+
const q = useLoader(() => api.settings.getSecurityLayers());
|
|
7181
|
+
const [dlp, setDlp] = useState4(null);
|
|
7182
|
+
const [available, setAvailable] = useState4([]);
|
|
7183
|
+
const [sel, setSel] = useState4(0);
|
|
7184
|
+
const [status, setStatus] = useState4(null);
|
|
7185
|
+
useEffect3(() => {
|
|
7186
|
+
if (q.data && !dlp) {
|
|
7187
|
+
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
|
|
7188
|
+
setAvailable(q.data.availablePatterns);
|
|
7189
|
+
}
|
|
7190
|
+
}, [q.data, dlp]);
|
|
7191
|
+
const persist = async (nextDlp) => {
|
|
7192
|
+
if (!q.data) return;
|
|
7193
|
+
setDlp(nextDlp);
|
|
7194
|
+
setStatus("Saving\u2026");
|
|
7195
|
+
try {
|
|
7196
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp: nextDlp });
|
|
7197
|
+
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns] });
|
|
7198
|
+
setStatus("\u2713 Saved");
|
|
7199
|
+
} catch (e) {
|
|
7200
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7201
|
+
}
|
|
7202
|
+
};
|
|
7203
|
+
useInput3(
|
|
7204
|
+
(input, key) => {
|
|
7205
|
+
if (!dlp) return;
|
|
7206
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7207
|
+
else if (key.downArrow) setSel((n) => Math.min(available.length - 1, n + 1));
|
|
7208
|
+
else if (input === " " || key.return) {
|
|
7209
|
+
const p = available[sel];
|
|
7210
|
+
if (!p) return;
|
|
7211
|
+
const set = new Set(dlp.patterns);
|
|
7212
|
+
if (set.has(p)) set.delete(p);
|
|
7213
|
+
else set.add(p);
|
|
7214
|
+
void persist({ ...dlp, patterns: [...set] });
|
|
7215
|
+
} else if (input === "m") {
|
|
7216
|
+
const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
|
|
7217
|
+
void persist({ ...dlp, mode: MODES2[idx] });
|
|
7218
|
+
}
|
|
7219
|
+
},
|
|
7220
|
+
{ isActive: focused }
|
|
7221
|
+
);
|
|
7222
|
+
const enabled = new Set(dlp?.patterns ?? []);
|
|
7223
|
+
return /* @__PURE__ */ jsx4(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
7224
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7225
|
+
/* @__PURE__ */ jsx4(Text4, { children: "mode: " }),
|
|
7226
|
+
/* @__PURE__ */ jsx4(Text4, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
7227
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? " \u2191\u2193 move \xB7 space toggle \xB7 m mode" : " press \u2192 to edit" })
|
|
7228
|
+
] }),
|
|
7229
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
7230
|
+
const on = enabled.has(p);
|
|
7231
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: focused && i === sel ? theme.accentBright : void 0, bold: focused && i === sel, children: [
|
|
7232
|
+
(focused && i === sel ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
|
|
7233
|
+
/* @__PURE__ */ jsx4(Text4, { color: on ? theme.ok : theme.dim, children: p })
|
|
7234
|
+
] }, p);
|
|
7235
|
+
}) }),
|
|
7236
|
+
dlp.custom.length ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
7237
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Custom:" }),
|
|
7238
|
+
dlp.custom.map((c2) => /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
7239
|
+
" ",
|
|
7240
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: c2.name }),
|
|
7241
|
+
" ",
|
|
7242
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: c2.re })
|
|
7243
|
+
] }, c2.name))
|
|
7244
|
+
] }) : null,
|
|
7245
|
+
status ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
7246
|
+
] }) : null });
|
|
7247
|
+
}
|
|
7248
|
+
var MODES2;
|
|
7249
|
+
var init_Dlp = __esm({
|
|
7250
|
+
"src/tui/panels/Dlp.tsx"() {
|
|
7251
|
+
"use strict";
|
|
7252
|
+
init_api_client();
|
|
7253
|
+
init_components();
|
|
7254
|
+
init_hooks();
|
|
7255
|
+
init_theme();
|
|
7256
|
+
MODES2 = ["off", "detect", "block"];
|
|
7257
|
+
}
|
|
7258
|
+
});
|
|
7259
|
+
|
|
7260
|
+
// src/tui/panels/Stats.tsx
|
|
7261
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
7262
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
7263
|
+
function StatsPanel({ active: active2 }) {
|
|
7264
|
+
const stats = useLoader(() => api.stats.get());
|
|
7265
|
+
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
7266
|
+
usePoll(() => {
|
|
7267
|
+
stats.reload();
|
|
7268
|
+
ts.reload();
|
|
7269
|
+
}, 5e3, active2);
|
|
7270
|
+
const s = stats.data;
|
|
7271
|
+
const points = ts.data?.timeseries ?? [];
|
|
7272
|
+
return /* @__PURE__ */ jsx5(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
7273
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7274
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
|
|
7275
|
+
s.total_calls,
|
|
7276
|
+
" "
|
|
7277
|
+
] }),
|
|
7278
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "calls " }),
|
|
7279
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.ok, children: [
|
|
7280
|
+
s.allowed,
|
|
7281
|
+
" allowed"
|
|
7282
|
+
] }),
|
|
7283
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " }),
|
|
7284
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.bad, children: [
|
|
7285
|
+
s.denied,
|
|
7286
|
+
" denied"
|
|
7287
|
+
] })
|
|
7288
|
+
] }),
|
|
7289
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
|
|
7290
|
+
s.active_policies,
|
|
7291
|
+
" policies \xB7 ",
|
|
7292
|
+
s.registered_tools,
|
|
7293
|
+
" tools"
|
|
7294
|
+
] }),
|
|
7295
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
7296
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
|
|
7297
|
+
"Last 24h ",
|
|
7298
|
+
ts.data ? `(per ${ts.data.granularity})` : ""
|
|
7299
|
+
] }),
|
|
7300
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7301
|
+
/* @__PURE__ */ jsx5(Text5, { children: "total " }),
|
|
7302
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
|
|
7303
|
+
] }),
|
|
7304
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7305
|
+
/* @__PURE__ */ jsx5(Text5, { children: "allowed " }),
|
|
7306
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
|
|
7307
|
+
] }),
|
|
7308
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7309
|
+
/* @__PURE__ */ jsx5(Text5, { children: "denied " }),
|
|
7310
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
|
|
7311
|
+
] })
|
|
7312
|
+
] }),
|
|
7313
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
7314
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Recent" }),
|
|
7315
|
+
/* @__PURE__ */ jsx5(
|
|
7316
|
+
Table,
|
|
7317
|
+
{
|
|
7318
|
+
columns: [
|
|
7319
|
+
{ header: "DECISION", width: 9 },
|
|
7320
|
+
{ header: "TOOL", width: 20 },
|
|
7321
|
+
{ header: "MS", width: 5 },
|
|
7322
|
+
{ header: "WHEN", width: 6 }
|
|
7323
|
+
],
|
|
7324
|
+
rows: (s.recent_activity ?? []).slice(0, 8).map((a) => [
|
|
7325
|
+
{ value: a.decision, color: decisionColor(a.decision) },
|
|
7326
|
+
{ value: truncate2(a.tool_name, 20), color: theme.accent },
|
|
7327
|
+
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
7328
|
+
{ value: ago(a.created_at), dim: true }
|
|
7329
|
+
])
|
|
7330
|
+
}
|
|
7331
|
+
)
|
|
7332
|
+
] })
|
|
7333
|
+
] }) : null });
|
|
7334
|
+
}
|
|
7335
|
+
var init_Stats = __esm({
|
|
7336
|
+
"src/tui/panels/Stats.tsx"() {
|
|
7337
|
+
"use strict";
|
|
7338
|
+
init_api_client();
|
|
7339
|
+
init_components();
|
|
7340
|
+
init_hooks();
|
|
7341
|
+
init_theme();
|
|
7342
|
+
}
|
|
7343
|
+
});
|
|
7344
|
+
|
|
7345
|
+
// src/tui/panels/Audit.tsx
|
|
7346
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
7347
|
+
import { useState as useState5 } from "react";
|
|
7348
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
7349
|
+
function AuditPanel({ active: active2, focused }) {
|
|
7350
|
+
const [fi, setFi] = useState5(0);
|
|
7351
|
+
const filter = FILTERS[fi];
|
|
7352
|
+
const audit = useLoader(() => api.audit.list({ filter, limit: 14 }), [fi]);
|
|
7353
|
+
usePoll(audit.reload, 5e3, active2);
|
|
7354
|
+
useInput4(
|
|
7355
|
+
(input) => {
|
|
7356
|
+
if (input === "f") setFi((n) => (n + 1) % FILTERS.length);
|
|
7357
|
+
},
|
|
7358
|
+
{ isActive: focused }
|
|
7359
|
+
);
|
|
7360
|
+
const entries = audit.data?.entries ?? [];
|
|
7361
|
+
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: [
|
|
7362
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
7363
|
+
audit.data?.total ?? 0,
|
|
7364
|
+
" entries \xB7 filter: ",
|
|
7365
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: filter ?? "all" }),
|
|
7366
|
+
" (press f)"
|
|
7367
|
+
] }),
|
|
7368
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
|
|
7369
|
+
Table,
|
|
7370
|
+
{
|
|
7371
|
+
columns: [
|
|
7372
|
+
{ header: "DECISION", width: 9 },
|
|
7373
|
+
{ header: "TOOL", width: 18 },
|
|
7374
|
+
{ header: "AGENT", width: 14 },
|
|
7375
|
+
{ header: "REASON", width: 26 },
|
|
7376
|
+
{ header: "DLP", width: 4 },
|
|
7377
|
+
{ header: "WHEN", width: 6 }
|
|
7378
|
+
],
|
|
7379
|
+
rows: entries.map((e) => [
|
|
7380
|
+
{ value: e.decision, color: decisionColor(e.decision) },
|
|
7381
|
+
{ value: truncate2(e.tool_name, 18), color: theme.accent },
|
|
7382
|
+
{ value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
|
|
7383
|
+
{ value: truncate2(e.reason ?? "\u2014", 26) },
|
|
7384
|
+
{ 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 },
|
|
7385
|
+
{ value: ago(e.created_at), dim: true }
|
|
7386
|
+
])
|
|
7387
|
+
}
|
|
7388
|
+
) })
|
|
7389
|
+
] }) });
|
|
7390
|
+
}
|
|
7391
|
+
var FILTERS;
|
|
7392
|
+
var init_Audit = __esm({
|
|
7393
|
+
"src/tui/panels/Audit.tsx"() {
|
|
7394
|
+
"use strict";
|
|
7395
|
+
init_api_client();
|
|
7396
|
+
init_components();
|
|
7397
|
+
init_hooks();
|
|
7398
|
+
init_theme();
|
|
7399
|
+
FILTERS = [void 0, "DENY", "ALLOW"];
|
|
7400
|
+
}
|
|
7401
|
+
});
|
|
7402
|
+
|
|
7403
|
+
// src/tui/panels/Agents.tsx
|
|
7404
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
7405
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7406
|
+
function AgentsPanel({ active: active2 }) {
|
|
7407
|
+
const agents = useLoader(() => api.agents.live({ limit: 14 }));
|
|
7408
|
+
usePoll(agents.reload, 4e3, active2);
|
|
7409
|
+
const data = agents.data;
|
|
7410
|
+
const rows = data?.agents ?? [];
|
|
7411
|
+
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: [
|
|
7412
|
+
data ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7413
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
|
|
7414
|
+
data.counts.active,
|
|
7415
|
+
" active"
|
|
7416
|
+
] }),
|
|
7417
|
+
" \xB7 ",
|
|
7418
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
7419
|
+
data.counts.idle,
|
|
7420
|
+
" idle"
|
|
7421
|
+
] }),
|
|
7422
|
+
" \xB7 ",
|
|
7423
|
+
data.counts.deactivated,
|
|
7424
|
+
" off"
|
|
7425
|
+
] }) : null,
|
|
7426
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(
|
|
7427
|
+
Table,
|
|
7428
|
+
{
|
|
7429
|
+
columns: [
|
|
7430
|
+
{ header: "STATUS", width: 7 },
|
|
7431
|
+
{ header: "AGENT", width: 20 },
|
|
7432
|
+
{ header: "CALLS", width: 6 },
|
|
7433
|
+
{ header: "DENY", width: 5 },
|
|
7434
|
+
{ header: "DLP", width: 4 },
|
|
7435
|
+
{ header: "TRUST", width: 6 },
|
|
7436
|
+
{ header: "CHARACTER", width: 20 }
|
|
7437
|
+
],
|
|
7438
|
+
rows: rows.map((a) => [
|
|
7439
|
+
{ value: a.status, color: modeColor(a.status) },
|
|
7440
|
+
{ value: truncate2(a.agent_name ?? a.agent_id ?? a.session_id, 20), color: theme.accent },
|
|
7441
|
+
{ value: String(a.total_calls) },
|
|
7442
|
+
{ value: String(a.denied_calls), color: a.denied_calls ? theme.bad : void 0, dim: !a.denied_calls },
|
|
7443
|
+
{ value: String(a.dlp_events), color: a.dlp_events ? theme.bad : void 0, dim: !a.dlp_events },
|
|
7444
|
+
{ value: `${a.trust_score}` },
|
|
7445
|
+
{ value: truncate2(a.character || "\u2014", 20), dim: true }
|
|
7446
|
+
])
|
|
7447
|
+
}
|
|
7448
|
+
) })
|
|
7449
|
+
] }) });
|
|
7450
|
+
}
|
|
7451
|
+
var init_Agents = __esm({
|
|
7452
|
+
"src/tui/panels/Agents.tsx"() {
|
|
7453
|
+
"use strict";
|
|
7454
|
+
init_api_client();
|
|
7455
|
+
init_components();
|
|
7456
|
+
init_hooks();
|
|
7457
|
+
init_theme();
|
|
7458
|
+
}
|
|
7459
|
+
});
|
|
7460
|
+
|
|
7461
|
+
// src/tui/App.tsx
|
|
7462
|
+
import { Box as Box8, Text as Text8, useApp, useInput as useInput5 } from "ink";
|
|
7463
|
+
import { useState as useState6 } from "react";
|
|
7464
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
7465
|
+
function Banner() {
|
|
7466
|
+
const wide = (process.stdout.columns ?? 80) >= 82;
|
|
7467
|
+
if (!wide) {
|
|
7468
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
7469
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
7470
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
|
|
7471
|
+
] });
|
|
7472
|
+
}
|
|
7473
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
7474
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
7475
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
7476
|
+
] });
|
|
7477
|
+
}
|
|
7478
|
+
function App() {
|
|
7479
|
+
const { exit } = useApp();
|
|
7480
|
+
const [section, setSection] = useState6(0);
|
|
7481
|
+
const [focus, setFocus] = useState6("nav");
|
|
7482
|
+
useInput5((input, key) => {
|
|
7483
|
+
if (focus === "nav") {
|
|
7484
|
+
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
7485
|
+
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
7486
|
+
else if (key.rightArrow || key.return || key.tab) setFocus("panel");
|
|
7487
|
+
else if (input === "q") exit();
|
|
7488
|
+
} else {
|
|
7489
|
+
if (key.escape || key.tab) setFocus("nav");
|
|
7490
|
+
}
|
|
7491
|
+
});
|
|
7492
|
+
const current = SECTIONS[section];
|
|
7493
|
+
const Panel = current.Panel;
|
|
7494
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
|
|
7495
|
+
/* @__PURE__ */ jsx8(Banner, {}),
|
|
7496
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
7497
|
+
/* @__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)) }),
|
|
7498
|
+
/* @__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" }) })
|
|
7499
|
+
] }),
|
|
7500
|
+
/* @__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"]] }) })
|
|
7501
|
+
] });
|
|
7502
|
+
}
|
|
7503
|
+
var SECTIONS, BANNER_HEX;
|
|
7504
|
+
var init_App = __esm({
|
|
7505
|
+
"src/tui/App.tsx"() {
|
|
7506
|
+
"use strict";
|
|
7507
|
+
init_cli_utils();
|
|
7508
|
+
init_components();
|
|
7509
|
+
init_theme();
|
|
7510
|
+
init_Policies();
|
|
7511
|
+
init_RateLimit();
|
|
7512
|
+
init_Dlp();
|
|
7513
|
+
init_Stats();
|
|
7514
|
+
init_Audit();
|
|
7515
|
+
init_Agents();
|
|
7516
|
+
SECTIONS = [
|
|
7517
|
+
{ label: "Policies", Panel: PoliciesPanel },
|
|
7518
|
+
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
7519
|
+
{ label: "DLP", Panel: DlpPanel },
|
|
7520
|
+
{ label: "Stats", Panel: StatsPanel },
|
|
7521
|
+
{ label: "Audit", Panel: AuditPanel },
|
|
7522
|
+
{ label: "Agents", Panel: AgentsPanel }
|
|
7523
|
+
];
|
|
7524
|
+
BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
7525
|
+
}
|
|
7526
|
+
});
|
|
7527
|
+
|
|
7528
|
+
// src/tui/index.tsx
|
|
7529
|
+
var tui_exports = {};
|
|
7530
|
+
__export(tui_exports, {
|
|
7531
|
+
launchTui: () => launchTui
|
|
7532
|
+
});
|
|
7533
|
+
import { render } from "ink";
|
|
7534
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
7535
|
+
async function launchTui() {
|
|
7536
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
7537
|
+
process.stderr.write(
|
|
7538
|
+
"The SolonGate TUI needs an interactive terminal.\nUse the scriptable commands instead, e.g. `solongate stats`, `solongate policy list`.\n"
|
|
7539
|
+
);
|
|
7540
|
+
return;
|
|
7541
|
+
}
|
|
7542
|
+
if (!isAuthenticated()) {
|
|
7543
|
+
process.stderr.write("Not logged in. Run `solongate login` first.\n");
|
|
7544
|
+
return;
|
|
7545
|
+
}
|
|
7546
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
|
|
7547
|
+
await waitUntilExit();
|
|
7548
|
+
}
|
|
7549
|
+
var init_tui = __esm({
|
|
7550
|
+
"src/tui/index.tsx"() {
|
|
7551
|
+
"use strict";
|
|
7552
|
+
init_App();
|
|
7553
|
+
init_client();
|
|
7554
|
+
}
|
|
7555
|
+
});
|
|
7556
|
+
|
|
7557
|
+
// src/commands/format.ts
|
|
7558
|
+
function printJson(value) {
|
|
7559
|
+
out(JSON.stringify(value, null, 2));
|
|
7560
|
+
}
|
|
7561
|
+
function decisionColor2(decision) {
|
|
7562
|
+
const d = decision.toUpperCase();
|
|
7563
|
+
if (d === "ALLOW") return green(d);
|
|
7564
|
+
if (d === "DENY" || d === "DENIED") return red(d);
|
|
7565
|
+
return dim(d);
|
|
7566
|
+
}
|
|
7567
|
+
function table(headers, rows) {
|
|
7568
|
+
const cols = headers.length;
|
|
7569
|
+
const w = new Array(cols).fill(0);
|
|
7570
|
+
for (let i = 0; i < cols; i++) w[i] = width(headers[i] ?? "");
|
|
7571
|
+
for (const row of rows) {
|
|
7572
|
+
for (let i = 0; i < cols; i++) w[i] = Math.max(w[i], width(row[i] ?? ""));
|
|
7573
|
+
}
|
|
7574
|
+
const pad = (s, i) => s + " ".repeat(Math.max(0, w[i] - width(s)));
|
|
7575
|
+
err(" " + headers.map((h, i) => dim(pad(h, i))).join(" "));
|
|
7576
|
+
for (const row of rows) {
|
|
7577
|
+
err(" " + row.map((cell, i) => pad(cell ?? "", i)).join(" "));
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
function truncate3(s, n) {
|
|
7581
|
+
if (s.length <= n) return s;
|
|
7582
|
+
return s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
7583
|
+
}
|
|
7584
|
+
function sparkline2(values) {
|
|
7585
|
+
if (values.length === 0) return "";
|
|
7586
|
+
const max = Math.max(...values, 0);
|
|
7587
|
+
if (max === 0) return BLOCKS2[0].repeat(values.length);
|
|
7588
|
+
return values.map((v) => BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(v / max * (BLOCKS2.length - 1)))]).join("");
|
|
7589
|
+
}
|
|
7590
|
+
var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS2;
|
|
7591
|
+
var init_format = __esm({
|
|
7592
|
+
"src/commands/format.ts"() {
|
|
7593
|
+
"use strict";
|
|
7594
|
+
init_cli_utils();
|
|
7595
|
+
out = (s = "") => void process.stdout.write(s + "\n");
|
|
7596
|
+
err = (s = "") => void process.stderr.write(s + "\n");
|
|
7597
|
+
dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
7598
|
+
bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
7599
|
+
green = (s) => `${c.green}${s}${c.reset}`;
|
|
7600
|
+
red = (s) => `${c.red}${s}${c.reset}`;
|
|
7601
|
+
yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
7602
|
+
cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
7603
|
+
ANSI = /\x1b\[[0-9;]*m/g;
|
|
7604
|
+
width = (s) => s.replace(ANSI, "").length;
|
|
7605
|
+
BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
7606
|
+
}
|
|
7607
|
+
});
|
|
7608
|
+
|
|
7609
|
+
// src/commands/args.ts
|
|
7610
|
+
function parse(argv) {
|
|
7611
|
+
const positionals = [];
|
|
7612
|
+
const flags = {};
|
|
7613
|
+
for (let i = 0; i < argv.length; i++) {
|
|
7614
|
+
const tok = argv[i];
|
|
7615
|
+
if (tok.startsWith("--")) {
|
|
7616
|
+
const body = tok.slice(2);
|
|
7617
|
+
const eq = body.indexOf("=");
|
|
7618
|
+
if (eq !== -1) {
|
|
7619
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
7620
|
+
continue;
|
|
7621
|
+
}
|
|
7622
|
+
const next = argv[i + 1];
|
|
7623
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
7624
|
+
flags[body] = next;
|
|
7625
|
+
i++;
|
|
7626
|
+
} else {
|
|
7627
|
+
flags[body] = true;
|
|
7628
|
+
}
|
|
7629
|
+
} else {
|
|
7630
|
+
positionals.push(tok);
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
return { positionals, flags };
|
|
7634
|
+
}
|
|
7635
|
+
function flagStr(flags, name) {
|
|
7636
|
+
const v = flags[name];
|
|
7637
|
+
return typeof v === "string" ? v : void 0;
|
|
7638
|
+
}
|
|
7639
|
+
function flagNum(flags, name) {
|
|
7640
|
+
const v = flagStr(flags, name);
|
|
7641
|
+
if (v === void 0) return void 0;
|
|
7642
|
+
const n = Number(v);
|
|
7643
|
+
return Number.isFinite(n) ? n : void 0;
|
|
7644
|
+
}
|
|
7645
|
+
function flagBool(flags, name) {
|
|
7646
|
+
return flags[name] === true || flags[name] === "true";
|
|
7647
|
+
}
|
|
7648
|
+
var init_args = __esm({
|
|
7649
|
+
"src/commands/args.ts"() {
|
|
7650
|
+
"use strict";
|
|
7651
|
+
}
|
|
7652
|
+
});
|
|
7653
|
+
|
|
7654
|
+
// src/commands/policy.ts
|
|
7655
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
7656
|
+
async function run(argv) {
|
|
7657
|
+
const { positionals, flags } = parse(argv);
|
|
7658
|
+
const sub = positionals[0];
|
|
7659
|
+
const json = flagBool(flags, "json");
|
|
7660
|
+
switch (sub) {
|
|
7661
|
+
case void 0:
|
|
7662
|
+
case "help":
|
|
7663
|
+
err(USAGE);
|
|
7664
|
+
return sub ? 0 : 1;
|
|
7665
|
+
case "list": {
|
|
7666
|
+
const { policies } = await api.policies.list();
|
|
7667
|
+
if (json) return printJson(policies), 0;
|
|
7668
|
+
if (policies.length === 0) {
|
|
7669
|
+
err(dim(" No policies. Create one at https://dashboard.solongate.com"));
|
|
7670
|
+
return 0;
|
|
7671
|
+
}
|
|
7672
|
+
table(
|
|
7673
|
+
["ID", "NAME", "MODE", "RULES", "VER", "UPDATED BY"],
|
|
7674
|
+
policies.map((p) => [
|
|
7675
|
+
cyan(p.id),
|
|
7676
|
+
truncate3(p.name, 28),
|
|
7677
|
+
p.mode === "whitelist" ? green("whitelist") : "denylist",
|
|
7678
|
+
String(p.rules?.length ?? 0),
|
|
7679
|
+
`v${p.version}`,
|
|
7680
|
+
dim(truncate3(p.created_by || "\u2014", 20))
|
|
7681
|
+
])
|
|
7682
|
+
);
|
|
7683
|
+
return 0;
|
|
7684
|
+
}
|
|
7685
|
+
case "show": {
|
|
7686
|
+
const id = positionals[1];
|
|
7687
|
+
if (!id) return err(" Usage: policy show <id> [--version N]"), 1;
|
|
7688
|
+
const p = await api.policies.get(id, flagNum(flags, "version"));
|
|
7689
|
+
if (json) return printJson(p), 0;
|
|
7690
|
+
err("");
|
|
7691
|
+
err(` ${bold(p.name)} ${dim(`(${p.id})`)} v${p._version}`);
|
|
7692
|
+
if (p.description) err(` ${dim(p.description)}`);
|
|
7693
|
+
err(` mode: ${p.mode === "whitelist" ? green("whitelist") : "denylist"} rules: ${p.rules.length}`);
|
|
7694
|
+
err("");
|
|
7695
|
+
printRules(p.rules);
|
|
7696
|
+
return 0;
|
|
7697
|
+
}
|
|
7698
|
+
case "rules": {
|
|
7699
|
+
const id = positionals[1];
|
|
7700
|
+
if (!id) return err(" Usage: policy rules <id>"), 1;
|
|
7701
|
+
const p = await api.policies.get(id);
|
|
7702
|
+
if (json) return printJson(p.rules), 0;
|
|
7703
|
+
printRules(p.rules);
|
|
7704
|
+
return 0;
|
|
7705
|
+
}
|
|
7706
|
+
case "allow": {
|
|
7707
|
+
const id = positionals[1];
|
|
7708
|
+
if (!id) return err(" Usage: policy allow <id> --tool <pattern> [--command|--path|--url <val>]"), 1;
|
|
7709
|
+
const toolPattern = flagStr(flags, "tool") ?? "*";
|
|
7710
|
+
let kind = "tool";
|
|
7711
|
+
let value;
|
|
7712
|
+
for (const k of ["command", "path", "url"]) {
|
|
7713
|
+
const v = flagStr(flags, k);
|
|
7714
|
+
if (v !== void 0) {
|
|
7715
|
+
kind = k;
|
|
7716
|
+
value = v;
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
const res = await api.policies.addRule(id, { toolPattern, kind, value });
|
|
7720
|
+
if (json) return printJson(res), 0;
|
|
7721
|
+
if (res.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW rule already present."));
|
|
7722
|
+
else err(green(` \u2713 Rule added`) + dim(` (${res.rule?.id}) \u2192 v${res.policy_version}`));
|
|
7723
|
+
return 0;
|
|
7724
|
+
}
|
|
7725
|
+
case "revoke": {
|
|
7726
|
+
const id = positionals[1];
|
|
7727
|
+
const ruleId = positionals[2];
|
|
7728
|
+
if (!id || !ruleId) return err(" Usage: policy revoke <id> <ruleId>"), 1;
|
|
7729
|
+
const res = await api.policies.revokeRule(id, ruleId);
|
|
7730
|
+
if (json) return printJson(res), 0;
|
|
7731
|
+
err(green(` \u2713 Revoked ${ruleId}`) + dim(` \u2192 v${res.policy_version}`));
|
|
7732
|
+
return 0;
|
|
7733
|
+
}
|
|
7734
|
+
case "versions": {
|
|
7735
|
+
const id = positionals[1];
|
|
7736
|
+
if (!id) return err(" Usage: policy versions <id>"), 1;
|
|
7737
|
+
const { versions: versions2 } = await api.policies.versions(id, { limit: flagNum(flags, "limit") });
|
|
7738
|
+
if (json) return printJson(versions2), 0;
|
|
7739
|
+
table(
|
|
7740
|
+
["VER", "RULES", "REASON", "BY", "WHEN"],
|
|
7741
|
+
versions2.map((v) => [
|
|
7742
|
+
`v${v.version}`,
|
|
7743
|
+
String(v.rules_count),
|
|
7744
|
+
truncate3(v.reason || "\u2014", 40),
|
|
7745
|
+
dim(truncate3(v.created_by || "\u2014", 18)),
|
|
7746
|
+
dim(v.created_at)
|
|
7747
|
+
])
|
|
7748
|
+
);
|
|
7749
|
+
return 0;
|
|
7750
|
+
}
|
|
7751
|
+
case "rollback": {
|
|
7752
|
+
const id = positionals[1];
|
|
7753
|
+
const version = Number(positionals[2]);
|
|
7754
|
+
if (!id || !Number.isFinite(version)) return err(" Usage: policy rollback <id> <version>"), 1;
|
|
7755
|
+
const res = await api.policies.rollback(id, version);
|
|
7756
|
+
if (json) return printJson(res), 0;
|
|
7757
|
+
err(green(` \u2713 Rolled back ${res.policy_id} from v${res.rolled_back_from} \u2192 v${res.version}`));
|
|
7758
|
+
return 0;
|
|
7759
|
+
}
|
|
7760
|
+
case "active": {
|
|
7761
|
+
const a = await api.policies.active();
|
|
7762
|
+
if (json) return printJson(a), 0;
|
|
7763
|
+
if (!a.policy) {
|
|
7764
|
+
err(dim(" No active policy resolves for this project."));
|
|
7765
|
+
return 0;
|
|
7766
|
+
}
|
|
7767
|
+
err("");
|
|
7768
|
+
err(` Active: ${bold(a.policy.name)} ${dim(`(${a.policy.id})`)} v${a.version}`);
|
|
7769
|
+
err(` matched by: ${cyan(a.matched_by ?? "\u2014")} self-protection: ${a.self_protection_enabled ? green("on") : dim("off")}`);
|
|
7770
|
+
const rl = a.security?.rateLimit;
|
|
7771
|
+
if (rl) err(` rate limit: ${rl.perMinute}/min ${rl.perHour}/h ${rl.perDay}/day`);
|
|
7772
|
+
if (a.security?.dlpBlock) err(` DLP block: ${a.security.dlpBlock.patterns.length} patterns`);
|
|
7773
|
+
return 0;
|
|
7774
|
+
}
|
|
7775
|
+
case "activate": {
|
|
7776
|
+
if (flagBool(flags, "clear")) {
|
|
7777
|
+
const res2 = await api.policies.setActive(null);
|
|
7778
|
+
if (json) return printJson(res2), 0;
|
|
7779
|
+
return err(green(" \u2713 Cleared active-policy pin.")), 0;
|
|
7780
|
+
}
|
|
7781
|
+
const id = positionals[1];
|
|
7782
|
+
if (!id) return err(" Usage: policy activate <id> | policy activate --clear"), 1;
|
|
7783
|
+
const res = await api.policies.setActive(id);
|
|
7784
|
+
if (json) return printJson(res), 0;
|
|
7785
|
+
err(green(` \u2713 Pinned active policy \u2192 ${res.active}`));
|
|
7786
|
+
return 0;
|
|
7787
|
+
}
|
|
7788
|
+
case "dry-run": {
|
|
7789
|
+
const target = positionals[1];
|
|
7790
|
+
if (!target) return err(" Usage: policy dry-run <id|file.json> [--limit N]"), 1;
|
|
7791
|
+
const rules = await resolveRules(target);
|
|
7792
|
+
const res = await api.policies.dryRun({
|
|
7793
|
+
rules,
|
|
7794
|
+
mode: flagStr(flags, "mode") ?? void 0,
|
|
7795
|
+
limit: flagNum(flags, "limit")
|
|
7796
|
+
});
|
|
7797
|
+
if (json) return printJson(res), 0;
|
|
7798
|
+
err("");
|
|
7799
|
+
err(` Replayed ${bold(String(res.evaluated))} recent calls against ${rules.length} rule(s)`);
|
|
7800
|
+
err(` would allow: ${green(String(res.would_allow))} would deny: ${decisionColor2("DENY")} ${res.would_deny}`);
|
|
7801
|
+
err(` ${green("newly allowed")}: ${res.newly_allowed} ${decisionColor2("DENY")}${dim(" newly blocked")}: ${res.newly_blocked} unchanged: ${res.unchanged}`);
|
|
7802
|
+
if (res.sample_newly_blocked.length) {
|
|
7803
|
+
err(dim("\n Sample newly-blocked:"));
|
|
7804
|
+
for (const s of res.sample_newly_blocked.slice(0, 8)) err(` ${decisionColor2("DENY")} ${s.tool} ${dim(truncate3(s.preview, 50))}`);
|
|
7805
|
+
}
|
|
7806
|
+
return 0;
|
|
7807
|
+
}
|
|
7808
|
+
default:
|
|
7809
|
+
err(` Unknown: policy ${sub}
|
|
7810
|
+
`);
|
|
7811
|
+
err(USAGE);
|
|
7812
|
+
return 1;
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
function printRules(rules) {
|
|
7816
|
+
if (rules.length === 0) return void err(dim(" (no rules)"));
|
|
7817
|
+
table(
|
|
7818
|
+
["EFFECT", "PRIO", "TOOL", "ID", "DESCRIPTION"],
|
|
7819
|
+
rules.map((r) => [
|
|
7820
|
+
r.effect === "ALLOW" ? green("ALLOW") : decisionColor2("DENY"),
|
|
7821
|
+
String(r.priority),
|
|
7822
|
+
cyan(truncate3(r.toolPattern, 24)),
|
|
7823
|
+
dim(truncate3(r.id, 22)),
|
|
7824
|
+
truncate3(r.description || "\u2014", 40)
|
|
7825
|
+
])
|
|
7826
|
+
);
|
|
7827
|
+
}
|
|
7828
|
+
async function resolveRules(target) {
|
|
7829
|
+
if (target.endsWith(".json")) {
|
|
7830
|
+
const parsed = JSON.parse(readFileSync5(target, "utf-8"));
|
|
7831
|
+
return parsed.rules ?? [];
|
|
7832
|
+
}
|
|
7833
|
+
const p = await api.policies.get(target);
|
|
7834
|
+
return p.rules;
|
|
7835
|
+
}
|
|
7836
|
+
var USAGE;
|
|
7837
|
+
var init_policy = __esm({
|
|
7838
|
+
"src/commands/policy.ts"() {
|
|
7839
|
+
"use strict";
|
|
7840
|
+
init_api_client();
|
|
7841
|
+
init_args();
|
|
7842
|
+
init_format();
|
|
7843
|
+
USAGE = `${bold("solongate policy")} \u2014 manage cloud policies
|
|
7844
|
+
|
|
7845
|
+
policy list List all policies
|
|
7846
|
+
policy show <id> [--version N] Show one policy (rules, mode)
|
|
7847
|
+
policy rules <id> List a policy's rules
|
|
7848
|
+
policy allow <id> --tool <p> [--command|--path|--url <val>]
|
|
7849
|
+
Append an ALLOW rule
|
|
7850
|
+
policy revoke <id> <ruleId> Remove a rule
|
|
7851
|
+
policy versions <id> List version history
|
|
7852
|
+
policy rollback <id> <version> Roll back to a version
|
|
7853
|
+
policy active Show the resolved active policy
|
|
7854
|
+
policy activate <id> | --clear Pin / unpin the active policy
|
|
7855
|
+
policy dry-run <id|file.json> [--limit N] [--mode denylist|whitelist]
|
|
7856
|
+
Replay recent traffic against a policy's rules
|
|
7857
|
+
|
|
7858
|
+
Add --json to any read command for machine-readable output.`;
|
|
7859
|
+
}
|
|
7860
|
+
});
|
|
7861
|
+
|
|
7862
|
+
// src/commands/ratelimit.ts
|
|
7863
|
+
async function run2(argv) {
|
|
7864
|
+
const { positionals, flags } = parse(argv);
|
|
7865
|
+
const sub = positionals[0] ?? "show";
|
|
7866
|
+
const json = flagBool(flags, "json");
|
|
7867
|
+
switch (sub) {
|
|
7868
|
+
case "help":
|
|
7869
|
+
return err(USAGE2), 0;
|
|
7870
|
+
case "show": {
|
|
7871
|
+
const [{ layers }, { history }] = await Promise.all([
|
|
7872
|
+
api.settings.getSecurityLayers(),
|
|
7873
|
+
api.settings.getRateLimitHistory()
|
|
7874
|
+
]);
|
|
7875
|
+
if (json) return printJson({ rateLimit: layers.rateLimit, history }), 0;
|
|
7876
|
+
const rl = layers.rateLimit;
|
|
7877
|
+
err("");
|
|
7878
|
+
err(` Rate limit mode: ${modeColor2(rl.mode)}`);
|
|
7879
|
+
err(` ${bold(String(rl.perMinute))} ${dim("/min")} ${bold(String(rl.perHour))} ${dim("/hour")} ${bold(String(rl.perDay))} ${dim("/day")}`);
|
|
7880
|
+
if (history.length) {
|
|
7881
|
+
const spark = sparkline2(history.map((h) => h.minute));
|
|
7882
|
+
err(` history ${cyan(spark)} ${dim(`(${history.length} changes, per-min)`)}`);
|
|
7883
|
+
}
|
|
7884
|
+
return 0;
|
|
7885
|
+
}
|
|
7886
|
+
case "history": {
|
|
7887
|
+
const { history } = await api.settings.getRateLimitHistory();
|
|
7888
|
+
if (json) return printJson(history), 0;
|
|
7889
|
+
if (!history.length) return err(dim(" No rate-limit changes recorded.")), 0;
|
|
7890
|
+
table(
|
|
7891
|
+
["WHEN", "MINUTE", "HOUR", "DAY"],
|
|
7892
|
+
history.map((h) => [dim(new Date(h.ts).toISOString()), String(h.minute), String(h.hour), String(h.day)])
|
|
7893
|
+
);
|
|
7894
|
+
return 0;
|
|
7895
|
+
}
|
|
7896
|
+
case "set": {
|
|
7897
|
+
const minute = flagNum(flags, "minute");
|
|
7898
|
+
const hour = flagNum(flags, "hour");
|
|
7899
|
+
const day = flagNum(flags, "day");
|
|
7900
|
+
const mode = flagStr(flags, "mode");
|
|
7901
|
+
if (minute === void 0 && hour === void 0 && day === void 0 && !mode) {
|
|
7902
|
+
return err(" Usage: ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]"), 1;
|
|
7903
|
+
}
|
|
7904
|
+
const { layers } = await api.settings.getSecurityLayers();
|
|
7905
|
+
const next = {
|
|
7906
|
+
...layers,
|
|
7907
|
+
rateLimit: {
|
|
7908
|
+
mode: mode ?? layers.rateLimit.mode,
|
|
7909
|
+
perMinute: minute ?? layers.rateLimit.perMinute,
|
|
7910
|
+
perHour: hour ?? layers.rateLimit.perHour,
|
|
7911
|
+
perDay: day ?? layers.rateLimit.perDay
|
|
7912
|
+
}
|
|
7913
|
+
};
|
|
7914
|
+
const res = await api.settings.setSecurityLayers(next);
|
|
7915
|
+
if (json) return printJson(res.layers.rateLimit), 0;
|
|
7916
|
+
const r = res.layers.rateLimit;
|
|
7917
|
+
err(green(" \u2713 Rate limit updated") + dim(` ${r.perMinute}/min ${r.perHour}/h ${r.perDay}/day (${r.mode})`));
|
|
7918
|
+
return 0;
|
|
7919
|
+
}
|
|
7920
|
+
default:
|
|
7921
|
+
return err(USAGE2), 1;
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
var USAGE2, modeColor2;
|
|
7925
|
+
var init_ratelimit = __esm({
|
|
7926
|
+
"src/commands/ratelimit.ts"() {
|
|
7927
|
+
"use strict";
|
|
7928
|
+
init_api_client();
|
|
7929
|
+
init_args();
|
|
7930
|
+
init_format();
|
|
7931
|
+
USAGE2 = `${bold("solongate ratelimit")} \u2014 request throttling
|
|
7932
|
+
|
|
7933
|
+
ratelimit show Current limits + change history
|
|
7934
|
+
ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]
|
|
7935
|
+
ratelimit history Recent limit changes
|
|
7936
|
+
|
|
7937
|
+
Add --json for machine-readable output.`;
|
|
7938
|
+
modeColor2 = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
7939
|
+
}
|
|
7940
|
+
});
|
|
7941
|
+
|
|
7942
|
+
// src/commands/dlp.ts
|
|
7943
|
+
async function run3(argv) {
|
|
7944
|
+
const { positionals, flags } = parse(argv);
|
|
7945
|
+
const sub = positionals[0] ?? "show";
|
|
7946
|
+
const json = flagBool(flags, "json");
|
|
7947
|
+
const { layers, availablePatterns } = await api.settings.getSecurityLayers();
|
|
7948
|
+
const save = async (next) => (await api.settings.setSecurityLayers(next)).layers;
|
|
7949
|
+
switch (sub) {
|
|
7950
|
+
case "help":
|
|
7951
|
+
return err(USAGE3), 0;
|
|
7952
|
+
case "show": {
|
|
7953
|
+
if (json) return printJson({ dlp: layers.dlp, availablePatterns }), 0;
|
|
7954
|
+
err("");
|
|
7955
|
+
err(` DLP mode: ${modeColor3(layers.dlp.mode)}`);
|
|
7956
|
+
const enabled = new Set(layers.dlp.patterns);
|
|
7957
|
+
table(
|
|
7958
|
+
["", "PATTERN"],
|
|
7959
|
+
availablePatterns.map((p) => [enabled.has(p) ? green("\u25CF") : dim("\u25CB"), enabled.has(p) ? p : dim(p)])
|
|
7960
|
+
);
|
|
7961
|
+
if (layers.dlp.custom.length) {
|
|
7962
|
+
err(dim("\n Custom:"));
|
|
7963
|
+
for (const c2 of layers.dlp.custom) err(` ${cyan(c2.name)} ${dim(c2.re)}`);
|
|
7964
|
+
}
|
|
7965
|
+
return 0;
|
|
7966
|
+
}
|
|
7967
|
+
case "mode": {
|
|
7968
|
+
const mode = positionals[1];
|
|
7969
|
+
if (!mode || !["off", "detect", "block"].includes(mode)) return err(" Usage: dlp mode <off|detect|block>"), 1;
|
|
7970
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, mode } });
|
|
7971
|
+
if (json) return printJson(saved.dlp), 0;
|
|
7972
|
+
return err(green(` \u2713 DLP mode \u2192 ${saved.dlp.mode}`)), 0;
|
|
7973
|
+
}
|
|
7974
|
+
case "enable":
|
|
7975
|
+
case "disable": {
|
|
7976
|
+
const pattern = positionals.slice(1).join(" ");
|
|
7977
|
+
if (!pattern) return err(` Usage: dlp ${sub} <pattern>`), 1;
|
|
7978
|
+
if (!availablePatterns.includes(pattern)) {
|
|
7979
|
+
err(` Unknown pattern: "${pattern}". Available:`);
|
|
7980
|
+
for (const p of availablePatterns) err(` ${dim("\u2022")} ${p}`);
|
|
7981
|
+
return 1;
|
|
7982
|
+
}
|
|
7983
|
+
const set = new Set(layers.dlp.patterns);
|
|
7984
|
+
if (sub === "enable") set.add(pattern);
|
|
7985
|
+
else set.delete(pattern);
|
|
7986
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, patterns: [...set] } });
|
|
7987
|
+
if (json) return printJson(saved.dlp), 0;
|
|
7988
|
+
return err(green(` \u2713 ${sub}d "${pattern}"`) + dim(` (${saved.dlp.patterns.length} active)`)), 0;
|
|
7989
|
+
}
|
|
7990
|
+
case "add-custom": {
|
|
7991
|
+
const name = flagStr(flags, "name");
|
|
7992
|
+
const re = flagStr(flags, "re");
|
|
7993
|
+
if (!name || !re) return err(" Usage: dlp add-custom --name <name> --re <regex>"), 1;
|
|
7994
|
+
const custom = [...layers.dlp.custom.filter((c2) => c2.name !== name), { name, re }];
|
|
7995
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
7996
|
+
if (json) return printJson(saved.dlp), 0;
|
|
7997
|
+
return err(green(` \u2713 Custom pattern "${name}" added`)), 0;
|
|
7998
|
+
}
|
|
7999
|
+
case "remove-custom": {
|
|
8000
|
+
const name = positionals.slice(1).join(" ");
|
|
8001
|
+
if (!name) return err(" Usage: dlp remove-custom <name>"), 1;
|
|
8002
|
+
const custom = layers.dlp.custom.filter((c2) => c2.name !== name);
|
|
8003
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
8004
|
+
if (json) return printJson(saved.dlp), 0;
|
|
8005
|
+
return err(green(` \u2713 Removed custom pattern "${name}"`)), 0;
|
|
8006
|
+
}
|
|
8007
|
+
default:
|
|
8008
|
+
return err(USAGE3), 1;
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
var USAGE3, modeColor3;
|
|
8012
|
+
var init_dlp = __esm({
|
|
8013
|
+
"src/commands/dlp.ts"() {
|
|
8014
|
+
"use strict";
|
|
8015
|
+
init_api_client();
|
|
8016
|
+
init_args();
|
|
8017
|
+
init_format();
|
|
8018
|
+
USAGE3 = `${bold("solongate dlp")} \u2014 data-loss prevention
|
|
8019
|
+
|
|
8020
|
+
dlp show Current mode + enabled patterns
|
|
8021
|
+
dlp mode <off|detect|block> Set enforcement mode
|
|
8022
|
+
dlp enable <pattern> Enable a built-in pattern
|
|
8023
|
+
dlp disable <pattern> Disable a built-in pattern
|
|
8024
|
+
dlp add-custom --name X --re <regex> Add a custom pattern
|
|
8025
|
+
dlp remove-custom <name> Remove a custom pattern
|
|
8026
|
+
|
|
8027
|
+
Add --json for machine-readable output.`;
|
|
8028
|
+
modeColor3 = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
8029
|
+
}
|
|
8030
|
+
});
|
|
8031
|
+
|
|
8032
|
+
// src/commands/stats.ts
|
|
8033
|
+
async function run4(argv) {
|
|
8034
|
+
const { positionals, flags } = parse(argv);
|
|
8035
|
+
const sub = positionals[0] ?? "overview";
|
|
8036
|
+
const json = flagBool(flags, "json");
|
|
8037
|
+
switch (sub) {
|
|
8038
|
+
case "help":
|
|
8039
|
+
return err(USAGE4), 0;
|
|
8040
|
+
case "overview": {
|
|
8041
|
+
const s = await api.stats.get();
|
|
8042
|
+
if (json) return printJson(s), 0;
|
|
8043
|
+
err("");
|
|
8044
|
+
err(` ${bold(String(s.total_calls))} calls ${green(String(s.allowed))} allowed ${red(String(s.denied))} denied`);
|
|
8045
|
+
err(` ${dim(`${s.active_policies} active policies \xB7 ${s.registered_tools} tools`)}`);
|
|
8046
|
+
if (s.recent_activity.length) {
|
|
8047
|
+
err(dim("\n Recent:"));
|
|
8048
|
+
table(
|
|
8049
|
+
["DECISION", "TOOL", "TRUST", "MS", "WHEN"],
|
|
8050
|
+
s.recent_activity.map((a) => [
|
|
8051
|
+
decisionColor2(a.decision),
|
|
8052
|
+
cyan(truncate3(a.tool_name, 24)),
|
|
8053
|
+
dim(a.trust_level),
|
|
8054
|
+
String(a.evaluation_time_ms ?? "\u2014"),
|
|
8055
|
+
dim(a.created_at)
|
|
8056
|
+
])
|
|
8057
|
+
);
|
|
8058
|
+
}
|
|
8059
|
+
return 0;
|
|
8060
|
+
}
|
|
8061
|
+
case "timeseries": {
|
|
8062
|
+
const period = flagStr(flags, "period") ?? "24h";
|
|
8063
|
+
const ts = await api.stats.timeseries({ period });
|
|
8064
|
+
if (json) return printJson(ts), 0;
|
|
8065
|
+
const pts = ts.timeseries;
|
|
8066
|
+
err("");
|
|
8067
|
+
err(` Timeseries ${dim(`(${ts.period}, per ${ts.granularity})`)}`);
|
|
8068
|
+
err(` total ${cyan(sparkline2(pts.map((p) => p.total)))} ${dim(`max ${Math.max(0, ...pts.map((p) => p.total))}`)}`);
|
|
8069
|
+
err(` allowed ${green(sparkline2(pts.map((p) => p.allowed)))}`);
|
|
8070
|
+
err(` denied ${red(sparkline2(pts.map((p) => p.denied)))}`);
|
|
8071
|
+
return 0;
|
|
8072
|
+
}
|
|
8073
|
+
case "drift": {
|
|
8074
|
+
const d = await api.stats.drift(flagNum(flags, "days"));
|
|
8075
|
+
if (json) return printJson(d), 0;
|
|
8076
|
+
err("");
|
|
8077
|
+
err(` Denial drift ${dim(`(${d.days}d: ${d.total_current} now vs ${d.total_previous} prev)`)}`);
|
|
8078
|
+
if (!d.rules.length) return err(dim(" No denials in window.")), 0;
|
|
8079
|
+
table(
|
|
8080
|
+
["NOW", "PREV", "\u0394", "RULE", "REASON"],
|
|
8081
|
+
d.rules.slice(0, 20).map((r) => [
|
|
8082
|
+
bold(String(r.current)),
|
|
8083
|
+
dim(String(r.previous)),
|
|
8084
|
+
r.is_new ? green("NEW") : r.spike ? red(`+${r.delta}`) : String(r.delta),
|
|
8085
|
+
cyan(truncate3(r.rule_id ?? "\u2014", 22)),
|
|
8086
|
+
truncate3(r.reason ?? r.last_tool ?? "\u2014", 36)
|
|
8087
|
+
])
|
|
8088
|
+
);
|
|
8089
|
+
return 0;
|
|
8090
|
+
}
|
|
8091
|
+
default:
|
|
8092
|
+
return err(USAGE4), 1;
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
var USAGE4;
|
|
8096
|
+
var init_stats2 = __esm({
|
|
8097
|
+
"src/commands/stats.ts"() {
|
|
8098
|
+
"use strict";
|
|
8099
|
+
init_api_client();
|
|
8100
|
+
init_args();
|
|
8101
|
+
init_format();
|
|
8102
|
+
USAGE4 = `${bold("solongate stats")} \u2014 traffic & security stats
|
|
8103
|
+
|
|
8104
|
+
stats Overview (totals, recent activity)
|
|
8105
|
+
stats timeseries [--period 24h|7d|30d|all]
|
|
8106
|
+
stats drift [--days N] Denials rising/falling vs previous window
|
|
8107
|
+
|
|
8108
|
+
Add --json for machine-readable output.`;
|
|
8109
|
+
}
|
|
8110
|
+
});
|
|
8111
|
+
|
|
8112
|
+
// src/commands/audit.ts
|
|
8113
|
+
async function run5(argv) {
|
|
8114
|
+
const { positionals, flags } = parse(argv);
|
|
8115
|
+
const json = flagBool(flags, "json");
|
|
8116
|
+
if (positionals[0] === "help") return err(USAGE5), 0;
|
|
8117
|
+
if (positionals[0] === "whitelist") {
|
|
8118
|
+
const id = positionals[1];
|
|
8119
|
+
if (!id) return err(" Usage: audit whitelist <logId> [--scope exact|tool]"), 1;
|
|
8120
|
+
const scope = flagStr(flags, "scope") ?? "exact";
|
|
8121
|
+
const res2 = await api.audit.whitelist(id, scope);
|
|
8122
|
+
if (json) return printJson(res2), 0;
|
|
8123
|
+
if (res2.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW already present."));
|
|
8124
|
+
else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
|
|
8125
|
+
return 0;
|
|
8126
|
+
}
|
|
8127
|
+
const query = {
|
|
8128
|
+
filter: flagStr(flags, "filter"),
|
|
8129
|
+
tool: flagStr(flags, "tool"),
|
|
8130
|
+
signal: flagStr(flags, "signal"),
|
|
8131
|
+
search: flagStr(flags, "search"),
|
|
8132
|
+
agent_name: flagStr(flags, "agent-name"),
|
|
8133
|
+
limit: flagNum(flags, "limit") ?? 30
|
|
8134
|
+
};
|
|
8135
|
+
const res = await api.audit.list(query);
|
|
8136
|
+
if (json) return printJson(res), 0;
|
|
8137
|
+
err("");
|
|
8138
|
+
err(` ${bold(String(res.total))} matching entries ${dim(`(showing ${res.entries.length})`)}`);
|
|
8139
|
+
if (!res.entries.length) return 0;
|
|
8140
|
+
table(
|
|
8141
|
+
["DECISION", "TOOL", "AGENT", "REASON", "DLP", "WHEN", "ID"],
|
|
8142
|
+
res.entries.map((e) => [
|
|
8143
|
+
decisionColor2(e.decision),
|
|
8144
|
+
cyan(truncate3(e.tool_name, 22)),
|
|
8145
|
+
dim(truncate3(e.agent_name ?? "\u2014", 16)),
|
|
8146
|
+
truncate3(e.reason ?? "\u2014", 30),
|
|
8147
|
+
e.dlp_matches?.length ? red(String(e.dlp_matches.length)) : dim("0"),
|
|
8148
|
+
dim(e.created_at),
|
|
8149
|
+
dim(truncate3(e.id, 10))
|
|
8150
|
+
])
|
|
8151
|
+
);
|
|
8152
|
+
return 0;
|
|
8153
|
+
}
|
|
8154
|
+
var USAGE5;
|
|
8155
|
+
var init_audit2 = __esm({
|
|
8156
|
+
"src/commands/audit.ts"() {
|
|
8157
|
+
"use strict";
|
|
8158
|
+
init_api_client();
|
|
8159
|
+
init_args();
|
|
8160
|
+
init_format();
|
|
8161
|
+
USAGE5 = `${bold("solongate audit")} \u2014 audit log
|
|
8162
|
+
|
|
8163
|
+
audit [--filter ALLOW|DENY] [--tool <substr>] [--signal dlp|ratelimit]
|
|
8164
|
+
[--search <text>] [--agent-name <name>] [--limit N]
|
|
8165
|
+
audit whitelist <logId> [--scope exact|tool]
|
|
8166
|
+
Turn a denied call into an ALLOW rule
|
|
8167
|
+
|
|
8168
|
+
Add --json for machine-readable output.`;
|
|
8169
|
+
}
|
|
8170
|
+
});
|
|
8171
|
+
|
|
8172
|
+
// src/commands/agents.ts
|
|
8173
|
+
async function runAgents(argv) {
|
|
8174
|
+
const { flags } = parse(argv);
|
|
8175
|
+
const json = flagBool(flags, "json");
|
|
8176
|
+
const res = await api.agents.live({ limit: flagNum(flags, "limit"), includeDeactivated: flagBool(flags, "all") });
|
|
8177
|
+
if (json) return printJson(res), 0;
|
|
8178
|
+
err("");
|
|
8179
|
+
err(` Agents ${green(String(res.counts.active))} active ${yellow(String(res.counts.idle))} idle ${dim(String(res.counts.deactivated) + " off")}`);
|
|
8180
|
+
if (!res.agents.length) return err(dim(" No agent sessions.")), 0;
|
|
8181
|
+
table(
|
|
8182
|
+
["STATUS", "AGENT", "CALLS", "DENY", "DLP", "TRUST", "CHARACTER"],
|
|
8183
|
+
res.agents.map((a) => [
|
|
8184
|
+
statusColor(a.status),
|
|
8185
|
+
cyan(truncate3(a.agent_name ?? a.agent_id ?? a.session_id, 20)),
|
|
8186
|
+
String(a.total_calls),
|
|
8187
|
+
a.denied_calls ? red(String(a.denied_calls)) : dim("0"),
|
|
8188
|
+
a.dlp_events ? red(String(a.dlp_events)) : dim("0"),
|
|
8189
|
+
`${a.trust_score}`,
|
|
8190
|
+
dim(truncate3(a.character || "\u2014", 22))
|
|
8191
|
+
])
|
|
8192
|
+
);
|
|
8193
|
+
return 0;
|
|
8194
|
+
}
|
|
8195
|
+
async function runAgent(argv) {
|
|
8196
|
+
const { positionals, flags } = parse(argv);
|
|
8197
|
+
const json = flagBool(flags, "json");
|
|
8198
|
+
const id = positionals[0];
|
|
8199
|
+
if (!id) return err(" Usage: solongate agent <agent_id> [--json]"), 1;
|
|
8200
|
+
const a = await api.agents.get(id);
|
|
8201
|
+
if (json) return printJson(a), 0;
|
|
8202
|
+
err("");
|
|
8203
|
+
err(` ${bold(a.agent_id)} status: ${statusColor(String(a.status))}`);
|
|
8204
|
+
const base = a.baseline;
|
|
8205
|
+
if (base) {
|
|
8206
|
+
err(` ${dim(base.character ?? "")} trust ${bold(String(base.trustScore ?? "\u2014"))}/100 deny-rate ${(Number(base.denyRate ?? 0) * 100).toFixed(0)}%`);
|
|
8207
|
+
}
|
|
8208
|
+
const feed = a.recent_feed ?? [];
|
|
8209
|
+
if (feed.length) {
|
|
8210
|
+
err(dim("\n Recent:"));
|
|
8211
|
+
table(
|
|
8212
|
+
["DECISION", "TOOL", "REASON", "WHEN"],
|
|
8213
|
+
feed.slice(0, 15).map((f) => [
|
|
8214
|
+
f.decision === "ALLOW" ? green("ALLOW") : red(String(f.decision)),
|
|
8215
|
+
cyan(truncate3(String(f.tool), 22)),
|
|
8216
|
+
truncate3(String(f.reason ?? "\u2014"), 30),
|
|
8217
|
+
dim(String(f.created_at))
|
|
8218
|
+
])
|
|
8219
|
+
);
|
|
8220
|
+
}
|
|
8221
|
+
return 0;
|
|
8222
|
+
}
|
|
8223
|
+
var statusColor;
|
|
8224
|
+
var init_agents2 = __esm({
|
|
8225
|
+
"src/commands/agents.ts"() {
|
|
8226
|
+
"use strict";
|
|
8227
|
+
init_api_client();
|
|
8228
|
+
init_args();
|
|
8229
|
+
init_format();
|
|
8230
|
+
statusColor = (s) => s === "active" ? green(s) : s === "idle" ? yellow(s) : dim(s);
|
|
8231
|
+
}
|
|
8232
|
+
});
|
|
8233
|
+
|
|
8234
|
+
// src/commands/index.ts
|
|
8235
|
+
var commands_exports = {};
|
|
8236
|
+
__export(commands_exports, {
|
|
8237
|
+
COMMAND_NAMES: () => COMMAND_NAMES,
|
|
8238
|
+
runCommand: () => runCommand
|
|
8239
|
+
});
|
|
8240
|
+
async function dispatch(command, argv) {
|
|
8241
|
+
switch (command) {
|
|
8242
|
+
case "policy":
|
|
8243
|
+
return run(argv);
|
|
8244
|
+
case "ratelimit":
|
|
8245
|
+
return run2(argv);
|
|
8246
|
+
case "dlp":
|
|
8247
|
+
return run3(argv);
|
|
8248
|
+
case "stats":
|
|
8249
|
+
return run4(argv);
|
|
8250
|
+
case "audit":
|
|
8251
|
+
return run5(argv);
|
|
8252
|
+
case "agents":
|
|
8253
|
+
return runAgents(argv);
|
|
8254
|
+
case "agent":
|
|
8255
|
+
return runAgent(argv);
|
|
8256
|
+
default:
|
|
8257
|
+
err(` Unknown command: ${command}`);
|
|
8258
|
+
return 1;
|
|
8259
|
+
}
|
|
8260
|
+
}
|
|
8261
|
+
async function runCommand(command, argv) {
|
|
8262
|
+
try {
|
|
8263
|
+
return await dispatch(command, argv);
|
|
8264
|
+
} catch (e) {
|
|
8265
|
+
if (e instanceof NotAuthenticatedError) {
|
|
8266
|
+
err(red(" \u2717 ") + e.message);
|
|
8267
|
+
return 1;
|
|
8268
|
+
}
|
|
8269
|
+
if (e instanceof ApiError) {
|
|
8270
|
+
err(red(" \u2717 ") + `${e.message}` + (e.status ? ` (${e.status})` : ""));
|
|
8271
|
+
return 1;
|
|
8272
|
+
}
|
|
8273
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8274
|
+
err(red(" \u2717 ") + msg);
|
|
8275
|
+
return 1;
|
|
8276
|
+
}
|
|
8277
|
+
}
|
|
8278
|
+
var COMMAND_NAMES;
|
|
8279
|
+
var init_commands = __esm({
|
|
8280
|
+
"src/commands/index.ts"() {
|
|
8281
|
+
"use strict";
|
|
8282
|
+
init_api_client();
|
|
8283
|
+
init_format();
|
|
8284
|
+
init_policy();
|
|
8285
|
+
init_ratelimit();
|
|
8286
|
+
init_dlp();
|
|
8287
|
+
init_stats2();
|
|
8288
|
+
init_audit2();
|
|
8289
|
+
init_agents2();
|
|
8290
|
+
COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
|
|
8291
|
+
}
|
|
8292
|
+
});
|
|
8293
|
+
|
|
6571
8294
|
// src/global-install.ts
|
|
6572
8295
|
var global_install_exports = {};
|
|
6573
8296
|
__export(global_install_exports, {
|
|
@@ -6580,9 +8303,9 @@ __export(global_install_exports, {
|
|
|
6580
8303
|
runGlobalRestore: () => runGlobalRestore,
|
|
6581
8304
|
unlockProtected: () => unlockProtected
|
|
6582
8305
|
});
|
|
6583
|
-
import { readFileSync as
|
|
6584
|
-
import { resolve as
|
|
6585
|
-
import { homedir as
|
|
8306
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
8307
|
+
import { resolve as resolve4, join as join5, dirname } from "path";
|
|
8308
|
+
import { homedir as homedir3 } from "os";
|
|
6586
8309
|
import { fileURLToPath } from "url";
|
|
6587
8310
|
import { createInterface } from "readline";
|
|
6588
8311
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -6645,10 +8368,10 @@ function unlockFile(file) {
|
|
|
6645
8368
|
function protectedTargets() {
|
|
6646
8369
|
const p = globalPaths();
|
|
6647
8370
|
return [
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
8371
|
+
join5(p.hooksDir, "guard.mjs"),
|
|
8372
|
+
join5(p.hooksDir, "audit.mjs"),
|
|
8373
|
+
join5(p.hooksDir, "stop.mjs"),
|
|
8374
|
+
join5(p.hooksDir, "shield.mjs"),
|
|
6652
8375
|
p.configPath,
|
|
6653
8376
|
p.settingsPath
|
|
6654
8377
|
];
|
|
@@ -6660,26 +8383,26 @@ function unlockProtected() {
|
|
|
6660
8383
|
for (const f of protectedTargets()) unlockFile(f);
|
|
6661
8384
|
}
|
|
6662
8385
|
function globalPaths() {
|
|
6663
|
-
const home =
|
|
6664
|
-
const sgDir =
|
|
6665
|
-
const hooksDir =
|
|
6666
|
-
const claudeDir =
|
|
8386
|
+
const home = homedir3();
|
|
8387
|
+
const sgDir = join5(home, ".solongate");
|
|
8388
|
+
const hooksDir = join5(sgDir, "hooks");
|
|
8389
|
+
const claudeDir = join5(home, ".claude");
|
|
6667
8390
|
return {
|
|
6668
8391
|
home,
|
|
6669
8392
|
sgDir,
|
|
6670
8393
|
hooksDir,
|
|
6671
8394
|
claudeDir,
|
|
6672
|
-
settingsPath:
|
|
6673
|
-
backupPath:
|
|
6674
|
-
configPath:
|
|
8395
|
+
settingsPath: join5(claudeDir, "settings.json"),
|
|
8396
|
+
backupPath: join5(claudeDir, "settings.solongate.bak"),
|
|
8397
|
+
configPath: join5(sgDir, "cloud-guard.json")
|
|
6675
8398
|
};
|
|
6676
8399
|
}
|
|
6677
8400
|
function readHook(filename) {
|
|
6678
|
-
return
|
|
8401
|
+
return readFileSync6(join5(HOOKS_DIR, filename), "utf-8");
|
|
6679
8402
|
}
|
|
6680
8403
|
function readGuard() {
|
|
6681
|
-
const bundled =
|
|
6682
|
-
return existsSync4(bundled) ?
|
|
8404
|
+
const bundled = join5(HOOKS_DIR, "guard.bundled.mjs");
|
|
8405
|
+
return existsSync4(bundled) ? readFileSync6(bundled, "utf-8") : readHook("guard.mjs");
|
|
6683
8406
|
}
|
|
6684
8407
|
function ask(question) {
|
|
6685
8408
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -6693,11 +8416,11 @@ function runGlobalRestore() {
|
|
|
6693
8416
|
unlockProtected();
|
|
6694
8417
|
removeClaudeShim();
|
|
6695
8418
|
if (existsSync4(p.backupPath)) {
|
|
6696
|
-
writeFileSync3(p.settingsPath,
|
|
8419
|
+
writeFileSync3(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
|
|
6697
8420
|
console.log(` Restored ${p.settingsPath} from backup.`);
|
|
6698
8421
|
} else if (existsSync4(p.settingsPath)) {
|
|
6699
8422
|
try {
|
|
6700
|
-
const s = JSON.parse(
|
|
8423
|
+
const s = JSON.parse(readFileSync6(p.settingsPath, "utf-8"));
|
|
6701
8424
|
delete s.hooks;
|
|
6702
8425
|
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6703
8426
|
console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
|
|
@@ -6714,12 +8437,12 @@ function escapeRe(s) {
|
|
|
6714
8437
|
function resolveRealClaude() {
|
|
6715
8438
|
try {
|
|
6716
8439
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
6717
|
-
const
|
|
8440
|
+
const out2 = execFileSync2(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
6718
8441
|
if (process.platform === "win32") {
|
|
6719
8442
|
const low = (s) => s.toLowerCase();
|
|
6720
|
-
return
|
|
8443
|
+
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;
|
|
6721
8444
|
}
|
|
6722
|
-
return
|
|
8445
|
+
return out2[0] || null;
|
|
6723
8446
|
} catch {
|
|
6724
8447
|
return null;
|
|
6725
8448
|
}
|
|
@@ -6733,17 +8456,17 @@ function shimTargets() {
|
|
|
6733
8456
|
return [];
|
|
6734
8457
|
}
|
|
6735
8458
|
}
|
|
6736
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) =>
|
|
8459
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join5(homedir3(), f)).filter((f) => existsSync4(f));
|
|
6737
8460
|
}
|
|
6738
8461
|
function writeShimBlock(file, block) {
|
|
6739
8462
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
6740
|
-
let content = existsSync4(file) ?
|
|
8463
|
+
let content = existsSync4(file) ? readFileSync6(file, "utf-8") : "";
|
|
6741
8464
|
content = content.replace(re, "");
|
|
6742
8465
|
if (block) {
|
|
6743
8466
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
6744
8467
|
content += block + "\n";
|
|
6745
8468
|
}
|
|
6746
|
-
|
|
8469
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
6747
8470
|
writeFileSync3(file, content);
|
|
6748
8471
|
}
|
|
6749
8472
|
function installClaudeShim(shieldPath) {
|
|
@@ -6788,7 +8511,7 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6788
8511
|
let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
|
|
6789
8512
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
6790
8513
|
try {
|
|
6791
|
-
const cfg = JSON.parse(
|
|
8514
|
+
const cfg = JSON.parse(readFileSync6(p.configPath, "utf-8"));
|
|
6792
8515
|
if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
|
|
6793
8516
|
} catch {
|
|
6794
8517
|
}
|
|
@@ -6801,20 +8524,20 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6801
8524
|
process.exit(1);
|
|
6802
8525
|
}
|
|
6803
8526
|
const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
6804
|
-
|
|
6805
|
-
|
|
8527
|
+
mkdirSync3(p.hooksDir, { recursive: true });
|
|
8528
|
+
mkdirSync3(p.claudeDir, { recursive: true });
|
|
6806
8529
|
unlockProtected();
|
|
6807
|
-
writeFileSync3(
|
|
6808
|
-
writeFileSync3(
|
|
6809
|
-
writeFileSync3(
|
|
6810
|
-
writeFileSync3(
|
|
8530
|
+
writeFileSync3(join5(p.hooksDir, "guard.mjs"), readGuard());
|
|
8531
|
+
writeFileSync3(join5(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
8532
|
+
writeFileSync3(join5(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
8533
|
+
writeFileSync3(join5(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
6811
8534
|
console.log(` Installed hooks \u2192 ${p.hooksDir}`);
|
|
6812
8535
|
removeClaudeShim();
|
|
6813
8536
|
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
6814
8537
|
console.log(` Wrote ${p.configPath}`);
|
|
6815
8538
|
let existing = {};
|
|
6816
8539
|
if (existsSync4(p.settingsPath)) {
|
|
6817
|
-
const raw =
|
|
8540
|
+
const raw = readFileSync6(p.settingsPath, "utf-8");
|
|
6818
8541
|
if (!existsSync4(p.backupPath)) {
|
|
6819
8542
|
writeFileSync3(p.backupPath, raw);
|
|
6820
8543
|
console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
|
|
@@ -6825,9 +8548,9 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6825
8548
|
existing = {};
|
|
6826
8549
|
}
|
|
6827
8550
|
}
|
|
6828
|
-
const guardAbs =
|
|
6829
|
-
const auditAbs =
|
|
6830
|
-
const stopAbs =
|
|
8551
|
+
const guardAbs = join5(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
|
|
8552
|
+
const auditAbs = join5(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
|
|
8553
|
+
const stopAbs = join5(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
|
|
6831
8554
|
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
6832
8555
|
const merged = {
|
|
6833
8556
|
...existing,
|
|
@@ -6851,8 +8574,8 @@ var __dirname, HOOKS_DIR, SHIM_BEGIN, SHIM_END;
|
|
|
6851
8574
|
var init_global_install = __esm({
|
|
6852
8575
|
"src/global-install.ts"() {
|
|
6853
8576
|
"use strict";
|
|
6854
|
-
__dirname =
|
|
6855
|
-
HOOKS_DIR =
|
|
8577
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
8578
|
+
HOOKS_DIR = resolve4(__dirname, "..", "hooks");
|
|
6856
8579
|
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
6857
8580
|
SHIM_END = "# <<< SolonGate shield <<<";
|
|
6858
8581
|
}
|
|
@@ -7001,8 +8724,8 @@ var init_login = __esm({
|
|
|
7001
8724
|
init_global_install();
|
|
7002
8725
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7003
8726
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7004
|
-
main().catch((
|
|
7005
|
-
console.log(`Fatal: ${
|
|
8727
|
+
main().catch((err2) => {
|
|
8728
|
+
console.log(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
7006
8729
|
process.exit(1);
|
|
7007
8730
|
});
|
|
7008
8731
|
}
|
|
@@ -7017,21 +8740,21 @@ import { createServer, request as httpRequest } from "http";
|
|
|
7017
8740
|
import { request as httpsRequest } from "https";
|
|
7018
8741
|
import { spawn as spawn2 } from "child_process";
|
|
7019
8742
|
import { URL as URL2 } from "url";
|
|
7020
|
-
import { readFileSync as
|
|
7021
|
-
import { resolve as
|
|
7022
|
-
import { homedir as
|
|
8743
|
+
import { readFileSync as readFileSync7, existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
8744
|
+
import { resolve as resolve5 } from "path";
|
|
8745
|
+
import { homedir as homedir4 } from "os";
|
|
7023
8746
|
function findCacheFile() {
|
|
7024
|
-
const dir =
|
|
8747
|
+
const dir = resolve5(homedir4(), ".solongate");
|
|
7025
8748
|
const envSel = process.env.SOLONGATE_AGENT_ID;
|
|
7026
8749
|
if (envSel) {
|
|
7027
|
-
const f =
|
|
8750
|
+
const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
|
|
7028
8751
|
if (existsSync5(f)) return f;
|
|
7029
8752
|
}
|
|
7030
8753
|
let best = null, bestTs = -1;
|
|
7031
8754
|
try {
|
|
7032
8755
|
for (const name of readdirSync(dir)) {
|
|
7033
8756
|
if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
|
|
7034
|
-
const full =
|
|
8757
|
+
const full = resolve5(dir, name);
|
|
7035
8758
|
const ts = statSync(full).mtimeMs;
|
|
7036
8759
|
if (ts > bestTs) {
|
|
7037
8760
|
bestTs = ts;
|
|
@@ -7047,9 +8770,9 @@ function loadCfg() {
|
|
|
7047
8770
|
try {
|
|
7048
8771
|
const f = findCacheFile();
|
|
7049
8772
|
if (f && existsSync5(f)) {
|
|
7050
|
-
const
|
|
7051
|
-
const d =
|
|
7052
|
-
const g =
|
|
8773
|
+
const c2 = JSON.parse(readFileSync7(f, "utf-8"));
|
|
8774
|
+
const d = c2?.security?.dlpRedact;
|
|
8775
|
+
const g = c2?.security?.ghost;
|
|
7053
8776
|
const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
|
|
7054
8777
|
if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [], ghost };
|
|
7055
8778
|
return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost };
|
|
@@ -7061,15 +8784,15 @@ function loadCfg() {
|
|
|
7061
8784
|
function ghostGlobToRegExp(glob) {
|
|
7062
8785
|
let re = "";
|
|
7063
8786
|
for (let i = 0; i < glob.length; i++) {
|
|
7064
|
-
const
|
|
7065
|
-
if (
|
|
8787
|
+
const c2 = glob[i];
|
|
8788
|
+
if (c2 === "*") {
|
|
7066
8789
|
if (glob[i + 1] === "*") {
|
|
7067
8790
|
re += ".*";
|
|
7068
8791
|
i++;
|
|
7069
8792
|
} else re += "[^/]*";
|
|
7070
|
-
} else if (
|
|
7071
|
-
else if ("\\^$.|+()[]{}".indexOf(
|
|
7072
|
-
else re +=
|
|
8793
|
+
} else if (c2 === "?") re += "[^/]";
|
|
8794
|
+
else if ("\\^$.|+()[]{}".indexOf(c2) !== -1) re += "\\" + c2;
|
|
8795
|
+
else re += c2;
|
|
7073
8796
|
}
|
|
7074
8797
|
try {
|
|
7075
8798
|
return new RegExp("^" + re + "$");
|
|
@@ -7160,31 +8883,31 @@ function dlpGlobToRe(glob, flags) {
|
|
|
7160
8883
|
function redactString(s, cfg) {
|
|
7161
8884
|
if (!cfg || typeof s !== "string" || !s) return s;
|
|
7162
8885
|
const allow = new Set(cfg.patterns);
|
|
7163
|
-
let
|
|
7164
|
-
for (const p of DLP_PATTERNS) if (allow.has(p.name))
|
|
7165
|
-
for (const
|
|
8886
|
+
let out2 = s;
|
|
8887
|
+
for (const p of DLP_PATTERNS) if (allow.has(p.name)) out2 = out2.replace(p.re, `[REDACTED: ${p.name}]`);
|
|
8888
|
+
for (const c2 of cfg.custom) {
|
|
7166
8889
|
try {
|
|
7167
|
-
|
|
8890
|
+
out2 = out2.replace(dlpGlobToRe(c2.re, "g"), `[REDACTED: ${c2.name || "custom"}]`);
|
|
7168
8891
|
} catch {
|
|
7169
8892
|
}
|
|
7170
8893
|
}
|
|
7171
|
-
return
|
|
8894
|
+
return out2;
|
|
7172
8895
|
}
|
|
7173
8896
|
function redactDeep(value, cfg) {
|
|
7174
8897
|
const ghost = cfg && Array.isArray(cfg.ghost) ? cfg.ghost : null;
|
|
7175
8898
|
if (typeof value === "string") {
|
|
7176
|
-
let
|
|
7177
|
-
if (ghost && ghost.length)
|
|
7178
|
-
return
|
|
8899
|
+
let out2 = redactString(value, cfg);
|
|
8900
|
+
if (ghost && ghost.length) out2 = ghostStripLines(out2, ghost);
|
|
8901
|
+
return out2;
|
|
7179
8902
|
}
|
|
7180
8903
|
if (Array.isArray(value)) {
|
|
7181
8904
|
const arr = ghost && ghost.length ? value.filter((v) => !(typeof v === "string" && ghostMatch(v.trim(), ghost))) : value;
|
|
7182
8905
|
return arr.map((v) => redactDeep(v, cfg));
|
|
7183
8906
|
}
|
|
7184
8907
|
if (value && typeof value === "object") {
|
|
7185
|
-
const
|
|
7186
|
-
for (const [k, v] of Object.entries(value))
|
|
7187
|
-
return
|
|
8908
|
+
const out2 = {};
|
|
8909
|
+
for (const [k, v] of Object.entries(value)) out2[k] = redactDeep(v, cfg);
|
|
8910
|
+
return out2;
|
|
7188
8911
|
}
|
|
7189
8912
|
return value;
|
|
7190
8913
|
}
|
|
@@ -7201,7 +8924,7 @@ function startProxy(upstream) {
|
|
|
7201
8924
|
const forward = upstream.protocol === "https:" ? httpsRequest : httpRequest;
|
|
7202
8925
|
const server = createServer((req, res) => {
|
|
7203
8926
|
const chunks = [];
|
|
7204
|
-
req.on("data", (
|
|
8927
|
+
req.on("data", (c2) => chunks.push(c2));
|
|
7205
8928
|
req.on("end", () => {
|
|
7206
8929
|
let body = Buffer.concat(chunks);
|
|
7207
8930
|
try {
|
|
@@ -7323,9 +9046,9 @@ __export(logs_server_exports, {
|
|
|
7323
9046
|
runLogsServer: () => runLogsServer
|
|
7324
9047
|
});
|
|
7325
9048
|
import { createServer as createServer2 } from "http";
|
|
7326
|
-
import { readFileSync as
|
|
7327
|
-
import { resolve as
|
|
7328
|
-
import { homedir as
|
|
9049
|
+
import { readFileSync as readFileSync8, statSync as statSync2 } from "fs";
|
|
9050
|
+
import { resolve as resolve6, join as join6, isAbsolute } from "path";
|
|
9051
|
+
import { homedir as homedir5 } from "os";
|
|
7329
9052
|
import { readdirSync as readdirSync2 } from "fs";
|
|
7330
9053
|
function allowedOrigins() {
|
|
7331
9054
|
const base = [
|
|
@@ -7342,16 +9065,16 @@ function resolveLocalLogDir(rawPath) {
|
|
|
7342
9065
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
7343
9066
|
if (!dir) return null;
|
|
7344
9067
|
if (isAbsolute(dir)) return dir;
|
|
7345
|
-
return
|
|
9068
|
+
return resolve6(homedir5(), ".solongate", "local-logs");
|
|
7346
9069
|
}
|
|
7347
9070
|
async function findLogDir() {
|
|
7348
|
-
const base =
|
|
9071
|
+
const base = resolve6(homedir5(), ".solongate");
|
|
7349
9072
|
try {
|
|
7350
9073
|
const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
7351
9074
|
for (const f of files) {
|
|
7352
9075
|
try {
|
|
7353
|
-
const
|
|
7354
|
-
const p =
|
|
9076
|
+
const c2 = JSON.parse(readFileSync8(join6(base, f), "utf-8"));
|
|
9077
|
+
const p = c2?.security?.localLogs?.path;
|
|
7355
9078
|
if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
|
|
7356
9079
|
} catch {
|
|
7357
9080
|
}
|
|
@@ -7359,7 +9082,7 @@ async function findLogDir() {
|
|
|
7359
9082
|
} catch {
|
|
7360
9083
|
}
|
|
7361
9084
|
try {
|
|
7362
|
-
const cfgRaw =
|
|
9085
|
+
const cfgRaw = readFileSync8(join6(base, "cloud-guard.json"), "utf-8");
|
|
7363
9086
|
const { apiKey, apiUrl } = JSON.parse(cfgRaw);
|
|
7364
9087
|
if (apiKey) {
|
|
7365
9088
|
const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
|
|
@@ -7388,7 +9111,7 @@ function setCors(req, res) {
|
|
|
7388
9111
|
}
|
|
7389
9112
|
function fileInfo(dir) {
|
|
7390
9113
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
7391
|
-
const file =
|
|
9114
|
+
const file = join6(dir, LOG_FILENAME);
|
|
7392
9115
|
try {
|
|
7393
9116
|
const st = statSync2(file);
|
|
7394
9117
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
@@ -7449,7 +9172,7 @@ async function runLogsServer() {
|
|
|
7449
9172
|
return;
|
|
7450
9173
|
}
|
|
7451
9174
|
try {
|
|
7452
|
-
const text =
|
|
9175
|
+
const text = readFileSync8(info.file, "utf-8");
|
|
7453
9176
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
|
|
7454
9177
|
res.end(text);
|
|
7455
9178
|
} catch {
|
|
@@ -7479,12 +9202,12 @@ async function runLogsServer() {
|
|
|
7479
9202
|
process.stdout.write(`[SolonGate] Keep this running; the dashboard reads your logs live from here. Ctrl+C to stop.
|
|
7480
9203
|
`);
|
|
7481
9204
|
});
|
|
7482
|
-
server.on("error", (
|
|
7483
|
-
if (
|
|
9205
|
+
server.on("error", (err2) => {
|
|
9206
|
+
if (err2.code === "EADDRINUSE") {
|
|
7484
9207
|
process.stderr.write(`[SolonGate] Port ${port} is already in use. Pass --port <n> or set SOLONGATE_LOGS_PORT.
|
|
7485
9208
|
`);
|
|
7486
9209
|
} else {
|
|
7487
|
-
process.stderr.write(`[SolonGate] Local logs agent error: ${
|
|
9210
|
+
process.stderr.write(`[SolonGate] Local logs agent error: ${err2.message}
|
|
7488
9211
|
`);
|
|
7489
9212
|
}
|
|
7490
9213
|
process.exit(1);
|
|
@@ -7501,8 +9224,8 @@ var init_logs_server = __esm({
|
|
|
7501
9224
|
|
|
7502
9225
|
// src/inject.ts
|
|
7503
9226
|
var inject_exports = {};
|
|
7504
|
-
import { readFileSync as
|
|
7505
|
-
import { resolve as
|
|
9227
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
|
|
9228
|
+
import { resolve as resolve7 } from "path";
|
|
7506
9229
|
import { execSync } from "child_process";
|
|
7507
9230
|
function parseInjectArgs(argv) {
|
|
7508
9231
|
const args = argv.slice(2);
|
|
@@ -7559,9 +9282,9 @@ WHAT IT DOES
|
|
|
7559
9282
|
`);
|
|
7560
9283
|
}
|
|
7561
9284
|
function detectProject() {
|
|
7562
|
-
if (!
|
|
9285
|
+
if (!existsSync6(resolve7("package.json"))) return false;
|
|
7563
9286
|
try {
|
|
7564
|
-
const pkg = JSON.parse(
|
|
9287
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7565
9288
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7566
9289
|
return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
|
|
7567
9290
|
} catch {
|
|
@@ -7570,18 +9293,18 @@ function detectProject() {
|
|
|
7570
9293
|
}
|
|
7571
9294
|
function findTsEntryFile() {
|
|
7572
9295
|
try {
|
|
7573
|
-
const pkg = JSON.parse(
|
|
9296
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7574
9297
|
if (pkg.bin) {
|
|
7575
9298
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
7576
9299
|
if (typeof binPath === "string") {
|
|
7577
9300
|
const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
7578
|
-
if (
|
|
7579
|
-
if (
|
|
9301
|
+
if (existsSync6(resolve7(srcPath))) return resolve7(srcPath);
|
|
9302
|
+
if (existsSync6(resolve7(binPath))) return resolve7(binPath);
|
|
7580
9303
|
}
|
|
7581
9304
|
}
|
|
7582
9305
|
if (pkg.main) {
|
|
7583
9306
|
const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
7584
|
-
if (
|
|
9307
|
+
if (existsSync6(resolve7(srcPath))) return resolve7(srcPath);
|
|
7585
9308
|
}
|
|
7586
9309
|
} catch {
|
|
7587
9310
|
}
|
|
@@ -7593,11 +9316,11 @@ function findTsEntryFile() {
|
|
|
7593
9316
|
"server.ts",
|
|
7594
9317
|
"main.ts"
|
|
7595
9318
|
];
|
|
7596
|
-
for (const
|
|
7597
|
-
const full =
|
|
7598
|
-
if (
|
|
9319
|
+
for (const c2 of candidates) {
|
|
9320
|
+
const full = resolve7(c2);
|
|
9321
|
+
if (existsSync6(full)) {
|
|
7599
9322
|
try {
|
|
7600
|
-
const content =
|
|
9323
|
+
const content = readFileSync9(full, "utf-8");
|
|
7601
9324
|
if (content.includes("McpServer") || content.includes("McpServer")) {
|
|
7602
9325
|
return full;
|
|
7603
9326
|
}
|
|
@@ -7605,19 +9328,19 @@ function findTsEntryFile() {
|
|
|
7605
9328
|
}
|
|
7606
9329
|
}
|
|
7607
9330
|
}
|
|
7608
|
-
for (const
|
|
7609
|
-
if (
|
|
9331
|
+
for (const c2 of candidates) {
|
|
9332
|
+
if (existsSync6(resolve7(c2))) return resolve7(c2);
|
|
7610
9333
|
}
|
|
7611
9334
|
return null;
|
|
7612
9335
|
}
|
|
7613
9336
|
function detectPackageManager() {
|
|
7614
|
-
if (
|
|
7615
|
-
if (
|
|
9337
|
+
if (existsSync6(resolve7("pnpm-lock.yaml"))) return "pnpm";
|
|
9338
|
+
if (existsSync6(resolve7("yarn.lock"))) return "yarn";
|
|
7616
9339
|
return "npm";
|
|
7617
9340
|
}
|
|
7618
9341
|
function installSdk() {
|
|
7619
9342
|
try {
|
|
7620
|
-
const pkg = JSON.parse(
|
|
9343
|
+
const pkg = JSON.parse(readFileSync9(resolve7("package.json"), "utf-8"));
|
|
7621
9344
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7622
9345
|
if (allDeps["@solongate/proxy"]) {
|
|
7623
9346
|
log3(" @solongate/proxy already installed");
|
|
@@ -7631,14 +9354,14 @@ function installSdk() {
|
|
|
7631
9354
|
try {
|
|
7632
9355
|
execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
7633
9356
|
return true;
|
|
7634
|
-
} catch (
|
|
7635
|
-
log3(` Failed to install: ${
|
|
9357
|
+
} catch (err2) {
|
|
9358
|
+
log3(` Failed to install: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
7636
9359
|
log3(" You can install manually: npm install @solongate/proxy");
|
|
7637
9360
|
return false;
|
|
7638
9361
|
}
|
|
7639
9362
|
}
|
|
7640
9363
|
function injectTypeScript(filePath) {
|
|
7641
|
-
const original =
|
|
9364
|
+
const original = readFileSync9(filePath, "utf-8");
|
|
7642
9365
|
const changes = [];
|
|
7643
9366
|
let modified = original;
|
|
7644
9367
|
if (modified.includes("SecureMcpServer")) {
|
|
@@ -7754,8 +9477,8 @@ async function main2() {
|
|
|
7754
9477
|
process.exit(1);
|
|
7755
9478
|
}
|
|
7756
9479
|
log3(" Language: TypeScript");
|
|
7757
|
-
const entryFile = opts.file ?
|
|
7758
|
-
if (!entryFile || !
|
|
9480
|
+
const entryFile = opts.file ? resolve7(opts.file) : findTsEntryFile();
|
|
9481
|
+
if (!entryFile || !existsSync6(entryFile)) {
|
|
7759
9482
|
log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
|
|
7760
9483
|
log3("");
|
|
7761
9484
|
log3(" Specify it manually: --file <path>");
|
|
@@ -7768,7 +9491,7 @@ async function main2() {
|
|
|
7768
9491
|
log3("");
|
|
7769
9492
|
const backupPath = entryFile + ".solongate-backup";
|
|
7770
9493
|
if (opts.restore) {
|
|
7771
|
-
if (!
|
|
9494
|
+
if (!existsSync6(backupPath)) {
|
|
7772
9495
|
log3(" No backup found. Nothing to restore.");
|
|
7773
9496
|
process.exit(1);
|
|
7774
9497
|
}
|
|
@@ -7804,7 +9527,7 @@ async function main2() {
|
|
|
7804
9527
|
log3(" To apply: npx @solongate/proxy inject");
|
|
7805
9528
|
process.exit(0);
|
|
7806
9529
|
}
|
|
7807
|
-
if (!
|
|
9530
|
+
if (!existsSync6(backupPath)) {
|
|
7808
9531
|
copyFileSync(entryFile, backupPath);
|
|
7809
9532
|
log3("");
|
|
7810
9533
|
log3(` Backup: ${backupPath}`);
|
|
@@ -7829,8 +9552,8 @@ var init_inject = __esm({
|
|
|
7829
9552
|
"src/inject.ts"() {
|
|
7830
9553
|
"use strict";
|
|
7831
9554
|
init_cli_utils();
|
|
7832
|
-
main2().catch((
|
|
7833
|
-
log3(`Fatal: ${
|
|
9555
|
+
main2().catch((err2) => {
|
|
9556
|
+
log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
7834
9557
|
process.exit(1);
|
|
7835
9558
|
});
|
|
7836
9559
|
}
|
|
@@ -7838,8 +9561,8 @@ var init_inject = __esm({
|
|
|
7838
9561
|
|
|
7839
9562
|
// src/create.ts
|
|
7840
9563
|
var create_exports = {};
|
|
7841
|
-
import { mkdirSync as
|
|
7842
|
-
import { resolve as
|
|
9564
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
9565
|
+
import { resolve as resolve8, join as join7 } from "path";
|
|
7843
9566
|
import { execSync as execSync2 } from "child_process";
|
|
7844
9567
|
function withSpinner(message, fn) {
|
|
7845
9568
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -7854,11 +9577,11 @@ function withSpinner(message, fn) {
|
|
|
7854
9577
|
process.stderr.write(`\r ${c.green}\u2713${c.reset} ${message}${" ".repeat(20)}
|
|
7855
9578
|
`);
|
|
7856
9579
|
return result;
|
|
7857
|
-
} catch (
|
|
9580
|
+
} catch (err2) {
|
|
7858
9581
|
clearInterval(id);
|
|
7859
9582
|
process.stderr.write(`\r ${c.red}\u2717${c.reset} ${message} \u2014 failed${" ".repeat(10)}
|
|
7860
9583
|
`);
|
|
7861
|
-
throw
|
|
9584
|
+
throw err2;
|
|
7862
9585
|
}
|
|
7863
9586
|
}
|
|
7864
9587
|
function parseCreateArgs(argv) {
|
|
@@ -7919,7 +9642,7 @@ EXAMPLES
|
|
|
7919
9642
|
}
|
|
7920
9643
|
function createProject(dir, name, _policy) {
|
|
7921
9644
|
writeFileSync5(
|
|
7922
|
-
|
|
9645
|
+
join7(dir, "package.json"),
|
|
7923
9646
|
JSON.stringify(
|
|
7924
9647
|
{
|
|
7925
9648
|
name,
|
|
@@ -7949,7 +9672,7 @@ function createProject(dir, name, _policy) {
|
|
|
7949
9672
|
) + "\n"
|
|
7950
9673
|
);
|
|
7951
9674
|
writeFileSync5(
|
|
7952
|
-
|
|
9675
|
+
join7(dir, "tsconfig.json"),
|
|
7953
9676
|
JSON.stringify(
|
|
7954
9677
|
{
|
|
7955
9678
|
compilerOptions: {
|
|
@@ -7969,9 +9692,9 @@ function createProject(dir, name, _policy) {
|
|
|
7969
9692
|
2
|
|
7970
9693
|
) + "\n"
|
|
7971
9694
|
);
|
|
7972
|
-
|
|
9695
|
+
mkdirSync4(join7(dir, "src"), { recursive: true });
|
|
7973
9696
|
writeFileSync5(
|
|
7974
|
-
|
|
9697
|
+
join7(dir, "src", "index.ts"),
|
|
7975
9698
|
`#!/usr/bin/env node
|
|
7976
9699
|
|
|
7977
9700
|
console.log = (...args: unknown[]) => {
|
|
@@ -8013,7 +9736,7 @@ console.log('Press Ctrl+C to stop.');
|
|
|
8013
9736
|
`
|
|
8014
9737
|
);
|
|
8015
9738
|
writeFileSync5(
|
|
8016
|
-
|
|
9739
|
+
join7(dir, ".mcp.json"),
|
|
8017
9740
|
JSON.stringify(
|
|
8018
9741
|
{
|
|
8019
9742
|
mcpServers: {
|
|
@@ -8031,12 +9754,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
8031
9754
|
) + "\n"
|
|
8032
9755
|
);
|
|
8033
9756
|
writeFileSync5(
|
|
8034
|
-
|
|
9757
|
+
join7(dir, ".env"),
|
|
8035
9758
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
8036
9759
|
`
|
|
8037
9760
|
);
|
|
8038
9761
|
writeFileSync5(
|
|
8039
|
-
|
|
9762
|
+
join7(dir, ".gitignore"),
|
|
8040
9763
|
`node_modules/
|
|
8041
9764
|
dist/
|
|
8042
9765
|
*.solongate-backup
|
|
@@ -8048,14 +9771,14 @@ dist/
|
|
|
8048
9771
|
}
|
|
8049
9772
|
async function main3() {
|
|
8050
9773
|
const opts = parseCreateArgs(process.argv);
|
|
8051
|
-
const dir =
|
|
9774
|
+
const dir = resolve8(opts.name);
|
|
8052
9775
|
printBanner("Create MCP Server");
|
|
8053
|
-
if (
|
|
9776
|
+
if (existsSync7(dir)) {
|
|
8054
9777
|
log3(` ${c.red}Error:${c.reset} Directory "${opts.name}" already exists.`);
|
|
8055
9778
|
process.exit(1);
|
|
8056
9779
|
}
|
|
8057
9780
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
8058
|
-
|
|
9781
|
+
mkdirSync4(dir, { recursive: true });
|
|
8059
9782
|
createProject(dir, opts.name, opts.policy);
|
|
8060
9783
|
});
|
|
8061
9784
|
if (!opts.noInstall) {
|
|
@@ -8120,8 +9843,8 @@ var init_create = __esm({
|
|
|
8120
9843
|
"src/create.ts"() {
|
|
8121
9844
|
"use strict";
|
|
8122
9845
|
init_cli_utils();
|
|
8123
|
-
main3().catch((
|
|
8124
|
-
log3(`Fatal: ${
|
|
9846
|
+
main3().catch((err2) => {
|
|
9847
|
+
log3(`Fatal: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
8125
9848
|
process.exit(1);
|
|
8126
9849
|
});
|
|
8127
9850
|
}
|
|
@@ -8129,14 +9852,14 @@ var init_create = __esm({
|
|
|
8129
9852
|
|
|
8130
9853
|
// src/pull-push.ts
|
|
8131
9854
|
var pull_push_exports = {};
|
|
8132
|
-
import { readFileSync as
|
|
8133
|
-
import { resolve as
|
|
9855
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
9856
|
+
import { resolve as resolve9 } from "path";
|
|
8134
9857
|
function loadEnv() {
|
|
8135
9858
|
if (process.env.SOLONGATE_API_KEY) return;
|
|
8136
|
-
const envPath =
|
|
8137
|
-
if (!
|
|
9859
|
+
const envPath = resolve9(".env");
|
|
9860
|
+
if (!existsSync8(envPath)) return;
|
|
8138
9861
|
try {
|
|
8139
|
-
const content =
|
|
9862
|
+
const content = readFileSync10(envPath, "utf-8");
|
|
8140
9863
|
for (const line of content.split("\n")) {
|
|
8141
9864
|
const trimmed = line.trim();
|
|
8142
9865
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -8178,7 +9901,7 @@ function parseCliArgs() {
|
|
|
8178
9901
|
}
|
|
8179
9902
|
}
|
|
8180
9903
|
if (!apiKey) {
|
|
8181
|
-
log5(
|
|
9904
|
+
log5(red2("ERROR: API key not found."));
|
|
8182
9905
|
log5("");
|
|
8183
9906
|
log5("Set it in .env file:");
|
|
8184
9907
|
log5(" SOLONGATE_API_KEY=sg_live_...");
|
|
@@ -8188,10 +9911,10 @@ function parseCliArgs() {
|
|
|
8188
9911
|
process.exit(1);
|
|
8189
9912
|
}
|
|
8190
9913
|
if (!apiKey.startsWith("sg_live_")) {
|
|
8191
|
-
log5(
|
|
9914
|
+
log5(red2("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
8192
9915
|
process.exit(1);
|
|
8193
9916
|
}
|
|
8194
|
-
return { command, apiKey, file:
|
|
9917
|
+
return { command, apiKey, file: resolve9(file), policyId };
|
|
8195
9918
|
}
|
|
8196
9919
|
async function listPolicies(apiKey) {
|
|
8197
9920
|
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
@@ -8201,21 +9924,21 @@ async function listPolicies(apiKey) {
|
|
|
8201
9924
|
const data = await res.json();
|
|
8202
9925
|
return data.policies ?? [];
|
|
8203
9926
|
}
|
|
8204
|
-
async function
|
|
9927
|
+
async function list3(apiKey, policyId) {
|
|
8205
9928
|
const policies = await listPolicies(apiKey);
|
|
8206
9929
|
if (policies.length === 0) {
|
|
8207
|
-
log5(
|
|
8208
|
-
log5(
|
|
9930
|
+
log5(yellow2("No policies found. Create one in the dashboard first."));
|
|
9931
|
+
log5(dim2(" https://dashboard.solongate.com/policies"));
|
|
8209
9932
|
return;
|
|
8210
9933
|
}
|
|
8211
9934
|
if (policyId) {
|
|
8212
9935
|
const match = policies.find((p) => p.id === policyId);
|
|
8213
9936
|
if (!match) {
|
|
8214
|
-
log5(
|
|
9937
|
+
log5(red2(`Policy not found: ${policyId}`));
|
|
8215
9938
|
log5("");
|
|
8216
9939
|
log5("Available policies:");
|
|
8217
9940
|
for (const p of policies) {
|
|
8218
|
-
log5(` ${
|
|
9941
|
+
log5(` ${dim2("\u2022")} ${p.id}`);
|
|
8219
9942
|
}
|
|
8220
9943
|
process.exit(1);
|
|
8221
9944
|
}
|
|
@@ -8224,8 +9947,8 @@ async function list(apiKey, policyId) {
|
|
|
8224
9947
|
return;
|
|
8225
9948
|
}
|
|
8226
9949
|
log5("");
|
|
8227
|
-
log5(
|
|
8228
|
-
log5(
|
|
9950
|
+
log5(bold2(` Policies (${policies.length})`));
|
|
9951
|
+
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"));
|
|
8229
9952
|
log5("");
|
|
8230
9953
|
const fullPolicies = await Promise.all(
|
|
8231
9954
|
policies.map(
|
|
@@ -8233,115 +9956,115 @@ async function list(apiKey, policyId) {
|
|
|
8233
9956
|
)
|
|
8234
9957
|
);
|
|
8235
9958
|
for (const { policy, rules } of fullPolicies) {
|
|
8236
|
-
printPolicySummary(policy, rules);
|
|
9959
|
+
printPolicySummary(policy, [...rules]);
|
|
8237
9960
|
}
|
|
8238
|
-
log5(
|
|
9961
|
+
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"));
|
|
8239
9962
|
log5("");
|
|
8240
|
-
log5(` ${
|
|
8241
|
-
log5(` ${
|
|
8242
|
-
log5(` ${
|
|
9963
|
+
log5(` ${dim2("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
9964
|
+
log5(` ${dim2("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
9965
|
+
log5(` ${dim2("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
8243
9966
|
log5("");
|
|
8244
9967
|
}
|
|
8245
9968
|
function printPolicySummary(p, rules) {
|
|
8246
9969
|
const ruleCount = rules.length;
|
|
8247
9970
|
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
8248
9971
|
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
8249
|
-
log5(` ${
|
|
8250
|
-
log5(` ${
|
|
8251
|
-
log5(` ${
|
|
9972
|
+
log5(` ${cyan2(p.id)}`);
|
|
9973
|
+
log5(` ${bold2(p.name)} ${dim2(`v${p.version ?? "?"}`)}`);
|
|
9974
|
+
log5(` ${dim2("Rules:")} ${ruleCount} ${green2(`${allowCount} ALLOW`)} ${red2(`${denyCount} DENY`)}`);
|
|
8252
9975
|
if (p.created_at) {
|
|
8253
|
-
log5(` ${
|
|
9976
|
+
log5(` ${dim2("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
8254
9977
|
}
|
|
8255
9978
|
log5("");
|
|
8256
9979
|
}
|
|
8257
9980
|
function printPolicyDetail(policy) {
|
|
8258
9981
|
log5("");
|
|
8259
|
-
log5(
|
|
8260
|
-
log5(` ${
|
|
9982
|
+
log5(bold2(` ${policy.name}`));
|
|
9983
|
+
log5(` ${dim2("ID:")} ${cyan2(policy.id)} ${dim2("Version:")} ${policy.version} ${dim2("Rules:")} ${policy.rules.length}`);
|
|
8261
9984
|
log5("");
|
|
8262
9985
|
if (policy.rules.length === 0) {
|
|
8263
|
-
log5(
|
|
9986
|
+
log5(yellow2(" No rules defined."));
|
|
8264
9987
|
log5("");
|
|
8265
9988
|
return;
|
|
8266
9989
|
}
|
|
8267
|
-
log5(
|
|
9990
|
+
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"));
|
|
8268
9991
|
for (const rule of policy.rules) {
|
|
8269
|
-
const effectColor = rule.effect === "ALLOW" ?
|
|
9992
|
+
const effectColor = rule.effect === "ALLOW" ? green2 : red2;
|
|
8270
9993
|
log5("");
|
|
8271
|
-
log5(` ${effectColor(rule.effect.padEnd(5))} ${
|
|
9994
|
+
log5(` ${effectColor(rule.effect.padEnd(5))} ${bold2(rule.toolPattern)} ${dim2(`P:${rule.priority}`)}`);
|
|
8272
9995
|
if (rule.description) {
|
|
8273
|
-
log5(` ${
|
|
9996
|
+
log5(` ${dim2(rule.description)}`);
|
|
8274
9997
|
}
|
|
8275
|
-
log5(` ${
|
|
9998
|
+
log5(` ${dim2(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
8276
9999
|
if (rule.pathConstraints) {
|
|
8277
10000
|
const pc = rule.pathConstraints;
|
|
8278
10001
|
if (pc.rootDirectory) log5(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
8279
|
-
if (pc.allowed?.length) log5(` ${
|
|
8280
|
-
if (pc.denied?.length) log5(` ${
|
|
10002
|
+
if (pc.allowed?.length) log5(` ${green2("PATHS")} ${pc.allowed.join(", ")}`);
|
|
10003
|
+
if (pc.denied?.length) log5(` ${red2("DENY")} ${pc.denied.join(", ")}`);
|
|
8281
10004
|
}
|
|
8282
10005
|
if (rule.commandConstraints) {
|
|
8283
10006
|
const cc = rule.commandConstraints;
|
|
8284
|
-
if (cc.allowed?.length) log5(` ${
|
|
8285
|
-
if (cc.denied?.length) log5(` ${
|
|
10007
|
+
if (cc.allowed?.length) log5(` ${green2("CMDS")} ${cc.allowed.join(", ")}`);
|
|
10008
|
+
if (cc.denied?.length) log5(` ${red2("DENY")} ${cc.denied.join(", ")}`);
|
|
8286
10009
|
}
|
|
8287
10010
|
if (rule.filenameConstraints) {
|
|
8288
10011
|
const fc = rule.filenameConstraints;
|
|
8289
|
-
if (fc.allowed?.length) log5(` ${
|
|
8290
|
-
if (fc.denied?.length) log5(` ${
|
|
10012
|
+
if (fc.allowed?.length) log5(` ${green2("FILES")} ${fc.allowed.join(", ")}`);
|
|
10013
|
+
if (fc.denied?.length) log5(` ${red2("DENY")} ${fc.denied.join(", ")}`);
|
|
8291
10014
|
}
|
|
8292
10015
|
if (rule.urlConstraints) {
|
|
8293
10016
|
const uc = rule.urlConstraints;
|
|
8294
|
-
if (uc.allowed?.length) log5(` ${
|
|
8295
|
-
if (uc.denied?.length) log5(` ${
|
|
10017
|
+
if (uc.allowed?.length) log5(` ${green2("URLS")} ${uc.allowed.join(", ")}`);
|
|
10018
|
+
if (uc.denied?.length) log5(` ${red2("DENY")} ${uc.denied.join(", ")}`);
|
|
8296
10019
|
}
|
|
8297
10020
|
}
|
|
8298
10021
|
log5("");
|
|
8299
|
-
log5(
|
|
10022
|
+
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"));
|
|
8300
10023
|
log5("");
|
|
8301
10024
|
}
|
|
8302
10025
|
async function pull(apiKey, file, policyId) {
|
|
8303
10026
|
if (!policyId) {
|
|
8304
10027
|
const policies = await listPolicies(apiKey);
|
|
8305
10028
|
if (policies.length === 0) {
|
|
8306
|
-
log5(
|
|
10029
|
+
log5(red2("No policies found. Create one in the dashboard first."));
|
|
8307
10030
|
process.exit(1);
|
|
8308
10031
|
}
|
|
8309
10032
|
if (policies.length === 1) {
|
|
8310
10033
|
policyId = policies[0].id;
|
|
8311
|
-
log5(
|
|
10034
|
+
log5(dim2(`Auto-selecting only policy: ${policyId}`));
|
|
8312
10035
|
} else {
|
|
8313
|
-
log5(
|
|
10036
|
+
log5(yellow2(`Found ${policies.length} policies:`));
|
|
8314
10037
|
log5("");
|
|
8315
10038
|
for (const p of policies) {
|
|
8316
|
-
log5(` ${
|
|
10039
|
+
log5(` ${cyan2(p.id)} ${p.name} ${dim2(`v${p.version ?? "?"}`)}`);
|
|
8317
10040
|
}
|
|
8318
10041
|
log5("");
|
|
8319
10042
|
log5("Use --policy-id <ID> to specify which one to pull.");
|
|
8320
10043
|
process.exit(1);
|
|
8321
10044
|
}
|
|
8322
10045
|
}
|
|
8323
|
-
log5(`Pulling ${
|
|
10046
|
+
log5(`Pulling ${cyan2(policyId)} from dashboard...`);
|
|
8324
10047
|
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
8325
10048
|
const { id: _id, ...policyWithoutId } = policy;
|
|
8326
10049
|
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
8327
10050
|
writeFileSync6(file, json, "utf-8");
|
|
8328
10051
|
log5("");
|
|
8329
|
-
log5(
|
|
8330
|
-
log5(` ${
|
|
8331
|
-
log5(` ${
|
|
8332
|
-
log5(` ${
|
|
10052
|
+
log5(green2(" Saved to: ") + file);
|
|
10053
|
+
log5(` ${dim2("Name:")} ${policy.name}`);
|
|
10054
|
+
log5(` ${dim2("Version:")} ${policy.version}`);
|
|
10055
|
+
log5(` ${dim2("Rules:")} ${policy.rules.length}`);
|
|
8333
10056
|
log5("");
|
|
8334
|
-
log5(
|
|
8335
|
-
log5(
|
|
10057
|
+
log5(dim2("The policy file does not contain an ID."));
|
|
10058
|
+
log5(dim2("Use --policy-id to specify the target when pushing/pulling."));
|
|
8336
10059
|
log5("");
|
|
8337
10060
|
}
|
|
8338
10061
|
async function push(apiKey, file, policyId) {
|
|
8339
|
-
if (!
|
|
8340
|
-
log5(
|
|
10062
|
+
if (!existsSync8(file)) {
|
|
10063
|
+
log5(red2(`ERROR: File not found: ${file}`));
|
|
8341
10064
|
process.exit(1);
|
|
8342
10065
|
}
|
|
8343
10066
|
if (!policyId) {
|
|
8344
|
-
log5(
|
|
10067
|
+
log5(red2("ERROR: --policy-id is required for push."));
|
|
8345
10068
|
log5("");
|
|
8346
10069
|
log5("This determines which cloud policy to update.");
|
|
8347
10070
|
log5("");
|
|
@@ -8353,18 +10076,18 @@ async function push(apiKey, file, policyId) {
|
|
|
8353
10076
|
log5(" solongate-proxy list");
|
|
8354
10077
|
process.exit(1);
|
|
8355
10078
|
}
|
|
8356
|
-
const content =
|
|
10079
|
+
const content = readFileSync10(file, "utf-8");
|
|
8357
10080
|
let policy;
|
|
8358
10081
|
try {
|
|
8359
10082
|
policy = JSON.parse(content);
|
|
8360
10083
|
} catch {
|
|
8361
|
-
log5(
|
|
10084
|
+
log5(red2(`ERROR: Invalid JSON in ${file}`));
|
|
8362
10085
|
process.exit(1);
|
|
8363
10086
|
}
|
|
8364
|
-
log5(`Pushing to ${
|
|
8365
|
-
log5(` ${
|
|
8366
|
-
log5(` ${
|
|
8367
|
-
log5(` ${
|
|
10087
|
+
log5(`Pushing to ${cyan2(policyId)}...`);
|
|
10088
|
+
log5(` ${dim2("File:")} ${file}`);
|
|
10089
|
+
log5(` ${dim2("Name:")} ${policy.name || "Unnamed"}`);
|
|
10090
|
+
log5(` ${dim2("Rules:")} ${(policy.rules || []).length}`);
|
|
8368
10091
|
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
8369
10092
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
8370
10093
|
});
|
|
@@ -8386,14 +10109,14 @@ async function push(apiKey, file, policyId) {
|
|
|
8386
10109
|
});
|
|
8387
10110
|
if (!res.ok) {
|
|
8388
10111
|
const body = await res.text().catch(() => "");
|
|
8389
|
-
log5(
|
|
10112
|
+
log5(red2(`ERROR: Push failed (${res.status}): ${body}`));
|
|
8390
10113
|
process.exit(1);
|
|
8391
10114
|
}
|
|
8392
10115
|
const data = await res.json();
|
|
8393
10116
|
log5("");
|
|
8394
|
-
log5(
|
|
8395
|
-
log5(` ${
|
|
8396
|
-
log5(` ${
|
|
10117
|
+
log5(green2(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
10118
|
+
log5(` ${dim2("Policy ID:")} ${policyId}`);
|
|
10119
|
+
log5(` ${dim2("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
8397
10120
|
log5("");
|
|
8398
10121
|
}
|
|
8399
10122
|
async function main4() {
|
|
@@ -8404,41 +10127,41 @@ async function main4() {
|
|
|
8404
10127
|
} else if (command === "push") {
|
|
8405
10128
|
await push(apiKey, file, policyId);
|
|
8406
10129
|
} else if (command === "list" || command === "ls") {
|
|
8407
|
-
await
|
|
10130
|
+
await list3(apiKey, policyId);
|
|
8408
10131
|
} else {
|
|
8409
|
-
log5(
|
|
10132
|
+
log5(red2(`Unknown command: ${command}`));
|
|
8410
10133
|
log5("");
|
|
8411
|
-
log5(
|
|
10134
|
+
log5(bold2("Usage:"));
|
|
8412
10135
|
log5(" solongate-proxy list List all policies");
|
|
8413
10136
|
log5(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
8414
10137
|
log5(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
8415
10138
|
log5(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
8416
10139
|
log5("");
|
|
8417
|
-
log5(
|
|
10140
|
+
log5(bold2("Flags:"));
|
|
8418
10141
|
log5(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
8419
10142
|
log5(" --file, -f <path> Local file path (default: policy.json)");
|
|
8420
10143
|
log5(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
8421
10144
|
log5("");
|
|
8422
10145
|
process.exit(1);
|
|
8423
10146
|
}
|
|
8424
|
-
} catch (
|
|
8425
|
-
log5(
|
|
10147
|
+
} catch (err2) {
|
|
10148
|
+
log5(red2(`ERROR: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
8426
10149
|
process.exit(1);
|
|
8427
10150
|
}
|
|
8428
10151
|
}
|
|
8429
|
-
var log5,
|
|
10152
|
+
var log5, dim2, bold2, green2, red2, yellow2, cyan2, magenta, API_URL;
|
|
8430
10153
|
var init_pull_push = __esm({
|
|
8431
10154
|
"src/pull-push.ts"() {
|
|
8432
10155
|
"use strict";
|
|
8433
10156
|
init_config();
|
|
8434
10157
|
log5 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
8435
10158
|
`);
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
10159
|
+
dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
10160
|
+
bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
10161
|
+
green2 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
10162
|
+
red2 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
10163
|
+
yellow2 = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
10164
|
+
cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
8442
10165
|
magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
8443
10166
|
API_URL = "https://api.solongate.com";
|
|
8444
10167
|
main4();
|
|
@@ -8467,7 +10190,7 @@ import {
|
|
|
8467
10190
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8468
10191
|
import { createServer as createHttpServer } from "http";
|
|
8469
10192
|
import { resolve as resolve2, join as join3 } from "path";
|
|
8470
|
-
import { mkdirSync as
|
|
10193
|
+
import { mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
8471
10194
|
|
|
8472
10195
|
// src/core/errors.ts
|
|
8473
10196
|
var SolonGateError = class extends Error {
|
|
@@ -9343,12 +11066,12 @@ function looksLikeFilename(s) {
|
|
|
9343
11066
|
}
|
|
9344
11067
|
|
|
9345
11068
|
// src/policy-engine/opa/request-adapter.ts
|
|
9346
|
-
function toOpaInput(
|
|
9347
|
-
const args =
|
|
11069
|
+
function toOpaInput(request2) {
|
|
11070
|
+
const args = request2.arguments ?? {};
|
|
9348
11071
|
return {
|
|
9349
|
-
tool_name:
|
|
9350
|
-
permission:
|
|
9351
|
-
trust_level:
|
|
11072
|
+
tool_name: request2.toolName,
|
|
11073
|
+
permission: request2.requiredPermission ?? "",
|
|
11074
|
+
trust_level: request2.context.trustLevel,
|
|
9352
11075
|
arguments: args,
|
|
9353
11076
|
paths: extractPathArguments(args),
|
|
9354
11077
|
commands: extractCommandArguments(args),
|
|
@@ -9421,12 +11144,12 @@ var OpaEvaluator = class {
|
|
|
9421
11144
|
// Evaluates an execution request against the loaded OPA policy.
|
|
9422
11145
|
// Returns a PolicyDecision matching the same interface as the legacy evaluator,
|
|
9423
11146
|
// ensuring full backward compatibility.
|
|
9424
|
-
evaluate(
|
|
11147
|
+
evaluate(request2) {
|
|
9425
11148
|
if (!this.policy || !this.initialized) {
|
|
9426
11149
|
throw new Error("OPA policy not loaded. Call loadBundle() or loadWasm() first.");
|
|
9427
11150
|
}
|
|
9428
11151
|
const startTime = performance.now();
|
|
9429
|
-
const input = toOpaInput(
|
|
11152
|
+
const input = toOpaInput(request2);
|
|
9430
11153
|
const results = this.policy.evaluate(input);
|
|
9431
11154
|
const endTime = performance.now();
|
|
9432
11155
|
const decision = results?.[0]?.result;
|
|
@@ -9496,11 +11219,11 @@ var PolicyEngine = class {
|
|
|
9496
11219
|
// Never throws for denials - denial is a normal outcome, not an error.
|
|
9497
11220
|
// OPA WASM is the only evaluator. If no WASM bundle is loaded, evaluation
|
|
9498
11221
|
// fails CLOSED (default DENY) — there is no legacy fallback.
|
|
9499
|
-
evaluate(
|
|
11222
|
+
evaluate(request2) {
|
|
9500
11223
|
const startTime = performance.now();
|
|
9501
11224
|
let decision;
|
|
9502
11225
|
if (this.opaEvaluator?.isReady()) {
|
|
9503
|
-
decision = this.opaEvaluator.evaluate(
|
|
11226
|
+
decision = this.opaEvaluator.evaluate(request2);
|
|
9504
11227
|
} else {
|
|
9505
11228
|
decision = {
|
|
9506
11229
|
effect: DEFAULT_POLICY_EFFECT,
|
|
@@ -9513,7 +11236,7 @@ var PolicyEngine = class {
|
|
|
9513
11236
|
const elapsed = performance.now() - startTime;
|
|
9514
11237
|
if (elapsed > this.timeoutMs) {
|
|
9515
11238
|
console.warn(
|
|
9516
|
-
`[SolonGate] Policy evaluation took ${elapsed.toFixed(1)}ms (limit: ${this.timeoutMs}ms) for tool "${
|
|
11239
|
+
`[SolonGate] Policy evaluation took ${elapsed.toFixed(1)}ms (limit: ${this.timeoutMs}ms) for tool "${request2.toolName}"`
|
|
9517
11240
|
);
|
|
9518
11241
|
}
|
|
9519
11242
|
return decision;
|
|
@@ -9677,7 +11400,7 @@ var PolicyStore = class {
|
|
|
9677
11400
|
|
|
9678
11401
|
// src/policy-engine/opa/rego-compiler.ts
|
|
9679
11402
|
import { execFileSync } from "child_process";
|
|
9680
|
-
import { writeFileSync, readFileSync as readFileSync2, mkdirSync
|
|
11403
|
+
import { writeFileSync, readFileSync as readFileSync2, mkdirSync, rmSync } from "fs";
|
|
9681
11404
|
import { join as join2 } from "path";
|
|
9682
11405
|
import { tmpdir } from "os";
|
|
9683
11406
|
import { randomUUID } from "crypto";
|
|
@@ -9776,7 +11499,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9776
11499
|
const requestId = randomUUID2();
|
|
9777
11500
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
9778
11501
|
const context = createSecurityContext({ requestId });
|
|
9779
|
-
const
|
|
11502
|
+
const request2 = {
|
|
9780
11503
|
context,
|
|
9781
11504
|
toolName: params.name,
|
|
9782
11505
|
serverName: "default",
|
|
@@ -9793,7 +11516,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9793
11516
|
if (!toolLimit.allowed) {
|
|
9794
11517
|
const result = {
|
|
9795
11518
|
status: "ERROR",
|
|
9796
|
-
request,
|
|
11519
|
+
request: request2,
|
|
9797
11520
|
error: new RateLimitError(params.name, options.rateLimitPerTool),
|
|
9798
11521
|
timestamp
|
|
9799
11522
|
};
|
|
@@ -9810,7 +11533,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9810
11533
|
if (!globalLimit.allowed) {
|
|
9811
11534
|
const result = {
|
|
9812
11535
|
status: "ERROR",
|
|
9813
|
-
request,
|
|
11536
|
+
request: request2,
|
|
9814
11537
|
error: new RateLimitError("*", options.globalRateLimitPerMinute),
|
|
9815
11538
|
timestamp
|
|
9816
11539
|
};
|
|
@@ -9823,7 +11546,7 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9823
11546
|
if (options.exfiltrationTracker.detectChain(params.name)) {
|
|
9824
11547
|
const result = {
|
|
9825
11548
|
status: "DENIED",
|
|
9826
|
-
request,
|
|
11549
|
+
request: request2,
|
|
9827
11550
|
decision: {
|
|
9828
11551
|
effect: "DENY",
|
|
9829
11552
|
matchedRule: null,
|
|
@@ -9840,11 +11563,11 @@ async function interceptToolCall(params, upstreamCall, options) {
|
|
|
9840
11563
|
}
|
|
9841
11564
|
options.exfiltrationTracker.record(params.name);
|
|
9842
11565
|
}
|
|
9843
|
-
const decision = options.policyEngine.evaluate(
|
|
11566
|
+
const decision = options.policyEngine.evaluate(request2);
|
|
9844
11567
|
if (decision.effect === "DENY") {
|
|
9845
11568
|
const result = {
|
|
9846
11569
|
status: "DENIED",
|
|
9847
|
-
request,
|
|
11570
|
+
request: request2,
|
|
9848
11571
|
decision,
|
|
9849
11572
|
timestamp
|
|
9850
11573
|
};
|
|
@@ -9894,7 +11617,7 @@ ${item.text}`;
|
|
|
9894
11617
|
}
|
|
9895
11618
|
const result = {
|
|
9896
11619
|
status: "ALLOWED",
|
|
9897
|
-
request,
|
|
11620
|
+
request: request2,
|
|
9898
11621
|
decision,
|
|
9899
11622
|
toolResult: finalResult,
|
|
9900
11623
|
durationMs,
|
|
@@ -9905,7 +11628,7 @@ ${item.text}`;
|
|
|
9905
11628
|
} catch (error) {
|
|
9906
11629
|
const result = {
|
|
9907
11630
|
status: "ERROR",
|
|
9908
|
-
request,
|
|
11631
|
+
request: request2,
|
|
9909
11632
|
error: error instanceof Error ? new PolicyDeniedError(params.name, error.message) : new PolicyDeniedError(params.name, "Unknown upstream error"),
|
|
9910
11633
|
timestamp
|
|
9911
11634
|
};
|
|
@@ -10170,8 +11893,8 @@ var ServerVerifier = class {
|
|
|
10170
11893
|
/**
|
|
10171
11894
|
* Validates a complete signed request including timestamp, nonce, and signature.
|
|
10172
11895
|
*/
|
|
10173
|
-
validateSignedRequest(
|
|
10174
|
-
const requestTime = new Date(
|
|
11896
|
+
validateSignedRequest(request2) {
|
|
11897
|
+
const requestTime = new Date(request2.timestamp).getTime();
|
|
10175
11898
|
const now = Date.now();
|
|
10176
11899
|
if (isNaN(requestTime)) {
|
|
10177
11900
|
return { valid: false, reason: "Invalid timestamp" };
|
|
@@ -10182,13 +11905,13 @@ var ServerVerifier = class {
|
|
|
10182
11905
|
if (requestTime > now + 3e4) {
|
|
10183
11906
|
return { valid: false, reason: "Request timestamp in the future" };
|
|
10184
11907
|
}
|
|
10185
|
-
if (this.usedNonces.has(
|
|
11908
|
+
if (this.usedNonces.has(request2.nonce)) {
|
|
10186
11909
|
return { valid: false, reason: "Duplicate nonce (replay detected)" };
|
|
10187
11910
|
}
|
|
10188
|
-
if (!this.verifySignature(
|
|
11911
|
+
if (!this.verifySignature(request2.params, request2.capabilityToken, request2.signature)) {
|
|
10189
11912
|
return { valid: false, reason: "Invalid signature" };
|
|
10190
11913
|
}
|
|
10191
|
-
this.usedNonces.add(
|
|
11914
|
+
this.usedNonces.add(request2.nonce);
|
|
10192
11915
|
return { valid: true };
|
|
10193
11916
|
}
|
|
10194
11917
|
};
|
|
@@ -10450,9 +12173,9 @@ var SolonGate = class {
|
|
|
10450
12173
|
throw new LicenseError("Your subscription is inactive. Renew at https://solongate.com");
|
|
10451
12174
|
}
|
|
10452
12175
|
this.licenseValidated = true;
|
|
10453
|
-
} catch (
|
|
10454
|
-
if (
|
|
10455
|
-
console.warn("[SolonGate] License validation failed (network error), allowing through:",
|
|
12176
|
+
} catch (err2) {
|
|
12177
|
+
if (err2 instanceof LicenseError) throw err2;
|
|
12178
|
+
console.warn("[SolonGate] License validation failed (network error), allowing through:", err2 instanceof Error ? err2.message : String(err2));
|
|
10456
12179
|
this.licenseValidated = true;
|
|
10457
12180
|
}
|
|
10458
12181
|
}
|
|
@@ -10501,10 +12224,10 @@ var SolonGate = class {
|
|
|
10501
12224
|
}
|
|
10502
12225
|
const wasmBytes = new Uint8Array(await res.arrayBuffer());
|
|
10503
12226
|
await this.policyEngine.loadWasmBundle(wasmBytes);
|
|
10504
|
-
} catch (
|
|
12227
|
+
} catch (err2) {
|
|
10505
12228
|
console.warn(
|
|
10506
12229
|
"[SolonGate] Failed to load policy WASM bundle:",
|
|
10507
|
-
|
|
12230
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
10508
12231
|
);
|
|
10509
12232
|
}
|
|
10510
12233
|
}
|
|
@@ -10719,8 +12442,8 @@ var PolicySyncManager = class {
|
|
|
10719
12442
|
this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
|
|
10720
12443
|
});
|
|
10721
12444
|
log(`Watching ${filePath} for changes`);
|
|
10722
|
-
} catch (
|
|
10723
|
-
log(`File watch failed: ${
|
|
12445
|
+
} catch (err2) {
|
|
12446
|
+
log(`File watch failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10724
12447
|
}
|
|
10725
12448
|
}
|
|
10726
12449
|
/**
|
|
@@ -10751,12 +12474,12 @@ var PolicySyncManager = class {
|
|
|
10751
12474
|
const result = await this.pushToCloud(newPolicy);
|
|
10752
12475
|
this.cloudVersion = result.version;
|
|
10753
12476
|
log(`Pushed to cloud: v${result.version}`);
|
|
10754
|
-
} catch (
|
|
10755
|
-
log(`Cloud push failed: ${
|
|
12477
|
+
} catch (err2) {
|
|
12478
|
+
log(`Cloud push failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10756
12479
|
}
|
|
10757
12480
|
}
|
|
10758
|
-
} catch (
|
|
10759
|
-
log(`File read error: ${
|
|
12481
|
+
} catch (err2) {
|
|
12482
|
+
log(`File read error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10760
12483
|
}
|
|
10761
12484
|
}
|
|
10762
12485
|
/**
|
|
@@ -10845,8 +12568,8 @@ var PolicySyncManager = class {
|
|
|
10845
12568
|
const { id: _id, ...rest } = policy;
|
|
10846
12569
|
const json = JSON.stringify(rest, null, 2) + "\n";
|
|
10847
12570
|
writeFileSync2(this.localPath, json, "utf-8");
|
|
10848
|
-
} catch (
|
|
10849
|
-
log(`File write error: ${
|
|
12571
|
+
} catch (err2) {
|
|
12572
|
+
log(`File write error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
10850
12573
|
}
|
|
10851
12574
|
}
|
|
10852
12575
|
/**
|
|
@@ -10870,7 +12593,7 @@ var Mutex = class {
|
|
|
10870
12593
|
this.locked = true;
|
|
10871
12594
|
return;
|
|
10872
12595
|
}
|
|
10873
|
-
return new Promise((
|
|
12596
|
+
return new Promise((resolve10, reject) => {
|
|
10874
12597
|
const timer = setTimeout(() => {
|
|
10875
12598
|
const idx = this.queue.indexOf(onReady);
|
|
10876
12599
|
if (idx !== -1) this.queue.splice(idx, 1);
|
|
@@ -10878,7 +12601,7 @@ var Mutex = class {
|
|
|
10878
12601
|
}, timeoutMs);
|
|
10879
12602
|
const onReady = () => {
|
|
10880
12603
|
clearTimeout(timer);
|
|
10881
|
-
|
|
12604
|
+
resolve10();
|
|
10882
12605
|
};
|
|
10883
12606
|
this.queue.push(onReady);
|
|
10884
12607
|
});
|
|
@@ -10914,8 +12637,6 @@ var SolonGateProxy = class {
|
|
|
10914
12637
|
/** Agent identity for trust map — resolved from CLI flag, HTTP headers, or MCP clientInfo */
|
|
10915
12638
|
agentId = null;
|
|
10916
12639
|
agentName = null;
|
|
10917
|
-
/** Per-session agent info for HTTP mode (keyed by session ID) */
|
|
10918
|
-
httpAgentInfo = /* @__PURE__ */ new Map();
|
|
10919
12640
|
/** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
|
|
10920
12641
|
httpSubAgent = null;
|
|
10921
12642
|
constructor(config) {
|
|
@@ -10949,8 +12670,8 @@ var SolonGateProxy = class {
|
|
|
10949
12670
|
return { id: raw.toLowerCase().replace(/\s+/g, "-"), name: raw };
|
|
10950
12671
|
}
|
|
10951
12672
|
/** Extract sub-agent identity from MCP _meta field */
|
|
10952
|
-
extractSubAgent(
|
|
10953
|
-
const meta =
|
|
12673
|
+
extractSubAgent(request2) {
|
|
12674
|
+
const meta = request2?.params?._meta;
|
|
10954
12675
|
if (meta && typeof meta === "object") {
|
|
10955
12676
|
const solonMeta = meta["io.solongate/agent"];
|
|
10956
12677
|
if (solonMeta && typeof solonMeta === "object") {
|
|
@@ -10994,9 +12715,9 @@ var SolonGateProxy = class {
|
|
|
10994
12715
|
process.exit(1);
|
|
10995
12716
|
}
|
|
10996
12717
|
log2("License validated.");
|
|
10997
|
-
} catch (
|
|
12718
|
+
} catch (err2) {
|
|
10998
12719
|
log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
|
|
10999
|
-
log2(`Details: ${
|
|
12720
|
+
log2(`Details: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
11000
12721
|
process.exit(1);
|
|
11001
12722
|
}
|
|
11002
12723
|
}
|
|
@@ -11005,8 +12726,8 @@ var SolonGateProxy = class {
|
|
|
11005
12726
|
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl, this.config.policyId);
|
|
11006
12727
|
this.config.policy = cloudPolicy;
|
|
11007
12728
|
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
11008
|
-
} catch (
|
|
11009
|
-
log2(`Cloud policy fetch failed, using local policy: ${
|
|
12729
|
+
} catch (err2) {
|
|
12730
|
+
log2(`Cloud policy fetch failed, using local policy: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
11010
12731
|
}
|
|
11011
12732
|
}
|
|
11012
12733
|
}
|
|
@@ -11121,7 +12842,7 @@ var SolonGateProxy = class {
|
|
|
11121
12842
|
pid: process.pid
|
|
11122
12843
|
};
|
|
11123
12844
|
const debugDir = resolve2(".solongate");
|
|
11124
|
-
|
|
12845
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
11125
12846
|
appendFileSync(join3(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
11126
12847
|
} catch {
|
|
11127
12848
|
}
|
|
@@ -11140,9 +12861,9 @@ var SolonGateProxy = class {
|
|
|
11140
12861
|
});
|
|
11141
12862
|
const MAX_ARGUMENT_SIZE = 1024 * 1024;
|
|
11142
12863
|
const MUTEX_TIMEOUT_MS = 3e4;
|
|
11143
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (
|
|
11144
|
-
const { name, arguments: args } =
|
|
11145
|
-
const subAgent = this.extractSubAgent(
|
|
12864
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
12865
|
+
const { name, arguments: args } = request2.params;
|
|
12866
|
+
const subAgent = this.extractSubAgent(request2) || this.httpSubAgent;
|
|
11146
12867
|
const argsSize = TEXT_ENCODER.encode(JSON.stringify(args ?? {})).length;
|
|
11147
12868
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
11148
12869
|
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
@@ -11226,9 +12947,9 @@ var SolonGateProxy = class {
|
|
|
11226
12947
|
return { resources: [] };
|
|
11227
12948
|
}
|
|
11228
12949
|
});
|
|
11229
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async (
|
|
12950
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request2) => {
|
|
11230
12951
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
11231
|
-
const uri =
|
|
12952
|
+
const uri = request2.params.uri;
|
|
11232
12953
|
log2(`Resource read: ${uri}`);
|
|
11233
12954
|
const resourceResult = await this.client.readResource({ uri });
|
|
11234
12955
|
if (resourceResult.contents) {
|
|
@@ -11263,12 +12984,12 @@ ${content.text}`;
|
|
|
11263
12984
|
return { prompts: [] };
|
|
11264
12985
|
}
|
|
11265
12986
|
});
|
|
11266
|
-
this.server.setRequestHandler(GetPromptRequestSchema, async (
|
|
12987
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request2) => {
|
|
11267
12988
|
if (!this.client) throw new Error("Upstream client disconnected");
|
|
11268
|
-
const args =
|
|
11269
|
-
log2(`Prompt get: ${
|
|
12989
|
+
const args = request2.params.arguments;
|
|
12990
|
+
log2(`Prompt get: ${request2.params.name}`);
|
|
11270
12991
|
const promptResult = await this.client.getPrompt({
|
|
11271
|
-
name:
|
|
12992
|
+
name: request2.params.name,
|
|
11272
12993
|
arguments: args
|
|
11273
12994
|
});
|
|
11274
12995
|
if (promptResult.messages) {
|
|
@@ -11277,7 +12998,7 @@ ${content.text}`;
|
|
|
11277
12998
|
const scan = scanResponse(msg.content.text);
|
|
11278
12999
|
if (!scan.safe) {
|
|
11279
13000
|
const threats = scan.threats.map((t) => t.type).join(", ");
|
|
11280
|
-
log2(`WARNING prompt response: ${
|
|
13001
|
+
log2(`WARNING prompt response: ${request2.params.name} \u2014 ${threats}`);
|
|
11281
13002
|
msg.content.text = `${RESPONSE_WARNING_MARKER}
|
|
11282
13003
|
|
|
11283
13004
|
${msg.content.text}`;
|
|
@@ -11383,8 +13104,8 @@ ${msg.content.text}`;
|
|
|
11383
13104
|
const body = await res.text().catch(() => "");
|
|
11384
13105
|
log2(`MCP server registration failed (${res.status}): ${body}`);
|
|
11385
13106
|
}
|
|
11386
|
-
}).catch((
|
|
11387
|
-
log2(`MCP server registration error: ${
|
|
13107
|
+
}).catch((err2) => {
|
|
13108
|
+
log2(`MCP server registration error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
11388
13109
|
});
|
|
11389
13110
|
}
|
|
11390
13111
|
/**
|
|
@@ -11397,33 +13118,6 @@ ${msg.content.text}`;
|
|
|
11397
13118
|
/**
|
|
11398
13119
|
* Extract protected filenames from policy DENY rules (filenameConstraints.denied).
|
|
11399
13120
|
*/
|
|
11400
|
-
extractProtectedFiles() {
|
|
11401
|
-
const files = /* @__PURE__ */ new Set();
|
|
11402
|
-
for (const rule of this.config.policy.rules) {
|
|
11403
|
-
if (rule.effect === "DENY" && rule.enabled !== false) {
|
|
11404
|
-
const denied = rule.filenameConstraints?.denied;
|
|
11405
|
-
if (denied) {
|
|
11406
|
-
for (const f of denied) files.add(f);
|
|
11407
|
-
}
|
|
11408
|
-
}
|
|
11409
|
-
}
|
|
11410
|
-
return [...files];
|
|
11411
|
-
}
|
|
11412
|
-
/**
|
|
11413
|
-
* Extract protected paths from policy DENY rules (pathConstraints.denied).
|
|
11414
|
-
*/
|
|
11415
|
-
extractProtectedPaths() {
|
|
11416
|
-
const paths = /* @__PURE__ */ new Set();
|
|
11417
|
-
for (const rule of this.config.policy.rules) {
|
|
11418
|
-
if (rule.effect === "DENY" && rule.enabled !== false) {
|
|
11419
|
-
const denied = rule.pathConstraints?.denied;
|
|
11420
|
-
if (denied) {
|
|
11421
|
-
for (const p of denied) paths.add(p);
|
|
11422
|
-
}
|
|
11423
|
-
}
|
|
11424
|
-
}
|
|
11425
|
-
return [...paths];
|
|
11426
|
-
}
|
|
11427
13121
|
startPolicySync() {
|
|
11428
13122
|
const apiKey = this.config.apiKey;
|
|
11429
13123
|
if (!apiKey) return;
|
|
@@ -11498,7 +13192,7 @@ ${msg.content.text}`;
|
|
|
11498
13192
|
|
|
11499
13193
|
// src/index.ts
|
|
11500
13194
|
init_cli_utils();
|
|
11501
|
-
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "logout", "shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs"]);
|
|
13195
|
+
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"]);
|
|
11502
13196
|
var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
|
|
11503
13197
|
if (!IS_HUMAN_CLI) {
|
|
11504
13198
|
console.log = (...args) => {
|
|
@@ -11533,9 +13227,26 @@ function printWelcome() {
|
|
|
11533
13227
|
async function main5() {
|
|
11534
13228
|
const subcommand = process.argv[2];
|
|
11535
13229
|
if (process.argv.length <= 2) {
|
|
13230
|
+
const { isAuthenticated: isAuthenticated2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
13231
|
+
if (process.stdout.isTTY && isAuthenticated2()) {
|
|
13232
|
+
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
13233
|
+
await launchTui2();
|
|
13234
|
+
return;
|
|
13235
|
+
}
|
|
11536
13236
|
printWelcome();
|
|
11537
13237
|
return;
|
|
11538
13238
|
}
|
|
13239
|
+
if (subcommand === "ui" || subcommand === "dashboard" || subcommand === "tui") {
|
|
13240
|
+
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
13241
|
+
await launchTui2();
|
|
13242
|
+
return;
|
|
13243
|
+
}
|
|
13244
|
+
const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"]);
|
|
13245
|
+
if (MGMT_COMMANDS.has(subcommand ?? "")) {
|
|
13246
|
+
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
13247
|
+
const code = await runCommand2(subcommand, process.argv.slice(3));
|
|
13248
|
+
process.exit(code);
|
|
13249
|
+
}
|
|
11539
13250
|
if (subcommand === "login") {
|
|
11540
13251
|
await Promise.resolve().then(() => (init_login(), login_exports));
|
|
11541
13252
|
return;
|
|
@@ -11573,8 +13284,8 @@ async function main5() {
|
|
|
11573
13284
|
const config = parseArgs(process.argv);
|
|
11574
13285
|
const proxy = new SolonGateProxy(config);
|
|
11575
13286
|
await proxy.start();
|
|
11576
|
-
} catch (
|
|
11577
|
-
const message =
|
|
13287
|
+
} catch (err2) {
|
|
13288
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
11578
13289
|
process.stderr.write(`[SolonGate] Fatal: ${message}
|
|
11579
13290
|
`);
|
|
11580
13291
|
process.exit(1);
|