Structure webber into three independent subprojects: - webber-api/: FastAPI backend server with all agent code - webber-cli/: Standalone CLI client (renamed from cli/ to webber_cli/) - webber-sandbox/: Test project for functional testing Key changes: - Each subproject has its own .venv (Python 3.12+) - Added sandbox.sh for managing test project templates - Created sandbox-templates/ with calculator-cli and empty starter - Updated CI/CD for prefixed tags (api/v*, cli/v*) - Added comprehensive AGENTS.md with operational instructions - Added gitignore filtering to glob and grep tools - Created pyproject.toml for each subproject Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
85 lines
1.9 KiB
Python
85 lines
1.9 KiB
Python
"""
|
|
Output formatting and display helpers.
|
|
"""
|
|
import re
|
|
|
|
from rich.markdown import Markdown
|
|
from rich.syntax import Syntax
|
|
from rich.text import Text
|
|
|
|
from src.cli.theme import get_theme
|
|
from src.cli.ui.console import get_console
|
|
|
|
|
|
def format_response(text: str) -> Markdown | Text:
|
|
"""
|
|
Format agent response for display.
|
|
|
|
Detects markdown and formats appropriately.
|
|
"""
|
|
# Check if response contains markdown patterns
|
|
has_markdown = any([
|
|
"```" in text, # Code blocks
|
|
text.startswith("#"), # Headers
|
|
"**" in text or "__" in text, # Bold
|
|
"- " in text or "* " in text, # Lists
|
|
])
|
|
|
|
if has_markdown:
|
|
return Markdown(text)
|
|
else:
|
|
return Text(text)
|
|
|
|
|
|
def format_code(code: str, language: str = "python") -> Syntax:
|
|
"""
|
|
Format code with syntax highlighting.
|
|
|
|
Args:
|
|
code: Source code to format
|
|
language: Programming language for highlighting
|
|
"""
|
|
theme = get_theme()
|
|
return Syntax(
|
|
code,
|
|
language,
|
|
theme=theme.syntax_theme,
|
|
line_numbers=True,
|
|
word_wrap=True,
|
|
)
|
|
|
|
|
|
def format_file_path(path: str, line: int | None = None) -> Text:
|
|
"""
|
|
Format a file path for display.
|
|
|
|
Args:
|
|
path: File path
|
|
line: Optional line number
|
|
"""
|
|
text = Text()
|
|
text.append(path, style="path")
|
|
if line:
|
|
text.append(f":{line}", style="dim")
|
|
return text
|
|
|
|
|
|
def truncate_text(text: str, max_length: int = 500, suffix: str = "...") -> str:
|
|
"""
|
|
Truncate text to maximum length.
|
|
|
|
Args:
|
|
text: Text to truncate
|
|
max_length: Maximum character length
|
|
suffix: Suffix to add if truncated
|
|
"""
|
|
if len(text) <= max_length:
|
|
return text
|
|
return text[:max_length - len(suffix)] + suffix
|
|
|
|
|
|
def strip_ansi(text: str) -> str:
|
|
"""Remove ANSI escape codes from text."""
|
|
ansi_pattern = re.compile(r'\x1b\[[0-9;]*m')
|
|
return ansi_pattern.sub('', text)
|