import fsp from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; import type { AgentPlugin, InstallConfig, PluginStatus } from './base.js'; import { SUPPORTED_AGENTS } from './registry.js'; const AGENT_KEY = 'opencode'; const entry = SUPPORTED_AGENTS.get(AGENT_KEY)!; function resolveConfigDir(): string { return path.join(os.homedir(), entry.configDir); } function resolveConfigPath(): string { return path.join(resolveConfigDir(), entry.configFile); } function generatePluginScript(hookScriptPath: string, guardScriptPath: string): string { return `// Elydora Audit Plugin for OpenCode // Generated by @elydora/sdk — DO NOT EDIT import { spawn, spawnSync } from 'node:child_process'; const HOOK_SCRIPT_PATH = ${JSON.stringify(hookScriptPath)}; const GUARD_SCRIPT_PATH = ${JSON.stringify(guardScriptPath)}; export const ElydoraAuditPlugin = async (ctx) => { return { "tool.execute.before": async () => { // Guard — blocks tool if agent is frozen let result; try { result = spawnSync('node', [GUARD_SCRIPT_PATH], { timeout: 5000, stdio: ['pipe', 'ignore', 'pipe'], }); } catch (error) { throw new Error('Elydora guard failed: ' + error.message); } if (result.error) { throw new Error('Elydora guard failed: ' + result.error.message); } if (result.status !== 0) { const message = result.stderr?.toString().trim() || 'Guard exited with status ' + result.status; const prefix = result.status === 2 ? '' : 'Elydora guard failed: '; throw new Error(prefix + message); } }, "tool.execute.after": async (input, output) => { const data = JSON.stringify({ tool_name: input?.tool || 'unknown', tool_input: input?.args || output?.args || {}, session_id: input?.sessionID || ctx?.project?.name || 'unknown', }); const child = spawn('node', [HOOK_SCRIPT_PATH], { detached: true, stdio: ['pipe', 'ignore', 'ignore'], }); child.on('error', (error) => { console.error('[elydora] audit hook failed:', error.message); }); child.stdin.on('error', (error) => { console.error('[elydora] audit input failed:', error.message); }); child.stdin.end(data); child.unref(); }, }; }; `; } export const opencodePlugin: AgentPlugin = { async install(config: InstallConfig): Promise { const configDir = resolveConfigDir(); await fsp.mkdir(configDir, { recursive: true }); const configPath = resolveConfigPath(); const script = generatePluginScript(config.hookScriptPath, config.guardScriptPath); await fsp.writeFile(configPath, script, 'utf-8'); }, async uninstall(_agentId?: string): Promise { const configPath = resolveConfigPath(); try { await fsp.unlink(configPath); } catch { // Already removed } }, async status(): Promise { const configPath = resolveConfigPath(); let hookConfigured = false; let hookScriptPath = ''; let guardScriptPath = ''; try { const raw = await fsp.readFile(configPath, 'utf-8'); const hookMatch = raw.match(/const HOOK_SCRIPT_PATH = ("(?:\\.|[^"\\])*");/); const guardMatch = raw.match(/const GUARD_SCRIPT_PATH = ("(?:\\.|[^"\\])*");/); if (hookMatch && guardMatch) { hookScriptPath = JSON.parse(hookMatch[1]); guardScriptPath = JSON.parse(guardMatch[1]); hookConfigured = true; } } catch { // File doesn't exist } let hookScriptExists = false; if (hookScriptPath && guardScriptPath) { try { await fsp.access(hookScriptPath); await fsp.access(guardScriptPath); hookScriptExists = true; } catch { // File doesn't exist } } return { installed: hookConfigured && hookScriptExists, agentName: AGENT_KEY, displayName: entry.name, hookConfigured, hookScriptExists, configPath, }; }, };