abapgit-agent 1.2.0 → 1.4.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,93 @@
1
+ *"*"use source
2
+ *"*"Local Interface:
3
+ *"**********************************************************************
4
+ CLASS zcl_abgagt_viewer_ttyp DEFINITION PUBLIC FINAL CREATE PUBLIC.
5
+
6
+ PUBLIC SECTION.
7
+ INTERFACES zif_abgagt_viewer.
8
+
9
+ ENDCLASS.
10
+
11
+ CLASS zcl_abgagt_viewer_ttyp IMPLEMENTATION.
12
+
13
+ METHOD zif_abgagt_viewer~get_info.
14
+ DATA: lv_obj_name TYPE tadir-obj_name,
15
+ lv_devclass TYPE tadir-devclass,
16
+ lv_linetype TYPE string,
17
+ lv_tabprottype TYPE string,
18
+ lv_keydef TYPE string,
19
+ lv_access_mode TYPE string,
20
+ lv_key_definition TYPE string.
21
+
22
+ rs_info-name = iv_name.
23
+ rs_info-type = 'TTYP'.
24
+ rs_info-type_text = 'Table Type'.
25
+
26
+ " Get package from TADIR
27
+ SELECT SINGLE obj_name devclass FROM tadir
28
+ INTO (lv_obj_name, lv_devclass)
29
+ WHERE obj_name = iv_name
30
+ AND object = 'TTYP'.
31
+ IF sy-subrc = 0.
32
+ rs_info-description = |Table Type { iv_name } in { lv_devclass }|.
33
+ ELSE.
34
+ rs_info-not_found = abap_true.
35
+ ENDIF.
36
+
37
+ " Get TTYP details from DD40L
38
+ SELECT SINGLE rowtype accessmode keydef FROM dd40l
39
+ INTO (lv_linetype, lv_tabprottype, lv_keydef)
40
+ WHERE typename = iv_name
41
+ AND as4local = 'A'.
42
+
43
+ " Build components table with TTYP details
44
+ IF lv_linetype IS NOT INITIAL.
45
+ APPEND VALUE #(
46
+ field = 'LINE_TYPE'
47
+ key = abap_false
48
+ type = 'CHAR'
49
+ length = 30
50
+ dataelement = ''
51
+ description = |Line Type: { lv_linetype }|
52
+ ) TO rs_info-components.
53
+ ENDIF.
54
+
55
+ " Convert access mode to text
56
+ CASE lv_tabprottype.
57
+ WHEN 'T'. lv_access_mode = 'STANDARD'.
58
+ WHEN 'S'. lv_access_mode = 'SORTED'.
59
+ WHEN 'H'. lv_access_mode = 'HASHED'.
60
+ WHEN OTHERS. lv_access_mode = lv_tabprottype.
61
+ ENDCASE.
62
+
63
+ IF lv_access_mode IS NOT INITIAL.
64
+ APPEND VALUE #(
65
+ field = 'ACCESS_MODE'
66
+ key = abap_false
67
+ type = 'CHAR'
68
+ length = 10
69
+ dataelement = ''
70
+ description = |Access Mode: { lv_access_mode }|
71
+ ) TO rs_info-components.
72
+ ENDIF.
73
+
74
+ " Convert key definition to text
75
+ CASE lv_keydef.
76
+ WHEN 'D'. lv_key_definition = 'WITH KEY'.
77
+ WHEN 'N'. lv_key_definition = 'NO KEY'.
78
+ WHEN OTHERS. lv_key_definition = lv_keydef.
79
+ ENDCASE.
80
+
81
+ IF lv_key_definition IS NOT INITIAL.
82
+ APPEND VALUE #(
83
+ field = 'KEY_DEFINITION'
84
+ key = abap_false
85
+ type = 'CHAR'
86
+ length = 10
87
+ dataelement = ''
88
+ description = |Key Definition: { lv_key_definition }|
89
+ ) TO rs_info-components.
90
+ ENDIF.
91
+ ENDMETHOD.
92
+
93
+ ENDCLASS.
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
3
+ <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
4
+ <asx:values>
5
+ <VSEOCLASS>
6
+ <CLSNAME>ZCL_ABGAGT_VIEWER_TTYP</CLSNAME>
7
+ <LANGU>E</LANGU>
8
+ <DESCRIPT>Viewer for ABAP Table Types</DESCRIPT>
9
+ <EXPOSURE>2</EXPOSURE>
10
+ <STATE>1</STATE>
11
+ <UNICODE>X</UNICODE>
12
+ </VSEOCLASS>
13
+ </asx:values>
14
+ </asx:abap>
15
+ </abapGit>
@@ -9,7 +9,8 @@ INTERFACE zif_abgagt_command PUBLIC.
9
9
  gc_create TYPE string VALUE 'CREATE',
10
10
  gc_import TYPE string VALUE 'IMPORT',
11
11
  gc_tree TYPE string VALUE 'TREE',
12
- gc_view TYPE string VALUE 'VIEW'.
12
+ gc_view TYPE string VALUE 'VIEW',
13
+ gc_preview TYPE string VALUE 'PREVIEW'.
13
14
 
14
15
  " Get command name
15
16
  METHODS get_name
@@ -6,6 +6,7 @@ INTERFACE zif_abgagt_viewer PUBLIC.
6
6
  " @parameter rs_info | Object information structure
7
7
  METHODS get_info
8
8
  IMPORTING iv_name TYPE string
9
- RETURNING VALUE(rs_info) TYPE zcl_abgagt_command_view=>ty_view_object.
9
+ RETURNING VALUE(rs_info) TYPE zcl_abgagt_command_view=>ty_view_object
10
+ RAISING cx_dd_ddl_read.
10
11
 
11
12
  ENDINTERFACE.
package/bin/abapgit-agent CHANGED
@@ -309,33 +309,70 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
309
309
 
310
310
  const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
311
311
 
312
- // Handle uppercase keys from ABAP - handle both 'X'/true (success) and false
313
- const success = result.SUCCESS !== undefined ? result.SUCCESS : (result.success !== undefined ? result.success : null);
314
- const errorCount = result.ERROR_COUNT || result.error_count || 0;
315
- const errors = result.ERRORS || result.errors || [];
312
+ // Handle new table-based result format (array) or old single result
313
+ let results = [];
314
+ if (Array.isArray(result)) {
315
+ results = result;
316
+ } else {
317
+ // Convert old single result to array format for compatibility
318
+ results = [{
319
+ OBJECT_TYPE: 'UNKNOWN',
320
+ OBJECT_NAME: pathModule.basename(sourceFile),
321
+ SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
322
+ ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
323
+ ERRORS: result.ERRORS || result.errors || [],
324
+ WARNINGS: result.warnings || []
325
+ }];
326
+ }
316
327
 
317
- console.log('\n');
328
+ // Process each result
329
+ let hasErrors = false;
330
+ for (const res of results) {
331
+ // Handle both uppercase and lowercase keys
332
+ const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
333
+ const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
334
+ const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
335
+ const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
336
+ const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
337
+ const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
338
+
339
+ console.log('\n');
340
+
341
+ if (errorCount > 0 || warnings.length > 0) {
342
+ if (errorCount > 0) {
343
+ console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
344
+ } else {
345
+ console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
346
+ }
347
+ console.log('\nErrors:');
348
+ console.log('─'.repeat(60));
318
349
 
319
- if (errorCount > 0) {
320
- console.log(`❌ Syntax check failed (${errorCount} error(s)):`);
321
- console.log('\nErrors:');
322
- console.log(''.repeat(60));
350
+ for (const err of errors) {
351
+ const line = err.LINE || err.line || '?';
352
+ const column = err.COLUMN || err.column || '?';
353
+ const text = err.TEXT || err.text || 'Unknown error';
323
354
 
324
- for (const err of errors) {
325
- const line = err.LINE || err.line || '?';
326
- const column = err.COLUMN || err.column || '?';
327
- const text = err.TEXT || err.text || 'Unknown error';
355
+ console.log(` Line ${line}, Column ${column}:`);
356
+ console.log(` ${text}`);
357
+ console.log('');
358
+ }
328
359
 
329
- console.log(` Line ${line}, Column ${column}:`);
330
- console.log(` ${text}`);
331
- console.log('');
360
+ // Show warnings if any
361
+ if (warnings.length > 0) {
362
+ console.log('Warnings:');
363
+ console.log('─'.repeat(60));
364
+ for (const warn of warnings) {
365
+ const line = warn.LINE || warn.line || '?';
366
+ const text = warn.MESSAGE || warn.message || 'Unknown warning';
367
+ console.log(` Line ${line}: ${text}`);
368
+ }
369
+ }
370
+ hasErrors = true;
371
+ } else if (success === true || success === 'X') {
372
+ console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
373
+ } else {
374
+ console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
332
375
  }
333
- } else if (success === 'X' || success === true) {
334
- console.log(`✅ ${pathModule.basename(sourceFile)} - Syntax check passed (0 errors)`);
335
- } else if (success === false) {
336
- console.log(`❌ ${pathModule.basename(sourceFile)} - Syntax check failed`);
337
- } else {
338
- console.log(`⚠️ Syntax check returned unexpected status: ${success}`);
339
376
  }
340
377
 
341
378
  return result;
@@ -345,6 +382,95 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
345
382
  }
346
383
  }
347
384
 
385
+ /**
386
+ * Inspect all files in one request
387
+ */
388
+ async function inspectAllFiles(files, csrfToken, config) {
389
+ // Convert files to uppercase names (same as syntaxCheckSource does)
390
+ const fileNames = files.map(f => {
391
+ const baseName = pathModule.basename(f).toUpperCase();
392
+ return baseName;
393
+ });
394
+
395
+ try {
396
+ // Send all files in one request
397
+ const data = {
398
+ files: fileNames
399
+ };
400
+
401
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
402
+
403
+ // Handle both table result and old single result
404
+ let results = [];
405
+ if (Array.isArray(result)) {
406
+ results = result;
407
+ } else {
408
+ // Convert single result to array format
409
+ results = [{
410
+ OBJECT_TYPE: 'UNKNOWN',
411
+ OBJECT_NAME: files.join(', '),
412
+ SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
413
+ ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
414
+ ERRORS: result.ERRORS || result.errors || [],
415
+ WARNINGS: result.warnings || []
416
+ }];
417
+ }
418
+
419
+ return results;
420
+ } catch (error) {
421
+ console.error(`\n Error: ${error.message}`);
422
+ process.exit(1);
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Process a single inspect result
428
+ */
429
+ async function processInspectResult(res) {
430
+ // Handle both uppercase and lowercase keys
431
+ const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
432
+ const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
433
+ const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
434
+ const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
435
+ const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
436
+ const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
437
+
438
+ if (errorCount > 0 || warnings.length > 0) {
439
+ if (errorCount > 0) {
440
+ console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
441
+ } else {
442
+ console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
443
+ }
444
+ console.log('\nErrors:');
445
+ console.log('─'.repeat(60));
446
+
447
+ for (const err of errors) {
448
+ const line = err.LINE || err.line || '?';
449
+ const column = err.COLUMN || err.column || '?';
450
+ const text = err.TEXT || err.text || 'Unknown error';
451
+
452
+ console.log(` Line ${line}, Column ${column}:`);
453
+ console.log(` ${text}`);
454
+ console.log('');
455
+ }
456
+
457
+ // Show warnings if any
458
+ if (warnings.length > 0) {
459
+ console.log('Warnings:');
460
+ console.log('─'.repeat(60));
461
+ for (const warn of warnings) {
462
+ const line = warn.LINE || warn.line || '?';
463
+ const text = warn.MESSAGE || warn.message || 'Unknown warning';
464
+ console.log(` Line ${line}: ${text}`);
465
+ }
466
+ }
467
+ } else if (success === true || success === 'X') {
468
+ console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
469
+ } else {
470
+ console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
471
+ }
472
+ }
473
+
348
474
  /**
349
475
  * Run unit tests for package or objects
350
476
  */
@@ -691,7 +817,46 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
691
817
  'W': '⚠️', // Warning
692
818
  'A': '🛑' // Abort
693
819
  };
694
- return icons[type] || ' ';
820
+ return icons[type] || '';
821
+ };
822
+
823
+ // Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
824
+ const calcWidth = (str) => {
825
+ if (!str) return 0;
826
+ let width = 0;
827
+ let i = 0;
828
+ while (i < str.length) {
829
+ const code = str.codePointAt(i);
830
+ if (!code) break;
831
+ // Variation selectors (FE00-FE0F) and ZWJ (200D) take 0 width
832
+ if (code >= 0xFE00 && code <= 0xFE0F) {
833
+ i += 1;
834
+ continue;
835
+ }
836
+ if (code === 0x200D) { // Zero width joiner
837
+ i += 1;
838
+ continue;
839
+ }
840
+ // Emoji and wide characters take 2 cells
841
+ if (code > 0xFFFF) {
842
+ width += 2;
843
+ i += 2; // Skip surrogate pair
844
+ } else if (code > 127) {
845
+ width += 2;
846
+ i += 1;
847
+ } else {
848
+ width += 1;
849
+ i += 1;
850
+ }
851
+ }
852
+ return width;
853
+ };
854
+
855
+ // Pad string to display width
856
+ const padToWidth = (str, width) => {
857
+ const s = str || '';
858
+ const currentWidth = calcWidth(s);
859
+ return s + ' '.repeat(Math.max(0, width - currentWidth));
695
860
  };
696
861
 
697
862
  if (success === 'X' || success === true) {
@@ -719,23 +884,15 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
719
884
 
720
885
  // Calculate column widths based on terminal width
721
886
  const tableWidth = Math.min(TERM_WIDTH, 120);
722
- const iconCol = 5; // Width for emoji column (emoji takes 2 visual cells)
887
+ const iconCol = 4; // Fixed width for icon column
723
888
  const objCol = 28;
724
889
  const msgCol = tableWidth - iconCol - objCol - 6; // Account for vertical lines (3 chars)
725
890
 
726
- // Helper to get visible width (emoji takes 2 cells)
727
- const getVisibleWidth = (str) => {
728
- let width = 0;
729
- for (const char of str) {
730
- width += (char.charCodeAt(0) > 127) ? 2 : 1; // Emoji/wide chars = 2
731
- }
732
- return width;
733
- };
734
-
735
- // Helper to pad to visible width
891
+ // Pad string to display width using calcWidth for emoji support
736
892
  const padToWidth = (str, width) => {
737
- const currentWidth = getVisibleWidth(str);
738
- return str + ' '.repeat(width - currentWidth);
893
+ const s = str || '';
894
+ const currentWidth = calcWidth(s);
895
+ return s + ' '.repeat(Math.max(0, width - currentWidth));
739
896
  };
740
897
 
741
898
  const headerLine = '─'.repeat(iconCol) + '┼' + '─'.repeat(objCol) + '┼' + '─'.repeat(msgCol);
@@ -1293,8 +1450,12 @@ Examples:
1293
1450
  const config = loadConfig();
1294
1451
  const csrfToken = await fetchCsrfToken(config);
1295
1452
 
1296
- for (const sourceFile of filesSyntaxCheck) {
1297
- await syntaxCheckSource(sourceFile, csrfToken, config);
1453
+ // Send all files in one request
1454
+ const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config);
1455
+
1456
+ // Process results
1457
+ for (const result of results) {
1458
+ await processInspectResult(result);
1298
1459
  }
1299
1460
  break;
1300
1461
  }
@@ -1430,9 +1591,9 @@ Examples:
1430
1591
  console.log(` ${description}`);
1431
1592
  }
1432
1593
 
1433
- // Display source code for classes and interfaces
1594
+ // Display source code for classes, interfaces, and CDS views
1434
1595
  const source = obj.SOURCE || obj.source || '';
1435
- if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class')) {
1596
+ if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
1436
1597
  console.log('');
1437
1598
  // Replace escaped newlines with actual newlines and display
1438
1599
  const displaySource = source.replace(/\\n/g, '\n');
@@ -1500,6 +1661,15 @@ Examples:
1500
1661
  }
1501
1662
 
1502
1663
  console.log(end);
1664
+ } else if (objType === 'TTYP' || objType === 'Table Type') {
1665
+ // Show TTYP details as simple text lines
1666
+ console.log('');
1667
+ for (const comp of components) {
1668
+ const desc = comp.DESCRIPTION || comp.description || '';
1669
+ if (desc) {
1670
+ console.log(` ${desc}`);
1671
+ }
1672
+ }
1503
1673
  } else {
1504
1674
  // Build table display for TABL/STRU with Data Element and Description
1505
1675
  const colWidths = {
@@ -1556,6 +1726,243 @@ Examples:
1556
1726
  break;
1557
1727
  }
1558
1728
 
1729
+ case 'preview': {
1730
+ const objectsArgIndex = args.indexOf('--objects');
1731
+ if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
1732
+ console.error('Error: --objects parameter required');
1733
+ console.error('Usage: abapgit-agent preview --objects <table1>,<view1>,... [--type <type>] [--limit <n>] [--where <condition>] [--columns <cols>] [--vertical] [--compact] [--json]');
1734
+ console.error('Example: abapgit-agent preview --objects SFLIGHT');
1735
+ console.error('Example: abapgit-agent preview --objects ZC_MY_CDS_VIEW --type DDLS');
1736
+ console.error('Example: abapgit-agent preview --objects SFLIGHT --where "CARRID = \'AA\'"');
1737
+ process.exit(1);
1738
+ }
1739
+
1740
+ const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
1741
+ const typeArg = args.indexOf('--type');
1742
+ const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
1743
+ const limitArg = args.indexOf('--limit');
1744
+ const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 10;
1745
+ const whereArg = args.indexOf('--where');
1746
+ const where = whereArg !== -1 ? args[whereArg + 1] : null;
1747
+ const columnsArg = args.indexOf('--columns');
1748
+ const columns = columnsArg !== -1 ? args[columnsArg + 1].split(',').map(c => c.trim().toUpperCase()) : null;
1749
+ const verticalOutput = args.includes('--vertical');
1750
+ const compactOutput = args.includes('--compact');
1751
+ const jsonOutput = args.includes('--json');
1752
+
1753
+ console.log(`\n Previewing ${objects.length} object(s)`);
1754
+
1755
+ const config = loadConfig();
1756
+ const csrfToken = await fetchCsrfToken(config);
1757
+
1758
+ const data = {
1759
+ objects: objects,
1760
+ limit: Math.min(Math.max(1, limit), 100)
1761
+ };
1762
+
1763
+ if (type) {
1764
+ data.type = type;
1765
+ }
1766
+
1767
+ if (where) {
1768
+ data.where = where;
1769
+ }
1770
+
1771
+ if (columns) {
1772
+ data.columns = columns;
1773
+ }
1774
+
1775
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/preview', data, { csrfToken });
1776
+
1777
+ // Handle uppercase keys from ABAP
1778
+ const success = result.SUCCESS || result.success;
1779
+ const previewObjects = result.OBJECTS || result.objects || [];
1780
+ const message = result.MESSAGE || result.message || '';
1781
+ const error = result.ERROR || result.error;
1782
+
1783
+ if (!success || error) {
1784
+ console.error(`\n Error: ${error || 'Failed to preview objects'}`);
1785
+ break;
1786
+ }
1787
+
1788
+ if (jsonOutput) {
1789
+ console.log(JSON.stringify(result, null, 2));
1790
+ } else {
1791
+ console.log(`\n ${message}`);
1792
+ console.log('');
1793
+
1794
+ // Track if columns were explicitly specified
1795
+ const columnsExplicit = columns !== null;
1796
+
1797
+ for (let i = 0; i < previewObjects.length; i++) {
1798
+ const obj = previewObjects[i];
1799
+ const objName = obj.NAME || obj.name || `Object ${i + 1}`;
1800
+ const objType = obj.TYPE || obj.type || '';
1801
+ const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
1802
+ // Parse rows - could be a JSON string or array
1803
+ let rows = obj.ROWS || obj.rows || [];
1804
+ if (typeof rows === 'string') {
1805
+ try {
1806
+ rows = JSON.parse(rows);
1807
+ } catch (e) {
1808
+ rows = [];
1809
+ }
1810
+ }
1811
+ const fields = obj.FIELDS || obj.fields || [];
1812
+ const rowCount = obj.ROW_COUNT || obj.row_count || 0;
1813
+ const totalRows = obj.TOTAL_ROWS || obj.total_rows || 0;
1814
+ const notFound = obj.NOT_FOUND || obj.not_found || false;
1815
+ const accessDenied = obj.ACCESS_DENIED || obj.access_denied || false;
1816
+
1817
+ // Check if object was not found
1818
+ if (notFound) {
1819
+ console.log(` ❌ ${objName} (${objTypeText})`);
1820
+ console.log(` Object not found: ${objName}`);
1821
+ continue;
1822
+ }
1823
+
1824
+ // Check if access denied
1825
+ if (accessDenied) {
1826
+ console.log(` ❌ ${objName} (${objTypeText})`);
1827
+ console.log(` Access denied to: ${objName}`);
1828
+ continue;
1829
+ }
1830
+
1831
+ console.log(` 📊 Preview: ${objName} (${objTypeText})`);
1832
+
1833
+ // Check for errors first
1834
+ const objError = obj.ERROR || obj.error;
1835
+ if (objError) {
1836
+ console.log(` ❌ Error: ${objError}`);
1837
+ continue;
1838
+ }
1839
+
1840
+ if (rows.length === 0) {
1841
+ console.log(' No data found');
1842
+ continue;
1843
+ }
1844
+
1845
+ // Get all unique field names from all rows
1846
+ const allFields = new Set();
1847
+ rows.forEach(row => {
1848
+ Object.keys(row).forEach(key => allFields.add(key));
1849
+ });
1850
+ const allFieldNames = Array.from(allFields);
1851
+
1852
+ // Display as table - use fields metadata only if --columns was explicitly specified
1853
+ let fieldNames;
1854
+ let columnsAutoSelected = false;
1855
+ if (columnsExplicit && fields && fields.length > 0) {
1856
+ // Use fields from metadata (filtered by explicit --columns)
1857
+ fieldNames = fields.map(f => f.FIELD || f.field);
1858
+ } else {
1859
+ // Use all fields - let terminal handle wrapping if needed
1860
+ // Terminal width detection is unreliable without a proper TTY
1861
+ fieldNames = allFieldNames;
1862
+ }
1863
+
1864
+ // Calculate column widths - use reasonable defaults
1865
+ const colWidths = {};
1866
+ const maxColWidth = compactOutput ? 10 : 20;
1867
+ fieldNames.forEach(field => {
1868
+ let maxWidth = field.length;
1869
+ rows.forEach(row => {
1870
+ const value = String(row[field] || '');
1871
+ maxWidth = Math.max(maxWidth, value.length);
1872
+ });
1873
+ // Cap at maxColWidth (truncates both headers and data in compact mode)
1874
+ colWidths[field] = Math.min(maxWidth, maxColWidth);
1875
+ });
1876
+
1877
+ // Render output - either vertical or table
1878
+ if (verticalOutput) {
1879
+ // Vertical format: each field on its own line
1880
+ rows.forEach((row, rowIndex) => {
1881
+ if (rows.length > 1) {
1882
+ console.log(`\n Row ${rowIndex + 1}:`);
1883
+ console.log(' ' + '─'.repeat(30));
1884
+ }
1885
+ fieldNames.forEach(field => {
1886
+ const value = String(row[field] || '');
1887
+ console.log(` ${field}: ${value}`);
1888
+ });
1889
+ });
1890
+ console.log('');
1891
+ continue;
1892
+ }
1893
+
1894
+ // Build table header
1895
+ let headerLine = ' ┌';
1896
+ let separatorLine = ' ├';
1897
+ fieldNames.forEach(field => {
1898
+ const width = colWidths[field];
1899
+ headerLine += '─'.repeat(width + 2) + '┬';
1900
+ separatorLine += '─'.repeat(width + 2) + '┼';
1901
+ });
1902
+ headerLine = headerLine.slice(0, -1) + '┐';
1903
+ separatorLine = separatorLine.slice(0, -1) + '┤';
1904
+
1905
+ // Build header row
1906
+ let headerRow = ' │';
1907
+ fieldNames.forEach(field => {
1908
+ const width = colWidths[field];
1909
+ let displayField = String(field);
1910
+ if (displayField.length > width) {
1911
+ displayField = displayField.slice(0, width - 3) + '...';
1912
+ }
1913
+ headerRow += ' ' + displayField.padEnd(width) + ' │';
1914
+ });
1915
+
1916
+ console.log(headerLine);
1917
+ console.log(headerRow);
1918
+ console.log(separatorLine);
1919
+
1920
+ // Build data rows
1921
+ rows.forEach(row => {
1922
+ let dataRow = ' │';
1923
+ fieldNames.forEach(field => {
1924
+ const width = colWidths[field];
1925
+ const value = String(row[field] || '');
1926
+ const displayValue = value.length > width ? value.slice(0, width - 3) + '...' : value;
1927
+ dataRow += ' ' + displayValue.padEnd(width) + ' │';
1928
+ });
1929
+ console.log(dataRow);
1930
+ });
1931
+
1932
+ // Build bottom border
1933
+ let bottomLine = ' └';
1934
+ fieldNames.forEach(field => {
1935
+ const width = colWidths[field];
1936
+ bottomLine += '─'.repeat(width + 2) + '┴';
1937
+ });
1938
+ bottomLine = bottomLine.slice(0, -1) + '┘';
1939
+ console.log(bottomLine);
1940
+
1941
+ // Show row count
1942
+ if (totalRows > rowCount) {
1943
+ console.log(`\n Showing ${rowCount} of ${totalRows} rows`);
1944
+ } else {
1945
+ console.log(`\n ${rowCount} row(s)`);
1946
+ }
1947
+
1948
+ // Note about hidden columns only when --columns was explicitly specified
1949
+ const columnsDisplayed = fieldNames.length;
1950
+ let columnsHidden = [];
1951
+
1952
+ if (columnsExplicit) {
1953
+ columnsHidden = obj.COLUMNS_HIDDEN || obj.columns_hidden || [];
1954
+ if (columnsHidden.length > 0) {
1955
+ console.log(`\n ⚠️ ${columnsHidden.length} more columns hidden (${columnsHidden.join(', ')})`);
1956
+ console.log(' Use --json for full data');
1957
+ }
1958
+ }
1959
+
1960
+ console.log('');
1961
+ }
1962
+ }
1963
+ break;
1964
+ }
1965
+
1559
1966
  case 'help':
1560
1967
  case '--help':
1561
1968
  case '-h':