@muxoai/platform 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,7 +6,10 @@ import readline from "node:readline";
6
6
  import { getConfig } from "./config.js";
7
7
  import { MuxoEngine } from "./engine.js";
8
8
  import { handleJsonRpcMcpRequest } from "./mcp.js";
9
+ import { 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({
@@ -74,8 +77,8 @@ async function runCli() {
74
77
  const cmd = positional[0] || "help";
75
78
  if (flags.version || flags.v || cmd === "version") {
76
79
  console.log(flags.json
77
- ? JSON.stringify({ ok: true, name: "@muxoai/cli", version: "1.3.1" }, null, 2)
78
- : "1.3.1");
80
+ ? JSON.stringify({ ok: true, name: "@muxoai/cli", version: MUXO_VERSION }, null, 2)
81
+ : MUXO_VERSION);
79
82
  return;
80
83
  }
81
84
  const savedCliConfig = readCliAuthConfig();
@@ -85,7 +88,7 @@ async function runCli() {
85
88
  "";
86
89
  const explicitUrlFlag = (typeof flags.url === "string"
87
90
  ? flags.url
88
- : process.env.MUXO_API_URL || "").replace(/\/+$/, "");
91
+ : process.env.MUXO_API_URL || process.env.MUXO_CLOUD_URL || "").replace(/\/+$/, "");
89
92
  const useLocalDb = Boolean((process.env.MUXO_DB_PATH || flags.local) && !explicitUrlFlag);
90
93
  const remoteUrl = useLocalDb
91
94
  ? ""
@@ -107,6 +110,9 @@ async function runCli() {
107
110
  body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
108
111
  });
109
112
  const data = (await res.json().catch(() => ({})));
113
+ if (opts?.acceptPaymentRequired && res.status === 402 && data.checkoutUrl) {
114
+ return data;
115
+ }
110
116
  if (!res.ok && data.error) {
111
117
  throw new Error(String(data.error));
112
118
  }
@@ -168,6 +174,23 @@ async function runCli() {
168
174
  };
169
175
  return { store, engine: getEngine(), ctx, tokenAuth };
170
176
  };
177
+ const maskKey = (key) => `${key.slice(0, 14)}…`;
178
+ // Hosted Muxo answers purchases with 402 + a Stripe Checkout URL; a self-hosted dev server may grant credits directly.
179
+ const printBillingResult = (data) => {
180
+ if (!data.checkoutUrl) {
181
+ console.log(JSON.stringify(data, null, 2));
182
+ return;
183
+ }
184
+ console.log(JSON.stringify({
185
+ ok: true,
186
+ paymentRequired: true,
187
+ kind: data.kind,
188
+ credits: data.credits,
189
+ checkoutUrl: data.checkoutUrl,
190
+ sessionId: data.sessionId,
191
+ next: `Open checkoutUrl to pay with Stripe. Muxo adds the credits once Stripe confirms the payment. Check with: muxo billing verify ${data.sessionId}`,
192
+ }, null, 2));
193
+ };
171
194
  // CLI Login / Autonomous External Signup (`muxo login`)
172
195
  if (cmd === "login" || cmd === "signup") {
173
196
  const explicitKey = typeof flags["api-key"] === "string"
@@ -200,6 +223,7 @@ async function runCli() {
200
223
  configPath: cfgPath,
201
224
  workspace: ws,
202
225
  ...authPayload,
226
+ apiKey: maskKey(explicitKey),
203
227
  }, null, 2));
204
228
  return;
205
229
  }
@@ -224,6 +248,7 @@ async function runCli() {
224
248
  mode: "api_key",
225
249
  configPath: cfgPath,
226
250
  ...authPayload,
251
+ apiKey: maskKey(explicitKey),
227
252
  }, null, 2));
228
253
  return;
229
254
  }
@@ -281,11 +306,52 @@ async function runCli() {
281
306
  return;
282
307
  }
283
308
  // One-command setup (`muxo setup` / `muxo init` / `muxo install`):
284
- // 1. Auto-creates or loads ~/.muxo/config.json with 250 starter credits
309
+ // 1. Auto-creates or loads ~/.muxo/config.json with 250 starter credits (or binds an explicit --api-key)
285
310
  // 2. Writes .mcp.json in target directory (or ~/.cursor/mcp.json & ~/.factory/mcp.json if present)
286
311
  if (cmd === "setup" || cmd === "init" || cmd === "install") {
312
+ const explicitKey = typeof flags["api-key"] === "string"
313
+ ? flags["api-key"].trim()
314
+ : typeof flags.key === "string"
315
+ ? flags.key.trim()
316
+ : "";
287
317
  let activeConfig = savedCliConfig;
288
- if (!activeConfig || !activeConfig.apiKey || flags.fresh) {
318
+ if (explicitKey) {
319
+ const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
320
+ if (remoteUrl) {
321
+ apiKeyFlag = explicitKey;
322
+ const orgRes = (await remoteFetch("/api/v1/organization").catch(() => ({})));
323
+ const org = orgRes?.organization || orgRes?.workspace || {};
324
+ activeConfig = {
325
+ authenticated: true,
326
+ apiKey: explicitKey,
327
+ workspaceId: org.id || "ws_remote",
328
+ workspaceName: org.name || agentName,
329
+ agentId: "usr_remote",
330
+ governanceDid: org.governanceDid || `did:muxo:${org.id || "ws_remote"}`,
331
+ supermemoryNamespace: `muxo_${org.id || "ws_remote"}`,
332
+ updatedAt: new Date().toISOString(),
333
+ };
334
+ }
335
+ else {
336
+ const { store, engine } = getLocalContext();
337
+ const auth = store.resolveAuthToken(explicitKey);
338
+ if (!auth) {
339
+ throw new Error(`Invalid API key provided to muxo setup: ${explicitKey.slice(0, 12)}...`);
340
+ }
341
+ activeConfig = {
342
+ authenticated: true,
343
+ apiKey: explicitKey,
344
+ workspaceId: auth.workspace.id,
345
+ workspaceName: auth.workspace.name,
346
+ agentId: auth.user.id,
347
+ governanceDid: engine.governance.getStatus(auth.workspace).agentDid,
348
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(auth.workspace.id),
349
+ updatedAt: new Date().toISOString(),
350
+ };
351
+ }
352
+ writeCliAuthConfig(activeConfig);
353
+ }
354
+ else if (!activeConfig || !activeConfig.apiKey || flags.fresh) {
289
355
  const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
290
356
  if (remoteUrl) {
291
357
  const { authPayload } = await signupHostedWorkspace(agentName, "muxo-setup");
@@ -310,6 +376,7 @@ async function runCli() {
310
376
  }
311
377
  writeCliAuthConfig(activeConfig);
312
378
  }
379
+ const ensuredConfig = activeConfig;
313
380
  const mcpServerUrl = remoteUrl
314
381
  ? `${remoteUrl}/mcp`
315
382
  : "https://api.muxo.ai/mcp";
@@ -317,7 +384,7 @@ async function runCli() {
317
384
  type: "http",
318
385
  url: mcpServerUrl,
319
386
  headers: {
320
- Authorization: `Bearer ${activeConfig.apiKey}`,
387
+ Authorization: `Bearer ${ensuredConfig.apiKey}`,
321
388
  },
322
389
  };
323
390
  const targetDir = typeof flags.dir === "string" ? path.resolve(flags.dir) : process.cwd();
@@ -344,9 +411,9 @@ async function runCli() {
344
411
  mergeMcpJsonFile(path.join(targetDir, ".mcp.json"));
345
412
  const resultPayload = {
346
413
  ok: true,
347
- workspaceId: activeConfig.workspaceId,
348
- workspaceName: activeConfig.workspaceName,
349
- apiKey: activeConfig.apiKey,
414
+ workspaceId: ensuredConfig.workspaceId,
415
+ workspaceName: ensuredConfig.workspaceName,
416
+ apiKey: ensuredConfig.apiKey,
350
417
  mcpUrl: mcpServerUrl,
351
418
  configPath: getCliConfigPath(),
352
419
  configuredFiles: writtenFiles,
@@ -356,8 +423,8 @@ async function runCli() {
356
423
  }
357
424
  else {
358
425
  console.log(`\n✓ Muxo is ready!`);
359
- console.log(` Workspace : ${activeConfig.workspaceName} (${activeConfig.workspaceId})`);
360
- console.log(` API Key : ${activeConfig.apiKey}`);
426
+ console.log(` Workspace : ${ensuredConfig.workspaceName} (${ensuredConfig.workspaceId})`);
427
+ console.log(` API Key : ${maskKey(ensuredConfig.apiKey)} (saved in ${getCliConfigPath()})`);
361
428
  console.log(` MCP URL : ${mcpServerUrl}`);
362
429
  console.log(` MCP Config: ${writtenFiles.join(", ")}`);
363
430
  console.log(`\nTry it now:`);
@@ -474,15 +541,193 @@ async function runCli() {
474
541
  return;
475
542
  }
476
543
  if (cmd === "harness") {
477
- let workspaceId = savedCliConfig?.workspaceId || "ws_muxo_default";
478
- if (!remoteUrl) {
479
- workspaceId = getLocalContext().ctx.workspace.id;
544
+ const checks = [];
545
+ const skip = (reason) => ({ skip: reason });
546
+ const runCheck = async (id, name, fn) => {
547
+ const started = Date.now();
548
+ try {
549
+ const out = await fn();
550
+ checks.push(typeof out === "string"
551
+ ? { id, name, status: "pass", detail: out, ms: Date.now() - started }
552
+ : { id, name, status: "skip", detail: out.skip, ms: Date.now() - started });
553
+ }
554
+ catch (err) {
555
+ checks.push({
556
+ id,
557
+ name,
558
+ status: "fail",
559
+ detail: String(err?.message || err),
560
+ ms: Date.now() - started,
561
+ });
562
+ }
563
+ };
564
+ const assertCheck = (cond, message) => {
565
+ if (!cond)
566
+ throw new Error(message);
567
+ };
568
+ const noKeyHint = "No API key. Run `muxo login --signup` to create a workspace.";
569
+ let workspaceId = savedCliConfig?.workspaceId || null;
570
+ if (remoteUrl) {
571
+ let mcpSessionId = "";
572
+ const harnessFetch = async (pathname, init = {}) => {
573
+ const headers = { Accept: "application/json" };
574
+ if (init.body !== undefined)
575
+ headers["Content-Type"] = "application/json";
576
+ if (init.auth !== false && apiKeyFlag)
577
+ headers.Authorization = `Bearer ${apiKeyFlag}`;
578
+ if (pathname === "/mcp" && mcpSessionId)
579
+ headers["Mcp-Session-Id"] = mcpSessionId;
580
+ return fetch(`${remoteUrl}${pathname}`, {
581
+ method: init.method || "GET",
582
+ headers,
583
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
584
+ signal: AbortSignal.timeout(15_000),
585
+ });
586
+ };
587
+ const harnessJson = async (pathname, init) => {
588
+ const res = await harnessFetch(pathname, init);
589
+ const data = (await res.json().catch(() => ({})));
590
+ if (!res.ok) {
591
+ throw new Error(`HTTP ${res.status}${data.error ? `: ${data.error}` : ""}`);
592
+ }
593
+ return data;
594
+ };
595
+ const mcpCall = async (id, method, params = {}) => {
596
+ const res = await harnessFetch("/mcp", {
597
+ method: "POST",
598
+ body: { jsonrpc: "2.0", id, method, params },
599
+ });
600
+ const sid = res.headers.get("mcp-session-id");
601
+ if (sid)
602
+ mcpSessionId = sid;
603
+ const data = (await res.json().catch(() => ({})));
604
+ if (!res.ok || data.error) {
605
+ throw new Error(data.error?.message || `HTTP ${res.status}`);
606
+ }
607
+ return data.result;
608
+ };
609
+ await runCheck("api_health", "API is reachable", async () => {
610
+ const health = await harnessJson("/health", { auth: false });
611
+ assertCheck(health.ok === true, "Health endpoint did not report ok");
612
+ return `${health.service || "muxo"} ${health.version || ""} at ${remoteUrl}`.trim();
613
+ });
614
+ await runCheck("agent_skill", "Agent skill is published", async () => {
615
+ const res = await harnessFetch("/skill.md", { auth: false });
616
+ assertCheck(res.ok, `HTTP ${res.status}`);
617
+ const body = await res.text();
618
+ assertCheck(/execute_(read|write)/.test(body), "skill.md is missing the execute_* tools");
619
+ return `${remoteUrl}/skill.md (${body.length.toLocaleString()} bytes)`;
620
+ });
621
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
622
+ const result = await mcpCall(1, "initialize", {
623
+ protocolVersion: "2025-06-18",
624
+ capabilities: {},
625
+ clientInfo: { name: "muxo-cli-harness", version: MUXO_VERSION },
626
+ });
627
+ assertCheck(result?.serverInfo?.name, "initialize returned no serverInfo");
628
+ return `${result.serverInfo.name} ${result.serverInfo.version || ""} (protocol ${result.protocolVersion})`.trim();
629
+ });
630
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
631
+ const result = await mcpCall(2, "tools/list");
632
+ const count = Array.isArray(result?.tools) ? result.tools.length : 0;
633
+ assertCheck(count > 0, "tools/list returned no tools");
634
+ return `${count} tools`;
635
+ });
636
+ await runCheck("auth", "API key opens a workspace", async () => {
637
+ if (!apiKeyFlag)
638
+ return skip(noKeyHint);
639
+ const boot = await harnessJson("/api/v1/bootstrap");
640
+ const ws = { ...(boot.organization || {}), ...(boot.profile?.workspace || {}) };
641
+ assertCheck(ws.id, "bootstrap returned no workspace");
642
+ workspaceId = String(ws.id);
643
+ return `${ws.name || ws.id} (${ws.tier || "free"})`;
644
+ });
645
+ await runCheck("governance", "Agents are allowed to act", async () => {
646
+ if (!apiKeyFlag)
647
+ return skip(noKeyHint);
648
+ const res = await harnessJson("/api/v1/governance");
649
+ const g = res.governance || {};
650
+ if (g.killSwitch?.active) {
651
+ throw new Error(`Kill switch is on${g.killSwitch.reason ? ` (${g.killSwitch.reason})` : ""}. Run \`muxo governance resume\`.`);
652
+ }
653
+ const chainOk = g.auditChain?.chainVerified ?? g.auditChainIntegrity ?? g.auditIntegrityValid;
654
+ assertCheck(chainOk !== false, "Audit hash chain failed verification");
655
+ return `Running, audit chain verified, policy ${res.workspacePolicy?.mode || "enforce"}`;
656
+ });
657
+ await runCheck("catalog", "Tool catalog search works", async () => {
658
+ if (!apiKeyFlag)
659
+ return skip(noKeyHint);
660
+ const res = await harnessJson("/api/v1/tools?lane=composio_instant&limit=5");
661
+ const count = Array.isArray(res.tools) ? res.tools.length : 0;
662
+ assertCheck(count > 0, "Catalog search returned no tools");
663
+ return `${count} instant tools returned`;
664
+ });
480
665
  }
666
+ else {
667
+ const local = getLocalContext();
668
+ workspaceId = local.ctx.workspace.id;
669
+ await runCheck("local_store", "Local workspace database opens", async () => {
670
+ const ws = local.store.getWorkspace(local.ctx.workspace.id);
671
+ assertCheck(ws, "Workspace not found in local database");
672
+ return `${ws.name} at ${config.dbPath}`;
673
+ });
674
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
675
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
676
+ jsonrpc: "2.0",
677
+ id: 1,
678
+ method: "initialize",
679
+ params: { protocolVersion: "2025-06-18", capabilities: {} },
680
+ });
681
+ const info = res?.result?.serverInfo;
682
+ assertCheck(info?.name, "initialize returned no serverInfo");
683
+ return `${info.name} ${info.version || ""}`.trim();
684
+ });
685
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
686
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
687
+ jsonrpc: "2.0",
688
+ id: 2,
689
+ method: "tools/list",
690
+ });
691
+ const count = (res?.result?.tools || []).length;
692
+ assertCheck(count > 0, "tools/list returned no tools");
693
+ return `${count} tools`;
694
+ });
695
+ await runCheck("auth", "API key opens a workspace", async () => {
696
+ if (!apiKeyFlag)
697
+ return skip("No API key. Using the local default workspace.");
698
+ assertCheck(local.tokenAuth, "API key is not valid for this local database");
699
+ return local.ctx.workspace.name;
700
+ });
701
+ await runCheck("governance", "Agents are allowed to act", async () => {
702
+ const ws = local.store.getWorkspace(local.ctx.workspace.id) || local.ctx.workspace;
703
+ const status = local.engine.governance.getGovernanceStatus(ws);
704
+ if (status.killSwitch?.active) {
705
+ throw new Error("Kill switch is on. Run `muxo governance resume`.");
706
+ }
707
+ assertCheck(status.auditChain?.chainVerified !== false, "Audit hash chain failed verification");
708
+ return `Running, audit chain verified, policy ${ws.policy.mode}`;
709
+ });
710
+ await runCheck("catalog", "Tool catalog search works", async () => {
711
+ const res = await local.engine.searchTools({ lane: "composio_instant", limit: 5 });
712
+ assertCheck(res.tools.length > 0, "Catalog search returned no tools");
713
+ return `${res.tools.length} instant tools returned`;
714
+ });
715
+ }
716
+ const passed = checks.filter((c) => c.status === "pass").length;
717
+ const failed = checks.filter((c) => c.status === "fail").length;
718
+ const skipped = checks.filter((c) => c.status === "skip").length;
719
+ if (failed > 0)
720
+ process.exitCode = 1;
481
721
  console.log(JSON.stringify({
482
- ok: true,
483
- version: "1.3.1",
484
- passed: 7,
485
- total: 7,
722
+ ok: failed === 0,
723
+ version: MUXO_VERSION,
724
+ mode: remoteUrl ? "hosted" : "local",
725
+ apiUrl: remoteUrl || null,
726
+ passed,
727
+ failed,
728
+ skipped,
729
+ total: checks.length,
730
+ checks,
486
731
  workspaceId,
487
732
  data: {
488
733
  mcpServers: {
@@ -960,8 +1205,9 @@ async function runCli() {
960
1205
  const data = await remoteFetch("/api/v1/billing/topup", {
961
1206
  method: "POST",
962
1207
  body: { credits },
1208
+ acceptPaymentRequired: true,
963
1209
  });
964
- console.log(JSON.stringify(data, null, 2));
1210
+ printBillingResult(data);
965
1211
  return;
966
1212
  }
967
1213
  const { store, ctx } = getLocalContext();
@@ -988,8 +1234,9 @@ async function runCli() {
988
1234
  const data = await remoteFetch("/api/v1/billing/upgrade", {
989
1235
  method: "POST",
990
1236
  body: { tier: "starter", bonusCredits: 1000 },
1237
+ acceptPaymentRequired: true,
991
1238
  });
992
- console.log(JSON.stringify(data, null, 2));
1239
+ printBillingResult(data);
993
1240
  return;
994
1241
  }
995
1242
  const { store, ctx } = getLocalContext();
@@ -1173,6 +1420,9 @@ async function runCli() {
1173
1420
  method: "POST",
1174
1421
  body: {
1175
1422
  prompt: promptText,
1423
+ dryRun: planOnly,
1424
+ planOnly,
1425
+ autoApprove,
1176
1426
  autoExecute: !planOnly,
1177
1427
  approveRisky: autoApprove,
1178
1428
  fundingMode,
@@ -1257,21 +1507,18 @@ async function runCli() {
1257
1507
  }
1258
1508
  if (choice === "3") {
1259
1509
  const action = (await promptUserLine("Type 'upgrade' for Starter Plan or credit amount (default 1000): ")).toLowerCase();
1260
- if (action === "upgrade") {
1261
- const up = await remoteFetch("/api/v1/billing/upgrade", {
1510
+ const up = action === "upgrade"
1511
+ ? await remoteFetch("/api/v1/billing/upgrade", {
1262
1512
  method: "POST",
1263
1513
  body: { tier: "starter", bonusCredits: 1000 },
1264
- });
1265
- console.log(JSON.stringify(up, null, 2));
1266
- }
1267
- else {
1268
- const cr = Math.max(100, Number(action) || 1000);
1269
- const up = await remoteFetch("/api/v1/billing/topup", {
1514
+ acceptPaymentRequired: true,
1515
+ })
1516
+ : await remoteFetch("/api/v1/billing/topup", {
1270
1517
  method: "POST",
1271
- body: { credits: cr },
1518
+ body: { credits: Math.max(100, Number(action) || 1000) },
1519
+ acceptPaymentRequired: true,
1272
1520
  });
1273
- console.log(JSON.stringify(up, null, 2));
1274
- }
1521
+ printBillingResult(up);
1275
1522
  return;
1276
1523
  }
1277
1524
  if (choice === "4") {
@@ -1410,6 +1657,47 @@ async function runCli() {
1410
1657
  }
1411
1658
  if (cmd === "memory") {
1412
1659
  const sub = positional[1];
1660
+ if (sub === "facts") {
1661
+ const query = positional.slice(2).join(" ") || (typeof flags.query === "string" ? flags.query : "");
1662
+ const includeHistorical = !flags.current;
1663
+ if (remoteUrl) {
1664
+ const params = new URLSearchParams({ historical: includeHistorical ? "true" : "false" });
1665
+ if (query)
1666
+ params.set("query", query);
1667
+ const res = await remoteFetch(`/api/v1/memory/facts?${params.toString()}`);
1668
+ console.log(JSON.stringify(res, null, 2));
1669
+ return;
1670
+ }
1671
+ const { store, ctx } = getLocalContext();
1672
+ const facts = store.listTemporalFacts(ctx.workspace.id, {
1673
+ includeHistorical,
1674
+ query: query || undefined,
1675
+ });
1676
+ console.log(JSON.stringify({
1677
+ ok: true,
1678
+ activeFacts: facts.filter((f) => f.validTo === null),
1679
+ historicalFacts: facts.filter((f) => f.validTo !== null),
1680
+ facts,
1681
+ }, null, 2));
1682
+ return;
1683
+ }
1684
+ if (sub === "forget") {
1685
+ const query = positional.slice(2).join(" ").trim() || String(flags.query || "").trim();
1686
+ if (!query)
1687
+ throw new Error("Usage: muxo memory forget <text>");
1688
+ if (remoteUrl) {
1689
+ const res = await remoteFetch("/api/v1/memory/forget", {
1690
+ method: "POST",
1691
+ body: { query },
1692
+ });
1693
+ console.log(JSON.stringify(res, null, 2));
1694
+ return;
1695
+ }
1696
+ const { engine, ctx } = getLocalContext();
1697
+ const pruned = await engine.forgetWorkspaceMemoryMatching(ctx, query);
1698
+ console.log(JSON.stringify({ ok: true, ...pruned }, null, 2));
1699
+ return;
1700
+ }
1413
1701
  if (sub === "add") {
1414
1702
  const content = positional.slice(2).join(" ") || String(flags.content || "");
1415
1703
  if (!content)
@@ -1433,8 +1721,26 @@ async function runCli() {
1433
1721
  console.log(JSON.stringify({ ok: true, memory: entry }, null, 2));
1434
1722
  return;
1435
1723
  }
1724
+ if (sub === "delete" || sub === "rm" || sub === "remove") {
1725
+ const id = positional[2] || String(flags.id || "");
1726
+ if (!id)
1727
+ throw new Error("Usage: muxo memory delete <memory_id>");
1728
+ if (remoteUrl) {
1729
+ const res = await remoteFetch(`/api/v1/memory/${encodeURIComponent(id)}`, { method: "DELETE" });
1730
+ console.log(JSON.stringify(res, null, 2));
1731
+ return;
1732
+ }
1733
+ const { engine, ctx } = getLocalContext();
1734
+ const deleted = await engine.deleteWorkspaceMemory(ctx, id);
1735
+ console.log(JSON.stringify({ ok: true, ...deleted }, null, 2));
1736
+ return;
1737
+ }
1436
1738
  if (remoteUrl) {
1437
- const res = await remoteFetch("/api/v1/memory");
1739
+ const query = sub === "recall" || sub === "search"
1740
+ ? positional.slice(2).join(" ") || flags.query || undefined
1741
+ : positional.slice(1).join(" ") || flags.query || undefined;
1742
+ const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
1743
+ const res = await remoteFetch(`/api/v1/memory${suffix}`);
1438
1744
  console.log(JSON.stringify({
1439
1745
  ok: true,
1440
1746
  ...res,
@@ -1455,25 +1761,125 @@ async function runCli() {
1455
1761
  return;
1456
1762
  }
1457
1763
  if (cmd === "governance") {
1764
+ const sub = positional[1];
1765
+ const ringLabel = (policy) => policy?.executionRing !== undefined
1766
+ ? RING_LABELS[policy.executionRing] || `Ring ${policy.executionRing}`
1767
+ : "Per action (set by risk level)";
1768
+ if (sub === "pause" || sub === "kill-switch" || sub === "kill" || sub === "quarantine") {
1769
+ const reason = positional.slice(2).join(" ").trim() ||
1770
+ (typeof flags.reason === "string" ? flags.reason : "") ||
1771
+ "Paused from the Muxo CLI";
1772
+ if (remoteUrl) {
1773
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1774
+ method: "POST",
1775
+ body: { action: "quarantine", reason },
1776
+ });
1777
+ console.log(JSON.stringify(res, null, 2));
1778
+ return;
1779
+ }
1780
+ const { engine, ctx } = getLocalContext();
1781
+ const out = await engine.activateWorkspaceKillSwitch(ctx, { reason });
1782
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
1783
+ return;
1784
+ }
1785
+ if (sub === "resume") {
1786
+ if (remoteUrl) {
1787
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1788
+ method: "POST",
1789
+ body: { action: "resume" },
1790
+ });
1791
+ console.log(JSON.stringify(res, null, 2));
1792
+ return;
1793
+ }
1794
+ const { engine, ctx } = getLocalContext();
1795
+ const out = engine.resumeWorkspaceKillSwitch(ctx);
1796
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
1797
+ return;
1798
+ }
1799
+ if (sub === "scan") {
1800
+ const content = positional.slice(2).join(" ").trim() || String(flags.content || "").trim();
1801
+ if (!content)
1802
+ throw new Error("Usage: muxo governance scan <text>");
1803
+ if (remoteUrl) {
1804
+ const res = await remoteFetch("/api/v1/governance/scan-context", {
1805
+ method: "POST",
1806
+ body: { content, sessionId: "cli" },
1807
+ });
1808
+ console.log(JSON.stringify(res, null, 2));
1809
+ return;
1810
+ }
1811
+ const { engine, ctx } = getLocalContext();
1812
+ const scan = engine.governance.scanMemoryContent({
1813
+ workspaceId: ctx.workspace.id,
1814
+ sessionId: "cli",
1815
+ role: "user",
1816
+ content,
1817
+ });
1818
+ console.log(JSON.stringify({ ok: true, scan }, null, 2));
1819
+ return;
1820
+ }
1821
+ if (sub === "policy") {
1822
+ const patch = {};
1823
+ if (typeof flags.mode === "string")
1824
+ patch.mode = flags.mode;
1825
+ if (flags.ring !== undefined)
1826
+ patch.executionRing = Number(flags.ring);
1827
+ if (typeof flags["require-approval"] === "string") {
1828
+ const raw = flags["require-approval"].trim();
1829
+ patch.requireApprovalFor =
1830
+ raw === "none" ? [] : raw.split(",").map((r) => r.trim()).filter(Boolean);
1831
+ }
1832
+ if (flags["block-destructive-sql"] !== undefined) {
1833
+ patch.blockDestructiveSql =
1834
+ flags["block-destructive-sql"] === true || flags["block-destructive-sql"] === "true";
1835
+ }
1836
+ if (flags["max-credits"] !== undefined)
1837
+ patch.maxCreditsPerRun = Number(flags["max-credits"]);
1838
+ if (Object.keys(patch).length === 0) {
1839
+ if (remoteUrl) {
1840
+ const res = await remoteFetch("/api/v1/governance");
1841
+ const policy = res.workspacePolicy || {};
1842
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1843
+ return;
1844
+ }
1845
+ const { store, ctx } = getLocalContext();
1846
+ const policy = (store.getWorkspace(ctx.workspace.id) || ctx.workspace).policy;
1847
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1848
+ return;
1849
+ }
1850
+ if (remoteUrl) {
1851
+ const res = await remoteFetch("/api/v1/policy", { method: "POST", body: patch });
1852
+ const policy = res.workspace?.policy || {};
1853
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1854
+ return;
1855
+ }
1856
+ const { store, ctx } = getLocalContext();
1857
+ const updated = store.updateWorkspacePolicy(ctx.workspace.id, sanitizePolicyPatch(patch));
1858
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(updated.policy), policy: updated.policy }, null, 2));
1859
+ return;
1860
+ }
1458
1861
  if (remoteUrl) {
1459
1862
  const res = await remoteFetch("/api/v1/governance");
1460
1863
  console.log(JSON.stringify({
1461
1864
  ok: true,
1462
1865
  did: res.governance?.agentDid,
1463
- chainValid: res.governance?.auditIntegrityValid ?? true,
1464
- ringName: "Ring 2 (Standard)",
1866
+ chainValid: res.governance?.auditChain?.chainVerified ??
1867
+ res.governance?.auditIntegrityValid ??
1868
+ true,
1869
+ ringName: ringLabel(res.workspacePolicy),
1465
1870
  ...res,
1466
1871
  }, null, 2));
1467
1872
  return;
1468
1873
  }
1469
- const { engine, ctx } = getLocalContext();
1470
- const status = engine.governance.getGovernanceStatus(ctx.workspace);
1471
- const summary = engine.governance.getStatus(ctx.workspace);
1874
+ const { engine, ctx, store } = getLocalContext();
1875
+ const ws = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
1876
+ const status = engine.governance.getGovernanceStatus(ws);
1877
+ const summary = engine.governance.getStatus(ws);
1472
1878
  console.log(JSON.stringify({
1473
1879
  ok: true,
1474
1880
  did: summary.agentDid,
1475
1881
  chainValid: summary.auditChainIntegrity,
1476
- ringName: "Ring 2 (Standard)",
1882
+ ringName: ringLabel(ws.policy),
1477
1883
  ...status,
1478
1884
  }, null, 2));
1479
1885
  return;
@@ -1517,6 +1923,9 @@ async function runCli() {
1517
1923
  method: "POST",
1518
1924
  body: {
1519
1925
  prompt: promptText,
1926
+ dryRun: planOnly,
1927
+ planOnly,
1928
+ autoApprove,
1520
1929
  autoExecute: !planOnly,
1521
1930
  approveRisky: autoApprove,
1522
1931
  fundingMode,
@@ -1565,14 +1974,22 @@ Core Commands:
1565
1974
  muxo receipts [<receipt_id>] List or verify execution audit records
1566
1975
  muxo billing [status|topup|upgrade|checkout] Manage credits, upgrade to Starter, or inspect settlements
1567
1976
  muxo memory [<query>] Recall Muxo Workspace Memory & hybrid context
1977
+ muxo memory add <text> | delete <id> Save or delete a workspace memory
1978
+ muxo memory facts [<query>] [--current] List temporal facts with their history (or only current facts)
1979
+ muxo memory forget <text> Delete memories and retire facts that match <text>
1568
1980
  muxo governance Inspect Muxo Safety & Approvals identity and audit chain
1981
+ muxo governance pause [<reason>] | resume Turn the workspace kill switch on or off
1982
+ muxo governance scan <text> Check text for prompt injection before storing or sending it
1983
+ muxo governance policy [--mode enforce|monitor] [--ring 0-3] [--require-approval write,admin|none]
1984
+ [--block-destructive-sql true|false] [--max-credits <n>]
1985
+ muxo harness Check API, MCP, auth, governance and catalog; print MCP config
1569
1986
  muxo serve [--port 4310] Start unified HTTP Console, Landing, OAuth Callback & MCP server
1570
1987
  muxo mcp Run stdio MCP server (20 canonical tools)
1571
1988
  `);
1572
1989
  }
1573
1990
  runCli()
1574
1991
  .then(() => {
1575
- process.exit(0);
1992
+ process.exit(Number(process.exitCode ?? 0));
1576
1993
  })
1577
1994
  .catch((err) => {
1578
1995
  console.error("Error:", err?.message || err);