@bpmnkit/proxy 0.0.17 → 0.0.22

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/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({ ready: available.length > 0, backend: names[0] ?? null, available: names }));
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);
@@ -1009,8 +1461,16 @@ const server = http.createServer(async (req, res) => {
1009
1461
  res.writeHead(404);
1010
1462
  res.end("Not Found");
1011
1463
  });
1012
- server.listen(PORT, () => {
1013
- console.log(`BPMN Kit AI Server running at http://localhost:${PORT}`);
1014
- console.log("Press Ctrl+C to stop");
1015
- });
1464
+ export function startServer(port = PORT) {
1465
+ server.listen(port, () => {
1466
+ console.log(`BPMN Kit AI Server running at http://localhost:${port}`);
1467
+ console.log("Press Ctrl+C to stop");
1468
+ startWorkerDaemon();
1469
+ startTriggers();
1470
+ });
1471
+ }
1472
+ // Auto-start when run directly as a binary (not imported as a library)
1473
+ const __isMain = fileURLToPath(import.meta.url) === process.argv[1];
1474
+ if (__isMain)
1475
+ startServer();
1016
1476
  //# sourceMappingURL=index.js.map