# CTXD — Context Dossier
# LLM Installation & Deployment Guide
# Feed this file to an LLM to assist with setup and deployment.

## What is CTXD?

CTXD (Context Dossier) is a single source of truth for multi-harness project context. It serves one canonical AGENTS.md per project to Claude, Hermes, Codex, Cursor, and any OAuth-capable MCP client via Streamable HTTP. It runs as two Docker containers: a PostgreSQL 16 database and a Python ASGI daemon.

## Prerequisites

- Docker 24+ and Docker Compose v2+
- A reverse proxy with TLS (Traefik recommended; Caddy or nginx also work)
- Linux host with ~1GB free RAM
- (Optional) Existing PostgreSQL 14+ instance if not using the bundled one

## Quick Deployment

1. Clone the repository:
   git clone ssh://git@code.cubecraftcreations.com:2288/CubeCraft-Creations/CTXD.git
   cd CTXD/app

2. Copy the environment template:
   cp .env.example .env

3. Generate secrets and fill in .env:
   python3 -c "import secrets; print(secrets.token_urlsafe(32))"  # Use for CTXD_API_KEY
   python3 -c "import secrets; print(secrets.token_urlsafe(32))"  # Use for OAUTH_APPROVAL_KEY

4. Edit .env with your values. Required variables:
   - DATABASE_URL — PostgreSQL connection string
   - POSTGRES_PASSWORD — Password for the bundled PG container
   - CTXD_API_KEY — Shared key for internal MCP and HTTP auth
   - OAUTH_ENABLED=true
   - OAUTH_ISSUER=https://your-domain.com
   - OAUTH_APPROVAL_KEY — Key for approving OAuth authorizations

5. Build and start:
   docker compose up -d

6. Wait for PostgreSQL to be healthy and the daemon to start:
   docker compose ps
   # Both ctxd-postgres and dossier should show "healthy" / "running"

7. Set the admin password:
   docker exec ctxd dossier user-set-password admin -p "your-admin-password"

8. Verify:
   curl http://localhost:9091/status
   # Expected: {"status": "ok", "db": "/data/ctxd.db"}

9. Access the Web UI:
   http://<server-ip>:9091/
   Sign in with admin / your-admin-password

## Environment Variables Reference

All config is driven by environment variables in .env. Precedence: env var > ctxd.yaml > defaults.

### Database
- DATABASE_URL — PostgreSQL connection string. If empty, falls back to SQLite.
- POSTGRES_USER — PostgreSQL user for bundled container (default: ctxd)
- POSTGRES_PASSWORD — PostgreSQL password for bundled container
- POSTGRES_DB — PostgreSQL database name (default: ctxd)

### Server
- CTXD_HOST — Bind address (default: 0.0.0.0)
- CTXD_PORT — Listen port (default: 9091)
- CTXD_HOME — Data directory inside container (default: /data)
- LOG_LEVEL — Uvicorn log level: debug, info, warning, error (default: info)

### Auth
- CTXD_AUTH_ENABLED — Enable authentication globally (default: false)
- CTXD_API_KEY — Shared API key for Hermes/internal MCP and HTTP auth
- CTXD_EXTERNAL_READONLY_KEY — Legacy ?key= on read-only MCP (migration only)

### OAuth
- OAUTH_ENABLED — Enable OAuth authorization server (default: false)
- OAUTH_ISSUER — Public URL used in OAuth discovery metadata
- OAUTH_APPROVAL_KEY — Fallback key for /oauth/authorize approval
- OAUTH_APPROVAL_USER_ID — User ID for attributing approvals (default: admin)
- OAUTH_ACCESS_TOKEN_TTL — Access token lifetime in seconds (default: 3600)
- OAUTH_REFRESH_TOKEN_TTL — Refresh token lifetime in seconds (default: 2592000)

### Web Sessions
- WEB_SESSION_TTL — Session cookie lifetime in seconds (default: 604800)

### Snapshots
- SNAPSHOT_MIN_KEEP — Minimum snapshots per project (default: 5)
- SNAPSHOT_MAX_KEEP — Maximum snapshots before rotation (default: 25)

## MCP Endpoints

CTXD exposes three MCP surfaces via Streamable HTTP:

1. /readonly/mcp — Read-only tools (OAuth ctxd.read or API key)
   Tools: list_projects, get_project_context, search_context, get_project_tags, list_files, get_file, get_client_guide

2. /write/mcp — Write tools (OAuth ctxd.write only)
   Tools: update_file, set_project_tags, sync_to_project, get_client_guide

3. /mcp — Internal full MCP (shared API key only, not exposed publicly)
   All read + write tools plus auto_generate_tags and get_user_profile

## Connecting LLM Clients

### Claude Desktop
Connector URL: https://your-domain.com/readonly/mcp
Claude auto-discovers OAuth metadata and registers via DCR.
For write access, use: https://your-domain.com/write/mcp

### Hermes Agent
Edit ~/.hermes/config.yaml:
  mcp_servers:
    dossier:
      url: http://<server-ip>:9091/mcp
      timeout: 30
      headers:
        Authorization: "Bearer your-api-key"
Restart Hermes after changing config.

### Other MCP Clients
1. Register an OAuth client: POST /oauth/register with your redirect URI
2. Authorize: GET /oauth/authorize (admin must approve)
3. Exchange code: POST /oauth/token
4. Connect to /readonly/mcp or /write/mcp with Bearer token

## Traefik Configuration

Route everything except the internal MCP endpoint:

  rule: Host(`ctxd.yourdomain.com`) && !Path(`/mcp`)

This exposes: landing page, dashboard, REST API, OAuth, read-only MCP, write MCP.
Only /mcp (internal, API key) is blocked.

## CLI Commands

All commands run inside the container:
  docker exec ctxd dossier <command>

Commands:
  init                                    Initialize database
  project-create <id> [--display-name N]  Create a project
  project-list                            List projects
  read <project_id>                       Print context to stdout
  edit <project_id>                       Open in $EDITOR
  file-list <project_id>                  List context files
  file-read <project_id> <file_path>      Read a single file
  sync <project_id> [path]               Sync AGENTS.md to project root
  search "query"                          Full-text search
  audit [--limit N]                       Show audit log
  user-list                               List users
  user-create <id> --display-name "Name" [--password "pw"]
  user-set-password <id> -p "password"
  oauth-client-create [-n "Name"] [--redirect-uri URI]
  oauth-client-list                       List OAuth clients
  oauth-client-revoke <client_id>         Revoke client and invalidate tokens
  import-vault <path>                     Import from Obsidian vault

## Admin UI

Sign in as admin, click "admin" in the masthead. Three tabs:
1. oauth clients — Create and revoke OAuth clients
2. users — List, create, edit, activate, inactivate, delete users
3. projects — List and delete projects (typed-name confirmation required)

## Project Files

Each project has multiple context files:
- CONTEXT.MD — Canonical overview (synced as AGENTS.md, cannot be deleted)
- DECISIONS.MD — Architecture decisions and rationale
- RUNBOOKS.MD — Deploy, troubleshoot, operate procedures
- PROMPTS.MD — Project-specific prompts for different harnesses
- GLOSSARY.MD — Project-specific terms and acronyms

The compiled view (get_project_context) concatenates all files with metadata header.

## Locked Files

- CONTEXT.MD — Cannot be deleted from any project (minimum required file)
- CONTEXT.MD and LLM-CLIENT.MD — Fully locked in the ctxd-docs project (cannot update or delete)

## Backups

PostgreSQL backup:
  docker exec ctxd-postgres pg_dump -U ctxd ctxd > backup.sql

PostgreSQL restore:
  cat backup.sql | docker exec -i ctxd-postgres psql -U ctxd ctxd

Snapshots are automatic (before each context update, rotated min 5 / max 25 per project).

## Migrating from SQLite to PostgreSQL

If you started with SQLite:
1. Start PostgreSQL: docker compose up -d postgres
2. Run migration: docker exec ctxd python3 -m ctxd.migrate_sqlite_to_pg
3. Set DATABASE_URL in .env and restart: docker compose up -d dossier

## Using an External PostgreSQL

1. Create database and user on your external PG instance
2. Set DATABASE_URL in .env to point to it
3. Start only the app: docker compose up -d dossier
   (or scale postgres to 0: docker compose up -d --scale postgres=0)

## Troubleshooting

Login fails: Reset password with "docker exec ctxd dossier user-set-password admin -p newpass"
  Use double quotes if password contains single quotes. Use single quotes for $ ` ! characters.

MCP 404: Traefik is not routing /readonly/mcp. Check router rule includes it.
MCP 401: Auth is working. Check OAuth token scope and expiry.

PostgreSQL connection fails: Check DATABASE_URL password matches POSTGRES_PASSWORD.
  If PG data volume was initialized with a different password, reset it:
  docker exec ctxd-postgres psql -U ctxd -c "ALTER USER ctxd PASSWORD 'newpass'"
  Then update .env.

Container keeps restarting: Check "docker logs ctxd --tail 30".
  Common causes: wrong PG password, OAUTH_ENABLED=true but OAUTH_ISSUER empty, missing CTXD_API_KEY.

## File Structure

  CTXD/
  ├── .env                    Production environment (gitignored, contains secrets)
  ├── .env.example            Template with all documented variables
  ├── .gitignore
  ├── README.md               Full documentation
  ├── SKILL.md                LLM client guide (canonical source for LLM-CLIENT.MD)
  ├── data/                   Runtime data (gitignored)
  │   ├── ctxd.yaml           Fallback config (env vars take precedence)
  │   ├── oauth_state.json    OAuth clients, codes, tokens
  │   ├── web_sessions.json   Web UI sessions
  │   ├── snapshots/          Point-in-time context backups
  │   └── pg/                 PostgreSQL data volume
  └── app/                    Application source
      ├── docker-compose.yml
      ├── Dockerfile
      ├── pyproject.toml
      └── src/ctxd/
          ├── config.py        Env-driven config
          ├── db.py            Database layer (PostgreSQL + SQLite)
          ├── schema.sql       PostgreSQL schema
          ├── schema_sqlite.sql SQLite schema fallback
          ├── server.py        ASGI: HTTP + MCP + OAuth
          ├── cli.py           CLI commands
          ├── ui.html          Web UI dashboard
          ├── landing.html     Public landing page
          ├── auth_password.py PBKDF2 password hashing
          └── migrate_sqlite_to_pg.py  Migration script