- vmux setup: idempotently adds hooks to ~/.claude/settings.json - vmux hook: receives Claude Code hook events via env vars and forwards to daemon - Daemon handles "hook" action on Unix socket (no HTTP server needed) - Hooks provide instant state detection (permission/question/idle)
107 lines
2.5 KiB
Go
107 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// parseHookEnv builds a HookEvent from Claude Code hook environment variables.
|
|
func parseHookEnv() HookEvent {
|
|
return HookEvent{
|
|
SessionID: os.Getenv("SESSION_ID"),
|
|
Cwd: os.Getenv("SESSION_CWD"),
|
|
HookEventName: os.Getenv("HOOK_EVENT_NAME"),
|
|
NotificationType: os.Getenv("NOTIFICATION_TYPE"),
|
|
ToolName: os.Getenv("TOOL_NAME"),
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|