diff --git a/dimos/utils/cli/agentspy/agentspy.py b/dimos/utils/cli/agentspy/agentspy.py index a3fc70f0b0..2d69e3537f 100644 --- a/dimos/utils/cli/agentspy/agentspy.py +++ b/dimos/utils/cli/agentspy/agentspy.py @@ -30,6 +30,7 @@ from textual.widgets import Footer, RichLog from dimos.protocol.pubsub.lcmpubsub import PickleLCM +from dimos.utils.cli import theme # Type alias for all message types we might receive AnyMessage = Union[SystemMessage, ToolMessage, AIMessage, HumanMessage] @@ -137,23 +138,25 @@ def format_message_content(msg: AnyMessage) -> str: class AgentSpyApp(App): """TUI application for monitoring agent messages.""" - CSS = """ - Screen { + CSS_PATH = theme.CSS_PATH + + CSS = f""" + Screen {{ layout: vertical; - background: black; - } + background: {theme.BACKGROUND}; + }} - RichLog { + RichLog {{ height: 1fr; border: none; - background: black; + background: {theme.BACKGROUND}; padding: 0 1; - } + }} - Footer { + Footer {{ dock: bottom; height: 1; - } + }} """ BINDINGS = [ diff --git a/dimos/utils/cli/dimos.tcss b/dimos/utils/cli/dimos.tcss new file mode 100644 index 0000000000..3ccbde957d --- /dev/null +++ b/dimos/utils/cli/dimos.tcss @@ -0,0 +1,91 @@ +/* DimOS Base Theme for Textual CLI Applications + * Based on colors.json - Official DimOS color palette + */ + +/* Base Color Palette (from colors.json) */ +$black: #0b0f0f; +$red: #ff0000; +$green: #00eeee; +$yellow: #ffcc00; +$blue: #5c9ff0; +$purple: #00eeee; +$cyan: #00eeee; +$white: #b5e4f4; + +/* Bright Colors */ +$bright-black: #404040; +$bright-red: #ff0000; +$bright-green: #00eeee; +$bright-yellow: #f2ea8c; +$bright-blue: #8cbdf2; +$bright-purple: #00eeee; +$bright-cyan: #00eeee; +$bright-white: #ffffff; + +/* Core Theme Colors */ +$background: #0b0f0f; +$foreground: #b5e4f4; +$cursor: #00eeee; + +/* Semantic Aliases */ +$bg: $black; +$border: $cyan; +$accent: $white; +$dim: $bright-black; +$timestamp: $bright-white; + +/* Message Type Colors */ +$system: $red; +$agent: #88ff88; +$tool: $cyan; +$tool-result: $yellow; +$human: $bright-white; + +/* Status Colors */ +$success: $green; +$error: $red; +$warning: $yellow; +$info: $cyan; + +/* Base Screen */ +Screen { + background: $bg; +} + +/* Default Container */ +Container { + background: $bg; +} + +/* Input Widget */ +Input { + background: $bg; + border: solid $border; + color: $accent; +} + +Input:focus { + border: solid $border; +} + +/* RichLog Widget */ +RichLog { + background: $bg; + border: solid $border; +} + +/* Button Widget */ +Button { + background: $bg; + border: solid $border; + color: $accent; +} + +Button:hover { + background: $dim; + border: solid $accent; +} + +Button:focus { + border: double $accent; +} diff --git a/dimos/agents2/cli/human_cli.py b/dimos/utils/cli/human/humancli.py similarity index 73% rename from dimos/agents2/cli/human_cli.py rename to dimos/utils/cli/human/humancli.py index d72389941d..fb0ebc5fe2 100644 --- a/dimos/agents2/cli/human_cli.py +++ b/dimos/utils/cli/human/humancli.py @@ -20,6 +20,8 @@ from typing import Optional from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolCall, ToolMessage +from rich.highlighter import JSONHighlighter +from rich.theme import Theme from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Container @@ -27,30 +29,45 @@ from textual.widgets import Input, RichLog from dimos.core import pLCMTransport +from dimos.utils.cli import theme from dimos.utils.generic import truncate_display_string +# Custom theme for JSON highlighting +JSON_THEME = Theme( + { + "json.key": theme.CYAN, + "json.str": theme.ACCENT, + "json.number": theme.ACCENT, + "json.bool_true": theme.ACCENT, + "json.bool_false": theme.ACCENT, + "json.null": theme.DIM, + "json.brace": theme.BRIGHT_WHITE, + } +) + + class HumanCLIApp(App): """IRC-like interface for interacting with DimOS agents.""" - CSS = """ - Screen { - background: black; - } - - #chat-container { + CSS_PATH = theme.CSS_PATH + + CSS = f""" + Screen {{ + background: {theme.BACKGROUND}; + }} + + #chat-container {{ height: 1fr; - background: black; - } - - Input { - background: black; + }} + + RichLog {{ + scrollbar-size: 0 0; + }} + + Input {{ dock: bottom; - } - - RichLog { - background: black; - } + }} """ BINDINGS = [ @@ -79,9 +96,14 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: """Initialize the app when mounted.""" - self.theme = "flexoki" self._running = True + # Apply custom JSON theme to app console + self.console.push_theme(JSON_THEME) + + # Set custom highlighter for RichLog + self.chat_log.highlighter = JSONHighlighter() + # Start subscription thread self._subscription_thread = threading.Thread(target=self._subscribe_to_agent, daemon=True) self._subscription_thread.start() @@ -89,6 +111,17 @@ 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}]") + # Welcome message self._add_system_message("Connected to DimOS Agent Interface") @@ -111,7 +144,7 @@ def receive_msg(msg): timestamp, "system", truncate_display_string(msg.content, 1000), - "red", + theme.YELLOW, ) elif isinstance(msg, AIMessage): content = msg.content or "" @@ -119,25 +152,31 @@ def receive_msg(msg): # Display the main content first if content: - self.call_from_thread(self._add_message, timestamp, "agent", content, "orange") + self.call_from_thread( + self._add_message, timestamp, "agent", content, theme.AGENT + ) # Display tool calls separately with different formatting if tool_calls: for tc in tool_calls: tool_info = self._format_tool_call(tc) self.call_from_thread( - self._add_message, timestamp, "tool", tool_info, "cyan" + self._add_message, timestamp, "tool", tool_info, theme.TOOL ) # If neither content nor tool calls, show a placeholder if not content and not tool_calls: self.call_from_thread( - self._add_message, timestamp, "agent", "", "dim" + self._add_message, timestamp, "agent", "", theme.DIM ) elif isinstance(msg, ToolMessage): - self.call_from_thread(self._add_message, timestamp, "tool", msg.content, "yellow") + self.call_from_thread( + self._add_message, timestamp, "tool", msg.content, theme.TOOL_RESULT + ) elif isinstance(msg, HumanMessage): - self.call_from_thread(self._add_message, timestamp, "human", msg.content, "green") + self.call_from_thread( + self._add_message, timestamp, "human", msg.content, theme.HUMAN + ) self.agent_transport.subscribe(receive_msg) @@ -156,9 +195,9 @@ def _add_message(self, timestamp: str, sender: str, content: str, color: str) -> time_parts = timestamp.split(":") if len(time_parts) == 3: # Format as HH:MM:SS with colored colons - timestamp_formatted = f" [dim white]{time_parts[0]}[/dim white][bright_black]:[/bright_black][dim white]{time_parts[1]}[/dim white][bright_black]:[/bright_black][dim white]{time_parts[2]}[/dim white]" + timestamp_formatted = f" [{theme.TIMESTAMP}]{time_parts[0]}:{time_parts[1]}:{time_parts[2]}[/{theme.TIMESTAMP}]" else: - timestamp_formatted = f" [dim white]{timestamp}[/dim white]" + timestamp_formatted = f" [{theme.TIMESTAMP}]{timestamp}[/{theme.TIMESTAMP}]" # Format sender with consistent width sender_formatted = f"[{color}]{sender:>8}[/{color}]" @@ -206,7 +245,7 @@ def _add_message(self, timestamp: str, sender: str, content: str, color: str) -> def _add_system_message(self, content: str) -> None: """Add a system message to the chat.""" timestamp = datetime.now().strftime("%H:%M:%S") - self._add_message(timestamp, "system", content, "red") + self._add_message(timestamp, "system", content, theme.YELLOW) def on_key(self, event: Key) -> None: """Handle key events.""" diff --git a/dimos/utils/cli/lcmspy/run_lcmspy.py b/dimos/utils/cli/lcmspy/run_lcmspy.py index 13288cafe9..4faef02892 100644 --- a/dimos/utils/cli/lcmspy/run_lcmspy.py +++ b/dimos/utils/cli/lcmspy/run_lcmspy.py @@ -26,49 +26,62 @@ from textual.containers import Container from textual.reactive import reactive from textual.renderables.sparkline import Sparkline as SparklineRenderable -from textual.widgets import DataTable, Footer, Header, Label, Sparkline +from textual.widgets import DataTable, Header, Label, Sparkline +from dimos.utils.cli import theme from dimos.utils.cli.lcmspy.lcmspy import GraphLCMSpy from dimos.utils.cli.lcmspy.lcmspy import GraphTopic as SpyTopic def gradient(max_value: float, value: float) -> str: + """Gradient from cyan (low) to yellow (high) using DimOS theme colors""" ratio = min(value / max_value, 1.0) - green = Color(0, 255, 0) - red = Color(255, 0, 0) - color = green.blend(red, ratio) + # Parse hex colors from theme + cyan = Color.parse(theme.CYAN) + yellow = Color.parse(theme.YELLOW) + color = cyan.blend(yellow, ratio) return color.hex def topic_text(topic_name: str) -> Text: + """Format topic name with DimOS theme colors""" if "#" in topic_name: parts = topic_name.split("#", 1) - return Text(parts[0], style="white") + Text("#" + parts[1], style="blue") + return Text(parts[0], style=theme.BRIGHT_WHITE) + Text("#" + parts[1], style=theme.BLUE) if topic_name[:4] == "/rpc": - return Text(topic_name[:4], style="red") + Text(topic_name[4:], style="white") + return Text(topic_name[:4], style=theme.BLUE) + Text( + topic_name[4:], style=theme.BRIGHT_WHITE + ) - return Text(topic_name, style="white") + return Text(topic_name, style=theme.BRIGHT_WHITE) class LCMSpyApp(App): """A real-time CLI dashboard for LCM traffic statistics using Textual.""" - CSS = """ - Screen { + CSS_PATH = "../dimos.tcss" + + CSS = f""" + Screen {{ layout: vertical; - } - DataTable { + background: {theme.BACKGROUND}; + }} + DataTable {{ height: 2fr; width: 1fr; - border: none; - background: black; - } + border: solid {theme.BORDER}; + background: {theme.BG}; + scrollbar-size: 0 0; + }} + DataTable > .datatable--header {{ + color: {theme.ACCENT}; + background: transparent; + }} """ refresh_interval: float = 0.5 # seconds - show_command_palette = reactive(True) BINDINGS = [ ("q", "quit"), @@ -81,18 +94,14 @@ def __init__(self, *args, **kwargs): self.table: DataTable | None = None def compose(self) -> ComposeResult: - # yield Header() - self.table = DataTable(zebra_stripes=False, cursor_type=None) self.table.add_column("Topic") self.table.add_column("Freq (Hz)") self.table.add_column("Bandwidth") self.table.add_column("Total Traffic") yield self.table - yield Footer() def on_mount(self): - self.theme = "flexoki" self.spy.start() self.set_interval(self.refresh_interval, self.refresh_table) diff --git a/dimos/utils/cli/skillspy/skillspy.py b/dimos/utils/cli/skillspy/skillspy.py index 68253aa848..bfb0a7edc8 100644 --- a/dimos/utils/cli/skillspy/skillspy.py +++ b/dimos/utils/cli/skillspy/skillspy.py @@ -22,12 +22,11 @@ from rich.text import Text from textual.app import App, ComposeResult from textual.binding import Binding -from textual.containers import Vertical -from textual.reactive import reactive -from textual.widgets import DataTable, Footer, RichLog +from textual.widgets import DataTable, Footer from dimos.protocol.skill.comms import SkillMsg from dimos.protocol.skill.coordinator import SkillCoordinator, SkillState, SkillStateEnum +from dimos.utils.cli import theme class AgentSpy: @@ -38,9 +37,11 @@ def __init__(self): self.message_callbacks: list[Callable[[Dict[str, SkillState]], None]] = [] self._lock = threading.Lock() self._latest_state: Dict[str, SkillState] = {} + self._running = False def start(self): """Start spying on agent messages.""" + self._running = True # Start the agent interface self.agent_interface.start() @@ -49,14 +50,21 @@ def start(self): def stop(self): """Stop spying.""" + self._running = False + # Give threads a moment to finish processing + time.sleep(0.2) self.agent_interface.stop() def _handle_message(self, msg: SkillMsg): """Handle incoming skill messages.""" + if not self._running: + return # Small delay to ensure agent_interface has processed the message def delayed_update(): time.sleep(0.1) + if not self._running: + return with self._lock: self._latest_state = self.agent_interface.generate_snapshot(clear=False) for callback in self.message_callbacks: @@ -78,14 +86,14 @@ def get_state(self) -> Dict[str, SkillState]: def state_color(state: SkillStateEnum) -> str: """Get color for skill state.""" if state == SkillStateEnum.pending: - return "yellow" + return theme.WARNING elif state == SkillStateEnum.running: - return "green" + return theme.AGENT elif state == SkillStateEnum.completed: - return "cyan" + return theme.SUCCESS elif state == SkillStateEnum.error: - return "red" - return "white" + return theme.ERROR + return theme.FOREGROUND def format_duration(duration: float) -> str: @@ -100,87 +108,40 @@ def format_duration(duration: float) -> str: return f"{duration / 3600:.1f}h" -class AgentSpyLogFilter(logging.Filter): - """Filter to suppress specific log messages in agentspy.""" - - def filter(self, record): - # Suppress the "Skill state not found" warning as it's expected in agentspy - if ( - record.levelname == "WARNING" - and "Skill state for" in record.getMessage() - and "not found" in record.getMessage() - ): - return False - return True - - -class TextualLogHandler(logging.Handler): - """Custom log handler that sends logs to a Textual RichLog widget.""" - - def __init__(self, log_widget: RichLog): - super().__init__() - self.log_widget = log_widget - # Add filter to suppress expected warnings - self.addFilter(AgentSpyLogFilter()) - - def emit(self, record): - """Emit a log record to the RichLog widget.""" - try: - msg = self.format(record) - # Color based on level - if record.levelno >= logging.ERROR: - style = "bold red" - elif record.levelno >= logging.WARNING: - style = "yellow" - elif record.levelno >= logging.INFO: - style = "green" - else: - style = "dim" - - self.log_widget.write(Text(msg, style=style)) - except Exception: - self.handleError(record) - - class AgentSpyApp(App): """A real-time CLI dashboard for agent skill monitoring using Textual.""" - CSS = """ - Screen { + CSS_PATH = theme.CSS_PATH + + CSS = f""" + Screen {{ layout: vertical; - } - Vertical { + background: {theme.BACKGROUND}; + }} + DataTable {{ height: 100%; - } - DataTable { - height: 70%; - border: none; - background: black; - } - RichLog { - height: 30%; - border: none; - background: black; - border-top: solid $primary; - } + border: solid $border; + background: {theme.BACKGROUND}; + }} + DataTable > .datatable--header {{ + background: transparent; + }} + Footer {{ + background: transparent; + }} """ BINDINGS = [ Binding("q", "quit", "Quit"), Binding("c", "clear", "Clear History"), - Binding("l", "toggle_logs", "Toggle Logs"), Binding("ctrl+c", "quit", "Quit", show=False), ] - show_logs = reactive(True) - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.spy = AgentSpy() self.table: Optional[DataTable] = None - self.log_view: Optional[RichLog] = None self.skill_history: list[tuple[str, SkillState, float]] = [] # (call_id, state, start_time) - self.log_handler: Optional[TextualLogHandler] = None def compose(self) -> ComposeResult: self.table = DataTable(zebra_stripes=False, cursor_type=None) @@ -191,79 +152,20 @@ def compose(self) -> ComposeResult: self.table.add_column("Messages") self.table.add_column("Details") - self.log_view = RichLog(markup=True, wrap=True) - - with Vertical(): - yield self.table - yield self.log_view - + yield self.table yield Footer() def on_mount(self): """Start the spy when app mounts.""" - self.theme = "flexoki" - - # Remove ALL existing handlers from ALL loggers to prevent console output - # This is needed because setup_logger creates loggers with propagate=False - for name in logging.root.manager.loggerDict: - logger = logging.getLogger(name) - logger.handlers.clear() - logger.propagate = True - - # Clear root logger handlers too - logging.root.handlers.clear() - - # Set up custom log handler to show logs in the UI - if self.log_view: - self.log_handler = TextualLogHandler(self.log_view) - - # Custom formatter that shortens the logger name and highlights call_ids - class ShortNameFormatter(logging.Formatter): - def format(self, record): - # Remove the common prefix from logger names - if record.name.startswith("dimos.protocol.skill."): - record.name = record.name.replace("dimos.protocol.skill.", "") - - # Highlight call_ids in the message - msg = record.getMessage() - if "call_id=" in msg: - # Extract and colorize call_id - import re - - msg = re.sub(r"call_id=([^\s\)]+)", r"call_id=\033[94m\1\033[0m", msg) - record.msg = msg - record.args = () - - return super().format(record) - - self.log_handler.setFormatter( - ShortNameFormatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S" - ) - ) - # Add handler to root logger - root_logger = logging.getLogger() - root_logger.addHandler(self.log_handler) - root_logger.setLevel(logging.INFO) - - # Set initial visibility - if not self.show_logs: - self.log_view.visible = False - self.table.styles.height = "100%" - self.spy.subscribe(self.update_state) self.spy.start() - # Also set up periodic refresh to update durations + # Set up periodic refresh to update durations self.set_interval(1.0, self.refresh_table) def on_unmount(self): """Stop the spy when app unmounts.""" self.spy.stop() - # Remove log handler to prevent errors on shutdown - if self.log_handler: - root_logger = logging.getLogger() - root_logger.removeHandler(self.log_handler) def update_state(self, state: Dict[str, SkillState]): """Update state from spy callback. State dict is keyed by call_id.""" @@ -341,12 +243,12 @@ def refresh_table(self): # Add row with colored state self.table.add_row( - Text(display_call_id, style="bright_blue"), - Text(skill_state.name, style="white"), + Text(display_call_id, style=theme.BRIGHT_BLUE), + Text(skill_state.name, style=theme.YELLOW), Text(skill_state.state.name, style=state_color(skill_state.state)), - Text(duration_str, style="dim"), - Text(str(msg_count), style="dim"), - Text(details, style="dim white"), + Text(duration_str, style=theme.WHITE), + Text(str(msg_count), style=theme.YELLOW), + Text(details, style=theme.FOREGROUND), ) def action_clear(self): @@ -354,15 +256,6 @@ def action_clear(self): self.skill_history.clear() self.refresh_table() - def action_toggle_logs(self): - """Toggle the log view visibility.""" - self.show_logs = not self.show_logs - if self.show_logs: - self.table.styles.height = "70%" - else: - self.table.styles.height = "100%" - self.log_view.visible = self.show_logs - def main(): """Main entry point for agentspy CLI.""" diff --git a/dimos/utils/cli/theme.py b/dimos/utils/cli/theme.py new file mode 100644 index 0000000000..aa061bc43a --- /dev/null +++ b/dimos/utils/cli/theme.py @@ -0,0 +1,99 @@ +# 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. + +"""Parse DimOS theme from tcss file.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +def parse_tcss_colors(tcss_path: str | Path) -> dict[str, str]: + """Parse color variables from a tcss file. + + Args: + tcss_path: Path to the tcss file + + Returns: + Dictionary mapping variable names to color values + """ + tcss_path = Path(tcss_path) + content = tcss_path.read_text() + + # Match $variable: value; patterns + pattern = r"\$([a-zA-Z0-9_-]+)\s*:\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3});" + matches = re.findall(pattern, content) + + return {name: value for name, value in matches} + + +# Load DimOS theme colors +_THEME_PATH = Path(__file__).parent / "dimos.tcss" +COLORS = parse_tcss_colors(_THEME_PATH) + +# Export CSS path for Textual apps +CSS_PATH = str(_THEME_PATH) + + +# Convenience accessors for common colors +def get(name: str, default: str = "#ffffff") -> str: + """Get a color by variable name.""" + return COLORS.get(name, default) + + +# Base color palette +BLACK = COLORS.get("black", "#0b0f0f") +RED = COLORS.get("red", "#ff0000") +GREEN = COLORS.get("green", "#00eeee") +YELLOW = COLORS.get("yellow", "#ffcc00") +BLUE = COLORS.get("blue", "#5c9ff0") +PURPLE = COLORS.get("purple", "#00eeee") +CYAN = COLORS.get("cyan", "#00eeee") +WHITE = COLORS.get("white", "#b5e4f4") + +# Bright colors +BRIGHT_BLACK = COLORS.get("bright-black", "#404040") +BRIGHT_RED = COLORS.get("bright-red", "#ff0000") +BRIGHT_GREEN = COLORS.get("bright-green", "#00eeee") +BRIGHT_YELLOW = COLORS.get("bright-yellow", "#f2ea8c") +BRIGHT_BLUE = COLORS.get("bright-blue", "#8cbdf2") +BRIGHT_PURPLE = COLORS.get("bright-purple", "#00eeee") +BRIGHT_CYAN = COLORS.get("bright-cyan", "#00eeee") +BRIGHT_WHITE = COLORS.get("bright-white", "#ffffff") + +# Core theme colors +BACKGROUND = COLORS.get("background", "#0b0f0f") +FOREGROUND = COLORS.get("foreground", "#b5e4f4") +CURSOR = COLORS.get("cursor", "#00eeee") + +# Semantic aliases +BG = COLORS.get("bg", "#0b0f0f") +BORDER = COLORS.get("border", "#00eeee") +ACCENT = COLORS.get("accent", "#b5e4f4") +DIM = COLORS.get("dim", "#404040") +TIMESTAMP = COLORS.get("timestamp", "#ffffff") + +# Message type colors +SYSTEM = COLORS.get("system", "#ff0000") +AGENT = COLORS.get("agent", "#88ff88") +TOOL = COLORS.get("tool", "#00eeee") +TOOL_RESULT = COLORS.get("tool-result", "#ffff00") +HUMAN = COLORS.get("human", "#ffffff") + +# Status colors +SUCCESS = COLORS.get("success", "#00eeee") +ERROR = COLORS.get("error", "#ff0000") +WARNING = COLORS.get("warning", "#ffcc00") +INFO = COLORS.get("info", "#00eeee") diff --git a/pyproject.toml b/pyproject.toml index 2eab703602..6a9738fedb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,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.agents2.cli.human_cli:main" +human-cli = "dimos.utils.cli.human.humancli:main" [project.optional-dependencies] manipulation = [