diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9520436 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,129 @@ +# Pyra — Developer Guide + +## What Is This + +Pyra is a personal AI assistant CLI combining a multi-provider AI chat interface with +an automation/skills system (Stage 2+) and an encrypted vault (Stage 3+). + +## Project Roadmap + +### Stage 1 — Core CLI (current) +Working `pyra` executable with provider setup wizard, streaming chat REPL, .md-based +memory in `~/.pyra/memory/`, and hard security boundaries around the vault. + +### Stage 2 — Skills / Automations +Shell (.sh), PowerShell (.ps1), and Python (.py) scripts in `~/.pyra/skills/`. The AI +can suggest running a skill, but execution requires explicit user approval (y/n prompt). +No skill can access the vault. Skills are discovered by the pyra CLI, not by the AI. + +### Stage 3 — Vault Encryption +Encrypt `~/.pyra/vault/secrets/` using `age` (or GPG fallback). Pyra decrypts in memory +at call time only — no plaintext ever written to disk after initial setup. Secret +rotation support. Per-key passphrases optional. + +### Stage 4 — Security Audit Sub-agent +A separate `pyra security audit` command that spins up a sandboxed AI agent whose sole +job is scanning for vulnerabilities: prompt injection in memory files, unexpected vault +access attempts in `security.log`, outdated dependency CVEs, permission drift on `~/.pyra/`. +Report written to `~/.pyra/security_audit.md` (not AI-readable during normal chat). + +### Stage 5 — Web UI / Advanced Features +Optional local web interface (FastAPI + HTMX or similar). Embedding-based memory search +(ChromaDB or sqlite-vec). Scheduled automations via cron-style skill scheduling. +Multi-profile support (work vs personal). + +--- + +## Architecture + +### Source: `src/pyra/` + +| Module | Purpose | +|--------|---------| +| `cli.py` | Click entrypoint. Subcommands: `setup`, `chat`, `memory` | +| `setup/providers.py` | Provider registry — pure data, no I/O | +| `setup/wizard.py` | questionary-based interactive setup wizard | +| `config/schema.py` | Pydantic v2 models — no API keys, only `provider_id/model/base_url` | +| `config/manager.py` | ruamel.yaml round-trip config read/write, chmod 600 enforced | +| `config/dirs.py` | `bootstrap()` — creates `~/.pyra/` tree, checks vault sentinel every startup | +| `chat/session.py` | prompt_toolkit REPL loop, slash commands, calls vault reader inline | +| `chat/renderer.py` | Live streaming markdown via rich, injection warning panel, key redaction | +| `chat/history.py` | Conversation list, token budget trimming, system prompt construction | +| `memory/reader.py` | `list_memories()`, `read_memory()`, `load_context_for_session()` | +| `memory/writer.py` | `write_memory()`, `append_memory()` — relative names only, no traversal | +| `memory/index.py` | Auto-regenerate `MEMORY_INDEX.md` on every write | +| `vault/reader.py` | `get_key(provider_id)` — sole accessor of `vault/secrets/api_keys.json` | +| `vault/writer.py` | `set_key()`, `delete_key()` — only called from setup wizard | +| `security/boundaries.py` | `assert_safe_path()`, `check_vault_lock()`, `BLOCKED_PREFIXES` | +| `security/injection.py` | `scan_response()` — 15 regex patterns, 4 categories, logs to `security.log` | +| `utils/paths.py` | `pyra_home()`, `ensure_dir()`, `safe_chmod()`, `expand()` | + +### Runtime: `~/.pyra/` + +``` +~/.pyra/ +├── config.yaml chmod 600 ← provider_id, model, base_url ONLY +├── security.log chmod 600 ← injection event log +├── memory/ chmod 700 +│ ├── user/profile.md +│ ├── context/ +│ ├── knowledge/ +│ └── MEMORY_INDEX.md +├── skills/ chmod 700 ← Stage 2 +│ ├── bash/ +│ ├── powershell/ +│ └── python/ +└── vault/ chmod 700 ← AI CANNOT ACCESS + ├── .vault_lock chmod 400 ← sentinel; missing = refuse to start + └── secrets/ + └── api_keys.json chmod 400 ← ALL API keys +``` + +--- + +## Security Rules (never break these) + +1. **Never pass config file contents into a system prompt** — config may reveal provider/model +2. **Never bypass `assert_safe_path()`** — not even in tests (use `tmp_pyra_home` fixture instead) +3. **Always `chmod 600/400`** after writing any file in `~/.pyra/` +4. **No shell execution from AI-generated text** — ever (Stage 2 uses explicit approval gates) +5. **`vault/reader.py` and `vault/writer.py` are the only modules that import from `pyra.vault`** +6. **API key retrieved inline at call time** — never stored as an instance variable or logged + +## Adding a New Provider + +Edit `src/pyra/setup/providers.py`. Add a new `Provider` dataclass entry with all required fields. +litellm handles dispatch automatically via the `litellm_prefix` field. +Add a test in `tests/unit/test_providers.py` to verify the new entry. + +## Installing for Development + +```bash +uv venv && source .venv/bin/activate +uv pip install -e ".[dev]" +pyra setup +``` + +Or with pip: +```bash +python -m venv .venv && source .venv/bin/activate +pip install -e ".[dev]" +pyra setup +``` + +## Running Tests + +```bash +pytest tests/ -v # all unit + security tests +pytest tests/integration/test_lmstudio.py # requires LM Studio at localhost:1234 +``` + +## Commit Convention + +``` +feat(module): short description +fix(module): short description +test: description +docs: description +chore: description +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddda8b0 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Pyra + +A personal AI assistant CLI with vault-first security. Combines multi-provider AI chat with +long-term memory and (coming) automation skills. + +## Quick Start + +```bash +pip install -e . # or: pipx install . +pyra setup # choose your AI provider +pyra chat # start talking +``` + +## Providers + +**Local (no API key needed):** +- LM Studio — `http://localhost:1234` +- Ollama — `http://localhost:11434` +- llama.cpp server — `http://localhost:8080` + +**Cloud:** +- Anthropic (Claude), OpenAI (GPT), Google (Gemini), DeepSeek, Qwen + +## Commands + +| Command | Description | +|---------|-------------| +| `pyra setup` | Run the provider setup wizard | +| `pyra chat` | Start interactive chat | +| `pyra memory list` | List memory files | +| `pyra memory read ` | Read a memory file | +| `pyra memory write ` | Write a memory file | +| `pyra memory append ` | Append to a memory file | + +### In-chat slash commands + +| Command | Description | +|---------|-------------| +| `/help` | Show available commands | +| `/memory list` | List memory files | +| `/clear` | Clear conversation history | +| `/quit` or `/exit` | Exit Pyra | + +## Security + +- **API keys live in `~/.pyra/vault/`** — the AI cannot read this directory +- **`config.yaml` never contains credentials** — only provider ID, model name, and base URL +- **Prompt injection scanner** — warns on suspicious AI output, logs to `~/.pyra/security.log` +- **Path sandboxing** — the AI can only reference memory files by name; traversal is blocked + +## Memory + +Pyra reads your memory files at the start of each session and injects them as context. +Files are plain Markdown stored in `~/.pyra/memory/`: + +``` +~/.pyra/memory/ +├── user/profile.md ← who you are +├── context/ ← ongoing projects +└── knowledge/ ← general notes +``` + +## `~/.pyra/` Directory + +``` +~/.pyra/ +├── config.yaml ← provider + model (no secrets) +├── security.log ← injection event log +├── memory/ ← AI-readable long-term memory +├── skills/ ← automation scripts (Stage 2) +└── vault/ ← secure, AI-inaccessible storage + └── secrets/api_keys.json +``` + +## Roadmap + +- **Stage 1** (now): Core CLI, multi-provider chat, memory, vault security +- **Stage 2**: Skills — shell/PowerShell/Python automations with user approval gates +- **Stage 3**: Vault encryption with `age` +- **Stage 4**: Security audit sub-agent +- **Stage 5**: Web UI, embedding-based memory search