Commit 81bbedeaf66c
Changed files (2)
dots
.config
opencode
plugin
dots/.config/opencode/plugin/claude-hooks.ts
@@ -0,0 +1,121 @@
+/**
+ * OpenCode plugin that wraps Claude Code hooks (Go binaries)
+ *
+ * This plugin provides compatibility with existing claude-hooks-* binaries
+ * by translating OpenCode's event format to Claude Code's JSON format.
+ *
+ * Hooks ported:
+ * - tool.execute.before -> claude-hooks-validate-git-push
+ * - tool.execute.after -> claude-hooks-capture-tool-output, claude-hooks-update-terminal-title
+ * - session.created -> claude-hooks-initialize-session
+ * - session.idle -> claude-hooks-save-session (approximation of SessionEnd)
+ */
+
+import type { Plugin } from "@opencode-ai/plugin"
+
+/**
+ * Convert OpenCode tool input to Claude Code PreToolUse format
+ */
+function toClaudePreToolUse(input: any): string {
+ return JSON.stringify({
+ tool_name: capitalizeFirst(input.tool || input.call?.name || "unknown"),
+ tool_input: input.args || input.call?.input || {},
+ conversation_id: "opencode",
+ })
+}
+
+/**
+ * Convert OpenCode tool result to Claude Code PostToolUse format
+ */
+function toClaudePostToolUse(input: any, result: any): string {
+ return JSON.stringify({
+ tool_name: capitalizeFirst(input.tool || input.call?.name || "unknown"),
+ tool_input: input.args || input.call?.input || {},
+ tool_response: result || {},
+ conversation_id: "opencode",
+ timestamp: new Date().toISOString(),
+ })
+}
+
+function capitalizeFirst(str: string): string {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+}
+
+export const ClaudeHooksPlugin: Plugin = async ({ $ }) => {
+ // Track if we've already run session init (debounce)
+ let sessionInitialized = false
+
+ return {
+ /**
+ * PreToolUse equivalent - validate git push commands
+ */
+ "tool.execute.before": async (input, output) => {
+ // Only check bash/shell commands
+ const toolName = input.tool || input.call?.name || ""
+ if (toolName.toLowerCase() !== "bash" && toolName.toLowerCase() !== "shell") {
+ return
+ }
+
+ try {
+ const jsonInput = toClaudePreToolUse(input)
+ const result = await $`echo ${jsonInput} | claude-hooks-validate-git-push`.quiet()
+
+ if (result.exitCode !== 0) {
+ // Block the command
+ const stderr = result.stderr.toString().trim()
+ output.abort = stderr || "Blocked by claude-hooks-validate-git-push"
+ }
+ } catch (error) {
+ // Don't block on hook errors - fail open
+ console.error("[claude-hooks] validate-git-push error:", error)
+ }
+ },
+
+ /**
+ * PostToolUse equivalent - capture tool output and update terminal title
+ */
+ "tool.execute.after": async (input, output) => {
+ try {
+ const jsonInput = toClaudePostToolUse(input, output)
+
+ // Run both hooks in parallel (fire and forget)
+ await Promise.allSettled([
+ $`echo ${jsonInput} | claude-hooks-capture-tool-output`.quiet(),
+ $`echo ${jsonInput} | claude-hooks-update-terminal-title`.quiet(),
+ ])
+ } catch (error) {
+ // Silent failure - don't disrupt workflow
+ console.error("[claude-hooks] post-tool error:", error)
+ }
+ },
+
+ /**
+ * Event handler for session lifecycle
+ */
+ event: async ({ event }) => {
+ // SessionStart equivalent
+ if (event.type === "session.created" && !sessionInitialized) {
+ sessionInitialized = true
+ try {
+ await $`claude-hooks-initialize-session`.quiet()
+ } catch (error) {
+ console.error("[claude-hooks] initialize-session error:", error)
+ }
+ }
+
+ // SessionEnd equivalent (session.idle is closest approximation)
+ // Note: This triggers when the session becomes idle, not on explicit end
+ // Uncomment if you want save-session prompts on idle:
+ // if (event.type === "session.idle") {
+ // try {
+ // await $`claude-hooks-save-session`.quiet()
+ // } catch (error) {
+ // console.error("[claude-hooks] save-session error:", error)
+ // }
+ // }
+ },
+ }
+}
+
+// Default export for OpenCode plugin discovery
+export default ClaudeHooksPlugin
dots/Makefile
@@ -53,9 +53,10 @@ lazypr : ~/.config/lazypr/config.toml
all += gh-news
gh-news : ~/.config/gh-news/config.toml
-all += git-template copilot-hooks
+all += git-template copilot-hooks opencode-plugin
git-template : ~/.config/git/template
copilot-hooks : ~/.config/copilot-hooks
+opencode-plugin : ~/.config/opencode/plugin
# Backward compatibility: symlink ~/.claude to ~/.config/claude
~/.claude : force