/** * Plugin discovery + file reading RPC handlers. */ import fs from "fs"; import path from "path"; import { join } from "path"; import { homedir } from "os"; const PLUGINS_DIR = join(homedir(), ".config", "agor", "plugins"); export function createPluginHandlers() { return { "plugin.discover": () => { try { const plugins: Array<{ id: string; name: string; version: string; description: string; main: string; permissions: string[]; }> = []; if (!fs.existsSync(PLUGINS_DIR)) return { plugins }; const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const manifestPath = join(PLUGINS_DIR, entry.name, "plugin.json"); if (!fs.existsSync(manifestPath)) continue; try { const raw = fs.readFileSync(manifestPath, "utf-8"); const manifest = JSON.parse(raw); plugins.push({ id: manifest.id ?? entry.name, name: manifest.name ?? entry.name, version: manifest.version ?? "0.0.0", description: manifest.description ?? "", main: manifest.main ?? "index.js", permissions: Array.isArray(manifest.permissions) ? manifest.permissions : [], }); } catch (parseErr) { console.error(`[plugin.discover] Bad manifest in ${entry.name}:`, parseErr); } } return { plugins }; } catch (err) { console.error("[plugin.discover]", err); return { plugins: [] }; } }, "plugin.readFile": ({ pluginId, filePath }: { pluginId: string; filePath: string }) => { try { const pluginDir = join(PLUGINS_DIR, pluginId); const resolved = path.resolve(pluginDir, filePath); if (!resolved.startsWith(pluginDir + path.sep) && resolved !== pluginDir) { return { ok: false, content: "", error: "Path traversal blocked" }; } if (!fs.existsSync(resolved)) { return { ok: false, content: "", error: "File not found" }; } const content = fs.readFileSync(resolved, "utf-8"); return { ok: true, content }; } catch (err) { const error = err instanceof Error ? err.message : String(err); console.error("[plugin.readFile]", err); return { ok: false, content: "", error }; } }, }; }