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)
This commit is contained in:
106
setup.go
Normal file
106
setup.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user