diff --git a/dimos/utils/cli/human/humancli.py b/dimos/utils/cli/human/humancli.py index 4c474b88d2..15fd82bf5b 100644 --- a/dimos/utils/cli/human/humancli.py +++ b/dimos/utils/cli/human/humancli.py @@ -112,16 +112,7 @@ def on_mount(self) -> None: # Focus on input self.input_widget.focus() - # Display ASCII art banner - ascii_art = """ - ██████╗ ██╗███╗ ███╗███████╗███╗ ██╗███████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██╗ - ██╔══██╗██║████╗ ████║██╔════╝████╗ ██║██╔════╝██║██╔═══██╗████╗ ██║██╔══██╗██║ - ██║ ██║██║██╔████╔██║█████╗ ██╔██╗ ██║███████╗██║██║ ██║██╔██╗ ██║███████║██║ - ██║ ██║██║██║╚██╔╝██║██╔══╝ ██║╚██╗██║╚════██║██║██║ ██║██║╚██╗██║██╔══██║██║ - ██████╔╝██║██║ ╚═╝ ██║███████╗██║ ╚████║███████║██║╚██████╔╝██║ ╚████║██║ ██║███████╗ - ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ -""" - self.chat_log.write(f"[{theme.ACCENT}]{ascii_art}[/{theme.ACCENT}]") + self.chat_log.write(f"[{theme.ACCENT}]{theme.ascii_logo}[/{theme.ACCENT}]") # Welcome message self._add_system_message("Connected to DimOS Agent Interface") diff --git a/dimos/utils/cli/human/humanclianim.py b/dimos/utils/cli/human/humanclianim.py new file mode 100644 index 0000000000..a0349eedf8 --- /dev/null +++ b/dimos/utils/cli/human/humanclianim.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import random +import sys +import threading +import time + +from terminaltexteffects import Color + +from dimos.utils.cli import theme + +# Global to store the imported main function +_humancli_main = None +_import_complete = threading.Event() + +print(theme.ACCENT) + + +def import_cli_in_background(): + """Import the heavy CLI modules in the background""" + global _humancli_main + try: + from dimos.utils.cli.human.humancli import main as humancli_main + + _humancli_main = humancli_main + except Exception as e: + print(f"Failed to import CLI: {e}") + finally: + _import_complete.set() + + +def get_effect_config(effect_name): + """Get hardcoded configuration for a specific effect""" + # Hardcoded configs for each effect + global_config = { + "final_gradient_stops": [Color(theme.ACCENT)], + } + + configs = { + "randomsequence": { + "speed": 0.075, + }, + "slide": {"direction": "left", "movement_speed": 1.5}, + "sweep": {"direction": "left"}, + "print": { + "print_speed": 10, + "print_head_return_speed": 10, + "final_gradient_stops": [Color(theme.ACCENT)], + }, + "pour": {"pour_speed": 9}, + "matrix": {"rain_symbols": "01", "rain_fall_speed_range": (4, 7)}, + "decrypt": {"typing_speed": 5, "decryption_speed": 3}, + "burn": {"fire_chars": "█", "flame_color": "ffffff"}, + "expand": {"expand_direction": "center"}, + "scattered": {"movement_speed": 0.5}, + "beams": {"movement_speed": 0.5, "beam_delay": 0}, + "middleout": {"center_movement_speed": 3, "full_movement_speed": 0.5}, + "rain": { + "rain_symbols": "░▒▓█", + "rain_fall_speed_range": (5, 10), + }, + "highlight": {"highlight_brightness": 3}, + } + + return {**configs.get(effect_name, {}), **global_config} + + +def run_banner_animation(): + """Run the ASCII banner animation before launching Textual""" + + # Check if we should animate + random_anim = ["scattered", "print", "expand", "slide", "rain"] + animation_style = os.environ.get("DIMOS_BANNER_ANIMATION", random.choice(random_anim)).lower() + + if animation_style == "none": + return # Skip animation + from terminaltexteffects.effects.effect_beams import Beams + from terminaltexteffects.effects.effect_burn import Burn + from terminaltexteffects.effects.effect_colorshift import ColorShift + from terminaltexteffects.effects.effect_decrypt import Decrypt + from terminaltexteffects.effects.effect_expand import Expand + from terminaltexteffects.effects.effect_highlight import Highlight + from terminaltexteffects.effects.effect_matrix import Matrix + from terminaltexteffects.effects.effect_middleout import MiddleOut + from terminaltexteffects.effects.effect_overflow import Overflow + from terminaltexteffects.effects.effect_pour import Pour + from terminaltexteffects.effects.effect_print import Print + from terminaltexteffects.effects.effect_rain import Rain + from terminaltexteffects.effects.effect_random_sequence import RandomSequence + from terminaltexteffects.effects.effect_scattered import Scattered + from terminaltexteffects.effects.effect_slide import Slide + from terminaltexteffects.effects.effect_sweep import Sweep + + # The DIMENSIONAL ASCII art + ascii_art = "\n" + theme.ascii_logo.replace("\n", "\n ") + # Choose effect based on style + effect_map = { + "slide": Slide, + "sweep": Sweep, + "print": Print, + "pour": Pour, + "burn": Burn, + "matrix": Matrix, + "rain": Rain, + "scattered": Scattered, + "expand": Expand, + "decrypt": Decrypt, + "overflow": Overflow, + "randomsequence": RandomSequence, + "beams": Beams, + "middleout": MiddleOut, + "highlight": Highlight, + } + + EffectClass = effect_map.get(animation_style, Slide) + + # Clear screen before starting animation + print("\033[2J\033[H", end="", flush=True) + + # Get effect configuration + effect_config = get_effect_config(animation_style) + + # Create and run the effect with config + effect = EffectClass(ascii_art) + for key, value in effect_config.items(): + setattr(effect.effect_config, key, value) + + # Run the animation - terminal.print() handles all screen management + with effect.terminal_output() as terminal: + for frame in effect: + terminal.print(frame) + + # Brief pause to see the final frame + time.sleep(0.5) + + # Clear screen for Textual to take over + print("\033[2J\033[H", end="") + + +def main(): + """Main entry point - run animation then launch the real CLI""" + + # Start importing CLI in background (this is slow) + import_thread = threading.Thread(target=import_cli_in_background, daemon=True) + import_thread.start() + + # Run the animation while imports happen (if not in web mode) + if not (len(sys.argv) > 1 and sys.argv[1] == "web"): + run_banner_animation() + + # Wait for import to complete + _import_complete.wait(timeout=10) # Max 10 seconds wait + + # Launch the real CLI + if _humancli_main: + _humancli_main() + else: + # Fallback if threaded import failed + from dimos.utils.cli.human.humancli import main as humancli_main + + humancli_main() + + +if __name__ == "__main__": + main() diff --git a/dimos/utils/cli/theme.py b/dimos/utils/cli/theme.py index e3d98b07de..9db369930f 100644 --- a/dimos/utils/cli/theme.py +++ b/dimos/utils/cli/theme.py @@ -97,3 +97,12 @@ def get(name: str, default: str = "#ffffff") -> str: ERROR = COLORS.get("error", "#ff0000") WARNING = COLORS.get("warning", "#ffcc00") INFO = COLORS.get("info", "#00eeee") + +ascii_logo = """ + ▇▇▇▇▇▇╗ ▇▇╗▇▇▇╗ ▇▇▇╗▇▇▇▇▇▇▇╗▇▇▇╗ ▇▇╗▇▇▇▇▇▇▇╗▇▇╗ ▇▇▇▇▇▇╗ ▇▇▇╗ ▇▇╗ ▇▇▇▇▇╗ ▇▇╗ + ▇▇╔══▇▇╗▇▇║▇▇▇▇╗ ▇▇▇▇║▇▇╔════╝▇▇▇▇╗ ▇▇║▇▇╔════╝▇▇║▇▇╔═══▇▇╗▇▇▇▇╗ ▇▇║▇▇╔══▇▇╗▇▇║ + ▇▇║ ▇▇║▇▇║▇▇╔▇▇▇▇╔▇▇║▇▇▇▇▇╗ ▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇╗▇▇║▇▇║ ▇▇║▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇║▇▇║ + ▇▇║ ▇▇║▇▇║▇▇║╚▇▇╔╝▇▇║▇▇╔══╝ ▇▇║╚▇▇╗▇▇║╚════▇▇║▇▇║▇▇║ ▇▇║▇▇║╚▇▇╗▇▇║▇▇╔══▇▇║▇▇║ + ▇▇▇▇▇▇╔╝▇▇║▇▇║ ╚═╝ ▇▇║▇▇▇▇▇▇▇╗▇▇║ ╚▇▇▇▇║▇▇▇▇▇▇▇║▇▇║╚▇▇▇▇▇▇╔╝▇▇║ ╚▇▇▇▇║▇▇║ ▇▇║▇▇▇▇▇▇▇╗ + ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ +""" diff --git a/pyproject.toml b/pyproject.toml index 4a7cec2c6c..50c91d009e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,7 @@ lcmspy = "dimos.utils.cli.lcmspy.run_lcmspy:main" foxglove-bridge = "dimos.utils.cli.foxglove_bridge.run_foxglove_bridge:main" skillspy = "dimos.utils.cli.skillspy.skillspy:main" agentspy = "dimos.utils.cli.agentspy.agentspy:main" -human-cli = "dimos.utils.cli.human.humancli:main" +humancli = "dimos.utils.cli.human.humanclianim:main" dimos-robot = "dimos.robot.cli.dimos_robot:main" [project.optional-dependencies] @@ -189,6 +189,7 @@ dev = [ "pytest-timeout==2.4.0", "textual==3.7.1", "requests-mock==1.12.1", + "terminaltexteffects==0.12.2", # Types "lxml-stubs>=0.5.1,<1",