CLAUDE.md: architecture table, security rules, all module descriptions, roadmap Stages 1-5, adding-provider guide, commit convention. README.md: quick start, provider table, command reference, security overview. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.2 KiB
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)
- Never pass config file contents into a system prompt — config may reveal provider/model
- Never bypass
assert_safe_path()— not even in tests (usetmp_pyra_homefixture instead) - Always
chmod 600/400after writing any file in~/.pyra/ - No shell execution from AI-generated text — ever (Stage 2 uses explicit approval gates)
vault/reader.pyandvault/writer.pyare the only modules that import frompyra.vault- 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
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pyra setup
Or with pip:
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pyra setup
Running Tests
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