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.
- package/README.md +254 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +12067 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +1599 -0
- package/dist/index.js.map +1 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-implementation.md +24 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-plan.md +1248 -0
- package/docs/superpowers/specs/2026-05-14-deepercode-design.md +560 -0
- package/package.json +60 -0
- package/src/cli/bootstrap.ts +69 -0
- package/src/cli/chat-repl.ts +932 -0
- package/src/cli/commands/chat.ts +39 -0
- package/src/cli/commands/chat.tsx +39 -0
- package/src/cli/commands/config.ts +133 -0
- package/src/cli/commands/mcp.ts +172 -0
- package/src/cli/commands/run.ts +147 -0
- package/src/cli/commands/skill.ts +152 -0
- package/src/cli/index.ts +184 -0
- package/src/core/bugscan.ts +145 -0
- package/src/core/config.ts +285 -0
- package/src/core/constants.ts +49 -0
- package/src/core/eventbus.ts +202 -0
- package/src/core/logger.ts +109 -0
- package/src/core/storage.ts +96 -0
- package/src/index.ts +26 -0
- package/src/mcp/ConfigLoader.ts +74 -0
- package/src/mcp/MCPClient.ts +326 -0
- package/src/mcp/ResourceAdapter.ts +58 -0
- package/src/mcp/SSETransport.ts +133 -0
- package/src/mcp/StdioTransport.ts +116 -0
- package/src/mcp/ToolAdapter.ts +71 -0
- package/src/mcp/types.ts +58 -0
- package/src/memory/xmemory.ts +275 -0
- package/src/model/DeepSeekClient.ts +292 -0
- package/src/model/MessageBuilder.ts +155 -0
- package/src/model/RetryManager.ts +82 -0
- package/src/model/StreamHandler.ts +158 -0
- package/src/model/types.ts +86 -0
- package/src/skills/SkillCreator.ts +153 -0
- package/src/skills/SkillEngine.ts +158 -0
- package/src/skills/SkillExecutor.ts +107 -0
- package/src/skills/SkillLoader.ts +182 -0
- package/src/skills/SkillRegistry.ts +73 -0
- package/src/skills/SkillTrigger.ts +82 -0
- package/src/skills/types.ts +28 -0
- package/src/tools/ToolExecutor.ts +103 -0
- package/src/tools/ToolRegistry.ts +71 -0
- package/src/tools/ToolValidator.ts +103 -0
- package/src/tools/builtin/ai/context_summarize.ts +76 -0
- package/src/tools/builtin/ai/memory_store.ts +86 -0
- package/src/tools/builtin/ai/prompt_template.ts +71 -0
- package/src/tools/builtin/ai/skill_create.ts +53 -0
- package/src/tools/builtin/ai/subagent.ts +39 -0
- package/src/tools/builtin/ai/todo_manager.ts +157 -0
- package/src/tools/builtin/ai/token_count.ts +196 -0
- package/src/tools/builtin/ai/tool_create.ts +52 -0
- package/src/tools/builtin/code/analyze_deps.ts +72 -0
- package/src/tools/builtin/code/bug_scan.ts +80 -0
- package/src/tools/builtin/code/code_metrics.ts +111 -0
- package/src/tools/builtin/code/extract_function.ts +86 -0
- package/src/tools/builtin/code/format_code.ts +57 -0
- package/src/tools/builtin/code/generate_code.ts +75 -0
- package/src/tools/builtin/code/import_organizer.ts +82 -0
- package/src/tools/builtin/code/lint_code.ts +48 -0
- package/src/tools/builtin/code/parse_ast.ts +86 -0
- package/src/tools/builtin/code/refactor_code.ts +63 -0
- package/src/tools/builtin/code/type_check.ts +48 -0
- package/src/tools/builtin/data/chart_generate.ts +62 -0
- package/src/tools/builtin/data/csv_parse.ts +56 -0
- package/src/tools/builtin/data/data_diff.ts +79 -0
- package/src/tools/builtin/data/data_transform.ts +74 -0
- package/src/tools/builtin/data/data_validate.ts +75 -0
- package/src/tools/builtin/data/json_parse.ts +71 -0
- package/src/tools/builtin/data/template_render.ts +58 -0
- package/src/tools/builtin/data/toml_parse.ts +42 -0
- package/src/tools/builtin/data/xml_parse.ts +79 -0
- package/src/tools/builtin/data/yaml_parse.ts +42 -0
- package/src/tools/builtin/database/db_backup.ts +53 -0
- package/src/tools/builtin/database/db_restore.ts +51 -0
- package/src/tools/builtin/database/db_schema.ts +66 -0
- package/src/tools/builtin/database/nosql_query.ts +50 -0
- package/src/tools/builtin/database/orm_generate.ts +66 -0
- package/src/tools/builtin/database/redis_command.ts +46 -0
- package/src/tools/builtin/database/sql_migrate.ts +55 -0
- package/src/tools/builtin/database/sql_query.ts +60 -0
- package/src/tools/builtin/filesystem/batch_read.ts +56 -0
- package/src/tools/builtin/filesystem/batch_write.ts +67 -0
- package/src/tools/builtin/filesystem/copy_file.ts +36 -0
- package/src/tools/builtin/filesystem/create_dir.ts +30 -0
- package/src/tools/builtin/filesystem/delete_file.ts +30 -0
- package/src/tools/builtin/filesystem/diff_files.ts +47 -0
- package/src/tools/builtin/filesystem/edit_file.ts +47 -0
- package/src/tools/builtin/filesystem/file_info.ts +52 -0
- package/src/tools/builtin/filesystem/glob_find.ts +44 -0
- package/src/tools/builtin/filesystem/list_dir.ts +51 -0
- package/src/tools/builtin/filesystem/merge_files.ts +44 -0
- package/src/tools/builtin/filesystem/move_file.ts +37 -0
- package/src/tools/builtin/filesystem/read_file.ts +55 -0
- package/src/tools/builtin/filesystem/watch_file.ts +33 -0
- package/src/tools/builtin/filesystem/write_file.ts +45 -0
- package/src/tools/builtin/index.ts +244 -0
- package/src/tools/builtin/network/api_call.ts +79 -0
- package/src/tools/builtin/network/browser_action.ts +54 -0
- package/src/tools/builtin/network/check_url.ts +59 -0
- package/src/tools/builtin/network/download_file.ts +64 -0
- package/src/tools/builtin/network/graphql_query.ts +46 -0
- package/src/tools/builtin/network/http_request.ts +61 -0
- package/src/tools/builtin/network/parse_html.ts +101 -0
- package/src/tools/builtin/network/proxy_request.ts +53 -0
- package/src/tools/builtin/network/screenshot_page.ts +58 -0
- package/src/tools/builtin/network/web_fetch.ts +70 -0
- package/src/tools/builtin/network/web_search.ts +128 -0
- package/src/tools/builtin/network/websocket_connect.ts +70 -0
- package/src/tools/builtin/project/build_project.ts +68 -0
- package/src/tools/builtin/project/config_manage.ts +99 -0
- package/src/tools/builtin/project/coverage_report.ts +59 -0
- package/src/tools/builtin/project/docker_manage.ts +90 -0
- package/src/tools/builtin/project/env_manage.ts +88 -0
- package/src/tools/builtin/project/npm_manage.ts +71 -0
- package/src/tools/builtin/project/project_init.ts +59 -0
- package/src/tools/builtin/project/run_test.ts +74 -0
- package/src/tools/builtin/search/codebase_search.ts +76 -0
- package/src/tools/builtin/search/find_definition.ts +84 -0
- package/src/tools/builtin/search/find_references.ts +75 -0
- package/src/tools/builtin/search/fuzzy_find.ts +75 -0
- package/src/tools/builtin/search/grep_search.ts +90 -0
- package/src/tools/builtin/search/regex_find.ts +91 -0
- package/src/tools/builtin/search/search_docs.ts +51 -0
- package/src/tools/builtin/search/search_package.ts +50 -0
- package/src/tools/builtin/search/symbol_search.ts +82 -0
- package/src/tools/builtin/search/text_search.ts +63 -0
- package/src/tools/builtin/security/decrypt_file.ts +54 -0
- package/src/tools/builtin/security/encrypt_file.ts +52 -0
- package/src/tools/builtin/security/hash_generate.ts +48 -0
- package/src/tools/builtin/security/jwt_decode.ts +53 -0
- package/src/tools/builtin/security/secret_scan.ts +82 -0
- package/src/tools/builtin/security/vulnerability_check.ts +71 -0
- package/src/tools/builtin/shell/background_terminal.ts +38 -0
- package/src/tools/builtin/shell/check_status.ts +48 -0
- package/src/tools/builtin/shell/interactive_terminal.ts +31 -0
- package/src/tools/builtin/shell/kill_terminal.ts +29 -0
- package/src/tools/builtin/shell/list_terminals.ts +61 -0
- package/src/tools/builtin/shell/pipe_commands.ts +55 -0
- package/src/tools/builtin/shell/process-pool.ts +150 -0
- package/src/tools/builtin/shell/run_async.ts +73 -0
- package/src/tools/builtin/shell/run_command.ts +60 -0
- package/src/tools/builtin/shell/send_ctrl_keys.ts +43 -0
- package/src/tools/builtin/shell/send_keys.ts +36 -0
- package/src/tools/builtin/shell/send_text.ts +35 -0
- package/src/tools/builtin/shell/shell_script.ts +65 -0
- package/src/tools/builtin/shell/stop_command.ts +40 -0
- package/src/tools/builtin/shell/terminal_resize.ts +31 -0
- package/src/tools/builtin/shell/terminal_screenshot.ts +28 -0
- package/src/tools/builtin/system/log_viewer.ts +89 -0
- package/src/tools/builtin/system/notify_user.ts +55 -0
- package/src/tools/builtin/system/process_list.ts +66 -0
- package/src/tools/builtin/system/resource_monitor.ts +66 -0
- package/src/tools/builtin/system/system_info.ts +41 -0
- package/src/tools/tool-types.ts +97 -0
- package/src/ui/AgentTree.tsx +98 -0
- package/src/ui/App.tsx +46 -0
- package/src/ui/ChatView.tsx +278 -0
- package/src/ui/ConfirmDialog.tsx +68 -0
- package/src/ui/DiffView.tsx +64 -0
- package/src/ui/FilePreview.tsx +59 -0
- package/src/ui/InputBox.tsx +267 -0
- package/src/ui/MessageBubble.tsx +30 -0
- package/src/ui/Spinner.tsx +35 -0
- package/src/ui/StatusBar.tsx +41 -0
- package/src/ui/ToolCallCard.tsx +73 -0
- package/src/ui/ansi.ts +50 -0
- package/src/ui/markdown.ts +238 -0
- package/src/ui/themes/dark.ts +4 -0
- package/src/ui/themes/default.ts +25 -0
- package/src/ui/themes/light.ts +14 -0
- package/tests/unit/BuiltinTools.test.ts +129 -0
- package/tests/unit/BuiltinToolsIntegration.test.ts +111 -0
- package/tests/unit/FilesystemTools.test.ts +211 -0
- package/tests/unit/SkillLoader.test.ts +141 -0
- package/tests/unit/SkillRegistry.test.ts +113 -0
- package/tests/unit/ToolExecutor.test.ts +160 -0
- package/tests/unit/ToolRegistry.test.ts +103 -0
- package/tests/unit/ToolValidator.test.ts +137 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const A = { R: '\x1b[0m', b: '\x1b[1m', d: '\x1b[2m', i: '\x1b[3m', u: '\x1b[4m', g: '\x1b[32m', inv: '\x1b[7m' };
|
|
2
|
+
function ansi(code: string, text: string) { return code + text + A.R; }
|
|
3
|
+
|
|
4
|
+
export class MarkdownStreamRenderer {
|
|
5
|
+
private lineBuf = '';
|
|
6
|
+
private inCodeBlock = false;
|
|
7
|
+
private codeLang = '';
|
|
8
|
+
private codeContent = '';
|
|
9
|
+
private codeDropped = 0;
|
|
10
|
+
private seenFirstLine = false;
|
|
11
|
+
private tableRows: string[] = [];
|
|
12
|
+
private inTable = false;
|
|
13
|
+
private tableAligns: ('L'|'C'|'R')[] = [];
|
|
14
|
+
|
|
15
|
+
private static readonly MAX_LINE = 4096;
|
|
16
|
+
private static readonly MAX_CODE = 51200;
|
|
17
|
+
|
|
18
|
+
feed(chunk: string): string | null {
|
|
19
|
+
this.lineBuf += chunk;
|
|
20
|
+
if (this.lineBuf.length > MarkdownStreamRenderer.MAX_LINE) this.lineBuf = this.lineBuf.slice(-MarkdownStreamRenderer.MAX_LINE);
|
|
21
|
+
|
|
22
|
+
const idx = this.lineBuf.indexOf('\n');
|
|
23
|
+
if (idx === -1) return null;
|
|
24
|
+
const line = this.lineBuf.slice(0, idx);
|
|
25
|
+
this.lineBuf = this.lineBuf.slice(idx + 1);
|
|
26
|
+
|
|
27
|
+
if (this.inCodeBlock) {
|
|
28
|
+
if (/^```\s*$/.test(line.trim())) {
|
|
29
|
+
this.inCodeBlock = false;
|
|
30
|
+
let out = this.flushCodeBlock() + '\n';
|
|
31
|
+
if (this.codeLang.toLowerCase() === 'markdown') out = '';
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
if (this.codeContent.length < MarkdownStreamRenderer.MAX_CODE) {
|
|
35
|
+
this.codeContent += line + '\n';
|
|
36
|
+
} else {
|
|
37
|
+
this.codeDropped++;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (/^```(\w*)\s*$/.test(line.trim())) {
|
|
43
|
+
this.inCodeBlock = true;
|
|
44
|
+
this.codeLang = line.trim().slice(3).trim();
|
|
45
|
+
this.codeContent = '';
|
|
46
|
+
if (!this.seenFirstLine) { this.seenFirstLine = true; return ansi(A.d, ` ┌ ${this.codeLang || 'code'}`) + '\n'; }
|
|
47
|
+
return `\n` + ansi(A.d, ` ┌ ${this.codeLang || 'code'}`) + '\n';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Table detection ──
|
|
51
|
+
const isPipeLine = /^\|.+|\s+\|/.test(line) && line.includes('|');
|
|
52
|
+
const isSepLine = /^\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(line.trim()) && !/[a-zA-Z\u4e00-\u9fff]/.test(line.trim());
|
|
53
|
+
|
|
54
|
+
if (isPipeLine) {
|
|
55
|
+
if (!this.inTable) {
|
|
56
|
+
this.inTable = true;
|
|
57
|
+
this.tableRows = [];
|
|
58
|
+
this.tableAligns = [];
|
|
59
|
+
}
|
|
60
|
+
if (isSepLine) {
|
|
61
|
+
// Parse alignment from separator row
|
|
62
|
+
const cells = line.split('|').filter(c => /[\-:]/.test(c));
|
|
63
|
+
this.tableAligns = cells.map(c => {
|
|
64
|
+
const t = c.trim();
|
|
65
|
+
if (t.startsWith(':') && t.endsWith(':')) return 'C';
|
|
66
|
+
if (t.endsWith(':')) return 'R';
|
|
67
|
+
return 'L';
|
|
68
|
+
});
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
this.tableRows.push(line);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// End of table
|
|
76
|
+
if (this.inTable) {
|
|
77
|
+
const out = this.renderTable();
|
|
78
|
+
this.inTable = false;
|
|
79
|
+
this.tableRows = [];
|
|
80
|
+
this.tableAligns = [];
|
|
81
|
+
this.seenFirstLine = true;
|
|
82
|
+
return out + '\n' + this.renderLine(line) + '\n';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.seenFirstLine = true;
|
|
86
|
+
return this.renderLine(line) + '\n';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
flush(): string {
|
|
90
|
+
let out = '';
|
|
91
|
+
if (this.inTable) {
|
|
92
|
+
out += this.renderTable();
|
|
93
|
+
this.inTable = false;
|
|
94
|
+
this.tableRows = [];
|
|
95
|
+
}
|
|
96
|
+
if (this.inCodeBlock) {
|
|
97
|
+
out += this.flushCodeBlock();
|
|
98
|
+
this.inCodeBlock = false;
|
|
99
|
+
}
|
|
100
|
+
if (this.lineBuf) {
|
|
101
|
+
out += this.lineBuf;
|
|
102
|
+
this.lineBuf = '';
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
reset(): void {
|
|
108
|
+
this.lineBuf = ''; this.inCodeBlock = false; this.codeLang = ''; this.codeContent = ''; this.codeDropped = 0; this.seenFirstLine = false;
|
|
109
|
+
this.inTable = false; this.tableRows = []; this.tableAligns = [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Table rendering ──
|
|
113
|
+
|
|
114
|
+
private renderTable(): string {
|
|
115
|
+
if (this.tableRows.length === 0) return '';
|
|
116
|
+
const headerRow = this.tableRows[0];
|
|
117
|
+
const dataRows = this.tableRows.slice(this.tableAligns.length === 0 ? 0 : 1);
|
|
118
|
+
const headerCells = headerRow ? this.splitCells(headerRow) : [];
|
|
119
|
+
const allRows: string[][] = [];
|
|
120
|
+
|
|
121
|
+
if (this.tableAligns.length > 0 && headerRow) {
|
|
122
|
+
allRows.push(headerCells);
|
|
123
|
+
allRows.push(...dataRows.map(r => this.splitCells(r)));
|
|
124
|
+
} else {
|
|
125
|
+
allRows.push(...this.tableRows.map(r => this.splitCells(r)));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const colCount = Math.max(...allRows.map(r => r.length), 1);
|
|
129
|
+
const colWidths: number[] = Array(colCount).fill(3);
|
|
130
|
+
for (const row of allRows) {
|
|
131
|
+
for (let i = 0; i < row.length; i++) {
|
|
132
|
+
const w = this.visualLen(row[i]);
|
|
133
|
+
if (w > colWidths[i]) colWidths[i] = Math.min(w, 30);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const aligns = Array(colCount).fill('L');
|
|
138
|
+
for (let i = 0; i < this.tableAligns.length && i < colCount; i++) aligns[i] = this.tableAligns[i];
|
|
139
|
+
|
|
140
|
+
const F = ansi(A.d + '\x1b[90m', '│');
|
|
141
|
+
let out = '';
|
|
142
|
+
|
|
143
|
+
// Top border
|
|
144
|
+
out += A.d + '\x1b[90m' + ' ┌' + colWidths.map(w => '─'.repeat(Math.max(0, w + 2))).join('┬') + '┐' + A.R + '\n';
|
|
145
|
+
|
|
146
|
+
// Header row
|
|
147
|
+
if (allRows.length > 0 && colWidths.length > 0) {
|
|
148
|
+
out += ` ${F} ` + allRows[0].map((c, i) => this.padCell(c, colWidths[i] || 3, 'C', true)).join(` ${F} `) + ` ${F}\n`;
|
|
149
|
+
// Separator
|
|
150
|
+
out += A.d + '\x1b[90m' + ' ├' + colWidths.map(w => '─'.repeat(Math.max(0, w + 2))).join('┼') + '┤' + A.R + '\n';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Data rows
|
|
154
|
+
const maxRows = 12;
|
|
155
|
+
for (let r = 1; r < allRows.length && r <= maxRows; r++) {
|
|
156
|
+
out += ` ${F} ` + allRows[r].map((c, i) => this.padCell(c, colWidths[i] || 3, aligns[i] || 'L', false)).join(` ${F} `) + ` ${F}\n`;
|
|
157
|
+
}
|
|
158
|
+
if (allRows.length > maxRows + 1) {
|
|
159
|
+
out += ansi(A.d + '\x1b[90m', ` ${F} ${ansi(A.d, '...')} `.padEnd(colWidths.reduce((a, w) => a + w + 3, 3))) + ` ${F}` + '\n';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Bottom border
|
|
163
|
+
out += A.d + '\x1b[90m' + ' └' + colWidths.map(w => '─'.repeat(Math.max(0, w + 2))).join('┴') + '┘' + A.R;
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private splitCells(line: string): string[] {
|
|
168
|
+
let s = line.trim();
|
|
169
|
+
if (s.startsWith('|')) s = s.slice(1);
|
|
170
|
+
if (s.endsWith('|')) s = s.slice(0, -1);
|
|
171
|
+
return s.split('|').map(c => c.trim());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private visualLen(s: string): number {
|
|
175
|
+
return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').replace(/[^\x00-\xff]/g, ' ').length;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private padCell(text: string, width: number, align: string, bold: boolean): string {
|
|
179
|
+
const raw = text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
180
|
+
const visual = this.visualLen(raw);
|
|
181
|
+
const pad = Math.max(0, width - visual);
|
|
182
|
+
const left = align === 'R' ? pad : align === 'C' ? Math.floor(pad / 2) : 0;
|
|
183
|
+
const right = Math.max(0, pad - left);
|
|
184
|
+
const content = bold ? ansi(A.b, raw) : raw;
|
|
185
|
+
return ' '.repeat(left) + content + ' '.repeat(right);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Flush code ──
|
|
189
|
+
|
|
190
|
+
private flushCodeBlock(): string {
|
|
191
|
+
const lines = this.codeContent.split('\n').filter(l => l || true);
|
|
192
|
+
let out = '';
|
|
193
|
+
const max = Math.min(lines.length, 20);
|
|
194
|
+
for (let i = 0; i < max; i++) {
|
|
195
|
+
out += ` ${ansi(A.d + '\x1b[90m', String(i + 1).padStart(3, ' '))} ${ansi(A.d, '│')} ${lines[i]}\n`;
|
|
196
|
+
}
|
|
197
|
+
if (lines.length > 20) out += ansi(A.d + '\x1b[90m', ` ... (${lines.length - 20} lines omitted)\n`);
|
|
198
|
+
if (this.codeDropped > 0) out += ansi(A.d + '\x1b[90m', ` ... (${this.codeDropped} lines dropped, code too large)\n`);
|
|
199
|
+
out += ansi(A.d, ` └ ${this.codeLang || 'code'}`);
|
|
200
|
+
this.codeContent = '';
|
|
201
|
+
this.codeDropped = 0;
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── Render line ──
|
|
206
|
+
|
|
207
|
+
private renderLine(line: string): string {
|
|
208
|
+
const t = line.trim();
|
|
209
|
+
if (t === '') return '';
|
|
210
|
+
|
|
211
|
+
let m: RegExpMatchArray | null;
|
|
212
|
+
if ((m = line.match(/^#{4}\s+(.+)/))) return ansi(A.b + '\x1b[36m', ` ## ${m[1]}`);
|
|
213
|
+
if ((m = line.match(/^#{3}\s+(.+)/))) return ansi(A.b + '\x1b[33m', ` ▸ ${m[1]}`);
|
|
214
|
+
if ((m = line.match(/^#{2}\s+(.+)/))) return ansi(A.b + '\x1b[34m', ` ▸▸ ${m[1]}`);
|
|
215
|
+
if ((m = line.match(/^#\s+(.+)/))) return ansi(A.b + '\x1b[35m', ` █ ${m[1]}`);
|
|
216
|
+
if (line.startsWith('> ')) return ansi(A.d + '\x1b[90m', ` │ ${line.slice(2)}`);
|
|
217
|
+
if (/^[-*_]{3,}\s*$/.test(t) && !t.includes('|')) return ansi(A.d + '\x1b[90m', ' ──'.repeat(12));
|
|
218
|
+
if ((m = line.match(/^(\s*)(\d+)\.\s+(.+)/))) { const sp = m[1].length; return ' '.repeat(Math.max(0, sp)) + ansi(A.b + '\x1b[36m', `${m[2]}.`) + ' ' + this.inline(m[3]); }
|
|
219
|
+
if ((m = line.match(/^(\s*)[*+-]\s+(.+)/))) { const sp = m[1].length; return ' '.repeat(Math.max(0, sp)) + ansi(A.g, '• ') + this.inline(m[2]); }
|
|
220
|
+
if ((m = line.match(/^(\s*)- \[([ x])\]\s+(.+)/))) {
|
|
221
|
+
const ck = m[2] === 'x' ? ansi(A.g, '✓') : ansi(A.d + '\x1b[90m', '○');
|
|
222
|
+
return ` ${ck} ${this.inline(m[3])}`;
|
|
223
|
+
}
|
|
224
|
+
return ' ' + this.inline(line);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private inline(text: string): string {
|
|
228
|
+
let s = text;
|
|
229
|
+
s = s.replace(/`([^`]+)`/g, (_, c) => ansi(A.inv + '\x1b[33m', c));
|
|
230
|
+
s = s.replace(/\*\*\*(.+?)\*\*\*/g, (_, c) => ansi(A.b + A.i, c));
|
|
231
|
+
s = s.replace(/\*\*(.+?)\*\*/g, (_, c) => ansi(A.b, c));
|
|
232
|
+
s = s.replace(/\*(.+?)\*/g, (_, c) => ansi(A.i, c));
|
|
233
|
+
s = s.replace(/~~(.+?)~~/g, (_, c) => ansi(A.d + '\x1b[9m', c));
|
|
234
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, txt, url) =>
|
|
235
|
+
ansi(A.u + '\x1b[36m', txt) + ansi(A.d + '\x1b[90m', ` (${url.slice(0, 40)})`));
|
|
236
|
+
return s;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Theme {
|
|
2
|
+
primary: string;
|
|
3
|
+
secondary: string;
|
|
4
|
+
success: string;
|
|
5
|
+
warning: string;
|
|
6
|
+
error: string;
|
|
7
|
+
text: string;
|
|
8
|
+
dimText: string;
|
|
9
|
+
border: string;
|
|
10
|
+
background: string;
|
|
11
|
+
cardBackground: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const defaultTheme: Theme = {
|
|
15
|
+
primary: '#00BFA5',
|
|
16
|
+
secondary: '#7C4DFF',
|
|
17
|
+
success: '#00E676',
|
|
18
|
+
warning: '#FFAB40',
|
|
19
|
+
error: '#FF5252',
|
|
20
|
+
text: '#E0E0E0',
|
|
21
|
+
dimText: '#757575',
|
|
22
|
+
border: '#424242',
|
|
23
|
+
background: '#121212',
|
|
24
|
+
cardBackground: '#1E1E1E',
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Theme } from './default.ts';
|
|
2
|
+
|
|
3
|
+
export const lightTheme: Theme = {
|
|
4
|
+
primary: '#00897B',
|
|
5
|
+
secondary: '#6200EA',
|
|
6
|
+
success: '#00C853',
|
|
7
|
+
warning: '#FF6D00',
|
|
8
|
+
error: '#D50000',
|
|
9
|
+
text: '#212121',
|
|
10
|
+
dimText: '#9E9E9E',
|
|
11
|
+
border: '#E0E0E0',
|
|
12
|
+
background: '#FAFAFA',
|
|
13
|
+
cardBackground: '#FFFFFF',
|
|
14
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { writeFileSync, 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('GrepSearch 工具', () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
|
|
10
|
+
function setup() {
|
|
11
|
+
testDir = join(tmpdir(), `deeper-grep-${randomUUID()}`);
|
|
12
|
+
mkdirSync(testDir, { recursive: true });
|
|
13
|
+
writeFileSync(join(testDir, 'file1.ts'), 'export function hello() {\n return "world";\n}\n', 'utf-8');
|
|
14
|
+
writeFileSync(join(testDir, 'file2.ts'), 'const x = hello();\nconst y = 42;\n', 'utf-8');
|
|
15
|
+
mkdirSync(join(testDir, 'subdir'), { recursive: true });
|
|
16
|
+
writeFileSync(join(testDir, 'subdir', 'nested.ts'), 'import { test } from "vitest";', 'utf-8');
|
|
17
|
+
return testDir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cleanup() {
|
|
21
|
+
if (existsSync(testDir)) rmSync(testDir, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
it('grep_search 应找到匹配的行', async () => {
|
|
25
|
+
const dir = setup();
|
|
26
|
+
try {
|
|
27
|
+
const { grep_search } = await import('../../src/tools/builtin/search/grep_search.js');
|
|
28
|
+
const result = await grep_search.execute({ pattern: 'hello', dir_path: dir });
|
|
29
|
+
expect(result.success).toBe(true);
|
|
30
|
+
expect(result.output).toContain('hello');
|
|
31
|
+
} finally {
|
|
32
|
+
cleanup();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('grep_search 不匹配时应返回提示', async () => {
|
|
37
|
+
const dir = setup();
|
|
38
|
+
try {
|
|
39
|
+
const { grep_search } = await import('../../src/tools/builtin/search/grep_search.js');
|
|
40
|
+
const result = await grep_search.execute({ pattern: 'NONEXISTENT_PATTERN', dir_path: dir });
|
|
41
|
+
expect(result.success).toBe(true);
|
|
42
|
+
expect(result.output).not.toContain('NONEXISTENT_PATTERN');
|
|
43
|
+
} finally {
|
|
44
|
+
cleanup();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('RunCommand 工具', () => {
|
|
50
|
+
it('run_command 应执行简单命令', async () => {
|
|
51
|
+
const { run_command } = await import('../../src/tools/builtin/shell/run_command.js');
|
|
52
|
+
const result = await run_command.execute({ command: 'echo hello_from_test' });
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
expect(result.output).toContain('hello_from_test');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('run_command 应执行 node --version', async () => {
|
|
58
|
+
const { run_command } = await import('../../src/tools/builtin/shell/run_command.js');
|
|
59
|
+
const result = await run_command.execute({ command: 'node --version' });
|
|
60
|
+
expect(result.success).toBe(true);
|
|
61
|
+
expect(result.output).toMatch(/v\d+/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('run_command 错误命令应返回失败', async () => {
|
|
65
|
+
const { run_command } = await import('../../src/tools/builtin/shell/run_command.js');
|
|
66
|
+
const result = await run_command.execute({ command: 'nonexistent_command_xyz' });
|
|
67
|
+
expect(result.success).toBe(false);
|
|
68
|
+
expect(result.error).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('JSON/YAML 数据工具', () => {
|
|
73
|
+
it('json_parse 应正确解析 JSON', async () => {
|
|
74
|
+
const { json_parse } = await import('../../src/tools/builtin/data/json_parse.js');
|
|
75
|
+
const result = await json_parse.execute({ content: '{"name":"deeper","version":"1.0.0"}' });
|
|
76
|
+
expect(result.success).toBe(true);
|
|
77
|
+
expect(result.output).toContain('deeper');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('json_parse 对无效 JSON 应返回错误', async () => {
|
|
81
|
+
const { json_parse } = await import('../../src/tools/builtin/data/json_parse.js');
|
|
82
|
+
const result = await json_parse.execute({ content: 'not valid json' });
|
|
83
|
+
expect(result.success).toBe(false);
|
|
84
|
+
expect(result.error).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('yaml_parse 应正确解析 YAML', async () => {
|
|
88
|
+
const { yaml_parse } = await import('../../src/tools/builtin/data/yaml_parse.js');
|
|
89
|
+
const result = await yaml_parse.execute({ content: 'name: deeper\nversion: 1.0.0' });
|
|
90
|
+
expect(result.success).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('csv_parse 应正确解析 CSV', async () => {
|
|
94
|
+
const { csv_parse } = await import('../../src/tools/builtin/data/csv_parse.js');
|
|
95
|
+
const result = await csv_parse.execute({ content: 'name,version\ndeeper,1.0.0' });
|
|
96
|
+
expect(result.success).toBe(true);
|
|
97
|
+
expect(result.output).toContain('deeper');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('AI 工具', () => {
|
|
102
|
+
it('token_count 应正确计数', async () => {
|
|
103
|
+
const { token_count } = await import('../../src/tools/builtin/ai/token_count.js');
|
|
104
|
+
const result = await token_count.execute({ text: 'Hello 你好 world' });
|
|
105
|
+
expect(result.success).toBe(true);
|
|
106
|
+
expect(result.output).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('memory_store 应存储记忆', async () => {
|
|
110
|
+
const { memory_store } = await import('../../src/tools/builtin/ai/memory_store.js');
|
|
111
|
+
const result = await memory_store.execute({ action: 'set', key: 'test_key', value: 'test_value' });
|
|
112
|
+
expect(result.success).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('系统工具', () => {
|
|
117
|
+
it('system_info 应返回系统信息', async () => {
|
|
118
|
+
const { system_info } = await import('../../src/tools/builtin/system/system_info.js');
|
|
119
|
+
const result = await system_info.execute({});
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
expect(result.output).toBeDefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('process_list 应返回进程列表', async () => {
|
|
125
|
+
const { process_list } = await import('../../src/tools/builtin/system/process_list.js');
|
|
126
|
+
const result = await process_list.execute({});
|
|
127
|
+
expect(result.success).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { builtinTools } from '../../src/tools/builtin/index.js';
|
|
3
|
+
import { TOOL_CATEGORIES } from '../../src/core/constants.js';
|
|
4
|
+
|
|
5
|
+
describe('内置工具集成', () => {
|
|
6
|
+
it('应至少有 100 个内置工具', () => {
|
|
7
|
+
expect(builtinTools.length).toBeGreaterThanOrEqual(100);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('所有工具名称应唯一', () => {
|
|
11
|
+
const names = builtinTools.map(t => t.name);
|
|
12
|
+
const unique = new Set(names);
|
|
13
|
+
expect(unique.size).toBe(builtinTools.length);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('所有工具应都有必需的属性', () => {
|
|
17
|
+
for (const tool of builtinTools) {
|
|
18
|
+
expect(tool.name).toBeTruthy();
|
|
19
|
+
expect(tool.description).toBeTruthy();
|
|
20
|
+
expect(tool.category).toBeTruthy();
|
|
21
|
+
expect(tool.parameters).toBeDefined();
|
|
22
|
+
expect(tool.parameters.type).toBe('object');
|
|
23
|
+
expect(typeof tool.execute).toBe('function');
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('所有工具分类应在允许范围内', () => {
|
|
28
|
+
for (const tool of builtinTools) {
|
|
29
|
+
expect(TOOL_CATEGORIES).toContain(tool.category);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('每个分类应至少有 5 个工具', () => {
|
|
34
|
+
for (const category of TOOL_CATEGORIES) {
|
|
35
|
+
const count = builtinTools.filter(t => t.category === category).length;
|
|
36
|
+
expect(count).toBeGreaterThanOrEqual(5);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('文件系统分类应至少有 15 个工具', () => {
|
|
41
|
+
const fsTools = builtinTools.filter(t => t.category === 'filesystem');
|
|
42
|
+
expect(fsTools.length).toBeGreaterThanOrEqual(15);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Shell 分类应至少有 15 个工具', () => {
|
|
46
|
+
const shellTools = builtinTools.filter(t => t.category === 'shell');
|
|
47
|
+
expect(shellTools.length).toBeGreaterThanOrEqual(15);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('安全工具 execute 应返回 Promise 对象', () => {
|
|
51
|
+
const testTools = builtinTools.filter(t =>
|
|
52
|
+
['read_file', 'list_dir', 'file_info', 'token_count',
|
|
53
|
+
'system_info', 'web_fetch', 'grep_search',
|
|
54
|
+
'codebase_search', 'json_parse', 'csv_parse',
|
|
55
|
+
'yaml_parse', 'toml_parse', 'data_transform',
|
|
56
|
+
'text_search', 'fuzzy_find', 'find_references',
|
|
57
|
+
'find_definition', 'symbol_search', 'search_package',
|
|
58
|
+
'search_docs', 'process_list', 'resource_monitor',
|
|
59
|
+
'log_viewer', 'check_url', 'parse_html'
|
|
60
|
+
].includes(t.name)
|
|
61
|
+
);
|
|
62
|
+
for (const tool of testTools) {
|
|
63
|
+
const result = tool.execute({});
|
|
64
|
+
expect(result).toBeInstanceOf(Promise);
|
|
65
|
+
}
|
|
66
|
+
expect(testTools.length).toBeGreaterThanOrEqual(10);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('工具定义应具有正确的格式', () => {
|
|
70
|
+
for (const tool of builtinTools) {
|
|
71
|
+
const def: any = {
|
|
72
|
+
name: tool.name,
|
|
73
|
+
description: tool.description,
|
|
74
|
+
category: tool.category,
|
|
75
|
+
parameters: tool.parameters,
|
|
76
|
+
};
|
|
77
|
+
expect(def.name).toBeTruthy();
|
|
78
|
+
expect(def.description).toBeTruthy();
|
|
79
|
+
expect(def.parameters).toBeDefined();
|
|
80
|
+
expect(typeof def.parameters).toBe('object');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('工具安全分级', () => {
|
|
86
|
+
it('安全工具应正确标记', () => {
|
|
87
|
+
const safeTools = builtinTools.filter(t => t.dangerous === false && t.requiresApproval === false);
|
|
88
|
+
expect(safeTools.length).toBeGreaterThan(0);
|
|
89
|
+
// 读文件、列表操作等应是安全的
|
|
90
|
+
const readFile = builtinTools.find(t => t.name === 'read_file');
|
|
91
|
+
expect(readFile).toBeDefined();
|
|
92
|
+
expect(readFile!.dangerous).toBe(false);
|
|
93
|
+
expect(readFile!.requiresApproval).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('危险工具应正确标记', () => {
|
|
97
|
+
const dangerousTools = builtinTools.filter(t => t.dangerous === true);
|
|
98
|
+
// 可能有 background_terminal, kill_terminal 等
|
|
99
|
+
expect(dangerousTools.length).toBeGreaterThanOrEqual(0); // 可能不直接标记 dangerous
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('write_file 应需要确认', () => {
|
|
103
|
+
const writeFile = builtinTools.find(t => t.name === 'write_file');
|
|
104
|
+
expect(writeFile).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('delete_file 应需要确认', () => {
|
|
108
|
+
const deleteFile = builtinTools.find(t => t.name === 'delete_file');
|
|
109
|
+
expect(deleteFile).toBeDefined();
|
|
110
|
+
});
|
|
111
|
+
});
|