--- name: pty_capture_idle kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func PTYCaptureIdle(ctx context.Context, name string, args []string, warmup time.Duration, inputs []string, stepDelay, idle, maxDur time.Duration) (string, error)" description: "Lanza un comando dentro de un pseudo-terminal (PTY) en memoria y captura todo su output hasta que el terminal permanece idle durante al menos `idle`, o se alcanza `maxDur`. Soporta envío de inputs interactivos tras el warmup inicial. Devuelve el output RAW con secuencias ANSI intactas." tags: ["terminal", "pty", "tui", "capture", "automation", "terminal-capture"] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: - "context" - "time" - "github.com/creack/pty" tested: true tests: - "captura output de echo hola" - "input interactivo con cat" - "timeout duro con sleep 10" test_file_path: "functions/infra/pty_capture_idle_test.go" file_path: "functions/infra/pty_capture_idle.go" params: - name: ctx desc: "Contexto de cancelación. Si se cancela, la función aborta la captura y retorna el output acumulado hasta ese momento." - name: name desc: "Nombre o path del ejecutable a lanzar (debe existir en PATH o ser un path absoluto)." - name: args desc: "Argumentos posicionales para el ejecutable. Puede ser nil o vacío." - name: warmup desc: "Tiempo que la función espera después de arrancar el proceso antes de enviar inputs. Permite que la TUI inicialice su render. Típico: 500ms–2s para CLIs lentas." - name: inputs desc: "Lista de strings a escribir al PTY en secuencia, uno por vez. Incluir '\\r' al final de cada string para simular Enter. Puede ser nil si solo se quiere observar la salida sin interactuar." - name: stepDelay desc: "Espera entre cada input enviado. Permite que la TUI procese y renderice la respuesta de cada paso antes de enviar el siguiente." - name: idle desc: "Tiempo sin nuevos bytes en el PTY que se considera 'respuesta terminada'. Cuando el terminal lleva idle sin actividad, la función retorna. Típico: 500ms–2s." - name: maxDur desc: "Timeout duro desde el inicio de la función. Garantiza que la función retorna aunque la TUI siga emitiendo output indefinidamente (spinners, relojes). Típico: 30s–120s." output: "String con el output completo del terminal desde el arranque hasta la captura, incluyendo secuencias de escape ANSI. Vacío string si el proceso no produjo nada. Error si el PTY no pudo arrancar o el contexto fue cancelado durante warmup." --- ## Ejemplo ```go // Capturar una sesión de claude con un prompt automático ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() raw, err := PTYCaptureIdle( ctx, "claude", nil, 2*time.Second, // warmup: esperar que claude cargue []string{"hola, responde PONG\r"}, // inputs: enviar mensaje + Enter 300*time.Millisecond, // stepDelay entre inputs 2*time.Second, // idle: cortar cuando lleve 2s sin output 120*time.Second, // maxDur: timeout duro ) if err != nil { log.Fatal(err) } // raw contiene el render completo con ANSI; limpiar antes de procesar texto: // clean := StripANSI(raw) // strip_ansi_go_tui fmt.Println(raw) ``` ## Cuando usarla Cuando necesites automatizar o scriptear una CLI interactiva que solo entra en modo interactivo si detecta un TTY real (como `claude`, `vim`, `fzf`, `htop`, `python` REPL, `psql`). El PTY hace creer al proceso que habla con un terminal real, sin abrir ninguna ventana gráfica. ## Gotchas - **Linux/Unix only.** Usa PTY POSIX (`creack/pty`). No funciona en Windows. - **Output RAW con ANSI.** El string devuelto contiene secuencias de escape (`\x1b[...m`, cursor moves, etc.). Para convertirlo a texto plano: usa `vt_render_go_tui` (reconstruye el layout 2D — correcto para TUIs con posicionamiento absoluto como `claude` o `htop`) o `strip_ansi_go_core` (para output secuencial tipo log). `strip_ansi` sobre una TUI con layout absoluto deja las palabras pegadas porque los espacios entre columnas eran movimientos de cursor. - **Idle es heurístico.** Si la TUI hace render periódico (spinners, relojes en pantalla, progress bars continuas), el idle nunca se dispara y la función esperará hasta `maxDur`. Aumentar `maxDur` o matar el spinner antes de capturar. - **El binario debe existir en PATH** (o usar path absoluto en `name`). La función devuelve error si `pty.Start` falla. - **EIO/EOF al cerrar PTY es normal en Linux.** El goroutine lector lo absorbe silenciosamente; no se propaga como error. - **SIGTERM → SIGKILL.** Al terminar la captura, la función envía SIGTERM al proceso y espera 2s antes de SIGKILL. Procesos que ignoran SIGTERM (como `sleep`) se matan limpiamente. - **Tamaño de terminal fijado a 40×120.** Suficiente para la mayoría de TUIs. Si el render se ve truncado, el llamador puede hacer `pty.Setsize` adicional después de obtener el ptmx (no expuesto por esta función; para casos avanzados, reimplementar con acceso directo al ptmx).