@1sat/cli 0.0.51 → 0.0.54

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/src/help.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * Help text and version display for the 1sat CLI.
3
+ *
4
+ * COMMANDS is the single source of truth. Both text help and `--json`
5
+ * agent-discovery output are rendered from it.
3
6
  */
4
7
 
5
8
  import { readFileSync } from 'node:fs'
@@ -24,116 +27,731 @@ export function printVersion(): void {
24
27
  console.log(`1sat ${getVersion()}`)
25
28
  }
26
29
 
27
- export function printHelp(): void {
30
+ export interface ArgSpec {
31
+ flag: string
32
+ values?: string
33
+ description?: string
34
+ required?: boolean
35
+ }
36
+
37
+ export interface SubcommandSpec {
38
+ name: string
39
+ description: string
40
+ positional?: string
41
+ args?: ArgSpec[]
42
+ unavailable?: boolean
43
+ notes?: string
44
+ }
45
+
46
+ export interface CommandSpec {
47
+ group: string
48
+ name: string
49
+ description: string
50
+ positional?: string
51
+ args?: ArgSpec[]
52
+ subcommands?: SubcommandSpec[]
53
+ notes?: string
54
+ }
55
+
56
+ export const GLOBAL_OPTIONS: ArgSpec[] = [
57
+ { flag: '--json', description: 'Output as JSON' },
58
+ { flag: '--quiet', description: 'Suppress output (also -q)' },
59
+ { flag: '--yes', description: 'Skip confirmation prompts (also -y)' },
60
+ {
61
+ flag: '--chain',
62
+ values: '<main|test>',
63
+ description: 'Network (default: main)',
64
+ },
65
+ { flag: '--help', description: 'Show help (also -h)' },
66
+ { flag: '--version', description: 'Show version (also -v)' },
67
+ ]
68
+
69
+ export const ENV_VARS: { name: string; description: string }[] = [
70
+ {
71
+ name: 'PRIVATE_KEY_WIF',
72
+ description: 'WIF private key (bypasses encrypted keyfile)',
73
+ },
74
+ { name: 'ONESAT_PASSWORD', description: 'Password for encrypted keyfile' },
75
+ {
76
+ name: 'ONESAT_PORT',
77
+ description: 'Override server port (used by `1sat serve`)',
78
+ },
79
+ {
80
+ name: 'ONESAT_MCP_URL',
81
+ description:
82
+ 'Override wallet-desktop MCP URL (default http://127.0.0.1:3322)',
83
+ },
84
+ ]
85
+
86
+ export const COMMANDS: CommandSpec[] = [
87
+ // Setup
88
+ {
89
+ group: 'Setup',
90
+ name: 'init',
91
+ description:
92
+ 'Interactive wallet setup wizard (creates encrypted keyfile and config)',
93
+ },
94
+ {
95
+ group: 'Setup',
96
+ name: 'config',
97
+ description: 'Manage CLI configuration in ~/.1sat/cli/config.json',
98
+ subcommands: [
99
+ { name: 'show', description: 'Display current configuration' },
100
+ {
101
+ name: 'set',
102
+ description: 'Set a config value',
103
+ positional: '<dotted.path> <value>',
104
+ },
105
+ {
106
+ name: 'unset',
107
+ description: 'Remove a configuration key',
108
+ positional: '<dotted.path>',
109
+ },
110
+ { name: 'path', description: 'Print config directory path' },
111
+ ],
112
+ },
113
+ {
114
+ group: 'Setup',
115
+ name: 'remote',
116
+ description: 'Manage remote wallet storage',
117
+ subcommands: [
118
+ {
119
+ name: 'add',
120
+ description: 'Add a remote storage as backup',
121
+ positional: '<url>',
122
+ },
123
+ {
124
+ name: 'list',
125
+ description: 'List configured remotes and active storage',
126
+ },
127
+ {
128
+ name: 'delete',
129
+ description: 'Remove a remote from the backup list',
130
+ positional: '<url>',
131
+ },
132
+ {
133
+ name: 'set-active',
134
+ description: 'Switch active storage to a remote or back to local',
135
+ positional: '<url | local>',
136
+ },
137
+ {
138
+ name: 'status',
139
+ description: 'Fetch GET /account/status from a remote',
140
+ positional: '[url]',
141
+ },
142
+ {
143
+ name: 'topup',
144
+ description: 'Buy capacity on a remote',
145
+ positional: '[url]',
146
+ args: [{ flag: '--units', values: '<n>' }],
147
+ },
148
+ ],
149
+ },
150
+
151
+ // Wallet
152
+ {
153
+ group: 'Wallet',
154
+ name: 'wallet',
155
+ description: 'Wallet operations (balance, send, BRC-100 interface)',
156
+ subcommands: [
157
+ { name: 'balance', description: 'Show wallet balance in satoshis' },
158
+ {
159
+ name: 'address',
160
+ description: 'Show BRC-29 deposit address(es)',
161
+ args: [
162
+ {
163
+ flag: '--prefix',
164
+ values: '<p>',
165
+ description: 'BRC-29 prefix (default: 1sat)',
166
+ },
167
+ { flag: '--start-index', values: '<n>' },
168
+ { flag: '--count', values: '<n>' },
169
+ ],
170
+ },
171
+ {
172
+ name: 'send',
173
+ description:
174
+ 'Send BSV. Specify exactly one of --to, --script, or --data-asm',
175
+ args: [
176
+ { flag: '--to', values: '<address>' },
177
+ { flag: '--script', values: '<hex>' },
178
+ { flag: '--data-asm', values: '"<asm>"' },
179
+ {
180
+ flag: '--sats',
181
+ values: '<n>',
182
+ description: 'Required with --to or --script',
183
+ },
184
+ ],
185
+ },
186
+ {
187
+ name: 'send-all',
188
+ description: 'Send all BSV to an address (empties the wallet)',
189
+ args: [{ flag: '--to', values: '<address>', required: true }],
190
+ },
191
+ {
192
+ name: 'sync',
193
+ description: 'Sync inbound payments at BRC-29 deposit addresses',
194
+ args: [
195
+ { flag: '--prefix', values: '<p>' },
196
+ { flag: '--start-index', values: '<n>' },
197
+ { flag: '--count', values: '<n>' },
198
+ ],
199
+ },
200
+ {
201
+ name: 'info',
202
+ description: 'Show address, identity key, balance, and network',
203
+ },
204
+ {
205
+ name: 'list-outputs',
206
+ description: 'List wallet outputs in a basket (BRC-100)',
207
+ args: [
208
+ { flag: '--basket', values: '<name>', required: true },
209
+ { flag: '--tags', values: '<t1,t2>' },
210
+ { flag: '--limit', values: '<n>' },
211
+ { flag: '--include-tags' },
212
+ { flag: '--include', values: '<val>' },
213
+ ],
214
+ },
215
+ {
216
+ name: 'relinquish-output',
217
+ description: 'Remove output from basket (BRC-100)',
218
+ args: [
219
+ { flag: '--basket', values: '<name>', required: true },
220
+ { flag: '--output', values: '<txid.vout>', required: true },
221
+ ],
222
+ },
223
+ {
224
+ name: 'list-actions',
225
+ description: 'List wallet actions (BRC-100)',
226
+ args: [
227
+ { flag: '--labels', values: '<l1,l2>' },
228
+ { flag: '--limit', values: '<n>' },
229
+ ],
230
+ },
231
+ {
232
+ name: 'create-action',
233
+ description: 'Create a raw action (BRC-100)',
234
+ positional: "'<json>'",
235
+ },
236
+ {
237
+ name: 'sign-action',
238
+ description: 'Sign a raw action (BRC-100)',
239
+ positional: "'<json>'",
240
+ },
241
+ {
242
+ name: 'abort-action',
243
+ description: 'Abort a pending action (BRC-100)',
244
+ args: [{ flag: '--reference', values: '<ref>', required: true }],
245
+ },
246
+ {
247
+ name: 'list-certificates',
248
+ description: 'List certificates (BRC-100)',
249
+ args: [
250
+ { flag: '--certifiers', values: '<c1,c2>' },
251
+ { flag: '--types', values: '<t1,t2>' },
252
+ { flag: '--limit', values: '<n>' },
253
+ ],
254
+ },
255
+ {
256
+ name: 'relinquish-certificate',
257
+ description: 'Relinquish a certificate (BRC-100)',
258
+ args: [
259
+ { flag: '--type', values: '<t>', required: true },
260
+ { flag: '--serialNumber', values: '<s>', required: true },
261
+ { flag: '--certifier', values: '<c>', required: true },
262
+ ],
263
+ },
264
+ ],
265
+ },
266
+
267
+ // Ordinals
268
+ {
269
+ group: 'Ordinals',
270
+ name: 'ordinals',
271
+ description: '1Sat Ordinal inscriptions',
272
+ subcommands: [
273
+ { name: 'list', description: 'List owned ordinals/inscriptions' },
274
+ {
275
+ name: 'mint',
276
+ description: 'Mint a new ordinal inscription',
277
+ args: [
278
+ { flag: '--file', values: '<path>', required: true },
279
+ {
280
+ flag: '--type',
281
+ values: '<mime>',
282
+ description: 'Override auto-detected MIME type',
283
+ },
284
+ {
285
+ flag: '--map',
286
+ values: '<json>',
287
+ description: 'MAP metadata as JSON object',
288
+ },
289
+ {
290
+ flag: '--sign-with-bap',
291
+ description: 'Sign inscription with BAP identity',
292
+ },
293
+ ],
294
+ },
295
+ {
296
+ name: 'transfer',
297
+ description: 'Transfer an ordinal',
298
+ args: [
299
+ { flag: '--outpoint', values: '<txid.vout>', required: true },
300
+ { flag: '--to', values: '<address>', required: true },
301
+ ],
302
+ },
303
+ {
304
+ name: 'sell',
305
+ description: 'List an ordinal for sale (OrdLock)',
306
+ args: [
307
+ { flag: '--outpoint', values: '<txid.vout>', required: true },
308
+ { flag: '--price', values: '<sats>', required: true },
309
+ ],
310
+ },
311
+ {
312
+ name: 'cancel',
313
+ description: 'Cancel an ordinal listing',
314
+ args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
315
+ },
316
+ {
317
+ name: 'buy',
318
+ description: 'Purchase a listed ordinal',
319
+ args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
320
+ },
321
+ {
322
+ name: 'burn',
323
+ description: 'Burn ordinals permanently',
324
+ args: [
325
+ { flag: '--outpoints', values: '<op1,op2,...>', required: true },
326
+ ],
327
+ },
328
+ ],
329
+ },
330
+
331
+ // Tokens
332
+ {
333
+ group: 'Tokens (BSV21)',
334
+ name: 'tokens',
335
+ description: 'BSV21 fungible tokens',
336
+ subcommands: [
337
+ { name: 'balances', description: 'Show token balances by token ID' },
338
+ {
339
+ name: 'list',
340
+ description: 'List owned token UTXOs',
341
+ args: [{ flag: '--token-id', values: '<id>' }],
342
+ },
343
+ {
344
+ name: 'send',
345
+ description: 'Transfer tokens',
346
+ args: [
347
+ { flag: '--token-id', values: '<id>', required: true },
348
+ { flag: '--amount', values: '<n>', required: true },
349
+ { flag: '--to', values: '<address>' },
350
+ { flag: '--counterparty', values: '<pubkey-hex>' },
351
+ { flag: '--locking-script', values: '<hex>' },
352
+ ],
353
+ },
354
+ {
355
+ name: 'deploy-mint',
356
+ description: 'Deploy a new BSV21 token with fixed supply (deploy+mint)',
357
+ args: [
358
+ { flag: '--symbol', values: '<ticker>', required: true },
359
+ { flag: '--amount', values: '<total-supply>', required: true },
360
+ { flag: '--decimals', values: '<0-18>' },
361
+ { flag: '--icon', values: '<url-or-data-uri>' },
362
+ { flag: '--to', values: '<address>' },
363
+ { flag: '--counterparty', values: '<pubkey-hex>' },
364
+ { flag: '--locking-script', values: '<hex>' },
365
+ ],
366
+ },
367
+ {
368
+ name: 'deploy-auth',
369
+ description:
370
+ 'Deploy a new BSV21 token with mintable supply via auth UTXOs (deploy+auth)',
371
+ args: [
372
+ { flag: '--symbol', values: '<ticker>', required: true },
373
+ { flag: '--decimals', values: '<0-18>' },
374
+ { flag: '--icon', values: '<url-or-data-uri>' },
375
+ { flag: '--to', values: '<address>' },
376
+ { flag: '--counterparty', values: '<pubkey-hex>' },
377
+ { flag: '--locking-script', values: '<hex>' },
378
+ ],
379
+ },
380
+ {
381
+ name: 'mint',
382
+ description:
383
+ 'Spend an auth UTXO to mint new supply, re-issue authority, or burn it',
384
+ args: [
385
+ { flag: '--token-id', values: '<id>', required: true },
386
+ { flag: '--amount', values: '<n>' },
387
+ { flag: '--to', values: '<address>' },
388
+ { flag: '--counterparty', values: '<pubkey-hex>' },
389
+ { flag: '--locking-script', values: '<hex>' },
390
+ { flag: '--auth-to', values: '<address>' },
391
+ { flag: '--auth-counterparty', values: '<pubkey-hex>' },
392
+ { flag: '--auth-locking-script', values: '<hex>' },
393
+ { flag: '--end-minting' },
394
+ ],
395
+ },
396
+ {
397
+ name: 'buy',
398
+ description: 'Purchase listed tokens',
399
+ args: [
400
+ { flag: '--outpoint', values: '<txid.vout>', required: true },
401
+ { flag: '--token-id', values: '<id>', required: true },
402
+ { flag: '--amount', values: '<n>', required: true },
403
+ ],
404
+ },
405
+ ],
406
+ },
407
+
408
+ // Locks
409
+ {
410
+ group: 'Locks',
411
+ name: 'locks',
412
+ description: 'Time-locked BSV',
413
+ subcommands: [
414
+ { name: 'info', description: 'Show locked totals and maturity status' },
415
+ {
416
+ name: 'lock',
417
+ description: 'Time-lock BSV until a block height',
418
+ args: [
419
+ { flag: '--sats', values: '<amount>', required: true },
420
+ {
421
+ flag: '--blocks',
422
+ values: '<n>',
423
+ required: true,
424
+ description: 'Target unlock block height',
425
+ },
426
+ ],
427
+ },
428
+ { name: 'unlock', description: 'Unlock all matured locks' },
429
+ ],
430
+ },
431
+
432
+ // Identity
433
+ {
434
+ group: 'Identity (BAP)',
435
+ name: 'identity',
436
+ description: 'BAP (Bitcoin Attestation Protocol) identity management',
437
+ subcommands: [
438
+ { name: 'create', description: 'Create/publish a BAP identity on-chain' },
439
+ {
440
+ name: 'update-profile',
441
+ description: 'Update BAP identity profile',
442
+ args: [{ flag: '--profile', values: '<json>', required: true }],
443
+ },
444
+ { name: 'info', description: 'Show identity public key' },
445
+ {
446
+ name: 'sign',
447
+ description: 'Sign a message with identity key (BSM)',
448
+ args: [
449
+ { flag: '--message', values: '<text>', required: true },
450
+ { flag: '--encoding', values: '<utf8|hex|base64>' },
451
+ ],
452
+ },
453
+ {
454
+ name: 'verify',
455
+ description: 'Verify a signed message',
456
+ unavailable: true,
457
+ notes: 'Not yet implemented.',
458
+ args: [
459
+ { flag: '--message', values: '<text>', required: true },
460
+ { flag: '--sig', values: '<sig>', required: true },
461
+ { flag: '--address', values: '<addr>', required: true },
462
+ ],
463
+ },
464
+ ],
465
+ },
466
+
467
+ // Social
468
+ {
469
+ group: 'Social',
470
+ name: 'social',
471
+ description: 'On-chain social posts (BSocial)',
472
+ subcommands: [
473
+ {
474
+ name: 'post',
475
+ description: 'Create an on-chain social post',
476
+ args: [
477
+ { flag: '--content', values: '<text>', required: true },
478
+ { flag: '--app', values: '<name>' },
479
+ {
480
+ flag: '--content-type',
481
+ values: '<text/plain|text/markdown>',
482
+ },
483
+ { flag: '--tags', values: '<t1,t2>' },
484
+ ],
485
+ },
486
+ ],
487
+ },
488
+
489
+ // OpNS
490
+ {
491
+ group: 'OpNS',
492
+ name: 'opns',
493
+ description: 'Ordinals Name System — bind BAP identity to a name',
494
+ subcommands: [
495
+ {
496
+ name: 'register',
497
+ description: 'Register identity on an OpNS name',
498
+ args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
499
+ },
500
+ {
501
+ name: 'deregister',
502
+ description: 'Deregister identity from an OpNS name',
503
+ args: [{ flag: '--outpoint', values: '<txid.vout>', required: true }],
504
+ },
505
+ { name: 'lookup', description: 'List OpNS names from wallet' },
506
+ ],
507
+ },
508
+
509
+ // Sweep
510
+ {
511
+ group: 'Sweep',
512
+ name: 'sweep',
513
+ description: 'Import assets from external private keys',
514
+ subcommands: [
515
+ {
516
+ name: 'scan',
517
+ description:
518
+ 'Scan an address for sweepable UTXOs (BSV, ordinals, BSV21)',
519
+ args: [{ flag: '--wif', values: '<key>', required: true }],
520
+ },
521
+ {
522
+ name: 'import',
523
+ description: 'Sweep UTXOs from a WIF into the wallet',
524
+ args: [{ flag: '--wif', values: '<key>', required: true }],
525
+ },
526
+ ],
527
+ },
528
+
529
+ // Server
530
+ {
531
+ group: 'Server',
532
+ name: 'serve',
533
+ description:
534
+ 'Run wallet HTTP server and/or monitor daemon (config under server.* in config.json)',
535
+ subcommands: [
536
+ {
537
+ name: '(no subcommand)',
538
+ description: 'Wallet server + monitor daemon',
539
+ },
540
+ { name: 'wallet', description: 'Wallet server only (BRC-100 HTTP)' },
541
+ { name: 'monitor', description: 'Monitor daemon only' },
542
+ {
543
+ name: 'messagebox',
544
+ description:
545
+ 'BSV message-box server (port 8771 default; uses wallet identity)',
546
+ },
547
+ ],
548
+ },
549
+
550
+ // MCP
551
+ {
552
+ group: 'MCP',
553
+ name: 'mcp-proxy',
554
+ description:
555
+ 'stdio JSON-RPC bridge to a running wallet-desktop MCP server (default http://127.0.0.1:3322). Performs BRC-31 handshake using ~/.1sat-wallet/mcp-agent.key.',
556
+ },
557
+
558
+ // Advanced
559
+ {
560
+ group: 'Advanced',
561
+ name: 'action',
562
+ description:
563
+ 'Execute any registered @1sat/actions action by name. Run with no args to list all actions.',
564
+ positional: "[<name> ['<json>']]",
565
+ },
566
+ {
567
+ group: 'Advanced',
568
+ name: 'tx',
569
+ description: 'Transaction utilities',
570
+ subcommands: [
571
+ {
572
+ name: 'decode',
573
+ description: 'Decode a raw transaction hex',
574
+ positional: '<hex>',
575
+ },
576
+ ],
577
+ },
578
+
579
+ // Help
580
+ {
581
+ group: 'Help',
582
+ name: 'help',
583
+ description:
584
+ 'Show this help. Use --json for machine-readable output. Use `1sat <command> help` for per-command help.',
585
+ },
586
+ ]
587
+
588
+ export function getCommand(name: string): CommandSpec | undefined {
589
+ return COMMANDS.find((c) => c.name === name)
590
+ }
591
+
592
+ function formatArgUsage(arg: ArgSpec): string {
593
+ const core = arg.values ? `${arg.flag} ${arg.values}` : arg.flag
594
+ return arg.required ? core : `[${core}]`
595
+ }
596
+
597
+ function formatSubcommandLine(sub: SubcommandSpec): string {
598
+ const parts: string[] = []
599
+ if (sub.positional) parts.push(sub.positional)
600
+ if (sub.args) parts.push(...sub.args.map(formatArgUsage))
601
+ return parts.join(' ')
602
+ }
603
+
604
+ export function printHelp(json = false): void {
605
+ if (json) {
606
+ printHelpJson()
607
+ return
608
+ }
609
+ printHelpText()
610
+ }
611
+
612
+ function printHelpJson(): void {
613
+ const tree = COMMANDS.map((cmd) => ({
614
+ group: cmd.group,
615
+ name: cmd.name,
616
+ description: cmd.description,
617
+ ...(cmd.positional ? { positional: cmd.positional } : {}),
618
+ ...(cmd.args ? { args: cmd.args } : {}),
619
+ ...(cmd.notes ? { notes: cmd.notes } : {}),
620
+ ...(cmd.subcommands
621
+ ? {
622
+ subcommands: cmd.subcommands.map((s) => ({
623
+ name: s.name,
624
+ description: s.description,
625
+ ...(s.positional ? { positional: s.positional } : {}),
626
+ ...(s.args ? { args: s.args } : {}),
627
+ ...(s.unavailable ? { unavailable: true } : {}),
628
+ ...(s.notes ? { notes: s.notes } : {}),
629
+ })),
630
+ }
631
+ : {}),
632
+ }))
633
+
634
+ console.log(
635
+ JSON.stringify(
636
+ {
637
+ name: '1sat',
638
+ version: getVersion(),
639
+ description: 'CLI for 1Sat Ordinals SDK',
640
+ usage: '1sat <command> [subcommand] [options]',
641
+ configDir: '~/.1sat/cli/',
642
+ globalOptions: GLOBAL_OPTIONS,
643
+ envVars: ENV_VARS,
644
+ commands: tree,
645
+ },
646
+ null,
647
+ 2,
648
+ ),
649
+ )
650
+ }
651
+
652
+ function printHelpText(): void {
28
653
  const dim = chalk.dim
29
654
  const cyan = chalk.cyan
30
655
  const bold = chalk.bold
31
656
 
32
- console.log(`
33
- ${bold('1sat')} - CLI for 1Sat Ordinals SDK
34
-
35
- ${bold('Usage:')}
36
- 1sat <command> [subcommand] [options]
37
-
38
- ${bold('Setup:')}
39
- ${cyan('init')} Interactive wallet setup wizard
40
- ${cyan('config')} <subcommand> Manage configuration
41
- ${cyan('remote')} <subcommand> Manage remote storage
42
-
43
- ${bold('Wallet:')}
44
- ${cyan('wallet balance')} Show wallet balance
45
- ${cyan('wallet address')} Show deposit address
46
- ${cyan('wallet send')} Send BSV to an address
47
- ${cyan('wallet send-all')} Send all BSV to an address
48
- ${cyan('wallet info')} Show wallet info
49
-
50
- ${bold('Wallet (BRC-100 Interface):')}
51
- ${cyan('wallet list-outputs')} List outputs in basket
52
- ${dim('--basket <name> [--tags <t1,t2>] [--limit N]')}
53
- ${cyan('wallet relinquish-output')} Remove output from basket
54
- ${dim('--basket <name> --output <txid.vout>')}
55
- ${cyan('wallet list-actions')} List wallet actions
56
- ${dim('[--labels <l1,l2>] [--limit N]')}
57
- ${cyan('wallet create-action')} Create raw action (JSON args)
58
- ${cyan('wallet sign-action')} Sign raw action (JSON args)
59
- ${cyan('wallet abort-action')} Abort pending action
60
- ${dim('--reference <ref>')}
61
- ${cyan('wallet list-certificates')} List certificates
62
- ${dim('[--certifiers <c1,c2>] [--types <t1,t2>] [--limit N]')}
63
- ${cyan('wallet relinquish-certificate')} Relinquish certificate
64
- ${dim('--type <t> --serialNumber <s> --certifier <c>')}
65
-
66
- ${bold('Ordinals:')}
67
- ${cyan('ordinals list')} List owned ordinals
68
- ${cyan('ordinals mint')} Mint a new ordinal inscription
69
- ${cyan('ordinals transfer')} Transfer an ordinal
70
- ${cyan('ordinals sell')} List an ordinal for sale
71
- ${cyan('ordinals cancel')} Cancel an ordinal listing
72
- ${cyan('ordinals buy')} Purchase a listed ordinal
73
-
74
- ${bold('Tokens (BSV21):')}
75
- ${cyan('tokens balances')} Show token balances
76
- ${cyan('tokens list')} List owned token UTXOs
77
- ${cyan('tokens send')} Transfer tokens
78
- ${cyan('tokens deploy')} Deploy a new BSV21 token
79
- ${cyan('tokens buy')} Purchase listed tokens
80
-
81
- ${bold('Locks:')}
82
- ${cyan('locks info')} Show lock information
83
- ${cyan('locks lock')} Time-lock BSV
84
- ${cyan('locks unlock')} Unlock matured BSV
85
-
86
- ${bold('Identity (BAP):')}
87
- ${cyan('identity create')} Create a new BAP identity
88
- ${cyan('identity update-profile')} Update BAP identity profile (--profile <json>)
89
- ${cyan('identity info')} Show identity information
90
- ${cyan('identity sign')} Sign a message with identity key
91
- ${cyan('identity verify')} Verify a signed message
92
-
93
- ${bold('Social:')}
94
- ${cyan('social post')} Create an on-chain social post
95
-
96
- ${bold('OpNS:')}
97
- ${cyan('opns register')} Register identity on OpNS name
98
- ${cyan('opns deregister')} Deregister identity from OpNS name
99
- ${cyan('opns lookup')} Look up an OpNS name
100
-
101
- ${bold('Sweep:')}
102
- ${cyan('sweep scan')} Scan an address for UTXOs
103
- ${cyan('sweep import')} Import UTXOs into wallet
104
-
105
- ${bold('Advanced:')}
106
- ${cyan('action')} <name> [json] Execute a registered action by name
107
- ${cyan('tx decode')} <hex> Decode a raw transaction
108
-
109
- ${bold('Global Options:')}
110
- ${dim('--json')} Output as JSON
111
- ${dim('--quiet, -q')} Suppress output
112
- ${dim('--yes, -y')} Skip confirmations
113
- ${dim('--chain <main|test>')} Network (default: main)
114
- ${dim('--help, -h')} Show help
115
- ${dim('--version, -v')} Show version
116
-
117
- ${bold('Environment Variables:')}
118
- ${dim('PRIVATE_KEY_WIF')} Private key (bypasses encrypted keyfile)
119
- ${dim('ONESAT_PASSWORD')} Password for encrypted keyfile
120
-
121
- ${bold('Config:')} ~/.1sat/cli/
122
- `)
657
+ const lines: string[] = []
658
+ lines.push('')
659
+ lines.push(`${bold('1sat')} - CLI for 1Sat Ordinals SDK`)
660
+ lines.push('')
661
+ lines.push(bold('Usage:'))
662
+ lines.push(' 1sat <command> [subcommand] [options]')
663
+ lines.push(' 1sat <command> help Per-command help')
664
+ lines.push(' 1sat help --json Machine-readable command tree')
665
+ lines.push('')
666
+
667
+ let currentGroup = ''
668
+ for (const cmd of COMMANDS) {
669
+ if (cmd.group !== currentGroup) {
670
+ lines.push(bold(`${cmd.group}:`))
671
+ currentGroup = cmd.group
672
+ }
673
+ const head = cmd.subcommands
674
+ ? `${cmd.name} <subcommand>`
675
+ : cmd.positional
676
+ ? `${cmd.name} ${cmd.positional}`
677
+ : cmd.name
678
+ lines.push(` ${cyan(head.padEnd(28))} ${dim(cmd.description)}`)
679
+
680
+ if (cmd.subcommands) {
681
+ for (const sub of cmd.subcommands) {
682
+ const usage = formatSubcommandLine(sub)
683
+ const label = `${cmd.name} ${sub.name}${usage ? ` ${usage}` : ''}`
684
+ const tag = sub.unavailable ? ' (unavailable)' : ''
685
+ lines.push(
686
+ ` ${cyan(label.padEnd(58))} ${dim(sub.description + tag)}`,
687
+ )
688
+ }
689
+ }
690
+ lines.push('')
691
+ }
692
+
693
+ lines.push(bold('Global Options:'))
694
+ for (const opt of GLOBAL_OPTIONS) {
695
+ const usage = opt.values ? `${opt.flag} ${opt.values}` : opt.flag
696
+ lines.push(` ${dim(usage.padEnd(26))} ${dim(opt.description ?? '')}`)
697
+ }
698
+ lines.push('')
699
+
700
+ lines.push(bold('Environment Variables:'))
701
+ for (const env of ENV_VARS) {
702
+ lines.push(` ${dim(env.name.padEnd(26))} ${dim(env.description)}`)
703
+ }
704
+ lines.push('')
705
+
706
+ lines.push(`${bold('Config:')} ~/.1sat/cli/`)
707
+ lines.push('')
708
+
709
+ console.log(lines.join('\n'))
123
710
  }
124
711
 
125
- export function printCommandHelp(
126
- command: string,
127
- subcommands: Record<string, string>,
128
- ): void {
712
+ export function printCommandHelp(commandName: string, json = false): void {
713
+ const spec = getCommand(commandName)
714
+ if (!spec) {
715
+ if (json) {
716
+ console.log(JSON.stringify({ error: `Unknown command: ${commandName}` }))
717
+ } else {
718
+ console.error(`Unknown command: ${commandName}`)
719
+ }
720
+ return
721
+ }
722
+
723
+ if (json) {
724
+ console.log(JSON.stringify(spec, null, 2))
725
+ return
726
+ }
727
+
129
728
  const bold = chalk.bold
130
729
  const cyan = chalk.cyan
131
730
  const dim = chalk.dim
132
731
 
133
- console.log(`\n${bold(`1sat ${command}`)}`)
134
- console.log(`\n${bold('Subcommands:')}`)
135
- for (const [sub, desc] of Object.entries(subcommands)) {
136
- console.log(` ${cyan(sub.padEnd(20))} ${dim(desc)}`)
137
- }
138
732
  console.log()
733
+ console.log(`${bold(`1sat ${spec.name}`)} - ${spec.description}`)
734
+ if (spec.notes) console.log(dim(` ${spec.notes}`))
735
+ console.log()
736
+
737
+ if (spec.subcommands) {
738
+ console.log(bold('Subcommands:'))
739
+ for (const sub of spec.subcommands) {
740
+ const usage = formatSubcommandLine(sub)
741
+ const label = `${sub.name}${usage ? ` ${usage}` : ''}`
742
+ const tag = sub.unavailable ? ' (unavailable)' : ''
743
+ console.log(` ${cyan(label.padEnd(56))} ${dim(sub.description + tag)}`)
744
+ if (sub.notes) console.log(dim(` ${sub.notes}`))
745
+ }
746
+ console.log()
747
+ }
748
+
749
+ if (spec.args && spec.args.length > 0) {
750
+ console.log(bold('Options:'))
751
+ for (const arg of spec.args) {
752
+ const usage = formatArgUsage(arg)
753
+ console.log(` ${cyan(usage.padEnd(36))} ${dim(arg.description ?? '')}`)
754
+ }
755
+ console.log()
756
+ }
139
757
  }