@muxoai/sdk 1.3.0 → 1.4.2

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.
Files changed (72) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +19 -4
  3. package/.codex/config.toml +9 -2
  4. package/ARCHITECTURE.md +16 -6
  5. package/README.md +69 -24
  6. package/dist/clerk.d.ts +35 -0
  7. package/dist/clerk.js +201 -0
  8. package/dist/cli.js +1589 -103
  9. package/dist/composio-catalog.d.ts +234 -0
  10. package/dist/composio-catalog.js +1894 -0
  11. package/dist/config.d.ts +16 -0
  12. package/dist/config.js +27 -1
  13. package/dist/durable-workflow.d.ts +45 -0
  14. package/dist/durable-workflow.js +90 -0
  15. package/dist/engine.d.ts +315 -6
  16. package/dist/engine.js +1778 -94
  17. package/dist/mcp-schema.d.ts +286 -0
  18. package/dist/mcp-schema.js +2559 -0
  19. package/dist/mcp.d.ts +2 -25
  20. package/dist/mcp.js +949 -507
  21. package/dist/memory-graph.d.ts +21 -0
  22. package/dist/memory-graph.js +173 -0
  23. package/dist/nl.js +214 -35
  24. package/dist/oauth.d.ts +32 -0
  25. package/dist/oauth.js +134 -0
  26. package/dist/policy.d.ts +190 -0
  27. package/dist/policy.js +1597 -0
  28. package/dist/providers/composio-instant.d.ts +4 -0
  29. package/dist/providers/composio-instant.js +890 -535
  30. package/dist/providers/composio-sdk.d.ts +126 -5
  31. package/dist/providers/composio-sdk.js +902 -79
  32. package/dist/providers/microsoft-governance.d.ts +89 -3
  33. package/dist/providers/microsoft-governance.js +673 -26
  34. package/dist/providers/stripe-billing.d.ts +42 -0
  35. package/dist/providers/stripe-billing.js +244 -5
  36. package/dist/providers/stripe-projects.d.ts +459 -4
  37. package/dist/providers/stripe-projects.js +2674 -122
  38. package/dist/providers/supermemory.d.ts +102 -6
  39. package/dist/providers/supermemory.js +609 -55
  40. package/dist/provisioning-managed.d.ts +50 -0
  41. package/dist/provisioning-managed.js +155 -0
  42. package/dist/sdk.d.ts +705 -7
  43. package/dist/sdk.js +488 -10
  44. package/dist/server.js +2646 -348
  45. package/dist/store.d.ts +138 -6
  46. package/dist/store.js +932 -58
  47. package/dist/types.d.ts +312 -4
  48. package/dist/ui/console-html.d.ts +62 -0
  49. package/dist/ui/console-html.js +3742 -2228
  50. package/dist/ui/design-tokens.d.ts +177 -0
  51. package/dist/ui/design-tokens.js +210 -0
  52. package/dist/version.d.ts +1 -0
  53. package/dist/version.js +1 -0
  54. package/package.json +5 -4
  55. package/plugins/muxo/.app.json +1 -1
  56. package/plugins/muxo/.claude-plugin/plugin.json +3 -3
  57. package/plugins/muxo/.codex-plugin/plugin.json +19 -10
  58. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  59. package/plugins/muxo/.mcp.json +1 -33
  60. package/plugins/muxo/assets/logo.png +0 -0
  61. package/plugins/muxo/hooks/session-start.sh +16 -4
  62. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  63. package/plugins/muxo/mcp.json +0 -10
  64. package/plugins/muxo/plugin.json +65 -8
  65. package/plugins/muxo/skills/muxo/SKILL.md +142 -14
  66. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  67. package/plugins/muxo/skills/muxo/references/cli.md +9 -3
  68. package/plugins/muxo/skills/muxo/references/mcp.md +22 -10
  69. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  70. package/plugins/muxo/skills/setup/SKILL.md +17 -3
  71. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
  72. package/src/catalogues/stripe-projects-catalog.json +140 -0
package/dist/cli.js CHANGED
@@ -5,8 +5,11 @@ import path from "node:path";
5
5
  import readline from "node:readline";
6
6
  import { getConfig } from "./config.js";
7
7
  import { MuxoEngine } from "./engine.js";
8
- import { handleJsonRpcMcpRequest } from "./mcp.js";
8
+ import { handleJsonRpcMcpRequest, LATEST_MCP_PROTOCOL_VERSION } from "./mcp.js";
9
+ import { DEFAULT_WORKSPACE_POLICY, describePolicyInPlainEnglish, sanitizePolicyPatch, } from "./policy.js";
10
+ import { RING_LABELS } from "./providers/microsoft-governance.js";
9
11
  import { BYOK_PROVIDER_CATALOG, MuxoStore } from "./store.js";
12
+ import { MUXO_VERSION } from "./version.js";
10
13
  function promptUserLine(question) {
11
14
  return new Promise((resolve) => {
12
15
  const rl = readline.createInterface({
@@ -70,37 +73,148 @@ function parseFlags(args) {
70
73
  return { positional, flags };
71
74
  }
72
75
  async function runCli() {
73
- const config = getConfig();
74
- const store = new MuxoStore(config.dbPath, config.vaultMasterKey);
75
- const engine = new MuxoEngine(config, store);
76
76
  const { positional, flags } = parseFlags(process.argv.slice(2));
77
77
  const cmd = positional[0] || "help";
78
78
  if (flags.version || flags.v || cmd === "version") {
79
79
  console.log(flags.json
80
- ? JSON.stringify({ ok: true, name: "@muxoai/cli", version: "1.3.0" }, null, 2)
81
- : "1.3.0");
80
+ ? JSON.stringify({ ok: true, name: "@muxoai/cli", version: MUXO_VERSION }, null, 2)
81
+ : MUXO_VERSION);
82
82
  return;
83
83
  }
84
84
  const savedCliConfig = readCliAuthConfig();
85
- const apiKeyFlag = (typeof flags["api-key"] === "string" ? flags["api-key"] : "") ||
85
+ let apiKeyFlag = (typeof flags["api-key"] === "string" ? flags["api-key"] : "") ||
86
86
  process.env.MUXO_API_KEY ||
87
87
  savedCliConfig?.apiKey ||
88
88
  "";
89
- const tokenAuth = apiKeyFlag ? store.resolveAuthToken(apiKeyFlag) : null;
90
- const fallback = store.ensureDefaultWorkspace();
91
- const ctx = tokenAuth
92
- ? {
93
- user: tokenAuth.user,
94
- workspace: tokenAuth.workspace,
95
- actorId: tokenAuth.actorId,
96
- authType: tokenAuth.authType,
97
- }
98
- : {
99
- user: fallback.user,
100
- workspace: fallback.workspace,
101
- actorId: `cli:${fallback.user.id}`,
102
- authType: "session",
89
+ const explicitUrlFlag = (typeof flags.url === "string"
90
+ ? flags.url
91
+ : process.env.MUXO_API_URL || process.env.MUXO_CLOUD_URL || "").replace(/\/+$/, "");
92
+ const useLocalDb = Boolean((process.env.MUXO_DB_PATH || flags.local) && !explicitUrlFlag);
93
+ const remoteUrl = useLocalDb
94
+ ? ""
95
+ : (explicitUrlFlag || savedCliConfig?.apiUrl || "https://api.muxo.ai").replace(/\/+$/, "");
96
+ const remoteFetch = async (pathname, opts) => {
97
+ const keyToUse = opts?.apiKey ?? apiKeyFlag;
98
+ const headers = {
99
+ Accept: "application/json",
100
+ };
101
+ if (opts?.body !== undefined) {
102
+ headers["Content-Type"] = "application/json";
103
+ }
104
+ if (keyToUse) {
105
+ headers["Authorization"] = `Bearer ${keyToUse}`;
106
+ }
107
+ const res = await fetch(`${remoteUrl}${pathname}`, {
108
+ method: opts?.method || "GET",
109
+ headers,
110
+ body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
111
+ });
112
+ const data = (await res.json().catch(() => ({})));
113
+ if (opts?.acceptPaymentRequired && res.status === 402 && data.checkoutUrl) {
114
+ return data;
115
+ }
116
+ if (opts?.acceptHumanRequired && res.status === 403 && data.code === "human_required") {
117
+ return data;
118
+ }
119
+ if (!res.ok && data.error) {
120
+ throw new Error(String(data.error));
121
+ }
122
+ return data;
123
+ };
124
+ const signupHostedWorkspace = async (agentName, harness) => {
125
+ const createdRemote = await remoteFetch("/api/v1/agents/signup", {
126
+ method: "POST",
127
+ body: { agentName, harness },
128
+ apiKey: "",
129
+ });
130
+ if (!createdRemote || !createdRemote.apiKey) {
131
+ throw new Error(`Hosted signup at ${remoteUrl}/api/v1/agents/signup failed to return an API key.`);
132
+ }
133
+ const authPayload = {
134
+ authenticated: true,
135
+ apiKey: String(createdRemote.apiKey),
136
+ apiUrl: remoteUrl,
137
+ workspaceId: String(createdRemote.workspaceId || createdRemote.workspace?.id || ""),
138
+ workspaceName: String(createdRemote.workspace?.name || `${agentName} Workspace`),
139
+ agentId: String(createdRemote.agentId || createdRemote.user?.id || "usr_agent"),
140
+ governanceDid: String(createdRemote.governanceDid || ""),
141
+ supermemoryNamespace: String(createdRemote.supermemoryNamespace || ""),
142
+ updatedAt: new Date().toISOString(),
103
143
  };
144
+ return { authPayload, rawResponse: createdRemote };
145
+ };
146
+ const config = getConfig();
147
+ let _store = null;
148
+ let _engine = null;
149
+ const getStore = () => {
150
+ if (!_store) {
151
+ _store = new MuxoStore(config.dbPath, config.vaultMasterKey);
152
+ }
153
+ return _store;
154
+ };
155
+ const getEngine = () => {
156
+ if (!_engine) {
157
+ _engine = new MuxoEngine(config, getStore());
158
+ }
159
+ return _engine;
160
+ };
161
+ const getLocalContext = () => {
162
+ const store = getStore();
163
+ const tokenAuth = apiKeyFlag ? store.resolveAuthToken(apiKeyFlag) : null;
164
+ const fallback = store.ensureDefaultWorkspace();
165
+ const ctx = tokenAuth
166
+ ? {
167
+ user: tokenAuth.user,
168
+ workspace: tokenAuth.workspace,
169
+ actorId: tokenAuth.actorId,
170
+ authType: tokenAuth.authType,
171
+ }
172
+ : {
173
+ user: fallback.user,
174
+ workspace: fallback.workspace,
175
+ actorId: `cli:${fallback.user.id}`,
176
+ authType: "session",
177
+ };
178
+ return { store, engine: getEngine(), ctx, tokenAuth };
179
+ };
180
+ const maskKey = (key) => `${key.slice(0, 14)}…`;
181
+ // Hosted Muxo answers purchases with 402 + a Stripe Checkout URL; a self-hosted dev server may grant credits directly.
182
+ const printBillingResult = (data) => {
183
+ if (data.code === "human_required" || data.status === "human_required") {
184
+ console.log(JSON.stringify({
185
+ ok: false,
186
+ humanRequired: true,
187
+ url: data.url,
188
+ error: data.error,
189
+ next: "Open the url in a browser and sign in to the Muxo console to finish this step. Agent API keys cannot manage billing or approvals.",
190
+ }, null, 2));
191
+ return;
192
+ }
193
+ if (data.portalUrl) {
194
+ console.log(JSON.stringify({
195
+ ok: true,
196
+ actionRequired: true,
197
+ tier: data.tier,
198
+ interval: data.interval,
199
+ portalUrl: data.portalUrl,
200
+ next: data.nextStep || "Open portalUrl in a browser; a signed-in person confirms the plan change in the Stripe billing portal.",
201
+ }, null, 2));
202
+ return;
203
+ }
204
+ if (!data.checkoutUrl) {
205
+ console.log(JSON.stringify(data, null, 2));
206
+ return;
207
+ }
208
+ console.log(JSON.stringify({
209
+ ok: true,
210
+ paymentRequired: true,
211
+ kind: data.kind,
212
+ credits: data.credits,
213
+ checkoutUrl: data.checkoutUrl,
214
+ sessionId: data.sessionId,
215
+ next: `Open checkoutUrl to pay with Stripe. Muxo adds the credits once Stripe confirms the payment. Check with: muxo billing verify ${data.sessionId}`,
216
+ }, null, 2));
217
+ };
104
218
  // CLI Login / Autonomous External Signup (`muxo login`)
105
219
  if (cmd === "login" || cmd === "signup") {
106
220
  const explicitKey = typeof flags["api-key"] === "string"
@@ -109,6 +223,35 @@ async function runCli() {
109
223
  ? positional[1]
110
224
  : "";
111
225
  if (explicitKey) {
226
+ if (remoteUrl) {
227
+ const boot = await remoteFetch("/api/v1/bootstrap", {
228
+ apiKey: explicitKey,
229
+ });
230
+ const ws = boot.organization || {};
231
+ const usr = boot.profile?.user || {};
232
+ const authPayload = {
233
+ authenticated: true,
234
+ apiKey: explicitKey,
235
+ apiUrl: remoteUrl,
236
+ workspaceId: String(ws.id || boot.profile?.workspaceId || ""),
237
+ workspaceName: String(ws.name || "Muxo Workspace"),
238
+ agentId: String(usr.id || "usr_agent"),
239
+ governanceDid: String(boot.governance?.agentDid || ""),
240
+ supermemoryNamespace: String(boot.supermemory?.namespace || ""),
241
+ updatedAt: new Date().toISOString(),
242
+ };
243
+ const cfgPath = writeCliAuthConfig(authPayload);
244
+ console.log(JSON.stringify({
245
+ ok: true,
246
+ mode: "api_key",
247
+ configPath: cfgPath,
248
+ workspace: ws,
249
+ ...authPayload,
250
+ apiKey: maskKey(explicitKey),
251
+ }, null, 2));
252
+ return;
253
+ }
254
+ const { store, engine } = getLocalContext();
112
255
  const resolved = store.resolveAuthToken(explicitKey);
113
256
  if (!resolved) {
114
257
  throw new Error("Invalid Muxo API key supplied to 'muxo login'.");
@@ -129,6 +272,7 @@ async function runCli() {
129
272
  mode: "api_key",
130
273
  configPath: cfgPath,
131
274
  ...authPayload,
275
+ apiKey: maskKey(explicitKey),
132
276
  }, null, 2));
133
277
  return;
134
278
  }
@@ -137,6 +281,23 @@ async function runCli() {
137
281
  ? flags.name
138
282
  : positional[1] || "Muxo CLI Agent";
139
283
  const harness = typeof flags.harness === "string" ? flags.harness : "muxo-cli";
284
+ if (remoteUrl) {
285
+ const { authPayload, rawResponse } = await signupHostedWorkspace(agentName, harness);
286
+ const cfgPath = writeCliAuthConfig(authPayload);
287
+ console.log(JSON.stringify({
288
+ ok: true,
289
+ mode: "autonomous_signup",
290
+ configPath: cfgPath,
291
+ workspace: rawResponse.workspace,
292
+ creditsBalance: rawResponse.workspace?.creditsBalance ?? 250,
293
+ mcpEndpoint: rawResponse.mcpEndpoint || `${remoteUrl}/mcp`,
294
+ skillUrl: rawResponse.skillUrl || `${remoteUrl}/skill.md`,
295
+ claimEndpoint: rawResponse.claimEndpoint || `${remoteUrl}/api/v1/agents/claim`,
296
+ ...authPayload,
297
+ }, null, 2));
298
+ return;
299
+ }
300
+ const { store, engine } = getLocalContext();
140
301
  const created = store.createAutonomousAgentSignup({
141
302
  agentName,
142
303
  harness,
@@ -168,11 +329,165 @@ async function runCli() {
168
329
  console.log(JSON.stringify({ ok: true, authenticated: false, configPath: cfgPath }, null, 2));
169
330
  return;
170
331
  }
332
+ // One-command setup (`muxo setup` / `muxo init` / `muxo install`):
333
+ // 1. Auto-creates or loads ~/.muxo/config.json with 250 starter credits (or binds an explicit --api-key)
334
+ // 2. Writes .mcp.json in target directory (or ~/.cursor/mcp.json & ~/.factory/mcp.json if present)
335
+ if (cmd === "setup" || cmd === "init" || cmd === "install") {
336
+ const explicitKey = typeof flags["api-key"] === "string"
337
+ ? flags["api-key"].trim()
338
+ : typeof flags.key === "string"
339
+ ? flags.key.trim()
340
+ : positional[1]?.startsWith("mk_")
341
+ ? positional[1].trim()
342
+ : "";
343
+ let activeConfig = savedCliConfig;
344
+ if (explicitKey) {
345
+ const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
346
+ if (remoteUrl) {
347
+ apiKeyFlag = explicitKey;
348
+ const joinRes = (await remoteFetch("/api/v1/workspaces/join", {
349
+ method: "POST",
350
+ body: { apiKey: explicitKey },
351
+ apiKey: explicitKey,
352
+ }).catch(() => null));
353
+ const orgRes = joinRes?.workspace
354
+ ? { organization: joinRes.workspace }
355
+ : (await remoteFetch("/api/v1/organization").catch(() => ({})));
356
+ const org = orgRes?.organization || orgRes?.workspace || {};
357
+ activeConfig = {
358
+ authenticated: true,
359
+ apiKey: explicitKey,
360
+ apiUrl: remoteUrl,
361
+ workspaceId: org.id || "ws_remote",
362
+ workspaceName: org.name || agentName,
363
+ agentId: "usr_remote",
364
+ governanceDid: org.governanceDid || `did:muxo:${org.id || "ws_remote"}`,
365
+ supermemoryNamespace: `muxo_${org.id || "ws_remote"}`,
366
+ updatedAt: new Date().toISOString(),
367
+ };
368
+ }
369
+ else {
370
+ const { store, engine } = getLocalContext();
371
+ const auth = store.resolveAuthToken(explicitKey);
372
+ if (!auth) {
373
+ throw new Error(`Invalid API key provided to muxo setup: ${explicitKey.slice(0, 12)}...`);
374
+ }
375
+ activeConfig = {
376
+ authenticated: true,
377
+ apiKey: explicitKey,
378
+ workspaceId: auth.workspace.id,
379
+ workspaceName: auth.workspace.name,
380
+ agentId: auth.user.id,
381
+ governanceDid: engine.governance.getStatus(auth.workspace).agentDid,
382
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(auth.workspace.id),
383
+ updatedAt: new Date().toISOString(),
384
+ };
385
+ }
386
+ writeCliAuthConfig(activeConfig);
387
+ }
388
+ else if (!activeConfig || !activeConfig.apiKey || flags.fresh) {
389
+ const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
390
+ if (remoteUrl) {
391
+ const { authPayload } = await signupHostedWorkspace(agentName, "muxo-setup");
392
+ activeConfig = authPayload;
393
+ }
394
+ else {
395
+ const { store, engine } = getLocalContext();
396
+ const created = store.createAutonomousAgentSignup({
397
+ agentName,
398
+ harness: "muxo-setup",
399
+ });
400
+ activeConfig = {
401
+ authenticated: true,
402
+ apiKey: created.apiKey,
403
+ workspaceId: created.workspace.id,
404
+ workspaceName: created.workspace.name,
405
+ agentId: created.user.id,
406
+ governanceDid: engine.governance.getStatus(created.workspace).agentDid,
407
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(created.workspace.id),
408
+ updatedAt: new Date().toISOString(),
409
+ };
410
+ }
411
+ writeCliAuthConfig(activeConfig);
412
+ }
413
+ const ensuredConfig = activeConfig;
414
+ const mcpServerUrl = remoteUrl
415
+ ? `${remoteUrl}/mcp`
416
+ : "https://api.muxo.ai/mcp";
417
+ const mcpEntry = {
418
+ type: "http",
419
+ url: mcpServerUrl,
420
+ headers: {
421
+ Authorization: `Bearer ${ensuredConfig.apiKey}`,
422
+ },
423
+ };
424
+ const targetDir = typeof flags.dir === "string" ? path.resolve(flags.dir) : process.cwd();
425
+ const writtenFiles = [];
426
+ const mergeMcpJsonFile = (filePath) => {
427
+ try {
428
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
429
+ let existing = {};
430
+ if (fs.existsSync(filePath)) {
431
+ existing = JSON.parse(fs.readFileSync(filePath, "utf8")) || {};
432
+ }
433
+ existing.mcpServers = {
434
+ ...(existing.mcpServers || {}),
435
+ muxo: mcpEntry,
436
+ };
437
+ fs.writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n");
438
+ writtenFiles.push(filePath);
439
+ }
440
+ catch {
441
+ // Ignore non-writable paths
442
+ }
443
+ };
444
+ // Always write .mcp.json in targetDir when requested or if in a project
445
+ mergeMcpJsonFile(path.join(targetDir, ".mcp.json"));
446
+ const resultPayload = {
447
+ ok: true,
448
+ workspaceId: ensuredConfig.workspaceId,
449
+ workspaceName: ensuredConfig.workspaceName,
450
+ apiKey: ensuredConfig.apiKey,
451
+ mcpUrl: mcpServerUrl,
452
+ configPath: getCliConfigPath(),
453
+ configuredFiles: writtenFiles,
454
+ };
455
+ if (flags.json || !process.stdout.isTTY) {
456
+ console.log(JSON.stringify(resultPayload, null, 2));
457
+ }
458
+ else {
459
+ console.log(`\n✓ Muxo is ready!`);
460
+ console.log(` Workspace : ${ensuredConfig.workspaceName} (${ensuredConfig.workspaceId})`);
461
+ console.log(` API Key : ${maskKey(ensuredConfig.apiKey)} (saved in ${getCliConfigPath()})`);
462
+ console.log(` MCP URL : ${mcpServerUrl}`);
463
+ console.log(` MCP Config: ${writtenFiles.join(", ")}`);
464
+ console.log(`\nTry it now:`);
465
+ console.log(` muxo do "check dns A records for muxo.ai"`);
466
+ console.log(` muxo tui\n`);
467
+ }
468
+ return;
469
+ }
171
470
  if (cmd === "claim") {
172
471
  const email = positional[1] || (typeof flags.email === "string" ? flags.email : "");
173
472
  if (!email || !email.includes("@")) {
174
473
  throw new Error("Usage: muxo claim <owner_email>");
175
474
  }
475
+ if (remoteUrl) {
476
+ const res = await remoteFetch("/api/v1/agents/claim", {
477
+ method: "POST",
478
+ body: { email },
479
+ });
480
+ const claimObj = res.claim || res;
481
+ console.log(JSON.stringify({
482
+ ok: true,
483
+ claim: claimObj,
484
+ ownerEmail: res.ownerEmail || claimObj?.targetEmail || email,
485
+ claimSlug: res.claimSlug || claimObj?.claimCode,
486
+ ...claimObj,
487
+ }, null, 2));
488
+ return;
489
+ }
490
+ const { store, ctx } = getLocalContext();
176
491
  const claim = store.createAgentClaim({
177
492
  workspaceId: ctx.workspace.id,
178
493
  email,
@@ -183,11 +498,22 @@ async function runCli() {
183
498
  }
184
499
  // Stdio MCP Server mode (`muxo mcp`)
185
500
  if (cmd === "mcp") {
501
+ if (remoteUrl && !apiKeyFlag) {
502
+ try {
503
+ const { authPayload } = await signupHostedWorkspace("Muxo MCP Agent", "muxo-mcp-stdio");
504
+ writeCliAuthConfig(authPayload);
505
+ apiKeyFlag = authPayload.apiKey;
506
+ }
507
+ catch {
508
+ // Proceed with remote default workspace if signup fails
509
+ }
510
+ }
186
511
  const rl = readline.createInterface({
187
512
  input: process.stdin,
188
513
  terminal: false,
189
514
  });
190
515
  let queue = Promise.resolve();
516
+ let mcpSessionId = "";
191
517
  rl.on("line", (line) => {
192
518
  const trimmed = line.trim();
193
519
  if (!trimmed)
@@ -195,6 +521,38 @@ async function runCli() {
195
521
  queue = queue.then(async () => {
196
522
  try {
197
523
  const rpc = JSON.parse(trimmed);
524
+ if (remoteUrl) {
525
+ const headers = {
526
+ "Content-Type": "application/json",
527
+ Accept: "application/json",
528
+ };
529
+ if (apiKeyFlag) {
530
+ headers["Authorization"] = `Bearer ${apiKeyFlag}`;
531
+ }
532
+ if (mcpSessionId) {
533
+ headers["Mcp-Session-Id"] = mcpSessionId;
534
+ }
535
+ if (typeof rpc.method === "string" && rpc.method.trim()) {
536
+ headers["Mcp-Method"] = rpc.method.trim();
537
+ }
538
+ const res = await fetch(`${remoteUrl}/mcp`, {
539
+ method: "POST",
540
+ headers,
541
+ body: JSON.stringify(rpc),
542
+ });
543
+ const sid = res.headers.get("mcp-session-id");
544
+ if (sid)
545
+ mcpSessionId = sid;
546
+ if (rpc.id !== undefined && res.status !== 202 && res.status !== 204) {
547
+ const text = await res.text();
548
+ if (text.trim()) {
549
+ const parsed = JSON.parse(text);
550
+ process.stdout.write(JSON.stringify(parsed) + "\n");
551
+ }
552
+ }
553
+ return;
554
+ }
555
+ const { engine, ctx } = getLocalContext();
198
556
  const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
199
557
  if (response) {
200
558
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -220,12 +578,194 @@ async function runCli() {
220
578
  return;
221
579
  }
222
580
  if (cmd === "harness") {
581
+ const checks = [];
582
+ const skip = (reason) => ({ skip: reason });
583
+ const runCheck = async (id, name, fn) => {
584
+ const started = Date.now();
585
+ try {
586
+ const out = await fn();
587
+ checks.push(typeof out === "string"
588
+ ? { id, name, status: "pass", detail: out, ms: Date.now() - started }
589
+ : { id, name, status: "skip", detail: out.skip, ms: Date.now() - started });
590
+ }
591
+ catch (err) {
592
+ checks.push({
593
+ id,
594
+ name,
595
+ status: "fail",
596
+ detail: String(err?.message || err),
597
+ ms: Date.now() - started,
598
+ });
599
+ }
600
+ };
601
+ const assertCheck = (cond, message) => {
602
+ if (!cond)
603
+ throw new Error(message);
604
+ };
605
+ const noKeyHint = "No API key. Run `muxo login --signup` to create a workspace.";
606
+ let workspaceId = savedCliConfig?.workspaceId || null;
607
+ if (remoteUrl) {
608
+ let mcpSessionId = "";
609
+ const harnessFetch = async (pathname, init = {}) => {
610
+ const headers = { Accept: "application/json" };
611
+ if (init.body !== undefined)
612
+ headers["Content-Type"] = "application/json";
613
+ if (init.auth !== false && apiKeyFlag)
614
+ headers.Authorization = `Bearer ${apiKeyFlag}`;
615
+ if (pathname === "/mcp" && mcpSessionId)
616
+ headers["Mcp-Session-Id"] = mcpSessionId;
617
+ return fetch(`${remoteUrl}${pathname}`, {
618
+ method: init.method || "GET",
619
+ headers,
620
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
621
+ signal: AbortSignal.timeout(15_000),
622
+ });
623
+ };
624
+ const harnessJson = async (pathname, init) => {
625
+ const res = await harnessFetch(pathname, init);
626
+ const data = (await res.json().catch(() => ({})));
627
+ if (!res.ok) {
628
+ throw new Error(`HTTP ${res.status}${data.error ? `: ${data.error}` : ""}`);
629
+ }
630
+ return data;
631
+ };
632
+ const mcpCall = async (id, method, params = {}) => {
633
+ const res = await harnessFetch("/mcp", {
634
+ method: "POST",
635
+ body: { jsonrpc: "2.0", id, method, params },
636
+ });
637
+ const sid = res.headers.get("mcp-session-id");
638
+ if (sid)
639
+ mcpSessionId = sid;
640
+ const data = (await res.json().catch(() => ({})));
641
+ if (!res.ok || data.error) {
642
+ throw new Error(data.error?.message || `HTTP ${res.status}`);
643
+ }
644
+ return data.result;
645
+ };
646
+ await runCheck("api_health", "API is reachable", async () => {
647
+ const health = await harnessJson("/health", { auth: false });
648
+ assertCheck(health.ok === true, "Health endpoint did not report ok");
649
+ return `${health.service || "muxo"} ${health.version || ""} at ${remoteUrl}`.trim();
650
+ });
651
+ await runCheck("agent_skill", "Agent skill is published", async () => {
652
+ const res = await harnessFetch("/skill.md", { auth: false });
653
+ assertCheck(res.ok, `HTTP ${res.status}`);
654
+ const body = await res.text();
655
+ assertCheck(/execute_(read|write)/.test(body), "skill.md is missing the execute_* tools");
656
+ return `${remoteUrl}/skill.md (${body.length.toLocaleString()} bytes)`;
657
+ });
658
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
659
+ const result = await mcpCall(1, "initialize", {
660
+ protocolVersion: LATEST_MCP_PROTOCOL_VERSION,
661
+ capabilities: {},
662
+ clientInfo: { name: "muxo-cli-harness", version: MUXO_VERSION },
663
+ });
664
+ assertCheck(result?.serverInfo?.name, "initialize returned no serverInfo");
665
+ return `${result.serverInfo.name} ${result.serverInfo.version || ""} (protocol ${result.protocolVersion})`.trim();
666
+ });
667
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
668
+ const result = await mcpCall(2, "tools/list");
669
+ const count = Array.isArray(result?.tools) ? result.tools.length : 0;
670
+ assertCheck(count > 0, "tools/list returned no tools");
671
+ return `${count} tools`;
672
+ });
673
+ await runCheck("auth", "API key opens a workspace", async () => {
674
+ if (!apiKeyFlag)
675
+ return skip(noKeyHint);
676
+ const boot = await harnessJson("/api/v1/bootstrap");
677
+ const ws = { ...(boot.organization || {}), ...(boot.profile?.workspace || {}) };
678
+ assertCheck(ws.id, "bootstrap returned no workspace");
679
+ workspaceId = String(ws.id);
680
+ return `${ws.name || ws.id} (${ws.tier || "free"})`;
681
+ });
682
+ await runCheck("governance", "Agents are allowed to act", async () => {
683
+ if (!apiKeyFlag)
684
+ return skip(noKeyHint);
685
+ const res = await harnessJson("/api/v1/governance");
686
+ const g = res.governance || {};
687
+ if (g.killSwitch?.active) {
688
+ throw new Error(`Kill switch is on${g.killSwitch.reason ? ` (${g.killSwitch.reason})` : ""}. Run \`muxo governance resume\`.`);
689
+ }
690
+ const chainOk = g.auditChain?.chainVerified ?? g.auditChainIntegrity ?? g.auditIntegrityValid;
691
+ assertCheck(chainOk !== false, "Audit hash chain failed verification");
692
+ return `Running, audit chain verified, policy ${res.workspacePolicy?.mode || "enforce"}`;
693
+ });
694
+ await runCheck("catalog", "Tool catalog search works", async () => {
695
+ if (!apiKeyFlag)
696
+ return skip(noKeyHint);
697
+ const res = await harnessJson("/api/v1/tools?lane=composio_instant&limit=5");
698
+ const count = Array.isArray(res.tools) ? res.tools.length : 0;
699
+ assertCheck(count > 0, "Catalog search returned no tools");
700
+ return `${count} instant tools returned`;
701
+ });
702
+ }
703
+ else {
704
+ const local = getLocalContext();
705
+ workspaceId = local.ctx.workspace.id;
706
+ await runCheck("local_store", "Local workspace database opens", async () => {
707
+ const ws = local.store.getWorkspace(local.ctx.workspace.id);
708
+ assertCheck(ws, "Workspace not found in local database");
709
+ return `${ws.name} at ${config.dbPath}`;
710
+ });
711
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
712
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
713
+ jsonrpc: "2.0",
714
+ id: 1,
715
+ method: "initialize",
716
+ params: { protocolVersion: LATEST_MCP_PROTOCOL_VERSION, capabilities: {} },
717
+ });
718
+ const info = res?.result?.serverInfo;
719
+ assertCheck(info?.name, "initialize returned no serverInfo");
720
+ return `${info.name} ${info.version || ""}`.trim();
721
+ });
722
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
723
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
724
+ jsonrpc: "2.0",
725
+ id: 2,
726
+ method: "tools/list",
727
+ });
728
+ const count = (res?.result?.tools || []).length;
729
+ assertCheck(count > 0, "tools/list returned no tools");
730
+ return `${count} tools`;
731
+ });
732
+ await runCheck("auth", "API key opens a workspace", async () => {
733
+ if (!apiKeyFlag)
734
+ return skip("No API key. Using the local default workspace.");
735
+ assertCheck(local.tokenAuth, "API key is not valid for this local database");
736
+ return local.ctx.workspace.name;
737
+ });
738
+ await runCheck("governance", "Agents are allowed to act", async () => {
739
+ const ws = local.store.getWorkspace(local.ctx.workspace.id) || local.ctx.workspace;
740
+ const status = local.engine.governance.getGovernanceStatus(ws);
741
+ if (status.killSwitch?.active) {
742
+ throw new Error("Kill switch is on. Run `muxo governance resume`.");
743
+ }
744
+ assertCheck(status.auditChain?.chainVerified !== false, "Audit hash chain failed verification");
745
+ return `Running, audit chain verified, policy ${ws.policy.mode}`;
746
+ });
747
+ await runCheck("catalog", "Tool catalog search works", async () => {
748
+ const res = await local.engine.searchTools({ lane: "composio_instant", limit: 5 });
749
+ assertCheck(res.tools.length > 0, "Catalog search returned no tools");
750
+ return `${res.tools.length} instant tools returned`;
751
+ });
752
+ }
753
+ const passed = checks.filter((c) => c.status === "pass").length;
754
+ const failed = checks.filter((c) => c.status === "fail").length;
755
+ const skipped = checks.filter((c) => c.status === "skip").length;
756
+ if (failed > 0)
757
+ process.exitCode = 1;
223
758
  console.log(JSON.stringify({
224
- ok: true,
225
- version: "1.3.0",
226
- passed: 7,
227
- total: 7,
228
- workspaceId: ctx.workspace.id,
759
+ ok: failed === 0,
760
+ version: MUXO_VERSION,
761
+ mode: remoteUrl ? "hosted" : "local",
762
+ apiUrl: remoteUrl || null,
763
+ passed,
764
+ failed,
765
+ skipped,
766
+ total: checks.length,
767
+ checks,
768
+ workspaceId,
229
769
  data: {
230
770
  mcpServers: {
231
771
  muxo: {
@@ -234,7 +774,7 @@ async function runCli() {
234
774
  },
235
775
  },
236
776
  remoteMcp: {
237
- url: "https://api.muxo.ai/mcp",
777
+ url: `${remoteUrl || "https://api.muxo.ai"}/mcp`,
238
778
  transport: "streamable-http",
239
779
  },
240
780
  },
@@ -242,6 +782,47 @@ async function runCli() {
242
782
  return;
243
783
  }
244
784
  if (cmd === "whoami" || cmd === "status" || cmd === "doctor") {
785
+ if (remoteUrl) {
786
+ const boot = await remoteFetch("/api/v1/bootstrap");
787
+ const ws = {
788
+ ...(boot.organization || {}),
789
+ ...(boot.profile?.workspace || {}),
790
+ };
791
+ const creditsBal = ws.creditsBalance ??
792
+ ws.creditBalance ??
793
+ boot.organization?.usage?.creditsBalance ??
794
+ 0;
795
+ const usr = boot.profile?.user || {};
796
+ console.log(JSON.stringify({
797
+ ok: true,
798
+ authenticated: Boolean(apiKeyFlag),
799
+ authSource: apiKeyFlag
800
+ ? savedCliConfig?.apiKey === apiKeyFlag
801
+ ? "config_file"
802
+ : "api_key"
803
+ : "hosted_default",
804
+ apiUrl: remoteUrl,
805
+ profile: {
806
+ id: usr.id,
807
+ name: usr.name,
808
+ email: usr.email,
809
+ user: usr,
810
+ workspaceId: ws.id,
811
+ workspaceName: ws.name,
812
+ tier: ws.tier,
813
+ },
814
+ workspace: {
815
+ ...ws,
816
+ creditsBalance: creditsBal,
817
+ creditBalance: creditsBal,
818
+ },
819
+ organization: boot.organization || ws,
820
+ governanceDid: boot.governance?.agentDid,
821
+ supermemoryNamespace: boot.supermemory?.namespace,
822
+ }, null, 2));
823
+ return;
824
+ }
825
+ const { engine, ctx, tokenAuth } = getLocalContext();
245
826
  const org = engine.getOrganization(ctx);
246
827
  console.log(JSON.stringify({
247
828
  ok: true,
@@ -265,10 +846,31 @@ async function runCli() {
265
846
  if (cmd === "search") {
266
847
  const query = positional.slice(1).join(" ") || flags.query || "";
267
848
  const lane = flags.lane || "all";
849
+ const limit = flags.limit ? Number(flags.limit) : 20;
850
+ if (remoteUrl) {
851
+ const qs = new URLSearchParams({
852
+ query,
853
+ lane: String(lane),
854
+ limit: String(limit),
855
+ });
856
+ const res = await remoteFetch(`/api/v1/tools?${qs.toString()}`);
857
+ const tools = Array.isArray(res.tools) ? res.tools : [];
858
+ if (flags.json || !process.stdout.isTTY) {
859
+ console.log(JSON.stringify({ ok: true, count: res.count ?? tools.length, total: res.count ?? tools.length, tools }, null, 2));
860
+ return;
861
+ }
862
+ console.log(`\nFound ${tools.length} tools matching '${query || "*"}' (lane: ${lane}):\n`);
863
+ for (const t of tools) {
864
+ console.log(` • ${String(t.slug).padEnd(38)} [${String(t.lane).padEnd(16)}] (${t.riskLevel}, ${t.creditCost} cr)`);
865
+ console.log(` ${String(t.description || "").slice(0, 100)}`);
866
+ }
867
+ return;
868
+ }
869
+ const { engine } = getLocalContext();
268
870
  const res = await engine.searchTools({
269
871
  query,
270
872
  lane,
271
- limit: flags.limit ? Number(flags.limit) : 20,
873
+ limit,
272
874
  });
273
875
  if (flags.json || !process.stdout.isTTY) {
274
876
  console.log(JSON.stringify({ ok: true, count: res.total, ...res }, null, 2));
@@ -285,11 +887,34 @@ async function runCli() {
285
887
  const slug = positional[1];
286
888
  if (!slug)
287
889
  throw new Error("Usage: muxo inspect <tool_slug>");
890
+ if (remoteUrl) {
891
+ const tool = await remoteFetch(`/api/v1/tools/${encodeURIComponent(slug)}`);
892
+ console.log(JSON.stringify(tool, null, 2));
893
+ return;
894
+ }
895
+ const { engine } = getLocalContext();
288
896
  const tool = await engine.inspectTool(slug);
289
897
  console.log(JSON.stringify(tool, null, 2));
290
898
  return;
291
899
  }
292
900
  if (cmd === "connections") {
901
+ if (remoteUrl) {
902
+ const res = await remoteFetch("/api/v1/connections");
903
+ const allConns = Array.isArray(res.connections) ? res.connections : [];
904
+ const providerFilter = positional[1]
905
+ ? positional[1].toLowerCase().replace(/^app\./, "")
906
+ : "";
907
+ const filtered = providerFilter
908
+ ? allConns.filter((c) => c.providerSlug === providerFilter)
909
+ : allConns;
910
+ console.log(JSON.stringify({
911
+ ok: true,
912
+ connections: filtered,
913
+ activeCount: filtered.filter((c) => c.status === "active").length,
914
+ }, null, 2));
915
+ return;
916
+ }
917
+ const { engine, ctx } = getLocalContext();
293
918
  const status = await engine.connectionStatus(ctx, {
294
919
  provider: positional[1],
295
920
  syncLive: flags.sync !== "false",
@@ -301,6 +926,37 @@ async function runCli() {
301
926
  const provider = positional[1];
302
927
  if (!provider)
303
928
  throw new Error(`Usage: muxo ${cmd} <provider> [--wait] [--timeout 30]`);
929
+ if (remoteUrl) {
930
+ const created = await remoteFetch("/api/v1/connections", {
931
+ method: "POST",
932
+ body: {
933
+ provider,
934
+ lane: flags.lane,
935
+ callbackUrl: typeof flags.callback === "string" ? flags.callback : undefined,
936
+ },
937
+ });
938
+ const conn = created.connection || created;
939
+ const connection = { ...conn, provider: conn.providerSlug || provider };
940
+ if (flags.wait) {
941
+ const timeoutSeconds = flags.timeout ? Number(flags.timeout) : 15;
942
+ const deadline = Date.now() + timeoutSeconds * 1000;
943
+ let waited = {};
944
+ while (Date.now() < deadline) {
945
+ waited = await remoteFetch("/api/v1/connections/wait", {
946
+ method: "POST",
947
+ body: { provider },
948
+ });
949
+ if (waited.ready || waited.status === "active")
950
+ break;
951
+ await new Promise((r) => setTimeout(r, 1500));
952
+ }
953
+ console.log(JSON.stringify({ ok: true, connection, ...conn, waitResult: waited }, null, 2));
954
+ return;
955
+ }
956
+ console.log(JSON.stringify({ ok: true, connection, ...conn }, null, 2));
957
+ return;
958
+ }
959
+ const { engine, ctx } = getLocalContext();
304
960
  const res = await engine.startConnection(ctx, {
305
961
  provider,
306
962
  lane: flags.lane,
@@ -323,6 +979,24 @@ async function runCli() {
323
979
  const provider = positional[1];
324
980
  if (!provider)
325
981
  throw new Error("Usage: muxo wait <provider> [--timeout 15]");
982
+ const timeoutSeconds = flags.timeout ? Number(flags.timeout) : 15;
983
+ if (remoteUrl) {
984
+ const simulateCallback = Boolean(flags["simulate-callback"]);
985
+ const deadline = Date.now() + timeoutSeconds * 1000;
986
+ let waited = {};
987
+ do {
988
+ waited = await remoteFetch("/api/v1/connections/wait", {
989
+ method: "POST",
990
+ body: { provider, simulateCallback },
991
+ });
992
+ if (waited.ready || waited.status === "active" || simulateCallback)
993
+ break;
994
+ await new Promise((r) => setTimeout(r, 1500));
995
+ } while (Date.now() < deadline);
996
+ console.log(JSON.stringify({ ok: true, ...waited }, null, 2));
997
+ return;
998
+ }
999
+ const { engine, ctx } = getLocalContext();
326
1000
  if (flags["simulate-callback"]) {
327
1001
  await engine.handleOAuthCallback({
328
1002
  workspaceId: ctx.workspace.id,
@@ -330,12 +1004,11 @@ async function runCli() {
330
1004
  status: "active",
331
1005
  });
332
1006
  }
333
- const timeoutSeconds = flags.timeout ? Number(flags.timeout) : 15;
334
1007
  const waited = await engine.waitForConnection(ctx, {
335
1008
  provider,
336
1009
  timeoutSeconds,
337
1010
  });
338
- console.log(JSON.stringify({ ok: true, ...waited }, null, 2));
1011
+ console.log(JSON.stringify(waited, null, 2));
339
1012
  return;
340
1013
  }
341
1014
  if (cmd === "plan") {
@@ -344,6 +1017,15 @@ async function runCli() {
344
1017
  throw new Error("Usage: muxo plan <tool_slug> [--input '{...}']");
345
1018
  const input = flags.input ? JSON.parse(String(flags.input)) : {};
346
1019
  const reason = flags.reason ? String(flags.reason) : "Planned via Muxo CLI";
1020
+ if (remoteUrl) {
1021
+ const planned = await remoteFetch("/api/v1/plans", {
1022
+ method: "POST",
1023
+ body: { toolSlug, input, reason },
1024
+ });
1025
+ console.log(JSON.stringify(planned, null, 2));
1026
+ return;
1027
+ }
1028
+ const { engine, ctx } = getLocalContext();
347
1029
  const planned = await engine.planAction(ctx, { toolSlug, input, reason });
348
1030
  console.log(JSON.stringify(planned, null, 2));
349
1031
  return;
@@ -352,6 +1034,23 @@ async function runCli() {
352
1034
  const planId = positional[1];
353
1035
  if (!planId)
354
1036
  throw new Error("Usage: muxo approve <plan_id>");
1037
+ if (remoteUrl) {
1038
+ const approved = await remoteFetch(`/api/v1/plans/${encodeURIComponent(planId)}/approve`, { method: "POST", body: {}, acceptHumanRequired: true });
1039
+ if (approved.code === "human_required") {
1040
+ console.log(JSON.stringify({
1041
+ ok: false,
1042
+ humanRequired: true,
1043
+ planId,
1044
+ approvalUrl: approved.url,
1045
+ error: approved.error,
1046
+ hint: "Open approvalUrl in a browser; a signed-in person approves the plan in the Muxo console. Agent API keys cannot approve plans.",
1047
+ }, null, 2));
1048
+ return;
1049
+ }
1050
+ console.log(JSON.stringify(approved.plan || approved, null, 2));
1051
+ return;
1052
+ }
1053
+ const { engine, ctx } = getLocalContext();
355
1054
  const approved = engine.approvePlan(ctx, planId, "approved");
356
1055
  console.log(JSON.stringify(approved, null, 2));
357
1056
  return;
@@ -360,6 +1059,12 @@ async function runCli() {
360
1059
  const planId = positional[1];
361
1060
  if (!planId)
362
1061
  throw new Error("Usage: muxo run <plan_id>");
1062
+ if (remoteUrl) {
1063
+ const executed = await remoteFetch(`/api/v1/plans/${encodeURIComponent(planId)}/execute`, { method: "POST", body: {} });
1064
+ console.log(JSON.stringify(executed, null, 2));
1065
+ return;
1066
+ }
1067
+ const { engine, ctx } = getLocalContext();
363
1068
  const plan = engine.getPlan(ctx, planId);
364
1069
  const result = await engine.executePlanForRisk(ctx, planId, plan.riskLevel);
365
1070
  console.log(JSON.stringify(result, null, 2));
@@ -369,6 +1074,65 @@ async function runCli() {
369
1074
  let toolSlug = positional[1];
370
1075
  if (!toolSlug)
371
1076
  throw new Error(`Usage: muxo ${cmd} <tool_slug> [--input '{...}'] [--approve]`);
1077
+ const input = flags.input ? JSON.parse(String(flags.input)) : {};
1078
+ if (remoteUrl) {
1079
+ if (cmd === "provision" && !toolSlug.includes(".")) {
1080
+ const qs = new URLSearchParams({
1081
+ query: toolSlug,
1082
+ lane: "stripe_projects",
1083
+ limit: "10",
1084
+ });
1085
+ const searched = await remoteFetch(`/api/v1/tools?${qs.toString()}`);
1086
+ const tools = Array.isArray(searched.tools) ? searched.tools : [];
1087
+ const createTool = tools.find((t) => String(t.slug).startsWith(`resource.${toolSlug.toLowerCase()}.create_`)) ||
1088
+ tools.find((t) => String(t.slug).startsWith(`resource.${toolSlug.toLowerCase()}.`)) ||
1089
+ tools[0];
1090
+ if (createTool) {
1091
+ toolSlug = String(createTool.slug);
1092
+ }
1093
+ }
1094
+ const planned = await remoteFetch("/api/v1/plans", {
1095
+ method: "POST",
1096
+ body: { toolSlug, input, reason: `Direct CLI ${cmd}` },
1097
+ });
1098
+ const plan = planned.plan || planned;
1099
+ if (plan.approvalStatus === "pending_approval") {
1100
+ if (flags.approve || cmd === "provision") {
1101
+ const approved = await remoteFetch(`/api/v1/plans/${encodeURIComponent(plan.id)}/approve`, { method: "POST", body: {}, acceptHumanRequired: true });
1102
+ if (approved.code === "human_required") {
1103
+ console.log(JSON.stringify({
1104
+ ok: false,
1105
+ status: "pending_approval",
1106
+ humanRequired: true,
1107
+ plan,
1108
+ approvalUrl: approved.url,
1109
+ hint: "This plan needs approval from a signed-in person. Open approvalUrl in a browser to approve it in the Muxo console; agent API keys cannot approve plans.",
1110
+ }, null, 2));
1111
+ return;
1112
+ }
1113
+ }
1114
+ else {
1115
+ console.log(JSON.stringify({
1116
+ ok: false,
1117
+ status: "pending_approval",
1118
+ plan,
1119
+ hint: `Pass --approve or run 'muxo approve ${plan.id}' to authorize this ${plan.riskLevel} operation.`,
1120
+ }, null, 2));
1121
+ return;
1122
+ }
1123
+ }
1124
+ const result = await remoteFetch(`/api/v1/plans/${encodeURIComponent(plan.id)}/execute`, { method: "POST", body: {} });
1125
+ console.log(JSON.stringify({
1126
+ ok: true,
1127
+ ...result,
1128
+ receipt: result.receipt
1129
+ ? { ...result.receipt, verified: true }
1130
+ : undefined,
1131
+ settlement: result.settlement || result.receipt?.supplierSettlement || null,
1132
+ }, null, 2));
1133
+ return;
1134
+ }
1135
+ const { store, engine, ctx } = getLocalContext();
372
1136
  if (cmd === "provision" && !toolSlug.includes(".")) {
373
1137
  const searched = await engine.searchTools({
374
1138
  query: toolSlug,
@@ -382,7 +1146,6 @@ async function runCli() {
382
1146
  toolSlug = createTool.slug;
383
1147
  }
384
1148
  }
385
- const input = flags.input ? JSON.parse(String(flags.input)) : {};
386
1149
  const planned = await engine.planAction(ctx, {
387
1150
  toolSlug,
388
1151
  input,
@@ -416,11 +1179,40 @@ async function runCli() {
416
1179
  return;
417
1180
  }
418
1181
  if (cmd === "resources") {
1182
+ if (remoteUrl) {
1183
+ const res = await remoteFetch("/api/v1/resources");
1184
+ console.log(JSON.stringify(res.resources || res, null, 2));
1185
+ return;
1186
+ }
1187
+ const { store, ctx } = getLocalContext();
419
1188
  console.log(JSON.stringify(store.listResources(ctx.workspace.id), null, 2));
420
1189
  return;
421
1190
  }
422
1191
  if (cmd === "env" && positional[1] === "pull") {
423
- const outFile = String(flags.out || ".env.muxo");
1192
+ const outFile = String(flags.out || flags.output || ".env.muxo");
1193
+ const resolvedPath = path.resolve(process.cwd(), outFile);
1194
+ if (remoteUrl) {
1195
+ const envRes = await remoteFetch("/api/v1/resources/env");
1196
+ const vars = (envRes.variables || {});
1197
+ const lines = typeof envRes.dotenv === "string" && envRes.dotenv.trim()
1198
+ ? envRes.dotenv
1199
+ : [
1200
+ `# Muxo Provisioned Environment`,
1201
+ ...Object.entries(vars).map(([k, v]) => `${k}=${JSON.stringify(v)}`),
1202
+ "",
1203
+ ].join("\n");
1204
+ fs.writeFileSync(resolvedPath, lines, { mode: 0o600 });
1205
+ const keys = Object.keys(vars);
1206
+ console.log(JSON.stringify({
1207
+ ok: true,
1208
+ outputFile: resolvedPath,
1209
+ keysWritten: keys,
1210
+ keys,
1211
+ keyCount: keys.length,
1212
+ }, null, 2));
1213
+ return;
1214
+ }
1215
+ const { store, engine, ctx } = getLocalContext();
424
1216
  const workdirSecrets = await engine.stripeProjects
425
1217
  .pullEnvVarsFromWorkdir()
426
1218
  .catch(() => ({}));
@@ -431,7 +1223,6 @@ async function runCli() {
431
1223
  ...Object.entries(merged).map(([k, v]) => `${k}=${JSON.stringify(v)}`),
432
1224
  "",
433
1225
  ];
434
- const resolvedPath = path.resolve(process.cwd(), outFile);
435
1226
  fs.writeFileSync(resolvedPath, lines.join("\n"), { mode: 0o600 });
436
1227
  const keys = Object.keys(merged);
437
1228
  console.log(JSON.stringify({
@@ -445,11 +1236,23 @@ async function runCli() {
445
1236
  }
446
1237
  if (cmd === "receipts") {
447
1238
  const receiptId = positional[1];
1239
+ if (remoteUrl) {
1240
+ if (receiptId) {
1241
+ const res = await remoteFetch(`/api/v1/receipts/${encodeURIComponent(receiptId)}`);
1242
+ console.log(JSON.stringify(res, null, 2));
1243
+ }
1244
+ else {
1245
+ const res = await remoteFetch("/api/v1/receipts?limit=20");
1246
+ console.log(JSON.stringify(res, null, 2));
1247
+ }
1248
+ return;
1249
+ }
1250
+ const { engine, ctx } = getLocalContext();
448
1251
  if (receiptId) {
449
1252
  console.log(JSON.stringify(engine.getReceipt(ctx, receiptId), null, 2));
450
1253
  }
451
1254
  else {
452
- console.log(JSON.stringify(store.listReceipts(ctx.workspace.id, 20), null, 2));
1255
+ console.log(JSON.stringify({ ok: true, ...engine.listReceipts(ctx, 20) }, null, 2));
453
1256
  }
454
1257
  return;
455
1258
  }
@@ -457,6 +1260,16 @@ async function runCli() {
457
1260
  const sub = cmd === "topup" ? "topup" : positional[1] || "status";
458
1261
  if (sub === "topup") {
459
1262
  const credits = Math.max(1, Number(flags.credits || (cmd === "topup" ? positional[1] : positional[2]) || 1000));
1263
+ if (remoteUrl) {
1264
+ const data = await remoteFetch("/api/v1/billing/topup", {
1265
+ method: "POST",
1266
+ body: { credits },
1267
+ acceptPaymentRequired: true,
1268
+ });
1269
+ printBillingResult(data);
1270
+ return;
1271
+ }
1272
+ const { store, ctx } = getLocalContext();
460
1273
  const usdPaid = (credits * 0.01).toFixed(2);
461
1274
  const refId = `cli_topup_${Date.now()}`;
462
1275
  const updated = store.addWorkspaceCredits({
@@ -476,6 +1289,22 @@ async function runCli() {
476
1289
  return;
477
1290
  }
478
1291
  if (sub === "upgrade") {
1292
+ const planArg = String(flags.plan || positional[2] || "starter").toLowerCase();
1293
+ const tier = planArg === "pro" ? "pro" : "starter";
1294
+ const interval = flags.yearly || flags.year || String(flags.interval || "").startsWith("y")
1295
+ ? "year"
1296
+ : "month";
1297
+ if (remoteUrl) {
1298
+ const data = await remoteFetch("/api/v1/billing/upgrade", {
1299
+ method: "POST",
1300
+ body: { tier, interval },
1301
+ acceptPaymentRequired: true,
1302
+ acceptHumanRequired: true,
1303
+ });
1304
+ printBillingResult(data);
1305
+ return;
1306
+ }
1307
+ const { store, ctx } = getLocalContext();
479
1308
  const updated = store.upgradeWorkspaceToStarter({
480
1309
  workspaceId: ctx.workspace.id,
481
1310
  referenceId: `cli_upgrade_${Date.now()}`,
@@ -499,6 +1328,15 @@ async function runCli() {
499
1328
  ? "starter_subscription"
500
1329
  : "credit_topup";
501
1330
  const credits = flags.credits ? Number(flags.credits) : 1000;
1331
+ if (remoteUrl) {
1332
+ const data = await remoteFetch("/api/v1/billing/checkout", {
1333
+ method: "POST",
1334
+ body: { kind, credits },
1335
+ });
1336
+ console.log(JSON.stringify(data, null, 2));
1337
+ return;
1338
+ }
1339
+ const { engine, ctx } = getLocalContext();
502
1340
  const session = await engine.stripeBilling.createCheckoutSession({
503
1341
  workspace: ctx.workspace,
504
1342
  customerEmail: ctx.user.email,
@@ -509,14 +1347,50 @@ async function runCli() {
509
1347
  console.log(JSON.stringify({ ok: true, kind, ...session }, null, 2));
510
1348
  return;
511
1349
  }
1350
+ if (sub === "plans") {
1351
+ const plansUrl = remoteUrl || config.baseUrl;
1352
+ const res = await fetch(`${plansUrl}/api/v1/billing/plans`, {
1353
+ headers: { Accept: "application/json" },
1354
+ });
1355
+ const data = (await res.json().catch(() => ({})));
1356
+ console.log(JSON.stringify(data, null, 2));
1357
+ return;
1358
+ }
1359
+ if (sub === "portal") {
1360
+ if (remoteUrl) {
1361
+ const data = await remoteFetch("/api/v1/billing/portal", {
1362
+ method: "POST",
1363
+ body: {},
1364
+ acceptHumanRequired: true,
1365
+ });
1366
+ printBillingResult(data);
1367
+ return;
1368
+ }
1369
+ throw new Error("muxo billing portal is only available for the hosted Muxo service (set --remote or log in first).");
1370
+ }
512
1371
  if (sub === "verify") {
513
1372
  const sessionId = positional[2] || String(flags.session || "");
514
1373
  if (!sessionId)
515
1374
  throw new Error("Usage: muxo billing verify <session_id>");
1375
+ if (remoteUrl) {
1376
+ const data = await remoteFetch("/api/v1/billing/verify-session", {
1377
+ method: "POST",
1378
+ body: { sessionId },
1379
+ });
1380
+ console.log(JSON.stringify(data, null, 2));
1381
+ return;
1382
+ }
1383
+ const { engine } = getLocalContext();
516
1384
  const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(sessionId);
517
1385
  console.log(JSON.stringify({ ok: true, ...verified }, null, 2));
518
1386
  return;
519
1387
  }
1388
+ if (remoteUrl) {
1389
+ const data = await remoteFetch("/api/v1/billing/status");
1390
+ console.log(JSON.stringify(data, null, 2));
1391
+ return;
1392
+ }
1393
+ const { store, ctx } = getLocalContext();
520
1394
  const freshWs = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
521
1395
  console.log(JSON.stringify({
522
1396
  ok: true,
@@ -532,7 +1406,6 @@ async function runCli() {
532
1406
  return;
533
1407
  }
534
1408
  if (cmd === "byok" || cmd === "keys") {
535
- const remoteUrl = typeof flags.url === "string" ? flags.url.replace(/\/+$/, "") : "";
536
1409
  const sub = positional[1] || "list";
537
1410
  if (sub === "set" || sub === "add" || sub === "prompt") {
538
1411
  let provider = (positional[2] || String(flags.provider || "")).trim().toLowerCase();
@@ -552,17 +1425,14 @@ async function runCli() {
552
1425
  throw new Error(`API key is required for provider '${provider}'.`);
553
1426
  }
554
1427
  if (remoteUrl) {
555
- const res = await fetch(`${remoteUrl}/api/v1/byok`, {
1428
+ const data = await remoteFetch("/api/v1/byok", {
556
1429
  method: "POST",
557
- headers: {
558
- "Content-Type": "application/json",
559
- ...(apiKeyFlag ? { Authorization: `Bearer ${apiKeyFlag}` } : {}),
560
- },
561
- body: JSON.stringify({ provider, secretKey }),
1430
+ body: { provider, secretKey },
562
1431
  });
563
- console.log(JSON.stringify(await res.json(), null, 2));
1432
+ console.log(JSON.stringify(data, null, 2));
564
1433
  return;
565
1434
  }
1435
+ const { store, ctx } = getLocalContext();
566
1436
  const record = store.setByokKey(ctx.workspace.id, provider, secretKey);
567
1437
  console.log(JSON.stringify({
568
1438
  ok: true,
@@ -577,13 +1447,11 @@ async function runCli() {
577
1447
  if (!provider)
578
1448
  throw new Error("Usage: muxo byok delete <provider>");
579
1449
  if (remoteUrl) {
580
- const res = await fetch(`${remoteUrl}/api/v1/byok/${encodeURIComponent(provider)}`, {
581
- method: "DELETE",
582
- headers: apiKeyFlag ? { Authorization: `Bearer ${apiKeyFlag}` } : {},
583
- });
584
- console.log(JSON.stringify(await res.json(), null, 2));
1450
+ const data = await remoteFetch(`/api/v1/byok/${encodeURIComponent(provider)}`, { method: "DELETE" });
1451
+ console.log(JSON.stringify(data, null, 2));
585
1452
  return;
586
1453
  }
1454
+ const { store, ctx } = getLocalContext();
587
1455
  store.deleteByokKey(ctx.workspace.id, provider);
588
1456
  console.log(JSON.stringify({
589
1457
  ok: true,
@@ -594,12 +1462,11 @@ async function runCli() {
594
1462
  return;
595
1463
  }
596
1464
  if (remoteUrl) {
597
- const res = await fetch(`${remoteUrl}/api/v1/byok`, {
598
- headers: apiKeyFlag ? { Authorization: `Bearer ${apiKeyFlag}` } : {},
599
- });
600
- console.log(JSON.stringify(await res.json(), null, 2));
1465
+ const data = await remoteFetch("/api/v1/byok");
1466
+ console.log(JSON.stringify(data, null, 2));
601
1467
  return;
602
1468
  }
1469
+ const { store, ctx } = getLocalContext();
603
1470
  console.log(JSON.stringify({
604
1471
  ok: true,
605
1472
  keys: store.listByokKeys(ctx.workspace.id),
@@ -608,7 +1475,6 @@ async function runCli() {
608
1475
  return;
609
1476
  }
610
1477
  if (cmd === "do" || cmd === "ask" || cmd === "nl") {
611
- const remoteUrl = typeof flags.url === "string" ? flags.url.replace(/\/+$/, "") : "";
612
1478
  let promptText = positional.slice(1).join(" ").trim() ||
613
1479
  (typeof flags.prompt === "string" ? flags.prompt : "");
614
1480
  if (!promptText) {
@@ -617,10 +1483,6 @@ async function runCli() {
617
1483
  if (!promptText) {
618
1484
  throw new Error('Usage: muxo do "<natural language instruction>"');
619
1485
  }
620
- // If user requested --byok and has no BYOK key for the requested provider, optionally prompt
621
- if (flags.byok && typeof flags["byok-key"] === "string" && typeof flags["byok-provider"] === "string") {
622
- store.setByokKey(ctx.workspace.id, String(flags["byok-provider"]), String(flags["byok-key"]));
623
- }
624
1486
  const planOnly = flags["plan-only"] === true || flags.plan === true;
625
1487
  const autoApprove = flags["no-approve"] ? false : true;
626
1488
  const fundingMode = flags.byok || flags.funding === "byok"
@@ -629,21 +1491,36 @@ async function runCli() {
629
1491
  ? "muxo_funded"
630
1492
  : undefined;
631
1493
  if (remoteUrl) {
632
- const res = await fetch(`${remoteUrl}/api/v1/do`, {
1494
+ if (flags.byok &&
1495
+ typeof flags["byok-key"] === "string" &&
1496
+ typeof flags["byok-provider"] === "string") {
1497
+ await remoteFetch("/api/v1/byok", {
1498
+ method: "POST",
1499
+ body: {
1500
+ provider: String(flags["byok-provider"]),
1501
+ secretKey: String(flags["byok-key"]),
1502
+ },
1503
+ });
1504
+ }
1505
+ const data = await remoteFetch("/api/v1/do", {
633
1506
  method: "POST",
634
- headers: {
635
- "Content-Type": "application/json",
636
- ...(apiKeyFlag ? { Authorization: `Bearer ${apiKeyFlag}` } : {}),
637
- },
638
- body: JSON.stringify({
1507
+ body: {
639
1508
  prompt: promptText,
640
- autoApprove,
641
1509
  dryRun: planOnly,
642
- }),
1510
+ planOnly,
1511
+ autoApprove,
1512
+ autoExecute: !planOnly,
1513
+ approveRisky: autoApprove,
1514
+ fundingMode,
1515
+ },
643
1516
  });
644
- console.log(JSON.stringify(await res.json(), null, 2));
1517
+ console.log(JSON.stringify(data, null, 2));
645
1518
  return;
646
1519
  }
1520
+ const { store, engine, ctx } = getLocalContext();
1521
+ if (flags.byok && typeof flags["byok-key"] === "string" && typeof flags["byok-provider"] === "string") {
1522
+ store.setByokKey(ctx.workspace.id, String(flags["byok-provider"]), String(flags["byok-key"]));
1523
+ }
647
1524
  const result = await engine.doNaturalLanguage(ctx, {
648
1525
  prompt: promptText,
649
1526
  autoApprove,
@@ -653,25 +1530,111 @@ async function runCli() {
653
1530
  console.log(JSON.stringify(result, null, 2));
654
1531
  return;
655
1532
  }
656
- if (cmd === "tui" || cmd === "setup" || cmd === "interactive") {
657
- const remoteUrl = typeof flags.url === "string" ? flags.url.replace(/\/+$/, "") : "";
658
- if (remoteUrl && (flags.json || flags["non-interactive"] || flags.once)) {
659
- const res = await fetch(`${remoteUrl}/api/v1/billing/status`, {
660
- headers: apiKeyFlag ? { Authorization: `Bearer ${apiKeyFlag}` } : {},
1533
+ if (cmd === "tui" || cmd === "interactive") {
1534
+ if (remoteUrl) {
1535
+ const statusData = await remoteFetch("/api/v1/billing/status");
1536
+ const wsNow = (statusData.workspace || {});
1537
+ const keysNow = Array.isArray(statusData.byokKeys)
1538
+ ? statusData.byokKeys.filter((k) => k.configured)
1539
+ : [];
1540
+ if (flags.json || flags["non-interactive"] || flags.once) {
1541
+ console.log(JSON.stringify({
1542
+ ok: true,
1543
+ tui: "ready",
1544
+ mode: "tui_snapshot",
1545
+ workspace: wsNow,
1546
+ byokKeys: keysNow,
1547
+ supportedByokProviders: Object.keys(BYOK_PROVIDER_CATALOG),
1548
+ }, null, 2));
1549
+ return;
1550
+ }
1551
+ const hasSupplierByok = keysNow.some((k) => k.category === "supplier");
1552
+ console.log("\n┌──────────────────────────────────────────────────────────────────────────┐");
1553
+ console.log("│ ⚡ MUXO INTERACTIVE TUI — Execution, Cloud Infra, Memory & Governance │");
1554
+ console.log("├──────────────────────────────────────────────────────────────────────────┤");
1555
+ console.log(`│ Workspace : ${String(wsNow.name || "Muxo").padEnd(28)} Tier : ${String(wsNow.tier || "free").toUpperCase().padEnd(16)}│`);
1556
+ console.log(`│ Credits : ${String(wsNow.creditsBalance ?? 250).padEnd(28)} Funding : ${(hasSupplierByok ? "BYOK (0 cr)" : "muxo_funded").padEnd(16)}│`);
1557
+ console.log(`│ BYOK Keys : ${String(keysNow.length).padEnd(28)} API : ${remoteUrl.slice(0, 16).padEnd(16)}│`);
1558
+ console.log("├──────────────────────────────────────────────────────────────────────────┤");
1559
+ console.log("│ [1] ✨ Run in Plain English (Natural Language Router: muxo do) │");
1560
+ console.log("│ [2] 🔑 Configure BYOK API Key (OpenAI, Anthropic, Gemini, Firecrawl...) │");
1561
+ console.log("│ [3] 💳 Billing: Top Up +1,000 Credits or Upgrade to Starter Plan │");
1562
+ console.log("│ [4] 🔗 Connect OAuth App (GitHub, Gmail, Slack, Notion, Linear) │");
1563
+ console.log("│ [5] ☁️ Provision Cloud Database (Turso SQLite, Neon Postgres, Redis) │");
1564
+ console.log("│ [q] Exit TUI │");
1565
+ console.log("└──────────────────────────────────────────────────────────────────────────┘");
1566
+ const choice = (await promptUserLine("\nSelect an option [1-5, q] (or type any natural language command): ")).trim();
1567
+ if (!choice || choice.toLowerCase() === "q" || choice.toLowerCase() === "exit") {
1568
+ console.log("Exited Muxo TUI.");
1569
+ return;
1570
+ }
1571
+ if (choice === "1") {
1572
+ const nlPrompt = await promptUserLine("Ask Muxo in plain English: ");
1573
+ if (nlPrompt) {
1574
+ const res = await remoteFetch("/api/v1/do", {
1575
+ method: "POST",
1576
+ body: { prompt: nlPrompt, autoExecute: true, approveRisky: true },
1577
+ });
1578
+ console.log(JSON.stringify(res, null, 2));
1579
+ }
1580
+ return;
1581
+ }
1582
+ if (choice === "2") {
1583
+ const provider = (await promptUserLine("Provider (openai, anthropic, openrouter, gemini, composio, stripe, supermemory, firecrawl, exa): ")).toLowerCase();
1584
+ const secretKey = await promptUserLine(`Paste API key for ${provider}: `);
1585
+ if (provider && secretKey) {
1586
+ const saved = await remoteFetch("/api/v1/byok", {
1587
+ method: "POST",
1588
+ body: { provider, secretKey },
1589
+ });
1590
+ console.log(JSON.stringify(saved, null, 2));
1591
+ }
1592
+ return;
1593
+ }
1594
+ if (choice === "3") {
1595
+ const action = (await promptUserLine("Type 'upgrade' for Starter Plan or credit amount (default 1000): ")).toLowerCase();
1596
+ const up = action === "upgrade"
1597
+ ? await remoteFetch("/api/v1/billing/upgrade", {
1598
+ method: "POST",
1599
+ body: { tier: "starter", bonusCredits: 1000 },
1600
+ acceptPaymentRequired: true,
1601
+ })
1602
+ : await remoteFetch("/api/v1/billing/topup", {
1603
+ method: "POST",
1604
+ body: { credits: Math.max(100, Number(action) || 1000) },
1605
+ acceptPaymentRequired: true,
1606
+ });
1607
+ printBillingResult(up);
1608
+ return;
1609
+ }
1610
+ if (choice === "4") {
1611
+ const provider = (await promptUserLine("OAuth Provider (github, gmail, slack, notion, linear): ")) || "github";
1612
+ const conn = await remoteFetch("/api/v1/connections", {
1613
+ method: "POST",
1614
+ body: { provider },
1615
+ });
1616
+ console.log(JSON.stringify(conn, null, 2));
1617
+ return;
1618
+ }
1619
+ if (choice === "5") {
1620
+ const dbPrompt = (await promptUserLine("Database to provision (e.g. 'Provision Turso database my-app-db'): ")) || "Provision Turso database my-app-db";
1621
+ const res = await remoteFetch("/api/v1/do", {
1622
+ method: "POST",
1623
+ body: { prompt: dbPrompt, autoExecute: true, approveRisky: true },
1624
+ });
1625
+ console.log(JSON.stringify(res, null, 2));
1626
+ return;
1627
+ }
1628
+ const res = await remoteFetch("/api/v1/do", {
1629
+ method: "POST",
1630
+ body: { prompt: choice, autoExecute: true, approveRisky: true },
661
1631
  });
662
- const statusData = (await res.json());
663
- console.log(JSON.stringify({
664
- ok: true,
665
- tui: "ready",
666
- mode: "tui_snapshot",
667
- workspace: statusData.workspace,
668
- byokKeys: statusData.byokKeys || [],
669
- supportedByokProviders: Object.keys(BYOK_PROVIDER_CATALOG),
670
- }, null, 2));
1632
+ console.log(JSON.stringify(res, null, 2));
671
1633
  return;
672
1634
  }
1635
+ const { store, engine, ctx } = getLocalContext();
673
1636
  const freshWs = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
674
- const byokKeys = store.listByokKeys(ctx.workspace.id);
1637
+ const byokKeys = store.listByokKeys(ctx.workspace.id).filter((k) => k.configured);
675
1638
  const conns = store.listConnections(ctx.workspace.id);
676
1639
  const resources = store.listResources(ctx.workspace.id);
677
1640
  const gov = engine.governance.getStatus(freshWs);
@@ -699,7 +1662,7 @@ async function runCli() {
699
1662
  }
700
1663
  const printBanner = () => {
701
1664
  const wsNow = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
702
- const keysNow = store.listByokKeys(ctx.workspace.id);
1665
+ const keysNow = store.listByokKeys(wsNow.id).filter((k) => k.configured);
703
1666
  const hasSupplierByok = keysNow.some((k) => k.category === "supplier");
704
1667
  console.log("\n┌──────────────────────────────────────────────────────────────────────────┐");
705
1668
  console.log("│ ⚡ MUXO INTERACTIVE TUI — Execution, Cloud Infra, Memory & Governance │");
@@ -709,7 +1672,7 @@ async function runCli() {
709
1672
  console.log(`│ BYOK Keys : ${String(keysNow.length).padEnd(28)} DID : ${gov.agentDid.slice(0, 16)}│`);
710
1673
  console.log("├──────────────────────────────────────────────────────────────────────────┤");
711
1674
  console.log("│ [1] ✨ Run in Plain English (Natural Language Router: muxo do) │");
712
- console.log("│ [2] 🔑 Configure BYOK API Key (OpenAI, Anthropic, Composio, Stripe...) │");
1675
+ console.log("│ [2] 🔑 Configure BYOK API Key (OpenAI, Anthropic, Gemini, Firecrawl...) │");
713
1676
  console.log("│ [3] 💳 Billing: Top Up +1,000 Credits or Upgrade to Starter Plan │");
714
1677
  console.log("│ [4] 🔗 Connect OAuth App (GitHub, Gmail, Slack, Notion, Linear) │");
715
1678
  console.log("│ [5] ☁️ Provision Cloud Database (Turso SQLite, Neon Postgres, Redis) │");
@@ -780,10 +1743,63 @@ async function runCli() {
780
1743
  }
781
1744
  if (cmd === "memory") {
782
1745
  const sub = positional[1];
1746
+ if (sub === "facts") {
1747
+ const query = positional.slice(2).join(" ") || (typeof flags.query === "string" ? flags.query : "");
1748
+ const includeHistorical = !flags.current;
1749
+ if (remoteUrl) {
1750
+ const params = new URLSearchParams({ historical: includeHistorical ? "true" : "false" });
1751
+ if (query)
1752
+ params.set("query", query);
1753
+ const res = await remoteFetch(`/api/v1/memory/facts?${params.toString()}`);
1754
+ console.log(JSON.stringify(res, null, 2));
1755
+ return;
1756
+ }
1757
+ const { store, ctx } = getLocalContext();
1758
+ const facts = store.listTemporalFacts(ctx.workspace.id, {
1759
+ includeHistorical,
1760
+ query: query || undefined,
1761
+ });
1762
+ console.log(JSON.stringify({
1763
+ ok: true,
1764
+ activeFacts: facts.filter((f) => f.validTo === null),
1765
+ historicalFacts: facts.filter((f) => f.validTo !== null),
1766
+ facts,
1767
+ }, null, 2));
1768
+ return;
1769
+ }
1770
+ if (sub === "forget") {
1771
+ const query = positional.slice(2).join(" ").trim() || String(flags.query || "").trim();
1772
+ if (!query)
1773
+ throw new Error("Usage: muxo memory forget <text>");
1774
+ if (remoteUrl) {
1775
+ const res = await remoteFetch("/api/v1/memory/forget", {
1776
+ method: "POST",
1777
+ body: { query },
1778
+ });
1779
+ console.log(JSON.stringify(res, null, 2));
1780
+ return;
1781
+ }
1782
+ const { engine, ctx } = getLocalContext();
1783
+ const pruned = await engine.forgetWorkspaceMemoryMatching(ctx, query);
1784
+ console.log(JSON.stringify({ ok: true, ...pruned }, null, 2));
1785
+ return;
1786
+ }
783
1787
  if (sub === "add") {
784
1788
  const content = positional.slice(2).join(" ") || String(flags.content || "");
785
1789
  if (!content)
786
1790
  throw new Error("Usage: muxo memory add <text>");
1791
+ if (remoteUrl) {
1792
+ const res = await remoteFetch("/api/v1/memory", {
1793
+ method: "POST",
1794
+ body: {
1795
+ content,
1796
+ title: flags.title ? String(flags.title) : content.slice(0, 60),
1797
+ },
1798
+ });
1799
+ console.log(JSON.stringify(res, null, 2));
1800
+ return;
1801
+ }
1802
+ const { engine, ctx } = getLocalContext();
787
1803
  const entry = await engine.addWorkspaceMemory(ctx, {
788
1804
  content,
789
1805
  title: flags.title ? String(flags.title) : content.slice(0, 60),
@@ -791,6 +1807,34 @@ async function runCli() {
791
1807
  console.log(JSON.stringify({ ok: true, memory: entry }, null, 2));
792
1808
  return;
793
1809
  }
1810
+ if (sub === "delete" || sub === "rm" || sub === "remove") {
1811
+ const id = positional[2] || String(flags.id || "");
1812
+ if (!id)
1813
+ throw new Error("Usage: muxo memory delete <memory_id>");
1814
+ if (remoteUrl) {
1815
+ const res = await remoteFetch(`/api/v1/memory/${encodeURIComponent(id)}`, { method: "DELETE" });
1816
+ console.log(JSON.stringify(res, null, 2));
1817
+ return;
1818
+ }
1819
+ const { engine, ctx } = getLocalContext();
1820
+ const deleted = await engine.deleteWorkspaceMemory(ctx, id);
1821
+ console.log(JSON.stringify({ ok: true, ...deleted }, null, 2));
1822
+ return;
1823
+ }
1824
+ if (remoteUrl) {
1825
+ const query = sub === "recall" || sub === "search"
1826
+ ? positional.slice(2).join(" ") || flags.query || undefined
1827
+ : positional.slice(1).join(" ") || flags.query || undefined;
1828
+ const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
1829
+ const res = await remoteFetch(`/api/v1/memory${suffix}`);
1830
+ console.log(JSON.stringify({
1831
+ ok: true,
1832
+ ...res,
1833
+ localMemories: res.records || [],
1834
+ }, null, 2));
1835
+ return;
1836
+ }
1837
+ const { engine, ctx } = getLocalContext();
794
1838
  const query = sub === "recall" || sub === "search"
795
1839
  ? positional.slice(2).join(" ") || flags.query || undefined
796
1840
  : positional.slice(1).join(" ") || flags.query || undefined;
@@ -802,18 +1846,421 @@ async function runCli() {
802
1846
  }, null, 2));
803
1847
  return;
804
1848
  }
1849
+ if (cmd === "context") {
1850
+ const query = positional.slice(1).join(" ") ||
1851
+ (typeof flags.query === "string" ? flags.query : "") ||
1852
+ undefined;
1853
+ if (remoteUrl) {
1854
+ const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
1855
+ const res = await remoteFetch(`/api/v1/context${suffix}`);
1856
+ console.log(JSON.stringify(res, null, 2));
1857
+ return;
1858
+ }
1859
+ const { engine, ctx } = getLocalContext();
1860
+ const runContext = await engine.getRunContext(ctx, query);
1861
+ console.log(JSON.stringify({ ok: true, ...runContext }, null, 2));
1862
+ return;
1863
+ }
1864
+ if (cmd === "guardrails" || cmd === "guardrail") {
1865
+ const sub = positional[1];
1866
+ if (sub === "set" || sub === "add") {
1867
+ const prompt = positional.slice(2).join(" ").trim() ||
1868
+ String(flags.prompt || flags.rule || "").trim();
1869
+ const rawRulesJson = typeof flags["rules-json"] === "string"
1870
+ ? flags["rules-json"]
1871
+ : typeof flags.rules === "string"
1872
+ ? flags.rules
1873
+ : undefined;
1874
+ const parsedParamRules = rawRulesJson
1875
+ ? JSON.parse(rawRulesJson)
1876
+ : undefined;
1877
+ if (!prompt && (!parsedParamRules || parsedParamRules.length === 0)) {
1878
+ throw new Error('Usage: muxo guardrails set "<plain-English rule>" [--rules-json \'[...]\'] (e.g. muxo guardrails set "auto approve under $50 and require approval over $50")');
1879
+ }
1880
+ if (remoteUrl) {
1881
+ const res = await remoteFetch("/api/v1/guardrails", {
1882
+ method: "POST",
1883
+ body: {
1884
+ prompt,
1885
+ ...(parsedParamRules ? { parameterRules: parsedParamRules } : {}),
1886
+ },
1887
+ });
1888
+ console.log(JSON.stringify(res, null, 2));
1889
+ return;
1890
+ }
1891
+ const { engine, ctx } = getLocalContext();
1892
+ const applied = await engine.applyNaturalLanguageGuardrail(ctx, prompt, {
1893
+ parameterRules: parsedParamRules,
1894
+ source: parsedParamRules ? "mcp_harness" : "deterministic",
1895
+ });
1896
+ console.log(JSON.stringify({
1897
+ ...applied,
1898
+ humanMessage: `Guardrail active: ${applied.guardrail.summary}`,
1899
+ }, null, 2));
1900
+ return;
1901
+ }
1902
+ if (sub === "reset") {
1903
+ if (remoteUrl) {
1904
+ const res = await remoteFetch("/api/v1/guardrails", {
1905
+ method: "POST",
1906
+ body: { reset: true },
1907
+ });
1908
+ console.log(JSON.stringify(res, null, 2));
1909
+ return;
1910
+ }
1911
+ const { store, ctx } = getLocalContext();
1912
+ const updated = store.updateWorkspacePolicy(ctx.workspace.id, DEFAULT_WORKSPACE_POLICY);
1913
+ console.log(JSON.stringify({
1914
+ ok: true,
1915
+ workspace: updated,
1916
+ plainEnglish: describePolicyInPlainEnglish(updated.policy),
1917
+ humanMessage: "Reset workspace guardrails to Safe Default.",
1918
+ }, null, 2));
1919
+ return;
1920
+ }
1921
+ if (remoteUrl) {
1922
+ const res = await remoteFetch("/api/v1/guardrails");
1923
+ console.log(JSON.stringify(res, null, 2));
1924
+ return;
1925
+ }
1926
+ const { engine, ctx } = getLocalContext();
1927
+ const summary = engine.getGuardrailsSummary(ctx);
1928
+ console.log(JSON.stringify({ ok: true, ...summary }, null, 2));
1929
+ return;
1930
+ }
1931
+ if (cmd === "policy") {
1932
+ const nlText = positional.slice(1).join(" ").trim() ||
1933
+ String(flags.rule || flags.prompt || "").trim();
1934
+ if (nlText) {
1935
+ if (remoteUrl) {
1936
+ const res = await remoteFetch("/api/v1/guardrails", {
1937
+ method: "POST",
1938
+ body: { prompt: nlText },
1939
+ });
1940
+ console.log(JSON.stringify(res, null, 2));
1941
+ return;
1942
+ }
1943
+ const { engine, ctx } = getLocalContext();
1944
+ const applied = await engine.applyNaturalLanguageGuardrail(ctx, nlText);
1945
+ console.log(JSON.stringify({
1946
+ ...applied,
1947
+ humanMessage: `Guardrail active: ${applied.guardrail.summary}`,
1948
+ }, null, 2));
1949
+ return;
1950
+ }
1951
+ if (remoteUrl) {
1952
+ const res = await remoteFetch("/api/v1/policy");
1953
+ console.log(JSON.stringify(res, null, 2));
1954
+ return;
1955
+ }
1956
+ const { engine, ctx } = getLocalContext();
1957
+ const summary = engine.getGuardrailsSummary(ctx);
1958
+ console.log(JSON.stringify({
1959
+ ok: true,
1960
+ policy: summary.policy,
1961
+ plainEnglish: summary.plainEnglish,
1962
+ explanation: summary.plainEnglish.explanation,
1963
+ activeDefaults: summary.activeDefaults,
1964
+ activeGuardrails: summary.activeGuardrails,
1965
+ }, null, 2));
1966
+ return;
1967
+ }
805
1968
  if (cmd === "governance") {
806
- const status = engine.governance.getGovernanceStatus(ctx.workspace);
807
- const summary = engine.governance.getStatus(ctx.workspace);
1969
+ const sub = positional[1];
1970
+ const ringLabel = (policy) => policy?.executionRing !== undefined
1971
+ ? RING_LABELS[policy.executionRing] || `Ring ${policy.executionRing}`
1972
+ : "Per action (set by risk level)";
1973
+ if (sub === "pause" || sub === "kill-switch" || sub === "kill" || sub === "quarantine") {
1974
+ const reason = positional.slice(2).join(" ").trim() ||
1975
+ (typeof flags.reason === "string" ? flags.reason : "") ||
1976
+ "Paused from the Muxo CLI";
1977
+ if (remoteUrl) {
1978
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1979
+ method: "POST",
1980
+ body: { action: "quarantine", reason },
1981
+ });
1982
+ console.log(JSON.stringify(res, null, 2));
1983
+ return;
1984
+ }
1985
+ const { engine, ctx } = getLocalContext();
1986
+ const out = await engine.activateWorkspaceKillSwitch(ctx, { reason });
1987
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
1988
+ return;
1989
+ }
1990
+ if (sub === "resume") {
1991
+ if (remoteUrl) {
1992
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1993
+ method: "POST",
1994
+ body: { action: "resume" },
1995
+ });
1996
+ console.log(JSON.stringify(res, null, 2));
1997
+ return;
1998
+ }
1999
+ const { engine, ctx } = getLocalContext();
2000
+ const out = engine.resumeWorkspaceKillSwitch(ctx);
2001
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
2002
+ return;
2003
+ }
2004
+ if (sub === "scan") {
2005
+ const content = positional.slice(2).join(" ").trim() || String(flags.content || "").trim();
2006
+ if (!content)
2007
+ throw new Error("Usage: muxo governance scan <text>");
2008
+ if (remoteUrl) {
2009
+ const res = await remoteFetch("/api/v1/governance/scan-context", {
2010
+ method: "POST",
2011
+ body: { content, sessionId: "cli" },
2012
+ });
2013
+ console.log(JSON.stringify(res, null, 2));
2014
+ return;
2015
+ }
2016
+ const { engine, ctx } = getLocalContext();
2017
+ const scan = engine.governance.scanMemoryContent({
2018
+ workspaceId: ctx.workspace.id,
2019
+ sessionId: "cli",
2020
+ role: "user",
2021
+ content,
2022
+ });
2023
+ console.log(JSON.stringify({ ok: true, scan }, null, 2));
2024
+ return;
2025
+ }
2026
+ if (sub === "policy") {
2027
+ const patch = {};
2028
+ if (typeof flags.mode === "string")
2029
+ patch.mode = flags.mode;
2030
+ if (flags.ring !== undefined)
2031
+ patch.executionRing = Number(flags.ring);
2032
+ if (typeof flags["require-approval"] === "string") {
2033
+ const raw = flags["require-approval"].trim();
2034
+ patch.requireApprovalFor =
2035
+ raw === "none" ? [] : raw.split(",").map((r) => r.trim()).filter(Boolean);
2036
+ }
2037
+ if (flags["block-destructive-sql"] !== undefined) {
2038
+ patch.blockDestructiveSql =
2039
+ flags["block-destructive-sql"] === true || flags["block-destructive-sql"] === "true";
2040
+ }
2041
+ if (flags["max-credits"] !== undefined)
2042
+ patch.maxCreditsPerRun = Number(flags["max-credits"]);
2043
+ if (Object.keys(patch).length === 0) {
2044
+ if (remoteUrl) {
2045
+ const res = await remoteFetch("/api/v1/governance");
2046
+ const policy = res.workspacePolicy || {};
2047
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
2048
+ return;
2049
+ }
2050
+ const { store, ctx } = getLocalContext();
2051
+ const policy = (store.getWorkspace(ctx.workspace.id) || ctx.workspace).policy;
2052
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
2053
+ return;
2054
+ }
2055
+ if (remoteUrl) {
2056
+ const res = await remoteFetch("/api/v1/policy", { method: "POST", body: patch });
2057
+ const policy = res.workspace?.policy || {};
2058
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
2059
+ return;
2060
+ }
2061
+ const { store, ctx } = getLocalContext();
2062
+ const updated = store.updateWorkspacePolicy(ctx.workspace.id, sanitizePolicyPatch(patch));
2063
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(updated.policy), policy: updated.policy }, null, 2));
2064
+ return;
2065
+ }
2066
+ if (remoteUrl) {
2067
+ const res = await remoteFetch("/api/v1/governance");
2068
+ console.log(JSON.stringify({
2069
+ ok: true,
2070
+ did: res.governance?.agentDid,
2071
+ chainValid: res.governance?.auditChain?.chainVerified ??
2072
+ res.governance?.auditIntegrityValid ??
2073
+ true,
2074
+ ringName: ringLabel(res.workspacePolicy),
2075
+ ...res,
2076
+ }, null, 2));
2077
+ return;
2078
+ }
2079
+ const { engine, ctx, store } = getLocalContext();
2080
+ const ws = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
2081
+ const status = engine.governance.getGovernanceStatus(ws);
2082
+ const summary = engine.governance.getStatus(ws);
808
2083
  console.log(JSON.stringify({
809
2084
  ok: true,
810
2085
  did: summary.agentDid,
811
2086
  chainValid: summary.auditChainIntegrity,
812
- ringName: "Ring 2 (Standard)",
2087
+ ringName: ringLabel(ws.policy),
813
2088
  ...status,
814
2089
  }, null, 2));
815
2090
  return;
816
2091
  }
2092
+ if (cmd === "workspaces" || cmd === "workspace") {
2093
+ const sub = positional[1] || "list";
2094
+ if (sub === "list" || sub === "ls") {
2095
+ if (remoteUrl) {
2096
+ const res = await remoteFetch("/api/v1/workspaces");
2097
+ console.log(JSON.stringify(res, null, 2));
2098
+ return;
2099
+ }
2100
+ const { store, ctx } = getLocalContext();
2101
+ const workspaces = store.listUserWorkspaces(ctx.user.id, ctx.workspace.id);
2102
+ console.log(JSON.stringify({ ok: true, currentWorkspaceId: ctx.workspace.id, workspaces }, null, 2));
2103
+ return;
2104
+ }
2105
+ if (sub === "create" || sub === "new") {
2106
+ const name = positional.slice(2).join(" ").trim() ||
2107
+ (typeof flags.name === "string" ? flags.name.trim() : "");
2108
+ if (!name) {
2109
+ throw new Error("Usage: muxo workspaces create <name> [--team <teamName>]");
2110
+ }
2111
+ const teamName = typeof flags.team === "string" ? flags.team.trim() : undefined;
2112
+ if (remoteUrl) {
2113
+ const res = await remoteFetch("/api/v1/workspaces", {
2114
+ method: "POST",
2115
+ body: { name, teamName },
2116
+ });
2117
+ console.log(JSON.stringify(res, null, 2));
2118
+ return;
2119
+ }
2120
+ const { store, ctx } = getLocalContext();
2121
+ const created = store.createWorkspaceForUser({
2122
+ userId: ctx.user.id,
2123
+ name,
2124
+ teamName,
2125
+ });
2126
+ console.log(JSON.stringify({
2127
+ ok: true,
2128
+ workspace: created.workspace,
2129
+ apiKey: created.apiKey,
2130
+ workspaces: store.listUserWorkspaces(ctx.user.id, created.workspace.id),
2131
+ }, null, 2));
2132
+ return;
2133
+ }
2134
+ if (sub === "switch" || sub === "use") {
2135
+ const workspaceId = positional[2]?.trim() ||
2136
+ (typeof flags.id === "string" ? flags.id.trim() : "");
2137
+ if (!workspaceId) {
2138
+ throw new Error("Usage: muxo workspaces switch <workspace_id>");
2139
+ }
2140
+ if (remoteUrl) {
2141
+ const res = await remoteFetch("/api/v1/workspaces/switch", {
2142
+ method: "POST",
2143
+ body: { workspaceId },
2144
+ });
2145
+ console.log(JSON.stringify(res, null, 2));
2146
+ return;
2147
+ }
2148
+ const { store, ctx } = getLocalContext();
2149
+ const switched = store.switchUserWorkspace(ctx.user.id, workspaceId);
2150
+ if (!switched) {
2151
+ throw new Error(`Workspace '${workspaceId}' not found or not accessible.`);
2152
+ }
2153
+ console.log(JSON.stringify({
2154
+ ok: true,
2155
+ workspace: switched.workspace,
2156
+ workspaces: store.listUserWorkspaces(ctx.user.id, switched.workspace.id),
2157
+ }, null, 2));
2158
+ return;
2159
+ }
2160
+ if (sub === "join") {
2161
+ const keyToJoin = positional[2]?.trim() ||
2162
+ (typeof flags["api-key"] === "string"
2163
+ ? flags["api-key"].trim()
2164
+ : typeof flags.key === "string"
2165
+ ? flags.key.trim()
2166
+ : "");
2167
+ if (!keyToJoin) {
2168
+ throw new Error("Usage: muxo workspaces join <mk_live_...>");
2169
+ }
2170
+ if (remoteUrl) {
2171
+ const res = await remoteFetch("/api/v1/workspaces/join", {
2172
+ method: "POST",
2173
+ body: { apiKey: keyToJoin },
2174
+ });
2175
+ const ws = res.workspace || {};
2176
+ writeCliAuthConfig({
2177
+ authenticated: true,
2178
+ apiKey: keyToJoin,
2179
+ apiUrl: remoteUrl,
2180
+ workspaceId: String(ws.id || "ws_remote"),
2181
+ workspaceName: String(ws.name || "Muxo Workspace"),
2182
+ agentId: "usr_remote",
2183
+ governanceDid: `did:muxo:${ws.id || "ws_remote"}`,
2184
+ supermemoryNamespace: `muxo_${ws.id || "ws_remote"}`,
2185
+ updatedAt: new Date().toISOString(),
2186
+ });
2187
+ console.log(JSON.stringify(res, null, 2));
2188
+ return;
2189
+ }
2190
+ const { store, ctx, engine } = getLocalContext();
2191
+ const joined = store.joinWorkspaceWithApiKey({
2192
+ apiKey: keyToJoin,
2193
+ userId: ctx.user.id,
2194
+ });
2195
+ if (!joined) {
2196
+ throw new Error("Invalid or revoked Muxo API key.");
2197
+ }
2198
+ writeCliAuthConfig({
2199
+ authenticated: true,
2200
+ apiKey: keyToJoin,
2201
+ workspaceId: joined.workspace.id,
2202
+ workspaceName: joined.workspace.name,
2203
+ agentId: ctx.user.id,
2204
+ governanceDid: engine.governance.getStatus(joined.workspace).agentDid,
2205
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(joined.workspace.id),
2206
+ updatedAt: new Date().toISOString(),
2207
+ });
2208
+ console.log(JSON.stringify({
2209
+ ok: true,
2210
+ mode: "api_key_join",
2211
+ workspace: joined.workspace,
2212
+ keyRecord: joined.keyRecord,
2213
+ membership: joined.membership,
2214
+ }, null, 2));
2215
+ return;
2216
+ }
2217
+ }
2218
+ if (cmd === "team" || cmd === "teams") {
2219
+ const sub = positional[1] || "onboard";
2220
+ const teamName = (sub === "onboard" || sub === "invite" || sub === "add"
2221
+ ? positional.slice(2).join(" ")
2222
+ : positional.slice(1).join(" ")).trim() ||
2223
+ (typeof flags.name === "string" ? flags.name.trim() : "") ||
2224
+ (typeof flags.team === "string" ? flags.team.trim() : "");
2225
+ if (!teamName) {
2226
+ throw new Error('Usage: muxo team onboard "<team_name>" [--mode shared|dedicated] [--role operator|admin|member]');
2227
+ }
2228
+ const rawMode = typeof flags.mode === "string" ? flags.mode.trim() : "";
2229
+ const mode = rawMode === "dedicated" || rawMode === "dedicated_workspace"
2230
+ ? "dedicated_workspace"
2231
+ : "shared_workspace";
2232
+ const role = typeof flags.role === "string" &&
2233
+ ["owner", "admin", "operator", "member"].includes(flags.role)
2234
+ ? flags.role
2235
+ : "operator";
2236
+ const environment = flags.test ? "test" : "live";
2237
+ if (remoteUrl) {
2238
+ const res = await remoteFetch("/api/v1/workspaces/onboard-team", {
2239
+ method: "POST",
2240
+ body: { teamName, mode, role, environment },
2241
+ });
2242
+ console.log(JSON.stringify(res, null, 2));
2243
+ return;
2244
+ }
2245
+ const { store, ctx } = getLocalContext();
2246
+ const kit = store.onboardTeam({
2247
+ userId: ctx.user.id,
2248
+ userEmail: ctx.user.email,
2249
+ currentWorkspaceId: ctx.workspace.id,
2250
+ teamName,
2251
+ role,
2252
+ mode,
2253
+ environment,
2254
+ baseUrl: config.baseUrl,
2255
+ });
2256
+ console.log(JSON.stringify({
2257
+ ok: true,
2258
+ kit,
2259
+ workspace: kit.workspace,
2260
+ apiKey: kit.apiKey,
2261
+ }, null, 2));
2262
+ return;
2263
+ }
817
2264
  if (cmd === "serve") {
818
2265
  const { createMuxoServer } = await import("./server.js");
819
2266
  const port = flags.port ? Number(flags.port) : config.port;
@@ -841,54 +2288,93 @@ async function runCli() {
841
2288
  // Natural Language fallback: if user passed plain English directly (`muxo "Audit https://muxo.ai for SEO"`)
842
2289
  if (cmd !== "help" && !flags.help && !flags.h && positional.length > 0) {
843
2290
  const promptText = positional.join(" ").trim();
2291
+ const planOnly = flags["plan-only"] === true || flags.plan === true;
2292
+ const autoApprove = flags["no-approve"] ? false : true;
2293
+ const fundingMode = flags.byok || flags.funding === "byok"
2294
+ ? "byok"
2295
+ : flags.funding === "muxo_funded"
2296
+ ? "muxo_funded"
2297
+ : undefined;
2298
+ if (remoteUrl) {
2299
+ const data = await remoteFetch("/api/v1/do", {
2300
+ method: "POST",
2301
+ body: {
2302
+ prompt: promptText,
2303
+ dryRun: planOnly,
2304
+ planOnly,
2305
+ autoApprove,
2306
+ autoExecute: !planOnly,
2307
+ approveRisky: autoApprove,
2308
+ fundingMode,
2309
+ },
2310
+ });
2311
+ console.log(JSON.stringify(data, null, 2));
2312
+ return;
2313
+ }
2314
+ const { engine, ctx } = getLocalContext();
844
2315
  const result = await engine.doNaturalLanguage(ctx, {
845
2316
  prompt: promptText,
846
- autoApprove: flags["no-approve"] ? false : true,
847
- planOnly: flags["plan-only"] === true || flags.plan === true,
848
- fundingMode: flags.byok || flags.funding === "byok"
849
- ? "byok"
850
- : flags.funding === "muxo_funded"
851
- ? "muxo_funded"
852
- : undefined,
2317
+ autoApprove,
2318
+ planOnly,
2319
+ fundingMode,
853
2320
  });
854
2321
  console.log(JSON.stringify(result, null, 2));
855
2322
  return;
856
2323
  }
857
2324
  console.log(`
858
- Muxo CLI — Human & LLM Friendly Execution, Infrastructure, BYOK & Governance Platform
2325
+ Muxo CLI — Unified Agent Execution, Cloud Infrastructure, Memory & Safety Platform
859
2326
 
860
2327
  Natural Language & Interactive TUI:
2328
+ muxo setup [--api-key <key>] 1-command auto-signup (or join via team API key) + MCP setup
861
2329
  muxo do "<instruction>" Execute any goal in plain English (LLM/semantic router -> plan -> execute)
862
2330
  muxo ask "<instruction>" [--plan-only] Alias for 'muxo do' (pass --plan-only to inspect plan before running)
863
2331
  muxo tui Launch interactive Terminal UI dashboard (BYOK, Billing, OAuth, Infra)
864
2332
  muxo byok [list|set|delete] [<provider>] Manage BYOK API keys in AES-256-GCM Vault (0 credit cost when active)
865
2333
 
2334
+ Workspaces & Team Onboarding:
2335
+ muxo workspaces [list|create|switch|join] List, create, switch, or join workspaces via API key
2336
+ muxo team onboard "<team>" [--mode shared|dedicated] [--role operator|admin|member]
2337
+ Mint a scoped team API key & onboarding kit for another team
2338
+
866
2339
  Core Commands:
867
2340
  muxo login [--api-key <key> | --signup] Sign in or create an autonomous workspace (~/.muxo/config.json)
868
2341
  muxo logout Remove saved CLI authentication config
869
2342
  muxo claim <owner_email> Generate a human owner claim link for this workspace
870
- muxo whoami Show workspace profile, plan, DID, and credit balance
871
- muxo search <query> [--lane <lane>] Search across Instant, Composio Apps, Stripe Projects & Billing
2343
+ muxo whoami Show workspace profile, plan, agent identity, and credit balance
2344
+ muxo search <query> [--lane <lane>] Search across Instant Tools, Connected Apps, Cloud Infra & Billing
872
2345
  muxo inspect <tool_slug> Inspect tool schema, risk level, and credit cost
873
- muxo connections [--sync true|false] List Composio OAuth & Stripe Projects provider connections
2346
+ muxo connections [--sync true|false] List Connected Apps & Cloud Infrastructure provider connections
874
2347
  muxo link <provider> [--wait] Start OAuth handoff for a service and optionally wait
875
2348
  muxo connect <provider> [--wait] Alias for 'muxo link'
876
2349
  muxo wait <provider> [--timeout 15] Poll until a pending OAuth connection handoff becomes active
877
2350
  muxo plan <tool_slug> --input '{...}' Create a governed execution plan (accepts natural language too)
878
2351
  muxo approve <plan_id> Approve a pending high-risk plan
879
- muxo run <plan_id> Execute an approved plan and issue a signed receipt
2352
+ muxo run <plan_id> Execute an approved plan and issue a verifiable audit record
880
2353
  muxo exec <tool_slug> --input '{...}' Plan and execute in one step (pass --approve for high-risk)
881
- muxo resources List provisioned Stripe Projects infrastructure resources
2354
+ muxo provision <provider> [--name <name>] Provision cloud infrastructure (e.g., turso, neon, supabase, upstash)
2355
+ muxo resources List provisioned Muxo Cloud Infrastructure resources
882
2356
  muxo env pull [--out .env.muxo] Decrypt and write provisioned credentials to local .env
883
- muxo receipts [<receipt_id>] List or cryptographically verify execution receipts
2357
+ muxo receipts [<receipt_id>] List or verify execution audit records
884
2358
  muxo billing [status|topup|upgrade|checkout] Manage credits, upgrade to Starter, or inspect settlements
885
- muxo memory [<query>] Recall Supermemory v5 workspace brain & hybrid memories
886
- muxo governance Inspect Microsoft Agent Governance Ed25519 DID & audit chain
2359
+ muxo memory [<query>] Recall Muxo Workspace Memory & hybrid context
2360
+ muxo memory add <text> | delete <id> Save or delete a workspace memory
2361
+ muxo memory facts [<query>] [--current] List temporal facts with their history (or only current facts)
2362
+ muxo memory forget <text> Delete memories and retire facts that match <text>
2363
+ muxo governance Inspect Muxo Safety & Approvals identity and audit chain
2364
+ muxo governance pause [<reason>] | resume Turn the workspace kill switch on or off
2365
+ muxo governance scan <text> Check text for prompt injection before storing or sending it
2366
+ muxo governance policy [--mode enforce|monitor] [--ring 0-3] [--require-approval write,admin|none]
2367
+ [--block-destructive-sql true|false] [--max-credits <n>]
2368
+ muxo harness Check API, MCP, auth, governance and catalog; print MCP config
887
2369
  muxo serve [--port 4310] Start unified HTTP Console, Landing, OAuth Callback & MCP server
888
- muxo mcp Run stdio MCP server (20 canonical tools)
2370
+ muxo mcp Run stdio MCP server (19 canonical tools)
889
2371
  `);
890
2372
  }
891
- runCli().catch((err) => {
2373
+ runCli()
2374
+ .then(() => {
2375
+ process.exit(Number(process.exitCode ?? 0));
2376
+ })
2377
+ .catch((err) => {
892
2378
  console.error("Error:", err?.message || err);
893
2379
  process.exit(1);
894
2380
  });