697c523604
App CLI que automatiza una TUI interactiva a traves de un pseudo-terminal y captura su texto. Pensada para la CLI claude (solo interactiva con TTY real), generica para cualquier TUI. - Modo screen: reconstruye layout 2D con vt_render_go_tui (emulador VT100). - Modo stream: limpia ANSI de output secuencial con strip_ansi_go_core. - Modo raw: bytes del PTY intactos. - --exec pipea el texto a otro proceso; --cwd salta el dialogo MCP de claude. Captura via pty_capture_idle_go_infra. Validada end-to-end contra claude (prompt enviado, respuesta capturada) y con 5 e2e_checks POSIX deterministas.
140 lines
6.8 KiB
Markdown
140 lines
6.8 KiB
Markdown
---
|
|
name: claude_extract
|
|
lang: go
|
|
domain: infra
|
|
version: 0.1.0
|
|
description: "CLI que automatiza una TUI interactiva a traves de un pseudo-terminal (PTY) headless y captura su texto. Pensada para la CLI 'claude' (solo entra en modo interactivo con un TTY real) pero sirve para cualquier TUI. Reconstruye el layout 2D con un emulador VT, o limpia ANSI de output secuencial, y permite pipear el resultado a otro proceso."
|
|
tags: [cli, terminal, pty, tui, automation, capture]
|
|
uses_functions:
|
|
- pty_capture_idle_go_infra
|
|
- vt_render_go_tui
|
|
- strip_ansi_go_core
|
|
uses_types: []
|
|
framework: ""
|
|
entry_point: "main.go"
|
|
dir_path: "apps/claude_extract"
|
|
icon:
|
|
phosphor: "terminal-window"
|
|
accent: "#7c3aed"
|
|
e2e_checks:
|
|
- id: build
|
|
cmd: "CGO_ENABLED=1 go build -tags fts5 -o claude_extract ."
|
|
timeout_s: 120
|
|
- id: smoke_capture
|
|
cmd: "./claude_extract --cmd bash --arg -lc --arg 'echo CAPTURA_OK' --warmup 200ms --idle 400ms --max 5s"
|
|
expect_stdout_contains: "CAPTURA_OK"
|
|
timeout_s: 15
|
|
- id: smoke_screen_layout
|
|
cmd: "./claude_extract --cmd bash --arg -lc --arg $'printf \"foo\\033[10Gbar\\n\"' --mode screen --warmup 200ms --idle 400ms --max 5s"
|
|
expect_stdout_contains: "foo"
|
|
timeout_s: 15
|
|
- id: smoke_stream_strip
|
|
cmd: "./claude_extract --cmd bash --arg -lc --arg $'printf \"\\033[31mROJO\\033[0m\\n\"' --mode stream --warmup 200ms --idle 400ms --max 5s"
|
|
expect_stdout_contains: "ROJO"
|
|
timeout_s: 15
|
|
- id: smoke_exec_pipe
|
|
cmd: "./claude_extract --cmd bash --arg -lc --arg 'echo pipe ok' --warmup 200ms --idle 400ms --max 5s --exec 'tr a-z A-Z'"
|
|
expect_stdout_contains: "PIPE OK"
|
|
timeout_s: 15
|
|
---
|
|
|
|
# claude_extract
|
|
|
|
## Que hace
|
|
|
|
Automatiza una CLI interactiva (TUI) y captura su texto, de forma headless, a traves de un
|
|
pseudo-terminal (PTY). Nunca abre una ventana de terminal: el PTY es virtual, en memoria.
|
|
|
|
Existe porque algunas CLIs — sobre todo la CLI `claude` — solo entran en su modo interactivo
|
|
rico cuando detectan un TTY real. Un pipe normal las degrada a modo "print". `claude_extract`
|
|
le da al proceso hijo un PTY real, lo dirige con input scripteado (teclea el prompt, espera, y
|
|
pulsa Enter como pasos separados), espera a que el render se estabilice, y devuelve el texto.
|
|
|
|
Por defecto el texto se imprime limpio a stdout. Con `--exec` se pipea a otro proceso por
|
|
stdin. Con `--mode raw` se obtienen los bytes del terminal sin tocar.
|
|
|
|
## Arquitectura
|
|
|
|
La app es solo orquestacion + superficie de linea de comandos + defaults amables con `claude`.
|
|
Toda la logica reutilizable vive en el registry:
|
|
|
|
| Pieza | Funcion del registry | Rol |
|
|
|---|---|---|
|
|
| Captura PTY | `pty_capture_idle_go_infra` | Lanza el comando en un PTY, inyecta input, corta por inactividad o timeout, devuelve bytes crudos. |
|
|
| Render de pantalla | `vt_render_go_tui` | Emula un terminal VT100 y reconstruye el layout 2D (espacios entre columnas que en el stream eran movimientos de cursor). Modo `screen`. |
|
|
| Limpieza de stream | `strip_ansi_go_core` | Quita secuencias ANSI de output secuencial tipo log. Modo `stream`. |
|
|
|
|
## Modos de salida (`--mode`)
|
|
|
|
| Modo | Que hace | Cuando |
|
|
|---|---|---|
|
|
| `screen` (default) | Reconstruye el layout 2D con `vt_render`. | TUIs que posicionan texto con cursor absoluto: `claude`, `htop`, `dialog`. Sin esto las palabras quedan pegadas ("2newMCPservers"). |
|
|
| `stream` | Quita ANSI del stream con `strip_ansi`. | Output secuencial: logs, builds, comandos que imprimen linea a linea. |
|
|
| `raw` | Bytes del PTY intactos (ANSI incluido). | Cuando quieres procesar los escape codes tu mismo. Atajo: `--raw`. |
|
|
|
|
## Ejemplo
|
|
|
|
```bash
|
|
cd apps/claude_extract
|
|
CGO_ENABLED=1 go build -tags fts5 -o claude_extract .
|
|
|
|
# Preguntar a claude y obtener su respuesta como texto con layout (modo screen).
|
|
# --cwd apunta a un repo donde los MCP de claude ya estan aprobados, para saltar
|
|
# el dialogo de arranque "new MCP servers found".
|
|
./claude_extract \
|
|
--prompt "responde unicamente con la palabra PONG" \
|
|
--cwd /home/enmanuel/fn_registry \
|
|
--warmup 4s --step-delay 600ms --idle 4s --max 60s
|
|
|
|
# Capturar una TUI cualquiera (sin prompt), output secuencial limpio.
|
|
./claude_extract --cmd bash --arg -lc --arg 'echo hola' --mode stream
|
|
|
|
# Pipear el texto capturado a otro proceso por stdin.
|
|
./claude_extract --prompt "lista 5 ideas" --cwd /home/enmanuel/fn_registry --exec "tee ideas.txt"
|
|
|
|
# Leer el prompt de un pipe.
|
|
echo "explica este error" | ./claude_extract --cwd /home/enmanuel/fn_registry
|
|
```
|
|
|
|
## Flags
|
|
|
|
| Flag | Default | Que hace |
|
|
|---|---|---|
|
|
| `--cmd` | `claude` | Comando a lanzar dentro del PTY. |
|
|
| `--arg` | — | Argumento extra para `--cmd` (repetible). |
|
|
| `--prompt` | — | Texto que se teclea primero, seguido de Enter. Si vacio y stdin es un pipe, se lee de stdin. |
|
|
| `--send` | — | Input crudo extra tras el prompt (repetible). Incluye `\r` para Enter, ej. `--send $'\r'`. |
|
|
| `--mode` | `screen` | `screen` \| `stream` \| `raw`. Ver tabla de modos. |
|
|
| `--raw` | false | Atajo de `--mode raw`. |
|
|
| `--warmup` | `2.5s` | Espera antes de enviar input, para que la TUI cargue. |
|
|
| `--step-delay` | `300ms` | Espera entre inputs sucesivos (entre teclear y Enter). |
|
|
| `--idle` | `2.5s` | Corta la captura tras este silencio (sin bytes nuevos de la TUI). |
|
|
| `--max` | `120s` | Timeout duro de toda la captura. |
|
|
| `--exec` | — | Pipea el texto capturado al stdin de este comando (via `sh -c`). |
|
|
| `--out` | — | Tambien escribe el texto capturado a este archivo. |
|
|
| `--cwd` | — | Ejecuta el hijo en este directorio (util para saltar el dialogo MCP de claude). |
|
|
|
|
## Cuando usarla
|
|
|
|
- Cuando necesites el render real de una CLI interactiva como texto, para auditar, scriptear o
|
|
alimentar otro proceso.
|
|
- Cuando `claude -p` (modo print) no te sirva porque quieres exactamente lo que muestra la TUI.
|
|
|
|
## Gotchas
|
|
|
|
- **Linux/Unix only**: el PTY es POSIX (heredado de `pty_capture_idle_go_infra`).
|
|
- **Enter separado**: el prompt y el Enter se envian como pasos distintos a proposito; un `\r`
|
|
pegado al texto lo trata `claude` como newline literal en el input, no como submit.
|
|
- **Layout pegado en modo stream**: para TUIs con posicionamiento absoluto usa `--mode screen`.
|
|
`--mode stream` (strip_ansi) pega las palabras porque los espacios entre columnas eran
|
|
movimientos de cursor.
|
|
- **Spinners y el corte por idle**: si la TUI hace render periodico (spinner, reloj), el `--idle`
|
|
no se dispara y la captura cae al `--max`. Para `claude`, el spinner se detiene al terminar la
|
|
respuesta, asi que `--idle` corta poco despues; sube `--max` si la respuesta es larga.
|
|
- **Dialogo de arranque de claude**: en un cwd cuyos MCP no estan aprobados, claude muestra
|
|
"new MCP servers found" y bloquea. Usa `--cwd <repo-raiz-aprobado>` o despacha el dialogo con
|
|
`--send`.
|
|
- **Dimensiones fijas 40x120**: el PTY y el render usan 40 filas x 120 columnas. Una respuesta
|
|
mas ancha se envuelve a 120 columnas.
|
|
- **Sin color**: el modo `screen` reconstruye texto y layout, no color.
|