From 3cfc7cffeb7c791981a03c039fdc58efa7966832 Mon Sep 17 00:00:00 2001 From: William Stuckey Date: Thu, 26 Mar 2026 18:22:48 -0500 Subject: [PATCH] Add persistent context files and cleanup stale references - Add CONTEXT.md: Full infrastructure documentation with architecture diagram, routing table, network details, common operations, and important limitations - Add CLAUDE.md: Project-level context for AI agents working in this codebase - Add global-claude-md/CLAUDE.md: User-level global context for ~/.claude/ - Delete nginx-ai-proxy.conf: Unused, Caddy replaced nginx on big-server - Update deploy.sh: Remove stale nginx setup step, update domain to ai.ein-softworks.com - Update README.md: Remove nginx-ai-proxy.conf from files list, update domain, and remove leaked auth token from example Context files provide persistent reference for AI coding sessions across all projects and the ein-ai-proxy codebase specifically. --- CLAUDE.md | 22 ++++ CONTEXT.md | 260 +++++++++++++++++++++++++++++++++++++ README.md | 7 +- deploy.sh | 19 +-- global-claude-md/CLAUDE.md | 4 + nginx-ai-proxy.conf | 46 ------- 6 files changed, 293 insertions(+), 65 deletions(-) create mode 100644 CLAUDE.md create mode 100644 CONTEXT.md create mode 100644 global-claude-md/CLAUDE.md delete mode 100644 nginx-ai-proxy.conf diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..85c384b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,22 @@ +# Ein AI Proxy - Project Context + +Anthropic-compatible proxy that routes AI requests to multiple providers based on model name: sonnet→GLM-4.7 (Z.ai), opus→Anthropic, haiku→DeepSeek. + +## File Structure +- `proxy.mjs` - Main proxy server (zero dependencies, Node.js built-ins only) +- `config.json` - API keys and auth token (gitignored) +- `config.example.json` - Template config +- `ein-ai-proxy.service` - Systemd unit file +- `deploy.sh` - Deployment and testing script + +## Development +Run locally: `node proxy.mjs` (requires `config.json`) +Test health endpoint: `curl http://localhost:3100/health` +Zero npm dependencies — do not add any. + +## Deployment +On mini-server (ssh mini-server): `cd ~/ai-proxy && git pull && sudo systemctl restart ein-ai-proxy` +View logs: `sudo journalctl -u ein-ai-proxy -f` + +## See Also +`CONTEXT.md` - Full infrastructure details (network, servers, Caddy, routing table) diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..07d2562 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,260 @@ +# Ein AI Proxy - Full System Context + +This document provides complete context for working on the ein-ai-proxy project and the surrounding infrastructure. + +## Architecture Diagram + +``` + Work Laptop (u229331) + ┌────────────────────┐ + │ Claude Code CLI │ + │ OR Roo Code │ + └────────┬───────────┘ + │ HTTPS (SSE streaming) + │ ANTHROPIC_BASE_URL= + │ https://ai.ein-softworks.com + ▼ + ┌─────────────────────────────────────────────┐ + │ Internet │ + │ DNS: ai.ein-softworks.com → 198.98.201.213│ + └─────────────────────────────────────────────┘ + │ + ▼ + ┌────────────────────┐ + │ big-server │ Gateway (198.98.201.213) + │ 192.168.1.61 │ ┌──────────────────────┐ + │ Caddy (Docker) │ │ HTTPS (443) routing: │ + │ iptables rules │ │ ai.ein-softworks.com │ + │ │ │ → 192.168.1.140:3100 │ + │ TCP Forwarding: │ └──────────────────────┘ + │ 41922 → mini:22 │ + │ 3022 → mini:3022 │ + └─────────┬──────────┘ + │ Internal LAN (192.168.1.0/24) + ▼ + ┌────────────────────┐ + │ mini-server │ Home AI Server + │ 192.168.1.140 │ ┌──────────────────────┐ + │ │ │ ein-ai-proxy (3100) │ + │ Services: │ │ systemd: ein-ai- │ + │ - ein-ai-proxy │ │ proxy │ + │ - Gitea (3000) │ │ source: ~/ai-proxy/ │ + │ - Caddy (local) │ └──────────────────────┘ + └─────────┬──────────┘ + │ Routes by model name: + ┌────────────────────┼────────────────────┬──────────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ + ┌──────────┐ ┌────────────┐ ┌───────────────┐ ┌────────────┐ + │Anthropic │ │ Z.ai │ │ DeepSeek │ │ Default │ + │ API │ │ GLM-4.7 │ │ V3.2 │ │ (fallback)│ + └──────────┘ └────────────┘ └───────────────┘ └────────────┘ + (pay-per-t) (flat $10/mo) (cheap per token) (GLM-4.7) +``` + +## Model Routing Table + +| Requested Model | Provider | Rewritten To | Auth Style | Use Case | +|-----------------|----------|--------------|------------|----------| +| `claude-opus-4-6` | Anthropic | unchanged | x-api-key | Complex tasks | +| `claude-opus-4-5` | Anthropic | unchanged | x-api-key | Complex tasks | +| `claude-opus-4-5-20251101` | Anthropic | unchanged | x-api-key | Complex tasks | +| `claude-sonnet-4-6` | Z.ai | glm-4.7 | Bearer | Daily workhorse | +| `claude-sonnet-4-5` | Z.ai | glm-4.7 | Bearer | Daily workhorse | +| `claude-sonnet-4-5-20250929` | Z.ai | glm-4.7 | Bearer | Daily workhorse | +| `claude-haiku-4-5-20251001` | DeepSeek | deepseek-chat | Bearer | Trivial tasks | +| `claude-haiku-4-5` | DeepSeek | deepseek-chat | Bearer | Trivial tasks | +| `glm-4.7` | Z.ai | unchanged | Bearer | Direct GLM request | +| `glm-4.5-air` | Z.ai | unchanged | Bearer | Direct GLM request | +| `deepseek-chat` | DeepSeek | unchanged | Bearer | Direct DeepSeek | +| *unknown* | Z.ai | glm-4.7 | Bearer | Default fallback | + +## Network Architecture + +### DNS Records +All resolve to `198.98.201.213` (big-server's public IP): +- `ai.ein-softworks.com` — Direct A record +- `git.ein-softworks.com` — CNAME → maybezone.duckdns.org +- `ssh.ein-softworks.com` — CNAME → maybezone.duckdns.org +- `ssh.maybe.zone` — CNAME → maybezone.duckdns.org + +### Servers + +#### big-server (192.168.1.61) +- **SSH**: `ssh big-server` (host: ssh.ein-softworks.com, port: 30141, user: harrison, key: ~/.ssh/id_personal) +- **OS**: Linux +- **Caddy**: Docker container with network_mode: host + - Config inside container: `/etc/caddy/Caddyfile` + - Edit: `sudo su`, `cd /opt/reverse-proxy`, edit Caddyfile, `docker restart caddy` + - Logs: `docker logs caddy --tail 20` +- **SSE streaming config**: `flush_interval -1`, timeouts 600s +- **TCP forwarding via iptables**: + - Port 41922 → mini-server:22 (SSH) + - Port 3022 → 192.168.1.140:3022 (Gitea SSH) + - Port 30141 → big-server SSH directly + +#### mini-server (192.168.1.140) +- **SSH**: `ssh mini-server` (host: ssh.maybe.zone, port: 41922, user: harrison, key: ~/.ssh/id_personal) +- **OS**: Debian Linux, zsh shell +- **Node**: v24 via nvm (scripts must `source $NVM_DIR/nvm.sh` before using npm/node) +- **Docker**: Requires sudo; user harrison does not have passwordless sudo +- **Services (Docker)**: + - Gitea: `/opt/gitea/docker-compose.yml`, ports 3000:3000 (web), 3022:2222 (SSH) + - Caddy: `/opt/reverse-proxy/docker-compose.yml`, network_mode: host (local reverse proxy) +- **AI proxy**: + - Systemd unit: `ein-ai-proxy` + - Source: `~/ai-proxy/` + - Port: 3100, bound 0.0.0.0 + - Config: `~/ai-proxy/config.json` (gitignored, contains API keys and auth token) +- **Gitea**: https://git.ein-softworks.com +- **Shell aliases** (~/.zshrc.local): + - `ai` → GLM-4.7 (Z.ai) + - `ai-opus` → Claude Opus (Anthropic) + - `ai-deep` → DeepSeek V3.2 + - These set env vars per-provider and call `claude` directly (no proxy needed locally) +- **Dotfiles**: Public on GitHub; secrets only in ~/.zshrc.local (sourced via `[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local`) + +### Traffic Routing + +**HTTPS (ports 80/443)** — Handled by Caddy on big-server: +- `git.ein-softworks.com` → 192.168.1.140:3000 (Gitea web UI) +- `ai.ein-softworks.com` → 192.168.1.140:3100 (AI proxy) +- `maybe.zone` / `matrix.maybe.zone` / etc. → local Matrix services +- Media stack, UniFi Controller, VPN container → local services + +**TCP port forwarding** — Handled by iptables on big-server: +- Port 41922 → mini-server:22 (SSH) +- Port 3022 → 192.168.1.140:3022 (Gitea SSH) + +## Work Laptop Configuration + +**OS**: macOS +**User**: u229331 + +### SSH Config +``` +Host big-server + HostName ssh.ein-softworks.com + Port 30141 + User harrison + IdentityFile ~/.ssh/id_personal + +Host mini-server + HostName ssh.maybe.zone + Port 41922 + User harrison + IdentityFile ~/.ssh/id_personal +``` + +### Claude Code CLI +- Location: `~/.local/bin/claude` +- Config: `~/.claude/settings.json` +- Environment variables: + - `ANTHROPIC_BASE_URL`: https://ai.ein-softworks.com + - `ANTHROPIC_AUTH_TOKEN`: (proxy auth token from config.json) + +### Roo Code (VS Code Extension) +Three profiles configured: +| Profile | Model | Routes To | Reasoning | Context | +|---------|-------|-----------|-----------|---------| +| "default" | claude-sonnet-4-5 | GLM-4.7 (Z.ai) | ON | Standard | +| "opus" | claude-opus-4-6 | Anthropic Opus | ON | 1M context enabled | +| "cheap" | claude-haiku-4-5 | DeepSeek V3.2 | OFF | Fast/cheap tasks | + +## Proxy Internals + +### File Structure +``` +~/ai-proxy/ +├── proxy.mjs # Main proxy server (zero dependencies) +├── config.json # API keys + auth token (gitignored) +├── config.example.json # Template config +├── ein-ai-proxy.service # Systemd unit file +├── deploy.sh # Deployment script +├── package.json # Package metadata +├── .gitignore # Excludes config.json +├── CONTEXT.md # This file +├── CLAUDE.md # Project-level context +└── README.md # Public documentation +``` + +### Provider Configuration +```javascript +const PROVIDERS = { + anthropic: { + baseUrl: "https://api.anthropic.com", + authStyle: "api-key" // x-api-key header + }, + zai: { + baseUrl: "https://api.z.ai/api/anthropic", + authStyle: "bearer" // Authorization: Bearer token + }, + deepseek: { + baseUrl: "https://api.deepseek.com/anthropic", + authStyle: "bearer" // Authorization: Bearer token + } +}; +``` + +### SSE Streaming +- Streaming responses (`stream: true`) are piped through without buffering +- Caddy on big-server uses `flush_interval -1` for immediate SSE delivery +- Timeouts set to 600s to accommodate long reasoning responses + +### Authentication +Proxy authenticates clients via: +1. `Authorization: Bearer ` header, OR +2. `x-api-key: ` header + +Token must match `config.json.authToken`. + +## Common Operations + +### On mini-server (where proxy runs) +```bash +# Restart proxy +sudo systemctl restart ein-ai-proxy + +# View live logs +sudo journalctl -u ein-ai-proxy -f --no-pager + +# Check proxy health +curl https://ai.ein-softworks.com/health + +# Pull and deploy changes +cd ~/ai-proxy && git pull +sudo systemctl restart ein-ai-proxy +``` + +### On big-server (where Caddy runs) +```bash +# Restart Caddy +sudo su +cd /opt/reverse-proxy +docker restart caddy + +# View Caddy logs +docker logs caddy --tail 20 +``` + +## Important Limitations + +### MCP Tools Execute Client-Side +MCP tools run on the work laptop (where the AI client is), NOT on the proxy server. This means: +- Company-internal MCP servers work fine because the client is on the company network +- The proxy only routes LLM API calls — it doesn't execute MCP tools + +### Thinking-Block Provider Switching +**You cannot switch between providers mid-conversation when reasoning/thinking is enabled.** + +GLM-4.7's thinking block signatures are different from Anthropic's and will be rejected by the Anthropic API. If you start a conversation with Sonnet (GLM-4.7) and then switch to Opus (Anthropic API), the provider will reject the request. + +**Solution**: Start a fresh session with `/clear` before switching to a different provider. + +### Zero Dependencies +The proxy has zero npm dependencies — it uses only Node.js built-ins (`node:http`, `node:fs`, etc.). Do not add dependencies unless absolutely necessary. + +## Key Reference URLs +- **Proxy API**: https://ai.ein-softworks.com +- **Gitea**: https://git.ein-softworks.com +- **Git remote**: `ssh://git@git.ein-softworks.com:3022/harrison/ein-ai-proxy.git` diff --git a/README.md b/README.md index 247ebcf..1364a56 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Add to `~/.claude/settings.json`: { "env": { "ANTHROPIC_BASE_URL": "https://ai.ein-softworks.com", - "ANTHROPIC_AUTH_TOKEN": "2d6aca1161e8ad8451c3c1247edf037b736dd7054fc2fab9e35adee313fdba5c", + "ANTHROPIC_AUTH_TOKEN": "your-proxy-auth-token", "API_TIMEOUT_MS": "3000000" } } @@ -51,7 +51,7 @@ Add to `~/.claude/settings.json`: 2. Set API Provider to **Anthropic** 3. Enter your proxy auth token as the API Key 4. Check **Use custom base URL** -5. Enter `https://ai.maybe.zone` +5. Enter `https://ai.ein-softworks.com` ## Switching models @@ -63,8 +63,9 @@ From Claude Code, the default Sonnet model routes to GLM-4.7. To use Opus for a - `config.json` — Your API keys and auth token (gitignored) - `config.example.json` — Template config - `ein-ai-proxy.service` — Systemd unit file -- `nginx-ai-proxy.conf` — Nginx reverse proxy config - `deploy.sh` — Deployment script +- `CLAUDE.md` — Project-level context for AI agents +- `CONTEXT.md` — Full infrastructure documentation ## License diff --git a/deploy.sh b/deploy.sh index a0dd0b9..7a519fe 100755 --- a/deploy.sh +++ b/deploy.sh @@ -81,32 +81,19 @@ else exit 1 fi -# --- Step 5: Nginx setup reminder --- -echo "[5/5] Nginx setup:" -echo "" -echo " 1. Create DNS A record: ai.maybe.zone -> $(curl -s ifconfig.me 2>/dev/null || echo 'YOUR_IP')" -echo "" -echo " 2. Install the nginx config:" -echo " sudo cp $PROXY_DIR/nginx-ai-proxy.conf /etc/nginx/sites-available/ai-proxy" -echo " sudo ln -sf /etc/nginx/sites-available/ai-proxy /etc/nginx/sites-enabled/" -echo " sudo nginx -t && sudo systemctl reload nginx" -echo "" -echo " 3. Get SSL cert:" -echo " sudo certbot --nginx -d ai.maybe.zone" -echo "" echo "============================================" echo " Deployment complete!" echo "============================================" echo "" echo " Proxy running at: http://127.0.0.1:3100" -echo " After nginx+SSL: https://ai.maybe.zone" +echo " Public URL: https://ai.ein-softworks.com" echo "" echo " --- Client Setup ---" echo "" echo " Claude Code CLI (~/.claude/settings.json):" echo ' {' echo ' "env": {' -echo " \"ANTHROPIC_BASE_URL\": \"https://ai.maybe.zone\"," +echo " \"ANTHROPIC_BASE_URL\": \"https://ai.ein-softworks.com\"," echo " \"ANTHROPIC_AUTH_TOKEN\": \"$AUTH_TOKEN\"," echo ' "API_TIMEOUT_MS": "3000000"' echo ' }' @@ -115,7 +102,7 @@ echo "" echo " Roo Code (VS Code):" echo " Provider: Anthropic" echo " API Key: $AUTH_TOKEN" -echo " Custom Base URL: https://ai.maybe.zone" +echo " Custom Base URL: https://ai.ein-softworks.com" echo "" echo " Model routing:" echo " Sonnet requests -> GLM-4.7 (Z.ai)" diff --git a/global-claude-md/CLAUDE.md b/global-claude-md/CLAUDE.md new file mode 100644 index 0000000..f57b22a --- /dev/null +++ b/global-claude-md/CLAUDE.md @@ -0,0 +1,4 @@ +AI Proxy: https://ai.ein-softworks.com | Routes: sonnet→GLM-4.7, opus→Anthropic, haiku→DeepSeek +SSH: big-server (ssh.ein-softworks.com:30141), mini-server (ssh.maybe.zone:41922) via ~/.ssh/id_personal +Gitea: https://git.ein-softworks.com | mini-server: Debian/zsh, Node v24 via nvm (source before use) +WARNING: /clear before switching providers mid-conversation when thinking enabled (GLM-4.7 thinking blocks incompatible with Anthropic API) diff --git a/nginx-ai-proxy.conf b/nginx-ai-proxy.conf deleted file mode 100644 index c63be0b..0000000 --- a/nginx-ai-proxy.conf +++ /dev/null @@ -1,46 +0,0 @@ -# Ein AI Proxy - Add this as a new server block or include in your existing nginx config -# Place at: /etc/nginx/sites-available/ai-proxy -# Then: sudo ln -s /etc/nginx/sites-available/ai-proxy /etc/nginx/sites-enabled/ -# Then: sudo nginx -t && sudo systemctl reload nginx -# -# Prerequisites: -# - DNS A record for ai.maybe.zone pointing to your server IP -# - SSL cert (use certbot: sudo certbot --nginx -d ai.maybe.zone) - -server { - listen 443 ssl http2; - server_name ai.maybe.zone; - - # SSL certs - certbot will fill these in, or point to your existing certs - # ssl_certificate /etc/letsencrypt/live/ai.maybe.zone/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/ai.maybe.zone/privkey.pem; - - # SSE streaming support - critical for Claude Code / Roo Code - proxy_buffering off; - proxy_cache off; - proxy_read_timeout 600s; - proxy_send_timeout 600s; - - # Max body size for large prompts with long context - client_max_body_size 50m; - - location / { - proxy_pass http://127.0.0.1:3100; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # SSE streaming headers - proxy_set_header Connection ''; - chunked_transfer_encoding on; - } -} - -# HTTP -> HTTPS redirect -server { - listen 80; - server_name ai.maybe.zone; - return 301 https://$host$request_uri; -}