deeper-cli 1.0.0

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 (188) hide show
  1. package/README.md +254 -0
  2. package/dist/cli/index.d.ts +1 -0
  3. package/dist/cli/index.js +12067 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/index.d.ts +415 -0
  6. package/dist/index.js +1599 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/superpowers/plans/2026-05-14-deepercode-implementation.md +24 -0
  9. package/docs/superpowers/plans/2026-05-14-deepercode-plan.md +1248 -0
  10. package/docs/superpowers/specs/2026-05-14-deepercode-design.md +560 -0
  11. package/package.json +60 -0
  12. package/src/cli/bootstrap.ts +69 -0
  13. package/src/cli/chat-repl.ts +932 -0
  14. package/src/cli/commands/chat.ts +39 -0
  15. package/src/cli/commands/chat.tsx +39 -0
  16. package/src/cli/commands/config.ts +133 -0
  17. package/src/cli/commands/mcp.ts +172 -0
  18. package/src/cli/commands/run.ts +147 -0
  19. package/src/cli/commands/skill.ts +152 -0
  20. package/src/cli/index.ts +184 -0
  21. package/src/core/bugscan.ts +145 -0
  22. package/src/core/config.ts +285 -0
  23. package/src/core/constants.ts +49 -0
  24. package/src/core/eventbus.ts +202 -0
  25. package/src/core/logger.ts +109 -0
  26. package/src/core/storage.ts +96 -0
  27. package/src/index.ts +26 -0
  28. package/src/mcp/ConfigLoader.ts +74 -0
  29. package/src/mcp/MCPClient.ts +326 -0
  30. package/src/mcp/ResourceAdapter.ts +58 -0
  31. package/src/mcp/SSETransport.ts +133 -0
  32. package/src/mcp/StdioTransport.ts +116 -0
  33. package/src/mcp/ToolAdapter.ts +71 -0
  34. package/src/mcp/types.ts +58 -0
  35. package/src/memory/xmemory.ts +275 -0
  36. package/src/model/DeepSeekClient.ts +292 -0
  37. package/src/model/MessageBuilder.ts +155 -0
  38. package/src/model/RetryManager.ts +82 -0
  39. package/src/model/StreamHandler.ts +158 -0
  40. package/src/model/types.ts +86 -0
  41. package/src/skills/SkillCreator.ts +153 -0
  42. package/src/skills/SkillEngine.ts +158 -0
  43. package/src/skills/SkillExecutor.ts +107 -0
  44. package/src/skills/SkillLoader.ts +182 -0
  45. package/src/skills/SkillRegistry.ts +73 -0
  46. package/src/skills/SkillTrigger.ts +82 -0
  47. package/src/skills/types.ts +28 -0
  48. package/src/tools/ToolExecutor.ts +103 -0
  49. package/src/tools/ToolRegistry.ts +71 -0
  50. package/src/tools/ToolValidator.ts +103 -0
  51. package/src/tools/builtin/ai/context_summarize.ts +76 -0
  52. package/src/tools/builtin/ai/memory_store.ts +86 -0
  53. package/src/tools/builtin/ai/prompt_template.ts +71 -0
  54. package/src/tools/builtin/ai/skill_create.ts +53 -0
  55. package/src/tools/builtin/ai/subagent.ts +39 -0
  56. package/src/tools/builtin/ai/todo_manager.ts +157 -0
  57. package/src/tools/builtin/ai/token_count.ts +196 -0
  58. package/src/tools/builtin/ai/tool_create.ts +52 -0
  59. package/src/tools/builtin/code/analyze_deps.ts +72 -0
  60. package/src/tools/builtin/code/bug_scan.ts +80 -0
  61. package/src/tools/builtin/code/code_metrics.ts +111 -0
  62. package/src/tools/builtin/code/extract_function.ts +86 -0
  63. package/src/tools/builtin/code/format_code.ts +57 -0
  64. package/src/tools/builtin/code/generate_code.ts +75 -0
  65. package/src/tools/builtin/code/import_organizer.ts +82 -0
  66. package/src/tools/builtin/code/lint_code.ts +48 -0
  67. package/src/tools/builtin/code/parse_ast.ts +86 -0
  68. package/src/tools/builtin/code/refactor_code.ts +63 -0
  69. package/src/tools/builtin/code/type_check.ts +48 -0
  70. package/src/tools/builtin/data/chart_generate.ts +62 -0
  71. package/src/tools/builtin/data/csv_parse.ts +56 -0
  72. package/src/tools/builtin/data/data_diff.ts +79 -0
  73. package/src/tools/builtin/data/data_transform.ts +74 -0
  74. package/src/tools/builtin/data/data_validate.ts +75 -0
  75. package/src/tools/builtin/data/json_parse.ts +71 -0
  76. package/src/tools/builtin/data/template_render.ts +58 -0
  77. package/src/tools/builtin/data/toml_parse.ts +42 -0
  78. package/src/tools/builtin/data/xml_parse.ts +79 -0
  79. package/src/tools/builtin/data/yaml_parse.ts +42 -0
  80. package/src/tools/builtin/database/db_backup.ts +53 -0
  81. package/src/tools/builtin/database/db_restore.ts +51 -0
  82. package/src/tools/builtin/database/db_schema.ts +66 -0
  83. package/src/tools/builtin/database/nosql_query.ts +50 -0
  84. package/src/tools/builtin/database/orm_generate.ts +66 -0
  85. package/src/tools/builtin/database/redis_command.ts +46 -0
  86. package/src/tools/builtin/database/sql_migrate.ts +55 -0
  87. package/src/tools/builtin/database/sql_query.ts +60 -0
  88. package/src/tools/builtin/filesystem/batch_read.ts +56 -0
  89. package/src/tools/builtin/filesystem/batch_write.ts +67 -0
  90. package/src/tools/builtin/filesystem/copy_file.ts +36 -0
  91. package/src/tools/builtin/filesystem/create_dir.ts +30 -0
  92. package/src/tools/builtin/filesystem/delete_file.ts +30 -0
  93. package/src/tools/builtin/filesystem/diff_files.ts +47 -0
  94. package/src/tools/builtin/filesystem/edit_file.ts +47 -0
  95. package/src/tools/builtin/filesystem/file_info.ts +52 -0
  96. package/src/tools/builtin/filesystem/glob_find.ts +44 -0
  97. package/src/tools/builtin/filesystem/list_dir.ts +51 -0
  98. package/src/tools/builtin/filesystem/merge_files.ts +44 -0
  99. package/src/tools/builtin/filesystem/move_file.ts +37 -0
  100. package/src/tools/builtin/filesystem/read_file.ts +55 -0
  101. package/src/tools/builtin/filesystem/watch_file.ts +33 -0
  102. package/src/tools/builtin/filesystem/write_file.ts +45 -0
  103. package/src/tools/builtin/index.ts +244 -0
  104. package/src/tools/builtin/network/api_call.ts +79 -0
  105. package/src/tools/builtin/network/browser_action.ts +54 -0
  106. package/src/tools/builtin/network/check_url.ts +59 -0
  107. package/src/tools/builtin/network/download_file.ts +64 -0
  108. package/src/tools/builtin/network/graphql_query.ts +46 -0
  109. package/src/tools/builtin/network/http_request.ts +61 -0
  110. package/src/tools/builtin/network/parse_html.ts +101 -0
  111. package/src/tools/builtin/network/proxy_request.ts +53 -0
  112. package/src/tools/builtin/network/screenshot_page.ts +58 -0
  113. package/src/tools/builtin/network/web_fetch.ts +70 -0
  114. package/src/tools/builtin/network/web_search.ts +128 -0
  115. package/src/tools/builtin/network/websocket_connect.ts +70 -0
  116. package/src/tools/builtin/project/build_project.ts +68 -0
  117. package/src/tools/builtin/project/config_manage.ts +99 -0
  118. package/src/tools/builtin/project/coverage_report.ts +59 -0
  119. package/src/tools/builtin/project/docker_manage.ts +90 -0
  120. package/src/tools/builtin/project/env_manage.ts +88 -0
  121. package/src/tools/builtin/project/npm_manage.ts +71 -0
  122. package/src/tools/builtin/project/project_init.ts +59 -0
  123. package/src/tools/builtin/project/run_test.ts +74 -0
  124. package/src/tools/builtin/search/codebase_search.ts +76 -0
  125. package/src/tools/builtin/search/find_definition.ts +84 -0
  126. package/src/tools/builtin/search/find_references.ts +75 -0
  127. package/src/tools/builtin/search/fuzzy_find.ts +75 -0
  128. package/src/tools/builtin/search/grep_search.ts +90 -0
  129. package/src/tools/builtin/search/regex_find.ts +91 -0
  130. package/src/tools/builtin/search/search_docs.ts +51 -0
  131. package/src/tools/builtin/search/search_package.ts +50 -0
  132. package/src/tools/builtin/search/symbol_search.ts +82 -0
  133. package/src/tools/builtin/search/text_search.ts +63 -0
  134. package/src/tools/builtin/security/decrypt_file.ts +54 -0
  135. package/src/tools/builtin/security/encrypt_file.ts +52 -0
  136. package/src/tools/builtin/security/hash_generate.ts +48 -0
  137. package/src/tools/builtin/security/jwt_decode.ts +53 -0
  138. package/src/tools/builtin/security/secret_scan.ts +82 -0
  139. package/src/tools/builtin/security/vulnerability_check.ts +71 -0
  140. package/src/tools/builtin/shell/background_terminal.ts +38 -0
  141. package/src/tools/builtin/shell/check_status.ts +48 -0
  142. package/src/tools/builtin/shell/interactive_terminal.ts +31 -0
  143. package/src/tools/builtin/shell/kill_terminal.ts +29 -0
  144. package/src/tools/builtin/shell/list_terminals.ts +61 -0
  145. package/src/tools/builtin/shell/pipe_commands.ts +55 -0
  146. package/src/tools/builtin/shell/process-pool.ts +150 -0
  147. package/src/tools/builtin/shell/run_async.ts +73 -0
  148. package/src/tools/builtin/shell/run_command.ts +60 -0
  149. package/src/tools/builtin/shell/send_ctrl_keys.ts +43 -0
  150. package/src/tools/builtin/shell/send_keys.ts +36 -0
  151. package/src/tools/builtin/shell/send_text.ts +35 -0
  152. package/src/tools/builtin/shell/shell_script.ts +65 -0
  153. package/src/tools/builtin/shell/stop_command.ts +40 -0
  154. package/src/tools/builtin/shell/terminal_resize.ts +31 -0
  155. package/src/tools/builtin/shell/terminal_screenshot.ts +28 -0
  156. package/src/tools/builtin/system/log_viewer.ts +89 -0
  157. package/src/tools/builtin/system/notify_user.ts +55 -0
  158. package/src/tools/builtin/system/process_list.ts +66 -0
  159. package/src/tools/builtin/system/resource_monitor.ts +66 -0
  160. package/src/tools/builtin/system/system_info.ts +41 -0
  161. package/src/tools/tool-types.ts +97 -0
  162. package/src/ui/AgentTree.tsx +98 -0
  163. package/src/ui/App.tsx +46 -0
  164. package/src/ui/ChatView.tsx +278 -0
  165. package/src/ui/ConfirmDialog.tsx +68 -0
  166. package/src/ui/DiffView.tsx +64 -0
  167. package/src/ui/FilePreview.tsx +59 -0
  168. package/src/ui/InputBox.tsx +267 -0
  169. package/src/ui/MessageBubble.tsx +30 -0
  170. package/src/ui/Spinner.tsx +35 -0
  171. package/src/ui/StatusBar.tsx +41 -0
  172. package/src/ui/ToolCallCard.tsx +73 -0
  173. package/src/ui/ansi.ts +50 -0
  174. package/src/ui/markdown.ts +238 -0
  175. package/src/ui/themes/dark.ts +4 -0
  176. package/src/ui/themes/default.ts +25 -0
  177. package/src/ui/themes/light.ts +14 -0
  178. package/tests/unit/BuiltinTools.test.ts +129 -0
  179. package/tests/unit/BuiltinToolsIntegration.test.ts +111 -0
  180. package/tests/unit/FilesystemTools.test.ts +211 -0
  181. package/tests/unit/SkillLoader.test.ts +141 -0
  182. package/tests/unit/SkillRegistry.test.ts +113 -0
  183. package/tests/unit/ToolExecutor.test.ts +160 -0
  184. package/tests/unit/ToolRegistry.test.ts +103 -0
  185. package/tests/unit/ToolValidator.test.ts +137 -0
  186. package/tsconfig.json +28 -0
  187. package/tsup.config.ts +17 -0
  188. package/vitest.config.ts +20 -0
@@ -0,0 +1,267 @@
1
+ import { useState, useCallback, useRef } from 'react';
2
+ import { Text, Box, useInput } from 'ink';
3
+ import { defaultTheme } from './themes/default.ts';
4
+ import { eventbus } from '../core/eventbus.ts';
5
+ import { SLASH_COMMANDS } from '../model/types.ts';
6
+
7
+ interface InputBoxProps {
8
+ onSubmit: (content: string) => void;
9
+ placeholder?: string;
10
+ }
11
+
12
+ const MAX_HISTORY = 100;
13
+ const history: string[] = [];
14
+
15
+ function addHistory(content: string) {
16
+ if (content.trim() && history[history.length - 1] !== content) {
17
+ history.push(content);
18
+ if (history.length > MAX_HISTORY) {
19
+ history.shift();
20
+ }
21
+ }
22
+ }
23
+
24
+ export function InputBox({ onSubmit, placeholder = '输入消息...' }: InputBoxProps) {
25
+ const [value, setValue] = useState('');
26
+ const [cursorPos, setCursorPos] = useState(0);
27
+ const [historyIndex, setHistoryIndex] = useState(-1);
28
+ const [showConfirmExit, setShowConfirmExit] = useState(false);
29
+ const [tabSuggestions, setTabSuggestions] = useState<string[]>([]);
30
+ const [showSuggestions, setShowSuggestions] = useState(false);
31
+
32
+ const valueRef = useRef(value);
33
+ valueRef.current = value;
34
+
35
+ const handleSubmit = useCallback(() => {
36
+ const trimmed = value.trim();
37
+ if (!trimmed) return;
38
+
39
+ if (trimmed.startsWith('/')) {
40
+ const cmd = SLASH_COMMANDS.find((c) => trimmed.startsWith(c.command));
41
+ if (cmd) {
42
+ eventbus.emitMessageSend({ content: trimmed });
43
+ addHistory(trimmed);
44
+ setValue('');
45
+ setCursorPos(0);
46
+ setHistoryIndex(-1);
47
+ return;
48
+ }
49
+ }
50
+
51
+ onSubmit(trimmed);
52
+ eventbus.emitMessageSend({ content: trimmed });
53
+ addHistory(trimmed);
54
+ setValue('');
55
+ setCursorPos(0);
56
+ setHistoryIndex(-1);
57
+ }, [value, onSubmit]);
58
+
59
+ const handleTabComplete = useCallback(() => {
60
+ if (value.startsWith('/')) {
61
+ const matching = SLASH_COMMANDS
62
+ .filter((c) => c.command.startsWith(value))
63
+ .map((c) => c.command);
64
+
65
+ if (matching.length === 1) {
66
+ setValue(matching[0] + ' ');
67
+ setCursorPos(matching[0].length + 1);
68
+ setShowSuggestions(false);
69
+ } else if (matching.length > 1) {
70
+ setTabSuggestions(matching);
71
+ setShowSuggestions(true);
72
+ }
73
+ }
74
+ }, [value]);
75
+
76
+ useInput((input, key) => {
77
+ if (showConfirmExit) {
78
+ if (input === 'y' || input === 'Y') {
79
+ eventbus.emitAppQuit();
80
+ return;
81
+ }
82
+ if (input === 'n' || input === 'N' || key.escape) {
83
+ setShowConfirmExit(false);
84
+ return;
85
+ }
86
+ return;
87
+ }
88
+
89
+ if (input === '\x03') {
90
+ setShowConfirmExit(true);
91
+ return;
92
+ }
93
+
94
+ if (key.return) {
95
+ if (key.ctrl) {
96
+ handleSubmit();
97
+ return;
98
+ }
99
+ if (showSuggestions) {
100
+ setShowSuggestions(false);
101
+ return;
102
+ }
103
+ handleSubmit();
104
+ return;
105
+ }
106
+
107
+ if (key.tab) {
108
+ handleTabComplete();
109
+ return;
110
+ }
111
+
112
+ if (key.upArrow) {
113
+ if (showSuggestions) {
114
+ setShowSuggestions(false);
115
+ return;
116
+ }
117
+ if (history.length === 0) return;
118
+ const newIndex = historyIndex === -1
119
+ ? history.length - 1
120
+ : Math.max(0, historyIndex - 1);
121
+ setHistoryIndex(newIndex);
122
+ setValue(history[newIndex]);
123
+ setCursorPos(history[newIndex].length);
124
+ return;
125
+ }
126
+
127
+ if (key.downArrow) {
128
+ if (showSuggestions) {
129
+ setShowSuggestions(false);
130
+ return;
131
+ }
132
+ if (historyIndex === -1) return;
133
+ const newIndex = historyIndex + 1;
134
+ if (newIndex >= history.length) {
135
+ setHistoryIndex(-1);
136
+ setValue('');
137
+ setCursorPos(0);
138
+ } else {
139
+ setHistoryIndex(newIndex);
140
+ setValue(history[newIndex]);
141
+ setCursorPos(history[newIndex].length);
142
+ }
143
+ return;
144
+ }
145
+
146
+ if (key.escape) {
147
+ setShowSuggestions(false);
148
+ return;
149
+ }
150
+
151
+ if (key.backspace || key.delete) {
152
+ setShowSuggestions(false);
153
+ if (key.backspace) {
154
+ if (cursorPos > 0) {
155
+ const newVal = value.slice(0, cursorPos - 1) + value.slice(cursorPos);
156
+ setValue(newVal);
157
+ setCursorPos(Math.max(0, cursorPos - 1));
158
+ }
159
+ }
160
+ if (key.delete) {
161
+ if (cursorPos < value.length) {
162
+ const newVal = value.slice(0, cursorPos) + value.slice(cursorPos + 1);
163
+ setValue(newVal);
164
+ }
165
+ }
166
+ return;
167
+ }
168
+
169
+ if (key.leftArrow) {
170
+ setCursorPos(Math.max(0, cursorPos - 1));
171
+ return;
172
+ }
173
+
174
+ if (key.rightArrow) {
175
+ setCursorPos(Math.min(value.length, cursorPos + 1));
176
+ return;
177
+ }
178
+
179
+ if ((key as Record<string, unknown>).home) {
180
+ setCursorPos(0);
181
+ return;
182
+ }
183
+
184
+ if ((key as Record<string, unknown>).end) {
185
+ setCursorPos(value.length);
186
+ return;
187
+ }
188
+
189
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
190
+ const newVal = value.slice(0, cursorPos) + input + value.slice(cursorPos);
191
+ setValue(newVal);
192
+ setCursorPos(cursorPos + 1);
193
+
194
+ if (newVal.startsWith('/') && newVal.length > 1) {
195
+ const matching = SLASH_COMMANDS
196
+ .filter((c) => c.command.startsWith(newVal))
197
+ .map((c) => c.command);
198
+ if (matching.length > 0) {
199
+ setTabSuggestions(matching);
200
+ setShowSuggestions(true);
201
+ } else {
202
+ setShowSuggestions(false);
203
+ }
204
+ } else {
205
+ setShowSuggestions(false);
206
+ }
207
+ return;
208
+ }
209
+ });
210
+
211
+ if (showConfirmExit) {
212
+ return (
213
+ <Box
214
+ flexDirection="column"
215
+ borderStyle="double"
216
+ borderColor={defaultTheme.error}
217
+ paddingX={2}
218
+ paddingY={0}
219
+ >
220
+ <Box>
221
+ <Text bold color={defaultTheme.error}>⚠ 确认退出 DeeperCode?</Text>
222
+ </Box>
223
+ <Box marginTop={0}>
224
+ <Text>按 [Y] 退出 [N/ESC] 取消</Text>
225
+ </Box>
226
+ </Box>
227
+ );
228
+ }
229
+
230
+ const cursorChar = '█';
231
+
232
+ const beforeCursor = value.slice(0, cursorPos);
233
+ const atCursor = value[cursorPos] || ' ';
234
+ const afterCursor = value.slice(cursorPos + 1);
235
+
236
+ return (
237
+ <Box flexDirection="column">
238
+ {showSuggestions && tabSuggestions.length > 0 ? (
239
+ <Box flexDirection="column" borderStyle="single" borderColor={defaultTheme.border} paddingX={1}>
240
+ {tabSuggestions.map((s) => (
241
+ <Text key={s} color={defaultTheme.primary}>{s}</Text>
242
+ ))}
243
+ </Box>
244
+ ) : null}
245
+
246
+ <Box
247
+ flexDirection="row"
248
+ borderStyle="round"
249
+ borderColor={defaultTheme.primary}
250
+ paddingX={1}
251
+ >
252
+ <Text color={defaultTheme.primary} bold>❯ </Text>
253
+ <Box>
254
+ <Text>{beforeCursor}</Text>
255
+ <Text backgroundColor={defaultTheme.primary} color={defaultTheme.background}>
256
+ {atCursor}
257
+ </Text>
258
+ <Text>{afterCursor}</Text>
259
+ {value.length === 0 ? (
260
+ <Text dimColor>{placeholder}</Text>
261
+ ) : null}
262
+ </Box>
263
+ <Text dimColor> Ctrl+Enter 发送</Text>
264
+ </Box>
265
+ </Box>
266
+ );
267
+ }
@@ -0,0 +1,30 @@
1
+ import { Text, Box } from 'ink';
2
+ import { defaultTheme } from './themes/default.ts';
3
+
4
+ interface Props {
5
+ role: 'user' | 'assistant' | 'system' | 'tool';
6
+ content: string | null;
7
+ thinking?: string;
8
+ timestamp?: number;
9
+ }
10
+
11
+ export function MessageBubble({ role, content }: Props) {
12
+ const display = content ?? '';
13
+ const lines = display.split('\n');
14
+ const color = role === 'user' ? defaultTheme.secondary : defaultTheme.text;
15
+
16
+ return (
17
+ <Box flexDirection="column" marginBottom={1}>
18
+ <Box>
19
+ <Text color={role === 'user' ? defaultTheme.secondary : defaultTheme.primary} bold>
20
+ {role === 'user' ? '> ' : role === 'system' ? '⚙ ' : '● '}
21
+ </Text>
22
+ </Box>
23
+ {lines.map((line, i) => (
24
+ <Box key={i} paddingLeft={1}>
25
+ <Text color={color}>{line || ' '}</Text>
26
+ </Box>
27
+ ))}
28
+ </Box>
29
+ );
30
+ }
@@ -0,0 +1,35 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { defaultTheme } from './themes/default.ts';
4
+
5
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
6
+
7
+ interface SpinnerProps {
8
+ label?: string;
9
+ type?: 'dots' | 'line' | 'braille';
10
+ }
11
+
12
+ export function Spinner({ label, type = 'braille' }: SpinnerProps) {
13
+ const [frame, setFrame] = useState(0);
14
+
15
+ useEffect(() => {
16
+ const timer = setInterval(() => {
17
+ setFrame((prev) => (prev + 1) % spinnerFrames.length);
18
+ }, 80);
19
+ return () => clearInterval(timer);
20
+ }, []);
21
+
22
+ const dotsFrames = [' ', '. ', '.. ', '...'];
23
+ const frameChar = type === 'dots'
24
+ ? dotsFrames[frame % dotsFrames.length]
25
+ : type === 'line'
26
+ ? ['|', '/', '-', '\\'][frame % 4]
27
+ : spinnerFrames[frame];
28
+
29
+ return (
30
+ <Box>
31
+ <Text color={defaultTheme.primary}>{frameChar}</Text>
32
+ {label ? <Text> {label}</Text> : null}
33
+ </Box>
34
+ );
35
+ }
@@ -0,0 +1,41 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { defaultTheme } from './themes/default.ts';
4
+ import { eventbus, type ContextUpdatedPayload } from '../core/eventbus.ts';
5
+
6
+ function fmtTokens(n: number): string {
7
+ return n > 1000 ? `${(n / 1000).toFixed(1)}K` : `${n}`;
8
+ }
9
+
10
+ function fmtUptime(ms: number): string {
11
+ const s = Math.floor(ms / 1000);
12
+ if (s < 60) return `${s}s`;
13
+ const m = Math.floor(s / 60);
14
+ return `${m}m ${s % 60}s`;
15
+ }
16
+
17
+ export function StatusBar() {
18
+ const [st, setSt] = useState<ContextUpdatedPayload>({
19
+ modelName: 'deepseek-v4-pro',
20
+ tokenCount: 0,
21
+ memoryUsage: 0,
22
+ uptime: 0,
23
+ });
24
+
25
+ useEffect(() => {
26
+ const h = (p: Partial<ContextUpdatedPayload>) => setSt(prev => ({ ...prev, ...p }));
27
+ eventbus.onStatusUpdate(h);
28
+ eventbus.onContextUpdated(h);
29
+ return () => { eventbus.removeAllListeners(); };
30
+ }, []);
31
+
32
+ return (
33
+ <Box flexDirection="row" paddingX={1}>
34
+ <Text dimColor>{st.modelName}</Text>
35
+ <Text dimColor> | Tok: {fmtTokens(st.tokenCount)}</Text>
36
+ <Text dimColor> | Mem: {st.memoryUsage}MB</Text>
37
+ <Text dimColor> | {fmtUptime(st.uptime)}</Text>
38
+ <Text dimColor> | Ctrl+C 退出</Text>
39
+ </Box>
40
+ );
41
+ }
@@ -0,0 +1,73 @@
1
+ import { Text, Box } from 'ink';
2
+ import { defaultTheme } from './themes/default.ts';
3
+
4
+ interface ToolCallCardProps {
5
+ toolName: string;
6
+ args: Record<string, unknown>;
7
+ result?: string;
8
+ status: 'pending' | 'running' | 'completed' | 'failed';
9
+ }
10
+
11
+ const statusConfig: Record<ToolCallCardProps['status'], { icon: string; color: string; label: string }> = {
12
+ pending: { icon: '⏳', color: defaultTheme.dimText, label: '等待中' },
13
+ running: { icon: '🔄', color: defaultTheme.primary, label: '执行中' },
14
+ completed: { icon: '✅', color: defaultTheme.success, label: '已完成' },
15
+ failed: { icon: '❌', color: defaultTheme.error, label: '失败' },
16
+ };
17
+
18
+ function summarizeArgs(args: Record<string, unknown>): string {
19
+ const entries = Object.entries(args);
20
+ if (entries.length === 0) return '无参数';
21
+
22
+ const maxLen = 60;
23
+ const summary = entries
24
+ .map(([k, v]) => {
25
+ const strVal = typeof v === 'string' ? v : JSON.stringify(v);
26
+ return `${k}: ${strVal.length > 40 ? strVal.slice(0, 40) + '...' : strVal}`;
27
+ })
28
+ .join(', ');
29
+
30
+ return summary.length > maxLen ? summary.slice(0, maxLen) + '...' : summary;
31
+ }
32
+
33
+ export function ToolCallCard({ toolName, args, result, status }: ToolCallCardProps) {
34
+ const cfg = statusConfig[status];
35
+
36
+ return (
37
+ <Box
38
+ flexDirection="column"
39
+ borderStyle="round"
40
+ borderColor={cfg.color}
41
+ paddingX={1}
42
+ marginY={0}
43
+ >
44
+ <Box>
45
+ <Text color={cfg.color}>{cfg.icon}</Text>
46
+ <Text bold color={cfg.color}> {toolName}</Text>
47
+ <Text dimColor> - {cfg.label}</Text>
48
+ </Box>
49
+
50
+ <Box marginTop={0}>
51
+ <Text dimColor>参数: </Text>
52
+ <Text>{summarizeArgs(args)}</Text>
53
+ </Box>
54
+
55
+ {result && status === 'completed' ? (
56
+ <Box marginTop={0} flexDirection="column">
57
+ <Text dimColor>结果: </Text>
58
+ <Box borderStyle="single" borderColor={defaultTheme.border} paddingX={1}>
59
+ <Text>
60
+ {result.length > 200 ? result.slice(0, 200) + '...' : result}
61
+ </Text>
62
+ </Box>
63
+ </Box>
64
+ ) : null}
65
+
66
+ {result && status === 'failed' ? (
67
+ <Box marginTop={0}>
68
+ <Text color={defaultTheme.error}>错误: {result}</Text>
69
+ </Box>
70
+ ) : null}
71
+ </Box>
72
+ );
73
+ }
package/src/ui/ansi.ts ADDED
@@ -0,0 +1,50 @@
1
+ import process from 'node:process';
2
+
3
+ // ---- output buffer ----
4
+ let writeBuf = '';
5
+ let writeTimer: ReturnType<typeof setTimeout> | null = null;
6
+ export const O = (s: string) => { writeBuf += s; if (!writeTimer) writeTimer = setTimeout(() => { const b = writeBuf; writeBuf = ''; writeTimer = null; if (b) process.stdout.write(b); }, 16); };
7
+ export function Oflush() { if (writeTimer) { clearTimeout(writeTimer); writeTimer = null; } if (writeBuf) { const b = writeBuf; writeBuf = ''; process.stdout.write(b); } }
8
+
9
+ // ---- ANSI colors ----
10
+ export const A = {
11
+ R: '\x1b[0m', d: '\x1b[2m', b: '\x1b[1m',
12
+ c: '\x1b[36m', g: '\x1b[32m', y: '\x1b[33m',
13
+ r: '\x1b[31m', B: '\x1b[34m', m: '\x1b[35m',
14
+ G: '\x1b[90m',
15
+ };
16
+ export function d(s: string) { return A.d + s + A.R; }
17
+ export function b(s: string) { return A.b + s + A.R; }
18
+ export function c(s: string) { return A.c + s + A.R; }
19
+ export function g(s: string) { return A.g + s + A.R; }
20
+ export function y(s: string) { return A.y + s + A.R; }
21
+ export function r(s: string) { return A.r + s + A.R; }
22
+ export function B(s: string) { return A.B + s + A.R; }
23
+ export function G(s: string) { return A.G + s + A.R; }
24
+
25
+ // ---- thinking animation ----
26
+ let stepTs = Date.now();
27
+ const WAVE_CHARS = '▁▂▃▄▅▆▇█';
28
+ const BRAILLE = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
29
+
30
+ export function resetTimer() { stepTs = Date.now(); }
31
+ export function elapsedSec() { return (Date.now() - stepTs) / 1000; }
32
+
33
+ export function thinkingAnim(label: string): string {
34
+ return thinkingAnimAt(label, stepTs);
35
+ }
36
+ export function thinkingAnimAt(label: string, start: number): string {
37
+ const t = (Date.now() - start) / 1000;
38
+ let wave = '';
39
+ for (let i = 0; i < 9; i++) {
40
+ const v = Math.sin((i / 9) * Math.PI * 2 + t * 2.5) * 0.5 + 0.5;
41
+ const ch = WAVE_CHARS[Math.floor(v * 7)] || '█';
42
+ if (v > 0.67) wave += A.c + ch + A.R;
43
+ else if (v > 0.33) wave += A.m + ch + A.R;
44
+ else wave += A.G + ch + A.R;
45
+ }
46
+ const spin = A.c + A.b + BRAILLE[Math.floor((t * 10) % 8)] + A.R;
47
+ const orb = ['◜', '◝', '◞', '◟'];
48
+ const pulse = Math.sin(t * 4) > 0 ? A.c + orb[Math.floor((t * 7) % 4)] + A.R : A.G + orb[Math.floor((t * 7) % 4)] + A.R;
49
+ return A.d + ' ' + A.R + wave + ' ' + spin + ' ' + pulse + ' ' + A.d + label + A.R + ' ' + A.G + t.toFixed(1) + 's' + A.R;
50
+ }