feat(cybersecurity): auto-commit con 48 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 23:44:39 +02:00
parent efc9911925
commit 729921e16e
48 changed files with 3765 additions and 8 deletions
+86
View File
@@ -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`).