from __future__ import annotations import argparse import sys from pathlib import Path from typing import Any from rich.console import Console from rich.markdown import Markdown from rich.panel import Panel from .agent import Agent from .config import Config, load_config from .gpfs_client import GPFSClient from .tools import ToolDispatcher console = Console() def _banner(cfg: Config) -> None: write_state = ( "writes + DELETE" if cfg.allow_writes and cfg.allow_destructive else "writes (no DELETE)" if cfg.allow_writes else "read-only" ) body = ( f"[bold]GPFS Agent[/bold] — {cfg.model}\n" f"API base: {cfg.gpfs_base}\n" f"Mode: {write_state}" f"{' · confirm-on-mutate' if cfg.confirm_mutations else ''}\n" "Type your question. Commands: /reset, /system, /quit" ) console.print(Panel.fit(body, border_style="cyan")) def _on_tool_event(name: str, tool_input: dict[str, Any], result: dict[str, Any]) -> None: method = tool_input.get("method", "?") path = tool_input.get("path", "?") status = result.get("status", "ERR") ok = result.get("ok", False) colour = "green" if ok else "red" console.print( f" [dim]→ {name}[/dim] [{colour}]{method} {path} → {status}[/{colour}]" ) def make_confirm(console: Console): def _confirm(message: str, call: dict[str, Any]) -> bool: console.print(Panel.fit( f"[yellow]{message}[/yellow]\n" f"[bold]{call['method']}[/bold] {call['path']}\n" + (f"body: {call.get('body')}\n" if call.get("body") else "") + (f"reason: {call.get('reason')}" if call.get("reason") else ""), border_style="yellow", title="confirm mutation", )) answer = console.input("[yellow]proceed?[/yellow] [y/N] ").strip().lower() return answer in {"y", "yes"} return _confirm def run() -> int: parser = argparse.ArgumentParser(prog="gpfs-agent") parser.add_argument("--env", type=Path, default=None, help="Path to a .env file") args = parser.parse_args() try: cfg = load_config(args.env) except RuntimeError as exc: console.print(f"[red]config error:[/red] {exc}") return 2 _banner(cfg) with GPFSClient(cfg) as client: dispatcher = ToolDispatcher(cfg, client, confirm=make_confirm(console)) agent = Agent(cfg, dispatcher, on_tool_event=_on_tool_event) while True: try: user = console.input("\n[bold cyan]you[/bold cyan] » ").strip() except (EOFError, KeyboardInterrupt): console.print("\nbye.") return 0 if not user: continue if user in {"/quit", "/exit"}: return 0 if user == "/reset": agent.reset() console.print("[dim]conversation reset[/dim]") continue if user == "/system": console.print(Panel(agent.system_prompt, title="system prompt", border_style="magenta")) continue try: reply = agent.chat(user) except Exception as exc: console.print(f"[red]agent error:[/red] {exc!r}") continue console.print() console.print(Markdown(reply or "_(no text returned)_")) if __name__ == "__main__": sys.exit(run())