refactor: reorganize into monorepo with separate subprojects
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:
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Chat command - interactive conversation mode.
|
||||
"""
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
from src.cli.theme import get_theme
|
||||
from src.cli.ui.console import get_console
|
||||
from src.cli.session.loop import AgenticLoop
|
||||
from src.shared.logging import setup_logging
|
||||
|
||||
console = get_console()
|
||||
|
||||
|
||||
def chat_command(
|
||||
directory: str = typer.Option(
|
||||
".",
|
||||
"--directory",
|
||||
"-d",
|
||||
help="Working directory to explore",
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose",
|
||||
"-V",
|
||||
help="Show detailed output and debug logging",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Start interactive chat session.
|
||||
|
||||
Enters a conversation loop where you can ask questions about the codebase.
|
||||
The explore agent will search files, read code, and answer questions.
|
||||
|
||||
Examples:
|
||||
webber chat
|
||||
webber chat -d ./src
|
||||
webber chat --verbose
|
||||
"""
|
||||
# Set up logging
|
||||
log_level = "DEBUG" if verbose else "WARNING"
|
||||
setup_logging(log_level)
|
||||
|
||||
# Resolve directory
|
||||
working_dir = str(Path(directory).resolve())
|
||||
|
||||
if not Path(working_dir).exists():
|
||||
console.print(f"[error]Error:[/] Directory not found: {working_dir}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Run the async chat loop
|
||||
try:
|
||||
asyncio.run(_chat_loop(working_dir, verbose))
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[dim]Goodbye![/]")
|
||||
|
||||
|
||||
async def _chat_loop(working_dir: str, verbose: bool) -> None:
|
||||
"""Async chat loop implementation."""
|
||||
from src.domains.agents.explore import explore_agent
|
||||
|
||||
# Create the agentic loop
|
||||
loop = AgenticLoop(
|
||||
agent=explore_agent,
|
||||
console=console,
|
||||
working_dir=working_dir,
|
||||
)
|
||||
|
||||
# Display welcome
|
||||
loop.display_welcome()
|
||||
|
||||
# Main conversation loop
|
||||
while True:
|
||||
try:
|
||||
# Get user input
|
||||
user_input = console.input("[prompt]>[/] ").strip()
|
||||
|
||||
# Handle special commands
|
||||
if not user_input:
|
||||
continue
|
||||
|
||||
if user_input.lower() in ("exit", "quit", "/exit", "/quit"):
|
||||
console.print("[dim]Goodbye![/]")
|
||||
break
|
||||
|
||||
if user_input.lower() in ("clear", "/clear"):
|
||||
loop.state.clear_history()
|
||||
console.print("[info]History cleared.[/]")
|
||||
continue
|
||||
|
||||
if user_input.lower() in ("status", "/status"):
|
||||
loop.display_status()
|
||||
continue
|
||||
|
||||
if user_input.lower().startswith("cd "):
|
||||
new_dir = user_input[3:].strip()
|
||||
new_path = Path(new_dir).resolve()
|
||||
if new_path.exists() and new_path.is_dir():
|
||||
loop.set_working_dir(str(new_path))
|
||||
else:
|
||||
console.print(f"[error]Directory not found:[/] {new_dir}")
|
||||
continue
|
||||
|
||||
# Process with agent
|
||||
theme = get_theme()
|
||||
with console.status("[info]Thinking...[/]", spinner=theme.spinner):
|
||||
response = await loop.run_turn(user_input)
|
||||
|
||||
# Display response
|
||||
console.print()
|
||||
loop.display_response(response)
|
||||
console.print()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[dim]Use 'exit' to quit or press Ctrl+C again.[/]")
|
||||
try:
|
||||
# Wait briefly for second Ctrl+C
|
||||
await asyncio.sleep(0.5)
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[dim]Goodbye![/]")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[error]Error:[/] {e}")
|
||||
if verbose:
|
||||
console.print_exception()
|
||||
Reference in New Issue
Block a user