Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
# Port for the proxy server (default: 4181)
PROXY_PORT=4181

# Container name for the proxy service
PROXY_CONTAINER_NAME=cc-proxy

# =============================================================================
# API KEYS (REQUIRED)
# =============================================================================
Expand All @@ -31,9 +28,6 @@ POSTGRES_PASSWORD=postgres
DB_PORT=5432
DB_HOST=cc-db

# Container name for the database service
DB_CONTAINER_NAME=cc-db

# Database Connection Pool Settings
DB_SSL=false
DB_MAX_CONNECTIONS=10
Expand All @@ -54,9 +48,6 @@ ADMINER_PORT=8080
# Adminer version (default: latest)
ADMINER_VERSION=latest

# Container name for Adminer service
ADMINER_CONTAINER_NAME=cc-adminer

# Adminer design theme (options: nette, pepa-linha, pappu687, etc.)
ADMINER_DESIGN=nette

Expand Down
21 changes: 21 additions & 0 deletions com.claude.sync-credentials.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.claude.sync-credentials</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/raulneiva/conductor/workspaces/claude-code-proxy/worcester/scripts/sync-credentials.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/claude-sync-credentials.log</string>
<key>StandardErrorPath</key>
<string>/tmp/claude-sync-credentials.err</string>
</dict>
</plist>
9 changes: 4 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
services:
# Claude Code Proxy
cc-proxy:
container_name: cc-proxy
build:
context: .
dockerfile: Dockerfile
container_name: ${PROXY_CONTAINER_NAME:-cc-proxy}
ports:
- "${HOST_PROXY_PORT:-4181}:4181"
env_file:
Expand All @@ -14,12 +14,11 @@ services:
- API_STREAMING_TIMEOUT_MS=${API_STREAMING_TIMEOUT_MS:-18000000}
- API_MAX_RETRIES=${API_MAX_RETRIES:-5}
- API_RETRY_DELAY_MS=${API_RETRY_DELAY_MS:-2000}
- LOG_LEVEL=${LOG_LEVEL:-info}
- NODE_ENV=${NODE_ENV:-production}
- CLAUDE_CREDENTIALS_PATH=/app/credentials.json
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@${DB_HOST:-cc-db}:5432/${POSTGRES_DB:-claude_proxy}
volumes:
- ~/.claude/.credentials.json:/app/credentials.json:rw
- ~/.claude/claude-credentials.json:/app/credentials.json:rw
healthcheck:
test:
[
Expand All @@ -42,8 +41,8 @@ services:

# PostgreSQL Database
cc-db:
container_name: cc-db
image: postgres:${POSTGRES_VERSION:-15}-alpine
container_name: ${DB_CONTAINER_NAME:-cc-db}
environment:
- POSTGRES_DB=${POSTGRES_DB:-claude_proxy}
- POSTGRES_USER=${POSTGRES_USER:-postgres}
Expand All @@ -64,8 +63,8 @@ services:

# Adminer (Database Management UI)
cc-adminer:
container_name: cc-adminer
image: adminer:${ADMINER_VERSION:-latest}
container_name: ${ADMINER_CONTAINER_NAME:-cc-adminer}
ports:
- "${ADMINER_PORT:-8080}:8080"
environment:
Expand Down
6 changes: 4 additions & 2 deletions docs/CLAUDE_SETTINGS_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ If you prefer to configure Claude Code manually, edit `~/.claude/settings.json`:
```json
{
"env": {
"ANTHROPIC_API_URL": "http://127.0.0.1:4181"
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4181",
"ANTHROPIC_API_URL": "http://127.0.0.1:4181",
"ANTHROPIC_API_KEY": "cc-proxy"
}
}
```
Expand All @@ -91,7 +93,7 @@ Then restart Claude Code.

### Claude Code isn't using the proxy

1. Check that `~/.claude/settings.json` contains the proxy URL
1. Check that `~/.claude/settings.json` contains `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_URL`, and `ANTHROPIC_API_KEY`
2. Restart Claude Code completely (not just the terminal)
3. Verify the proxy is running: `curl http://127.0.0.1:4181/health`

Expand Down
100 changes: 100 additions & 0 deletions docs/CREDENTIAL_SYNC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Auto-Sync Claude Credentials

The proxy needs fresh Claude OAuth credentials to use your subscription (savings plan) instead of API keys. This setup automatically syncs credentials from the macOS Keychain to a file that the proxy container reads.

## How It Works

1. **macOS Keychain** stores your Claude OAuth token (service: `Claude Code-credentials`)
2. **sync-credentials.sh** extracts the token and writes to `~/.claude/claude-credentials.json`
3. **LaunchAgent** runs the sync script every 5 minutes automatically
4. **Docker mount** shares the credentials file with the `cc-proxy` container (read-write)

## One-Time Setup

Run the setup script to install the LaunchAgent:

```bash
bash scripts/setup-credential-sync.sh
```

This will:
- Copy the LaunchAgent plist to `~/Library/LaunchAgents/`
- Start the background job that syncs every 5 minutes
- Log output to `/tmp/claude-sync-credentials.log`

## Manual Sync

To manually refresh credentials (e.g., after CLI login):

```bash
bash scripts/sync-credentials.sh
```

## Monitoring

Check sync status:

```bash
# View recent sync logs
tail -f /tmp/claude-sync-credentials.log

# Check if LaunchAgent is running
launchctl list | grep com.claude

# View token expiry
cat ~/.claude/claude-credentials.json | python3 -c "
import sys, json, datetime
d = json.load(sys.stdin)
ms = d['claudeAiOauth'].get('expiresAt', 0)
if ms:
dt = datetime.datetime.fromtimestamp(ms / 1000).strftime('%Y-%m-%d %H:%M:%S')
print(f'Token expires: {dt}')
"
```

## Troubleshooting

### LaunchAgent not running

```bash
# Unload and reload
launchctl unload ~/Library/LaunchAgents/com.claude.sync-credentials.plist
launchctl load ~/Library/LaunchAgents/com.claude.sync-credentials.plist
```

### Token still expired after sync

The CLI auto-refreshes tokens when used directly. Run a CLI command to trigger refresh:

```bash
claude --version
bash scripts/sync-credentials.sh
podman restart cc-proxy
```

### Proxy can't read credentials

Check the mount is read-write:

```bash
podman inspect cc-proxy --format '{{range .Mounts}}{{if eq .Destination "/app/credentials.json"}}{{.Mode}}{{end}}{{end}}'
```

Should return `rw`. If `ro`, update `docker-compose.yml`:

```yaml
volumes:
- ~/.claude/claude-credentials.json:/app/credentials.json:rw
```

## Docker Integration

The proxy container mounts the credentials file:

```yaml
cc-proxy:
volumes:
- ~/.claude/claude-credentials.json:/app/credentials.json:rw
```

Changes to the file on the host are immediately visible to the container (no restart needed for token updates, only for initial mount changes).
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"db:seed": "node dist/database/seed.js",
"setup:claude": "node scripts/setup-claude-proxy.js",
"setup:docker": "powershell -ExecutionPolicy Bypass -File scripts/setup-docker.ps1",
"settings:update": "node scripts/update-claude-settings.js",
"settings:update:bash": "bash scripts/update-claude-settings.sh",
"settings:restore": "node scripts/restore-claude-settings.js",
"settings:backup": "node scripts/backup-claude-settings.js",
"providers:reset": "curl -s -X POST http://127.0.0.1:4181/providers/reset",
Expand All @@ -39,16 +41,16 @@
"usage-tracking",
"prisma"
],
"author": "Raul Neiva",
"author": "0xPuncker",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/raulneiva/claude-code-proxy.git"
},
"bugs": {
"url": "https://github.com/raulneiva/claude-code-proxy/issues"
"url": "https://github.com/0xPuncker/claude-code-proxy/issues"
},
"homepage": "https://github.com/raulneiva/claude-code-proxy#readme",
"homepage": "https://github.com/0xPuncker/claude-code-proxy#readme",
"engines": {
"node": ">=18.0.0"
},
Expand Down
22 changes: 13 additions & 9 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ npm run settings:restore
1. **Backup**: Creates `~/.claude/backups/settings.json.backup.TIMESTAMP`
2. **Update**: Modifies `~/.claude/settings.json`:
- Sets `ANTHROPIC_BASE_URL` to `http://127.0.0.1:4181`
- Sets `ANTHROPIC_API_URL` to `http://127.0.0.1:4181` for compatibility
- Sets `ANTHROPIC_API_KEY` to a local placeholder (`cc-proxy`) so Claude Code uses the API path
- Removes `ANTHROPIC_AUTH_TOKEN` (handled by cc-proxy)
3. **Validate**: Ensures JSON syntax is correct
4. **Report**: Shows exactly what was changed
Expand All @@ -62,7 +64,9 @@ If you prefer to configure manually, update `~/.claude/settings.json`:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4181"
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4181",
"ANTHROPIC_API_URL": "http://127.0.0.1:4181",
"ANTHROPIC_API_KEY": "cc-proxy"
// Remove ANTHROPIC_AUTH_TOKEN if present
}
}
Expand All @@ -88,19 +92,19 @@ curl -s http://127.0.0.1:4181/api/logs | jq .
**Proxy not responding:**
```bash
# Check if proxy is running
docker ps | grep cc-proxy
docker compose ps cc-proxy

# Start proxy if needed
docker-compose up -d
docker compose up -d

# Check proxy logs
docker logs cc-proxy --tail 50
docker compose logs --tail 50 cc-proxy
```

**Settings not applied:**
```bash
# Verify settings file
cat ~/.claude/settings.json | jq .env.ANTHROPIC_BASE_URL
cat ~/.claude/settings.json | jq .env.ANTHROPIC_API_URL

# Restore from backup
cp ~/.claude/backups/settings.json.backup.<TIMESTAMP> ~/.claude/settings.json
Expand All @@ -109,10 +113,10 @@ cp ~/.claude/backups/settings.json.backup.<TIMESTAMP> ~/.claude/settings.json
**Database connection issues:**
```bash
# Check database is running
docker ps | grep cc-db
docker compose ps cc-db

# Verify database connection
docker exec cc-proxy printenv | grep DB_HOST
docker compose exec cc-proxy printenv | grep DB_HOST
```

## Additional NPM Scripts
Expand Down Expand Up @@ -145,7 +149,7 @@ npm run db:seed
## Support

For issues or questions:
1. Check proxy logs: `docker logs cc-proxy`
1. Check proxy logs: `docker compose logs cc-proxy`
2. Verify settings: `cat ~/.claude/settings.json | jq .`
3. Test proxy: `curl http://127.0.0.1:4181/health`
4. Check database: `docker logs cc-db`
4. Check database: `docker compose logs cc-db`
14 changes: 9 additions & 5 deletions scripts/setup-claude-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));

const PROXY_PORT = process.env.HOST_PROXY_PORT || process.env.PROXY_PORT || '4181';
const PROXY_URL = `http://127.0.0.1:${PROXY_PORT}`;
const PROXY_API_KEY = process.env.CC_PROXY_API_KEY || 'cc-proxy';

// Paths
const homeDir = os.homedir();
Expand Down Expand Up @@ -66,11 +67,12 @@ function backupSettings() {
function isProxyConfigured(settings) {
if (!settings) return false;

// Check for various ways the proxy might be configured
if (settings.env?.ANTHROPIC_API_URL?.includes('127.0.0.1:4181')) return true;
if (settings.apiUrl?.includes('127.0.0.1:4181')) return true;
if (settings.env?.ANTHROPIC_API_URL !== PROXY_URL) return false;
if (settings.env?.ANTHROPIC_BASE_URL !== PROXY_URL) return false;
if (!settings.env?.ANTHROPIC_API_KEY) return false;
if (settings.env?.ANTHROPIC_AUTH_TOKEN) return false;

return false;
return true;
}

/**
Expand All @@ -87,8 +89,10 @@ function addProxyConfig(settings) {
settings.env = {};
}

// Set the proxy URL — ANTHROPIC_BASE_URL is what the Anthropic SDK reads
settings.env.ANTHROPIC_API_URL = PROXY_URL;
settings.env.ANTHROPIC_BASE_URL = PROXY_URL;
settings.env.ANTHROPIC_API_KEY = PROXY_API_KEY;
delete settings.env.ANTHROPIC_AUTH_TOKEN;

return settings;
}
Expand Down
40 changes: 40 additions & 0 deletions scripts/setup-credential-sync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Install LaunchAgent to auto-sync Claude credentials every 5 minutes

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PLIST_FILE="$PROJECT_ROOT/com.claude.sync-credentials.plist"
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
TARGET_PLIST="$LAUNCH_AGENTS_DIR/com.claude.sync-credentials.plist"

echo "📦 Installing Claude credential sync LaunchAgent..."

# Ensure LaunchAgents directory exists
mkdir -p "$LAUNCH_AGENTS_DIR"

# Copy plist to LaunchAgents directory
cp "$PLIST_FILE" "$TARGET_PLIST"

# Update the script path in the plist to use the absolute path
/usr/bin/sed -i '' "s|/Users/raulneiva/conductor/workspaces/claude-code-proxy/worcester/scripts/sync-credentials.sh|$SCRIPT_DIR/sync-credentials.sh|g" "$TARGET_PLIST"

# Load the LaunchAgent
/bin/launchctl unload "$TARGET_PLIST" 2>/dev/null || true
/bin/launchctl load "$TARGET_PLIST"

echo "✅ LaunchAgent installed and started!"
echo ""
echo "Details:"
echo " - Sync interval: Every 5 minutes"
echo " - Log file: /tmp/claude-sync-credentials.log"
echo " - Error log: /tmp/claude-sync-credentials.err"
echo ""
echo "Commands:"
echo " - Start: launchctl load $TARGET_PLIST"
echo " - Stop: launchctl unload $TARGET_PLIST"
echo " - Status: launchctl list | grep com.claude"
echo " - Logs: tail -f /tmp/claude-sync-credentials.log"
echo ""
echo "The proxy container will automatically pick up credential changes."
Loading
Loading