0sec-cli 0.22.0 → 0.22.4
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/0.js +56 -45
- package/README.md +12 -0
- package/chunks/{adapt-loop-7I57LXXR.js → adapt-loop-3CCOWE43.js} +4 -4
- package/chunks/{artifact-scraper-PYZ3RU3O.js → artifact-scraper-Z4UUIOH3.js} +2 -2
- package/chunks/{assumption-mining-MFLF43IR.js → assumption-mining-2UAKQB6V.js} +18 -14
- package/chunks/{chunk-RE5EZDG2.js → chunk-2WWAPKAW.js} +126 -1089
- package/chunks/{chunk-LPTRPRZR.js → chunk-3BPX75OR.js} +1 -1
- package/chunks/chunk-4ZYYX5GY.js +74 -0
- package/chunks/{chunk-RSIUX3RW.js → chunk-5JNTMWPK.js} +2 -2
- package/chunks/{chunk-RRD4XXPU.js → chunk-5WAJGLCW.js} +3 -3
- package/chunks/{chunk-76KSBWOQ.js → chunk-6KFB6AYN.js} +515 -20
- package/chunks/{chunk-HR2TU42B.js → chunk-6WQDXWQC.js} +169 -15
- package/chunks/chunk-6YZE552K.js +5603 -0
- package/chunks/chunk-BGX53365.js +309 -0
- package/chunks/{chunk-7DEHG7YT.js → chunk-C6JPA32H.js} +1 -1
- package/chunks/chunk-CF3MNS3O.js +990 -0
- package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
- package/chunks/{chunk-DM5C5L5D.js → chunk-DPUXXMJW.js} +1 -1
- package/chunks/{chunk-LYUAM3TG.js → chunk-E3OOBVOK.js} +30 -12
- package/chunks/{chunk-HUNAPTKV.js → chunk-E74LXQFY.js} +1 -1
- package/chunks/{chunk-UU7PQP6N.js → chunk-ELI3BVIK.js} +3 -3
- package/chunks/chunk-FFHRANYC.js +547 -0
- package/chunks/{chunk-IO7R7WBM.js → chunk-G2BE2FC7.js} +1 -1
- package/chunks/chunk-GQ3DGHWE.js +1659 -0
- package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
- package/chunks/{chunk-UV5QWZGE.js → chunk-H4DH7COV.js} +192 -40
- package/chunks/{chunk-SJBCK2EI.js → chunk-H6Q2QAIY.js} +23475 -22747
- package/chunks/{chunk-VMRRNRMR.js → chunk-IHPTOTLL.js} +1 -1
- package/chunks/chunk-IXAHQICR.js +31 -0
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/{chunk-VNRY6AWF.js → chunk-L2CCO4KE.js} +282 -34
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-EGZGZFAH.js → chunk-OAS5SAVZ.js} +6768 -2100
- package/chunks/chunk-PS5E7LUQ.js +277 -0
- package/chunks/{chunk-2GV4HQKE.js → chunk-QVIRWJ45.js} +257 -145
- package/chunks/{chunk-OWLJYJ5O.js → chunk-QWK4R3DB.js} +9 -9
- package/chunks/{chunk-XFCTUKXR.js → chunk-RCRTSKQN.js} +1 -1
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-7UFUALR2.js → chunk-RTBZ36BD.js} +8 -274
- package/chunks/{chunk-TZFIAMWZ.js → chunk-SCWWLABR.js} +1 -1
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-5MWAG3FT.js → chunk-WBRGXCU2.js} +79 -18
- package/chunks/{chunk-LCRTAQN2.js → chunk-WHUAUFSX.js} +1 -1
- package/chunks/{chunk-YIQSGNAA.js → chunk-XODDLNBI.js} +1535 -1306
- package/chunks/chunk-YAZARMJY.js +355 -0
- package/chunks/{chunk-OAYUPGF6.js → chunk-YED25PA4.js} +1 -1
- package/chunks/{codex-models-7TLJSY3L.js → codex-models-Z43V3427.js} +9 -3
- package/chunks/{commands-NEI7L3QA.js → commands-EUGF4FUZ.js} +5844 -4095
- package/chunks/{db-5AP3JB4W.js → db-NVAW4WWR.js} +3 -3
- package/chunks/{dist-3FNZIBB4.js → dist-27BNG6WD.js} +195 -112
- package/chunks/{dist-5YHYNLI3.js → dist-7RUOKPRJ.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-DD4H6HMQ.js → dist-NG2HT5XU.js} +27 -1
- package/chunks/{eval-runner-43BO2C4T.js → eval-runner-6Q3H52SP.js} +13 -9
- package/chunks/{exploit-agent-N4A5ELBK.js → exploit-agent-FCCR2HYX.js} +1 -1
- package/chunks/{exploit-autoclimb-N7P6KIF5.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
- package/chunks/{exploit-climb-WNEJUB5W.js → exploit-climb-3JHGEO36.js} +2 -2
- package/chunks/{fix-Z452M4EC.js → fix-NSNPU33B.js} +12 -6
- package/chunks/{harness-QJJJVSG4.js → harness-KXQOVIDQ.js} +3 -3
- package/chunks/{hunt-scan-K6D2V3PX.js → hunt-scan-ELTKGKC4.js} +16 -12
- package/chunks/{kernel-primitive-TKUACPKD.js → kernel-primitive-MT7YZSIG.js} +2 -2
- package/chunks/{kernel-vm-runner-RAAO2PGT.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
- package/chunks/{native-loop-2ZRJENZ3.js → native-loop-OFJDRHPI.js} +18 -8
- package/chunks/orchestrate-DRZ2SVCP.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/{run-U3XU4CTC.js → run-WTJCZZHV.js} +3091 -6037
- package/chunks/{runtime-G75TEWQY.js → runtime-FCKOUB7V.js} +10 -4
- package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
- package/chunks/{scope-WXI46UHJ.js → scope-E5QZUOOH.js} +2 -2
- package/chunks/service-plugins-MSK2EQP6.js +62 -0
- package/chunks/{session-store-27L3ZU73.js → session-store-H3Q7KCYG.js} +6 -4
- package/chunks/{variant-candidates-YWZORZMK.js → variant-candidates-SZKDCOLK.js} +9 -3
- package/chunks/{web-recon-prepass-YVMFXCIK.js → web-recon-prepass-IO3GXQMW.js} +11 -6
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
- package/dashboard/assets/operations-D8Ii01SV.js +114 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-6GHSR47F.js +0 -147
- package/chunks/chunk-CAYANSH6.js +0 -3524
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/chunk-MICHDTBK.js +0 -193
- package/chunks/orchestrate-2GWGNTBF.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
createWorkbenchConsoleSession,
|
|
6
|
+
currentWorkbenchAssets,
|
|
7
|
+
loadGlobalSettings,
|
|
8
|
+
loadWorkbenchConfig,
|
|
9
|
+
resolveWorkbenchGuestSettings,
|
|
10
|
+
workbenchConfigPath,
|
|
11
|
+
workbenchNetworkEnabled
|
|
12
|
+
} from "./chunk-XODDLNBI.js";
|
|
13
|
+
import {
|
|
14
|
+
createWorkbenchProviderBroker
|
|
15
|
+
} from "./chunk-OAS5SAVZ.js";
|
|
16
|
+
import {
|
|
17
|
+
osecDB
|
|
18
|
+
} from "./chunk-L2CCO4KE.js";
|
|
19
|
+
import {
|
|
20
|
+
isAdmittedSmolvmWorkbench
|
|
21
|
+
} from "./chunk-2WWAPKAW.js";
|
|
22
|
+
import {
|
|
23
|
+
SERVICE_PLUGIN_CATALOG,
|
|
24
|
+
findingSchema,
|
|
25
|
+
homeStateDir
|
|
26
|
+
} from "./chunk-6KFB6AYN.js";
|
|
27
|
+
|
|
28
|
+
// packages/cli/src/web/service-plugins.ts
|
|
29
|
+
import { constants, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, chmodSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
32
|
+
|
|
33
|
+
// packages/cli/src/console-execution.ts
|
|
34
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
35
|
+
import { randomUUID } from "node:crypto";
|
|
36
|
+
function consoleExecutionProfile(homeDir) {
|
|
37
|
+
if (process.platform === "linux" && isAdmittedSmolvmWorkbench()) return "local";
|
|
38
|
+
return loadGlobalSettings(homeDir, { requireExecutionProfile: existsSync(workbenchConfigPath(homeDir)) }).executionProfile;
|
|
39
|
+
}
|
|
40
|
+
function createIsolatedConsoleSession(config, options = {}) {
|
|
41
|
+
if (consoleExecutionProfile(options.homeDir) !== "smolvm") return void 0;
|
|
42
|
+
const workbench = loadWorkbenchConfig(options.homeDir);
|
|
43
|
+
if (!workbench) throw new Error("SmolVM is selected but no workbench is configured. Run 0 workbench setup --image <approved archive>. Host fallback is refused.");
|
|
44
|
+
const requestedWorkspace = options.workspaceRoot ?? config.workspaceRoot;
|
|
45
|
+
if (workbench.workspaceRoot && requestedWorkspace && realpathSync(workbench.workspaceRoot) !== realpathSync(requestedWorkspace)) throw new Error("This chat folder differs from the configured SmolVM workspace grant. Start a chat in the granted folder or explicitly reconfigure the workbench before execution.");
|
|
46
|
+
if (config.pluginHost || config.mcpHost) throw new Error("Host plugin and MCP resources cannot be forwarded into SmolVM. Configure tools inside the approved guest image.");
|
|
47
|
+
const runtime = config.runtime;
|
|
48
|
+
const provider = runtime.resolvedProvider?.();
|
|
49
|
+
const model = runtime.resolvedModel?.() ?? config.costModel;
|
|
50
|
+
if (provider !== "chatgpt-codex" || !model) throw new Error("The isolated console currently requires the brokered chatgpt-codex provider and an explicit resolved model. Host fallback is refused.");
|
|
51
|
+
if (!workbench.providers.includes(provider)) throw new Error("The selected provider is not granted to this workbench. Configure an explicit chatgpt-codex provider grant.");
|
|
52
|
+
const routing = runtime.modelSelection?.();
|
|
53
|
+
const models = [.../* @__PURE__ */ new Set([model, ...Object.values(routing?.agentModels ?? {}).filter((value) => value !== "auto")])];
|
|
54
|
+
if (!runtime.workbenchCredentialResolver) throw new Error("The selected runtime cannot bind its account to the host provider broker.");
|
|
55
|
+
const broker = createWorkbenchProviderBroker({ provider, models, resolveCredentials: runtime.workbenchCredentialResolver() });
|
|
56
|
+
const { runtime: _runtime, ...guestConfig } = config;
|
|
57
|
+
const scanId = config.scanId ?? `console-${randomUUID()}`;
|
|
58
|
+
try {
|
|
59
|
+
return createWorkbenchConsoleSession({
|
|
60
|
+
config: { ...guestConfig, scanId, workspaceRoot: realpathSync(workbench.workspaceRoot ?? options.workspaceRoot ?? config.workspaceRoot ?? process.cwd()) },
|
|
61
|
+
workbench,
|
|
62
|
+
selection: { provider, model, contextWindowTokens: config.contextWindowTokens, ...routing },
|
|
63
|
+
provider: { ...broker.grant, request: broker.request, close: broker.close },
|
|
64
|
+
pluginHomeDir: options.homeDir,
|
|
65
|
+
assets: currentWorkbenchAssets(),
|
|
66
|
+
guestSettings: resolveWorkbenchGuestSettings(loadGlobalSettings(options.homeDir)),
|
|
67
|
+
network: workbenchNetworkEnabled(),
|
|
68
|
+
onExecution: options.onExecution,
|
|
69
|
+
onFindings: (findings, completion) => {
|
|
70
|
+
const validated = findings.map((finding) => findingSchema.parse(finding));
|
|
71
|
+
const db = new osecDB(options.dbPath);
|
|
72
|
+
try {
|
|
73
|
+
if (!db.getScan(scanId)) db.createScan({ target: config.target ?? "", depth: "default", format: "terminal", runtime: "api" }, scanId);
|
|
74
|
+
for (const finding of validated) db.saveFinding(scanId, finding);
|
|
75
|
+
if (!completion?.outcome || completion.outcome.stopReason === "error") db.failScan(scanId, completion?.outcome?.error ?? "Guest turn ended before a complete outcome was saved");
|
|
76
|
+
else db.completeScan(scanId, { source: "console", execution: "smolvm", stopReason: completion.outcome.stopReason });
|
|
77
|
+
} finally {
|
|
78
|
+
db.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
void broker.close();
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// packages/cli/src/web/service-plugins.ts
|
|
89
|
+
var ServicePluginError = class extends Error {
|
|
90
|
+
constructor(status, code, message) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.status = status;
|
|
93
|
+
this.code = code;
|
|
94
|
+
}
|
|
95
|
+
status;
|
|
96
|
+
code;
|
|
97
|
+
};
|
|
98
|
+
var invalid = (message = "Invalid plugin configuration.") => new ServicePluginError(400, "invalid_configuration", message);
|
|
99
|
+
function object(value) {
|
|
100
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid();
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function descriptor(id) {
|
|
104
|
+
const plugin = SERVICE_PLUGIN_CATALOG.find((item) => item.id === id);
|
|
105
|
+
if (!plugin) throw new ServicePluginError(404, "unknown_plugin", "Plugin does not exist.");
|
|
106
|
+
return plugin;
|
|
107
|
+
}
|
|
108
|
+
function validateFields(id, value, previous = {}) {
|
|
109
|
+
const plugin = descriptor(id);
|
|
110
|
+
const supplied = object(value);
|
|
111
|
+
if (Object.keys(supplied).some((key) => !plugin.fields.some((field) => field.key === key))) throw invalid();
|
|
112
|
+
const result = {};
|
|
113
|
+
for (const field of plugin.fields) {
|
|
114
|
+
const suppliedValue = supplied[field.key];
|
|
115
|
+
const raw = (suppliedValue === void 0 || field.type === "secret" && suppliedValue === "") && field.type === "secret" ? previous[field.key] : suppliedValue;
|
|
116
|
+
if (raw === void 0 || raw === "") {
|
|
117
|
+
if (field.required) throw invalid(`Enter ${field.label}.`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (typeof raw !== "string" || raw.length > 8192 || /[\x00-\x1f\x7f]/.test(raw)) throw invalid();
|
|
121
|
+
const text = raw.trim();
|
|
122
|
+
if (!text) {
|
|
123
|
+
if (field.required) throw invalid(`Enter ${field.label}.`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (field.type === "url") {
|
|
127
|
+
let url;
|
|
128
|
+
try {
|
|
129
|
+
url = new URL(text);
|
|
130
|
+
} catch {
|
|
131
|
+
throw invalid("Enter a valid service URL.");
|
|
132
|
+
}
|
|
133
|
+
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) throw invalid("Use HTTPS, or localhost HTTP, without credentials or query parameters.");
|
|
134
|
+
result[field.key] = url.href.replace(/\/$/, "");
|
|
135
|
+
} else result[field.key] = text;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function assertRegular(path, directory = false) {
|
|
140
|
+
try {
|
|
141
|
+
const stat = lstatSync(path);
|
|
142
|
+
if (stat.isSymbolicLink() || (directory ? !stat.isDirectory() : !stat.isFile())) throw new Error("Unsafe plugin configuration path.");
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.code !== "ENOENT") throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function readStore(homeDir) {
|
|
148
|
+
const directory = homeStateDir(homeDir);
|
|
149
|
+
assertRegular(directory, true);
|
|
150
|
+
const path = join(directory, "service-plugins.json");
|
|
151
|
+
assertRegular(path);
|
|
152
|
+
let fd;
|
|
153
|
+
try {
|
|
154
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error.code === "ENOENT") return [];
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const stat = fstatSync(fd);
|
|
161
|
+
if (!stat.isFile() || stat.size > 1048576) throw new Error("Invalid plugin configuration file.");
|
|
162
|
+
const raw = JSON.parse(readFileSync(fd, "utf8"));
|
|
163
|
+
if (!Array.isArray(raw)) throw new Error("Invalid plugin configuration file.");
|
|
164
|
+
const ids = /* @__PURE__ */ new Set();
|
|
165
|
+
return raw.map((value) => {
|
|
166
|
+
const row = object(value);
|
|
167
|
+
if (typeof row.id !== "string" || ids.has(row.id) || typeof row.enabled !== "boolean" || typeof row.testedAt !== "string" || row.testedAt.length > 64 || !Array.isArray(row.tools) || row.tools.length > 512) throw invalid();
|
|
168
|
+
ids.add(row.id);
|
|
169
|
+
const fields = validateFields(row.id, row.fields);
|
|
170
|
+
const tools = row.tools.map((value2) => {
|
|
171
|
+
const tool = object(value2);
|
|
172
|
+
if (typeof tool.name !== "string" || tool.name.length > 256 || typeof tool.description !== "string" || tool.description.length > 16384) throw invalid();
|
|
173
|
+
return { name: tool.name, description: tool.description };
|
|
174
|
+
});
|
|
175
|
+
return { id: row.id, enabled: row.enabled, fields, testedAt: row.testedAt, tools };
|
|
176
|
+
});
|
|
177
|
+
} finally {
|
|
178
|
+
closeSync(fd);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function saveStore(rows, homeDir) {
|
|
182
|
+
const serialized = JSON.stringify(rows) + "\n";
|
|
183
|
+
if (Buffer.byteLength(serialized, "utf8") > 1048576) throw new Error("Plugin configuration is too large.");
|
|
184
|
+
const directory = homeStateDir(homeDir);
|
|
185
|
+
assertRegular(directory, true);
|
|
186
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
187
|
+
chmodSync(directory, 448);
|
|
188
|
+
const path = join(directory, "service-plugins.json");
|
|
189
|
+
assertRegular(path);
|
|
190
|
+
const temporary = join(directory, `.service-plugins-${randomUUID2()}.tmp`);
|
|
191
|
+
try {
|
|
192
|
+
writeFileSync(temporary, serialized, { mode: 384, flag: "wx" });
|
|
193
|
+
renameSync(temporary, path);
|
|
194
|
+
} finally {
|
|
195
|
+
rmSync(temporary, { force: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function loadServicePluginConnections(homeDir) {
|
|
199
|
+
return readStore(homeDir).map(({ id, enabled, fields }) => ({ id, enabled, fields }));
|
|
200
|
+
}
|
|
201
|
+
var ServicePluginServices = class {
|
|
202
|
+
#options;
|
|
203
|
+
#pending = Promise.resolve();
|
|
204
|
+
constructor(options = {}) {
|
|
205
|
+
this.#options = options;
|
|
206
|
+
}
|
|
207
|
+
#view() {
|
|
208
|
+
const rows = readStore(this.#options.homeDir);
|
|
209
|
+
const secrets = rows.flatMap((row) => descriptor(row.id).fields.filter((field) => field.type === "secret").map((field) => row.fields[field.key])).filter((value) => Boolean(value));
|
|
210
|
+
const redact = (text) => secrets.reduce((result, secret) => result.split(secret).join("[redacted]"), text);
|
|
211
|
+
return { executionProfile: consoleExecutionProfile(), items: SERVICE_PLUGIN_CATALOG.map((plugin) => {
|
|
212
|
+
const row = rows.find((item) => item.id === plugin.id);
|
|
213
|
+
const values = Object.fromEntries(plugin.fields.filter((field) => field.type !== "secret").flatMap((field) => row?.fields[field.key] ? [[field.key, redact(row.fields[field.key])]] : []));
|
|
214
|
+
const tools = row?.tools.map((tool) => ({ name: redact(tool.name), description: redact(tool.description) })) ?? [];
|
|
215
|
+
return { ...plugin, configured: Boolean(row), enabled: row?.enabled ?? false, tools, values, testedAt: row?.testedAt ?? null };
|
|
216
|
+
}) };
|
|
217
|
+
}
|
|
218
|
+
async #probe(connection) {
|
|
219
|
+
try {
|
|
220
|
+
const probe = this.#options.probe ?? (await import("./dist-27BNG6WD.js")).testServicePluginConnection;
|
|
221
|
+
const result = await probe(connection);
|
|
222
|
+
if (!Array.isArray(result.tools) || result.tools.length > 512 || result.tools.some((tool) => typeof tool.name !== "string" || tool.name.length > 256 || typeof tool.description !== "string" || tool.description.length > 16384)) throw new Error("Invalid tool catalog.");
|
|
223
|
+
const configurations = [...readStore(this.#options.homeDir), connection];
|
|
224
|
+
const secrets = configurations.flatMap((row) => descriptor(row.id).fields.filter((field) => field.type === "secret").map((field) => row.fields[field.key])).filter((value) => Boolean(value));
|
|
225
|
+
const redact = (text) => secrets.reduce((result2, secret) => result2.split(secret).join("[redacted]"), text).replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, "$1[redacted]@");
|
|
226
|
+
return { tools: result.tools.map(({ name, description }) => ({ name: redact(name), description: redact(description) })) };
|
|
227
|
+
} catch {
|
|
228
|
+
throw new ServicePluginError(409, "connection_failed", "Could not connect. Check the service URL and credentials.");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async #handle(path, method, input) {
|
|
232
|
+
if (path === "service-plugins" && method === "GET") return this.#view();
|
|
233
|
+
const match = /^service-plugins\/([a-z0-9-]+)(\/connect)?$/.exec(path);
|
|
234
|
+
if (!match) throw new ServicePluginError(405, "method_not_allowed", "Unsupported plugin operation.");
|
|
235
|
+
const id = match[1];
|
|
236
|
+
descriptor(id);
|
|
237
|
+
const rows = readStore(this.#options.homeDir);
|
|
238
|
+
const previous = rows.find((row) => row.id === id);
|
|
239
|
+
let next;
|
|
240
|
+
if (match[2] && method === "POST") {
|
|
241
|
+
const body = object(input);
|
|
242
|
+
if (body.approved !== true) throw new ServicePluginError(400, "approval_required", "Confirm connecting this plugin.");
|
|
243
|
+
const connection = { id, enabled: true, fields: validateFields(id, body.fields, previous?.fields) };
|
|
244
|
+
const result = await this.#probe(connection);
|
|
245
|
+
next = { ...connection, ...result, testedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
246
|
+
} else if (!match[2] && method === "PATCH") {
|
|
247
|
+
const body = object(input);
|
|
248
|
+
if (typeof body.enabled !== "boolean") throw invalid();
|
|
249
|
+
if (!previous) throw new ServicePluginError(409, "not_connected", "Connect this plugin first.");
|
|
250
|
+
next = { ...previous, enabled: body.enabled };
|
|
251
|
+
if (body.enabled) next = { ...next, ...await this.#probe(next), testedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
252
|
+
} else if (!match[2] && method === "DELETE") {
|
|
253
|
+
} else throw new ServicePluginError(405, "method_not_allowed", "Unsupported plugin operation.");
|
|
254
|
+
saveStore([...rows.filter((row) => row.id !== id), ...next ? [next] : []], this.#options.homeDir);
|
|
255
|
+
return this.#view();
|
|
256
|
+
}
|
|
257
|
+
async handle(path, method, input) {
|
|
258
|
+
if (!/^service-plugins(?:\/|$)/.test(path)) return null;
|
|
259
|
+
const operation = async () => {
|
|
260
|
+
try {
|
|
261
|
+
return { status: 200, data: await this.#handle(path, method, input) };
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return { status: error instanceof ServicePluginError ? error.status : 500, data: { code: error instanceof ServicePluginError ? error.code : "configuration_unavailable", error: error instanceof ServicePluginError ? error.message : "Plugin configuration could not be read or saved." } };
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const result = this.#pending.then(operation, operation);
|
|
267
|
+
this.#pending = result;
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
consoleExecutionProfile,
|
|
274
|
+
createIsolatedConsoleSession,
|
|
275
|
+
loadServicePluginConnections,
|
|
276
|
+
ServicePluginServices
|
|
277
|
+
};
|