# 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 ```