mppx 0.6.2 → 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 (76) hide show
  1. package/CHANGELOG.md +20 -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/client/SessionManager.d.ts.map +1 -1
  29. package/dist/tempo/client/SessionManager.js +15 -1
  30. package/dist/tempo/client/SessionManager.js.map +1 -1
  31. package/dist/tempo/internal/proof.d.ts +6 -2
  32. package/dist/tempo/internal/proof.d.ts.map +1 -1
  33. package/dist/tempo/internal/proof.js +7 -4
  34. package/dist/tempo/internal/proof.js.map +1 -1
  35. package/dist/tempo/server/Charge.d.ts.map +1 -1
  36. package/dist/tempo/server/Charge.js +4 -3
  37. package/dist/tempo/server/Charge.js.map +1 -1
  38. package/dist/tempo/server/Session.d.ts.map +1 -1
  39. package/dist/tempo/server/Session.js +23 -0
  40. package/dist/tempo/server/Session.js.map +1 -1
  41. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  42. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  43. package/dist/tempo/server/internal/html.gen.js +1 -1
  44. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  45. package/dist/tempo/session/Chain.d.ts.map +1 -1
  46. package/dist/tempo/session/Chain.js +29 -0
  47. package/dist/tempo/session/Chain.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/Challenge.test.ts +45 -0
  50. package/src/Credential.test.ts +66 -0
  51. package/src/Credential.ts +23 -3
  52. package/src/bin.ts +1 -1
  53. package/src/cli/cli.ts +194 -58
  54. package/src/cli/mcp.test.ts +233 -0
  55. package/src/discovery/Discovery.test.ts +66 -4
  56. package/src/discovery/Discovery.ts +40 -7
  57. package/src/discovery/OpenApi.test.ts +61 -33
  58. package/src/discovery/OpenApi.ts +2 -2
  59. package/src/discovery/Validate.test.ts +117 -0
  60. package/src/discovery/Validate.ts +2 -1
  61. package/src/middlewares/elysia.test.ts +1 -1
  62. package/src/middlewares/express.test.ts +1 -1
  63. package/src/middlewares/hono.test.ts +1 -1
  64. package/src/middlewares/nextjs.test.ts +1 -1
  65. package/src/proxy/Proxy.test.ts +3 -3
  66. package/src/stripe/server/internal/html.gen.ts +1 -1
  67. package/src/tempo/client/Charge.ts +1 -1
  68. package/src/tempo/client/SessionManager.ts +13 -1
  69. package/src/tempo/internal/proof.test.ts +11 -5
  70. package/src/tempo/internal/proof.ts +7 -4
  71. package/src/tempo/server/Charge.test.ts +51 -15
  72. package/src/tempo/server/Charge.ts +5 -3
  73. package/src/tempo/server/Session.test.ts +265 -3
  74. package/src/tempo/server/Session.ts +30 -0
  75. package/src/tempo/server/internal/html.gen.ts +1 -1
  76. package/src/tempo/session/Chain.ts +55 -0
package/src/cli/cli.ts CHANGED
@@ -40,6 +40,45 @@ const packageJson = createRequire(import.meta.url)('../../package.json') as {
40
40
  version: string
41
41
  }
42
42
 
43
+ const accountSummarySchema = z.object({
44
+ address: z.string(),
45
+ isDefault: z.boolean().optional(),
46
+ name: z.string(),
47
+ source: z.string().optional(),
48
+ })
49
+
50
+ const accountViewSchema = z.object({
51
+ address: z.string(),
52
+ balances: z.array(z.string()).optional(),
53
+ name: z.string(),
54
+ type: z.string().optional(),
55
+ })
56
+
57
+ const discoveryIssueSchema = z.object({
58
+ message: z.string(),
59
+ path: z.string(),
60
+ severity: z.string(),
61
+ })
62
+
63
+ function shouldReturnStructured(c: { format: string; formatExplicit: boolean }) {
64
+ return c.format === 'json' && c.formatExplicit
65
+ }
66
+
67
+ function outputResult<Data>(
68
+ c: { format: string; formatExplicit: boolean; ok: (data: Data) => never },
69
+ data: Data,
70
+ print: () => void,
71
+ ): Data {
72
+ if (shouldReturnStructured(c)) return c.ok(data)
73
+ print()
74
+ return undefined as unknown as Data
75
+ }
76
+
77
+ function canReadCommandStdin() {
78
+ if (process.stdin.isTTY !== false) return false
79
+ return process.stdin.listenerCount('data') === 0 && process.stdin.listenerCount('readable') === 0
80
+ }
81
+
43
82
  const cli = Cli.create('mppx', {
44
83
  version: packageJson.version,
45
84
  description: 'Make HTTP requests with automatic payment handling',
@@ -505,23 +544,37 @@ const account = Cli.create('account', {
505
544
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
506
545
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
507
546
  }),
547
+ output: z.object({ address: z.string(), name: z.string() }),
508
548
  alias: { account: 'a', rpcUrl: 'r' },
509
549
  async run(c) {
550
+ const structured = shouldReturnStructured(c)
510
551
  let resolvedName = c.options.account
511
552
  if (!resolvedName) {
512
553
  const existing = await createKeychain().list()
513
554
  if (existing.length === 0) resolvedName = 'main'
514
555
  else {
556
+ if (structured)
557
+ return c.error({
558
+ code: 'ACCOUNT_REQUIRED',
559
+ message: 'Account name is required in structured mode.',
560
+ exitCode: 2,
561
+ })
515
562
  const input = await prompt('Account name')
516
- if (!input) return
563
+ if (!input) return undefined as never
517
564
  resolvedName = input
518
565
  }
519
566
  }
520
567
  let keychain = createKeychain(resolvedName)
521
568
  while (await keychain.get()) {
569
+ if (structured)
570
+ return c.error({
571
+ code: 'ACCOUNT_EXISTS',
572
+ message: `Account "${resolvedName}" already exists.`,
573
+ exitCode: 1,
574
+ })
522
575
  process.stderr.write(`${pc.dim(`Account "${resolvedName}" already exists.`)}\n\n`)
523
576
  const input = await prompt('Enter different name')
524
- if (!input) return
577
+ if (!input) return undefined as never
525
578
  resolvedName = input
526
579
  keychain = createKeychain(resolvedName)
527
580
  }
@@ -530,12 +583,10 @@ const account = Cli.create('account', {
530
583
  await keychain.set(privateKey)
531
584
  const accounts = await createKeychain().list()
532
585
  if (accounts.length === 1) createDefaultStore().set(resolvedName)
533
- console.log(`Account "${resolvedName}" saved to keychain.`)
534
586
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url
535
587
  const addrDisplay = explorerUrl
536
588
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
537
589
  : acct.address
538
- console.log(pc.dim(`Address ${addrDisplay}`))
539
590
  const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
540
591
  resolveChain({ rpcUrl })
541
592
  .then((chain) => createClient({ chain, transport: http(rpcUrl) }))
@@ -544,6 +595,10 @@ const account = Cli.create('account', {
544
595
  Actions.faucet.fund(client, { account: acct }).catch(() => {}),
545
596
  ),
546
597
  )
598
+ return outputResult(c, { address: acct.address, name: resolvedName }, () => {
599
+ console.log(`Account "${resolvedName}" saved to keychain.`)
600
+ console.log(pc.dim(`Address ${addrDisplay}`))
601
+ })
547
602
  },
548
603
  })
549
604
  .command('default', {
@@ -551,6 +606,7 @@ const account = Cli.create('account', {
551
606
  options: z.object({
552
607
  account: z.string().describe('Account name'),
553
608
  }),
609
+ output: z.object({ name: z.string() }),
554
610
  alias: { account: 'a' },
555
611
  async run(c) {
556
612
  const accountName = c.options.account
@@ -564,8 +620,9 @@ const account = Cli.create('account', {
564
620
  })
565
621
  }
566
622
  createDefaultStore().set(accountName)
567
- console.log(`Default account set to "${accountName}"`)
568
- return
623
+ return outputResult(c, { name: accountName }, () => {
624
+ console.log(`Default account set to "${accountName}"`)
625
+ })
569
626
  }
570
627
  const key = await createKeychain(accountName).get()
571
628
  if (!key) {
@@ -576,7 +633,9 @@ const account = Cli.create('account', {
576
633
  })
577
634
  }
578
635
  createDefaultStore().set(accountName)
579
- console.log(`Default account set to "${accountName}"`)
636
+ return outputResult(c, { name: accountName }, () => {
637
+ console.log(`Default account set to "${accountName}"`)
638
+ })
580
639
  },
581
640
  })
582
641
  .command('delete', {
@@ -585,6 +644,7 @@ const account = Cli.create('account', {
585
644
  account: z.string().describe('Account name'),
586
645
  yes: z.boolean().optional().describe('DANGER!! Skip confirmation prompts'),
587
646
  }),
647
+ output: z.object({ defaultAccount: z.string().optional(), name: z.string() }),
588
648
  alias: { account: 'a' },
589
649
  async run(c) {
590
650
  const keychain = createKeychain(c.options.account)
@@ -599,6 +659,12 @@ const account = Cli.create('account', {
599
659
  const acct = privateKeyToAccount(key as `0x${string}`)
600
660
  const balanceLines = await fetchBalanceLines(acct.address, { includeTestnet: false })
601
661
  if (!c.options.yes) {
662
+ if (shouldReturnStructured(c))
663
+ return c.error({
664
+ code: 'CONFIRMATION_REQUIRED',
665
+ message: 'Pass --yes to delete an account in structured mode.',
666
+ exitCode: 2,
667
+ })
602
668
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url
603
669
  const addrDisplay = explorerUrl
604
670
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
@@ -611,21 +677,25 @@ const account = Cli.create('account', {
611
677
  const confirmed = await confirm('Confirm delete?')
612
678
  if (!confirmed) {
613
679
  console.log('Canceled')
614
- return
680
+ return undefined as never
615
681
  }
616
682
  }
617
683
  await keychain.delete()
618
684
  const currentDefault = createDefaultStore().get()
685
+ let defaultAccount: string | undefined
619
686
  if (currentDefault === c.options.account) {
620
687
  const remaining = await createKeychain().list()
621
688
  if (remaining.length > 0) {
622
- createDefaultStore().set(remaining[0]!)
623
- console.log(`Default account set to "${remaining[0]}"`)
689
+ defaultAccount = remaining[0]!
690
+ createDefaultStore().set(defaultAccount)
624
691
  } else {
625
692
  createDefaultStore().clear()
626
693
  }
627
694
  }
628
- console.log(`Account "${c.options.account}" deleted`)
695
+ return outputResult(c, { defaultAccount, name: c.options.account }, () => {
696
+ if (defaultAccount) console.log(`Default account set to "${defaultAccount}"`)
697
+ console.log(`Account "${c.options.account}" deleted`)
698
+ })
629
699
  },
630
700
  })
631
701
  .command('fund', {
@@ -634,8 +704,10 @@ const account = Cli.create('account', {
634
704
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
635
705
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
636
706
  }),
707
+ output: z.object({ account: z.string(), chain: z.string(), transactions: z.array(z.string()) }),
637
708
  alias: { account: 'a', rpcUrl: 'r' },
638
709
  async run(c) {
710
+ const structured = shouldReturnStructured(c)
639
711
  const accountName = resolveAccountName(c.options.account)
640
712
  const keychain = createKeychain(accountName)
641
713
  const key = await keychain.get()
@@ -653,26 +725,42 @@ const account = Cli.create('account', {
653
725
  const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
654
726
  const chain = await resolveChain({ rpcUrl })
655
727
  const client = createClient({ chain, transport: http(rpcUrl) })
656
- console.log(`Funding "${accountName}" on ${chainName(chain)}`)
728
+ if (!structured) console.log(`Funding "${accountName}" on ${chainName(chain)}`)
657
729
  try {
658
730
  const { Actions } = await import('viem/tempo')
659
731
  const hashes = await Actions.faucet.fund(client, { account: acct })
660
732
  const explorerUrl = chain.blockExplorers?.default?.url
661
- for (const hash of hashes) {
662
- const label = explorerUrl ? link(`${explorerUrl}/tx/${hash}`, pc.gray(hash)) : hash
663
- console.log(` ${label}`)
733
+ if (!structured) {
734
+ for (const hash of hashes) {
735
+ const label = explorerUrl ? link(`${explorerUrl}/tx/${hash}`, pc.gray(hash)) : hash
736
+ console.log(` ${label}`)
737
+ }
664
738
  }
665
739
  const { waitForTransactionReceipt } = await import('viem/actions')
666
740
  await Promise.all(hashes.map((hash) => waitForTransactionReceipt(client, { hash })))
667
- console.log('Funded successfully')
741
+ return outputResult(
742
+ c,
743
+ { account: accountName, chain: chainName(chain), transactions: [...hashes] },
744
+ () => {
745
+ console.log('Funded successfully')
746
+ },
747
+ )
668
748
  } catch (err) {
749
+ if (structured)
750
+ return c.error({
751
+ code: 'FUNDING_FAILED',
752
+ message: err instanceof Error ? err.message : String(err),
753
+ exitCode: 1,
754
+ })
669
755
  console.error('Funding failed:', err instanceof Error ? err.message : err)
756
+ return undefined as never
670
757
  }
671
758
  },
672
759
  })
673
760
  .command('list', {
674
761
  description: 'List all accounts',
675
- async run() {
762
+ output: z.object({ accounts: z.array(accountSummarySchema) }),
763
+ async run(c) {
676
764
  const currentDefault = createDefaultStore().get()
677
765
  const accounts = (await createKeychain().list()).sort()
678
766
  const resolved: { name: string; address: string; source?: string }[] = []
@@ -692,25 +780,37 @@ const account = Cli.create('account', {
692
780
  resolved.push({ name: tempoName, address: entry.wallet_address, source: 'tempo wallet' })
693
781
  }
694
782
  if (resolved.length === 0) {
695
- console.log(`No accounts found.`)
696
- return
783
+ return outputResult(c, { accounts: [] }, () => {
784
+ console.log(`No accounts found.`)
785
+ })
697
786
  }
698
787
  const explorerUrl = tempoMainnet.blockExplorers?.default?.url
699
788
  const maxWidth = Math.max(
700
789
  ...resolved.map((e) => e.name.length + (e.name === currentDefault ? 1 : 0)),
701
790
  )
702
- for (const entry of resolved) {
703
- const isDefault = entry.name === currentDefault
704
- const label = isDefault ? `${entry.name}${pc.dim('*')}` : entry.name
705
- const width = entry.name.length + (isDefault ? 1 : 0)
706
- const addrDisplay = explorerUrl
707
- ? link(`${explorerUrl}/address/${entry.address}`, entry.address)
708
- : entry.address
709
- const sourceLabel = entry.source ? ` ${pc.dim(`(${entry.source})`)}` : ''
710
- console.log(
711
- `${label}${' '.repeat(maxWidth - width + 2)}${pc.dim(addrDisplay)}${sourceLabel}`,
712
- )
713
- }
791
+ return outputResult(
792
+ c,
793
+ {
794
+ accounts: resolved.map((entry) => ({
795
+ ...entry,
796
+ ...(entry.name === currentDefault ? { isDefault: true } : undefined),
797
+ })),
798
+ },
799
+ () => {
800
+ for (const entry of resolved) {
801
+ const isDefault = entry.name === currentDefault
802
+ const label = isDefault ? `${entry.name}${pc.dim('*')}` : entry.name
803
+ const width = entry.name.length + (isDefault ? 1 : 0)
804
+ const addrDisplay = explorerUrl
805
+ ? link(`${explorerUrl}/address/${entry.address}`, entry.address)
806
+ : entry.address
807
+ const sourceLabel = entry.source ? ` ${pc.dim(`(${entry.source})`)}` : ''
808
+ console.log(
809
+ `${label}${' '.repeat(maxWidth - width + 2)}${pc.dim(addrDisplay)}${sourceLabel}`,
810
+ )
811
+ }
812
+ },
813
+ )
714
814
  },
715
815
  })
716
816
  .command('export', {
@@ -718,6 +818,7 @@ const account = Cli.create('account', {
718
818
  options: z.object({
719
819
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
720
820
  }),
821
+ output: z.object({ privateKey: z.string() }),
721
822
  alias: { account: 'a' },
722
823
  async run(c) {
723
824
  const accountName = resolveAccountName(c.options.account)
@@ -742,7 +843,9 @@ const account = Cli.create('account', {
742
843
  return c.error({ code: 'ACCOUNT_NOT_FOUND', message: 'No account found.', exitCode: 69 })
743
844
  }
744
845
 
745
- console.log(key)
846
+ return outputResult(c, { privateKey: key }, () => {
847
+ console.log(key)
848
+ })
746
849
  },
747
850
  })
748
851
  .command('view', {
@@ -751,6 +854,7 @@ const account = Cli.create('account', {
751
854
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
752
855
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
753
856
  }),
857
+ output: accountViewSchema,
754
858
  alias: { account: 'a', rpcUrl: 'r' },
755
859
  async run(c) {
756
860
  const accountName = resolveAccountName(c.options.account)
@@ -771,18 +875,29 @@ const account = Cli.create('account', {
771
875
  const addrDisplay = explorerUrl
772
876
  ? link(`${explorerUrl}/address/${address}`, address)
773
877
  : address
774
- console.log(`${pc.dim('Address')} ${addrDisplay}`)
775
878
 
776
879
  const balanceLines = await fetchBalanceLines(
777
880
  address,
778
881
  chain && rpcUrl ? { chain, rpcUrl } : undefined,
779
882
  )
780
- for (let i = 0; i < balanceLines.length; i++)
781
- console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`)
782
-
783
- console.log(`${pc.dim('Name')} ${accountName}`)
784
- console.log(`${pc.dim('Type')} ${tempoEntry.wallet_type} ${pc.dim('(tempo wallet)')}`)
785
- return
883
+ return outputResult(
884
+ c,
885
+ {
886
+ address,
887
+ balances: balanceLines,
888
+ name: accountName,
889
+ type: `${tempoEntry.wallet_type} (tempo wallet)`,
890
+ },
891
+ () => {
892
+ console.log(`${pc.dim('Address')} ${addrDisplay}`)
893
+ for (let i = 0; i < balanceLines.length; i++)
894
+ console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`)
895
+ console.log(`${pc.dim('Name')} ${accountName}`)
896
+ console.log(
897
+ `${pc.dim('Type')} ${tempoEntry.wallet_type} ${pc.dim('(tempo wallet)')}`,
898
+ )
899
+ },
900
+ )
786
901
  }
787
902
 
788
903
  const keychain = createKeychain(accountName)
@@ -804,16 +919,21 @@ const account = Cli.create('account', {
804
919
  const addrDisplay = explorerUrl
805
920
  ? link(`${explorerUrl}/address/${acct.address}`, acct.address)
806
921
  : acct.address
807
- console.log(`${pc.dim('Address')} ${addrDisplay}`)
808
922
 
809
923
  const balanceLines = await fetchBalanceLines(
810
924
  acct.address,
811
925
  chain && rpcUrl ? { chain, rpcUrl } : undefined,
812
926
  )
813
- for (let i = 0; i < balanceLines.length; i++)
814
- console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`)
815
-
816
- console.log(`${pc.dim('Name')} ${accountName}`)
927
+ return outputResult(
928
+ c,
929
+ { address: acct.address, balances: balanceLines, name: accountName },
930
+ () => {
931
+ console.log(`${pc.dim('Address')} ${addrDisplay}`)
932
+ for (let i = 0; i < balanceLines.length; i++)
933
+ console.log(`${pc.dim(i === 0 ? 'Balance' : ' ')} ${balanceLines[i]}`)
934
+ console.log(`${pc.dim('Name')} ${accountName}`)
935
+ },
936
+ )
817
937
  },
818
938
  })
819
939
 
@@ -837,6 +957,7 @@ const sign = Cli.create('sign', {
837
957
  .optional()
838
958
  .describe('RPC endpoint, defaults to public RPC for chain (env: MPPX_RPC_URL)'),
839
959
  }),
960
+ output: z.object({ authorization: z.string() }),
840
961
  alias: {
841
962
  account: 'a',
842
963
  challenge: 'C',
@@ -847,7 +968,7 @@ const sign = Cli.create('sign', {
847
968
  async run(c) {
848
969
  const raw =
849
970
  c.options.challenge ||
850
- (process.stdin.isTTY === false
971
+ (canReadCommandStdin()
851
972
  ? await new Promise<string>((resolve, reject) => {
852
973
  let data = ''
853
974
  process.stdin.setEncoding('utf-8')
@@ -879,7 +1000,7 @@ const sign = Cli.create('sign', {
879
1000
 
880
1001
  if (c.options.dryRun) {
881
1002
  process.stderr.write('Challenge is valid.\n')
882
- return
1003
+ return undefined as never
883
1004
  }
884
1005
 
885
1006
  const loaded = await loadConfig(c.options.config)
@@ -931,11 +1052,9 @@ const sign = Cli.create('sign', {
931
1052
  })
932
1053
  }
933
1054
 
934
- if (c.format === 'json') {
935
- console.log(JSON.stringify({ authorization: credential }))
936
- } else {
1055
+ return outputResult(c, { authorization: credential }, () => {
937
1056
  console.log(credential)
938
- }
1057
+ })
939
1058
  },
940
1059
  })
941
1060
 
@@ -944,6 +1063,7 @@ const init = Cli.create('init', {
944
1063
  options: z.object({
945
1064
  force: z.boolean().optional().describe('Overwrite existing config file'),
946
1065
  }),
1066
+ output: z.object({ file: z.string() }),
947
1067
  alias: { force: 'f' },
948
1068
  async run(c) {
949
1069
  const cwd = process.cwd()
@@ -978,7 +1098,9 @@ export default defineConfig({
978
1098
  `
979
1099
 
980
1100
  fs.writeFileSync(dest, template)
981
- console.log(`Created ${filename}`)
1101
+ return outputResult(c, { file: dest }, () => {
1102
+ console.log(`Created ${filename}`)
1103
+ })
982
1104
  },
983
1105
  })
984
1106
 
@@ -993,6 +1115,7 @@ const discover = Cli.create('discover', {
993
1115
  options: z.object({
994
1116
  output: z.string().optional().describe('Write output to a file instead of stdout'),
995
1117
  }),
1118
+ output: z.record(z.string(), z.unknown()),
996
1119
  alias: { output: 'o' },
997
1120
  async run(c) {
998
1121
  const modulePath = path.resolve(c.args.module)
@@ -1041,8 +1164,11 @@ const discover = Cli.create('discover', {
1041
1164
  const outPath = path.resolve(c.options.output)
1042
1165
  fs.writeFileSync(outPath, `${json}\n`)
1043
1166
  process.stderr.write(`Wrote ${outPath}\n`)
1167
+ return outputResult(c, doc, () => {})
1044
1168
  } else {
1045
- console.log(json)
1169
+ return outputResult(c, doc, () => {
1170
+ console.log(json)
1171
+ })
1046
1172
  }
1047
1173
  },
1048
1174
  })
@@ -1051,6 +1177,12 @@ const discover = Cli.create('discover', {
1051
1177
  args: z.object({
1052
1178
  input: z.string().describe('Path or URL to a discovery document'),
1053
1179
  }),
1180
+ output: z.object({
1181
+ errorCount: z.number(),
1182
+ issues: z.array(discoveryIssueSchema),
1183
+ valid: z.boolean(),
1184
+ warningCount: z.number(),
1185
+ }),
1054
1186
  async run(c) {
1055
1187
  const input = c.args.input
1056
1188
  let raw: string
@@ -1121,7 +1253,9 @@ const discover = Cli.create('discover', {
1121
1253
  }
1122
1254
 
1123
1255
  const issues = validateDiscovery(doc)
1124
- for (const issue of issues) console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`)
1256
+ if (!shouldReturnStructured(c))
1257
+ for (const issue of issues)
1258
+ console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`)
1125
1259
 
1126
1260
  const errorCount = issues.filter((issue) => issue.severity === 'error').length
1127
1261
  const warningCount = issues.filter((issue) => issue.severity === 'warning').length
@@ -1134,11 +1268,13 @@ const discover = Cli.create('discover', {
1134
1268
  })
1135
1269
  }
1136
1270
 
1137
- console.log(
1138
- warningCount > 0
1139
- ? `Discovery document is valid with ${warningCount} warning(s).`
1140
- : 'Discovery document is valid.',
1141
- )
1271
+ return outputResult(c, { errorCount, issues, valid: true, warningCount }, () => {
1272
+ console.log(
1273
+ warningCount > 0
1274
+ ? `Discovery document is valid with ${warningCount} warning(s).`
1275
+ : 'Discovery document is valid.',
1276
+ )
1277
+ })
1142
1278
  },
1143
1279
  })
1144
1280