import * as fs from "node:fs"; import * as path from "node:path"; import { execSync } from "node:child_process"; import * as os from "node:os"; const BASE_DIR = path.resolve(process.env.GCA_WORKSPACE ?? process.cwd()); function safePath(p: string): string { const r = path.resolve(BASE_DIR, p); if (!r.startsWith(BASE_DIR)) throw new Error(`Path traversal blocked: ${p}`); return r; } // ── C-002: file_list ──────────────────────────────────────── export async function file_list(args: { path: string; recursive?: boolean }): Promise { const dir = safePath(args.path || "."); const entries = fs.readdirSync(dir, { withFileTypes: true }); const result = entries.map(e => ({ name: e.name, type: e.isDirectory() ? "dir" : e.isFile() ? "file" : "other", size: e.isFile() ? fs.statSync(path.join(dir, e.name)).size : undefined, })); return { path: dir, entries: result, count: result.length }; } // ── C-003: file_read ──────────────────────────────────────── export async function file_read(args: { path: string; encoding?: string; offset?: number; limit?: number }): Promise { const filePath = safePath(args.path); const enc = (args.encoding === "base64" ? "base64" : "utf8") as BufferEncoding; let content = fs.readFileSync(filePath, enc); if (enc === "utf8") { const lines = (content as string).split("\n"); content = lines.slice(args.offset ?? 0, args.limit ? (args.offset ?? 0) + args.limit : undefined).join("\n"); } return { path: filePath, content, size: fs.statSync(filePath).size }; } // ── C-004: file_write ─────────────────────────────────────── export async function file_write(args: { path: string; content: string; encoding?: string }): Promise { const filePath = safePath(args.path); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, args.content, (args.encoding === "base64" ? "base64" : "utf8") as BufferEncoding); return { ok: true, path: filePath, size: fs.statSync(filePath).size }; } // ── C-005: file_move ──────────────────────────────────────── export async function file_move(args: { from: string; to: string }): Promise { const fromPath = safePath(args.from); const toPath = safePath(args.to); fs.mkdirSync(path.dirname(toPath), { recursive: true }); fs.renameSync(fromPath, toPath); return { ok: true, from: fromPath, to: toPath }; } // ── C-008: exec ───────────────────────────────────────────── function sanitizeCommand(cmd: string): string { // Allow: alphanumeric, dash, slash, dot, colon, underscore, space, equals, comma if (/[^a-zA-Z0-9_./\-: =,]/.test(cmd)) throw new Error(`Dangerous chars: ${cmd.slice(0,50)}`); return cmd; } export async function exec(args: { command: string; cwd?: string; timeout?: number }): Promise { const command = sanitizeCommand(args.command); const cwd = safePath(args.cwd ?? "."); try { const stdout = execSync(command, { cwd, timeout: args.timeout ?? 30000, encoding: "utf8", maxBuffer: 10*1024*1024, stdio: ["pipe","pipe","pipe"] }); return { ok: true, stdout: stdout.toString(), stderr: "" }; } catch (err: any) { return { ok: false, stdout: err.stdout?.toString() ?? "", stderr: err.stderr?.toString() ?? "", exitCode: err.status ?? -1, signal: err.signal }; } } // ── C-010: process_list ───────────────────────────────────── export async function process_list(args: { filter?: string }): Promise { let stdout: string; try { if (process.platform === "win32") { stdout = execSync("tasklist /FO CSV /NH", { encoding: "utf8" }).toString(); } else { stdout = execSync("ps aux --no-headers", { encoding: "utf8" }).toString(); } } catch { return { error: "Failed to list processes" }; } const processes = stdout.trim().split("\n").map(line => { if (process.platform === "win32") { const [name, pid, , mem] = line.replace(/"/g, "").split(","); return { name: name?.trim(), pid: Number(pid), mem: mem?.trim() }; } const parts = line.trim().split(/\s+/); return { user: parts[0], pid: Number(parts[1]), cpu: parts[2], mem: parts[3], name: parts[10] }; }); const filtered = args.filter ? processes.filter(p => p.name?.toLowerCase().includes(args.filter!.toLowerCase())) : processes.slice(0, 50); return { count: filtered.length, processes: filtered }; } // ── C-011: sysinfo ────────────────────────────────────────── export async function sysinfo(_args: {}): Promise { const tm = os.totalmem(); const fm = os.freemem(); return { hostname: os.hostname(), platform: os.platform(), arch: os.arch(), cpus: os.cpus().length, loadavg: os.loadavg(), memory: { total: tm, free: fm, used: tm - fm, percent: Math.round(((tm-fm)/tm)*100) }, uptime: os.uptime(), node: process.version }; }