feat(cli): wire all subcommands

pyra (default→chat), pyra setup, pyra chat, pyra memory list/read/write/append
All routes call bootstrap() first; PyraSecurityError exits with clear message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-17 12:52:26 +02:00
parent 0fe6332316
commit e792c5e0c9
+100
View File
@@ -0,0 +1,100 @@
import sys
import click
from rich.console import Console
from pyra.config.dirs import bootstrap
from pyra.security.boundaries import PyraSecurityError
console = Console()
def _bootstrap_or_exit() -> None:
try:
bootstrap()
except PyraSecurityError as exc:
console.print(f"[bold red]Security error:[/bold red] {exc}")
sys.exit(1)
@click.group(invoke_without_command=True)
@click.pass_context
def main(ctx: click.Context) -> None:
"""Pyra — personal AI assistant."""
_bootstrap_or_exit()
if ctx.invoked_subcommand is None:
# Default to chat when no subcommand given
from pyra.chat.session import start_chat
start_chat()
@main.command()
def setup() -> None:
"""Run the interactive provider setup wizard."""
_bootstrap_or_exit()
from pyra.setup.wizard import run_setup
run_setup()
@main.command()
def chat() -> None:
"""Start an interactive chat session."""
_bootstrap_or_exit()
from pyra.chat.session import start_chat
start_chat()
@main.group()
def memory() -> None:
"""Manage Pyra's long-term memory files."""
_bootstrap_or_exit()
@memory.command("list")
def memory_list() -> None:
"""List all memory files."""
from pyra.memory.reader import list_memories
memories = list_memories()
if not memories:
console.print("[dim]No memory files found.[/dim]")
return
console.print(f"{'File':<45} {'Category':<14} {'Modified'}")
console.print("" * 80)
for m in memories:
mtime = m.modified.strftime("%Y-%m-%d %H:%M")
console.print(f"{m.name:<45} {m.category:<14} {mtime}")
@memory.command("read")
@click.argument("name")
def memory_read(name: str) -> None:
"""Read a memory file by name."""
from pyra.memory.reader import read_memory
from pyra.security.boundaries import VaultAccessError
try:
content = read_memory(name)
console.print(content)
except VaultAccessError as exc:
console.print(f"[bold red]Blocked:[/bold red] {exc}")
except (FileNotFoundError, PermissionError) as exc:
console.print(f"[red]Error:[/red] {exc}")
@memory.command("write")
@click.argument("name")
@click.argument("content")
def memory_write(name: str, content: str) -> None:
"""Write content to a memory file."""
from pyra.memory.writer import write_memory
path = write_memory(name, content)
console.print(f"[green]Written:[/green] {path}")
@memory.command("append")
@click.argument("name")
@click.argument("content")
def memory_append(name: str, content: str) -> None:
"""Append content to a memory file."""
from pyra.memory.writer import append_memory
path = append_memory(name, content)
console.print(f"[green]Appended to:[/green] {path}")