0d3118d98d
- launch_fleetclaude: si hay TTY, exec tmux attach en la terminal actual (no abre ventana kitty nueva); atajos alt+q (cerrar flota con confirmacion) y alt+flecha izquierda (volver atras); estetica neutra de tmux (status/bordes gris). - ancho del sidebar 47 -> 52; tmux_swap_window_into_console preserva 52 por defecto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
5.3 KiB
Go
149 lines
5.3 KiB
Go
//go:build !windows
|
|
|
|
package infra
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// TmuxSwapWindowIntoConsole trae el primer pane de <windowID> al pane derecho
|
|
// de la window "console" de <session> (al lado del pane 0 = la TUI), parkeando
|
|
// el Claude que estuviera a la derecha en su propia window (detached, sin robar
|
|
// foco), y re-fija el ancho del pane 0 (TUI) a 40 columnas.
|
|
//
|
|
// Contrato de la window console:
|
|
// - pane indice 0 = siempre la TUI fleetview (no se toca).
|
|
// - cualquier otro pane en console = el Claude activo (puede no haber ninguno).
|
|
//
|
|
// Idempotente: si <windowID> ES ya la window console, no hace nada. Si el Claude
|
|
// objetivo ya esta en console, tampoco rompe nada (el break-pane se aplica al
|
|
// pane derecho que estuviera; si no lo hay, se salta). Opera SIEMPRE sobre el
|
|
// socket aislado pasado como parametro (tmux -L <socket>).
|
|
func TmuxSwapWindowIntoConsole(socket, session, windowID string) error {
|
|
if socket == "" {
|
|
return fmt.Errorf("tmux_swap_window_into_console: socket vacio")
|
|
}
|
|
if session == "" {
|
|
return fmt.Errorf("tmux_swap_window_into_console: session vacia")
|
|
}
|
|
if windowID == "" {
|
|
return fmt.Errorf("tmux_swap_window_into_console: windowID vacio")
|
|
}
|
|
|
|
consoleTarget := session + ":console"
|
|
|
|
// Capturar el ancho ACTUAL del pane 0 (la TUI) antes de tocar nada, para
|
|
// preservarlo tras el break/join (que redistribuyen el espacio). Así el ancho
|
|
// del sidebar lo decide quien creó la sesión (launch_kittyclaude), no un valor
|
|
// fijo aquí.
|
|
width := tmuxPane0Width(socket, session)
|
|
|
|
// Caso borde: si windowID ya ES la window console, no hay nada que hacer.
|
|
// Resolvemos el window_id real de console y lo comparamos con el pedido.
|
|
consoleID, err := tmuxConsoleWindowID(socket, session)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if consoleID == windowID {
|
|
// El objetivo ya es console. Solo re-fijamos el ancho de la TUI.
|
|
return tmuxResizeConsoleTUI(socket, session, width)
|
|
}
|
|
|
|
// 1. Localiza el pane derecho actual de console (cualquier pane con indice != 0).
|
|
out, stderr, err := runTmux(socket, "list-panes", "-t", consoleTarget, "-F", "#{pane_index} #{pane_id}")
|
|
if err != nil {
|
|
return fmt.Errorf("tmux_swap_window_into_console: list-panes de %q: %w (%s)", consoleTarget, err, stderr)
|
|
}
|
|
|
|
var rightPaneID string
|
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
if fields[0] != "0" {
|
|
rightPaneID = fields[1]
|
|
break
|
|
}
|
|
}
|
|
|
|
// 2. Si existe un pane no-0 en console, sacarlo a su propia window (parking),
|
|
// detached y sin cambiar foco.
|
|
if rightPaneID != "" {
|
|
if _, stderr, err := runTmux(socket, "break-pane", "-d", "-s", rightPaneID); err != nil {
|
|
return fmt.Errorf("tmux_swap_window_into_console: break-pane de %q: %w (%s)", rightPaneID, err, stderr)
|
|
}
|
|
}
|
|
|
|
// 3. Traer el primer pane de windowID a la derecha de la TUI (-h = split
|
|
// horizontal, lado a lado). join-pane requiere que origen y destino sean
|
|
// windows distintas (ya garantizado: consoleID != windowID arriba).
|
|
src := windowID + ".0"
|
|
dst := consoleTarget + ".0"
|
|
if _, stderr, err := runTmux(socket, "join-pane", "-h", "-s", src, "-t", dst); err != nil {
|
|
return fmt.Errorf("tmux_swap_window_into_console: join-pane %q -> %q: %w (%s)", src, dst, err, stderr)
|
|
}
|
|
|
|
// 4. Re-fijar el ancho del pane 0 (TUI) al que tenia antes del swap.
|
|
return tmuxResizeConsoleTUI(socket, session, width)
|
|
}
|
|
|
|
// tmuxConsoleWindowID resuelve el window_id (ej "@3") de la window llamada
|
|
// "console" en <session>.
|
|
func tmuxConsoleWindowID(socket, session string) (string, error) {
|
|
out, stderr, err := runTmux(socket, "list-windows", "-t", session, "-F", "#{window_id} #{window_name}")
|
|
if err != nil {
|
|
return "", fmt.Errorf("tmux_swap_window_into_console: list-windows de %q: %w (%s)", session, err, stderr)
|
|
}
|
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
|
fields := strings.Fields(strings.TrimSpace(line))
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
if fields[1] == "console" {
|
|
return fields[0], nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("tmux_swap_window_into_console: window 'console' no encontrada en %q", session)
|
|
}
|
|
|
|
// tmuxPane0Width devuelve el ancho a preservar para el pane 0 (la TUI). Solo
|
|
// tiene sentido leer el ancho actual si console ya tiene >1 pane (TUI + Claude);
|
|
// con un único pane, el pane 0 es full-width y no representa el sidebar, así que
|
|
// se usa el default (47 columnas).
|
|
func tmuxPane0Width(socket, session string) int {
|
|
const def = 52
|
|
out, _, err := runTmux(socket, "list-panes", "-t", session+":console", "-F", "#{pane_index} #{pane_width}")
|
|
if err != nil {
|
|
return def
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
if len(lines) <= 1 {
|
|
return def
|
|
}
|
|
for _, l := range lines {
|
|
f := strings.Fields(strings.TrimSpace(l))
|
|
if len(f) >= 2 && f[0] == "0" {
|
|
if n, e := strconv.Atoi(f[1]); e == nil && n > 0 {
|
|
return n
|
|
}
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
// tmuxResizeConsoleTUI fija el ancho del pane 0 de console a width columnas.
|
|
func tmuxResizeConsoleTUI(socket, session string, width int) error {
|
|
target := session + ":console.0"
|
|
if _, stderr, err := runTmux(socket, "resize-pane", "-t", target, "-x", strconv.Itoa(width)); err != nil {
|
|
return fmt.Errorf("tmux_swap_window_into_console: resize-pane de %q a %d col: %w (%s)", target, width, err, stderr)
|
|
}
|
|
return nil
|
|
}
|