feat(repl): Add REPL effect with HUD-style interactive interface

Implement a Read-Eval-Print Loop (REPL) effect that provides a
HUD-style overlay for interactive pipeline control.

## New Files

- engine/effects/plugins/repl.py - REPL effect plugin with command processor
- engine/display/backends/terminal.py - Added raw mode and input handling
- examples/repl_simple.py - Simple demonstration script
- tests/test_repl_effect.py - 18 comprehensive tests

## Features

### REPL Interface
- HUD-style overlay showing FPS, command history, output buffer size
- Command history navigation (Up/Down arrows)
- Command execution (Enter)
- Character input and backspace support
- Output buffer with scrolling

### Commands
- help - Show available commands
- status - Show pipeline status and metrics
- effects - List all effects in pipeline
- effect <name> <on|off> - Toggle effect
- param <effect> <param> <value> - Set parameter
- pipeline - Show current pipeline order
- clear - Clear output buffer
- quit - Show exit message

### Terminal Input Support
- Added set_raw_mode() to TerminalDisplay for capturing keystrokes
- Added get_input_keys() to read keyboard input
- Proper terminal state restoration on cleanup

## Usage

Add 'repl' to effects in your configuration:

## Testing

All 18 REPL tests pass, covering:
- Effect registration
- Command processing
- Navigation (history, editing)
- Configuration
- Rendering

## Integration

The REPL effect integrates with the existing pipeline system:
- Uses EffectPlugin interface
- Supports partial updates
- Reads metrics from EffectContext
- Can be controlled via keyboard when terminal display is in raw mode

Next steps:
- Integrate REPL input handling into pipeline_runner.py
- Add keyboard event processing loop
- Create full demo with interactive features
This commit is contained in:
2026-03-21 21:12:38 -07:00
parent 2c23c423a0
commit fb0dd4592f
6 changed files with 975 additions and 12 deletions

View File

@@ -124,22 +124,44 @@ class PipelineConfig:
# Add effect nodes
for effect in self.effects:
graph.node(
effect.name,
NodeType.EFFECT,
effect=effect.name,
intensity=effect.intensity,
enabled=effect.enabled,
**effect.params,
)
# Handle both EffectConfig objects and dictionaries
if isinstance(effect, dict):
name = effect.get("name", "")
intensity = effect.get("intensity", 1.0)
enabled = effect.get("enabled", True)
params = effect.get("params", {})
else:
name = effect.name
intensity = effect.intensity
enabled = effect.enabled
params = effect.params
if name:
graph.node(
name,
NodeType.EFFECT,
effect=name,
intensity=intensity,
enabled=enabled,
**params,
)
# Add display node
display_config = self.display or DisplayConfig()
if isinstance(self.display, dict):
display_backend = self.display.get("backend", "terminal")
display_positioning = self.display.get("positioning", "mixed")
elif self.display:
display_backend = self.display.backend
display_positioning = self.display.positioning
else:
display_backend = "terminal"
display_positioning = "mixed"
graph.node(
"display",
NodeType.DISPLAY,
backend=display_config.backend,
positioning=display_config.positioning,
backend=display_backend,
positioning=display_positioning,
)
# Create linear connections
@@ -151,7 +173,9 @@ class PipelineConfig:
# Add all effects in order
for effect in self.effects:
chain.append(effect.name)
name = effect.get("name", "") if isinstance(effect, dict) else effect.name
if name:
chain.append(name)
chain.append("display")