abapgit-agent 1.1.6 → 1.3.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 (46) hide show
  1. package/.github/workflows/release.yml +3 -1
  2. package/API.md +261 -0
  3. package/CLAUDE.md +384 -0
  4. package/README.md +16 -2
  5. package/RELEASE_NOTES.md +93 -8
  6. package/abap/CLAUDE.md +282 -6
  7. package/abap/copilot-instructions.md +79 -0
  8. package/abap/zcl_abgagt_agent.clas.abap +2 -2
  9. package/abap/zcl_abgagt_cmd_factory.clas.abap +2 -0
  10. package/abap/zcl_abgagt_command_inspect.clas.abap +255 -36
  11. package/abap/zcl_abgagt_command_tree.clas.abap +237 -0
  12. package/abap/zcl_abgagt_command_tree.clas.xml +15 -0
  13. package/abap/zcl_abgagt_command_view.clas.abap +240 -0
  14. package/abap/zcl_abgagt_command_view.clas.xml +15 -0
  15. package/abap/zcl_abgagt_resource_tree.clas.abap +70 -0
  16. package/abap/zcl_abgagt_resource_tree.clas.xml +15 -0
  17. package/abap/zcl_abgagt_resource_view.clas.abap +68 -0
  18. package/abap/zcl_abgagt_resource_view.clas.xml +15 -0
  19. package/abap/zcl_abgagt_rest_handler.clas.abap +2 -0
  20. package/abap/zcl_abgagt_util.clas.abap +2 -2
  21. package/abap/zcl_abgagt_viewer_clas.clas.abap +58 -0
  22. package/abap/zcl_abgagt_viewer_clas.clas.xml +15 -0
  23. package/abap/zcl_abgagt_viewer_ddls.clas.abap +83 -0
  24. package/abap/zcl_abgagt_viewer_ddls.clas.xml +15 -0
  25. package/abap/zcl_abgagt_viewer_dtel.clas.abap +98 -0
  26. package/abap/zcl_abgagt_viewer_dtel.clas.xml +15 -0
  27. package/abap/zcl_abgagt_viewer_factory.clas.abap +41 -0
  28. package/abap/zcl_abgagt_viewer_factory.clas.xml +15 -0
  29. package/abap/zcl_abgagt_viewer_intf.clas.abap +58 -0
  30. package/abap/zcl_abgagt_viewer_intf.clas.xml +15 -0
  31. package/abap/zcl_abgagt_viewer_stru.clas.abap +59 -0
  32. package/abap/zcl_abgagt_viewer_stru.clas.xml +15 -0
  33. package/abap/zcl_abgagt_viewer_tabl.clas.abap +59 -0
  34. package/abap/zcl_abgagt_viewer_tabl.clas.xml +15 -0
  35. package/abap/zcl_abgagt_viewer_ttyp.clas.abap +93 -0
  36. package/abap/zcl_abgagt_viewer_ttyp.clas.xml +15 -0
  37. package/abap/zif_abgagt_command.intf.abap +3 -1
  38. package/abap/zif_abgagt_viewer.intf.abap +12 -0
  39. package/abap/zif_abgagt_viewer.intf.xml +15 -0
  40. package/bin/abapgit-agent +605 -38
  41. package/docs/commands.md +27 -8
  42. package/docs/tree-command.md +303 -0
  43. package/docs/view-command.md +501 -0
  44. package/package.json +1 -1
  45. package/src/abap-client.js +22 -0
  46. package/src/agent.js +27 -0
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
  */
@@ -495,6 +621,136 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
495
621
  }
496
622
  }
497
623
 
624
+ /**
625
+ * Run tree command and return raw result
626
+ */
627
+ async function runTreeCommand(packageName, depth, includeObjects, csrfToken, config) {
628
+ const data = {
629
+ package: packageName,
630
+ depth: depth,
631
+ include_objects: includeObjects
632
+ };
633
+
634
+ return await request('POST', '/sap/bc/z_abapgit_agent/tree', data, { csrfToken });
635
+ }
636
+
637
+ /**
638
+ * Display tree output in human-readable format
639
+ */
640
+ async function displayTreeOutput(packageName, depth, includeObjects) {
641
+ const config = loadConfig();
642
+ const csrfToken = await fetchCsrfToken(config);
643
+
644
+ console.log(`\n Getting package tree for: ${packageName}`);
645
+
646
+ const result = await runTreeCommand(packageName, depth, includeObjects, csrfToken, config);
647
+
648
+ // Handle uppercase keys from ABAP
649
+ const success = result.SUCCESS || result.success;
650
+ const error = result.ERROR || result.error;
651
+
652
+ if (!success || error) {
653
+ console.error(`\n ❌ Error: ${error || 'Failed to get tree'}`);
654
+ return;
655
+ }
656
+
657
+ // Parse hierarchy structure (ABAP returns flat nodes with parent refs)
658
+ const nodes = result.NODES || result.nodes || [];
659
+ const rootPackage = result.PACKAGE || result.package || packageName;
660
+ const parentPackage = result.PARENT_PACKAGE || result.parent_package;
661
+ const totalPackages = result.TOTAL_PACKAGES || result.total_packages || 0;
662
+ const totalObjects = result.TOTAL_OBJECTS || result.total_objects || 0;
663
+ const objectTypes = result.OBJECTS || result.objects || [];
664
+
665
+ console.log(`\n Package Tree: ${rootPackage}`);
666
+
667
+ // Display parent info if available
668
+ if (parentPackage && parentPackage !== rootPackage) {
669
+ console.log(` ⬆️ Parent: ${parentPackage}`);
670
+ }
671
+
672
+ console.log('');
673
+
674
+ // Build and display tree from flat nodes list
675
+ const lines = buildTreeLinesFromNodes(nodes, '', true);
676
+ for (const line of lines) {
677
+ console.log(` ${line}`);
678
+ }
679
+
680
+ console.log('');
681
+ console.log(' Summary');
682
+ console.log(` PACKAGES: ${totalPackages}`);
683
+ console.log(` OBJECTS: ${totalObjects}`);
684
+
685
+ // Display object types if available
686
+ if (includeObjects && objectTypes.length > 0) {
687
+ const typeStr = objectTypes.map(t => `${t.OBJECT || t.object}=${t.COUNT || t.count}`).join(' ');
688
+ console.log(` TYPES: ${typeStr}`);
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Build tree display lines from flat nodes list
694
+ */
695
+ function buildTreeLinesFromNodes(nodes, prefix, isLast) {
696
+ const lines = [];
697
+
698
+ if (nodes.length === 0) {
699
+ return lines;
700
+ }
701
+
702
+ // First node is the root
703
+ const root = nodes[0];
704
+ const icon = '📦';
705
+ lines.push(`${prefix}${isLast ? '└─ ' : '├─ '} ${icon} ${root.PACKAGE || root.package}`);
706
+
707
+ // Get children (nodes with depth > 0, grouped by depth)
708
+ const children = nodes.filter(n => (n.DEPTH || n.depth) > 0);
709
+
710
+ // Group children by depth
711
+ const byDepth = {};
712
+ children.forEach(n => {
713
+ const d = n.DEPTH || n.depth;
714
+ if (!byDepth[d]) byDepth[d] = [];
715
+ byDepth[d].push(n);
716
+ });
717
+
718
+ // Process depth 1 children
719
+ const depth1 = byDepth[1] || [];
720
+ const newPrefix = prefix + (isLast ? ' ' : '│ ');
721
+
722
+ for (let i = 0; i < depth1.length; i++) {
723
+ const child = depth1[i];
724
+ const isSubLast = i === depth1.length - 1;
725
+ const childLines = buildChildLines(child, newPrefix, isSubLast, byDepth);
726
+ lines.push(...childLines);
727
+ }
728
+
729
+ return lines;
730
+ }
731
+
732
+ function buildChildLines(node, prefix, isLast, byDepth) {
733
+ const lines = [];
734
+ const icon = '📦';
735
+ lines.push(`${prefix}${isLast ? '└─ ' : '├─ '} ${icon} ${node.PACKAGE || node.package}`);
736
+
737
+ const newPrefix = prefix + (isLast ? ' ' : '│ ');
738
+ const nodeDepth = node.DEPTH || node.depth;
739
+ const children = byDepth[nodeDepth + 1] || [];
740
+
741
+ // Find children of this node
742
+ const myChildren = children.filter(n => (n.PARENT || n.parent) === (node.PACKAGE || node.package));
743
+
744
+ for (let i = 0; i < myChildren.length; i++) {
745
+ const child = myChildren[i];
746
+ const isSubLast = i === myChildren.length - 1;
747
+ const childLines = buildChildLines(child, newPrefix, isSubLast, byDepth);
748
+ lines.push(...childLines);
749
+ }
750
+
751
+ return lines;
752
+ }
753
+
498
754
  /**
499
755
  * Pull and activate repository
500
756
  */
@@ -561,7 +817,46 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
561
817
  'W': '⚠️', // Warning
562
818
  'A': '🛑' // Abort
563
819
  };
564
- 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));
565
860
  };
566
861
 
567
862
  if (success === 'X' || success === true) {
@@ -589,23 +884,15 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
589
884
 
590
885
  // Calculate column widths based on terminal width
591
886
  const tableWidth = Math.min(TERM_WIDTH, 120);
592
- const iconCol = 5; // Width for emoji column (emoji takes 2 visual cells)
887
+ const iconCol = 4; // Fixed width for icon column
593
888
  const objCol = 28;
594
889
  const msgCol = tableWidth - iconCol - objCol - 6; // Account for vertical lines (3 chars)
595
890
 
596
- // Helper to get visible width (emoji takes 2 cells)
597
- const getVisibleWidth = (str) => {
598
- let width = 0;
599
- for (const char of str) {
600
- width += (char.charCodeAt(0) > 127) ? 2 : 1; // Emoji/wide chars = 2
601
- }
602
- return width;
603
- };
604
-
605
- // Helper to pad to visible width
891
+ // Pad string to display width using calcWidth for emoji support
606
892
  const padToWidth = (str, width) => {
607
- const currentWidth = getVisibleWidth(str);
608
- return str + ' '.repeat(width - currentWidth);
893
+ const s = str || '';
894
+ const currentWidth = calcWidth(s);
895
+ return s + ' '.repeat(Math.max(0, width - currentWidth));
609
896
  };
610
897
 
611
898
  const headerLine = '─'.repeat(iconCol) + '┼' + '─'.repeat(objCol) + '┼' + '─'.repeat(msgCol);
@@ -818,6 +1105,25 @@ async function runInit(args) {
818
1105
  console.error(`Error copying CLAUDE.md: ${error.message}`);
819
1106
  }
820
1107
 
1108
+ // Copy copilot-instructions.md for GitHub Copilot
1109
+ const copilotMdPath = pathModule.join(__dirname, '..', 'abap', 'copilot-instructions.md');
1110
+ const githubDir = pathModule.join(process.cwd(), '.github');
1111
+ const localCopilotMdPath = pathModule.join(githubDir, 'copilot-instructions.md');
1112
+ try {
1113
+ if (fs.existsSync(copilotMdPath)) {
1114
+ // Ensure .github directory exists
1115
+ if (!fs.existsSync(githubDir)) {
1116
+ fs.mkdirSync(githubDir, { recursive: true });
1117
+ }
1118
+ fs.copyFileSync(copilotMdPath, localCopilotMdPath);
1119
+ console.log(`✅ Created .github/copilot-instructions.md`);
1120
+ } else {
1121
+ console.log(`⚠️ copilot-instructions.md not found in abap/ directory`);
1122
+ }
1123
+ } catch (error) {
1124
+ console.error(`Error copying copilot-instructions.md: ${error.message}`);
1125
+ }
1126
+
821
1127
  // Create folder
822
1128
  const folderPath = pathModule.join(process.cwd(), folder);
823
1129
  try {
@@ -1144,8 +1450,12 @@ Examples:
1144
1450
  const config = loadConfig();
1145
1451
  const csrfToken = await fetchCsrfToken(config);
1146
1452
 
1147
- for (const sourceFile of filesSyntaxCheck) {
1148
- 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);
1149
1459
  }
1150
1460
  break;
1151
1461
  }
@@ -1173,6 +1483,249 @@ Examples:
1173
1483
  break;
1174
1484
  }
1175
1485
 
1486
+ case 'tree': {
1487
+ const packageArgIndex = args.indexOf('--package');
1488
+ if (packageArgIndex === -1 || packageArgIndex + 1 >= args.length) {
1489
+ console.error('Error: --package parameter required');
1490
+ console.error('Usage: abapgit-agent tree --package <package> [--depth <n>] [--include-objects] [--json]');
1491
+ console.error('Example: abapgit-agent tree --package $ZMY_PACKAGE');
1492
+ process.exit(1);
1493
+ }
1494
+
1495
+ const packageName = args[packageArgIndex + 1];
1496
+
1497
+ // Optional depth parameter
1498
+ const depthArgIndex = args.indexOf('--depth');
1499
+ let depth = 3;
1500
+ if (depthArgIndex !== -1 && depthArgIndex + 1 < args.length) {
1501
+ depth = parseInt(args[depthArgIndex + 1], 10);
1502
+ if (isNaN(depth) || depth < 1) {
1503
+ console.error('Error: --depth must be a positive number');
1504
+ process.exit(1);
1505
+ }
1506
+ }
1507
+
1508
+ // Optional include-objects parameter
1509
+ const includeObjects = args.includes('--include-objects');
1510
+
1511
+ // Optional json parameter
1512
+ const jsonOutput = args.includes('--json');
1513
+
1514
+ if (jsonOutput) {
1515
+ const config = loadConfig();
1516
+ const csrfToken = await fetchCsrfToken(config);
1517
+ const result = await runTreeCommand(packageName, depth, includeObjects, csrfToken, config);
1518
+ console.log(JSON.stringify(result, null, 2));
1519
+ } else {
1520
+ await displayTreeOutput(packageName, depth, includeObjects);
1521
+ }
1522
+ break;
1523
+ }
1524
+
1525
+ case 'view': {
1526
+ const objectsArgIndex = args.indexOf('--objects');
1527
+ if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
1528
+ console.error('Error: --objects parameter required');
1529
+ console.error('Usage: abapgit-agent view --objects <obj1>,<obj2>,... [--type <type>] [--json]');
1530
+ console.error('Example: abapgit-agent view --objects ZCL_MY_CLASS');
1531
+ console.error('Example: abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --type CLAS');
1532
+ process.exit(1);
1533
+ }
1534
+
1535
+ const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
1536
+ const typeArg = args.indexOf('--type');
1537
+ const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
1538
+ const jsonOutput = args.includes('--json');
1539
+
1540
+ console.log(`\n Viewing ${objects.length} object(s)`);
1541
+
1542
+ const config = loadConfig();
1543
+ const csrfToken = await fetchCsrfToken(config);
1544
+
1545
+ const data = {
1546
+ objects: objects
1547
+ };
1548
+
1549
+ if (type) {
1550
+ data.type = type;
1551
+ }
1552
+
1553
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/view', data, { csrfToken });
1554
+
1555
+ // Handle uppercase keys from ABAP
1556
+ const success = result.SUCCESS || result.success;
1557
+ const viewObjects = result.OBJECTS || result.objects || [];
1558
+ const message = result.MESSAGE || result.message || '';
1559
+ const error = result.ERROR || result.error;
1560
+
1561
+ if (!success || error) {
1562
+ console.error(`\n Error: ${error || 'Failed to view objects'}`);
1563
+ break;
1564
+ }
1565
+
1566
+ if (jsonOutput) {
1567
+ console.log(JSON.stringify(result, null, 2));
1568
+ } else {
1569
+ console.log(`\n ${message}`);
1570
+ console.log('');
1571
+
1572
+ for (let i = 0; i < viewObjects.length; i++) {
1573
+ const obj = viewObjects[i];
1574
+ const objName = obj.NAME || obj.name || `Object ${i + 1}`;
1575
+ const objType = obj.TYPE || obj.type || '';
1576
+ const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
1577
+ const description = obj.DESCRIPTION || obj.description || '';
1578
+ const methods = obj.METHODS || obj.methods || [];
1579
+ const components = obj.COMPONENTS || obj.components || [];
1580
+ const notFound = obj.NOT_FOUND || obj.not_found || false;
1581
+
1582
+ // Check if object was not found
1583
+ if (notFound) {
1584
+ console.log(` ❌ ${objName} (${objTypeText})`);
1585
+ console.log(` Object not found: ${objName}`);
1586
+ continue;
1587
+ }
1588
+
1589
+ console.log(` 📖 ${objName} (${objTypeText})`);
1590
+ if (description) {
1591
+ console.log(` ${description}`);
1592
+ }
1593
+
1594
+ // Display source code for classes, interfaces, and CDS views
1595
+ const source = obj.SOURCE || obj.source || '';
1596
+ if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
1597
+ console.log('');
1598
+ // Replace escaped newlines with actual newlines and display
1599
+ const displaySource = source.replace(/\\n/g, '\n');
1600
+ const lines = displaySource.split('\n');
1601
+ for (const line of lines) {
1602
+ console.log(` ${line}`);
1603
+ }
1604
+ }
1605
+
1606
+ if (methods.length > 0) {
1607
+ console.log(` Methods: ${methods.length}`);
1608
+ for (const method of methods.slice(0, 5)) {
1609
+ const name = method.NAME || method.name || '';
1610
+ const visibility = method.VISIBILITY || method.visibility || '';
1611
+ console.log(` - ${visibility} ${name}`);
1612
+ }
1613
+ if (methods.length > 5) {
1614
+ console.log(` ... and ${methods.length - 5} more`);
1615
+ }
1616
+ }
1617
+
1618
+ if (components.length > 0) {
1619
+ // Check if this is a data element (DTEL) - show domain info in property format
1620
+ if (objType === 'DTEL' || objType === 'Data Element') {
1621
+ const propWidth = 18;
1622
+ const valueWidth = 40;
1623
+
1624
+ // Build separator with corners
1625
+ const sep = '┌' + '─'.repeat(propWidth + 2) + '┬' + '─'.repeat(valueWidth + 2) + '┐';
1626
+ const mid = '├' + '─'.repeat(propWidth + 2) + '┼' + '─'.repeat(valueWidth + 2) + '┤';
1627
+ const end = '└' + '─'.repeat(propWidth + 2) + '┴' + '─'.repeat(valueWidth + 2) + '┘';
1628
+
1629
+ // Helper to build row
1630
+ const buildPropRow = (property, value) => {
1631
+ return '│ ' + String(property || '').padEnd(propWidth) + ' │ ' +
1632
+ String(value || '').substring(0, valueWidth).padEnd(valueWidth) + ' │';
1633
+ };
1634
+
1635
+ console.log(` DATA ELEMENT ${objName}:`);
1636
+ console.log(sep);
1637
+ console.log(buildPropRow('Property', 'Value'));
1638
+ console.log(mid);
1639
+
1640
+ // Collect properties from top-level fields and components
1641
+ const domain = obj.DOMAIN || obj.domain || '';
1642
+ const domainType = obj.DOMAIN_TYPE || obj.domain_type || '';
1643
+ const domainLength = obj.DOMAIN_LENGTH || obj.domain_length || 0;
1644
+ const domainDecimals = obj.DOMAIN_DECIMALS || obj.domain_decimals || 0;
1645
+ const description = obj.DESCRIPTION || obj.description || '';
1646
+
1647
+ if (domainType) {
1648
+ console.log(buildPropRow('Data Type', domainType));
1649
+ }
1650
+ if (domainLength) {
1651
+ console.log(buildPropRow('Length', String(domainLength)));
1652
+ }
1653
+ if (domainDecimals) {
1654
+ console.log(buildPropRow('Decimals', String(domainDecimals)));
1655
+ }
1656
+ if (description) {
1657
+ console.log(buildPropRow('Description', description));
1658
+ }
1659
+ if (domain) {
1660
+ console.log(buildPropRow('Domain', domain));
1661
+ }
1662
+
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
+ }
1673
+ } else {
1674
+ // Build table display for TABL/STRU with Data Element and Description
1675
+ const colWidths = {
1676
+ field: 16, // Max field name length
1677
+ key: 3,
1678
+ type: 8,
1679
+ length: 8,
1680
+ dataelement: 30, // Max data element name length
1681
+ description: 60, // Max field description length
1682
+ };
1683
+
1684
+ // Helper to truncate with ellipsis if needed
1685
+ const truncate = (str, maxLen) => {
1686
+ const s = String(str || '');
1687
+ if (s.length <= maxLen) return s;
1688
+ return s.substring(0, maxLen - 1) + '…';
1689
+ };
1690
+
1691
+ // Helper to build row
1692
+ const buildRow = (field, key, type, length, dataelement, description) => {
1693
+ return ' | ' + truncate(field, colWidths.field).padEnd(colWidths.field) + ' | ' + String(key || '').padEnd(colWidths.key) + ' | ' + truncate(type, colWidths.type).padEnd(colWidths.type) + ' | ' + String(length || '').padStart(colWidths.length) + ' | ' + truncate(dataelement, colWidths.dataelement).padEnd(colWidths.dataelement) + ' | ' + truncate(description, colWidths.description).padEnd(colWidths.description) + ' |';
1694
+ };
1695
+
1696
+ // Build separator line (matches row structure with | at ends and + between columns)
1697
+ const sep = ' |' + '-'.repeat(colWidths.field + 2) + '+' +
1698
+ '-'.repeat(colWidths.key + 2) + '+' +
1699
+ '-'.repeat(colWidths.type + 2) + '+' +
1700
+ '-'.repeat(colWidths.length + 2) + '+' +
1701
+ '-'.repeat(colWidths.dataelement + 2) + '+' +
1702
+ '-'.repeat(colWidths.description + 2) + '|';
1703
+
1704
+ // Header
1705
+ console.log(` TABLE ${objName}:`);
1706
+ console.log(sep);
1707
+ console.log(buildRow('Field', 'Key', 'Type', 'Length', 'Data Elem', 'Description'));
1708
+ console.log(sep);
1709
+
1710
+ // Rows
1711
+ for (const comp of components) {
1712
+ const key = comp.KEY || comp.key || false ? 'X' : '';
1713
+ const dataelement = comp.DATAELEMENT || comp.dataelement || '';
1714
+ const description = comp.DESCRIPTION || comp.description || '';
1715
+ console.log(buildRow(comp.FIELD || comp.field, key, comp.TYPE || comp.type, comp.LENGTH || comp.length, dataelement, description));
1716
+ }
1717
+
1718
+ console.log(sep);
1719
+ }
1720
+ }
1721
+
1722
+ console.log('');
1723
+ }
1724
+
1725
+ }
1726
+ break;
1727
+ }
1728
+
1176
1729
  case 'help':
1177
1730
  case '--help':
1178
1731
  case '-h':
@@ -1206,6 +1759,12 @@ Commands:
1206
1759
  unit --files <file1>,<file2>,...
1207
1760
  Run AUnit tests for ABAP test class files (.testclasses.abap)
1208
1761
 
1762
+ tree --package <package> [--depth <n>] [--include-objects] [--json]
1763
+ Display package hierarchy tree from ABAP system
1764
+
1765
+ view --objects <obj1>,<obj2>,... [--type <type>] [--json]
1766
+ View ABAP object definitions from the ABAP system
1767
+
1209
1768
  health
1210
1769
  Check if ABAP REST API is healthy
1211
1770
 
@@ -1223,6 +1782,14 @@ Examples:
1223
1782
  abapgit-agent pull --transport DEVK900001 # With transport
1224
1783
  abapgit-agent inspect --files zcl_my_class.clas.abap # Syntax check
1225
1784
  abapgit-agent unit --files zcl_my_test.clas.testclasses.abap # Run tests
1785
+ abapgit-agent tree --package $ZMY_PACKAGE # Show package tree
1786
+ abapgit-agent tree --package $ZMY_PACKAGE --depth 2 # Shallow tree
1787
+ abapgit-agent tree --package $ZMY_PACKAGE --include-objects # With object counts
1788
+ abapgit-agent tree --package $ZMY_PACKAGE --json # JSON output
1789
+ abapgit-agent view --objects ZCL_MY_CLASS # View class definition
1790
+ abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
1791
+ abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
1792
+ abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
1226
1793
  abapgit-agent health
1227
1794
  abapgit-agent status
1228
1795
  `);