veilrender / AGENTS.md
Peng Ding
docs: rewrite AGENTS.md and README for v0.4.0
2424eab
|
Raw History Blame Contribute Delete
6.41 kB
# AGENTS.md β€” VeilRender
> Context file for AI coding assistants. Symlinked as `CLAUDE.md`.
## What this project is
VeilRender is a **headless browser rendering API** β€” accepts a URL, renders it
with a stealth Chromium browser (CloakBrowser + Patchright), returns
HTML/Markdown/readability content. Designed as a fallback for fetch tools that
fail on JavaScript-rendered pages.
Supports two deployment modes: **single-instance** (embedded browser) and
**gateway + worker pool** (browser containers scale independently).
Only runtime dependency is `patchright`. Everything else is vendored from
[zerodep](https://github.com/Oaklight/zerodep) β€” HTTP server, S3 client, HTML
parsing, readability extraction.
## Architecture
### Request flow
```
HTTP request β†’ auth β†’ route handler β†’ browser_manager.get_page()
↓
LocalWorker (embedded CloakBrowser)
RemoteWorker (CDP, Chromium)
PlaywrightWorker (Playwright protocol, Firefox/Camoufox)
```
### Key components
| Component | File | Purpose |
|-----------|------|---------|
| HTTP server | `_vendor/httpserver` | Async HTTP framework (zerodep) |
| Browser pool | `browser.py` | `LocalWorker`, `RemoteWorker`, `PlaywrightWorker`, `BrowserManager` |
| Config | `config.py` | `VEILRENDER_*` env vars, worker protocol parsing |
| Routes | `routes/*.py` | `/render`, `/screenshot`, `/health`, `/metrics`, `/stats`, `/` dashboard |
| Storage | `storage.py` | L1 TTLCache + L2 S3 (vendored `S3Client`) |
| Filters | `filters.py` | Outbound request blocklist (StevenBlack/hosts) |
| CDP proxy | `cdp_proxy.py` | WebSocket proxy for direct CDP access |
| Stats | `stats.py` | In-memory counters, p50/p95 latency |
### Worker types
| Type | Connection | Browser | Stealth |
|------|-----------|---------|---------|
| `LocalWorker` | Local subprocess | CloakBrowser (auto-downloaded) | Browser-level + driver-level |
| `RemoteWorker` | `chromium.connect_over_cdp()` | Any CDP-compatible Chromium | Driver-level (Patchright) |
| `PlaywrightWorker` | `firefox.connect()` | Camoufox or Playwright-served Firefox | Browser-level (Camoufox) |
## Repository layout
```
src/veilrender/
β”œβ”€β”€ app.py # HTTP server + CDP WebSocket multiplexing
β”œβ”€β”€ config.py # Settings from env vars (VEILRENDER_ prefix)
β”œβ”€β”€ auth.py # Token verification
β”œβ”€β”€ browser.py # Worker classes + BrowserManager pool coordinator
β”œβ”€β”€ cdp_proxy.py # CDP WebSocket proxy
β”œβ”€β”€ filters.py # Outbound request blocklist
β”œβ”€β”€ models.py # Request/response dataclasses
β”œβ”€β”€ stats.py # In-memory request counters and latency
β”œβ”€β”€ storage.py # L1 (TTLCache) + L2 (S3) render cache
β”œβ”€β”€ routes/
β”‚ β”œβ”€β”€ dashboard.py # GET / β€” stats dashboard with i18n (en/zh)
β”‚ β”œβ”€β”€ health.py # GET /health
β”‚ β”œβ”€β”€ metrics.py # GET /metrics β€” Prometheus exposition
β”‚ β”œβ”€β”€ render.py # POST /render
β”‚ └── screenshot.py # POST /screenshot
└── _vendor/ # zerodep modules β€” DO NOT EDIT manually
β”œβ”€β”€ httpserver.py # Async HTTP server
β”œβ”€β”€ cache.py # TTLCache
β”œβ”€β”€ s3.py # S3-compatible client
β”œβ”€β”€ readability.py # Article extraction
└── soup.py # HTML parser
deploy/
β”œβ”€β”€ compose.yaml # Single-instance (prod)
β”œβ”€β”€ compose-dev.yaml # Dev instance (port 7861)
β”œβ”€β”€ compose-pool.yaml # Gateway + CloakBrowser workers
β”œβ”€β”€ compose-pool-mixed.yaml # Gateway + CloakBrowser + Camoufox
└── Dockerfile.camoufox # Camoufox server image
scripts/
└── download-cloakbrowser.py # Download CloakBrowser binary
```
## Commands
```bash
make dev # Run dev server on :7860
make build # Docker build (full target, with CloakBrowser)
make build-gateway # Docker build (gateway target, no browser)
make run # Docker run
make lint # ruff check --fix && ruff format
make typecheck # ty check
make vendor # Re-vendor zerodep modules
```
## Deployment
### Dev deployment
```bash
make deploy-dev SSH_TARGET=oaklight.buttercup # single-instance
make deploy-dev SSH_TARGET=oaklight.buttercup POOL=1 # gateway + workers
```
Builds from the **working tree** (not committed state), tags as `dev-test`,
deploys to `veilrender-dev` stack (port 7861). Always verify no dirty files
in `src/` before deploying.
### Release
1. Update `CHANGELOG.md` β€” move `[Unreleased]` to `[X.Y.Z]`
2. Bump `__version__` in `src/veilrender/__init__.py`
3. Commit: `git commit -m "bump version to X.Y.Z"`
4. Tag and push: `git tag vX.Y.Z && git push && git push origin vX.Y.Z`
5. Release workflow auto-publishes to PyPI, Docker Hub, GHCR, and creates
GitHub Release from changelog
### Production
Prod runs on `oaklight.buttercup` at `/dockervol/dockge/stacks/veilrender`
(port 7860), exposed via NPS as `veilrender.service.oaklight.top`.
Update prod to a released version:
```bash
ssh oaklight.buttercup 'cd /dockervol/dockge/stacks/veilrender && \
docker compose pull && docker compose up -d'
```
## Escalation
- Browser won't start β†’ check `_find_browser_binary()` cascade:
`CLOAKBROWSER_BINARY` env β†’ `~/.cloakbrowser/*/chrome` β†’ auto-download
- Remote worker won't connect β†’ check `_resolve_to_ip()` (Chromium rejects
non-IP Host headers), verify CDP port is accessible
- Camoufox worker won't connect β†’ check Playwright version compatibility
(client/server major.minor must match)
- Vendor module issue β†’ never fix in-place; update upstream zerodep, re-vendor
- Test failure after 3 attempts β†’ stop, report full output
- Never: delete files to fix errors, skip lint, modify `_vendor/` directly
## Files to never edit
- `src/veilrender/_vendor/**` β€” vendored zerodep modules, update via
`make vendor` or `zerodep add` from ~/projects/zerodep
- `CLAUDE.md` β€” symlink to `AGENTS.md`, edit `AGENTS.md` instead
## Definition of done
1. `ruff check --fix && ruff format` on changed Python files
2. `ty check` passes
3. Server starts and `/health` returns 200
4. `/render` returns valid content for a test URL
5. `/metrics` returns valid Prometheus output