@solongate/proxy 0.77.2 → 0.79.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/audit.d.ts +8 -0
- package/dist/api-client/index.d.ts +4 -0
- package/dist/api-client/keys.d.ts +17 -0
- package/dist/api-client/mcp.d.ts +10 -0
- package/dist/api-client/policies.d.ts +1 -0
- package/dist/api-client/settings.d.ts +46 -0
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +421 -2
- package/dist/commands/keys.d.ts +1 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/watch.d.ts +1 -0
- package/dist/commands/webhooks.d.ts +1 -0
- package/dist/index.js +710 -121
- package/dist/tui/index.js +197 -39
- package/package.json +1 -1
|
@@ -20,3 +20,11 @@ export declare function whitelist(id: string, scope?: 'exact' | 'tool'): Promise
|
|
|
20
20
|
policy_version?: number;
|
|
21
21
|
message?: string;
|
|
22
22
|
}>;
|
|
23
|
+
export declare function block(id: string, scope?: 'exact' | 'tool'): Promise<{
|
|
24
|
+
ok: true;
|
|
25
|
+
deduped: boolean;
|
|
26
|
+
scope: string;
|
|
27
|
+
policy_id: string;
|
|
28
|
+
policy_version?: number;
|
|
29
|
+
message?: string;
|
|
30
|
+
}>;
|
|
@@ -9,10 +9,14 @@ import * as settings from './settings.js';
|
|
|
9
9
|
import * as stats from './stats.js';
|
|
10
10
|
import * as audit from './audit.js';
|
|
11
11
|
import * as agents from './agents.js';
|
|
12
|
+
import * as keys from './keys.js';
|
|
13
|
+
import * as mcp from './mcp.js';
|
|
12
14
|
export declare const api: {
|
|
13
15
|
policies: typeof policies;
|
|
14
16
|
settings: typeof settings;
|
|
15
17
|
stats: typeof stats;
|
|
16
18
|
audit: typeof audit;
|
|
17
19
|
agents: typeof agents;
|
|
20
|
+
keys: typeof keys;
|
|
21
|
+
mcp: typeof mcp;
|
|
18
22
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ApiKey {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
key_prefix: string;
|
|
5
|
+
is_live?: boolean;
|
|
6
|
+
created_at: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function list(): Promise<{
|
|
9
|
+
keys: ApiKey[];
|
|
10
|
+
}>;
|
|
11
|
+
export declare function create(name: string, isLive?: boolean): Promise<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
key: string;
|
|
15
|
+
key_prefix: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function revoke(id: string): Promise<unknown>;
|
|
@@ -20,3 +20,49 @@ export interface GuardStatus {
|
|
|
20
20
|
outdated_count: number;
|
|
21
21
|
}
|
|
22
22
|
export declare function getGuardStatus(): Promise<GuardStatus>;
|
|
23
|
+
export interface AlertRule {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
threshold: number;
|
|
28
|
+
windowSeconds: number;
|
|
29
|
+
signal: 'any' | 'deny' | 'dlp' | 'ratelimit';
|
|
30
|
+
slackUrls?: string[];
|
|
31
|
+
emails?: string[];
|
|
32
|
+
telegram?: string[];
|
|
33
|
+
createdAt: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function getAlerts(): Promise<{
|
|
36
|
+
rules: AlertRule[];
|
|
37
|
+
}>;
|
|
38
|
+
export declare function createAlert(body: Partial<AlertRule> & {
|
|
39
|
+
emails?: string[];
|
|
40
|
+
telegram?: string[];
|
|
41
|
+
slackUrl?: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
rule: AlertRule;
|
|
44
|
+
}>;
|
|
45
|
+
export declare function deleteAlert(id: string): Promise<{
|
|
46
|
+
ok: true;
|
|
47
|
+
}>;
|
|
48
|
+
export interface DenialWebhook {
|
|
49
|
+
id: string;
|
|
50
|
+
url: string;
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
events: 'denials' | 'allowed' | 'all';
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
createdAt: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function getWebhooks(): Promise<{
|
|
57
|
+
webhooks: DenialWebhook[];
|
|
58
|
+
}>;
|
|
59
|
+
export declare function createWebhook(body: {
|
|
60
|
+
url: string;
|
|
61
|
+
events?: 'denials' | 'allowed' | 'all';
|
|
62
|
+
headers?: Record<string, string>;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
webhook: DenialWebhook;
|
|
65
|
+
}>;
|
|
66
|
+
export declare function deleteWebhook(id: string): Promise<{
|
|
67
|
+
ok: true;
|
|
68
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function runCommand(command: string, argv: string[]): Promise<number>;
|
|
6
6
|
/** The subcommand names this router owns (used by src/index.ts to route). */
|
|
7
|
-
export declare const COMMAND_NAMES: readonly ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
|
|
7
|
+
export declare const COMMAND_NAMES: readonly ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks"];
|
package/dist/commands/index.js
CHANGED
|
@@ -53,6 +53,14 @@ function dotenvApiKey() {
|
|
|
53
53
|
return void 0;
|
|
54
54
|
}
|
|
55
55
|
var cached = null;
|
|
56
|
+
function isAuthenticated() {
|
|
57
|
+
try {
|
|
58
|
+
resolveCredentials();
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
56
64
|
function resolveCredentials(apiUrlOverride) {
|
|
57
65
|
if (cached && !apiUrlOverride) return cached;
|
|
58
66
|
const file = loginCredentialFile();
|
|
@@ -188,9 +196,15 @@ function dryRun(body) {
|
|
|
188
196
|
var settings_exports = {};
|
|
189
197
|
__export(settings_exports, {
|
|
190
198
|
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
199
|
+
createAlert: () => createAlert,
|
|
200
|
+
createWebhook: () => createWebhook,
|
|
201
|
+
deleteAlert: () => deleteAlert,
|
|
202
|
+
deleteWebhook: () => deleteWebhook,
|
|
203
|
+
getAlerts: () => getAlerts,
|
|
191
204
|
getGuardStatus: () => getGuardStatus,
|
|
192
205
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
193
206
|
getSecurityLayers: () => getSecurityLayers,
|
|
207
|
+
getWebhooks: () => getWebhooks,
|
|
194
208
|
setSecurityLayers: () => setSecurityLayers
|
|
195
209
|
});
|
|
196
210
|
function getSecurityLayers() {
|
|
@@ -208,6 +222,24 @@ function clearRateLimitHistory() {
|
|
|
208
222
|
function getGuardStatus() {
|
|
209
223
|
return request("GET", "/settings/guard-status");
|
|
210
224
|
}
|
|
225
|
+
function getAlerts() {
|
|
226
|
+
return request("GET", "/settings/denial-alerts");
|
|
227
|
+
}
|
|
228
|
+
function createAlert(body) {
|
|
229
|
+
return request("POST", "/settings/denial-alerts", { body });
|
|
230
|
+
}
|
|
231
|
+
function deleteAlert(id) {
|
|
232
|
+
return request("DELETE", "/settings/denial-alerts", { query: { id } });
|
|
233
|
+
}
|
|
234
|
+
function getWebhooks() {
|
|
235
|
+
return request("GET", "/settings/denial-webhook");
|
|
236
|
+
}
|
|
237
|
+
function createWebhook(body) {
|
|
238
|
+
return request("POST", "/settings/denial-webhook", { body });
|
|
239
|
+
}
|
|
240
|
+
function deleteWebhook(id) {
|
|
241
|
+
return request("DELETE", "/settings/denial-webhook", { query: { id } });
|
|
242
|
+
}
|
|
211
243
|
|
|
212
244
|
// src/api-client/stats.ts
|
|
213
245
|
var stats_exports = {};
|
|
@@ -233,6 +265,7 @@ function securityInsights(days) {
|
|
|
233
265
|
// src/api-client/audit.ts
|
|
234
266
|
var audit_exports = {};
|
|
235
267
|
__export(audit_exports, {
|
|
268
|
+
block: () => block,
|
|
236
269
|
list: () => list2,
|
|
237
270
|
whitelist: () => whitelist
|
|
238
271
|
});
|
|
@@ -242,6 +275,9 @@ function list2(query = {}) {
|
|
|
242
275
|
function whitelist(id, scope = "exact") {
|
|
243
276
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
244
277
|
}
|
|
278
|
+
function block(id, scope = "exact") {
|
|
279
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/block`, { body: { scope } });
|
|
280
|
+
}
|
|
245
281
|
|
|
246
282
|
// src/api-client/agents.ts
|
|
247
283
|
var agents_exports = {};
|
|
@@ -264,8 +300,34 @@ function anomalies(id, limit) {
|
|
|
264
300
|
});
|
|
265
301
|
}
|
|
266
302
|
|
|
303
|
+
// src/api-client/keys.ts
|
|
304
|
+
var keys_exports = {};
|
|
305
|
+
__export(keys_exports, {
|
|
306
|
+
create: () => create2,
|
|
307
|
+
list: () => list3,
|
|
308
|
+
revoke: () => revoke
|
|
309
|
+
});
|
|
310
|
+
function list3() {
|
|
311
|
+
return request("GET", "/keys");
|
|
312
|
+
}
|
|
313
|
+
function create2(name, isLive = true) {
|
|
314
|
+
return request("POST", "/keys", { body: { name, is_live: isLive } });
|
|
315
|
+
}
|
|
316
|
+
function revoke(id) {
|
|
317
|
+
return request("DELETE", `/keys/${encodeURIComponent(id)}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/api-client/mcp.ts
|
|
321
|
+
var mcp_exports = {};
|
|
322
|
+
__export(mcp_exports, {
|
|
323
|
+
list: () => list4
|
|
324
|
+
});
|
|
325
|
+
function list4() {
|
|
326
|
+
return request("GET", "/mcp-servers");
|
|
327
|
+
}
|
|
328
|
+
|
|
267
329
|
// src/api-client/index.ts
|
|
268
|
-
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports };
|
|
330
|
+
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
|
|
269
331
|
|
|
270
332
|
// src/cli-utils.ts
|
|
271
333
|
var c = {
|
|
@@ -821,6 +883,16 @@ async function run5(argv) {
|
|
|
821
883
|
else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
|
|
822
884
|
return 0;
|
|
823
885
|
}
|
|
886
|
+
if (positionals[0] === "block") {
|
|
887
|
+
const id = positionals[1];
|
|
888
|
+
if (!id) return err(" Usage: audit block <logId> [--scope exact|tool]"), 1;
|
|
889
|
+
const scope = flagStr(flags, "scope") ?? "exact";
|
|
890
|
+
const res2 = await api.audit.block(id, scope);
|
|
891
|
+
if (json) return printJson(res2), 0;
|
|
892
|
+
if (res2.deduped) err(green(" \u2713 ") + dim("Equivalent DENY already present."));
|
|
893
|
+
else err(green(` \u2713 Blocked (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
|
|
894
|
+
return 0;
|
|
895
|
+
}
|
|
824
896
|
const query = {
|
|
825
897
|
filter: flagStr(flags, "filter"),
|
|
826
898
|
tool: flagStr(flags, "tool"),
|
|
@@ -902,6 +974,341 @@ async function runAgent(argv) {
|
|
|
902
974
|
return 0;
|
|
903
975
|
}
|
|
904
976
|
|
|
977
|
+
// src/commands/doctor.ts
|
|
978
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
979
|
+
import { homedir as homedir2 } from "os";
|
|
980
|
+
import { join as join2 } from "path";
|
|
981
|
+
var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
982
|
+
async function run6(argv) {
|
|
983
|
+
const { flags } = parse(argv);
|
|
984
|
+
const json = flagBool(flags, "json");
|
|
985
|
+
const checks = [];
|
|
986
|
+
if (!isAuthenticated()) {
|
|
987
|
+
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate login`" });
|
|
988
|
+
} else {
|
|
989
|
+
const { apiUrl } = resolveCredentials();
|
|
990
|
+
checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
|
|
991
|
+
try {
|
|
992
|
+
const active2 = await api.policies.active();
|
|
993
|
+
if (active2.policy) {
|
|
994
|
+
checks.push({ name: "active policy", ok: true, detail: `${active2.policy.name} v${active2.version} \xB7 ${active2.policy.mode ?? "denylist"} \xB7 matched by ${active2.matched_by}` });
|
|
995
|
+
} else {
|
|
996
|
+
checks.push({ name: "active policy", ok: "warn", detail: "no policy resolves \u2014 every call falls back to default" });
|
|
997
|
+
}
|
|
998
|
+
const sec = active2.security;
|
|
999
|
+
checks.push({ name: "rate limit", ok: sec?.rateLimit ? true : "warn", detail: sec?.rateLimit ? `${sec.rateLimit.perMinute}/min` : "off" });
|
|
1000
|
+
checks.push({ name: "dlp", ok: sec?.dlpBlock ? true : "warn", detail: sec?.dlpBlock ? `block \xB7 ${sec.dlpBlock.patterns.length} patterns` : sec?.dlpRedact ? "redact" : "off" });
|
|
1001
|
+
checks.push({ name: "self-protection", ok: active2.self_protection_enabled ? true : "warn", detail: active2.self_protection_enabled ? "on" : "off" });
|
|
1002
|
+
} catch (e) {
|
|
1003
|
+
checks.push({ name: "api", ok: false, detail: "unreachable: " + (e instanceof Error ? e.message : String(e)) });
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const g = await api.settings.getGuardStatus();
|
|
1007
|
+
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 run \`solongate login\`` });
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (existsSync2(LOCAL_LOG)) {
|
|
1012
|
+
const st = statSync(LOCAL_LOG);
|
|
1013
|
+
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
1014
|
+
checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
|
|
1015
|
+
} else {
|
|
1016
|
+
checks.push({ name: "local logs", ok: "warn", detail: "off (logs go to cloud) \u2014 enable in dashboard \u2192 Settings" });
|
|
1017
|
+
}
|
|
1018
|
+
if (json) return printJson(checks), checks.some((c2) => c2.ok === false) ? 1 : 0;
|
|
1019
|
+
err("");
|
|
1020
|
+
err(` ${bold("SolonGate doctor")}`);
|
|
1021
|
+
err("");
|
|
1022
|
+
for (const c2 of checks) {
|
|
1023
|
+
const mark = c2.ok === true ? green("\u2713") : c2.ok === "warn" ? yellow("!") : red("\u2717");
|
|
1024
|
+
err(` ${mark} ${c2.name.padEnd(16)} ${dim(c2.detail)}`);
|
|
1025
|
+
}
|
|
1026
|
+
err("");
|
|
1027
|
+
const bad = checks.filter((c2) => c2.ok === false).length;
|
|
1028
|
+
const warn = checks.filter((c2) => c2.ok === "warn").length;
|
|
1029
|
+
if (bad) err(` ${red(`${bad} problem(s)`)}${warn ? dim(` \xB7 ${warn} warning(s)`) : ""}`);
|
|
1030
|
+
else if (warn) err(` ${yellow(`${warn} warning(s)`)} ${dim("\u2014 guard is working")}`);
|
|
1031
|
+
else err(` ${green("all good")}`);
|
|
1032
|
+
return bad ? 1 : 0;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/commands/watch.ts
|
|
1036
|
+
import { closeSync, existsSync as existsSync3, openSync, readSync, statSync as statSync2 } from "fs";
|
|
1037
|
+
import { homedir as homedir3 } from "os";
|
|
1038
|
+
import { join as join3 } from "path";
|
|
1039
|
+
var LOCAL_LOG2 = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
1040
|
+
var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1041
|
+
var time = (ms) => new Date(ms).toTimeString().slice(0, 8);
|
|
1042
|
+
function tailLocal(file, maxBytes = 131072) {
|
|
1043
|
+
try {
|
|
1044
|
+
const size = statSync2(file).size;
|
|
1045
|
+
const start = Math.max(0, size - maxBytes);
|
|
1046
|
+
const fd = openSync(file, "r");
|
|
1047
|
+
const buf = Buffer.alloc(size - start);
|
|
1048
|
+
readSync(fd, buf, 0, buf.length, start);
|
|
1049
|
+
closeSync(fd);
|
|
1050
|
+
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
1051
|
+
if (start > 0) lines.shift();
|
|
1052
|
+
return lines;
|
|
1053
|
+
} catch {
|
|
1054
|
+
return [];
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function print(r, json) {
|
|
1058
|
+
if (json) return out(JSON.stringify(r));
|
|
1059
|
+
const dec = r.decision === "ALLOW" ? c.green : c.red;
|
|
1060
|
+
const src = r.source === "local" ? c.green + "LOC" : c.blue4 + "CLD";
|
|
1061
|
+
out(
|
|
1062
|
+
`${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
async function run7(argv) {
|
|
1066
|
+
const { flags } = parse(argv);
|
|
1067
|
+
const json = flagBool(flags, "json");
|
|
1068
|
+
const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
|
|
1069
|
+
const toolFilter = (flagStr(flags, "tool") || "").toLowerCase();
|
|
1070
|
+
const localOnly = flagBool(flags, "local-only");
|
|
1071
|
+
const cloudOnly = flagBool(flags, "cloud-only");
|
|
1072
|
+
const keep = (r) => {
|
|
1073
|
+
if (decisionFilter && r.decision.toUpperCase() !== decisionFilter && !(decisionFilter === "DENY" && r.decision === "DENIED")) return false;
|
|
1074
|
+
if (toolFilter && !r.tool.toLowerCase().includes(toolFilter)) return false;
|
|
1075
|
+
return true;
|
|
1076
|
+
};
|
|
1077
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1078
|
+
let lastLocalTs = 0;
|
|
1079
|
+
let first = true;
|
|
1080
|
+
const emit = (rows) => {
|
|
1081
|
+
rows.sort((a, b) => a.at - b.at);
|
|
1082
|
+
for (const r of rows) if (keep(r)) print(r, json);
|
|
1083
|
+
};
|
|
1084
|
+
const pollLocal = () => {
|
|
1085
|
+
if (cloudOnly || !existsSync3(LOCAL_LOG2)) return;
|
|
1086
|
+
const rows = [];
|
|
1087
|
+
for (const line of tailLocal(LOCAL_LOG2)) {
|
|
1088
|
+
try {
|
|
1089
|
+
const j = JSON.parse(line);
|
|
1090
|
+
const at = Date.parse(String(j.ts ?? ""));
|
|
1091
|
+
if (!Number.isFinite(at) || at <= lastLocalTs) continue;
|
|
1092
|
+
rows.push({
|
|
1093
|
+
at,
|
|
1094
|
+
tool: String(j.tool ?? "?"),
|
|
1095
|
+
decision: String(j.decision ?? "ALLOW"),
|
|
1096
|
+
permission: String(j.permission ?? ""),
|
|
1097
|
+
detail: (j.arguments ? JSON.stringify(j.arguments) : String(j.reason ?? "")).replace(/\s+/g, " "),
|
|
1098
|
+
agent: String(j.agent_name ?? ""),
|
|
1099
|
+
source: "local",
|
|
1100
|
+
dlp: !!j.dlp
|
|
1101
|
+
});
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (rows.length) lastLocalTs = rows[rows.length - 1].at;
|
|
1106
|
+
if (!first) emit(rows);
|
|
1107
|
+
};
|
|
1108
|
+
const pollCloud = async () => {
|
|
1109
|
+
if (localOnly) return;
|
|
1110
|
+
try {
|
|
1111
|
+
const res = await api.audit.list({ limit: 50 });
|
|
1112
|
+
const rows = [];
|
|
1113
|
+
for (const e of res.entries) {
|
|
1114
|
+
if (seen.has(e.id)) continue;
|
|
1115
|
+
seen.add(e.id);
|
|
1116
|
+
rows.push({
|
|
1117
|
+
at: Date.parse(e.created_at),
|
|
1118
|
+
tool: e.tool_name,
|
|
1119
|
+
decision: e.decision,
|
|
1120
|
+
permission: e.permission ?? "",
|
|
1121
|
+
detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
|
|
1122
|
+
agent: e.agent_name ?? "",
|
|
1123
|
+
source: "cloud",
|
|
1124
|
+
dlp: !!e.dlp_matches?.length
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
if (!first) emit(rows);
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
if (!json) err(` ${c.dim}watching guard stream \u2014 Ctrl+C to stop${c.reset}`);
|
|
1132
|
+
pollLocal();
|
|
1133
|
+
await pollCloud();
|
|
1134
|
+
first = false;
|
|
1135
|
+
return new Promise(() => {
|
|
1136
|
+
setInterval(pollLocal, 2e3);
|
|
1137
|
+
setInterval(() => void pollCloud(), 4e3);
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/commands/keys.ts
|
|
1142
|
+
var USAGE6 = `${bold("solongate keys")} \u2014 API keys
|
|
1143
|
+
|
|
1144
|
+
keys list List keys (prefix only)
|
|
1145
|
+
keys create --name <n> Create a key (secret shown once)
|
|
1146
|
+
keys revoke <id> Revoke a key
|
|
1147
|
+
|
|
1148
|
+
Add --json for machine-readable output.`;
|
|
1149
|
+
async function run8(argv) {
|
|
1150
|
+
const { positionals, flags } = parse(argv);
|
|
1151
|
+
const sub = positionals[0] ?? "list";
|
|
1152
|
+
const json = flagBool(flags, "json");
|
|
1153
|
+
switch (sub) {
|
|
1154
|
+
case "help":
|
|
1155
|
+
return err(USAGE6), 0;
|
|
1156
|
+
case "list": {
|
|
1157
|
+
const { keys } = await api.keys.list();
|
|
1158
|
+
if (json) return printJson(keys), 0;
|
|
1159
|
+
if (!keys.length) return err(dim(" No API keys.")), 0;
|
|
1160
|
+
table(
|
|
1161
|
+
["ID", "NAME", "PREFIX", "CREATED"],
|
|
1162
|
+
keys.map((k) => [dim(truncate(k.id, 10)), truncate(k.name, 24), cyan(k.key_prefix), dim(k.created_at)])
|
|
1163
|
+
);
|
|
1164
|
+
return 0;
|
|
1165
|
+
}
|
|
1166
|
+
case "create": {
|
|
1167
|
+
const name = flagStr(flags, "name");
|
|
1168
|
+
if (!name) return err(" Usage: keys create --name <name>"), 1;
|
|
1169
|
+
const res = await api.keys.create(name, !flagBool(flags, "test"));
|
|
1170
|
+
if (json) return printJson(res), 0;
|
|
1171
|
+
err(green(` \u2713 Created "${res.name}"`));
|
|
1172
|
+
err("");
|
|
1173
|
+
err(" " + bold(res.key));
|
|
1174
|
+
err(dim(" \u2191 shown only once \u2014 copy it now."));
|
|
1175
|
+
return 0;
|
|
1176
|
+
}
|
|
1177
|
+
case "revoke": {
|
|
1178
|
+
const id = positionals[1];
|
|
1179
|
+
if (!id) return err(" Usage: keys revoke <id>"), 1;
|
|
1180
|
+
await api.keys.revoke(id);
|
|
1181
|
+
if (json) return printJson({ ok: true, id }), 0;
|
|
1182
|
+
return err(green(` \u2713 Revoked ${id}`)), 0;
|
|
1183
|
+
}
|
|
1184
|
+
default:
|
|
1185
|
+
return err(USAGE6), 1;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/commands/mcp.ts
|
|
1190
|
+
async function run9(argv) {
|
|
1191
|
+
const { flags } = parse(argv);
|
|
1192
|
+
const json = flagBool(flags, "json");
|
|
1193
|
+
const { servers } = await api.mcp.list();
|
|
1194
|
+
if (json) return printJson(servers), 0;
|
|
1195
|
+
if (!servers.length) return err(dim(" No MCP servers registered.")), 0;
|
|
1196
|
+
table(
|
|
1197
|
+
["STATUS", "NAME", "TARGET"],
|
|
1198
|
+
servers.map((s) => [
|
|
1199
|
+
s.status === "active" ? green(s.status) : yellow(s.status),
|
|
1200
|
+
cyan(truncate(s.name, 24)),
|
|
1201
|
+
dim(truncate(s.url || s.command || "\u2014", 46))
|
|
1202
|
+
])
|
|
1203
|
+
);
|
|
1204
|
+
return 0;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/commands/alerts.ts
|
|
1208
|
+
var USAGE7 = `${bold("solongate alerts")} \u2014 spike alerts (Telegram / email)
|
|
1209
|
+
|
|
1210
|
+
alerts list
|
|
1211
|
+
alerts add --signal deny|dlp|ratelimit|any --threshold N --window S
|
|
1212
|
+
(--email <a> | --telegram <chatId> | --slack <url>)
|
|
1213
|
+
alerts remove <id>
|
|
1214
|
+
|
|
1215
|
+
Add --json for machine-readable output.`;
|
|
1216
|
+
async function run10(argv) {
|
|
1217
|
+
const { positionals, flags } = parse(argv);
|
|
1218
|
+
const sub = positionals[0] ?? "list";
|
|
1219
|
+
const json = flagBool(flags, "json");
|
|
1220
|
+
switch (sub) {
|
|
1221
|
+
case "help":
|
|
1222
|
+
return err(USAGE7), 0;
|
|
1223
|
+
case "list": {
|
|
1224
|
+
const { rules } = await api.settings.getAlerts();
|
|
1225
|
+
if (json) return printJson(rules), 0;
|
|
1226
|
+
if (!rules.length) return err(dim(" No alert rules.")), 0;
|
|
1227
|
+
table(
|
|
1228
|
+
["ID", "ON", "SIGNAL", "THRESH", "WINDOW", "CHANNELS"],
|
|
1229
|
+
rules.map((r) => [
|
|
1230
|
+
dim(truncate(r.id, 10)),
|
|
1231
|
+
r.enabled ? green("\u25CF") : dim("\u25CB"),
|
|
1232
|
+
cyan(r.signal),
|
|
1233
|
+
`${r.threshold}`,
|
|
1234
|
+
`${r.windowSeconds}s`,
|
|
1235
|
+
dim([...r.emails ?? [], ...(r.telegram ?? []).map((t) => "tg:" + t), ...(r.slackUrls ?? []).map(() => "slack")].join(" ") || "\u2014")
|
|
1236
|
+
])
|
|
1237
|
+
);
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1240
|
+
case "add": {
|
|
1241
|
+
const email = flagStr(flags, "email");
|
|
1242
|
+
const telegram = flagStr(flags, "telegram");
|
|
1243
|
+
const slack = flagStr(flags, "slack");
|
|
1244
|
+
if (!email && !telegram && !slack) return err(" Need a channel: --email / --telegram / --slack"), 1;
|
|
1245
|
+
const res = await api.settings.createAlert({
|
|
1246
|
+
signal: flagStr(flags, "signal") ?? "deny",
|
|
1247
|
+
threshold: flagNum(flags, "threshold") ?? 5,
|
|
1248
|
+
windowSeconds: flagNum(flags, "window") ?? 300,
|
|
1249
|
+
emails: email ? [email] : void 0,
|
|
1250
|
+
telegram: telegram ? [telegram] : void 0,
|
|
1251
|
+
slackUrl: slack
|
|
1252
|
+
});
|
|
1253
|
+
if (json) return printJson(res.rule), 0;
|
|
1254
|
+
return err(green(` \u2713 Alert added (${res.rule.signal}, ${res.rule.threshold}/${res.rule.windowSeconds}s)`)), 0;
|
|
1255
|
+
}
|
|
1256
|
+
case "remove": {
|
|
1257
|
+
const id = positionals[1];
|
|
1258
|
+
if (!id) return err(" Usage: alerts remove <id>"), 1;
|
|
1259
|
+
await api.settings.deleteAlert(id);
|
|
1260
|
+
if (json) return printJson({ ok: true, id }), 0;
|
|
1261
|
+
return err(green(` \u2713 Removed ${id}`)), 0;
|
|
1262
|
+
}
|
|
1263
|
+
default:
|
|
1264
|
+
return err(USAGE7), 1;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/commands/webhooks.ts
|
|
1269
|
+
var USAGE8 = `${bold("solongate webhooks")} \u2014 event webhooks
|
|
1270
|
+
|
|
1271
|
+
webhooks list
|
|
1272
|
+
webhooks add --url <https://\u2026> [--events denials|allowed|all]
|
|
1273
|
+
webhooks remove <id>
|
|
1274
|
+
|
|
1275
|
+
Add --json for machine-readable output.`;
|
|
1276
|
+
async function run11(argv) {
|
|
1277
|
+
const { positionals, flags } = parse(argv);
|
|
1278
|
+
const sub = positionals[0] ?? "list";
|
|
1279
|
+
const json = flagBool(flags, "json");
|
|
1280
|
+
switch (sub) {
|
|
1281
|
+
case "help":
|
|
1282
|
+
return err(USAGE8), 0;
|
|
1283
|
+
case "list": {
|
|
1284
|
+
const { webhooks } = await api.settings.getWebhooks();
|
|
1285
|
+
if (json) return printJson(webhooks), 0;
|
|
1286
|
+
if (!webhooks.length) return err(dim(" No webhooks.")), 0;
|
|
1287
|
+
table(
|
|
1288
|
+
["ID", "ON", "EVENTS", "URL"],
|
|
1289
|
+
webhooks.map((w) => [dim(truncate(w.id, 10)), w.enabled ? green("\u25CF") : dim("\u25CB"), cyan(w.events), dim(truncate(w.url, 46))])
|
|
1290
|
+
);
|
|
1291
|
+
return 0;
|
|
1292
|
+
}
|
|
1293
|
+
case "add": {
|
|
1294
|
+
const url = flagStr(flags, "url");
|
|
1295
|
+
if (!url) return err(" Usage: webhooks add --url <https://\u2026> [--events denials|allowed|all]"), 1;
|
|
1296
|
+
const res = await api.settings.createWebhook({ url, events: flagStr(flags, "events") ?? "denials" });
|
|
1297
|
+
if (json) return printJson(res.webhook), 0;
|
|
1298
|
+
return err(green(` \u2713 Webhook added (${res.webhook.events}) \u2192 ${res.webhook.url}`)), 0;
|
|
1299
|
+
}
|
|
1300
|
+
case "remove": {
|
|
1301
|
+
const id = positionals[1];
|
|
1302
|
+
if (!id) return err(" Usage: webhooks remove <id>"), 1;
|
|
1303
|
+
await api.settings.deleteWebhook(id);
|
|
1304
|
+
if (json) return printJson({ ok: true, id }), 0;
|
|
1305
|
+
return err(green(` \u2713 Removed ${id}`)), 0;
|
|
1306
|
+
}
|
|
1307
|
+
default:
|
|
1308
|
+
return err(USAGE8), 1;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
905
1312
|
// src/commands/index.ts
|
|
906
1313
|
async function dispatch(command, argv) {
|
|
907
1314
|
switch (command) {
|
|
@@ -919,6 +1326,18 @@ async function dispatch(command, argv) {
|
|
|
919
1326
|
return runAgents(argv);
|
|
920
1327
|
case "agent":
|
|
921
1328
|
return runAgent(argv);
|
|
1329
|
+
case "doctor":
|
|
1330
|
+
return run6(argv);
|
|
1331
|
+
case "watch":
|
|
1332
|
+
return run7(argv);
|
|
1333
|
+
case "keys":
|
|
1334
|
+
return run8(argv);
|
|
1335
|
+
case "mcp":
|
|
1336
|
+
return run9(argv);
|
|
1337
|
+
case "alerts":
|
|
1338
|
+
return run10(argv);
|
|
1339
|
+
case "webhooks":
|
|
1340
|
+
return run11(argv);
|
|
922
1341
|
default:
|
|
923
1342
|
err(` Unknown command: ${command}`);
|
|
924
1343
|
return 1;
|
|
@@ -941,7 +1360,7 @@ async function runCommand(command, argv) {
|
|
|
941
1360
|
return 1;
|
|
942
1361
|
}
|
|
943
1362
|
}
|
|
944
|
-
var COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
|
|
1363
|
+
var COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks"];
|
|
945
1364
|
export {
|
|
946
1365
|
COMMAND_NAMES,
|
|
947
1366
|
runCommand
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|