Initial commit

This commit is contained in:
2025-12-10 16:04:26 +08:00
commit ff3ec65139
13 changed files with 2714 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import type { Tool, ToolResult } from '../types/index.js';
const execAsync = promisify(exec);
export const bashTool: Tool = {
name: 'bash',
description: '执行 bash 命令。可以用于运行系统命令、安装包、git 操作等。',
parameters: {
command: {
type: 'string',
description: '要执行的 bash 命令',
required: true,
},
cwd: {
type: 'string',
description: '工作目录(可选)',
required: false,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const command = params.command as string;
const cwd = (params.cwd as string) || process.cwd();
// 安全检查:禁止危险命令
const dangerousPatterns = [
/rm\s+-rf\s+\//, // rm -rf /
/mkfs/, // 格式化磁盘
/dd\s+if=.*of=\/dev/, // 直接写入设备
/>\s*\/dev\/sd/, // 重定向到磁盘设备
];
for (const pattern of dangerousPatterns) {
if (pattern.test(command)) {
return {
success: false,
output: '',
error: '检测到危险命令,已阻止执行',
};
}
}
try {
const { stdout, stderr } = await execAsync(command, {
cwd,
timeout: 60000, // 60 秒超时
maxBuffer: 1024 * 1024 * 10, // 10MB 输出限制
});
return {
success: true,
output: stdout + (stderr ? `\nSTDERR: ${stderr}` : ''),
};
} catch (error) {
const execError = error as { stdout?: string; stderr?: string; message: string };
return {
success: false,
output: execError.stdout || '',
error: execError.stderr || execError.message,
};
}
},
};
+189
View File
@@ -0,0 +1,189 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { Tool, ToolResult } from '../types/index.js';
// 读取文件工具
export const readFileTool: Tool = {
name: 'read_file',
description: '读取指定文件的内容',
parameters: {
path: {
type: 'string',
description: '要读取的文件路径(相对或绝对路径)',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const filePath = params.path as string;
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath);
try {
const content = await fs.readFile(absolutePath, 'utf-8');
return {
success: true,
output: content,
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
// 写入文件工具
export const writeFileTool: Tool = {
name: 'write_file',
description: '创建或覆盖文件内容',
parameters: {
path: {
type: 'string',
description: '要写入的文件路径',
required: true,
},
content: {
type: 'string',
description: '要写入的内容',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const filePath = params.path as string;
const content = params.content as string;
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath);
try {
// 确保目录存在
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
await fs.writeFile(absolutePath, content, 'utf-8');
return {
success: true,
output: `文件已写入: ${absolutePath}`,
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
// 列出目录工具
export const listDirTool: Tool = {
name: 'list_directory',
description: '列出指定目录下的文件和文件夹',
parameters: {
path: {
type: 'string',
description: '要列出的目录路径',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const dirPath = params.path as string;
const absolutePath = path.isAbsolute(dirPath)
? dirPath
: path.join(process.cwd(), dirPath);
try {
const entries = await fs.readdir(absolutePath, { withFileTypes: true });
const result = entries
.map((entry) => {
const prefix = entry.isDirectory() ? '📁' : '📄';
return `${prefix} ${entry.name}`;
})
.join('\n');
return {
success: true,
output: result || '(空目录)',
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
// 搜索文件工具
export const searchFilesTool: Tool = {
name: 'search_files',
description: '在目录中搜索匹配模式的文件',
parameters: {
directory: {
type: 'string',
description: '搜索的起始目录',
required: true,
},
pattern: {
type: 'string',
description: '文件名匹配模式(支持 glob 模式,如 *.ts',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const directory = params.directory as string;
const pattern = params.pattern as string;
const absolutePath = path.isAbsolute(directory)
? directory
: path.join(process.cwd(), directory);
const matches: string[] = [];
const regex = new RegExp(
pattern.replace(/\*/g, '.*').replace(/\?/g, '.'),
'i'
);
async function searchRecursive(dir: string, depth = 0): Promise<void> {
if (depth > 10) return; // 限制递归深度
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
// 跳过 node_modules 和隐藏目录
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
continue;
}
if (entry.isDirectory()) {
await searchRecursive(fullPath, depth + 1);
} else if (regex.test(entry.name)) {
matches.push(fullPath);
}
}
} catch {
// 忽略权限错误
}
}
try {
await searchRecursive(absolutePath);
return {
success: true,
output:
matches.length > 0
? matches.join('\n')
: '没有找到匹配的文件',
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
+7
View File
@@ -0,0 +1,7 @@
export { bashTool } from './bash.js';
export {
readFileTool,
writeFileTool,
listDirTool,
searchFilesTool,
} from './file.js';