5.5 KiB
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
# 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 <package>
# Add a dev/test dependency
uv add --group test <package>
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/tools/ |
Tool modules (auto-discovered) |
config.yaml |
Runtime configuration |
identity/SOUL.md |
Agent personality |
identity/USER.md |
User profile |
skills/ |
Prompt templates for tools (press releases, etc.) |
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 undercheddahbot/tools/— auto-discovered on startup - Tool context: Tools can accept
ctx: dict = Noneto getconfig,db,agent,memoryinjected - Database: SQLite with WAL mode, thread-local connections via
threading.local() - KV store: Task state stored as JSON at
clickup:task:{id}:statekeys - ClickUp field mapping:
Work Categoryfield (notTask Type) identifies task types like "Press Release", "Link Building". TheClientfield (notCompany) holds the client name. - Notifications: All scheduler events go through
NotificationBus.push(), never directly to a UI - Tests: Use
respxto mock httpx calls,tmp_dbfixture for isolated SQLite instances
ClickUp Skill Mapping
The scheduler maps ClickUp Work Category → tool name via config.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
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_scanand notifications table methodstest_notifications.py— NotificationBus pub/sub behaviortest_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 creationtest_press_advantage.py— Press Advantage API client, company parsing, link building, submit tool
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— useuv add(pyproject.toml) - Don't call tools directly from UI code — go through
NotificationBusfor scheduler events - Don't store ClickUp state outside of
kv_store— it's the single source of truth