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,211 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { writeFileSync, unlinkSync, mkdirSync, rmSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ describe('内置文件系统工具', () => {
8
+ let testDir: string;
9
+
10
+ function setup() {
11
+ testDir = join(tmpdir(), `deeper-fs-test-${randomUUID()}`);
12
+ mkdirSync(testDir, { recursive: true });
13
+ return testDir;
14
+ }
15
+
16
+ function cleanup() {
17
+ if (existsSync(testDir)) {
18
+ rmSync(testDir, { recursive: true, force: true });
19
+ }
20
+ }
21
+
22
+ it('read_file 应正确读取文件内容', async () => {
23
+ const dir = setup();
24
+ const filePath = join(dir, 'test.txt');
25
+ writeFileSync(filePath, 'line1\nline2\nline3\nline4\nline5', 'utf-8');
26
+ try {
27
+ const { read_file } = await import('../../src/tools/builtin/filesystem/read_file.js');
28
+ const result = await read_file.execute({ file_path: filePath });
29
+ expect(result.success).toBe(true);
30
+ expect(result.output).toContain('line1');
31
+ expect(result.output).toContain('line5');
32
+ } finally {
33
+ cleanup();
34
+ }
35
+ });
36
+
37
+ it('read_file 应支持行范围读取', async () => {
38
+ const dir = setup();
39
+ const filePath = join(dir, 'test.txt');
40
+ writeFileSync(filePath, 'line1\nline2\nline3\nline4\nline5', 'utf-8');
41
+ try {
42
+ const { read_file } = await import('../../src/tools/builtin/filesystem/read_file.js');
43
+ const result = await read_file.execute({ file_path: filePath, offset: 2, limit: 2 });
44
+ expect(result.success).toBe(true);
45
+ expect(result.output).toContain('line2');
46
+ expect(result.output).toContain('line3');
47
+ expect(result.output).not.toContain('line1');
48
+ } finally {
49
+ cleanup();
50
+ }
51
+ });
52
+
53
+ it('read_file 不存在的文件应返回错误', async () => {
54
+ const { read_file } = await import('../../src/tools/builtin/filesystem/read_file.js');
55
+ const result = await read_file.execute({ file_path: '/nonexistent/file.txt' });
56
+ expect(result.success).toBe(false);
57
+ expect(result.error).toContain('不存在');
58
+ });
59
+
60
+ it('write_file 应写入文件', async () => {
61
+ const dir = setup();
62
+ const filePath = join(dir, 'output.txt');
63
+ try {
64
+ const { write_file } = await import('../../src/tools/builtin/filesystem/write_file.js');
65
+ const result = await write_file.execute({ file_path: filePath, content: 'Hello DeeperCode' });
66
+ expect(result.success).toBe(true);
67
+ const { readFileSync } = await import('node:fs');
68
+ expect(readFileSync(filePath, 'utf-8')).toBe('Hello DeeperCode');
69
+ } finally {
70
+ cleanup();
71
+ }
72
+ });
73
+
74
+ it('write_file 应覆盖已有文件', async () => {
75
+ const dir = setup();
76
+ const filePath = join(dir, 'output.txt');
77
+ writeFileSync(filePath, 'old content', 'utf-8');
78
+ try {
79
+ const { write_file } = await import('../../src/tools/builtin/filesystem/write_file.js');
80
+ await write_file.execute({ file_path: filePath, content: 'new content' });
81
+ const { readFileSync } = await import('node:fs');
82
+ expect(readFileSync(filePath, 'utf-8')).toBe('new content');
83
+ } finally {
84
+ cleanup();
85
+ }
86
+ });
87
+
88
+ it('delete_file 应删除文件', async () => {
89
+ const dir = setup();
90
+ const filePath = join(dir, 'to_delete.txt');
91
+ writeFileSync(filePath, 'delete me', 'utf-8');
92
+ try {
93
+ const { delete_file } = await import('../../src/tools/builtin/filesystem/delete_file.js');
94
+ const result = await delete_file.execute({ file_path: filePath });
95
+ expect(result.success).toBe(true);
96
+ expect(existsSync(filePath)).toBe(false);
97
+ } finally {
98
+ cleanup();
99
+ }
100
+ });
101
+
102
+ it('list_dir 应列出目录内容', async () => {
103
+ const dir = setup();
104
+ writeFileSync(join(dir, 'a.txt'), '', 'utf-8');
105
+ writeFileSync(join(dir, 'b.txt'), '', 'utf-8');
106
+ mkdirSync(join(dir, 'subdir'));
107
+ try {
108
+ const { list_dir } = await import('../../src/tools/builtin/filesystem/list_dir.js');
109
+ const result = await list_dir.execute({ dir_path: dir });
110
+ expect(result.success).toBe(true);
111
+ expect(result.output).toContain('a.txt');
112
+ expect(result.output).toContain('b.txt');
113
+ } finally {
114
+ cleanup();
115
+ }
116
+ });
117
+
118
+ it('create_dir 应创建目录', async () => {
119
+ const dir = setup();
120
+ const newDir = join(dir, 'nested', 'deep');
121
+ try {
122
+ const { create_dir } = await import('../../src/tools/builtin/filesystem/create_dir.js');
123
+ const result = await create_dir.execute({ dir_path: newDir });
124
+ expect(result.success).toBe(true);
125
+ expect(existsSync(newDir)).toBe(true);
126
+ } finally {
127
+ cleanup();
128
+ }
129
+ });
130
+
131
+ it('glob_find 应匹配文件', async () => {
132
+ const dir = setup();
133
+ writeFileSync(join(dir, 'file1.ts'), '', 'utf-8');
134
+ writeFileSync(join(dir, 'file2.ts'), '', 'utf-8');
135
+ writeFileSync(join(dir, 'file3.js'), '', 'utf-8');
136
+ try {
137
+ const { glob_find } = await import('../../src/tools/builtin/filesystem/glob_find.js');
138
+ const result = await glob_find.execute({ pattern: '*.ts', cwd: dir });
139
+ expect(result.success).toBe(true);
140
+ expect(result.output).toContain('file1.ts');
141
+ expect(result.output).toContain('file2.ts');
142
+ expect(result.output).not.toContain('file3.js');
143
+ } finally {
144
+ cleanup();
145
+ }
146
+ });
147
+
148
+ it('file_info 应返回文件信息', async () => {
149
+ const dir = setup();
150
+ const filePath = join(dir, 'info.txt');
151
+ writeFileSync(filePath, 'content', 'utf-8');
152
+ try {
153
+ const { file_info } = await import('../../src/tools/builtin/filesystem/file_info.js');
154
+ const result = await file_info.execute({ file_path: filePath });
155
+ expect(result.success).toBe(true);
156
+ expect(result.output).toContain('info.txt');
157
+ } finally {
158
+ cleanup();
159
+ }
160
+ });
161
+
162
+ it('batch_read 应批量读取', async () => {
163
+ const dir = setup();
164
+ const p1 = join(dir, 'batch1.txt');
165
+ const p2 = join(dir, 'batch2.txt');
166
+ writeFileSync(p1, 'content1', 'utf-8');
167
+ writeFileSync(p2, 'content2', 'utf-8');
168
+ try {
169
+ const { batch_read } = await import('../../src/tools/builtin/filesystem/batch_read.js');
170
+ const result = await batch_read.execute({ file_paths: [p1, p2] });
171
+ expect(result.success).toBe(true);
172
+ expect(result.output).toContain('content1');
173
+ expect(result.output).toContain('content2');
174
+ } finally {
175
+ cleanup();
176
+ }
177
+ });
178
+
179
+ it('copy_file 应复制文件', async () => {
180
+ const dir = setup();
181
+ const src = join(dir, 'src.txt');
182
+ const dest = join(dir, 'dest.txt');
183
+ writeFileSync(src, 'copy content', 'utf-8');
184
+ try {
185
+ const { copy_file } = await import('../../src/tools/builtin/filesystem/copy_file.js');
186
+ const result = await copy_file.execute({ source: src, destination: dest });
187
+ expect(result.success).toBe(true);
188
+ const { readFileSync } = await import('node:fs');
189
+ expect(readFileSync(dest, 'utf-8')).toBe('copy content');
190
+ } finally {
191
+ cleanup();
192
+ }
193
+ });
194
+
195
+ it('move_file 应移动文件', async () => {
196
+ const dir = setup();
197
+ const src = join(dir, 'src.txt');
198
+ const dest = join(dir, 'dest.txt');
199
+ writeFileSync(src, 'move content', 'utf-8');
200
+ try {
201
+ const { move_file } = await import('../../src/tools/builtin/filesystem/move_file.js');
202
+ const result = await move_file.execute({ source: src, destination: dest });
203
+ expect(result.success).toBe(true);
204
+ expect(existsSync(src)).toBe(false);
205
+ const { readFileSync } = await import('node:fs');
206
+ expect(readFileSync(dest, 'utf-8')).toBe('move content');
207
+ } finally {
208
+ cleanup();
209
+ }
210
+ });
211
+ });
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SkillLoader } from '../../src/skills/SkillLoader.js';
3
+ import { writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { randomUUID } from 'node:crypto';
7
+
8
+ describe('SkillLoader', () => {
9
+ function createTempSkillDir(): string {
10
+ const baseDir = join(tmpdir(), `deeper-test-${randomUUID()}`);
11
+ const skillDir = join(baseDir, 'test-skill');
12
+ mkdirSync(skillDir, { recursive: true });
13
+
14
+ const markdown = `---
15
+ name: test-skill
16
+ description: A test skill
17
+ version: 1.0.0
18
+ author: test
19
+ triggers:
20
+ - test
21
+ - testing
22
+ tools:
23
+ - read_file
24
+ - write_file
25
+ dependencies:
26
+ - test-lib
27
+ ---
28
+
29
+ # Test Skill
30
+
31
+ ## 概述
32
+ 这是一个测试 Skill。
33
+
34
+ ## 工作流程
35
+ 1. 读取文件
36
+ 2. 处理数据
37
+ 3. 写入结果
38
+ `;
39
+
40
+ writeFileSync(join(skillDir, 'skill.md'), markdown, 'utf-8');
41
+ return baseDir;
42
+ }
43
+
44
+ function createTempSkillWithCode(): string {
45
+ const baseDir = join(tmpdir(), `deeper-test-code-${randomUUID()}`);
46
+ const skillDir = join(baseDir, 'code-skill');
47
+ mkdirSync(skillDir, { recursive: true });
48
+
49
+ const markdown = `---
50
+ name: code-skill
51
+ description: A skill with code
52
+ version: 2.0.0
53
+ author: test
54
+ triggers:
55
+ - code
56
+ - script
57
+ tools:
58
+ - run_command
59
+ dependencies: []
60
+ ---
61
+
62
+ # Code Skill
63
+
64
+ ## Overview
65
+ A skill that uses JavaScript code.`;
66
+
67
+ const jsCode = `module.exports = function(params) { return { result: params.input }; };`;
68
+
69
+ writeFileSync(join(skillDir, 'skill.md'), markdown, 'utf-8');
70
+ writeFileSync(join(skillDir, 'skill.js'), jsCode, 'utf-8');
71
+ return baseDir;
72
+ }
73
+
74
+ it('应正确解析 Skill Markdown 文件', async () => {
75
+ const dir = createTempSkillDir();
76
+ try {
77
+ const loader = new SkillLoader([dir]);
78
+ const results = await loader.loadAll();
79
+ const found = results.find(r => r.skill.meta.name === 'test-skill');
80
+ expect(found).toBeDefined();
81
+ expect(found!.skill.meta.description).toBe('A test skill');
82
+ expect(found!.skill.meta.version).toBe('1.0.0');
83
+ expect(found!.skill.meta.triggers).toContain('test');
84
+ expect(found!.skill.meta.tools).toContain('read_file');
85
+ expect(found!.skill.meta.dependencies).toContain('test-lib');
86
+ expect(found!.skill.content).toContain('测试 Skill');
87
+ expect(found!.fromCodeFile).toBe(false);
88
+ } finally {
89
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
90
+ }
91
+ });
92
+
93
+ it('应正确解析带代码的 Skill', async () => {
94
+ const dir = createTempSkillWithCode();
95
+ try {
96
+ const loader = new SkillLoader([dir]);
97
+ const results = await loader.loadAll();
98
+ const found = results.find(r => r.skill.meta.name === 'code-skill');
99
+ expect(found).toBeDefined();
100
+ expect(found!.skill.meta.version).toBe('2.0.0');
101
+ expect(found!.skill.code).toBeDefined();
102
+ expect(found!.fromCodeFile).toBe(true);
103
+ } finally {
104
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
105
+ }
106
+ });
107
+
108
+ it('不存在的目录应不报错', async () => {
109
+ const loader = new SkillLoader([join(tmpdir(), `nonexistent-${randomUUID()}`)]);
110
+ const results = await loader.loadAll();
111
+ expect(Array.isArray(results)).toBe(true);
112
+ });
113
+
114
+ it('loadSingle 应正确加载单个 Skill', async () => {
115
+ const dir = createTempSkillDir();
116
+ try {
117
+ const loader = new SkillLoader([dir]);
118
+ const result = await loader.loadSingle(dir, 'test-skill');
119
+ expect(result).toBeDefined();
120
+ expect(result!.skill.meta.name).toBe('test-skill');
121
+ } finally {
122
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
123
+ }
124
+ });
125
+
126
+ it('没有 frontmatter 的 Markdown 应使用默认值', async () => {
127
+ const dir = join(tmpdir(), `deeper-test-nofm-${randomUUID()}`);
128
+ const skillDir = join(dir, 'no-fm');
129
+ mkdirSync(skillDir, { recursive: true });
130
+ writeFileSync(join(skillDir, 'skill.md'), '# Just a header\n\nNo frontmatter here.\n', 'utf-8');
131
+ try {
132
+ const loader = new SkillLoader([dir]);
133
+ const results = await loader.loadAll();
134
+ const found = results.find(r => r.skill.meta.name === 'unnamed');
135
+ expect(found).toBeDefined();
136
+ expect(found!.skill.meta.triggers).toEqual([]);
137
+ } finally {
138
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
139
+ }
140
+ });
141
+ });
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SkillRegistry } from '../../src/skills/SkillRegistry.js';
3
+ import type { Skill } from '../../src/skills/types.js';
4
+
5
+ function makeSkill(overrides: Partial<Skill> = {}): Skill {
6
+ return {
7
+ meta: {
8
+ name: 'pdf',
9
+ description: 'Handle PDF files',
10
+ version: '1.0.0',
11
+ author: 'deeper',
12
+ triggers: ['pdf', '合并pdf', '提取pdf'],
13
+ tools: ['read_file', 'write_file'],
14
+ dependencies: ['pdf-lib'],
15
+ },
16
+ content: '# PDF Skill\n\n处理 PDF 文件。',
17
+ ...overrides,
18
+ };
19
+ }
20
+
21
+ describe('SkillRegistry', () => {
22
+ let registry: SkillRegistry;
23
+
24
+ beforeEach(() => {
25
+ registry = new SkillRegistry();
26
+ });
27
+
28
+ it('应正确注册 Skill', () => {
29
+ registry.register(makeSkill());
30
+ expect(registry.size).toBe(1);
31
+ });
32
+
33
+ it('重复注册应抛出错误', () => {
34
+ registry.register(makeSkill());
35
+ expect(() => registry.register(makeSkill())).toThrow('already registered');
36
+ });
37
+
38
+ it('应能获取 Skill', () => {
39
+ registry.register(makeSkill());
40
+ const skill = registry.get('pdf');
41
+ expect(skill).toBeDefined();
42
+ expect(skill!.meta.name).toBe('pdf');
43
+ });
44
+
45
+ it('获取不存在的 Skill 返回 undefined', () => {
46
+ expect(registry.get('nonexistent')).toBeUndefined();
47
+ });
48
+
49
+ it('应能注销 Skill', () => {
50
+ registry.register(makeSkill());
51
+ expect(registry.unregister('pdf')).toBe(true);
52
+ expect(registry.size).toBe(0);
53
+ expect(registry.unregister('pdf')).toBe(false);
54
+ });
55
+
56
+ it('findByTrigger 应通过关键词匹配', () => {
57
+ registry.register(makeSkill());
58
+ const matches = registry.findByTrigger('合并pdf文件');
59
+ expect(matches).toHaveLength(1);
60
+
61
+ const noMatch = registry.findByTrigger('excel');
62
+ expect(noMatch).toHaveLength(0);
63
+ });
64
+
65
+ it('findByTool 应通过工具名查找', () => {
66
+ registry.register(makeSkill());
67
+ const matches = registry.findByTool('read_file');
68
+ expect(matches).toHaveLength(1);
69
+
70
+ const noMatch = registry.findByTool('unknown_tool');
71
+ expect(noMatch).toHaveLength(0);
72
+ });
73
+
74
+ it('findByDependency 应通过依赖查找', () => {
75
+ registry.register(makeSkill());
76
+ const matches = registry.findByDependency('pdf-lib');
77
+ expect(matches).toHaveLength(1);
78
+
79
+ const noMatch = registry.findByDependency('unknown-dep');
80
+ expect(noMatch).toHaveLength(0);
81
+ });
82
+
83
+ it('getAll 应返回所有 Skill', () => {
84
+ registry.register(makeSkill({ meta: { ...makeSkill().meta, name: 'pdf' } }));
85
+ const skill2: Skill = {
86
+ meta: {
87
+ name: 'web-dev',
88
+ description: 'Web development',
89
+ version: '1.0.0',
90
+ author: 'deeper',
91
+ triggers: ['web', 'html', 'css'],
92
+ tools: ['write_file'],
93
+ dependencies: [],
94
+ },
95
+ content: '# Web Dev Skill',
96
+ };
97
+ registry.register(skill2);
98
+ expect(registry.getAll()).toHaveLength(2);
99
+ });
100
+
101
+ it('getMeta 应返回所有元数据', () => {
102
+ registry.register(makeSkill());
103
+ const metas = registry.getMeta();
104
+ expect(metas).toHaveLength(1);
105
+ expect(metas[0].name).toBe('pdf');
106
+ });
107
+
108
+ it('clear 应清空所有 Skill', () => {
109
+ registry.register(makeSkill());
110
+ registry.clear();
111
+ expect(registry.size).toBe(0);
112
+ });
113
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { ToolExecutor } from '../../src/tools/ToolExecutor.js';
3
+ import { ToolRegistry } from '../../src/tools/ToolRegistry.js';
4
+ import type { Tool, ToolCall } from '../../src/tools/tool-types.js';
5
+
6
+ function safeTool(name: string): Tool {
7
+ return {
8
+ name,
9
+ description: `Tool ${name}`,
10
+ category: 'filesystem',
11
+ parameters: { type: 'object', properties: {}, required: [] },
12
+ dangerous: false,
13
+ requiresApproval: false,
14
+ async execute(params) {
15
+ return { success: true, output: JSON.stringify(params) };
16
+ },
17
+ };
18
+ }
19
+
20
+ function dangerousTool(name: string): Tool {
21
+ return {
22
+ name,
23
+ description: `Dangerous tool ${name}`,
24
+ category: 'shell',
25
+ parameters: { type: 'object', properties: {}, required: [] },
26
+ dangerous: true,
27
+ requiresApproval: true,
28
+ async execute() {
29
+ return { success: true, output: 'executed' };
30
+ },
31
+ };
32
+ }
33
+
34
+ describe('ToolExecutor', () => {
35
+ let registry: ToolRegistry;
36
+ let executor: ToolExecutor;
37
+
38
+ beforeEach(() => {
39
+ registry = new ToolRegistry();
40
+ executor = new ToolExecutor(registry);
41
+ });
42
+
43
+ it('应执行已注册的工具', async () => {
44
+ registry.register(safeTool('read_file'));
45
+ const call: ToolCall = {
46
+ id: 'call1',
47
+ name: 'read_file',
48
+ arguments: { path: '/test.ts' },
49
+ };
50
+ const result = await executor.execute(call);
51
+ expect(result.callId).toBe('call1');
52
+ expect(result.result.success).toBe(true);
53
+ expect(result.result.output).toContain('path');
54
+ });
55
+
56
+ it('未知工具应返回错误', async () => {
57
+ const call: ToolCall = {
58
+ id: 'call2',
59
+ name: 'unknown_tool',
60
+ arguments: {},
61
+ };
62
+ const result = await executor.execute(call);
63
+ expect(result.result.success).toBe(false);
64
+ expect(result.result.error).toContain('未知工具');
65
+ });
66
+
67
+ it('应拒绝危险工具 (非交互模式)', async () => {
68
+ registry.register(dangerousTool('nuke_system'));
69
+ const call: ToolCall = {
70
+ id: 'call3',
71
+ name: 'nuke_system',
72
+ arguments: {},
73
+ };
74
+ const result = await executor.execute(call);
75
+ expect(result.result.success).toBe(false);
76
+ expect(result.result.metadata).toBeDefined();
77
+ expect((result.result.metadata as any)?.requiresUserApproval).toBe(true);
78
+ });
79
+
80
+ it('应执行批量调用', async () => {
81
+ registry.register(safeTool('t1'));
82
+ registry.register(safeTool('t2'));
83
+ const calls: ToolCall[] = [
84
+ { id: 'c1', name: 't1', arguments: { a: 1 } },
85
+ { id: 'c2', name: 't2', arguments: { b: 2 } },
86
+ ];
87
+ const results = await executor.executeBatch(calls);
88
+ expect(results).toHaveLength(2);
89
+ expect(results[0].callId).toBe('c1');
90
+ expect(results[1].callId).toBe('c2');
91
+ });
92
+
93
+ it('应并行执行调用', async () => {
94
+ const startTimes: number[] = [];
95
+ const tool: Tool = {
96
+ name: 'slow_tool',
97
+ description: 'Slow tool',
98
+ category: 'filesystem',
99
+ parameters: { type: 'object', properties: {}, required: [] },
100
+ dangerous: false,
101
+ requiresApproval: false,
102
+ async execute(params) {
103
+ startTimes.push(Date.now());
104
+ await new Promise(r => setTimeout(r, 50));
105
+ return { success: true, output: 'done' };
106
+ },
107
+ };
108
+ registry.register(tool);
109
+ const calls: ToolCall[] = [
110
+ { id: 'c1', name: 'slow_tool', arguments: {} },
111
+ { id: 'c2', name: 'slow_tool', arguments: {} },
112
+ { id: 'c3', name: 'slow_tool', arguments: {} },
113
+ ];
114
+ const results = await executor.executeParallel(calls);
115
+ expect(results).toHaveLength(3);
116
+ expect(startTimes).toHaveLength(3);
117
+ // 并行执行:所有开始时间应在 30ms 内
118
+ const maxDiff = Math.max(...startTimes) - Math.min(...startTimes);
119
+ expect(maxDiff).toBeLessThan(30);
120
+ });
121
+
122
+ it('工具超时应返回错误', async () => {
123
+ const slowTool: Tool = {
124
+ name: 'very_slow',
125
+ description: 'Very slow tool',
126
+ category: 'filesystem',
127
+ parameters: { type: 'object', properties: {}, required: [] },
128
+ dangerous: false,
129
+ requiresApproval: false,
130
+ async execute() {
131
+ await new Promise(r => setTimeout(r, 5000));
132
+ return { success: true, output: 'done' };
133
+ },
134
+ };
135
+ registry.register(slowTool);
136
+ const call: ToolCall = { id: 'c1', name: 'very_slow', arguments: {} };
137
+ const result = await executor.execute(call, undefined, 100);
138
+ expect(result.result.success).toBe(false);
139
+ expect(result.result.error).toContain('超时');
140
+ }, 5000);
141
+
142
+ it('应处理工具抛出异常', async () => {
143
+ const errorTool: Tool = {
144
+ name: 'bad_tool',
145
+ description: 'Throws error',
146
+ category: 'filesystem',
147
+ parameters: { type: 'object', properties: {}, required: [] },
148
+ dangerous: false,
149
+ requiresApproval: false,
150
+ async execute() {
151
+ throw new Error('模拟错误');
152
+ },
153
+ };
154
+ registry.register(errorTool);
155
+ const call: ToolCall = { id: 'c1', name: 'bad_tool', arguments: {} };
156
+ const result = await executor.execute(call);
157
+ expect(result.result.success).toBe(false);
158
+ expect(result.result.error).toContain('模拟错误');
159
+ });
160
+ });