init commit

This commit is contained in:
2026-03-11 07:21:21 -05:00
commit 5aff2183d6
29 changed files with 2270 additions and 0 deletions

183
app/main.py Normal file
View File

@@ -0,0 +1,183 @@
"""SneakyCode entrypoint — argument parsing, config loading, and interactive REPL."""
import argparse
import asyncio
import sys
from pathlib import Path
import structlog
from app.agent.context import SessionContext
from app.models.config import AppConfig, load_config
from app.services.llm import LLMClient, LLMConnectionError, LLMError
from app.services.streaming import StreamHandler
from app.utils.display import (
print_banner,
print_error,
print_history,
print_info,
print_success,
print_token_usage,
print_user_message,
print_warning,
)
from app.utils.logging import console, get_logger, setup_logging
def parse_args() -> argparse.Namespace:
"""Parse command-line arguments.
Returns:
Parsed arguments namespace.
"""
parser = argparse.ArgumentParser(
prog="sneakycode",
description="SneakyCode — A privacy-first local AI coding agent",
)
parser.add_argument(
"--config",
type=Path,
default=None,
help="Path to config YAML file (default: config/config.yaml)",
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
default=False,
help="Enable verbose (DEBUG) logging",
)
parser.add_argument(
"--log-file",
type=Path,
default=None,
help="Path to log file for persistent logging",
)
return parser.parse_args()
async def _run_repl(
ctx: SessionContext,
config: AppConfig,
logger: structlog.stdlib.BoundLogger,
) -> None:
"""Run the interactive REPL loop with streaming LLM responses.
Args:
ctx: Session context for conversation state.
config: Application configuration.
logger: Structured logger instance.
"""
async with LLMClient(config.llm) as client:
handler = StreamHandler(config.display)
while True:
try:
user_input = console.input("[bold cyan]> [/bold cyan]")
except (KeyboardInterrupt, EOFError):
console.print("\n[dim]Goodbye![/dim]")
break
user_input = user_input.strip()
if not user_input:
continue
# Handle slash commands
if user_input.startswith("/"):
command = user_input.lower()
if command == "/quit":
console.print("[dim]Goodbye![/dim]")
break
elif command == "/history":
print_history(ctx.get_history())
elif command == "/clear":
ctx.clear_history()
print_success("Conversation history cleared.")
else:
print_warning(f"Unknown command: {user_input}")
continue
# Add user message and display it
ctx.add_message("user", user_input)
print_user_message(user_input)
# Stream LLM response
try:
chunk_iter = client.stream_chat(ctx.get_history())
assistant_msg = await handler.process_stream(chunk_iter)
except KeyboardInterrupt:
print_warning("Response interrupted.")
handler.reset()
continue
except LLMConnectionError as e:
print_error(f"Connection error: {e}")
continue
except LLMError as e:
print_error(f"LLM error: {e}")
continue
# Handle empty response
if assistant_msg.content is None and assistant_msg.tool_calls is None:
print_warning("Received empty response from model.")
# Record assistant message in history
ctx.add_message(
"assistant",
assistant_msg.content,
tool_calls=assistant_msg.tool_calls,
)
# Record API token usage if available, fall back to heuristic
if handler.usage:
ctx.token_counter.count_usage(handler.usage)
# Show token usage if configured
if config.display.show_token_usage:
print_token_usage(
ctx.token_counter.cumulative_usage.total_tokens
or ctx.estimated_tokens,
ctx.token_counter.budget,
)
handler.reset()
logger.debug("message_exchanged", message_count=ctx.message_count)
def main() -> None:
"""Main entrypoint: load config, setup logging, launch interactive REPL."""
args = parse_args()
# Setup logging first
setup_logging(
log_file=args.log_file,
verbose=args.verbose,
)
logger = get_logger(__name__)
# Load configuration
try:
config = load_config(config_path=args.config)
except (FileNotFoundError, ValueError) as e:
print_error(f"Configuration error: {e}")
sys.exit(1)
logger.info("config_loaded", model=config.llm.model, endpoint=config.llm.endpoint)
# Print startup info
print_banner()
print_info(f"Model: {config.llm.model}")
print_info(f"Endpoint: {config.llm.endpoint}")
print_info(f"Workspace: {config.agent.workspace_root}")
if args.verbose:
print_info("Verbose mode enabled")
# Create session and start REPL
ctx = SessionContext(config)
logger.info("startup_complete")
print_info("Commands: /quit, /history, /clear")
asyncio.run(_run_repl(ctx, config, logger))
if __name__ == "__main__":
main()