@fluxa-pay/planner 0.2.3 → 0.2.4

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 (2) hide show
  1. package/package.json +2 -2
  2. package/planner.mjs +20 -180
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fluxa-pay/planner",
3
- "version": "0.2.3",
4
- "description": "FluxA Planner CLI. Recommend the right models, APIs and skills for a task, run paid calls via x402, and manage your oneshot APIs.",
3
+ "version": "0.2.4",
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"
package/planner.mjs CHANGED
@@ -281,12 +281,9 @@ async function keysRevoke(id) {
281
281
  console.log(c.green("✓") + ` revoked ${id} — it stops authenticating on its next call`);
282
282
  }
283
283
 
284
- // --- paid call (the x402 v3 dance, wrapped) ---------------------------------
285
- // `planner call <url>` collapses the 10-step manual flow into one command:
286
- // register discover ensure a signed mandate call → on 402 settle via
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.
284
+ // --- wallet helpers (x402 v3 settlement, shared by topup) --------------------
285
+ // The wallet CLI does the signing; planner reuses a signed, unexpired mandate so
286
+ // the user signs once per budget window, not once per settlement.
290
287
  async function walletRaw(args) {
291
288
  const { stdout: out } = await pexec("fluxa-wallet", args);
292
289
  return out;
@@ -319,39 +316,6 @@ async function rawCall(url, method, data, extraHeaders = {}, auth = false) {
319
316
  return { status: res.status, text };
320
317
  }
321
318
 
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
319
  async function ensureMandate(currency, budget, seconds, url) {
356
320
  const cfg = loadConfig();
357
321
  // only reuse a signed mandate of the SAME currency with budget left
@@ -381,101 +345,7 @@ async function ensureMandate(currency, budget, seconds, url) {
381
345
  const mm = s.mandate || s;
382
346
  if (mm && mm.status === "signed") { console.error(c.green(" ✓ mandate signed")); return mandateId; }
383
347
  }
384
- die("mandate not signed in time — re-run `planner call` once you've approved the link.");
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
348
+ die("mandate not signed in time — re-run once you've approved the link.");
479
349
  }
480
350
 
481
351
  // --- topup (prepay Units to a merchant) -------------------------------------
@@ -577,7 +447,7 @@ async function cmdApiCreate(opts) {
577
447
  if (data.proxyUrl) console.log(c.dim(` proxy: ${data.proxyUrl}`));
578
448
  console.log(c.dim(
579
449
  data.status === "published"
580
- ? ` test it: planner call ${data.proxyUrl || data.slug}`
450
+ ? ` test it: curl ${data.proxyUrl || `${PROXY}/api/${data.slug}`}`
581
451
  : ` publish: planner api edit ${data.slug || data.configId} --publish`,
582
452
  ));
583
453
  }
@@ -683,15 +553,14 @@ ${c.bold("planner")} ${c.dim("— what you're working with")}
683
553
  Balances are ${c.bold("per merchant")} (prepaid Units). ${c.cyan("planner topup <merchant>")} to prefund.
684
554
 
685
555
  ${c.bold("Paying (x402)")}
686
- A paid call returns HTTP 402 → planner settles it via ${c.dim("fluxa-wallet")} inside a spending
687
- mandate you sign once (budget + window), then reuses it. planner never holds keys.
556
+ A paid call returns HTTP 402 → settle it with the ${c.dim("fluxa-wallet")} CLI inside a spending
557
+ mandate you sign once (budget + window), then reuse it. ${c.cyan("planner topup")} prefunds instead.
688
558
 
689
559
  ${c.bold("Auth")} an ${c.dim("fxa_live_")} key OR an auto-minted agent VC. Nothing to log in.
690
560
  ${c.bold("Bases")} platform ${c.dim(new URL(PLATFORM).host)} · proxy ${c.dim(new URL(PROXY).host)}
691
561
 
692
562
  ${c.bold("Commands")}
693
563
  ${c.cyan('plan "<task>"')} recommend tools for a task
694
- ${c.cyan("call <url> --data '…'")} make one paid API call (handles the 402)
695
564
  ${c.cyan("topup <merchant>")} prepay Units
696
565
  ${c.cyan('search "<q>"')} discover apis/models/skills
697
566
  ${c.dim("models · balance · ledger · keys · whoami")}
@@ -729,19 +598,21 @@ ${c.bold("API keys — programmatic management")} ${c.dim("(Agent VC only)")}
729
598
  pay: () => `
730
599
  ${c.bold("Paying — x402 v3")}
731
600
  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. planner then:
733
- 1. ensures a signed spending ${c.bold("mandate")} (you pre-approve a budget + time window — once)
734
- 2. signs the 402 via ${c.dim("fluxa-wallet x402-v3")} (planner never holds keys)
735
- 3. retries with the payment token
736
- It reuses the signed mandate for later calls in its window. ${c.cyan("planner topup")} prefunds instead.
601
+ On a shortfall a paid endpoint returns HTTP 402 with an x402 challenge. Settle it with
602
+ the ${c.dim("fluxa-wallet")} CLI:
603
+ 1. sign a spending ${c.bold("mandate")} once (you pre-approve a budget + time window)
604
+ ${c.dim("fluxa-wallet mandate-create --amount <units> --seconds <ttl> --currency <…>")}
605
+ 2. settle the 402 challenge ${c.dim("fluxa-wallet x402-v3 --mandate <id> --payload <402 body>")}
606
+ 3. retry the call with the returned payment token in the ${c.dim("X-Payment")} header
607
+ Reuse the signed mandate for later calls in its window. ${c.cyan("planner topup")} prefunds instead.
737
608
  `,
738
609
  apis: () => `
739
610
  ${c.bold("Oneshot APIs")}
740
611
  ${c.dim("/api/{config}")} is two-level: ${c.dim("GET <base>")} returns a descriptor of sub-endpoints
741
612
  (path, method, price, input schema). The paid action is a sub-endpoint.
742
- ${c.cyan("planner call <base>")} show the paid endpoint + its required body
743
- ${c.cyan("planner call <base> --data '<json>'")} call it; pay the 402 if needed
744
- ${c.dim("--endpoint <path>")} picks when there are several; ${c.dim("--raw")} hits a URL directly.
613
+ ${c.dim(`curl ${new URL(PROXY).host}/api/<slug>`)} list the sub-endpoints + their input schema
614
+ Call a sub-endpoint; on a 402, settle with ${c.dim("fluxa-wallet")} (see ${c.cyan("planner info pay")}) and
615
+ retry with the ${c.dim("X-Payment")} token. ${c.dim("X-Agent-ID")} is optional (attribution only).
745
616
  ${c.bold("Manage your APIs")} ${c.dim("(fxa_live_ key or auto-minted Agent VC)")}
746
617
  ${c.cyan("planner api create --file <spec.json> [--publish]")} create (draft unless --publish)
747
618
  ${c.cyan("planner api list")} your api configs
@@ -781,9 +652,7 @@ ${c.bold("planner")} ${c.dim("— FluxA Planner CLI (live)")}
781
652
 
782
653
  ${c.bold("Headline")}
783
654
  planner plan "<task>" recommend the models, APIs & skills for a task
784
- planner call <url> make a paid API call handles the x402 mandate + payment
785
- [--pay credits|usdc] [--method GET|POST] [--data '<json>']
786
- [--header "Name: Value"] (repeatable; e.g. an upstream Bearer token)
655
+ (run the recommended calls yourself; pay 402s via fluxa-wallet)
787
656
 
788
657
  ${c.bold("Primitives")} ${c.dim("(all live)")}
789
658
  planner search "<q>" [--type api,skill,model] discover resources
@@ -811,31 +680,9 @@ function die(msg, ctx) {
811
680
  async function main() {
812
681
  const args = argv.slice(2);
813
682
  const cmd = args[0];
814
- const FLAGS = ["--type", "--vendor", "--pay", "--method", "--data", "--budget", "--seconds", "--endpoint", "--credits", "--bundle", "--name", "--cap", "--file", "--upstream", "--wallet", "--header"];
815
- const BOOLS = ["--raw", "--publish", "--private", "--require-agent-auth", "--dry-run"];
683
+ const FLAGS = ["--type", "--vendor", "--budget", "--seconds", "--credits", "--bundle", "--name", "--cap", "--file", "--upstream", "--wallet"];
684
+ const BOOLS = ["--publish", "--private", "--require-agent-auth", "--dry-run"];
816
685
  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
686
  const type = getFlag("--type");
840
687
  const vendor = getFlag("--vendor");
841
688
  const rest = [];
@@ -848,13 +695,6 @@ async function main() {
848
695
 
849
696
  switch (cmd) {
850
697
  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
698
  case "search": return cmdSearch(arg, type);
859
699
  case "models": return cmdModels(vendor);
860
700
  case "vendors": return cmdVendors();