# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What is BIBIVIDI

Token optimization middleware for AI APIs. It sits as a local HTTP proxy between an app and an AI API (Anthropic, OpenAI, etc.), intercepts requests, and when a matching BSS skill script is found, executes it locally to produce a condensed context — reducing token usage by 60–95%.

```
App → BIBIVIDI Proxy :8080 → Matching engine
                                  ↓
             Script found → Local Python execution → condensed context → AI API
             No script    → Pass-through + auto-generate skill (background) → AI API
```

For HTTPS traffic, a separate CONNECT tunnel runs on port 8443 (see SSL section).

## Setup

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
```

## Commands

```bash
# Start the proxy
bibividi start            # HTTP only (port 8080)
bibividi start --ssl      # HTTP (8080) + HTTPS CONNECT tunnel (8443)

# Skill management
bibividi add scripts/pdf-clause-extractor/   # install a BSS pack
bibividi list                                 # list installed scripts
bibividi status                               # token savings stats

# Certificate management (for --ssl mode)
bibividi cert generate    # generate CA + server cert in ~/.bibividi/
bibividi cert install     # install CA in OS trust store (may need sudo)
bibividi cert path        # show cert file paths and existence

# Tests
pytest                          # all tests
pytest tests/test_proxy.py -v   # single file

# Lint & type-check
ruff check .
mypy bvd/
```

**HTTP mode — Host header substitution (the standard approach):**
```bash
curl http://127.0.0.1:8080/v1/messages \
  -H "Host: api.anthropic.com" \
  -H "x-api-key: sk-ant-..." \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-haiku-4-5-20251001","max_tokens":100,"messages":[{"role":"user","content":"..."}]}'
```

**HTTPS mode — CONNECT tunnel (`bibividi start --ssl`):**
```bash
curl -x http://127.0.0.1:8443 https://api.anthropic.com/v1/messages \
  -H "x-api-key: sk-ant-..." \
  --cacert ~/.bibividi/ca.crt \
  ...
```

> `curl -x` on port 8443 sends a CONNECT request which the tunnel handles. Do NOT use `-x` on port 8080 (FastAPI doesn't handle CONNECT).

## Architecture

### Package: `bvd/`

The Python package is `bvd` (not `bibividi` — collision with CLI command name).

| Module | Role |
|--------|------|
| `bvd/config.py` | Pydantic Settings; all config via `BIBIVIDI_` env vars; `~/.bibividi/` for data |
| `bvd/cli.py` | CLI entry point (`bibividi` command) — click + rich |
| `bvd/core/proxy.py` | FastAPI app; lifespan handler; catch-all route runs match pipeline |
| `bvd/core/certs.py` | CA + per-domain cert generation (`cryptography` lib); OS trust store install |
| `bvd/core/tunnel.py` | asyncio TCP server; CONNECT handler; TLS MITM via `writer.start_tls()` |
| `bvd/engine/library.py` | SQLite + aiosqlite; BSS v1 schema; stores scripts + embeddings |
| `bvd/engine/matcher.py` | Loads `all-MiniLM-L6-v2` (lazy); cosine similarity against in-memory index |
| `bvd/engine/runner.py` | Sandboxed subprocess executor; injects `PROMPT` via stdin; reads stdout |
| `bvd/engine/stats.py` | Records token savings per execution in SQLite |
| `bvd/engine/generator.py` | On no-match: calls LLM to generate a new BSS skill; saves + invalidates index |

### Request flow — HTTP proxy (`proxy.py`)

1. Extract `Host` header → check against `BIBIVIDI_TARGET_APIS`
2. Parse JSON body → extract last user message (`_extract_prompt`)
3. `matcher.match(prompt)` → cosine similarity ≥ 0.85 → returns `MatchResult`
4. Match found → `run_script(script, prompt)` → sandboxed subprocess → condensed output → `_inject_result`
5. No match → `asyncio.create_task(generator.maybe_generate(prompt))` (fire-and-forget)
6. Forward with httpx; record stats

### Request flow — HTTPS CONNECT tunnel (`tunnel.py`)

1. Client sends `CONNECT api.anthropic.com:443`
2. Tunnel responds `200 Connection Established`
3. AI API host → `writer.start_tls(ssl_ctx)` with a per-domain cert signed by local CA
4. Inner HTTP request extracted → forwarded to `http://127.0.0.1:8080` with `Host` header → normal proxy pipeline
5. Non-AI host → transparent TCP pipe to real host

### Generator (`generator.py`)

When no script matches, calls the configured LLM (default: `claude-haiku-4-5-20251001`) with a system prompt requesting a BSS skill JSON. The response is parsed, embedded, and saved. The next similar request will match it. Requires `BIBIVIDI_GENERATOR_API_KEY`.

### Matcher index

The `Matcher` singleton keeps script embeddings in memory as `list[tuple[script_id, np.ndarray, Script]]`. Rebuilt lazily from SQLite on first match or after `invalidate()` is called (e.g. after generator saves a new script).

### BSS Skill format

A BSS pack is a directory with:
- `skill.json` — metadata: `id`, `name`, `description`, `tags`, `version`
- `skill.py` — Python script; reads `PROMPT` (injected by sandbox), writes condensed output to stdout

**Sandbox allowed imports only:**
```
json, re, math, statistics, datetime, collections, itertools, functools,
pathlib, string, textwrap, csv, io, base64, hashlib, uuid
```
No OS, network, or subprocess access inside scripts.

Demo packs in `scripts/`: `pdf-clause-extractor`, `csv-data-summarizer`, `batch-translation-condenser`, `structured-report-extractor`, `code-review-condenser`.

## Code conventions

- **Python 3.11+**, type hints everywhere, `async`/`await` for all I/O
- **ruff** with `line-length = 100`, rules `E, F, I, N, W, UP`; `scripts/` excluded (BSS files are standalone)
- **mypy** non-strict, `ignore_missing_imports = true`, `explicit_package_bases = true`
- **pytest-asyncio** in auto mode (`asyncio_mode = "auto"`)
- All `BIBIVIDI_` env vars defined in `bvd/config.py` via pydantic-settings
- FastAPI uses `lifespan=` context manager (not deprecated `@app.on_event`)
- `Library.db` property asserts `_db is not None` — always call `library.init()` before use

## Known issues / history

- Root `__init__.py` exists (artifact) — `explicit_package_bases = true` in mypy prevents double-module detection
- `db_path: Path | None` in Settings — pydantic v2 validates eagerly; `resolve_paths` validator sets it post-init
- `embeddings.position_ids` warning from sentence-transformers on load is harmless
- `writer.start_tls()` requires Python 3.11+ (upgrades both read and write sides of asyncio stream)
