Qué es
El manager es aditivo: crea lo que falta, reabre pipes si hace falta y no mata sesiones o ventanas sanas. Esa decisión es clave porque lo podés dejar corriendo en el server tmux default sin que te borre trabajo.
Diagramas
Arquitectura
LaunchAgent"] watch --> tmux["server tmux default"] tmux --> daily["daily"] tmux --> personal["personal"] tmux --> remote["remote"] watch --> logs["pipe-pane logs
~/.tmux-manager/logs"] watch --> snaps["snapshots
~/.tmux-manager/snapshots"] watch --> daemon["watch-daemon.log"]
Flujo de cada tick
Config.load + resolve"] changed -->|no| reconcile reload --> valid{"JSON válido?"} valid -->|sí| reconcile["reconcile aditivo
crear lo que falta"] valid -->|no| old["seguir con config anterior
loggear error una vez"] old --> reconcile reconcile --> sweep["sweep pipes
attach si falta pipe
rotación diaria"] sweep --> snap{"pasaron 30 min?"} snap -->|sí| snapshot["snapshot scrollback"] snap -->|no| done["fin del tick"] snapshot --> done
Layout actual
codex | claude"] finanzas["finanzas
codex | claude"] comms["comms
codex | claude"] end subgraph personal artisview["artisview
codex | claude"] fonzo["fonzo
codex | claude"] agro["agro
codex | claude"] end subgraph remote shell["shell
ssh | ssh"] monitor["monitor
ssh | ssh"] end
Capturas reales
Los bloques de abajo muestran salidas neutrales del CLI y de tmux. La parte importante para operar está ahí: comandos reproducibles y output consistente.
$ tmux-manager status --config ~/.tmux-manager/config.json
session daily: OK
session personal: OK
session remote: OK
converged: yes
Help
$ ~/.local/bin/tmux-manager help
tmux-manager 0.1.0 — declarative tmux session manager
USAGE:
tmux-manager [global options] <command>
COMMANDS:
up Create/attach sessions from config
status Show managed sessions status
save Snapshot every managed pane's scrollback
watch Convergence loop: up + pipe logs + snapshots
--interval N Seconds between passes (default: 15)
logs List pane log files (grouped by session)
--path <sess>:<win> Print today's log path(s) for a window
--prune --days N Delete logs older than N days
install-launchagent Write the launchd plist (does NOT launchctl)
help Show this help
Status
$ ~/.local/bin/tmux-manager status --config ~/.tmux-manager/config.json
session daily: OK
window hoy: OK (2 panes)
pane %26: node [piped]
pane %27: claude.exe [piped]
window finanzas: OK (2 panes)
pane %101: node [piped]
pane %102: claude.exe [piped]
window comms: OK (2 panes)
pane %103: node [piped]
pane %104: claude.exe [piped]
session personal: OK
window artisview: OK (2 panes)
window fonzo: OK (2 panes)
window agro: OK (2 panes)
session remote: OK
window shell: OK (2 panes)
pane %139: ssh [piped]
pane %140: ssh [piped]
converged: yes
Logs y tmux
$ ~/.local/bin/tmux-manager logs --config ~/.tmux-manager/config.json | head -18
daily:
hoy-p1-2026-07-09.log 1.5 KB 2026-07-09 12:08
hoy-p0-2026-07-09.log 2.8 KB 2026-07-09 12:08
finanzas-p1-2026-07-09.log 20 B 2026-07-09 12:08
comms-p1-2026-07-09.log 52 B 2026-07-09 12:08
personal:
artisview-p0-2026-07-09.log 26.4 MB 2026-07-09 12:17
fonzo-p1-2026-07-09.log 13.6 KB 2026-07-09 12:15
remote:
shell-p0-2026-07-09.log 9.5 KB 2026-07-09 12:11
shell-p1-2026-07-09.log 9.5 KB 2026-07-09 12:11
$ tmux list-windows -a
daily:0: hoy* (2 panes)
daily:1: finanzas (2 panes)
daily:2: comms (2 panes)
personal:0: fonzo (2 panes)
personal:1: agro- (2 panes)
personal:2: artisview* (2 panes)
remote:0: shell* (2 panes)
$ rg 'config reloaded' ~/.tmux-manager/watch-daemon.log [2026-07-09 00:34:47] config reloaded (3 sessions) up[... windows created: 1 (daily:reload-test) ...] sweep[attached: 2, rotated: 0] [2026-07-09 00:35:34] config reloaded (3 sessions) up[... windows existing: 8 ...] sweep[attached: 0, rotated: 0] [2026-07-09 00:36:51] config reloaded (3 sessions) up[... windows existing: 8 ...] sweep[attached: 0, rotated: 0] $ tail ~/.tmux-manager/watch-daemon.log [2026-07-09 09:06:53] up[... windows existing: 8 ...] sweep[attached: 0, rotated: 0] [2026-07-09 09:07:08] up[... windows existing: 8 ...] sweep[attached: 0, rotated: 0] [2026-07-09 09:07:24] up[... windows existing: 8 ...] sweep[attached: 0, rotated: 0]
$ ls -la ~/.tmux-manager/logs/remote/
-rw-r--r-- shell-p0-2026-07-09.log 9728
-rw-r--r-- shell-p1-2026-07-09.log 9728
-rw-r--r-- monitor-p0-2026-07-09.log 4096
-rw-r--r-- monitor-p1-2026-07-09.log 4096
Tutorial paso a paso
1. Instalar
Desde el repo, el script compila release, instala el binario en `~/.local/bin` y escribe el LaunchAgent. El install del binario es atómico para no romper la firma/caché de macOS.
$ cd ~/personal/tools/tmux-manager $ scripts/install.sh $ ~/.local/bin/tmux-manager help
2. Editar config
El archivo real vive en `~/.tmux-manager/config.json`. Esta es la config actual usada para las capturas:
{
"defaults": {
"left": "codex",
"right": "claude",
"cwd": "~"
},
"sessions": [
{
"name": "daily",
"cwd": "~",
"windows": [
{ "name": "hoy" },
{ "name": "finanzas", "cwd": "~/personal/finanzas" },
{ "name": "comms", "cwd": "~/personal" }
]
},
{
"name": "personal",
"cwd": "~/personal",
"windows": [
{ "name": "artisview", "cwd": "~/personal/github-repos/artisview-prod-quantlab" },
{ "name": "fonzo", "cwd": "~/personal/github-repos/fonzo" },
{ "name": "agro", "cwd": "~/personal/github-repos/proj-agro-inteligente" }
]
},
{
"name": "remote",
"cwd": "~",
"windows": [
{
"name": "shell",
"left": "ssh -t mi-servidor 'cd ~/proyecto && exec $SHELL'",
"right": "ssh -t mi-servidor 'cd ~/proyecto && exec $SHELL'"
}
]
}
]
}
3. Hot-reload
Ya no hace falta reiniciar el daemon para cambios normales de JSON. Guardás el archivo y en el próximo tick se recarga. Si el JSON está roto, sigue corriendo con la última config buena y lo deja en el log una sola vez para ese cambio.
// agregado temporal en daily.windows { "name": "reload-test", "left": "", "right": "" } $ rg 'reload-test' ~/.tmux-manager/watch-daemon.log [2026-07-09 00:34:47] config reloaded (3 sessions) ... windows created: 1 (daily:reload-test) ...
4. Si tmux muere
En el siguiente tick, `watch` vuelve a levantar el server y recrea lo que está en config. Lo que recupera bien: sesiones, ventanas, dos panes por window, pipes de logs y snapshots futuros. Lo que no puede inventar: procesos que estaban corriendo dentro del pane si murieron con el server.
Por eso los logs y snapshots importan: el historial anterior queda en `~/.tmux-manager/logs` y `~/.tmux-manager/snapshots`.
5. Leer logs
Los logs son bytes crudos de terminal, incluyendo ANSI. Abrilos con `less -R` para conservar colores.
$ tmux-manager logs $ tmux-manager logs --path remote:shell $ less -R ~/.tmux-manager/logs/remote/shell-p0-2026-07-09.log
6. Snapshot manual
`save` captura el scrollback de cada pane gestionado en el momento. Es útil antes de reiniciar, cerrar la Mac o hacer limpieza.
$ tmux-manager save
snapshots written: 16, pruned: 0
7. LaunchAgent
El installer escribe el plist pero no ejecuta `launchctl` por vos. Para cargarlo o reiniciarlo:
$ launchctl bootout gui/$(id -u)/com.luis.tmux-manager.watch $ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.luis.tmux-manager.watch.plist # para descargarlo $ launchctl bootout gui/$(id -u)/com.luis.tmux-manager.watch
Troubleshooting
El binario muere con SIGKILL / exit 137
No pises el binario instalado in-place. En macOS puede quedar inválido por caché/firma. Instalá atómico:
$ cp .build/release/tmux-manager ~/.local/bin/tmux-manager.new $ chmod +x ~/.local/bin/tmux-manager.new $ mv -f ~/.local/bin/tmux-manager.new ~/.local/bin/tmux-manager
`launchctl bootstrap` devuelve error 5
Suele pasar justo después de `bootout`, cuando launchd todavía no terminó de soltar el job. Esperá unos segundos y reintentá.
JSON roto
El daemon no crashea. Deja un error `config reload: ...` en `watch-daemon.log` y sigue con la última config buena. Arreglás el JSON, guardás, y en el siguiente tick recarga limpio.
Pidfile stale
Si el daemon murió y quedó `~/.tmux-manager/watch.pid`, borrá el pidfile y volvé a cargar el LaunchAgent.
$ rm -f ~/.tmux-manager/watch.pid $ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.luis.tmux-manager.watch.plist