@muxoai/sdk 1.2.3 → 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.
- package/.agents/plugins/marketplace.json +22 -0
- package/.claude-plugin/marketplace.json +32 -0
- package/.codex/config.toml +12 -0
- package/ARCHITECTURE.md +121 -0
- package/README.md +85 -31
- package/dist/clerk.d.ts +35 -0
- package/dist/clerk.js +201 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2380 -0
- package/dist/composio-catalog.d.ts +234 -0
- package/dist/composio-catalog.js +1894 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.js +115 -0
- package/dist/durable-workflow.d.ts +45 -0
- package/dist/durable-workflow.js +90 -0
- package/dist/engine.d.ts +465 -0
- package/dist/engine.js +2560 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mcp-schema.d.ts +286 -0
- package/dist/mcp-schema.js +2559 -0
- package/dist/mcp.d.ts +22 -0
- package/dist/mcp.js +1356 -0
- package/dist/memory-graph.d.ts +21 -0
- package/dist/memory-graph.js +173 -0
- package/dist/nl.d.ts +34 -0
- package/dist/nl.js +756 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/policy.d.ts +190 -0
- package/dist/policy.js +1597 -0
- package/dist/providers/composio-instant.d.ts +18 -0
- package/dist/providers/composio-instant.js +1169 -0
- package/dist/providers/composio-sdk.d.ts +162 -0
- package/dist/providers/composio-sdk.js +1395 -0
- package/dist/providers/microsoft-governance.d.ts +152 -0
- package/dist/providers/microsoft-governance.js +1062 -0
- package/dist/providers/stripe-billing.d.ts +83 -0
- package/dist/providers/stripe-billing.js +611 -0
- package/dist/providers/stripe-projects.d.ts +508 -0
- package/dist/providers/stripe-projects.js +3188 -0
- package/dist/providers/supermemory.d.ts +160 -0
- package/dist/providers/supermemory.js +778 -0
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +951 -0
- package/dist/sdk.js +748 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +3575 -0
- package/dist/store.d.ts +348 -0
- package/dist/store.js +2323 -0
- package/dist/types.d.ts +593 -0
- package/dist/types.js +1 -0
- package/dist/ui/console-html.d.ts +64 -0
- package/dist/ui/console-html.js +3857 -0
- package/dist/ui/design-tokens.d.ts +177 -0
- package/dist/ui/design-tokens.js +210 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +68 -7
- package/plugins/muxo/.app.json +8 -0
- package/plugins/muxo/.claude-plugin/plugin.json +16 -0
- package/plugins/muxo/.codex-plugin/plugin.json +64 -0
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/.mcp.json +8 -0
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/assets/logo.svg +6 -0
- package/plugins/muxo/hooks/hooks.json +27 -0
- package/plugins/muxo/hooks/session-start.sh +97 -0
- package/plugins/muxo/hooks/user-prompt-submit.sh +58 -0
- package/plugins/muxo/mcp.json +28 -0
- package/plugins/muxo/plugin.json +116 -0
- package/plugins/muxo/skills/muxo/SKILL.md +153 -0
- package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
- package/plugins/muxo/skills/muxo/references/cli.md +63 -0
- package/plugins/muxo/skills/muxo/references/mcp.md +37 -0
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +42 -0
- package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
- package/src/catalogues/stripe-projects-catalog.json +9972 -0
- package/vendor/microsoft-governance/README.md +363 -0
- package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
- package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/audit.js +107 -0
- package/vendor/microsoft-governance/dist/audit.js.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
- package/vendor/microsoft-governance/dist/client.d.ts +34 -0
- package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/client.js +112 -0
- package/vendor/microsoft-governance/dist/client.js.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
- package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.js +404 -0
- package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
- package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
- package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
- package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/identity.js +446 -0
- package/vendor/microsoft-governance/dist/identity.js.map +1 -0
- package/vendor/microsoft-governance/dist/index.d.ts +39 -0
- package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/index.js +95 -0
- package/vendor/microsoft-governance/dist/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
- package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
- package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.js +233 -0
- package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.js +289 -0
- package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
- package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy.js +760 -0
- package/vendor/microsoft-governance/dist/policy.js.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
- package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
- package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/rings.js +61 -0
- package/vendor/microsoft-governance/dist/rings.js.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.js +199 -0
- package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
- package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
- package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
- package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/trust.js +209 -0
- package/vendor/microsoft-governance/dist/trust.js.map +1 -0
- package/vendor/microsoft-governance/dist/types.d.ts +422 -0
- package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/types.js +29 -0
- package/vendor/microsoft-governance/dist/types.js.map +1 -0
- package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
- package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/verify.js +356 -0
- package/vendor/microsoft-governance/dist/verify.js.map +1 -0
- package/vendor/microsoft-governance/package.json +64 -0
- package/index.d.ts +0 -177
- package/index.js +0 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import { getConfig } from "./config.js";
|
|
7
|
+
import { MuxoEngine } from "./engine.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";
|
|
11
|
+
import { BYOK_PROVIDER_CATALOG, MuxoStore } from "./store.js";
|
|
12
|
+
import { MUXO_VERSION } from "./version.js";
|
|
13
|
+
function promptUserLine(question) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stderr,
|
|
18
|
+
});
|
|
19
|
+
rl.question(question, (answer) => {
|
|
20
|
+
rl.close();
|
|
21
|
+
resolve(answer.trim());
|
|
22
|
+
});
|
|
23
|
+
rl.on("close", () => resolve(""));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function getCliConfigPath() {
|
|
27
|
+
if (process.env.MUXO_CONFIG_PATH) {
|
|
28
|
+
return path.resolve(process.env.MUXO_CONFIG_PATH);
|
|
29
|
+
}
|
|
30
|
+
return path.join(os.homedir(), ".muxo", "config.json");
|
|
31
|
+
}
|
|
32
|
+
function readCliAuthConfig() {
|
|
33
|
+
try {
|
|
34
|
+
const cfgPath = getCliConfigPath();
|
|
35
|
+
if (!fs.existsSync(cfgPath))
|
|
36
|
+
return null;
|
|
37
|
+
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
38
|
+
if (raw && typeof raw.apiKey === "string" && raw.apiKey.trim()) {
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function writeCliAuthConfig(cfg) {
|
|
48
|
+
const cfgPath = getCliConfigPath();
|
|
49
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
50
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 0o600 });
|
|
51
|
+
return cfgPath;
|
|
52
|
+
}
|
|
53
|
+
function parseFlags(args) {
|
|
54
|
+
const positional = [];
|
|
55
|
+
const flags = {};
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
if (arg.startsWith("--")) {
|
|
59
|
+
const key = arg.slice(2);
|
|
60
|
+
const next = args[i + 1];
|
|
61
|
+
if (next && !next.startsWith("--")) {
|
|
62
|
+
flags[key] = next;
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
flags[key] = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
positional.push(arg);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { positional, flags };
|
|
74
|
+
}
|
|
75
|
+
async function runCli() {
|
|
76
|
+
const { positional, flags } = parseFlags(process.argv.slice(2));
|
|
77
|
+
const cmd = positional[0] || "help";
|
|
78
|
+
if (flags.version || flags.v || cmd === "version") {
|
|
79
|
+
console.log(flags.json
|
|
80
|
+
? JSON.stringify({ ok: true, name: "@muxoai/cli", version: MUXO_VERSION }, null, 2)
|
|
81
|
+
: MUXO_VERSION);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const savedCliConfig = readCliAuthConfig();
|
|
85
|
+
let apiKeyFlag = (typeof flags["api-key"] === "string" ? flags["api-key"] : "") ||
|
|
86
|
+
process.env.MUXO_API_KEY ||
|
|
87
|
+
savedCliConfig?.apiKey ||
|
|
88
|
+
"";
|
|
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(),
|
|
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
|
+
};
|
|
218
|
+
// CLI Login / Autonomous External Signup (`muxo login`)
|
|
219
|
+
if (cmd === "login" || cmd === "signup") {
|
|
220
|
+
const explicitKey = typeof flags["api-key"] === "string"
|
|
221
|
+
? flags["api-key"]
|
|
222
|
+
: positional[1]?.startsWith("mk_")
|
|
223
|
+
? positional[1]
|
|
224
|
+
: "";
|
|
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();
|
|
255
|
+
const resolved = store.resolveAuthToken(explicitKey);
|
|
256
|
+
if (!resolved) {
|
|
257
|
+
throw new Error("Invalid Muxo API key supplied to 'muxo login'.");
|
|
258
|
+
}
|
|
259
|
+
const authPayload = {
|
|
260
|
+
authenticated: true,
|
|
261
|
+
apiKey: explicitKey,
|
|
262
|
+
workspaceId: resolved.workspace.id,
|
|
263
|
+
workspaceName: resolved.workspace.name,
|
|
264
|
+
agentId: resolved.user.id,
|
|
265
|
+
governanceDid: engine.governance.getStatus(resolved.workspace).agentDid,
|
|
266
|
+
supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(resolved.workspace.id),
|
|
267
|
+
updatedAt: new Date().toISOString(),
|
|
268
|
+
};
|
|
269
|
+
const cfgPath = writeCliAuthConfig(authPayload);
|
|
270
|
+
console.log(JSON.stringify({
|
|
271
|
+
ok: true,
|
|
272
|
+
mode: "api_key",
|
|
273
|
+
configPath: cfgPath,
|
|
274
|
+
...authPayload,
|
|
275
|
+
apiKey: maskKey(explicitKey),
|
|
276
|
+
}, null, 2));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// Create an isolated external workspace + mk_live_* key (`muxo login --signup` or `muxo signup`)
|
|
280
|
+
const agentName = typeof flags.name === "string"
|
|
281
|
+
? flags.name
|
|
282
|
+
: positional[1] || "Muxo CLI Agent";
|
|
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();
|
|
301
|
+
const created = store.createAutonomousAgentSignup({
|
|
302
|
+
agentName,
|
|
303
|
+
harness,
|
|
304
|
+
});
|
|
305
|
+
const authPayload = {
|
|
306
|
+
authenticated: true,
|
|
307
|
+
apiKey: created.apiKey,
|
|
308
|
+
workspaceId: created.workspace.id,
|
|
309
|
+
workspaceName: created.workspace.name,
|
|
310
|
+
agentId: created.user.id,
|
|
311
|
+
governanceDid: engine.governance.getStatus(created.workspace).agentDid,
|
|
312
|
+
supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(created.workspace.id),
|
|
313
|
+
updatedAt: new Date().toISOString(),
|
|
314
|
+
};
|
|
315
|
+
const cfgPath = writeCliAuthConfig(authPayload);
|
|
316
|
+
console.log(JSON.stringify({
|
|
317
|
+
ok: true,
|
|
318
|
+
mode: "autonomous_signup",
|
|
319
|
+
configPath: cfgPath,
|
|
320
|
+
...authPayload,
|
|
321
|
+
}, null, 2));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (cmd === "logout") {
|
|
325
|
+
const cfgPath = getCliConfigPath();
|
|
326
|
+
if (fs.existsSync(cfgPath)) {
|
|
327
|
+
fs.unlinkSync(cfgPath);
|
|
328
|
+
}
|
|
329
|
+
console.log(JSON.stringify({ ok: true, authenticated: false, configPath: cfgPath }, null, 2));
|
|
330
|
+
return;
|
|
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
|
+
}
|
|
470
|
+
if (cmd === "claim") {
|
|
471
|
+
const email = positional[1] || (typeof flags.email === "string" ? flags.email : "");
|
|
472
|
+
if (!email || !email.includes("@")) {
|
|
473
|
+
throw new Error("Usage: muxo claim <owner_email>");
|
|
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();
|
|
491
|
+
const claim = store.createAgentClaim({
|
|
492
|
+
workspaceId: ctx.workspace.id,
|
|
493
|
+
email,
|
|
494
|
+
baseUrl: config.baseUrl,
|
|
495
|
+
});
|
|
496
|
+
console.log(JSON.stringify({ ok: true, claim, ownerEmail: claim.targetEmail, ...claim }, null, 2));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Stdio MCP Server mode (`muxo mcp`)
|
|
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
|
+
}
|
|
511
|
+
const rl = readline.createInterface({
|
|
512
|
+
input: process.stdin,
|
|
513
|
+
terminal: false,
|
|
514
|
+
});
|
|
515
|
+
let queue = Promise.resolve();
|
|
516
|
+
let mcpSessionId = "";
|
|
517
|
+
rl.on("line", (line) => {
|
|
518
|
+
const trimmed = line.trim();
|
|
519
|
+
if (!trimmed)
|
|
520
|
+
return;
|
|
521
|
+
queue = queue.then(async () => {
|
|
522
|
+
try {
|
|
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();
|
|
556
|
+
const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
|
|
557
|
+
if (response) {
|
|
558
|
+
process.stdout.write(JSON.stringify(response) + "\n");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
process.stdout.write(JSON.stringify({
|
|
563
|
+
jsonrpc: "2.0",
|
|
564
|
+
id: null,
|
|
565
|
+
error: {
|
|
566
|
+
code: -32700,
|
|
567
|
+
message: String(err?.message || "Parse error"),
|
|
568
|
+
},
|
|
569
|
+
}) + "\n");
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
await new Promise((resolve) => {
|
|
574
|
+
rl.on("close", () => {
|
|
575
|
+
queue.finally(() => resolve());
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
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;
|
|
758
|
+
console.log(JSON.stringify({
|
|
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,
|
|
769
|
+
data: {
|
|
770
|
+
mcpServers: {
|
|
771
|
+
muxo: {
|
|
772
|
+
command: "npx",
|
|
773
|
+
args: ["-y", "@muxoai/cli@latest", "mcp"],
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
remoteMcp: {
|
|
777
|
+
url: `${remoteUrl || "https://api.muxo.ai"}/mcp`,
|
|
778
|
+
transport: "streamable-http",
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
}, null, 2));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
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();
|
|
826
|
+
const org = engine.getOrganization(ctx);
|
|
827
|
+
console.log(JSON.stringify({
|
|
828
|
+
ok: true,
|
|
829
|
+
authenticated: true,
|
|
830
|
+
authSource: tokenAuth
|
|
831
|
+
? savedCliConfig?.apiKey === apiKeyFlag
|
|
832
|
+
? "config_file"
|
|
833
|
+
: "api_key"
|
|
834
|
+
: "local_default",
|
|
835
|
+
profile: engine.getProfile(ctx),
|
|
836
|
+
workspace: {
|
|
837
|
+
...ctx.workspace,
|
|
838
|
+
creditBalance: ctx.workspace.creditsBalance,
|
|
839
|
+
},
|
|
840
|
+
governanceDid: engine.governance.getStatus(ctx.workspace).agentDid,
|
|
841
|
+
supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(ctx.workspace.id),
|
|
842
|
+
...org,
|
|
843
|
+
}, null, 2));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (cmd === "search") {
|
|
847
|
+
const query = positional.slice(1).join(" ") || flags.query || "";
|
|
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();
|
|
870
|
+
const res = await engine.searchTools({
|
|
871
|
+
query,
|
|
872
|
+
lane,
|
|
873
|
+
limit,
|
|
874
|
+
});
|
|
875
|
+
if (flags.json || !process.stdout.isTTY) {
|
|
876
|
+
console.log(JSON.stringify({ ok: true, count: res.total, ...res }, null, 2));
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
console.log(`\nFound ${res.total} tools matching '${query || "*"}' (lane: ${lane}):\n`);
|
|
880
|
+
for (const t of res.tools) {
|
|
881
|
+
console.log(` • ${t.slug.padEnd(38)} [${t.lane.padEnd(16)}] (${t.riskLevel}, ${t.creditCost} cr)`);
|
|
882
|
+
console.log(` ${t.description.slice(0, 100)}`);
|
|
883
|
+
}
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (cmd === "inspect") {
|
|
887
|
+
const slug = positional[1];
|
|
888
|
+
if (!slug)
|
|
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();
|
|
896
|
+
const tool = await engine.inspectTool(slug);
|
|
897
|
+
console.log(JSON.stringify(tool, null, 2));
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
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();
|
|
918
|
+
const status = await engine.connectionStatus(ctx, {
|
|
919
|
+
provider: positional[1],
|
|
920
|
+
syncLive: flags.sync !== "false",
|
|
921
|
+
});
|
|
922
|
+
console.log(JSON.stringify(status, null, 2));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (cmd === "connect" || cmd === "link") {
|
|
926
|
+
const provider = positional[1];
|
|
927
|
+
if (!provider)
|
|
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();
|
|
960
|
+
const res = await engine.startConnection(ctx, {
|
|
961
|
+
provider,
|
|
962
|
+
lane: flags.lane,
|
|
963
|
+
callbackUrl: typeof flags.callback === "string" ? flags.callback : undefined,
|
|
964
|
+
});
|
|
965
|
+
const connection = { ...res, provider: res.providerSlug };
|
|
966
|
+
if (flags.wait) {
|
|
967
|
+
const timeoutSeconds = flags.timeout ? Number(flags.timeout) : 15;
|
|
968
|
+
const waited = await engine.waitForConnection(ctx, {
|
|
969
|
+
provider,
|
|
970
|
+
timeoutSeconds,
|
|
971
|
+
});
|
|
972
|
+
console.log(JSON.stringify({ ok: true, connection, ...res, waitResult: waited }, null, 2));
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
console.log(JSON.stringify({ ok: true, connection, ...res }, null, 2));
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (cmd === "wait") {
|
|
979
|
+
const provider = positional[1];
|
|
980
|
+
if (!provider)
|
|
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();
|
|
1000
|
+
if (flags["simulate-callback"]) {
|
|
1001
|
+
await engine.handleOAuthCallback({
|
|
1002
|
+
workspaceId: ctx.workspace.id,
|
|
1003
|
+
provider,
|
|
1004
|
+
status: "active",
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
const waited = await engine.waitForConnection(ctx, {
|
|
1008
|
+
provider,
|
|
1009
|
+
timeoutSeconds,
|
|
1010
|
+
});
|
|
1011
|
+
console.log(JSON.stringify(waited, null, 2));
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (cmd === "plan") {
|
|
1015
|
+
const toolSlug = positional[1];
|
|
1016
|
+
if (!toolSlug)
|
|
1017
|
+
throw new Error("Usage: muxo plan <tool_slug> [--input '{...}']");
|
|
1018
|
+
const input = flags.input ? JSON.parse(String(flags.input)) : {};
|
|
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();
|
|
1029
|
+
const planned = await engine.planAction(ctx, { toolSlug, input, reason });
|
|
1030
|
+
console.log(JSON.stringify(planned, null, 2));
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (cmd === "approve") {
|
|
1034
|
+
const planId = positional[1];
|
|
1035
|
+
if (!planId)
|
|
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();
|
|
1054
|
+
const approved = engine.approvePlan(ctx, planId, "approved");
|
|
1055
|
+
console.log(JSON.stringify(approved, null, 2));
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (cmd === "run") {
|
|
1059
|
+
const planId = positional[1];
|
|
1060
|
+
if (!planId)
|
|
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();
|
|
1068
|
+
const plan = engine.getPlan(ctx, planId);
|
|
1069
|
+
const result = await engine.executePlanForRisk(ctx, planId, plan.riskLevel);
|
|
1070
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (cmd === "exec" || cmd === "provision") {
|
|
1074
|
+
let toolSlug = positional[1];
|
|
1075
|
+
if (!toolSlug)
|
|
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();
|
|
1136
|
+
if (cmd === "provision" && !toolSlug.includes(".")) {
|
|
1137
|
+
const searched = await engine.searchTools({
|
|
1138
|
+
query: toolSlug,
|
|
1139
|
+
lane: "stripe_projects",
|
|
1140
|
+
limit: 10,
|
|
1141
|
+
});
|
|
1142
|
+
const createTool = searched.tools.find((t) => t.slug.startsWith(`resource.${toolSlug.toLowerCase()}.create_`)) ||
|
|
1143
|
+
searched.tools.find((t) => t.slug.startsWith(`resource.${toolSlug.toLowerCase()}.`)) ||
|
|
1144
|
+
searched.tools[0];
|
|
1145
|
+
if (createTool) {
|
|
1146
|
+
toolSlug = createTool.slug;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const planned = await engine.planAction(ctx, {
|
|
1150
|
+
toolSlug,
|
|
1151
|
+
input,
|
|
1152
|
+
reason: `Direct CLI ${cmd}`,
|
|
1153
|
+
});
|
|
1154
|
+
if (planned.plan.approvalStatus === "pending_approval") {
|
|
1155
|
+
if (flags.approve || cmd === "provision") {
|
|
1156
|
+
engine.approvePlan(ctx, planned.plan.id, "approved");
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
console.log(JSON.stringify({
|
|
1160
|
+
ok: false,
|
|
1161
|
+
status: "pending_approval",
|
|
1162
|
+
plan: planned.plan,
|
|
1163
|
+
hint: `Pass --approve or run 'muxo approve ${planned.plan.id}' to authorize this ${planned.plan.riskLevel} operation.`,
|
|
1164
|
+
}, null, 2));
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const result = await engine.executePlanForRisk(ctx, planned.plan.id, planned.plan.riskLevel);
|
|
1169
|
+
const latestSettlement = result.receipt.supplierSettlement || null;
|
|
1170
|
+
console.log(JSON.stringify({
|
|
1171
|
+
ok: true,
|
|
1172
|
+
...result,
|
|
1173
|
+
receipt: {
|
|
1174
|
+
...result.receipt,
|
|
1175
|
+
verified: store.verifyReceipt(result.receipt),
|
|
1176
|
+
},
|
|
1177
|
+
settlement: latestSettlement,
|
|
1178
|
+
}, null, 2));
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
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();
|
|
1188
|
+
console.log(JSON.stringify(store.listResources(ctx.workspace.id), null, 2));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (cmd === "env" && positional[1] === "pull") {
|
|
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();
|
|
1216
|
+
const workdirSecrets = await engine.stripeProjects
|
|
1217
|
+
.pullEnvVarsFromWorkdir()
|
|
1218
|
+
.catch(() => ({}));
|
|
1219
|
+
const vaultSecrets = store.getDecryptedWorkspaceEnv(ctx.workspace.id);
|
|
1220
|
+
const merged = { ...workdirSecrets, ...vaultSecrets };
|
|
1221
|
+
const lines = [
|
|
1222
|
+
`# Muxo Provisioned Environment (${ctx.workspace.slug})`,
|
|
1223
|
+
...Object.entries(merged).map(([k, v]) => `${k}=${JSON.stringify(v)}`),
|
|
1224
|
+
"",
|
|
1225
|
+
];
|
|
1226
|
+
fs.writeFileSync(resolvedPath, lines.join("\n"), { mode: 0o600 });
|
|
1227
|
+
const keys = Object.keys(merged);
|
|
1228
|
+
console.log(JSON.stringify({
|
|
1229
|
+
ok: true,
|
|
1230
|
+
outputFile: resolvedPath,
|
|
1231
|
+
keysWritten: keys,
|
|
1232
|
+
keys,
|
|
1233
|
+
keyCount: keys.length,
|
|
1234
|
+
}, null, 2));
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (cmd === "receipts") {
|
|
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();
|
|
1251
|
+
if (receiptId) {
|
|
1252
|
+
console.log(JSON.stringify(engine.getReceipt(ctx, receiptId), null, 2));
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
console.log(JSON.stringify({ ok: true, ...engine.listReceipts(ctx, 20) }, null, 2));
|
|
1256
|
+
}
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (cmd === "billing" || cmd === "topup") {
|
|
1260
|
+
const sub = cmd === "topup" ? "topup" : positional[1] || "status";
|
|
1261
|
+
if (sub === "topup") {
|
|
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();
|
|
1273
|
+
const usdPaid = (credits * 0.01).toFixed(2);
|
|
1274
|
+
const refId = `cli_topup_${Date.now()}`;
|
|
1275
|
+
const updated = store.addWorkspaceCredits({
|
|
1276
|
+
workspaceId: ctx.workspace.id,
|
|
1277
|
+
credits,
|
|
1278
|
+
referenceId: refId,
|
|
1279
|
+
description: `User purchased +${credits.toLocaleString()} Muxo Credits ($${usdPaid} USD -> Muxo Master Wallet)`,
|
|
1280
|
+
});
|
|
1281
|
+
console.log(JSON.stringify({
|
|
1282
|
+
ok: true,
|
|
1283
|
+
creditsAdded: credits,
|
|
1284
|
+
usdPaid: Number(usdPaid),
|
|
1285
|
+
creditsBalance: updated.creditsBalance,
|
|
1286
|
+
creditBalance: updated.creditsBalance,
|
|
1287
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1288
|
+
}, null, 2));
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
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();
|
|
1308
|
+
const updated = store.upgradeWorkspaceToStarter({
|
|
1309
|
+
workspaceId: ctx.workspace.id,
|
|
1310
|
+
referenceId: `cli_upgrade_${Date.now()}`,
|
|
1311
|
+
bonusCredits: 1000,
|
|
1312
|
+
});
|
|
1313
|
+
console.log(JSON.stringify({
|
|
1314
|
+
ok: true,
|
|
1315
|
+
upgraded: true,
|
|
1316
|
+
tier: updated.tier,
|
|
1317
|
+
monthlyActionsLimit: updated.monthlyActionsLimit,
|
|
1318
|
+
creditsAdded: 1000,
|
|
1319
|
+
creditsBalance: updated.creditsBalance,
|
|
1320
|
+
creditBalance: updated.creditsBalance,
|
|
1321
|
+
workspace: updated,
|
|
1322
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1323
|
+
}, null, 2));
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (sub === "checkout") {
|
|
1327
|
+
const kind = flags.kind === "starter_subscription" || positional[2] === "starter"
|
|
1328
|
+
? "starter_subscription"
|
|
1329
|
+
: "credit_topup";
|
|
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();
|
|
1340
|
+
const session = await engine.stripeBilling.createCheckoutSession({
|
|
1341
|
+
workspace: ctx.workspace,
|
|
1342
|
+
customerEmail: ctx.user.email,
|
|
1343
|
+
kind,
|
|
1344
|
+
credits,
|
|
1345
|
+
returnBaseUrl: config.baseUrl,
|
|
1346
|
+
});
|
|
1347
|
+
console.log(JSON.stringify({ ok: true, kind, ...session }, null, 2));
|
|
1348
|
+
return;
|
|
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
|
+
}
|
|
1371
|
+
if (sub === "verify") {
|
|
1372
|
+
const sessionId = positional[2] || String(flags.session || "");
|
|
1373
|
+
if (!sessionId)
|
|
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();
|
|
1384
|
+
const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(sessionId);
|
|
1385
|
+
console.log(JSON.stringify({ ok: true, ...verified }, null, 2));
|
|
1386
|
+
return;
|
|
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();
|
|
1394
|
+
const freshWs = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
|
|
1395
|
+
console.log(JSON.stringify({
|
|
1396
|
+
ok: true,
|
|
1397
|
+
tier: freshWs.tier,
|
|
1398
|
+
creditsBalance: freshWs.creditsBalance,
|
|
1399
|
+
creditBalance: freshWs.creditsBalance,
|
|
1400
|
+
monthlyActionsUsed: freshWs.monthlyActionsUsed,
|
|
1401
|
+
monthlyActionsLimit: freshWs.monthlyActionsLimit,
|
|
1402
|
+
byokKeys: store.listByokKeys(ctx.workspace.id),
|
|
1403
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1404
|
+
recentLedger: store.listLedgerEntries(ctx.workspace.id, 25),
|
|
1405
|
+
}, null, 2));
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (cmd === "byok" || cmd === "keys") {
|
|
1409
|
+
const sub = positional[1] || "list";
|
|
1410
|
+
if (sub === "set" || sub === "add" || sub === "prompt") {
|
|
1411
|
+
let provider = (positional[2] || String(flags.provider || "")).trim().toLowerCase();
|
|
1412
|
+
if (!provider) {
|
|
1413
|
+
provider = (await promptUserLine("Enter provider slug (openai, anthropic, openrouter, gemini, composio, stripe, supermemory, firecrawl, exa): ")).toLowerCase();
|
|
1414
|
+
}
|
|
1415
|
+
if (!provider) {
|
|
1416
|
+
throw new Error("Usage: muxo byok set <provider> [<api_key>]");
|
|
1417
|
+
}
|
|
1418
|
+
let secretKey = positional[3] ||
|
|
1419
|
+
(typeof flags.key === "string" ? flags.key : "") ||
|
|
1420
|
+
(typeof flags["secret-key"] === "string" ? flags["secret-key"] : "");
|
|
1421
|
+
if (!secretKey) {
|
|
1422
|
+
secretKey = await promptUserLine(`Enter API key for ${provider} (stored encrypted with AES-256-GCM in Muxo Vault): `);
|
|
1423
|
+
}
|
|
1424
|
+
if (!secretKey) {
|
|
1425
|
+
throw new Error(`API key is required for provider '${provider}'.`);
|
|
1426
|
+
}
|
|
1427
|
+
if (remoteUrl) {
|
|
1428
|
+
const data = await remoteFetch("/api/v1/byok", {
|
|
1429
|
+
method: "POST",
|
|
1430
|
+
body: { provider, secretKey },
|
|
1431
|
+
});
|
|
1432
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const { store, ctx } = getLocalContext();
|
|
1436
|
+
const record = store.setByokKey(ctx.workspace.id, provider, secretKey);
|
|
1437
|
+
console.log(JSON.stringify({
|
|
1438
|
+
ok: true,
|
|
1439
|
+
action: "saved",
|
|
1440
|
+
key: record,
|
|
1441
|
+
fundingMode: record.category === "supplier" ? "byok (0 credit cost)" : "llm_router",
|
|
1442
|
+
}, null, 2));
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (sub === "delete" || sub === "remove" || sub === "rm") {
|
|
1446
|
+
const provider = (positional[2] || String(flags.provider || "")).trim().toLowerCase();
|
|
1447
|
+
if (!provider)
|
|
1448
|
+
throw new Error("Usage: muxo byok delete <provider>");
|
|
1449
|
+
if (remoteUrl) {
|
|
1450
|
+
const data = await remoteFetch(`/api/v1/byok/${encodeURIComponent(provider)}`, { method: "DELETE" });
|
|
1451
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const { store, ctx } = getLocalContext();
|
|
1455
|
+
store.deleteByokKey(ctx.workspace.id, provider);
|
|
1456
|
+
console.log(JSON.stringify({
|
|
1457
|
+
ok: true,
|
|
1458
|
+
action: "deleted",
|
|
1459
|
+
provider,
|
|
1460
|
+
keys: store.listByokKeys(ctx.workspace.id),
|
|
1461
|
+
}, null, 2));
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (remoteUrl) {
|
|
1465
|
+
const data = await remoteFetch("/api/v1/byok");
|
|
1466
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const { store, ctx } = getLocalContext();
|
|
1470
|
+
console.log(JSON.stringify({
|
|
1471
|
+
ok: true,
|
|
1472
|
+
keys: store.listByokKeys(ctx.workspace.id),
|
|
1473
|
+
supportedProviders: BYOK_PROVIDER_CATALOG,
|
|
1474
|
+
}, null, 2));
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (cmd === "do" || cmd === "ask" || cmd === "nl") {
|
|
1478
|
+
let promptText = positional.slice(1).join(" ").trim() ||
|
|
1479
|
+
(typeof flags.prompt === "string" ? flags.prompt : "");
|
|
1480
|
+
if (!promptText) {
|
|
1481
|
+
promptText = await promptUserLine("What would you like Muxo to do? ");
|
|
1482
|
+
}
|
|
1483
|
+
if (!promptText) {
|
|
1484
|
+
throw new Error('Usage: muxo do "<natural language instruction>"');
|
|
1485
|
+
}
|
|
1486
|
+
const planOnly = flags["plan-only"] === true || flags.plan === true;
|
|
1487
|
+
const autoApprove = flags["no-approve"] ? false : true;
|
|
1488
|
+
const fundingMode = flags.byok || flags.funding === "byok"
|
|
1489
|
+
? "byok"
|
|
1490
|
+
: flags.funding === "muxo_funded"
|
|
1491
|
+
? "muxo_funded"
|
|
1492
|
+
: undefined;
|
|
1493
|
+
if (remoteUrl) {
|
|
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", {
|
|
1506
|
+
method: "POST",
|
|
1507
|
+
body: {
|
|
1508
|
+
prompt: promptText,
|
|
1509
|
+
dryRun: planOnly,
|
|
1510
|
+
planOnly,
|
|
1511
|
+
autoApprove,
|
|
1512
|
+
autoExecute: !planOnly,
|
|
1513
|
+
approveRisky: autoApprove,
|
|
1514
|
+
fundingMode,
|
|
1515
|
+
},
|
|
1516
|
+
});
|
|
1517
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1518
|
+
return;
|
|
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
|
+
}
|
|
1524
|
+
const result = await engine.doNaturalLanguage(ctx, {
|
|
1525
|
+
prompt: promptText,
|
|
1526
|
+
autoApprove,
|
|
1527
|
+
planOnly,
|
|
1528
|
+
fundingMode,
|
|
1529
|
+
});
|
|
1530
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
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 },
|
|
1631
|
+
});
|
|
1632
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const { store, engine, ctx } = getLocalContext();
|
|
1636
|
+
const freshWs = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
|
|
1637
|
+
const byokKeys = store.listByokKeys(ctx.workspace.id).filter((k) => k.configured);
|
|
1638
|
+
const conns = store.listConnections(ctx.workspace.id);
|
|
1639
|
+
const resources = store.listResources(ctx.workspace.id);
|
|
1640
|
+
const gov = engine.governance.getStatus(freshWs);
|
|
1641
|
+
if (flags.json || flags["non-interactive"] || flags.once) {
|
|
1642
|
+
console.log(JSON.stringify({
|
|
1643
|
+
ok: true,
|
|
1644
|
+
tui: "ready",
|
|
1645
|
+
mode: "tui_snapshot",
|
|
1646
|
+
workspace: {
|
|
1647
|
+
id: freshWs.id,
|
|
1648
|
+
name: freshWs.name,
|
|
1649
|
+
slug: freshWs.slug,
|
|
1650
|
+
tier: freshWs.tier,
|
|
1651
|
+
creditsBalance: freshWs.creditsBalance,
|
|
1652
|
+
monthlyActionsUsed: freshWs.monthlyActionsUsed,
|
|
1653
|
+
monthlyActionsLimit: freshWs.monthlyActionsLimit,
|
|
1654
|
+
},
|
|
1655
|
+
governanceDid: gov.agentDid,
|
|
1656
|
+
byokKeys,
|
|
1657
|
+
activeConnections: conns.filter((c) => c.status === "active").length,
|
|
1658
|
+
activeResources: resources.filter((r) => r.status === "active").length,
|
|
1659
|
+
supportedByokProviders: Object.keys(BYOK_PROVIDER_CATALOG),
|
|
1660
|
+
}, null, 2));
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const printBanner = () => {
|
|
1664
|
+
const wsNow = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
|
|
1665
|
+
const keysNow = store.listByokKeys(wsNow.id).filter((k) => k.configured);
|
|
1666
|
+
const hasSupplierByok = keysNow.some((k) => k.category === "supplier");
|
|
1667
|
+
console.log("\n┌──────────────────────────────────────────────────────────────────────────┐");
|
|
1668
|
+
console.log("│ ⚡ MUXO INTERACTIVE TUI — Execution, Cloud Infra, Memory & Governance │");
|
|
1669
|
+
console.log("├──────────────────────────────────────────────────────────────────────────┤");
|
|
1670
|
+
console.log(`│ Workspace : ${wsNow.name.padEnd(28)} Tier : ${wsNow.tier.toUpperCase().padEnd(16)}│`);
|
|
1671
|
+
console.log(`│ Credits : ${String(wsNow.creditsBalance).padEnd(28)} Funding : ${(hasSupplierByok ? "BYOK (0 cr)" : "muxo_funded").padEnd(16)}│`);
|
|
1672
|
+
console.log(`│ BYOK Keys : ${String(keysNow.length).padEnd(28)} DID : ${gov.agentDid.slice(0, 16)}│`);
|
|
1673
|
+
console.log("├──────────────────────────────────────────────────────────────────────────┤");
|
|
1674
|
+
console.log("│ [1] ✨ Run in Plain English (Natural Language Router: muxo do) │");
|
|
1675
|
+
console.log("│ [2] 🔑 Configure BYOK API Key (OpenAI, Anthropic, Gemini, Firecrawl...) │");
|
|
1676
|
+
console.log("│ [3] 💳 Billing: Top Up +1,000 Credits or Upgrade to Starter Plan │");
|
|
1677
|
+
console.log("│ [4] 🔗 Connect OAuth App (GitHub, Gmail, Slack, Notion, Linear) │");
|
|
1678
|
+
console.log("│ [5] ☁️ Provision Cloud Database (Turso SQLite, Neon Postgres, Redis) │");
|
|
1679
|
+
console.log("│ [q] Exit TUI │");
|
|
1680
|
+
console.log("└──────────────────────────────────────────────────────────────────────────┘");
|
|
1681
|
+
};
|
|
1682
|
+
printBanner();
|
|
1683
|
+
const choice = (await promptUserLine("\nSelect an option [1-5, q] (or type any natural language command): ")).trim();
|
|
1684
|
+
if (!choice || choice.toLowerCase() === "q" || choice.toLowerCase() === "exit") {
|
|
1685
|
+
console.log("Exited Muxo TUI.");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (choice === "1") {
|
|
1689
|
+
const nlPrompt = await promptUserLine("Ask Muxo in plain English: ");
|
|
1690
|
+
if (nlPrompt) {
|
|
1691
|
+
const res = await engine.doNaturalLanguage(ctx, { prompt: nlPrompt, autoApprove: true });
|
|
1692
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1693
|
+
}
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (choice === "2") {
|
|
1697
|
+
const provider = (await promptUserLine("Provider (openai, anthropic, openrouter, gemini, composio, stripe, supermemory, firecrawl, exa): ")).toLowerCase();
|
|
1698
|
+
const secretKey = await promptUserLine(`Paste API key for ${provider}: `);
|
|
1699
|
+
if (provider && secretKey) {
|
|
1700
|
+
const saved = store.setByokKey(ctx.workspace.id, provider, secretKey);
|
|
1701
|
+
console.log(JSON.stringify({ ok: true, saved }, null, 2));
|
|
1702
|
+
}
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (choice === "3") {
|
|
1706
|
+
const action = (await promptUserLine("Type 'upgrade' for Starter Plan or credit amount (default 1000): ")).toLowerCase();
|
|
1707
|
+
if (action === "upgrade") {
|
|
1708
|
+
const up = store.upgradeWorkspaceToStarter({
|
|
1709
|
+
workspaceId: ctx.workspace.id,
|
|
1710
|
+
referenceId: `tui_upgrade_${Date.now()}`,
|
|
1711
|
+
bonusCredits: 1000,
|
|
1712
|
+
});
|
|
1713
|
+
console.log(JSON.stringify({ ok: true, upgraded: true, workspace: up }, null, 2));
|
|
1714
|
+
}
|
|
1715
|
+
else {
|
|
1716
|
+
const cr = Math.max(100, Number(action) || 1000);
|
|
1717
|
+
const up = store.addWorkspaceCredits({
|
|
1718
|
+
workspaceId: ctx.workspace.id,
|
|
1719
|
+
credits: cr,
|
|
1720
|
+
referenceId: `tui_topup_${Date.now()}`,
|
|
1721
|
+
description: `TUI credit top-up (+${cr} credits)`,
|
|
1722
|
+
});
|
|
1723
|
+
console.log(JSON.stringify({ ok: true, creditsAdded: cr, creditsBalance: up.creditsBalance }, null, 2));
|
|
1724
|
+
}
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (choice === "4") {
|
|
1728
|
+
const provider = (await promptUserLine("OAuth Provider (github, gmail, slack, notion, linear): ")) || "github";
|
|
1729
|
+
const conn = await engine.startConnection(ctx, { provider });
|
|
1730
|
+
console.log(JSON.stringify({ ok: true, connection: conn }, null, 2));
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
if (choice === "5") {
|
|
1734
|
+
const dbPrompt = (await promptUserLine("Database to provision (e.g. 'Provision Turso database my-app-db'): ")) || "Provision Turso database my-app-db";
|
|
1735
|
+
const res = await engine.doNaturalLanguage(ctx, { prompt: dbPrompt, autoApprove: true });
|
|
1736
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
// Otherwise treat whatever they typed as a natural language command!
|
|
1740
|
+
const res = await engine.doNaturalLanguage(ctx, { prompt: choice, autoApprove: true });
|
|
1741
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
if (cmd === "memory") {
|
|
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
|
+
}
|
|
1787
|
+
if (sub === "add") {
|
|
1788
|
+
const content = positional.slice(2).join(" ") || String(flags.content || "");
|
|
1789
|
+
if (!content)
|
|
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();
|
|
1803
|
+
const entry = await engine.addWorkspaceMemory(ctx, {
|
|
1804
|
+
content,
|
|
1805
|
+
title: flags.title ? String(flags.title) : content.slice(0, 60),
|
|
1806
|
+
});
|
|
1807
|
+
console.log(JSON.stringify({ ok: true, memory: entry }, null, 2));
|
|
1808
|
+
return;
|
|
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();
|
|
1838
|
+
const query = sub === "recall" || sub === "search"
|
|
1839
|
+
? positional.slice(2).join(" ") || flags.query || undefined
|
|
1840
|
+
: positional.slice(1).join(" ") || flags.query || undefined;
|
|
1841
|
+
const recall = await engine.getMemoryOverview(ctx, query);
|
|
1842
|
+
console.log(JSON.stringify({
|
|
1843
|
+
ok: true,
|
|
1844
|
+
...recall,
|
|
1845
|
+
localMemories: recall.recentMemories,
|
|
1846
|
+
}, null, 2));
|
|
1847
|
+
return;
|
|
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
|
+
}
|
|
1968
|
+
if (cmd === "governance") {
|
|
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);
|
|
2083
|
+
console.log(JSON.stringify({
|
|
2084
|
+
ok: true,
|
|
2085
|
+
did: summary.agentDid,
|
|
2086
|
+
chainValid: summary.auditChainIntegrity,
|
|
2087
|
+
ringName: ringLabel(ws.policy),
|
|
2088
|
+
...status,
|
|
2089
|
+
}, null, 2));
|
|
2090
|
+
return;
|
|
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
|
+
}
|
|
2264
|
+
if (cmd === "serve") {
|
|
2265
|
+
const { createMuxoServer } = await import("./server.js");
|
|
2266
|
+
const port = flags.port ? Number(flags.port) : config.port;
|
|
2267
|
+
const baseUrl = `http://localhost:${port}`;
|
|
2268
|
+
const srvConfig = { ...config, port, baseUrl };
|
|
2269
|
+
const { server } = createMuxoServer(srvConfig);
|
|
2270
|
+
await new Promise((resolve) => {
|
|
2271
|
+
server.listen(port, () => {
|
|
2272
|
+
console.log(`\n⚡ Muxo Platform running at ${baseUrl}`);
|
|
2273
|
+
console.log(` ├─ Web Console: ${baseUrl}/`);
|
|
2274
|
+
console.log(` ├─ Landing Page: ${baseUrl}/landing`);
|
|
2275
|
+
console.log(` ├─ MCP Server: ${baseUrl}/mcp`);
|
|
2276
|
+
console.log(` ├─ OAuth Callback: ${baseUrl}/oauth/callback`);
|
|
2277
|
+
console.log(` └─ Agent Skill: ${baseUrl}/skill.md\n`);
|
|
2278
|
+
});
|
|
2279
|
+
process.on("SIGINT", () => {
|
|
2280
|
+
server.close(() => resolve());
|
|
2281
|
+
});
|
|
2282
|
+
process.on("SIGTERM", () => {
|
|
2283
|
+
server.close(() => resolve());
|
|
2284
|
+
});
|
|
2285
|
+
});
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
// Natural Language fallback: if user passed plain English directly (`muxo "Audit https://muxo.ai for SEO"`)
|
|
2289
|
+
if (cmd !== "help" && !flags.help && !flags.h && positional.length > 0) {
|
|
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();
|
|
2315
|
+
const result = await engine.doNaturalLanguage(ctx, {
|
|
2316
|
+
prompt: promptText,
|
|
2317
|
+
autoApprove,
|
|
2318
|
+
planOnly,
|
|
2319
|
+
fundingMode,
|
|
2320
|
+
});
|
|
2321
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
console.log(`
|
|
2325
|
+
Muxo CLI — Unified Agent Execution, Cloud Infrastructure, Memory & Safety Platform
|
|
2326
|
+
|
|
2327
|
+
Natural Language & Interactive TUI:
|
|
2328
|
+
muxo setup [--api-key <key>] 1-command auto-signup (or join via team API key) + MCP setup
|
|
2329
|
+
muxo do "<instruction>" Execute any goal in plain English (LLM/semantic router -> plan -> execute)
|
|
2330
|
+
muxo ask "<instruction>" [--plan-only] Alias for 'muxo do' (pass --plan-only to inspect plan before running)
|
|
2331
|
+
muxo tui Launch interactive Terminal UI dashboard (BYOK, Billing, OAuth, Infra)
|
|
2332
|
+
muxo byok [list|set|delete] [<provider>] Manage BYOK API keys in AES-256-GCM Vault (0 credit cost when active)
|
|
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
|
+
|
|
2339
|
+
Core Commands:
|
|
2340
|
+
muxo login [--api-key <key> | --signup] Sign in or create an autonomous workspace (~/.muxo/config.json)
|
|
2341
|
+
muxo logout Remove saved CLI authentication config
|
|
2342
|
+
muxo claim <owner_email> Generate a human owner claim link for this workspace
|
|
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
|
|
2345
|
+
muxo inspect <tool_slug> Inspect tool schema, risk level, and credit cost
|
|
2346
|
+
muxo connections [--sync true|false] List Connected Apps & Cloud Infrastructure provider connections
|
|
2347
|
+
muxo link <provider> [--wait] Start OAuth handoff for a service and optionally wait
|
|
2348
|
+
muxo connect <provider> [--wait] Alias for 'muxo link'
|
|
2349
|
+
muxo wait <provider> [--timeout 15] Poll until a pending OAuth connection handoff becomes active
|
|
2350
|
+
muxo plan <tool_slug> --input '{...}' Create a governed execution plan (accepts natural language too)
|
|
2351
|
+
muxo approve <plan_id> Approve a pending high-risk plan
|
|
2352
|
+
muxo run <plan_id> Execute an approved plan and issue a verifiable audit record
|
|
2353
|
+
muxo exec <tool_slug> --input '{...}' Plan and execute in one step (pass --approve for high-risk)
|
|
2354
|
+
muxo provision <provider> [--name <name>] Provision cloud infrastructure (e.g., turso, neon, supabase, upstash)
|
|
2355
|
+
muxo resources List provisioned Muxo Cloud Infrastructure resources
|
|
2356
|
+
muxo env pull [--out .env.muxo] Decrypt and write provisioned credentials to local .env
|
|
2357
|
+
muxo receipts [<receipt_id>] List or verify execution audit records
|
|
2358
|
+
muxo billing [status|topup|upgrade|checkout] Manage credits, upgrade to Starter, or inspect settlements
|
|
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
|
|
2369
|
+
muxo serve [--port 4310] Start unified HTTP Console, Landing, OAuth Callback & MCP server
|
|
2370
|
+
muxo mcp Run stdio MCP server (19 canonical tools)
|
|
2371
|
+
`);
|
|
2372
|
+
}
|
|
2373
|
+
runCli()
|
|
2374
|
+
.then(() => {
|
|
2375
|
+
process.exit(Number(process.exitCode ?? 0));
|
|
2376
|
+
})
|
|
2377
|
+
.catch((err) => {
|
|
2378
|
+
console.error("Error:", err?.message || err);
|
|
2379
|
+
process.exit(1);
|
|
2380
|
+
});
|