- Hook reads JSON from stdin (not env vars) matching Claude Code protocol - end_turn = Idle (not NeedsInput); real questions come from hooks - Permission prompt (stale tool_use) never becomes Idle - Notifications auto-expire after 10s (--expire-time) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// parseHookStdin reads a HookEvent from stdin (Claude Code sends JSON on stdin).
|
|
func parseHookStdin() HookEvent {
|
|
var event HookEvent
|
|
json.NewDecoder(os.Stdin).Decode(&event)
|
|
return event
|
|
}
|
|
|
|
// runSetup adds vmux hooks to ~/.claude/settings.json (idempotent).
|
|
func runSetup() {
|
|
home := os.Getenv("HOME")
|
|
settingsPath := filepath.Join(home, ".claude", "settings.json")
|
|
|
|
vmuxBin, err := os.Executable()
|
|
if err != nil {
|
|
vmuxBin = filepath.Join(home, "Code", "vibe", "vmux", "vmux")
|
|
}
|
|
|
|
data, err := os.ReadFile(settingsPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Cannot read %s: %v\n", settingsPath, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var settings map[string]interface{}
|
|
if err := json.Unmarshal(data, &settings); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Cannot parse %s: %v\n", settingsPath, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
hooks, _ := settings["hooks"].(map[string]interface{})
|
|
if hooks == nil {
|
|
hooks = make(map[string]interface{})
|
|
settings["hooks"] = hooks
|
|
}
|
|
|
|
hookCommand := vmuxBin + " hook"
|
|
vmuxHook := map[string]interface{}{
|
|
"type": "command",
|
|
"command": hookCommand,
|
|
"timeout": 5,
|
|
}
|
|
|
|
events := []string{"Notification", "Stop", "PostToolUse", "PreToolUse"}
|
|
changed := false
|
|
|
|
for _, event := range events {
|
|
entries, _ := hooks[event].([]interface{})
|
|
|
|
if hasVmuxHook(entries, hookCommand) {
|
|
continue
|
|
}
|
|
|
|
entry := map[string]interface{}{
|
|
"hooks": []interface{}{vmuxHook},
|
|
}
|
|
|
|
hooks[event] = append(entries, entry)
|
|
changed = true
|
|
}
|
|
|
|
if !changed {
|
|
fmt.Println("vmux hooks already configured.")
|
|
return
|
|
}
|
|
|
|
out, err := json.MarshalIndent(settings, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Cannot marshal settings: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := os.WriteFile(settingsPath, out, 0o644); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Cannot write %s: %v\n", settingsPath, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("vmux hooks added to %s for events: %v\n", settingsPath, events)
|
|
fmt.Println("Restart Claude Code sessions for hooks to take effect.")
|
|
}
|
|
|
|
func hasVmuxHook(entries []interface{}, hookCommand string) bool {
|
|
for _, entry := range entries {
|
|
e, _ := entry.(map[string]interface{})
|
|
hookList, _ := e["hooks"].([]interface{})
|
|
for _, h := range hookList {
|
|
hm, _ := h.(map[string]interface{})
|
|
if cmd, _ := hm["command"].(string); cmd == hookCommand {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|