feat(cybersecurity): auto-commit con 48 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# terminal-capture
|
||||
|
||||
Automatizar una CLI/TUI interactiva y capturar su texto, de forma headless, a través de un
|
||||
pseudo-terminal (PTY). Cubre el ciclo completo: lanzar el proceso con un TTY real, inyectarle
|
||||
input scripteado, esperar a que el render se estabilice, y convertir el stream crudo de bytes a
|
||||
texto plano — bien reconstruyendo el layout 2D (TUIs con cursor absoluto), bien limpiando ANSI
|
||||
de output secuencial.
|
||||
|
||||
Existe porque muchas CLIs (sobre todo la CLI `claude`) solo entran en su modo interactivo rico
|
||||
cuando detectan un TTY; un pipe normal las degrada. El PTY es virtual, en memoria: **nunca abre
|
||||
una ventana de terminal**.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma | Qué hace |
|
||||
|---|---|---|
|
||||
| `pty_capture_idle_go_infra` | `func PTYCaptureIdle(ctx, name string, args []string, warmup time.Duration, inputs []string, stepDelay, idle, maxDur time.Duration) (string, error)` | Lanza `name args` en un PTY (40×120), espera `warmup`, escribe cada `inputs` separado por `stepDelay`, y captura todos los bytes hasta que pasa `idle` sin output nuevo o se alcanza `maxDur`. Devuelve el stream **crudo** (ANSI intacto). One-shot. |
|
||||
| `pty_capture_stream_go_infra` | `func PTYCaptureStream(ctx, name string, args []string, warmup time.Duration, inputs []string, stepDelay, snapshotInterval, idle, maxDur time.Duration) (<-chan string, error)` | Igual que `pty_capture_idle` pero emite **snapshots acumulativos** del buffer por un canal cada `snapshotInterval` — para hacer streaming de la TUI mientras renderiza. El consumidor renderiza/parsea cada snapshot. |
|
||||
| `text_prefix_delta_go_core` | `func PrefixDelta(prev, curr string) string` | Devuelve la parte de `curr` que sigue al prefijo común con `prev` (delta de streaming por snapshots). Pura, compara por runas. Heurística ante reflow. |
|
||||
| `vt_render_go_tui` | `func VTRender(raw string, rows, cols int) string` | Emula un terminal VT100 de `rows×cols`, alimenta `raw`, y devuelve el estado final de la pantalla como texto plano **con el layout reconstruido** (espacios reales donde el stream tenía movimientos de cursor). Pura. |
|
||||
| `strip_ansi_go_core` | `func StripANSI(s string) string` | Elimina secuencias ANSI/VT100 y caracteres de control de un stream **secuencial** (logs), preservando `\n`, `\t`, `\r`. Pura. NO reconstruye layout 2D. |
|
||||
| `parse_claude_tui_go_tui` | `func ParseClaudeTUI(screen string) ClaudeTUIParse` | Parsea la pantalla renderizada de la TUI de `claude` (salida de `vt_render`) y extrae los turnos (user/assistant/tool_use/tool_result) + la respuesta final (`Answer`), equivalente a lo que devolvería `claude -p`. Pura, heurística, específica de la TUI de claude. |
|
||||
|
||||
## Cuándo usar cada limpiador
|
||||
|
||||
El corazón del grupo es `pty_capture_idle` (la captura). Lo que cambia es cómo conviertes el raw a texto:
|
||||
|
||||
| Si la salida es… | Usa | Porque |
|
||||
|---|---|---|
|
||||
| Una TUI con posicionamiento absoluto (`claude`, `htop`, `dialog`) | `vt_render_go_tui` (modo screen) | Los "espacios" entre columnas eran movimientos de cursor; sin emular el grid las palabras se pegan (`2newMCPservers`). |
|
||||
| Output secuencial línea a línea (logs, builds) | `strip_ansi_go_core` (modo stream) | No hay layout 2D que reconstruir; basta quitar los escape codes. |
|
||||
| Quieres procesar los escape codes tú mismo | (ninguno — usa el raw) | El raw de `pty_capture_idle` ya los conserva. |
|
||||
|
||||
## Ejemplo canónico (end-to-end)
|
||||
|
||||
Capturar la respuesta de la CLI `claude` como texto con layout, en Go:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"fn-registry/functions/infra"
|
||||
"fn-registry/functions/tui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
// Teclear el prompt y pulsar Enter como pasos separados: un "\r" pegado al
|
||||
// texto lo trata claude como newline literal, no como submit.
|
||||
inputs := []string{"resume el README en 3 lineas", "\r"}
|
||||
raw, _ := infra.PTYCaptureIdle(ctx, "claude", nil,
|
||||
4*time.Second, // warmup: deja cargar la TUI
|
||||
inputs, 600*time.Millisecond,
|
||||
4*time.Second, // idle: corta tras 4s de silencio
|
||||
60*time.Second) // maxDur: tope duro
|
||||
screen := tui.VTRender(raw, 40, 120) // reconstruye el layout 2D
|
||||
print(screen)
|
||||
}
|
||||
```
|
||||
|
||||
La app `claude_extract` (`apps/claude_extract`) empaqueta exactamente este flujo como CLI, con
|
||||
modos `screen|stream|raw`, `--exec` para pipear a otro proceso, y `--cwd` para saltar el diálogo
|
||||
de arranque de claude. Es el consumidor de referencia del grupo.
|
||||
|
||||
La app `claude_pipe` (`apps/claude_pipe`) va un paso más allá: añade `parse_claude_tui_go_tui`
|
||||
al final del pipeline para devolver la respuesta de claude **como dato** con el mismo shape que
|
||||
`claude -p --output-format json` (`--format json|text|turns`). Es la alternativa "parsea la TUI"
|
||||
a `claude -p`, para cuando se quiere expresamente ir a través de la TUI en vez del stream-json.
|
||||
|
||||
## Fronteras
|
||||
|
||||
- **No es `claude -p`**: este grupo captura la TUI real (lo que se ve). Para interacción programática
|
||||
limpia con la CLI `claude`, usa `claude_stream_go_core` (`claude -p --output-format stream-json`).
|
||||
- **Linux/Unix only**: PTY POSIX (`creack/pty`). No Windows.
|
||||
- **Sin color**: `vt_render` reconstruye texto y layout, no atributos de color.
|
||||
- **Idle es heurístico**: TUIs con render periódico (spinners, relojes) no disparan el idle y caen
|
||||
al `maxDur`. Para `claude` el spinner se detiene al terminar la respuesta, así que corta bien.
|
||||
- **Dimensiones fijas 40×120**: el render debe usar el mismo tamaño que la captura o el wrapping no
|
||||
cuadra.
|
||||
|
||||
## Notas
|
||||
|
||||
- Las dos funciones de limpieza son **puras**; solo `pty_capture_idle` es impura (lanza procesos).
|
||||
Puras en los bordes, impura en el centro de la captura.
|
||||
- `pty_capture_idle` no fija el cwd del hijo: para controlarlo, cambia el cwd del proceso que la
|
||||
invoca antes de llamarla (lo que hace `claude_extract --cwd`).
|
||||
Reference in New Issue
Block a user