Add REPL effect detection and input handling to pipeline runner

- Detect REPL effect in pipeline and enable interactive mode
- Enable raw terminal mode for REPL input capture
- Add keyboard input loop for REPL commands (return, up/down arrows, backspace)
- Process commands and handle pipeline mutations from REPL
- Fix lint issues in graph and REPL modules (type annotations, imports)
This commit is contained in:
2026-03-21 21:19:30 -07:00
parent fb0dd4592f
commit 6646ed78b3
7 changed files with 89 additions and 52 deletions

View File

@@ -30,7 +30,6 @@ Keyboard:
"""
from dataclasses import dataclass, field
from typing import Any, Optional
from engine.effects.types import (
EffectConfig,
@@ -69,7 +68,7 @@ class ReplEffect(EffectPlugin):
def __init__(self):
super().__init__()
self.state = REPLState()
self._last_metrics: Optional[dict] = None
self._last_metrics: dict | None = None
def process_partial(
self, buf: list[str], ctx: EffectContext, partial: PartialUpdate
@@ -82,8 +81,6 @@ class ReplEffect(EffectPlugin):
def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
"""Render buffer with REPL overlay."""
result = list(buf)
# Get display dimensions from context
height = ctx.terminal_height if hasattr(ctx, "terminal_height") else len(buf)
width = ctx.terminal_width if hasattr(ctx, "terminal_width") else 80
@@ -94,7 +91,6 @@ class ReplEffect(EffectPlugin):
# Reserve space for REPL at bottom
# HUD uses top 3 lines if enabled
hud_lines = 3 if show_hud else 0
content_height = max(1, height - repl_height)
# Build output
@@ -220,9 +216,7 @@ class ReplEffect(EffectPlugin):
return {"fps": 0.0, "frame_time": 0.0}
def process_command(
self, command: str, ctx: Optional[EffectContext] = None
) -> None:
def process_command(self, command: str, ctx: EffectContext | None = None) -> None:
"""Process a REPL command."""
cmd = command.strip()
if not cmd:
@@ -283,7 +277,7 @@ class ReplEffect(EffectPlugin):
self.state.output_buffer.append(" clear - Clear output buffer")
self.state.output_buffer.append(" quit - Show exit message")
def _cmd_status(self, ctx: Optional[EffectContext]):
def _cmd_status(self, ctx: EffectContext | None):
"""Show pipeline status."""
if ctx:
metrics = self._get_metrics(ctx)
@@ -299,7 +293,7 @@ class ReplEffect(EffectPlugin):
f"History: {len(self.state.command_history)} commands"
)
def _cmd_effects(self, ctx: Optional[EffectContext]):
def _cmd_effects(self, ctx: EffectContext | None):
"""List all effects."""
if ctx:
# Try to get effect list from context
@@ -313,7 +307,7 @@ class ReplEffect(EffectPlugin):
else:
self.state.output_buffer.append("No context available")
def _cmd_effect(self, args: list[str], ctx: Optional[EffectContext]):
def _cmd_effect(self, args: list[str], ctx: EffectContext | None):
"""Toggle effect on/off."""
if len(args) < 2:
self.state.output_buffer.append("Usage: effect <name> <on|off>")
@@ -336,7 +330,7 @@ class ReplEffect(EffectPlugin):
"stage": effect_name,
}
def _cmd_param(self, args: list[str], ctx: Optional[EffectContext]):
def _cmd_param(self, args: list[str], ctx: EffectContext | None):
"""Set effect parameter."""
if len(args) < 3:
self.state.output_buffer.append("Usage: param <effect> <param> <value>")
@@ -362,7 +356,7 @@ class ReplEffect(EffectPlugin):
"delta": param_value, # Note: This sets absolute value, need adjustment
}
def _cmd_pipeline(self, ctx: Optional[EffectContext]):
def _cmd_pipeline(self, ctx: EffectContext | None):
"""Show current pipeline order."""
if ctx:
pipeline_order = ctx.get_state("pipeline_order")
@@ -375,7 +369,7 @@ class ReplEffect(EffectPlugin):
else:
self.state.output_buffer.append("No context available")
def get_pending_command(self) -> Optional[dict]:
def get_pending_command(self) -> dict | None:
"""Get and clear pending command for external handling."""
cmd = getattr(self, "_pending_command", None)
if cmd: