# Deployment & Configuration Guide

qc-trace consists of three components: a **PostgreSQL database**, an **ingest server**, and a **daemon** that watches local AI tool session files and pushes them to the server.

---

## Quick Start (Local Development)

```bash
# 1. Start PostgreSQL
scripts/dev-db.sh start

# 2. Start the ingest server
uv run python -m qc_trace.server.app

# 3. Start the daemon
uv run quickcall start

# 4. Start the dashboard
cd dashboard && npm run dev
```

---

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `QC_TRACE_DSN` | `postgresql://qc_trace:qc_trace_dev@localhost:5432/qc_trace` | PostgreSQL connection string |
| `QC_TRACE_PORT` | `19777` | Ingest server listen port |
| `QC_TRACE_API_KEYS` | _(empty — auth disabled)_ | Comma-separated API keys. When set, all endpoints (except `/health`) require `X-API-Key` header |
| `QC_TRACE_CORS_ORIGIN` | `http://localhost:3000` | Allowed CORS origin for dashboard |
| `QC_TRACE_INGEST_URL` | `http://localhost:19777/ingest` | Daemon's target server URL |

---

## Authentication

Auth is **opt-in**. When no keys are configured, all endpoints are open.

### Enabling auth

Set API keys via either method:

**Option A: Environment variable**
```bash
export QC_TRACE_API_KEYS="my-secret-key-1,another-key"
```

**Option B: Config file** (`~/.quickcall-trace/config.json`)
```json
{
  "api_key": "my-secret-key"
}
```

### How it works

- All `POST` and `GET /api/*` endpoints check the `X-API-Key` request header
- `GET /health` is always unauthenticated
- If `QC_TRACE_API_KEYS` is empty **and** no key in config file → auth bypassed
- Server logs will say `NO AUTH` or `auth enabled, N key(s)` on startup

### Daemon auth

The daemon currently does **not** send API keys. If you enable auth on the server, you'll need to add the key to the daemon's HTTP requests or use a reverse proxy to inject it.

---

## Database

### Docker (recommended for dev)

```bash
scripts/dev-db.sh start    # Start PostgreSQL 16 on localhost:5432
scripts/dev-db.sh status   # Check container status
scripts/dev-db.sh reset    # Destroy and recreate (loses all data)
scripts/dev-db.sh stop     # Stop container
```

Default credentials (from `docker-compose.yml`):
- **Database:** `qc_trace`
- **User:** `qc_trace`
- **Password:** `qc_trace_dev`
- **Port:** `5432`

### Production database

Point `QC_TRACE_DSN` at your PostgreSQL instance:

```bash
export QC_TRACE_DSN="postgresql://user:password@db-host:5432/qc_trace"
```

Schema is auto-applied on server startup. Migrations run automatically (current version: **v4**).

### Connection pool

| Setting | Default |
|---------|---------|
| Min connections | 2 |
| Max connections | 10 |

---

## Ingest Server

```bash
# Defaults to localhost:19777
uv run python -m qc_trace.server.app

# Custom port
QC_TRACE_PORT=8080 uv run python -m qc_trace.server.app
```

### Server limits

| Setting | Value |
|---------|-------|
| Max request body | 10 MB |
| Batch flush size | 100 messages |
| Batch flush interval | 5 seconds |
| Max in-memory buffer | 10,000 messages |
| Max flush retries | 3 |

### API Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/health` | No | Health check + DB connectivity |
| POST | `/ingest` | Yes | Ingest normalized messages (JSON array) |
| POST | `/sessions` | Yes | Upsert a session record |
| POST | `/api/file-progress` | Yes | Report daemon file read position |
| GET | `/api/stats` | Yes | Aggregate dashboard stats |
| GET | `/api/sessions` | Yes | List sessions (supports `?source=`, `?id=`, `?limit=`, `?offset=`) |
| GET | `/api/messages` | Yes | Messages for a session (`?session_id=` required) |
| GET | `/api/sync` | Yes | File sync state for daemon reconciliation |
| GET | `/api/feed` | Yes | Latest messages across all sessions (`?since=`, `?limit=`) |

---

## Daemon (`quickcall`)

The daemon watches local AI tool session files, transforms them into normalized messages, and pushes to the ingest server.

### CLI commands

```bash
quickcall start     # Start daemon (background)
quickcall stop      # Stop daemon
quickcall status    # Check if running + server reachable
quickcall logs      # View recent logs
quickcall logs -f   # Follow logs
quickcall db init   # Initialize DB schema via server
```

### Daemon configuration

All settings are in `qc_trace/daemon/config.py` with sensible defaults:

| Setting | Default | Description |
|---------|---------|-------------|
| `poll_interval` | 5.0s | How often to scan for file changes |
| `base_dir` | `~/.quickcall-trace` | State, PID, and log directory |
| `ingest_url` | `http://localhost:19777/ingest` | Target server (override with `QC_TRACE_INGEST_URL`) |
| `max_file_size` | 100 MB | Skip files larger than this |
| `max_retries_per_file` | 3 | Retries before skipping a file |
| `batch_size` | 100 | Messages per HTTP push |
| `retry_backoff_base` | 1.0s | Initial backoff on push failure |
| `retry_backoff_max` | 60.0s | Max backoff delay |
| `retry_queue_max` | 10,000 | Max queued messages on failure |
| `retry_timeout` | 300s (5 min) | Log escalation threshold |

### Watched file patterns

| Source | Glob (relative to `$HOME`) |
|--------|---------------------------|
| Claude Code | `.claude/projects/**/*.jsonl` |
| Codex CLI | `.codex/sessions/*/*/*/rollout-*.jsonl` |
| Gemini CLI | `.gemini/tmp/*/chats/session-*.json` |
| Cursor | `.cursor/projects/*/agent-transcripts/*.txt` |

### Daemon files

| File | Path | Purpose |
|------|------|---------|
| State | `~/.quickcall-trace/state.json` | Tracks processing progress per file |
| PID | `~/.quickcall-trace/quickcall.pid` | Running daemon PID |
| Log | `~/.quickcall-trace/quickcall.log` | stdout (macOS launchd) |
| Errors | `~/.quickcall-trace/quickcall.err` | stderr (macOS launchd) |
| Config | `~/.quickcall-trace/config.json` | Optional API key config |

### Reconciliation

On startup, the daemon compares its local state against the server's `/api/sync` endpoint:
- **JSONL sources:** Uses `last_line_read` from `file_progress` table (falls back to `max(raw_line_number)` from messages)
- **Hash-based sources:** Checks if server has any messages for the file
- Files where the server is behind get rewound to the server's position

---

## Installation as System Service

### macOS (launchd)

```bash
scripts/install.sh    # Installs plist to ~/Library/LaunchAgents/
scripts/uninstall.sh  # Removes service and stops daemon
```

The plist is at `~/Library/LaunchAgents/com.quickcall.traced.plist`. It starts on login and auto-restarts.

### Linux (systemd)

```bash
# Copy and configure service file
sudo cp scripts/quickcall.service /etc/systemd/system/quickcall@.service
sudo systemctl enable quickcall@$USER
sudo systemctl start quickcall@$USER
```

---

## Dashboard

```bash
cd dashboard
npm install
npm run dev    # Vite dev server (default: http://localhost:5173)
```

The dashboard reads from the ingest server's API endpoints. Configure the API base URL in `dashboard/src/api.ts` if the server isn't on the default port.

### CORS

The server's `QC_TRACE_CORS_ORIGIN` must match the dashboard's origin. For local dev with Vite on port 5173:

```bash
export QC_TRACE_CORS_ORIGIN="http://localhost:5173"
```

---

## Production Checklist

- [ ] Set `QC_TRACE_DSN` to a production PostgreSQL instance
- [ ] Set `QC_TRACE_API_KEYS` to enable authentication
- [ ] Set `QC_TRACE_CORS_ORIGIN` to your dashboard's domain
- [ ] Set `QC_TRACE_PORT` if needed (default 19777)
- [ ] Set `QC_TRACE_INGEST_URL` on daemon machines to point at the server
- [ ] Install daemon as system service (launchd/systemd)
- [ ] Change default DB credentials from `qc_trace_dev`
- [ ] Build dashboard for production: `cd dashboard && npm run build`
