Initial commit
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export { bashTool } from './bash.js';
|
||||
export {
|
||||
readFileTool,
|
||||
writeFileTool,
|
||||
listDirTool,
|
||||
searchFilesTool,
|
||||
} from './file.js';
|
||||
Reference in New Issue
Block a user