@onekeyfe/hardware-cli 1.1.25-alpha.0

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/dist/cli.js ADDED
@@ -0,0 +1,824 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ const commander_1 = require("commander");
6
+ const sdk_1 = require("./sdk");
7
+ const chains_1 = require("./chains");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name('onekey-hw')
11
+ .description('OneKey hardware wallet CLI for AI agent integration')
12
+ .version('1.1.25-alpha.0');
13
+ // ============================================================
14
+ // Global Options
15
+ // ============================================================
16
+ program.option('--json', 'Output in JSON format (for agent consumption)');
17
+ program.option('--connect-id <id>', 'Device connection ID (USB: serial, iOS: uuid, Android: MAC)');
18
+ program.option('--device-id <id>', 'Persistent device ID from getFeatures (changes when seed changes)');
19
+ program.option('--passphrase-state <state>', 'Passphrase state for hidden wallet access');
20
+ program.option('--use-empty-passphrase', 'Use standard wallet (skip passphrase prompt)');
21
+ // ============================================================
22
+ // Device Commands
23
+ // ============================================================
24
+ program
25
+ .command('search')
26
+ .description('Search for connected OneKey hardware wallet devices')
27
+ .option('--timeout <ms>', 'Search timeout in milliseconds', '10000')
28
+ .action(async (opts) => {
29
+ const sdk = await (0, sdk_1.createSDK)(program.opts());
30
+ try {
31
+ const result = await sdk.searchDevices();
32
+ outputResult(program.opts(), result);
33
+ }
34
+ finally {
35
+ sdk.dispose();
36
+ }
37
+ });
38
+ program
39
+ .command('status')
40
+ .description('Get device features and current status')
41
+ .action(async () => {
42
+ const globalOpts = program.opts();
43
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
44
+ try {
45
+ const result = await sdk.getFeatures(globalOpts.connectId);
46
+ outputResult(globalOpts, result);
47
+ }
48
+ finally {
49
+ sdk.dispose();
50
+ }
51
+ });
52
+ // ============================================================
53
+ // Signing Commands
54
+ // ============================================================
55
+ program
56
+ .command('get-address')
57
+ .description('Get a cryptocurrency address from the hardware wallet')
58
+ .requiredOption('--chain <chain>', 'Target blockchain (evm, btc, sol, ...)')
59
+ .option('--path <path>', 'BIP44 derivation path')
60
+ .option('--show-on-device <bool>', 'Display address on device for verification', 'true')
61
+ .action(async (opts) => {
62
+ const globalOpts = program.opts();
63
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
64
+ try {
65
+ const result = await (0, chains_1.resolveGetAddress)(sdk, {
66
+ chain: opts.chain,
67
+ path: opts.path,
68
+ showOnDevice: opts.showOnDevice === 'true',
69
+ ...getCommonParams(globalOpts),
70
+ });
71
+ outputResult(globalOpts, result);
72
+ }
73
+ finally {
74
+ sdk.dispose();
75
+ }
76
+ });
77
+ program
78
+ .command('get-public-key')
79
+ .description('Get public key from the hardware wallet')
80
+ .requiredOption('--chain <chain>', 'Target blockchain')
81
+ .option('--path <path>', 'BIP44 derivation path')
82
+ .action(async (opts) => {
83
+ const globalOpts = program.opts();
84
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
85
+ try {
86
+ const result = await (0, chains_1.resolveGetPublicKey)(sdk, {
87
+ chain: opts.chain,
88
+ path: opts.path,
89
+ ...getCommonParams(globalOpts),
90
+ });
91
+ outputResult(globalOpts, result);
92
+ }
93
+ finally {
94
+ sdk.dispose();
95
+ }
96
+ });
97
+ program
98
+ .command('sign-transaction')
99
+ .description('Sign a blockchain transaction (requires device confirmation)')
100
+ .requiredOption('--chain <chain>', 'Target blockchain')
101
+ .requiredOption('--tx <json>', 'Transaction data (JSON)')
102
+ .option('--path <path>', 'BIP44 derivation path')
103
+ .action(async (opts) => {
104
+ const globalOpts = program.opts();
105
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
106
+ try {
107
+ const tx = safeJsonParse(opts.tx, '--tx');
108
+ const result = await (0, chains_1.resolveSignTransaction)(sdk, {
109
+ chain: opts.chain,
110
+ path: opts.path,
111
+ transaction: tx,
112
+ ...getCommonParams(globalOpts),
113
+ });
114
+ outputResult(globalOpts, result);
115
+ }
116
+ finally {
117
+ sdk.dispose();
118
+ }
119
+ });
120
+ program
121
+ .command('sign-message')
122
+ .description('Sign a message (requires device confirmation)')
123
+ .requiredOption('--chain <chain>', 'Target blockchain')
124
+ .requiredOption('--message <msg>', 'Message to sign')
125
+ .option('--path <path>', 'BIP44 derivation path')
126
+ .action(async (opts) => {
127
+ const globalOpts = program.opts();
128
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
129
+ try {
130
+ const result = await (0, chains_1.resolveSignMessage)(sdk, {
131
+ chain: opts.chain,
132
+ path: opts.path,
133
+ message: opts.message,
134
+ ...getCommonParams(globalOpts),
135
+ });
136
+ outputResult(globalOpts, result);
137
+ }
138
+ finally {
139
+ sdk.dispose();
140
+ }
141
+ });
142
+ program
143
+ .command('sign-typed-data')
144
+ .description('Sign EIP-712 typed data (EVM only, requires device confirmation)')
145
+ .requiredOption('--data <json>', 'EIP-712 typed data JSON')
146
+ .option('--path <path>', 'BIP44 derivation path')
147
+ .option('--metamask-v4-compat', 'Use MetaMask V4 compatibility mode', true)
148
+ .action(async (opts) => {
149
+ const globalOpts = program.opts();
150
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
151
+ try {
152
+ const data = safeJsonParse(opts.data, '--data');
153
+ const params = getCommonParams(globalOpts);
154
+ const path = opts.path || "m/44'/60'/0'/0/0";
155
+ const result = await sdk.evmSignTypedData(params.connectId || '', params.deviceId || '', {
156
+ path,
157
+ metamaskV4Compat: opts.metamaskV4Compat,
158
+ data,
159
+ ...params,
160
+ });
161
+ outputResult(globalOpts, result);
162
+ }
163
+ finally {
164
+ sdk.dispose();
165
+ }
166
+ });
167
+ program
168
+ .command('sign-psbt')
169
+ .description('Sign a Bitcoin PSBT (Pro/Classic1s only, requires device confirmation)')
170
+ .requiredOption('--psbt <hex>', 'Hex-encoded PSBT data')
171
+ .option('--coin <coin>', 'Bitcoin network: btc, ltc, etc.', 'btc')
172
+ .action(async (opts) => {
173
+ const globalOpts = program.opts();
174
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
175
+ try {
176
+ const params = getCommonParams(globalOpts);
177
+ const result = await sdk.btcSignPsbt(params.connectId || '', params.deviceId || '', {
178
+ psbt: opts.psbt,
179
+ coin: opts.coin,
180
+ ...params,
181
+ });
182
+ outputResult(globalOpts, result);
183
+ }
184
+ finally {
185
+ sdk.dispose();
186
+ }
187
+ });
188
+ program
189
+ .command('verify-message')
190
+ .description('Verify a signed message on-device (BTC, EVM, Starcoin)')
191
+ .requiredOption('--chain <chain>', 'Target blockchain (btc, evm, starcoin)')
192
+ .requiredOption('--address <addr>', 'Signer address')
193
+ .requiredOption('--message <msg>', 'Original message')
194
+ .requiredOption('--signature <sig>', 'Signature to verify')
195
+ .action(async (opts) => {
196
+ const globalOpts = program.opts();
197
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
198
+ try {
199
+ const params = getCommonParams(globalOpts);
200
+ const cid = params.connectId || '';
201
+ const did = params.deviceId || '';
202
+ let result;
203
+ switch (opts.chain.toLowerCase()) {
204
+ case 'evm':
205
+ case 'eth':
206
+ case 'ethereum':
207
+ result = await sdk.evmVerifyMessage(cid, did, {
208
+ address: opts.address,
209
+ messageHex: opts.message,
210
+ signature: opts.signature,
211
+ });
212
+ break;
213
+ case 'btc':
214
+ case 'bitcoin':
215
+ result = await sdk.btcVerifyMessage(cid, did, {
216
+ address: opts.address,
217
+ message: opts.message,
218
+ signature: opts.signature,
219
+ coin: 'btc',
220
+ });
221
+ break;
222
+ case 'starcoin':
223
+ case 'stc':
224
+ result = await sdk.starcoinVerifyMessage(cid, did, {
225
+ publicKey: opts.address,
226
+ message: opts.message,
227
+ signature: opts.signature,
228
+ });
229
+ break;
230
+ default:
231
+ throw new Error(`verifyMessage not supported for chain: ${opts.chain}. Supported: evm, btc, starcoin`);
232
+ }
233
+ outputResult(globalOpts, result);
234
+ }
235
+ finally {
236
+ sdk.dispose();
237
+ }
238
+ });
239
+ program
240
+ .command('batch-get-address')
241
+ .description('Get addresses for multiple chains/paths in a single session')
242
+ .requiredOption('--bundle <json>', 'JSON array of {chain, path, showOnDevice}')
243
+ .action(async (opts) => {
244
+ const globalOpts = program.opts();
245
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
246
+ try {
247
+ const bundle = safeJsonParse(opts.bundle, '--bundle');
248
+ const result = await (0, chains_1.resolveBatchGetAddress)(sdk, {
249
+ bundle,
250
+ ...getCommonParams(globalOpts),
251
+ });
252
+ outputResult(globalOpts, result);
253
+ }
254
+ finally {
255
+ sdk.dispose();
256
+ }
257
+ });
258
+ // ============================================================
259
+ // Chain-Specific Commands
260
+ // ============================================================
261
+ program
262
+ .command('evm-sign-eip712')
263
+ .description('Sign EIP-712 message by domain/message hash (EVM, requires device confirmation)')
264
+ .requiredOption('--domain-hash <hex>', 'EIP-712 domain separator hash')
265
+ .requiredOption('--message-hash <hex>', 'EIP-712 message hash')
266
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/60'/0'/0/0")
267
+ .action(async (opts) => {
268
+ const globalOpts = program.opts();
269
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
270
+ try {
271
+ const p = getCommonParams(globalOpts);
272
+ const result = await sdk.evmSignMessageEIP712(p.connectId || '', p.deviceId || '', {
273
+ path: opts.path,
274
+ domainHash: opts.domainHash,
275
+ messageHash: opts.messageHash,
276
+ });
277
+ outputResult(globalOpts, result);
278
+ }
279
+ finally {
280
+ sdk.dispose();
281
+ }
282
+ });
283
+ program
284
+ .command('sol-sign-offchain')
285
+ .description('Sign a Solana off-chain message (requires device confirmation)')
286
+ .requiredOption('--message-hex <hex>', 'Off-chain message as hex')
287
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/501'/0'/0'")
288
+ .action(async (opts) => {
289
+ const globalOpts = program.opts();
290
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
291
+ try {
292
+ const p = getCommonParams(globalOpts);
293
+ const result = await sdk.solSignOffchainMessage(p.connectId || '', p.deviceId || '', {
294
+ path: opts.path,
295
+ messageHex: opts.messageHex,
296
+ });
297
+ outputResult(globalOpts, result);
298
+ }
299
+ finally {
300
+ sdk.dispose();
301
+ }
302
+ });
303
+ program
304
+ .command('nostr-encrypt')
305
+ .description('Encrypt a message for a Nostr recipient')
306
+ .requiredOption('--pubkey <hex>', 'Recipient Nostr public key')
307
+ .requiredOption('--plaintext <text>', 'Message to encrypt')
308
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
309
+ .option('--show-on-device <bool>', 'Display on device', 'false')
310
+ .action(async (opts) => {
311
+ const globalOpts = program.opts();
312
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
313
+ try {
314
+ const p = getCommonParams(globalOpts);
315
+ const result = await sdk.nostrEncryptMessage(p.connectId || '', p.deviceId || '', {
316
+ path: opts.path,
317
+ pubkey: opts.pubkey,
318
+ plaintext: opts.plaintext,
319
+ showOnOneKey: opts.showOnDevice === 'true',
320
+ });
321
+ outputResult(globalOpts, result);
322
+ }
323
+ finally {
324
+ sdk.dispose();
325
+ }
326
+ });
327
+ program
328
+ .command('nostr-decrypt')
329
+ .description('Decrypt a Nostr encrypted message')
330
+ .requiredOption('--pubkey <hex>', 'Sender Nostr public key')
331
+ .requiredOption('--ciphertext <text>', 'Encrypted message')
332
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
333
+ .option('--show-on-device <bool>', 'Display on device', 'false')
334
+ .action(async (opts) => {
335
+ const globalOpts = program.opts();
336
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
337
+ try {
338
+ const p = getCommonParams(globalOpts);
339
+ const result = await sdk.nostrDecryptMessage(p.connectId || '', p.deviceId || '', {
340
+ path: opts.path,
341
+ pubkey: opts.pubkey,
342
+ ciphertext: opts.ciphertext,
343
+ showOnOneKey: opts.showOnDevice === 'true',
344
+ });
345
+ outputResult(globalOpts, result);
346
+ }
347
+ finally {
348
+ sdk.dispose();
349
+ }
350
+ });
351
+ program
352
+ .command('nostr-sign-schnorr')
353
+ .description('Sign a Schnorr signature for Nostr')
354
+ .requiredOption('--hash <hex>', 'Hash to sign (hex)')
355
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
356
+ .action(async (opts) => {
357
+ const globalOpts = program.opts();
358
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
359
+ try {
360
+ const p = getCommonParams(globalOpts);
361
+ const result = await sdk.nostrSignSchnorr(p.connectId || '', p.deviceId || '', {
362
+ path: opts.path,
363
+ hash: opts.hash,
364
+ });
365
+ outputResult(globalOpts, result);
366
+ }
367
+ finally {
368
+ sdk.dispose();
369
+ }
370
+ });
371
+ program
372
+ .command('lnurl-auth')
373
+ .description('Authenticate with LNURL (Lightning Network)')
374
+ .requiredOption('--domain <domain>', 'Service domain')
375
+ .requiredOption('--k1 <hex>', 'Challenge k1 parameter')
376
+ .action(async (opts) => {
377
+ const globalOpts = program.opts();
378
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
379
+ try {
380
+ const p = getCommonParams(globalOpts);
381
+ const result = await sdk.lnurlAuth(p.connectId || '', p.deviceId || '', {
382
+ domain: opts.domain,
383
+ k1: opts.k1,
384
+ });
385
+ outputResult(globalOpts, result);
386
+ }
387
+ finally {
388
+ sdk.dispose();
389
+ }
390
+ });
391
+ program
392
+ .command('conflux-sign-cip23')
393
+ .description('Sign a Conflux CIP-23 structured message')
394
+ .requiredOption('--domain-hash <hex>', 'CIP-23 domain hash')
395
+ .requiredOption('--message-hash <hex>', 'CIP-23 message hash')
396
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/503'/0'/0/0")
397
+ .action(async (opts) => {
398
+ const globalOpts = program.opts();
399
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
400
+ try {
401
+ const p = getCommonParams(globalOpts);
402
+ const result = await sdk.confluxSignMessageCIP23(p.connectId || '', p.deviceId || '', {
403
+ path: opts.path,
404
+ domainHash: opts.domainHash,
405
+ messageHash: opts.messageHash,
406
+ });
407
+ outputResult(globalOpts, result);
408
+ }
409
+ finally {
410
+ sdk.dispose();
411
+ }
412
+ });
413
+ program
414
+ .command('aptos-sign-in')
415
+ .description('Sign an Aptos sign-in message')
416
+ .requiredOption('--payload <text>', 'Sign-in payload string')
417
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/637'/0'/0'/0'")
418
+ .action(async (opts) => {
419
+ const globalOpts = program.opts();
420
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
421
+ try {
422
+ const p = getCommonParams(globalOpts);
423
+ const result = await sdk.aptosSignInMessage(p.connectId || '', p.deviceId || '', {
424
+ path: opts.path,
425
+ payload: opts.payload,
426
+ });
427
+ outputResult(globalOpts, result);
428
+ }
429
+ finally {
430
+ sdk.dispose();
431
+ }
432
+ });
433
+ program
434
+ .command('ton-sign-proof')
435
+ .description('Sign a TON proof (for wallet authentication)')
436
+ .requiredOption('--appdomain <domain>', 'Application domain')
437
+ .requiredOption('--expire-at <timestamp>', 'Proof expiration timestamp')
438
+ .option('--comment <text>', 'Optional comment')
439
+ .option('--path <path>', 'BIP44 derivation path', "m/44'/607'/0'")
440
+ .action(async (opts) => {
441
+ const globalOpts = program.opts();
442
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
443
+ try {
444
+ const p = getCommonParams(globalOpts);
445
+ const result = await sdk.tonSignProof(p.connectId || '', p.deviceId || '', {
446
+ path: opts.path,
447
+ appdomain: opts.appdomain,
448
+ expireAt: safeParseInt(opts.expireAt, '--expire-at'),
449
+ ...(opts.comment ? { comment: opts.comment } : {}),
450
+ });
451
+ outputResult(globalOpts, result);
452
+ }
453
+ finally {
454
+ sdk.dispose();
455
+ }
456
+ });
457
+ // ============================================================
458
+ // Firmware Commands
459
+ // ============================================================
460
+ program
461
+ .command('firmware-check')
462
+ .description('Check if firmware updates are available')
463
+ .action(async () => {
464
+ const globalOpts = program.opts();
465
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
466
+ try {
467
+ const result = await sdk.checkFirmwareRelease(globalOpts.connectId);
468
+ outputResult(globalOpts, result);
469
+ }
470
+ finally {
471
+ sdk.dispose();
472
+ }
473
+ });
474
+ program
475
+ .command('firmware-check-all')
476
+ .description('Check all firmware components (system, BLE, bootloader)')
477
+ .action(async () => {
478
+ const globalOpts = program.opts();
479
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
480
+ try {
481
+ const result = await sdk.checkAllFirmwareRelease(globalOpts.connectId);
482
+ outputResult(globalOpts, result);
483
+ }
484
+ finally {
485
+ sdk.dispose();
486
+ }
487
+ });
488
+ program
489
+ .command('firmware-update')
490
+ .description('Update device firmware')
491
+ .option('--version <ver>', 'Target firmware version (e.g., "4.8.0")')
492
+ .option('--platform <platform>', 'Platform: native | desktop | ext | web', 'desktop')
493
+ .action(async (opts) => {
494
+ const globalOpts = program.opts();
495
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
496
+ try {
497
+ // firmwareUpdateV2 requires: connectId, deviceId, { updateType, platform, version? }
498
+ const params = {
499
+ updateType: 'firmware',
500
+ platform: opts.platform,
501
+ };
502
+ if (opts.version) {
503
+ params.version = parseVersion(opts.version);
504
+ }
505
+ // firmwareUpdateV2 signature: (connectId, params) — 2 args only
506
+ const result = await sdk.firmwareUpdateV2(globalOpts.connectId, params);
507
+ outputResult(globalOpts, result);
508
+ }
509
+ finally {
510
+ sdk.dispose();
511
+ }
512
+ });
513
+ program
514
+ .command('firmware-update-ble')
515
+ .description('Update BLE (Bluetooth) firmware')
516
+ .option('--version <ver>', 'Target BLE firmware version')
517
+ .option('--platform <platform>', 'Platform: native | desktop | ext | web', 'desktop')
518
+ .action(async (opts) => {
519
+ const globalOpts = program.opts();
520
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
521
+ try {
522
+ const params = {
523
+ updateType: 'ble',
524
+ platform: opts.platform,
525
+ };
526
+ if (opts.version) {
527
+ params.version = parseVersion(opts.version);
528
+ }
529
+ const result = await sdk.firmwareUpdateV2(globalOpts.connectId, params);
530
+ outputResult(globalOpts, result);
531
+ }
532
+ finally {
533
+ sdk.dispose();
534
+ }
535
+ });
536
+ program
537
+ .command('bootloader-check')
538
+ .description('Check bootloader version and status')
539
+ .action(async () => {
540
+ const globalOpts = program.opts();
541
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
542
+ try {
543
+ const result = await sdk.checkBootloaderRelease(globalOpts.connectId);
544
+ outputResult(globalOpts, result);
545
+ }
546
+ finally {
547
+ sdk.dispose();
548
+ }
549
+ });
550
+ // ============================================================
551
+ // Security / Management Commands
552
+ // ============================================================
553
+ program
554
+ .command('change-pin')
555
+ .description('Change or set the device PIN code')
556
+ .option('--remove', 'Remove PIN protection instead of changing')
557
+ .action(async (opts) => {
558
+ const globalOpts = program.opts();
559
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
560
+ try {
561
+ const result = await sdk.deviceChangePin(globalOpts.connectId, {
562
+ remove: opts.remove ?? false,
563
+ });
564
+ outputResult(globalOpts, result);
565
+ }
566
+ finally {
567
+ sdk.dispose();
568
+ }
569
+ });
570
+ program
571
+ .command('passphrase-state')
572
+ .description('Get current passphrase state (for hidden wallet session management)')
573
+ .action(async () => {
574
+ const globalOpts = program.opts();
575
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
576
+ try {
577
+ const result = await sdk.getPassphraseState(globalOpts.connectId, {
578
+ useEmptyPassphrase: globalOpts.useEmptyPassphrase,
579
+ });
580
+ outputResult(globalOpts, result);
581
+ }
582
+ finally {
583
+ sdk.dispose();
584
+ }
585
+ });
586
+ program
587
+ .command('toggle-passphrase')
588
+ .description('Enable or disable passphrase (hidden wallet) protection')
589
+ .requiredOption('--enable <bool>', 'true to enable, false to disable')
590
+ .action(async (opts) => {
591
+ const globalOpts = program.opts();
592
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
593
+ try {
594
+ const result = await sdk.deviceSettings(globalOpts.connectId, {
595
+ usePassphrase: opts.enable === 'true',
596
+ });
597
+ outputResult(globalOpts, result);
598
+ }
599
+ finally {
600
+ sdk.dispose();
601
+ }
602
+ });
603
+ program
604
+ .command('device-backup')
605
+ .description('Trigger recovery phrase backup on device')
606
+ .action(async () => {
607
+ const globalOpts = program.opts();
608
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
609
+ try {
610
+ const result = await sdk.deviceBackup(globalOpts.connectId);
611
+ outputResult(globalOpts, result);
612
+ }
613
+ finally {
614
+ sdk.dispose();
615
+ }
616
+ });
617
+ program
618
+ .command('device-recovery')
619
+ .description('Recover wallet from recovery phrase (entered on device)')
620
+ .option('--word-count <count>', 'Recovery phrase length: 12, 18, or 24', '24')
621
+ .option('--passphrase-protection <bool>', 'Enable passphrase after recovery', 'false')
622
+ .option('--pin-protection <bool>', 'Set PIN after recovery', 'true')
623
+ .option('--label <name>', 'Device label')
624
+ .action(async (opts) => {
625
+ const globalOpts = program.opts();
626
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
627
+ try {
628
+ const result = await sdk.deviceRecovery(globalOpts.connectId, {
629
+ wordCount: safeParseInt(opts.wordCount, '--word-count'),
630
+ passphraseProtection: opts.passphraseProtection === 'true',
631
+ pinProtection: opts.pinProtection === 'true',
632
+ label: opts.label,
633
+ });
634
+ outputResult(globalOpts, result);
635
+ }
636
+ finally {
637
+ sdk.dispose();
638
+ }
639
+ });
640
+ program
641
+ .command('device-reset')
642
+ .description('Initialize device with a new wallet seed (DESTROYS current wallet)')
643
+ .option('--word-count <count>', 'Seed phrase length: 12, 18, or 24', '24')
644
+ .option('--passphrase-protection <bool>', 'Enable passphrase', 'false')
645
+ .option('--pin-protection <bool>', 'Set PIN', 'true')
646
+ .option('--label <name>', 'Device label')
647
+ .action(async (opts) => {
648
+ const globalOpts = program.opts();
649
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
650
+ try {
651
+ const result = await sdk.deviceReset(globalOpts.connectId, {
652
+ strength: wordCountToStrength(safeParseInt(opts.wordCount, '--word-count')),
653
+ passphraseProtection: opts.passphraseProtection === 'true',
654
+ pinProtection: opts.pinProtection === 'true',
655
+ label: opts.label,
656
+ });
657
+ outputResult(globalOpts, result);
658
+ }
659
+ finally {
660
+ sdk.dispose();
661
+ }
662
+ });
663
+ program
664
+ .command('device-wipe')
665
+ .description('Factory reset — erase ALL data (IRREVERSIBLE)')
666
+ .action(async () => {
667
+ const globalOpts = program.opts();
668
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
669
+ try {
670
+ const result = await sdk.deviceWipe(globalOpts.connectId);
671
+ outputResult(globalOpts, result);
672
+ }
673
+ finally {
674
+ sdk.dispose();
675
+ }
676
+ });
677
+ program
678
+ .command('device-settings')
679
+ .description('Update device label and settings')
680
+ .option('--label <name>', 'Device display name')
681
+ .option('--auto-lock-delay <seconds>', 'Auto-lock timeout in seconds (0 = disabled)')
682
+ .option('--language <lang>', 'Device language')
683
+ .option('--passphrase-always-on-device <bool>', 'Always enter passphrase on device')
684
+ .option('--haptic-feedback <bool>', 'Enable/disable haptic feedback')
685
+ .option('--auto-shutdown-delay <seconds>', 'Auto shutdown timeout in seconds')
686
+ .action(async (opts) => {
687
+ const globalOpts = program.opts();
688
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
689
+ try {
690
+ // Map CLI options to SDK param names (camelCase → snake_case handled by SDK)
691
+ // Reference: packages/core/src/api/device/DeviceSettings.ts
692
+ const settings = {};
693
+ if (opts.label !== undefined)
694
+ settings.label = opts.label;
695
+ if (opts.autoLockDelay !== undefined)
696
+ settings.autoLockDelayMs = safeParseInt(opts.autoLockDelay, '--auto-lock-delay') * 1000;
697
+ if (opts.language !== undefined)
698
+ settings.language = opts.language;
699
+ if (opts.passphraseAlwaysOnDevice !== undefined)
700
+ settings.passphraseAlwaysOnDevice = opts.passphraseAlwaysOnDevice === 'true';
701
+ if (opts.hapticFeedback !== undefined)
702
+ settings.hapticFeedback = opts.hapticFeedback === 'true';
703
+ if (opts.autoShutdownDelay !== undefined)
704
+ settings.autoShutdownDelayMs =
705
+ safeParseInt(opts.autoShutdownDelay, '--auto-shutdown-delay') * 1000;
706
+ if (Object.keys(settings).length === 0) {
707
+ outputResult(globalOpts, {
708
+ success: false,
709
+ error: 'No settings provided. Use --label, --auto-lock-delay, --language, etc.',
710
+ });
711
+ return;
712
+ }
713
+ const result = await sdk.deviceSettings(globalOpts.connectId, settings);
714
+ outputResult(globalOpts, result);
715
+ }
716
+ finally {
717
+ sdk.dispose();
718
+ }
719
+ });
720
+ program
721
+ .command('device-verify')
722
+ .description('Verify device is genuine OneKey hardware')
723
+ .action(async () => {
724
+ const globalOpts = program.opts();
725
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
726
+ try {
727
+ const result = await sdk.deviceVerify(globalOpts.connectId);
728
+ outputResult(globalOpts, result);
729
+ }
730
+ finally {
731
+ sdk.dispose();
732
+ }
733
+ });
734
+ program
735
+ .command('lock')
736
+ .description('Lock the device')
737
+ .action(async () => {
738
+ const globalOpts = program.opts();
739
+ const sdk = await (0, sdk_1.createSDK)(globalOpts);
740
+ try {
741
+ const result = await sdk.deviceLock(globalOpts.connectId);
742
+ outputResult(globalOpts, result);
743
+ }
744
+ finally {
745
+ sdk.dispose();
746
+ }
747
+ });
748
+ // ============================================================
749
+ // Helpers
750
+ // ============================================================
751
+ /**
752
+ * Extract common device params from global CLI options.
753
+ * These are passed to every SDK method call.
754
+ */
755
+ function getCommonParams(globalOpts) {
756
+ return {
757
+ connectId: globalOpts.connectId,
758
+ deviceId: globalOpts.deviceId,
759
+ passphraseState: globalOpts.passphraseState,
760
+ useEmptyPassphrase: globalOpts.useEmptyPassphrase,
761
+ };
762
+ }
763
+ function outputResult(globalOpts, result) {
764
+ // #10 FIX: Always use JSON.stringify to avoid [Object] truncation
765
+ console.log(JSON.stringify(result, null, 2));
766
+ // #11 FIX: Exit with code 1 on SDK failure
767
+ if (result && typeof result === 'object' && 'success' in result && !result.success) {
768
+ process.exitCode = 1;
769
+ }
770
+ }
771
+ function wordCountToStrength(wordCount) {
772
+ // #8 FIX: Validate word count is one of the allowed values
773
+ if (![12, 18, 24].includes(wordCount)) {
774
+ throw new Error(`Invalid word count: ${wordCount}. Must be 12, 18, or 24.`);
775
+ }
776
+ switch (wordCount) {
777
+ case 12:
778
+ return 128;
779
+ case 18:
780
+ return 192;
781
+ case 24:
782
+ default:
783
+ return 256;
784
+ }
785
+ }
786
+ /**
787
+ * #6 FIX: Safe JSON.parse with structured error output
788
+ */
789
+ function safeJsonParse(input, label) {
790
+ try {
791
+ return JSON.parse(input);
792
+ }
793
+ catch {
794
+ console.error(JSON.stringify({
795
+ success: false,
796
+ payload: {
797
+ error: `Invalid JSON for ${label}: ${input.slice(0, 100)}`,
798
+ code: 'INVALID_JSON',
799
+ },
800
+ }));
801
+ process.exit(1);
802
+ }
803
+ }
804
+ /**
805
+ * #9 FIX: Safe parseInt with NaN check
806
+ */
807
+ function safeParseInt(input, label) {
808
+ const num = parseInt(input, 10);
809
+ if (Number.isNaN(num)) {
810
+ throw new Error(`Invalid number for ${label}: "${input}"`);
811
+ }
812
+ return num;
813
+ }
814
+ /**
815
+ * #15 FIX: Validate firmware version format
816
+ */
817
+ function parseVersion(input) {
818
+ const parts = input.split('.').map(Number);
819
+ if (parts.length < 2 || parts.length > 4 || parts.some(Number.isNaN)) {
820
+ throw new Error(`Invalid version format: "${input}". Expected format: "4.8.0"`);
821
+ }
822
+ return parts;
823
+ }
824
+ program.parse();