setup.sh now installs eco → ~/.local/bin/eco (symlink to cli.py). README.md updated with full eco command reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Run all tests
source .venv/bin/activate && pytest tests/
# Run a single test file
pytest tests/test_router.py
# Run a specific test
pytest tests/test_router.py::test_clear_command -v
# Start the bot (via systemd)
systemctl --user start echo-core
# Start manually (foreground)
source .venv/bin/activate && python3 src/main.py
# WhatsApp bridge
systemctl --user start echo-whatsapp-bridge
# CLI diagnostics (eco = symlink to cli.py, installed by setup.sh)
eco status
eco doctor
# Install dependencies
source .venv/bin/activate && pip install -r requirements.txt
Architecture
Message flow: Adapter → router.py → claude_session.py → Claude CLI subprocess → response split → Adapter sends reply.
Three adapters run concurrently in one asyncio event loop via asyncio.gather() in src/main.py:
- Discord (
src/adapters/discord_bot.py): discord.py slash commands, 2000 char split - Telegram (
src/adapters/telegram_bot.py): python-telegram-bot commands + inline keyboards, 4096 char split - WhatsApp (
src/adapters/whatsapp.py): polls Node.js Baileys bridge (bridge/whatsapp/index.js) athttp://127.0.0.1:8098every 2s, 4096 char split
All adapters follow the same pattern: module-level _config, authorization helpers (is_owner, is_admin, is_registered_channel), split_message(), and routing through route_message(channel_id, user_id, text, model).
Claude sessions (src/claude_session.py): Each channel has one persistent session. send_message() auto-starts or resumes via claude --resume <session_id>. System prompt is built by concatenating personality/*.md files. External messages are wrapped in [EXTERNAL CONTENT] injection markers. Sensitive env vars are stripped before subprocess execution.
Session state lives in sessions/active.json — maps channel IDs to {session_id, model, message_count, ...}.
Credentials (src/credential_store.py): All tokens stored in system keyring under service "echo-core". Required: discord_token. Optional: telegram_token. Never pass secrets as CLI arguments — use stdin.
Config (src/config.py): Loads config.json with dot-notation access (config.get("bot.name")). Channel namespaces: channels (Discord), telegram_channels, whatsapp_channels.
Scheduler (src/scheduler.py): APScheduler loading jobs from cron/jobs.json, runs Claude prompts in isolated sessions.
Heartbeat (src/heartbeat.py): Periodic checks (email, calendar, KB, git), quiet hours 23-08.
Memory search (src/memory_search.py): Ollama all-minilm embeddings (384 dim) + SQLite cosine similarity.
Import Convention
All modules use absolute imports from project root via sys.path.insert(0, PROJECT_ROOT). Import as from src.config import ..., from src.adapters.discord_bot import .... No circular imports — adapters → router → claude_session.
Key Files
| Path | Role |
|---|---|
src/main.py |
Entry point — starts all adapters + scheduler + heartbeat |
src/router.py |
Dispatches commands vs Claude messages |
src/claude_session.py |
Claude CLI wrapper with --resume |
src/credential_store.py |
Keyring secrets (service: echo-core) |
cli.py |
CLI tool (status, doctor, logs, secrets, cron, whatsapp) |
config.json |
Runtime config (channels, admins, models, bridges) |
setup.sh |
Interactive 10-step onboarding wizard |
bridge/whatsapp/index.js |
Node.js Baileys + Express bridge on port 8098 |
personality/*.md |
System prompt files concatenated in order |