--- 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 ` 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.