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>
129 lines
3.6 KiB
Python
129 lines
3.6 KiB
Python
"""
|
|
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()
|