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,29 @@
|
|
|
1
|
+
import { killProcess } from './process-pool.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const kill_terminal: Tool = {
|
|
5
|
+
name: 'kill_terminal',
|
|
6
|
+
description: '强制关闭后台终端',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
terminal_id: { type: 'string', description: '终端标识' },
|
|
12
|
+
},
|
|
13
|
+
required: ['terminal_id'],
|
|
14
|
+
},
|
|
15
|
+
dangerous: false,
|
|
16
|
+
requiresApproval: false,
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const terminalId = params.terminal_id as string;
|
|
20
|
+
const ok = killProcess(terminalId);
|
|
21
|
+
if (!ok) {
|
|
22
|
+
return { success: false, error: `终端不存在: ${terminalId}`, output: '' };
|
|
23
|
+
}
|
|
24
|
+
return { success: true, output: `已关闭终端: ${terminalId}`, metadata: { terminalId } };
|
|
25
|
+
} catch (err: unknown) {
|
|
26
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { listProcesses, readOutput as poolRead } from './process-pool.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const list_terminals: Tool = {
|
|
5
|
+
name: 'list_terminals',
|
|
6
|
+
description: '列出所有后台终端及最近输出',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {},
|
|
11
|
+
required: [],
|
|
12
|
+
},
|
|
13
|
+
dangerous: false,
|
|
14
|
+
requiresApproval: false,
|
|
15
|
+
async execute() {
|
|
16
|
+
try {
|
|
17
|
+
const entries = listProcesses();
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
return { success: true, output: '没有后台终端' };
|
|
20
|
+
}
|
|
21
|
+
let out = `后台终端 (${entries.length}):\n`;
|
|
22
|
+
for (const e of entries) {
|
|
23
|
+
const status = e.alive ? `运行 ${e.runningSec}s` : '已结束';
|
|
24
|
+
out += ` ${e.id} PID:${e.pid ?? '-'} ${e.command}\n`;
|
|
25
|
+
out += ` ${status} | 输出:${e.outputLen}B | ${e.outputTail}\n\n`;
|
|
26
|
+
}
|
|
27
|
+
return { success: true, output: out, metadata: { entries } };
|
|
28
|
+
} catch (err: unknown) {
|
|
29
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const read_terminal: Tool = {
|
|
35
|
+
name: 'read_terminal',
|
|
36
|
+
description: '读取后台终端的输出内容',
|
|
37
|
+
category: 'shell',
|
|
38
|
+
parameters: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
terminal_id: { type: 'string', description: '终端标识' },
|
|
42
|
+
tail: { type: 'boolean', description: '仅显示末尾输出' },
|
|
43
|
+
},
|
|
44
|
+
required: ['terminal_id'],
|
|
45
|
+
},
|
|
46
|
+
dangerous: false,
|
|
47
|
+
requiresApproval: false,
|
|
48
|
+
async execute(params) {
|
|
49
|
+
try {
|
|
50
|
+
const terminalId = params.terminal_id as string;
|
|
51
|
+
const tail = (params.tail as boolean) ?? true;
|
|
52
|
+
const output = poolRead(terminalId, tail);
|
|
53
|
+
if (output === null) {
|
|
54
|
+
return { success: false, error: `终端不存在: ${terminalId}`, output: '' };
|
|
55
|
+
}
|
|
56
|
+
return { success: true, output, metadata: { terminalId, tail } };
|
|
57
|
+
} catch (err: unknown) {
|
|
58
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { exec as execCb } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(execCb);
|
|
6
|
+
|
|
7
|
+
export const pipe_commands: Tool = {
|
|
8
|
+
name: 'pipe_commands',
|
|
9
|
+
description: '管道串联多个命令',
|
|
10
|
+
category: 'shell',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
commands: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: { type: 'string' },
|
|
17
|
+
description: '命令列表,按顺序通过管道串联',
|
|
18
|
+
},
|
|
19
|
+
cwd: { type: 'string', description: '工作目录' },
|
|
20
|
+
timeout_ms: { type: 'number', description: '超时毫秒数' },
|
|
21
|
+
},
|
|
22
|
+
required: ['commands'],
|
|
23
|
+
},
|
|
24
|
+
dangerous: false,
|
|
25
|
+
requiresApproval: true,
|
|
26
|
+
async execute(params) {
|
|
27
|
+
try {
|
|
28
|
+
const commands = params.commands as string[];
|
|
29
|
+
const cwd = (params.cwd as string) ?? process.cwd();
|
|
30
|
+
const timeout = (params.timeout_ms as number) ?? 30000;
|
|
31
|
+
const pipeline = commands.join(' | ');
|
|
32
|
+
|
|
33
|
+
const { stdout, stderr } = await execAsync(pipeline, {
|
|
34
|
+
cwd,
|
|
35
|
+
timeout,
|
|
36
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
shell: process.env.SHELL || process.env.ComSpec || 'cmd.exe',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
output: stdout + (stderr ? `\n[stderr]\n${stderr}` : ''),
|
|
44
|
+
metadata: { pipeline },
|
|
45
|
+
};
|
|
46
|
+
} catch (err: unknown) {
|
|
47
|
+
const e = err as { message?: string; stdout?: string; stderr?: string };
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: e.message || String(err),
|
|
51
|
+
output: [e.stdout, e.stderr].filter(Boolean).join('\n'),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export interface ProcessEntry {
|
|
4
|
+
proc: ChildProcess;
|
|
5
|
+
output: string;
|
|
6
|
+
startTime: number;
|
|
7
|
+
command: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
alive: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const pool = new Map<string, ProcessEntry>();
|
|
13
|
+
const RING_SIZE = 64_000;
|
|
14
|
+
const TAIL_SIZE = 16_000;
|
|
15
|
+
|
|
16
|
+
export function decodeBuffer(bufs: Buffer[]): string {
|
|
17
|
+
const buf = Buffer.concat(bufs);
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
try {
|
|
20
|
+
const decoder = new TextDecoder('gbk', { fatal: false });
|
|
21
|
+
return decoder.decode(buf);
|
|
22
|
+
} catch {
|
|
23
|
+
return buf.toString('utf-8');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return buf.toString('utf-8');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ringPush(entry: ProcessEntry, chunk: string): void {
|
|
30
|
+
entry.output += chunk;
|
|
31
|
+
if (entry.output.length > RING_SIZE) {
|
|
32
|
+
entry.output = entry.output.slice(-RING_SIZE);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function startBgProcess(params: {
|
|
37
|
+
command: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
terminalId?: string;
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
}): { terminalId: string; pid: number | undefined } {
|
|
42
|
+
const cwd = params.cwd || process.cwd();
|
|
43
|
+
const terminalId = params.terminalId || `bg_${Date.now()}_${Math.random().toString(36).slice(2,6)}`;
|
|
44
|
+
const [cmd, ...args] = params.command.split(/\s+/);
|
|
45
|
+
|
|
46
|
+
if (pool.has(terminalId)) {
|
|
47
|
+
const old = pool.get(terminalId)!;
|
|
48
|
+
try { old.proc.kill(); } catch {}
|
|
49
|
+
pool.delete(terminalId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const proc = spawn(cmd, args, {
|
|
53
|
+
cwd,
|
|
54
|
+
shell: true,
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
|
+
env: params.env ? { ...process.env, ...params.env } : process.env,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const entry: ProcessEntry = {
|
|
60
|
+
proc,
|
|
61
|
+
output: '',
|
|
62
|
+
startTime: Date.now(),
|
|
63
|
+
command: params.command,
|
|
64
|
+
cwd,
|
|
65
|
+
alive: true,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const stdoutChunks: Buffer[] = [];
|
|
69
|
+
const stderrChunks: Buffer[] = [];
|
|
70
|
+
|
|
71
|
+
proc.stdout?.on('data', (d: Buffer) => stdoutChunks.push(d));
|
|
72
|
+
proc.stderr?.on('data', (d: Buffer) => stderrChunks.push(d));
|
|
73
|
+
|
|
74
|
+
const flushInterval = setInterval(() => {
|
|
75
|
+
if (stdoutChunks.length > 0) {
|
|
76
|
+
ringPush(entry, decodeBuffer(stdoutChunks.splice(0)));
|
|
77
|
+
}
|
|
78
|
+
if (stderrChunks.length > 0) {
|
|
79
|
+
ringPush(entry, '[stderr] ' + decodeBuffer(stderrChunks.splice(0)));
|
|
80
|
+
}
|
|
81
|
+
}, 50);
|
|
82
|
+
|
|
83
|
+
proc.on('close', (code) => {
|
|
84
|
+
clearInterval(flushInterval);
|
|
85
|
+
if (stdoutChunks.length > 0) ringPush(entry, decodeBuffer(stdoutChunks.splice(0)));
|
|
86
|
+
if (stderrChunks.length > 0) ringPush(entry, '[stderr] ' + decodeBuffer(stderrChunks.splice(0)));
|
|
87
|
+
entry.alive = false;
|
|
88
|
+
ringPush(entry, `\n[进程结束, exitCode=${code}]\n`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
proc.on('error', (err) => {
|
|
92
|
+
clearInterval(flushInterval);
|
|
93
|
+
entry.alive = false;
|
|
94
|
+
ringPush(entry, `\n[进程错误: ${err.message}]\n`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
pool.set(terminalId, entry);
|
|
98
|
+
return { terminalId, pid: proc.pid ?? undefined };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function readOutput(terminalId: string, tailOnly?: boolean): string | null {
|
|
102
|
+
const entry = pool.get(terminalId);
|
|
103
|
+
if (!entry) return null;
|
|
104
|
+
const full = entry.output || '(暂无输出)';
|
|
105
|
+
if (tailOnly) {
|
|
106
|
+
return full.length > TAIL_SIZE ? '...(截断)\n' + full.slice(-TAIL_SIZE) : full;
|
|
107
|
+
}
|
|
108
|
+
return full.length > TAIL_SIZE * 2 ? full.slice(-TAIL_SIZE * 2) : full;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function listProcesses(): Array<{
|
|
112
|
+
id: string; pid: number | undefined; command: string;
|
|
113
|
+
alive: boolean; runningSec: number; outputLen: number; outputTail: string;
|
|
114
|
+
}> {
|
|
115
|
+
return Array.from(pool.entries()).map(([id, e]) => ({
|
|
116
|
+
id,
|
|
117
|
+
pid: e.proc.pid ?? undefined,
|
|
118
|
+
command: e.command.slice(0, 60),
|
|
119
|
+
alive: e.alive,
|
|
120
|
+
runningSec: Math.round((Date.now() - e.startTime) / 1000),
|
|
121
|
+
outputLen: e.output.length,
|
|
122
|
+
outputTail: e.output.slice(-200).replace(/\n/g, '↵').slice(0, 160),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function killProcess(terminalId: string): boolean {
|
|
127
|
+
const entry = pool.get(terminalId);
|
|
128
|
+
if (!entry) return false;
|
|
129
|
+
try { entry.proc.kill('SIGKILL'); } catch {}
|
|
130
|
+
pool.delete(terminalId);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getProcess(terminalId: string): ProcessEntry | undefined {
|
|
135
|
+
return pool.get(terminalId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function sendToStdin(terminalId: string, text: string): boolean {
|
|
139
|
+
const entry = pool.get(terminalId);
|
|
140
|
+
if (!entry || !entry.alive || !entry.proc.stdin) return false;
|
|
141
|
+
entry.proc.stdin.write(text + '\n');
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function sendSignal(terminalId: string, signal: 'SIGINT' | 'SIGTERM' | 'SIGKILL'): boolean {
|
|
146
|
+
const entry = pool.get(terminalId);
|
|
147
|
+
if (!entry || !entry.alive) return false;
|
|
148
|
+
try { entry.proc.kill(signal); } catch { return false; }
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { decodeBuffer } from './process-pool.js';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
const runningProcesses = new Map<string, ReturnType<typeof spawn>>();
|
|
6
|
+
|
|
7
|
+
export const run_async: Tool = {
|
|
8
|
+
name: 'run_async',
|
|
9
|
+
description: '异步执行命令并等待完成 (可配合 check_status / stop_command)',
|
|
10
|
+
category: 'shell',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
command: { type: 'string', description: '命令和参数' },
|
|
15
|
+
cwd: { type: 'string', description: '工作目录' },
|
|
16
|
+
task_id: { type: 'string', description: '任务标识,用于后续查询' },
|
|
17
|
+
},
|
|
18
|
+
required: ['command'],
|
|
19
|
+
},
|
|
20
|
+
dangerous: false,
|
|
21
|
+
requiresApproval: false,
|
|
22
|
+
async execute(params) {
|
|
23
|
+
try {
|
|
24
|
+
const command = params.command as string;
|
|
25
|
+
const cwd = (params.cwd as string) ?? process.cwd();
|
|
26
|
+
const taskId = (params.task_id as string) ?? `async_${Date.now()}`;
|
|
27
|
+
|
|
28
|
+
const [cmd, ...args] = command.split(/\s+/);
|
|
29
|
+
const proc = spawn(cmd, args, {
|
|
30
|
+
cwd,
|
|
31
|
+
shell: true,
|
|
32
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
env: process.env,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
runningProcesses.set(taskId, proc);
|
|
37
|
+
|
|
38
|
+
const stdoutChunks: Buffer[] = [];
|
|
39
|
+
const stderrChunks: Buffer[] = [];
|
|
40
|
+
proc.stdout?.on('data', (d: Buffer) => stdoutChunks.push(d));
|
|
41
|
+
proc.stderr?.on('data', (d: Buffer) => stderrChunks.push(d));
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await new Promise<void>((resolve, reject) => {
|
|
45
|
+
proc.on('close', (code) => {
|
|
46
|
+
runningProcesses.delete(taskId);
|
|
47
|
+
if (code === 0) resolve();
|
|
48
|
+
else reject(new Error(`进程退出码: ${code}`));
|
|
49
|
+
});
|
|
50
|
+
proc.on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
} catch (e) {
|
|
53
|
+
runningProcesses.delete(taskId);
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: (e as Error).message,
|
|
57
|
+
output: decodeBuffer(stdoutChunks) + decodeBuffer(stderrChunks),
|
|
58
|
+
metadata: { taskId },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
output: decodeBuffer(stdoutChunks) + (stderrChunks.length ? '\n[stderr]\n' + decodeBuffer(stderrChunks) : '') || '(无输出)',
|
|
65
|
+
metadata: { taskId, command },
|
|
66
|
+
};
|
|
67
|
+
} catch (err: unknown) {
|
|
68
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export { runningProcesses };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { decodeBuffer } from './process-pool.js';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const run_command: Tool = {
|
|
6
|
+
name: 'run_command',
|
|
7
|
+
description: '执行系统命令并返回结果 (阻塞等待完成)',
|
|
8
|
+
category: 'shell',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
command: { type: 'string', description: '要执行的命令' },
|
|
13
|
+
cwd: { type: 'string', description: '工作目录' },
|
|
14
|
+
timeout_ms: { type: 'number', description: '超时毫秒数 (默认 30s)' },
|
|
15
|
+
},
|
|
16
|
+
required: ['command'],
|
|
17
|
+
},
|
|
18
|
+
dangerous: false,
|
|
19
|
+
requiresApproval: false,
|
|
20
|
+
async execute(params) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const command = params.command as string;
|
|
23
|
+
const cwd = (params.cwd as string) || process.cwd();
|
|
24
|
+
const timeout = (params.timeout_ms as number) ?? 30_000;
|
|
25
|
+
|
|
26
|
+
const proc = spawn(command, {
|
|
27
|
+
cwd, shell: true,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
try { proc.kill(); } catch {}
|
|
33
|
+
resolve({ success: false, error: `命令超时 (${timeout}ms)`, output: '' });
|
|
34
|
+
}, timeout);
|
|
35
|
+
|
|
36
|
+
const stdoutChunks: Buffer[] = [];
|
|
37
|
+
const stderrChunks: Buffer[] = [];
|
|
38
|
+
proc.stdout?.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));
|
|
39
|
+
proc.stderr?.on('data', (chunk: Buffer) => stderrChunks.push(chunk));
|
|
40
|
+
|
|
41
|
+
proc.on('error', (err: Error) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({ success: false, error: err.message, output: '' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
proc.on('close', (code: number | null) => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
const stdout = decodeBuffer(stdoutChunks);
|
|
49
|
+
const stderr = decodeBuffer(stderrChunks);
|
|
50
|
+
const output = (stdout + (stderr ? `\n[stderr]\n${stderr}` : '')).slice(0, 8000);
|
|
51
|
+
|
|
52
|
+
if (code === 0) {
|
|
53
|
+
resolve({ success: true, output, metadata: { command, cwd, exitCode: code } });
|
|
54
|
+
} else {
|
|
55
|
+
resolve({ success: false, error: `Exit code: ${code}`, output });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sendSignal } from './process-pool.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const send_ctrl_keys: Tool = {
|
|
5
|
+
name: 'send_ctrl_keys',
|
|
6
|
+
description: '向后台终端发送控制信号 (Ctrl+C=SIGINT, Ctrl+D=关闭stdin)',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
terminal_id: { type: 'string', description: '终端标识' },
|
|
12
|
+
signal: { type: 'string', enum: ['ctrl_c', 'ctrl_d', 'ctrl_z'], description: 'ctrl_c=SIGINT, ctrl_d=关闭stdin, ctrl_z=SIGTSTP' },
|
|
13
|
+
},
|
|
14
|
+
required: ['terminal_id', 'signal'],
|
|
15
|
+
},
|
|
16
|
+
dangerous: false,
|
|
17
|
+
requiresApproval: false,
|
|
18
|
+
async execute(params) {
|
|
19
|
+
try {
|
|
20
|
+
const terminalId = params.terminal_id as string;
|
|
21
|
+
const signal = params.signal as string;
|
|
22
|
+
|
|
23
|
+
switch (signal) {
|
|
24
|
+
case 'ctrl_c':
|
|
25
|
+
sendSignal(terminalId, 'SIGINT');
|
|
26
|
+
return { success: true, output: `已发送 Ctrl+C 到终端: ${terminalId}`, metadata: { terminalId }, };
|
|
27
|
+
case 'ctrl_d': {
|
|
28
|
+
const { getProcess } = await import('./process-pool.js');
|
|
29
|
+
const entry = getProcess(terminalId);
|
|
30
|
+
if (entry?.proc.stdin) { entry.proc.stdin.end(); }
|
|
31
|
+
return { success: true, output: `已发送 Ctrl+D 到终端: ${terminalId}`, metadata: { terminalId }, };
|
|
32
|
+
}
|
|
33
|
+
case 'ctrl_z':
|
|
34
|
+
sendSignal(terminalId, 'SIGTERM');
|
|
35
|
+
return { success: true, output: `已发送 Ctrl+Z 到终端: ${terminalId}`, metadata: { terminalId }, };
|
|
36
|
+
default:
|
|
37
|
+
return { success: false, error: `未知信号: ${signal}`, output: '' };
|
|
38
|
+
}
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getProcess } from './process-pool.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const send_keys: Tool = {
|
|
5
|
+
name: 'send_keys',
|
|
6
|
+
description: '检查后台终端是否可写入',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
terminal_id: { type: 'string', description: '终端标识' },
|
|
12
|
+
},
|
|
13
|
+
required: ['terminal_id'],
|
|
14
|
+
},
|
|
15
|
+
dangerous: false,
|
|
16
|
+
requiresApproval: false,
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const terminalId = params.terminal_id as string;
|
|
20
|
+
const entry = getProcess(terminalId);
|
|
21
|
+
if (!entry || !entry.alive) {
|
|
22
|
+
return { success: false, error: `终端不可用: ${terminalId}`, output: '' };
|
|
23
|
+
}
|
|
24
|
+
if (!entry.proc.stdin) {
|
|
25
|
+
return { success: false, error: `终端 stdin 不可写: ${terminalId}`, output: '' };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
output: `终端 "${terminalId}" 就绪,可通过 send_text 发送文本`,
|
|
30
|
+
metadata: { terminalId, pid: entry.proc.pid },
|
|
31
|
+
};
|
|
32
|
+
} catch (err: unknown) {
|
|
33
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sendToStdin } from './process-pool.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const send_text: Tool = {
|
|
5
|
+
name: 'send_text',
|
|
6
|
+
description: '向后台终端发送文本输入',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
terminal_id: { type: 'string', description: '终端标识' },
|
|
12
|
+
text: { type: 'string', description: '要发送的文本' },
|
|
13
|
+
},
|
|
14
|
+
required: ['terminal_id', 'text'],
|
|
15
|
+
},
|
|
16
|
+
dangerous: false,
|
|
17
|
+
requiresApproval: false,
|
|
18
|
+
async execute(params) {
|
|
19
|
+
try {
|
|
20
|
+
const terminalId = params.terminal_id as string;
|
|
21
|
+
const text = params.text as string;
|
|
22
|
+
const ok = sendToStdin(terminalId, text);
|
|
23
|
+
if (!ok) {
|
|
24
|
+
return { success: false, error: `无法发送到终端: ${terminalId}`, output: '' };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
output: `已发送文本到终端: ${terminalId}`,
|
|
29
|
+
metadata: { terminalId, length: text.length },
|
|
30
|
+
};
|
|
31
|
+
} catch (err: unknown) {
|
|
32
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { exec as execCb } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import type { Tool } from '../../tool-types.js';
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(execCb);
|
|
8
|
+
|
|
9
|
+
export const shell_script: Tool = {
|
|
10
|
+
name: 'shell_script',
|
|
11
|
+
description: '执行 Shell 脚本文件',
|
|
12
|
+
category: 'shell',
|
|
13
|
+
parameters: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
script_path: { type: 'string', description: '脚本文件路径' },
|
|
17
|
+
args: { type: 'array', items: { type: 'string' }, description: '脚本参数' },
|
|
18
|
+
cwd: { type: 'string', description: '工作目录' },
|
|
19
|
+
timeout_ms: { type: 'number', description: '超时毫秒数' },
|
|
20
|
+
},
|
|
21
|
+
required: ['script_path'],
|
|
22
|
+
},
|
|
23
|
+
dangerous: false,
|
|
24
|
+
requiresApproval: true,
|
|
25
|
+
async execute(params) {
|
|
26
|
+
try {
|
|
27
|
+
const scriptPath = resolve(params.script_path as string);
|
|
28
|
+
const args = (params.args as string[]) ?? [];
|
|
29
|
+
const cwd = (params.cwd as string) ?? process.cwd();
|
|
30
|
+
const timeout = (params.timeout_ms as number) ?? 30000;
|
|
31
|
+
|
|
32
|
+
if (!existsSync(scriptPath)) {
|
|
33
|
+
return { success: false, error: `脚本文件不存在: ${scriptPath}`, output: '' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const shell = process.platform === 'win32'
|
|
37
|
+
? process.env.ComSpec || 'cmd.exe'
|
|
38
|
+
: process.env.SHELL || '/bin/sh';
|
|
39
|
+
|
|
40
|
+
const cmd = process.platform === 'win32'
|
|
41
|
+
? `"${shell}" /c "${scriptPath}" ${args.join(' ')}`
|
|
42
|
+
: `${shell} "${scriptPath}" ${args.join(' ')}`;
|
|
43
|
+
|
|
44
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
45
|
+
cwd,
|
|
46
|
+
timeout,
|
|
47
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
success: true,
|
|
53
|
+
output: stdout + (stderr ? `\n[stderr]\n${stderr}` : ''),
|
|
54
|
+
metadata: { script: scriptPath },
|
|
55
|
+
};
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
const e = err as { message?: string; stdout?: string; stderr?: string };
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: e.message || String(err),
|
|
61
|
+
output: [e.stdout, e.stderr].filter(Boolean).join('\n'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { runningProcesses } from './run_async.js';
|
|
2
|
+
import type { Tool } from '../../tool-types.js';
|
|
3
|
+
|
|
4
|
+
export const stop_command: Tool = {
|
|
5
|
+
name: 'stop_command',
|
|
6
|
+
description: '终止正在执行的异步命令',
|
|
7
|
+
category: 'shell',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
task_id: { type: 'string', description: '任务标识' },
|
|
12
|
+
signal: { type: 'string', description: '终止信号: SIGTERM, SIGKILL', enum: ['SIGTERM', 'SIGKILL'] },
|
|
13
|
+
},
|
|
14
|
+
required: ['task_id'],
|
|
15
|
+
},
|
|
16
|
+
dangerous: false,
|
|
17
|
+
requiresApproval: true,
|
|
18
|
+
async execute(params) {
|
|
19
|
+
try {
|
|
20
|
+
const taskId = params.task_id as string;
|
|
21
|
+
const signal = (params.signal as string) ?? 'SIGTERM';
|
|
22
|
+
const proc = runningProcesses.get(taskId);
|
|
23
|
+
|
|
24
|
+
if (!proc) {
|
|
25
|
+
return { success: false, error: `任务不存在或已完成: ${taskId}`, output: '' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (signal === 'SIGKILL') {
|
|
29
|
+
proc.kill('SIGKILL');
|
|
30
|
+
} else {
|
|
31
|
+
proc.kill('SIGTERM');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
runningProcesses.delete(taskId);
|
|
35
|
+
return { success: true, output: `已终止任务: ${taskId} (信号: ${signal})` };
|
|
36
|
+
} catch (err: unknown) {
|
|
37
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Tool } from '../../tool-types.js';
|
|
2
|
+
|
|
3
|
+
export const terminal_resize: Tool = {
|
|
4
|
+
name: 'terminal_resize',
|
|
5
|
+
description: '调整终端尺寸',
|
|
6
|
+
category: 'shell',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
rows: { type: 'number', description: '行数' },
|
|
11
|
+
cols: { type: 'number', description: '列数' },
|
|
12
|
+
},
|
|
13
|
+
required: ['rows', 'cols'],
|
|
14
|
+
},
|
|
15
|
+
dangerous: false,
|
|
16
|
+
requiresApproval: false,
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const rows = params.rows as number;
|
|
20
|
+
const cols = params.cols as number;
|
|
21
|
+
process.stdout.write(`\x1b[8;${rows};${cols}t`);
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
output: `终端尺寸已调整: ${cols}x${rows}`,
|
|
25
|
+
metadata: { rows, cols },
|
|
26
|
+
};
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|