@fluxa-pay/planner 0.2.3 → 0.2.5
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/package.json +4 -3
- package/plan-format.mjs +49 -0
- package/planner.mjs +27 -193
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluxa-pay/planner",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "FluxA Planner CLI. Recommend the right models, APIs and skills for a task,
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "FluxA Planner CLI. Recommend the right models, APIs and skills for a task, and manage your oneshot APIs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"planner": "planner.mjs"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"node": ">=20"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"planner.mjs"
|
|
13
|
+
"planner.mjs",
|
|
14
|
+
"plan-format.mjs"
|
|
14
15
|
],
|
|
15
16
|
"publishConfig": {
|
|
16
17
|
"access": "public"
|
package/plan-format.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Pure formatting of a `planner plan` result into terminal lines. No I/O and no
|
|
2
|
+
// color of its own — the caller passes a helpers bag (h) so planner.mjs injects
|
|
3
|
+
// ANSI while tests pass identity helpers and assert on plain text.
|
|
4
|
+
//
|
|
5
|
+
// When the plan carries capability `groups`, each capability prints its options
|
|
6
|
+
// (the recommended one first, tagged) so the agent sees the alternatives. Older
|
|
7
|
+
// / template plans without groups fall back to the flat `tools` list.
|
|
8
|
+
export function planLines(plan, h) {
|
|
9
|
+
const lines = [];
|
|
10
|
+
const heading = h.unesc(plan.reason || plan.task || "Recommended");
|
|
11
|
+
lines.push("");
|
|
12
|
+
lines.push(" " + h.bold("Recommended") + " " + h.dim(heading));
|
|
13
|
+
|
|
14
|
+
const groups = Array.isArray(plan.groups) ? plan.groups : null;
|
|
15
|
+
let total = 0;
|
|
16
|
+
|
|
17
|
+
if (groups && groups.length) {
|
|
18
|
+
for (const g of groups) {
|
|
19
|
+
lines.push(" " + h.dim(h.unesc(g.capability || "")));
|
|
20
|
+
const lead = g.options.find((o) => o.recommended) || g.options[0];
|
|
21
|
+
const ordered = lead ? [lead, ...g.options.filter((o) => o !== lead)] : g.options;
|
|
22
|
+
if (lead) total += lead.units || 0;
|
|
23
|
+
for (const o of ordered) {
|
|
24
|
+
const k = h.kind(o.kind);
|
|
25
|
+
const tag = o === lead ? " " + h.lime("recommended") : "";
|
|
26
|
+
lines.push(
|
|
27
|
+
` ${k} ${h.bold(h.pad(o.slug, 34))} ${h.dim(h.pad(h.unesc(o.detail || ""), 24))} ${h.lime("~" + (o.units || 0).toLocaleString() + " Units")}${tag}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
for (const t of plan.tools || []) {
|
|
33
|
+
total += t.units || 0;
|
|
34
|
+
const k = h.kind(t.kind);
|
|
35
|
+
lines.push(
|
|
36
|
+
` ${k} ${h.bold(h.pad(t.slug, 36))} ${h.dim(h.pad(h.unesc(t.detail || ""), 30))} ${h.lime("~" + (t.units || 0).toLocaleString() + " Units")}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
lines.push(h.gray(" " + "─".repeat(78)));
|
|
42
|
+
lines.push(` ${h.bold("est")} ${h.lime("~" + total.toLocaleString() + " Units")} ${h.dim("(~" + h.usd(total) + ")")}`);
|
|
43
|
+
if (plan.instructions) {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(" " + h.dim(h.unesc(plan.instructions)));
|
|
46
|
+
}
|
|
47
|
+
lines.push(h.dim("\n → Run these yourself. Paid endpoints answer HTTP 402; settle with fluxa-wallet (planner info pay)."));
|
|
48
|
+
return { lines, total };
|
|
49
|
+
}
|
package/planner.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { promisify } from "node:util";
|
|
|
26
26
|
import { homedir } from "node:os";
|
|
27
27
|
import { join } from "node:path";
|
|
28
28
|
import { stdout, argv, env, exit } from "node:process";
|
|
29
|
+
import { planLines } from "./plan-format.mjs";
|
|
29
30
|
|
|
30
31
|
const pexec = promisify(execFile);
|
|
31
32
|
|
|
@@ -281,12 +282,9 @@ async function keysRevoke(id) {
|
|
|
281
282
|
console.log(c.green("✓") + ` revoked ${id} — it stops authenticating on its next call`);
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
// ---
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
// `fluxa-wallet x402-v3` → retry with the payment token → return the raw body.
|
|
288
|
-
// Signing the mandate is the one human step; planner reuses a signed, unexpired
|
|
289
|
-
// mandate so you sign once per budget window, not once per call.
|
|
285
|
+
// --- wallet helpers (x402 v3 settlement, shared by topup) --------------------
|
|
286
|
+
// The wallet CLI does the signing; planner reuses a signed, unexpired mandate so
|
|
287
|
+
// the user signs once per budget window, not once per settlement.
|
|
290
288
|
async function walletRaw(args) {
|
|
291
289
|
const { stdout: out } = await pexec("fluxa-wallet", args);
|
|
292
290
|
return out;
|
|
@@ -319,39 +317,6 @@ async function rawCall(url, method, data, extraHeaders = {}, auth = false) {
|
|
|
319
317
|
return { status: res.status, text };
|
|
320
318
|
}
|
|
321
319
|
|
|
322
|
-
// The proxy's API/MCP lanes bill the caller identified by the X-Agent-ID header
|
|
323
|
-
// and reject every non-discovery request without it. Default to the agent_id
|
|
324
|
-
// (UUID) from `fluxa-wallet status`; configs with require_agent_auth need a JWT
|
|
325
|
-
// instead, so agentCall() retries with a freshly minted wallet JWT on the
|
|
326
|
-
// "valid JWT" 401. Both values are cached for the process.
|
|
327
|
-
let _agentId = null;
|
|
328
|
-
async function agentId() {
|
|
329
|
-
if (_agentId) return _agentId;
|
|
330
|
-
const s = unwrap(await walletJson(["status"]).catch(() => ({})));
|
|
331
|
-
const id = s.agent_id || s.agentId || s.id;
|
|
332
|
-
if (!id) die("could not read agent_id from `fluxa-wallet status` — register once with `fluxa-wallet init`.");
|
|
333
|
-
return (_agentId = id);
|
|
334
|
-
}
|
|
335
|
-
let _walletJwt = null;
|
|
336
|
-
async function walletJwt() {
|
|
337
|
-
if (_walletJwt) return _walletJwt;
|
|
338
|
-
const j = unwrap(await walletJson(["refreshJWT"]).catch(() => ({})));
|
|
339
|
-
const jwt = j.jwt || j.token;
|
|
340
|
-
if (!jwt) die("could not mint a wallet JWT (`fluxa-wallet refreshJWT`).");
|
|
341
|
-
return (_walletJwt = jwt);
|
|
342
|
-
}
|
|
343
|
-
// rawCall + X-Agent-ID, with the UUID→JWT fallback for require_agent_auth configs.
|
|
344
|
-
// Auto-attached headers go FIRST so a caller-supplied `extra` (e.g. a user's
|
|
345
|
-
// `--header "X-Agent-ID: …"`) can override — collisions are surfaced upstream
|
|
346
|
-
// in cmdCall so the override isn't silent.
|
|
347
|
-
async function agentCall(url, method, data, extra = {}) {
|
|
348
|
-
const r = await rawCall(url, method, data, { "X-Agent-ID": await agentId(), ...extra });
|
|
349
|
-
if (r.status === 401 && /valid JWT/i.test(r.text)) {
|
|
350
|
-
return rawCall(url, method, data, { "X-Agent-ID": await walletJwt(), ...extra });
|
|
351
|
-
}
|
|
352
|
-
return r;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
320
|
async function ensureMandate(currency, budget, seconds, url) {
|
|
356
321
|
const cfg = loadConfig();
|
|
357
322
|
// only reuse a signed mandate of the SAME currency with budget left
|
|
@@ -381,101 +346,7 @@ async function ensureMandate(currency, budget, seconds, url) {
|
|
|
381
346
|
const mm = s.mandate || s;
|
|
382
347
|
if (mm && mm.status === "signed") { console.error(c.green(" ✓ mandate signed")); return mandateId; }
|
|
383
348
|
}
|
|
384
|
-
die("mandate not signed in time — re-run
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function descriptorEndpoints(text) {
|
|
388
|
-
try { const j = JSON.parse(text); if (j && Array.isArray(j.endpoints)) return j; } catch {}
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function cmdCall(url, opts = {}) {
|
|
393
|
-
if (!url) {
|
|
394
|
-
return die("usage: planner call <url|slug> [--endpoint <path>] [--data '<json>'] [--header 'K: V']… [--pay credits|usdc] [--method GET|POST] [--budget <units>] [--raw]");
|
|
395
|
-
}
|
|
396
|
-
// accept a bare AgentMarket slug (e.g. `web-search-api`) → the proxy API base.
|
|
397
|
-
// full http(s) URLs are used as-is.
|
|
398
|
-
if (!/^https?:\/\//.test(url)) url = `${PROXY}/api/${encodeURIComponent(url)}`;
|
|
399
|
-
const pay = (opts.pay || loadConfig().pay || "credits").toLowerCase();
|
|
400
|
-
const currency = pay === "usdc" ? "USDC" : "FLUXA_MONETIZE_CREDITS";
|
|
401
|
-
const budget = opts.budget ? Number(opts.budget) : (pay === "usdc" ? 5000000 : 500); // ~$5 default
|
|
402
|
-
const seconds = opts.seconds ? Number(opts.seconds) : 28800; // 8h
|
|
403
|
-
|
|
404
|
-
// user-supplied request headers (repeatable --header "K: V") are merged
|
|
405
|
-
// into every outbound call below. A user value wins over the auto-attached
|
|
406
|
-
// X-Agent-ID; X-Payment is the one exception, see the retry step. We warn
|
|
407
|
-
// up front when a user header would override an auto-attached one so the
|
|
408
|
-
// override isn't silent.
|
|
409
|
-
const userHeaders = opts.headers || {};
|
|
410
|
-
const reservedNames = ["x-agent-id", "x-payment"];
|
|
411
|
-
for (const k of Object.keys(userHeaders)) {
|
|
412
|
-
if (reservedNames.includes(k.toLowerCase())) {
|
|
413
|
-
console.error(c.dim(`! --header ${k} overrides the auto-attached value`));
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
await ensureAgent();
|
|
418
|
-
|
|
419
|
-
let targetUrl = url;
|
|
420
|
-
let method = (opts.method || (opts.data ? "POST" : "GET")).toUpperCase();
|
|
421
|
-
const data = opts.data;
|
|
422
|
-
|
|
423
|
-
// proxy endpoints are two-level: GET <base> returns a descriptor listing the
|
|
424
|
-
// real paid sub-endpoints. Resolve it (unless --raw) so the agent doesn't have
|
|
425
|
-
// to read the catalog itself. We never spend without a supplied body.
|
|
426
|
-
if (!opts.raw) {
|
|
427
|
-
const probe = await agentCall(url, "GET", undefined, userHeaders);
|
|
428
|
-
const desc = probe.status === 200 ? descriptorEndpoints(probe.text) : null;
|
|
429
|
-
if (desc) {
|
|
430
|
-
const eps = desc.endpoints.filter((e) => e.enabled !== false);
|
|
431
|
-
const norm = (p) => "/" + String(p || "").replace(/^\//, "");
|
|
432
|
-
let ep = opts.endpoint ? eps.find((e) => norm(e.path) === norm(opts.endpoint)) : null;
|
|
433
|
-
if (!ep) {
|
|
434
|
-
const paid = eps.filter((e) => Number(e.price || 0) > 0);
|
|
435
|
-
ep = paid.length === 1 ? paid[0] : null;
|
|
436
|
-
}
|
|
437
|
-
if (!ep) {
|
|
438
|
-
console.error(c.bold(`\n ${desc.name || "endpoint"}`) + c.dim(" — pick one with --endpoint <path>:"));
|
|
439
|
-
for (const e of eps) {
|
|
440
|
-
console.error(` ${pad(e.method || "GET", 5)} ${c.bold(pad(e.path, 22))} ${c.lime(pad(e.price ? `${e.price} ${e.currency || ""}` : "free", 12))} ${c.gray(e.description || "")}`);
|
|
441
|
-
}
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
targetUrl = url.replace(/\/$/, "") + norm(ep.path);
|
|
445
|
-
method = opts.method ? opts.method.toUpperCase() : (ep.method || "GET").toUpperCase();
|
|
446
|
-
const bodyFields = ep.inputSchema && Array.isArray(ep.inputSchema.bodyFields) ? ep.inputSchema.bodyFields : [];
|
|
447
|
-
if (bodyFields.length > 0 && !data) {
|
|
448
|
-
console.error(c.bold(`\n ${method} ${norm(ep.path)}`) + c.dim(` ${ep.price ? `${ep.price} ${ep.currency || ""}` : "free"}`));
|
|
449
|
-
console.error(c.dim(" needs a JSON body — pass it with --data '<json>'. Expected shape:"));
|
|
450
|
-
const ex = bodyFields[0]?.description;
|
|
451
|
-
if (ex) console.error(c.gray(" " + String(ex).replace(/\n/g, "\n ")));
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// call the resolved endpoint — pay if it answers 402
|
|
458
|
-
let r = await agentCall(targetUrl, method, data, userHeaders);
|
|
459
|
-
if (r.status !== 402) {
|
|
460
|
-
if (r.status >= 400) die(`${r.status} — ${r.text.slice(0, 300)}`);
|
|
461
|
-
return console.log(r.text); // raw body, no post-processing
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
console.error(c.dim(`· 402 Payment Required — settling via x402 v3 (${currency})`));
|
|
465
|
-
const mandateId = await ensureMandate(currency, budget, seconds, targetUrl);
|
|
466
|
-
|
|
467
|
-
// 2. sign the payment for this exact 402 challenge (pass the FULL body)
|
|
468
|
-
const paid = unwrap(await walletJson(["x402-v3", "--mandate", mandateId, "--payload", r.text]));
|
|
469
|
-
const xPayment = paid.xPaymentB64 || paid.xPayment;
|
|
470
|
-
if (!xPayment) die("x402-v3 did not return a payment token (xPaymentB64).");
|
|
471
|
-
|
|
472
|
-
// 3. retry with the payment token (against the resolved endpoint, with
|
|
473
|
-
// X-Agent-ID + user headers). The freshly-minted X-Payment is appended
|
|
474
|
-
// LAST so it wins over any user-supplied X-Payment — preserving the x402
|
|
475
|
-
// settlement that the CLI just signed for this exact 402 challenge.
|
|
476
|
-
r = await agentCall(targetUrl, method, opts.data, { ...userHeaders, "X-Payment": xPayment });
|
|
477
|
-
if (r.status >= 400) die(`paid retry failed: ${r.status} — ${r.text.slice(0, 300)}`);
|
|
478
|
-
console.log(r.text); // raw body, no post-processing
|
|
349
|
+
die("mandate not signed in time — re-run once you've approved the link.");
|
|
479
350
|
}
|
|
480
351
|
|
|
481
352
|
// --- topup (prepay Units to a merchant) -------------------------------------
|
|
@@ -577,7 +448,7 @@ async function cmdApiCreate(opts) {
|
|
|
577
448
|
if (data.proxyUrl) console.log(c.dim(` proxy: ${data.proxyUrl}`));
|
|
578
449
|
console.log(c.dim(
|
|
579
450
|
data.status === "published"
|
|
580
|
-
? ` test it:
|
|
451
|
+
? ` test it: curl ${data.proxyUrl || `${PROXY}/api/${data.slug}`}`
|
|
581
452
|
: ` publish: planner api edit ${data.slug || data.configId} --publish`,
|
|
582
453
|
));
|
|
583
454
|
}
|
|
@@ -649,19 +520,12 @@ function printAnswer(a) {
|
|
|
649
520
|
|
|
650
521
|
// Render a PlanOrAnswer plan: tools carry kind/slug/name/units/detail.
|
|
651
522
|
function printPlan(plan) {
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
total += t.units || 0;
|
|
659
|
-
console.log(` ${k} ${c.bold(pad(t.slug, 36))} ${c.dim(pad(unesc(t.detail || ""), 30))} ${c.lime("~" + (t.units || 0).toLocaleString() + " Units")}`);
|
|
660
|
-
}
|
|
661
|
-
console.log(c.gray(" " + "─".repeat(78)));
|
|
662
|
-
console.log(` ${c.bold("est")} ${c.lime("~" + total.toLocaleString() + " Units")} ${c.dim("(~" + usd(total) + ")")}`);
|
|
663
|
-
if (plan.instructions) console.log("\n " + c.dim(unesc(plan.instructions)));
|
|
664
|
-
console.log(c.dim("\n → call these tools directly — FluxA settles each paid call from your wallet. No keys, no subs."));
|
|
523
|
+
const { lines, total } = planLines(plan, {
|
|
524
|
+
bold: c.bold, dim: c.dim, lime: c.lime, gray: c.gray,
|
|
525
|
+
kind: (k) => KIND[k] || pad(k, 5),
|
|
526
|
+
pad, usd, unesc,
|
|
527
|
+
});
|
|
528
|
+
for (const l of lines) console.log(l);
|
|
665
529
|
return total;
|
|
666
530
|
}
|
|
667
531
|
|
|
@@ -683,15 +547,14 @@ ${c.bold("planner")} ${c.dim("— what you're working with")}
|
|
|
683
547
|
Balances are ${c.bold("per merchant")} (prepaid Units). ${c.cyan("planner topup <merchant>")} to prefund.
|
|
684
548
|
|
|
685
549
|
${c.bold("Paying (x402)")}
|
|
686
|
-
A paid call returns HTTP 402 →
|
|
687
|
-
mandate you sign once (budget + window), then
|
|
550
|
+
A paid call returns HTTP 402 → settle it with the ${c.dim("fluxa-wallet")} CLI inside a spending
|
|
551
|
+
mandate you sign once (budget + window), then reuse it. ${c.cyan("planner topup")} prefunds instead.
|
|
688
552
|
|
|
689
553
|
${c.bold("Auth")} an ${c.dim("fxa_live_")} key OR an auto-minted agent VC. Nothing to log in.
|
|
690
554
|
${c.bold("Bases")} platform ${c.dim(new URL(PLATFORM).host)} · proxy ${c.dim(new URL(PROXY).host)}
|
|
691
555
|
|
|
692
556
|
${c.bold("Commands")}
|
|
693
557
|
${c.cyan('plan "<task>"')} recommend tools for a task
|
|
694
|
-
${c.cyan("call <url> --data '…'")} make one paid API call (handles the 402)
|
|
695
558
|
${c.cyan("topup <merchant>")} prepay Units
|
|
696
559
|
${c.cyan('search "<q>"')} discover apis/models/skills
|
|
697
560
|
${c.dim("models · balance · ledger · keys · whoami")}
|
|
@@ -729,19 +592,21 @@ ${c.bold("API keys — programmatic management")} ${c.dim("(Agent VC only)")}
|
|
|
729
592
|
pay: () => `
|
|
730
593
|
${c.bold("Paying — x402 v3")}
|
|
731
594
|
Prepaid: each merchant has a Units balance; while it's funded, calls just work.
|
|
732
|
-
On a shortfall a paid endpoint returns HTTP 402 with an x402 challenge.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
595
|
+
On a shortfall a paid endpoint returns HTTP 402 with an x402 challenge. Settle it with
|
|
596
|
+
the ${c.dim("fluxa-wallet")} CLI:
|
|
597
|
+
1. sign a spending ${c.bold("mandate")} once (you pre-approve a budget + time window)
|
|
598
|
+
${c.dim("fluxa-wallet mandate-create --amount <units> --seconds <ttl> --currency <…>")}
|
|
599
|
+
2. settle the 402 challenge ${c.dim("fluxa-wallet x402-v3 --mandate <id> --payload <402 body>")}
|
|
600
|
+
3. retry the call with the returned payment token in the ${c.dim("X-Payment")} header
|
|
601
|
+
Reuse the signed mandate for later calls in its window. ${c.cyan("planner topup")} prefunds instead.
|
|
737
602
|
`,
|
|
738
603
|
apis: () => `
|
|
739
604
|
${c.bold("Oneshot APIs")}
|
|
740
605
|
${c.dim("/api/{config}")} is two-level: ${c.dim("GET <base>")} returns a descriptor of sub-endpoints
|
|
741
606
|
(path, method, price, input schema). The paid action is a sub-endpoint.
|
|
742
|
-
${c.
|
|
743
|
-
|
|
744
|
-
${c.dim("
|
|
607
|
+
${c.dim(`curl ${new URL(PROXY).host}/api/<slug>`)} list the sub-endpoints + their input schema
|
|
608
|
+
Call a sub-endpoint; on a 402, settle with ${c.dim("fluxa-wallet")} (see ${c.cyan("planner info pay")}) and
|
|
609
|
+
retry with the ${c.dim("X-Payment")} token. ${c.dim("X-Agent-ID")} is optional (attribution only).
|
|
745
610
|
${c.bold("Manage your APIs")} ${c.dim("(fxa_live_ key or auto-minted Agent VC)")}
|
|
746
611
|
${c.cyan("planner api create --file <spec.json> [--publish]")} create (draft unless --publish)
|
|
747
612
|
${c.cyan("planner api list")} your api configs
|
|
@@ -781,9 +646,7 @@ ${c.bold("planner")} ${c.dim("— FluxA Planner CLI (live)")}
|
|
|
781
646
|
|
|
782
647
|
${c.bold("Headline")}
|
|
783
648
|
planner plan "<task>" recommend the models, APIs & skills for a task
|
|
784
|
-
|
|
785
|
-
[--pay credits|usdc] [--method GET|POST] [--data '<json>']
|
|
786
|
-
[--header "Name: Value"] (repeatable; e.g. an upstream Bearer token)
|
|
649
|
+
(run the recommended calls yourself; pay 402s via fluxa-wallet)
|
|
787
650
|
|
|
788
651
|
${c.bold("Primitives")} ${c.dim("(all live)")}
|
|
789
652
|
planner search "<q>" [--type api,skill,model] discover resources
|
|
@@ -811,31 +674,9 @@ function die(msg, ctx) {
|
|
|
811
674
|
async function main() {
|
|
812
675
|
const args = argv.slice(2);
|
|
813
676
|
const cmd = args[0];
|
|
814
|
-
const FLAGS = ["--type", "--vendor", "--
|
|
815
|
-
const BOOLS = ["--
|
|
677
|
+
const FLAGS = ["--type", "--vendor", "--budget", "--seconds", "--credits", "--bundle", "--name", "--cap", "--file", "--upstream", "--wallet"];
|
|
678
|
+
const BOOLS = ["--publish", "--private", "--require-agent-auth", "--dry-run"];
|
|
816
679
|
const getFlag = (n) => { const i = args.indexOf(n); return i !== -1 ? args[i + 1] : null; };
|
|
817
|
-
// repeatable flag → array of all values (in order)
|
|
818
|
-
const getFlagAll = (n) => {
|
|
819
|
-
const out = [];
|
|
820
|
-
for (let i = 0; i < args.length - 1; i++) if (args[i] === n) out.push(args[i + 1]);
|
|
821
|
-
return out;
|
|
822
|
-
};
|
|
823
|
-
// parse `--header "Name: Value"` (repeatable) into a headers object. Splits
|
|
824
|
-
// on the FIRST colon so JWTs/URLs in the value are preserved. Malformed
|
|
825
|
-
// entries are warned and skipped, never thrown — a broken header shouldn't
|
|
826
|
-
// poison the whole call.
|
|
827
|
-
const parseHeaders = (specs) => {
|
|
828
|
-
const out = {};
|
|
829
|
-
for (const s of specs) {
|
|
830
|
-
const i = s.indexOf(":");
|
|
831
|
-
if (i <= 0) { console.error(c.dim(`! ignoring malformed --header "${s}" (expected "Name: Value")`)); continue; }
|
|
832
|
-
const name = s.slice(0, i).trim();
|
|
833
|
-
const value = s.slice(i + 1).trim();
|
|
834
|
-
if (!name) continue;
|
|
835
|
-
out[name] = value;
|
|
836
|
-
}
|
|
837
|
-
return out;
|
|
838
|
-
};
|
|
839
680
|
const type = getFlag("--type");
|
|
840
681
|
const vendor = getFlag("--vendor");
|
|
841
682
|
const rest = [];
|
|
@@ -848,13 +689,6 @@ async function main() {
|
|
|
848
689
|
|
|
849
690
|
switch (cmd) {
|
|
850
691
|
case "plan": return cmdPlan(arg);
|
|
851
|
-
case "call":
|
|
852
|
-
return cmdCall(rest[0], {
|
|
853
|
-
pay: getFlag("--pay"), method: getFlag("--method"), data: getFlag("--data"),
|
|
854
|
-
budget: getFlag("--budget"), seconds: getFlag("--seconds"),
|
|
855
|
-
endpoint: getFlag("--endpoint"), raw: args.includes("--raw"),
|
|
856
|
-
headers: parseHeaders(getFlagAll("--header")),
|
|
857
|
-
});
|
|
858
692
|
case "search": return cmdSearch(arg, type);
|
|
859
693
|
case "models": return cmdModels(vendor);
|
|
860
694
|
case "vendors": return cmdVendors();
|