@unstoppabledomains/ud-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.
Files changed (79) hide show
  1. package/README.md +344 -0
  2. package/dist/commands/api-commands.d.ts +9 -0
  3. package/dist/commands/api-commands.d.ts.map +1 -0
  4. package/dist/commands/api-commands.js +303 -0
  5. package/dist/commands/api-commands.js.map +1 -0
  6. package/dist/commands/auth.d.ts +3 -0
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/auth.js +97 -0
  9. package/dist/commands/auth.js.map +1 -0
  10. package/dist/commands/cart.d.ts +10 -0
  11. package/dist/commands/cart.d.ts.map +1 -0
  12. package/dist/commands/cart.js +125 -0
  13. package/dist/commands/cart.js.map +1 -0
  14. package/dist/commands/config.d.ts +3 -0
  15. package/dist/commands/config.d.ts.map +1 -0
  16. package/dist/commands/config.js +90 -0
  17. package/dist/commands/config.js.map +1 -0
  18. package/dist/commands/env.d.ts +3 -0
  19. package/dist/commands/env.d.ts.map +1 -0
  20. package/dist/commands/env.js +29 -0
  21. package/dist/commands/env.js.map +1 -0
  22. package/dist/generated/openapi-spec.json +5903 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +3 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib/api.d.ts +7 -0
  28. package/dist/lib/api.d.ts.map +1 -0
  29. package/dist/lib/api.js +162 -0
  30. package/dist/lib/api.js.map +1 -0
  31. package/dist/lib/command-hooks.d.ts +49 -0
  32. package/dist/lib/command-hooks.d.ts.map +1 -0
  33. package/dist/lib/command-hooks.js +150 -0
  34. package/dist/lib/command-hooks.js.map +1 -0
  35. package/dist/lib/command-registry.d.ts +30 -0
  36. package/dist/lib/command-registry.d.ts.map +1 -0
  37. package/dist/lib/command-registry.js +322 -0
  38. package/dist/lib/command-registry.js.map +1 -0
  39. package/dist/lib/config.d.ts +19 -0
  40. package/dist/lib/config.d.ts.map +1 -0
  41. package/dist/lib/config.js +95 -0
  42. package/dist/lib/config.js.map +1 -0
  43. package/dist/lib/credentials.d.ts +16 -0
  44. package/dist/lib/credentials.d.ts.map +1 -0
  45. package/dist/lib/credentials.js +126 -0
  46. package/dist/lib/credentials.js.map +1 -0
  47. package/dist/lib/formatter.d.ts +34 -0
  48. package/dist/lib/formatter.d.ts.map +1 -0
  49. package/dist/lib/formatter.js +691 -0
  50. package/dist/lib/formatter.js.map +1 -0
  51. package/dist/lib/oauth.d.ts +8 -0
  52. package/dist/lib/oauth.d.ts.map +1 -0
  53. package/dist/lib/oauth.js +212 -0
  54. package/dist/lib/oauth.js.map +1 -0
  55. package/dist/lib/param-builder.d.ts +28 -0
  56. package/dist/lib/param-builder.d.ts.map +1 -0
  57. package/dist/lib/param-builder.js +178 -0
  58. package/dist/lib/param-builder.js.map +1 -0
  59. package/dist/lib/prompt.d.ts +17 -0
  60. package/dist/lib/prompt.d.ts.map +1 -0
  61. package/dist/lib/prompt.js +53 -0
  62. package/dist/lib/prompt.js.map +1 -0
  63. package/dist/lib/spec-parser.d.ts +76 -0
  64. package/dist/lib/spec-parser.d.ts.map +1 -0
  65. package/dist/lib/spec-parser.js +201 -0
  66. package/dist/lib/spec-parser.js.map +1 -0
  67. package/dist/lib/spinner.d.ts +16 -0
  68. package/dist/lib/spinner.d.ts.map +1 -0
  69. package/dist/lib/spinner.js +23 -0
  70. package/dist/lib/spinner.js.map +1 -0
  71. package/dist/lib/types.d.ts +63 -0
  72. package/dist/lib/types.d.ts.map +1 -0
  73. package/dist/lib/types.js +11 -0
  74. package/dist/lib/types.js.map +1 -0
  75. package/dist/program.d.ts +3 -0
  76. package/dist/program.d.ts.map +1 -0
  77. package/dist/program.js +47 -0
  78. package/dist/program.js.map +1 -0
  79. package/package.json +70 -0
@@ -0,0 +1,691 @@
1
+ /**
2
+ * Output formatting engine for CLI results.
3
+ * Supports JSON, table, and CSV output formats.
4
+ */
5
+ import chalk from 'chalk';
6
+ import Table from 'cli-table3';
7
+ /**
8
+ * Format and print API response data.
9
+ */
10
+ export function formatOutput(data, options) {
11
+ const { format } = options;
12
+ if (format === 'json') {
13
+ return formatJson(data);
14
+ }
15
+ const obj = data;
16
+ let main;
17
+ if (format === 'csv') {
18
+ main = formatCsv(data, options);
19
+ }
20
+ else {
21
+ main = formatTable(data, options);
22
+ }
23
+ // Only append annotations for table format — they break CSV/JSON machine parsing
24
+ if (format === 'table') {
25
+ const bulkSummary = formatBulkSummary(obj);
26
+ const paginationHint = formatPaginationHint(obj, options.responsePattern);
27
+ const parts = [main, bulkSummary, paginationHint].filter(Boolean);
28
+ return parts.join('\n');
29
+ }
30
+ return main;
31
+ }
32
+ // --- JSON ---
33
+ function formatJson(data) {
34
+ return JSON.stringify(data, null, 2);
35
+ }
36
+ // --- Table ---
37
+ function formatTable(data, options) {
38
+ const obj = data;
39
+ // Check for detail view (single-item response with detail config)
40
+ // Skip detail view when --fields is specified — user wants explicit columns
41
+ const detailConfig = !options.fields ? DETAIL_CONFIGS[options.toolName ?? ''] : undefined;
42
+ if (detailConfig) {
43
+ if (detailConfig.source === 'response') {
44
+ // Response-level detail: render the top-level response object directly
45
+ return formatDetail(obj, detailConfig);
46
+ }
47
+ // Item-level detail: only for single-item responses
48
+ const { rows } = extractTableData(obj, options);
49
+ if (rows.length === 1) {
50
+ return formatDetail(rows[0], detailConfig);
51
+ }
52
+ // Multiple items — fall through to normal table
53
+ }
54
+ // Find the primary data array in the response
55
+ const { rows, columns } = extractTableData(obj, options);
56
+ if (rows.length === 0) {
57
+ return chalk.dim('No results.');
58
+ }
59
+ const table = new Table({
60
+ head: columns.map((c) => chalk.bold(formatHeaderName(c))),
61
+ style: { head: [], border: [] },
62
+ });
63
+ for (const row of rows) {
64
+ table.push(columns.map((col) => formatCellValue(getNestedValue(row, col), true)));
65
+ }
66
+ return table.toString();
67
+ }
68
+ // --- CSV ---
69
+ function formatCsv(data, options) {
70
+ const obj = data;
71
+ const { rows, columns } = extractTableData(obj, options);
72
+ if (rows.length === 0)
73
+ return '';
74
+ const lines = [columns.map(formatHeaderName).join(',')];
75
+ for (const row of rows) {
76
+ // useColor=false: ANSI escape codes break CSV consumers (cut, awk, spreadsheets)
77
+ lines.push(columns.map((col) => csvEscape(formatCellValue(getNestedValue(row, col), false))).join(','));
78
+ }
79
+ return lines.join('\n');
80
+ }
81
+ function csvEscape(value) {
82
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
83
+ return `"${value.replace(/"/g, '""')}"`;
84
+ }
85
+ return value;
86
+ }
87
+ // --- Bulk summary ---
88
+ function formatBulkSummary(obj) {
89
+ if (typeof obj.successCount !== 'number' || typeof obj.failureCount !== 'number') {
90
+ return '';
91
+ }
92
+ const parts = [];
93
+ if (obj.successCount > 0) {
94
+ parts.push(chalk.green(`${obj.successCount} succeeded`));
95
+ }
96
+ if (obj.failureCount > 0) {
97
+ parts.push(chalk.red(`${obj.failureCount} failed`));
98
+ }
99
+ return parts.join(', ');
100
+ }
101
+ // --- Pagination hint ---
102
+ function formatPaginationHint(obj, pattern) {
103
+ const pagination = obj.pagination;
104
+ if (!pagination)
105
+ return '';
106
+ // Build context line: "Page 1 of 3 (150 total)" or "Showing 20 of 150"
107
+ const parts = [];
108
+ const context = formatPaginationContext(pagination);
109
+ if (context)
110
+ parts.push(chalk.dim(context));
111
+ if (!pagination.hasMore)
112
+ return parts.join('\n');
113
+ // Compute next page/offset for the actionable hint
114
+ if (pattern === 'paginated-page') {
115
+ const nextPage = typeof pagination.nextPage === 'number'
116
+ ? pagination.nextPage
117
+ : typeof pagination.page === 'number' ? pagination.page + 1 : undefined;
118
+ if (nextPage !== undefined) {
119
+ parts.push(chalk.dim(`Next page: --page ${nextPage}`));
120
+ return parts.join('\n');
121
+ }
122
+ }
123
+ if (pattern === 'paginated-offset') {
124
+ const nextOffset = typeof pagination.nextOffset === 'number'
125
+ ? pagination.nextOffset
126
+ : undefined;
127
+ if (nextOffset !== undefined) {
128
+ parts.push(chalk.dim(`Next page: --offset ${nextOffset}`));
129
+ return parts.join('\n');
130
+ }
131
+ }
132
+ parts.push(chalk.dim('More results available.'));
133
+ return parts.join('\n');
134
+ }
135
+ function formatPaginationContext(pagination) {
136
+ const page = pagination.page;
137
+ const totalPages = pagination.totalPages;
138
+ const total = pagination.total;
139
+ if (typeof page === 'number' && typeof totalPages === 'number') {
140
+ const suffix = typeof total === 'number' ? ` (${total} total)` : '';
141
+ return `Page ${page} of ${totalPages}${suffix}`;
142
+ }
143
+ if (typeof page === 'number' && typeof total === 'number') {
144
+ return `Page ${page} (${total} total)`;
145
+ }
146
+ return '';
147
+ }
148
+ // --- Helpers ---
149
+ /**
150
+ * Known column configs for specific response types.
151
+ * Falls back to auto-detection if no specific config is found.
152
+ */
153
+ const CART_ADD_COLUMNS = ['domain', 'success', 'productId', 'error'];
154
+ const TABLE_CONFIGS = {
155
+ // Domain search results
156
+ ud_domains_search: ['name', 'available', 'marketplace.status', 'pricing.formatted'],
157
+ // Portfolio list
158
+ ud_portfolio_list: ['name', 'expiresAt', 'autoRenewal.status', 'tags'],
159
+ // Domain get (multi-domain fallback; single domain uses DETAIL_CONFIGS)
160
+ ud_domain_get: ['domain', 'extension', 'lifecycle.expiresAt', 'lifecycle.autoRenewal.status', 'tags'],
161
+ // TLD list (spec returns string[], so extractTableData wraps them)
162
+ ud_tld_list: ['tld'],
163
+ // DNS records
164
+ ud_dns_records_list: ['type', 'subName', 'values', 'ttl'],
165
+ // Cart
166
+ ud_cart_get: ['name', 'type', 'pricing.formatted'],
167
+ // Contacts
168
+ ud_contacts_list: ['id', 'firstName', 'lastName', 'email'],
169
+ // Offers
170
+ ud_offers_list: ['domainName', 'amount', 'status', 'createdAt'],
171
+ ud_offer_respond: ['offerId', 'domainName', 'action', 'success', 'priceFormatted', 'newStatus', 'error'],
172
+ // Leads
173
+ ud_leads_list: ['domain', 'status', 'lastMessage', 'createdAt'],
174
+ ud_lead_messages_list: ['id', 'content', 'senderUserId', 'createdAt'],
175
+ ud_lead_message_send: ['id', 'content', 'createdAt'],
176
+ // Listings
177
+ ud_listing_create: ['domain', 'success', 'listingId'],
178
+ ud_listing_update: ['listingId', 'domainName', 'success', 'status', 'error'],
179
+ ud_listing_cancel: ['listingId', 'domainName', 'success', 'error'],
180
+ // Cart add responses (registration, listed, afternic, sedo, renewal all share same shape)
181
+ ...Object.fromEntries(['registration', 'listed', 'afternic', 'sedo', 'renewal'].map((t) => [`ud_cart_add_domain_${t}`, CART_ADD_COLUMNS])),
182
+ // Cart remove
183
+ ud_cart_remove: ['removedCount'],
184
+ // Payment methods (spec returns savedCards array)
185
+ ud_cart_get_payment_methods: ['id', 'brand', 'last4', 'expMonth', 'expYear', 'isDefault'],
186
+ // --- DNS mutation responses ---
187
+ ud_dns_record_add: ['domain', 'success', 'operationId', 'error'],
188
+ ud_dns_record_update: ['domain', 'success', 'operationId', 'error'],
189
+ ud_dns_record_remove: ['domain', 'success', 'operationId', 'error'],
190
+ ud_dns_records_remove_all: ['domain', 'success', 'operationId', 'error'],
191
+ ud_dns_nameservers_set_custom: ['domain', 'success', 'nameservers', 'error'],
192
+ ud_dns_nameservers_set_default: ['domain', 'success', 'nameservers', 'error'],
193
+ ud_dns_hosting_add: ['domain', 'success', 'config.type', 'error'],
194
+ ud_dns_hosting_remove: ['domain', 'success', 'subName', 'deletedAll', 'error'],
195
+ // Nameserver list (single-object response — rendered as single row)
196
+ ud_dns_nameservers_list: ['domain', 'nameservers', 'isUsingDefaultNameservers'],
197
+ // Hosting list (array in "configs" key)
198
+ ud_dns_hosting_list: ['type', 'subName', 'targetUrl', 'status'],
199
+ // --- Domain lifecycle ---
200
+ ud_domain_pending_operations: ['domain', 'hasPendingOperations'],
201
+ ud_domain_auto_renewal_update: ['domain', 'success', 'error'],
202
+ ud_domain_tags_add: ['domain', 'success', 'tagsApplied', 'error'],
203
+ ud_domain_tags_remove: ['domain', 'success', 'tagsRemoved', 'error'],
204
+ ud_domain_flags_update: ['domain', 'success', 'updatedFlags', 'error'],
205
+ ud_domain_generate_lander: ['domain', 'success', 'jobId', 'error'],
206
+ ud_domain_lander_status: ['domain', 'status', 'hostingType'],
207
+ ud_domain_remove_lander: ['domain', 'success', 'operationId', 'error'],
208
+ ud_domain_push: ['success', 'message'],
209
+ };
210
+ const DETAIL_CONFIGS = {
211
+ ud_domain_get: {
212
+ sections: [
213
+ {
214
+ title: 'General',
215
+ fields: [
216
+ { label: 'Domain', path: 'domain' },
217
+ { label: 'Extension', path: 'extension' },
218
+ { label: 'Purchased', path: 'lifecycle.purchasedAt' },
219
+ { label: 'Expires', path: 'lifecycle.expiresAt' },
220
+ { label: 'Transfer Status', path: 'lifecycle.transferStatus' },
221
+ { label: 'Externally Owned', path: 'lifecycle.isExternallyOwned' },
222
+ { label: 'Reverse Resolution', path: 'lifecycle.reverse' },
223
+ { label: 'Tags', path: 'tags' },
224
+ ],
225
+ },
226
+ {
227
+ title: 'Renewal',
228
+ fields: [
229
+ { label: 'Auto-Renewal', path: 'lifecycle.autoRenewal.status' },
230
+ { label: 'Next Renewal', path: 'lifecycle.autoRenewal.expiresAt' },
231
+ { label: 'Eligible', path: 'lifecycle.renewal.isEligible' },
232
+ { label: 'Price Per Year', path: 'lifecycle.renewal.pricePerYearFormatted' },
233
+ ],
234
+ },
235
+ {
236
+ title: 'Flags',
237
+ fields: [
238
+ { label: 'Transfer Lock', path: 'flags.DNS_TRANSFER_OUT.status' },
239
+ { label: 'WHOIS Privacy', path: 'flags.DNS_WHOIS_PROXY.status' },
240
+ { label: 'DNS Resolution', path: 'flags.DNS_RESOLUTION.status' },
241
+ { label: 'DNS Updates', path: 'flags.DNS_UPDATE.status' },
242
+ { label: 'Tokenization', path: 'flags.DNS_UNS_TOKENIZATION.status' },
243
+ ],
244
+ },
245
+ {
246
+ title: 'DNS',
247
+ fields: [
248
+ { label: 'Nameserver Mode', path: 'dns.nameservers.status' },
249
+ { label: 'Nameservers', path: 'dns.nameservers.nameservers' },
250
+ { label: 'DNSSEC Enabled', path: 'dns.dnssec.enabled' },
251
+ { label: 'DNSSEC Valid', path: 'dns.dnssec.valid' },
252
+ ],
253
+ },
254
+ {
255
+ title: 'Marketplace',
256
+ fields: [
257
+ { label: 'Listing Status', path: 'marketplace.listing.status' },
258
+ { label: 'Listing Price', path: 'marketplace.listing.price' },
259
+ { label: 'Listing Views', path: 'marketplace.listing.views' },
260
+ { label: 'Offers', path: 'marketplace.offersCount' },
261
+ { label: 'Leads', path: 'marketplace.leadsCount' },
262
+ { label: 'Watchlist', path: 'marketplace.watchlistCount' },
263
+ ],
264
+ },
265
+ ],
266
+ subTables: [
267
+ { title: 'Pending Operations', arrayPath: 'pendingOperations', columns: ['id', 'type', 'status', 'createdAt'] },
268
+ ],
269
+ },
270
+ ud_cart_get: {
271
+ source: 'response',
272
+ sections: [
273
+ {
274
+ title: 'Summary',
275
+ fields: [
276
+ { label: 'Items', path: 'itemCount' },
277
+ { label: 'Total Value', path: 'pricing.totalOrderValueFormatted' },
278
+ { label: 'Total Discounts', path: 'totalDiscountsFormatted' },
279
+ { label: 'Amount Due', path: 'pricing.totalAmountDueFormatted' },
280
+ ],
281
+ },
282
+ {
283
+ title: 'Pricing',
284
+ fields: [
285
+ { label: 'Subtotal', path: 'pricing.preTaxAmountDueFormatted' },
286
+ { label: 'Sales Tax', path: 'pricing.salesTaxFormatted' },
287
+ { label: 'Tax Rate', path: 'pricing.taxRate' },
288
+ { label: 'Promo Credits', path: 'pricing.promoCreditsUsedFormatted' },
289
+ { label: 'Store Credits', path: 'pricing.storeCreditsUsedFormatted' },
290
+ { label: 'Account Balance', path: 'pricing.accountBalanceUsedFormatted' },
291
+ { label: 'Total Due', path: 'pricing.totalAmountDueFormatted' },
292
+ ],
293
+ },
294
+ ],
295
+ subTables: [
296
+ { title: 'Items', arrayPath: 'items', columns: ['domain', 'productType', 'originalPriceFormatted', 'discountAmountFormatted'] },
297
+ { title: 'Discounts', arrayPath: 'discounts', columns: ['title', 'type', 'amountFormatted', 'code'] },
298
+ ],
299
+ },
300
+ ud_dns_records_list: {
301
+ source: 'response',
302
+ sections: [
303
+ {
304
+ title: 'DNS Status',
305
+ fields: [
306
+ { label: 'Domain', path: 'domain' },
307
+ { label: 'Provider', path: 'dnsStatus.provider' },
308
+ { label: 'Configured', path: 'dnsStatus.configured' },
309
+ { label: 'Status', path: 'dnsStatus.message' },
310
+ ],
311
+ },
312
+ ],
313
+ subTables: [
314
+ { title: 'Records', arrayPath: 'records', columns: ['type', 'subName', 'values', 'ttl', 'readonly'] },
315
+ ],
316
+ },
317
+ ud_cart_checkout: {
318
+ source: 'response',
319
+ sections: [
320
+ {
321
+ title: 'Order',
322
+ fields: [
323
+ { label: 'Order ID', path: 'orderId' },
324
+ { label: 'Success', path: 'success' },
325
+ { label: 'Note', path: 'note' },
326
+ ],
327
+ },
328
+ {
329
+ title: 'Summary',
330
+ fields: [
331
+ { label: 'Items', path: 'summary.itemCount' },
332
+ { label: 'Subtotal', path: 'summary.subtotalFormatted' },
333
+ { label: 'Discounts', path: 'summary.discountsFormatted' },
334
+ { label: 'Credits Used', path: 'summary.creditsUsedFormatted' },
335
+ { label: 'Sales Tax', path: 'summary.salesTaxFormatted' },
336
+ { label: 'Total Charged', path: 'summary.totalChargedFormatted' },
337
+ { label: 'Payment Method', path: 'summary.paymentMethod' },
338
+ ],
339
+ },
340
+ ],
341
+ },
342
+ ud_lead_get: {
343
+ source: 'response',
344
+ sections: [
345
+ {
346
+ title: 'Conversation',
347
+ fields: [
348
+ { label: 'ID', path: 'conversation.id' },
349
+ { label: 'Domain', path: 'conversation.domainName' },
350
+ { label: 'Created', path: 'conversation.createdAt' },
351
+ { label: 'Existing', path: 'conversation.isExisting' },
352
+ { label: 'Message', path: 'message' },
353
+ ],
354
+ },
355
+ ],
356
+ },
357
+ ud_cart_get_url: {
358
+ source: 'response',
359
+ sections: [
360
+ {
361
+ title: 'Checkout URL',
362
+ fields: [
363
+ { label: 'URL', path: 'checkoutUrl' },
364
+ { label: 'Items', path: 'cartSummary.itemCount' },
365
+ { label: 'Subtotal', path: 'cartSummary.subtotalFormatted' },
366
+ { label: 'Instructions', path: 'instructions' },
367
+ ],
368
+ },
369
+ ],
370
+ },
371
+ ud_cart_add_payment_method_url: {
372
+ source: 'response',
373
+ sections: [
374
+ {
375
+ title: 'Payment Method',
376
+ fields: [
377
+ { label: 'URL', path: 'url' },
378
+ { label: 'Instructions', path: 'instructions' },
379
+ ],
380
+ },
381
+ ],
382
+ },
383
+ ud_domain_pending_operations: {
384
+ sections: [
385
+ {
386
+ title: 'Domain',
387
+ fields: [
388
+ { label: 'Domain', path: 'domain' },
389
+ { label: 'Has Pending Operations', path: 'hasPendingOperations' },
390
+ ],
391
+ },
392
+ ],
393
+ subTables: [
394
+ { title: 'Operations', arrayPath: 'operations', columns: ['id', 'type', 'status', 'createdAt', 'updatedAt', 'errorCode'] },
395
+ ],
396
+ },
397
+ };
398
+ function formatDetail(obj, config) {
399
+ const parts = [];
400
+ for (const section of config.sections) {
401
+ const rows = [];
402
+ for (const field of section.fields) {
403
+ const value = getNestedValue(obj, field.path);
404
+ if (value === null || value === undefined)
405
+ continue;
406
+ rows.push([field.label, formatDetailValue(value)]);
407
+ }
408
+ if (rows.length === 0)
409
+ continue;
410
+ parts.push('');
411
+ parts.push(chalk.bold.underline(section.title));
412
+ const table = new Table({
413
+ style: { head: [], border: [], 'padding-left': 1, 'padding-right': 1 },
414
+ });
415
+ for (const [label, val] of rows) {
416
+ table.push({ [chalk.dim(label)]: val });
417
+ }
418
+ parts.push(table.toString());
419
+ }
420
+ // Render sub-tables
421
+ if (config.subTables) {
422
+ for (const sub of config.subTables) {
423
+ const arr = getNestedValue(obj, sub.arrayPath);
424
+ if (!Array.isArray(arr) || arr.length === 0)
425
+ continue;
426
+ parts.push('');
427
+ parts.push(chalk.bold.underline(sub.title));
428
+ const subTable = new Table({
429
+ head: sub.columns.map((c) => chalk.bold(formatHeaderName(c))),
430
+ style: { head: [], border: [] },
431
+ });
432
+ for (const item of arr) {
433
+ subTable.push(sub.columns.map((col) => formatCellValue(getNestedValue(item, col), true)));
434
+ }
435
+ parts.push(subTable.toString());
436
+ }
437
+ }
438
+ return parts.join('\n');
439
+ }
440
+ /**
441
+ * Format a value for the detail view with enhanced readability.
442
+ */
443
+ function formatDetailValue(value) {
444
+ if (value === null || value === undefined)
445
+ return '';
446
+ // Flag statuses: ENABLED/DISABLED → colored
447
+ if (value === 'ENABLED')
448
+ return chalk.green('Enabled');
449
+ if (value === 'DISABLED')
450
+ return chalk.dim('Disabled');
451
+ return formatCellValue(value, true);
452
+ }
453
+ function extractTableData(obj, options) {
454
+ const isTable = options.format === 'table';
455
+ // Find the primary array — common keys: results, domains, tlds, records, items, contacts, offers, leads
456
+ const arrayKeys = ['results', 'domains', 'tlds', 'records', 'items', 'contacts', 'offers', 'leads', 'messages', 'listings', 'savedCards', 'configs', 'pushedDomains', 'failedDomains', 'addedProducts'];
457
+ let rows = [];
458
+ for (const key of arrayKeys) {
459
+ if (Array.isArray(obj[key]) && obj[key].length > 0) {
460
+ const arr = obj[key];
461
+ // Handle arrays of primitives (e.g., string[]) by wrapping them as objects
462
+ if (typeof arr[0] !== 'object' || arr[0] === null) {
463
+ rows = arr.map((v) => ({ [key.replace(/s$/, '')]: v }));
464
+ }
465
+ else {
466
+ rows = arr;
467
+ }
468
+ break;
469
+ }
470
+ }
471
+ // If no array found, try to treat the whole response as a single row
472
+ if (rows.length === 0 && typeof obj === 'object') {
473
+ // Check if it's a simple key-value result (like cart url, checkout, etc.)
474
+ const hasScalars = Object.values(obj).some((v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean');
475
+ if (hasScalars) {
476
+ rows = [obj];
477
+ }
478
+ }
479
+ if (rows.length === 0)
480
+ return { rows: [], columns: [] };
481
+ // Priority: user --fields > TABLE_CONFIGS > auto-detect
482
+ if (options.fields && options.fields.length > 0) {
483
+ return { rows, columns: options.fields };
484
+ }
485
+ const toolName = options.toolName ?? '';
486
+ const configuredCols = TABLE_CONFIGS[toolName];
487
+ if (configuredCols) {
488
+ return { rows, columns: configuredCols };
489
+ }
490
+ // Auto-detect columns from first row (cap at 8 only for table readability)
491
+ const columns = autoDetectColumns(rows[0], isTable ? 8 : Infinity);
492
+ return { rows, columns };
493
+ }
494
+ function autoDetectColumns(row, maxCols) {
495
+ const cols = [];
496
+ for (const [key, value] of Object.entries(row)) {
497
+ if (value === null || value === undefined)
498
+ continue;
499
+ if (typeof value === 'object' && !Array.isArray(value))
500
+ continue; // skip nested objects
501
+ if (cols.length >= maxCols)
502
+ break;
503
+ cols.push(key);
504
+ }
505
+ return cols;
506
+ }
507
+ function formatCellValue(value, useColor = true) {
508
+ if (value === null || value === undefined)
509
+ return '';
510
+ if (typeof value === 'boolean') {
511
+ return useColor
512
+ ? (value ? chalk.green('Yes') : chalk.dim('No'))
513
+ : (value ? 'true' : 'false');
514
+ }
515
+ // Detect ISO date strings and format consistently
516
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
517
+ const d = new Date(value);
518
+ if (!isNaN(d.getTime())) {
519
+ return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
520
+ }
521
+ }
522
+ // Humanize kebab-case/snake_case enum values (e.g., "registered-not-for-sale" → "Registered Not For Sale")
523
+ // Also capitalizes known single-word API enums via VALUE_OVERRIDES.
524
+ if (typeof value === 'string') {
525
+ if (VALUE_OVERRIDES[value])
526
+ return VALUE_OVERRIDES[value];
527
+ // Multi-word enums: require at least one hyphen or underscore to avoid
528
+ // false positives on single words like "www" or "mail".
529
+ if (/^[a-z]+([_-][a-z]+)+$/.test(value)) {
530
+ return value
531
+ .split(/[-_]/)
532
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
533
+ .join(' ');
534
+ }
535
+ }
536
+ if (Array.isArray(value))
537
+ return value.join(', ');
538
+ if (typeof value === 'object')
539
+ return JSON.stringify(value);
540
+ return String(value);
541
+ }
542
+ /**
543
+ * Explicit value overrides for single-word API enum strings that can't be
544
+ * auto-humanized (they'd collide with identifiers like "www" or "mail").
545
+ */
546
+ const VALUE_OVERRIDES = {
547
+ accepted: 'Accepted',
548
+ available: 'Available',
549
+ active: 'Active',
550
+ cancelled: 'Cancelled',
551
+ completed: 'Completed',
552
+ expired: 'Expired',
553
+ failed: 'Failed',
554
+ inactive: 'Inactive',
555
+ listed: 'Listed',
556
+ pending: 'Pending',
557
+ processing: 'Processing',
558
+ registered: 'Registered',
559
+ rejected: 'Rejected',
560
+ unlisted: 'Unlisted',
561
+ };
562
+ /** Explicit header overrides for column keys where the auto-generated name is awkward. */
563
+ const HEADER_OVERRIDES = {
564
+ 'autoRenewal.status': 'Auto-Renewal',
565
+ 'config.type': 'Type',
566
+ 'expMonth': 'Exp. Month',
567
+ 'expYear': 'Exp. Year',
568
+ 'hasPendingOperations': 'Pending Ops',
569
+ 'id': 'ID',
570
+ 'isDefault': 'Default',
571
+ 'isUsingDefaultNameservers': 'Default NS',
572
+ 'jobId': 'Job ID',
573
+ 'last4': 'Last 4',
574
+ 'lifecycle.autoRenewal.status': 'Auto-Renewal',
575
+ 'lifecycle.expiresAt': 'Expires At',
576
+ 'listingId': 'Listing ID',
577
+ 'offerId': 'Offer ID',
578
+ 'productId': 'Product ID',
579
+ 'priceFormatted': 'Price',
580
+ 'newStatus': 'New Status',
581
+ 'senderUserId': 'Sender',
582
+ 'removedCount': 'Removed',
583
+ 'orderId': 'Order ID',
584
+ 'conversationId': 'Conversation ID',
585
+ 'domainName': 'Domain',
586
+ 'marketplace.status': 'Status',
587
+ 'operationId': 'Operation ID',
588
+ 'pricing.formatted': 'Price',
589
+ 'subName': 'Record',
590
+ 'targetUrl': 'Target URL',
591
+ 'tld': 'TLD',
592
+ 'ttl': 'TTL',
593
+ };
594
+ /**
595
+ * Convert a column key (e.g., "autoRenewal.status", "expiresAt") into a
596
+ * human-readable header: "Auto Renewal Status", "Expires At".
597
+ * Splits on dots, expands camelCase, and title-cases each word.
598
+ */
599
+ function formatHeaderName(key) {
600
+ if (HEADER_OVERRIDES[key])
601
+ return HEADER_OVERRIDES[key];
602
+ return key
603
+ .split('.')
604
+ .map((segment) => segment
605
+ // Insert space before uppercase letters in camelCase
606
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
607
+ // Capitalize first letter of each word
608
+ .replace(/\b\w/g, (c) => c.toUpperCase()))
609
+ .join(' ');
610
+ }
611
+ /**
612
+ * Resolve a dotted path like "pricing.formatted" from a row object.
613
+ * Patches Table extraction to support nested column references.
614
+ */
615
+ function getNestedValue(obj, path) {
616
+ const parts = path.split('.');
617
+ let current = obj;
618
+ for (const part of parts) {
619
+ if (current === null || current === undefined || typeof current !== 'object')
620
+ return undefined;
621
+ current = current[part];
622
+ }
623
+ return current;
624
+ }
625
+ /**
626
+ * Return known field paths for a given tool.
627
+ * `defaults` are the TABLE_CONFIGS columns; `all` merges DETAIL_CONFIGS and spec response fields.
628
+ * Pass `responseFields` from the parsed OpenAPI spec for complete discovery.
629
+ */
630
+ export function getKnownFields(toolName, responseFields) {
631
+ const tableColumns = TABLE_CONFIGS[toolName];
632
+ if (!tableColumns && (!responseFields || responseFields.length === 0))
633
+ return null;
634
+ const defaults = tableColumns ?? [];
635
+ const allFields = new Set(defaults);
636
+ // Merge detail config paths
637
+ const detailConfig = DETAIL_CONFIGS[toolName];
638
+ if (detailConfig) {
639
+ for (const section of detailConfig.sections) {
640
+ for (const field of section.fields) {
641
+ allFields.add(field.path);
642
+ }
643
+ }
644
+ }
645
+ // Merge spec response fields
646
+ if (responseFields) {
647
+ for (const field of responseFields) {
648
+ allFields.add(field);
649
+ }
650
+ }
651
+ return { defaults, all: [...allFields] };
652
+ }
653
+ /**
654
+ * Format the --fields list output for a command.
655
+ * Pass `responseFields` from the parsed OpenAPI spec for complete discovery.
656
+ */
657
+ export function formatFieldsList(toolName, commandName, responseFields) {
658
+ const known = getKnownFields(toolName, responseFields);
659
+ const lines = [];
660
+ if (known && known.all.length > 0) {
661
+ lines.push(chalk.bold(`Available fields for ${commandName}:`));
662
+ if (known.defaults.length > 0) {
663
+ lines.push('');
664
+ lines.push(chalk.dim(' Default columns (shown without --fields):'));
665
+ for (const col of known.defaults) {
666
+ lines.push(` ${col}`);
667
+ }
668
+ }
669
+ const extra = known.all.filter((f) => !known.defaults.includes(f));
670
+ if (extra.length > 0) {
671
+ lines.push('');
672
+ lines.push(chalk.dim(' Additional fields:'));
673
+ for (const field of extra) {
674
+ lines.push(` ${field}`);
675
+ }
676
+ }
677
+ }
678
+ else {
679
+ lines.push(chalk.dim(`No pre-configured fields for ${commandName}.`));
680
+ lines.push('');
681
+ lines.push(chalk.dim(' Tip: Use --format json to see all available response fields.'));
682
+ }
683
+ return lines.join('\n');
684
+ }
685
+ export function formatError(error) {
686
+ if (error instanceof Error) {
687
+ return chalk.red(`Error: ${error.message}`);
688
+ }
689
+ return chalk.red(`Error: ${String(error)}`);
690
+ }
691
+ //# sourceMappingURL=formatter.js.map