@muxoai/platform 1.2.2 → 1.3.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/server.js CHANGED
@@ -1,9 +1,10 @@
1
+ import crypto from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import http from "node:http";
3
4
  import path from "node:path";
4
5
  import { getConfig, ROOT_DIR } from "./config.js";
5
6
  import { MuxoEngine } from "./engine.js";
6
- import { handleJsonRpcMcpRequest, MUXO_MCP_TOOLS } from "./mcp.js";
7
+ import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.js";
7
8
  import { MuxoStore } from "./store.js";
8
9
  import { renderConsoleHtml, renderLandingHtml } from "./ui/console-html.js";
9
10
  function renderSkillMarkdown(baseUrl) {
@@ -82,13 +83,15 @@ async function readBody(req) {
82
83
  }
83
84
  return Buffer.concat(chunks).toString("utf8");
84
85
  }
85
- function sendJson(res, status, payload) {
86
+ function sendJson(res, status, payload, extraHeaders) {
86
87
  const body = JSON.stringify(payload, null, 2);
87
88
  res.writeHead(status, {
88
89
  "Content-Type": "application/json; charset=utf-8",
89
90
  "Access-Control-Allow-Origin": "*",
90
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
91
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id",
92
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
91
93
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
94
+ ...(extraHeaders || {}),
92
95
  });
93
96
  res.end(body);
94
97
  }
@@ -97,6 +100,23 @@ export function createMuxoServer(customConfig) {
97
100
  const store = new MuxoStore(config.dbPath, config.vaultMasterKey);
98
101
  const engine = new MuxoEngine(config, store);
99
102
  const bootstrap = store.ensureDefaultWorkspace();
103
+ // OAuth 2.1 DCR + PKCE Authorization Code store for OpenAI Plugin / MCP clients
104
+ const oauthClients = new Map();
105
+ oauthClients.set("muxo-openai-plugin", {
106
+ client_id: "muxo-openai-plugin",
107
+ client_name: "Muxo Official OpenAI Plugin",
108
+ redirect_uris: [
109
+ "https://chatgpt.com/connector_platform_oauth_redirect",
110
+ "https://platform.openai.com/apps-manage/oauth",
111
+ "http://127.0.0.1:3118/oauth/callback",
112
+ "http://localhost:3118/oauth/callback",
113
+ ],
114
+ grant_types: ["authorization_code", "refresh_token"],
115
+ response_types: ["code"],
116
+ token_endpoint_auth_method: "none",
117
+ client_id_issued_at: Math.floor(Date.now() / 1000),
118
+ });
119
+ const oauthCodes = new Map();
100
120
  const resolveContext = (req) => {
101
121
  const authHeader = String(req.headers.authorization || "").trim();
102
122
  if (authHeader.toLowerCase().startsWith("bearer ")) {
@@ -134,7 +154,7 @@ export function createMuxoServer(customConfig) {
134
154
  ok: true,
135
155
  status: "ready",
136
156
  service: "muxo-platform",
137
- version: "1.2.0",
157
+ version: "1.3.0",
138
158
  environment: "production",
139
159
  live_execution: true,
140
160
  customer_billing: true,
@@ -167,8 +187,9 @@ export function createMuxoServer(customConfig) {
167
187
  if ((pathname === "/.well-known/oauth-protected-resource" ||
168
188
  pathname === "/.well-known/oauth-protected-resource/mcp") &&
169
189
  req.method === "GET") {
190
+ const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
170
191
  const requestOrigin = req.headers.host
171
- ? `http://${req.headers.host}`
192
+ ? `${proto}://${req.headers.host}`
172
193
  : config.baseUrl;
173
194
  const resource = pathname === "/.well-known/oauth-protected-resource/mcp"
174
195
  ? `${requestOrigin}/mcp`
@@ -183,13 +204,229 @@ export function createMuxoServer(customConfig) {
183
204
  });
184
205
  return;
185
206
  }
207
+ if ((pathname === "/.well-known/oauth-authorization-server" ||
208
+ pathname === "/.well-known/openid-configuration" ||
209
+ pathname === "/.well-known/oauth-authorization-server/mcp") &&
210
+ req.method === "GET") {
211
+ const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
212
+ const requestOrigin = req.headers.host
213
+ ? `${proto}://${req.headers.host}`
214
+ : config.baseUrl;
215
+ sendJson(res, 200, {
216
+ issuer: requestOrigin,
217
+ authorization_endpoint: `${requestOrigin}/oauth/authorize`,
218
+ token_endpoint: `${requestOrigin}/oauth/token`,
219
+ registration_endpoint: `${requestOrigin}/oauth/register`,
220
+ userinfo_endpoint: `${requestOrigin}/oauth/userinfo`,
221
+ jwks_uri: `${requestOrigin}/.well-known/jwks.json`,
222
+ scopes_supported: [
223
+ "muxo:mcp",
224
+ "muxo:read",
225
+ "muxo:execute",
226
+ "muxo:provision",
227
+ "openid",
228
+ "profile",
229
+ "email",
230
+ ],
231
+ response_types_supported: ["code"],
232
+ grant_types_supported: [
233
+ "authorization_code",
234
+ "refresh_token",
235
+ "client_credentials",
236
+ ],
237
+ token_endpoint_auth_methods_supported: [
238
+ "none",
239
+ "client_secret_post",
240
+ "client_secret_basic",
241
+ ],
242
+ code_challenge_methods_supported: ["S256", "plain"],
243
+ client_id_metadata_document_supported: true,
244
+ service_documentation: `${config.docsUrl}/quickstart`,
245
+ });
246
+ return;
247
+ }
248
+ if (pathname === "/oauth/register" && req.method === "POST") {
249
+ const body = JSON.parse((await readBody(req)) || "{}");
250
+ const clientId = typeof body.client_id === "string" && body.client_id.trim()
251
+ ? body.client_id.trim()
252
+ : `muxo_client_${crypto.randomBytes(8).toString("hex")}`;
253
+ const clientRecord = {
254
+ client_id: clientId,
255
+ client_name: String(body.client_name || "OpenAI Plugin MCP Client"),
256
+ redirect_uris: Array.isArray(body.redirect_uris)
257
+ ? body.redirect_uris.map(String)
258
+ : [
259
+ "https://chatgpt.com/connector_platform_oauth_redirect",
260
+ "http://127.0.0.1:3118/oauth/callback",
261
+ ],
262
+ grant_types: Array.isArray(body.grant_types)
263
+ ? body.grant_types.map(String)
264
+ : ["authorization_code", "refresh_token"],
265
+ response_types: Array.isArray(body.response_types)
266
+ ? body.response_types.map(String)
267
+ : ["code"],
268
+ token_endpoint_auth_method: String(body.token_endpoint_auth_method || "none"),
269
+ client_id_issued_at: Math.floor(Date.now() / 1000),
270
+ };
271
+ oauthClients.set(clientId, clientRecord);
272
+ sendJson(res, 201, clientRecord);
273
+ return;
274
+ }
275
+ if (pathname === "/oauth/authorize" &&
276
+ (req.method === "GET" || req.method === "POST")) {
277
+ const bodyParams = req.method === "POST"
278
+ ? new URLSearchParams(await readBody(req))
279
+ : url.searchParams;
280
+ const clientId = bodyParams.get("client_id") ||
281
+ url.searchParams.get("client_id") ||
282
+ "muxo-openai-plugin";
283
+ const redirectUri = bodyParams.get("redirect_uri") ||
284
+ url.searchParams.get("redirect_uri") ||
285
+ "http://127.0.0.1:3118/oauth/callback";
286
+ const stateParam = bodyParams.get("state") || url.searchParams.get("state") || "";
287
+ const scope = bodyParams.get("scope") ||
288
+ url.searchParams.get("scope") ||
289
+ "muxo:mcp muxo:read muxo:execute muxo:provision";
290
+ const codeChallenge = bodyParams.get("code_challenge") ||
291
+ url.searchParams.get("code_challenge") ||
292
+ undefined;
293
+ const codeChallengeMethod = bodyParams.get("code_challenge_method") ||
294
+ url.searchParams.get("code_challenge_method") ||
295
+ "S256";
296
+ const code = `muxo_auth_code_${crypto.randomBytes(16).toString("hex")}`;
297
+ const issuedKey = bootstrap.defaultApiKey ||
298
+ store.createApiKey({
299
+ workspaceId: bootstrap.workspace.id,
300
+ name: `OAuth Client (${clientId})`,
301
+ environment: "live",
302
+ }).secretKey;
303
+ oauthCodes.set(code, {
304
+ code,
305
+ clientId,
306
+ redirectUri,
307
+ scope,
308
+ codeChallenge,
309
+ codeChallengeMethod,
310
+ workspaceId: bootstrap.workspace.id,
311
+ apiKey: issuedKey,
312
+ expiresAt: Date.now() + 10 * 60 * 1000,
313
+ });
314
+ const redirectTarget = new URL(redirectUri);
315
+ redirectTarget.searchParams.set("code", code);
316
+ if (stateParam)
317
+ redirectTarget.searchParams.set("state", stateParam);
318
+ if (url.searchParams.get("format") === "json" ||
319
+ String(req.headers.accept || "").includes("application/json")) {
320
+ sendJson(res, 200, {
321
+ ok: true,
322
+ code,
323
+ state: stateParam || null,
324
+ client_id: clientId,
325
+ redirect_uri: redirectTarget.toString(),
326
+ });
327
+ return;
328
+ }
329
+ res.writeHead(302, {
330
+ Location: redirectTarget.toString(),
331
+ "Access-Control-Allow-Origin": "*",
332
+ });
333
+ res.end();
334
+ return;
335
+ }
336
+ if (pathname === "/oauth/token" && req.method === "POST") {
337
+ const rawBody = await readBody(req);
338
+ const contentType = String(req.headers["content-type"] || "");
339
+ const params = {};
340
+ if (contentType.includes("application/json")) {
341
+ const parsed = JSON.parse(rawBody || "{}");
342
+ for (const [k, v] of Object.entries(parsed)) {
343
+ if (v !== undefined && v !== null)
344
+ params[k] = String(v);
345
+ }
346
+ }
347
+ else {
348
+ const sp = new URLSearchParams(rawBody);
349
+ for (const [k, v] of sp.entries()) {
350
+ params[k] = v;
351
+ }
352
+ }
353
+ const grantType = params.grant_type || "authorization_code";
354
+ const code = params.code || "";
355
+ const codeVerifier = params.code_verifier || "";
356
+ let accessToken = bootstrap.defaultApiKey || "";
357
+ let scope = params.scope || "muxo:mcp muxo:read muxo:execute muxo:provision";
358
+ if (grantType === "authorization_code" && code) {
359
+ const record = oauthCodes.get(code);
360
+ if (!record || record.expiresAt < Date.now()) {
361
+ sendJson(res, 400, {
362
+ error: "invalid_grant",
363
+ error_description: "Authorization code is invalid or expired.",
364
+ });
365
+ return;
366
+ }
367
+ if (record.codeChallenge) {
368
+ if (!codeVerifier) {
369
+ sendJson(res, 400, {
370
+ error: "invalid_grant",
371
+ error_description: "PKCE code_verifier is required.",
372
+ });
373
+ return;
374
+ }
375
+ const method = (record.codeChallengeMethod || "S256").toUpperCase();
376
+ const computed = method === "S256"
377
+ ? crypto
378
+ .createHash("sha256")
379
+ .update(codeVerifier)
380
+ .digest("base64url")
381
+ : codeVerifier;
382
+ if (computed !== record.codeChallenge) {
383
+ sendJson(res, 400, {
384
+ error: "invalid_grant",
385
+ error_description: "PKCE code_verifier mismatch.",
386
+ });
387
+ return;
388
+ }
389
+ }
390
+ accessToken = record.apiKey;
391
+ scope = record.scope;
392
+ oauthCodes.delete(code);
393
+ }
394
+ else if (!accessToken) {
395
+ accessToken = store.createApiKey({
396
+ workspaceId: bootstrap.workspace.id,
397
+ name: "OAuth Token Key",
398
+ environment: "live",
399
+ }).secretKey;
400
+ }
401
+ sendJson(res, 200, {
402
+ access_token: accessToken,
403
+ token_type: "Bearer",
404
+ expires_in: 3600,
405
+ refresh_token: `muxo_rt_${crypto.randomBytes(16).toString("hex")}`,
406
+ scope,
407
+ });
408
+ return;
409
+ }
410
+ if (pathname === "/oauth/userinfo" && req.method === "GET") {
411
+ const ctx = resolveContext(req);
412
+ sendJson(res, 200, {
413
+ sub: ctx.user.id,
414
+ id: ctx.user.id,
415
+ name: ctx.user.name,
416
+ email: ctx.user.email,
417
+ workspace_id: ctx.workspace.id,
418
+ workspace_name: ctx.workspace.name,
419
+ tier: ctx.workspace.tier,
420
+ });
421
+ return;
422
+ }
186
423
  if ((pathname === "/api/openapi.json" || pathname === "/openapi.json") &&
187
424
  req.method === "GET") {
188
425
  sendJson(res, 200, {
189
426
  openapi: "3.1.0",
190
427
  info: {
191
428
  title: "Muxo Unified Agent Execution, Infrastructure, Memory & Governance API",
192
- version: "1.2.0",
429
+ version: "1.3.0",
193
430
  description: "One governed boundary over Connected Apps (Composio SDK), Instant Tools (Composio Instant), Cloud Infrastructure (Stripe Projects), Supermemory v5 Brain, and Microsoft Agent Governance Toolkit.",
194
431
  },
195
432
  servers: [{ url: config.baseUrl }],
@@ -273,10 +510,12 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
273
510
  res.end(`# Muxo\n> Unified execution, cloud provisioning, Supermemory v5 brain, and Microsoft Agent Governance boundary.\n\n- Skill Document: ${config.baseUrl}/skill.md\n- Auth Guide: ${config.baseUrl}/auth.md\n- OpenAPI 3.1 Spec: ${config.baseUrl}/api/openapi.json\n- MCP Endpoint: ${config.baseUrl}/mcp\n- OAuth Callback Handoff: ${config.baseUrl}/oauth/callback\n- Plugin Marketplace (Codex/OpenAI): ${config.baseUrl}/.agents/plugins/marketplace.json\n- Plugin Marketplace (Claude/Droid): ${config.baseUrl}/.claude-plugin/marketplace.json\n- Console (app.muxo.ai): ${config.baseUrl}/\n- Landing & Provider Canvas (muxo.ai): ${config.baseUrl}/landing\n- Documentation (docs.muxo.ai): ${config.docsUrl}\n`);
274
511
  return;
275
512
  }
276
- // Serve Installable Muxo Plugin Manifests & Metadata
513
+ // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude)
277
514
  const pluginJsonRoutes = {
278
515
  "/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
279
516
  "/.claude-plugin/marketplace.json": path.join(ROOT_DIR, ".claude-plugin", "marketplace.json"),
517
+ "/plugins/muxo/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"),
518
+ "/plugins/muxo/mcp.json": path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"),
280
519
  "/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
281
520
  "/plugins/muxo/.claude-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".claude-plugin", "plugin.json"),
282
521
  "/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
@@ -289,15 +528,66 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
289
528
  return;
290
529
  }
291
530
  }
531
+ const pluginTextRoutes = {
532
+ "/plugins/muxo/skills/muxo/SKILL.md": {
533
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "SKILL.md"),
534
+ contentType: "text/markdown; charset=utf-8",
535
+ },
536
+ "/plugins/muxo/skills/muxo/references/cli.md": {
537
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "cli.md"),
538
+ contentType: "text/markdown; charset=utf-8",
539
+ },
540
+ "/plugins/muxo/skills/muxo/references/mcp.md": {
541
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "mcp.md"),
542
+ contentType: "text/markdown; charset=utf-8",
543
+ },
544
+ "/plugins/muxo/skills/setup/SKILL.md": {
545
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "setup", "SKILL.md"),
546
+ contentType: "text/markdown; charset=utf-8",
547
+ },
548
+ "/.codex/config.toml": {
549
+ file: path.join(ROOT_DIR, ".codex", "config.toml"),
550
+ contentType: "text/plain; charset=utf-8",
551
+ },
552
+ };
553
+ if (pluginTextRoutes[pathname] && req.method === "GET") {
554
+ const entry = pluginTextRoutes[pathname];
555
+ if (fs.existsSync(entry.file)) {
556
+ res.writeHead(200, {
557
+ "Content-Type": entry.contentType,
558
+ "Access-Control-Allow-Origin": "*",
559
+ });
560
+ res.end(fs.readFileSync(entry.file, "utf8"));
561
+ return;
562
+ }
563
+ }
292
564
  if (pathname === "/api/v1/plugin" && req.method === "GET") {
565
+ const portableManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"), "utf8"));
566
+ const mcpConfig = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"), "utf8"));
293
567
  const codexManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"), "utf8"));
294
568
  sendJson(res, 200, {
295
569
  ok: true,
296
- plugin: codexManifest,
570
+ plugin: portableManifest,
571
+ codexPlugin: codexManifest,
572
+ mcp: mcpConfig,
573
+ skills: getMuxoMcpSkills().map((s) => ({
574
+ name: s.name,
575
+ description: s.description,
576
+ uri: s.uri,
577
+ version: s.version,
578
+ files: s.files.length,
579
+ })),
297
580
  marketplaces: {
298
581
  openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
299
582
  claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
300
583
  },
584
+ manifests: {
585
+ portablePluginJson: `${config.baseUrl}/plugins/muxo/plugin.json`,
586
+ portableMcpJson: `${config.baseUrl}/plugins/muxo/mcp.json`,
587
+ onboardingSkill: `${config.baseUrl}/plugins/muxo/skills/setup/SKILL.md`,
588
+ codexPluginJson: `${config.baseUrl}/plugins/muxo/.codex-plugin/plugin.json`,
589
+ codexConfigToml: `${config.baseUrl}/.codex/config.toml`,
590
+ },
301
591
  installCommands: {
302
592
  codex: "codex plugin add muxo@muxo",
303
593
  cliSignup: "muxo login --signup --name my-agent",
@@ -445,18 +735,66 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
445
735
  });
446
736
  return;
447
737
  }
448
- // Streamable HTTP MCP Endpoint (POST /mcp)
738
+ // Streamable HTTP MCP Endpoint (POST /mcp, GET /mcp, DELETE /mcp)
739
+ if (pathname === "/mcp" && req.method === "GET") {
740
+ const accept = String(req.headers.accept || "");
741
+ const sessionId = String(req.headers["mcp-session-id"] || "") ||
742
+ `mcp_sess_${crypto.randomBytes(8).toString("hex")}`;
743
+ if (accept.includes("text/event-stream")) {
744
+ res.writeHead(200, {
745
+ "Content-Type": "text/event-stream; charset=utf-8",
746
+ "Cache-Control": "no-cache",
747
+ Connection: "keep-alive",
748
+ "Mcp-Session-Id": sessionId,
749
+ "Access-Control-Allow-Origin": "*",
750
+ });
751
+ res.write(`event: endpoint\ndata: ${JSON.stringify({ uri: `${config.baseUrl}/mcp`, sessionId })}\n\n`);
752
+ res.end();
753
+ return;
754
+ }
755
+ sendJson(res, 200, {
756
+ ok: true,
757
+ transport: "streamable-http",
758
+ protocolVersion: "2025-06-18",
759
+ serverInfo: {
760
+ name: "muxo-mcp-server",
761
+ version: "1.3.0",
762
+ },
763
+ instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
764
+ toolsCount: MUXO_MCP_TOOLS.length,
765
+ skillsCount: getMuxoMcpSkills().length,
766
+ });
767
+ return;
768
+ }
769
+ if (pathname === "/mcp" && req.method === "DELETE") {
770
+ res.writeHead(204, {
771
+ "Access-Control-Allow-Origin": "*",
772
+ });
773
+ res.end();
774
+ return;
775
+ }
449
776
  if (pathname === "/mcp" && req.method === "POST") {
450
777
  const ctx = resolveContext(req);
778
+ const sessionId = String(req.headers["mcp-session-id"] || "") ||
779
+ `mcp_sess_${ctx.workspace.id}`;
451
780
  const raw = await readBody(req);
452
- const rpc = JSON.parse(raw || "{}");
781
+ const parsed = JSON.parse(raw || "{}");
782
+ if (Array.isArray(parsed)) {
783
+ const responses = (await Promise.all(parsed.map((item) => handleJsonRpcMcpRequest(engine, ctx, (item || {}))))).filter((r) => r !== null);
784
+ sendJson(res, 200, responses, { "Mcp-Session-Id": sessionId });
785
+ return;
786
+ }
787
+ const rpc = parsed;
453
788
  const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
454
789
  if (!response) {
455
- res.writeHead(202);
790
+ res.writeHead(202, {
791
+ "Access-Control-Allow-Origin": "*",
792
+ "Mcp-Session-Id": sessionId,
793
+ });
456
794
  res.end();
457
795
  return;
458
796
  }
459
- sendJson(res, 200, response);
797
+ sendJson(res, 200, response, { "Mcp-Session-Id": sessionId });
460
798
  return;
461
799
  }
462
800
  // Auth Endpoints
@@ -500,6 +838,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
500
838
  landingUrl: `${config.baseUrl}/landing`,
501
839
  },
502
840
  apiKeys: store.listApiKeys(ctx.workspace.id),
841
+ byokKeys: store.listByokKeys(ctx.workspace.id),
503
842
  defaultApiKey: bootstrap.defaultApiKey || null,
504
843
  connections: store.listConnections(ctx.workspace.id),
505
844
  resources: store.listResources(ctx.workspace.id),
@@ -619,8 +958,16 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
619
958
  }
620
959
  if (pathname === "/api/v1/connections/wait" && req.method === "POST") {
621
960
  const body = JSON.parse((await readBody(req)) || "{}");
961
+ const provider = String(body.provider || "");
962
+ if (body.simulateCallback === true || body.simulate_callback === true) {
963
+ await engine.handleOAuthCallback({
964
+ workspaceId: ctx.workspace.id,
965
+ provider,
966
+ status: "success",
967
+ });
968
+ }
622
969
  const result = await engine.waitForConnection(ctx, {
623
- provider: String(body.provider || ""),
970
+ provider,
624
971
  timeoutSeconds: body.timeoutSeconds !== undefined
625
972
  ? Number(body.timeoutSeconds)
626
973
  : body.timeout_seconds !== undefined
@@ -712,7 +1059,91 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
712
1059
  sendJson(res, 200, { ok: true, ...verified });
713
1060
  return;
714
1061
  }
1062
+ // BYOK (Bring Your Own Key) Encrypted Vault Management
1063
+ if (pathname === "/api/v1/byok" && req.method === "GET") {
1064
+ sendJson(res, 200, {
1065
+ ok: true,
1066
+ workspaceId: ctx.workspace.id,
1067
+ keys: store.listByokKeys(ctx.workspace.id),
1068
+ });
1069
+ return;
1070
+ }
1071
+ if (pathname === "/api/v1/byok" && req.method === "POST") {
1072
+ const body = JSON.parse((await readBody(req)) || "{}");
1073
+ const provider = String(body.provider || "").trim();
1074
+ const secretKey = String(body.secretKey || body.apiKey || body.key || "").trim();
1075
+ if (!provider || !secretKey) {
1076
+ sendJson(res, 400, {
1077
+ ok: false,
1078
+ error: "Both 'provider' and 'secretKey' are required",
1079
+ });
1080
+ return;
1081
+ }
1082
+ const record = store.setByokKey(ctx.workspace.id, provider, secretKey);
1083
+ sendJson(res, 201, {
1084
+ ok: true,
1085
+ key: record,
1086
+ keys: store.listByokKeys(ctx.workspace.id),
1087
+ });
1088
+ return;
1089
+ }
1090
+ if (pathname.startsWith("/api/v1/byok/") && req.method === "DELETE") {
1091
+ const provider = decodeURIComponent(pathname.slice("/api/v1/byok/".length));
1092
+ const deleted = store.deleteByokKey(ctx.workspace.id, provider);
1093
+ sendJson(res, 200, {
1094
+ ok: true,
1095
+ deleted,
1096
+ provider,
1097
+ keys: store.listByokKeys(ctx.workspace.id),
1098
+ });
1099
+ return;
1100
+ }
1101
+ // Natural Language Intent & Governed Execution Endpoint
1102
+ if ((pathname === "/api/v1/do" || pathname === "/v1/do") &&
1103
+ req.method === "POST") {
1104
+ const body = JSON.parse((await readBody(req)) || "{}");
1105
+ const prompt = String(body.prompt || body.instruction || body.query || body.command || "").trim();
1106
+ if (!prompt) {
1107
+ sendJson(res, 400, {
1108
+ ok: false,
1109
+ error: "Missing 'prompt' or 'instruction' in request body",
1110
+ });
1111
+ return;
1112
+ }
1113
+ const result = await engine.doNaturalLanguage(ctx, {
1114
+ prompt,
1115
+ input: body.input && typeof body.input === "object"
1116
+ ? body.input
1117
+ : undefined,
1118
+ autoApprove: body.autoApprove !== false,
1119
+ dryRun: body.dryRun === true,
1120
+ });
1121
+ sendJson(res, 200, result);
1122
+ return;
1123
+ }
715
1124
  // Live Stripe Billing, Credit Top-Up & Supplier Settlement Ledger
1125
+ if (pathname === "/api/v1/billing/status" && req.method === "GET") {
1126
+ const ws = store.getWorkspace(ctx.workspace.id);
1127
+ const settlementSummary = store.getSupplierSettlementSummary(ctx.workspace.id);
1128
+ const byokKeys = store.listByokKeys(ctx.workspace.id);
1129
+ sendJson(res, 200, {
1130
+ ok: true,
1131
+ workspaceId: ws.id,
1132
+ workspace: ws,
1133
+ tier: ws.tier,
1134
+ creditsBalance: ws.creditsBalance,
1135
+ creditBalance: ws.creditsBalance,
1136
+ monthlyActionsUsed: ws.monthlyActionsUsed,
1137
+ monthlyActionsLimit: ws.monthlyActionsLimit,
1138
+ stripeConfigured: engine.stripeBilling.isConfigured,
1139
+ stripeCustomerId: ws.stripeCustomerId,
1140
+ stripeSubscriptionId: ws.stripeSubscriptionId,
1141
+ settlementSummary,
1142
+ byokKeys,
1143
+ entries: store.listLedgerEntries(ctx.workspace.id, 25),
1144
+ });
1145
+ return;
1146
+ }
716
1147
  if (pathname === "/api/v1/billing/ledger" && req.method === "GET") {
717
1148
  sendJson(res, 200, {
718
1149
  ok: true,
@@ -751,21 +1182,51 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
751
1182
  });
752
1183
  return;
753
1184
  }
1185
+ if (pathname === "/api/v1/billing/upgrade" && req.method === "POST") {
1186
+ const body = JSON.parse((await readBody(req)) || "{}");
1187
+ const referenceId = String(body.referenceId || "").trim() ||
1188
+ `upgrade_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1189
+ const bonusCredits = body.bonusCredits !== undefined ? Number(body.bonusCredits) : 1000;
1190
+ const updatedWorkspace = store.upgradeWorkspaceToStarter({
1191
+ workspaceId: ctx.workspace.id,
1192
+ stripeCustomerId: ctx.workspace.stripeCustomerId ||
1193
+ `cus_muxo_${ctx.workspace.id.slice(3, 11)}`,
1194
+ stripeSubscriptionId: ctx.workspace.stripeSubscriptionId ||
1195
+ `sub_muxo_${referenceId.slice(-10)}`,
1196
+ referenceId,
1197
+ bonusCredits,
1198
+ });
1199
+ sendJson(res, 200, {
1200
+ ok: true,
1201
+ tier: updatedWorkspace.tier,
1202
+ creditsBalance: updatedWorkspace.creditsBalance,
1203
+ creditBalance: updatedWorkspace.creditsBalance,
1204
+ workspace: updatedWorkspace,
1205
+ settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
1206
+ });
1207
+ return;
1208
+ }
754
1209
  if (pathname === "/api/v1/billing/checkout" && req.method === "POST") {
755
1210
  const body = JSON.parse((await readBody(req)) || "{}");
756
1211
  const kind = body.kind === "credit_topup" ? "credit_topup" : "starter_subscription";
1212
+ const credits = body.credits ? Number(body.credits) : 1000;
757
1213
  const session = await engine.stripeBilling.createCheckoutSession({
758
1214
  workspace: ctx.workspace,
759
1215
  customerEmail: ctx.user.email,
760
1216
  kind,
761
- returnBaseUrl: config.baseUrl,
1217
+ credits,
1218
+ returnBaseUrl: body.returnBaseUrl ? String(body.returnBaseUrl) : config.baseUrl,
762
1219
  });
763
1220
  sendJson(res, 200, { ok: true, ...session });
764
1221
  return;
765
1222
  }
766
1223
  if (pathname === "/api/v1/billing/verify-session" && req.method === "POST") {
767
1224
  const body = JSON.parse((await readBody(req)) || "{}");
768
- const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(body.sessionId || ""));
1225
+ const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(body.sessionId || ""), ctx.workspace.id, body.kind === "credit_topup"
1226
+ ? "credit_topup"
1227
+ : body.kind === "starter_subscription"
1228
+ ? "starter_subscription"
1229
+ : undefined);
769
1230
  sendJson(res, 200, { ok: true, ...verified });
770
1231
  return;
771
1232
  }