729921e16e
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
5.1 KiB
Markdown
84 lines
5.1 KiB
Markdown
---
|
||
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).
|