CheddahBot/CLAUDE.md

5.8 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 under cheddahbot/tools/ — auto-discovered on startup
  • Tool context: Tools can accept ctx: dict = None to get config, db, agent, memory injected
  • 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:

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.pykv_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