diff --git a/completion/mainline-completion.bash b/completion/mainline-completion.bash new file mode 100644 index 0000000..ed1309a --- /dev/null +++ b/completion/mainline-completion.bash @@ -0,0 +1,99 @@ +# Mainline bash completion script +# +# To install: +# source /path/to/completion/mainline-completion.bash +# +# Or add to ~/.bashrc: +# source /path/to/completion/mainline-completion.bash + +_mainline_completion() { + local cur prev words cword + _init_completion || return + + # Get current word and previous word + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # Completion options based on previous word + case "${prev}" in + --display) + # Display backends + COMPREPLY=($(compgen -W "terminal null replay websocket pygame moderngl" -- "${cur}")) + return + ;; + + --pipeline-source) + # Available sources + COMPREPLY=($(compgen -W "headlines poetry empty fixture pipeline-inspect" -- "${cur}")) + return + ;; + + --pipeline-effects) + # Available effects (comma-separated) + local effects="afterimage border crop fade firehose glitch hud motionblur noise tint" + COMPREPLY=($(compgen -W "${effects}" -- "${cur}")) + return + ;; + + --pipeline-camera) + # Camera modes + COMPREPLY=($(compgen -W "feed scroll horizontal omni floating bounce radial" -- "${cur}")) + return + ;; + + --pipeline-border) + # Border modes + COMPREPLY=($(compgen -W "off simple ui" -- "${cur}")) + return + ;; + + --pipeline-display) + # Display backends (same as --display) + COMPREPLY=($(compgen -W "terminal null replay websocket pygame moderngl" -- "${cur}")) + return + ;; + + --theme) + # Theme colors + COMPREPLY=($(compgen -W "green orange purple blue red" -- "${cur}")) + return + ;; + + --viewport) + # Viewport size suggestions + COMPREPLY=($(compgen -W "80x24 100x30 120x40 60x20" -- "${cur}")) + return + ;; + + --preset) + # Presets (would need to query available presets) + COMPREPLY=($(compgen -W "demo demo-base demo-pygame demo-camera-showcase poetry headlines empty test-basic test-border test-scroll-camera" -- "${cur}")) + return + ;; + esac + + # Flag completion (start with --) + if [[ "${cur}" == -* ]]; then + COMPREPLY=($(compgen -W " + --display + --pipeline-source + --pipeline-effects + --pipeline-camera + --pipeline-display + --pipeline-ui + --pipeline-border + --viewport + --preset + --theme + --websocket + --websocket-port + --allow-unsafe + --help + " -- "${cur}")) + return + fi +} + +complete -F _mainline_completion mainline.py +complete -F _mainline_completion python\ -m\ engine.app +complete -F _mainline_completion python\ -m\ mainline diff --git a/completion/mainline-completion.fish b/completion/mainline-completion.fish new file mode 100644 index 0000000..e49c308 --- /dev/null +++ b/completion/mainline-completion.fish @@ -0,0 +1,81 @@ +# Fish completion script for Mainline +# +# To install: +# source /path/to/completion/mainline-completion.fish +# +# Or copy to ~/.config/fish/completions/mainline.fish + +# Define display backends +set -l display_backends terminal null replay websocket pygame moderngl + +# Define sources +set -l sources headlines poetry empty fixture pipeline-inspect + +# Define effects +set -l effects afterimage border crop fade firehose glitch hud motionblur noise tint + +# Define camera modes +set -l cameras feed scroll horizontal omni floating bounce radial + +# Define border modes +set -l borders off simple ui + +# Define themes +set -l themes green orange purple blue red + +# Define presets +set -l presets demo demo-base demo-pygame demo-camera-showcase poetry headlines empty test-basic test-border test-scroll-camera test-figment test-message-overlay + +# Main completion function +function __mainline_complete + set -l cmd (commandline -po) + set -l token (commandline -t) + + # Complete display backends + complete -c mainline.py -n '__fish_seen_argument --display' -a "$display_backends" -d 'Display backend' + + # Complete sources + complete -c mainline.py -n '__fish_seen_argument --pipeline-source' -a "$sources" -d 'Data source' + + # Complete effects + complete -c mainline.py -n '__fish_seen_argument --pipeline-effects' -a "$effects" -d 'Effect plugin' + + # Complete camera modes + complete -c mainline.py -n '__fish_seen_argument --pipeline-camera' -a "$cameras" -d 'Camera mode' + + # Complete display backends (pipeline) + complete -c mainline.py -n '__fish_seen_argument --pipeline-display' -a "$display_backends" -d 'Display backend' + + # Complete border modes + complete -c mainline.py -n '__fish_seen_argument --pipeline-border' -a "$borders" -d 'Border mode' + + # Complete themes + complete -c mainline.py -n '__fish_seen_argument --theme' -a "$themes" -d 'Color theme' + + # Complete presets + complete -c mainline.py -n '__fish_seen_argument --preset' -a "$presets" -d 'Preset name' + + # Complete viewport sizes + complete -c mainline.py -n '__fish_seen_argument --viewport' -a '80x24 100x30 120x40 60x20' -d 'Viewport size (WxH)' + + # Complete flag options + complete -c mainline.py -n 'not __fish_seen_argument --display' -l display -d 'Display backend' -a "$display_backends" + complete -c mainline.py -n 'not __fish_seen_argument --preset' -l preset -d 'Preset to use' -a "$presets" + complete -c mainline.py -n 'not __fish_seen_argument --viewport' -l viewport -d 'Viewport size (WxH)' -a '80x24 100x30 120x40 60x20' + complete -c mainline.py -n 'not __fish_seen_argument --theme' -l theme -d 'Color theme' -a "$themes" + complete -c mainline.py -l websocket -d 'Enable WebSocket server' + complete -c mainline.py -n 'not __fish_seen_argument --websocket-port' -l websocket-port -d 'WebSocket port' -a '8765' + complete -c mainline.py -l allow-unsafe -d 'Allow unsafe pipeline configuration' + complete -c mainline.py -n 'not __fish_seen_argument --help' -l help -d 'Show help' + + # Pipeline-specific flags + complete -c mainline.py -n 'not __fish_seen_argument --pipeline-source' -l pipeline-source -d 'Data source' -a "$sources" + complete -c mainline.py -n 'not __fish_seen_argument --pipeline-effects' -l pipeline-effects -d 'Effect plugins (comma-separated)' -a "$effects" + complete -c mainline.py -n 'not __fish_seen_argument --pipeline-camera' -l pipeline-camera -d 'Camera mode' -a "$cameras" + complete -c mainline.py -n 'not __fish_seen_argument --pipeline-display' -l pipeline-display -d 'Display backend' -a "$display_backends" + complete -c mainline.py -l pipeline-ui -d 'Enable UI panel' + complete -c mainline.py -n 'not __fish_seen_argument --pipeline-border' -l pipeline-border -d 'Border mode' -a "$borders" +end + +# Register the completion function +__mainline_complete diff --git a/completion/mainline-completion.zsh b/completion/mainline-completion.zsh new file mode 100644 index 0000000..17cae64 --- /dev/null +++ b/completion/mainline-completion.zsh @@ -0,0 +1,48 @@ +#compdef mainline.py + +# Mainline zsh completion script +# +# To install: +# source /path/to/completion/mainline-completion.zsh +# +# Or add to ~/.zshrc: +# source /path/to/completion/mainline-completion.zsh + +# Define completion function +_mainline() { + local -a commands + local curcontext="$curcontext" state line + typeset -A opt_args + + _arguments -C \ + '(-h --help)'{-h,--help}'[Show help]' \ + '--display=[Display backend]:backend:(terminal null replay websocket pygame moderngl)' \ + '--preset=[Preset to use]:preset:(demo demo-base demo-pygame demo-camera-showcase poetry headlines empty test-basic test-border test-scroll-camera test-figment test-message-overlay)' \ + '--viewport=[Viewport size]:size:(80x24 100x30 120x40 60x20)' \ + '--theme=[Color theme]:theme:(green orange purple blue red)' \ + '--websocket[Enable WebSocket server]' \ + '--websocket-port=[WebSocket port]:port:' \ + '--allow-unsafe[Allow unsafe pipeline configuration]' \ + '(-)*: :{_files}' \ + && ret=0 + + # Handle --pipeline-* arguments + if [[ -n ${words[*]} ]]; then + _arguments -C \ + '--pipeline-source=[Data source]:source:(headlines poetry empty fixture pipeline-inspect)' \ + '--pipeline-effects=[Effect plugins]:effects:(afterimage border crop fade firehose glitch hud motionblur noise tint)' \ + '--pipeline-camera=[Camera mode]:camera:(feed scroll horizontal omni floating bounce radial)' \ + '--pipeline-display=[Display backend]:backend:(terminal null replay websocket pygame moderngl)' \ + '--pipeline-ui[Enable UI panel]' \ + '--pipeline-border=[Border mode]:mode:(off simple ui)' \ + '--viewport=[Viewport size]:size:(80x24 100x30 120x40 60x20)' \ + && ret=0 + fi + + return ret +} + +# Register completion function +compdef _mainline mainline.py +compdef _mainline "python -m engine.app" +compdef _mainline "python -m mainline" diff --git a/engine/app/main.py b/engine/app/main.py index 483686b..fa66dd8 100644 --- a/engine/app/main.py +++ b/engine/app/main.py @@ -254,6 +254,16 @@ def run_pipeline_mode_direct(): # Create display using validated display name display_name = result.config.display or "terminal" # Default to terminal if empty + + # Warn if display was auto-selected (not explicitly specified) + if not display_name: + print( + " \033[38;5;226mWarning: No --pipeline-display specified, using default: terminal\033[0m" + ) + print( + " \033[38;5;245mTip: Use --pipeline-display null for headless mode (useful for testing)\033[0m" + ) + display = DisplayRegistry.create(display_name) if not display: print(f" \033[38;5;196mFailed to create display: {display_name}\033[0m") diff --git a/engine/app/pipeline_runner.py b/engine/app/pipeline_runner.py index df8a2c2..e21aa8a 100644 --- a/engine/app/pipeline_runner.py +++ b/engine/app/pipeline_runner.py @@ -189,10 +189,19 @@ def run_pipeline_mode(preset_name: str = "demo"): # CLI --display flag takes priority over preset # Check if --display was explicitly provided display_name = preset.display - if "--display" in sys.argv: + display_explicitly_specified = "--display" in sys.argv + if display_explicitly_specified: idx = sys.argv.index("--display") if idx + 1 < len(sys.argv): display_name = sys.argv[idx + 1] + else: + # Warn user that display is falling back to preset default + print( + f" \033[38;5;226mWarning: No --display specified, using preset default: {display_name}\033[0m" + ) + print( + " \033[38;5;245mTip: Use --display null for headless mode (useful for testing/capture)\033[0m" + ) display = DisplayRegistry.create(display_name) if not display and not display_name.startswith("multi"): diff --git a/engine/pipeline/controller.py b/engine/pipeline/controller.py index bc857ce..62eeb49 100644 --- a/engine/pipeline/controller.py +++ b/engine/pipeline/controller.py @@ -474,9 +474,10 @@ class Pipeline: not self._find_stage_with_capability("display.output") and "display" not in self._stages ): - display = DisplayRegistry.create("terminal") + display_name = self.config.display or "terminal" + display = DisplayRegistry.create(display_name) if display: - self.add_stage("display", DisplayStage(display, name="terminal")) + self.add_stage("display", DisplayStage(display, name=display_name)) injected.append("display") # Rebuild pipeline if stages were injected