Files
agent 4574f08a22 feat: streaming incremental (--stream) parseando snapshots de la TUI
Fase 2. Anade modo --stream que emite la respuesta de claude como NDJSON
(eventos text_delta + result final), re-parseando snapshots del render.

Compone dos funciones nuevas del registry:
- pty_capture_stream_go_infra: captura snapshots acumulativos del PTY por canal.
- text_prefix_delta_go_core: delta por prefijo comun entre snapshots sucesivos.

Por cada snapshot: vt_render -> parse_claude_tui -> delta del Answer. Solo emite
text_delta cuando el answer extiende limpiamente al anterior (HasPrefix); los
frames no monotonos se reconcilian en el result final. Heuristico y documentado.

e2e_check smoke_fake_stream verifica el flujo con el fake TUI (sin gastar claude).
2026-06-03 23:27:12 +02:00

7.9 KiB

name, lang, domain, version, description, tags, uses_functions, uses_types, framework, entry_point, dir_path, icon, e2e_checks
name lang domain version description tags uses_functions uses_types framework entry_point dir_path icon e2e_checks
claude_pipe go infra 0.1.0 Alternativa a 'claude -p' que obtiene la respuesta de claude como dato PARSEANDO su TUI interactiva: captura el render via PTY, reconstruye el layout y extrae los turnos + la respuesta final. Emite JSON con el mismo shape que 'claude -p --output-format json'. Para interaccion programatica robusta prefiere el camino stream-json (claude_stream_go_core); esta app es la variante que va a traves de la TUI.
cli
claude
terminal
pty
tui
parser
pty_capture_idle_go_infra
pty_capture_stream_go_infra
vt_render_go_tui
parse_claude_tui_go_tui
text_prefix_delta_go_core
claude_tui_parse_go_tui
claude_turn_go_tui
main.go apps/claude_pipe
phosphor accent
faders #0ea5e9
id cmd timeout_s
build CGO_ENABLED=1 go build -tags fts5 -o claude_pipe . 120
id cmd expect_stdout_contains timeout_s
smoke_fake_text ./claude_pipe --bin ./tests/fake_claude.sh --warmup 300ms --step-delay 100ms --idle 700ms --max 5s --format text test RESPUESTA_FAKE_OK 15
id cmd expect_stdout_contains timeout_s
smoke_fake_json ./claude_pipe --bin ./tests/fake_claude.sh --warmup 300ms --step-delay 100ms --idle 700ms --max 5s --format json test "result":"RESPUESTA_FAKE_OK" 15
id cmd expect_stdout_contains timeout_s
smoke_fake_stream ./claude_pipe --stream --bin ./tests/fake_claude.sh --warmup 300ms --step-delay 100ms --snapshot-interval 120ms --idle 700ms --max 5s test "type":"text_delta" 15

claude_pipe

Que hace

Devuelve la respuesta de claude como dato parseando su TUI interactiva, en lugar de usar claude -p. Internamente lanza el claude interactivo dentro de un pseudo-terminal, captura el render de la pantalla, reconstruye el layout 2D, y extrae los turnos de la conversacion mas la respuesta final del asistente. La salida por defecto imita claude -p --output-format json.

Es la respuesta a "quiero parsear la TUI y que devuelva lo mismo que claude -p". Para la mayoria de los casos programaticos el camino limpio es stream-json (ver claude_stream_go_core), que no toca la TUI; claude_pipe existe para cuando se quiere expresamente ir a traves de la TUI.

Arquitectura (composicion de funciones del registry)

claude (TUI)  ─pty_capture_idle_go_infra─▶  bytes crudos del terminal
              ─vt_render_go_tui──────────▶  pantalla como texto plano (layout 2D)
              ─parse_claude_tui_go_tui───▶  ClaudeTUIParse {Turns, Answer}
              ─(esta app)────────────────▶  JSON estilo claude -p / texto / turns

La app no aporta logica reutilizable: solo orquesta las tres funciones y elige el formato de salida.

Formatos de salida (--format)

Formato Salida Equivale a
json (default) {"type":"result","subtype":"success","is_error":false,"result":"<respuesta>"} claude -p --output-format json (campos esenciales)
text solo el texto de la respuesta claude -p plano
turns el ClaudeTUIParse completo: cada turno visible (user/assistant/tool_use/tool_result) + answer — (mas rico que claude -p)
screen el render de la pantalla sin parsear debug

Ejemplo

cd apps/claude_pipe
CGO_ENABLED=1 go build -tags fts5 -o claude_pipe .

# Respuesta como JSON (mismo shape que claude -p --output-format json)
./claude_pipe --cwd /home/enmanuel/fn_registry "responde unicamente con la palabra PONG"
# {"type":"result","subtype":"success","is_error":false,"result":"PONG"}

# Solo el texto
./claude_pipe --format text --cwd /home/enmanuel/fn_registry "resume el README en 3 lineas"

# Todos los turnos visibles (incluye tool_use/tool_result que la TUI muestra)
./claude_pipe --format turns --cwd /home/enmanuel/fn_registry "lee main.go y resumelo"

# Prompt por stdin
echo "explica este error" | ./claude_pipe --cwd /home/enmanuel/fn_registry

Flags

Flag Default Que hace
--prompt Prompt a enviar. Si vacio, se toma del arg posicional o de stdin.
--format json json | text | turns | screen.
--cwd Directorio donde se lanza claude (usa un repo con los MCP aprobados, para saltar el dialogo de arranque).
--bin claude Binario claude a lanzar (o un fake para tests).
--warmup 4s Espera antes de enviar el prompt, para que la TUI cargue.
--step-delay 600ms Espera entre teclear el prompt y pulsar Enter.
--idle 4s Corta la captura tras este silencio (respuesta terminada de renderizar).
--max 120s Timeout duro de toda la captura.
--stream false Emite la respuesta incrementalmente como NDJSON (text_delta por snapshot) y un result final.
--snapshot-interval 150ms En --stream, cada cuánto se captura y re-parsea la TUI.

Streaming (--stream)

Con --stream, la app usa pty_capture_stream_go_infra para tomar snapshots del render cada --snapshot-interval, re-parsea cada snapshot con parse_claude_tui_go_tui, y emite el delta del Answer (via text_prefix_delta_go_core) como NDJSON, terminando con un result:

./claude_pipe --stream --cwd /home/enmanuel/fn_registry "explica Go en 3 frases"
# {"type":"text_delta","text":"Go es un lenguaje..."}
# {"type":"text_delta","text":" compilado y concurrente..."}
# {"type":"result","subtype":"success","result":"Go es un lenguaje... compilado y concurrente..."}

Un programa externo Go (u otro) lo lanza como subprocess y lee líneas. Es heurístico (ver Gotchas): la TUI re-renderiza el frame entero, así que solo se emite text_delta cuando el nuevo Answer extiende limpiamente al anterior; los frames no monótonos (reflow) se reconcilian en el result final, cuyo result lleva siempre la respuesta completa. Para streaming limpio y monótono nativo sin tocar la TUI, claude_stream_go_core (stream-json) es superior.

Cuando usarla

  • Cuando quieras la respuesta de una sesion claude como dato yendo a traves de la TUI (no via claude -p).
  • Para auditar/scriptear lo que la TUI muestra y obtener ademas los tool_use/tool_result visibles (--format turns).

Si no necesitas pasar por la TUI, usa claude_stream_go_core (stream-json) — es mas robusto.

Gotchas

  • No reemplaza 1:1 a claude -p: el campo result (la respuesta) coincide, pero NO se extraen los metadatos (cost, tokens, session_id, duration_ms) porque la TUI no los expone de forma fiable.
  • Truncacion por scroll: la captura solo ve el grid visible (40x120). Una respuesta mas larga que la pantalla se trunca por arriba (el render no guarda scrollback). Respuestas cortas/medias van bien.
  • Heuristico y version-dependiente: el parser asume el layout actual de la TUI de claude. Si claude cambia su UI, hay que ajustar parse_claude_tui_go_tui.
  • Dialogo de arranque: en un cwd cuyos MCP no estan aprobados, claude bloquea con "new MCP servers found". Usa --cwd <repo-raiz-aprobado>.
  • Latencia: anade warmup + idle (por defecto ~8s de overhead) sobre el tiempo de respuesta de claude. claude -p no tiene ese overhead. Es el precio de ir por la TUI.
  • Linux/Unix only: hereda el PTY POSIX de pty_capture_idle_go_infra.
  • Streaming heurístico: --stream emite deltas re-parseando snapshots del render. Como la TUI re-renderiza el frame entero, el Answer parseado puede no ser monótono (reflow al crecer la respuesta, spinner). Solo se emite text_delta cuando el nuevo answer extiende limpiamente al anterior; el result final siempre lleva la respuesta completa. Posibles artefactos: deltas que reaparecen, o fragmentos perdidos en un frame intermedio. Si necesitas streaming exacto, usa claude_stream_go_core (stream-json).