@ktmcp-cli/billingo 1.0.0 → 1.0.1

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/index.js ADDED
@@ -0,0 +1,808 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { getConfig, setConfig, isConfigured } from './config.js';
5
+ import {
6
+ listDocuments,
7
+ getDocument,
8
+ createDocument,
9
+ downloadDocument,
10
+ sendDocument,
11
+ listPartners,
12
+ getPartner,
13
+ createPartner,
14
+ updatePartner,
15
+ deletePartner,
16
+ listProducts,
17
+ getProduct,
18
+ createProduct,
19
+ updateProduct,
20
+ deleteProduct,
21
+ listBankAccounts,
22
+ getBankAccount,
23
+ createBankAccount,
24
+ updateBankAccount,
25
+ deleteBankAccount,
26
+ listDocumentBlocks,
27
+ getDocumentBlock
28
+ } from './api.js';
29
+
30
+ const program = new Command();
31
+
32
+ // ============================================================
33
+ // Helpers
34
+ // ============================================================
35
+
36
+ function printSuccess(message) {
37
+ console.log(chalk.green('✓') + ' ' + message);
38
+ }
39
+
40
+ function printError(message) {
41
+ console.error(chalk.red('✗') + ' ' + message);
42
+ }
43
+
44
+ function printTable(data, columns) {
45
+ if (!data || data.length === 0) {
46
+ console.log(chalk.yellow('No results found.'));
47
+ return;
48
+ }
49
+
50
+ const widths = {};
51
+ columns.forEach(col => {
52
+ widths[col.key] = col.label.length;
53
+ data.forEach(row => {
54
+ const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
55
+ if (val.length > widths[col.key]) widths[col.key] = val.length;
56
+ });
57
+ widths[col.key] = Math.min(widths[col.key], 40);
58
+ });
59
+
60
+ const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
61
+ console.log(chalk.bold(chalk.cyan(header)));
62
+ console.log(chalk.dim('─'.repeat(header.length)));
63
+
64
+ data.forEach(row => {
65
+ const line = columns.map(col => {
66
+ const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
67
+ return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
68
+ }).join(' ');
69
+ console.log(line);
70
+ });
71
+
72
+ console.log(chalk.dim(`\n${data.length} result(s)`));
73
+ }
74
+
75
+ function printJson(data) {
76
+ console.log(JSON.stringify(data, null, 2));
77
+ }
78
+
79
+ async function withSpinner(message, fn) {
80
+ const spinner = ora(message).start();
81
+ try {
82
+ const result = await fn();
83
+ spinner.stop();
84
+ return result;
85
+ } catch (error) {
86
+ spinner.stop();
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ function requireAuth() {
92
+ if (!isConfigured()) {
93
+ printError('Billingo API key not configured.');
94
+ console.log('\nRun the following to configure:');
95
+ console.log(chalk.cyan(' billingohu config set --api-key <key>'));
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ // ============================================================
101
+ // Program metadata
102
+ // ============================================================
103
+
104
+ program
105
+ .name('billingohu')
106
+ .description(chalk.bold('Billingo CLI') + ' - Hungarian invoicing from your terminal')
107
+ .version('1.0.0');
108
+
109
+ // ============================================================
110
+ // CONFIG
111
+ // ============================================================
112
+
113
+ const configCmd = program.command('config').description('Manage CLI configuration');
114
+
115
+ configCmd
116
+ .command('set')
117
+ .description('Set API key')
118
+ .option('--api-key <key>', 'Billingo API key')
119
+ .action((options) => {
120
+ if (options.apiKey) {
121
+ setConfig('apiKey', options.apiKey);
122
+ printSuccess(`API key set`);
123
+ } else {
124
+ printError('No API key provided. Use --api-key <key>');
125
+ }
126
+ });
127
+
128
+ configCmd
129
+ .command('show')
130
+ .description('Show current configuration')
131
+ .action(() => {
132
+ const apiKey = getConfig('apiKey');
133
+ console.log(chalk.bold('\nBillingo CLI Configuration\n'));
134
+ console.log('API Key: ', apiKey ? chalk.green('*'.repeat(16)) : chalk.red('not set'));
135
+ console.log('');
136
+ });
137
+
138
+ // ============================================================
139
+ // DOCUMENTS (Invoices)
140
+ // ============================================================
141
+
142
+ const documentsCmd = program.command('documents').description('Manage documents (invoices)');
143
+
144
+ documentsCmd
145
+ .command('list')
146
+ .description('List documents')
147
+ .option('--type <type>', 'Filter by type (invoice, receipt, proforma, etc.)')
148
+ .option('--status <status>', 'Filter by status')
149
+ .option('--page <n>', 'Page number', '1')
150
+ .option('--per-page <n>', 'Results per page', '25')
151
+ .option('--json', 'Output as JSON')
152
+ .action(async (options) => {
153
+ requireAuth();
154
+ try {
155
+ const documents = await withSpinner('Fetching documents...', () =>
156
+ listDocuments({
157
+ page: parseInt(options.page),
158
+ perPage: parseInt(options.perPage),
159
+ type: options.type,
160
+ status: options.status
161
+ })
162
+ );
163
+
164
+ if (options.json) {
165
+ printJson(documents);
166
+ return;
167
+ }
168
+
169
+ printTable(documents, [
170
+ { key: 'id', label: 'ID' },
171
+ { key: 'invoice_number', label: 'Number' },
172
+ { key: 'type', label: 'Type' },
173
+ { key: 'partner', label: 'Partner', format: (v) => v?.name || 'N/A' },
174
+ { key: 'gross_total', label: 'Total' },
175
+ { key: 'currency', label: 'Currency' },
176
+ { key: 'fulfillment_date', label: 'Date' }
177
+ ]);
178
+ } catch (error) {
179
+ printError(error.message);
180
+ process.exit(1);
181
+ }
182
+ });
183
+
184
+ documentsCmd
185
+ .command('get <id>')
186
+ .description('Get a specific document')
187
+ .option('--json', 'Output as JSON')
188
+ .action(async (id, options) => {
189
+ requireAuth();
190
+ try {
191
+ const document = await withSpinner('Fetching document...', () => getDocument(id));
192
+
193
+ if (!document) {
194
+ printError('Document not found');
195
+ process.exit(1);
196
+ }
197
+
198
+ if (options.json) {
199
+ printJson(document);
200
+ return;
201
+ }
202
+
203
+ console.log(chalk.bold('\nDocument Details\n'));
204
+ console.log('ID: ', chalk.cyan(document.id));
205
+ console.log('Number: ', document.invoice_number || 'N/A');
206
+ console.log('Type: ', document.type);
207
+ console.log('Partner: ', document.partner?.name || 'N/A');
208
+ console.log('Currency: ', document.currency);
209
+ console.log('Net Total: ', document.net_total);
210
+ console.log('Gross Total: ', chalk.bold(document.gross_total));
211
+ console.log('Date: ', document.fulfillment_date);
212
+ } catch (error) {
213
+ printError(error.message);
214
+ process.exit(1);
215
+ }
216
+ });
217
+
218
+ documentsCmd
219
+ .command('create')
220
+ .description('Create a new document')
221
+ .requiredOption('--data <json>', 'Document data as JSON')
222
+ .option('--json', 'Output as JSON')
223
+ .action(async (options) => {
224
+ requireAuth();
225
+ let documentData;
226
+ try {
227
+ documentData = JSON.parse(options.data);
228
+ } catch {
229
+ printError('Invalid JSON for --data');
230
+ process.exit(1);
231
+ }
232
+
233
+ try {
234
+ const document = await withSpinner('Creating document...', () =>
235
+ createDocument(documentData)
236
+ );
237
+
238
+ if (options.json) {
239
+ printJson(document);
240
+ return;
241
+ }
242
+
243
+ printSuccess(`Document created: ${chalk.bold(document.id)}`);
244
+ console.log('Number: ', document.invoice_number || 'N/A');
245
+ console.log('Total: ', document.gross_total, document.currency);
246
+ } catch (error) {
247
+ printError(error.message);
248
+ process.exit(1);
249
+ }
250
+ });
251
+
252
+ documentsCmd
253
+ .command('download <id>')
254
+ .description('Download document PDF')
255
+ .action(async (id) => {
256
+ requireAuth();
257
+ try {
258
+ const data = await withSpinner('Downloading document...', () => downloadDocument(id));
259
+ printSuccess('Document downloaded (data returned)');
260
+ printJson(data);
261
+ } catch (error) {
262
+ printError(error.message);
263
+ process.exit(1);
264
+ }
265
+ });
266
+
267
+ documentsCmd
268
+ .command('send <id>')
269
+ .description('Send document via email')
270
+ .requiredOption('--emails <emails>', 'Comma-separated email addresses')
271
+ .action(async (id, options) => {
272
+ requireAuth();
273
+ const emails = options.emails.split(',').map(e => e.trim());
274
+ try {
275
+ await withSpinner('Sending document...', () => sendDocument(id, emails));
276
+ printSuccess(`Document sent to: ${emails.join(', ')}`);
277
+ } catch (error) {
278
+ printError(error.message);
279
+ process.exit(1);
280
+ }
281
+ });
282
+
283
+ // ============================================================
284
+ // PARTNERS (Clients)
285
+ // ============================================================
286
+
287
+ const partnersCmd = program.command('partners').description('Manage partners (clients)');
288
+
289
+ partnersCmd
290
+ .command('list')
291
+ .description('List partners')
292
+ .option('--query <q>', 'Search query')
293
+ .option('--page <n>', 'Page number', '1')
294
+ .option('--per-page <n>', 'Results per page', '25')
295
+ .option('--json', 'Output as JSON')
296
+ .action(async (options) => {
297
+ requireAuth();
298
+ try {
299
+ const partners = await withSpinner('Fetching partners...', () =>
300
+ listPartners({
301
+ page: parseInt(options.page),
302
+ perPage: parseInt(options.perPage),
303
+ query: options.query
304
+ })
305
+ );
306
+
307
+ if (options.json) {
308
+ printJson(partners);
309
+ return;
310
+ }
311
+
312
+ printTable(partners, [
313
+ { key: 'id', label: 'ID' },
314
+ { key: 'name', label: 'Name' },
315
+ { key: 'email', label: 'Email' },
316
+ { key: 'taxcode', label: 'Tax Code' },
317
+ { key: 'iban', label: 'IBAN' }
318
+ ]);
319
+ } catch (error) {
320
+ printError(error.message);
321
+ process.exit(1);
322
+ }
323
+ });
324
+
325
+ partnersCmd
326
+ .command('get <id>')
327
+ .description('Get a specific partner')
328
+ .option('--json', 'Output as JSON')
329
+ .action(async (id, options) => {
330
+ requireAuth();
331
+ try {
332
+ const partner = await withSpinner('Fetching partner...', () => getPartner(id));
333
+
334
+ if (!partner) {
335
+ printError('Partner not found');
336
+ process.exit(1);
337
+ }
338
+
339
+ if (options.json) {
340
+ printJson(partner);
341
+ return;
342
+ }
343
+
344
+ console.log(chalk.bold('\nPartner Details\n'));
345
+ console.log('ID: ', chalk.cyan(partner.id));
346
+ console.log('Name: ', chalk.bold(partner.name));
347
+ console.log('Email: ', partner.email || 'N/A');
348
+ console.log('Tax Code: ', partner.taxcode || 'N/A');
349
+ console.log('IBAN: ', partner.iban || 'N/A');
350
+ console.log('Address: ', partner.address?.address || 'N/A');
351
+ } catch (error) {
352
+ printError(error.message);
353
+ process.exit(1);
354
+ }
355
+ });
356
+
357
+ partnersCmd
358
+ .command('create')
359
+ .description('Create a new partner')
360
+ .requiredOption('--data <json>', 'Partner data as JSON')
361
+ .option('--json', 'Output as JSON')
362
+ .action(async (options) => {
363
+ requireAuth();
364
+ let partnerData;
365
+ try {
366
+ partnerData = JSON.parse(options.data);
367
+ } catch {
368
+ printError('Invalid JSON for --data');
369
+ process.exit(1);
370
+ }
371
+
372
+ try {
373
+ const partner = await withSpinner('Creating partner...', () =>
374
+ createPartner(partnerData)
375
+ );
376
+
377
+ if (options.json) {
378
+ printJson(partner);
379
+ return;
380
+ }
381
+
382
+ printSuccess(`Partner created: ${chalk.bold(partner.name)}`);
383
+ console.log('ID: ', partner.id);
384
+ } catch (error) {
385
+ printError(error.message);
386
+ process.exit(1);
387
+ }
388
+ });
389
+
390
+ partnersCmd
391
+ .command('update <id>')
392
+ .description('Update a partner')
393
+ .requiredOption('--data <json>', 'Partner data as JSON')
394
+ .option('--json', 'Output as JSON')
395
+ .action(async (id, options) => {
396
+ requireAuth();
397
+ let partnerData;
398
+ try {
399
+ partnerData = JSON.parse(options.data);
400
+ } catch {
401
+ printError('Invalid JSON for --data');
402
+ process.exit(1);
403
+ }
404
+
405
+ try {
406
+ const partner = await withSpinner('Updating partner...', () =>
407
+ updatePartner(id, partnerData)
408
+ );
409
+
410
+ if (options.json) {
411
+ printJson(partner);
412
+ return;
413
+ }
414
+
415
+ printSuccess(`Partner updated: ${chalk.bold(partner.name)}`);
416
+ } catch (error) {
417
+ printError(error.message);
418
+ process.exit(1);
419
+ }
420
+ });
421
+
422
+ partnersCmd
423
+ .command('delete <id>')
424
+ .description('Delete a partner')
425
+ .action(async (id) => {
426
+ requireAuth();
427
+ try {
428
+ await withSpinner('Deleting partner...', () => deletePartner(id));
429
+ printSuccess('Partner deleted');
430
+ } catch (error) {
431
+ printError(error.message);
432
+ process.exit(1);
433
+ }
434
+ });
435
+
436
+ // ============================================================
437
+ // PRODUCTS
438
+ // ============================================================
439
+
440
+ const productsCmd = program.command('products').description('Manage products');
441
+
442
+ productsCmd
443
+ .command('list')
444
+ .description('List products')
445
+ .option('--page <n>', 'Page number', '1')
446
+ .option('--per-page <n>', 'Results per page', '25')
447
+ .option('--json', 'Output as JSON')
448
+ .action(async (options) => {
449
+ requireAuth();
450
+ try {
451
+ const products = await withSpinner('Fetching products...', () =>
452
+ listProducts({ page: parseInt(options.page), perPage: parseInt(options.perPage) })
453
+ );
454
+
455
+ if (options.json) {
456
+ printJson(products);
457
+ return;
458
+ }
459
+
460
+ printTable(products, [
461
+ { key: 'id', label: 'ID' },
462
+ { key: 'name', label: 'Name' },
463
+ { key: 'net_unit_price', label: 'Net Price' },
464
+ { key: 'gross_unit_price', label: 'Gross Price' },
465
+ { key: 'currency', label: 'Currency' },
466
+ { key: 'vat', label: 'VAT' }
467
+ ]);
468
+ } catch (error) {
469
+ printError(error.message);
470
+ process.exit(1);
471
+ }
472
+ });
473
+
474
+ productsCmd
475
+ .command('get <id>')
476
+ .description('Get a specific product')
477
+ .option('--json', 'Output as JSON')
478
+ .action(async (id, options) => {
479
+ requireAuth();
480
+ try {
481
+ const product = await withSpinner('Fetching product...', () => getProduct(id));
482
+
483
+ if (!product) {
484
+ printError('Product not found');
485
+ process.exit(1);
486
+ }
487
+
488
+ if (options.json) {
489
+ printJson(product);
490
+ return;
491
+ }
492
+
493
+ console.log(chalk.bold('\nProduct Details\n'));
494
+ console.log('ID: ', chalk.cyan(product.id));
495
+ console.log('Name: ', chalk.bold(product.name));
496
+ console.log('Net Price: ', product.net_unit_price);
497
+ console.log('Gross Price: ', product.gross_unit_price);
498
+ console.log('Currency: ', product.currency);
499
+ console.log('VAT: ', product.vat);
500
+ } catch (error) {
501
+ printError(error.message);
502
+ process.exit(1);
503
+ }
504
+ });
505
+
506
+ productsCmd
507
+ .command('create')
508
+ .description('Create a new product')
509
+ .requiredOption('--data <json>', 'Product data as JSON')
510
+ .option('--json', 'Output as JSON')
511
+ .action(async (options) => {
512
+ requireAuth();
513
+ let productData;
514
+ try {
515
+ productData = JSON.parse(options.data);
516
+ } catch {
517
+ printError('Invalid JSON for --data');
518
+ process.exit(1);
519
+ }
520
+
521
+ try {
522
+ const product = await withSpinner('Creating product...', () =>
523
+ createProduct(productData)
524
+ );
525
+
526
+ if (options.json) {
527
+ printJson(product);
528
+ return;
529
+ }
530
+
531
+ printSuccess(`Product created: ${chalk.bold(product.name)}`);
532
+ console.log('ID: ', product.id);
533
+ } catch (error) {
534
+ printError(error.message);
535
+ process.exit(1);
536
+ }
537
+ });
538
+
539
+ productsCmd
540
+ .command('update <id>')
541
+ .description('Update a product')
542
+ .requiredOption('--data <json>', 'Product data as JSON')
543
+ .option('--json', 'Output as JSON')
544
+ .action(async (id, options) => {
545
+ requireAuth();
546
+ let productData;
547
+ try {
548
+ productData = JSON.parse(options.data);
549
+ } catch {
550
+ printError('Invalid JSON for --data');
551
+ process.exit(1);
552
+ }
553
+
554
+ try {
555
+ const product = await withSpinner('Updating product...', () =>
556
+ updateProduct(id, productData)
557
+ );
558
+
559
+ if (options.json) {
560
+ printJson(product);
561
+ return;
562
+ }
563
+
564
+ printSuccess(`Product updated: ${chalk.bold(product.name)}`);
565
+ } catch (error) {
566
+ printError(error.message);
567
+ process.exit(1);
568
+ }
569
+ });
570
+
571
+ productsCmd
572
+ .command('delete <id>')
573
+ .description('Delete a product')
574
+ .action(async (id) => {
575
+ requireAuth();
576
+ try {
577
+ await withSpinner('Deleting product...', () => deleteProduct(id));
578
+ printSuccess('Product deleted');
579
+ } catch (error) {
580
+ printError(error.message);
581
+ process.exit(1);
582
+ }
583
+ });
584
+
585
+ // ============================================================
586
+ // BANK ACCOUNTS
587
+ // ============================================================
588
+
589
+ const bankAccountsCmd = program.command('bank-accounts').description('Manage bank accounts');
590
+
591
+ bankAccountsCmd
592
+ .command('list')
593
+ .description('List bank accounts')
594
+ .option('--page <n>', 'Page number', '1')
595
+ .option('--per-page <n>', 'Results per page', '25')
596
+ .option('--json', 'Output as JSON')
597
+ .action(async (options) => {
598
+ requireAuth();
599
+ try {
600
+ const accounts = await withSpinner('Fetching bank accounts...', () =>
601
+ listBankAccounts({ page: parseInt(options.page), perPage: parseInt(options.perPage) })
602
+ );
603
+
604
+ if (options.json) {
605
+ printJson(accounts);
606
+ return;
607
+ }
608
+
609
+ printTable(accounts, [
610
+ { key: 'id', label: 'ID' },
611
+ { key: 'name', label: 'Name' },
612
+ { key: 'account_number', label: 'Account Number' },
613
+ { key: 'iban', label: 'IBAN' },
614
+ { key: 'swift', label: 'SWIFT' },
615
+ { key: 'currency', label: 'Currency' }
616
+ ]);
617
+ } catch (error) {
618
+ printError(error.message);
619
+ process.exit(1);
620
+ }
621
+ });
622
+
623
+ bankAccountsCmd
624
+ .command('get <id>')
625
+ .description('Get a specific bank account')
626
+ .option('--json', 'Output as JSON')
627
+ .action(async (id, options) => {
628
+ requireAuth();
629
+ try {
630
+ const account = await withSpinner('Fetching bank account...', () => getBankAccount(id));
631
+
632
+ if (!account) {
633
+ printError('Bank account not found');
634
+ process.exit(1);
635
+ }
636
+
637
+ if (options.json) {
638
+ printJson(account);
639
+ return;
640
+ }
641
+
642
+ console.log(chalk.bold('\nBank Account Details\n'));
643
+ console.log('ID: ', chalk.cyan(account.id));
644
+ console.log('Name: ', chalk.bold(account.name));
645
+ console.log('Account Number: ', account.account_number);
646
+ console.log('IBAN: ', account.iban);
647
+ console.log('SWIFT: ', account.swift);
648
+ console.log('Currency: ', account.currency);
649
+ } catch (error) {
650
+ printError(error.message);
651
+ process.exit(1);
652
+ }
653
+ });
654
+
655
+ bankAccountsCmd
656
+ .command('create')
657
+ .description('Create a new bank account')
658
+ .requiredOption('--data <json>', 'Bank account data as JSON')
659
+ .option('--json', 'Output as JSON')
660
+ .action(async (options) => {
661
+ requireAuth();
662
+ let accountData;
663
+ try {
664
+ accountData = JSON.parse(options.data);
665
+ } catch {
666
+ printError('Invalid JSON for --data');
667
+ process.exit(1);
668
+ }
669
+
670
+ try {
671
+ const account = await withSpinner('Creating bank account...', () =>
672
+ createBankAccount(accountData)
673
+ );
674
+
675
+ if (options.json) {
676
+ printJson(account);
677
+ return;
678
+ }
679
+
680
+ printSuccess(`Bank account created: ${chalk.bold(account.name)}`);
681
+ console.log('ID: ', account.id);
682
+ } catch (error) {
683
+ printError(error.message);
684
+ process.exit(1);
685
+ }
686
+ });
687
+
688
+ bankAccountsCmd
689
+ .command('update <id>')
690
+ .description('Update a bank account')
691
+ .requiredOption('--data <json>', 'Bank account data as JSON')
692
+ .option('--json', 'Output as JSON')
693
+ .action(async (id, options) => {
694
+ requireAuth();
695
+ let accountData;
696
+ try {
697
+ accountData = JSON.parse(options.data);
698
+ } catch {
699
+ printError('Invalid JSON for --data');
700
+ process.exit(1);
701
+ }
702
+
703
+ try {
704
+ const account = await withSpinner('Updating bank account...', () =>
705
+ updateBankAccount(id, accountData)
706
+ );
707
+
708
+ if (options.json) {
709
+ printJson(account);
710
+ return;
711
+ }
712
+
713
+ printSuccess(`Bank account updated: ${chalk.bold(account.name)}`);
714
+ } catch (error) {
715
+ printError(error.message);
716
+ process.exit(1);
717
+ }
718
+ });
719
+
720
+ bankAccountsCmd
721
+ .command('delete <id>')
722
+ .description('Delete a bank account')
723
+ .action(async (id) => {
724
+ requireAuth();
725
+ try {
726
+ await withSpinner('Deleting bank account...', () => deleteBankAccount(id));
727
+ printSuccess('Bank account deleted');
728
+ } catch (error) {
729
+ printError(error.message);
730
+ process.exit(1);
731
+ }
732
+ });
733
+
734
+ // ============================================================
735
+ // DOCUMENT BLOCKS (Invoice pads)
736
+ // ============================================================
737
+
738
+ const blocksCmd = program.command('document-blocks').description('Manage document blocks (invoice pads)');
739
+
740
+ blocksCmd
741
+ .command('list')
742
+ .description('List document blocks')
743
+ .option('--page <n>', 'Page number', '1')
744
+ .option('--per-page <n>', 'Results per page', '25')
745
+ .option('--json', 'Output as JSON')
746
+ .action(async (options) => {
747
+ requireAuth();
748
+ try {
749
+ const blocks = await withSpinner('Fetching document blocks...', () =>
750
+ listDocumentBlocks({ page: parseInt(options.page), perPage: parseInt(options.perPage) })
751
+ );
752
+
753
+ if (options.json) {
754
+ printJson(blocks);
755
+ return;
756
+ }
757
+
758
+ printTable(blocks, [
759
+ { key: 'id', label: 'ID' },
760
+ { key: 'name', label: 'Name' },
761
+ { key: 'prefix', label: 'Prefix' },
762
+ { key: 'type', label: 'Type' }
763
+ ]);
764
+ } catch (error) {
765
+ printError(error.message);
766
+ process.exit(1);
767
+ }
768
+ });
769
+
770
+ blocksCmd
771
+ .command('get <id>')
772
+ .description('Get a specific document block')
773
+ .option('--json', 'Output as JSON')
774
+ .action(async (id, options) => {
775
+ requireAuth();
776
+ try {
777
+ const block = await withSpinner('Fetching document block...', () => getDocumentBlock(id));
778
+
779
+ if (!block) {
780
+ printError('Document block not found');
781
+ process.exit(1);
782
+ }
783
+
784
+ if (options.json) {
785
+ printJson(block);
786
+ return;
787
+ }
788
+
789
+ console.log(chalk.bold('\nDocument Block Details\n'));
790
+ console.log('ID: ', chalk.cyan(block.id));
791
+ console.log('Name: ', chalk.bold(block.name));
792
+ console.log('Prefix: ', block.prefix);
793
+ console.log('Type: ', block.type);
794
+ } catch (error) {
795
+ printError(error.message);
796
+ process.exit(1);
797
+ }
798
+ });
799
+
800
+ // ============================================================
801
+ // Parse
802
+ // ============================================================
803
+
804
+ program.parse(process.argv);
805
+
806
+ if (process.argv.length <= 2) {
807
+ program.help();
808
+ }