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,59 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import type { Tool } from '../../tool-types.js';
|
|
5
|
+
|
|
6
|
+
export const project_init: Tool = {
|
|
7
|
+
name: 'project_init',
|
|
8
|
+
description: '初始化新项目(npm / 框架模板)',
|
|
9
|
+
category: 'project',
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
template: { type: 'string', description: '模板: npm, vite, next, express, react', enum: ['npm', 'vite', 'next', 'express', 'react'] },
|
|
14
|
+
name: { type: 'string', description: '项目名称' },
|
|
15
|
+
cwd: { type: 'string', description: '创建目录' },
|
|
16
|
+
},
|
|
17
|
+
required: ['template', 'name'],
|
|
18
|
+
},
|
|
19
|
+
dangerous: false,
|
|
20
|
+
requiresApproval: true,
|
|
21
|
+
async execute(params) {
|
|
22
|
+
try {
|
|
23
|
+
const template = params.template as string;
|
|
24
|
+
const name = params.name as string;
|
|
25
|
+
const cwd = (params.cwd as string) ?? process.cwd();
|
|
26
|
+
const registry = 'https://registry.npmmirror.com';
|
|
27
|
+
|
|
28
|
+
const commands: Record<string, string> = {
|
|
29
|
+
npm: `npm init -y --registry ${registry}`,
|
|
30
|
+
vite: `npm create vite@latest ${name} -- --template react-ts`,
|
|
31
|
+
next: `npx create-next-app@latest ${name} --ts --app --no-tailwind --src-dir`,
|
|
32
|
+
express: `npx express-generator ${name}`,
|
|
33
|
+
react: `npx create-react-app ${name}`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const cmd = commands[template] || commands.npm;
|
|
37
|
+
const output = execSync(cmd, {
|
|
38
|
+
cwd,
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
timeout: 120000,
|
|
41
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
42
|
+
stdio: 'pipe',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
output: `项目已创建: ${name} (${template})\n${output.slice(0, 5000)}`,
|
|
48
|
+
metadata: { template, name, cwd },
|
|
49
|
+
};
|
|
50
|
+
} catch (err: unknown) {
|
|
51
|
+
const e = err as { message?: string; stdout?: string; stderr?: string };
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: e.message || String(err),
|
|
55
|
+
output: (e.stdout || '') + (e.stderr || ''),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import type { Tool } from '../../tool-types.js';
|
|
5
|
+
|
|
6
|
+
export const run_test: Tool = {
|
|
7
|
+
name: 'run_test',
|
|
8
|
+
description: '运行项目测试',
|
|
9
|
+
category: 'project',
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
cwd: { type: 'string', description: '项目目录' },
|
|
14
|
+
test_file: { type: 'string', description: '指定测试文件' },
|
|
15
|
+
runner: { type: 'string', description: '测试运行器: auto, vitest, jest, mocha', enum: ['auto', 'vitest', 'jest', 'mocha'] },
|
|
16
|
+
coverage: { type: 'boolean', description: '是否生成覆盖报告' },
|
|
17
|
+
},
|
|
18
|
+
required: [],
|
|
19
|
+
},
|
|
20
|
+
dangerous: false,
|
|
21
|
+
requiresApproval: true,
|
|
22
|
+
async execute(params) {
|
|
23
|
+
try {
|
|
24
|
+
const cwd = (params.cwd as string) ?? process.cwd();
|
|
25
|
+
const testFile = params.test_file as string | undefined;
|
|
26
|
+
const runner = (params.runner as string) ?? 'auto';
|
|
27
|
+
const coverage = (params.coverage as boolean) ?? false;
|
|
28
|
+
|
|
29
|
+
let cmd = '';
|
|
30
|
+
if (runner === 'auto') {
|
|
31
|
+
const pkgPath = resolve(cwd, 'package.json');
|
|
32
|
+
if (existsSync(pkgPath)) {
|
|
33
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
34
|
+
const scripts = pkg.scripts || {};
|
|
35
|
+
if (scripts.test) cmd = `npm run test`;
|
|
36
|
+
else if (scripts['test:watch']) cmd = `npm run test:watch`;
|
|
37
|
+
else cmd = 'npx vitest run';
|
|
38
|
+
} else {
|
|
39
|
+
cmd = 'npx vitest run';
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
switch (runner) {
|
|
43
|
+
case 'vitest': cmd = 'npx vitest run'; break;
|
|
44
|
+
case 'jest': cmd = 'npx jest'; break;
|
|
45
|
+
case 'mocha': cmd = 'npx mocha'; break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (testFile) cmd += ` "${testFile}"`;
|
|
50
|
+
if (coverage) cmd += ' --coverage';
|
|
51
|
+
|
|
52
|
+
const output = execSync(cmd, {
|
|
53
|
+
cwd,
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
timeout: 180000,
|
|
56
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
output: `测试完成\n${output.slice(0, 10000)}`,
|
|
63
|
+
metadata: { cwd, coverage },
|
|
64
|
+
};
|
|
65
|
+
} catch (err: unknown) {
|
|
66
|
+
const e = err as { message?: string; stdout?: string; stderr?: string };
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: '测试失败',
|
|
70
|
+
output: (e.stdout || '') + (e.stderr || ''),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const codebase_search: Tool = {
|
|
6
|
+
name: 'codebase_search',
|
|
7
|
+
description: '代码库语义搜索,基于关键词进行加权评分搜索',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
query: { type: 'string', description: '搜索查询关键词' },
|
|
13
|
+
cwd: { type: 'string', description: '搜索根目录' },
|
|
14
|
+
glob: { type: 'string', description: '文件过滤模式' },
|
|
15
|
+
max_results: { type: 'number', description: '最大结果数' },
|
|
16
|
+
},
|
|
17
|
+
required: ['query'],
|
|
18
|
+
},
|
|
19
|
+
dangerous: false,
|
|
20
|
+
requiresApproval: false,
|
|
21
|
+
async execute(params) {
|
|
22
|
+
try {
|
|
23
|
+
const query = (params.query as string).toLowerCase();
|
|
24
|
+
const cwd = (params.cwd as string) ? resolve(params.cwd as string) : process.cwd();
|
|
25
|
+
const glob = (params.glob as string) ?? '**/*.{ts,js,tsx,jsx,py,java,go,rs}';
|
|
26
|
+
const maxResults = (params.max_results as number) ?? 50;
|
|
27
|
+
const keywords = query.split(/\s+/).filter(k => k.length > 0);
|
|
28
|
+
|
|
29
|
+
const fg = await import('fast-glob');
|
|
30
|
+
const files = await fg.default(glob, {
|
|
31
|
+
cwd,
|
|
32
|
+
absolute: true,
|
|
33
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**', '*.min.*'],
|
|
34
|
+
onlyFiles: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const scored: { file: string; score: number; snippet: string }[] = [];
|
|
38
|
+
const maxFileSize = 500 * 1024;
|
|
39
|
+
|
|
40
|
+
for (const fp of files) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = statSync(fp);
|
|
43
|
+
if (stat.size > maxFileSize) continue;
|
|
44
|
+
const content = readFileSync(fp, 'utf-8');
|
|
45
|
+
const lower = content.toLowerCase();
|
|
46
|
+
let score = 0;
|
|
47
|
+
for (const kw of keywords) {
|
|
48
|
+
const matches = lower.split(kw).length - 1;
|
|
49
|
+
score += matches * 10;
|
|
50
|
+
}
|
|
51
|
+
const fileName = fp.split(/[/\\]/).pop()?.toLowerCase() ?? '';
|
|
52
|
+
for (const kw of keywords) {
|
|
53
|
+
if (fileName.includes(kw)) score += 50;
|
|
54
|
+
}
|
|
55
|
+
if (score > 0) {
|
|
56
|
+
const idx = lower.indexOf(keywords[0]);
|
|
57
|
+
const snippet = idx >= 0
|
|
58
|
+
? content.slice(Math.max(0, idx - 40), idx + 80).replace(/\n/g, ' ')
|
|
59
|
+
: content.slice(0, 120).replace(/\n/g, ' ');
|
|
60
|
+
scored.push({ file: relative(cwd, fp), score, snippet });
|
|
61
|
+
}
|
|
62
|
+
} catch { /* skip */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
scored.sort((a, b) => b.score - a.score);
|
|
66
|
+
const top = scored.slice(0, maxResults);
|
|
67
|
+
const output = top.length > 0
|
|
68
|
+
? top.map(r => `${r.file} (score: ${r.score})\n ${r.snippet}`).join('\n\n')
|
|
69
|
+
: '未找到相关结果';
|
|
70
|
+
|
|
71
|
+
return { success: true, output, metadata: { total: scored.length, shown: top.length } };
|
|
72
|
+
} catch (err: unknown) {
|
|
73
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const find_definition: Tool = {
|
|
6
|
+
name: 'find_definition',
|
|
7
|
+
description: '查找指定符号的定义位置',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
name: { type: 'string', description: '符号名称' },
|
|
13
|
+
cwd: { type: 'string', description: '搜索根目录' },
|
|
14
|
+
},
|
|
15
|
+
required: ['name'],
|
|
16
|
+
},
|
|
17
|
+
dangerous: false,
|
|
18
|
+
requiresApproval: false,
|
|
19
|
+
async execute(params) {
|
|
20
|
+
try {
|
|
21
|
+
const name = params.name as string;
|
|
22
|
+
const cwd = (params.cwd as string) ? resolve(params.cwd as string) : process.cwd();
|
|
23
|
+
|
|
24
|
+
const fg = await import('fast-glob');
|
|
25
|
+
const files = await fg.default('**/*.{ts,js,tsx,jsx}', {
|
|
26
|
+
cwd,
|
|
27
|
+
absolute: true,
|
|
28
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
29
|
+
onlyFiles: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const defRegex = new RegExp(
|
|
33
|
+
`(?:function\\s+${escapeRegex(name)}\\b|` +
|
|
34
|
+
`class\\s+${escapeRegex(name)}\\b|` +
|
|
35
|
+
`(?:const|let|var)\\s+${escapeRegex(name)}\\s*=|` +
|
|
36
|
+
`interface\\s+${escapeRegex(name)}\\b|` +
|
|
37
|
+
`type\\s+${escapeRegex(name)}\\s*=|` +
|
|
38
|
+
`(?:export\\s+)?(?:async\\s+)?${escapeRegex(name)}\\s*\\([^)]*\\)\\s*\\{|` +
|
|
39
|
+
`this\\.${escapeRegex(name)}\\s*=\\s*(?:async\\s+)?(?:function|\\(|${escapeRegex(name)})|` +
|
|
40
|
+
`(?:public|private|protected|static|readonly)?\\s*(?:async\\s+)?${escapeRegex(name)}\\s*\\([^)]*\\)\\s*\\{` +
|
|
41
|
+
`)`,
|
|
42
|
+
'gmi'
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const results: { file: string; line: number; text: string }[] = [];
|
|
46
|
+
const maxFileSize = 2 * 1024 * 1024;
|
|
47
|
+
|
|
48
|
+
for (const fp of files) {
|
|
49
|
+
try {
|
|
50
|
+
const stat = statSync(fp);
|
|
51
|
+
if (stat.size > maxFileSize) continue;
|
|
52
|
+
const content = readFileSync(fp, 'utf-8');
|
|
53
|
+
const lines = content.split('\n');
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
if (defRegex.test(lines[i])) {
|
|
56
|
+
defRegex.lastIndex = 0;
|
|
57
|
+
results.push({
|
|
58
|
+
file: relative(cwd, fp),
|
|
59
|
+
line: i + 1,
|
|
60
|
+
text: lines[i].trim(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch { /* skip */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const output = results.length > 0
|
|
68
|
+
? results.map(r => `${r.file}:${r.line}: ${r.text}`).join('\n')
|
|
69
|
+
: `未找到 "${name}" 的定义`;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
output,
|
|
74
|
+
metadata: { count: results.length },
|
|
75
|
+
};
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function escapeRegex(s: string): string {
|
|
83
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const find_references: Tool = {
|
|
6
|
+
name: 'find_references',
|
|
7
|
+
description: '查找指定符号的所有引用位置',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
name: { type: 'string', description: '符号名称' },
|
|
13
|
+
cwd: { type: 'string', description: '搜索根目录' },
|
|
14
|
+
max_results: { type: 'number', description: '最大结果数' },
|
|
15
|
+
},
|
|
16
|
+
required: ['name'],
|
|
17
|
+
},
|
|
18
|
+
dangerous: false,
|
|
19
|
+
requiresApproval: false,
|
|
20
|
+
async execute(params) {
|
|
21
|
+
try {
|
|
22
|
+
const name = params.name as string;
|
|
23
|
+
const cwd = (params.cwd as string) ? resolve(params.cwd as string) : process.cwd();
|
|
24
|
+
const maxResults = (params.max_results as number) ?? 100;
|
|
25
|
+
|
|
26
|
+
const fg = await import('fast-glob');
|
|
27
|
+
const files = await fg.default('**/*.{ts,js,tsx,jsx}', {
|
|
28
|
+
cwd,
|
|
29
|
+
absolute: true,
|
|
30
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
31
|
+
onlyFiles: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const refRegex = new RegExp(`\\b${escapeRegex(name)}\\b`, 'gm');
|
|
35
|
+
const defRegex = new RegExp(`(?:function\\s+${escapeRegex(name)}|class\\s+${escapeRegex(name)}|(?:const|let|var)\\s+${escapeRegex(name)}\\s*=|interface\\s+${escapeRegex(name)}|type\\s+${escapeRegex(name)}\\s*=)`, 'gm');
|
|
36
|
+
|
|
37
|
+
const results: { file: string; line: number; text: string; type: 'definition' | 'reference' }[] = [];
|
|
38
|
+
const maxFileSize = 2 * 1024 * 1024;
|
|
39
|
+
|
|
40
|
+
for (const fp of files) {
|
|
41
|
+
if (results.length >= maxResults) break;
|
|
42
|
+
try {
|
|
43
|
+
const stat = statSync(fp);
|
|
44
|
+
if (stat.size > maxFileSize) continue;
|
|
45
|
+
const content = readFileSync(fp, 'utf-8');
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
48
|
+
if (lines[i].match(refRegex)) {
|
|
49
|
+
const isDef = defRegex.test(lines[i]);
|
|
50
|
+
results.push({
|
|
51
|
+
file: relative(cwd, fp),
|
|
52
|
+
line: i + 1,
|
|
53
|
+
text: lines[i].trim(),
|
|
54
|
+
type: isDef ? 'definition' : 'reference',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const output = results.map(r => `${r.file}:${r.line} [${r.type}] ${r.text}`).join('\n');
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
output: output || '未找到引用',
|
|
65
|
+
metadata: { count: results.length },
|
|
66
|
+
};
|
|
67
|
+
} catch (err: unknown) {
|
|
68
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function escapeRegex(s: string): string {
|
|
74
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
75
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const fuzzy_find: Tool = {
|
|
6
|
+
name: 'fuzzy_find',
|
|
7
|
+
description: '模糊匹配文件名',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
name: { type: 'string', description: '模糊文件名' },
|
|
13
|
+
cwd: { type: 'string', description: '搜索目录' },
|
|
14
|
+
max_results: { type: 'number', description: '最大结果数' },
|
|
15
|
+
},
|
|
16
|
+
required: ['name'],
|
|
17
|
+
},
|
|
18
|
+
dangerous: false,
|
|
19
|
+
requiresApproval: false,
|
|
20
|
+
async execute(params) {
|
|
21
|
+
try {
|
|
22
|
+
const name = (params.name as string).toLowerCase();
|
|
23
|
+
const cwd = (params.cwd as string) ? resolve(params.cwd as string) : process.cwd();
|
|
24
|
+
const maxResults = (params.max_results as number) ?? 20;
|
|
25
|
+
|
|
26
|
+
const fg = await import('fast-glob');
|
|
27
|
+
const files = await fg.default('**/*', {
|
|
28
|
+
cwd,
|
|
29
|
+
absolute: false,
|
|
30
|
+
ignore: ['node_modules/**', '.git/**'],
|
|
31
|
+
onlyFiles: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const scored = files
|
|
35
|
+
.map(f => ({ file: f, score: fuzzyScore(name, f.toLowerCase()) }))
|
|
36
|
+
.filter(r => r.score > 0)
|
|
37
|
+
.sort((a, b) => b.score - a.score)
|
|
38
|
+
.slice(0, maxResults);
|
|
39
|
+
|
|
40
|
+
const output = scored.length > 0
|
|
41
|
+
? scored.map(r => r.file).join('\n')
|
|
42
|
+
: '未找到匹配文件';
|
|
43
|
+
|
|
44
|
+
return { success: true, output, metadata: { count: scored.length } };
|
|
45
|
+
} catch (err: unknown) {
|
|
46
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function fuzzyScore(needle: string, haystack: string): number {
|
|
52
|
+
let p = 0;
|
|
53
|
+
let prevMatch = -2;
|
|
54
|
+
let score = 0;
|
|
55
|
+
for (let i = 0; i < needle.length && p < haystack.length; i++) {
|
|
56
|
+
const nc = needle[i];
|
|
57
|
+
let found = false;
|
|
58
|
+
while (p < haystack.length) {
|
|
59
|
+
if (haystack[p] === nc) {
|
|
60
|
+
score += 10;
|
|
61
|
+
if (p === prevMatch + 1) score += 5;
|
|
62
|
+
prevMatch = p;
|
|
63
|
+
p++;
|
|
64
|
+
found = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
p++;
|
|
68
|
+
}
|
|
69
|
+
if (!found) return 0;
|
|
70
|
+
}
|
|
71
|
+
const fileName = haystack.split(/[/\\]/).pop() ?? '';
|
|
72
|
+
if (fileName.includes(needle)) score += 30;
|
|
73
|
+
if (fileName.startsWith(needle)) score += 20;
|
|
74
|
+
return score;
|
|
75
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const grep_search: Tool = {
|
|
6
|
+
name: 'grep_search',
|
|
7
|
+
description: '使用正则表达式搜索文件内容',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
pattern: { type: 'string', description: '搜索正则表达式' },
|
|
13
|
+
file_path: { type: 'string', description: '目标文件路径' },
|
|
14
|
+
dir_path: { type: 'string', description: '目标目录路径' },
|
|
15
|
+
case_sensitive: { type: 'boolean', description: '是否区分大小写' },
|
|
16
|
+
glob: { type: 'string', description: '文件过滤模式,如 *.ts' },
|
|
17
|
+
max_results: { type: 'number', description: '最大结果数' },
|
|
18
|
+
context_lines: { type: 'number', description: '上下文行数' },
|
|
19
|
+
},
|
|
20
|
+
required: ['pattern'],
|
|
21
|
+
},
|
|
22
|
+
dangerous: false,
|
|
23
|
+
requiresApproval: false,
|
|
24
|
+
async execute(params) {
|
|
25
|
+
try {
|
|
26
|
+
const pattern = params.pattern as string;
|
|
27
|
+
const filePath = params.file_path as string | undefined;
|
|
28
|
+
const dirPath = params.dir_path as string | undefined;
|
|
29
|
+
const caseSensitive = (params.case_sensitive as boolean) ?? false;
|
|
30
|
+
const glob = params.glob as string | undefined;
|
|
31
|
+
const maxResults = (params.max_results as number) ?? 100;
|
|
32
|
+
const contextLines = (params.context_lines as number) ?? 0;
|
|
33
|
+
|
|
34
|
+
const regex = new RegExp(pattern, caseSensitive ? 'gm' : 'gim');
|
|
35
|
+
const results: string[] = [];
|
|
36
|
+
const maxFileSize = 5 * 1024 * 1024;
|
|
37
|
+
|
|
38
|
+
function searchFile(fp: string): void {
|
|
39
|
+
if (results.length >= maxResults) return;
|
|
40
|
+
try {
|
|
41
|
+
const stat = statSync(fp);
|
|
42
|
+
if (stat.size > maxFileSize) return;
|
|
43
|
+
const content = readFileSync(fp, 'utf-8');
|
|
44
|
+
const lines = content.split('\n');
|
|
45
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
46
|
+
if (regex.test(lines[i])) {
|
|
47
|
+
regex.lastIndex = 0;
|
|
48
|
+
const lineNum = i + 1;
|
|
49
|
+
if (contextLines > 0) {
|
|
50
|
+
const from = Math.max(0, i - contextLines);
|
|
51
|
+
const to = Math.min(lines.length, i + contextLines + 1);
|
|
52
|
+
for (let j = from; j < to; j++) {
|
|
53
|
+
results.push(`${fp}:${j + 1}:${lines[j]}`);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
results.push(`${fp}:${lineNum}:${lines[i]}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch { /* skip */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (filePath) {
|
|
64
|
+
const abs = resolve(filePath);
|
|
65
|
+
if (existsSync(abs)) searchFile(abs);
|
|
66
|
+
} else if (dirPath) {
|
|
67
|
+
const fg = await import('fast-glob');
|
|
68
|
+
const pattern = glob ? `**/${glob}` : '**/*';
|
|
69
|
+
const files = await fg.default(pattern, {
|
|
70
|
+
cwd: resolve(dirPath),
|
|
71
|
+
absolute: true,
|
|
72
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
73
|
+
onlyFiles: true,
|
|
74
|
+
});
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
if (results.length >= maxResults) break;
|
|
77
|
+
searchFile(f);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
output: results.join('\n') || '未找到匹配结果',
|
|
84
|
+
metadata: { matches: results.length, maxResults },
|
|
85
|
+
};
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const regex_find: Tool = {
|
|
6
|
+
name: 'regex_find',
|
|
7
|
+
description: '使用正则表达式搜索并替换文件内容',
|
|
8
|
+
category: 'search',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
pattern: { type: 'string', description: '正则表达式' },
|
|
13
|
+
replacement: { type: 'string', description: '替换字符串' },
|
|
14
|
+
file_path: { type: 'string', description: '文件路径' },
|
|
15
|
+
dir_path: { type: 'string', description: '目录路径' },
|
|
16
|
+
file_pattern: { type: 'string', description: '文件匹配模式' },
|
|
17
|
+
case_sensitive: { type: 'boolean', description: '是否区分大小写' },
|
|
18
|
+
dry_run: { type: 'boolean', description: '仅预览不实际修改' },
|
|
19
|
+
max_results: { type: 'number', description: '最大结果数' },
|
|
20
|
+
},
|
|
21
|
+
required: ['pattern'],
|
|
22
|
+
},
|
|
23
|
+
dangerous: false,
|
|
24
|
+
requiresApproval: true,
|
|
25
|
+
async execute(params) {
|
|
26
|
+
try {
|
|
27
|
+
const regPattern = params.pattern as string;
|
|
28
|
+
const replacement = (params.replacement as string) ?? '';
|
|
29
|
+
const filePath = params.file_path as string | undefined;
|
|
30
|
+
const dirPath = params.dir_path as string | undefined;
|
|
31
|
+
const filePattern = (params.file_pattern as string) ?? '**/*';
|
|
32
|
+
const caseSensitive = (params.case_sensitive as boolean) ?? false;
|
|
33
|
+
const dryRun = (params.dry_run as boolean) ?? true;
|
|
34
|
+
const maxResults = (params.max_results as number) ?? 100;
|
|
35
|
+
|
|
36
|
+
const regex = new RegExp(regPattern, caseSensitive ? 'gm' : 'gim');
|
|
37
|
+
const results: string[] = [];
|
|
38
|
+
const maxFileSize = 2 * 1024 * 1024;
|
|
39
|
+
|
|
40
|
+
async function processFile(fp: string): Promise<void> {
|
|
41
|
+
if (results.length >= maxResults) return;
|
|
42
|
+
try {
|
|
43
|
+
const stat = await import('node:fs').then(m => m.statSync(fp));
|
|
44
|
+
if (stat.size > maxFileSize) return;
|
|
45
|
+
let content = readFileSync(fp, 'utf-8');
|
|
46
|
+
if (!regex.test(content)) return;
|
|
47
|
+
regex.lastIndex = 0;
|
|
48
|
+
const lines = content.split('\n');
|
|
49
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
50
|
+
if (regex.test(lines[i])) {
|
|
51
|
+
regex.lastIndex = 0;
|
|
52
|
+
const newLine = lines[i].replace(new RegExp(regPattern, caseSensitive ? 'gm' : 'gim'), replacement.replace(/\$/g, '$$$$'));
|
|
53
|
+
results.push(`${fp}:${i + 1}: - ${lines[i].trim()}\n + ${newLine.trim()}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!dryRun) {
|
|
57
|
+
regex.lastIndex = 0;
|
|
58
|
+
content = content.replace(new RegExp(regPattern, caseSensitive ? 'gm' : 'gim'), replacement.replace(/\$/g, '$$$$'));
|
|
59
|
+
writeFileSync(fp, content, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
} catch { /* skip */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (filePath) {
|
|
65
|
+
const abs = resolve(filePath);
|
|
66
|
+
if (existsSync(abs)) await processFile(abs);
|
|
67
|
+
} else if (dirPath) {
|
|
68
|
+
const fg = await import('fast-glob');
|
|
69
|
+
const files = await fg.default(filePattern, {
|
|
70
|
+
cwd: resolve(dirPath),
|
|
71
|
+
absolute: true,
|
|
72
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
73
|
+
onlyFiles: true,
|
|
74
|
+
});
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
if (results.length >= maxResults) break;
|
|
77
|
+
await processFile(f);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const prefix = dryRun ? '[预览模式] ' : '[已修改] ';
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
output: results.length > 0 ? prefix + `找到 ${results.length} 处匹配:\n${results.join('\n')}` : '未找到匹配',
|
|
85
|
+
metadata: { matches: results.length, dryRun },
|
|
86
|
+
};
|
|
87
|
+
} catch (err: unknown) {
|
|
88
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Tool } from '../../tool-types.js';
|
|
2
|
+
|
|
3
|
+
export const search_docs: Tool = {
|
|
4
|
+
name: 'search_docs',
|
|
5
|
+
description: '搜索在线文档,返回搜索建议和常用文档链接',
|
|
6
|
+
category: 'search',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
query: { type: 'string', description: '搜索关键词' },
|
|
11
|
+
source: { type: 'string', description: '文档源: mdn, npm, typescript, nodejs, react, all', enum: ['mdn', 'npm', 'typescript', 'nodejs', 'react', 'all'] },
|
|
12
|
+
},
|
|
13
|
+
required: ['query'],
|
|
14
|
+
},
|
|
15
|
+
dangerous: false,
|
|
16
|
+
requiresApproval: false,
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const query = params.query as string;
|
|
20
|
+
const source = (params.source as string) ?? 'all';
|
|
21
|
+
const encoded = encodeURIComponent(query);
|
|
22
|
+
|
|
23
|
+
const docLinks: Record<string, string> = {
|
|
24
|
+
mdn: `https://developer.mozilla.org/zh-CN/search?q=${encoded}`,
|
|
25
|
+
npm: `https://www.npmjs.com/search?q=${encoded}`,
|
|
26
|
+
typescript: `https://www.typescriptlang.org/search?q=${encoded}`,
|
|
27
|
+
nodejs: `https://nodejs.org/api/all.html`,
|
|
28
|
+
react: `https://react.dev/`,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const lines: string[] = ['建议访问以下文档搜索:', ''];
|
|
32
|
+
if (source === 'all') {
|
|
33
|
+
for (const [name, url] of Object.entries(docLinks)) {
|
|
34
|
+
lines.push(`- ${name.toUpperCase()}: ${url}`);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
lines.push(`- ${docLinks[source]}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
lines.push('', `同时可以在 Stack Overflow 搜索: https://stackoverflow.com/search?q=${encoded}`);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
output: lines.join('\n'),
|
|
45
|
+
metadata: { query, source },
|
|
46
|
+
};
|
|
47
|
+
} catch (err: unknown) {
|
|
48
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|