feat: 新增 6 个工具并重组目录结构
新增工具: - grep_content: 在文件内容中搜索文本/正则表达式 - get_file_info: 获取文件元信息(大小、权限、时间等) - move_file: 移动或重命名文件/目录 - copy_file: 复制文件或目录(支持递归) - delete_file: 删除文件或目录 - create_directory: 创建目录 目录重组: - src/tools/shell/ - Shell 相关工具(bash) - src/tools/filesystem/ - 文件系统工具(12个) - 每个子目录有独立的 index.ts 导出 权限系统扩展: - 新增操作类型: grep, info, move, copy, mkdir - 读取类操作默认允许,写入类操作需要确认
This commit is contained in:
@@ -22,7 +22,12 @@ const DEFAULT_CONFIG: FilePermissionConfig = {
|
|||||||
edit: 'ask', // 编辑需要确认
|
edit: 'ask', // 编辑需要确认
|
||||||
list: 'allow', // 列目录默认允许
|
list: 'allow', // 列目录默认允许
|
||||||
search: 'allow', // 搜索默认允许
|
search: 'allow', // 搜索默认允许
|
||||||
|
grep: 'allow', // 内容搜索默认允许
|
||||||
|
info: 'allow', // 获取文件信息默认允许
|
||||||
|
move: 'ask', // 移动需要确认
|
||||||
|
copy: 'ask', // 复制需要确认
|
||||||
delete: 'ask', // 删除需要确认
|
delete: 'ask', // 删除需要确认
|
||||||
|
mkdir: 'ask', // 创建目录需要确认
|
||||||
},
|
},
|
||||||
sensitivePaths: [
|
sensitivePaths: [
|
||||||
// 系统关键路径 - 拒绝
|
// 系统关键路径 - 拒绝
|
||||||
|
|||||||
+17
-1
@@ -26,7 +26,18 @@ export interface PermissionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 文件操作类型
|
// 文件操作类型
|
||||||
export type FileOperation = 'read' | 'write' | 'edit' | 'list' | 'search' | 'delete';
|
export type FileOperation =
|
||||||
|
| 'read' // 读取文件
|
||||||
|
| 'write' // 写入文件
|
||||||
|
| 'edit' // 编辑文件
|
||||||
|
| 'list' // 列出目录
|
||||||
|
| 'search' // 搜索文件
|
||||||
|
| 'grep' // 搜索内容
|
||||||
|
| 'info' // 获取文件信息
|
||||||
|
| 'move' // 移动/重命名
|
||||||
|
| 'copy' // 复制
|
||||||
|
| 'delete' // 删除
|
||||||
|
| 'mkdir'; // 创建目录
|
||||||
|
|
||||||
// 文件权限请求上下文
|
// 文件权限请求上下文
|
||||||
export interface FilePermissionContext {
|
export interface FilePermissionContext {
|
||||||
@@ -46,7 +57,12 @@ export interface FilePermissionConfig {
|
|||||||
edit: PermissionAction;
|
edit: PermissionAction;
|
||||||
list: PermissionAction;
|
list: PermissionAction;
|
||||||
search: PermissionAction;
|
search: PermissionAction;
|
||||||
|
grep: PermissionAction;
|
||||||
|
info: PermissionAction;
|
||||||
|
move: PermissionAction;
|
||||||
|
copy: PermissionAction;
|
||||||
delete: PermissionAction;
|
delete: PermissionAction;
|
||||||
|
mkdir: PermissionAction;
|
||||||
};
|
};
|
||||||
// 敏感路径规则(优先于操作默认策略)
|
// 敏感路径规则(优先于操作默认策略)
|
||||||
sensitivePaths: {
|
sensitivePaths: {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
复制文件或目录。支持递归复制整个目录结构。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
创建新目录。支持递归创建父目录。如果目录已存在则不会报错。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
删除文件或目录。删除目录时可以选择是否递归删除。需要谨慎使用。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
获取文件或目录的详细信息,包括大小、权限、创建时间、修改时间等元数据。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
在指定目录中搜索文件内容。支持正则表达式,可以指定文件类型过滤。用于查找代码中的特定文本、函数调用、变量引用等。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
移动或重命名文件/目录。可以将文件移动到新位置或更改文件名。
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
async function copyRecursive(source: string, dest: string): Promise<void> {
|
||||||
|
const stats = await fs.stat(source);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
await fs.mkdir(dest, { recursive: true });
|
||||||
|
const entries = await fs.readdir(source);
|
||||||
|
for (const entry of entries) {
|
||||||
|
await copyRecursive(path.join(source, entry), path.join(dest, entry));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await fs.copyFile(source, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const copyFileTool: Tool = {
|
||||||
|
name: 'copy_file',
|
||||||
|
description: loadDescription('copy_file'),
|
||||||
|
parameters: {
|
||||||
|
source: {
|
||||||
|
type: 'string',
|
||||||
|
description: '源文件或目录的路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
type: 'string',
|
||||||
|
description: '目标路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const source = params.source as string;
|
||||||
|
const destination = params.destination as string;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
const absoluteSource = path.isAbsolute(source)
|
||||||
|
? source
|
||||||
|
: path.join(cwd, source);
|
||||||
|
|
||||||
|
const absoluteDest = path.isAbsolute(destination)
|
||||||
|
? destination
|
||||||
|
: path.join(cwd, destination);
|
||||||
|
|
||||||
|
// 权限检查 - 源文件需要 read 权限
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const sourcePermResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'read',
|
||||||
|
path: absoluteSource,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sourcePermResult.allowed) {
|
||||||
|
if (sourcePermResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 读取 ${absoluteSource}\n原因: ${sourcePermResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${sourcePermResult.reason || '不允许读取此文件'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查 - 目标位置需要 copy 权限
|
||||||
|
const destPermResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'copy',
|
||||||
|
path: absoluteDest,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!destPermResult.allowed) {
|
||||||
|
if (destPermResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 复制到 ${absoluteDest}\n原因: ${destPermResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${destPermResult.reason || '不允许复制到此位置'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查源文件是否存在
|
||||||
|
const sourceStats = await fs.stat(absoluteSource);
|
||||||
|
|
||||||
|
// 检查目标是否是目录
|
||||||
|
let finalDest = absoluteDest;
|
||||||
|
try {
|
||||||
|
const destStats = await fs.stat(absoluteDest);
|
||||||
|
if (destStats.isDirectory()) {
|
||||||
|
// 如果目标是目录,将源文件复制到该目录下
|
||||||
|
finalDest = path.join(absoluteDest, path.basename(absoluteSource));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 目标不存在,直接使用目标路径
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目标目录存在
|
||||||
|
await fs.mkdir(path.dirname(finalDest), { recursive: true });
|
||||||
|
|
||||||
|
// 执行复制
|
||||||
|
if (sourceStats.isDirectory()) {
|
||||||
|
await copyRecursive(absoluteSource, finalDest);
|
||||||
|
} else {
|
||||||
|
await fs.copyFile(absoluteSource, finalDest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `已复制: ${absoluteSource} -> ${finalDest}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
export const createDirectoryTool: Tool = {
|
||||||
|
name: 'create_directory',
|
||||||
|
description: loadDescription('create_directory'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要创建的目录路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const dirPath = params.path as string;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
const absolutePath = path.isAbsolute(dirPath)
|
||||||
|
? dirPath
|
||||||
|
: path.join(cwd, dirPath);
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const permResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'mkdir',
|
||||||
|
path: absolutePath,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permResult.allowed) {
|
||||||
|
if (permResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 创建目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${permResult.reason || '不允许创建此目录'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查目录是否已存在
|
||||||
|
try {
|
||||||
|
const stats = await fs.stat(absolutePath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `目录已存在: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `路径已存在且不是目录: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 目录不存在,继续创建
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目录(递归创建父目录)
|
||||||
|
await fs.mkdir(absolutePath, { recursive: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `已创建目录: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
export const deleteFileTool: Tool = {
|
||||||
|
name: 'delete_file',
|
||||||
|
description: loadDescription('delete_file'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要删除的文件或目录的路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
recursive: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: '是否递归删除目录(默认 false)',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const filePath = params.path as string;
|
||||||
|
const recursive = (params.recursive as boolean) || false;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.join(cwd, filePath);
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const permResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'delete',
|
||||||
|
path: absolutePath,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permResult.allowed) {
|
||||||
|
if (permResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 删除 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${permResult.reason || '不允许删除此文件'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.stat(absolutePath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
if (!recursive) {
|
||||||
|
// 检查目录是否为空
|
||||||
|
const entries = await fs.readdir(absolutePath);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `目录不为空。如需删除非空目录,请设置 recursive: true`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await fs.rmdir(absolutePath);
|
||||||
|
} else {
|
||||||
|
await fs.rm(absolutePath, { recursive: true });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `已删除目录: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await fs.unlink(absolutePath);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `已删除文件: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const editFileTool: Tool = {
|
export const editFileTool: Tool = {
|
||||||
name: 'edit_file',
|
name: 'edit_file',
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
function formatSize(bytes: number): string {
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
let unitIndex = 0;
|
||||||
|
let size = bytes;
|
||||||
|
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPermissions(mode: number): string {
|
||||||
|
const types: Record<number, string> = {
|
||||||
|
0o140000: 'socket',
|
||||||
|
0o120000: 'symbolic link',
|
||||||
|
0o100000: 'regular file',
|
||||||
|
0o060000: 'block device',
|
||||||
|
0o040000: 'directory',
|
||||||
|
0o020000: 'character device',
|
||||||
|
0o010000: 'FIFO',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileType = Object.entries(types).find(([mask]) => (mode & 0o170000) === Number(mask));
|
||||||
|
|
||||||
|
const perms = [
|
||||||
|
(mode & 0o400) ? 'r' : '-',
|
||||||
|
(mode & 0o200) ? 'w' : '-',
|
||||||
|
(mode & 0o100) ? 'x' : '-',
|
||||||
|
(mode & 0o040) ? 'r' : '-',
|
||||||
|
(mode & 0o020) ? 'w' : '-',
|
||||||
|
(mode & 0o010) ? 'x' : '-',
|
||||||
|
(mode & 0o004) ? 'r' : '-',
|
||||||
|
(mode & 0o002) ? 'w' : '-',
|
||||||
|
(mode & 0o001) ? 'x' : '-',
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return `${fileType?.[1] || 'unknown'} (${perms})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFileInfoTool: Tool = {
|
||||||
|
name: 'get_file_info',
|
||||||
|
description: loadDescription('get_file_info'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '文件或目录的路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const filePath = params.path as string;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.join(cwd, filePath);
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const permResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'info',
|
||||||
|
path: absolutePath,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permResult.allowed) {
|
||||||
|
if (permResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 获取文件信息 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${permResult.reason || '不允许获取此文件信息'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.stat(absolutePath);
|
||||||
|
const info = [
|
||||||
|
`路径: ${absolutePath}`,
|
||||||
|
`类型: ${stats.isDirectory() ? '目录' : stats.isFile() ? '文件' : stats.isSymbolicLink() ? '符号链接' : '其他'}`,
|
||||||
|
`大小: ${formatSize(stats.size)}`,
|
||||||
|
`权限: ${formatPermissions(stats.mode)}`,
|
||||||
|
`创建时间: ${stats.birthtime.toLocaleString()}`,
|
||||||
|
`修改时间: ${stats.mtime.toLocaleString()}`,
|
||||||
|
`访问时间: ${stats.atime.toLocaleString()}`,
|
||||||
|
`inode: ${stats.ino}`,
|
||||||
|
`硬链接数: ${stats.nlink}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果是符号链接,显示目标
|
||||||
|
if (stats.isSymbolicLink()) {
|
||||||
|
try {
|
||||||
|
const target = await fs.readlink(absolutePath);
|
||||||
|
info.push(`链接目标: ${target}`);
|
||||||
|
} catch {
|
||||||
|
// 忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是目录,统计子项数量
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(absolutePath);
|
||||||
|
info.push(`子项数量: ${entries.length}`);
|
||||||
|
} catch {
|
||||||
|
// 忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: info.join('\n'),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
interface GrepMatch {
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const grepContentTool: Tool = {
|
||||||
|
name: 'grep_content',
|
||||||
|
description: loadDescription('grep_content'),
|
||||||
|
parameters: {
|
||||||
|
directory: {
|
||||||
|
type: 'string',
|
||||||
|
description: '搜索的起始目录',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
type: 'string',
|
||||||
|
description: '搜索的文本或正则表达式模式',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
file_pattern: {
|
||||||
|
type: 'string',
|
||||||
|
description: '文件名匹配模式(可选,如 *.ts)',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
max_results: {
|
||||||
|
type: 'number',
|
||||||
|
description: '最大结果数量(可选,默认 100)',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const directory = params.directory as string;
|
||||||
|
const pattern = params.pattern as string;
|
||||||
|
const filePattern = params.file_pattern as string | undefined;
|
||||||
|
const maxResults = (params.max_results as number) || 100;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const absolutePath = path.isAbsolute(directory)
|
||||||
|
? directory
|
||||||
|
: path.join(cwd, directory);
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const permResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'grep',
|
||||||
|
path: absolutePath,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permResult.allowed) {
|
||||||
|
if (permResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 搜索目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${permResult.reason || '不允许搜索此目录'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches: GrepMatch[] = [];
|
||||||
|
const searchRegex = new RegExp(pattern, 'gi');
|
||||||
|
const fileRegex = filePattern
|
||||||
|
? new RegExp(filePattern.replace(/\*/g, '.*').replace(/\?/g, '.'), 'i')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
async function searchFile(filePath: string): Promise<void> {
|
||||||
|
if (matches.length >= maxResults) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(filePath, 'utf-8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (matches.length >= maxResults) break;
|
||||||
|
|
||||||
|
if (searchRegex.test(lines[i])) {
|
||||||
|
matches.push({
|
||||||
|
file: filePath,
|
||||||
|
line: i + 1,
|
||||||
|
content: lines[i].trim().substring(0, 200), // 截断过长的行
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 重置正则表达式的 lastIndex
|
||||||
|
searchRegex.lastIndex = 0;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 忽略无法读取的文件(如二进制文件)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchDirectory(dir: string, depth = 0): Promise<void> {
|
||||||
|
if (depth > 10 || matches.length >= maxResults) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (matches.length >= maxResults) break;
|
||||||
|
|
||||||
|
const fullPath = path.join(dir, entry.name);
|
||||||
|
|
||||||
|
// 跳过隐藏文件和 node_modules
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await searchDirectory(fullPath, depth + 1);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
// 检查文件名是否匹配
|
||||||
|
if (fileRegex && !fileRegex.test(entry.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await searchFile(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 忽略权限错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await searchDirectory(absolutePath);
|
||||||
|
|
||||||
|
if (matches.length === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: '没有找到匹配的内容',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = matches
|
||||||
|
.map((m) => `${m.file}:${m.line}: ${m.content}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `找到 ${matches.length} 处匹配${matches.length >= maxResults ? '(已达上限)' : ''}:\n\n${output}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 文件读写
|
||||||
|
export { readFileTool } from './read_file.js';
|
||||||
|
export { writeFileTool } from './write_file.js';
|
||||||
|
export { editFileTool } from './edit_file.js';
|
||||||
|
|
||||||
|
// 目录操作
|
||||||
|
export { listDirTool } from './list_directory.js';
|
||||||
|
export { createDirectoryTool } from './create_directory.js';
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
export { searchFilesTool } from './search_files.js';
|
||||||
|
export { grepContentTool } from './grep_content.js';
|
||||||
|
|
||||||
|
// 文件信息
|
||||||
|
export { getFileInfoTool } from './get_file_info.js';
|
||||||
|
|
||||||
|
// 文件管理
|
||||||
|
export { moveFileTool } from './move_file.js';
|
||||||
|
export { copyFileTool } from './copy_file.js';
|
||||||
|
export { deleteFileTool } from './delete_file.js';
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const listDirTool: Tool = {
|
export const listDirTool: Tool = {
|
||||||
name: 'list_directory',
|
name: 'list_directory',
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
|
import { loadDescription } from '../load_description.js';
|
||||||
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
|
export const moveFileTool: Tool = {
|
||||||
|
name: 'move_file',
|
||||||
|
description: loadDescription('move_file'),
|
||||||
|
parameters: {
|
||||||
|
source: {
|
||||||
|
type: 'string',
|
||||||
|
description: '源文件或目录的路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
type: 'string',
|
||||||
|
description: '目标路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const source = params.source as string;
|
||||||
|
const destination = params.destination as string;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
const absoluteSource = path.isAbsolute(source)
|
||||||
|
? source
|
||||||
|
: path.join(cwd, source);
|
||||||
|
|
||||||
|
const absoluteDest = path.isAbsolute(destination)
|
||||||
|
? destination
|
||||||
|
: path.join(cwd, destination);
|
||||||
|
|
||||||
|
// 权限检查 - 源文件需要 move 权限
|
||||||
|
const permissionManager = getPermissionManager();
|
||||||
|
const sourcePermResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'move',
|
||||||
|
path: absoluteSource,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sourcePermResult.allowed) {
|
||||||
|
if (sourcePermResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 移动 ${absoluteSource}\n原因: ${sourcePermResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${sourcePermResult.reason || '不允许移动此文件'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查 - 目标位置需要 write 权限
|
||||||
|
const destPermResult = await permissionManager.checkFilePermission({
|
||||||
|
operation: 'write',
|
||||||
|
path: absoluteDest,
|
||||||
|
workdir: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!destPermResult.allowed) {
|
||||||
|
if (destPermResult.needsConfirmation) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `需要用户确认: 写入到 ${absoluteDest}\n原因: ${destPermResult.reason || '需要权限确认'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `权限被拒绝: ${destPermResult.reason || '不允许写入到此位置'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查源文件是否存在
|
||||||
|
await fs.access(absoluteSource);
|
||||||
|
|
||||||
|
// 检查目标是否是目录
|
||||||
|
let finalDest = absoluteDest;
|
||||||
|
try {
|
||||||
|
const destStats = await fs.stat(absoluteDest);
|
||||||
|
if (destStats.isDirectory()) {
|
||||||
|
// 如果目标是目录,将源文件移动到该目录下
|
||||||
|
finalDest = path.join(absoluteDest, path.basename(absoluteSource));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 目标不存在,直接使用目标路径
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目标目录存在
|
||||||
|
await fs.mkdir(path.dirname(finalDest), { recursive: true });
|
||||||
|
|
||||||
|
// 执行移动
|
||||||
|
await fs.rename(absoluteSource, finalDest);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `已移动: ${absoluteSource} -> ${finalDest}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const readFileTool: Tool = {
|
export const readFileTool: Tool = {
|
||||||
name: 'read_file',
|
name: 'read_file',
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const searchFilesTool: Tool = {
|
export const searchFilesTool: Tool = {
|
||||||
name: 'search_files',
|
name: 'search_files',
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const writeFileTool: Tool = {
|
export const writeFileTool: Tool = {
|
||||||
name: 'write_file',
|
name: 'write_file',
|
||||||
+39
-10
@@ -1,18 +1,47 @@
|
|||||||
import type { Tool } from '../types/index.js';
|
import type { Tool } from '../types/index.js';
|
||||||
import { bashTool } from './bash.js';
|
|
||||||
import { readFileTool } from './read_file.js';
|
|
||||||
import { writeFileTool } from './write_file.js';
|
|
||||||
import { editFileTool } from './edit_file.js';
|
|
||||||
import { listDirTool } from './list_directory.js';
|
|
||||||
import { searchFilesTool } from './search_files.js';
|
|
||||||
|
|
||||||
// 所有可用工具的注册中心
|
// Shell 工具
|
||||||
// 添加新工具只需在此数组中添加一行
|
import { bashTool } from './shell/index.js';
|
||||||
export const allTools: Tool[] = [
|
|
||||||
bashTool,
|
// 文件系统工具
|
||||||
|
import {
|
||||||
readFileTool,
|
readFileTool,
|
||||||
writeFileTool,
|
writeFileTool,
|
||||||
editFileTool,
|
editFileTool,
|
||||||
listDirTool,
|
listDirTool,
|
||||||
|
createDirectoryTool,
|
||||||
searchFilesTool,
|
searchFilesTool,
|
||||||
|
grepContentTool,
|
||||||
|
getFileInfoTool,
|
||||||
|
moveFileTool,
|
||||||
|
copyFileTool,
|
||||||
|
deleteFileTool,
|
||||||
|
} from './filesystem/index.js';
|
||||||
|
|
||||||
|
// 所有可用工具的注册中心
|
||||||
|
// 添加新工具只需在此数组中添加一行
|
||||||
|
export const allTools: Tool[] = [
|
||||||
|
// Shell
|
||||||
|
bashTool,
|
||||||
|
|
||||||
|
// 文件读写
|
||||||
|
readFileTool,
|
||||||
|
writeFileTool,
|
||||||
|
editFileTool,
|
||||||
|
|
||||||
|
// 目录操作
|
||||||
|
listDirTool,
|
||||||
|
createDirectoryTool,
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
searchFilesTool,
|
||||||
|
grepContentTool,
|
||||||
|
|
||||||
|
// 文件信息
|
||||||
|
getFileInfoTool,
|
||||||
|
|
||||||
|
// 文件管理
|
||||||
|
moveFileTool,
|
||||||
|
copyFileTool,
|
||||||
|
deleteFileTool,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../../types/index.js';
|
||||||
import { loadDescription } from './load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { bashTool } from './bash.js';
|
||||||
Reference in New Issue
Block a user