@muxoai/sdk 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/.agents/plugins/marketplace.json +22 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.codex/config.toml +5 -0
- package/ARCHITECTURE.md +111 -0
- package/README.md +57 -28
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +894 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +89 -0
- package/dist/engine.d.ts +156 -0
- package/dist/engine.js +876 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mcp.d.ts +45 -0
- package/dist/mcp.js +914 -0
- package/dist/nl.d.ts +34 -0
- package/dist/nl.js +577 -0
- package/dist/providers/composio-instant.d.ts +14 -0
- package/dist/providers/composio-instant.js +814 -0
- package/dist/providers/composio-sdk.d.ts +41 -0
- package/dist/providers/composio-sdk.js +572 -0
- package/dist/providers/microsoft-governance.d.ts +66 -0
- package/dist/providers/microsoft-governance.js +415 -0
- package/dist/providers/stripe-billing.d.ts +41 -0
- package/dist/providers/stripe-billing.js +372 -0
- package/dist/providers/stripe-projects.d.ts +53 -0
- package/dist/providers/stripe-projects.js +636 -0
- package/dist/providers/supermemory.d.ts +64 -0
- package/dist/providers/supermemory.js +224 -0
- package/dist/sdk.d.ts +84 -1
- package/dist/sdk.js +53 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +1277 -0
- package/dist/store.d.ts +216 -0
- package/dist/store.js +1449 -0
- package/dist/types.d.ts +14 -2
- package/dist/ui/console-html.d.ts +2 -0
- package/dist/ui/console-html.js +2343 -0
- package/package.json +66 -17
- package/plugins/muxo/.app.json +8 -0
- package/plugins/muxo/.claude-plugin/plugin.json +16 -0
- package/plugins/muxo/.codex-plugin/plugin.json +55 -0
- package/plugins/muxo/.mcp.json +40 -0
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/assets/logo.svg +6 -0
- package/plugins/muxo/hooks/hooks.json +27 -0
- package/plugins/muxo/hooks/session-start.sh +85 -0
- package/plugins/muxo/hooks/user-prompt-submit.sh +45 -0
- package/plugins/muxo/mcp.json +38 -0
- package/plugins/muxo/plugin.json +59 -0
- package/plugins/muxo/skills/muxo/SKILL.md +25 -0
- package/plugins/muxo/skills/muxo/references/cli.md +57 -0
- package/plugins/muxo/skills/muxo/references/mcp.md +25 -0
- package/plugins/muxo/skills/setup/SKILL.md +28 -0
- package/src/catalogues/stripe-projects-catalog.json +9832 -0
- package/vendor/microsoft-governance/README.md +363 -0
- package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
- package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/audit.js +107 -0
- package/vendor/microsoft-governance/dist/audit.js.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
- package/vendor/microsoft-governance/dist/client.d.ts +34 -0
- package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/client.js +112 -0
- package/vendor/microsoft-governance/dist/client.js.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
- package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.js +404 -0
- package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
- package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
- package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
- package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/identity.js +446 -0
- package/vendor/microsoft-governance/dist/identity.js.map +1 -0
- package/vendor/microsoft-governance/dist/index.d.ts +39 -0
- package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/index.js +95 -0
- package/vendor/microsoft-governance/dist/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
- package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
- package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.js +233 -0
- package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.js +289 -0
- package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
- package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy.js +760 -0
- package/vendor/microsoft-governance/dist/policy.js.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
- package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
- package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/rings.js +61 -0
- package/vendor/microsoft-governance/dist/rings.js.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.js +199 -0
- package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
- package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
- package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
- package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/trust.js +209 -0
- package/vendor/microsoft-governance/dist/trust.js.map +1 -0
- package/vendor/microsoft-governance/dist/types.d.ts +422 -0
- package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/types.js +29 -0
- package/vendor/microsoft-governance/dist/types.js.map +1 -0
- package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
- package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/verify.js +356 -0
- package/vendor/microsoft-governance/dist/verify.js.map +1 -0
- package/vendor/microsoft-governance/package.json +64 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getConfig, ROOT_DIR } from "./config.js";
|
|
6
|
+
import { MuxoEngine } from "./engine.js";
|
|
7
|
+
import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.js";
|
|
8
|
+
import { MuxoStore } from "./store.js";
|
|
9
|
+
import { renderConsoleHtml, renderLandingHtml } from "./ui/console-html.js";
|
|
10
|
+
function renderSkillMarkdown(baseUrl) {
|
|
11
|
+
return `---
|
|
12
|
+
name: muxo
|
|
13
|
+
description: Discover, plan, and execute governed actions across Connected Apps (Composio SDK), Instant Tools (Composio Instant), Cloud Infrastructure (Stripe Projects), Contextual Memory (Supermemory v5), and Cryptographic Governance (Microsoft Agent Governance Toolkit) through Muxo.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Muxo — Unified Agent Execution, Infrastructure, Memory & Governance Platform
|
|
17
|
+
|
|
18
|
+
Muxo provides one governed interface over 4 pillars so agents and customers only ever interact with Muxo:
|
|
19
|
+
|
|
20
|
+
1. **Instant Tools (\`instant.*\`)**: Zero-OAuth tools powered by Composio Instant (\`instant.web.search\`, \`instant.web.scrape\`, \`instant.hackernews.top_stories\`, \`instant.github.repo_intel\`, \`instant.dns.lookup\`).
|
|
21
|
+
2. **Connected Apps (\`app.*\`)**: Managed OAuth SaaS integrations powered by Composio SDK (\`app.github.*\`, \`app.gmail.*\`, \`app.slack.*\`, \`app.notion.*\`, \`app.linear.*\`, etc.).
|
|
22
|
+
3. **Cloud Infrastructure (\`resource.*\`)**: Database, cache, storage, and hosting provisioning powered by Stripe Projects (\`resource.neon.*\`, \`resource.supabase.*\`, \`resource.turso.*\`, \`resource.cloudflare.*\`, \`resource.vercel.*\`, etc.) with AES-256-GCM secret custody in Muxo Vault.
|
|
23
|
+
4. **Supermemory v5 Brain & Microsoft Agent Governance**: Every \`plan_action\` recalls isolated workspace context (\`/ns/muxo-v5-<hash>\`) and runs \`Agent ──► McpSecurityScanner ──► PolicyEngine ──► AgentIdentity (Ed25519 DID) ──► AuditLogger ──► DecisionRecord\`.
|
|
24
|
+
|
|
25
|
+
## Installable Muxo Plugin (OpenAI Codex, ChatGPT, Claude Code & Factory Droid)
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
# Install Muxo CLI & sign in (or auto-provision an isolated external workspace)
|
|
29
|
+
npm install -g muxo-platform
|
|
30
|
+
muxo login --signup --name "my-agent"
|
|
31
|
+
|
|
32
|
+
# Link OAuth apps with live browser handoff + polling recovery
|
|
33
|
+
muxo link github --wait
|
|
34
|
+
|
|
35
|
+
# Plugin manifests served by this instance:
|
|
36
|
+
# - ${baseUrl}/.agents/plugins/marketplace.json (OpenAI Codex / ChatGPT)
|
|
37
|
+
# - ${baseUrl}/.claude-plugin/marketplace.json (Claude Code / Factory Droid)
|
|
38
|
+
# - ${baseUrl}/api/v1/plugin
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
## Autonomous Agent 2-Call Signup (Zero Browser Required)
|
|
42
|
+
|
|
43
|
+
\`\`\`bash
|
|
44
|
+
# 1. Create an autonomous workspace + mk_live_* API key
|
|
45
|
+
curl -sX POST ${baseUrl}/api/v1/agents/signup \\
|
|
46
|
+
-H "Content-Type: application/json" \\
|
|
47
|
+
-d '{"agentName":"MyAgent","harness":"claude-code"}'
|
|
48
|
+
|
|
49
|
+
# 2. (Optional) Generate a human owner claim link
|
|
50
|
+
curl -sX POST ${baseUrl}/api/v1/agents/claim \\
|
|
51
|
+
-H "Authorization: Bearer <mk_live_key>" \\
|
|
52
|
+
-H "Content-Type: application/json" \\
|
|
53
|
+
-d '{"email":"founder@company.com"}'
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
## Endpoint & Authentication
|
|
57
|
+
|
|
58
|
+
- **MCP Endpoint (Streamable HTTP)**: \`${baseUrl}/mcp\`
|
|
59
|
+
- **OAuth Callback Handoff**: \`${baseUrl}/oauth/callback\`
|
|
60
|
+
- **REST API Base**: \`${baseUrl}/api/v1\`
|
|
61
|
+
- **Marketing & Provider Canvas (muxo.ai)**: \`${baseUrl}/landing\`
|
|
62
|
+
- **Documentation (docs.muxo.ai)**: \`https://docs.muxo.ai\`
|
|
63
|
+
- **Authorization Header**: \`Authorization: Bearer mk_live_<your_key>\`
|
|
64
|
+
|
|
65
|
+
## 19 Canonical MCP Operations
|
|
66
|
+
|
|
67
|
+
${MUXO_MCP_TOOLS.map((t, i) => `${i + 1}. \`${t.name}\` — ${t.description}`).join("\n")}
|
|
68
|
+
|
|
69
|
+
## Standard Execution & OAuth Handoff Workflow
|
|
70
|
+
|
|
71
|
+
1. **Discover**: Call \`search_tools\` with a keyword or lane filter.
|
|
72
|
+
2. **Inspect**: Call \`inspect_tool\` to read the tool's \`inputSchema\`, \`riskLevel\`, and \`creditCost\`.
|
|
73
|
+
3. **Plan**: Call \`plan_action({ tool_slug, input, reason })\` to receive a \`plan_*\` ID.
|
|
74
|
+
- If the tool is an unconnected \`app.<provider>.*\` action, \`plan_action\` automatically attaches \`authHandoff.redirectUrl\`. Show that URL to the user, then call \`wait_for_connection({ provider })\` until \`status === "active"\`.
|
|
75
|
+
4. **Execute**: Call the matching risk lane tool (\`execute_read\`, \`execute_write\`, \`execute_admin\`, \`execute_financial\`, \`execute_provisioning\`, or \`execute_destructive\`) with \`{ plan_id }\`.
|
|
76
|
+
5. **Verify Receipt**: Every run issues an HMAC-SHA256 chained cryptographic receipt (\`rcpt_*\`) verifiable via \`get_receipt\`.
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
async function readBody(req) {
|
|
80
|
+
const chunks = [];
|
|
81
|
+
for await (const chunk of req) {
|
|
82
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
83
|
+
}
|
|
84
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
85
|
+
}
|
|
86
|
+
function sendJson(res, status, payload, extraHeaders) {
|
|
87
|
+
const body = JSON.stringify(payload, null, 2);
|
|
88
|
+
res.writeHead(status, {
|
|
89
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
90
|
+
"Access-Control-Allow-Origin": "*",
|
|
91
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id",
|
|
92
|
+
"Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
|
|
93
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
94
|
+
...(extraHeaders || {}),
|
|
95
|
+
});
|
|
96
|
+
res.end(body);
|
|
97
|
+
}
|
|
98
|
+
export function createMuxoServer(customConfig) {
|
|
99
|
+
const config = { ...getConfig(), ...customConfig };
|
|
100
|
+
const store = new MuxoStore(config.dbPath, config.vaultMasterKey);
|
|
101
|
+
const engine = new MuxoEngine(config, store);
|
|
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();
|
|
120
|
+
const resolveContext = (req) => {
|
|
121
|
+
const authHeader = String(req.headers.authorization || "").trim();
|
|
122
|
+
if (authHeader.toLowerCase().startsWith("bearer ")) {
|
|
123
|
+
const token = authHeader.slice(7).trim();
|
|
124
|
+
const resolved = store.resolveAuthToken(token);
|
|
125
|
+
if (resolved)
|
|
126
|
+
return resolved;
|
|
127
|
+
}
|
|
128
|
+
// Default local console session fallback to the primary workspace
|
|
129
|
+
const ws = store.getWorkspace(bootstrap.workspace.id);
|
|
130
|
+
const user = store.getUser(ws.ownerUserId);
|
|
131
|
+
return {
|
|
132
|
+
user,
|
|
133
|
+
workspace: ws,
|
|
134
|
+
actorId: user.id,
|
|
135
|
+
authType: "session",
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
const server = http.createServer(async (req, res) => {
|
|
139
|
+
if (req.method === "OPTIONS") {
|
|
140
|
+
res.writeHead(204, {
|
|
141
|
+
"Access-Control-Allow-Origin": "*",
|
|
142
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
143
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
144
|
+
});
|
|
145
|
+
res.end();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const url = new URL(req.url || "/", config.baseUrl);
|
|
149
|
+
const pathname = url.pathname;
|
|
150
|
+
try {
|
|
151
|
+
// Public Discovery & Health Endpoints
|
|
152
|
+
if (pathname === "/health" && req.method === "GET") {
|
|
153
|
+
sendJson(res, 200, {
|
|
154
|
+
ok: true,
|
|
155
|
+
status: "ready",
|
|
156
|
+
service: "muxo-platform",
|
|
157
|
+
version: "1.3.0",
|
|
158
|
+
environment: "production",
|
|
159
|
+
live_execution: true,
|
|
160
|
+
customer_billing: true,
|
|
161
|
+
supplier_spending: true,
|
|
162
|
+
fundingMode: "muxo_funded",
|
|
163
|
+
lanes: {
|
|
164
|
+
composio_sdk: Boolean(config.composioApiKey),
|
|
165
|
+
composio_instant: true,
|
|
166
|
+
stripe_projects: true,
|
|
167
|
+
stripe_billing: Boolean(config.stripeSecretKey),
|
|
168
|
+
supermemory_v5: engine.supermemory.isEnabled(),
|
|
169
|
+
microsoft_governance: true,
|
|
170
|
+
},
|
|
171
|
+
billing: {
|
|
172
|
+
subscriptions: "configured",
|
|
173
|
+
prepaid: "configured",
|
|
174
|
+
resources: "configured",
|
|
175
|
+
merchantOfRecord: "muxo_pays_suppliers",
|
|
176
|
+
},
|
|
177
|
+
surfaces: {
|
|
178
|
+
consoleUrl: `${config.baseUrl}/`,
|
|
179
|
+
landingUrl: `${config.baseUrl}/landing`,
|
|
180
|
+
publicWebUrl: config.publicWebUrl,
|
|
181
|
+
docsUrl: config.docsUrl,
|
|
182
|
+
},
|
|
183
|
+
mcpToolsCount: MUXO_MCP_TOOLS.length,
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if ((pathname === "/.well-known/oauth-protected-resource" ||
|
|
188
|
+
pathname === "/.well-known/oauth-protected-resource/mcp") &&
|
|
189
|
+
req.method === "GET") {
|
|
190
|
+
const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
|
|
191
|
+
const requestOrigin = req.headers.host
|
|
192
|
+
? `${proto}://${req.headers.host}`
|
|
193
|
+
: config.baseUrl;
|
|
194
|
+
const resource = pathname === "/.well-known/oauth-protected-resource/mcp"
|
|
195
|
+
? `${requestOrigin}/mcp`
|
|
196
|
+
: requestOrigin;
|
|
197
|
+
sendJson(res, 200, {
|
|
198
|
+
resource,
|
|
199
|
+
authorization_servers: [requestOrigin],
|
|
200
|
+
bearer_methods_supported: ["header"],
|
|
201
|
+
scopes_supported: ["muxo:mcp", "muxo:read", "muxo:execute", "muxo:provision"],
|
|
202
|
+
resource_documentation: `${config.docsUrl}/quickstart`,
|
|
203
|
+
mcp_endpoint: `${requestOrigin}/mcp`,
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
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
|
+
}
|
|
423
|
+
if ((pathname === "/api/openapi.json" || pathname === "/openapi.json") &&
|
|
424
|
+
req.method === "GET") {
|
|
425
|
+
sendJson(res, 200, {
|
|
426
|
+
openapi: "3.1.0",
|
|
427
|
+
info: {
|
|
428
|
+
title: "Muxo Unified Agent Execution, Infrastructure, Memory & Governance API",
|
|
429
|
+
version: "1.3.0",
|
|
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.",
|
|
431
|
+
},
|
|
432
|
+
servers: [{ url: config.baseUrl }],
|
|
433
|
+
paths: {
|
|
434
|
+
"/health": { get: { summary: "Platform health & 4-pillar readiness" } },
|
|
435
|
+
"/mcp": { post: { summary: "Streamable HTTP MCP JSON-RPC 2.0 endpoint (19 tools)" } },
|
|
436
|
+
"/skill.md": { get: { summary: "Canonical agent skill & onboarding markdown" } },
|
|
437
|
+
"/api/v1/agents/signup": { post: { summary: "Autonomous 2-call agent workspace & mk_live_* key signup" } },
|
|
438
|
+
"/api/v1/agents/claim": { post: { summary: "Generate human owner claim link for an agent workspace" } },
|
|
439
|
+
"/api/v1/tools": { get: { summary: "Search unified catalogue across instant.*, app.*, and resource.*" } },
|
|
440
|
+
"/api/v1/tools/{slug}": { get: { summary: "Inspect tool schema, risk level, credit cost, and LLM context URL" } },
|
|
441
|
+
"/api/v1/connections": {
|
|
442
|
+
get: { summary: "List connected OAuth accounts and cloud providers" },
|
|
443
|
+
post: { summary: "Start managed OAuth connection handoff" },
|
|
444
|
+
},
|
|
445
|
+
"/api/v1/connections/wait": { post: { summary: "Wait/poll for browser OAuth handoff completion" } },
|
|
446
|
+
"/api/v1/plans": {
|
|
447
|
+
get: { summary: "List governed action plans" },
|
|
448
|
+
post: { summary: "Create governed action plan with Supermemory recall & Ed25519 signature" },
|
|
449
|
+
},
|
|
450
|
+
"/api/v1/plans/{planId}/approve": { post: { summary: "Approve a pending high-risk action plan" } },
|
|
451
|
+
"/api/v1/plans/{planId}/execute": { post: { summary: "Execute an approved plan and issue HMAC-SHA256 receipt" } },
|
|
452
|
+
"/api/v1/resources": { get: { summary: "List provisioned cloud infrastructure resources" } },
|
|
453
|
+
"/api/v1/resources/env": { get: { summary: "Pull decrypted AES-256-GCM workspace environment secrets" } },
|
|
454
|
+
"/api/v1/memory": {
|
|
455
|
+
get: { summary: "Recall isolated Supermemory v5 workspace brain facts" },
|
|
456
|
+
post: { summary: "Store a workspace memory fact or decision" },
|
|
457
|
+
},
|
|
458
|
+
"/api/v1/governance": { get: { summary: "Inspect Microsoft Agent Governance DID, policy, and Merkle audit chain" } },
|
|
459
|
+
"/api/v1/billing/topup": { post: { summary: "Purchase Muxo credits (User pays Muxo -> Muxo pays suppliers)" } },
|
|
460
|
+
"/api/v1/billing/settlements": { get: { summary: "Inspect Merchant-of-Record supplier settlement ledger" } },
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (pathname === "/auth.md" && req.method === "GET") {
|
|
466
|
+
res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8" });
|
|
467
|
+
res.end(`# Muxo Authentication & OAuth Handoff Guide
|
|
468
|
+
|
|
469
|
+
## 1. CLI & Autonomous Agent Signup
|
|
470
|
+
\`\`\`bash
|
|
471
|
+
muxo login --signup --name "my-agent"
|
|
472
|
+
\`\`\`
|
|
473
|
+
Or via HTTP:
|
|
474
|
+
\`\`\`bash
|
|
475
|
+
curl -sX POST ${config.baseUrl}/api/v1/agents/signup -H "Content-Type: application/json" -d '{"agentName":"my-agent"}'
|
|
476
|
+
\`\`\`
|
|
477
|
+
|
|
478
|
+
## 2. OAuth Connection Handoff (Composio Plugin Pattern)
|
|
479
|
+
When \`plan_action\` targets an unconnected \`app.<provider>.*\` tool, Muxo returns \`authHandoff.redirectUrl\`.
|
|
480
|
+
Open the URL in a browser, complete OAuth (which redirects to \`${config.baseUrl}/oauth/callback\`), and call \`wait_for_connection({ provider })\` or \`muxo link <provider> --wait\`.
|
|
481
|
+
|
|
482
|
+
## 3. Merchant-of-Record Credit Settlement
|
|
483
|
+
Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing topup 1000\`), and Muxo settles wholesale supplier costs across Composio and Stripe Projects from Muxo's master accounts (\`fundingMode: "muxo_funded"\`).
|
|
484
|
+
`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (pathname === "/api/setup" && req.method === "GET") {
|
|
488
|
+
sendJson(res, 200, {
|
|
489
|
+
ok: true,
|
|
490
|
+
status: "ready",
|
|
491
|
+
mcpToolsCount: MUXO_MCP_TOOLS.length,
|
|
492
|
+
mcpEndpoint: `${config.baseUrl}/mcp`,
|
|
493
|
+
skillUrl: `${config.baseUrl}/skill.md`,
|
|
494
|
+
openApiUrl: `${config.baseUrl}/api/openapi.json`,
|
|
495
|
+
oauthCallbackUrl: `${config.baseUrl}/oauth/callback`,
|
|
496
|
+
plugins: {
|
|
497
|
+
openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
|
|
498
|
+
claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if ((pathname === "/skill.md" || pathname === "/skills.md") && req.method === "GET") {
|
|
504
|
+
res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8" });
|
|
505
|
+
res.end(renderSkillMarkdown(config.baseUrl));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (pathname === "/llms.txt" && req.method === "GET") {
|
|
509
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
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`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude)
|
|
514
|
+
const pluginJsonRoutes = {
|
|
515
|
+
"/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
|
|
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"),
|
|
519
|
+
"/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
|
|
520
|
+
"/plugins/muxo/.claude-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".claude-plugin", "plugin.json"),
|
|
521
|
+
"/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
|
|
522
|
+
"/plugins/muxo/.mcp.json": path.join(ROOT_DIR, "plugins", "muxo", ".mcp.json"),
|
|
523
|
+
};
|
|
524
|
+
if (pluginJsonRoutes[pathname] && req.method === "GET") {
|
|
525
|
+
const targetFile = pluginJsonRoutes[pathname];
|
|
526
|
+
if (fs.existsSync(targetFile)) {
|
|
527
|
+
sendJson(res, 200, JSON.parse(fs.readFileSync(targetFile, "utf8")));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
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
|
+
}
|
|
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"));
|
|
567
|
+
const codexManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"), "utf8"));
|
|
568
|
+
sendJson(res, 200, {
|
|
569
|
+
ok: true,
|
|
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
|
+
})),
|
|
580
|
+
marketplaces: {
|
|
581
|
+
openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
|
|
582
|
+
claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
|
|
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
|
+
},
|
|
591
|
+
installCommands: {
|
|
592
|
+
codex: "codex plugin add muxo@muxo",
|
|
593
|
+
cliSignup: "muxo login --signup --name my-agent",
|
|
594
|
+
oauthHandoff: "muxo link github --wait",
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
// Web Console UI (app.muxo.ai)
|
|
600
|
+
if ((pathname === "/" || pathname === "/console") && req.method === "GET") {
|
|
601
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
602
|
+
res.end(renderConsoleHtml());
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
// Built-in Interactive Landing & Provider Canvas (muxo.ai)
|
|
606
|
+
if (pathname === "/landing" && req.method === "GET") {
|
|
607
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
608
|
+
res.end(renderLandingHtml());
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
// Autonomous Agent Claim Redemption Route
|
|
612
|
+
if (pathname.startsWith("/claim/") && req.method === "GET") {
|
|
613
|
+
const claimSlug = decodeURIComponent(pathname.slice("/claim/".length));
|
|
614
|
+
const redeemed = store.redeemAgentClaim(claimSlug);
|
|
615
|
+
if (!redeemed) {
|
|
616
|
+
sendJson(res, 404, { ok: false, error: "Invalid or expired agent claim link" });
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const wantsHtml = url.searchParams.get("format") !== "json" &&
|
|
620
|
+
String(req.headers.accept || "").includes("text/html");
|
|
621
|
+
if (wantsHtml) {
|
|
622
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
623
|
+
res.end(`<!DOCTYPE html>
|
|
624
|
+
<html lang="en">
|
|
625
|
+
<head>
|
|
626
|
+
<meta charset="utf-8" />
|
|
627
|
+
<title>Muxo Agent Workspace Claimed</title>
|
|
628
|
+
<style>
|
|
629
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #090a0f; color: #f4f4f6; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
|
630
|
+
.card { background: #12141f; border: 1px solid #26293d; border-radius: 14px; padding: 32px; max-width: 480px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
|
|
631
|
+
.badge { display: inline-block; background: rgba(45,212,191,0.15); color: #2dd4bf; border: 1px solid rgba(45,212,191,0.35); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
|
|
632
|
+
h1 { font-size: 22px; margin: 0 0 10px; }
|
|
633
|
+
p { color: #a1a7bb; font-size: 14px; line-height: 1.5; margin: 0 0 20px; }
|
|
634
|
+
code { background: #1c1f30; padding: 3px 7px; border-radius: 6px; color: #e2e8f0; font-size: 12px; }
|
|
635
|
+
.btn { display: inline-block; background: #ff5a1f; color: #fff; text-decoration: none; padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 600; }
|
|
636
|
+
</style>
|
|
637
|
+
</head>
|
|
638
|
+
<body>
|
|
639
|
+
<div class="card">
|
|
640
|
+
<div class="badge">Ownership Bound</div>
|
|
641
|
+
<h1>Workspace Claimed</h1>
|
|
642
|
+
<p>Workspace <code>${redeemed.workspace.name}</code> (${redeemed.workspace.id}) claimed by <strong>${redeemed.targetEmail}</strong>.</p>
|
|
643
|
+
<a class="btn" href="/?claimed=${encodeURIComponent(redeemed.workspace.slug)}">Open Muxo Console →</a>
|
|
644
|
+
</div>
|
|
645
|
+
</body>
|
|
646
|
+
</html>`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
sendJson(res, 200, {
|
|
650
|
+
ok: true,
|
|
651
|
+
message: `Workspace '${redeemed.workspace.name}' claimed by ${redeemed.targetEmail}`,
|
|
652
|
+
claim: redeemed,
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
// Browser OAuth Handoff Callback Route (GET /oauth/callback)
|
|
657
|
+
if (pathname === "/oauth/callback" && req.method === "GET") {
|
|
658
|
+
const workspaceId = url.searchParams.get("workspace_id") ||
|
|
659
|
+
url.searchParams.get("user_id") ||
|
|
660
|
+
bootstrap.workspace.id;
|
|
661
|
+
const provider = url.searchParams.get("provider") ||
|
|
662
|
+
url.searchParams.get("toolkit") ||
|
|
663
|
+
url.searchParams.get("appName") ||
|
|
664
|
+
"unknown";
|
|
665
|
+
const connectedAccountId = url.searchParams.get("connected_account_id") ||
|
|
666
|
+
url.searchParams.get("connectedAccountId") ||
|
|
667
|
+
undefined;
|
|
668
|
+
const callbackStatus = url.searchParams.get("status") || "success";
|
|
669
|
+
const handoff = await engine.handleOAuthCallback({
|
|
670
|
+
workspaceId,
|
|
671
|
+
provider,
|
|
672
|
+
connectedAccountId,
|
|
673
|
+
status: callbackStatus,
|
|
674
|
+
});
|
|
675
|
+
const wantsJson = url.searchParams.get("format") === "json" ||
|
|
676
|
+
String(req.headers.accept || "").includes("application/json");
|
|
677
|
+
if (wantsJson) {
|
|
678
|
+
sendJson(res, 200, {
|
|
679
|
+
ok: true,
|
|
680
|
+
handoff,
|
|
681
|
+
message: `${handoff.providerName} OAuth handoff completed (${handoff.status}). Return to your agent or terminal.`,
|
|
682
|
+
});
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
686
|
+
res.end(`<!DOCTYPE html>
|
|
687
|
+
<html lang="en">
|
|
688
|
+
<head>
|
|
689
|
+
<meta charset="utf-8" />
|
|
690
|
+
<title>Muxo OAuth Handoff Complete</title>
|
|
691
|
+
<style>
|
|
692
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #090a0f; color: #f4f4f6; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
|
693
|
+
.card { background: #12141f; border: 1px solid #26293d; border-radius: 14px; padding: 32px; max-width: 460px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
|
|
694
|
+
.badge { display: inline-block; background: rgba(16,185,129,0.15); color: #34d399; border: 1px solid rgba(16,185,129,0.35); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
|
|
695
|
+
h1 { font-size: 22px; margin: 0 0 10px; }
|
|
696
|
+
p { color: #a1a7bb; font-size: 14px; line-height: 1.5; margin: 0 0 20px; }
|
|
697
|
+
code { background: #1c1f30; padding: 3px 7px; border-radius: 6px; color: #e2e8f0; font-size: 12px; }
|
|
698
|
+
.btn { display: inline-block; background: #6366f1; color: #fff; text-decoration: none; padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 600; }
|
|
699
|
+
</style>
|
|
700
|
+
</head>
|
|
701
|
+
<body>
|
|
702
|
+
<div class="card">
|
|
703
|
+
<div class="badge">OAuth Handoff Complete (${handoff.status.toUpperCase()})</div>
|
|
704
|
+
<h1>${handoff.providerName} Connected to Muxo</h1>
|
|
705
|
+
<p>Workspace <code>${handoff.workspaceId}</code> is now linked${handoff.externalAccountId ? ` (<code>${handoff.externalAccountId}</code>)` : ""}. You can close this window and return to ChatGPT, Codex, Claude Code, Droid, or your terminal.</p>
|
|
706
|
+
<a class="btn" href="/?connected=${encodeURIComponent(handoff.providerSlug)}">Open Muxo Console</a>
|
|
707
|
+
</div>
|
|
708
|
+
</body>
|
|
709
|
+
</html>`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// Autonomous Agent 2-Call Signup (agents.composio.dev pattern) — Public
|
|
713
|
+
if (pathname === "/api/v1/agents/signup" && req.method === "POST") {
|
|
714
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
715
|
+
const created = store.createAutonomousAgentSignup({
|
|
716
|
+
agentName: body.agentName ? String(body.agentName) : undefined,
|
|
717
|
+
harness: body.harness ? String(body.harness) : undefined,
|
|
718
|
+
});
|
|
719
|
+
sendJson(res, 201, {
|
|
720
|
+
ok: true,
|
|
721
|
+
agentId: created.user.id,
|
|
722
|
+
workspace: {
|
|
723
|
+
id: created.workspace.id,
|
|
724
|
+
name: created.workspace.name,
|
|
725
|
+
slug: created.workspace.slug,
|
|
726
|
+
tier: created.workspace.tier,
|
|
727
|
+
creditsBalance: created.workspace.creditsBalance,
|
|
728
|
+
},
|
|
729
|
+
apiKey: created.apiKey,
|
|
730
|
+
mcpEndpoint: `${config.baseUrl}/mcp`,
|
|
731
|
+
skillUrl: `${config.baseUrl}/skill.md`,
|
|
732
|
+
claimEndpoint: `${config.baseUrl}/api/v1/agents/claim`,
|
|
733
|
+
governanceDid: engine.governance.getStatus(created.workspace).agentDid,
|
|
734
|
+
supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(created.workspace.id),
|
|
735
|
+
});
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
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
|
+
}
|
|
776
|
+
if (pathname === "/mcp" && req.method === "POST") {
|
|
777
|
+
const ctx = resolveContext(req);
|
|
778
|
+
const sessionId = String(req.headers["mcp-session-id"] || "") ||
|
|
779
|
+
`mcp_sess_${ctx.workspace.id}`;
|
|
780
|
+
const raw = await readBody(req);
|
|
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;
|
|
788
|
+
const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
|
|
789
|
+
if (!response) {
|
|
790
|
+
res.writeHead(202, {
|
|
791
|
+
"Access-Control-Allow-Origin": "*",
|
|
792
|
+
"Mcp-Session-Id": sessionId,
|
|
793
|
+
});
|
|
794
|
+
res.end();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
sendJson(res, 200, response, { "Mcp-Session-Id": sessionId });
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
// Auth Endpoints
|
|
801
|
+
if (pathname === "/api/v1/auth/register" && req.method === "POST") {
|
|
802
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
803
|
+
const created = store.registerUser({
|
|
804
|
+
email: String(body.email || ""),
|
|
805
|
+
name: String(body.name || "User"),
|
|
806
|
+
password: String(body.password || ""),
|
|
807
|
+
workspaceName: body.workspaceName ? String(body.workspaceName) : undefined,
|
|
808
|
+
});
|
|
809
|
+
sendJson(res, 201, { ok: true, ...created });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (pathname === "/api/v1/auth/login" && req.method === "POST") {
|
|
813
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
814
|
+
const loggedIn = store.loginUser(String(body.email || ""), String(body.password || ""));
|
|
815
|
+
if (!loggedIn) {
|
|
816
|
+
sendJson(res, 401, { ok: false, error: "Invalid email or password" });
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
sendJson(res, 200, { ok: true, ...loggedIn });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const ctx = resolveContext(req);
|
|
823
|
+
// Console Bootstrap
|
|
824
|
+
if (pathname === "/api/v1/bootstrap" && req.method === "GET") {
|
|
825
|
+
const org = engine.getOrganization(ctx).organization;
|
|
826
|
+
sendJson(res, 200, {
|
|
827
|
+
ok: true,
|
|
828
|
+
profile: engine.getProfile(ctx),
|
|
829
|
+
organization: org,
|
|
830
|
+
governance: engine.governance.getStatus(),
|
|
831
|
+
supermemory: {
|
|
832
|
+
enabled: engine.supermemory.isEnabled(),
|
|
833
|
+
namespace: engine.supermemory.getWorkspaceNamespace(ctx.workspace.id),
|
|
834
|
+
},
|
|
835
|
+
surfaces: {
|
|
836
|
+
publicWebUrl: config.publicWebUrl,
|
|
837
|
+
docsUrl: config.docsUrl,
|
|
838
|
+
landingUrl: `${config.baseUrl}/landing`,
|
|
839
|
+
},
|
|
840
|
+
apiKeys: store.listApiKeys(ctx.workspace.id),
|
|
841
|
+
byokKeys: store.listByokKeys(ctx.workspace.id),
|
|
842
|
+
defaultApiKey: bootstrap.defaultApiKey || null,
|
|
843
|
+
connections: store.listConnections(ctx.workspace.id),
|
|
844
|
+
resources: store.listResources(ctx.workspace.id),
|
|
845
|
+
plans: store.listPlans(ctx.workspace.id, 25),
|
|
846
|
+
runs: store.listRuns(ctx.workspace.id, 25),
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
// Autonomous Agent Claim Link Creation (Authenticated)
|
|
851
|
+
if (pathname === "/api/v1/agents/claim" && req.method === "POST") {
|
|
852
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
853
|
+
const email = String(body.email || "").trim();
|
|
854
|
+
if (!email || !email.includes("@")) {
|
|
855
|
+
sendJson(res, 400, { ok: false, error: "Valid target email is required" });
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const claim = store.createAgentClaim({
|
|
859
|
+
workspaceId: ctx.workspace.id,
|
|
860
|
+
email,
|
|
861
|
+
baseUrl: config.baseUrl,
|
|
862
|
+
});
|
|
863
|
+
sendJson(res, 201, { ok: true, claim, ...claim });
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
// Microsoft Agent Governance Status & Audit Verification
|
|
867
|
+
if (pathname === "/api/v1/governance" && req.method === "GET") {
|
|
868
|
+
sendJson(res, 200, {
|
|
869
|
+
ok: true,
|
|
870
|
+
governance: engine.governance.getStatus(),
|
|
871
|
+
workspacePolicy: ctx.workspace.policy,
|
|
872
|
+
});
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
// Supermemory v5 Workspace Brain Endpoints
|
|
876
|
+
if (pathname === "/api/v1/memory" && req.method === "GET") {
|
|
877
|
+
const query = url.searchParams.get("query") ||
|
|
878
|
+
url.searchParams.get("q") ||
|
|
879
|
+
undefined;
|
|
880
|
+
const overview = await engine.getMemoryOverview(ctx, query);
|
|
881
|
+
sendJson(res, 200, { ok: true, ...overview });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (pathname === "/api/v1/memory" && req.method === "POST") {
|
|
885
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
886
|
+
const record = await engine.addWorkspaceMemory(ctx, {
|
|
887
|
+
title: String(body.title || "Workspace Fact"),
|
|
888
|
+
content: String(body.content || ""),
|
|
889
|
+
kind: body.kind || "fact",
|
|
890
|
+
metadata: body.metadata || {},
|
|
891
|
+
});
|
|
892
|
+
sendJson(res, 201, { ok: true, memory: record });
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (pathname.startsWith("/api/v1/memory/") && req.method === "DELETE") {
|
|
896
|
+
const id = decodeURIComponent(pathname.slice("/api/v1/memory/".length));
|
|
897
|
+
const deleted = await engine.deleteWorkspaceMemory(ctx, id);
|
|
898
|
+
sendJson(res, 200, { ok: true, ...deleted });
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
// API Keys
|
|
902
|
+
if (pathname === "/api/v1/api-keys" && req.method === "POST") {
|
|
903
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
904
|
+
const created = store.createApiKey({
|
|
905
|
+
workspaceId: ctx.workspace.id,
|
|
906
|
+
name: String(body.name || "API Key"),
|
|
907
|
+
environment: body.environment === "test" ? "test" : "live",
|
|
908
|
+
});
|
|
909
|
+
sendJson(res, 201, { ok: true, ...created });
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
// Profile & Organization (v1 and api/v1)
|
|
913
|
+
if ((pathname === "/api/v1/profile" || pathname === "/v1/profile") && req.method === "GET") {
|
|
914
|
+
sendJson(res, 200, { ok: true, ...engine.getProfile(ctx) });
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if ((pathname === "/api/v1/organization" || pathname === "/v1/organization") && req.method === "GET") {
|
|
918
|
+
sendJson(res, 200, { ok: true, ...engine.getOrganization(ctx) });
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
// Catalog Search & Inspect
|
|
922
|
+
if ((pathname === "/api/v1/tools" || pathname === "/v1/tools/search") &&
|
|
923
|
+
(req.method === "GET" || req.method === "POST")) {
|
|
924
|
+
const body = req.method === "POST" ? JSON.parse((await readBody(req)) || "{}") : {};
|
|
925
|
+
const query = url.searchParams.get("query") || body.query || undefined;
|
|
926
|
+
const lane = (url.searchParams.get("lane") || body.lane) ||
|
|
927
|
+
"all";
|
|
928
|
+
const riskLevel = (url.searchParams.get("risk_level") || body.risk_level) ||
|
|
929
|
+
undefined;
|
|
930
|
+
const limit = Number(url.searchParams.get("limit") || body.limit || 30);
|
|
931
|
+
const results = await engine.searchTools({ query, lane, riskLevel, limit });
|
|
932
|
+
sendJson(res, 200, { ok: true, ...results });
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (pathname.startsWith("/api/v1/tools/") && req.method === "GET") {
|
|
936
|
+
const slug = decodeURIComponent(pathname.slice("/api/v1/tools/".length));
|
|
937
|
+
const tool = await engine.inspectTool(slug);
|
|
938
|
+
sendJson(res, 200, tool);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
// Connections
|
|
942
|
+
if (pathname === "/api/v1/connections" && req.method === "GET") {
|
|
943
|
+
const syncLive = url.searchParams.get("sync") === "true";
|
|
944
|
+
const provider = url.searchParams.get("provider") || undefined;
|
|
945
|
+
const status = await engine.connectionStatus(ctx, { provider, syncLive });
|
|
946
|
+
sendJson(res, 200, { ok: true, ...status });
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (pathname === "/api/v1/connections" && req.method === "POST") {
|
|
950
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
951
|
+
const connection = await engine.startConnection(ctx, {
|
|
952
|
+
provider: String(body.provider || ""),
|
|
953
|
+
lane: body.lane,
|
|
954
|
+
callbackUrl: body.callbackUrl ? String(body.callbackUrl) : undefined,
|
|
955
|
+
});
|
|
956
|
+
sendJson(res, 201, { ok: true, connection });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (pathname === "/api/v1/connections/wait" && req.method === "POST") {
|
|
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
|
+
}
|
|
969
|
+
const result = await engine.waitForConnection(ctx, {
|
|
970
|
+
provider,
|
|
971
|
+
timeoutSeconds: body.timeoutSeconds !== undefined
|
|
972
|
+
? Number(body.timeoutSeconds)
|
|
973
|
+
: body.timeout_seconds !== undefined
|
|
974
|
+
? Number(body.timeout_seconds)
|
|
975
|
+
: 15,
|
|
976
|
+
});
|
|
977
|
+
sendJson(res, 200, { ok: true, ...result });
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
// Provisioned Resources & Decrypted Vault Env Pull
|
|
981
|
+
if (pathname === "/api/v1/resources" && req.method === "GET") {
|
|
982
|
+
sendJson(res, 200, {
|
|
983
|
+
ok: true,
|
|
984
|
+
resources: store.listResources(ctx.workspace.id),
|
|
985
|
+
});
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (pathname === "/api/v1/resources/env" && req.method === "GET") {
|
|
989
|
+
// Also pull any fresh .env from Stripe Projects workdir if present
|
|
990
|
+
const workdirSecrets = await engine.stripeProjects
|
|
991
|
+
.pullEnvVarsFromWorkdir()
|
|
992
|
+
.catch(() => ({}));
|
|
993
|
+
const vaultSecrets = store.getDecryptedWorkspaceEnv(ctx.workspace.id);
|
|
994
|
+
const merged = { ...workdirSecrets, ...vaultSecrets };
|
|
995
|
+
const dotenvLines = [
|
|
996
|
+
`# Muxo Encrypted Vault Export — Workspace ${ctx.workspace.slug} (${ctx.workspace.id})`,
|
|
997
|
+
`# Generated at ${new Date().toISOString()}`,
|
|
998
|
+
...Object.entries(merged).map(([k, v]) => `${k}=${JSON.stringify(v)}`),
|
|
999
|
+
];
|
|
1000
|
+
sendJson(res, 200, {
|
|
1001
|
+
ok: true,
|
|
1002
|
+
workspaceId: ctx.workspace.id,
|
|
1003
|
+
variables: merged,
|
|
1004
|
+
dotenv: dotenvLines.join("\n") + "\n",
|
|
1005
|
+
});
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
// Governance Policy
|
|
1009
|
+
if (pathname === "/api/v1/policy" && req.method === "POST") {
|
|
1010
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1011
|
+
const updated = store.updateWorkspacePolicy(ctx.workspace.id, body);
|
|
1012
|
+
sendJson(res, 200, { ok: true, workspace: updated });
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
// Plans & Approvals & Execution
|
|
1016
|
+
if (pathname === "/api/v1/plans" && req.method === "GET") {
|
|
1017
|
+
sendJson(res, 200, {
|
|
1018
|
+
ok: true,
|
|
1019
|
+
plans: store.listPlans(ctx.workspace.id, 50),
|
|
1020
|
+
});
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (pathname === "/api/v1/plans" && req.method === "POST") {
|
|
1024
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1025
|
+
const planned = await engine.planAction(ctx, {
|
|
1026
|
+
toolSlug: String(body.toolSlug || body.tool_slug || ""),
|
|
1027
|
+
input: body.input || {},
|
|
1028
|
+
reason: body.reason ? String(body.reason) : undefined,
|
|
1029
|
+
});
|
|
1030
|
+
sendJson(res, 201, { ok: true, ...planned });
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const planApproveMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)\/approve$/);
|
|
1034
|
+
if (planApproveMatch && req.method === "POST") {
|
|
1035
|
+
const planId = decodeURIComponent(planApproveMatch[1]);
|
|
1036
|
+
const plan = engine.approvePlan(ctx, planId, "approved");
|
|
1037
|
+
sendJson(res, 200, { ok: true, plan });
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const planExecuteMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)\/execute$/);
|
|
1041
|
+
if (planExecuteMatch && req.method === "POST") {
|
|
1042
|
+
const planId = decodeURIComponent(planExecuteMatch[1]);
|
|
1043
|
+
const plan = engine.getPlan(ctx, planId);
|
|
1044
|
+
const result = await engine.executePlanForRisk(ctx, planId, plan.riskLevel);
|
|
1045
|
+
sendJson(res, 200, { ok: true, ...result });
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
// Runs & Receipts
|
|
1049
|
+
if (pathname === "/api/v1/runs" && req.method === "GET") {
|
|
1050
|
+
sendJson(res, 200, {
|
|
1051
|
+
ok: true,
|
|
1052
|
+
runs: store.listRuns(ctx.workspace.id, 50),
|
|
1053
|
+
});
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (pathname.startsWith("/api/v1/receipts/") && req.method === "GET") {
|
|
1057
|
+
const receiptId = decodeURIComponent(pathname.slice("/api/v1/receipts/".length));
|
|
1058
|
+
const verified = engine.getReceipt(ctx, receiptId);
|
|
1059
|
+
sendJson(res, 200, { ok: true, ...verified });
|
|
1060
|
+
return;
|
|
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
|
+
}
|
|
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
|
+
}
|
|
1147
|
+
if (pathname === "/api/v1/billing/ledger" && req.method === "GET") {
|
|
1148
|
+
sendJson(res, 200, {
|
|
1149
|
+
ok: true,
|
|
1150
|
+
entries: store.listLedgerEntries(ctx.workspace.id, 50),
|
|
1151
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1152
|
+
});
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (pathname === "/api/v1/billing/settlements" && req.method === "GET") {
|
|
1156
|
+
sendJson(res, 200, {
|
|
1157
|
+
ok: true,
|
|
1158
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1159
|
+
entries: store.listLedgerEntries(ctx.workspace.id, 100),
|
|
1160
|
+
});
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (pathname === "/api/v1/billing/topup" && req.method === "POST") {
|
|
1164
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1165
|
+
const credits = Math.max(1, Math.min(1000000, Number(body.credits || 1000)));
|
|
1166
|
+
const referenceId = String(body.referenceId || "").trim() ||
|
|
1167
|
+
`topup_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1168
|
+
const usdPaid = (credits * 0.01).toFixed(2);
|
|
1169
|
+
const updatedWorkspace = store.addWorkspaceCredits({
|
|
1170
|
+
workspaceId: ctx.workspace.id,
|
|
1171
|
+
credits,
|
|
1172
|
+
referenceId,
|
|
1173
|
+
description: String(body.description || "").trim() ||
|
|
1174
|
+
`User purchased +${credits.toLocaleString()} Muxo Credits ($${usdPaid} USD -> Muxo Master Wallet)`,
|
|
1175
|
+
});
|
|
1176
|
+
sendJson(res, 200, {
|
|
1177
|
+
ok: true,
|
|
1178
|
+
creditsAdded: credits,
|
|
1179
|
+
usdPaid: Number(usdPaid),
|
|
1180
|
+
workspace: updatedWorkspace,
|
|
1181
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1182
|
+
});
|
|
1183
|
+
return;
|
|
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
|
+
}
|
|
1209
|
+
if (pathname === "/api/v1/billing/checkout" && req.method === "POST") {
|
|
1210
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1211
|
+
const kind = body.kind === "credit_topup" ? "credit_topup" : "starter_subscription";
|
|
1212
|
+
const credits = body.credits ? Number(body.credits) : 1000;
|
|
1213
|
+
const session = await engine.stripeBilling.createCheckoutSession({
|
|
1214
|
+
workspace: ctx.workspace,
|
|
1215
|
+
customerEmail: ctx.user.email,
|
|
1216
|
+
kind,
|
|
1217
|
+
credits,
|
|
1218
|
+
returnBaseUrl: body.returnBaseUrl ? String(body.returnBaseUrl) : config.baseUrl,
|
|
1219
|
+
});
|
|
1220
|
+
sendJson(res, 200, { ok: true, ...session });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (pathname === "/api/v1/billing/verify-session" && req.method === "POST") {
|
|
1224
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
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);
|
|
1230
|
+
sendJson(res, 200, { ok: true, ...verified });
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (pathname === "/api/v1/billing/webhook" && req.method === "POST") {
|
|
1234
|
+
const rawBody = await readBody(req);
|
|
1235
|
+
const sig = String(req.headers["stripe-signature"] || "");
|
|
1236
|
+
if (!engine.stripeBilling.verifyWebhookSignature(rawBody, sig)) {
|
|
1237
|
+
sendJson(res, 400, { ok: false, error: "Invalid Stripe webhook signature" });
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const event = JSON.parse(rawBody || "{}");
|
|
1241
|
+
if (event.type === "checkout.session.completed") {
|
|
1242
|
+
const sessionObj = event.data?.object;
|
|
1243
|
+
if (sessionObj?.id) {
|
|
1244
|
+
await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(sessionObj.id));
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
sendJson(res, 200, { ok: true, received: true });
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
sendJson(res, 404, { ok: false, error: `Route not found: ${req.method} ${pathname}` });
|
|
1251
|
+
}
|
|
1252
|
+
catch (err) {
|
|
1253
|
+
sendJson(res, 400, {
|
|
1254
|
+
ok: false,
|
|
1255
|
+
error: String(err?.message || err || "Internal error"),
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
return { server, engine, store, defaultApiKey: bootstrap.defaultApiKey };
|
|
1260
|
+
}
|
|
1261
|
+
// Start server when invoked directly
|
|
1262
|
+
const isMain = process.argv[1] &&
|
|
1263
|
+
(process.argv[1].endsWith("server.ts") || process.argv[1].endsWith("server.js"));
|
|
1264
|
+
if (isMain) {
|
|
1265
|
+
const config = getConfig();
|
|
1266
|
+
const { server, defaultApiKey } = createMuxoServer(config);
|
|
1267
|
+
server.listen(config.port, () => {
|
|
1268
|
+
console.log(`\n⚡ Muxo Platform running at ${config.baseUrl}`);
|
|
1269
|
+
console.log(` ├─ Web Console: ${config.baseUrl}/`);
|
|
1270
|
+
console.log(` ├─ MCP Server: ${config.baseUrl}/mcp`);
|
|
1271
|
+
console.log(` ├─ Agent Skill: ${config.baseUrl}/skill.md`);
|
|
1272
|
+
console.log(` └─ Health Check: ${config.baseUrl}/health`);
|
|
1273
|
+
if (defaultApiKey) {
|
|
1274
|
+
console.log(`\n🔑 Initial Workspace API Key created (save this): ${defaultApiKey}\n`);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|