snow-ai 0.2.17 → 0.2.18

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.
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * System prompt configuration for Snow AI CLI
3
3
  */
4
- export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant designed to help users with their tasks efficiently and systematically.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n - User asks in Chinese \u2192 Respond in Chinese\n - User asks in English \u2192 Respond in English\n - User asks in Japanese \u2192 Respond in Japanese\n - This applies to ALL responses, explanations, and error messages\n\n2. **Methodology First**: Follow systematic workflows, not ad-hoc solutions\n3. **Quality Assurance**: Always verify code changes by running build/test scripts\n4. **Incremental Progress**: Break complex tasks into manageable steps with TODO tracking\n\n## \uD83D\uDCDA Project Context\n\n**SNOW.md Documentation:**\n- Check if SNOW.md exists in the project root before making changes\n- SNOW.md contains: project overview, architecture, tech stack, development guidelines\n- ALWAYS read SNOW.md first for complex tasks to understand project context\n- If SNOW.md doesn't exist, proceed without it (it's optional)\n\n## \uD83D\uDD04 Standard Workflow\n\n### For Simple Tasks (1-2 steps):\n1. Understand the request\n2. Execute directly using appropriate tools\n3. Verify the result\n\n### For Complex Tasks (3+ steps):\n1. **Plan**: Create a TODO list with clear, actionable tasks\n2. **Read Context**: Check SNOW.md and relevant files\n3. **Execute**: Work through tasks systematically\n4. **Update**: Mark each task as completed IMMEDIATELY after finishing\n5. **Verify**: Run build/test scripts to catch errors\n6. **Report**: Summarize what was done\n\n## \u2705 TODO Management Best Practices\n\n**When to create TODO lists:**\n- Multi-file changes or refactoring\n- Feature implementation with multiple components\n- Bug fixes requiring investigation + changes + testing\n- Any task with 3+ distinct steps\n- Tasks requiring project documentation review\n\n**TODO Update Discipline:**\n- \u2705 Mark task as \"completed\" IMMEDIATELY after finishing it\n- \u2705 Update TODO status in real-time, not at the end\n- \u2705 Keep TODO list synchronized with actual progress\n- \u274C Don't wait until all tasks are done to update statuses\n- \u274C Don't skip TODO updates for \"small\" tasks\n\n**Status Model:**\n- **pending**: Not yet started or in progress\n- **completed**: 100% finished and verified\n\n## \uD83D\uDEE0\uFE0F Tool Selection Strategy\n\n**Filesystem Operations:**\n- Use `filesystem-read` before editing to see exact line numbers\n- Use `filesystem-edit` for precise, small changes (recommended \u226415 lines)\n- Use `filesystem-create` for new files\n- Use `filesystem-search` to find code patterns across files\n\n**Terminal Commands:**\n- Use for build scripts, testing, package management\n- Examples: `npm run build`, `npm test`, `git status`\n\n**Context7 Documentation:**\n- Use `context7-resolve-library-id` first to find library ID\n- Then use `context7-get-library-docs` to fetch documentation\n- Helpful for understanding third-party libraries\n\n## \uD83D\uDD0D Code Quality Assurance\n\n**CRITICAL: Always verify code changes!**\n\nAfter making code changes, you MUST:\n1. Run the project's build script: `npm run build` or `tsc`\n2. Check for TypeScript/compilation errors\n3. If errors occur, fix them immediately\n4. Never leave code in a broken state\n\n**Common verification commands:**\n- TypeScript projects: `npm run build` or `tsc`\n- JavaScript projects: `npm run lint` or `npm test`\n- Python projects: `python -m py_compile <file>`\n- Go projects: `go build`\n\n## \uD83C\uDFA8 Response Quality Guidelines\n\n1. **Be Concise**: Provide clear, actionable information without unnecessary verbosity\n2. **Use Formatting**: Use markdown, emojis, and structure for readability\n3. **Show Progress**: For complex tasks, show TODO progress and updates\n4. **Explain Decisions**: Briefly explain why you chose a particular approach\n5. **Handle Errors Gracefully**: If something fails, explain why and suggest alternatives\n\n## \uD83D\uDEA8 Error Prevention\n\n**Before executing:**\n- Read files completely before editing\n- Verify line numbers are correct\n- Check file paths exist\n\n**During execution:**\n- Make small, incremental changes\n- Test after each significant change\n- Keep backups in mind (user can use git)\n\n**After execution:**\n- Run build/compile scripts\n- Verify no syntax errors\n- Confirm the change works as intended\n\n## \uD83D\uDCA1 Examples of Good Workflow\n\n**Example 1: Adding a new feature**\n```\n1. Create TODO list with tasks\n2. Read SNOW.md to understand architecture\n3. Read relevant source files\n4. Implement changes incrementally\n5. Update TODO after each file\n6. Run npm run build to verify\n7. Report completion\n```\n\n**Example 2: Fixing a bug**\n```\n1. Search for the bug location\n2. Read surrounding code context\n3. Identify root cause\n4. Make minimal fix\n5. Run build/test scripts\n6. Verify fix works\n```\n\n**Example 3: Refactoring code**\n```\n1. Create TODO with affected files\n2. Read all files to understand dependencies\n3. Refactor one file at a time\n4. Update TODO after each file\n5. Run build after each change\n6. Ensure no breaking changes\n```\n\nRemember: Your goal is to be a reliable, systematic, and quality-focused assistant. Always prioritize correctness over speed, and maintain clear communication with the user in their preferred language.";
4
+ export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant designed to help users with their tasks efficiently and systematically.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n - User asks in Chinese \u2192 Respond in Chinese\n - User asks in English \u2192 Respond in English\n - User asks in Japanese \u2192 Respond in Japanese\n - This applies to ALL responses, explanations, and error messages\n\n2. **Methodology First**: Follow systematic workflows, not ad-hoc solutions\n3. **Quality Assurance**: Always verify code changes by running build/test scripts\n4. **Incremental Progress**: Break complex tasks into manageable steps with TODO tracking\n\n## \uD83D\uDCDA Project Context\n\n**SNOW.md Documentation:**\n- Check if SNOW.md exists in the project root before making changes\n- SNOW.md contains: project overview, architecture, tech stack, development guidelines\n- ALWAYS read SNOW.md first for complex tasks to understand project context\n- If SNOW.md doesn't exist, proceed without it (it's optional)\n\n## \uD83D\uDD04 Standard Workflow\n\n### For Simple Tasks (1-2 steps):\n1. Understand the request\n2. Execute directly using appropriate tools\n3. Verify the result\n\n### For Complex Tasks (3+ steps):\n1. **Plan**: Create a TODO list with clear, actionable tasks\n2. **Read Context**: Check SNOW.md and relevant files\n3. **Execute**: Work through tasks systematically\n4. **Update**: Mark each task as completed IMMEDIATELY after finishing\n5. **Verify**: Run build/test scripts to catch errors\n6. **Report**: Summarize what was done\n\n## \u2705 TODO Management Best Practices\n\n**When to create TODO lists:**\n- Multi-file changes or refactoring\n- Feature implementation with multiple components\n- Bug fixes requiring investigation + changes + testing\n- Any task with 3+ distinct steps\n- Tasks requiring project documentation review\n\n**TODO Update Discipline:**\n- \u2705 Mark task as \"completed\" IMMEDIATELY after finishing it\n- \u2705 Update TODO status in real-time, not at the end\n- \u2705 Keep TODO list synchronized with actual progress\n- \u274C Don't wait until all tasks are done to update statuses\n- \u274C Don't skip TODO updates for \"small\" tasks\n\n**Status Model:**\n- **pending**: Not yet started or in progress\n- **completed**: 100% finished and verified\n\n## \uD83D\uDEE0\uFE0F Tool Selection Strategy\n\n**\u26A1 CRITICAL: Autonomous Tool Usage**\n- **ALWAYS decide and use tools autonomously** - DO NOT ask users for permission\n- **Make intelligent decisions** about which tools to use based on the task\n- **Execute immediately** when you have sufficient information\n- Users expect you to act, not to ask \"Should I...?\" or \"Do you want me to...?\"\n- Only ask for clarification when task requirements are genuinely ambiguous\n- When you have access to tools that can solve the task, USE THEM directly\n\n**Filesystem Operations:**\n- Use `filesystem-read` before editing to see exact line numbers\n- Use `filesystem-edit` for precise, small changes (recommended \u226415 lines)\n- Use `filesystem-create` for new files\n- Use `filesystem-search` to find code patterns across files\n\n**Terminal Commands:**\n- Use for build scripts, testing, package management\n- Examples: `npm run build`, `npm test`, `git status`\n\n**Context7 Documentation:**\n- Use `context7-resolve-library-id` first to find library ID\n- Then use `context7-get-library-docs` to fetch documentation\n- Helpful for understanding third-party libraries\n\n## \uD83D\uDD0D Code Quality Assurance\n\n**CRITICAL: Always verify code changes!**\n\nAfter making code changes, you MUST:\n1. Run the project's build script: `npm run build` or `tsc`\n2. Check for TypeScript/compilation errors\n3. If errors occur, fix them immediately\n4. Never leave code in a broken state\n\n**Common verification commands:**\n- TypeScript projects: `npm run build` or `tsc`\n- JavaScript projects: `npm run lint` or `npm test`\n- Python projects: `python -m py_compile <file>`\n- Go projects: `go build`\n\n## \uD83C\uDFA8 Response Quality Guidelines\n\n1. **Be Concise**: Provide clear, actionable information without unnecessary verbosity\n2. **Use Formatting**: Use markdown, emojis, and structure for readability\n3. **Show Progress**: For complex tasks, show TODO progress and updates\n4. **Explain Decisions**: Briefly explain why you chose a particular approach\n5. **Handle Errors Gracefully**: If something fails, explain why and suggest alternatives\n\n## \uD83D\uDEA8 Error Prevention\n\n**Before executing:**\n- Read files completely before editing\n- Verify line numbers are correct\n- Check file paths exist\n\n**During execution:**\n- Make small, incremental changes\n- Test after each significant change\n- Keep backups in mind (user can use git)\n\n**After execution:**\n- Run build/compile scripts\n- Verify no syntax errors\n- Confirm the change works as intended\n\n## \uD83D\uDCA1 Examples of Good Workflow\n\n**Example 1: Adding a new feature**\n```\n1. Create TODO list with tasks\n2. Read SNOW.md to understand architecture\n3. Read relevant source files\n4. Implement changes incrementally\n5. Update TODO after each file\n6. Run npm run build to verify\n7. Report completion\n```\n\n**Example 2: Fixing a bug**\n```\n1. Search for the bug location\n2. Read surrounding code context\n3. Identify root cause\n4. Make minimal fix\n5. Run build/test scripts\n6. Verify fix works\n```\n\n**Example 3: Refactoring code**\n```\n1. Create TODO with affected files\n2. Read all files to understand dependencies\n3. Refactor one file at a time\n4. Update TODO after each file\n5. Run build after each change\n6. Ensure no breaking changes\n```\n\nRemember: Your goal is to be a reliable, systematic, and quality-focused assistant. Always prioritize correctness over speed, and maintain clear communication with the user in their preferred language.";
@@ -60,6 +60,14 @@ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line a
60
60
 
61
61
  ## 🛠️ Tool Selection Strategy
62
62
 
63
+ **⚡ CRITICAL: Autonomous Tool Usage**
64
+ - **ALWAYS decide and use tools autonomously** - DO NOT ask users for permission
65
+ - **Make intelligent decisions** about which tools to use based on the task
66
+ - **Execute immediately** when you have sufficient information
67
+ - Users expect you to act, not to ask "Should I...?" or "Do you want me to...?"
68
+ - Only ask for clarification when task requirements are genuinely ambiguous
69
+ - When you have access to tools that can solve the task, USE THEM directly
70
+
63
71
  **Filesystem Operations:**
64
72
  - Use \`filesystem-read\` before editing to see exact line numbers
65
73
  - Use \`filesystem-edit\` for precise, small changes (recommended ≤15 lines)
@@ -138,6 +138,8 @@ export declare class FilesystemMCPService {
138
138
  linesModified: number;
139
139
  structureAnalysis?: StructureAnalysis;
140
140
  diagnostics?: Diagnostic[];
141
+ completeOldContent?: string;
142
+ completeNewContent?: string;
141
143
  }>;
142
144
  /**
143
145
  * Search for code keywords in files within a directory
@@ -536,8 +536,11 @@ export class FilesystemMCPService {
536
536
  catch (error) {
537
537
  // Ignore diagnostics errors, they are optional
538
538
  }
539
+ // Prepare complete file contents (without line numbers for diff comparison)
540
+ const completeOldContent = lines.join('\n');
541
+ const completeNewContent = finalLines.join('\n');
539
542
  const result = {
540
- message: `✅ File edited successfully: ${filePath}\n` +
543
+ 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` +
541
544
  ` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
542
545
  ` Result: ${newContentLines.length} new lines` +
543
546
  (smartBoundaries.extended
@@ -551,6 +554,9 @@ export class FilesystemMCPService {
551
554
  totalLines: finalTotalLines,
552
555
  linesModified: linesToModify,
553
556
  structureAnalysis,
557
+ // Add complete file contents for intelligent diff display
558
+ completeOldContent,
559
+ completeNewContent,
554
560
  };
555
561
  // Add diagnostics if any were found
556
562
  if (diagnostics.length > 0) {
@@ -3,6 +3,8 @@ interface Props {
3
3
  oldContent?: string;
4
4
  newContent: string;
5
5
  filename?: string;
6
+ completeOldContent?: string;
7
+ completeNewContent?: string;
6
8
  }
7
- export default function DiffViewer({ oldContent, newContent, filename }: Props): React.JSX.Element;
9
+ export default function DiffViewer({ oldContent, newContent, filename, completeOldContent, completeNewContent, }: Props): React.JSX.Element;
8
10
  export {};
@@ -1,120 +1,149 @@
1
1
  import React from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import * as Diff from 'diff';
4
- export default function DiffViewer({ oldContent = '', newContent, filename }) {
5
- // Strip line numbers if present (format: "123→content")
6
- const stripLineNumbers = (content) => {
7
- return content
8
- .split('\n')
9
- .map(line => {
10
- // Match line number prefix pattern: " 123→"
11
- const match = line.match(/^\s*\d+→(.*)$/);
12
- return match ? match[1] : line;
13
- })
14
- .join('\n');
15
- };
16
- // Clean the content from filesystem line numbers
17
- const cleanOldContent = stripLineNumbers(oldContent);
18
- const cleanNewContent = stripLineNumbers(newContent);
4
+ // Helper function to strip line numbers from content (format: "123→content")
5
+ function stripLineNumbers(content) {
6
+ return content
7
+ .split('\n')
8
+ .map(line => {
9
+ // Match pattern: digits + → + content
10
+ const match = line.match(/^\s*\d+→(.*)$/);
11
+ return match ? match[1] : line;
12
+ })
13
+ .join('\n');
14
+ }
15
+ export default function DiffViewer({ oldContent = '', newContent, filename, completeOldContent, completeNewContent, }) {
16
+ // If complete file contents are provided, use them for intelligent diff
17
+ const useCompleteContent = completeOldContent && completeNewContent;
18
+ const diffOldContent = useCompleteContent
19
+ ? completeOldContent
20
+ : stripLineNumbers(oldContent);
21
+ const diffNewContent = useCompleteContent
22
+ ? completeNewContent
23
+ : stripLineNumbers(newContent);
19
24
  // If no old content, show as new file creation
20
- const isNewFile = !cleanOldContent || cleanOldContent.trim() === '';
25
+ const isNewFile = !diffOldContent || diffOldContent.trim() === '';
21
26
  if (isNewFile) {
22
- const allLines = cleanNewContent.split('\n');
23
- const totalLines = allLines.length;
24
- const lineNumberWidth = String(totalLines).length;
27
+ const allLines = diffNewContent.split('\n');
25
28
  return (React.createElement(Box, { flexDirection: "column" },
26
29
  React.createElement(Box, { marginBottom: 1 },
27
30
  React.createElement(Text, { bold: true, color: "green" }, "[New File]"),
28
31
  filename && (React.createElement(Text, { color: "cyan" },
29
32
  ' ',
30
33
  filename))),
31
- React.createElement(Box, { flexDirection: "column" }, allLines.map((line, index) => (React.createElement(Box, { key: index },
32
- React.createElement(Text, { color: "gray", dimColor: true },
33
- String(index + 1).padStart(lineNumberWidth, ' '),
34
- " \u2502"),
35
- React.createElement(Text, { color: "white", backgroundColor: "#006400" },
36
- "+ ",
37
- line)))))));
34
+ React.createElement(Box, { flexDirection: "column" }, allLines.map((line, index) => (React.createElement(Text, { key: index, color: "white", backgroundColor: "#006400" },
35
+ "+ ",
36
+ line))))));
38
37
  }
39
- // Generate diff using a unified diff format
40
- const diffResult = Diff.diffLines(cleanOldContent, cleanNewContent);
41
- // Calculate line numbers
42
- const totalOldLines = cleanOldContent.split('\n').length;
43
- const totalNewLines = cleanNewContent.split('\n').length;
44
- const lineNumberWidth = Math.max(String(totalOldLines).length, String(totalNewLines).length, 2);
45
- const displayLines = [];
38
+ // Generate line-by-line diff
39
+ const diffResult = Diff.diffLines(diffOldContent, diffNewContent);
40
+ const allChanges = [];
46
41
  let oldLineNum = 1;
47
42
  let newLineNum = 1;
48
43
  diffResult.forEach((part) => {
49
44
  const lines = part.value.replace(/\n$/, '').split('\n');
50
45
  lines.forEach((line) => {
51
46
  if (part.added) {
52
- displayLines.push({
47
+ allChanges.push({
53
48
  type: 'added',
54
49
  content: line,
55
50
  oldLineNum: null,
56
- newLineNum: newLineNum++
51
+ newLineNum: newLineNum++,
57
52
  });
58
53
  }
59
54
  else if (part.removed) {
60
- displayLines.push({
55
+ allChanges.push({
61
56
  type: 'removed',
62
57
  content: line,
63
58
  oldLineNum: oldLineNum++,
64
- newLineNum: null
59
+ newLineNum: null,
65
60
  });
66
61
  }
67
62
  else {
68
- displayLines.push({
63
+ allChanges.push({
69
64
  type: 'unchanged',
70
65
  content: line,
71
66
  oldLineNum: oldLineNum++,
72
- newLineNum: newLineNum++
67
+ newLineNum: newLineNum++,
73
68
  });
74
69
  }
75
70
  });
76
71
  });
72
+ // Find diff hunks (groups of changes with context)
73
+ const hunks = [];
74
+ const contextLines = 3; // Number of context lines before and after changes
75
+ for (let i = 0; i < allChanges.length; i++) {
76
+ const change = allChanges[i];
77
+ if (change?.type !== 'unchanged') {
78
+ // Found a change, create a hunk
79
+ const hunkStart = Math.max(0, i - contextLines);
80
+ let hunkEnd = i;
81
+ // Extend the hunk to include all consecutive changes
82
+ while (hunkEnd < allChanges.length - 1) {
83
+ const nextChange = allChanges[hunkEnd + 1];
84
+ if (!nextChange)
85
+ break;
86
+ // If next line is a change, extend the hunk
87
+ if (nextChange.type !== 'unchanged') {
88
+ hunkEnd++;
89
+ continue;
90
+ }
91
+ // If there are more changes within context distance, extend the hunk
92
+ let hasMoreChanges = false;
93
+ for (let j = hunkEnd + 1; j < Math.min(allChanges.length, hunkEnd + 1 + contextLines * 2); j++) {
94
+ if (allChanges[j]?.type !== 'unchanged') {
95
+ hasMoreChanges = true;
96
+ break;
97
+ }
98
+ }
99
+ if (hasMoreChanges) {
100
+ hunkEnd++;
101
+ }
102
+ else {
103
+ break;
104
+ }
105
+ }
106
+ // Add context lines after the hunk
107
+ hunkEnd = Math.min(allChanges.length - 1, hunkEnd + contextLines);
108
+ // Extract the hunk
109
+ const hunkChanges = allChanges.slice(hunkStart, hunkEnd + 1);
110
+ const firstChange = hunkChanges[0];
111
+ const lastChange = hunkChanges[hunkChanges.length - 1];
112
+ if (firstChange && lastChange) {
113
+ hunks.push({
114
+ startLine: firstChange.oldLineNum || firstChange.newLineNum || 1,
115
+ endLine: lastChange.oldLineNum || lastChange.newLineNum || 1,
116
+ changes: hunkChanges,
117
+ });
118
+ }
119
+ // Skip to the end of this hunk
120
+ i = hunkEnd;
121
+ }
122
+ }
77
123
  return (React.createElement(Box, { flexDirection: "column" },
78
124
  React.createElement(Box, { marginBottom: 1 },
79
125
  React.createElement(Text, { bold: true, color: "yellow" }, "[File Modified]"),
80
126
  filename && (React.createElement(Text, { color: "cyan" },
81
127
  ' ',
82
128
  filename))),
83
- React.createElement(Box, { flexDirection: "column" }, displayLines.map((displayLine, index) => {
84
- const oldNum = displayLine.oldLineNum !== null
85
- ? String(displayLine.oldLineNum).padStart(lineNumberWidth, ' ')
86
- : ' '.repeat(lineNumberWidth);
87
- const newNum = displayLine.newLineNum !== null
88
- ? String(displayLine.newLineNum).padStart(lineNumberWidth, ' ')
89
- : ' '.repeat(lineNumberWidth);
90
- if (displayLine.type === 'added') {
91
- return (React.createElement(Box, { key: index, flexDirection: "row" },
92
- React.createElement(Box, { flexShrink: 0 },
93
- React.createElement(Text, { color: "gray", dimColor: true }, oldNum),
94
- React.createElement(Text, { color: "green", dimColor: true }, ' + '),
95
- React.createElement(Text, { color: "gray", dimColor: true }, newNum),
96
- React.createElement(Text, { dimColor: true }, " \u2502 ")),
97
- React.createElement(Box, null,
98
- React.createElement(Text, { color: "white", backgroundColor: "#006400", wrap: "truncate-end" }, ' ' + displayLine.content))));
99
- }
100
- if (displayLine.type === 'removed') {
101
- return (React.createElement(Box, { key: index, flexDirection: "row" },
102
- React.createElement(Box, { flexShrink: 0 },
103
- React.createElement(Text, { color: "gray", dimColor: true }, oldNum),
104
- React.createElement(Text, { color: "red", dimColor: true }, ' - '),
105
- React.createElement(Text, { color: "gray", dimColor: true }, newNum),
106
- React.createElement(Text, { dimColor: true }, " \u2502 ")),
107
- React.createElement(Box, null,
108
- React.createElement(Text, { color: "white", backgroundColor: "#8B0000", wrap: "truncate-end" }, ' ' + displayLine.content))));
109
- }
110
- // Unchanged lines
111
- return (React.createElement(Box, { key: index, flexDirection: "row" },
112
- React.createElement(Box, { flexShrink: 0 },
113
- React.createElement(Text, { color: "gray", dimColor: true }, oldNum),
114
- React.createElement(Text, { dimColor: true }, ' '),
115
- React.createElement(Text, { color: "gray", dimColor: true }, newNum),
116
- React.createElement(Text, { dimColor: true }, " \u2502 ")),
117
- React.createElement(Box, null,
118
- React.createElement(Text, { dimColor: true, wrap: "truncate-end" }, displayLine.content))));
119
- }))));
129
+ React.createElement(Box, { flexDirection: "column" },
130
+ hunks.map((hunk, hunkIndex) => (React.createElement(Box, { key: hunkIndex, flexDirection: "column", marginBottom: 1 }, hunk.changes.map((change, changeIndex) => {
131
+ if (change.type === 'added') {
132
+ return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: "#006400" },
133
+ "+ ",
134
+ change.content));
135
+ }
136
+ if (change.type === 'removed') {
137
+ return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: "#8B0000" },
138
+ "- ",
139
+ change.content));
140
+ }
141
+ // Unchanged lines (context)
142
+ return (React.createElement(Text, { key: changeIndex, dimColor: true }, change.content));
143
+ })))),
144
+ hunks.length > 1 && (React.createElement(Box, { marginTop: 1 },
145
+ React.createElement(Text, { color: "gray", dimColor: true },
146
+ "Total: ",
147
+ hunks.length,
148
+ " change region(s)"))))));
120
149
  }
@@ -1,73 +1,108 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { Box, Text } from 'ink';
3
- import { getMCPServicesInfo } from '../../utils/mcpToolsManager.js';
3
+ import SelectInput from 'ink-select-input';
4
+ import { getMCPServicesInfo, refreshMCPToolsCache, reconnectMCPService } from '../../utils/mcpToolsManager.js';
4
5
  export default function MCPInfoPanel() {
5
6
  const [mcpStatus, setMcpStatus] = useState([]);
6
7
  const [isLoading, setIsLoading] = useState(true);
7
8
  const [errorMessage, setErrorMessage] = useState(null);
9
+ const [isReconnecting, setIsReconnecting] = useState(false);
10
+ const loadMCPStatus = async () => {
11
+ try {
12
+ const servicesInfo = await getMCPServicesInfo();
13
+ const statusList = servicesInfo.map(service => ({
14
+ name: service.serviceName,
15
+ connected: service.connected,
16
+ tools: service.tools.map(tool => tool.name),
17
+ connectionMethod: service.isBuiltIn ? 'Built-in' : 'External',
18
+ isBuiltIn: service.isBuiltIn,
19
+ error: service.error
20
+ }));
21
+ setMcpStatus(statusList);
22
+ setErrorMessage(null);
23
+ setIsLoading(false);
24
+ }
25
+ catch (error) {
26
+ setErrorMessage(error instanceof Error ? error.message : 'Failed to load MCP services');
27
+ setIsLoading(false);
28
+ }
29
+ };
8
30
  useEffect(() => {
9
31
  let isMounted = true;
10
- const loadMCPStatus = async () => {
11
- try {
12
- const servicesInfo = await getMCPServicesInfo();
13
- if (isMounted) {
14
- const statusList = servicesInfo.map(service => ({
15
- name: service.serviceName,
16
- connected: service.connected,
17
- tools: service.tools.map(tool => tool.name),
18
- connectionMethod: service.isBuiltIn ? 'Built-in' : 'External',
19
- isBuiltIn: service.isBuiltIn,
20
- error: service.error
21
- }));
22
- setMcpStatus(statusList);
23
- setErrorMessage(null);
24
- setIsLoading(false);
25
- }
26
- }
27
- catch (error) {
28
- if (isMounted) {
29
- setErrorMessage(error instanceof Error ? error.message : 'Failed to load MCP services');
30
- setIsLoading(false);
31
- }
32
- }
32
+ const load = async () => {
33
+ await loadMCPStatus();
33
34
  };
34
- loadMCPStatus();
35
+ if (isMounted) {
36
+ load();
37
+ }
35
38
  return () => {
36
39
  isMounted = false;
37
40
  };
38
41
  }, []);
42
+ const handleServiceSelect = async (item) => {
43
+ setIsReconnecting(true);
44
+ try {
45
+ if (item.value === 'refresh-all') {
46
+ // Refresh all services
47
+ await refreshMCPToolsCache();
48
+ }
49
+ else {
50
+ // Reconnect specific service
51
+ await reconnectMCPService(item.value);
52
+ }
53
+ await loadMCPStatus();
54
+ }
55
+ catch (error) {
56
+ setErrorMessage(error instanceof Error ? error.message : 'Failed to reconnect');
57
+ }
58
+ finally {
59
+ setIsReconnecting(false);
60
+ }
61
+ };
62
+ // Build select items from all services
63
+ const selectItems = [
64
+ { label: 'Refresh all services', value: 'refresh-all', isRefreshAll: true },
65
+ ...mcpStatus.map(s => ({
66
+ label: s.name,
67
+ value: s.name,
68
+ connected: s.connected,
69
+ isBuiltIn: s.isBuiltIn,
70
+ error: s.error
71
+ }))
72
+ ];
73
+ // Custom item component to render with colors
74
+ const ItemComponent = ({ isSelected, label }) => {
75
+ const item = selectItems.find(i => i.label === label);
76
+ if (!item)
77
+ return React.createElement(Text, null, label);
78
+ if (item.isRefreshAll) {
79
+ return (React.createElement(Text, { color: isSelected ? 'cyan' : 'blue' },
80
+ "\u21BB ",
81
+ label));
82
+ }
83
+ const statusColor = item.connected ? 'green' : 'red';
84
+ const suffix = item.isBuiltIn ? ' (System)' : item.connected ? ' (External)' : ` - ${item.error || 'Failed'}`;
85
+ return (React.createElement(Box, null,
86
+ React.createElement(Text, { color: statusColor }, "\u25CF "),
87
+ React.createElement(Text, { color: isSelected ? 'cyan' : 'white' }, label),
88
+ React.createElement(Text, { color: "gray", dimColor: true }, suffix)));
89
+ };
39
90
  if (isLoading) {
40
91
  return (React.createElement(Text, { color: "gray" }, "Loading MCP services..."));
41
92
  }
42
93
  if (errorMessage) {
43
- return (React.createElement(Box, { borderColor: "red", borderStyle: "round", paddingX: 2, paddingY: 1, marginBottom: 1 },
94
+ return (React.createElement(Box, { borderColor: "red", borderStyle: "round", paddingX: 2, paddingY: 0 },
44
95
  React.createElement(Text, { color: "red", dimColor: true },
45
- "Error loading MCP service information:",
96
+ "Error: ",
46
97
  errorMessage)));
47
98
  }
48
99
  if (mcpStatus.length === 0) {
49
- return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 1, marginBottom: 1 },
50
- React.createElement(Text, { color: "gray", dimColor: true }, "No available MCP service was detected")));
100
+ return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 0 },
101
+ React.createElement(Text, { color: "gray", dimColor: true }, "No available MCP services detected")));
51
102
  }
52
- return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 1, marginBottom: 1 },
103
+ return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 0 },
53
104
  React.createElement(Box, { flexDirection: "column" },
54
- React.createElement(Text, { color: "cyan", bold: true }, "MCP Services"),
55
- mcpStatus.map((status, index) => (React.createElement(Box, { key: index, flexDirection: "column", marginTop: index > 0 ? 1 : 0 },
56
- React.createElement(Box, { flexDirection: "row" },
57
- React.createElement(Text, { color: status.connected ? "green" : "red" }, status.connected ? "●" : "●"),
58
- React.createElement(Box, { marginLeft: 1 },
59
- React.createElement(Text, { color: "white", bold: true }, status.name),
60
- status.isBuiltIn && (React.createElement(Text, { color: "blue", dimColor: true }, "(System)")),
61
- status.connected && status.connectionMethod && !status.isBuiltIn && (React.createElement(Text, { color: "gray", dimColor: true },
62
- "(",
63
- status.connectionMethod,
64
- ")")))),
65
- status.connected && status.tools.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
66
- React.createElement(Text, { color: "gray", dimColor: true },
67
- "Tools: ",
68
- status.tools.join(', ')))),
69
- !status.connected && status.error && (React.createElement(Box, { marginLeft: 2 },
70
- React.createElement(Text, { color: "red", dimColor: true },
71
- "Error: ",
72
- status.error)))))))));
105
+ React.createElement(Text, { color: "cyan", bold: true }, isReconnecting ? 'Refreshing services...' : 'MCP Services'),
106
+ !isReconnecting && (React.createElement(SelectInput, { items: selectItems, onSelect: handleServiceSelect, itemComponent: ItemComponent })),
107
+ isReconnecting && (React.createElement(Text, { color: "yellow", dimColor: true }, "Please wait...")))));
73
108
  }
@@ -1,7 +1,32 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
- import MCPInfoPanel from './MCPInfoPanel.js';
3
+ import SelectInput from 'ink-select-input';
4
+ import { getMCPServicesInfo, refreshMCPToolsCache, reconnectMCPService } from '../../utils/mcpToolsManager.js';
4
5
  export default function MCPInfoScreen({ onClose, panelKey }) {
6
+ const [mcpStatus, setMcpStatus] = useState([]);
7
+ const [isLoading, setIsLoading] = useState(true);
8
+ const [errorMessage, setErrorMessage] = useState(null);
9
+ const [isReconnecting, setIsReconnecting] = useState(false);
10
+ const loadMCPStatus = async () => {
11
+ try {
12
+ const servicesInfo = await getMCPServicesInfo();
13
+ const statusList = servicesInfo.map(service => ({
14
+ name: service.serviceName,
15
+ connected: service.connected,
16
+ tools: service.tools.map(tool => tool.name),
17
+ connectionMethod: service.isBuiltIn ? 'Built-in' : 'External',
18
+ isBuiltIn: service.isBuiltIn,
19
+ error: service.error
20
+ }));
21
+ setMcpStatus(statusList);
22
+ setErrorMessage(null);
23
+ setIsLoading(false);
24
+ }
25
+ catch (error) {
26
+ setErrorMessage(error instanceof Error ? error.message : 'Failed to load MCP services');
27
+ setIsLoading(false);
28
+ }
29
+ };
5
30
  useEffect(() => {
6
31
  process.stdout.write('\x1B[?1049h');
7
32
  process.stdout.write('\x1B[2J');
@@ -11,17 +36,80 @@ export default function MCPInfoScreen({ onClose, panelKey }) {
11
36
  process.stdout.write('\x1B[?1049l');
12
37
  };
13
38
  }, []);
39
+ useEffect(() => {
40
+ loadMCPStatus();
41
+ }, [panelKey]);
14
42
  useInput((_, key) => {
15
43
  if (key.escape) {
16
44
  onClose();
17
45
  }
18
46
  });
47
+ const handleServiceSelect = async (item) => {
48
+ setIsReconnecting(true);
49
+ try {
50
+ if (item.value === 'refresh-all') {
51
+ // Refresh all services
52
+ await refreshMCPToolsCache();
53
+ }
54
+ else {
55
+ // Reconnect specific service
56
+ await reconnectMCPService(item.value);
57
+ }
58
+ await loadMCPStatus();
59
+ }
60
+ catch (error) {
61
+ setErrorMessage(error instanceof Error ? error.message : 'Failed to reconnect');
62
+ }
63
+ finally {
64
+ setIsReconnecting(false);
65
+ }
66
+ };
67
+ // Build select items from all services
68
+ const selectItems = [
69
+ { label: 'Refresh all services', value: 'refresh-all', isRefreshAll: true },
70
+ ...mcpStatus.map(s => ({
71
+ label: s.name,
72
+ value: s.name,
73
+ connected: s.connected,
74
+ isBuiltIn: s.isBuiltIn,
75
+ error: s.error
76
+ }))
77
+ ];
78
+ // Custom item component to render with colors
79
+ const ItemComponent = ({ isSelected, label }) => {
80
+ const item = selectItems.find(i => i.label === label);
81
+ if (!item)
82
+ return React.createElement(Text, null, label);
83
+ if (item.isRefreshAll) {
84
+ return (React.createElement(Text, { color: isSelected ? 'cyan' : 'blue' },
85
+ "\u21BB ",
86
+ label));
87
+ }
88
+ const statusColor = item.connected ? 'green' : 'red';
89
+ const suffix = item.isBuiltIn ? ' (System)' : item.connected ? ' (External)' : ` - ${item.error || 'Failed'}`;
90
+ return (React.createElement(Box, null,
91
+ React.createElement(Text, { color: statusColor }, "\u25CF "),
92
+ React.createElement(Text, { color: isSelected ? 'cyan' : 'white' }, label),
93
+ React.createElement(Text, { color: "gray", dimColor: true }, suffix)));
94
+ };
95
+ if (isLoading) {
96
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
97
+ React.createElement(Text, { color: "gray" }, "Loading MCP services...")));
98
+ }
19
99
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
20
- React.createElement(Box, { marginBottom: 1, borderStyle: "double", paddingX: 2, paddingY: 1, borderColor: 'cyan' },
100
+ React.createElement(Box, { borderStyle: "double", paddingX: 2, paddingY: 0, borderColor: "cyan" },
21
101
  React.createElement(Box, { flexDirection: "column" },
22
102
  React.createElement(Text, { color: "white", bold: true },
23
103
  React.createElement(Text, { color: "cyan" }, "\u2746 "),
24
104
  "MCP Services Overview"),
25
- React.createElement(Text, { color: "gray", dimColor: true }, "Press ESC to return to the chat"))),
26
- React.createElement(MCPInfoPanel, { key: panelKey })));
105
+ React.createElement(Text, { color: "gray", dimColor: true }, "Press ESC to return | Use \u2191\u2193 and Enter to refresh"))),
106
+ errorMessage && (React.createElement(Box, { borderColor: "red", borderStyle: "round", paddingX: 2, paddingY: 0, marginTop: 1 },
107
+ React.createElement(Text, { color: "red", dimColor: true },
108
+ "Error: ",
109
+ errorMessage))),
110
+ React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 0, marginTop: 1 },
111
+ React.createElement(Box, { flexDirection: "column" },
112
+ React.createElement(Text, { color: "cyan", bold: true }, isReconnecting ? 'Refreshing services...' : 'MCP Services'),
113
+ !isReconnecting && (React.createElement(SelectInput, { items: selectItems, onSelect: handleServiceSelect, itemComponent: ItemComponent })),
114
+ isReconnecting && (React.createElement(Text, { color: "yellow", dimColor: true }, "Please wait..."))))));
27
115
  }
@@ -83,7 +83,7 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
83
83
  onConfirm(item.value);
84
84
  }
85
85
  };
86
- return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginY: 1, paddingX: 1 },
86
+ return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginY: 1, borderStyle: 'round', borderColor: 'yellow', paddingX: 1 },
87
87
  React.createElement(Box, { marginBottom: 1 },
88
88
  React.createElement(Text, { bold: true, color: "yellow" }, "[Tool Confirmation]")),
89
89
  !formattedAllTools && (React.createElement(React.Fragment, null,
@@ -493,7 +493,7 @@ export default function ChatScreen({}) {
493
493
  message.toolCall.name === 'filesystem-edit' &&
494
494
  message.toolCall.arguments.oldContent &&
495
495
  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 }))),
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 }))),
497
497
  message.toolCall &&
498
498
  message.toolCall.name === 'terminal-execute' &&
499
499
  message.toolCall.arguments.command && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
@@ -26,6 +26,11 @@ export declare function getTodoService(): TodoService;
26
26
  * Manually refresh the tools cache (for configuration changes)
27
27
  */
28
28
  export declare function refreshMCPToolsCache(): Promise<void>;
29
+ /**
30
+ * Reconnect a specific MCP service and update cache
31
+ * @param serviceName - Name of the service to reconnect
32
+ */
33
+ export declare function reconnectMCPService(serviceName: string): Promise<void>;
29
34
  /**
30
35
  * Clear the tools cache (useful for testing or forcing refresh)
31
36
  */
@@ -182,6 +182,70 @@ export async function refreshMCPToolsCache() {
182
182
  toolsCache = null;
183
183
  await refreshToolsCache();
184
184
  }
185
+ /**
186
+ * Reconnect a specific MCP service and update cache
187
+ * @param serviceName - Name of the service to reconnect
188
+ */
189
+ export async function reconnectMCPService(serviceName) {
190
+ if (!toolsCache) {
191
+ // If no cache, do full refresh
192
+ await refreshToolsCache();
193
+ return;
194
+ }
195
+ // Handle built-in services (they don't need reconnection)
196
+ if (serviceName === 'filesystem' || serviceName === 'terminal' || serviceName === 'todo') {
197
+ return;
198
+ }
199
+ // Get the server config
200
+ const mcpConfig = getMCPConfig();
201
+ const server = mcpConfig.mcpServers[serviceName];
202
+ if (!server) {
203
+ throw new Error(`Service ${serviceName} not found in configuration`);
204
+ }
205
+ // Find and update the service in cache
206
+ const serviceIndex = toolsCache.servicesInfo.findIndex(s => s.serviceName === serviceName);
207
+ if (serviceIndex === -1) {
208
+ // Service not in cache, do full refresh
209
+ await refreshToolsCache();
210
+ return;
211
+ }
212
+ try {
213
+ // Try to reconnect to the service
214
+ const serviceTools = await probeServiceTools(serviceName, server);
215
+ // Update service info in cache
216
+ toolsCache.servicesInfo[serviceIndex] = {
217
+ serviceName,
218
+ tools: serviceTools,
219
+ isBuiltIn: false,
220
+ connected: true,
221
+ };
222
+ // Remove old tools for this service from the tools list
223
+ toolsCache.tools = toolsCache.tools.filter(tool => !tool.function.name.startsWith(`${serviceName}-`));
224
+ // Add new tools for this service
225
+ for (const tool of serviceTools) {
226
+ toolsCache.tools.push({
227
+ type: 'function',
228
+ function: {
229
+ name: `${serviceName}-${tool.name}`,
230
+ description: tool.description,
231
+ parameters: tool.inputSchema,
232
+ },
233
+ });
234
+ }
235
+ }
236
+ catch (error) {
237
+ // Update service as failed
238
+ toolsCache.servicesInfo[serviceIndex] = {
239
+ serviceName,
240
+ tools: [],
241
+ isBuiltIn: false,
242
+ connected: false,
243
+ error: error instanceof Error ? error.message : 'Unknown error',
244
+ };
245
+ // Remove tools for this service from the tools list
246
+ toolsCache.tools = toolsCache.tools.filter(tool => !tool.function.name.startsWith(`${serviceName}-`));
247
+ }
248
+ }
185
249
  /**
186
250
  * Clear the tools cache (useful for testing or forcing refresh)
187
251
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {