@muxoai/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.js +643 -234
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ muxo plan Server-side diff → ordered op list
|
|
|
34
34
|
muxo apply Push manifest, snapshot version
|
|
35
35
|
muxo add <bundle> Append a bundle stanza
|
|
36
36
|
muxo run <workflow> Execute a workflow one-shot
|
|
37
|
+
muxo call <capability> Invoke a capability directly
|
|
37
38
|
muxo status Project, bundles, budget
|
|
38
39
|
muxo keys list|create|rotate|revoke
|
|
39
40
|
muxo credits balance|packs|buy <pack>
|
|
@@ -42,10 +43,21 @@ muxo logs [workflow] Execution history
|
|
|
42
43
|
muxo steps <instanceId> Per-step log for one execution
|
|
43
44
|
muxo login Store your key in the OS keychain
|
|
44
45
|
muxo mcp Stdio MCP proxy for agents
|
|
46
|
+
muxo ui Launch the interactive TUI dashboard
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
Global flags: `--json`, `--quiet`, `--debug`.
|
|
48
50
|
|
|
51
|
+
## Dashboard
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
muxo ui
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A full-screen TUI (from [`@muxoai/tui`](https://www.npmjs.com/package/@muxoai/tui))
|
|
58
|
+
with tabs for overview + budget, capabilities, workflows, runs, keys, and
|
|
59
|
+
credits. Keyboard-first, mouse-friendly.
|
|
60
|
+
|
|
49
61
|
## Agent-first
|
|
50
62
|
|
|
51
63
|
Point any MCP client at `https://api.muxo.ai/mcp` (Bearer auth), or use the
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,148 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import { access, writeFile } from "node:fs/promises";
|
|
7
|
+
import { access, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
8
8
|
import { basename } from "node:path";
|
|
9
|
+
import { validateManifest } from "@muxoai/core";
|
|
10
|
+
|
|
11
|
+
// src/lib/api.ts
|
|
12
|
+
async function request(opts, method, path, body) {
|
|
13
|
+
if (opts.debug) {
|
|
14
|
+
console.error(`muxo: ${method} ${opts.baseUrl}${path}`);
|
|
15
|
+
}
|
|
16
|
+
let res;
|
|
17
|
+
try {
|
|
18
|
+
res = await fetch(`${opts.baseUrl}${path}`, {
|
|
19
|
+
method,
|
|
20
|
+
headers: {
|
|
21
|
+
...opts.key ? { authorization: `Bearer ${opts.key}` } : {},
|
|
22
|
+
"content-type": "application/json"
|
|
23
|
+
},
|
|
24
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: {
|
|
31
|
+
code: "internal",
|
|
32
|
+
message: `network error reaching ${opts.baseUrl}${path}: ${reason}. Check your connection and retry (default endpoint: https://api.muxo.ai/v1)`
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const envelope = await res.json();
|
|
38
|
+
if (!envelope.ok && res.status === 401 && opts.key === void 0) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: {
|
|
42
|
+
code: envelope.error.code,
|
|
43
|
+
message: `${envelope.error.message} \u2014 no key found: run \`muxo keys create\` for a free one, or set MUXO_KEY`
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return envelope;
|
|
48
|
+
} catch {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { code: "internal", message: `muxo returned HTTP ${res.status} with a non-JSON body` }
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function makeApi(opts) {
|
|
56
|
+
return {
|
|
57
|
+
get: (path) => request(opts, "GET", path),
|
|
58
|
+
post: (path, body) => request(opts, "POST", path, body),
|
|
59
|
+
put: (path, body) => request(opts, "PUT", path, body),
|
|
60
|
+
del: (path) => request(opts, "DELETE", path)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/lib/config.ts
|
|
65
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
66
|
+
|
|
67
|
+
// src/lib/keychain.ts
|
|
68
|
+
import { execFile } from "node:child_process";
|
|
69
|
+
import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
70
|
+
import { homedir } from "node:os";
|
|
71
|
+
import { dirname, join } from "node:path";
|
|
72
|
+
import { promisify } from "node:util";
|
|
73
|
+
var run = promisify(execFile);
|
|
74
|
+
var SERVICE = "muxo";
|
|
75
|
+
var ACCOUNT = "muxo-key";
|
|
76
|
+
var KEY_FILE = join(homedir(), ".muxo", "key");
|
|
77
|
+
async function keychainGet() {
|
|
78
|
+
if (process.platform === "darwin") {
|
|
79
|
+
try {
|
|
80
|
+
const { stdout } = await run("security", [
|
|
81
|
+
"find-generic-password",
|
|
82
|
+
"-s",
|
|
83
|
+
SERVICE,
|
|
84
|
+
"-a",
|
|
85
|
+
ACCOUNT,
|
|
86
|
+
"-w"
|
|
87
|
+
]);
|
|
88
|
+
const key = stdout.trim();
|
|
89
|
+
if (key) return key;
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const key = (await readFile(KEY_FILE, "utf8")).trim();
|
|
95
|
+
return key || void 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function keychainSet(key) {
|
|
101
|
+
if (process.platform === "darwin") {
|
|
102
|
+
try {
|
|
103
|
+
await run("security", [
|
|
104
|
+
"add-generic-password",
|
|
105
|
+
"-U",
|
|
106
|
+
"-s",
|
|
107
|
+
SERVICE,
|
|
108
|
+
"-a",
|
|
109
|
+
ACCOUNT,
|
|
110
|
+
"-w",
|
|
111
|
+
key
|
|
112
|
+
]);
|
|
113
|
+
return "keychain";
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await mkdir(dirname(KEY_FILE), { recursive: true });
|
|
118
|
+
await writeFile(KEY_FILE, `${key}
|
|
119
|
+
`, { mode: 384 });
|
|
120
|
+
await chmod(KEY_FILE, 384);
|
|
121
|
+
return "file";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/lib/config.ts
|
|
125
|
+
var DEFAULT_BASE_URL = "https://api.muxo.ai/v1";
|
|
126
|
+
function normalizeBaseUrl(raw) {
|
|
127
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
128
|
+
if (trimmed === "") return DEFAULT_BASE_URL;
|
|
129
|
+
return /\/v1$/.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
130
|
+
}
|
|
131
|
+
function baseUrl() {
|
|
132
|
+
return normalizeBaseUrl(process.env.MUXO_API_BASE || DEFAULT_BASE_URL);
|
|
133
|
+
}
|
|
134
|
+
async function resolveKey() {
|
|
135
|
+
if (process.env.MUXO_KEY) return process.env.MUXO_KEY;
|
|
136
|
+
return keychainGet();
|
|
137
|
+
}
|
|
138
|
+
var MANIFEST_FILES = ["muxo.yaml", "muxo.muxo", ".muxo/muxo.yaml"];
|
|
139
|
+
async function readManifestText() {
|
|
140
|
+
for (const file of MANIFEST_FILES) {
|
|
141
|
+
try {
|
|
142
|
+
return await readFile2(file, "utf8");
|
|
143
|
+
} catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
9
149
|
|
|
10
150
|
// src/lib/templates.ts
|
|
11
151
|
import { stringify } from "yaml";
|
|
@@ -18,6 +158,8 @@ var KNOWN_BUNDLES = [
|
|
|
18
158
|
{ name: "compute", capabilities: ["compute.sandbox", "compute.container"] },
|
|
19
159
|
{ name: "deploy", capabilities: ["deploy.site", "deploy.container", "deploy.edge"] },
|
|
20
160
|
{ name: "domains", capabilities: ["domains.register", "dns.manage"] },
|
|
161
|
+
{ name: "data", capabilities: ["db.write", "db.query"] },
|
|
162
|
+
{ name: "kv", capabilities: ["kv.store"] },
|
|
21
163
|
{ name: "storage", capabilities: ["storage.object", "vector.search"] },
|
|
22
164
|
{ name: "ops", capabilities: ["observability.log", "queue.emit", "email.send"] }
|
|
23
165
|
];
|
|
@@ -30,28 +172,50 @@ var webIntel = {
|
|
|
30
172
|
var llm = {
|
|
31
173
|
bundles: { llm: { capabilities: ["llm.chat", "llm.embed"] } }
|
|
32
174
|
};
|
|
175
|
+
var deploySite = {
|
|
176
|
+
bundles: { deploy: { capabilities: ["deploy.site", "deploy.edge"] } }
|
|
177
|
+
};
|
|
178
|
+
var storage = {
|
|
179
|
+
bundles: { storage: { capabilities: ["storage.object", "vector.search"] } }
|
|
180
|
+
};
|
|
181
|
+
var data = {
|
|
182
|
+
bundles: { data: { capabilities: ["db.write", "db.query"] } }
|
|
183
|
+
};
|
|
184
|
+
var kv = {
|
|
185
|
+
bundles: { kv: { capabilities: ["kv.store"] } }
|
|
186
|
+
};
|
|
187
|
+
var ops = {
|
|
188
|
+
bundles: { ops: { capabilities: ["observability.log", "queue.emit", "email.send"] } }
|
|
189
|
+
};
|
|
190
|
+
var compute = {
|
|
191
|
+
bundles: { compute: { capabilities: ["compute.sandbox", "compute.container"] } }
|
|
192
|
+
};
|
|
193
|
+
var browser = {
|
|
194
|
+
bundles: { browser: { capabilities: ["browser.drive"] } }
|
|
195
|
+
};
|
|
196
|
+
var domains = {
|
|
197
|
+
bundles: { domains: { capabilities: ["domains.register", "dns.manage"] } }
|
|
198
|
+
};
|
|
33
199
|
var priceRadar = {
|
|
34
|
-
bundles: {
|
|
200
|
+
bundles: {
|
|
201
|
+
"web-intel": { capabilities: ["web.search", "web.scrape"] }
|
|
202
|
+
},
|
|
35
203
|
workflows: {
|
|
36
204
|
"daily-prices": {
|
|
37
205
|
on: { schedule: "0 6 * * *" },
|
|
38
206
|
steps: [
|
|
39
|
-
|
|
40
|
-
{
|
|
207
|
+
// web.search resolves to an array of {url, title, markdown}
|
|
208
|
+
{ search: { cap: "web.search", query: "competitor pricing", limit: 5, into: "results" } },
|
|
209
|
+
// fan out: scrape each result page
|
|
41
210
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
into: "prices"
|
|
211
|
+
each: {
|
|
212
|
+
over: "${results}",
|
|
213
|
+
as: "item",
|
|
214
|
+
steps: [{ scrape: { cap: "web.scrape", url: "${item.url}", into: "page" } }]
|
|
47
215
|
}
|
|
48
|
-
}
|
|
49
|
-
{ store: { cap: "db.write", into: "stack.database.main.prices" } }
|
|
216
|
+
}
|
|
50
217
|
]
|
|
51
218
|
}
|
|
52
|
-
},
|
|
53
|
-
stack: {
|
|
54
|
-
database: { main: { provider: "neon", service: "postgres" } }
|
|
55
219
|
}
|
|
56
220
|
};
|
|
57
221
|
function slugify(name) {
|
|
@@ -77,34 +241,63 @@ function matchTemplate(intent) {
|
|
|
77
241
|
const lower = intent.toLowerCase();
|
|
78
242
|
const bundles = {};
|
|
79
243
|
let workflows;
|
|
80
|
-
|
|
81
|
-
let schedule = detectSchedule(intent);
|
|
244
|
+
const schedule = detectSchedule(intent);
|
|
82
245
|
if (/\bprice|pricing\b/.test(lower)) {
|
|
83
246
|
return priceRadar;
|
|
84
247
|
}
|
|
85
|
-
if (
|
|
248
|
+
if (/\bhost(ing)?\b|\bdeploy|\blanding\b|\bwebsite\b|web ?site|static site|\bpublish\b|\bserve\b/.test(lower)) {
|
|
249
|
+
Object.assign(bundles, deploySite.bundles);
|
|
250
|
+
}
|
|
251
|
+
if (/\btrack|monitor|scrape|search|extract|crawl|\bweb\b|news|hacker|\bhn\b/.test(lower)) {
|
|
86
252
|
Object.assign(bundles, webIntel.bundles);
|
|
87
253
|
}
|
|
88
|
-
if (
|
|
254
|
+
if (/\bchat|llm|summar|embed|\bai\b|assistant|generate\b/.test(lower)) {
|
|
89
255
|
Object.assign(bundles, llm.bundles);
|
|
90
256
|
}
|
|
91
|
-
if (
|
|
92
|
-
|
|
257
|
+
if (/\bstore|save|database|postgres|sql|record|table\b/.test(lower)) {
|
|
258
|
+
Object.assign(bundles, data.bundles);
|
|
259
|
+
}
|
|
260
|
+
if (/\bkv|cache|config|settings|feature flag\b/.test(lower)) {
|
|
261
|
+
Object.assign(bundles, kv.bundles);
|
|
262
|
+
}
|
|
263
|
+
if (/\bupload|\bfile|asset|object|bucket\b/.test(lower)) {
|
|
264
|
+
Object.assign(bundles, storage.bundles);
|
|
265
|
+
}
|
|
266
|
+
if (/\bvector|embedding|semantic|similarity|\brag\b|recommend\b/.test(lower)) {
|
|
267
|
+
Object.assign(bundles, storage.bundles);
|
|
268
|
+
}
|
|
269
|
+
if (/\bemail|notif|alert|notify|\bmail\b/.test(lower)) {
|
|
270
|
+
Object.assign(bundles, ops.bundles);
|
|
93
271
|
}
|
|
94
|
-
if (
|
|
272
|
+
if (/\bsandbox|execute|run code|compute|container|docker|\bcode\b/.test(lower)) {
|
|
273
|
+
Object.assign(bundles, compute.bundles);
|
|
274
|
+
}
|
|
275
|
+
if (/\bbrowser|automate|click|login form|\bform\b/.test(lower)) {
|
|
276
|
+
Object.assign(bundles, browser.bundles);
|
|
277
|
+
}
|
|
278
|
+
if (/\bdomain|\bdns\b/.test(lower)) {
|
|
279
|
+
Object.assign(bundles, domains.bundles);
|
|
280
|
+
}
|
|
281
|
+
if (schedule !== void 0 && bundles["web-intel"] !== void 0) {
|
|
95
282
|
const topic = (lower.match(/[a-z][a-z-]{3,}/)?.[0] ?? "watch").slice(0, 20);
|
|
96
283
|
workflows = {
|
|
97
284
|
[`${topic.replace(/-+$/, "")}-watch`]: {
|
|
98
285
|
on: { schedule },
|
|
99
286
|
steps: [
|
|
100
287
|
{ search: { cap: "web.search", query: intent.slice(0, 80), limit: 3, into: "results" } },
|
|
101
|
-
{
|
|
288
|
+
{
|
|
289
|
+
each: {
|
|
290
|
+
over: "${results}",
|
|
291
|
+
as: "item",
|
|
292
|
+
steps: [{ scrape: { cap: "web.scrape", url: "${item.url}", into: "page" } }]
|
|
293
|
+
}
|
|
294
|
+
}
|
|
102
295
|
]
|
|
103
296
|
}
|
|
104
297
|
};
|
|
105
298
|
}
|
|
106
299
|
if (Object.keys(bundles).length === 0) return { bundles: {} };
|
|
107
|
-
return { bundles, workflows
|
|
300
|
+
return { bundles, workflows };
|
|
108
301
|
}
|
|
109
302
|
function renderManifest(project, description, tpl) {
|
|
110
303
|
const doc = {
|
|
@@ -177,102 +370,114 @@ async function exists(path) {
|
|
|
177
370
|
return false;
|
|
178
371
|
}
|
|
179
372
|
}
|
|
373
|
+
function landingPage(title) {
|
|
374
|
+
return `<!doctype html>
|
|
375
|
+
<html lang="en">
|
|
376
|
+
<head>
|
|
377
|
+
<meta charset="utf-8" />
|
|
378
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
379
|
+
<title>${title}</title>
|
|
380
|
+
<style>
|
|
381
|
+
:root { color-scheme: dark; }
|
|
382
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
|
|
383
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
384
|
+
background: #0b0b12; color: #f5f5f7; }
|
|
385
|
+
main { text-align: center; padding: 2rem; }
|
|
386
|
+
h1 { font-size: clamp(2rem, 6vw, 4rem); margin: 0 0 0.5rem; }
|
|
387
|
+
p { color: #a1a1aa; margin: 0; }
|
|
388
|
+
</style>
|
|
389
|
+
</head>
|
|
390
|
+
<body>
|
|
391
|
+
<main>
|
|
392
|
+
<h1>${title}</h1>
|
|
393
|
+
<p>Deployed with muxo.</p>
|
|
394
|
+
</main>
|
|
395
|
+
</body>
|
|
396
|
+
</html>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
async function harnessManifest(intent, project) {
|
|
400
|
+
const base = process.env.MUXO_HARNESS_URL;
|
|
401
|
+
if (base === void 0 || base.trim() === "") return void 0;
|
|
402
|
+
try {
|
|
403
|
+
const res = await fetch(`${base.replace(/\/+$/, "")}/architect`, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers: { "content-type": "application/json" },
|
|
406
|
+
body: JSON.stringify({ intent, project }),
|
|
407
|
+
signal: AbortSignal.timeout(18e4)
|
|
408
|
+
});
|
|
409
|
+
if (!res.ok) return void 0;
|
|
410
|
+
const body = await res.json();
|
|
411
|
+
const yaml = body.data?.yaml;
|
|
412
|
+
if (typeof yaml !== "string" || yaml.trim() === "") return void 0;
|
|
413
|
+
if (!validateManifest(yaml).ok) return void 0;
|
|
414
|
+
return { yaml, source: "harness" };
|
|
415
|
+
} catch {
|
|
416
|
+
return void 0;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function architectManifest(intent, project, opts) {
|
|
420
|
+
const key = await resolveKey();
|
|
421
|
+
if (key === void 0) return void 0;
|
|
422
|
+
const api = makeApi({ baseUrl: baseUrl(), key, debug: opts.debug });
|
|
423
|
+
const res = await api.post("/architect", { intent, project });
|
|
424
|
+
if (!res.ok) return void 0;
|
|
425
|
+
const data2 = res.data;
|
|
426
|
+
if (typeof data2?.yaml !== "string" || data2.yaml.trim() === "") return void 0;
|
|
427
|
+
return { yaml: data2.yaml, source: "architect" };
|
|
428
|
+
}
|
|
180
429
|
async function init(intent, opts) {
|
|
181
430
|
if (await exists("muxo.yaml")) {
|
|
182
431
|
console.error("error: muxo.yaml already exists in this directory");
|
|
183
432
|
return 1;
|
|
184
433
|
}
|
|
185
|
-
const it = intent ?? "";
|
|
186
|
-
const tpl = matchTemplate(it);
|
|
434
|
+
const it = (intent ?? "").trim();
|
|
187
435
|
const project = slugify(basename(process.cwd()));
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
nextSteps(["muxo validate", "muxo plan", "muxo apply", "muxo run <workflow> # or wait for the cron schedule", "muxo logs # watch executions"], opts);
|
|
192
|
-
return 0;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// src/lib/config.ts
|
|
196
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
197
|
-
|
|
198
|
-
// src/lib/keychain.ts
|
|
199
|
-
import { execFile } from "node:child_process";
|
|
200
|
-
import { chmod, mkdir, readFile, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
201
|
-
import { homedir } from "node:os";
|
|
202
|
-
import { dirname, join } from "node:path";
|
|
203
|
-
import { promisify } from "node:util";
|
|
204
|
-
var run = promisify(execFile);
|
|
205
|
-
var SERVICE = "muxo";
|
|
206
|
-
var ACCOUNT = "muxo-key";
|
|
207
|
-
var KEY_FILE = join(homedir(), ".muxo", "key");
|
|
208
|
-
async function keychainGet() {
|
|
209
|
-
if (process.platform === "darwin") {
|
|
436
|
+
let text;
|
|
437
|
+
let source = "template";
|
|
438
|
+
if (it !== "") {
|
|
210
439
|
try {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
ACCOUNT,
|
|
217
|
-
"-w"
|
|
218
|
-
]);
|
|
219
|
-
const key = stdout.trim();
|
|
220
|
-
if (key) return key;
|
|
440
|
+
const designed = await harnessManifest(it, project) ?? await architectManifest(it, project, opts);
|
|
441
|
+
if (designed !== void 0) {
|
|
442
|
+
text = designed.yaml;
|
|
443
|
+
source = designed.source;
|
|
444
|
+
}
|
|
221
445
|
} catch {
|
|
222
446
|
}
|
|
223
447
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
448
|
+
const tpl = matchTemplate(it);
|
|
449
|
+
if (text === void 0) text = renderManifest(project, it, tpl);
|
|
450
|
+
await writeFile2("muxo.yaml", text, "utf8");
|
|
451
|
+
const designedBy = source === "harness" ? " (designed by the muxo harness)" : source === "architect" ? " (designed by the muxo architect)" : "";
|
|
452
|
+
info(`wrote muxo.yaml${designedBy}`, opts);
|
|
453
|
+
const parsed = validateManifest(text);
|
|
454
|
+
const bundles = parsed.ok && parsed.manifest !== void 0 ? Object.keys(parsed.manifest.bundles ?? {}) : Object.keys(tpl.bundles);
|
|
455
|
+
if (bundles.length > 0) {
|
|
456
|
+
info(`bundles: ${bundles.join(", ")}`, opts);
|
|
457
|
+
} else {
|
|
458
|
+
info("no bundles yet \u2014 add one with `muxo add <bundle>`", opts);
|
|
229
459
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"-s",
|
|
238
|
-
SERVICE,
|
|
239
|
-
"-a",
|
|
240
|
-
ACCOUNT,
|
|
241
|
-
"-w",
|
|
242
|
-
key
|
|
243
|
-
]);
|
|
244
|
-
return "keychain";
|
|
245
|
-
} catch {
|
|
246
|
-
}
|
|
460
|
+
const hasDeploy = parsed.ok && parsed.manifest !== void 0 ? Object.values(parsed.manifest.bundles ?? {}).some((b) => b.capabilities.includes("deploy.site")) : tpl.bundles.deploy !== void 0;
|
|
461
|
+
let scaffolded = false;
|
|
462
|
+
if (hasDeploy && !await exists("public/index.html")) {
|
|
463
|
+
await mkdir2("public", { recursive: true });
|
|
464
|
+
await writeFile2("public/index.html", landingPage(project), "utf8");
|
|
465
|
+
info("wrote public/index.html", opts);
|
|
466
|
+
scaffolded = true;
|
|
247
467
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return "file";
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// src/lib/config.ts
|
|
256
|
-
var DEFAULT_BASE_URL = "https://api.muxo.ai/v1";
|
|
257
|
-
var LOCAL_BASE_URL = "http://localhost:8787/v1";
|
|
258
|
-
function baseUrl(local) {
|
|
259
|
-
if (local) return LOCAL_BASE_URL;
|
|
260
|
-
return process.env.MUXO_API_BASE || DEFAULT_BASE_URL;
|
|
261
|
-
}
|
|
262
|
-
async function resolveKey() {
|
|
263
|
-
if (process.env.MUXO_KEY) return process.env.MUXO_KEY;
|
|
264
|
-
return keychainGet();
|
|
265
|
-
}
|
|
266
|
-
var MANIFEST_FILES = ["muxo.yaml", "muxo.muxo", ".muxo/muxo.yaml"];
|
|
267
|
-
async function readManifestText() {
|
|
268
|
-
for (const file of MANIFEST_FILES) {
|
|
269
|
-
try {
|
|
270
|
-
return await readFile2(file, "utf8");
|
|
271
|
-
} catch {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
468
|
+
const steps = ["muxo validate", "muxo plan", "muxo apply"];
|
|
469
|
+
const workflowNames = parsed.ok && parsed.manifest !== void 0 ? Object.keys(parsed.manifest.workflows ?? {}) : Object.keys(tpl.workflows ?? {});
|
|
470
|
+
if (workflowNames.length > 0) {
|
|
471
|
+
steps.push(`muxo run ${workflowNames[0]}`, "muxo logs # watch executions");
|
|
274
472
|
}
|
|
275
|
-
|
|
473
|
+
if (hasDeploy) {
|
|
474
|
+
steps.push("muxo keys create # a key scoped to this manifest");
|
|
475
|
+
steps.push(`muxo deploy ${scaffolded ? "public" : "<dir>"} # upload the site \u2192 live URL`);
|
|
476
|
+
} else if (workflowNames.length === 0) {
|
|
477
|
+
steps.push("muxo status # confirm the applied state");
|
|
478
|
+
}
|
|
479
|
+
nextSteps(steps, opts);
|
|
480
|
+
return 0;
|
|
276
481
|
}
|
|
277
482
|
|
|
278
483
|
// src/commands/validate.ts
|
|
@@ -330,7 +535,7 @@ var manifestSchema = z.object({
|
|
|
330
535
|
}).optional(),
|
|
331
536
|
workflows: z.record(workflow).optional()
|
|
332
537
|
}).strict();
|
|
333
|
-
function
|
|
538
|
+
function validateManifest2(text) {
|
|
334
539
|
let doc;
|
|
335
540
|
try {
|
|
336
541
|
doc = parseYaml(text);
|
|
@@ -381,20 +586,32 @@ function validateManifest(text) {
|
|
|
381
586
|
}
|
|
382
587
|
|
|
383
588
|
// src/commands/validate.ts
|
|
589
|
+
var ZERO_USAGE = { credits: 0, provider: "muxo", latency_ms: 0 };
|
|
384
590
|
async function validate(opts) {
|
|
385
591
|
const text = await readManifestText();
|
|
386
592
|
if (text === void 0) {
|
|
387
|
-
const
|
|
388
|
-
if (opts.json)
|
|
389
|
-
|
|
593
|
+
const message = "no muxo.yaml found in this directory";
|
|
594
|
+
if (opts.json) {
|
|
595
|
+
printJson({ ok: false, error: { code: "not_found", message } });
|
|
596
|
+
} else {
|
|
597
|
+
console.error(`error: ${message}`);
|
|
598
|
+
}
|
|
390
599
|
return 1;
|
|
391
600
|
}
|
|
392
601
|
const issues = [
|
|
393
602
|
...coreValidate(text).errors.map((e) => ({ path: e.path, message: e.message + (e.hint ? ` (${e.hint})` : "") })),
|
|
394
|
-
...
|
|
603
|
+
...validateManifest2(text)
|
|
395
604
|
];
|
|
396
605
|
if (opts.json) {
|
|
397
|
-
|
|
606
|
+
if (issues.length === 0) {
|
|
607
|
+
printJson({ ok: true, data: { valid: true, issues: [] }, usage: ZERO_USAGE });
|
|
608
|
+
} else {
|
|
609
|
+
printJson({
|
|
610
|
+
ok: false,
|
|
611
|
+
error: { code: "invalid_manifest", message: `muxo.yaml is invalid (${issues.length} issue${issues.length === 1 ? "" : "s"})` },
|
|
612
|
+
data: { valid: false, issues }
|
|
613
|
+
});
|
|
614
|
+
}
|
|
398
615
|
} else if (issues.length === 0) {
|
|
399
616
|
if (!opts.quiet) console.log("muxo.yaml is valid");
|
|
400
617
|
} else {
|
|
@@ -405,49 +622,6 @@ async function validate(opts) {
|
|
|
405
622
|
return issues.length > 0 ? 1 : 0;
|
|
406
623
|
}
|
|
407
624
|
|
|
408
|
-
// src/lib/api.ts
|
|
409
|
-
async function request(opts, method, path, body) {
|
|
410
|
-
if (opts.debug) {
|
|
411
|
-
console.error(`muxo: ${method} ${opts.baseUrl}${path}`);
|
|
412
|
-
}
|
|
413
|
-
let res;
|
|
414
|
-
try {
|
|
415
|
-
res = await fetch(`${opts.baseUrl}${path}`, {
|
|
416
|
-
method,
|
|
417
|
-
headers: {
|
|
418
|
-
...opts.key ? { authorization: `Bearer ${opts.key}` } : {},
|
|
419
|
-
"content-type": "application/json"
|
|
420
|
-
},
|
|
421
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
422
|
-
});
|
|
423
|
-
} catch (err) {
|
|
424
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
425
|
-
return {
|
|
426
|
-
ok: false,
|
|
427
|
-
error: {
|
|
428
|
-
code: "internal",
|
|
429
|
-
message: `network error reaching ${opts.baseUrl}${path}: ${reason}. If this is your first run, set MUXO_API_BASE to your runtime URL (e.g. export MUXO_API_BASE=https://api.muxo.ai/v1)`
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
try {
|
|
434
|
-
return await res.json();
|
|
435
|
-
} catch {
|
|
436
|
-
return {
|
|
437
|
-
ok: false,
|
|
438
|
-
error: { code: "internal", message: `muxo returned HTTP ${res.status} with a non-JSON body` }
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
function makeApi(opts) {
|
|
443
|
-
return {
|
|
444
|
-
get: (path) => request(opts, "GET", path),
|
|
445
|
-
post: (path, body) => request(opts, "POST", path, body),
|
|
446
|
-
put: (path, body) => request(opts, "PUT", path, body),
|
|
447
|
-
del: (path) => request(opts, "DELETE", path)
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
625
|
// src/commands/plan.ts
|
|
452
626
|
async function plan(opts) {
|
|
453
627
|
const text = await readManifestText();
|
|
@@ -456,15 +630,15 @@ async function plan(opts) {
|
|
|
456
630
|
return 1;
|
|
457
631
|
}
|
|
458
632
|
const api = makeApi({
|
|
459
|
-
baseUrl: baseUrl(
|
|
633
|
+
baseUrl: baseUrl(),
|
|
460
634
|
key: await resolveKey(),
|
|
461
635
|
debug: opts.debug
|
|
462
636
|
});
|
|
463
637
|
const env = await api.post("/plan", { yaml: text });
|
|
464
|
-
return renderEnvelope(env, !!opts.json, (
|
|
465
|
-
const
|
|
466
|
-
if (Array.isArray(
|
|
467
|
-
if (
|
|
638
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
639
|
+
const ops2 = data2?.ops;
|
|
640
|
+
if (Array.isArray(ops2)) {
|
|
641
|
+
if (ops2.length === 0) {
|
|
468
642
|
console.log("no changes \u2014 manifest matches the deployed state");
|
|
469
643
|
return;
|
|
470
644
|
}
|
|
@@ -477,16 +651,16 @@ async function plan(opts) {
|
|
|
477
651
|
deprovision: "-",
|
|
478
652
|
noop: "!"
|
|
479
653
|
};
|
|
480
|
-
|
|
654
|
+
ops2.forEach((op, i) => {
|
|
481
655
|
const icon = ICONS[op.kind] ?? "\xB7";
|
|
482
656
|
const label = op.detail !== void 0 && op.detail !== "" ? `${op.ref}: ${op.detail}` : op.ref;
|
|
483
657
|
console.log(`${i + 1}. ${icon} ${label}`);
|
|
484
658
|
});
|
|
485
|
-
const removes =
|
|
659
|
+
const removes = ops2.filter((o) => o.kind === "deprovision").length;
|
|
486
660
|
if (removes > 0) console.log(`
|
|
487
661
|
\u26A0 ${removes} deprovision op(s) \u2014 apply will remove these resources`);
|
|
488
662
|
} else {
|
|
489
|
-
console.log(JSON.stringify(
|
|
663
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
490
664
|
}
|
|
491
665
|
});
|
|
492
666
|
}
|
|
@@ -497,16 +671,16 @@ async function apply(opts) {
|
|
|
497
671
|
return 1;
|
|
498
672
|
}
|
|
499
673
|
const api = makeApi({
|
|
500
|
-
baseUrl: baseUrl(
|
|
674
|
+
baseUrl: baseUrl(),
|
|
501
675
|
key: await resolveKey(),
|
|
502
676
|
debug: opts.debug
|
|
503
677
|
});
|
|
504
678
|
const env = await api.put("/manifest", { yaml: text });
|
|
505
|
-
return renderEnvelope(env, !!opts.json, (
|
|
506
|
-
const d =
|
|
679
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
680
|
+
const d = data2;
|
|
507
681
|
const version = d?.version !== void 0 ? ` version ${d.version}` : "";
|
|
508
|
-
const
|
|
509
|
-
console.log(`applied${version}${
|
|
682
|
+
const ops2 = Array.isArray(d?.ops) ? ` (${d.ops.length} ops)` : "";
|
|
683
|
+
console.log(`applied${version}${ops2}`);
|
|
510
684
|
if (Array.isArray(d?.ops)) {
|
|
511
685
|
d.ops.forEach((op, i) => {
|
|
512
686
|
const label = typeof op === "string" ? op : JSON.stringify(op);
|
|
@@ -559,21 +733,7 @@ async function add(bundleName, opts) {
|
|
|
559
733
|
return 0;
|
|
560
734
|
}
|
|
561
735
|
|
|
562
|
-
// src/
|
|
563
|
-
function printSteps(data) {
|
|
564
|
-
const d = data;
|
|
565
|
-
if (d?.execution_id !== void 0) console.log(`execution ${String(d.execution_id)}`);
|
|
566
|
-
if (Array.isArray(d?.steps)) {
|
|
567
|
-
for (const step of d.steps) {
|
|
568
|
-
const name = typeof step?.name === "string" ? step.name : "step";
|
|
569
|
-
const status2 = typeof step?.status === "string" ? step.status : JSON.stringify(step);
|
|
570
|
-
console.log(` ${name}: ${status2}`);
|
|
571
|
-
if (step?.error !== void 0) console.log(` ${JSON.stringify(step.error)}`);
|
|
572
|
-
}
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
console.log(JSON.stringify(data, null, 2));
|
|
576
|
-
}
|
|
736
|
+
// src/lib/params.ts
|
|
577
737
|
function coerceParam(value) {
|
|
578
738
|
if (value === "true") return true;
|
|
579
739
|
if (value === "false") return false;
|
|
@@ -589,9 +749,125 @@ function parseParams(raw) {
|
|
|
589
749
|
}
|
|
590
750
|
return params;
|
|
591
751
|
}
|
|
752
|
+
|
|
753
|
+
// src/commands/call.ts
|
|
754
|
+
async function call(capability, opts) {
|
|
755
|
+
let input = {};
|
|
756
|
+
if (opts.data !== void 0) {
|
|
757
|
+
try {
|
|
758
|
+
const parsed = JSON.parse(opts.data);
|
|
759
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
760
|
+
console.error("error: --data must be a JSON object");
|
|
761
|
+
return 1;
|
|
762
|
+
}
|
|
763
|
+
input = parsed;
|
|
764
|
+
} catch (e) {
|
|
765
|
+
console.error(`error: --data is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
766
|
+
return 1;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
input = { ...input, ...parseParams(opts.param) };
|
|
771
|
+
} catch (e) {
|
|
772
|
+
console.error(`error: ${e instanceof Error ? e.message : String(e)}`);
|
|
773
|
+
return 1;
|
|
774
|
+
}
|
|
775
|
+
const api = makeApi({
|
|
776
|
+
baseUrl: baseUrl(),
|
|
777
|
+
key: await resolveKey(),
|
|
778
|
+
debug: opts.debug
|
|
779
|
+
});
|
|
780
|
+
const env = await api.post(`/capabilities/${capability}`, input);
|
|
781
|
+
return renderEnvelope(env, !!opts.json, (data2) => printJson(data2));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/commands/deploy.ts
|
|
785
|
+
import { access as access2, readdir, readFile as readFile4 } from "node:fs/promises";
|
|
786
|
+
import { join as join2, relative, sep } from "node:path";
|
|
787
|
+
async function exists2(path) {
|
|
788
|
+
try {
|
|
789
|
+
await access2(path);
|
|
790
|
+
return true;
|
|
791
|
+
} catch {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async function collectFiles(dir) {
|
|
796
|
+
const out = {};
|
|
797
|
+
async function walk(current) {
|
|
798
|
+
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
799
|
+
if (entry.name.startsWith(".")) continue;
|
|
800
|
+
const full = join2(current, entry.name);
|
|
801
|
+
if (entry.isDirectory()) {
|
|
802
|
+
await walk(full);
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (!entry.isFile()) continue;
|
|
806
|
+
const rel = relative(dir, full).split(sep).join("/");
|
|
807
|
+
out[`/${rel}`] = await readFile4(full, "utf8");
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
await walk(dir);
|
|
811
|
+
return out;
|
|
812
|
+
}
|
|
813
|
+
async function deploy(dirArg, opts) {
|
|
814
|
+
let dir = dirArg;
|
|
815
|
+
if (dir === void 0) {
|
|
816
|
+
for (const candidate of ["public", "dist", "build", "out"]) {
|
|
817
|
+
if (await exists2(candidate)) {
|
|
818
|
+
dir = candidate;
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (dir === void 0 || !await exists2(dir)) {
|
|
824
|
+
console.error(`error: no site directory found${dir !== void 0 ? ` at "${dir}"` : ""} \u2014 pass one: muxo deploy <dir>`);
|
|
825
|
+
return 1;
|
|
826
|
+
}
|
|
827
|
+
const files = await collectFiles(dir);
|
|
828
|
+
const count = Object.keys(files).length;
|
|
829
|
+
if (count === 0) {
|
|
830
|
+
console.error(`error: "${dir}" is empty`);
|
|
831
|
+
return 1;
|
|
832
|
+
}
|
|
833
|
+
if (!opts.quiet && !opts.json) console.error(`deploying ${count} file(s) from ${dir}\u2026`);
|
|
834
|
+
const api = makeApi({
|
|
835
|
+
baseUrl: baseUrl(),
|
|
836
|
+
key: await resolveKey(),
|
|
837
|
+
debug: opts.debug
|
|
838
|
+
});
|
|
839
|
+
const env = await api.post("/capabilities/deploy.site", {
|
|
840
|
+
files,
|
|
841
|
+
...opts.target !== void 0 ? { target: opts.target } : {}
|
|
842
|
+
});
|
|
843
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
844
|
+
const d = data2;
|
|
845
|
+
if (typeof d?.url === "string" && d.url !== "") {
|
|
846
|
+
console.log(`deployed: ${d.url}`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/commands/run.ts
|
|
854
|
+
function printSteps(data2) {
|
|
855
|
+
const d = data2;
|
|
856
|
+
if (d?.execution_id !== void 0) console.log(`execution ${String(d.execution_id)}`);
|
|
857
|
+
if (Array.isArray(d?.steps)) {
|
|
858
|
+
for (const step of d.steps) {
|
|
859
|
+
const name = typeof step?.name === "string" ? step.name : "step";
|
|
860
|
+
const status2 = typeof step?.status === "string" ? step.status : JSON.stringify(step);
|
|
861
|
+
console.log(` ${name}: ${status2}`);
|
|
862
|
+
if (step?.error !== void 0) console.log(` ${JSON.stringify(step.error)}`);
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
867
|
+
}
|
|
592
868
|
async function run2(workflow2, opts) {
|
|
593
869
|
const api = makeApi({
|
|
594
|
-
baseUrl: baseUrl(
|
|
870
|
+
baseUrl: baseUrl(),
|
|
595
871
|
key: await resolveKey(),
|
|
596
872
|
debug: opts.debug
|
|
597
873
|
});
|
|
@@ -621,13 +897,13 @@ async function run2(workflow2, opts) {
|
|
|
621
897
|
}
|
|
622
898
|
async function status(opts) {
|
|
623
899
|
const api = makeApi({
|
|
624
|
-
baseUrl: baseUrl(
|
|
900
|
+
baseUrl: baseUrl(),
|
|
625
901
|
key: await resolveKey(),
|
|
626
902
|
debug: opts.debug
|
|
627
903
|
});
|
|
628
904
|
const env = await api.get("/status");
|
|
629
|
-
return renderEnvelope(env, !!opts.json, (
|
|
630
|
-
const d =
|
|
905
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
906
|
+
const d = data2;
|
|
631
907
|
if (d && typeof d === "object" && !Array.isArray(d)) {
|
|
632
908
|
if (d.project !== void 0) console.log(`project: ${String(d.project)}`);
|
|
633
909
|
if (d.version !== void 0) console.log(`version: ${String(d.version)}`);
|
|
@@ -640,31 +916,31 @@ async function status(opts) {
|
|
|
640
916
|
if (d.drift !== void 0) console.log(`drift: ${JSON.stringify(d.drift)}`);
|
|
641
917
|
return;
|
|
642
918
|
}
|
|
643
|
-
console.log(JSON.stringify(
|
|
919
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
644
920
|
});
|
|
645
921
|
}
|
|
646
922
|
async function rollback(version, opts) {
|
|
647
923
|
const api = makeApi({
|
|
648
|
-
baseUrl: baseUrl(
|
|
924
|
+
baseUrl: baseUrl(),
|
|
649
925
|
key: await resolveKey(),
|
|
650
926
|
debug: opts.debug
|
|
651
927
|
});
|
|
652
928
|
const body = version === void 0 ? {} : { version: Number(version) };
|
|
653
929
|
const env = await api.post("/rollback", body);
|
|
654
|
-
return renderEnvelope(env, !!opts.json, (
|
|
655
|
-
console.log(`rolled back to ${JSON.stringify(
|
|
930
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
931
|
+
console.log(`rolled back to ${JSON.stringify(data2)}`);
|
|
656
932
|
});
|
|
657
933
|
}
|
|
658
934
|
async function logs(workflow2, opts) {
|
|
659
935
|
const api = makeApi({
|
|
660
|
-
baseUrl: baseUrl(
|
|
936
|
+
baseUrl: baseUrl(),
|
|
661
937
|
key: await resolveKey(),
|
|
662
938
|
debug: opts.debug
|
|
663
939
|
});
|
|
664
940
|
const qs = workflow2 ? `?workflow=${encodeURIComponent(workflow2)}` : "";
|
|
665
941
|
const env = await api.get(`/workflows/${encodeURIComponent(workflow2 ?? "_all")}/executions${qs}`);
|
|
666
|
-
return renderEnvelope(env, !!opts.json, (
|
|
667
|
-
const d =
|
|
942
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
943
|
+
const d = data2;
|
|
668
944
|
if (Array.isArray(d?.executions)) {
|
|
669
945
|
for (const e of d.executions) {
|
|
670
946
|
const when = new Date(e.started_at).toISOString().slice(0, 19).replace("T", " ");
|
|
@@ -674,18 +950,18 @@ async function logs(workflow2, opts) {
|
|
|
674
950
|
}
|
|
675
951
|
return;
|
|
676
952
|
}
|
|
677
|
-
console.log(JSON.stringify(
|
|
953
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
678
954
|
});
|
|
679
955
|
}
|
|
680
956
|
async function stepLogs(instanceId, opts) {
|
|
681
957
|
const api = makeApi({
|
|
682
|
-
baseUrl: baseUrl(
|
|
958
|
+
baseUrl: baseUrl(),
|
|
683
959
|
key: await resolveKey(),
|
|
684
960
|
debug: opts.debug
|
|
685
961
|
});
|
|
686
962
|
const env = await api.get(`/executions/${encodeURIComponent(instanceId)}/steps`);
|
|
687
|
-
return renderEnvelope(env, !!opts.json, (
|
|
688
|
-
const rows =
|
|
963
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
964
|
+
const rows = data2?.steps ?? [];
|
|
689
965
|
for (const s of rows) {
|
|
690
966
|
const mark = s.status === "ok" ? "\u2713" : "\u2717";
|
|
691
967
|
const err = s.error ? ` \u2014 ${s.error}` : "";
|
|
@@ -695,10 +971,25 @@ async function stepLogs(instanceId, opts) {
|
|
|
695
971
|
}
|
|
696
972
|
|
|
697
973
|
// src/commands/keys.ts
|
|
974
|
+
import { parseManifest } from "@muxoai/core";
|
|
975
|
+
async function bootstrapBody() {
|
|
976
|
+
const text = await readManifestText();
|
|
977
|
+
if (text === void 0) return {};
|
|
978
|
+
const parsed = parseManifest(text);
|
|
979
|
+
if (!parsed.ok || parsed.manifest === void 0) return {};
|
|
980
|
+
const capabilities2 = [
|
|
981
|
+
...new Set(Object.values(parsed.manifest.bundles ?? {}).flatMap((b) => b.capabilities ?? []))
|
|
982
|
+
];
|
|
983
|
+
return {
|
|
984
|
+
project: parsed.manifest.project,
|
|
985
|
+
...capabilities2.length > 0 ? { scopes: capabilities2 } : {}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
698
988
|
async function keysCommand(action, id, opts) {
|
|
989
|
+
const existingKey = await resolveKey();
|
|
699
990
|
const api = makeApi({
|
|
700
|
-
baseUrl: baseUrl(
|
|
701
|
-
key:
|
|
991
|
+
baseUrl: baseUrl(),
|
|
992
|
+
key: existingKey,
|
|
702
993
|
debug: opts.debug
|
|
703
994
|
});
|
|
704
995
|
if (action === "revoke" && !id) {
|
|
@@ -722,7 +1013,7 @@ async function keysCommand(action, id, opts) {
|
|
|
722
1013
|
env = await api.get("/keys");
|
|
723
1014
|
break;
|
|
724
1015
|
case "create":
|
|
725
|
-
env = await api.post("/keys");
|
|
1016
|
+
env = await api.post("/keys", existingKey === void 0 ? await bootstrapBody() : void 0);
|
|
726
1017
|
break;
|
|
727
1018
|
case "rotate":
|
|
728
1019
|
env = await api.post(`/keys/${id}/rotate`);
|
|
@@ -731,32 +1022,62 @@ async function keysCommand(action, id, opts) {
|
|
|
731
1022
|
env = await api.del(`/keys/${id}`);
|
|
732
1023
|
break;
|
|
733
1024
|
}
|
|
1025
|
+
let storedIn;
|
|
1026
|
+
if (env.ok && action === "create" && existingKey === void 0) {
|
|
1027
|
+
const data2 = env.data;
|
|
1028
|
+
if (typeof data2?.key === "string") {
|
|
1029
|
+
storedIn = await keychainSet(data2.key);
|
|
1030
|
+
if (!opts.json && storedIn === "file") {
|
|
1031
|
+
console.error("warning: keychain unavailable; key stored in ~/.muxo/key (chmod 600)");
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
734
1035
|
if (env.ok && (action === "create" || action === "rotate") && !opts.json) {
|
|
735
|
-
const
|
|
736
|
-
if (
|
|
737
|
-
const
|
|
738
|
-
console.log(`${action === "rotate" ? "rotated" : "created"} key ${
|
|
739
|
-
console.log(String(
|
|
740
|
-
|
|
1036
|
+
const data2 = env.data;
|
|
1037
|
+
if (data2?.key !== void 0) {
|
|
1038
|
+
const keyId = data2.keyId ?? data2.key_id ?? data2.id;
|
|
1039
|
+
console.log(`${action === "rotate" ? "rotated" : "created"} key ${keyId !== void 0 ? String(keyId) : ""}`);
|
|
1040
|
+
console.log(String(data2.key));
|
|
1041
|
+
if (data2.project_id !== void 0) {
|
|
1042
|
+
console.log(`project ${String(data2.project_id)}${data2.credits !== void 0 ? ` \xB7 ${String(data2.credits)} credits` : ""}`);
|
|
1043
|
+
}
|
|
1044
|
+
console.log(
|
|
1045
|
+
storedIn !== void 0 ? `stored in ${storedIn === "keychain" ? "OS keychain" : "~/.muxo/key"} \u2014 ready to use` : "store it now \u2014 it will not be shown again"
|
|
1046
|
+
);
|
|
741
1047
|
return 0;
|
|
742
1048
|
}
|
|
743
1049
|
}
|
|
744
|
-
return renderEnvelope(env, !!opts.json, (
|
|
745
|
-
if (Array.isArray(
|
|
746
|
-
for (const key of
|
|
1050
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
1051
|
+
if (Array.isArray(data2)) {
|
|
1052
|
+
for (const key of data2) {
|
|
747
1053
|
const k = key;
|
|
748
1054
|
console.log(`${String(k?.id ?? "?")}${k?.revoked ? " (revoked)" : ""}`);
|
|
749
1055
|
}
|
|
750
1056
|
return;
|
|
751
1057
|
}
|
|
752
|
-
printJson(
|
|
1058
|
+
printJson(data2);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/commands/capabilities.ts
|
|
1063
|
+
async function capabilities(opts) {
|
|
1064
|
+
const api = makeApi({ baseUrl: baseUrl(), key: await resolveKey(), debug: opts.debug });
|
|
1065
|
+
const env = await api.get("/capabilities");
|
|
1066
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
1067
|
+
const rows = data2.capabilities ?? [];
|
|
1068
|
+
for (const c of rows) {
|
|
1069
|
+
const providers = c.backends.map((b) => b.configured ? b.provider : `${b.provider}*`).join(", ");
|
|
1070
|
+
console.log(`${c.available ? "ok " : "-- "}${(c.friendly ?? c.name).padEnd(18)} ${c.name.padEnd(22)} ${providers}`);
|
|
1071
|
+
}
|
|
1072
|
+
console.log("\n* = not configured (missing runtime secrets)");
|
|
1073
|
+
console.log("friendly names work anywhere a capability name does (REST, MCP, CLI, SDK)");
|
|
753
1074
|
});
|
|
754
1075
|
}
|
|
755
1076
|
|
|
756
1077
|
// src/commands/credits.ts
|
|
757
1078
|
async function creditsCommand(action, packId, opts) {
|
|
758
1079
|
const api = makeApi({
|
|
759
|
-
baseUrl: baseUrl(
|
|
1080
|
+
baseUrl: baseUrl(),
|
|
760
1081
|
key: await resolveKey(),
|
|
761
1082
|
debug: opts.debug
|
|
762
1083
|
});
|
|
@@ -766,8 +1087,8 @@ async function creditsCommand(action, packId, opts) {
|
|
|
766
1087
|
return 1;
|
|
767
1088
|
}
|
|
768
1089
|
const env2 = await api.post("/credits/checkout", { pack: packId });
|
|
769
|
-
return renderEnvelope(env2, !!opts.json, (
|
|
770
|
-
const d =
|
|
1090
|
+
return renderEnvelope(env2, !!opts.json, (data2) => {
|
|
1091
|
+
const d = data2;
|
|
771
1092
|
if (d?.url !== void 0) {
|
|
772
1093
|
console.log(`checkout: ${d.url}`);
|
|
773
1094
|
if (d.stubbed) console.log("(stub \u2014 set STRIPE_SECRET_KEY on the runtime for real checkout)");
|
|
@@ -775,8 +1096,8 @@ async function creditsCommand(action, packId, opts) {
|
|
|
775
1096
|
});
|
|
776
1097
|
}
|
|
777
1098
|
const env = await api.get("/credits");
|
|
778
|
-
return renderEnvelope(env, !!opts.json, (
|
|
779
|
-
const d =
|
|
1099
|
+
return renderEnvelope(env, !!opts.json, (data2) => {
|
|
1100
|
+
const d = data2;
|
|
780
1101
|
if (action === "packs") {
|
|
781
1102
|
for (const p of d?.packs ?? []) {
|
|
782
1103
|
console.log(`${p.id} ${p.credits} credits $${(p.price_cents / 100).toFixed(2)}`);
|
|
@@ -807,7 +1128,7 @@ function promptHidden(promptText) {
|
|
|
807
1128
|
});
|
|
808
1129
|
return;
|
|
809
1130
|
}
|
|
810
|
-
process.
|
|
1131
|
+
process.stderr.write(promptText);
|
|
811
1132
|
const wasRaw = process.stdin.isRaw ?? false;
|
|
812
1133
|
process.stdin.setRawMode(true);
|
|
813
1134
|
process.stdin.resume();
|
|
@@ -818,7 +1139,7 @@ function promptHidden(promptText) {
|
|
|
818
1139
|
process.stdin.setRawMode(wasRaw);
|
|
819
1140
|
process.stdin.pause();
|
|
820
1141
|
process.stdin.removeListener("data", onData);
|
|
821
|
-
process.
|
|
1142
|
+
process.stderr.write("\n");
|
|
822
1143
|
resolve(buf);
|
|
823
1144
|
return;
|
|
824
1145
|
}
|
|
@@ -828,7 +1149,7 @@ function promptHidden(promptText) {
|
|
|
828
1149
|
process.stdin.setRawMode(wasRaw);
|
|
829
1150
|
process.stdin.pause();
|
|
830
1151
|
process.stdin.removeListener("data", onData);
|
|
831
|
-
process.
|
|
1152
|
+
process.stderr.write("\n");
|
|
832
1153
|
process.exit(130);
|
|
833
1154
|
} else {
|
|
834
1155
|
buf += ch;
|
|
@@ -841,14 +1162,41 @@ function promptHidden(promptText) {
|
|
|
841
1162
|
async function login(opts) {
|
|
842
1163
|
const key = await promptHidden("Paste your muxo key: ");
|
|
843
1164
|
if (!key) {
|
|
844
|
-
|
|
1165
|
+
if (opts.json) {
|
|
1166
|
+
printJson({ ok: false, error: { code: "invalid_input", message: "no key provided" } });
|
|
1167
|
+
} else {
|
|
1168
|
+
console.error("error: no key provided");
|
|
1169
|
+
}
|
|
845
1170
|
return 1;
|
|
846
1171
|
}
|
|
1172
|
+
const api = makeApi({ baseUrl: baseUrl(), key, debug: opts.debug });
|
|
1173
|
+
const check = await api.get("/status");
|
|
1174
|
+
if (!check.ok && check.error.code === "forbidden") {
|
|
1175
|
+
if (opts.json) {
|
|
1176
|
+
printJson(check);
|
|
1177
|
+
} else {
|
|
1178
|
+
console.error(`error: ${check.error.message}`);
|
|
1179
|
+
}
|
|
1180
|
+
return 2;
|
|
1181
|
+
}
|
|
847
1182
|
const where = await keychainSet(key);
|
|
848
1183
|
if (where === "file") {
|
|
849
1184
|
console.error("warning: keychain unavailable; key stored in ~/.muxo/key (chmod 600)");
|
|
850
1185
|
}
|
|
1186
|
+
if (opts.json) {
|
|
1187
|
+
printJson({
|
|
1188
|
+
ok: true,
|
|
1189
|
+
data: { stored: where, validated: check.ok },
|
|
1190
|
+
usage: { credits: 0, provider: "muxo", latency_ms: 0 }
|
|
1191
|
+
});
|
|
1192
|
+
return 0;
|
|
1193
|
+
}
|
|
851
1194
|
info(where === "keychain" ? "key stored in OS keychain" : "key stored in ~/.muxo/key", opts);
|
|
1195
|
+
if (!check.ok) {
|
|
1196
|
+
console.error(`warning: could not verify key against the runtime (${check.error.message}) \u2014 stored anyway`);
|
|
1197
|
+
} else {
|
|
1198
|
+
info("key verified against the runtime", opts);
|
|
1199
|
+
}
|
|
852
1200
|
nextSteps(["muxo status"], opts);
|
|
853
1201
|
return 0;
|
|
854
1202
|
}
|
|
@@ -867,13 +1215,29 @@ var STATIC_TOOLS = [
|
|
|
867
1215
|
{ name: "llm_tts", description: "Text to speech. Inputs: text (required), voice?." },
|
|
868
1216
|
{ name: "compute_sandbox", description: "Run code in a sandbox. Inputs: code (required), runtime?, timeout?." },
|
|
869
1217
|
{ name: "kv_store", description: "Key-value store. Inputs: key (required), value (to write) or get (to read)." },
|
|
1218
|
+
{ name: "db_write", description: "Write rows to a stack database. Inputs: into (stack address, required), rows (required)." },
|
|
1219
|
+
{ name: "db_query", description: "Query a stack database. Inputs: sql (required)." },
|
|
1220
|
+
{ name: "dns_manage", description: "Manage DNS records for a domain. Inputs: domain (required), records." },
|
|
870
1221
|
{ name: "storage_object", description: "Object storage. Inputs: key (required), file (write) or get (read)." },
|
|
871
1222
|
{ name: "vector_search", description: "Vector search. Inputs: query (required), k?." },
|
|
872
1223
|
{ name: "email_send", description: "Send an email. Inputs: to, subject, body (all required)." },
|
|
873
1224
|
{ name: "observability_log", description: "Log an event. Inputs: level (required), message (required)." },
|
|
874
|
-
{ name: "queue_emit", description: "Emit an event to a queue. Inputs: topic (required), payload (required)." }
|
|
1225
|
+
{ name: "queue_emit", description: "Emit an event to a queue. Inputs: topic (required), payload (required)." },
|
|
1226
|
+
{ name: "llm_image", description: "Generate an image from a prompt. Inputs: prompt (required), size?, model?." },
|
|
1227
|
+
{ name: "llm_stt", description: "Transcribe audio to text. Inputs: audio_url or audio_base64 (required), model?." },
|
|
1228
|
+
{ name: "sms_send", description: "Send an SMS. Inputs: to (required), body (required), from?." },
|
|
1229
|
+
{ name: "analytics_track", description: "Track an analytics event. Inputs: event (required), distinct_id?, properties?." },
|
|
1230
|
+
{ name: "appsearch_query", description: "Full-text search an app index. Inputs: query (required), index?, limit?." },
|
|
1231
|
+
{ name: "memory_store", description: "Store a memory. Inputs: content (required), metadata?." },
|
|
1232
|
+
{ name: "memory_search", description: "Search stored memories. Inputs: query (required), k?." },
|
|
1233
|
+
{ name: "featureflags_evaluate", description: "Evaluate a feature flag. Inputs: flag (required), context?." },
|
|
1234
|
+
{ name: "video_generate", description: "Generate a video from a prompt. Inputs: prompt (required), avatar_id?, voice_id?." },
|
|
1235
|
+
{ name: "auth_users", description: "Manage users. Inputs: action (create|get|list|delete), id?, email?, password?, metadata?." },
|
|
1236
|
+
{ name: "payments_charge", description: "Accept a payment from an agent (Stripe PaymentIntents / MPP). Inputs: amount (required), currency?, payment_method_types?, payment_method?, confirm?, metadata?." },
|
|
1237
|
+
{ name: "payments_refund", description: "Refund a payment. Inputs: payment_intent (required), amount?, reason?." }
|
|
875
1238
|
];
|
|
876
1239
|
function excluded(tool) {
|
|
1240
|
+
if (process.env.MUXO_MCP_EXPOSE_ALL === "true") return false;
|
|
877
1241
|
return tool.startsWith("deploy_") || tool === "domains_register" || tool === "compute_container";
|
|
878
1242
|
}
|
|
879
1243
|
function toToolName(capability) {
|
|
@@ -889,10 +1253,10 @@ async function fetchTools(baseUrl2, key) {
|
|
|
889
1253
|
const api = makeApi({ baseUrl: baseUrl2, key });
|
|
890
1254
|
const env = await api.get("/status");
|
|
891
1255
|
if (!env.ok) return STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
|
|
892
|
-
const
|
|
893
|
-
if (Array.isArray(
|
|
1256
|
+
const data2 = env.data;
|
|
1257
|
+
if (Array.isArray(data2?.tools)) {
|
|
894
1258
|
const tools = [];
|
|
895
|
-
for (const t of
|
|
1259
|
+
for (const t of data2.tools) {
|
|
896
1260
|
if (typeof t === "string") {
|
|
897
1261
|
tools.push(toolShape(toToolName(t), ""));
|
|
898
1262
|
continue;
|
|
@@ -908,15 +1272,15 @@ async function fetchTools(baseUrl2, key) {
|
|
|
908
1272
|
const filtered = tools.filter((t) => !excluded(t.name));
|
|
909
1273
|
return filtered.length > 0 ? filtered : STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
|
|
910
1274
|
}
|
|
911
|
-
if (Array.isArray(
|
|
912
|
-
const tools =
|
|
1275
|
+
if (Array.isArray(data2?.capabilities)) {
|
|
1276
|
+
const tools = data2.capabilities.filter((c) => typeof c === "string").map((c) => toolShape(toToolName(c), "")).filter((t) => !excluded(t.name));
|
|
913
1277
|
return tools.length > 0 ? tools : STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
|
|
914
1278
|
}
|
|
915
1279
|
return STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
|
|
916
1280
|
}
|
|
917
|
-
function successResult(
|
|
1281
|
+
function successResult(data2, usage) {
|
|
918
1282
|
return {
|
|
919
|
-
content: [{ type: "text", text: JSON.stringify({ data, usage }) }],
|
|
1283
|
+
content: [{ type: "text", text: JSON.stringify({ data: data2, usage }) }],
|
|
920
1284
|
isError: false
|
|
921
1285
|
};
|
|
922
1286
|
}
|
|
@@ -931,7 +1295,7 @@ function errorResult(error) {
|
|
|
931
1295
|
};
|
|
932
1296
|
}
|
|
933
1297
|
async function mcp(opts) {
|
|
934
|
-
const baseUrl2 =
|
|
1298
|
+
const baseUrl2 = process.env.MUXO_API_BASE || DEFAULT_BASE_URL;
|
|
935
1299
|
const key = await resolveKey();
|
|
936
1300
|
const api = makeApi({ baseUrl: baseUrl2, key });
|
|
937
1301
|
let tools = null;
|
|
@@ -953,7 +1317,7 @@ async function mcp(opts) {
|
|
|
953
1317
|
reply(id, {
|
|
954
1318
|
protocolVersion: version,
|
|
955
1319
|
capabilities: { tools: { listChanged: false } },
|
|
956
|
-
serverInfo: { name: "muxo", version: "0.1.
|
|
1320
|
+
serverInfo: { name: "muxo", version: "0.1.1" }
|
|
957
1321
|
});
|
|
958
1322
|
return;
|
|
959
1323
|
}
|
|
@@ -1001,18 +1365,41 @@ async function mcp(opts) {
|
|
|
1001
1365
|
return 0;
|
|
1002
1366
|
}
|
|
1003
1367
|
|
|
1368
|
+
// src/commands/ui.ts
|
|
1369
|
+
async function ui(opts) {
|
|
1370
|
+
if (process.stdout.isTTY !== true) {
|
|
1371
|
+
console.error("error: `muxo ui` needs an interactive terminal (TTY)");
|
|
1372
|
+
return 1;
|
|
1373
|
+
}
|
|
1374
|
+
let mod;
|
|
1375
|
+
try {
|
|
1376
|
+
mod = await import("@muxoai/tui");
|
|
1377
|
+
} catch (err) {
|
|
1378
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1379
|
+
if (/Cannot find (module|package)|ERR_MODULE_NOT_FOUND/.test(message)) {
|
|
1380
|
+
console.error("error: @muxoai/tui is not installed \u2014 run `npm i -g @muxoai/tui`");
|
|
1381
|
+
return 1;
|
|
1382
|
+
}
|
|
1383
|
+
throw err;
|
|
1384
|
+
}
|
|
1385
|
+
await mod.run({ baseUrl: baseUrl(), key: await resolveKey(), debug: opts.debug });
|
|
1386
|
+
return 0;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1004
1389
|
// src/index.ts
|
|
1005
1390
|
function flags(cmd) {
|
|
1006
1391
|
cmd.option("--json", "structured JSON output").option("--quiet", "suppress non-essential output").option("--debug", "log requests to stderr");
|
|
1007
1392
|
}
|
|
1008
1393
|
async function main() {
|
|
1009
1394
|
const program = new Command();
|
|
1010
|
-
program.name("muxo").description("Muxo CLI \u2014 one key for your whole stack").version("0.1.
|
|
1395
|
+
program.name("muxo").description("Muxo CLI \u2014 one key for your whole stack").version("0.1.1").addHelpText("after", `
|
|
1011
1396
|
Examples:
|
|
1012
1397
|
muxo init "scrape hacker news every hour and store titles"
|
|
1013
1398
|
muxo validate && muxo plan && muxo apply
|
|
1014
1399
|
muxo run hn-scraper --param max_items=3
|
|
1400
|
+
muxo call web.search --data '{"query":"agent infra","limit":3}'
|
|
1015
1401
|
muxo logs && muxo steps <instanceId>
|
|
1402
|
+
muxo ui # interactive dashboard
|
|
1016
1403
|
|
|
1017
1404
|
Auth: export MUXO_KEY=mk_... Endpoint: export MUXO_API_BASE=https://api.muxo.ai/v1
|
|
1018
1405
|
Docs: https://api.muxo.ai/docs/quickstart`);
|
|
@@ -1047,11 +1434,29 @@ Docs: https://api.muxo.ai/docs/quickstart`);
|
|
|
1047
1434
|
runCmd.description("Execute a workflow one-shot").action(async (workflow2, opts) => {
|
|
1048
1435
|
process.exitCode = await run2(workflow2, opts);
|
|
1049
1436
|
});
|
|
1437
|
+
const callCmd = program.command("call <capability>");
|
|
1438
|
+
flags(callCmd);
|
|
1439
|
+
callCmd.option("--data <json>", `JSON object of capability inputs, e.g. --data '{"query":"x"}'`).option("--param <key=value>", "input field, e.g. --param limit=5 (repeatable)", (v, prev) => [...prev, v], []);
|
|
1440
|
+
callCmd.description("Invoke a capability directly (web.search, deploy.site, llm.chat, \u2026)").action(
|
|
1441
|
+
async (capability, opts) => {
|
|
1442
|
+
process.exitCode = await call(capability, opts);
|
|
1443
|
+
}
|
|
1444
|
+
);
|
|
1445
|
+
const deployCmd = program.command("deploy [dir]");
|
|
1446
|
+
flags(deployCmd);
|
|
1447
|
+
deployCmd.option("--target <target>", "deployment target, e.g. production").description("Deploy a local directory as a static site (default: public/ or dist/)").action(async (dir, opts) => {
|
|
1448
|
+
process.exitCode = await deploy(dir, opts);
|
|
1449
|
+
});
|
|
1050
1450
|
const statusCmd = program.command("status");
|
|
1051
1451
|
flags(statusCmd);
|
|
1052
1452
|
statusCmd.description("Project health, bundles, budget state").action(async (opts) => {
|
|
1053
1453
|
process.exitCode = await status(opts);
|
|
1054
1454
|
});
|
|
1455
|
+
const capabilitiesCmd = program.command("capabilities");
|
|
1456
|
+
flags(capabilitiesCmd);
|
|
1457
|
+
capabilitiesCmd.description("Show which capabilities this deployment can serve").action(async (opts) => {
|
|
1458
|
+
process.exitCode = await capabilities(opts);
|
|
1459
|
+
});
|
|
1055
1460
|
const keys = program.command("keys");
|
|
1056
1461
|
flags(keys);
|
|
1057
1462
|
keys.description("Manage project keys");
|
|
@@ -1110,15 +1515,19 @@ Docs: https://api.muxo.ai/docs/quickstart`);
|
|
|
1110
1515
|
});
|
|
1111
1516
|
const loginCmd = program.command("login");
|
|
1112
1517
|
flags(loginCmd);
|
|
1113
|
-
loginCmd.description("Store your muxo key in the OS keychain").action(async (opts) => {
|
|
1518
|
+
loginCmd.description("Store your muxo key in the OS keychain (validated against the runtime)").action(async (opts) => {
|
|
1114
1519
|
process.exitCode = await login(opts);
|
|
1115
1520
|
});
|
|
1116
1521
|
const mcpCmd = program.command("mcp");
|
|
1117
1522
|
flags(mcpCmd);
|
|
1118
|
-
mcpCmd.
|
|
1119
|
-
mcpCmd.description("Stdio MCP proxy wrapping the muxo API (SPEC \xA712)").action(async (opts) => {
|
|
1523
|
+
mcpCmd.description("Stdio MCP proxy wrapping the muxo API").action(async (opts) => {
|
|
1120
1524
|
process.exitCode = await mcp(opts);
|
|
1121
1525
|
});
|
|
1526
|
+
const uiCmd = program.command("ui");
|
|
1527
|
+
flags(uiCmd);
|
|
1528
|
+
uiCmd.description("Launch the interactive TUI dashboard (status, capabilities, workflows, runs, keys, credits)").action(async (opts) => {
|
|
1529
|
+
process.exitCode = await ui(opts);
|
|
1530
|
+
});
|
|
1122
1531
|
await program.parseAsync(process.argv);
|
|
1123
1532
|
}
|
|
1124
1533
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muxoai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Muxo CLI: one key for your whole stack — scaffold, validate, apply, run, and monitor agent workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"prepublishOnly": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@muxoai/core": "^0.1.
|
|
42
|
+
"@muxoai/core": "^0.1.2",
|
|
43
|
+
"@muxoai/tui": "^0.1.0",
|
|
43
44
|
"commander": "^12.0.0",
|
|
44
45
|
"yaml": "^2.4.0",
|
|
45
46
|
"zod": "^3.23.0"
|