snow-ai 0.1.12

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 (46) hide show
  1. package/dist/api/chat.d.ts +29 -0
  2. package/dist/api/chat.js +88 -0
  3. package/dist/api/models.d.ts +12 -0
  4. package/dist/api/models.js +40 -0
  5. package/dist/app.d.ts +6 -0
  6. package/dist/app.js +47 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +19 -0
  9. package/dist/constants/index.d.ts +18 -0
  10. package/dist/constants/index.js +18 -0
  11. package/dist/hooks/useGlobalExit.d.ts +5 -0
  12. package/dist/hooks/useGlobalExit.js +32 -0
  13. package/dist/types/index.d.ts +15 -0
  14. package/dist/types/index.js +1 -0
  15. package/dist/ui/components/ChatInput.d.ts +9 -0
  16. package/dist/ui/components/ChatInput.js +206 -0
  17. package/dist/ui/components/CommandPanel.d.ts +13 -0
  18. package/dist/ui/components/CommandPanel.js +22 -0
  19. package/dist/ui/components/Menu.d.ts +14 -0
  20. package/dist/ui/components/Menu.js +32 -0
  21. package/dist/ui/components/MessageList.d.ts +15 -0
  22. package/dist/ui/components/MessageList.js +16 -0
  23. package/dist/ui/components/PendingMessages.d.ts +6 -0
  24. package/dist/ui/components/PendingMessages.js +19 -0
  25. package/dist/ui/pages/ApiConfigScreen.d.ts +7 -0
  26. package/dist/ui/pages/ApiConfigScreen.js +126 -0
  27. package/dist/ui/pages/ChatScreen.d.ts +5 -0
  28. package/dist/ui/pages/ChatScreen.js +287 -0
  29. package/dist/ui/pages/ModelConfigScreen.d.ts +7 -0
  30. package/dist/ui/pages/ModelConfigScreen.js +239 -0
  31. package/dist/ui/pages/WelcomeScreen.d.ts +7 -0
  32. package/dist/ui/pages/WelcomeScreen.js +48 -0
  33. package/dist/utils/apiConfig.d.ts +17 -0
  34. package/dist/utils/apiConfig.js +86 -0
  35. package/dist/utils/commandExecutor.d.ts +11 -0
  36. package/dist/utils/commandExecutor.js +26 -0
  37. package/dist/utils/commands/clear.d.ts +2 -0
  38. package/dist/utils/commands/clear.js +12 -0
  39. package/dist/utils/index.d.ts +7 -0
  40. package/dist/utils/index.js +12 -0
  41. package/dist/utils/textBuffer.d.ts +52 -0
  42. package/dist/utils/textBuffer.js +310 -0
  43. package/dist/utils/textUtils.d.ts +33 -0
  44. package/dist/utils/textUtils.js +83 -0
  45. package/package.json +86 -0
  46. package/readme.md +9 -0
@@ -0,0 +1,310 @@
1
+ import { cpLen, cpSlice, visualWidth, toCodePoints } from './textUtils.js';
2
+ /**
3
+ * Strip characters that can break terminal rendering.
4
+ */
5
+ function sanitizeInput(str) {
6
+ // Replace problematic characters but preserve basic formatting
7
+ return str
8
+ .replace(/\r\n/g, '\n') // Normalize line endings
9
+ .replace(/\r/g, '\n') // Convert remaining \r to \n
10
+ .replace(/\t/g, ' ') // Convert tabs to spaces
11
+ // Remove control characters except newlines
12
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
13
+ }
14
+ export class TextBuffer {
15
+ constructor(viewport) {
16
+ Object.defineProperty(this, "lines", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: ['']
21
+ });
22
+ Object.defineProperty(this, "cursorRow", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: 0
27
+ });
28
+ Object.defineProperty(this, "cursorCol", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: 0
33
+ });
34
+ Object.defineProperty(this, "viewport", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ }); // Still needed for width-based text wrapping
40
+ Object.defineProperty(this, "pendingUpdates", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: false
45
+ });
46
+ Object.defineProperty(this, "pasteStorage", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: new Map()
51
+ }); // 存储大粘贴内容
52
+ Object.defineProperty(this, "pasteCounter", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: 0
57
+ }); // 粘贴计数器
58
+ Object.defineProperty(this, "lastPasteTime", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: 0
63
+ }); // 最后粘贴时间
64
+ this.viewport = viewport;
65
+ }
66
+ get text() {
67
+ return this.lines.join('\n');
68
+ }
69
+ /**
70
+ * 获取完整文本,包括替换占位符为原始内容
71
+ */
72
+ getFullText() {
73
+ let fullText = this.text;
74
+ // 替换所有占位符为原始内容
75
+ for (const [, placeholder] of this.pasteStorage) {
76
+ fullText = fullText.replace(placeholder.placeholder, placeholder.content);
77
+ }
78
+ return fullText;
79
+ }
80
+ get visualCursor() {
81
+ return [this.cursorRow, this.cursorCol];
82
+ }
83
+ 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;
87
+ }
88
+ get maxWidth() {
89
+ // Viewport width is still useful for text wrapping/formatting
90
+ return this.viewport.width;
91
+ }
92
+ getCurrentLine() {
93
+ return this.lines[this.cursorRow] || '';
94
+ }
95
+ 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
+ });
103
+ }
104
+ setText(text) {
105
+ 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 === '') {
114
+ this.pasteStorage.clear();
115
+ this.pasteCounter = 0;
116
+ this.lastPasteTime = 0;
117
+ }
118
+ this.scheduleUpdate();
119
+ }
120
+ insert(input) {
121
+ const sanitized = sanitizeInput(input);
122
+ const lines = sanitized.split('\n');
123
+ const now = Date.now();
124
+ // 检查是否为大量粘贴(超过10行),并防止重复处理
125
+ if (lines.length > 10) {
126
+ // 防止在短时间内重复处理同样的粘贴内容
127
+ if (now - this.lastPasteTime < 100) {
128
+ return;
129
+ }
130
+ this.lastPasteTime = now;
131
+ this.pasteCounter++;
132
+ const pasteId = `paste_${now}_${this.pasteCounter}`;
133
+ const placeholder = `[Paste ${lines.length} line #${this.pasteCounter}]`;
134
+ // 存储原始内容
135
+ this.pasteStorage.set(pasteId, {
136
+ id: pasteId,
137
+ content: sanitized,
138
+ lineCount: lines.length,
139
+ index: this.pasteCounter,
140
+ placeholder: placeholder
141
+ });
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);
148
+ this.scheduleUpdate();
149
+ return;
150
+ }
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
+ }
206
+ this.scheduleUpdate();
207
+ }
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;
222
+ }
223
+ return codePoints.length;
224
+ }
225
+ 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--;
240
+ }
241
+ this.scheduleUpdate();
242
+ }
243
+ 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);
254
+ }
255
+ this.scheduleUpdate();
256
+ }
257
+ 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());
264
+ }
265
+ }
266
+ 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;
274
+ }
275
+ }
276
+ moveUp() {
277
+ if (this.cursorRow > 0) {
278
+ this.cursorRow--;
279
+ const newLineLength = cpLen(this.getCurrentLine());
280
+ this.cursorCol = Math.min(this.cursorCol, newLineLength);
281
+ }
282
+ }
283
+ 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);
288
+ }
289
+ }
290
+ /**
291
+ * Update the viewport dimensions, useful for terminal resize handling.
292
+ */
293
+ updateViewport(viewport) {
294
+ this.viewport = viewport;
295
+ this.scheduleUpdate();
296
+ }
297
+ /**
298
+ * Get the character and its visual info at cursor position for proper rendering.
299
+ */
300
+ getCharAtCursor() {
301
+ const currentLine = this.getCurrentLine();
302
+ const codePoints = toCodePoints(currentLine);
303
+ if (this.cursorCol >= codePoints.length) {
304
+ return { char: ' ', isWideChar: false };
305
+ }
306
+ const char = codePoints[this.cursorCol] || ' ';
307
+ const isWideChar = visualWidth(char) > 1;
308
+ return { char, isWideChar };
309
+ }
310
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Convert a string to an array of code points (Unicode characters).
3
+ * Handles surrogate pairs correctly.
4
+ */
5
+ export declare function toCodePoints(str: string): string[];
6
+ /**
7
+ * Get the length of a string in code points (not bytes).
8
+ */
9
+ export declare function cpLen(str: string): number;
10
+ /**
11
+ * Slice a string by code point indices (not byte indices).
12
+ */
13
+ export declare function cpSlice(str: string, start: number, end?: number): string;
14
+ /**
15
+ * Get the visual width of a string (how many columns it occupies in terminal).
16
+ * Handles wide characters like Chinese, emojis, etc.
17
+ */
18
+ export declare function visualWidth(str: string): number;
19
+ /**
20
+ * Get character at visual position (accounting for wide characters).
21
+ */
22
+ export declare function getCharAtVisualPos(str: string, visualPos: number): {
23
+ char: string;
24
+ codePointIndex: number;
25
+ } | null;
26
+ /**
27
+ * Convert code point index to visual position.
28
+ */
29
+ export declare function codePointToVisualPos(str: string, codePointIndex: number): number;
30
+ /**
31
+ * Convert visual position to code point index.
32
+ */
33
+ export declare function visualPosToCodePoint(str: string, visualPos: number): number;
@@ -0,0 +1,83 @@
1
+ import stringWidth from 'string-width';
2
+ /**
3
+ * Convert a string to an array of code points (Unicode characters).
4
+ * Handles surrogate pairs correctly.
5
+ */
6
+ export function toCodePoints(str) {
7
+ return Array.from(str);
8
+ }
9
+ /**
10
+ * Get the length of a string in code points (not bytes).
11
+ */
12
+ export function cpLen(str) {
13
+ return toCodePoints(str).length;
14
+ }
15
+ /**
16
+ * Slice a string by code point indices (not byte indices).
17
+ */
18
+ export function cpSlice(str, start, end) {
19
+ const codePoints = toCodePoints(str);
20
+ return codePoints.slice(start, end).join('');
21
+ }
22
+ /**
23
+ * Get the visual width of a string (how many columns it occupies in terminal).
24
+ * Handles wide characters like Chinese, emojis, etc.
25
+ */
26
+ export function visualWidth(str) {
27
+ return stringWidth(str);
28
+ }
29
+ /**
30
+ * Get character at visual position (accounting for wide characters).
31
+ */
32
+ export function getCharAtVisualPos(str, visualPos) {
33
+ const codePoints = toCodePoints(str);
34
+ let currentVisualPos = 0;
35
+ for (let i = 0; i < codePoints.length; i++) {
36
+ const char = codePoints[i] || '';
37
+ const charWidth = visualWidth(char);
38
+ if (currentVisualPos === visualPos) {
39
+ return { char, codePointIndex: i };
40
+ }
41
+ if (currentVisualPos + charWidth > visualPos) {
42
+ // We're in the middle of a wide character
43
+ return { char, codePointIndex: i };
44
+ }
45
+ currentVisualPos += charWidth;
46
+ }
47
+ // Position is at the end of the string
48
+ if (currentVisualPos === visualPos) {
49
+ return { char: '', codePointIndex: codePoints.length };
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Convert code point index to visual position.
55
+ */
56
+ export function codePointToVisualPos(str, codePointIndex) {
57
+ const codePoints = toCodePoints(str);
58
+ let visualPos = 0;
59
+ for (let i = 0; i < Math.min(codePointIndex, codePoints.length); i++) {
60
+ const char = codePoints[i] || '';
61
+ visualPos += visualWidth(char);
62
+ }
63
+ return visualPos;
64
+ }
65
+ /**
66
+ * Convert visual position to code point index.
67
+ */
68
+ export function visualPosToCodePoint(str, visualPos) {
69
+ const codePoints = toCodePoints(str);
70
+ let currentVisualPos = 0;
71
+ for (let i = 0; i < codePoints.length; i++) {
72
+ const char = codePoints[i] || '';
73
+ const charWidth = visualWidth(char);
74
+ if (currentVisualPos + charWidth > visualPos) {
75
+ return i;
76
+ }
77
+ currentVisualPos += charWidth;
78
+ if (currentVisualPos >= visualPos) {
79
+ return i + 1;
80
+ }
81
+ }
82
+ return codePoints.length;
83
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "snow-ai",
3
+ "version": "0.1.12",
4
+ "description": "Intelligent Command Line Assistant powered by AI",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "snow": "dist/cli.js"
8
+ },
9
+ "type": "module",
10
+ "keywords": [
11
+ "cli",
12
+ "ai",
13
+ "assistant",
14
+ "bot",
15
+ "terminal"
16
+ ],
17
+ "author": "Mufasa",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/MayDay-wpf/snow-cli.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/MayDay-wpf/snow-cli/issues"
24
+ },
25
+ "homepage": "https://github.com/MayDay-wpf/snow-cli#readme",
26
+ "engines": {
27
+ "node": ">=16"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsc --watch",
32
+ "start": "node dist/cli.js",
33
+ "prepublishOnly": "npm run build",
34
+ "test": "prettier --check . && xo && ava",
35
+ "lint": "xo",
36
+ "format": "prettier --write ."
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "dependencies": {
42
+ "@inkjs/ui": "^2.0.0",
43
+ "figlet": "^1.8.2",
44
+ "ink": "^5.2.1",
45
+ "ink-gradient": "^3.0.0",
46
+ "ink-text-input": "^6.0.0",
47
+ "ink-tree-select": "^2.3.1",
48
+ "meow": "^11.0.0",
49
+ "openai": "^5.12.2",
50
+ "react": "^18.2.0",
51
+ "string-width": "^7.2.0"
52
+ },
53
+ "devDependencies": {
54
+ "@sindresorhus/tsconfig": "^3.0.1",
55
+ "@types/figlet": "^1.7.0",
56
+ "@types/react": "^18.0.32",
57
+ "@vdemedes/prettier-config": "^2.0.1",
58
+ "ava": "^5.2.0",
59
+ "chalk": "^5.2.0",
60
+ "eslint-config-xo-react": "^0.27.0",
61
+ "eslint-plugin-react": "^7.32.2",
62
+ "eslint-plugin-react-hooks": "^4.6.0",
63
+ "ink-testing-library": "^3.0.0",
64
+ "prettier": "^2.8.7",
65
+ "ts-node": "^10.9.1",
66
+ "typescript": "^5.0.3",
67
+ "xo": "^0.53.1"
68
+ },
69
+ "ava": {
70
+ "extensions": {
71
+ "ts": "module",
72
+ "tsx": "module"
73
+ },
74
+ "nodeArguments": [
75
+ "--loader=ts-node/esm"
76
+ ]
77
+ },
78
+ "xo": {
79
+ "extends": "xo-react",
80
+ "prettier": true,
81
+ "rules": {
82
+ "react/prop-types": "off"
83
+ }
84
+ },
85
+ "prettier": "@vdemedes/prettier-config"
86
+ }
package/readme.md ADDED
@@ -0,0 +1,9 @@
1
+ # aibotpro-cli
2
+
3
+ > This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app)
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ $ npm install --global aibotpro-cli
9
+ ```