incwo-cli 0.1.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.
@@ -0,0 +1,560 @@
1
+ export interface ColumnDef {
2
+ key: string;
3
+ label: string;
4
+ width?: number;
5
+ }
6
+
7
+ export interface ResourceDef {
8
+ /** CLI command name — matches the API endpoint */
9
+ command: string;
10
+ /** Short description */
11
+ description: string;
12
+ /** REST endpoint (always matches command) */
13
+ endpoint: string;
14
+ /** Columns to display in list output */
15
+ columns: ColumnDef[];
16
+ /** Fixed params appended to every list request */
17
+ defaultParams?: Record<string, string>;
18
+ /** Whether to expose --from / --to date filters */
19
+ hasDateFilter?: boolean;
20
+ /** Whether to expose --sheet-type filter (proposal_sheets only) */
21
+ hasSheetType?: boolean;
22
+ }
23
+
24
+ export const RESOURCES: ResourceDef[] = [
25
+
26
+ // ── CRM ──────────────────────────────────────────────────────────────────
27
+
28
+ {
29
+ command: 'contacts',
30
+ description: 'Contacts (people)',
31
+ endpoint: 'contacts',
32
+ columns: [
33
+ { key: 'id', label: 'ID', width: 8 },
34
+ { key: 'first_name', label: 'First name', width: 16 },
35
+ { key: 'last_name', label: 'Last name', width: 18 },
36
+ { key: 'firm_name', label: 'Company name', width: 24 },
37
+ { key: 'job_title', label: 'Job title', width: 22 },
38
+ { key: 'created_at', label: 'Created', width: 12 },
39
+ ],
40
+ },
41
+
42
+ {
43
+ command: 'firms',
44
+ description: 'Companies / organisations',
45
+ endpoint: 'firms',
46
+ columns: [
47
+ { key: 'id', label: 'ID', width: 8 },
48
+ { key: 'name', label: 'Name', width: 32 },
49
+ { key: 'siren', label: 'Siren', width: 14 },
50
+ { key: 'reference', label: 'Reference', width: 16 },
51
+ { key: 'created_at', label: 'Created', width: 12 },
52
+ ],
53
+ },
54
+
55
+ {
56
+ command: 'leads',
57
+ description: 'Sales opportunities',
58
+ endpoint: 'leads',
59
+ columns: [
60
+ { key: 'id', label: 'ID', width: 8 },
61
+ { key: 'reference', label: 'Reference', width: 16 },
62
+ { key: 'contact_id', label: 'Contact ID', width: 12 },
63
+ { key: 'firm_id', label: 'Firm ID', width: 10 },
64
+ { key: 'status_id', label: 'Status', width: 10 },
65
+ { key: 'step_id', label: 'Progress', width: 10 },
66
+ { key: 'value', label: 'Value', width: 12 },
67
+ { key: 'value_date', label: 'Est. sign. date', width: 14 },
68
+ { key: 'confidence_pct', label: 'Confidence %', width: 12 },
69
+ ],
70
+ hasDateFilter: true,
71
+ },
72
+
73
+ {
74
+ command: 'contact_items',
75
+ description: 'Contact details (emails, phones…)',
76
+ endpoint: 'contact_items',
77
+ columns: [
78
+ { key: 'id', label: 'ID', width: 8 },
79
+ { key: 'type_id', label: 'Type', width: 14 },
80
+ { key: 'value', label: 'Value', width: 34 },
81
+ { key: 'contact_id', label: 'Contact ID', width: 12 },
82
+ { key: 'is_default', label: 'Default', width: 8 },
83
+ ],
84
+ },
85
+
86
+ {
87
+ command: 'contact_addresses',
88
+ description: 'Contact addresses',
89
+ endpoint: 'contact_addresses',
90
+ columns: [
91
+ { key: 'id', label: 'ID', width: 8 },
92
+ { key: 'contact_id', label: 'Contact ID', width: 12 },
93
+ { key: 'street_address', label: 'Address', width: 30 },
94
+ { key: 'zip_code', label: 'ZIP', width: 8 },
95
+ { key: 'city', label: 'City', width: 18 },
96
+ { key: 'country', label: 'Country', width: 12 },
97
+ ],
98
+ },
99
+
100
+ {
101
+ command: 'contact_lists',
102
+ description: 'Contact lists',
103
+ endpoint: 'contact_lists',
104
+ columns: [
105
+ { key: 'id', label: 'ID', width: 8 },
106
+ { key: 'name', label: 'Name', width: 36 },
107
+ { key: 'description', label: 'Description', width: 30 },
108
+ { key: 'created_at', label: 'Created', width: 12 },
109
+ ],
110
+ },
111
+
112
+ {
113
+ command: 'missions',
114
+ description: 'Missions',
115
+ endpoint: 'missions',
116
+ columns: [
117
+ { key: 'id', label: 'ID', width: 8 },
118
+ { key: 'kind', label: 'Kind', width: 14 },
119
+ { key: 'title', label: 'Title', width: 32 },
120
+ { key: 'status_id', label: 'Status', width: 10 },
121
+ { key: 'progress', label: 'Progress', width: 12 },
122
+ { key: 'created_at', label: 'Created', width: 12 },
123
+ ],
124
+ },
125
+
126
+ // ── SALES ─────────────────────────────────────────────────────────────────
127
+
128
+ {
129
+ command: 'proposal_sheets',
130
+ description: 'Proposal sheets (quotes, orders, delivery notes…)',
131
+ endpoint: 'proposal_sheets',
132
+ hasSheetType: true,
133
+ columns: [
134
+ { key: 'id', label: 'ID', width: 8 },
135
+ { key: 'reference', label: 'Reference', width: 16 },
136
+ { key: 'sheet_type', label: 'Type', width: 14 },
137
+ { key: 'title', label: 'Title', width: 24 },
138
+ { key: 'billing_date', label: 'Billing date', width: 12 },
139
+ { key: 'firm_name', label: 'Company', width: 22 },
140
+ { key: 'vat_exl_total', label: 'Total excl. VAT', width: 14 },
141
+ { key: 'vat_inc_total', label: 'Total incl. VAT', width: 14 },
142
+ { key: 'status_id', label: 'Status', width: 10 },
143
+ { key: 'sent', label: 'Sent', width: 8 },
144
+ ],
145
+ hasDateFilter: true,
146
+ },
147
+
148
+ {
149
+ command: 'bill_sheets',
150
+ description: 'Customer invoices',
151
+ endpoint: 'bill_sheets',
152
+ columns: [
153
+ { key: 'id', label: 'ID', width: 8 },
154
+ { key: 'reference', label: 'Reference', width: 16 },
155
+ { key: 'sheet_type', label: 'Type', width: 12 },
156
+ { key: 'title', label: 'Title', width: 24 },
157
+ { key: 'billing_date', label: 'Billing date', width: 12 },
158
+ { key: 'firm_name', label: 'Company', width: 22 },
159
+ { key: 'vat_exl_total', label: 'Total excl. VAT', width: 14 },
160
+ { key: 'vat_inc_total', label: 'Total incl. VAT', width: 14 },
161
+ { key: 'payed_amount', label: 'Paid amount', width: 12 },
162
+ { key: 'sent', label: 'Sent', width: 8 },
163
+ ],
164
+ hasDateFilter: true,
165
+ },
166
+
167
+ {
168
+ command: 'emitted_payments',
169
+ description: 'Vendor bills / purchase invoices',
170
+ endpoint: 'emitted_payments',
171
+ columns: [
172
+ { key: 'id', label: 'ID', width: 8 },
173
+ { key: 'reference', label: 'Reference', width: 16 },
174
+ { key: 'kind', label: 'Kind', width: 12 },
175
+ { key: 'title', label: 'Title', width: 24 },
176
+ { key: 'payment_date', label: 'Payment date', width: 12 },
177
+ { key: 'amount', label: 'Amount', width: 12 },
178
+ { key: 'status_id', label: 'Status', width: 10 },
179
+ ],
180
+ hasDateFilter: true,
181
+ },
182
+
183
+ {
184
+ command: 'customer_products',
185
+ description: 'Product / service catalog',
186
+ endpoint: 'customer_products',
187
+ columns: [
188
+ { key: 'id', label: 'ID', width: 8 },
189
+ { key: 'reference', label: 'Ref.', width: 14 },
190
+ { key: 'name', label: 'Name', width: 30 },
191
+ { key: 'price', label: 'Price', width: 10 },
192
+ { key: 'unit', label: 'Unit', width: 8 },
193
+ { key: 'vat_id', label: 'VAT', width: 8 },
194
+ { key: 'is_active', label: 'Active', width: 8 },
195
+ ],
196
+ },
197
+
198
+ {
199
+ command: 'customer_product_categories',
200
+ description: 'Product categories',
201
+ endpoint: 'customer_product_categories',
202
+ columns: [
203
+ { key: 'id', label: 'ID', width: 8 },
204
+ { key: 'name', label: 'Name', width: 30 },
205
+ { key: 'parent_id', label: 'Parent ID', width: 10 },
206
+ { key: 'accounting_ref', label: 'Accounting ref', width: 16 },
207
+ { key: 'created_at', label: 'Created', width: 12 },
208
+ ],
209
+ },
210
+
211
+ {
212
+ command: 'customer_pricings',
213
+ description: 'Customer pricing rules',
214
+ endpoint: 'customer_pricings',
215
+ columns: [
216
+ { key: 'id', label: 'ID', width: 8 },
217
+ { key: 'customer_product_id', label: 'Product ID', width: 12 },
218
+ { key: 'name', label: 'Name', width: 26 },
219
+ { key: 'price_ht', label: 'Price excl. VAT', width: 14 },
220
+ { key: 'reduction_percent', label: 'Discount %', width: 12 },
221
+ { key: 'supplier_ref', label: 'Supplier ref', width: 16 },
222
+ ],
223
+ },
224
+
225
+ {
226
+ command: 'vendor_pricings',
227
+ description: 'Vendor pricing rules',
228
+ endpoint: 'vendor_pricings',
229
+ columns: [
230
+ { key: 'id', label: 'ID', width: 8 },
231
+ { key: 'customer_product_id', label: 'Product ID', width: 12 },
232
+ { key: 'name', label: 'Name', width: 26 },
233
+ { key: 'price_ht', label: 'Price excl. VAT', width: 14 },
234
+ { key: 'reduction_percent', label: 'Discount %', width: 12 },
235
+ { key: 'supplier_ref', label: 'Supplier ref', width: 16 },
236
+ ],
237
+ },
238
+
239
+ {
240
+ command: 'bank_accounts',
241
+ description: 'Bank accounts',
242
+ endpoint: 'bank_accounts',
243
+ columns: [
244
+ { key: 'id', label: 'ID', width: 8 },
245
+ { key: 'name', label: 'Name', width: 28 },
246
+ { key: 'city', label: 'City', width: 18 },
247
+ { key: 'accounting_ref', label: 'Accounting ref', width: 18 },
248
+ { key: 'created_at', label: 'Created', width: 12 },
249
+ ],
250
+ },
251
+
252
+ {
253
+ command: 'campaigns',
254
+ description: 'Marketing campaigns',
255
+ endpoint: 'campaigns',
256
+ columns: [
257
+ { key: 'id', label: 'ID', width: 8 },
258
+ { key: 'title', label: 'Title', width: 30 },
259
+ { key: 'reference', label: 'Reference', width: 16 },
260
+ { key: 'begins_at', label: 'Begins at', width: 12 },
261
+ { key: 'ends_at', label: 'Ends at', width: 12 },
262
+ ],
263
+ hasDateFilter: true,
264
+ },
265
+
266
+ // ── INVENTORY ─────────────────────────────────────────────────────────────
267
+
268
+ {
269
+ command: 'stock_movements',
270
+ description: 'Stock movements',
271
+ endpoint: 'stock_movements',
272
+ columns: [
273
+ { key: 'id', label: 'ID', width: 8 },
274
+ { key: 'customer_product_id', label: 'Product ID', width: 12 },
275
+ { key: 'direction', label: 'Direction', width: 10 },
276
+ { key: 'quantity', label: 'Qty', width: 8 },
277
+ { key: 'origin_warehouse_id', label: 'Origin WH', width: 12 },
278
+ { key: 'destination_warehouse_id', label: 'Dest. WH', width: 12 },
279
+ { key: 'moved_at', label: 'Moved at', width: 14 },
280
+ ],
281
+ hasDateFilter: true,
282
+ },
283
+
284
+ {
285
+ command: 'serial_numbers',
286
+ description: 'Serial numbers',
287
+ endpoint: 'serial_numbers',
288
+ columns: [
289
+ { key: 'id', label: 'ID', width: 8 },
290
+ { key: 'batch', label: 'Batch', width: 20 },
291
+ { key: 'barcode', label: 'Barcode', width: 18 },
292
+ { key: 'customer_product_id', label: 'Product ID', width: 12 },
293
+ { key: 'warehouse_id', label: 'Warehouse ID', width: 14 },
294
+ { key: 'quantity', label: 'Qty', width: 8 },
295
+ { key: 'moved_at', label: 'Moved at', width: 14 },
296
+ ],
297
+ },
298
+
299
+ {
300
+ command: 'serial_lots',
301
+ description: 'Serial lot numbers',
302
+ endpoint: 'serial_lots',
303
+ columns: [
304
+ { key: 'id', label: 'ID', width: 8 },
305
+ { key: 'batch', label: 'Batch', width: 20 },
306
+ { key: 'barcode', label: 'Barcode', width: 18 },
307
+ { key: 'customer_product_id', label: 'Product ID', width: 12 },
308
+ { key: 'warehouse_id', label: 'Warehouse ID', width: 14 },
309
+ { key: 'quantity', label: 'Qty', width: 8 },
310
+ { key: 'moved_at', label: 'Moved at', width: 14 },
311
+ ],
312
+ },
313
+
314
+ {
315
+ command: 'warehouses',
316
+ description: 'Warehouses / depots',
317
+ endpoint: 'warehouses',
318
+ columns: [
319
+ { key: 'id', label: 'ID', width: 8 },
320
+ { key: 'name', label: 'Name', width: 28 },
321
+ { key: 'city', label: 'City', width: 16 },
322
+ { key: 'zip_code', label: 'ZIP', width: 8 },
323
+ { key: 'country', label: 'Country', width: 12 },
324
+ { key: 'street_address', label: 'Address', width: 24 },
325
+ ],
326
+ },
327
+
328
+ // ── PROJECTS / TIME ───────────────────────────────────────────────────────
329
+
330
+ {
331
+ command: 'projects',
332
+ description: 'Projects',
333
+ endpoint: 'projects',
334
+ columns: [
335
+ { key: 'id', label: 'ID', width: 8 },
336
+ { key: 'name', label: 'Name', width: 28 },
337
+ { key: 'reference', label: 'Reference', width: 14 },
338
+ { key: 'status_id', label: 'Status', width: 10 },
339
+ { key: 'start_date', label: 'Start', width: 12 },
340
+ { key: 'end_date', label: 'End', width: 12 },
341
+ ],
342
+ hasDateFilter: true,
343
+ },
344
+
345
+ {
346
+ command: 'tasks',
347
+ description: 'Tasks',
348
+ endpoint: 'tasks',
349
+ columns: [
350
+ { key: 'id', label: 'ID', width: 8 },
351
+ { key: 'title', label: 'Title', width: 30 },
352
+ { key: 'status_id', label: 'Status', width: 10 },
353
+ { key: 'due_at', label: 'Due at', width: 12 },
354
+ { key: 'assigned_user_id', label: 'Assigned user', width: 14 },
355
+ { key: 'completion_percent', label: '% done', width: 8 },
356
+ ],
357
+ hasDateFilter: true,
358
+ },
359
+
360
+ {
361
+ command: 'timesheets',
362
+ description: 'Timesheets',
363
+ endpoint: 'timesheets',
364
+ columns: [
365
+ { key: 'id', label: 'ID', width: 8 },
366
+ { key: 'title', label: 'Title', width: 28 },
367
+ { key: 'period', label: 'Month', width: 10 },
368
+ { key: 'user_id', label: 'User ID', width: 10 },
369
+ { key: 'status_id', label: 'Status', width: 10 },
370
+ { key: 'sheet_type', label: 'Sheet type', width: 14 },
371
+ ],
372
+ hasDateFilter: true,
373
+ },
374
+
375
+ // ── HR ────────────────────────────────────────────────────────────────────
376
+
377
+ {
378
+ command: 'staff_members',
379
+ description: 'Staff members',
380
+ endpoint: 'staff_members',
381
+ columns: [
382
+ { key: 'id', label: 'ID', width: 8 },
383
+ { key: 'first_name', label: 'First name', width: 16 },
384
+ { key: 'last_name', label: 'Last name', width: 18 },
385
+ { key: 'job_label', label: 'Job', width: 20 },
386
+ { key: 'email', label: 'Email', width: 26 },
387
+ { key: 'status_id', label: 'Status', width: 10 },
388
+ ],
389
+ },
390
+
391
+ {
392
+ command: 'expense_sheets',
393
+ description: 'Expense reports',
394
+ endpoint: 'expense_sheets',
395
+ columns: [
396
+ { key: 'id', label: 'ID', width: 8 },
397
+ { key: 'staff_member_id', label: 'Staff member', width: 14 },
398
+ { key: 'period', label: 'Month', width: 10 },
399
+ { key: 'status_id', label: 'Status', width: 10 },
400
+ { key: 'payed_amount', label: 'Paid amount', width: 12 },
401
+ { key: 'submission_date', label: 'Submitted', width: 14 },
402
+ ],
403
+ hasDateFilter: true,
404
+ },
405
+
406
+ {
407
+ command: 'vacation_requests',
408
+ description: 'Vacation requests',
409
+ endpoint: 'vacation_requests',
410
+ columns: [
411
+ { key: 'id', label: 'ID', width: 8 },
412
+ { key: 'staff_member_id', label: 'Staff member', width: 14 },
413
+ { key: 'vacation_type_id', label: 'Type', width: 10 },
414
+ { key: 'start_date', label: 'Start', width: 14 },
415
+ { key: 'stop_date', label: 'End', width: 14 },
416
+ { key: 'duration', label: 'Duration', width: 10 },
417
+ { key: 'status_id', label: 'Status', width: 10 },
418
+ ],
419
+ hasDateFilter: true,
420
+ },
421
+
422
+ {
423
+ command: 'salary_campaigns',
424
+ description: 'Salary campaigns',
425
+ endpoint: 'salary_campaigns',
426
+ columns: [
427
+ { key: 'id', label: 'ID', width: 8 },
428
+ { key: 'period', label: 'Period', width: 14 },
429
+ { key: 'status_id', label: 'Status', width: 10 },
430
+ { key: 'created_at', label: 'Created', width: 12 },
431
+ ],
432
+ },
433
+
434
+ {
435
+ command: 'contracts',
436
+ description: 'Contracts',
437
+ endpoint: 'contracts',
438
+ columns: [
439
+ { key: 'id', label: 'ID', width: 8 },
440
+ { key: 'title', label: 'Title', width: 30 },
441
+ { key: 'reference', label: 'Reference', width: 16 },
442
+ { key: 'begin_date', label: 'Start', width: 12 },
443
+ { key: 'end_date', label: 'End', width: 12 },
444
+ { key: 'is_active', label: 'Active', width: 8 },
445
+ ],
446
+ hasDateFilter: true,
447
+ },
448
+
449
+ {
450
+ command: 'interviews',
451
+ description: 'HR interviews',
452
+ endpoint: 'interviews',
453
+ columns: [
454
+ { key: 'id', label: 'ID', width: 8 },
455
+ { key: 'job_candidate_id', label: 'Candidate ID', width: 14 },
456
+ { key: 'staff_member_id', label: 'Staff member', width: 14 },
457
+ { key: 'status_id', label: 'Status', width: 10 },
458
+ { key: 'comments', label: 'Comments', width: 30 },
459
+ { key: 'created_at', label: 'Created', width: 12 },
460
+ ],
461
+ hasDateFilter: true,
462
+ },
463
+
464
+ // ── MISC ──────────────────────────────────────────────────────────────────
465
+
466
+ {
467
+ command: 'notes',
468
+ description: 'Notes',
469
+ endpoint: 'notes',
470
+ columns: [
471
+ { key: 'id', label: 'ID', width: 8 },
472
+ { key: 'title', label: 'Title', width: 32 },
473
+ { key: 'type_id', label: 'Type', width: 10 },
474
+ { key: 'object_name', label: 'Support object', width: 16 },
475
+ { key: 'created_at', label: 'Created', width: 14 },
476
+ ],
477
+ hasDateFilter: true,
478
+ },
479
+
480
+ {
481
+ command: 'upload_files',
482
+ description: 'Files / attachments',
483
+ endpoint: 'upload_files',
484
+ columns: [
485
+ { key: 'id', label: 'ID', width: 8 },
486
+ { key: 'object_zname', label: 'Support object', width: 20 },
487
+ { key: 'object_zid', label: 'Object ID', width: 12 },
488
+ { key: 'shared_with_party', label: 'Shared', width: 8 },
489
+ { key: 'created_at', label: 'Created', width: 14 },
490
+ ],
491
+ hasDateFilter: true,
492
+ },
493
+
494
+ {
495
+ command: 'custom_labels',
496
+ description: 'Custom labels',
497
+ endpoint: 'custom_labels',
498
+ columns: [
499
+ { key: 'id', label: 'ID', width: 8 },
500
+ { key: 'label_type', label: 'Label type', width: 16 },
501
+ { key: 'long_label', label: 'Label', width: 28 },
502
+ { key: 'short_label', label: 'Short label', width: 16 },
503
+ { key: 'color', label: 'Color', width: 10 },
504
+ ],
505
+ },
506
+
507
+ {
508
+ command: 'conversations',
509
+ description: 'Conversations / messaging',
510
+ endpoint: 'conversations',
511
+ columns: [
512
+ { key: 'id', label: 'ID', width: 8 },
513
+ { key: 'title', label: 'Title', width: 34 },
514
+ { key: 'type_id', label: 'Type', width: 10 },
515
+ { key: 'status_id', label: 'Status', width: 10 },
516
+ { key: 'created_at', label: 'Created', width: 14 },
517
+ ],
518
+ hasDateFilter: true,
519
+ },
520
+
521
+ {
522
+ command: 'webhooks',
523
+ description: 'Configured webhooks',
524
+ endpoint: 'webhooks',
525
+ columns: [
526
+ { key: 'id', label: 'ID', width: 8 },
527
+ { key: 'object_zname', label: 'Object', width: 20 },
528
+ { key: 'object_zfield', label: 'Field', width: 20 },
529
+ { key: 'turl', label: 'URL', width: 36 },
530
+ { key: 'active', label: 'Active', width: 8 },
531
+ ],
532
+ },
533
+
534
+ {
535
+ command: 'bookables',
536
+ description: 'Bookable resources',
537
+ endpoint: 'bookables',
538
+ columns: [
539
+ { key: 'id', label: 'ID', width: 8 },
540
+ { key: 'kind', label: 'Kind', width: 16 },
541
+ { key: 'title', label: 'Title', width: 30 },
542
+ { key: 'price', label: 'Price', width: 12 },
543
+ { key: 'created_at', label: 'Created', width: 12 },
544
+ ],
545
+ },
546
+
547
+ {
548
+ command: 'fleets',
549
+ description: 'Vehicle fleets',
550
+ endpoint: 'fleets',
551
+ columns: [
552
+ { key: 'id', label: 'ID', width: 8 },
553
+ { key: 'kind', label: 'Kind', width: 16 },
554
+ { key: 'title', label: 'Title', width: 30 },
555
+ { key: 'contact_id', label: 'Contact ID', width: 12 },
556
+ { key: 'created_at', label: 'Created', width: 12 },
557
+ ],
558
+ },
559
+
560
+ ];
package/src/ui.ts ADDED
@@ -0,0 +1,57 @@
1
+ import chalk from 'chalk';
2
+ import figlet from 'figlet';
3
+
4
+ export function printBanner(): void {
5
+ const banner = figlet.textSync('incwo CLI', {
6
+ font: 'ANSI Shadow',
7
+ horizontalLayout: 'default',
8
+ });
9
+ console.log(chalk.cyan(banner));
10
+ console.log(chalk.dim(' Your CRM/ERP · incwo.com\n'));
11
+ }
12
+
13
+ export function printTable(rows: any[], columns: { key: string; label: string; width?: number }[]): void {
14
+ if (!rows || rows.length === 0) {
15
+ console.log(chalk.yellow(' No results.'));
16
+ return;
17
+ }
18
+
19
+ const widths = columns.map(col => {
20
+ const max = Math.max(col.label.length, ...rows.map(r => String(r[col.key] ?? '').length));
21
+ return col.width ?? Math.min(max, 40);
22
+ });
23
+
24
+ const header = columns.map((col, i) => col.label.padEnd(widths[i])).join(' ');
25
+ const separator = widths.map(w => '─'.repeat(w)).join(' ');
26
+
27
+ console.log(chalk.bold(' ' + header));
28
+ console.log(chalk.dim(' ' + separator));
29
+
30
+ for (const row of rows) {
31
+ const line = columns.map((col, i) => {
32
+ const val = String(row[col.key] ?? '');
33
+ return val.length > widths[i] ? val.substring(0, widths[i] - 1) + '…' : val.padEnd(widths[i]);
34
+ }).join(' ');
35
+ console.log(' ' + line);
36
+ }
37
+
38
+ console.log(chalk.dim(`\n ${rows.length} result(s)`));
39
+ }
40
+
41
+ export function printObject(obj: any, title?: string): void {
42
+ if (title) console.log(chalk.bold(`\n ${title}\n`));
43
+ for (const [key, value] of Object.entries(obj)) {
44
+ if (value !== null && value !== undefined && value !== '') {
45
+ console.log(` ${chalk.dim(key.padEnd(28))} ${value}`);
46
+ }
47
+ }
48
+ console.log();
49
+ }
50
+
51
+ export function error(msg: string): void {
52
+ console.error(chalk.red(`\n ✗ ${msg}\n`));
53
+ }
54
+
55
+ export function success(msg: string): void {
56
+ console.log(chalk.green(`\n ✓ ${msg}\n`));
57
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }