mppx 0.6.3 → 0.6.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/Credential.d.ts +4 -0
  3. package/dist/Credential.d.ts.map +1 -1
  4. package/dist/Credential.js +18 -2
  5. package/dist/Credential.js.map +1 -1
  6. package/dist/bin.js +1 -1
  7. package/dist/bin.js.map +1 -1
  8. package/dist/cli/cli.d.ts.map +1 -1
  9. package/dist/cli/cli.js +166 -54
  10. package/dist/cli/cli.js.map +1 -1
  11. package/dist/discovery/Discovery.d.ts +234 -18
  12. package/dist/discovery/Discovery.d.ts.map +1 -1
  13. package/dist/discovery/Discovery.js +24 -2
  14. package/dist/discovery/Discovery.js.map +1 -1
  15. package/dist/discovery/OpenApi.d.ts +1 -1
  16. package/dist/discovery/OpenApi.d.ts.map +1 -1
  17. package/dist/discovery/OpenApi.js +2 -1
  18. package/dist/discovery/OpenApi.js.map +1 -1
  19. package/dist/discovery/Validate.d.ts.map +1 -1
  20. package/dist/discovery/Validate.js +2 -1
  21. package/dist/discovery/Validate.js.map +1 -1
  22. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  23. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  24. package/dist/stripe/server/internal/html.gen.js +1 -1
  25. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  26. package/dist/tempo/client/Charge.js +1 -1
  27. package/dist/tempo/client/Charge.js.map +1 -1
  28. package/dist/tempo/internal/proof.d.ts +6 -2
  29. package/dist/tempo/internal/proof.d.ts.map +1 -1
  30. package/dist/tempo/internal/proof.js +7 -4
  31. package/dist/tempo/internal/proof.js.map +1 -1
  32. package/dist/tempo/server/Charge.d.ts.map +1 -1
  33. package/dist/tempo/server/Charge.js +4 -3
  34. package/dist/tempo/server/Charge.js.map +1 -1
  35. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  36. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  37. package/dist/tempo/server/internal/html.gen.js +1 -1
  38. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/Challenge.test.ts +45 -0
  41. package/src/Credential.test.ts +66 -0
  42. package/src/Credential.ts +23 -3
  43. package/src/bin.ts +1 -1
  44. package/src/cli/cli.ts +194 -58
  45. package/src/cli/mcp.test.ts +233 -0
  46. package/src/discovery/Discovery.test.ts +66 -4
  47. package/src/discovery/Discovery.ts +40 -7
  48. package/src/discovery/OpenApi.test.ts +61 -33
  49. package/src/discovery/OpenApi.ts +2 -2
  50. package/src/discovery/Validate.test.ts +117 -0
  51. package/src/discovery/Validate.ts +2 -1
  52. package/src/middlewares/elysia.test.ts +1 -1
  53. package/src/middlewares/express.test.ts +1 -1
  54. package/src/middlewares/hono.test.ts +1 -1
  55. package/src/middlewares/nextjs.test.ts +1 -1
  56. package/src/proxy/Proxy.test.ts +3 -3
  57. package/src/stripe/server/internal/html.gen.ts +1 -1
  58. package/src/tempo/client/Charge.ts +1 -1
  59. package/src/tempo/internal/proof.test.ts +11 -5
  60. package/src/tempo/internal/proof.ts +7 -4
  61. package/src/tempo/server/Charge.test.ts +51 -15
  62. package/src/tempo/server/Charge.ts +5 -3
  63. package/src/tempo/server/internal/html.gen.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # mppx
2
2
 
3
+ ## 0.6.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 3c5cd4b: Fixed zero-amount Tempo proof credentials to bind signatures to the challenge realm.
8
+ - 24604ff: Fixed MCP stdio startup and returned structured CLI command results without writing raw tool output to stdout.
9
+
10
+ ## 0.6.4
11
+
12
+ ### Patch Changes
13
+
14
+ - 0d1e548: Fixed credential `opaque` serialization to use the spec-compliant base64url string shape, while keeping deserialization backward-compatible with legacy object-shaped credentials.
15
+ - 9536014: Added canonical discovery output using `x-payment-info.offers[]` while continuing to accept the legacy flat shorthand during validation and parsing.
16
+
3
17
  ## 0.6.3
4
18
 
5
19
  ### Patch Changes
@@ -24,6 +24,8 @@ export declare class InvalidCredentialEncodingError extends Error {
24
24
  }
25
25
  /**
26
26
  * Deserializes an Authorization header value to a credential.
27
+ * Accepts the spec-compliant base64url `opaque` string shape and the legacy
28
+ * object-shaped `opaque` form emitted by older mppx versions.
27
29
  *
28
30
  * @param header - The Authorization header value.
29
31
  * @returns The deserialized credential.
@@ -79,6 +81,8 @@ export declare namespace from {
79
81
  export declare function fromRequest<payload = unknown>(request: Request): Credential<payload>;
80
82
  /**
81
83
  * Serializes a credential to the Authorization header format.
84
+ * When present, `challenge.opaque` is encoded as the base64url string required
85
+ * by the Payment auth credential format.
82
86
  *
83
87
  * @param credential - The credential to serialize.
84
88
  * @returns A string suitable for the Authorization header value.
@@ -1 +1 @@
1
- {"version":3,"file":"Credential.d.ts","sourceRoot":"","sources":["../src/Credential.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAG3C;;GAEG;AACH,MAAM,MAAM,UAAU,CACpB,OAAO,GAAG,OAAO,EACjB,SAAS,SAAS,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,IACzD;IACF,2CAA2C;IAC3C,SAAS,EAAE,SAAS,CAAA;IACpB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,SAAkB,IAAI,qCAAoC;;CAK3D;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,SAAkB,IAAI,+BAA8B;;CAKrD;AAED,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,SAAkB,IAAI,oCAAmC;;CAK1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAsBjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,UAAU,SAAS,IAAI,CAAC,UAAU,EAC3D,UAAU,EAAE,UAAU,GACrB,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAO5D;AAED,MAAM,CAAC,OAAO,WAAW,IAAI,CAAC;IAC5B,KAAK,UAAU,GAAG;QAChB,2CAA2C;QAC3C,SAAS,EAAE,SAAS,CAAC,SAAS,CAAA;QAC9B,qCAAqC;QACrC,OAAO,EAAE,OAAO,CAAA;QAChB,2EAA2E;QAC3E,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAMpF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAYxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE"}
1
+ {"version":3,"file":"Credential.d.ts","sourceRoot":"","sources":["../src/Credential.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAG3C;;GAEG;AACH,MAAM,MAAM,UAAU,CACpB,OAAO,GAAG,OAAO,EACjB,SAAS,SAAS,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,IACzD;IACF,2CAA2C;IAC3C,SAAS,EAAE,SAAS,CAAA;IACpB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,SAAkB,IAAI,qCAAoC;;CAK3D;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,SAAkB,IAAI,+BAA8B;;CAKrD;AAED,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,SAAkB,IAAI,oCAAmC;;CAK1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAoCjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,UAAU,SAAS,IAAI,CAAC,UAAU,EAC3D,UAAU,EAAE,UAAU,GACrB,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAO5D;AAED,MAAM,CAAC,OAAO,WAAW,IAAI,CAAC;IAC5B,KAAK,UAAU,GAAG;QAChB,2CAA2C;QAC3C,SAAS,EAAE,SAAS,CAAC,SAAS,CAAA;QAC9B,qCAAqC;QACrC,OAAO,EAAE,OAAO,CAAA;QAChB,2EAA2E;QAC3E,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAMpF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAcxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE"}
@@ -21,6 +21,8 @@ export class InvalidCredentialEncodingError extends Error {
21
21
  }
22
22
  /**
23
23
  * Deserializes an Authorization header value to a credential.
24
+ * Accepts the spec-compliant base64url `opaque` string shape and the legacy
25
+ * object-shaped `opaque` form emitted by older mppx versions.
24
26
  *
25
27
  * @param header - The Authorization header value.
26
28
  * @returns The deserialized credential.
@@ -41,6 +43,16 @@ export function deserialize(value) {
41
43
  const parsed = JSON.parse(json);
42
44
  const challenge = Challenge.Schema.parse({
43
45
  ...parsed.challenge,
46
+ ...(parsed.challenge.opaque !== undefined && {
47
+ // TODO: Drop the legacy object-shaped `opaque` fallback after old mppx
48
+ // clients are no longer in circulation. Older mppx versions echoed
49
+ // `opaque` as an expanded JSON object in credentials, but the Payment
50
+ // auth spec requires clients to return the original base64url string
51
+ // unchanged in the credential challenge object.
52
+ opaque: typeof parsed.challenge.opaque === 'string'
53
+ ? PaymentRequest.deserialize(parsed.challenge.opaque)
54
+ : parsed.challenge.opaque,
55
+ }),
44
56
  request: PaymentRequest.deserialize(parsed.challenge.request),
45
57
  });
46
58
  return {
@@ -101,6 +113,8 @@ export function fromRequest(request) {
101
113
  }
102
114
  /**
103
115
  * Serializes a credential to the Authorization header format.
116
+ * When present, `challenge.opaque` is encoded as the base64url string required
117
+ * by the Payment auth credential format.
104
118
  *
105
119
  * @param credential - The credential to serialize.
106
120
  * @returns A string suitable for the Authorization header value.
@@ -114,10 +128,12 @@ export function fromRequest(request) {
114
128
  * ```
115
129
  */
116
130
  export function serialize(credential) {
131
+ const { opaque, request, ...challenge } = credential.challenge;
117
132
  const wire = {
118
133
  challenge: {
119
- ...credential.challenge,
120
- request: PaymentRequest.serialize(credential.challenge.request),
134
+ ...challenge,
135
+ ...(opaque !== undefined && { opaque: PaymentRequest.serialize(opaque) }),
136
+ request: PaymentRequest.serialize(request),
121
137
  },
122
138
  payload: credential.payload,
123
139
  ...(credential.source && { source: credential.source }),
@@ -1 +1 @@
1
- {"version":3,"file":"Credential.js","sourceRoot":"","sources":["../src/Credential.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Credential.js","sourceRoot":"","sources":["../src/Credential.ts"],"names":[],"mappings":"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"}
package/dist/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import cli from './cli/cli.js';
3
- cli.serve().then(() => process.exit(0));
3
+ await cli.serve();
4
4
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B,MAAM,GAAG,CAAC,KAAK,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAa,MAAM,OAAO,CAAA;AAsCtC,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;wBAscP,CAAA;AA6oBF,eAAe,GAAG,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAa,MAAM,OAAO,CAAA;AA6EtC,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;wBAscP,CAAA;AA8uBF,eAAe,GAAG,CAAA"}
package/dist/cli/cli.js CHANGED
@@ -15,6 +15,37 @@ import { loadConfig, resolveAcceptPayment, selectChallenge } from './internal.js
15
15
  import { readTempoKeystore, resolveTempoAccount } from './plugins/tempo.js';
16
16
  import { chainName, confirm, decodeMemo, fetchBalanceLines, fmtBalance, fmtChallengeValue, fmtRequestValue, isTempoAccount, link, parseMethodOpts, pc, printRequestHeaders, printResponseHeaders, prompt, resolveChain, resolveRpcUrl, } from './utils.js';
17
17
  const packageJson = createRequire(import.meta.url)('../../package.json');
18
+ const accountSummarySchema = z.object({
19
+ address: z.string(),
20
+ isDefault: z.boolean().optional(),
21
+ name: z.string(),
22
+ source: z.string().optional(),
23
+ });
24
+ const accountViewSchema = z.object({
25
+ address: z.string(),
26
+ balances: z.array(z.string()).optional(),
27
+ name: z.string(),
28
+ type: z.string().optional(),
29
+ });
30
+ const discoveryIssueSchema = z.object({
31
+ message: z.string(),
32
+ path: z.string(),
33
+ severity: z.string(),
34
+ });
35
+ function shouldReturnStructured(c) {
36
+ return c.format === 'json' && c.formatExplicit;
37
+ }
38
+ function outputResult(c, data, print) {
39
+ if (shouldReturnStructured(c))
40
+ return c.ok(data);
41
+ print();
42
+ return undefined;
43
+ }
44
+ function canReadCommandStdin() {
45
+ if (process.stdin.isTTY !== false)
46
+ return false;
47
+ return process.stdin.listenerCount('data') === 0 && process.stdin.listenerCount('readable') === 0;
48
+ }
18
49
  const cli = Cli.create('mppx', {
19
50
  version: packageJson.version,
20
51
  description: 'Make HTTP requests with automatic payment handling',
@@ -468,26 +499,40 @@ const account = Cli.create('account', {
468
499
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
469
500
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
470
501
  }),
502
+ output: z.object({ address: z.string(), name: z.string() }),
471
503
  alias: { account: 'a', rpcUrl: 'r' },
472
504
  async run(c) {
505
+ const structured = shouldReturnStructured(c);
473
506
  let resolvedName = c.options.account;
474
507
  if (!resolvedName) {
475
508
  const existing = await createKeychain().list();
476
509
  if (existing.length === 0)
477
510
  resolvedName = 'main';
478
511
  else {
512
+ if (structured)
513
+ return c.error({
514
+ code: 'ACCOUNT_REQUIRED',
515
+ message: 'Account name is required in structured mode.',
516
+ exitCode: 2,
517
+ });
479
518
  const input = await prompt('Account name');
480
519
  if (!input)
481
- return;
520
+ return undefined;
482
521
  resolvedName = input;
483
522
  }
484
523
  }
485
524
  let keychain = createKeychain(resolvedName);
486
525
  while (await keychain.get()) {
526
+ if (structured)
527
+ return c.error({
528
+ code: 'ACCOUNT_EXISTS',
529
+ message: `Account "${resolvedName}" already exists.`,
530
+ exitCode: 1,
531
+ });
487
532
  process.stderr.write(`${pc.dim(`Account "${resolvedName}" already exists.`)}\n\n`);
488
533
  const input = await prompt('Enter different name');
489
534
  if (!input)
490
- return;
535
+ return undefined;
491
536
  resolvedName = input;
492
537
  keychain = createKeychain(resolvedName);
493
538
  }
@@ -497,16 +542,18 @@ const account = Cli.create('account', {
497
542
  const accounts = await createKeychain().list();
498
543
  if (accounts.length === 1)
499
544
  createDefaultStore().set(resolvedName);
500
- console.log(`Account "${resolvedName}" saved to keychain.`);
501
545
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url;
502
546
  const addrDisplay = explorerUrl
503
547
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
504
548
  : acct.address;
505
- console.log(pc.dim(`Address ${addrDisplay}`));
506
549
  const rpcUrl = resolveRpcUrl(c.options.rpcUrl);
507
550
  resolveChain({ rpcUrl })
508
551
  .then((chain) => createClient({ chain, transport: http(rpcUrl) }))
509
552
  .then((client) => import('viem/tempo').then(({ Actions }) => Actions.faucet.fund(client, { account: acct }).catch(() => { })));
553
+ return outputResult(c, { address: acct.address, name: resolvedName }, () => {
554
+ console.log(`Account "${resolvedName}" saved to keychain.`);
555
+ console.log(pc.dim(`Address ${addrDisplay}`));
556
+ });
510
557
  },
511
558
  })
512
559
  .command('default', {
@@ -514,6 +561,7 @@ const account = Cli.create('account', {
514
561
  options: z.object({
515
562
  account: z.string().describe('Account name'),
516
563
  }),
564
+ output: z.object({ name: z.string() }),
517
565
  alias: { account: 'a' },
518
566
  async run(c) {
519
567
  const accountName = c.options.account;
@@ -527,8 +575,9 @@ const account = Cli.create('account', {
527
575
  });
528
576
  }
529
577
  createDefaultStore().set(accountName);
530
- console.log(`Default account set to "${accountName}"`);
531
- return;
578
+ return outputResult(c, { name: accountName }, () => {
579
+ console.log(`Default account set to "${accountName}"`);
580
+ });
532
581
  }
533
582
  const key = await createKeychain(accountName).get();
534
583
  if (!key) {
@@ -539,7 +588,9 @@ const account = Cli.create('account', {
539
588
  });
540
589
  }
541
590
  createDefaultStore().set(accountName);
542
- console.log(`Default account set to "${accountName}"`);
591
+ return outputResult(c, { name: accountName }, () => {
592
+ console.log(`Default account set to "${accountName}"`);
593
+ });
543
594
  },
544
595
  })
545
596
  .command('delete', {
@@ -548,6 +599,7 @@ const account = Cli.create('account', {
548
599
  account: z.string().describe('Account name'),
549
600
  yes: z.boolean().optional().describe('DANGER!! Skip confirmation prompts'),
550
601
  }),
602
+ output: z.object({ defaultAccount: z.string().optional(), name: z.string() }),
551
603
  alias: { account: 'a' },
552
604
  async run(c) {
553
605
  const keychain = createKeychain(c.options.account);
@@ -562,6 +614,12 @@ const account = Cli.create('account', {
562
614
  const acct = privateKeyToAccount(key);
563
615
  const balanceLines = await fetchBalanceLines(acct.address, { includeTestnet: false });
564
616
  if (!c.options.yes) {
617
+ if (shouldReturnStructured(c))
618
+ return c.error({
619
+ code: 'CONFIRMATION_REQUIRED',
620
+ message: 'Pass --yes to delete an account in structured mode.',
621
+ exitCode: 2,
622
+ });
565
623
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url;
566
624
  const addrDisplay = explorerUrl
567
625
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
@@ -574,22 +632,27 @@ const account = Cli.create('account', {
574
632
  const confirmed = await confirm('Confirm delete?');
575
633
  if (!confirmed) {
576
634
  console.log('Canceled');
577
- return;
635
+ return undefined;
578
636
  }
579
637
  }
580
638
  await keychain.delete();
581
639
  const currentDefault = createDefaultStore().get();
640
+ let defaultAccount;
582
641
  if (currentDefault === c.options.account) {
583
642
  const remaining = await createKeychain().list();
584
643
  if (remaining.length > 0) {
585
- createDefaultStore().set(remaining[0]);
586
- console.log(`Default account set to "${remaining[0]}"`);
644
+ defaultAccount = remaining[0];
645
+ createDefaultStore().set(defaultAccount);
587
646
  }
588
647
  else {
589
648
  createDefaultStore().clear();
590
649
  }
591
650
  }
592
- console.log(`Account "${c.options.account}" deleted`);
651
+ return outputResult(c, { defaultAccount, name: c.options.account }, () => {
652
+ if (defaultAccount)
653
+ console.log(`Default account set to "${defaultAccount}"`);
654
+ console.log(`Account "${c.options.account}" deleted`);
655
+ });
593
656
  },
594
657
  })
595
658
  .command('fund', {
@@ -598,8 +661,10 @@ const account = Cli.create('account', {
598
661
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
599
662
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
600
663
  }),
664
+ output: z.object({ account: z.string(), chain: z.string(), transactions: z.array(z.string()) }),
601
665
  alias: { account: 'a', rpcUrl: 'r' },
602
666
  async run(c) {
667
+ const structured = shouldReturnStructured(c);
603
668
  const accountName = resolveAccountName(c.options.account);
604
669
  const keychain = createKeychain(accountName);
605
670
  const key = await keychain.get();
@@ -617,27 +682,40 @@ const account = Cli.create('account', {
617
682
  const rpcUrl = resolveRpcUrl(c.options.rpcUrl);
618
683
  const chain = await resolveChain({ rpcUrl });
619
684
  const client = createClient({ chain, transport: http(rpcUrl) });
620
- console.log(`Funding "${accountName}" on ${chainName(chain)}`);
685
+ if (!structured)
686
+ console.log(`Funding "${accountName}" on ${chainName(chain)}`);
621
687
  try {
622
688
  const { Actions } = await import('viem/tempo');
623
689
  const hashes = await Actions.faucet.fund(client, { account: acct });
624
690
  const explorerUrl = chain.blockExplorers?.default?.url;
625
- for (const hash of hashes) {
626
- const label = explorerUrl ? link(`${explorerUrl}/tx/${hash}`, pc.gray(hash)) : hash;
627
- console.log(` ${label}`);
691
+ if (!structured) {
692
+ for (const hash of hashes) {
693
+ const label = explorerUrl ? link(`${explorerUrl}/tx/${hash}`, pc.gray(hash)) : hash;
694
+ console.log(` ${label}`);
695
+ }
628
696
  }
629
697
  const { waitForTransactionReceipt } = await import('viem/actions');
630
698
  await Promise.all(hashes.map((hash) => waitForTransactionReceipt(client, { hash })));
631
- console.log('Funded successfully');
699
+ return outputResult(c, { account: accountName, chain: chainName(chain), transactions: [...hashes] }, () => {
700
+ console.log('Funded successfully');
701
+ });
632
702
  }
633
703
  catch (err) {
704
+ if (structured)
705
+ return c.error({
706
+ code: 'FUNDING_FAILED',
707
+ message: err instanceof Error ? err.message : String(err),
708
+ exitCode: 1,
709
+ });
634
710
  console.error('Funding failed:', err instanceof Error ? err.message : err);
711
+ return undefined;
635
712
  }
636
713
  },
637
714
  })
638
715
  .command('list', {
639
716
  description: 'List all accounts',
640
- async run() {
717
+ output: z.object({ accounts: z.array(accountSummarySchema) }),
718
+ async run(c) {
641
719
  const currentDefault = createDefaultStore().get();
642
720
  const accounts = (await createKeychain().list()).sort();
643
721
  const resolved = [];
@@ -658,21 +736,29 @@ const account = Cli.create('account', {
658
736
  resolved.push({ name: tempoName, address: entry.wallet_address, source: 'tempo wallet' });
659
737
  }
660
738
  if (resolved.length === 0) {
661
- console.log(`No accounts found.`);
662
- return;
739
+ return outputResult(c, { accounts: [] }, () => {
740
+ console.log(`No accounts found.`);
741
+ });
663
742
  }
664
743
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url;
665
744
  const maxWidth = Math.max(...resolved.map((e) => e.name.length + (e.name === currentDefault ? 1 : 0)));
666
- for (const entry of resolved) {
667
- const isDefault = entry.name === currentDefault;
668
- const label = isDefault ? `${entry.name}${pc.dim('*')}` : entry.name;
669
- const width = entry.name.length + (isDefault ? 1 : 0);
670
- const addrDisplay = explorerUrl
671
- ? link(`${explorerUrl}/address/${entry.address}`, entry.address)
672
- : entry.address;
673
- const sourceLabel = entry.source ? ` ${pc.dim(`(${entry.source})`)}` : '';
674
- console.log(`${label}${' '.repeat(maxWidth - width + 2)}${pc.dim(addrDisplay)}${sourceLabel}`);
675
- }
745
+ return outputResult(c, {
746
+ accounts: resolved.map((entry) => ({
747
+ ...entry,
748
+ ...(entry.name === currentDefault ? { isDefault: true } : undefined),
749
+ })),
750
+ }, () => {
751
+ for (const entry of resolved) {
752
+ const isDefault = entry.name === currentDefault;
753
+ const label = isDefault ? `${entry.name}${pc.dim('*')}` : entry.name;
754
+ const width = entry.name.length + (isDefault ? 1 : 0);
755
+ const addrDisplay = explorerUrl
756
+ ? link(`${explorerUrl}/address/${entry.address}`, entry.address)
757
+ : entry.address;
758
+ const sourceLabel = entry.source ? ` ${pc.dim(`(${entry.source})`)}` : '';
759
+ console.log(`${label}${' '.repeat(maxWidth - width + 2)}${pc.dim(addrDisplay)}${sourceLabel}`);
760
+ }
761
+ });
676
762
  },
677
763
  })
678
764
  .command('export', {
@@ -680,6 +766,7 @@ const account = Cli.create('account', {
680
766
  options: z.object({
681
767
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
682
768
  }),
769
+ output: z.object({ privateKey: z.string() }),
683
770
  alias: { account: 'a' },
684
771
  async run(c) {
685
772
  const accountName = resolveAccountName(c.options.account);
@@ -701,7 +788,9 @@ const account = Cli.create('account', {
701
788
  else
702
789
  return c.error({ code: 'ACCOUNT_NOT_FOUND', message: 'No account found.', exitCode: 69 });
703
790
  }
704
- console.log(key);
791
+ return outputResult(c, { privateKey: key }, () => {
792
+ console.log(key);
793
+ });
705
794
  },
706
795
  })
707
796
  .command('view', {
@@ -710,6 +799,7 @@ const account = Cli.create('account', {
710
799
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
711
800
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
712
801
  }),
802
+ output: accountViewSchema,
713
803
  alias: { account: 'a', rpcUrl: 'r' },
714
804
  async run(c) {
715
805
  const accountName = resolveAccountName(c.options.account);
@@ -729,13 +819,19 @@ const account = Cli.create('account', {
729
819
  const addrDisplay = explorerUrl
730
820
  ? link(`${explorerUrl}/address/${address}`, address)
731
821
  : address;
732
- console.log(`${pc.dim('Address')} ${addrDisplay}`);
733
822
  const balanceLines = await fetchBalanceLines(address, chain && rpcUrl ? { chain, rpcUrl } : undefined);
734
- for (let i = 0; i < balanceLines.length; i++)
735
- console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`);
736
- console.log(`${pc.dim('Name')} ${accountName}`);
737
- console.log(`${pc.dim('Type')} ${tempoEntry.wallet_type} ${pc.dim('(tempo wallet)')}`);
738
- return;
823
+ return outputResult(c, {
824
+ address,
825
+ balances: balanceLines,
826
+ name: accountName,
827
+ type: `${tempoEntry.wallet_type} (tempo wallet)`,
828
+ }, () => {
829
+ console.log(`${pc.dim('Address')} ${addrDisplay}`);
830
+ for (let i = 0; i < balanceLines.length; i++)
831
+ console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`);
832
+ console.log(`${pc.dim('Name')} ${accountName}`);
833
+ console.log(`${pc.dim('Type')} ${tempoEntry.wallet_type} ${pc.dim('(tempo wallet)')}`);
834
+ });
739
835
  }
740
836
  const keychain = createKeychain(accountName);
741
837
  const key = await keychain.get();
@@ -756,11 +852,13 @@ const account = Cli.create('account', {
756
852
  const addrDisplay = explorerUrl
757
853
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
758
854
  : acct.address;
759
- console.log(`${pc.dim('Address')} ${addrDisplay}`);
760
855
  const balanceLines = await fetchBalanceLines(acct.address, chain && rpcUrl ? { chain, rpcUrl } : undefined);
761
- for (let i = 0; i < balanceLines.length; i++)
762
- console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`);
763
- console.log(`${pc.dim('Name')} ${accountName}`);
856
+ return outputResult(c, { address: acct.address, balances: balanceLines, name: accountName }, () => {
857
+ console.log(`${pc.dim('Address')} ${addrDisplay}`);
858
+ for (let i = 0; i < balanceLines.length; i++)
859
+ console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`);
860
+ console.log(`${pc.dim('Name')} ${accountName}`);
861
+ });
764
862
  },
765
863
  });
766
864
  const sign = Cli.create('sign', {
@@ -783,6 +881,7 @@ const sign = Cli.create('sign', {
783
881
  .optional()
784
882
  .describe('RPC endpoint, defaults to public RPC for chain (env: MPPX_RPC_URL)'),
785
883
  }),
884
+ output: z.object({ authorization: z.string() }),
786
885
  alias: {
787
886
  account: 'a',
788
887
  challenge: 'C',
@@ -792,7 +891,7 @@ const sign = Cli.create('sign', {
792
891
  },
793
892
  async run(c) {
794
893
  const raw = c.options.challenge ||
795
- (process.stdin.isTTY === false
894
+ (canReadCommandStdin()
796
895
  ? await new Promise((resolve, reject) => {
797
896
  let data = '';
798
897
  process.stdin.setEncoding('utf-8');
@@ -823,7 +922,7 @@ const sign = Cli.create('sign', {
823
922
  }
824
923
  if (c.options.dryRun) {
825
924
  process.stderr.write('Challenge is valid.\n');
826
- return;
925
+ return undefined;
827
926
  }
828
927
  const loaded = await loadConfig(c.options.config);
829
928
  const selected = selectChallenge(challenges, loaded?.config);
@@ -870,12 +969,9 @@ const sign = Cli.create('sign', {
870
969
  exitCode: 2,
871
970
  });
872
971
  }
873
- if (c.format === 'json') {
874
- console.log(JSON.stringify({ authorization: credential }));
875
- }
876
- else {
972
+ return outputResult(c, { authorization: credential }, () => {
877
973
  console.log(credential);
878
- }
974
+ });
879
975
  },
880
976
  });
881
977
  const init = Cli.create('init', {
@@ -883,6 +979,7 @@ const init = Cli.create('init', {
883
979
  options: z.object({
884
980
  force: z.boolean().optional().describe('Overwrite existing config file'),
885
981
  }),
982
+ output: z.object({ file: z.string() }),
886
983
  alias: { force: 'f' },
887
984
  async run(c) {
888
985
  const cwd = process.cwd();
@@ -915,7 +1012,9 @@ export default defineConfig({
915
1012
  })
916
1013
  `;
917
1014
  fs.writeFileSync(dest, template);
918
- console.log(`Created ${filename}`);
1015
+ return outputResult(c, { file: dest }, () => {
1016
+ console.log(`Created ${filename}`);
1017
+ });
919
1018
  },
920
1019
  });
921
1020
  const discover = Cli.create('discover', {
@@ -929,6 +1028,7 @@ const discover = Cli.create('discover', {
929
1028
  options: z.object({
930
1029
  output: z.string().optional().describe('Write output to a file instead of stdout'),
931
1030
  }),
1031
+ output: z.record(z.string(), z.unknown()),
932
1032
  alias: { output: 'o' },
933
1033
  async run(c) {
934
1034
  const modulePath = path.resolve(c.args.module);
@@ -974,9 +1074,12 @@ const discover = Cli.create('discover', {
974
1074
  const outPath = path.resolve(c.options.output);
975
1075
  fs.writeFileSync(outPath, `${json}\n`);
976
1076
  process.stderr.write(`Wrote ${outPath}\n`);
1077
+ return outputResult(c, doc, () => { });
977
1078
  }
978
1079
  else {
979
- console.log(json);
1080
+ return outputResult(c, doc, () => {
1081
+ console.log(json);
1082
+ });
980
1083
  }
981
1084
  },
982
1085
  })
@@ -985,6 +1088,12 @@ const discover = Cli.create('discover', {
985
1088
  args: z.object({
986
1089
  input: z.string().describe('Path or URL to a discovery document'),
987
1090
  }),
1091
+ output: z.object({
1092
+ errorCount: z.number(),
1093
+ issues: z.array(discoveryIssueSchema),
1094
+ valid: z.boolean(),
1095
+ warningCount: z.number(),
1096
+ }),
988
1097
  async run(c) {
989
1098
  const input = c.args.input;
990
1099
  let raw;
@@ -1055,8 +1164,9 @@ const discover = Cli.create('discover', {
1055
1164
  });
1056
1165
  }
1057
1166
  const issues = validateDiscovery(doc);
1058
- for (const issue of issues)
1059
- console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`);
1167
+ if (!shouldReturnStructured(c))
1168
+ for (const issue of issues)
1169
+ console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`);
1060
1170
  const errorCount = issues.filter((issue) => issue.severity === 'error').length;
1061
1171
  const warningCount = issues.filter((issue) => issue.severity === 'warning').length;
1062
1172
  if (errorCount > 0) {
@@ -1066,9 +1176,11 @@ const discover = Cli.create('discover', {
1066
1176
  exitCode: 1,
1067
1177
  });
1068
1178
  }
1069
- console.log(warningCount > 0
1070
- ? `Discovery document is valid with ${warningCount} warning(s).`
1071
- : 'Discovery document is valid.');
1179
+ return outputResult(c, { errorCount, issues, valid: true, warningCount }, () => {
1180
+ console.log(warningCount > 0
1181
+ ? `Discovery document is valid with ${warningCount} warning(s).`
1182
+ : 'Discovery document is valid.');
1183
+ });
1072
1184
  },
1073
1185
  });
1074
1186
  cli.command(account);