auto-update-daily-20260202
  1/**
  2 * OpenCode plugin that wraps Claude Code hooks (Go binaries)
  3 *
  4 * This plugin provides compatibility with existing claude-hooks-* binaries
  5 * by translating OpenCode's event format to Claude Code's JSON format.
  6 *
  7 * Hooks ported:
  8 * - tool.execute.before -> claude-hooks-validate-git-push
  9 * - tool.execute.after  -> claude-hooks-capture-tool-output, claude-hooks-update-terminal-title
 10 * - session.created     -> claude-hooks-initialize-session
 11 * - session.idle        -> claude-hooks-save-session (approximation of SessionEnd)
 12 */
 13
 14import type { Plugin } from "@opencode-ai/plugin"
 15
 16/**
 17 * Convert OpenCode tool input to Claude Code PreToolUse format
 18 */
 19function toClaudePreToolUse(input: any): string {
 20  return JSON.stringify({
 21    tool_name: capitalizeFirst(input.tool || input.call?.name || "unknown"),
 22    tool_input: input.args || input.call?.input || {},
 23    conversation_id: "opencode",
 24  })
 25}
 26
 27/**
 28 * Convert OpenCode tool result to Claude Code PostToolUse format
 29 */
 30function toClaudePostToolUse(input: any, result: any): string {
 31  return JSON.stringify({
 32    tool_name: capitalizeFirst(input.tool || input.call?.name || "unknown"),
 33    tool_input: input.args || input.call?.input || {},
 34    tool_response: result || {},
 35    conversation_id: "opencode",
 36    timestamp: new Date().toISOString(),
 37  })
 38}
 39
 40function capitalizeFirst(str: string): string {
 41  return str.charAt(0).toUpperCase() + str.slice(1)
 42}
 43
 44export const ClaudeHooksPlugin: Plugin = async ({ $ }) => {
 45  // Track if we've already run session init (debounce)
 46  let sessionInitialized = false
 47
 48  return {
 49    /**
 50     * PreToolUse equivalent - validate git push commands
 51     */
 52    "tool.execute.before": async (input, output) => {
 53      // Only check bash/shell commands
 54      const toolName = input.tool || input.call?.name || ""
 55      if (toolName.toLowerCase() !== "bash" && toolName.toLowerCase() !== "shell") {
 56        return
 57      }
 58
 59      try {
 60        const jsonInput = toClaudePreToolUse(input)
 61        const result = await $`echo ${jsonInput} | claude-hooks-validate-git-push`.quiet()
 62
 63        if (result.exitCode !== 0) {
 64          // Block the command
 65          const stderr = result.stderr.toString().trim()
 66          output.abort = stderr || "Blocked by claude-hooks-validate-git-push"
 67        }
 68      } catch (error) {
 69        // Don't block on hook errors - fail open
 70        console.error("[claude-hooks] validate-git-push error:", error)
 71      }
 72    },
 73
 74    /**
 75     * PostToolUse equivalent - capture tool output and update terminal title
 76     */
 77    "tool.execute.after": async (input, output) => {
 78      try {
 79        const jsonInput = toClaudePostToolUse(input, output)
 80
 81        // Run both hooks in parallel (fire and forget)
 82        await Promise.allSettled([
 83          $`echo ${jsonInput} | claude-hooks-capture-tool-output`.quiet(),
 84          $`echo ${jsonInput} | claude-hooks-update-terminal-title`.quiet(),
 85        ])
 86      } catch (error) {
 87        // Silent failure - don't disrupt workflow
 88        console.error("[claude-hooks] post-tool error:", error)
 89      }
 90    },
 91
 92    /**
 93     * Event handler for session lifecycle
 94     */
 95    event: async ({ event }) => {
 96      // SessionStart equivalent
 97      if (event.type === "session.created" && !sessionInitialized) {
 98        sessionInitialized = true
 99        try {
100          await $`claude-hooks-initialize-session`.quiet()
101        } catch (error) {
102          console.error("[claude-hooks] initialize-session error:", error)
103        }
104      }
105
106      // SessionEnd equivalent (session.idle is closest approximation)
107      // Note: This triggers when the session becomes idle, not on explicit end
108      // Uncomment if you want save-session prompts on idle:
109      // if (event.type === "session.idle") {
110      //   try {
111      //     await $`claude-hooks-save-session`.quiet()
112      //   } catch (error) {
113      //     console.error("[claude-hooks] save-session error:", error)
114      //   }
115      // }
116    },
117  }
118}
119
120// Default export for OpenCode plugin discovery
121export default ClaudeHooksPlugin