refactor: reorganize into monorepo with separate subprojects
Build and Push API / release (push) Successful in 3s
Build and Push API / build (push) Successful in 2m27s

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>
This commit is contained in:
2026-01-10 10:37:47 +01:00
co-authored by Claude Opus 4.5
parent f4e8552298
commit 3b58fa4f8b
121 changed files with 2034 additions and 284 deletions
+7
View File
@@ -0,0 +1,7 @@
"""
CLI UI components.
"""
from src.cli.ui.console import get_console
from src.cli.ui.display import format_response, format_code
__all__ = ["get_console", "format_response", "format_code"]
+37
View File
@@ -0,0 +1,37 @@
"""
Rich console helpers.
"""
from functools import lru_cache
from rich.console import Console
from rich.theme import Theme
from src.cli.theme import get_theme
@lru_cache
def get_console() -> Console:
"""Get the shared console instance with theme applied."""
theme = get_theme()
rich_theme = Theme(theme.to_rich_theme_dict())
return Console(theme=rich_theme)
def print_info(message: str) -> None:
"""Print an info message."""
get_console().print(f"[info]{message}[/]")
def print_warning(message: str) -> None:
"""Print a warning message."""
get_console().print(f"[warning]Warning:[/] {message}")
def print_error(message: str) -> None:
"""Print an error message."""
get_console().print(f"[error]Error:[/] {message}")
def print_success(message: str) -> None:
"""Print a success message."""
get_console().print(f"[success]{message}[/]")
+84
View File
@@ -0,0 +1,84 @@
"""
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)