docs: add README, licence WTFPL et documentation
- README.md : vision, installation, commandes, aperçu fonctionnement - LICENSE : WTFPL v2 - docs/architecture.md : composants, flux de données, index des fichiers - docs/i3-integration.md : config i3bar, switch, focus, dépannage - .gitignore : exclure .direnv/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
vmux
|
vmux
|
||||||
|
.direnv/
|
||||||
|
|
||||||
|
|||||||
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||||
|
|
||||||
|
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.
|
||||||
113
README.md
Normal file
113
README.md
Normal file
@@ -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 <query>` | Basculer vers le workspace i3 de la session |
|
||||||
|
| `vmux label <id> <texte>` | Assigner un label à une session |
|
||||||
|
| `vmux focus <minutes>` | 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.
|
||||||
101
docs/architecture.md
Normal file
101
docs/architecture.md
Normal file
@@ -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/<pid>/cwd` — répertoire de travail courant
|
||||||
|
- `/proc/<pid>/fd/0` — PTY associé (pour la résolution workspace)
|
||||||
|
|
||||||
|
### Lecture JSONL (`session.go`)
|
||||||
|
|
||||||
|
Claude Code écrit un fichier JSONL par session dans `~/.claude/projects/<encoded-cwd>/<uuid>.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/<pid>/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 <minutes>`) 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`) |
|
||||||
85
docs/i3-integration.md
Normal file
85
docs/i3-integration.md
Normal file
@@ -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.
|
||||||
Reference in New Issue
Block a user