@muxoai/platform 1.3.1 → 1.3.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.
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
  ? ""
@@ -281,11 +284,52 @@ async function runCli() {
281
284
  return;
282
285
  }
283
286
  // One-command setup (`muxo setup` / `muxo init` / `muxo install`):
284
- // 1. Auto-creates or loads ~/.muxo/config.json with 250 starter credits
287
+ // 1. Auto-creates or loads ~/.muxo/config.json with 250 starter credits (or binds an explicit --api-key)
285
288
  // 2. Writes .mcp.json in target directory (or ~/.cursor/mcp.json & ~/.factory/mcp.json if present)
286
289
  if (cmd === "setup" || cmd === "init" || cmd === "install") {
290
+ const explicitKey = typeof flags["api-key"] === "string"
291
+ ? flags["api-key"].trim()
292
+ : typeof flags.key === "string"
293
+ ? flags.key.trim()
294
+ : "";
287
295
  let activeConfig = savedCliConfig;
288
- if (!activeConfig || !activeConfig.apiKey || flags.fresh) {
296
+ if (explicitKey) {
297
+ const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
298
+ if (remoteUrl) {
299
+ apiKeyFlag = explicitKey;
300
+ const orgRes = (await remoteFetch("/api/v1/organization").catch(() => ({})));
301
+ const org = orgRes?.organization || orgRes?.workspace || {};
302
+ activeConfig = {
303
+ authenticated: true,
304
+ apiKey: explicitKey,
305
+ workspaceId: org.id || "ws_remote",
306
+ workspaceName: org.name || agentName,
307
+ agentId: "usr_remote",
308
+ governanceDid: org.governanceDid || `did:muxo:${org.id || "ws_remote"}`,
309
+ supermemoryNamespace: `muxo_${org.id || "ws_remote"}`,
310
+ updatedAt: new Date().toISOString(),
311
+ };
312
+ }
313
+ else {
314
+ const { store, engine } = getLocalContext();
315
+ const auth = store.resolveAuthToken(explicitKey);
316
+ if (!auth) {
317
+ throw new Error(`Invalid API key provided to muxo setup: ${explicitKey.slice(0, 12)}...`);
318
+ }
319
+ activeConfig = {
320
+ authenticated: true,
321
+ apiKey: explicitKey,
322
+ workspaceId: auth.workspace.id,
323
+ workspaceName: auth.workspace.name,
324
+ agentId: auth.user.id,
325
+ governanceDid: engine.governance.getStatus(auth.workspace).agentDid,
326
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(auth.workspace.id),
327
+ updatedAt: new Date().toISOString(),
328
+ };
329
+ }
330
+ writeCliAuthConfig(activeConfig);
331
+ }
332
+ else if (!activeConfig || !activeConfig.apiKey || flags.fresh) {
289
333
  const agentName = typeof flags.name === "string" ? flags.name : "Muxo Agent";
290
334
  if (remoteUrl) {
291
335
  const { authPayload } = await signupHostedWorkspace(agentName, "muxo-setup");
@@ -310,6 +354,7 @@ async function runCli() {
310
354
  }
311
355
  writeCliAuthConfig(activeConfig);
312
356
  }
357
+ const ensuredConfig = activeConfig;
313
358
  const mcpServerUrl = remoteUrl
314
359
  ? `${remoteUrl}/mcp`
315
360
  : "https://api.muxo.ai/mcp";
@@ -317,7 +362,7 @@ async function runCli() {
317
362
  type: "http",
318
363
  url: mcpServerUrl,
319
364
  headers: {
320
- Authorization: `Bearer ${activeConfig.apiKey}`,
365
+ Authorization: `Bearer ${ensuredConfig.apiKey}`,
321
366
  },
322
367
  };
323
368
  const targetDir = typeof flags.dir === "string" ? path.resolve(flags.dir) : process.cwd();
@@ -344,9 +389,9 @@ async function runCli() {
344
389
  mergeMcpJsonFile(path.join(targetDir, ".mcp.json"));
345
390
  const resultPayload = {
346
391
  ok: true,
347
- workspaceId: activeConfig.workspaceId,
348
- workspaceName: activeConfig.workspaceName,
349
- apiKey: activeConfig.apiKey,
392
+ workspaceId: ensuredConfig.workspaceId,
393
+ workspaceName: ensuredConfig.workspaceName,
394
+ apiKey: ensuredConfig.apiKey,
350
395
  mcpUrl: mcpServerUrl,
351
396
  configPath: getCliConfigPath(),
352
397
  configuredFiles: writtenFiles,
@@ -356,8 +401,8 @@ async function runCli() {
356
401
  }
357
402
  else {
358
403
  console.log(`\n✓ Muxo is ready!`);
359
- console.log(` Workspace : ${activeConfig.workspaceName} (${activeConfig.workspaceId})`);
360
- console.log(` API Key : ${activeConfig.apiKey}`);
404
+ console.log(` Workspace : ${ensuredConfig.workspaceName} (${ensuredConfig.workspaceId})`);
405
+ console.log(` API Key : ${ensuredConfig.apiKey}`);
361
406
  console.log(` MCP URL : ${mcpServerUrl}`);
362
407
  console.log(` MCP Config: ${writtenFiles.join(", ")}`);
363
408
  console.log(`\nTry it now:`);
@@ -474,15 +519,193 @@ async function runCli() {
474
519
  return;
475
520
  }
476
521
  if (cmd === "harness") {
477
- let workspaceId = savedCliConfig?.workspaceId || "ws_muxo_default";
478
- if (!remoteUrl) {
479
- workspaceId = getLocalContext().ctx.workspace.id;
522
+ const checks = [];
523
+ const skip = (reason) => ({ skip: reason });
524
+ const runCheck = async (id, name, fn) => {
525
+ const started = Date.now();
526
+ try {
527
+ const out = await fn();
528
+ checks.push(typeof out === "string"
529
+ ? { id, name, status: "pass", detail: out, ms: Date.now() - started }
530
+ : { id, name, status: "skip", detail: out.skip, ms: Date.now() - started });
531
+ }
532
+ catch (err) {
533
+ checks.push({
534
+ id,
535
+ name,
536
+ status: "fail",
537
+ detail: String(err?.message || err),
538
+ ms: Date.now() - started,
539
+ });
540
+ }
541
+ };
542
+ const assertCheck = (cond, message) => {
543
+ if (!cond)
544
+ throw new Error(message);
545
+ };
546
+ const noKeyHint = "No API key. Run `muxo login --signup` to create a workspace.";
547
+ let workspaceId = savedCliConfig?.workspaceId || null;
548
+ if (remoteUrl) {
549
+ let mcpSessionId = "";
550
+ const harnessFetch = async (pathname, init = {}) => {
551
+ const headers = { Accept: "application/json" };
552
+ if (init.body !== undefined)
553
+ headers["Content-Type"] = "application/json";
554
+ if (init.auth !== false && apiKeyFlag)
555
+ headers.Authorization = `Bearer ${apiKeyFlag}`;
556
+ if (pathname === "/mcp" && mcpSessionId)
557
+ headers["Mcp-Session-Id"] = mcpSessionId;
558
+ return fetch(`${remoteUrl}${pathname}`, {
559
+ method: init.method || "GET",
560
+ headers,
561
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
562
+ signal: AbortSignal.timeout(15_000),
563
+ });
564
+ };
565
+ const harnessJson = async (pathname, init) => {
566
+ const res = await harnessFetch(pathname, init);
567
+ const data = (await res.json().catch(() => ({})));
568
+ if (!res.ok) {
569
+ throw new Error(`HTTP ${res.status}${data.error ? `: ${data.error}` : ""}`);
570
+ }
571
+ return data;
572
+ };
573
+ const mcpCall = async (id, method, params = {}) => {
574
+ const res = await harnessFetch("/mcp", {
575
+ method: "POST",
576
+ body: { jsonrpc: "2.0", id, method, params },
577
+ });
578
+ const sid = res.headers.get("mcp-session-id");
579
+ if (sid)
580
+ mcpSessionId = sid;
581
+ const data = (await res.json().catch(() => ({})));
582
+ if (!res.ok || data.error) {
583
+ throw new Error(data.error?.message || `HTTP ${res.status}`);
584
+ }
585
+ return data.result;
586
+ };
587
+ await runCheck("api_health", "API is reachable", async () => {
588
+ const health = await harnessJson("/health", { auth: false });
589
+ assertCheck(health.ok === true, "Health endpoint did not report ok");
590
+ return `${health.service || "muxo"} ${health.version || ""} at ${remoteUrl}`.trim();
591
+ });
592
+ await runCheck("agent_skill", "Agent skill is published", async () => {
593
+ const res = await harnessFetch("/skill.md", { auth: false });
594
+ assertCheck(res.ok, `HTTP ${res.status}`);
595
+ const body = await res.text();
596
+ assertCheck(/execute_(read|write)/.test(body), "skill.md is missing the execute_* tools");
597
+ return `${remoteUrl}/skill.md (${body.length.toLocaleString()} bytes)`;
598
+ });
599
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
600
+ const result = await mcpCall(1, "initialize", {
601
+ protocolVersion: "2025-06-18",
602
+ capabilities: {},
603
+ clientInfo: { name: "muxo-cli-harness", version: MUXO_VERSION },
604
+ });
605
+ assertCheck(result?.serverInfo?.name, "initialize returned no serverInfo");
606
+ return `${result.serverInfo.name} ${result.serverInfo.version || ""} (protocol ${result.protocolVersion})`.trim();
607
+ });
608
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
609
+ const result = await mcpCall(2, "tools/list");
610
+ const count = Array.isArray(result?.tools) ? result.tools.length : 0;
611
+ assertCheck(count > 0, "tools/list returned no tools");
612
+ return `${count} tools`;
613
+ });
614
+ await runCheck("auth", "API key opens a workspace", async () => {
615
+ if (!apiKeyFlag)
616
+ return skip(noKeyHint);
617
+ const boot = await harnessJson("/api/v1/bootstrap");
618
+ const ws = { ...(boot.organization || {}), ...(boot.profile?.workspace || {}) };
619
+ assertCheck(ws.id, "bootstrap returned no workspace");
620
+ workspaceId = String(ws.id);
621
+ return `${ws.name || ws.id} (${ws.tier || "free"})`;
622
+ });
623
+ await runCheck("governance", "Agents are allowed to act", async () => {
624
+ if (!apiKeyFlag)
625
+ return skip(noKeyHint);
626
+ const res = await harnessJson("/api/v1/governance");
627
+ const g = res.governance || {};
628
+ if (g.killSwitch?.active) {
629
+ throw new Error(`Kill switch is on${g.killSwitch.reason ? ` (${g.killSwitch.reason})` : ""}. Run \`muxo governance resume\`.`);
630
+ }
631
+ const chainOk = g.auditChain?.chainVerified ?? g.auditChainIntegrity ?? g.auditIntegrityValid;
632
+ assertCheck(chainOk !== false, "Audit hash chain failed verification");
633
+ return `Running, audit chain verified, policy ${res.workspacePolicy?.mode || "enforce"}`;
634
+ });
635
+ await runCheck("catalog", "Tool catalog search works", async () => {
636
+ if (!apiKeyFlag)
637
+ return skip(noKeyHint);
638
+ const res = await harnessJson("/api/v1/tools?lane=composio_instant&limit=5");
639
+ const count = Array.isArray(res.tools) ? res.tools.length : 0;
640
+ assertCheck(count > 0, "Catalog search returned no tools");
641
+ return `${count} instant tools returned`;
642
+ });
643
+ }
644
+ else {
645
+ const local = getLocalContext();
646
+ workspaceId = local.ctx.workspace.id;
647
+ await runCheck("local_store", "Local workspace database opens", async () => {
648
+ const ws = local.store.getWorkspace(local.ctx.workspace.id);
649
+ assertCheck(ws, "Workspace not found in local database");
650
+ return `${ws.name} at ${config.dbPath}`;
651
+ });
652
+ await runCheck("mcp_initialize", "MCP server accepts a session", async () => {
653
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
654
+ jsonrpc: "2.0",
655
+ id: 1,
656
+ method: "initialize",
657
+ params: { protocolVersion: "2025-06-18", capabilities: {} },
658
+ });
659
+ const info = res?.result?.serverInfo;
660
+ assertCheck(info?.name, "initialize returned no serverInfo");
661
+ return `${info.name} ${info.version || ""}`.trim();
662
+ });
663
+ await runCheck("mcp_tools", "MCP tools are listed", async () => {
664
+ const res = await handleJsonRpcMcpRequest(local.engine, local.ctx, {
665
+ jsonrpc: "2.0",
666
+ id: 2,
667
+ method: "tools/list",
668
+ });
669
+ const count = (res?.result?.tools || []).length;
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("No API key. Using the local default workspace.");
676
+ assertCheck(local.tokenAuth, "API key is not valid for this local database");
677
+ return local.ctx.workspace.name;
678
+ });
679
+ await runCheck("governance", "Agents are allowed to act", async () => {
680
+ const ws = local.store.getWorkspace(local.ctx.workspace.id) || local.ctx.workspace;
681
+ const status = local.engine.governance.getGovernanceStatus(ws);
682
+ if (status.killSwitch?.active) {
683
+ throw new Error("Kill switch is on. Run `muxo governance resume`.");
684
+ }
685
+ assertCheck(status.auditChain?.chainVerified !== false, "Audit hash chain failed verification");
686
+ return `Running, audit chain verified, policy ${ws.policy.mode}`;
687
+ });
688
+ await runCheck("catalog", "Tool catalog search works", async () => {
689
+ const res = await local.engine.searchTools({ lane: "composio_instant", limit: 5 });
690
+ assertCheck(res.tools.length > 0, "Catalog search returned no tools");
691
+ return `${res.tools.length} instant tools returned`;
692
+ });
480
693
  }
694
+ const passed = checks.filter((c) => c.status === "pass").length;
695
+ const failed = checks.filter((c) => c.status === "fail").length;
696
+ const skipped = checks.filter((c) => c.status === "skip").length;
697
+ if (failed > 0)
698
+ process.exitCode = 1;
481
699
  console.log(JSON.stringify({
482
- ok: true,
483
- version: "1.3.1",
484
- passed: 7,
485
- total: 7,
700
+ ok: failed === 0,
701
+ version: MUXO_VERSION,
702
+ mode: remoteUrl ? "hosted" : "local",
703
+ apiUrl: remoteUrl || null,
704
+ passed,
705
+ failed,
706
+ skipped,
707
+ total: checks.length,
708
+ checks,
486
709
  workspaceId,
487
710
  data: {
488
711
  mcpServers: {
@@ -1173,6 +1396,9 @@ async function runCli() {
1173
1396
  method: "POST",
1174
1397
  body: {
1175
1398
  prompt: promptText,
1399
+ dryRun: planOnly,
1400
+ planOnly,
1401
+ autoApprove,
1176
1402
  autoExecute: !planOnly,
1177
1403
  approveRisky: autoApprove,
1178
1404
  fundingMode,
@@ -1410,6 +1636,47 @@ async function runCli() {
1410
1636
  }
1411
1637
  if (cmd === "memory") {
1412
1638
  const sub = positional[1];
1639
+ if (sub === "facts") {
1640
+ const query = positional.slice(2).join(" ") || (typeof flags.query === "string" ? flags.query : "");
1641
+ const includeHistorical = !flags.current;
1642
+ if (remoteUrl) {
1643
+ const params = new URLSearchParams({ historical: includeHistorical ? "true" : "false" });
1644
+ if (query)
1645
+ params.set("query", query);
1646
+ const res = await remoteFetch(`/api/v1/memory/facts?${params.toString()}`);
1647
+ console.log(JSON.stringify(res, null, 2));
1648
+ return;
1649
+ }
1650
+ const { store, ctx } = getLocalContext();
1651
+ const facts = store.listTemporalFacts(ctx.workspace.id, {
1652
+ includeHistorical,
1653
+ query: query || undefined,
1654
+ });
1655
+ console.log(JSON.stringify({
1656
+ ok: true,
1657
+ activeFacts: facts.filter((f) => f.validTo === null),
1658
+ historicalFacts: facts.filter((f) => f.validTo !== null),
1659
+ facts,
1660
+ }, null, 2));
1661
+ return;
1662
+ }
1663
+ if (sub === "forget") {
1664
+ const query = positional.slice(2).join(" ").trim() || String(flags.query || "").trim();
1665
+ if (!query)
1666
+ throw new Error("Usage: muxo memory forget <text>");
1667
+ if (remoteUrl) {
1668
+ const res = await remoteFetch("/api/v1/memory/forget", {
1669
+ method: "POST",
1670
+ body: { query },
1671
+ });
1672
+ console.log(JSON.stringify(res, null, 2));
1673
+ return;
1674
+ }
1675
+ const { engine, ctx } = getLocalContext();
1676
+ const pruned = await engine.forgetWorkspaceMemoryMatching(ctx, query);
1677
+ console.log(JSON.stringify({ ok: true, ...pruned }, null, 2));
1678
+ return;
1679
+ }
1413
1680
  if (sub === "add") {
1414
1681
  const content = positional.slice(2).join(" ") || String(flags.content || "");
1415
1682
  if (!content)
@@ -1433,8 +1700,26 @@ async function runCli() {
1433
1700
  console.log(JSON.stringify({ ok: true, memory: entry }, null, 2));
1434
1701
  return;
1435
1702
  }
1703
+ if (sub === "delete" || sub === "rm" || sub === "remove") {
1704
+ const id = positional[2] || String(flags.id || "");
1705
+ if (!id)
1706
+ throw new Error("Usage: muxo memory delete <memory_id>");
1707
+ if (remoteUrl) {
1708
+ const res = await remoteFetch(`/api/v1/memory/${encodeURIComponent(id)}`, { method: "DELETE" });
1709
+ console.log(JSON.stringify(res, null, 2));
1710
+ return;
1711
+ }
1712
+ const { engine, ctx } = getLocalContext();
1713
+ const deleted = await engine.deleteWorkspaceMemory(ctx, id);
1714
+ console.log(JSON.stringify({ ok: true, ...deleted }, null, 2));
1715
+ return;
1716
+ }
1436
1717
  if (remoteUrl) {
1437
- const res = await remoteFetch("/api/v1/memory");
1718
+ const query = sub === "recall" || sub === "search"
1719
+ ? positional.slice(2).join(" ") || flags.query || undefined
1720
+ : positional.slice(1).join(" ") || flags.query || undefined;
1721
+ const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
1722
+ const res = await remoteFetch(`/api/v1/memory${suffix}`);
1438
1723
  console.log(JSON.stringify({
1439
1724
  ok: true,
1440
1725
  ...res,
@@ -1455,25 +1740,125 @@ async function runCli() {
1455
1740
  return;
1456
1741
  }
1457
1742
  if (cmd === "governance") {
1743
+ const sub = positional[1];
1744
+ const ringLabel = (policy) => policy?.executionRing !== undefined
1745
+ ? RING_LABELS[policy.executionRing] || `Ring ${policy.executionRing}`
1746
+ : "Per action (set by risk level)";
1747
+ if (sub === "pause" || sub === "kill-switch" || sub === "kill" || sub === "quarantine") {
1748
+ const reason = positional.slice(2).join(" ").trim() ||
1749
+ (typeof flags.reason === "string" ? flags.reason : "") ||
1750
+ "Paused from the Muxo CLI";
1751
+ if (remoteUrl) {
1752
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1753
+ method: "POST",
1754
+ body: { action: "quarantine", reason },
1755
+ });
1756
+ console.log(JSON.stringify(res, null, 2));
1757
+ return;
1758
+ }
1759
+ const { engine, ctx } = getLocalContext();
1760
+ const out = await engine.activateWorkspaceKillSwitch(ctx, { reason });
1761
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
1762
+ return;
1763
+ }
1764
+ if (sub === "resume") {
1765
+ if (remoteUrl) {
1766
+ const res = await remoteFetch("/api/v1/governance/kill-switch", {
1767
+ method: "POST",
1768
+ body: { action: "resume" },
1769
+ });
1770
+ console.log(JSON.stringify(res, null, 2));
1771
+ return;
1772
+ }
1773
+ const { engine, ctx } = getLocalContext();
1774
+ const out = engine.resumeWorkspaceKillSwitch(ctx);
1775
+ console.log(JSON.stringify({ ok: true, ...out }, null, 2));
1776
+ return;
1777
+ }
1778
+ if (sub === "scan") {
1779
+ const content = positional.slice(2).join(" ").trim() || String(flags.content || "").trim();
1780
+ if (!content)
1781
+ throw new Error("Usage: muxo governance scan <text>");
1782
+ if (remoteUrl) {
1783
+ const res = await remoteFetch("/api/v1/governance/scan-context", {
1784
+ method: "POST",
1785
+ body: { content, sessionId: "cli" },
1786
+ });
1787
+ console.log(JSON.stringify(res, null, 2));
1788
+ return;
1789
+ }
1790
+ const { engine, ctx } = getLocalContext();
1791
+ const scan = engine.governance.scanMemoryContent({
1792
+ workspaceId: ctx.workspace.id,
1793
+ sessionId: "cli",
1794
+ role: "user",
1795
+ content,
1796
+ });
1797
+ console.log(JSON.stringify({ ok: true, scan }, null, 2));
1798
+ return;
1799
+ }
1800
+ if (sub === "policy") {
1801
+ const patch = {};
1802
+ if (typeof flags.mode === "string")
1803
+ patch.mode = flags.mode;
1804
+ if (flags.ring !== undefined)
1805
+ patch.executionRing = Number(flags.ring);
1806
+ if (typeof flags["require-approval"] === "string") {
1807
+ const raw = flags["require-approval"].trim();
1808
+ patch.requireApprovalFor =
1809
+ raw === "none" ? [] : raw.split(",").map((r) => r.trim()).filter(Boolean);
1810
+ }
1811
+ if (flags["block-destructive-sql"] !== undefined) {
1812
+ patch.blockDestructiveSql =
1813
+ flags["block-destructive-sql"] === true || flags["block-destructive-sql"] === "true";
1814
+ }
1815
+ if (flags["max-credits"] !== undefined)
1816
+ patch.maxCreditsPerRun = Number(flags["max-credits"]);
1817
+ if (Object.keys(patch).length === 0) {
1818
+ if (remoteUrl) {
1819
+ const res = await remoteFetch("/api/v1/governance");
1820
+ const policy = res.workspacePolicy || {};
1821
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1822
+ return;
1823
+ }
1824
+ const { store, ctx } = getLocalContext();
1825
+ const policy = (store.getWorkspace(ctx.workspace.id) || ctx.workspace).policy;
1826
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1827
+ return;
1828
+ }
1829
+ if (remoteUrl) {
1830
+ const res = await remoteFetch("/api/v1/policy", { method: "POST", body: patch });
1831
+ const policy = res.workspace?.policy || {};
1832
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(policy), policy }, null, 2));
1833
+ return;
1834
+ }
1835
+ const { store, ctx } = getLocalContext();
1836
+ const updated = store.updateWorkspacePolicy(ctx.workspace.id, sanitizePolicyPatch(patch));
1837
+ console.log(JSON.stringify({ ok: true, ringName: ringLabel(updated.policy), policy: updated.policy }, null, 2));
1838
+ return;
1839
+ }
1458
1840
  if (remoteUrl) {
1459
1841
  const res = await remoteFetch("/api/v1/governance");
1460
1842
  console.log(JSON.stringify({
1461
1843
  ok: true,
1462
1844
  did: res.governance?.agentDid,
1463
- chainValid: res.governance?.auditIntegrityValid ?? true,
1464
- ringName: "Ring 2 (Standard)",
1845
+ chainValid: res.governance?.auditChain?.chainVerified ??
1846
+ res.governance?.auditIntegrityValid ??
1847
+ true,
1848
+ ringName: ringLabel(res.workspacePolicy),
1465
1849
  ...res,
1466
1850
  }, null, 2));
1467
1851
  return;
1468
1852
  }
1469
- const { engine, ctx } = getLocalContext();
1470
- const status = engine.governance.getGovernanceStatus(ctx.workspace);
1471
- const summary = engine.governance.getStatus(ctx.workspace);
1853
+ const { engine, ctx, store } = getLocalContext();
1854
+ const ws = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
1855
+ const status = engine.governance.getGovernanceStatus(ws);
1856
+ const summary = engine.governance.getStatus(ws);
1472
1857
  console.log(JSON.stringify({
1473
1858
  ok: true,
1474
1859
  did: summary.agentDid,
1475
1860
  chainValid: summary.auditChainIntegrity,
1476
- ringName: "Ring 2 (Standard)",
1861
+ ringName: ringLabel(ws.policy),
1477
1862
  ...status,
1478
1863
  }, null, 2));
1479
1864
  return;
@@ -1517,6 +1902,9 @@ async function runCli() {
1517
1902
  method: "POST",
1518
1903
  body: {
1519
1904
  prompt: promptText,
1905
+ dryRun: planOnly,
1906
+ planOnly,
1907
+ autoApprove,
1520
1908
  autoExecute: !planOnly,
1521
1909
  approveRisky: autoApprove,
1522
1910
  fundingMode,
@@ -1565,14 +1953,22 @@ Core Commands:
1565
1953
  muxo receipts [<receipt_id>] List or verify execution audit records
1566
1954
  muxo billing [status|topup|upgrade|checkout] Manage credits, upgrade to Starter, or inspect settlements
1567
1955
  muxo memory [<query>] Recall Muxo Workspace Memory & hybrid context
1956
+ muxo memory add <text> | delete <id> Save or delete a workspace memory
1957
+ muxo memory facts [<query>] [--current] List temporal facts with their history (or only current facts)
1958
+ muxo memory forget <text> Delete memories and retire facts that match <text>
1568
1959
  muxo governance Inspect Muxo Safety & Approvals identity and audit chain
1960
+ muxo governance pause [<reason>] | resume Turn the workspace kill switch on or off
1961
+ muxo governance scan <text> Check text for prompt injection before storing or sending it
1962
+ muxo governance policy [--mode enforce|monitor] [--ring 0-3] [--require-approval write,admin|none]
1963
+ [--block-destructive-sql true|false] [--max-credits <n>]
1964
+ muxo harness Check API, MCP, auth, governance and catalog; print MCP config
1569
1965
  muxo serve [--port 4310] Start unified HTTP Console, Landing, OAuth Callback & MCP server
1570
1966
  muxo mcp Run stdio MCP server (20 canonical tools)
1571
1967
  `);
1572
1968
  }
1573
1969
  runCli()
1574
1970
  .then(() => {
1575
- process.exit(0);
1971
+ process.exit(Number(process.exitCode ?? 0));
1576
1972
  })
1577
1973
  .catch((err) => {
1578
1974
  console.error("Error:", err?.message || err);
package/dist/engine.d.ts CHANGED
@@ -78,6 +78,7 @@ export declare class MuxoEngine {
78
78
  toolSlug: string;
79
79
  input?: Record<string, unknown>;
80
80
  reason?: string;
81
+ fundingMode?: "muxo_funded" | "byok";
81
82
  }): Promise<{
82
83
  plan: ActionPlan;
83
84
  tool: MuxoTool;
@@ -100,26 +101,45 @@ export declare class MuxoEngine {
100
101
  getMemoryOverview(ctx: ExecutionContext, query?: string): Promise<{
101
102
  enabled: boolean;
102
103
  namespace: string;
104
+ fundingSource: string;
103
105
  context: MemoryContextSummary;
104
106
  recentMemories: WorkspaceMemoryRecord[];
105
107
  memories: WorkspaceMemoryRecord[];
108
+ temporalFacts: ReturnType<MuxoStore["listTemporalFacts"]>;
109
+ historicalFacts: ReturnType<MuxoStore["listTemporalFacts"]>;
106
110
  }>;
107
111
  addWorkspaceMemory(ctx: ExecutionContext, params: {
108
112
  title: string;
109
113
  content: string;
110
114
  kind?: WorkspaceMemoryRecord["kind"];
111
115
  metadata?: Record<string, unknown>;
116
+ subject?: string;
117
+ predicate?: string;
118
+ object?: string;
112
119
  }): Promise<WorkspaceMemoryRecord>;
113
120
  deleteWorkspaceMemory(ctx: ExecutionContext, idOrDocId: string): Promise<{
114
121
  deleted: boolean;
115
122
  record: WorkspaceMemoryRecord | null;
116
123
  }>;
124
+ forgetWorkspaceMemoryMatching(ctx: ExecutionContext, query: string): Promise<{
125
+ query: string;
126
+ deletedLocalCount: number;
127
+ invalidatedFactsCount: number;
128
+ deletedRemoteIds: string[];
129
+ deletedMemories: WorkspaceMemoryRecord[];
130
+ invalidatedFacts: ReturnType<MuxoStore["listTemporalFacts"]>;
131
+ }>;
132
+ activateWorkspaceKillSwitch(ctx: ExecutionContext, params: {
133
+ reason: string;
134
+ action?: "terminate" | "quarantine" | "suspend";
135
+ }): Promise<Record<string, unknown>>;
136
+ resumeWorkspaceKillSwitch(ctx: ExecutionContext): Record<string, unknown>;
117
137
  executePlanForRisk(ctx: ExecutionContext, planId: string, expectedRisk: RiskLevel): Promise<{
118
138
  run: ExecutionRun;
119
139
  receipt: ExecutionReceipt;
120
140
  }>;
121
141
  getRun(ctx: ExecutionContext, runId: string): ExecutionRun;
122
- cancelRun(ctx: ExecutionContext, runId: string): {
142
+ cancelRun(ctx: ExecutionContext, runId: string, reason?: string): {
123
143
  run: ExecutionRun;
124
144
  receipt: ExecutionReceipt | null;
125
145
  };