@xagent-ai/cli 1.2.0 → 1.2.2

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 (80) hide show
  1. package/README.md +1 -1
  2. package/README_CN.md +1 -1
  3. package/dist/agents.js +164 -164
  4. package/dist/agents.js.map +1 -1
  5. package/dist/ai-client.d.ts +4 -6
  6. package/dist/ai-client.d.ts.map +1 -1
  7. package/dist/ai-client.js +137 -115
  8. package/dist/ai-client.js.map +1 -1
  9. package/dist/auth.js +4 -4
  10. package/dist/auth.js.map +1 -1
  11. package/dist/cli.js +184 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.js +3 -3
  14. package/dist/config.js.map +1 -1
  15. package/dist/context-compressor.d.ts.map +1 -1
  16. package/dist/context-compressor.js +65 -81
  17. package/dist/context-compressor.js.map +1 -1
  18. package/dist/conversation.d.ts +1 -1
  19. package/dist/conversation.d.ts.map +1 -1
  20. package/dist/conversation.js +5 -31
  21. package/dist/conversation.js.map +1 -1
  22. package/dist/memory.d.ts +5 -1
  23. package/dist/memory.d.ts.map +1 -1
  24. package/dist/memory.js +77 -37
  25. package/dist/memory.js.map +1 -1
  26. package/dist/remote-ai-client.d.ts +1 -8
  27. package/dist/remote-ai-client.d.ts.map +1 -1
  28. package/dist/remote-ai-client.js +55 -65
  29. package/dist/remote-ai-client.js.map +1 -1
  30. package/dist/retry.d.ts +35 -0
  31. package/dist/retry.d.ts.map +1 -0
  32. package/dist/retry.js +166 -0
  33. package/dist/retry.js.map +1 -0
  34. package/dist/session.d.ts +0 -5
  35. package/dist/session.d.ts.map +1 -1
  36. package/dist/session.js +243 -312
  37. package/dist/session.js.map +1 -1
  38. package/dist/slash-commands.d.ts +1 -0
  39. package/dist/slash-commands.d.ts.map +1 -1
  40. package/dist/slash-commands.js +91 -9
  41. package/dist/slash-commands.js.map +1 -1
  42. package/dist/smart-approval.d.ts.map +1 -1
  43. package/dist/smart-approval.js +18 -17
  44. package/dist/smart-approval.js.map +1 -1
  45. package/dist/system-prompt-generator.d.ts.map +1 -1
  46. package/dist/system-prompt-generator.js +149 -139
  47. package/dist/system-prompt-generator.js.map +1 -1
  48. package/dist/theme.d.ts +48 -0
  49. package/dist/theme.d.ts.map +1 -1
  50. package/dist/theme.js +254 -0
  51. package/dist/theme.js.map +1 -1
  52. package/dist/tools/edit-diff.d.ts +32 -0
  53. package/dist/tools/edit-diff.d.ts.map +1 -0
  54. package/dist/tools/edit-diff.js +185 -0
  55. package/dist/tools/edit-diff.js.map +1 -0
  56. package/dist/tools/edit.d.ts +11 -0
  57. package/dist/tools/edit.d.ts.map +1 -0
  58. package/dist/tools/edit.js +129 -0
  59. package/dist/tools/edit.js.map +1 -0
  60. package/dist/tools.d.ts +19 -5
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +979 -631
  63. package/dist/tools.js.map +1 -1
  64. package/dist/types.d.ts +6 -31
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +3 -2
  67. package/src/agents.ts +504 -504
  68. package/src/ai-client.ts +1559 -1458
  69. package/src/auth.ts +4 -4
  70. package/src/cli.ts +195 -1
  71. package/src/config.ts +3 -3
  72. package/src/memory.ts +55 -14
  73. package/src/remote-ai-client.ts +663 -683
  74. package/src/retry.ts +217 -0
  75. package/src/session.ts +1736 -1840
  76. package/src/slash-commands.ts +98 -9
  77. package/src/smart-approval.ts +626 -625
  78. package/src/system-prompt-generator.ts +853 -843
  79. package/src/theme.ts +284 -0
  80. package/src/tools.ts +390 -70
package/src/theme.ts CHANGED
@@ -1,5 +1,72 @@
1
1
  import chalk from 'chalk';
2
2
 
3
+ /**
4
+ * Detect if terminal has dark or light background
5
+ * Returns 'dark' for dark backgrounds (default), 'light' for light backgrounds
6
+ */
7
+ function getTerminalBackground(): 'dark' | 'light' {
8
+ // Check common environment variables
9
+ const colorfgbg = process.env.COLORFGBG; // e.g., "15;0" (light fg, dark bg)
10
+ const termProgram = process.env.TERM_PROGRAM;
11
+ const termProgramVersion = process.env.TERM_PROGRAM_VERSION;
12
+
13
+ // Try to parse COLORFGBG (format: "fg;bg" or "fg;color;bg")
14
+ if (colorfgbg) {
15
+ const parts = colorfgbg.split(';');
16
+ const bg = parts[parts.length - 1];
17
+ // 0-6 are typically dark colors, 7-15 are light
18
+ const bgNum = parseInt(bg, 10);
19
+ if (!isNaN(bgNum)) {
20
+ // Most terminal colors: 0=black, 1=red, 2=green, ... 7=white, 8-15=bright variants
21
+ // For background: 0-6 = dark, 7 = light
22
+ if (bgNum >= 0 && bgNum <= 6) return 'dark';
23
+ if (bgNum >= 7) return 'light';
24
+ }
25
+ }
26
+
27
+ // iTerm2 on macOS can indicate background brightness
28
+ if (termProgram === 'Apple_Terminal' || termProgram === 'iTerm.app') {
29
+ // Apple Terminal and iTerm2 default to dark
30
+ return 'dark';
31
+ }
32
+
33
+ // Windows Terminal and VS Code Terminal typically dark
34
+ if (termProgram?.includes('WindowsTerminal') || termProgram?.includes('Code')) {
35
+ return 'dark';
36
+ }
37
+
38
+ // Default to dark background (most common for developers)
39
+ return 'dark';
40
+ }
41
+
42
+ /**
43
+ * Mix a color with background to create a transparent effect
44
+ * @param hexColor - The base color in hex format
45
+ * @param opacity - Opacity from 0 (background) to 1 (full color)
46
+ * @param background - Terminal background color ('dark' or 'light')
47
+ */
48
+ function mixWithBackground(hexColor: string, opacity: number, background: 'dark' | 'light' = 'dark'): string {
49
+ const hex = hexColor.replace('#', '');
50
+ const r = parseInt(hex.substring(0, 2), 16);
51
+ const g = parseInt(hex.substring(2, 4), 16);
52
+ const b = parseInt(hex.substring(4, 6), 16);
53
+
54
+ // Background colors
55
+ const bg = background === 'dark'
56
+ ? { r: 0, g: 0, b: 0 } // Black for dark terminals
57
+ : { r: 255, g: 255, b: 255 }; // White for light terminals
58
+
59
+ const mr = Math.round(bg.r + (r - bg.r) * opacity);
60
+ const mg = Math.round(bg.g + (g - bg.g) * opacity);
61
+ const mb = Math.round(bg.b + (b - bg.b) * opacity);
62
+
63
+ return `#${mr.toString(16).padStart(2, '0')}${mg.toString(16).padStart(2, '0')}${mb.toString(16).padStart(2, '0')}`;
64
+ }
65
+
66
+ // Get terminal background once at module load
67
+ const TERMINAL_BG = getTerminalBackground();
68
+ export { TERMINAL_BG, getTerminalBackground, mixWithBackground };
69
+
3
70
  type ColorFunction = (text: string) => string;
4
71
 
5
72
  interface BoxOptions {
@@ -66,6 +133,13 @@ export const colors = {
66
133
  codeBackground: chalk.hex('#1f2937'), // Gray-800
67
134
  codeText: chalk.hex('#e5e7eb'), // Gray-200
68
135
 
136
+ // Diff colors - git-style diff highlighting with transparent backgrounds
137
+ diffAdded: chalk.hex('#10b981'), // Green - added lines
138
+ diffRemoved: chalk.hex('#ef4444'), // Red - removed lines
139
+ diffContext: chalk.hex('#6b7280'), // Gray - context lines
140
+ diffAddedInverse: (text: string) => chalk.bgHex(mixWithBackground('#10b981', 0.25, TERMINAL_BG)).hex(TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937')(text), // Adaptive green bg
141
+ diffRemovedInverse: (text: string) => chalk.bgHex(mixWithBackground('#ef4444', 0.25, TERMINAL_BG)).hex(TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937')(text), // Adaptive red bg
142
+
69
143
  // Gradient colors
70
144
  gradient: (text: string) => {
71
145
  const gradientColors = ['#06b6d4', '#8b5cf6', '#ec4899'];
@@ -452,4 +526,214 @@ export function renderMarkdown(text: string, maxWidth: number = 80): string {
452
526
  return result.join('\n');
453
527
  }
454
528
 
529
+ /**
530
+ * Parse a single diff line.
531
+ * Format: "+<num> content" or "-<num> content" or " <num> content"
532
+ * Also handles: "+<num>..." for skipped lines
533
+ */
534
+ function parseDiffLine(line: string): { prefix: string; lineNum: string; content: string } | null {
535
+ // Skip empty lines
536
+ if (!line || line.trim() === '') return null;
537
+
538
+ const firstChar = line[0];
539
+ if (!['+', '-', ' '].includes(firstChar)) {
540
+ // Try to detect if this is a diff-like line with different format
541
+ // e.g., "+ 1 content" (with padding)
542
+ const match = line.match(/^[+\-\s]\s*/);
543
+ if (match) {
544
+ // This looks like a diff line but with unexpected format
545
+ // Extract the content after the prefix
546
+ const afterPrefix = line.slice(match[0].length).trim();
547
+ return { prefix: firstChar, lineNum: '', content: afterPrefix };
548
+ }
549
+ return null;
550
+ }
551
+
552
+ // Extract content after the prefix char
553
+ const afterPrefix = line.slice(1).trim();
554
+
555
+ // Check for skipped lines marker
556
+ if (afterPrefix === '...') {
557
+ return { prefix: firstChar, lineNum: '', content: '...' };
558
+ }
559
+
560
+ // Return the rest as content
561
+ return { prefix: firstChar, lineNum: '', content: afterPrefix };
562
+ }
563
+
564
+ /**
565
+ * Replace tabs with spaces for consistent rendering.
566
+ */
567
+ function replaceTabs(text: string): string {
568
+ return text.replace(/\t/g, ' ');
569
+ }
570
+
571
+ /**
572
+ * Compute word-level diff and render with inverse on changed parts.
573
+ * Uses diffWords which groups whitespace with adjacent words for cleaner highlighting.
574
+ */
575
+ function renderIntraLineDiff(oldContent: string, newContent: string): { removedLine: string; addedLine: string } {
576
+ // Simple word diff without external dependency
577
+ const oldWords = oldContent.split(/(\s+)/);
578
+ const newWords = newContent.split(/(\s+)/);
579
+
580
+ let removedLine = '';
581
+ let addedLine = '';
582
+ let i = 0, j = 0;
583
+
584
+ while (i < oldWords.length || j < newWords.length) {
585
+ const oldWord = oldWords[i] || '';
586
+ const newWord = newWords[j] || '';
587
+
588
+ if (oldWord === newWord) {
589
+ removedLine += oldWord;
590
+ addedLine += newWord;
591
+ i++;
592
+ j++;
593
+ } else if (oldWord === '' || oldWord.match(/^\s+$/)) {
594
+ addedLine += newWord;
595
+ j++;
596
+ } else if (newWord === '' || newWord.match(/^\s+$/)) {
597
+ removedLine += oldWord;
598
+ i++;
599
+ } else {
600
+ // Simple heuristic: show removed with inverse, added with inverse
601
+ removedLine += colors.diffRemovedInverse(oldWord);
602
+ addedLine += colors.diffAddedInverse(newWord);
603
+ i++;
604
+ j++;
605
+ }
606
+ }
607
+
608
+ return { removedLine, addedLine };
609
+ }
610
+
611
+ /**
612
+ * Render a diff string with colored lines and intra-line change highlighting.
613
+ * - Context lines: gray
614
+ * - Removed lines: red
615
+ * - Added lines: green
616
+ */
617
+ export function renderDiff(diffText: string): string {
618
+ if (!diffText) return '';
619
+
620
+ const lines = diffText.split('\n');
621
+ const result: string[] = [];
622
+
623
+ let i = 0;
624
+ while (i < lines.length) {
625
+ const line = lines[i];
626
+ const parsed = parseDiffLine(line);
627
+
628
+ if (!parsed) {
629
+ result.push(colors.diffContext(line));
630
+ i++;
631
+ continue;
632
+ }
633
+
634
+ if (parsed.prefix === '-') {
635
+ // Collect consecutive removed lines
636
+ const removedLines: { lineNum: string; content: string }[] = [];
637
+ while (i < lines.length) {
638
+ const p = parseDiffLine(lines[i]);
639
+ if (!p || p.prefix !== '-') break;
640
+ removedLines.push({ lineNum: p.lineNum, content: p.content });
641
+ i++;
642
+ }
643
+
644
+ // Collect consecutive added lines
645
+ const addedLines: { lineNum: string; content: string }[] = [];
646
+ while (i < lines.length) {
647
+ const p = parseDiffLine(lines[i]);
648
+ if (!p || p.prefix !== '+') break;
649
+ addedLines.push({ lineNum: p.lineNum, content: p.content });
650
+ i++;
651
+ }
652
+
653
+ // Intra-line diff for single line modification
654
+ if (removedLines.length === 1 && addedLines.length === 1) {
655
+ const removed = removedLines[0];
656
+ const added = addedLines[0];
657
+ const { removedLine, addedLine } = renderIntraLineDiff(replaceTabs(removed.content), replaceTabs(added.content));
658
+ const textColor = TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937';
659
+ result.push(chalk.bgHex(mixWithBackground('#ef4444', 0.25, TERMINAL_BG)).hex(textColor)(`-${removedLine}`));
660
+ result.push(chalk.bgHex(mixWithBackground('#10b981', 0.25, TERMINAL_BG)).hex(textColor)(`+${addedLine}`));
661
+ } else {
662
+ const textColor = TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937';
663
+ for (const removed of removedLines) {
664
+ result.push(chalk.bgHex(mixWithBackground('#ef4444', 0.25, TERMINAL_BG)).hex(textColor)(`-${replaceTabs(removed.content)}`));
665
+ }
666
+ for (const added of addedLines) {
667
+ result.push(chalk.bgHex(mixWithBackground('#10b981', 0.25, TERMINAL_BG)).hex(textColor)(`+${replaceTabs(added.content)}`));
668
+ }
669
+ }
670
+ } else if (parsed.prefix === '+') {
671
+ const textColor = TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937';
672
+ result.push(chalk.bgHex(mixWithBackground('#10b981', 0.25, TERMINAL_BG)).hex(textColor)(`+${replaceTabs(parsed.content)}`));
673
+ i++;
674
+ } else {
675
+ result.push(chalk.hex('#374151')(` ${replaceTabs(parsed.content)}`));
676
+ i++;
677
+ }
678
+ }
679
+
680
+ return result.join('\n');
681
+ }
682
+
683
+ /**
684
+ * Render a code preview with line numbers and syntax highlighting.
685
+ * Automatically adapts to dark/light terminal backgrounds.
686
+ */
687
+ export function renderCodePreview(content: string, options: { filePath?: string; maxLines?: number; indent?: string } = {}): string {
688
+ const { filePath = '', maxLines = 10, indent = '' } = options;
689
+ const lines = content.split('\n');
690
+ const displayLines = lines.slice(0, maxLines);
691
+ const hasMore = lines.length > maxLines;
692
+
693
+ const textColor = TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2931';
694
+ const bgColor = TERMINAL_BG === 'dark' ? '#1f2937' : '#f3f4f6';
695
+ const codeStyle = chalk.bgHex(bgColor).hex(textColor);
696
+
697
+ const lineNumWidth = String(displayLines.length).length;
698
+ const parts: string[] = [];
699
+
700
+ if (filePath) {
701
+ parts.push(`${indent}${chalk.hex(TERMINAL_BG === 'dark' ? '#22d3ee' : '#0891b2').bold(filePath)}`);
702
+ }
703
+
704
+ displayLines.forEach((line, idx) => {
705
+ const lineNum = String(idx + 1).padStart(lineNumWidth, ' ');
706
+ parts.push(`${indent} ${codeStyle(`${lineNum} │ ${line}`)}`);
707
+ });
708
+
709
+ if (hasMore) {
710
+ const dotsLine = `${indent} ${chalk.hex(TERMINAL_BG === 'dark' ? '#6b7280' : '#9ca3af').dim('...')}`;
711
+ parts.push(dotsLine);
712
+ }
713
+
714
+ return parts.join('\n');
715
+ }
716
+
717
+ /**
718
+ * Render new file content in diff-like style (without line numbers).
719
+ * Uses + prefix for added lines.
720
+ */
721
+ export function renderLines(content: string, options: { maxLines?: number; indent?: string } = {}): string {
722
+ const { maxLines = 20, indent = '' } = options;
723
+ const lines = content.split('\n').slice(0, maxLines);
724
+ const hasMore = content.split('\n').length > maxLines;
725
+
726
+ const textColor = TERMINAL_BG === 'dark' ? '#e5e7eb' : '#1f2937';
727
+ const bgColor = mixWithBackground('#10b981', 0.15, TERMINAL_BG);
728
+ const lineStyle = chalk.bgHex(bgColor).hex(textColor);
729
+
730
+ const parts: string[] = lines.map(line => `${indent}${lineStyle(`+ ${line}`)}`);
731
+
732
+ if (hasMore) {
733
+ parts.push(`${indent}${chalk.hex(TERMINAL_BG === 'dark' ? '#6b7280' : '#9ca3af').dim('...')}`);
734
+ }
735
+
736
+ return parts.join('\n');
737
+ }
738
+
455
739
  export default theme;