diff --git a/.gitignore b/.gitignore index ba700ff..037b201 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vmux +.direnv/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c93f45 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e4e3be --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# vmux + +Cockpit pour le vibe coding. vmux surveille toutes vos sessions Claude Code actives, détecte leur état (travaille / attend input / idle), et vous notifie quand l'une d'elles a besoin de vous. + +**Problème résolu :** savoir instantanément quelle session Claude Code attend votre attention, sans scanner manuellement vos workspaces i3. + +## Aperçu + +``` +2-vmux[W] 3-api[⚡ 2m] 5-front[?] +``` + +Chaque bloc i3bar représente une session. Les indicateurs : +- `[W]` vert — Claude travaille +- `[⚡ 2m]` rouge — attend une permission (depuis 2 min) +- `[?]` rouge — attend une réponse +- `[I]` gris — idle + +## Prérequis + +- Linux avec i3 WM +- Go 1.22+ (build uniquement) +- `notify-send` (paquet `libnotify`) +- X11 + +## Installation + +```sh +# Cloner et builder +git clone https://gitea.example.com/pieMusic/vmux +cd vmux +make build + +# Installer le binaire +install -m755 vmux ~/.local/bin/vmux + +# Configurer les hooks Claude Code (idempotent) +vmux setup +``` + +`vmux setup` injecte des hooks dans `~/.claude/settings.json` pour les événements `Notification`, `Stop`, `PostToolUse` et `PreToolUse`. Redémarrez vos sessions Claude Code après. + +## Configuration i3 + +Ajoutez `vmux i3bar` comme `status_command` dans votre config i3 : + +``` +bar { + status_command vmux i3bar + ... +} +``` + +Puis rechargez i3 : `$mod+Shift+r`. + +## Commandes + +| Commande | Description | +|---|---| +| `vmux list` | Lister les sessions actives | +| `vmux switch ` | Basculer vers le workspace i3 de la session | +| `vmux label ` | Assigner un label à une session | +| `vmux focus ` | Couper les notifications pendant N minutes | +| `vmux stop` | Arrêter le daemon | +| `vmux setup` | Configurer les hooks Claude Code | +| `vmux i3bar` | Lancer la sortie i3bar (usage : config i3) | + +### Exemples + +```sh +# Voir l'état de toutes les sessions +vmux list + +# Switcher vers la session "vmux" ou "api" +vmux switch vmux +vmux switch api + +# Nommer une session (accepte un UUID partiel) +vmux label abc123 "refacto auth" + +# Couper les notifs 30 minutes (focus session de travail) +vmux focus 30 +vmux focus 0 # réactiver immédiatement +``` + +## Comment ça marche + +Le daemon (`~/.vmux/vmux.sock`) tourne en arrière-plan, lancé automatiquement par la première commande `vmux`. Il : + +1. **Scrute `/proc`** toutes les 5s pour détecter les processus `claude` +2. **Lit les fichiers JSONL** de `~/.claude/projects/` pour obtenir l'état et le contexte de chaque session +3. **Reçoit les hooks** de Claude Code sur `localhost:3119` pour des mises à jour temps réel +4. **Résout les workspaces i3** via IPC + X11 (quel workspace contient quel terminal) +5. **Envoie une notification** `notify-send` quand une session passe de "Working" à "Needs Input" + +Voir [docs/architecture.md](docs/architecture.md) pour le détail technique. + +## Développement + +```sh +# Environnement Nix +nix-shell + +# Build + watch +make watch + +# Tests +make test +``` + +## Licence + +WTFPL — Do What The Fuck You Want To Public License. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..791f1c3 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,101 @@ +# Architecture + +## Vue d'ensemble + +vmux suit une architecture **daemon + CLI** classique. Un daemon unique tourne en arrière-plan ; toutes les commandes `vmux` communiquent avec lui via une Unix socket. + +``` +Claude Code session + │ hooks (HTTP localhost:3119) + ▼ +┌─────────────┐ +│ Daemon │◄── /proc scan (5s) +│ │◄── JSONL reader (~/.claude/projects/) +│ registry │◄── i3 IPC + X11 (workspace resolution) +└──────┬──────┘ + │ Unix socket (~/.vmux/vmux.sock) + ▼ + vmux list / switch / label / focus / i3bar +``` + +## Composants + +### Daemon (`daemon.go`) + +- Écoute sur `~/.vmux/vmux.sock` (Unix socket) +- Lance un hook server HTTP sur `localhost:3119` +- Maintient un `SessionRegistry` en mémoire (map sessionID → SessionInfo) +- Poll `/proc` toutes les **5s** (ou **20s** si des hooks arrivent activement) + +### Détection des processus (`proc.go`) + +Scrute `/proc/*/cmdline` pour trouver les processus dont la commande contient `claude`. Pour chaque processus trouvé, lit : +- `/proc//cwd` — répertoire de travail courant +- `/proc//fd/0` — PTY associé (pour la résolution workspace) + +### Lecture JSONL (`session.go`) + +Claude Code écrit un fichier JSONL par session dans `~/.claude/projects//.jsonl`. vmux associe un PID au bon fichier par correspondance du `cwd`, puis lit les messages pour extraire : +- `sessionId`, `gitBranch` — identité de la session +- `type: "assistant"` — dernier message Claude (pour le state et le preview) + +### Détection d'état (`state.go`) + +| État | Critère | +|---|---| +| `Working` | Dernier event hook = `PreToolUse` ou `PostToolUse` | +| `Needs Input` | Dernier event hook = `Notification` ou `Stop` | +| `Idle` | Aucun event récent, dernier message JSONL vieux de plus de 30s | + +Les hooks Claude Code sont prioritaires sur la détection JSONL : ils arrivent en temps réel. + +### Hooks Claude Code (`hook.go`) + +Claude Code appelle `vmux hook` à chaque événement (via la config `~/.claude/settings.json`). Le binaire lit le JSON sur stdin et le transmet au daemon via la socket. Le daemon met à jour le registry et déclenche une notification si la session passe de `Working` à `Needs Input`. + +### Résolution workspace i3 (`workspace.go`, `i3bridge.go`, `x11_resolver.go`) + +1. `i3.GetTree()` — récupère l'arbre des fenêtres i3 +2. Pour chaque fenêtre avec "Claude Code" dans le titre : lit son PID via `_NET_WM_PID` (X11) +3. Remonte la chaîne `/proc//fd/0` → PTY → processus parent → PID Claude +4. Associe le PID Claude au numéro de workspace i3 + +Dégradation gracieuse : si i3 ou X11 est indisponible, les workspaces restent vides (pas de crash). + +### i3bar (`i3bar.go`) + +Implémente le [protocole i3bar](https://i3wm.org/docs/i3bar-protocol.html). Tourne en boucle infinie, interroge le daemon toutes les 2s, écrit du JSON sur stdout. + +### Notifications (`notify.go`) + +Appelle `notify-send` avec `--urgency=critical` quand une session passe de `Working` à `Needs Input`. Le mode focus (`vmux focus `) supprime les notifications pendant la durée donnée. + +## Fichiers + +| Fichier | Rôle | +|---|---| +| `main.go` | Entrypoint, routing des commandes CLI | +| `daemon.go` | Daemon, registry, label store | +| `proc.go` | Scan /proc pour les processus claude | +| `session.go` | Lecture des fichiers JSONL Claude Code | +| `state.go` | Détection d'état (Working/NeedsInput/Idle) | +| `hook.go` | Réception et traitement des hooks Claude Code | +| `workspace.go` | Résolution workspace via i3 tree | +| `i3bridge.go` | Abstraction i3 IPC (testable) | +| `x11_resolver.go` | Lecture _NET_WM_PID via XGB | +| `i3bar.go` | Protocole i3bar | +| `notify.go` | Notifications desktop (notify-send) | +| `focus.go` | Timer de mode focus | +| `display.go` | Affichage `vmux list` | +| `client.go` | Client Unix socket | +| `protocol.go` | Types Request/Response partagés | +| `setup.go` | Injection des hooks dans settings.json | +| `types.go` | Types de données (Session, SessionState…) | + +## Données persistées + +| Chemin | Contenu | +|---|---| +| `~/.vmux/vmux.sock` | Unix socket du daemon | +| `~/.vmux/labels.json` | Labels assignés par l'utilisateur | +| `~/.claude/settings.json` | Hooks Claude Code (modifié par `vmux setup`) | diff --git a/docs/i3-integration.md b/docs/i3-integration.md new file mode 100644 index 0000000..8bfd699 --- /dev/null +++ b/docs/i3-integration.md @@ -0,0 +1,85 @@ +# Intégration i3 + +## i3bar + +Ajoutez `vmux i3bar` comme `status_command` dans votre `~/.config/i3/config` : + +``` +bar { + status_command vmux i3bar + position top + ... +} +``` + +Rechargez i3 (`$mod+Shift+r`). vmux démarre le daemon automatiquement si nécessaire. + +### Format des blocs + +Un bloc par session active, trié par numéro de workspace : + +``` +3-vmux[W] 5-api[⚡ 45s] 7-front[? 3m] +``` + +- `3-vmux` — workspace i3 + nom (label ou dernier composant du cwd) +- `[W]` vert — Working +- `[⚡ 45s]` rouge — Needs Input, type permission, en attente depuis 45s +- `[?]` rouge — Needs Input, type question +- `[!]` rouge — Needs Input, autre type +- `[I]` gris — Idle +- `[?]` gris — état inconnu + +Si le daemon est offline : `vmux: offline`. +Si aucune session détectée : `vmux: no sessions`. + +## Commande switch + +Basculez vers le workspace d'une session depuis votre terminal : + +```sh +vmux switch vmux # cherche une session dont le cwd ou label contient "vmux" +vmux switch api +``` + +La correspondance est fuzzy : le premier mot du workspace/label/cwd qui contient la query gagne. + +Vous pouvez aussi lier cette commande à un raccourci i3 : + +``` +bindsym $mod+F1 exec vmux switch feature-1 +bindsym $mod+F2 exec vmux switch feature-2 +``` + +## Notifications desktop + +vmux envoie une notification `notify-send` quand une session passe de `Working` à `Needs Input`. Assurez-vous qu'un daemon de notification tourne (`dunst`, `mako`, etc.). + +### Mode focus + +Pour couper les notifications temporairement : + +```sh +vmux focus 30 # supprimer 30 minutes +vmux focus 0 # réactiver immédiatement +``` + +## Dépannage + +**Le workspace n'apparaît pas dans `vmux list`** + +vmux résout les workspaces via i3 IPC et X11. Vérifiez que `DISPLAY` est défini dans l'environnement du daemon. Si le daemon est lancé depuis un service systemd, il peut ne pas hériter de `DISPLAY`. + +**Les hooks ne fonctionnent pas** + +1. Vérifiez que `vmux setup` a été lancé et que `~/.claude/settings.json` contient les hooks +2. Redémarrez les sessions Claude Code après `vmux setup` +3. Le hook server écoute sur `localhost:3119` — vérifiez qu'aucun autre processus n'occupe ce port + +**`vmux list` ne voit aucune session** + +Le daemon scrute `/proc` pour les processus dont la commande contient `claude`. Vérifiez : +```sh +ps aux | grep claude +``` +Si aucun processus n'apparaît, Claude Code n'est pas détecté. Vérifiez le nom du binaire sur votre système.