Files
vmux/setup.go
Pierre Martin 0e4ced5b1f feat: add vmux setup and vmux hook for Claude Code integration
- 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)
2026-03-23 23:11:47 +01:00

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
}