snow-ai 0.1.12 → 0.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.
- package/dist/api/chat.d.ts +65 -2
- package/dist/api/chat.js +299 -16
- package/dist/api/responses.d.ts +52 -0
- package/dist/api/responses.js +541 -0
- package/dist/api/systemPrompt.d.ts +4 -0
- package/dist/api/systemPrompt.js +43 -0
- package/dist/app.js +15 -4
- package/dist/cli.js +38 -2
- package/dist/hooks/useConversation.d.ts +32 -0
- package/dist/hooks/useConversation.js +403 -0
- package/dist/hooks/useGlobalNavigation.d.ts +6 -0
- package/dist/hooks/useGlobalNavigation.js +15 -0
- package/dist/hooks/useSessionManagement.d.ts +10 -0
- package/dist/hooks/useSessionManagement.js +43 -0
- package/dist/hooks/useSessionSave.d.ts +8 -0
- package/dist/hooks/useSessionSave.js +52 -0
- package/dist/hooks/useToolConfirmation.d.ts +18 -0
- package/dist/hooks/useToolConfirmation.js +49 -0
- package/dist/mcp/bash.d.ts +57 -0
- package/dist/mcp/bash.js +138 -0
- package/dist/mcp/filesystem.d.ts +307 -0
- package/dist/mcp/filesystem.js +520 -0
- package/dist/mcp/todo.d.ts +55 -0
- package/dist/mcp/todo.js +329 -0
- package/dist/test/logger-test.d.ts +1 -0
- package/dist/test/logger-test.js +7 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/ui/components/ChatInput.d.ts +15 -2
- package/dist/ui/components/ChatInput.js +445 -59
- package/dist/ui/components/CommandPanel.d.ts +2 -2
- package/dist/ui/components/CommandPanel.js +11 -7
- package/dist/ui/components/DiffViewer.d.ts +9 -0
- package/dist/ui/components/DiffViewer.js +93 -0
- package/dist/ui/components/FileList.d.ts +14 -0
- package/dist/ui/components/FileList.js +131 -0
- package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
- package/dist/ui/components/MCPInfoPanel.js +74 -0
- package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
- package/dist/ui/components/MCPInfoScreen.js +27 -0
- package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
- package/dist/ui/components/MarkdownRenderer.js +110 -0
- package/dist/ui/components/Menu.d.ts +5 -2
- package/dist/ui/components/Menu.js +60 -9
- package/dist/ui/components/MessageList.d.ts +30 -2
- package/dist/ui/components/MessageList.js +64 -12
- package/dist/ui/components/PendingMessages.js +1 -1
- package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
- package/dist/ui/components/ScrollableSelectInput.js +157 -0
- package/dist/ui/components/SessionListScreen.d.ts +7 -0
- package/dist/ui/components/SessionListScreen.js +196 -0
- package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
- package/dist/ui/components/SessionListScreenWrapper.js +14 -0
- package/dist/ui/components/TodoTree.d.ts +15 -0
- package/dist/ui/components/TodoTree.js +60 -0
- package/dist/ui/components/ToolConfirmation.d.ts +8 -0
- package/dist/ui/components/ToolConfirmation.js +38 -0
- package/dist/ui/components/ToolResultPreview.d.ts +12 -0
- package/dist/ui/components/ToolResultPreview.js +115 -0
- package/dist/ui/pages/ChatScreen.d.ts +4 -0
- package/dist/ui/pages/ChatScreen.js +385 -196
- package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
- package/dist/ui/pages/MCPConfigScreen.js +55 -0
- package/dist/ui/pages/ModelConfigScreen.js +73 -12
- package/dist/ui/pages/WelcomeScreen.js +17 -11
- package/dist/utils/apiConfig.d.ts +12 -0
- package/dist/utils/apiConfig.js +95 -9
- package/dist/utils/commandExecutor.d.ts +2 -1
- package/dist/utils/commands/init.d.ts +2 -0
- package/dist/utils/commands/init.js +93 -0
- package/dist/utils/commands/mcp.d.ts +2 -0
- package/dist/utils/commands/mcp.js +12 -0
- package/dist/utils/commands/resume.d.ts +2 -0
- package/dist/utils/commands/resume.js +12 -0
- package/dist/utils/commands/yolo.d.ts +2 -0
- package/dist/utils/commands/yolo.js +12 -0
- package/dist/utils/fileUtils.d.ts +44 -0
- package/dist/utils/fileUtils.js +222 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/mcpToolsManager.d.ts +47 -0
- package/dist/utils/mcpToolsManager.js +476 -0
- package/dist/utils/messageFormatter.d.ts +12 -0
- package/dist/utils/messageFormatter.js +32 -0
- package/dist/utils/sessionConverter.d.ts +6 -0
- package/dist/utils/sessionConverter.js +61 -0
- package/dist/utils/sessionManager.d.ts +39 -0
- package/dist/utils/sessionManager.js +141 -0
- package/dist/utils/textBuffer.d.ts +36 -7
- package/dist/utils/textBuffer.js +265 -179
- package/dist/utils/todoPreprocessor.d.ts +5 -0
- package/dist/utils/todoPreprocessor.js +19 -0
- package/dist/utils/toolExecutor.d.ts +21 -0
- package/dist/utils/toolExecutor.js +28 -0
- package/package.json +12 -3
- package/readme.md +2 -2
package/dist/utils/textBuffer.js
CHANGED
|
@@ -13,298 +13,384 @@ function sanitizeInput(str) {
|
|
|
13
13
|
}
|
|
14
14
|
export class TextBuffer {
|
|
15
15
|
constructor(viewport) {
|
|
16
|
-
Object.defineProperty(this, "
|
|
16
|
+
Object.defineProperty(this, "content", {
|
|
17
17
|
enumerable: true,
|
|
18
18
|
configurable: true,
|
|
19
19
|
writable: true,
|
|
20
|
-
value:
|
|
20
|
+
value: ''
|
|
21
21
|
});
|
|
22
|
-
Object.defineProperty(this, "
|
|
22
|
+
Object.defineProperty(this, "cursorIndex", {
|
|
23
23
|
enumerable: true,
|
|
24
24
|
configurable: true,
|
|
25
25
|
writable: true,
|
|
26
26
|
value: 0
|
|
27
27
|
});
|
|
28
|
-
Object.defineProperty(this, "
|
|
28
|
+
Object.defineProperty(this, "viewport", {
|
|
29
29
|
enumerable: true,
|
|
30
30
|
configurable: true,
|
|
31
31
|
writable: true,
|
|
32
|
-
value: 0
|
|
32
|
+
value: void 0
|
|
33
33
|
});
|
|
34
|
-
Object.defineProperty(this, "
|
|
34
|
+
Object.defineProperty(this, "pasteStorage", {
|
|
35
35
|
enumerable: true,
|
|
36
36
|
configurable: true,
|
|
37
37
|
writable: true,
|
|
38
|
-
value:
|
|
39
|
-
});
|
|
40
|
-
Object.defineProperty(this, "
|
|
38
|
+
value: new Map()
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "pasteCounter", {
|
|
41
41
|
enumerable: true,
|
|
42
42
|
configurable: true,
|
|
43
43
|
writable: true,
|
|
44
|
-
value:
|
|
44
|
+
value: 0
|
|
45
45
|
});
|
|
46
|
-
Object.defineProperty(this, "
|
|
46
|
+
Object.defineProperty(this, "lastPasteTime", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: 0
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(this, "imageStorage", {
|
|
47
53
|
enumerable: true,
|
|
48
54
|
configurable: true,
|
|
49
55
|
writable: true,
|
|
50
56
|
value: new Map()
|
|
51
|
-
});
|
|
52
|
-
Object.defineProperty(this, "
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(this, "imageCounter", {
|
|
53
59
|
enumerable: true,
|
|
54
60
|
configurable: true,
|
|
55
61
|
writable: true,
|
|
56
62
|
value: 0
|
|
57
|
-
});
|
|
58
|
-
Object.defineProperty(this, "
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(this, "visualLines", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
value: ['']
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, "visualLineStarts", {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
value: [0]
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(this, "visualCursorPos", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: true,
|
|
80
|
+
value: [0, 0]
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(this, "preferredVisualCol", {
|
|
59
83
|
enumerable: true,
|
|
60
84
|
configurable: true,
|
|
61
85
|
writable: true,
|
|
62
86
|
value: 0
|
|
63
|
-
});
|
|
87
|
+
});
|
|
64
88
|
this.viewport = viewport;
|
|
89
|
+
this.recalculateVisualState();
|
|
65
90
|
}
|
|
66
91
|
get text() {
|
|
67
|
-
return this.
|
|
92
|
+
return this.content;
|
|
68
93
|
}
|
|
69
94
|
/**
|
|
70
95
|
* 获取完整文本,包括替换占位符为原始内容
|
|
71
96
|
*/
|
|
72
97
|
getFullText() {
|
|
73
|
-
let fullText = this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
let fullText = this.content;
|
|
99
|
+
for (const placeholder of this.pasteStorage.values()) {
|
|
100
|
+
if (placeholder.placeholder) {
|
|
101
|
+
fullText = fullText.split(placeholder.placeholder).join(placeholder.content);
|
|
102
|
+
}
|
|
77
103
|
}
|
|
78
104
|
return fullText;
|
|
79
105
|
}
|
|
80
106
|
get visualCursor() {
|
|
81
|
-
return
|
|
107
|
+
return this.visualCursorPos;
|
|
108
|
+
}
|
|
109
|
+
getCursorPosition() {
|
|
110
|
+
return this.cursorIndex;
|
|
82
111
|
}
|
|
83
112
|
get viewportVisualLines() {
|
|
84
|
-
|
|
85
|
-
// Viewport height should only be used for initial sizing, not content limiting
|
|
86
|
-
return this.lines;
|
|
113
|
+
return this.visualLines;
|
|
87
114
|
}
|
|
88
115
|
get maxWidth() {
|
|
89
|
-
// Viewport width is still useful for text wrapping/formatting
|
|
90
116
|
return this.viewport.width;
|
|
91
117
|
}
|
|
92
|
-
getCurrentLine() {
|
|
93
|
-
return this.lines[this.cursorRow] || '';
|
|
94
|
-
}
|
|
95
118
|
scheduleUpdate() {
|
|
96
|
-
|
|
97
|
-
return;
|
|
98
|
-
this.pendingUpdates = true;
|
|
99
|
-
// Defer updates to next tick to handle rapid input
|
|
100
|
-
process.nextTick(() => {
|
|
101
|
-
this.pendingUpdates = false;
|
|
102
|
-
});
|
|
119
|
+
// Removed pendingUpdates logic since it's not needed
|
|
103
120
|
}
|
|
104
121
|
setText(text) {
|
|
105
122
|
const sanitized = sanitizeInput(text);
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
this.cursorRow = Math.min(this.cursorRow, this.lines.length - 1);
|
|
111
|
-
this.cursorCol = Math.min(this.cursorCol, cpLen(this.getCurrentLine()));
|
|
112
|
-
// 清空时重置粘贴存储和计数器
|
|
113
|
-
if (text === '') {
|
|
123
|
+
this.content = sanitized;
|
|
124
|
+
this.clampCursorIndex();
|
|
125
|
+
if (sanitized === '') {
|
|
114
126
|
this.pasteStorage.clear();
|
|
115
127
|
this.pasteCounter = 0;
|
|
116
128
|
this.lastPasteTime = 0;
|
|
129
|
+
this.imageStorage.clear();
|
|
130
|
+
this.imageCounter = 0;
|
|
117
131
|
}
|
|
132
|
+
this.recalculateVisualState();
|
|
118
133
|
this.scheduleUpdate();
|
|
119
134
|
}
|
|
120
135
|
insert(input) {
|
|
121
136
|
const sanitized = sanitizeInput(input);
|
|
122
|
-
|
|
137
|
+
if (!sanitized) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const charCount = sanitized.length;
|
|
123
141
|
const now = Date.now();
|
|
124
|
-
|
|
125
|
-
if (lines.length > 10) {
|
|
126
|
-
// 防止在短时间内重复处理同样的粘贴内容
|
|
142
|
+
if (charCount > 300) {
|
|
127
143
|
if (now - this.lastPasteTime < 100) {
|
|
128
144
|
return;
|
|
129
145
|
}
|
|
130
146
|
this.lastPasteTime = now;
|
|
131
147
|
this.pasteCounter++;
|
|
132
148
|
const pasteId = `paste_${now}_${this.pasteCounter}`;
|
|
133
|
-
const
|
|
134
|
-
// 存储原始内容
|
|
149
|
+
const placeholderText = `[Paste ${charCount} characters #${this.pasteCounter}]`;
|
|
135
150
|
this.pasteStorage.set(pasteId, {
|
|
136
151
|
id: pasteId,
|
|
137
152
|
content: sanitized,
|
|
138
|
-
|
|
153
|
+
charCount: charCount,
|
|
139
154
|
index: this.pasteCounter,
|
|
140
|
-
placeholder:
|
|
155
|
+
placeholder: placeholderText
|
|
141
156
|
});
|
|
142
|
-
|
|
143
|
-
const currentLine = this.getCurrentLine();
|
|
144
|
-
const before = cpSlice(currentLine, 0, this.cursorCol);
|
|
145
|
-
const after = cpSlice(currentLine, this.cursorCol);
|
|
146
|
-
this.lines[this.cursorRow] = before + placeholder + after;
|
|
147
|
-
this.cursorCol += cpLen(placeholder);
|
|
157
|
+
this.insertPlainText(placeholderText);
|
|
148
158
|
this.scheduleUpdate();
|
|
149
159
|
return;
|
|
150
160
|
}
|
|
151
|
-
|
|
152
|
-
// Single line input - check for wrapping
|
|
153
|
-
const currentLine = this.getCurrentLine();
|
|
154
|
-
const before = cpSlice(currentLine, 0, this.cursorCol);
|
|
155
|
-
const after = cpSlice(currentLine, this.cursorCol);
|
|
156
|
-
const newLine = before + sanitized + after;
|
|
157
|
-
// 简化换行逻辑,只在超过最大宽度时换行
|
|
158
|
-
if (visualWidth(newLine) > this.viewport.width && this.viewport.width > 20) {
|
|
159
|
-
// 尝试在合适位置换行
|
|
160
|
-
const breakPoint = this.findBreakPoint(newLine, this.viewport.width);
|
|
161
|
-
if (breakPoint > 0 && breakPoint < newLine.length) {
|
|
162
|
-
const firstPart = cpSlice(newLine, 0, breakPoint);
|
|
163
|
-
const secondPart = cpSlice(newLine, breakPoint);
|
|
164
|
-
this.lines[this.cursorRow] = firstPart;
|
|
165
|
-
this.lines.splice(this.cursorRow + 1, 0, secondPart);
|
|
166
|
-
// Calculate where cursor should be after insertion
|
|
167
|
-
const newCursorPos = this.cursorCol + cpLen(sanitized);
|
|
168
|
-
if (newCursorPos <= cpLen(firstPart)) {
|
|
169
|
-
// Cursor stays on first line
|
|
170
|
-
this.cursorCol = newCursorPos;
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
// Cursor moves to second line
|
|
174
|
-
this.cursorRow++;
|
|
175
|
-
this.cursorCol = newCursorPos - cpLen(firstPart);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
// 无法找到合适换行点,直接设置
|
|
180
|
-
this.lines[this.cursorRow] = newLine;
|
|
181
|
-
this.cursorCol += cpLen(sanitized);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
this.lines[this.cursorRow] = newLine;
|
|
186
|
-
this.cursorCol += cpLen(sanitized);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
// Multi-line input (paste) - 正常处理小于等于10行的粘贴
|
|
191
|
-
const currentLine = this.getCurrentLine();
|
|
192
|
-
const before = cpSlice(currentLine, 0, this.cursorCol);
|
|
193
|
-
const after = cpSlice(currentLine, this.cursorCol);
|
|
194
|
-
// First line: current line prefix + first paste line
|
|
195
|
-
this.lines[this.cursorRow] = before + lines[0];
|
|
196
|
-
// Middle lines: insert as new lines
|
|
197
|
-
const middleLines = lines.slice(1, -1);
|
|
198
|
-
this.lines.splice(this.cursorRow + 1, 0, ...middleLines);
|
|
199
|
-
// Last line: last paste line + current line suffix
|
|
200
|
-
const lastLine = lines[lines.length - 1] + after;
|
|
201
|
-
this.lines.splice(this.cursorRow + lines.length - 1, 0, lastLine);
|
|
202
|
-
// Update cursor position
|
|
203
|
-
this.cursorRow += lines.length - 1;
|
|
204
|
-
this.cursorCol = cpLen(lines[lines.length - 1] || '');
|
|
205
|
-
}
|
|
161
|
+
this.insertPlainText(sanitized);
|
|
206
162
|
this.scheduleUpdate();
|
|
207
163
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let lastSpaceIndex = -1;
|
|
212
|
-
for (let i = 0; i < codePoints.length; i++) {
|
|
213
|
-
const char = codePoints[i] || '';
|
|
214
|
-
const charWidth = visualWidth(char);
|
|
215
|
-
if (char === ' ') {
|
|
216
|
-
lastSpaceIndex = i;
|
|
217
|
-
}
|
|
218
|
-
if (currentWidth + charWidth > maxWidth) {
|
|
219
|
-
return lastSpaceIndex > 0 ? lastSpaceIndex : Math.max(1, i);
|
|
220
|
-
}
|
|
221
|
-
currentWidth += charWidth;
|
|
164
|
+
insertPlainText(text) {
|
|
165
|
+
if (!text) {
|
|
166
|
+
return;
|
|
222
167
|
}
|
|
223
|
-
|
|
168
|
+
this.clampCursorIndex();
|
|
169
|
+
const before = cpSlice(this.content, 0, this.cursorIndex);
|
|
170
|
+
const after = cpSlice(this.content, this.cursorIndex);
|
|
171
|
+
this.content = before + text + after;
|
|
172
|
+
this.cursorIndex += cpLen(text);
|
|
173
|
+
this.recalculateVisualState();
|
|
224
174
|
}
|
|
225
175
|
backspace() {
|
|
226
|
-
if (this.
|
|
227
|
-
|
|
228
|
-
const before = cpSlice(currentLine, 0, this.cursorCol - 1);
|
|
229
|
-
const after = cpSlice(currentLine, this.cursorCol);
|
|
230
|
-
this.lines[this.cursorRow] = before + after;
|
|
231
|
-
this.cursorCol--;
|
|
232
|
-
}
|
|
233
|
-
else if (this.cursorRow > 0) {
|
|
234
|
-
const currentLine = this.getCurrentLine();
|
|
235
|
-
const prevLine = this.lines[this.cursorRow - 1] || '';
|
|
236
|
-
this.cursorCol = cpLen(prevLine);
|
|
237
|
-
this.lines[this.cursorRow - 1] = prevLine + currentLine;
|
|
238
|
-
this.lines.splice(this.cursorRow, 1);
|
|
239
|
-
this.cursorRow--;
|
|
176
|
+
if (this.cursorIndex === 0) {
|
|
177
|
+
return;
|
|
240
178
|
}
|
|
179
|
+
const before = cpSlice(this.content, 0, this.cursorIndex - 1);
|
|
180
|
+
const after = cpSlice(this.content, this.cursorIndex);
|
|
181
|
+
this.content = before + after;
|
|
182
|
+
this.cursorIndex -= 1;
|
|
183
|
+
this.recalculateVisualState();
|
|
241
184
|
this.scheduleUpdate();
|
|
242
185
|
}
|
|
243
186
|
delete() {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const before = cpSlice(currentLine, 0, this.cursorCol);
|
|
247
|
-
const after = cpSlice(currentLine, this.cursorCol + 1);
|
|
248
|
-
this.lines[this.cursorRow] = before + after;
|
|
249
|
-
}
|
|
250
|
-
else if (this.cursorRow < this.lines.length - 1) {
|
|
251
|
-
const nextLine = this.lines[this.cursorRow + 1] || '';
|
|
252
|
-
this.lines[this.cursorRow] = currentLine + nextLine;
|
|
253
|
-
this.lines.splice(this.cursorRow + 1, 1);
|
|
187
|
+
if (this.cursorIndex >= cpLen(this.content)) {
|
|
188
|
+
return;
|
|
254
189
|
}
|
|
190
|
+
const before = cpSlice(this.content, 0, this.cursorIndex);
|
|
191
|
+
const after = cpSlice(this.content, this.cursorIndex + 1);
|
|
192
|
+
this.content = before + after;
|
|
193
|
+
this.recalculateVisualState();
|
|
255
194
|
this.scheduleUpdate();
|
|
256
195
|
}
|
|
257
196
|
moveLeft() {
|
|
258
|
-
if (this.
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
else if (this.cursorRow > 0) {
|
|
262
|
-
this.cursorRow--;
|
|
263
|
-
this.cursorCol = cpLen(this.getCurrentLine());
|
|
197
|
+
if (this.cursorIndex === 0) {
|
|
198
|
+
return;
|
|
264
199
|
}
|
|
200
|
+
this.cursorIndex -= 1;
|
|
201
|
+
this.recomputeVisualCursorOnly();
|
|
265
202
|
}
|
|
266
203
|
moveRight() {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.cursorCol++;
|
|
270
|
-
}
|
|
271
|
-
else if (this.cursorRow < this.lines.length - 1) {
|
|
272
|
-
this.cursorRow++;
|
|
273
|
-
this.cursorCol = 0;
|
|
204
|
+
if (this.cursorIndex >= cpLen(this.content)) {
|
|
205
|
+
return;
|
|
274
206
|
}
|
|
207
|
+
this.cursorIndex += 1;
|
|
208
|
+
this.recomputeVisualCursorOnly();
|
|
275
209
|
}
|
|
276
210
|
moveUp() {
|
|
277
|
-
if (this.
|
|
278
|
-
|
|
279
|
-
const newLineLength = cpLen(this.getCurrentLine());
|
|
280
|
-
this.cursorCol = Math.min(this.cursorCol, newLineLength);
|
|
211
|
+
if (this.visualLines.length === 0) {
|
|
212
|
+
return;
|
|
281
213
|
}
|
|
214
|
+
const currentRow = this.visualCursorPos[0];
|
|
215
|
+
if (currentRow <= 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.moveCursorToVisualRow(currentRow - 1);
|
|
282
219
|
}
|
|
283
220
|
moveDown() {
|
|
284
|
-
if (this.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
221
|
+
if (this.visualLines.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const currentRow = this.visualCursorPos[0];
|
|
225
|
+
if (currentRow >= this.visualLines.length - 1) {
|
|
226
|
+
return;
|
|
288
227
|
}
|
|
228
|
+
this.moveCursorToVisualRow(currentRow + 1);
|
|
289
229
|
}
|
|
290
230
|
/**
|
|
291
231
|
* Update the viewport dimensions, useful for terminal resize handling.
|
|
292
232
|
*/
|
|
293
233
|
updateViewport(viewport) {
|
|
294
234
|
this.viewport = viewport;
|
|
235
|
+
this.recalculateVisualState();
|
|
295
236
|
this.scheduleUpdate();
|
|
296
237
|
}
|
|
297
238
|
/**
|
|
298
239
|
* Get the character and its visual info at cursor position for proper rendering.
|
|
299
240
|
*/
|
|
300
241
|
getCharAtCursor() {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
if (this.cursorCol >= codePoints.length) {
|
|
242
|
+
const codePoints = toCodePoints(this.content);
|
|
243
|
+
if (this.cursorIndex >= codePoints.length) {
|
|
304
244
|
return { char: ' ', isWideChar: false };
|
|
305
245
|
}
|
|
306
|
-
const char = codePoints[this.
|
|
307
|
-
|
|
308
|
-
|
|
246
|
+
const char = codePoints[this.cursorIndex] || ' ';
|
|
247
|
+
return { char, isWideChar: visualWidth(char) > 1 };
|
|
248
|
+
}
|
|
249
|
+
clampCursorIndex() {
|
|
250
|
+
const length = cpLen(this.content);
|
|
251
|
+
if (this.cursorIndex < 0) {
|
|
252
|
+
this.cursorIndex = 0;
|
|
253
|
+
}
|
|
254
|
+
else if (this.cursorIndex > length) {
|
|
255
|
+
this.cursorIndex = length;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
recalculateVisualState() {
|
|
259
|
+
this.clampCursorIndex();
|
|
260
|
+
const width = this.viewport.width;
|
|
261
|
+
const effectiveWidth = Number.isFinite(width) && width > 0 ? width : Number.POSITIVE_INFINITY;
|
|
262
|
+
const rawLines = this.content.split('\n');
|
|
263
|
+
const nextVisualLines = [];
|
|
264
|
+
const nextStarts = [];
|
|
265
|
+
let cpOffset = 0;
|
|
266
|
+
const linesToProcess = rawLines.length > 0 ? rawLines : [''];
|
|
267
|
+
for (let i = 0; i < linesToProcess.length; i++) {
|
|
268
|
+
const rawLine = linesToProcess[i] ?? '';
|
|
269
|
+
const segments = this.wrapLineToWidth(rawLine, effectiveWidth);
|
|
270
|
+
if (segments.length === 0) {
|
|
271
|
+
nextVisualLines.push('');
|
|
272
|
+
nextStarts.push(cpOffset);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
for (const segment of segments) {
|
|
276
|
+
nextVisualLines.push(segment);
|
|
277
|
+
nextStarts.push(cpOffset);
|
|
278
|
+
cpOffset += cpLen(segment);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (i < linesToProcess.length - 1) {
|
|
282
|
+
// Account for the newline character that separates raw lines
|
|
283
|
+
cpOffset += 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (nextVisualLines.length === 0) {
|
|
287
|
+
nextVisualLines.push('');
|
|
288
|
+
nextStarts.push(0);
|
|
289
|
+
}
|
|
290
|
+
this.visualLines = nextVisualLines;
|
|
291
|
+
this.visualLineStarts = nextStarts;
|
|
292
|
+
this.visualCursorPos = this.computeVisualCursorFromIndex(this.cursorIndex);
|
|
293
|
+
this.preferredVisualCol = this.visualCursorPos[1];
|
|
294
|
+
}
|
|
295
|
+
wrapLineToWidth(line, width) {
|
|
296
|
+
if (line === '') {
|
|
297
|
+
return [''];
|
|
298
|
+
}
|
|
299
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
300
|
+
return [line];
|
|
301
|
+
}
|
|
302
|
+
const codePoints = toCodePoints(line);
|
|
303
|
+
const segments = [];
|
|
304
|
+
let start = 0;
|
|
305
|
+
while (start < codePoints.length) {
|
|
306
|
+
let currentWidth = 0;
|
|
307
|
+
let end = start;
|
|
308
|
+
let lastBreak = -1;
|
|
309
|
+
while (end < codePoints.length) {
|
|
310
|
+
const char = codePoints[end] || '';
|
|
311
|
+
const charWidth = visualWidth(char);
|
|
312
|
+
if (char === ' ') {
|
|
313
|
+
lastBreak = end + 1;
|
|
314
|
+
}
|
|
315
|
+
if (currentWidth + charWidth > width) {
|
|
316
|
+
if (lastBreak > start) {
|
|
317
|
+
end = lastBreak;
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
currentWidth += charWidth;
|
|
322
|
+
end++;
|
|
323
|
+
}
|
|
324
|
+
if (end === start) {
|
|
325
|
+
end = Math.min(start + 1, codePoints.length);
|
|
326
|
+
}
|
|
327
|
+
segments.push(codePoints.slice(start, end).join(''));
|
|
328
|
+
start = end;
|
|
329
|
+
}
|
|
330
|
+
return segments;
|
|
331
|
+
}
|
|
332
|
+
computeVisualCursorFromIndex(position) {
|
|
333
|
+
if (this.visualLines.length === 0) {
|
|
334
|
+
return [0, 0];
|
|
335
|
+
}
|
|
336
|
+
const totalLength = cpLen(this.content);
|
|
337
|
+
const clamped = Math.max(0, Math.min(position, totalLength));
|
|
338
|
+
for (let i = this.visualLines.length - 1; i >= 0; i--) {
|
|
339
|
+
const start = this.visualLineStarts[i] ?? 0;
|
|
340
|
+
if (clamped >= start) {
|
|
341
|
+
const line = this.visualLines[i] ?? '';
|
|
342
|
+
const col = Math.min(cpLen(line), clamped - start);
|
|
343
|
+
return [i, col];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return [0, clamped];
|
|
347
|
+
}
|
|
348
|
+
moveCursorToVisualRow(targetRow) {
|
|
349
|
+
if (this.visualLines.length === 0) {
|
|
350
|
+
this.cursorIndex = 0;
|
|
351
|
+
this.visualCursorPos = [0, 0];
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const row = Math.max(0, Math.min(targetRow, this.visualLines.length - 1));
|
|
355
|
+
const start = this.visualLineStarts[row] ?? 0;
|
|
356
|
+
const line = this.visualLines[row] ?? '';
|
|
357
|
+
const lineLength = cpLen(line);
|
|
358
|
+
const column = Math.min(this.preferredVisualCol, lineLength);
|
|
359
|
+
this.cursorIndex = start + column;
|
|
360
|
+
this.visualCursorPos = [row, column];
|
|
361
|
+
}
|
|
362
|
+
recomputeVisualCursorOnly() {
|
|
363
|
+
this.visualCursorPos = this.computeVisualCursorFromIndex(this.cursorIndex);
|
|
364
|
+
this.preferredVisualCol = this.visualCursorPos[1];
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 插入图片数据
|
|
368
|
+
*/
|
|
369
|
+
insertImage(base64Data, mimeType) {
|
|
370
|
+
this.imageCounter++;
|
|
371
|
+
const imageId = `image_${Date.now()}_${this.imageCounter}`;
|
|
372
|
+
const placeholderText = `[image #${this.imageCounter}]`;
|
|
373
|
+
this.imageStorage.set(imageId, {
|
|
374
|
+
id: imageId,
|
|
375
|
+
data: base64Data,
|
|
376
|
+
mimeType: mimeType,
|
|
377
|
+
index: this.imageCounter,
|
|
378
|
+
placeholder: placeholderText
|
|
379
|
+
});
|
|
380
|
+
this.insertPlainText(placeholderText);
|
|
381
|
+
this.scheduleUpdate();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 获取所有图片数据
|
|
385
|
+
*/
|
|
386
|
+
getImages() {
|
|
387
|
+
return Array.from(this.imageStorage.values()).sort((a, b) => a.index - b.index);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 清除所有图片
|
|
391
|
+
*/
|
|
392
|
+
clearImages() {
|
|
393
|
+
this.imageStorage.clear();
|
|
394
|
+
this.imageCounter = 0;
|
|
309
395
|
}
|
|
310
396
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function formatTodoContext(todos) {
|
|
2
|
+
if (todos.length === 0) {
|
|
3
|
+
return '';
|
|
4
|
+
}
|
|
5
|
+
const statusSymbol = {
|
|
6
|
+
pending: '[ ]',
|
|
7
|
+
in_progress: '[~]',
|
|
8
|
+
completed: '[x]',
|
|
9
|
+
};
|
|
10
|
+
const lines = [
|
|
11
|
+
'## Current TODO List',
|
|
12
|
+
'',
|
|
13
|
+
...todos.map(t => `${statusSymbol[t.status]} ${t.content} (ID: ${t.id})`),
|
|
14
|
+
'',
|
|
15
|
+
'**Important**: Update TODO status immediately after completing each task using todo-update tool.',
|
|
16
|
+
'',
|
|
17
|
+
];
|
|
18
|
+
return lines.join('\n');
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ToolCall {
|
|
2
|
+
id: string;
|
|
3
|
+
type: 'function';
|
|
4
|
+
function: {
|
|
5
|
+
name: string;
|
|
6
|
+
arguments: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface ToolResult {
|
|
10
|
+
tool_call_id: string;
|
|
11
|
+
role: 'tool';
|
|
12
|
+
content: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Execute a single tool call and return the result
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeToolCall(toolCall: ToolCall): Promise<ToolResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Execute multiple tool calls in parallel
|
|
20
|
+
*/
|
|
21
|
+
export declare function executeToolCalls(toolCalls: ToolCall[]): Promise<ToolResult[]>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { executeMCPTool } from './mcpToolsManager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a single tool call and return the result
|
|
4
|
+
*/
|
|
5
|
+
export async function executeToolCall(toolCall) {
|
|
6
|
+
try {
|
|
7
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
8
|
+
const result = await executeMCPTool(toolCall.function.name, args);
|
|
9
|
+
return {
|
|
10
|
+
tool_call_id: toolCall.id,
|
|
11
|
+
role: 'tool',
|
|
12
|
+
content: JSON.stringify(result)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return {
|
|
17
|
+
tool_call_id: toolCall.id,
|
|
18
|
+
role: 'tool',
|
|
19
|
+
content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute multiple tool calls in parallel
|
|
25
|
+
*/
|
|
26
|
+
export async function executeToolCalls(toolCalls) {
|
|
27
|
+
return Promise.all(toolCalls.map(tc => executeToolCall(tc)));
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Intelligent Command Line Assistant powered by AI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -40,18 +40,27 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@inkjs/ui": "^2.0.0",
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.17.3",
|
|
44
|
+
"chalk-template": "^1.1.2",
|
|
45
|
+
"cli-highlight": "^2.1.11",
|
|
46
|
+
"diff": "^8.0.2",
|
|
43
47
|
"figlet": "^1.8.2",
|
|
44
48
|
"ink": "^5.2.1",
|
|
45
49
|
"ink-gradient": "^3.0.0",
|
|
50
|
+
"ink-markdown": "^1.0.4",
|
|
51
|
+
"ink-select-input": "^6.2.0",
|
|
52
|
+
"ink-spinner": "^5.0.0",
|
|
46
53
|
"ink-text-input": "^6.0.0",
|
|
47
54
|
"ink-tree-select": "^2.3.1",
|
|
48
55
|
"meow": "^11.0.0",
|
|
49
|
-
"openai": "^
|
|
56
|
+
"openai": "^6.1.0",
|
|
50
57
|
"react": "^18.2.0",
|
|
51
|
-
"string-width": "^7.2.0"
|
|
58
|
+
"string-width": "^7.2.0",
|
|
59
|
+
"tiktoken": "^1.0.22"
|
|
52
60
|
},
|
|
53
61
|
"devDependencies": {
|
|
54
62
|
"@sindresorhus/tsconfig": "^3.0.1",
|
|
63
|
+
"@types/diff": "^7.0.2",
|
|
55
64
|
"@types/figlet": "^1.7.0",
|
|
56
65
|
"@types/react": "^18.0.32",
|
|
57
66
|
"@vdemedes/prettier-config": "^2.0.1",
|