@muxoai/cli 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +580 -233
  3. package/package.json +2 -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>
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: { "web-intel": { capabilities: ["web.search", "web.scrape", "web.extract"] } },
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
- { search: { cap: "web.search", query: "competitor pricing", into: "results" } },
40
- { scrape: { cap: "web.scrape", urls: "${results.urls}", into: "pages" } },
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
- extract: {
43
- cap: "web.extract",
44
- from: "pages",
45
- schema: { product: "str", price: "num" },
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
- let stack;
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 (/track|monitor|scrape|search|extract|crawl|web|news|hacker|hn/.test(lower)) {
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 (/chat|llm|summar|embed|\bai\b/.test(lower)) {
254
+ if (/\bchat|llm|summar|embed|\bai\b|assistant|generate\b/.test(lower)) {
89
255
  Object.assign(bundles, llm.bundles);
90
256
  }
91
- if (/store|database|db|postgres|save\b/.test(lower)) {
92
- stack = { database: { main: { provider: "neon", service: "postgres" } } };
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);
93
268
  }
94
- if (schedule !== void 0 && Object.keys(bundles).length > 0) {
269
+ if (/\bemail|notif|alert|notify|\bmail\b/.test(lower)) {
270
+ Object.assign(bundles, ops.bundles);
271
+ }
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
- { log: { cap: "observability.log", level: "info", message: "found ${results.count} results" } }
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, stack };
300
+ return { bundles, workflows };
108
301
  }
109
302
  function renderManifest(project, description, tpl) {
110
303
  const doc = {
@@ -177,102 +370,93 @@ 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 architectManifest(intent, project, opts) {
400
+ const key = await resolveKey();
401
+ if (key === void 0) return void 0;
402
+ const api = makeApi({ baseUrl: baseUrl(), key, debug: opts.debug });
403
+ const res = await api.post("/architect", { intent, project });
404
+ if (!res.ok) return void 0;
405
+ const data2 = res.data;
406
+ if (typeof data2?.yaml !== "string" || data2.yaml.trim() === "") return void 0;
407
+ return { yaml: data2.yaml, source: "architect" };
408
+ }
180
409
  async function init(intent, opts) {
181
410
  if (await exists("muxo.yaml")) {
182
411
  console.error("error: muxo.yaml already exists in this directory");
183
412
  return 1;
184
413
  }
185
- const it = intent ?? "";
186
- const tpl = matchTemplate(it);
414
+ const it = (intent ?? "").trim();
187
415
  const project = slugify(basename(process.cwd()));
188
- const text = renderManifest(project, it, tpl);
189
- await writeFile("muxo.yaml", text, "utf8");
190
- info("wrote muxo.yaml", opts);
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") {
416
+ let text;
417
+ let source = "template";
418
+ if (it !== "") {
210
419
  try {
211
- const { stdout } = await run("security", [
212
- "find-generic-password",
213
- "-s",
214
- SERVICE,
215
- "-a",
216
- ACCOUNT,
217
- "-w"
218
- ]);
219
- const key = stdout.trim();
220
- if (key) return key;
420
+ const designed = await architectManifest(it, project, opts);
421
+ if (designed !== void 0) {
422
+ text = designed.yaml;
423
+ source = designed.source;
424
+ }
221
425
  } catch {
222
426
  }
223
427
  }
224
- try {
225
- const key = (await readFile(KEY_FILE, "utf8")).trim();
226
- return key || void 0;
227
- } catch {
228
- return void 0;
428
+ const tpl = matchTemplate(it);
429
+ if (text === void 0) text = renderManifest(project, it, tpl);
430
+ await writeFile2("muxo.yaml", text, "utf8");
431
+ info(`wrote muxo.yaml${source === "architect" ? " (designed by the muxo architect)" : ""}`, opts);
432
+ const parsed = validateManifest(text);
433
+ const bundles = parsed.ok && parsed.manifest !== void 0 ? Object.keys(parsed.manifest.bundles ?? {}) : Object.keys(tpl.bundles);
434
+ if (bundles.length > 0) {
435
+ info(`bundles: ${bundles.join(", ")}`, opts);
436
+ } else {
437
+ info("no bundles yet \u2014 add one with `muxo add <bundle>`", opts);
229
438
  }
230
- }
231
- async function keychainSet(key) {
232
- if (process.platform === "darwin") {
233
- try {
234
- await run("security", [
235
- "add-generic-password",
236
- "-U",
237
- "-s",
238
- SERVICE,
239
- "-a",
240
- ACCOUNT,
241
- "-w",
242
- key
243
- ]);
244
- return "keychain";
245
- } catch {
246
- }
439
+ 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;
440
+ let scaffolded = false;
441
+ if (hasDeploy && !await exists("public/index.html")) {
442
+ await mkdir2("public", { recursive: true });
443
+ await writeFile2("public/index.html", landingPage(project), "utf8");
444
+ info("wrote public/index.html", opts);
445
+ scaffolded = true;
247
446
  }
248
- await mkdir(dirname(KEY_FILE), { recursive: true });
249
- await writeFile2(KEY_FILE, `${key}
250
- `, { mode: 384 });
251
- await chmod(KEY_FILE, 384);
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
- }
447
+ const steps = ["muxo validate", "muxo plan", "muxo apply"];
448
+ const workflowNames = parsed.ok && parsed.manifest !== void 0 ? Object.keys(parsed.manifest.workflows ?? {}) : Object.keys(tpl.workflows ?? {});
449
+ if (workflowNames.length > 0) {
450
+ steps.push(`muxo run ${workflowNames[0]}`, "muxo logs # watch executions");
274
451
  }
275
- return void 0;
452
+ if (hasDeploy) {
453
+ steps.push("muxo keys create # a key scoped to this manifest");
454
+ steps.push(`muxo deploy ${scaffolded ? "public" : "<dir>"} # upload the site \u2192 live URL`);
455
+ } else if (workflowNames.length === 0) {
456
+ steps.push("muxo status # confirm the applied state");
457
+ }
458
+ nextSteps(steps, opts);
459
+ return 0;
276
460
  }
277
461
 
278
462
  // src/commands/validate.ts
@@ -330,7 +514,7 @@ var manifestSchema = z.object({
330
514
  }).optional(),
331
515
  workflows: z.record(workflow).optional()
332
516
  }).strict();
333
- function validateManifest(text) {
517
+ function validateManifest2(text) {
334
518
  let doc;
335
519
  try {
336
520
  doc = parseYaml(text);
@@ -381,20 +565,32 @@ function validateManifest(text) {
381
565
  }
382
566
 
383
567
  // src/commands/validate.ts
568
+ var ZERO_USAGE = { credits: 0, provider: "muxo", latency_ms: 0 };
384
569
  async function validate(opts) {
385
570
  const text = await readManifestText();
386
571
  if (text === void 0) {
387
- const err = [{ path: "", message: "no muxo.yaml found in this directory" }];
388
- if (opts.json) printJson(err);
389
- else console.error(`error: ${err[0].message}`);
572
+ const message = "no muxo.yaml found in this directory";
573
+ if (opts.json) {
574
+ printJson({ ok: false, error: { code: "not_found", message } });
575
+ } else {
576
+ console.error(`error: ${message}`);
577
+ }
390
578
  return 1;
391
579
  }
392
580
  const issues = [
393
581
  ...coreValidate(text).errors.map((e) => ({ path: e.path, message: e.message + (e.hint ? ` (${e.hint})` : "") })),
394
- ...validateManifest(text)
582
+ ...validateManifest2(text)
395
583
  ];
396
584
  if (opts.json) {
397
- printJson(issues);
585
+ if (issues.length === 0) {
586
+ printJson({ ok: true, data: { valid: true, issues: [] }, usage: ZERO_USAGE });
587
+ } else {
588
+ printJson({
589
+ ok: false,
590
+ error: { code: "invalid_manifest", message: `muxo.yaml is invalid (${issues.length} issue${issues.length === 1 ? "" : "s"})` },
591
+ data: { valid: false, issues }
592
+ });
593
+ }
398
594
  } else if (issues.length === 0) {
399
595
  if (!opts.quiet) console.log("muxo.yaml is valid");
400
596
  } else {
@@ -405,49 +601,6 @@ async function validate(opts) {
405
601
  return issues.length > 0 ? 1 : 0;
406
602
  }
407
603
 
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
604
  // src/commands/plan.ts
452
605
  async function plan(opts) {
453
606
  const text = await readManifestText();
@@ -456,15 +609,15 @@ async function plan(opts) {
456
609
  return 1;
457
610
  }
458
611
  const api = makeApi({
459
- baseUrl: baseUrl(opts.local),
612
+ baseUrl: baseUrl(),
460
613
  key: await resolveKey(),
461
614
  debug: opts.debug
462
615
  });
463
616
  const env = await api.post("/plan", { yaml: text });
464
- return renderEnvelope(env, !!opts.json, (data) => {
465
- const ops = data?.ops;
466
- if (Array.isArray(ops)) {
467
- if (ops.length === 0) {
617
+ return renderEnvelope(env, !!opts.json, (data2) => {
618
+ const ops2 = data2?.ops;
619
+ if (Array.isArray(ops2)) {
620
+ if (ops2.length === 0) {
468
621
  console.log("no changes \u2014 manifest matches the deployed state");
469
622
  return;
470
623
  }
@@ -477,16 +630,16 @@ async function plan(opts) {
477
630
  deprovision: "-",
478
631
  noop: "!"
479
632
  };
480
- ops.forEach((op, i) => {
633
+ ops2.forEach((op, i) => {
481
634
  const icon = ICONS[op.kind] ?? "\xB7";
482
635
  const label = op.detail !== void 0 && op.detail !== "" ? `${op.ref}: ${op.detail}` : op.ref;
483
636
  console.log(`${i + 1}. ${icon} ${label}`);
484
637
  });
485
- const removes = ops.filter((o) => o.kind === "deprovision").length;
638
+ const removes = ops2.filter((o) => o.kind === "deprovision").length;
486
639
  if (removes > 0) console.log(`
487
640
  \u26A0 ${removes} deprovision op(s) \u2014 apply will remove these resources`);
488
641
  } else {
489
- console.log(JSON.stringify(data, null, 2));
642
+ console.log(JSON.stringify(data2, null, 2));
490
643
  }
491
644
  });
492
645
  }
@@ -497,16 +650,16 @@ async function apply(opts) {
497
650
  return 1;
498
651
  }
499
652
  const api = makeApi({
500
- baseUrl: baseUrl(opts.local),
653
+ baseUrl: baseUrl(),
501
654
  key: await resolveKey(),
502
655
  debug: opts.debug
503
656
  });
504
657
  const env = await api.put("/manifest", { yaml: text });
505
- return renderEnvelope(env, !!opts.json, (data) => {
506
- const d = data;
658
+ return renderEnvelope(env, !!opts.json, (data2) => {
659
+ const d = data2;
507
660
  const version = d?.version !== void 0 ? ` version ${d.version}` : "";
508
- const ops = Array.isArray(d?.ops) ? ` (${d.ops.length} ops)` : "";
509
- console.log(`applied${version}${ops}`);
661
+ const ops2 = Array.isArray(d?.ops) ? ` (${d.ops.length} ops)` : "";
662
+ console.log(`applied${version}${ops2}`);
510
663
  if (Array.isArray(d?.ops)) {
511
664
  d.ops.forEach((op, i) => {
512
665
  const label = typeof op === "string" ? op : JSON.stringify(op);
@@ -559,21 +712,7 @@ async function add(bundleName, opts) {
559
712
  return 0;
560
713
  }
561
714
 
562
- // src/commands/run.ts
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
- }
715
+ // src/lib/params.ts
577
716
  function coerceParam(value) {
578
717
  if (value === "true") return true;
579
718
  if (value === "false") return false;
@@ -589,9 +728,125 @@ function parseParams(raw) {
589
728
  }
590
729
  return params;
591
730
  }
731
+
732
+ // src/commands/call.ts
733
+ async function call(capability, opts) {
734
+ let input = {};
735
+ if (opts.data !== void 0) {
736
+ try {
737
+ const parsed = JSON.parse(opts.data);
738
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
739
+ console.error("error: --data must be a JSON object");
740
+ return 1;
741
+ }
742
+ input = parsed;
743
+ } catch (e) {
744
+ console.error(`error: --data is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
745
+ return 1;
746
+ }
747
+ }
748
+ try {
749
+ input = { ...input, ...parseParams(opts.param) };
750
+ } catch (e) {
751
+ console.error(`error: ${e instanceof Error ? e.message : String(e)}`);
752
+ return 1;
753
+ }
754
+ const api = makeApi({
755
+ baseUrl: baseUrl(),
756
+ key: await resolveKey(),
757
+ debug: opts.debug
758
+ });
759
+ const env = await api.post(`/capabilities/${capability}`, input);
760
+ return renderEnvelope(env, !!opts.json, (data2) => printJson(data2));
761
+ }
762
+
763
+ // src/commands/deploy.ts
764
+ import { access as access2, readdir, readFile as readFile4 } from "node:fs/promises";
765
+ import { join as join2, relative, sep } from "node:path";
766
+ async function exists2(path) {
767
+ try {
768
+ await access2(path);
769
+ return true;
770
+ } catch {
771
+ return false;
772
+ }
773
+ }
774
+ async function collectFiles(dir) {
775
+ const out = {};
776
+ async function walk(current) {
777
+ for (const entry of await readdir(current, { withFileTypes: true })) {
778
+ if (entry.name.startsWith(".")) continue;
779
+ const full = join2(current, entry.name);
780
+ if (entry.isDirectory()) {
781
+ await walk(full);
782
+ continue;
783
+ }
784
+ if (!entry.isFile()) continue;
785
+ const rel = relative(dir, full).split(sep).join("/");
786
+ out[`/${rel}`] = await readFile4(full, "utf8");
787
+ }
788
+ }
789
+ await walk(dir);
790
+ return out;
791
+ }
792
+ async function deploy(dirArg, opts) {
793
+ let dir = dirArg;
794
+ if (dir === void 0) {
795
+ for (const candidate of ["public", "dist", "build", "out"]) {
796
+ if (await exists2(candidate)) {
797
+ dir = candidate;
798
+ break;
799
+ }
800
+ }
801
+ }
802
+ if (dir === void 0 || !await exists2(dir)) {
803
+ console.error(`error: no site directory found${dir !== void 0 ? ` at "${dir}"` : ""} \u2014 pass one: muxo deploy <dir>`);
804
+ return 1;
805
+ }
806
+ const files = await collectFiles(dir);
807
+ const count = Object.keys(files).length;
808
+ if (count === 0) {
809
+ console.error(`error: "${dir}" is empty`);
810
+ return 1;
811
+ }
812
+ if (!opts.quiet && !opts.json) console.error(`deploying ${count} file(s) from ${dir}\u2026`);
813
+ const api = makeApi({
814
+ baseUrl: baseUrl(),
815
+ key: await resolveKey(),
816
+ debug: opts.debug
817
+ });
818
+ const env = await api.post("/capabilities/deploy.site", {
819
+ files,
820
+ ...opts.target !== void 0 ? { target: opts.target } : {}
821
+ });
822
+ return renderEnvelope(env, !!opts.json, (data2) => {
823
+ const d = data2;
824
+ if (typeof d?.url === "string" && d.url !== "") {
825
+ console.log(`deployed: ${d.url}`);
826
+ return;
827
+ }
828
+ console.log(JSON.stringify(data2, null, 2));
829
+ });
830
+ }
831
+
832
+ // src/commands/run.ts
833
+ function printSteps(data2) {
834
+ const d = data2;
835
+ if (d?.execution_id !== void 0) console.log(`execution ${String(d.execution_id)}`);
836
+ if (Array.isArray(d?.steps)) {
837
+ for (const step of d.steps) {
838
+ const name = typeof step?.name === "string" ? step.name : "step";
839
+ const status2 = typeof step?.status === "string" ? step.status : JSON.stringify(step);
840
+ console.log(` ${name}: ${status2}`);
841
+ if (step?.error !== void 0) console.log(` ${JSON.stringify(step.error)}`);
842
+ }
843
+ return;
844
+ }
845
+ console.log(JSON.stringify(data2, null, 2));
846
+ }
592
847
  async function run2(workflow2, opts) {
593
848
  const api = makeApi({
594
- baseUrl: baseUrl(opts.local),
849
+ baseUrl: baseUrl(),
595
850
  key: await resolveKey(),
596
851
  debug: opts.debug
597
852
  });
@@ -621,13 +876,13 @@ async function run2(workflow2, opts) {
621
876
  }
622
877
  async function status(opts) {
623
878
  const api = makeApi({
624
- baseUrl: baseUrl(opts.local),
879
+ baseUrl: baseUrl(),
625
880
  key: await resolveKey(),
626
881
  debug: opts.debug
627
882
  });
628
883
  const env = await api.get("/status");
629
- return renderEnvelope(env, !!opts.json, (data) => {
630
- const d = data;
884
+ return renderEnvelope(env, !!opts.json, (data2) => {
885
+ const d = data2;
631
886
  if (d && typeof d === "object" && !Array.isArray(d)) {
632
887
  if (d.project !== void 0) console.log(`project: ${String(d.project)}`);
633
888
  if (d.version !== void 0) console.log(`version: ${String(d.version)}`);
@@ -640,31 +895,31 @@ async function status(opts) {
640
895
  if (d.drift !== void 0) console.log(`drift: ${JSON.stringify(d.drift)}`);
641
896
  return;
642
897
  }
643
- console.log(JSON.stringify(data, null, 2));
898
+ console.log(JSON.stringify(data2, null, 2));
644
899
  });
645
900
  }
646
901
  async function rollback(version, opts) {
647
902
  const api = makeApi({
648
- baseUrl: baseUrl(opts.local),
903
+ baseUrl: baseUrl(),
649
904
  key: await resolveKey(),
650
905
  debug: opts.debug
651
906
  });
652
907
  const body = version === void 0 ? {} : { version: Number(version) };
653
908
  const env = await api.post("/rollback", body);
654
- return renderEnvelope(env, !!opts.json, (data) => {
655
- console.log(`rolled back to ${JSON.stringify(data)}`);
909
+ return renderEnvelope(env, !!opts.json, (data2) => {
910
+ console.log(`rolled back to ${JSON.stringify(data2)}`);
656
911
  });
657
912
  }
658
913
  async function logs(workflow2, opts) {
659
914
  const api = makeApi({
660
- baseUrl: baseUrl(opts.local),
915
+ baseUrl: baseUrl(),
661
916
  key: await resolveKey(),
662
917
  debug: opts.debug
663
918
  });
664
919
  const qs = workflow2 ? `?workflow=${encodeURIComponent(workflow2)}` : "";
665
920
  const env = await api.get(`/workflows/${encodeURIComponent(workflow2 ?? "_all")}/executions${qs}`);
666
- return renderEnvelope(env, !!opts.json, (data) => {
667
- const d = data;
921
+ return renderEnvelope(env, !!opts.json, (data2) => {
922
+ const d = data2;
668
923
  if (Array.isArray(d?.executions)) {
669
924
  for (const e of d.executions) {
670
925
  const when = new Date(e.started_at).toISOString().slice(0, 19).replace("T", " ");
@@ -674,18 +929,18 @@ async function logs(workflow2, opts) {
674
929
  }
675
930
  return;
676
931
  }
677
- console.log(JSON.stringify(data, null, 2));
932
+ console.log(JSON.stringify(data2, null, 2));
678
933
  });
679
934
  }
680
935
  async function stepLogs(instanceId, opts) {
681
936
  const api = makeApi({
682
- baseUrl: baseUrl(opts.local),
937
+ baseUrl: baseUrl(),
683
938
  key: await resolveKey(),
684
939
  debug: opts.debug
685
940
  });
686
941
  const env = await api.get(`/executions/${encodeURIComponent(instanceId)}/steps`);
687
- return renderEnvelope(env, !!opts.json, (data) => {
688
- const rows = data?.steps ?? [];
942
+ return renderEnvelope(env, !!opts.json, (data2) => {
943
+ const rows = data2?.steps ?? [];
689
944
  for (const s of rows) {
690
945
  const mark = s.status === "ok" ? "\u2713" : "\u2717";
691
946
  const err = s.error ? ` \u2014 ${s.error}` : "";
@@ -695,10 +950,25 @@ async function stepLogs(instanceId, opts) {
695
950
  }
696
951
 
697
952
  // src/commands/keys.ts
953
+ import { parseManifest } from "@muxoai/core";
954
+ async function bootstrapBody() {
955
+ const text = await readManifestText();
956
+ if (text === void 0) return {};
957
+ const parsed = parseManifest(text);
958
+ if (!parsed.ok || parsed.manifest === void 0) return {};
959
+ const capabilities2 = [
960
+ ...new Set(Object.values(parsed.manifest.bundles ?? {}).flatMap((b) => b.capabilities ?? []))
961
+ ];
962
+ return {
963
+ project: parsed.manifest.project,
964
+ ...capabilities2.length > 0 ? { scopes: capabilities2 } : {}
965
+ };
966
+ }
698
967
  async function keysCommand(action, id, opts) {
968
+ const existingKey = await resolveKey();
699
969
  const api = makeApi({
700
- baseUrl: baseUrl(opts.local),
701
- key: await resolveKey(),
970
+ baseUrl: baseUrl(),
971
+ key: existingKey,
702
972
  debug: opts.debug
703
973
  });
704
974
  if (action === "revoke" && !id) {
@@ -722,7 +992,7 @@ async function keysCommand(action, id, opts) {
722
992
  env = await api.get("/keys");
723
993
  break;
724
994
  case "create":
725
- env = await api.post("/keys");
995
+ env = await api.post("/keys", existingKey === void 0 ? await bootstrapBody() : void 0);
726
996
  break;
727
997
  case "rotate":
728
998
  env = await api.post(`/keys/${id}/rotate`);
@@ -731,32 +1001,61 @@ async function keysCommand(action, id, opts) {
731
1001
  env = await api.del(`/keys/${id}`);
732
1002
  break;
733
1003
  }
1004
+ let storedIn;
1005
+ if (env.ok && action === "create" && existingKey === void 0) {
1006
+ const data2 = env.data;
1007
+ if (typeof data2?.key === "string") {
1008
+ storedIn = await keychainSet(data2.key);
1009
+ if (!opts.json && storedIn === "file") {
1010
+ console.error("warning: keychain unavailable; key stored in ~/.muxo/key (chmod 600)");
1011
+ }
1012
+ }
1013
+ }
734
1014
  if (env.ok && (action === "create" || action === "rotate") && !opts.json) {
735
- const data = env.data;
736
- if (data?.key !== void 0) {
737
- const id2 = data.keyId ?? data.id;
738
- console.log(`${action === "rotate" ? "rotated" : "created"} key ${id2 !== void 0 ? String(id2) : ""}`);
739
- console.log(String(data.key));
740
- console.log("store it now \u2014 it will not be shown again");
1015
+ const data2 = env.data;
1016
+ if (data2?.key !== void 0) {
1017
+ const keyId = data2.keyId ?? data2.key_id ?? data2.id;
1018
+ console.log(`${action === "rotate" ? "rotated" : "created"} key ${keyId !== void 0 ? String(keyId) : ""}`);
1019
+ console.log(String(data2.key));
1020
+ if (data2.project_id !== void 0) {
1021
+ console.log(`project ${String(data2.project_id)}${data2.credits !== void 0 ? ` \xB7 ${String(data2.credits)} credits` : ""}`);
1022
+ }
1023
+ console.log(
1024
+ 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"
1025
+ );
741
1026
  return 0;
742
1027
  }
743
1028
  }
744
- return renderEnvelope(env, !!opts.json, (data) => {
745
- if (Array.isArray(data)) {
746
- for (const key of data) {
1029
+ return renderEnvelope(env, !!opts.json, (data2) => {
1030
+ if (Array.isArray(data2)) {
1031
+ for (const key of data2) {
747
1032
  const k = key;
748
1033
  console.log(`${String(k?.id ?? "?")}${k?.revoked ? " (revoked)" : ""}`);
749
1034
  }
750
1035
  return;
751
1036
  }
752
- printJson(data);
1037
+ printJson(data2);
1038
+ });
1039
+ }
1040
+
1041
+ // src/commands/capabilities.ts
1042
+ async function capabilities(opts) {
1043
+ const api = makeApi({ baseUrl: baseUrl(), key: await resolveKey(), debug: opts.debug });
1044
+ const env = await api.get("/capabilities");
1045
+ return renderEnvelope(env, !!opts.json, (data2) => {
1046
+ const rows = data2.capabilities ?? [];
1047
+ for (const c of rows) {
1048
+ const providers = c.backends.map((b) => b.configured ? b.provider : `${b.provider}*`).join(", ");
1049
+ console.log(`${c.available ? "ok " : "-- "}${c.name.padEnd(20)} ${providers}`);
1050
+ }
1051
+ console.log("\n* = not configured (missing runtime secrets)");
753
1052
  });
754
1053
  }
755
1054
 
756
1055
  // src/commands/credits.ts
757
1056
  async function creditsCommand(action, packId, opts) {
758
1057
  const api = makeApi({
759
- baseUrl: baseUrl(opts.local),
1058
+ baseUrl: baseUrl(),
760
1059
  key: await resolveKey(),
761
1060
  debug: opts.debug
762
1061
  });
@@ -766,8 +1065,8 @@ async function creditsCommand(action, packId, opts) {
766
1065
  return 1;
767
1066
  }
768
1067
  const env2 = await api.post("/credits/checkout", { pack: packId });
769
- return renderEnvelope(env2, !!opts.json, (data) => {
770
- const d = data;
1068
+ return renderEnvelope(env2, !!opts.json, (data2) => {
1069
+ const d = data2;
771
1070
  if (d?.url !== void 0) {
772
1071
  console.log(`checkout: ${d.url}`);
773
1072
  if (d.stubbed) console.log("(stub \u2014 set STRIPE_SECRET_KEY on the runtime for real checkout)");
@@ -775,8 +1074,8 @@ async function creditsCommand(action, packId, opts) {
775
1074
  });
776
1075
  }
777
1076
  const env = await api.get("/credits");
778
- return renderEnvelope(env, !!opts.json, (data) => {
779
- const d = data;
1077
+ return renderEnvelope(env, !!opts.json, (data2) => {
1078
+ const d = data2;
780
1079
  if (action === "packs") {
781
1080
  for (const p of d?.packs ?? []) {
782
1081
  console.log(`${p.id} ${p.credits} credits $${(p.price_cents / 100).toFixed(2)}`);
@@ -807,7 +1106,7 @@ function promptHidden(promptText) {
807
1106
  });
808
1107
  return;
809
1108
  }
810
- process.stdout.write(promptText);
1109
+ process.stderr.write(promptText);
811
1110
  const wasRaw = process.stdin.isRaw ?? false;
812
1111
  process.stdin.setRawMode(true);
813
1112
  process.stdin.resume();
@@ -818,7 +1117,7 @@ function promptHidden(promptText) {
818
1117
  process.stdin.setRawMode(wasRaw);
819
1118
  process.stdin.pause();
820
1119
  process.stdin.removeListener("data", onData);
821
- process.stdout.write("\n");
1120
+ process.stderr.write("\n");
822
1121
  resolve(buf);
823
1122
  return;
824
1123
  }
@@ -828,7 +1127,7 @@ function promptHidden(promptText) {
828
1127
  process.stdin.setRawMode(wasRaw);
829
1128
  process.stdin.pause();
830
1129
  process.stdin.removeListener("data", onData);
831
- process.stdout.write("\n");
1130
+ process.stderr.write("\n");
832
1131
  process.exit(130);
833
1132
  } else {
834
1133
  buf += ch;
@@ -841,14 +1140,41 @@ function promptHidden(promptText) {
841
1140
  async function login(opts) {
842
1141
  const key = await promptHidden("Paste your muxo key: ");
843
1142
  if (!key) {
844
- console.error("error: no key provided");
1143
+ if (opts.json) {
1144
+ printJson({ ok: false, error: { code: "invalid_input", message: "no key provided" } });
1145
+ } else {
1146
+ console.error("error: no key provided");
1147
+ }
845
1148
  return 1;
846
1149
  }
1150
+ const api = makeApi({ baseUrl: baseUrl(), key, debug: opts.debug });
1151
+ const check = await api.get("/status");
1152
+ if (!check.ok && check.error.code === "forbidden") {
1153
+ if (opts.json) {
1154
+ printJson(check);
1155
+ } else {
1156
+ console.error(`error: ${check.error.message}`);
1157
+ }
1158
+ return 2;
1159
+ }
847
1160
  const where = await keychainSet(key);
848
1161
  if (where === "file") {
849
1162
  console.error("warning: keychain unavailable; key stored in ~/.muxo/key (chmod 600)");
850
1163
  }
1164
+ if (opts.json) {
1165
+ printJson({
1166
+ ok: true,
1167
+ data: { stored: where, validated: check.ok },
1168
+ usage: { credits: 0, provider: "muxo", latency_ms: 0 }
1169
+ });
1170
+ return 0;
1171
+ }
851
1172
  info(where === "keychain" ? "key stored in OS keychain" : "key stored in ~/.muxo/key", opts);
1173
+ if (!check.ok) {
1174
+ console.error(`warning: could not verify key against the runtime (${check.error.message}) \u2014 stored anyway`);
1175
+ } else {
1176
+ info("key verified against the runtime", opts);
1177
+ }
852
1178
  nextSteps(["muxo status"], opts);
853
1179
  return 0;
854
1180
  }
@@ -867,6 +1193,9 @@ var STATIC_TOOLS = [
867
1193
  { name: "llm_tts", description: "Text to speech. Inputs: text (required), voice?." },
868
1194
  { name: "compute_sandbox", description: "Run code in a sandbox. Inputs: code (required), runtime?, timeout?." },
869
1195
  { name: "kv_store", description: "Key-value store. Inputs: key (required), value (to write) or get (to read)." },
1196
+ { name: "db_write", description: "Write rows to a stack database. Inputs: into (stack address, required), rows (required)." },
1197
+ { name: "db_query", description: "Query a stack database. Inputs: sql (required)." },
1198
+ { name: "dns_manage", description: "Manage DNS records for a domain. Inputs: domain (required), records." },
870
1199
  { name: "storage_object", description: "Object storage. Inputs: key (required), file (write) or get (read)." },
871
1200
  { name: "vector_search", description: "Vector search. Inputs: query (required), k?." },
872
1201
  { name: "email_send", description: "Send an email. Inputs: to, subject, body (all required)." },
@@ -889,10 +1218,10 @@ async function fetchTools(baseUrl2, key) {
889
1218
  const api = makeApi({ baseUrl: baseUrl2, key });
890
1219
  const env = await api.get("/status");
891
1220
  if (!env.ok) return STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
892
- const data = env.data;
893
- if (Array.isArray(data?.tools)) {
1221
+ const data2 = env.data;
1222
+ if (Array.isArray(data2?.tools)) {
894
1223
  const tools = [];
895
- for (const t of data.tools) {
1224
+ for (const t of data2.tools) {
896
1225
  if (typeof t === "string") {
897
1226
  tools.push(toolShape(toToolName(t), ""));
898
1227
  continue;
@@ -908,15 +1237,15 @@ async function fetchTools(baseUrl2, key) {
908
1237
  const filtered = tools.filter((t) => !excluded(t.name));
909
1238
  return filtered.length > 0 ? filtered : STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
910
1239
  }
911
- if (Array.isArray(data?.capabilities)) {
912
- const tools = data.capabilities.filter((c) => typeof c === "string").map((c) => toolShape(toToolName(c), "")).filter((t) => !excluded(t.name));
1240
+ if (Array.isArray(data2?.capabilities)) {
1241
+ const tools = data2.capabilities.filter((c) => typeof c === "string").map((c) => toolShape(toToolName(c), "")).filter((t) => !excluded(t.name));
913
1242
  return tools.length > 0 ? tools : STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
914
1243
  }
915
1244
  return STATIC_TOOLS.map((t) => toolShape(t.name, t.description));
916
1245
  }
917
- function successResult(data, usage) {
1246
+ function successResult(data2, usage) {
918
1247
  return {
919
- content: [{ type: "text", text: JSON.stringify({ data, usage }) }],
1248
+ content: [{ type: "text", text: JSON.stringify({ data: data2, usage }) }],
920
1249
  isError: false
921
1250
  };
922
1251
  }
@@ -931,7 +1260,7 @@ function errorResult(error) {
931
1260
  };
932
1261
  }
933
1262
  async function mcp(opts) {
934
- const baseUrl2 = opts.local ? LOCAL_BASE_URL : process.env.MUXO_API_BASE || DEFAULT_BASE_URL;
1263
+ const baseUrl2 = process.env.MUXO_API_BASE || DEFAULT_BASE_URL;
935
1264
  const key = await resolveKey();
936
1265
  const api = makeApi({ baseUrl: baseUrl2, key });
937
1266
  let tools = null;
@@ -953,7 +1282,7 @@ async function mcp(opts) {
953
1282
  reply(id, {
954
1283
  protocolVersion: version,
955
1284
  capabilities: { tools: { listChanged: false } },
956
- serverInfo: { name: "muxo", version: "0.1.0" }
1285
+ serverInfo: { name: "muxo", version: "0.1.1" }
957
1286
  });
958
1287
  return;
959
1288
  }
@@ -1007,11 +1336,12 @@ function flags(cmd) {
1007
1336
  }
1008
1337
  async function main() {
1009
1338
  const program = new Command();
1010
- program.name("muxo").description("Muxo CLI \u2014 one key for your whole stack").version("0.1.0").addHelpText("after", `
1339
+ program.name("muxo").description("Muxo CLI \u2014 one key for your whole stack").version("0.1.1").addHelpText("after", `
1011
1340
  Examples:
1012
1341
  muxo init "scrape hacker news every hour and store titles"
1013
1342
  muxo validate && muxo plan && muxo apply
1014
1343
  muxo run hn-scraper --param max_items=3
1344
+ muxo call web.search --data '{"query":"agent infra","limit":3}'
1015
1345
  muxo logs && muxo steps <instanceId>
1016
1346
 
1017
1347
  Auth: export MUXO_KEY=mk_... Endpoint: export MUXO_API_BASE=https://api.muxo.ai/v1
@@ -1047,11 +1377,29 @@ Docs: https://api.muxo.ai/docs/quickstart`);
1047
1377
  runCmd.description("Execute a workflow one-shot").action(async (workflow2, opts) => {
1048
1378
  process.exitCode = await run2(workflow2, opts);
1049
1379
  });
1380
+ const callCmd = program.command("call <capability>");
1381
+ flags(callCmd);
1382
+ 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], []);
1383
+ callCmd.description("Invoke a capability directly (web.search, deploy.site, llm.chat, \u2026)").action(
1384
+ async (capability, opts) => {
1385
+ process.exitCode = await call(capability, opts);
1386
+ }
1387
+ );
1388
+ const deployCmd = program.command("deploy [dir]");
1389
+ flags(deployCmd);
1390
+ 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) => {
1391
+ process.exitCode = await deploy(dir, opts);
1392
+ });
1050
1393
  const statusCmd = program.command("status");
1051
1394
  flags(statusCmd);
1052
1395
  statusCmd.description("Project health, bundles, budget state").action(async (opts) => {
1053
1396
  process.exitCode = await status(opts);
1054
1397
  });
1398
+ const capabilitiesCmd = program.command("capabilities");
1399
+ flags(capabilitiesCmd);
1400
+ capabilitiesCmd.description("Show which capabilities this deployment can serve").action(async (opts) => {
1401
+ process.exitCode = await capabilities(opts);
1402
+ });
1055
1403
  const keys = program.command("keys");
1056
1404
  flags(keys);
1057
1405
  keys.description("Manage project keys");
@@ -1110,13 +1458,12 @@ Docs: https://api.muxo.ai/docs/quickstart`);
1110
1458
  });
1111
1459
  const loginCmd = program.command("login");
1112
1460
  flags(loginCmd);
1113
- loginCmd.description("Store your muxo key in the OS keychain").action(async (opts) => {
1461
+ loginCmd.description("Store your muxo key in the OS keychain (validated against the runtime)").action(async (opts) => {
1114
1462
  process.exitCode = await login(opts);
1115
1463
  });
1116
1464
  const mcpCmd = program.command("mcp");
1117
1465
  flags(mcpCmd);
1118
- mcpCmd.option("--local", "target local runtime (http://localhost:8787/v1)");
1119
- mcpCmd.description("Stdio MCP proxy wrapping the muxo API (SPEC \xA712)").action(async (opts) => {
1466
+ mcpCmd.description("Stdio MCP proxy wrapping the muxo API").action(async (opts) => {
1120
1467
  process.exitCode = await mcp(opts);
1121
1468
  });
1122
1469
  await program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muxoai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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,7 @@
39
39
  "prepublishOnly": "npm run build"
40
40
  },
41
41
  "dependencies": {
42
- "@muxoai/core": "^0.1.0",
42
+ "@muxoai/core": "^0.1.1",
43
43
  "commander": "^12.0.0",
44
44
  "yaml": "^2.4.0",
45
45
  "zod": "^3.23.0"