# CLAUDE.md ## Project Overview CheddahBot is a personal AI assistant for Bryan's SEO/AI agency and general life. It has a two-brain architecture: a **chat brain** (OpenRouter/Ollama/LM Studio) for conversational UI, and an **execution brain** (Claude Code CLI) for autonomous tasks like press releases, file operations, and shell commands among others. The bot polls ClickUp for tasks, maps them to skills, and auto-executes or asks permission — then reports results back to ClickUp and the chat UI. ## Coding Workflow Rules - while we are working on building/changing the code - Before making any changes, explain WHAT you plan to change and WHY - Wait for my approval before editing files UNLESS I TELL YOU I AM GOING AWAY FOR A BIT - then you can use your judgement but commit the code at every big change. - After making changes, provide a brief summary of every file modified and what changed - If you encounter a problem during implementation, STOP and explain it instead of trying to fix it silently - Never refactor, rename, or reorganize code beyond what was explicitly asked for ## Architecture ``` Gradio UI (ui.py) ↓ Agent (agent.py) ← Memory (memory.py, 4-layer: identity/long-term/daily/semantic) ↓ LLM Adapter (llm.py) ├── Chat brain: OpenRouter / Ollama / LM Studio └── Execution brain: Claude Code CLI (subprocess) ↓ Tool Registry (tools/__init__.py) ← auto-discovers tools in tools/ ↓ Scheduler (scheduler.py) ├── Poll loop: cron-based scheduled tasks ├── Heartbeat: periodic checklist from HEARTBEAT.md └── ClickUp loop: polls ClickUp → maps to skills → executes ↓ NotificationBus (notifications.py) → Gradio / future Discord / Slack ``` ## Commands ```bash # Run the app uv run python -m cheddahbot # Run tests (118 tests, ~3s) uv run pytest # Run tests verbose uv run pytest -v --no-cov # Run only integration tests (requires live ClickUp API token) uv run pytest -m integration # Lint uv run ruff check . # Format uv run ruff format . # Add a dependency uv add # Add a dev/test dependency uv add --group test ``` ## Key Files | File | Purpose | |------|---------| | `cheddahbot/__main__.py` | Entry point, wires all components | | `cheddahbot/agent.py` | Core agentic loop (chat + tool execution) | | `cheddahbot/llm.py` | Two-brain LLM adapter | | `cheddahbot/config.py` | Dataclass config (env → YAML → defaults) | | `cheddahbot/db.py` | SQLite persistence (WAL, thread-safe) | | `cheddahbot/scheduler.py` | Three daemon threads: poll, heartbeat, ClickUp | | `cheddahbot/clickup.py` | ClickUp REST API v2 client (httpx) | | `cheddahbot/notifications.py` | UI-agnostic pub/sub notification bus | | `cheddahbot/memory.py` | 4-layer memory with semantic search | | `cheddahbot/router.py` | System prompt builder | | `cheddahbot/ui.py` | Gradio web interface | | `cheddahbot/skills.py` | Markdown skill registry (discovers skills/*.md) | | `cheddahbot/tools/` | Tool modules (auto-discovered) | | `config.yaml` | Runtime configuration | | `identity/SOUL.md` | Agent personality | | `identity/USER.md` | User profile | | `skills/` | Markdown skill files with YAML frontmatter | ## Conventions - **Config precedence**: env vars > config.yaml > dataclass defaults - **ClickUp env vars**: `CLICKUP_API_TOKEN`, `CLICKUP_WORKSPACE_ID`, `CLICKUP_SPACE_ID` - **Tool registration**: Use the `@tool("name", "description", category="cat")` decorator in any file under `cheddahbot/tools/` — auto-discovered on startup - **Tool context**: Tools can accept `ctx: dict | None = None` to get `config`, `db`, `agent`, `memory` injected - **Skills**: `.md` files in `skills/` with YAML frontmatter (`name`, `description`, `tools`, `agents`). Files without frontmatter are data files (skipped by registry) - **Database**: SQLite with WAL mode, thread-local connections via `threading.local()` - **KV store**: Task state stored as JSON at `clickup:task:{id}:state` keys - **ClickUp field mapping**: `Work Category` field (not `Task Type`) identifies task types like "Press Release", "Link Building". The `Client` field (not `Company`) holds the client name. - **Notifications**: All scheduler events go through `NotificationBus.push()`, never directly to a UI - **Tests**: Use `respx` to mock httpx calls, `tmp_db` fixture for isolated SQLite instances - **ClickUp attachments**: `ClickUpClient.upload_attachment()` uses module-level `httpx.post()` (not the shared client) for multipart uploads ## ClickUp Skill Mapping The scheduler maps ClickUp `Work Category` → tool name via `config.yaml`: ```yaml skill_map: "Press Release": tool: "write_press_releases" auto_execute: true field_mapping: topic: "task_name" # uses ClickUp task name company_name: "Client" # looks up "Client" custom field ``` Task lifecycle: `to do` → discovered → approved/awaiting_approval → executing → completed/failed (+ attachments uploaded) ## Testing Tests live in `tests/` and use pytest. All tests run offline with mocked APIs. - `test_clickup.py` — API response parsing + HTTP client (respx mocks) - `test_db.py` — `kv_scan` and notifications table methods - `test_notifications.py` — NotificationBus pub/sub behavior - `test_clickup_tools.py` — Chat tool state machine (approve/decline) - `test_email.py` — EmailClient SMTP send + attachments (mocked) - `test_docx_export.py` — Plain text → .docx formatting and file creation - `test_press_advantage.py` — Press Advantage API client, company parsing, link building, submit tool - `test_scheduler_helpers.py` — `_extract_docx_paths` regex extraction from tool output Fixtures in `conftest.py`: `tmp_db` (fresh SQLite), `sample_clickup_task_data` (realistic API response). ## Don't - Don't edit `.env` — it contains secrets - Don't manually activate venvs — use `uv run` - Don't add to `requirements.txt` — use `uv add` (pyproject.toml) - Don't call tools directly from UI code — go through `NotificationBus` for scheduler events - Don't store ClickUp state outside of `kv_store` — it's the single source of truth