Compare commits
4 Commits
6646ed78b3
...
a050e26c03
| Author | SHA1 | Date | |
|---|---|---|---|
| a050e26c03 | |||
| d5406a6b11 | |||
| 3fac583d94 | |||
| 995badbffc |
116
REPL_USAGE.md
Normal file
116
REPL_USAGE.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# REPL Usage Guide
|
||||||
|
|
||||||
|
The REPL (Read-Eval-Print Loop) effect provides an interactive command-line interface for controlling Mainline's pipeline in real-time.
|
||||||
|
|
||||||
|
## How to Access the REPL
|
||||||
|
|
||||||
|
### Method 1: Using CLI Arguments (Recommended)
|
||||||
|
|
||||||
|
Run Mainline with the `repl` effect added to the effects list:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With empty source (for testing)
|
||||||
|
python mainline.py --pipeline-source empty --pipeline-effects repl
|
||||||
|
|
||||||
|
# With headlines source (requires network)
|
||||||
|
python mainline.py --pipeline-source headlines --pipeline-effects repl
|
||||||
|
|
||||||
|
# With poetry source
|
||||||
|
python mainline.py --pipeline-source poetry --pipeline-effects repl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Using a Preset
|
||||||
|
|
||||||
|
Add a preset to your `~/.config/mainline/presets.toml` or `./presets.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[presets.repl]
|
||||||
|
description = "Interactive REPL control"
|
||||||
|
source = "headlines"
|
||||||
|
display = "terminal"
|
||||||
|
effects = ["repl"]
|
||||||
|
viewport_width = 80
|
||||||
|
viewport_height = 24
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
```bash
|
||||||
|
python mainline.py --preset repl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: Using Graph Config
|
||||||
|
|
||||||
|
Create a TOML file (e.g., `repl_config.toml`):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
source = "empty"
|
||||||
|
display = "terminal"
|
||||||
|
effects = ["repl"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
```bash
|
||||||
|
python mainline.py --graph-config repl_config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## REPL Commands
|
||||||
|
|
||||||
|
Once the REPL is active, you can type commands:
|
||||||
|
|
||||||
|
- **help** - Show available commands
|
||||||
|
- **status** - Show pipeline status and metrics
|
||||||
|
- **effects** - List all effects in the pipeline
|
||||||
|
- **effect \<name\> \<on|off\>** - Toggle an effect
|
||||||
|
- **param \<effect\> \<param\> \<value\>** - Set effect parameter
|
||||||
|
- **pipeline** - Show current pipeline order
|
||||||
|
- **clear** - Clear output buffer
|
||||||
|
- **quit/exit** - Show exit message (use Ctrl+C to actually exit)
|
||||||
|
|
||||||
|
## Keyboard Controls
|
||||||
|
|
||||||
|
- **Enter** - Execute command
|
||||||
|
- **Up/Down arrows** - Navigate command history
|
||||||
|
- **Backspace** - Delete last character
|
||||||
|
- **Ctrl+C** - Exit Mainline
|
||||||
|
|
||||||
|
## Visual Features
|
||||||
|
|
||||||
|
The REPL displays:
|
||||||
|
- **HUD header** (top 3 lines): Shows FPS, frame time, command count, and output buffer size
|
||||||
|
- **Content area**: Main content from the data source
|
||||||
|
- **Separator line**: Visual divider
|
||||||
|
- **REPL area**: Output buffer and input prompt
|
||||||
|
|
||||||
|
## Example Session
|
||||||
|
|
||||||
|
```
|
||||||
|
MAINLINE REPL | FPS: 60.0 | 12.5ms
|
||||||
|
COMMANDS: 3 | [2/3]
|
||||||
|
OUTPUT: 5 lines
|
||||||
|
────────────────────────────────────────
|
||||||
|
Content from source appears here...
|
||||||
|
More content...
|
||||||
|
────────────────────────────────────────
|
||||||
|
> help
|
||||||
|
Available commands:
|
||||||
|
help - Show this help
|
||||||
|
status - Show pipeline status
|
||||||
|
effects - List all effects
|
||||||
|
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
|
||||||
|
> effects
|
||||||
|
Pipeline effects:
|
||||||
|
1. repl
|
||||||
|
> effect repl off
|
||||||
|
Effect 'repl' set to off
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The REPL effect needs a content source to overlay on (e.g., headlines, poetry, empty)
|
||||||
|
- The REPL uses terminal display with raw input mode
|
||||||
|
- Command history is preserved across sessions (up to 50 commands)
|
||||||
|
- Pipeline mutations (enabling/disabling effects) are handled automatically
|
||||||
@@ -34,6 +34,82 @@ except ImportError:
|
|||||||
from .pipeline_runner import run_pipeline_mode
|
from .pipeline_runner import run_pipeline_mode
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_pipeline_mutation(pipeline: Pipeline, command: dict) -> bool:
|
||||||
|
"""Handle pipeline mutation commands from REPL or other external control.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pipeline: The pipeline to mutate
|
||||||
|
command: Command dictionary with 'action' and other parameters
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if command was successfully handled, False otherwise
|
||||||
|
"""
|
||||||
|
action = command.get("action")
|
||||||
|
|
||||||
|
if action == "add_stage":
|
||||||
|
print(f" [Pipeline] add_stage command received: {command}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif action == "remove_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
if stage_name:
|
||||||
|
result = pipeline.remove_stage(stage_name)
|
||||||
|
print(f" [Pipeline] Removed stage '{stage_name}': {result is not None}")
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
elif action == "replace_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
print(f" [Pipeline] replace_stage command received: {command}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif action == "swap_stages":
|
||||||
|
stage1 = command.get("stage1")
|
||||||
|
stage2 = command.get("stage2")
|
||||||
|
if stage1 and stage2:
|
||||||
|
result = pipeline.swap_stages(stage1, stage2)
|
||||||
|
print(f" [Pipeline] Swapped stages '{stage1}' and '{stage2}': {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif action == "move_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
after = command.get("after")
|
||||||
|
before = command.get("before")
|
||||||
|
if stage_name:
|
||||||
|
result = pipeline.move_stage(stage_name, after, before)
|
||||||
|
print(f" [Pipeline] Moved stage '{stage_name}': {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif action == "enable_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
if stage_name:
|
||||||
|
result = pipeline.enable_stage(stage_name)
|
||||||
|
print(f" [Pipeline] Enabled stage '{stage_name}': {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif action == "disable_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
if stage_name:
|
||||||
|
result = pipeline.disable_stage(stage_name)
|
||||||
|
print(f" [Pipeline] Disabled stage '{stage_name}': {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif action == "cleanup_stage":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
if stage_name:
|
||||||
|
pipeline.cleanup_stage(stage_name)
|
||||||
|
print(f" [Pipeline] Cleaned up stage '{stage_name}'")
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif action == "can_hot_swap":
|
||||||
|
stage_name = command.get("stage")
|
||||||
|
if stage_name:
|
||||||
|
can_swap = pipeline.can_hot_swap(stage_name)
|
||||||
|
print(f" [Pipeline] Can hot-swap '{stage_name}': {can_swap}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main entry point - all modes now use presets or CLI construction."""
|
"""Main entry point - all modes now use presets or CLI construction."""
|
||||||
if config.PIPELINE_DIAGRAM:
|
if config.PIPELINE_DIAGRAM:
|
||||||
@@ -391,6 +467,21 @@ def run_pipeline_mode_direct():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Check for REPL effect in pipeline
|
||||||
|
repl_effect = None
|
||||||
|
for stage in pipeline.stages.values():
|
||||||
|
if isinstance(stage, EffectPluginStage) and stage._effect.name == "repl":
|
||||||
|
repl_effect = stage._effect
|
||||||
|
print(
|
||||||
|
" \033[38;5;46mREPL effect detected - Interactive mode enabled\033[0m"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Enable raw mode for REPL if present and not already enabled
|
||||||
|
# Also enable for UI border mode (already handled above)
|
||||||
|
if repl_effect and ui_panel is None and hasattr(display, "set_raw_mode"):
|
||||||
|
display.set_raw_mode(True)
|
||||||
|
|
||||||
# Run pipeline loop
|
# Run pipeline loop
|
||||||
from engine.display import render_ui_panel
|
from engine.display import render_ui_panel
|
||||||
|
|
||||||
@@ -453,6 +544,37 @@ def run_pipeline_mode_direct():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# --- REPL Input Handling ---
|
||||||
|
if repl_effect and hasattr(display, "get_input_keys"):
|
||||||
|
# Get keyboard input (non-blocking)
|
||||||
|
keys = display.get_input_keys(timeout=0.0)
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
if key == "ctrl_c":
|
||||||
|
# Request quit when Ctrl+C is pressed
|
||||||
|
if hasattr(display, "request_quit"):
|
||||||
|
display.request_quit()
|
||||||
|
else:
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
elif key == "return":
|
||||||
|
# Get command string before processing
|
||||||
|
cmd_str = repl_effect.state.current_command
|
||||||
|
if cmd_str:
|
||||||
|
repl_effect.process_command(cmd_str, ctx)
|
||||||
|
# Check for pending pipeline mutations
|
||||||
|
pending = repl_effect.get_pending_command()
|
||||||
|
if pending:
|
||||||
|
_handle_pipeline_mutation(pipeline, pending)
|
||||||
|
elif key == "up":
|
||||||
|
repl_effect.navigate_history(-1)
|
||||||
|
elif key == "down":
|
||||||
|
repl_effect.navigate_history(1)
|
||||||
|
elif key == "backspace":
|
||||||
|
repl_effect.backspace()
|
||||||
|
elif len(key) == 1:
|
||||||
|
repl_effect.append_to_command(key)
|
||||||
|
# --- End REPL Input Handling ---
|
||||||
|
|
||||||
# Check for quit request
|
# Check for quit request
|
||||||
if hasattr(display, "is_quit_requested") and display.is_quit_requested():
|
if hasattr(display, "is_quit_requested") and display.is_quit_requested():
|
||||||
if hasattr(display, "clear_quit_request"):
|
if hasattr(display, "clear_quit_request"):
|
||||||
|
|||||||
@@ -986,7 +986,13 @@ def run_pipeline_mode(preset_name: str = "demo", graph_config: str | None = None
|
|||||||
keys = display.get_input_keys(timeout=0.0)
|
keys = display.get_input_keys(timeout=0.0)
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key == "return":
|
if key == "ctrl_c":
|
||||||
|
# Request quit when Ctrl+C is pressed
|
||||||
|
if hasattr(display, "request_quit"):
|
||||||
|
display.request_quit()
|
||||||
|
else:
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
elif key == "return":
|
||||||
# Get command string before processing
|
# Get command string before processing
|
||||||
cmd_str = repl_effect.state.current_command
|
cmd_str = repl_effect.state.current_command
|
||||||
if cmd_str:
|
if cmd_str:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class TerminalDisplay:
|
|||||||
self._cached_dimensions: tuple[int, int] | None = None
|
self._cached_dimensions: tuple[int, int] | None = None
|
||||||
self._raw_mode_enabled: bool = False
|
self._raw_mode_enabled: bool = False
|
||||||
self._original_termios: list = []
|
self._original_termios: list = []
|
||||||
|
self._quit_requested: bool = False
|
||||||
|
|
||||||
def init(self, width: int, height: int, reuse: bool = False) -> None:
|
def init(self, width: int, height: int, reuse: bool = False) -> None:
|
||||||
"""Initialize display with dimensions.
|
"""Initialize display with dimensions.
|
||||||
@@ -163,11 +164,15 @@ class TerminalDisplay:
|
|||||||
|
|
||||||
def is_quit_requested(self) -> bool:
|
def is_quit_requested(self) -> bool:
|
||||||
"""Check if quit was requested (optional protocol method)."""
|
"""Check if quit was requested (optional protocol method)."""
|
||||||
return False
|
return self._quit_requested
|
||||||
|
|
||||||
def clear_quit_request(self) -> None:
|
def clear_quit_request(self) -> None:
|
||||||
"""Clear quit request (optional protocol method)."""
|
"""Clear quit request (optional protocol method)."""
|
||||||
pass
|
self._quit_requested = False
|
||||||
|
|
||||||
|
def request_quit(self) -> None:
|
||||||
|
"""Request quit (e.g., when Ctrl+C is pressed)."""
|
||||||
|
self._quit_requested = True
|
||||||
|
|
||||||
def set_raw_mode(self, enable: bool = True) -> None:
|
def set_raw_mode(self, enable: bool = True) -> None:
|
||||||
"""Enable/disable raw terminal mode for input capture.
|
"""Enable/disable raw terminal mode for input capture.
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class ReplEffect(EffectPlugin):
|
|||||||
fps_str = f"FPS: {fps:.1f}" if fps > 0 else "FPS: --"
|
fps_str = f"FPS: {fps:.1f}" if fps > 0 else "FPS: --"
|
||||||
time_str = f"{frame_time:.1f}ms" if frame_time > 0 else "--ms"
|
time_str = f"{frame_time:.1f}ms" if frame_time > 0 else "--ms"
|
||||||
line1 = (
|
line1 = (
|
||||||
f"\033[1;1H\033[38;5;46mMAINLINE REPL\033[0m "
|
f"\033[38;5;46mMAINLINE REPL\033[0m "
|
||||||
f"\033[38;5;245m|\033[0m \033[38;5;39m{fps_str}\033[0m "
|
f"\033[38;5;245m|\033[0m \033[38;5;39m{fps_str}\033[0m "
|
||||||
f"\033[38;5;245m|\033[0m \033[38;5;208m{time_str}\033[0m"
|
f"\033[38;5;245m|\033[0m \033[38;5;208m{time_str}\033[0m"
|
||||||
)
|
)
|
||||||
@@ -150,7 +150,7 @@ class ReplEffect(EffectPlugin):
|
|||||||
f"[{self.state.history_index + 1}/{cmd_count}]" if cmd_count > 0 else ""
|
f"[{self.state.history_index + 1}/{cmd_count}]" if cmd_count > 0 else ""
|
||||||
)
|
)
|
||||||
line2 = (
|
line2 = (
|
||||||
f"\033[2;1H\033[38;5;45mCOMMANDS:\033[0m "
|
f"\033[38;5;45mCOMMANDS:\033[0m "
|
||||||
f"\033[1;38;5;227m{cmd_count}\033[0m "
|
f"\033[1;38;5;227m{cmd_count}\033[0m "
|
||||||
f"\033[38;5;245m|\033[0m \033[38;5;219m{hist_idx}\033[0m"
|
f"\033[38;5;245m|\033[0m \033[38;5;219m{hist_idx}\033[0m"
|
||||||
)
|
)
|
||||||
@@ -158,10 +158,7 @@ class ReplEffect(EffectPlugin):
|
|||||||
|
|
||||||
# Line 3: Output buffer count
|
# Line 3: Output buffer count
|
||||||
out_count = len(self.state.output_buffer)
|
out_count = len(self.state.output_buffer)
|
||||||
line3 = (
|
line3 = f"\033[38;5;44mOUTPUT:\033[0m \033[1;38;5;227m{out_count}\033[0m lines"
|
||||||
f"\033[3;1H\033[38;5;44mOUTPUT:\033[0m "
|
|
||||||
f"\033[1;38;5;227m{out_count}\033[0m lines"
|
|
||||||
)
|
|
||||||
lines.append(line3[:width])
|
lines.append(line3[:width])
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
54
examples/repl_demo_terminal.py
Normal file
54
examples/repl_demo_terminal.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
REPL Demo with Terminal Display - Shows how to use the REPL effect
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python examples/repl_demo_terminal.py
|
||||||
|
|
||||||
|
This demonstrates the REPL effect with terminal display and interactive input.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from engine.effects.plugins import discover_plugins
|
||||||
|
from engine.pipeline.hybrid_config import PipelineConfig
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run REPL demo with terminal display."""
|
||||||
|
print("REPL Demo with Terminal Display")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Discover plugins
|
||||||
|
discover_plugins()
|
||||||
|
|
||||||
|
# Create a pipeline with REPL effect
|
||||||
|
# Using empty source so there's content to overlay on
|
||||||
|
config = PipelineConfig(
|
||||||
|
source="empty",
|
||||||
|
effects=[{"name": "repl", "intensity": 1.0}],
|
||||||
|
display="terminal",
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline = config.to_pipeline(viewport_width=80, viewport_height=24)
|
||||||
|
|
||||||
|
# Initialize pipeline
|
||||||
|
if not pipeline.initialize():
|
||||||
|
print("Failed to initialize pipeline")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nREPL is now active!")
|
||||||
|
print("Try typing commands:")
|
||||||
|
print(" help - Show available commands")
|
||||||
|
print(" status - Show pipeline status")
|
||||||
|
print(" effects - List all effects")
|
||||||
|
print(" pipeline - Show current pipeline order")
|
||||||
|
print(" clear - Clear output buffer")
|
||||||
|
print("\nPress Ctrl+C to exit")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user