@bpmnkit/proxy 0.0.17 → 0.0.21
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/README.md +2 -0
- package/dist/adapters/copilot.js +1 -1
- package/dist/adapters/gemini.js +1 -1
- package/dist/index.js +459 -5
- package/dist/routes/run-history.js +307 -0
- package/dist/triggers/file-watcher.js +215 -0
- package/dist/triggers/index.js +18 -0
- package/dist/triggers/timer.js +239 -0
- package/dist/triggers/webhook.js +76 -0
- package/dist/worker-templates.js +612 -0
- package/dist/worker.js +166 -0
- package/dist/workers/cli.js +72 -0
- package/dist/workers/email.js +102 -0
- package/dist/workers/fs.js +90 -0
- package/dist/workers/http.js +46 -0
- package/dist/workers/js.js +21 -0
- package/dist/workers/llm.js +55 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/@bpmnkit/proxy)
|
|
7
7
|
[](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
|
|
8
8
|
[](https://github.com/bpmnkit/monorepo)
|
|
9
|
+
[](https://github.com/bpmnkit/monorepo)
|
|
10
|
+
[](https://github.com/bpmnkit/monorepo)
|
|
9
11
|
|
|
10
12
|
[Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/apps/proxy/CHANGELOG.md)
|
|
11
13
|
</div>
|
package/dist/adapters/copilot.js
CHANGED
|
@@ -16,7 +16,7 @@ export async function stream(messages, systemPrompt, mcpConfigFile, onToken) {
|
|
|
16
16
|
const prompt = `${systemPrompt}\n\nUser: ${lastUser?.content ?? "help"}`;
|
|
17
17
|
const args = ["-p", prompt, "--yolo"];
|
|
18
18
|
if (mcpConfigFile) {
|
|
19
|
-
args.push("--additional-mcp-config", mcpConfigFile);
|
|
19
|
+
args.push("--additional-mcp-config", `@${mcpConfigFile}`);
|
|
20
20
|
args.push("--allow-all-tools");
|
|
21
21
|
}
|
|
22
22
|
console.log(`[copilot] spawning with MCP: ${mcpConfigFile !== null}`);
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -15,7 +15,7 @@ export async function available() {
|
|
|
15
15
|
export async function stream(messages, systemPrompt, _mcpConfigFile, onToken) {
|
|
16
16
|
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
17
17
|
const prompt = `${systemPrompt}\n\nUser: ${lastUser?.content ?? "help"}`;
|
|
18
|
-
const args = ["--prompt", prompt
|
|
18
|
+
const args = ["--prompt", prompt];
|
|
19
19
|
console.log("[gemini] spawning (no MCP support — using system prompt fallback)");
|
|
20
20
|
await new Promise((resolve, reject) => {
|
|
21
21
|
const proc = spawn("gemini", args, {
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import http from "node:http";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { basename, dirname, extname, join, relative, sep } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { Bpmn, applyOperations, compactify, expand, optimize } from "@bpmnkit/core";
|
|
8
8
|
import { createClientFromProfile } from "@bpmnkit/profiles";
|
|
@@ -11,6 +11,10 @@ import * as claude from "./adapters/claude.js";
|
|
|
11
11
|
import * as copilot from "./adapters/copilot.js";
|
|
12
12
|
import * as gemini from "./adapters/gemini.js";
|
|
13
13
|
import { buildImproveSystemPrompt, buildImproveUserMessage, buildIncidentSystemPrompt, buildIncidentUserMessage, buildMcpExplainPrompt, buildMcpImprovePrompt, buildMcpSystemPrompt, buildOperateChatSystemPrompt, buildSearchSystemPrompt, buildSystemPrompt, } from "./prompt.js";
|
|
14
|
+
import { handleDeleteRunHistory, handleGetRunHistory, handleGetRunHistoryDetail, handleRerunHistory, matchRerunHistoryRoute, matchRunHistoryRoute, } from "./routes/run-history.js";
|
|
15
|
+
import { handleWebhook, matchWebhookRoute, startTriggers } from "./triggers/index.js";
|
|
16
|
+
import { WORKER_TEMPLATES } from "./worker-templates.js";
|
|
17
|
+
import { startWorkerDaemon, workerState } from "./worker.js";
|
|
14
18
|
const PORT = process.env.AI_SERVER_PORT ? Number(process.env.AI_SERVER_PORT) : 3033;
|
|
15
19
|
// Resolve the compiled mcp-server entry point relative to this file.
|
|
16
20
|
// When bundled as bundle.cjs, import.meta.url ends with .cjs → use mcp-server.cjs.
|
|
@@ -80,9 +84,114 @@ function extractOperations(text) {
|
|
|
80
84
|
}
|
|
81
85
|
return null;
|
|
82
86
|
}
|
|
87
|
+
// ── File System helpers ───────────────────────────────────────────────────────
|
|
88
|
+
const SUPPORTED_EXTS = new Set([".bpmn", ".dmn", ".form", ".md"]);
|
|
89
|
+
/** Expand a leading `~` to the user's home directory. */
|
|
90
|
+
function expandHome(p) {
|
|
91
|
+
if (p === "~" || p.startsWith("~/"))
|
|
92
|
+
return homedir() + p.slice(1);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
/** Reject any path that escapes the root via `..` or is outside it. */
|
|
96
|
+
function fsValidate(root, target) {
|
|
97
|
+
const normRoot = root.endsWith(sep) ? root : root + sep;
|
|
98
|
+
const normTarget = target + (statSync(target, { throwIfNoEntry: false })?.isDirectory() ? sep : "");
|
|
99
|
+
return (!target.includes("..") &&
|
|
100
|
+
(target === root || target.startsWith(normRoot) || normTarget.startsWith(normRoot)));
|
|
101
|
+
}
|
|
102
|
+
function sidecarPath(filePath) {
|
|
103
|
+
return join(dirname(filePath), ".bpmnkit", `${basename(filePath)}.meta.json`);
|
|
104
|
+
}
|
|
105
|
+
function readMeta(filePath) {
|
|
106
|
+
const sp = sidecarPath(filePath);
|
|
107
|
+
try {
|
|
108
|
+
if (!existsSync(sp))
|
|
109
|
+
return null;
|
|
110
|
+
return JSON.parse(readFileSync(sp, "utf8"));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function writeMeta(filePath, meta) {
|
|
117
|
+
const sp = sidecarPath(filePath);
|
|
118
|
+
const dir = dirname(sp);
|
|
119
|
+
if (!existsSync(dir))
|
|
120
|
+
mkdirSync(dir, { recursive: true });
|
|
121
|
+
writeFileSync(sp, JSON.stringify(meta, null, 2), "utf8");
|
|
122
|
+
}
|
|
123
|
+
function buildTree(root, dir) {
|
|
124
|
+
let entries;
|
|
125
|
+
try {
|
|
126
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const result = [];
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (entry.name.startsWith("."))
|
|
134
|
+
continue;
|
|
135
|
+
const abs = join(dir, entry.name);
|
|
136
|
+
const rel = relative(root, abs);
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
result.push({
|
|
139
|
+
name: entry.name,
|
|
140
|
+
relativePath: rel,
|
|
141
|
+
type: "dir",
|
|
142
|
+
children: buildTree(root, abs),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (entry.isFile()) {
|
|
146
|
+
const ext = extname(entry.name).toLowerCase();
|
|
147
|
+
if (!SUPPORTED_EXTS.has(ext))
|
|
148
|
+
continue;
|
|
149
|
+
const fileType = ext.slice(1);
|
|
150
|
+
result.push({ name: entry.name, relativePath: rel, type: "file", fileType });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
function collectFiles(root, dir) {
|
|
156
|
+
const results = [];
|
|
157
|
+
let entries;
|
|
158
|
+
try {
|
|
159
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
if (entry.name.startsWith("."))
|
|
166
|
+
continue;
|
|
167
|
+
const abs = join(dir, entry.name);
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
results.push(...collectFiles(root, abs));
|
|
170
|
+
}
|
|
171
|
+
else if (entry.isFile()) {
|
|
172
|
+
const ext = extname(entry.name).toLowerCase();
|
|
173
|
+
if (!SUPPORTED_EXTS.has(ext))
|
|
174
|
+
continue;
|
|
175
|
+
const rel = relative(root, abs);
|
|
176
|
+
const fileType = ext.slice(1);
|
|
177
|
+
let content = "";
|
|
178
|
+
try {
|
|
179
|
+
content = readFileSync(abs, "utf8");
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* skip unreadable files */
|
|
183
|
+
}
|
|
184
|
+
const meta = readMeta(abs);
|
|
185
|
+
const nameNoExt = basename(entry.name, extname(entry.name));
|
|
186
|
+
results.push({ relativePath: rel, name: nameNoExt, absPath: abs, fileType, content, meta });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
192
|
const server = http.createServer(async (req, res) => {
|
|
84
193
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
85
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
194
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
86
195
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Profile");
|
|
87
196
|
if (req.method === "OPTIONS") {
|
|
88
197
|
res.writeHead(204);
|
|
@@ -96,7 +205,48 @@ const server = http.createServer(async (req, res) => {
|
|
|
96
205
|
const names = available.map((a) => a.name);
|
|
97
206
|
console.log(`[server] /status → available: [${names.join(", ")}]`);
|
|
98
207
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
99
|
-
res.end(JSON.stringify({
|
|
208
|
+
res.end(JSON.stringify({
|
|
209
|
+
ready: available.length > 0,
|
|
210
|
+
backend: names[0] ?? null,
|
|
211
|
+
available: names,
|
|
212
|
+
workers: {
|
|
213
|
+
active: workerState.active,
|
|
214
|
+
jobTypes: workerState.jobTypes,
|
|
215
|
+
pollCount: workerState.pollCount,
|
|
216
|
+
lastError: workerState.lastError,
|
|
217
|
+
},
|
|
218
|
+
}));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// ── GET /worker-templates — built-in element templates for Studio ────────
|
|
222
|
+
if (url.pathname === "/worker-templates" && req.method === "GET") {
|
|
223
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
224
|
+
res.end(JSON.stringify(WORKER_TEMPLATES));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// ── Run history routes ────────────────────────────────────────────────────
|
|
228
|
+
if (url.pathname === "/run-history" && req.method === "GET") {
|
|
229
|
+
handleGetRunHistory(req, res);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (url.pathname === "/run-history" && req.method === "DELETE") {
|
|
233
|
+
handleDeleteRunHistory(req, res);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const rerunMatch = matchRerunHistoryRoute(req);
|
|
237
|
+
if (rerunMatch) {
|
|
238
|
+
await handleRerunHistory(req, res, rerunMatch.id);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const runHistoryMatch = matchRunHistoryRoute(req);
|
|
242
|
+
if (runHistoryMatch && req.method === "GET") {
|
|
243
|
+
handleGetRunHistoryDetail(req, res, runHistoryMatch.id);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// ── POST /webhooks/:processId — webhook trigger ───────────────────────────
|
|
247
|
+
const webhookMatch = matchWebhookRoute(req);
|
|
248
|
+
if (webhookMatch) {
|
|
249
|
+
await handleWebhook(req, res, webhookMatch.processId);
|
|
100
250
|
return;
|
|
101
251
|
}
|
|
102
252
|
if (url.pathname === "/chat" && req.method === "POST") {
|
|
@@ -915,6 +1065,245 @@ const server = http.createServer(async (req, res) => {
|
|
|
915
1065
|
res.end();
|
|
916
1066
|
return;
|
|
917
1067
|
}
|
|
1068
|
+
// ── File System API (/fs/*) ───────────────────────────────────────────────
|
|
1069
|
+
if (url.pathname.startsWith("/fs/")) {
|
|
1070
|
+
const fsPath = url.pathname.slice("/fs".length); // e.g. "/tree", "/list", "/read"
|
|
1071
|
+
// GET /fs/tree?root=<abs> — lightweight directory tree
|
|
1072
|
+
if (fsPath === "/tree" && req.method === "GET") {
|
|
1073
|
+
const rawRoot = url.searchParams.get("root") ?? "";
|
|
1074
|
+
const root = expandHome(rawRoot);
|
|
1075
|
+
const rootExists = root !== "" && existsSync(root);
|
|
1076
|
+
console.log(`[fs/tree] raw param: ${JSON.stringify(rawRoot)}`);
|
|
1077
|
+
console.log(`[fs/tree] expanded: ${JSON.stringify(root)}`);
|
|
1078
|
+
console.log(`[fs/tree] existsSync: ${rootExists}`);
|
|
1079
|
+
if (!rootExists) {
|
|
1080
|
+
console.log("[fs/tree] → 404 not found");
|
|
1081
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1082
|
+
res.end(JSON.stringify({ error: "Project root not found" }));
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const tree = buildTree(root, root);
|
|
1086
|
+
console.log(`[fs/tree] → 200 ok, ${tree.length} entries`);
|
|
1087
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1088
|
+
res.end(JSON.stringify(tree));
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
// GET /fs/list?root=<abs> — all files with content + metadata
|
|
1092
|
+
if (fsPath === "/list" && req.method === "GET") {
|
|
1093
|
+
const root = expandHome(url.searchParams.get("root") ?? "");
|
|
1094
|
+
if (!root || !existsSync(root)) {
|
|
1095
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1096
|
+
res.end(JSON.stringify({ error: "Project root not found" }));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const files = collectFiles(root, root);
|
|
1100
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1101
|
+
res.end(JSON.stringify(files));
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
// GET /fs/read?path=<abs> — read single file content
|
|
1105
|
+
if (fsPath === "/read" && req.method === "GET") {
|
|
1106
|
+
const filePath = expandHome(url.searchParams.get("path") ?? "");
|
|
1107
|
+
if (!filePath || !existsSync(filePath)) {
|
|
1108
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1109
|
+
res.end(JSON.stringify({ error: "File not found" }));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
let content;
|
|
1113
|
+
try {
|
|
1114
|
+
content = readFileSync(filePath, "utf8");
|
|
1115
|
+
}
|
|
1116
|
+
catch (err) {
|
|
1117
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1118
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1122
|
+
res.end(JSON.stringify({ content }));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
// POST /fs/write — write file (creates parent directories)
|
|
1126
|
+
if (fsPath === "/write" && req.method === "POST") {
|
|
1127
|
+
const body = await readBody(req);
|
|
1128
|
+
let filePath;
|
|
1129
|
+
let content;
|
|
1130
|
+
try {
|
|
1131
|
+
const parsed = JSON.parse(body);
|
|
1132
|
+
filePath = expandHome(parsed.path);
|
|
1133
|
+
content = parsed.content;
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1137
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (!filePath) {
|
|
1141
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1142
|
+
res.end(JSON.stringify({ error: "Missing path" }));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
try {
|
|
1146
|
+
const dir = dirname(filePath);
|
|
1147
|
+
if (!existsSync(dir))
|
|
1148
|
+
mkdirSync(dir, { recursive: true });
|
|
1149
|
+
writeFileSync(filePath, content, "utf8");
|
|
1150
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1151
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1152
|
+
}
|
|
1153
|
+
catch (err) {
|
|
1154
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1155
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
// DELETE /fs/file?path=<abs> — delete file and its sidecar
|
|
1160
|
+
if (fsPath === "/file" && req.method === "DELETE") {
|
|
1161
|
+
const filePath = expandHome(url.searchParams.get("path") ?? "");
|
|
1162
|
+
if (!filePath) {
|
|
1163
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1164
|
+
res.end(JSON.stringify({ error: "Missing path" }));
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
try {
|
|
1168
|
+
if (existsSync(filePath))
|
|
1169
|
+
unlinkSync(filePath);
|
|
1170
|
+
const sp = sidecarPath(filePath);
|
|
1171
|
+
if (existsSync(sp))
|
|
1172
|
+
unlinkSync(sp);
|
|
1173
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1174
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1175
|
+
}
|
|
1176
|
+
catch (err) {
|
|
1177
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1178
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1179
|
+
}
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
// POST /fs/move — rename/move file and its sidecar
|
|
1183
|
+
if (fsPath === "/move" && req.method === "POST") {
|
|
1184
|
+
const body = await readBody(req);
|
|
1185
|
+
let from;
|
|
1186
|
+
let to;
|
|
1187
|
+
try {
|
|
1188
|
+
const parsed = JSON.parse(body);
|
|
1189
|
+
from = expandHome(parsed.from);
|
|
1190
|
+
to = expandHome(parsed.to);
|
|
1191
|
+
}
|
|
1192
|
+
catch {
|
|
1193
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1194
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (!from || !to) {
|
|
1198
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1199
|
+
res.end(JSON.stringify({ error: "Missing from/to" }));
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
try {
|
|
1203
|
+
const toDir = dirname(to);
|
|
1204
|
+
if (!existsSync(toDir))
|
|
1205
|
+
mkdirSync(toDir, { recursive: true });
|
|
1206
|
+
renameSync(from, to);
|
|
1207
|
+
// Move sidecar if it exists
|
|
1208
|
+
const fromSidecar = sidecarPath(from);
|
|
1209
|
+
const toSidecar = sidecarPath(to);
|
|
1210
|
+
if (existsSync(fromSidecar)) {
|
|
1211
|
+
const toSidecarDir = dirname(toSidecar);
|
|
1212
|
+
if (!existsSync(toSidecarDir))
|
|
1213
|
+
mkdirSync(toSidecarDir, { recursive: true });
|
|
1214
|
+
renameSync(fromSidecar, toSidecar);
|
|
1215
|
+
}
|
|
1216
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1217
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1218
|
+
}
|
|
1219
|
+
catch (err) {
|
|
1220
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1221
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1222
|
+
}
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
// POST /fs/mkdir — create directory
|
|
1226
|
+
if (fsPath === "/mkdir" && req.method === "POST") {
|
|
1227
|
+
const body = await readBody(req);
|
|
1228
|
+
let dirPath;
|
|
1229
|
+
try {
|
|
1230
|
+
const parsed = JSON.parse(body);
|
|
1231
|
+
dirPath = expandHome(parsed.path);
|
|
1232
|
+
}
|
|
1233
|
+
catch {
|
|
1234
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1235
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (!dirPath) {
|
|
1239
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1240
|
+
res.end(JSON.stringify({ error: "Missing path" }));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
try {
|
|
1244
|
+
mkdirSync(dirPath, { recursive: true });
|
|
1245
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1246
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1247
|
+
}
|
|
1248
|
+
catch (err) {
|
|
1249
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1250
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1251
|
+
}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
// GET /fs/meta?path=<abs> — read sidecar metadata
|
|
1255
|
+
if (fsPath === "/meta" && req.method === "GET") {
|
|
1256
|
+
const filePath = expandHome(url.searchParams.get("path") ?? "");
|
|
1257
|
+
if (!filePath) {
|
|
1258
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1259
|
+
res.end(JSON.stringify({ error: "Missing path" }));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const meta = readMeta(filePath);
|
|
1263
|
+
if (!meta) {
|
|
1264
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1265
|
+
res.end(JSON.stringify({ error: "No metadata found" }));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1269
|
+
res.end(JSON.stringify(meta));
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
// POST /fs/meta — write sidecar metadata
|
|
1273
|
+
if (fsPath === "/meta" && req.method === "POST") {
|
|
1274
|
+
const body = await readBody(req);
|
|
1275
|
+
let filePath;
|
|
1276
|
+
let meta;
|
|
1277
|
+
try {
|
|
1278
|
+
const parsed = JSON.parse(body);
|
|
1279
|
+
filePath = expandHome(parsed.path);
|
|
1280
|
+
meta = parsed.meta;
|
|
1281
|
+
}
|
|
1282
|
+
catch {
|
|
1283
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1284
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (!filePath || !meta) {
|
|
1288
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1289
|
+
res.end(JSON.stringify({ error: "Missing path or meta" }));
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
writeMeta(filePath, meta);
|
|
1294
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1295
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1296
|
+
}
|
|
1297
|
+
catch (err) {
|
|
1298
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1299
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1300
|
+
}
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
res.writeHead(404);
|
|
1304
|
+
res.end("FS route not found");
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
918
1307
|
// ── ALL /api/* — transparent Camunda API proxy ─────────────────────────────
|
|
919
1308
|
if (url.pathname.startsWith("/api/")) {
|
|
920
1309
|
const profileName = req.headers["x-profile"];
|
|
@@ -966,6 +1355,69 @@ const server = http.createServer(async (req, res) => {
|
|
|
966
1355
|
res.end(await upstream.text());
|
|
967
1356
|
return;
|
|
968
1357
|
}
|
|
1358
|
+
// ── POST /secrets/check — bulk existence check for secret names ───────────
|
|
1359
|
+
if (url.pathname === "/secrets/check" && req.method === "POST") {
|
|
1360
|
+
const body = await readBody(req);
|
|
1361
|
+
let names = [];
|
|
1362
|
+
try {
|
|
1363
|
+
names = (JSON.parse(body).names ?? []);
|
|
1364
|
+
}
|
|
1365
|
+
catch {
|
|
1366
|
+
/* treat as empty list */
|
|
1367
|
+
}
|
|
1368
|
+
const result = {};
|
|
1369
|
+
for (const name of names) {
|
|
1370
|
+
result[name] = process.env[name] !== undefined;
|
|
1371
|
+
}
|
|
1372
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1373
|
+
res.end(JSON.stringify(result));
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
// ── POST /secrets/:name — resolve and encrypt a secret for the client ─────
|
|
1377
|
+
if (url.pathname.startsWith("/secrets/") && req.method === "POST") {
|
|
1378
|
+
const name = url.pathname.slice("/secrets/".length);
|
|
1379
|
+
if (!name) {
|
|
1380
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1381
|
+
res.end(JSON.stringify({ error: "Secret name required" }));
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const secretValue = process.env[name];
|
|
1385
|
+
if (secretValue === undefined) {
|
|
1386
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1387
|
+
res.end(JSON.stringify({ error: `Secret "${name}" is not configured` }));
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const body = await readBody(req);
|
|
1391
|
+
let keyBase64;
|
|
1392
|
+
try {
|
|
1393
|
+
const parsed = JSON.parse(body);
|
|
1394
|
+
if (typeof parsed.key !== "string" || !parsed.key)
|
|
1395
|
+
throw new Error("missing key");
|
|
1396
|
+
keyBase64 = parsed.key;
|
|
1397
|
+
}
|
|
1398
|
+
catch {
|
|
1399
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1400
|
+
res.end(JSON.stringify({ error: "Body must be { key: string } with a base64 AES-256 key" }));
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
try {
|
|
1404
|
+
const rawKey = Buffer.from(keyBase64, "base64");
|
|
1405
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey("raw", rawKey, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
1406
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
1407
|
+
const encrypted = await globalThis.crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, new TextEncoder().encode(secretValue));
|
|
1408
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1409
|
+
res.end(JSON.stringify({
|
|
1410
|
+
encrypted: Buffer.from(encrypted).toString("base64"),
|
|
1411
|
+
iv: Buffer.from(iv).toString("base64"),
|
|
1412
|
+
}));
|
|
1413
|
+
}
|
|
1414
|
+
catch (err) {
|
|
1415
|
+
console.error(`[secrets] encryption failed for "${name}":`, err);
|
|
1416
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1417
|
+
res.end(JSON.stringify({ error: "Encryption failed" }));
|
|
1418
|
+
}
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
969
1421
|
// ── POST /http-request — CORS bypass for wasm worker REST connectors ─────
|
|
970
1422
|
if (url.pathname === "/http-request" && req.method === "POST") {
|
|
971
1423
|
const body = await readBody(req);
|
|
@@ -1012,5 +1464,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1012
1464
|
server.listen(PORT, () => {
|
|
1013
1465
|
console.log(`BPMN Kit AI Server running at http://localhost:${PORT}`);
|
|
1014
1466
|
console.log("Press Ctrl+C to stop");
|
|
1467
|
+
startWorkerDaemon();
|
|
1468
|
+
startTriggers();
|
|
1015
1469
|
});
|
|
1016
1470
|
//# sourceMappingURL=index.js.map
|