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.
Files changed (97) hide show
  1. package/dist/api/chat.d.ts +65 -2
  2. package/dist/api/chat.js +299 -16
  3. package/dist/api/responses.d.ts +52 -0
  4. package/dist/api/responses.js +541 -0
  5. package/dist/api/systemPrompt.d.ts +4 -0
  6. package/dist/api/systemPrompt.js +43 -0
  7. package/dist/app.js +15 -4
  8. package/dist/cli.js +38 -2
  9. package/dist/hooks/useConversation.d.ts +32 -0
  10. package/dist/hooks/useConversation.js +403 -0
  11. package/dist/hooks/useGlobalNavigation.d.ts +6 -0
  12. package/dist/hooks/useGlobalNavigation.js +15 -0
  13. package/dist/hooks/useSessionManagement.d.ts +10 -0
  14. package/dist/hooks/useSessionManagement.js +43 -0
  15. package/dist/hooks/useSessionSave.d.ts +8 -0
  16. package/dist/hooks/useSessionSave.js +52 -0
  17. package/dist/hooks/useToolConfirmation.d.ts +18 -0
  18. package/dist/hooks/useToolConfirmation.js +49 -0
  19. package/dist/mcp/bash.d.ts +57 -0
  20. package/dist/mcp/bash.js +138 -0
  21. package/dist/mcp/filesystem.d.ts +307 -0
  22. package/dist/mcp/filesystem.js +520 -0
  23. package/dist/mcp/todo.d.ts +55 -0
  24. package/dist/mcp/todo.js +329 -0
  25. package/dist/test/logger-test.d.ts +1 -0
  26. package/dist/test/logger-test.js +7 -0
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/ui/components/ChatInput.d.ts +15 -2
  29. package/dist/ui/components/ChatInput.js +445 -59
  30. package/dist/ui/components/CommandPanel.d.ts +2 -2
  31. package/dist/ui/components/CommandPanel.js +11 -7
  32. package/dist/ui/components/DiffViewer.d.ts +9 -0
  33. package/dist/ui/components/DiffViewer.js +93 -0
  34. package/dist/ui/components/FileList.d.ts +14 -0
  35. package/dist/ui/components/FileList.js +131 -0
  36. package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
  37. package/dist/ui/components/MCPInfoPanel.js +74 -0
  38. package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
  39. package/dist/ui/components/MCPInfoScreen.js +27 -0
  40. package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
  41. package/dist/ui/components/MarkdownRenderer.js +110 -0
  42. package/dist/ui/components/Menu.d.ts +5 -2
  43. package/dist/ui/components/Menu.js +60 -9
  44. package/dist/ui/components/MessageList.d.ts +30 -2
  45. package/dist/ui/components/MessageList.js +64 -12
  46. package/dist/ui/components/PendingMessages.js +1 -1
  47. package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
  48. package/dist/ui/components/ScrollableSelectInput.js +157 -0
  49. package/dist/ui/components/SessionListScreen.d.ts +7 -0
  50. package/dist/ui/components/SessionListScreen.js +196 -0
  51. package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
  52. package/dist/ui/components/SessionListScreenWrapper.js +14 -0
  53. package/dist/ui/components/TodoTree.d.ts +15 -0
  54. package/dist/ui/components/TodoTree.js +60 -0
  55. package/dist/ui/components/ToolConfirmation.d.ts +8 -0
  56. package/dist/ui/components/ToolConfirmation.js +38 -0
  57. package/dist/ui/components/ToolResultPreview.d.ts +12 -0
  58. package/dist/ui/components/ToolResultPreview.js +115 -0
  59. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  60. package/dist/ui/pages/ChatScreen.js +385 -196
  61. package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
  62. package/dist/ui/pages/MCPConfigScreen.js +55 -0
  63. package/dist/ui/pages/ModelConfigScreen.js +73 -12
  64. package/dist/ui/pages/WelcomeScreen.js +17 -11
  65. package/dist/utils/apiConfig.d.ts +12 -0
  66. package/dist/utils/apiConfig.js +95 -9
  67. package/dist/utils/commandExecutor.d.ts +2 -1
  68. package/dist/utils/commands/init.d.ts +2 -0
  69. package/dist/utils/commands/init.js +93 -0
  70. package/dist/utils/commands/mcp.d.ts +2 -0
  71. package/dist/utils/commands/mcp.js +12 -0
  72. package/dist/utils/commands/resume.d.ts +2 -0
  73. package/dist/utils/commands/resume.js +12 -0
  74. package/dist/utils/commands/yolo.d.ts +2 -0
  75. package/dist/utils/commands/yolo.js +12 -0
  76. package/dist/utils/fileUtils.d.ts +44 -0
  77. package/dist/utils/fileUtils.js +222 -0
  78. package/dist/utils/index.d.ts +4 -0
  79. package/dist/utils/index.js +6 -0
  80. package/dist/utils/logger.d.ts +31 -0
  81. package/dist/utils/logger.js +97 -0
  82. package/dist/utils/mcpToolsManager.d.ts +47 -0
  83. package/dist/utils/mcpToolsManager.js +476 -0
  84. package/dist/utils/messageFormatter.d.ts +12 -0
  85. package/dist/utils/messageFormatter.js +32 -0
  86. package/dist/utils/sessionConverter.d.ts +6 -0
  87. package/dist/utils/sessionConverter.js +61 -0
  88. package/dist/utils/sessionManager.d.ts +39 -0
  89. package/dist/utils/sessionManager.js +141 -0
  90. package/dist/utils/textBuffer.d.ts +36 -7
  91. package/dist/utils/textBuffer.js +265 -179
  92. package/dist/utils/todoPreprocessor.d.ts +5 -0
  93. package/dist/utils/todoPreprocessor.js +19 -0
  94. package/dist/utils/toolExecutor.d.ts +21 -0
  95. package/dist/utils/toolExecutor.js +28 -0
  96. package/package.json +12 -3
  97. package/readme.md +2 -2
@@ -13,298 +13,384 @@ function sanitizeInput(str) {
13
13
  }
14
14
  export class TextBuffer {
15
15
  constructor(viewport) {
16
- Object.defineProperty(this, "lines", {
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, "cursorRow", {
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, "cursorCol", {
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, "viewport", {
34
+ Object.defineProperty(this, "pasteStorage", {
35
35
  enumerable: true,
36
36
  configurable: true,
37
37
  writable: true,
38
- value: void 0
39
- }); // Still needed for width-based text wrapping
40
- Object.defineProperty(this, "pendingUpdates", {
38
+ value: new Map()
39
+ });
40
+ Object.defineProperty(this, "pasteCounter", {
41
41
  enumerable: true,
42
42
  configurable: true,
43
43
  writable: true,
44
- value: false
44
+ value: 0
45
45
  });
46
- Object.defineProperty(this, "pasteStorage", {
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, "pasteCounter", {
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, "lastPasteTime", {
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.lines.join('\n');
92
+ return this.content;
68
93
  }
69
94
  /**
70
95
  * 获取完整文本,包括替换占位符为原始内容
71
96
  */
72
97
  getFullText() {
73
- let fullText = this.text;
74
- // 替换所有占位符为原始内容
75
- for (const [, placeholder] of this.pasteStorage) {
76
- fullText = fullText.replace(placeholder.placeholder, placeholder.content);
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 [this.cursorRow, this.cursorCol];
107
+ return this.visualCursorPos;
108
+ }
109
+ getCursorPosition() {
110
+ return this.cursorIndex;
82
111
  }
83
112
  get viewportVisualLines() {
84
- // Return all lines instead of limiting by viewport height
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
- if (this.pendingUpdates)
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.lines = sanitized.split('\n');
107
- if (this.lines.length === 0) {
108
- this.lines = [''];
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
- const lines = sanitized.split('\n');
137
+ if (!sanitized) {
138
+ return;
139
+ }
140
+ const charCount = sanitized.length;
123
141
  const now = Date.now();
124
- // 检查是否为大量粘贴(超过10行),并防止重复处理
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 placeholder = `[Paste ${lines.length} line #${this.pasteCounter}]`;
134
- // 存储原始内容
149
+ const placeholderText = `[Paste ${charCount} characters #${this.pasteCounter}]`;
135
150
  this.pasteStorage.set(pasteId, {
136
151
  id: pasteId,
137
152
  content: sanitized,
138
- lineCount: lines.length,
153
+ charCount: charCount,
139
154
  index: this.pasteCounter,
140
- placeholder: 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
- if (lines.length === 1) {
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
- findBreakPoint(line, maxWidth) {
209
- const codePoints = toCodePoints(line);
210
- let currentWidth = 0;
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
- return codePoints.length;
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.cursorCol > 0) {
227
- const currentLine = this.getCurrentLine();
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
- const currentLine = this.getCurrentLine();
245
- if (this.cursorCol < cpLen(currentLine)) {
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.cursorCol > 0) {
259
- this.cursorCol--;
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
- const currentLine = this.getCurrentLine();
268
- if (this.cursorCol < cpLen(currentLine)) {
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.cursorRow > 0) {
278
- this.cursorRow--;
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.cursorRow < this.lines.length - 1) {
285
- this.cursorRow++;
286
- const newLineLength = cpLen(this.getCurrentLine());
287
- this.cursorCol = Math.min(this.cursorCol, newLineLength);
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 currentLine = this.getCurrentLine();
302
- const codePoints = toCodePoints(currentLine);
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.cursorCol] || ' ';
307
- const isWideChar = visualWidth(char) > 1;
308
- return { char, isWideChar };
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,5 @@
1
+ export declare function formatTodoContext(todos: Array<{
2
+ id: string;
3
+ content: string;
4
+ status: 'pending' | 'in_progress' | 'completed';
5
+ }>): string;
@@ -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.1.12",
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": "^5.12.2",
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",