package plugins import ( "fmt" "os" "path/filepath" "strings" ) // OpenCodePlugin manages the Elydora audit hook for OpenCode. // It writes .opencode/plugins/elydora-audit.js in the home directory. type OpenCodePlugin struct{} func (p *OpenCodePlugin) configPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("resolve home directory: %w", err) } return filepath.Join(home, ".opencode", "plugins", "elydora-audit.js"), nil } func (p *OpenCodePlugin) Install(config InstallConfig) error { scriptPath, err := hookScriptPath(config.AgentID) if err != nil { return err } if config.HookScript != "" { scriptPath = config.HookScript } if err := GenerateHookScript(scriptPath, config); err != nil { return fmt.Errorf("generate hook script: %w", err) } guardPath := config.GuardScriptPath if guardPath == "" { guardPath, err = guardScriptPath(config.AgentID) if err != nil { return err } } pluginFile, err := p.configPath() if err != nil { return err } dir := filepath.Dir(pluginFile) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("create directory %s: %w", dir, err) } content := buildOpenCodePlugin(scriptPath, guardPath) if err := os.WriteFile(pluginFile, []byte(content), 0644); err != nil { return fmt.Errorf("write %s: %w", pluginFile, err) } fmt.Printf("Installed Elydora hook for OpenCode at %s\n", pluginFile) return nil } func buildOpenCodePlugin(scriptPath, guardPath string) string { return fmt.Sprintf(`// Elydora Audit Plugin for OpenCode // Generated by @elydora/sdk — DO NOT EDIT const { spawn, spawnSync } = require('node:child_process'); module.exports = { name: 'elydora-audit', version: '1.0.0', description: 'Sends tool-use events to the Elydora tamper-evident audit platform', hooks: { preToolUse(context) { // Synchronous guard — blocks tool if agent is frozen try { const result = spawnSync('node', [%q], { timeout: 5000, stdio: ['pipe', 'ignore', 'pipe'], }); if (result.status !== 0) { const msg = result.stderr ? result.stderr.toString().trim() : 'Agent is frozen by Elydora.'; return { blocked: true, reason: msg }; } } catch { // Fail-open — allow if guard can't run } }, postToolUse(context) { try { const input = JSON.stringify({ tool_name: context.toolName || context.tool_name || 'unknown', tool_input: context.toolInput || context.tool_input || {}, session_id: context.sessionId || context.session_id || 'unknown', }); const child = spawn('node', [%q], { detached: true, stdio: ['pipe', 'ignore', 'ignore'], }); child.stdin.write(input); child.stdin.end(); child.unref(); } catch { // Fire-and-forget — never block the developer } }, }, }; `, guardPath, scriptPath) } func (p *OpenCodePlugin) Uninstall(agentID string) error { pluginFile, err := p.configPath() if err != nil { return err } if err := os.Remove(pluginFile); err != nil && !os.IsNotExist(err) { return fmt.Errorf("remove %s: %w", pluginFile, err) } if agentID != "" { scriptPath, _ := hookScriptPath(agentID) if scriptPath != "" { os.Remove(scriptPath) } gPath, _ := guardScriptPath(agentID) if gPath != "" { os.Remove(gPath) } } fmt.Println("Uninstalled Elydora hook for OpenCode.") return nil } func (p *OpenCodePlugin) Status() (PluginStatus, error) { pluginFile, err := p.configPath() if err != nil { return PluginStatus{}, err } status := PluginStatus{ AgentName: "opencode", DisplayName: "OpenCode", ConfigPath: pluginFile, } if _, err := os.Stat(pluginFile); err == nil { status.HookConfigured = true // Read the plugin file to extract the hook script path data, readErr := os.ReadFile(pluginFile) if readErr == nil { content := string(data) // Look for the elydora hook.js path in the plugin file content if idx := strings.Index(content, ".elydora"); idx >= 0 { // Extract path from the spawn('node', ['/path/to/hook.js']) pattern // Pragmatic: scan ~/.elydora/*/hook.js for existence home, homeErr := os.UserHomeDir() if homeErr == nil { entries, dirErr := os.ReadDir(filepath.Join(home, ".elydora")) if dirErr == nil { for _, entry := range entries { if entry.IsDir() { hookPath := filepath.Join(home, ".elydora", entry.Name(), "hook.js") if _, statErr := os.Stat(hookPath); statErr == nil { status.HookScriptExists = true break } } } } } } } } status.Installed = status.HookConfigured && status.HookScriptExists return status, nil }