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