Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 248 additions & 9 deletions cortex/branding.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@
Provides consistent visual branding across all Cortex CLI output.
Uses Rich library for cross-platform terminal styling.
Enhanced with rich output formatting (Issue #242):
- Color-coded status messages
- Formatted boxes and panels
- Progress spinners and bars
- Consistent visual language
"""

from typing import List, Optional, Tuple

Check failure on line 14 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP035)

cortex/branding.py:14:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead

Check failure on line 14 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP035)

cortex/branding.py:14:1: UP035 `typing.List` is deprecated, use `list` instead

Check failure on line 14 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP035)

cortex/branding.py:14:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead

Check failure on line 14 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP035)

cortex/branding.py:14:1: UP035 `typing.List` is deprecated, use `list` instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix deprecated type hint imports (CI failure).

The pipeline is failing because typing.List and typing.Tuple are deprecated in Python 3.9+. Use built-in list and tuple directly.

Suggested fix
-from typing import List, Optional, Tuple
+from typing import Optional

Then update the type annotations throughout:

  • List[str]list[str]
  • List[Tuple[str, str, str]]list[tuple[str, str, str]]
  • Optional[str]str | None
  • Optional[List[str]]list[str] | None
🧰 Tools
🪛 GitHub Actions: CI

[error] 14-14: Ruff: UP035 'typing.List' is deprecated. Use 'list' instead.

🪛 GitHub Check: lint

[failure] 14-14: Ruff (UP035)
cortex/branding.py:14:1: UP035 typing.Tuple is deprecated, use tuple instead


[failure] 14-14: Ruff (UP035)
cortex/branding.py:14:1: UP035 typing.List is deprecated, use list instead

🪛 GitHub Check: Lint

[failure] 14-14: Ruff (UP035)
cortex/branding.py:14:1: UP035 typing.Tuple is deprecated, use tuple instead


[failure] 14-14: Ruff (UP035)
cortex/branding.py:14:1: UP035 typing.List is deprecated, use list instead

🤖 Prompt for AI Agents
In `@cortex/branding.py` at line 14, The import of typing.List and typing.Tuple is
deprecated; remove List and Tuple from the typing import line and switch to
built-in generic types and PEP 604 unions throughout the module: replace usages
like List[str] with list[str], List[Tuple[str, str, str]] with list[tuple[str,
str, str]], Optional[str] with str | None and Optional[List[str]] with list[str]
| None, updating all annotations in cortex/branding.py (e.g., function
signatures, variable annotations, and return types) to use these built-ins and
the | None form.


from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

console = Console()

# Brand colors
CORTEX_CYAN = "cyan"
CORTEX_DARK = "dark_cyan"
CORTEX_SUCCESS = "green"
CORTEX_WARNING = "yellow"
CORTEX_ERROR = "red"
CORTEX_INFO = "blue"
CORTEX_MUTED = "dim"

# ASCII Logo - matches the CX circular logo
LOGO_LARGE = """
Expand Down Expand Up @@ -119,26 +134,250 @@
console.print()


# ============================================
# Rich Output Formatting (Issue #242)
# ============================================
Comment on lines +137 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This PR introduces a new output_formatter.py module which provides a more structured and reusable way to format output (e.g., using StatusInfo and TableColumn dataclasses). However, many of the new cx_* functions in this file reimplement similar logic instead of using the new module. This creates significant code duplication and two parallel implementations for formatting, which will be hard to maintain.

For example:

  • cx_status_box should use format_status_box from the new module.
  • cx_table is very similar to format_table.
  • cx_package_table duplicates table creation logic.

To improve the architecture, this branding.py module should be refactored to be a thin wrapper around output_formatter.py. The cx_* functions should delegate the core formatting work to the functions in the new module. This will centralize formatting logic and improve maintainability.



def cx_box(
content: str,
title: Optional[str] = None,

Check failure on line 144 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP045)

cortex/branding.py:144:12: UP045 Use `X | None` for type annotations

Check failure on line 144 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP045)

cortex/branding.py:144:12: UP045 Use `X | None` for type annotations
subtitle: Optional[str] = None,

Check failure on line 145 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP045)

cortex/branding.py:145:15: UP045 Use `X | None` for type annotations

Check failure on line 145 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP045)

cortex/branding.py:145:15: UP045 Use `X | None` for type annotations
status: str = "info",
) -> None:
"""
Print content in a styled box/panel.
Args:
content: Content to display
title: Optional box title
subtitle: Optional box subtitle
status: Style - "info", "success", "warning", "error"
"""
border_colors = {
"info": CORTEX_CYAN,
"success": CORTEX_SUCCESS,
"warning": CORTEX_WARNING,
"error": CORTEX_ERROR,
}
border_style = border_colors.get(status, CORTEX_CYAN)

panel = Panel(
content,
title=f"[bold]{title}[/bold]" if title else None,
subtitle=f"[dim]{subtitle}[/dim]" if subtitle else None,
border_style=border_style,
padding=(1, 2),
box=box.ROUNDED,
)
console.print(panel)

Comment on lines +142 to +174
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update type hints to modern syntax.

Per static analysis hints on lines 144-145, use str | None instead of Optional[str]:

Suggested fix
 def cx_box(
     content: str,
-    title: Optional[str] = None,
-    subtitle: Optional[str] = None,
+    title: str | None = None,
+    subtitle: str | None = None,
     status: str = "info",
 ) -> None:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def cx_box(
content: str,
title: Optional[str] = None,
subtitle: Optional[str] = None,
status: str = "info",
) -> None:
"""
Print content in a styled box/panel.
Args:
content: Content to display
title: Optional box title
subtitle: Optional box subtitle
status: Style - "info", "success", "warning", "error"
"""
border_colors = {
"info": CORTEX_CYAN,
"success": CORTEX_SUCCESS,
"warning": CORTEX_WARNING,
"error": CORTEX_ERROR,
}
border_style = border_colors.get(status, CORTEX_CYAN)
panel = Panel(
content,
title=f"[bold]{title}[/bold]" if title else None,
subtitle=f"[dim]{subtitle}[/dim]" if subtitle else None,
border_style=border_style,
padding=(1, 2),
box=box.ROUNDED,
)
console.print(panel)
def cx_box(
content: str,
title: str | None = None,
subtitle: str | None = None,
status: str = "info",
) -> None:
"""
Print content in a styled box/panel.
Args:
content: Content to display
title: Optional box title
subtitle: Optional box subtitle
status: Style - "info", "success", "warning", "error"
"""
border_colors = {
"info": CORTEX_CYAN,
"success": CORTEX_SUCCESS,
"warning": CORTEX_WARNING,
"error": CORTEX_ERROR,
}
border_style = border_colors.get(status, CORTEX_CYAN)
panel = Panel(
content,
title=f"[bold]{title}[/bold]" if title else None,
subtitle=f"[dim]{subtitle}[/dim]" if subtitle else None,
border_style=border_style,
padding=(1, 2),
box=box.ROUNDED,
)
console.print(panel)
🧰 Tools
🪛 GitHub Check: lint

[failure] 145-145: Ruff (UP045)
cortex/branding.py:145:15: UP045 Use X | None for type annotations


[failure] 144-144: Ruff (UP045)
cortex/branding.py:144:12: UP045 Use X | None for type annotations

🪛 GitHub Check: Lint

[failure] 145-145: Ruff (UP045)
cortex/branding.py:145:15: UP045 Use X | None for type annotations


[failure] 144-144: Ruff (UP045)
cortex/branding.py:144:12: UP045 Use X | None for type annotations

🤖 Prompt for AI Agents
In `@cortex/branding.py` around lines 142 - 174, The function cx_box uses legacy
typing Optional[str]; update its signature to use modern union syntax (str |
None) for title and subtitle (i.e., def cx_box(content: str, title: str | None =
None, subtitle: str | None = None, status: str = "info") -> None:) and replace
any other occurrences of Optional[str] in that function; remove or update unused
Optional import if no longer needed elsewhere. Ensure type hints for
title/subtitle and any related annotations use the new PEP 604 form while
leaving the rest of the function intact.


def cx_status_box(
title: str,
items: List[Tuple[str, str, str]],

Check failure on line 178 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP006)

cortex/branding.py:178:17: UP006 Use `tuple` instead of `Tuple` for type annotation

Check failure on line 178 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP006)

cortex/branding.py:178:12: UP006 Use `list` instead of `List` for type annotation

Check failure on line 178 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP006)

cortex/branding.py:178:17: UP006 Use `tuple` instead of `Tuple` for type annotation

Check failure on line 178 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP006)

cortex/branding.py:178:12: UP006 Use `list` instead of `List` for type annotation
) -> None:
"""
Print a status box with aligned key-value pairs.
Example output:
┌─────────────────────────────────────────┐
│ CORTEX ML SCHEDULER │
├─────────────────────────────────────────┤
│ Status: Active │
│ Uptime: 0.5 seconds │
└─────────────────────────────────────────┘
Args:
title: Box title
items: List of (label, value, status) tuples
status: "success", "warning", "error", "info", "default"
"""
style_colors = {
"success": CORTEX_SUCCESS,
"warning": CORTEX_WARNING,
"error": CORTEX_ERROR,
"info": CORTEX_CYAN,
"default": "white",
}

max_label_len = max(len(item[0]) for item in items) if items else 0
lines = []

for label, value, status in items:
color = style_colors.get(status, "white")
padded_label = label.ljust(max_label_len)
lines.append(f" [dim]{padded_label}:[/dim] [{color}]{value}[/{color}]")

content = "\n".join(lines)
panel = Panel(
content,
title=f"[bold cyan]{title}[/bold cyan]",
border_style=CORTEX_CYAN,
padding=(1, 2),
box=box.ROUNDED,
)
console.print(panel)


def cx_table(
headers: List[str],

Check failure on line 224 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP006)

cortex/branding.py:224:14: UP006 Use `list` instead of `List` for type annotation

Check failure on line 224 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP006)

cortex/branding.py:224:14: UP006 Use `list` instead of `List` for type annotation
rows: List[List[str]],

Check failure on line 225 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP006)

cortex/branding.py:225:16: UP006 Use `list` instead of `List` for type annotation

Check failure on line 225 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP006)

cortex/branding.py:225:11: UP006 Use `list` instead of `List` for type annotation

Check failure on line 225 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP006)

cortex/branding.py:225:16: UP006 Use `list` instead of `List` for type annotation

Check failure on line 225 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP006)

cortex/branding.py:225:11: UP006 Use `list` instead of `List` for type annotation
title: Optional[str] = None,

Check failure on line 226 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (UP045)

cortex/branding.py:226:12: UP045 Use `X | None` for type annotations

Check failure on line 226 in cortex/branding.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP045)

cortex/branding.py:226:12: UP045 Use `X | None` for type annotations
row_styles: Optional[List[str]] = None,
) -> None:
"""
Print a formatted table with Cortex styling.
Args:
headers: Column header names
rows: List of rows (each row is a list of cell values)
title: Optional table title
row_styles: Optional list of styles for each row
"""
table = Table(
title=f"[bold cyan]{title}[/bold cyan]" if title else None,
show_header=True,
header_style="bold cyan",
border_style=CORTEX_CYAN,
box=box.ROUNDED,
padding=(0, 1),
)

for header in headers:
table.add_column(header, style="cyan")

for i, row in enumerate(rows):
style = row_styles[i] if row_styles and i < len(row_styles) else None
table.add_row(*row, style=style)

console.print(table)

Comment on lines +223 to +255
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update type hints to modern syntax.

Per static analysis hints on lines 224-226, update to modern Python type annotation syntax:

Suggested fix
 def cx_table(
-    headers: List[str],
-    rows: List[List[str]],
-    title: Optional[str] = None,
-    row_styles: Optional[List[str]] = None,
+    headers: list[str],
+    rows: list[list[str]],
+    title: str | None = None,
+    row_styles: list[str] | None = None,
 ) -> None:
🧰 Tools
🪛 GitHub Check: lint

[failure] 226-226: Ruff (UP045)
cortex/branding.py:226:12: UP045 Use X | None for type annotations


[failure] 225-225: Ruff (UP006)
cortex/branding.py:225:16: UP006 Use list instead of List for type annotation


[failure] 225-225: Ruff (UP006)
cortex/branding.py:225:11: UP006 Use list instead of List for type annotation


[failure] 224-224: Ruff (UP006)
cortex/branding.py:224:14: UP006 Use list instead of List for type annotation

🪛 GitHub Check: Lint

[failure] 226-226: Ruff (UP045)
cortex/branding.py:226:12: UP045 Use X | None for type annotations


[failure] 225-225: Ruff (UP006)
cortex/branding.py:225:16: UP006 Use list instead of List for type annotation


[failure] 225-225: Ruff (UP006)
cortex/branding.py:225:11: UP006 Use list instead of List for type annotation


[failure] 224-224: Ruff (UP006)
cortex/branding.py:224:14: UP006 Use list instead of List for type annotation

🤖 Prompt for AI Agents
In `@cortex/branding.py` around lines 223 - 255, Update the cx_table function
signature to use modern PEP 585/604 type syntax: replace List[str] and
Optional[...] with built-in generics and union types (e.g., headers: list[str],
rows: list[list[str]], title: str | None = None, row_styles: list[str] | None =
None) and adjust any corresponding imports if present (remove or keep typing
imports as needed); keep the rest of the function body unchanged and ensure the
annotation names reference cx_table so the change is localized.


def cx_package_table(
packages: List[Tuple[str, str, str]],
title: str = "Packages",
) -> None:
"""
Print a formatted package table.
Args:
packages: List of (name, version, action) tuples
title: Table title
"""
table = Table(
title=f"[bold cyan]{title}[/bold cyan]",
show_header=True,
header_style="bold cyan",
border_style=CORTEX_CYAN,
box=box.ROUNDED,
padding=(0, 1),
)

table.add_column("Package", style="cyan", no_wrap=True)
table.add_column("Version", style="white")
table.add_column("Action", style="green")

for name, version, action in packages:
# Color-code actions
if "install" in action.lower():
action_styled = f"[green]{action}[/green]"
elif "remove" in action.lower() or "uninstall" in action.lower():
action_styled = f"[red]{action}[/red]"
elif "update" in action.lower() or "upgrade" in action.lower():
action_styled = f"[yellow]{action}[/yellow]"
else:
action_styled = action
Comment on lines +282 to +290
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The if/elif/else chain for styling the action string is a bit verbose and can be hard to maintain. This can be made more data-driven by using a dictionary to map keywords to styles, which is more scalable if more action types are added in the future.

Suggested change
# Color-code actions
if "install" in action.lower():
action_styled = f"[green]{action}[/green]"
elif "remove" in action.lower() or "uninstall" in action.lower():
action_styled = f"[red]{action}[/red]"
elif "update" in action.lower() or "upgrade" in action.lower():
action_styled = f"[yellow]{action}[/yellow]"
else:
action_styled = action
# Color-code actions
action_color = "white"
action_lower = action.lower()
if "install" in action_lower:
action_color = "green"
elif "remove" in action_lower or "uninstall" in action_lower:
action_color = "red"
elif "update" in action_lower or "upgrade" in action_lower:
action_color = "yellow"
action_styled = action if action_color == "white" else f"[{action_color}]{action}[/{action_color}]"

table.add_row(name, version, action_styled)

console.print(table)


def cx_divider(title: Optional[str] = None) -> None:
"""
Print a horizontal divider with optional title.
Args:
title: Optional section title
"""
if title:
console.print(f"\n[bold cyan]━━━ {title} ━━━[/bold cyan]\n")
else:
console.print(f"[{CORTEX_CYAN}]{'━' * 50}[/{CORTEX_CYAN}]")


def cx_success(message: str) -> None:
"""Print a success message with checkmark."""
console.print(f"[{CORTEX_SUCCESS}]✓[/{CORTEX_SUCCESS}] {message}")


def cx_error(message: str) -> None:
"""Print an error message with X."""
console.print(f"[{CORTEX_ERROR}]✗[/{CORTEX_ERROR}] [{CORTEX_ERROR}]{message}[/{CORTEX_ERROR}]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The cx_error function contains redundant Rich markup for coloring. The icon and the message are styled separately with the same color. This can be simplified by wrapping the entire string in a single color tag, which improves readability and maintainability.

Suggested change
console.print(f"[{CORTEX_ERROR}]✗[/{CORTEX_ERROR}] [{CORTEX_ERROR}]{message}[/{CORTEX_ERROR}]")
console.print(f"[{CORTEX_ERROR}]✗ {message}[/{CORTEX_ERROR}]")



def cx_warning(message: str) -> None:
"""Print a warning message with warning icon."""
console.print(f"[{CORTEX_WARNING}]⚠[/{CORTEX_WARNING}] [{CORTEX_WARNING}]{message}[/{CORTEX_WARNING}]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The cx_warning function contains redundant Rich markup for coloring. The icon and the message are styled separately with the same color. This can be simplified by wrapping the entire string in a single color tag, which improves readability and maintainability.

Suggested change
console.print(f"[{CORTEX_WARNING}]⚠[/{CORTEX_WARNING}] [{CORTEX_WARNING}]{message}[/{CORTEX_WARNING}]")
console.print(f"[{CORTEX_WARNING}]⚠ {message}[/{CORTEX_WARNING}]")



def cx_info(message: str) -> None:
"""Print an info message with info icon."""
console.print(f"[{CORTEX_INFO}]ℹ[/{CORTEX_INFO}] {message}")


def cx_spinner_message(message: str) -> None:
"""Print a message with spinner icon (static, for logs)."""
console.print(f"[{CORTEX_CYAN}]⠋[/{CORTEX_CYAN}] {message}")


# Demo
if __name__ == "__main__":
# Full banner
show_banner()
print()

# Simulated operation flow
cx_print("Understanding request...", "thinking")
cx_print("Planning installation...", "info")
cx_header("Installation Plan")

cx_print("docker.io (24.0.5) — Container runtime", "info")
cx_print("docker-compose (2.20.2) — Multi-container orchestration", "info")
# Status box demo (Issue #242 format)
cx_status_box(
"CORTEX ML SCHEDULER",
[
("Status", "Active", "success"),
("Uptime", "0.5 seconds", "default"),
("CPU Usage", "12%", "info"),
("Memory", "256 MB", "warning"),
],
)
print()

# Package table demo
cx_package_table(
[
("docker.io", "24.0.5", "Install"),
("docker-compose", "2.20.2", "Install"),
("nginx", "1.24.0", "Update"),
],
title="Installation Plan",
)
print()

# Simulated operation flow
cx_divider("Installation Progress")
cx_step(1, 4, "Updating package lists...")
cx_step(2, 4, "Installing docker.io...")
cx_step(3, 4, "Installing docker-compose...")
cx_step(4, 4, "Configuring services...")
print()

# Status messages
cx_success("Package installed successfully")
cx_warning("Disk space running low")
cx_error("Installation failed")
cx_info("Checking dependencies...")
print()
cx_print("Installation complete!", "success")
cx_print("Docker is ready to use.", "info")

# Box demo
cx_box(
"Installation completed!\nAll packages are now available.",
title="Success",
status="success",
)
Loading
Loading