Files
fn_registry/functions/infra/tmux_new_claude_window_test.go
T
egutierrez 927437a8d8 feat(infra): grupo claude-fleet — FleetView TUI + orquestacion de Claudes
Sistema FleetView para centralizar la flota de procesos Claude Code vivos en una
sola ventana kitty + tmux (socket aislado -L fleet) con un panel TUI:

- list_claude_fleet (+ tipo claude_fleet): escanea ~/.claude/sessions + goals +
  runtime, valida procesos vivos (anti-PID-reciclado), join por sessionId.
- list_resumable_claudes (+ tipo resumable_claude): sesiones cerradas reanudables.
- wrappers tmux: tmux_new_claude_window (con --resume), tmux_swap_window_into_console
  (preserva ancho del sidebar), tmux_map_claude_panes.
- launch_kittyclaude: comando entrypoint; instala atajos alt+flechas/enter/n/0/k/r,
  mouse on, remain-on-exit off; fija el ancho del sidebar con hooks.
- docs/capabilities/claude-fleet.md + entrada en el INDEX.

Incluye ademas funciones datascience en progreso (excel/duckdb/postgres) y ajustes
varios de docs e infra de otra sesion, agrupados aqui para no perderlos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:04:41 +02:00

85 lines
2.5 KiB
Go

//go:build !windows && linux
package infra
import (
"fmt"
"os"
"os/exec"
"strings"
"testing"
"time"
)
// tmuxAvailable reports whether the tmux binary is present. Tests skip when it
// is not (CI without tmux).
func tmuxAvailable(t *testing.T) {
t.Helper()
if _, err := exec.LookPath("tmux"); err != nil {
t.Skipf("tmux no disponible en PATH: %v", err)
}
}
// isolatedSocket returns a per-test isolated tmux socket name and registers a
// cleanup that kills its server. All commands in a test run against
// `tmux -L <socket> ...`, never the user's default server.
func isolatedSocket(t *testing.T) string {
t.Helper()
socket := fmt.Sprintf("fleettest_%d_%d", os.Getpid(), time.Now().UnixNano())
t.Cleanup(func() {
// best-effort: el server puede no existir si el test fallo antes de crearlo
_ = exec.Command("tmux", "-L", socket, "kill-server").Run()
})
return socket
}
// startConsoleSession crea una sesion <session> con una window "console" cuyo
// pane 0 corre `cat` (simula la TUI fleetview, un proceso que no termina).
func startConsoleSession(t *testing.T, socket, session string) {
t.Helper()
cmd := exec.Command("tmux", "-L", socket,
"new-session", "-d", "-s", session, "-n", "console", "cat")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("new-session: %v (%s)", err, out)
}
}
func TestTmuxNewClaudeWindow(t *testing.T) {
tmuxAvailable(t)
socket := isolatedSocket(t)
session := "fleet"
startConsoleSession(t, socket, session)
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("getwd: %v", err)
}
// El comando real ("claude ...") no esta disponible en el test, pero
// new-window devuelve el window_id ANTES de que el comando pueda fallar:
// tmux crea la window y reporta su id sincronamente. Validamos que el id
// venga con la forma esperada (@N) y no vacio.
windowID, err := TmuxNewClaudeWindow(socket, session, cwd)
if err != nil {
t.Fatalf("TmuxNewClaudeWindow: %v", err)
}
if windowID == "" {
t.Fatal("window_id vacio")
}
if !strings.HasPrefix(windowID, "@") {
t.Errorf("window_id %q no tiene la forma esperada @N", windowID)
}
}
func TestTmuxNewClaudeWindowEmptyArgs(t *testing.T) {
if _, err := TmuxNewClaudeWindow("", "fleet", "/tmp"); err == nil {
t.Error("socket vacio deberia dar error")
}
if _, err := TmuxNewClaudeWindow("sock", "", "/tmp"); err == nil {
t.Error("session vacia deberia dar error")
}
if _, err := TmuxNewClaudeWindow("sock", "fleet", ""); err == nil {
t.Error("cwd vacio deberia dar error")
}
}