snow-ai 0.2.19 → 0.2.21

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.
@@ -268,13 +268,15 @@ export class FilesystemMCPService {
268
268
  const end = Math.min(totalLines, endLine);
269
269
  // Extract specified lines (convert to 0-indexed) and add line numbers
270
270
  const selectedLines = lines.slice(start - 1, end);
271
- // Format with line numbers (similar to cat -n)
272
- // Calculate the width needed for line numbers
273
- const maxLineNumWidth = String(end).length;
271
+ // Format with line numbers (no padding to save tokens)
272
+ // Normalize whitespace: tabs single space, multiple spaces → single space
274
273
  const numberedLines = selectedLines.map((line, index) => {
275
274
  const lineNum = start + index;
276
- const paddedLineNum = String(lineNum).padStart(maxLineNumWidth, ' ');
277
- return `${paddedLineNum}→${line}`;
275
+ // Normalize whitespace to reduce token usage
276
+ const normalizedLine = line
277
+ .replace(/\t/g, ' ') // Convert tabs to single space
278
+ .replace(/ +/g, ' '); // Compress multiple spaces to single space
279
+ return `${lineNum}→${normalizedLine}`;
278
280
  });
279
281
  const partialContent = numberedLines.join('\n');
280
282
  return {
@@ -450,23 +452,31 @@ export class FilesystemMCPService {
450
452
  // Read the entire file
451
453
  const content = await fs.readFile(fullPath, 'utf-8');
452
454
  const lines = content.split('\n');
453
- // Normalize search content (handle different line ending styles)
455
+ // Normalize line endings
454
456
  const normalizedSearch = searchContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
455
457
  const normalizedContent = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
456
- // Find all matches
458
+ // Apply whitespace normalization for matching (same as getFileContent output)
459
+ const normalizeWhitespace = (text) => text.replace(/\t/g, ' ').replace(/ +/g, ' ');
460
+ const normalizedSearchForMatch = normalizeWhitespace(normalizedSearch);
461
+ const normalizedContentForMatch = normalizeWhitespace(normalizedContent);
462
+ // Find all matches by comparing normalized versions line by line
463
+ // This avoids complex character position mapping
457
464
  const matches = [];
458
- let searchIndex = 0;
459
- while (true) {
460
- const matchIndex = normalizedContent.indexOf(normalizedSearch, searchIndex);
461
- if (matchIndex === -1)
462
- break;
463
- // Calculate line numbers for this match
464
- const beforeMatch = normalizedContent.substring(0, matchIndex);
465
- const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
466
- const matchLines = (normalizedSearch.match(/\n/g) || []).length;
467
- const endLine = startLine + matchLines;
468
- matches.push({ index: matchIndex, line: startLine, endLine });
469
- searchIndex = matchIndex + normalizedSearch.length;
465
+ const searchLines = normalizedSearchForMatch.split('\n');
466
+ const contentLines = normalizedContentForMatch.split('\n');
467
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
468
+ let isMatch = true;
469
+ for (let j = 0; j < searchLines.length; j++) {
470
+ if (contentLines[i + j] !== searchLines[j]) {
471
+ isMatch = false;
472
+ break;
473
+ }
474
+ }
475
+ if (isMatch) {
476
+ const startLine = i + 1; // Convert to 1-indexed
477
+ const endLine = startLine + searchLines.length - 1;
478
+ matches.push({ startLine, endLine });
479
+ }
470
480
  }
471
481
  // Handle no matches
472
482
  if (matches.length === 0) {
@@ -485,34 +495,33 @@ export class FilesystemMCPService {
485
495
  }
486
496
  else if (occurrence < 1 || occurrence > matches.length) {
487
497
  throw new Error(`Invalid occurrence ${occurrence}. Found ${matches.length} match(es) at lines: ${matches
488
- .map(m => m.line)
498
+ .map(m => m.startLine)
489
499
  .join(', ')}`);
490
500
  }
491
501
  else {
492
502
  selectedMatch = matches[occurrence - 1];
493
503
  }
494
- const { line: startLine, endLine } = selectedMatch;
504
+ const { startLine, endLine } = selectedMatch;
495
505
  // Backup file before editing
496
506
  await incrementalSnapshotManager.backupFile(fullPath);
497
- // Perform the replacement
507
+ // Perform the replacement by replacing the matched lines
498
508
  const normalizedReplace = replaceContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
499
- const beforeContent = normalizedContent.substring(0, selectedMatch.index);
500
- const afterContent = normalizedContent.substring(selectedMatch.index + normalizedSearch.length);
501
- const modifiedContent = beforeContent + normalizedReplace + afterContent;
509
+ const beforeLines = lines.slice(0, startLine - 1);
510
+ const afterLines = lines.slice(endLine);
511
+ const replaceLines = normalizedReplace.split('\n');
512
+ const modifiedLines = [...beforeLines, ...replaceLines, ...afterLines];
513
+ const modifiedContent = modifiedLines.join('\n');
502
514
  // Calculate replaced content for display
503
515
  const replacedLines = lines.slice(startLine - 1, endLine);
504
- const maxLineNumWidth = String(endLine).length;
505
516
  const replacedContent = replacedLines
506
517
  .map((line, idx) => {
507
518
  const lineNum = startLine + idx;
508
- const paddedNum = String(lineNum).padStart(maxLineNumWidth, ' ');
509
- return `${paddedNum}→${line}`;
519
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
520
+ return `${lineNum}→${normalizedLine}`;
510
521
  })
511
522
  .join('\n');
512
523
  // Calculate context boundaries
513
- const modifiedLines = modifiedContent.split('\n');
514
- const replaceLines = normalizedReplace.split('\n');
515
- const lineDifference = replaceLines.length - (endLine - startLine);
524
+ const lineDifference = replaceLines.length - (endLine - startLine + 1);
516
525
  const smartBoundaries = this.findSmartContextBoundaries(lines, startLine, endLine, contextLines);
517
526
  const contextStart = smartBoundaries.start;
518
527
  const contextEnd = smartBoundaries.end;
@@ -521,8 +530,8 @@ export class FilesystemMCPService {
521
530
  const oldContent = oldContextLines
522
531
  .map((line, idx) => {
523
532
  const lineNum = contextStart + idx;
524
- const paddedNum = String(lineNum).padStart(String(contextEnd).length, ' ');
525
- return `${paddedNum}→${line}`;
533
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
534
+ return `${lineNum}→${normalizedLine}`;
526
535
  })
527
536
  .join('\n');
528
537
  // Write the modified content
@@ -556,8 +565,8 @@ export class FilesystemMCPService {
556
565
  const newContextContent = newContextLines
557
566
  .map((line, idx) => {
558
567
  const lineNum = contextStart + idx;
559
- const paddedNum = String(lineNum).padStart(String(finalContextEnd).length, ' ');
560
- return `${paddedNum}→${line}`;
568
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
569
+ return `${lineNum}→${normalizedLine}`;
561
570
  })
562
571
  .join('\n');
563
572
  // Analyze code structure
@@ -588,19 +597,21 @@ export class FilesystemMCPService {
588
597
  contextEndLine: finalContextEnd,
589
598
  totalLines: finalTotalLines,
590
599
  structureAnalysis,
591
- completeOldContent: content,
592
- completeNewContent: finalContent,
593
600
  diagnostics: undefined,
594
601
  };
595
602
  // Add diagnostics if found
596
603
  if (diagnostics.length > 0) {
597
- result.diagnostics = diagnostics;
604
+ // Limit diagnostics to top 10 to avoid excessive token usage
605
+ const limitedDiagnostics = diagnostics.slice(0, 10);
606
+ result.diagnostics = limitedDiagnostics;
598
607
  const errorCount = diagnostics.filter(d => d.severity === 'error').length;
599
608
  const warningCount = diagnostics.filter(d => d.severity === 'warning').length;
600
609
  if (errorCount > 0 || warningCount > 0) {
601
610
  result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)`;
611
+ // Format diagnostics for better readability (limit to first 5 for message display)
602
612
  const formattedDiagnostics = diagnostics
603
613
  .filter(d => d.severity === 'error' || d.severity === 'warning')
614
+ .slice(0, 5)
604
615
  .map(d => {
605
616
  const icon = d.severity === 'error' ? '❌' : '⚠️';
606
617
  const location = `${filePath}:${d.line}:${d.character}`;
@@ -608,6 +619,9 @@ export class FilesystemMCPService {
608
619
  })
609
620
  .join('\n\n');
610
621
  result.message += `\n\n📋 Diagnostic Details:\n${formattedDiagnostics}`;
622
+ if (errorCount + warningCount > 5) {
623
+ result.message += `\n ... and ${errorCount + warningCount - 5} more issue(s)`;
624
+ }
611
625
  result.message += `\n\n ⚡ TIP: Review the errors above and make another edit to fix them`;
612
626
  }
613
627
  }
@@ -700,8 +714,8 @@ export class FilesystemMCPService {
700
714
  const replacedContent = replacedLines
701
715
  .map((line, idx) => {
702
716
  const lineNum = startLine + idx;
703
- const paddedNum = String(lineNum).padStart(String(adjustedEndLine).length, ' ');
704
- return `${paddedNum}→${line}`;
717
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
718
+ return `${lineNum}→${normalizedLine}`;
705
719
  })
706
720
  .join('\n');
707
721
  // Calculate context range using smart boundary detection
@@ -713,8 +727,8 @@ export class FilesystemMCPService {
713
727
  const oldContent = oldContextLines
714
728
  .map((line, idx) => {
715
729
  const lineNum = contextStart + idx;
716
- const paddedNum = String(lineNum).padStart(String(contextEnd).length, ' ');
717
- return `${paddedNum}→${line}`;
730
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
731
+ return `${lineNum}→${normalizedLine}`;
718
732
  })
719
733
  .join('\n');
720
734
  // Replace the specified lines
@@ -731,8 +745,8 @@ export class FilesystemMCPService {
731
745
  const newContextContent = newContextLines
732
746
  .map((line, idx) => {
733
747
  const lineNum = contextStart + idx;
734
- const paddedNum = String(lineNum).padStart(String(newContextEnd).length, ' ');
735
- return `${paddedNum}→${line}`;
748
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
749
+ return `${lineNum}→${normalizedLine}`;
736
750
  })
737
751
  .join('\n');
738
752
  // Write the modified content back to file
@@ -762,8 +776,8 @@ export class FilesystemMCPService {
762
776
  finalContextContent = formattedContextLines
763
777
  .map((line, idx) => {
764
778
  const lineNum = contextStart + idx;
765
- const paddedNum = String(lineNum).padStart(String(finalContextEnd).length, ' ');
766
- return `${paddedNum}→${line}`;
779
+ const normalizedLine = line.replace(/\t/g, ' ').replace(/ +/g, ' ');
780
+ return `${lineNum}→${normalizedLine}`;
767
781
  })
768
782
  .join('\n');
769
783
  }
@@ -785,9 +799,6 @@ export class FilesystemMCPService {
785
799
  catch (error) {
786
800
  // Ignore diagnostics errors, they are optional
787
801
  }
788
- // Prepare complete file contents (without line numbers for diff comparison)
789
- const completeOldContent = lines.join('\n');
790
- const completeNewContent = finalLines.join('\n');
791
802
  const result = {
792
803
  message: `✅ File edited successfully,Please check the edit results and pay attention to code boundary issues to avoid syntax errors caused by missing closed parts: ${filePath}\n` +
793
804
  ` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
@@ -803,20 +814,20 @@ export class FilesystemMCPService {
803
814
  totalLines: finalTotalLines,
804
815
  linesModified: linesToModify,
805
816
  structureAnalysis,
806
- // Add complete file contents for intelligent diff display
807
- completeOldContent,
808
- completeNewContent,
809
817
  };
810
818
  // Add diagnostics if any were found
811
819
  if (diagnostics.length > 0) {
812
- result.diagnostics = diagnostics;
820
+ // Limit diagnostics to top 10 to avoid excessive token usage
821
+ const limitedDiagnostics = diagnostics.slice(0, 10);
822
+ result.diagnostics = limitedDiagnostics;
813
823
  const errorCount = diagnostics.filter(d => d.severity === 'error').length;
814
824
  const warningCount = diagnostics.filter(d => d.severity === 'warning').length;
815
825
  if (errorCount > 0 || warningCount > 0) {
816
826
  result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)`;
817
- // Format diagnostics for better readability
827
+ // Format diagnostics for better readability (limit to first 5 for message display)
818
828
  const formattedDiagnostics = diagnostics
819
829
  .filter(d => d.severity === 'error' || d.severity === 'warning')
830
+ .slice(0, 5)
820
831
  .map(d => {
821
832
  const icon = d.severity === 'error' ? '❌' : '⚠️';
822
833
  const location = `${filePath}:${d.line}:${d.character}`;
@@ -824,6 +835,9 @@ export class FilesystemMCPService {
824
835
  })
825
836
  .join('\n\n');
826
837
  result.message += `\n\n📋 Diagnostic Details:\n${formattedDiagnostics}`;
838
+ if (errorCount + warningCount > 5) {
839
+ result.message += `\n ... and ${errorCount + warningCount - 5} more issue(s)`;
840
+ }
827
841
  result.message += `\n\n ⚡ TIP: Review the errors above and make another small edit to fix them`;
828
842
  }
829
843
  }
@@ -177,7 +177,7 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
177
177
  return (React.createElement(Box, { paddingX: 1, marginTop: 1, flexDirection: "column" },
178
178
  React.createElement(Box, { marginBottom: 1 },
179
179
  React.createElement(Text, { color: "blue", bold: true },
180
- "\uD83D\uDDD0 Files",
180
+ "\u2261 Files",
181
181
  ' ',
182
182
  allFilteredFiles.length > effectiveMaxItems &&
183
183
  `(${selectedIndex + 1}/${allFilteredFiles.length})`)),
@@ -11,8 +11,23 @@ export default function MarkdownRenderer({ content, color }) {
11
11
  block.language && (React.createElement(Text, { backgroundColor: "green", color: "white" }, block.language)),
12
12
  React.createElement(Text, null, highlightCode(block.code, block.language))))));
13
13
  }
14
+ // Render heading
15
+ if (block.type === 'heading') {
16
+ const headingColors = ['cyan', 'blue', 'magenta', 'yellow'];
17
+ const headingColor = headingColors[block.level - 1] || 'white';
18
+ return (React.createElement(Box, { key: index, marginY: 0 },
19
+ React.createElement(Text, { bold: true, color: headingColor }, renderInlineFormatting(block.content))));
20
+ }
21
+ // Render list
22
+ if (block.type === 'list') {
23
+ return (React.createElement(Box, { key: index, flexDirection: "column", marginY: 0 }, block.items.map((item, itemIndex) => (React.createElement(Box, { key: itemIndex },
24
+ React.createElement(Text, { color: "yellow" }, "\u2022 "),
25
+ React.createElement(Text, { color: color }, renderInlineFormatting(item)))))));
26
+ }
14
27
  // Render text with inline formatting
15
- return (React.createElement(Box, { key: index, flexDirection: "column" }, block.content.split('\n').map((line, lineIndex) => (React.createElement(Text, { key: lineIndex, color: color }, line === '' ? ' ' : renderInlineFormatting(line))))));
28
+ return (React.createElement(Box, { key: index, flexDirection: "column" }, block.content
29
+ .split('\n')
30
+ .map((line, lineIndex) => (React.createElement(Text, { key: lineIndex, color: color }, line === '' ? ' ' : renderInlineFormatting(line))))));
16
31
  })));
17
32
  }
18
33
  function parseMarkdown(content) {
@@ -44,11 +59,45 @@ function parseMarkdown(content) {
44
59
  i++; // Skip closing ```
45
60
  continue;
46
61
  }
47
- // Collect text lines until next code block
62
+ // Check for heading (# ## ### ####)
63
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
64
+ if (headingMatch) {
65
+ blocks.push({
66
+ type: 'heading',
67
+ level: headingMatch[1].length,
68
+ content: headingMatch[2].trim(),
69
+ });
70
+ i++;
71
+ continue;
72
+ }
73
+ // Check for list item (* or -)
74
+ const listMatch = line.match(/^[\s]*[*\-]\s+(.+)$/);
75
+ if (listMatch) {
76
+ const listItems = [listMatch[1].trim()];
77
+ i++;
78
+ // Collect consecutive list items
79
+ while (i < lines.length) {
80
+ const currentLine = lines[i] ?? '';
81
+ const nextListMatch = currentLine.match(/^[\s]*[*\-]\s+(.+)$/);
82
+ if (!nextListMatch) {
83
+ break;
84
+ }
85
+ listItems.push(nextListMatch[1].trim());
86
+ i++;
87
+ }
88
+ blocks.push({
89
+ type: 'list',
90
+ items: listItems,
91
+ });
92
+ continue;
93
+ }
94
+ // Collect text lines until next code block, heading, or list
48
95
  const textLines = [];
49
96
  while (i < lines.length) {
50
97
  const currentLine = lines[i] ?? '';
51
- if (currentLine.trim().startsWith('```')) {
98
+ if (currentLine.trim().startsWith('```') ||
99
+ currentLine.match(/^#{1,6}\s+/) ||
100
+ currentLine.match(/^[\s]*[*\-]\s+/)) {
52
101
  break;
53
102
  }
54
103
  textLines.push(currentLine);
@@ -71,18 +120,18 @@ function highlightCode(code, language) {
71
120
  }
72
121
  // Map common language aliases to cli-highlight supported names
73
122
  const languageMap = {
74
- 'js': 'javascript',
75
- 'ts': 'typescript',
76
- 'py': 'python',
77
- 'rb': 'ruby',
78
- 'sh': 'bash',
79
- 'shell': 'bash',
80
- 'cs': 'csharp',
123
+ js: 'javascript',
124
+ ts: 'typescript',
125
+ py: 'python',
126
+ rb: 'ruby',
127
+ sh: 'bash',
128
+ shell: 'bash',
129
+ cs: 'csharp',
81
130
  'c#': 'csharp',
82
- 'cpp': 'cpp',
131
+ cpp: 'cpp',
83
132
  'c++': 'cpp',
84
- 'yml': 'yaml',
85
- 'md': 'markdown',
133
+ yml: 'yaml',
134
+ md: 'markdown',
86
135
  };
87
136
  const mappedLanguage = languageMap[language.toLowerCase()] || language.toLowerCase();
88
137
  return highlight(code, { language: mappedLanguage, ignoreIllegals: true });
@@ -93,16 +142,16 @@ function highlightCode(code, language) {
93
142
  }
94
143
  }
95
144
  function renderInlineFormatting(text) {
96
- // Handle inline code `code`
145
+ // Handle inline code `code` - remove backticks
97
146
  text = text.replace(/`([^`]+)`/g, (_, code) => {
98
147
  // Use ANSI codes for inline code styling
99
148
  return `\x1b[36m${code}\x1b[0m`;
100
149
  });
101
- // Handle bold **text** or __text__
150
+ // Handle bold **text** or __text__ - remove markers
102
151
  text = text.replace(/(\*\*|__)([^*_]+)\1/g, (_, __, content) => {
103
152
  return `\x1b[1m${content}\x1b[0m`;
104
153
  });
105
- // Handle italic *text* or _text_ (but not part of bold)
154
+ // Handle italic *text* or _text_ (but not part of bold) - remove markers
106
155
  text = text.replace(/(?<!\*)(\*)(?!\*)([^*]+)\1(?!\*)/g, (_, __, content) => {
107
156
  return `\x1b[3m${content}\x1b[0m`;
108
157
  });
@@ -24,6 +24,7 @@ import { useSnapshotState } from '../../hooks/useSnapshotState.js';
24
24
  import { useStreamingState } from '../../hooks/useStreamingState.js';
25
25
  import { useCommandHandler } from '../../hooks/useCommandHandler.js';
26
26
  import { parseAndValidateFileReferences, createMessageWithFileInstructions, getSystemInfo, } from '../../utils/fileUtils.js';
27
+ import { executeCommand } from '../../utils/commandExecutor.js';
27
28
  import { convertSessionMessagesToUI } from '../../utils/sessionConverter.js';
28
29
  import { incrementalSnapshotManager } from '../../utils/incrementalSnapshot.js';
29
30
  import { formatElapsedTime } from '../../utils/textUtils.js';
@@ -41,6 +42,7 @@ export default function ChatScreen({}) {
41
42
  const [currentTodos, setCurrentTodos] = useState([]);
42
43
  const [pendingMessages, setPendingMessages] = useState([]);
43
44
  const pendingMessagesRef = useRef([]);
45
+ const hasAttemptedAutoVscodeConnect = useRef(false);
44
46
  const [remountKey, setRemountKey] = useState(0);
45
47
  const [showMcpInfo, setShowMcpInfo] = useState(false);
46
48
  const [mcpPanelKey, setMcpPanelKey] = useState(0);
@@ -105,6 +107,31 @@ export default function ChatScreen({}) {
105
107
  setVscodeConnectionStatus: vscodeState.setVscodeConnectionStatus,
106
108
  processMessage: (message, images, useBasicModel, hideUserMessage) => processMessageRef.current?.(message, images, useBasicModel, hideUserMessage) || Promise.resolve(),
107
109
  });
110
+ useEffect(() => {
111
+ if (hasAttemptedAutoVscodeConnect.current) {
112
+ return;
113
+ }
114
+ if (vscodeState.vscodeConnectionStatus !== 'disconnected') {
115
+ hasAttemptedAutoVscodeConnect.current = true;
116
+ return;
117
+ }
118
+ hasAttemptedAutoVscodeConnect.current = true;
119
+ (async () => {
120
+ try {
121
+ const result = await executeCommand('ide');
122
+ await handleCommandExecution('ide', result);
123
+ }
124
+ catch (error) {
125
+ console.error('Failed to auto-connect VSCode:', error);
126
+ await handleCommandExecution('ide', {
127
+ success: false,
128
+ message: error instanceof Error
129
+ ? error.message
130
+ : 'Failed to start VSCode connection',
131
+ });
132
+ }
133
+ })();
134
+ }, [handleCommandExecution, vscodeState.vscodeConnectionStatus]);
108
135
  // Pending messages are now handled inline during tool execution in useConversation
109
136
  // Auto-send pending messages when streaming completely stops (as fallback)
110
137
  useEffect(() => {
@@ -142,7 +169,9 @@ export default function ChatScreen({}) {
142
169
  }
143
170
  return;
144
171
  }
145
- if (key.escape && streamingState.isStreaming && streamingState.abortController) {
172
+ if (key.escape &&
173
+ streamingState.isStreaming &&
174
+ streamingState.abortController) {
146
175
  // Abort the controller
147
176
  streamingState.abortController.abort();
148
177
  // Add discontinued message
@@ -427,6 +456,7 @@ export default function ChatScreen({}) {
427
456
  React.createElement(Text, { color: "white" }, " \u26C7")),
428
457
  React.createElement(Text, null, "\u2022 Ask for code explanations and debugging help"),
429
458
  React.createElement(Text, null, "\u2022 Press ESC during response to interrupt"),
459
+ React.createElement(Text, null, "\u2022 Press Shift+Tab: toggle YOLO"),
430
460
  React.createElement(Text, null,
431
461
  "\u2022 Working directory: ",
432
462
  workingDirectory)))),
@@ -493,12 +523,17 @@ export default function ChatScreen({}) {
493
523
  message.toolCall.name === 'filesystem-edit' &&
494
524
  message.toolCall.arguments.oldContent &&
495
525
  message.toolCall.arguments.newContent && (React.createElement(Box, { marginTop: 1 },
496
- React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments.completeOldContent, completeNewContent: message.toolCall.arguments.completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
526
+ React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
527
+ .completeOldContent, completeNewContent: message.toolCall.arguments
528
+ .completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
497
529
  message.toolCall &&
498
- message.toolCall.name === 'filesystem-edit_search' &&
530
+ message.toolCall.name ===
531
+ 'filesystem-edit_search' &&
499
532
  message.toolCall.arguments.oldContent &&
500
533
  message.toolCall.arguments.newContent && (React.createElement(Box, { marginTop: 1 },
501
- React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments.completeOldContent, completeNewContent: message.toolCall.arguments.completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
534
+ React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename, completeOldContent: message.toolCall.arguments
535
+ .completeOldContent, completeNewContent: message.toolCall.arguments
536
+ .completeNewContent, startLineNumber: message.toolCall.arguments.contextStartLine }))),
502
537
  message.toolCall &&
503
538
  message.toolCall.name === 'terminal-execute' &&
504
539
  message.toolCall.arguments.command && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
@@ -580,24 +615,31 @@ export default function ChatScreen({}) {
580
615
  React.createElement(Spinner, { type: "dots" }))))))),
581
616
  (streamingState.isStreaming || isSaving) && !pendingToolConfirmation && (React.createElement(Box, { marginBottom: 1, paddingX: 1, width: "100%" },
582
617
  React.createElement(Text, { color: ['#FF6EBF', 'green', 'blue', 'cyan', '#B588F8'][streamingState.animationFrame], bold: true }, "\u2746"),
583
- React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, streamingState.isStreaming ? (React.createElement(React.Fragment, null, streamingState.retryStatus && streamingState.retryStatus.isRetrying ? (
618
+ React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, streamingState.isStreaming ? (React.createElement(React.Fragment, null, streamingState.retryStatus &&
619
+ streamingState.retryStatus.isRetrying ? (
584
620
  // Retry status display - hide "Thinking" and show retry info
585
621
  React.createElement(Box, { flexDirection: "column" },
586
622
  streamingState.retryStatus.errorMessage && (React.createElement(Text, { color: "red", dimColor: true },
587
623
  "\u2717 Error: ",
588
624
  streamingState.retryStatus.errorMessage)),
589
- streamingState.retryStatus.remainingSeconds !== undefined && streamingState.retryStatus.remainingSeconds > 0 ? (React.createElement(Text, { color: "yellow", dimColor: true },
625
+ streamingState.retryStatus.remainingSeconds !==
626
+ undefined &&
627
+ streamingState.retryStatus.remainingSeconds > 0 ? (React.createElement(Text, { color: "yellow", dimColor: true },
590
628
  "\u27F3 Retry ",
591
629
  streamingState.retryStatus.attempt,
592
- "/5 in ",
630
+ "/5 in",
631
+ ' ',
593
632
  streamingState.retryStatus.remainingSeconds,
594
633
  "s...")) : (React.createElement(Text, { color: "yellow", dimColor: true },
595
- "\u27F3 Resending... (Attempt ",
634
+ "\u27F3 Resending... (Attempt",
635
+ ' ',
596
636
  streamingState.retryStatus.attempt,
597
637
  "/5)")))) : (
598
638
  // Normal thinking status
599
639
  React.createElement(Text, { color: "gray", dimColor: true },
600
- React.createElement(ShimmerText, { text: streamingState.isReasoning ? 'Deep thinking...' : 'Thinking...' }),
640
+ React.createElement(ShimmerText, { text: streamingState.isReasoning
641
+ ? 'Deep thinking...'
642
+ : 'Thinking...' }),
601
643
  ' ',
602
644
  "(",
603
645
  formatElapsedTime(streamingState.elapsedSeconds),
@@ -649,12 +691,12 @@ export default function ChatScreen({}) {
649
691
  "\u25CF",
650
692
  ' ',
651
693
  vscodeState.vscodeConnectionStatus === 'connecting'
652
- ? 'Waiting for VSCode extension to connect...'
694
+ ? 'Connecting to IDE...'
653
695
  : vscodeState.vscodeConnectionStatus === 'connected'
654
- ? 'VSCode Connected'
696
+ ? 'IDE Connected'
655
697
  : vscodeState.vscodeConnectionStatus === 'error'
656
- ? 'Connection Failed - Make sure Snow CLI extension is installed and active in VSCode'
657
- : 'VSCode',
698
+ ? 'Connection Failed - Make sure Snow CLI plugin is installed and active in your IDE'
699
+ : 'IDE',
658
700
  vscodeState.vscodeConnectionStatus === 'connected' &&
659
701
  vscodeState.editorContext.activeFile &&
660
702
  ` | ${vscodeState.editorContext.activeFile}`,
@@ -3,6 +3,7 @@ export interface CommandResult {
3
3
  message?: string;
4
4
  action?: 'clear' | 'resume' | 'info' | 'showMcpInfo' | 'goHome' | 'toggleYolo' | 'initProject' | 'compact' | 'showSessionPanel' | 'showMcpPanel';
5
5
  prompt?: string;
6
+ alreadyConnected?: boolean;
6
7
  }
7
8
  export interface CommandHandler {
8
9
  execute: () => Promise<CommandResult> | CommandResult;
@@ -3,42 +3,30 @@ import { vscodeConnection } from '../vscodeConnection.js';
3
3
  // IDE connection command handler
4
4
  registerCommand('ide', {
5
5
  execute: async () => {
6
- // Check if already connected
6
+ // Check if already connected to IDE plugin
7
7
  if (vscodeConnection.isConnected()) {
8
8
  return {
9
9
  success: true,
10
10
  action: 'info',
11
- message: 'Already connected to VSCode editor'
11
+ alreadyConnected: true,
12
+ message: `Already connected to IDE (port ${vscodeConnection.getPort()})`
12
13
  };
13
14
  }
14
- // Check if server is already running (but not connected yet)
15
- if (vscodeConnection.isServerRunning()) {
16
- return {
17
- success: true,
18
- action: 'info',
19
- message: `VSCode connection server is already running on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
20
- };
21
- }
22
- // Start the server
15
+ // Try to connect to IDE plugin server
23
16
  try {
24
17
  await vscodeConnection.start();
25
18
  return {
26
19
  success: true,
27
20
  action: 'info',
28
- message: `VSCode connection server started on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
21
+ message: `Connected to IDE on port ${vscodeConnection.getPort()}\nMake sure your IDE plugin (VSCode/JetBrains) is active and running.`
29
22
  };
30
23
  }
31
24
  catch (error) {
32
- // Handle EADDRINUSE error specifically
33
- if (error instanceof Error && 'code' in error && error.code === 'EADDRINUSE') {
34
- return {
35
- success: false,
36
- message: `Port ${vscodeConnection.getPort()} is already in use. Please restart Snow CLI to reset the connection.`
37
- };
38
- }
39
25
  return {
40
26
  success: false,
41
- message: error instanceof Error ? error.message : 'Failed to start IDE connection'
27
+ message: error instanceof Error
28
+ ? `Failed to connect to IDE: ${error.message}\nMake sure your IDE plugin is installed and active.`
29
+ : 'Failed to connect to IDE. Make sure your IDE plugin is installed and active.'
42
30
  };
43
31
  }
44
32
  }