mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
feat: add ocp-connect lightweight client + restructure README
- Add standalone `ocp-connect` script for zero-dependency client setup (only needs curl + python3, no Node/repo clone required) - Add `ocp connect` command to main CLI - Add `authMode` field to /health endpoint - Restructure README with clear Server Setup / Client Setup sections - Add dashboard screenshot to README - Fix smoke test model name (claude-haiku-4-5-20251001) - Fix auth mode detection for shared/multi/none - Fix rc file cleanup idempotency (blank line handling) - Fix key input echo (now hidden with read -rs) - Add host validation - Bump version to 3.5.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# OCP — Open Claude Proxy
|
# OCP — Open Claude Proxy
|
||||||
|
|
||||||
> **Status: Stable (v3.4.0)** — Feature-complete. Bug fixes only.
|
> **Status: Stable (v3.5.0)** — Feature-complete. Bug fixes only.
|
||||||
|
|
||||||
> **Already paying for Claude Pro/Max? Use your subscription as an OpenAI-compatible API — $0 extra cost.**
|
> **Already paying for Claude Pro/Max? Use your subscription as an OpenAI-compatible API — $0 extra cost.**
|
||||||
|
|
||||||
@@ -29,9 +29,34 @@ Any tool that accepts `OPENAI_BASE_URL` works with OCP:
|
|||||||
| **OpenClaw** | `setup.mjs` auto-configures |
|
| **OpenClaw** | `setup.mjs` auto-configures |
|
||||||
| **Any OpenAI client** | Set base URL to `http://127.0.0.1:3456/v1` |
|
| **Any OpenAI client** | Set base URL to `http://127.0.0.1:3456/v1` |
|
||||||
|
|
||||||
## Quick Start
|
## Installation
|
||||||
|
|
||||||
|
OCP has two roles: **Server** (runs the proxy, needs Claude CLI) and **Client** (connects to a server, zero dependencies).
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ Server (always-on device) ─────────────────────────────┐
|
||||||
|
│ Mac mini / NAS / Raspberry Pi / Desktop │
|
||||||
|
│ Claude CLI + OCP server → bound to 0.0.0.0:3456 │
|
||||||
|
└───────────────────────┬─────────────────────────────────┘
|
||||||
|
│ LAN
|
||||||
|
┌───────────────────┼───────────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
Laptop Phone/Tablet Pi / Server
|
||||||
|
(client) (browser) (client)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Server Setup
|
||||||
|
|
||||||
|
> **Recommended:** Install OCP on a device that stays powered on — Mac mini, NAS, Raspberry Pi, or a desktop that doesn't sleep. This ensures all clients always have access.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
- Node.js 18+
|
||||||
|
- [Claude CLI](https://docs.anthropic.com/en/docs/claude-cli) installed and authenticated (`claude auth login`)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 1. Clone and run setup
|
||||||
git clone https://github.com/dtzp555-max/ocp.git
|
git clone https://github.com/dtzp555-max/ocp.git
|
||||||
cd ocp
|
cd ocp
|
||||||
node setup.mjs
|
node setup.mjs
|
||||||
@@ -41,59 +66,23 @@ The setup script will:
|
|||||||
1. Verify Claude CLI is installed and authenticated
|
1. Verify Claude CLI is installed and authenticated
|
||||||
2. Start the proxy on port 3456
|
2. Start the proxy on port 3456
|
||||||
3. Install auto-start (launchd on macOS, systemd on Linux)
|
3. Install auto-start (launchd on macOS, systemd on Linux)
|
||||||
|
4. Symlink `ocp` to `/usr/local/bin` for CLI access
|
||||||
|
|
||||||
Then point your IDE to the proxy:
|
**Single-machine use** — just set your IDE to use the proxy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export OPENAI_BASE_URL=http://127.0.0.1:3456/v1
|
export OPENAI_BASE_URL=http://127.0.0.1:3456/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verify
|
**LAN mode** — share with other devices on your network:
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://127.0.0.1:3456/v1/models
|
|
||||||
# Returns: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4
|
|
||||||
```
|
|
||||||
|
|
||||||
## LAN Mode — Share with Family
|
|
||||||
|
|
||||||
OCP can serve your entire household from a single machine. One Claude Pro/Max subscription, shared across all devices on your network.
|
|
||||||
|
|
||||||
```
|
|
||||||
Wife's laptop ──┐
|
|
||||||
Son's iPad ───┼──→ OCP :3456 (your Mac) ──→ Claude subscription
|
|
||||||
Your Pi server ───┤
|
|
||||||
Your desktop ──┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1: Enable LAN Access
|
|
||||||
|
|
||||||
**Quick start (temporary, until restart):**
|
|
||||||
```bash
|
|
||||||
export CLAUDE_BIND=0.0.0.0
|
|
||||||
export CLAUDE_AUTH_MODE=multi # per-user keys
|
|
||||||
export OCP_ADMIN_KEY=your-secret-admin-key
|
|
||||||
ocp restart
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permanent (survives reboot):**
|
|
||||||
```bash
|
```bash
|
||||||
|
# Enable LAN access with per-user auth (recommended)
|
||||||
node setup.mjs --bind 0.0.0.0 --auth-mode multi
|
node setup.mjs --bind 0.0.0.0 --auth-mode multi
|
||||||
```
|
```
|
||||||
|
|
||||||
Then set your admin key in the launchd/systemd environment, or save it to a file:
|
Then create API keys for each person/device:
|
||||||
```bash
|
```bash
|
||||||
echo "your-secret-admin-key" > ~/.ocp/admin-key
|
|
||||||
chmod 600 ~/.ocp/admin-key
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Create Keys for Family Members
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set admin key for CLI (or save to ~/.ocp/admin-key)
|
|
||||||
export OCP_ADMIN_KEY=your-secret-admin-key
|
export OCP_ADMIN_KEY=your-secret-admin-key
|
||||||
|
|
||||||
# Create a key for each person/device
|
|
||||||
ocp keys add wife-laptop
|
ocp keys add wife-laptop
|
||||||
# ✓ Key created for "wife-laptop"
|
# ✓ Key created for "wife-laptop"
|
||||||
# API Key: ocp_xDYzOB9ZKYzn...
|
# API Key: ocp_xDYzOB9ZKYzn...
|
||||||
@@ -103,33 +92,72 @@ ocp keys add son-ipad
|
|||||||
ocp keys add pi-server
|
ocp keys add pi-server
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Share Connection Info
|
Run `ocp lan` to see your IP and ready-to-share instructions.
|
||||||
|
|
||||||
Run `ocp lan` to see your IP and ready-to-share instructions:
|
**Verify:**
|
||||||
|
|
||||||
```
|
|
||||||
$ ocp lan
|
|
||||||
OCP LAN Setup
|
|
||||||
─────────────────────────────────────
|
|
||||||
Your IP: 192.168.1.100
|
|
||||||
Port: 3456
|
|
||||||
|
|
||||||
For IDE users, set:
|
|
||||||
OPENAI_BASE_URL=http://192.168.1.100:3456/v1
|
|
||||||
OPENAI_API_KEY=<their-key>
|
|
||||||
|
|
||||||
Dashboard: http://192.168.1.100:3456/dashboard
|
|
||||||
|
|
||||||
Status: ✓ LAN-accessible
|
|
||||||
```
|
|
||||||
|
|
||||||
Give each family member their key and these two settings:
|
|
||||||
```bash
|
```bash
|
||||||
export OPENAI_BASE_URL=http://192.168.1.100:3456/v1
|
curl http://127.0.0.1:3456/v1/models
|
||||||
export OPENAI_API_KEY=ocp_<their-key>
|
# Returns: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Monitor Usage
|
---
|
||||||
|
|
||||||
|
### Client Setup
|
||||||
|
|
||||||
|
> Clients do **not** need to install Node.js, Claude CLI, or the OCP repo. Only `curl` and `python3` are required (pre-installed on most Linux/Mac systems).
|
||||||
|
|
||||||
|
**One-command setup** — download the lightweight `ocp-connect` script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect -o ocp-connect
|
||||||
|
chmod +x ocp-connect
|
||||||
|
./ocp-connect <server-ip> --key <your-api-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or as a one-liner (no file saved):
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect | bash -s -- <server-ip> --key <your-api-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
$ ./ocp-connect 192.168.1.100 --key ocp_xDYzOB9ZKYzn
|
||||||
|
|
||||||
|
OCP Connect
|
||||||
|
─────────────────────────────────────
|
||||||
|
Remote: http://192.168.1.100:3456
|
||||||
|
|
||||||
|
Checking connectivity...
|
||||||
|
✓ Connected
|
||||||
|
|
||||||
|
Remote OCP v3.5.0 (auth: multi)
|
||||||
|
|
||||||
|
Testing API access...
|
||||||
|
✓ API accessible (3 models available)
|
||||||
|
|
||||||
|
Written to /home/user/.bashrc:
|
||||||
|
OPENAI_BASE_URL=http://192.168.1.100:3456/v1
|
||||||
|
OPENAI_API_KEY=ocp_xDYz...
|
||||||
|
|
||||||
|
Running smoke test...
|
||||||
|
✓ Smoke test passed: OK
|
||||||
|
|
||||||
|
Done. Reload your shell to apply:
|
||||||
|
source /home/user/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
After running, reload your shell (`source ~/.bashrc` or `source ~/.zshrc`) and your IDE will automatically use the remote OCP.
|
||||||
|
|
||||||
|
**Manual setup** — if you prefer not to use the script:
|
||||||
|
```bash
|
||||||
|
export OPENAI_BASE_URL=http://<server-ip>:3456/v1
|
||||||
|
export OPENAI_API_KEY=ocp_<your-key>
|
||||||
|
```
|
||||||
|
Add these lines to `~/.bashrc` or `~/.zshrc` to persist across sessions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Monitoring (Server-side)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Per-key usage stats
|
# Per-key usage stats
|
||||||
@@ -143,7 +171,9 @@ ocp keys # List all keys
|
|||||||
ocp keys revoke son-ipad # Revoke a key
|
ocp keys revoke son-ipad # Revoke a key
|
||||||
```
|
```
|
||||||
|
|
||||||
**Web Dashboard:** Open `http://<your-ip>:3456/dashboard` in any browser for real-time monitoring — per-key usage, request history, plan utilization, and system health. No login needed.
|
**Web Dashboard:** Open `http://<server-ip>:3456/dashboard` in any browser for real-time monitoring — per-key usage, request history, plan utilization, and system health.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Auth Modes
|
### Auth Modes
|
||||||
|
|
||||||
@@ -197,6 +227,7 @@ ocp health Proxy diagnostics
|
|||||||
ocp keys List all API keys (multi mode)
|
ocp keys List all API keys (multi mode)
|
||||||
ocp keys add <name> Create a new API key
|
ocp keys add <name> Create a new API key
|
||||||
ocp keys revoke <name> Revoke an API key
|
ocp keys revoke <name> Revoke an API key
|
||||||
|
ocp connect <ip> One-command LAN client setup
|
||||||
ocp lan Show LAN connection info & IP
|
ocp lan Show LAN connection info & IP
|
||||||
ocp settings View tunable settings
|
ocp settings View tunable settings
|
||||||
ocp settings <k> <v> Update a setting at runtime
|
ocp settings <k> <v> Update a setting at runtime
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 857 KiB |
@@ -329,6 +329,203 @@ else:
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── connect ─────────────────────────────────────────────────────────────
|
||||||
|
cmd_connect_help() {
|
||||||
|
cat <<'EOF'
|
||||||
|
ocp connect — Connect this machine to a remote OCP as a LAN client
|
||||||
|
|
||||||
|
Configures OPENAI_BASE_URL (and optionally OPENAI_API_KEY) in your shell
|
||||||
|
rc file so tools like Claude Code point to a remote OCP instance.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ocp connect <host-ip> [--port PORT] [--key API_KEY]
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
host-ip IP address of the machine running OCP
|
||||||
|
--port PORT Port OCP listens on (default: 3456)
|
||||||
|
--key API_KEY API key to use (prompted if remote requires auth)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
ocp connect 192.168.1.10
|
||||||
|
ocp connect 192.168.1.10 --port 8080
|
||||||
|
ocp connect 192.168.1.10 --key sk-abc123
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_connect() {
|
||||||
|
local host="" port=3456 key=""
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--port) port="${2:?'--port requires a value'}"; shift 2 ;;
|
||||||
|
--key) key="${2:?'--key requires a value'}"; shift 2 ;;
|
||||||
|
--help|-h) cmd_connect_help; return 0 ;;
|
||||||
|
-*) echo "Unknown option: $1"; cmd_connect_help; return 1 ;;
|
||||||
|
*) host="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$host" ]]; then
|
||||||
|
echo "Error: host IP is required."
|
||||||
|
echo ""
|
||||||
|
cmd_connect_help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
||||||
|
echo "Error: invalid host '$host'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local base_url="http://$host:$port"
|
||||||
|
|
||||||
|
echo "OCP Connect"
|
||||||
|
echo "─────────────────────────────────────"
|
||||||
|
echo " Remote: $base_url"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Test connectivity via /health
|
||||||
|
echo " Checking connectivity..."
|
||||||
|
local health_json
|
||||||
|
health_json=$(curl -sf --max-time 10 "$base_url/health" 2>/dev/null) || {
|
||||||
|
echo " ✗ Cannot reach $base_url/health"
|
||||||
|
echo " Make sure OCP is running on $host and bound to 0.0.0.0 (LAN mode)."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
echo " ✓ Connected"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 2: Show remote info
|
||||||
|
local remote_version auth_mode
|
||||||
|
remote_version=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('version','?'))" 2>/dev/null || echo "?")
|
||||||
|
auth_mode=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('authMode','none'))" 2>/dev/null || echo "none")
|
||||||
|
|
||||||
|
echo " Remote OCP v$remote_version (auth: $auth_mode)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Determine if key is needed
|
||||||
|
local needs_key=0
|
||||||
|
if [[ "$auth_mode" != "none" ]]; then
|
||||||
|
needs_key=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $needs_key -eq 1 && -z "$key" ]]; then
|
||||||
|
echo " Remote requires authentication."
|
||||||
|
echo " Ask the admin to run: ocp keys add <name>"
|
||||||
|
printf " Enter API key (or press Enter to skip): "
|
||||||
|
read -rs key </dev/tty
|
||||||
|
echo
|
||||||
|
if [[ -z "$key" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo " ✗ No key provided — cannot connect to an auth-required remote without a key."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 4: Test API access via /v1/models
|
||||||
|
echo " Testing API access..."
|
||||||
|
local models_out models_ok=0
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
models_out=$(curl -sf --max-time 10 \
|
||||||
|
-H "Authorization: Bearer $key" \
|
||||||
|
"$base_url/v1/models" 2>/dev/null) && models_ok=1
|
||||||
|
else
|
||||||
|
models_out=$(curl -sf --max-time 10 "$base_url/v1/models" 2>/dev/null) && models_ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $models_ok -eq 0 ]]; then
|
||||||
|
echo " ✗ API access failed — key may be invalid or revoked."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local model_count
|
||||||
|
model_count=$(echo "$models_out" | python3 -c "import sys,json; print(len(json.loads(sys.stdin.read()).get('data',[])))" 2>/dev/null || echo "?")
|
||||||
|
echo " ✓ API accessible ($model_count models available)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 5: Detect shell rc file
|
||||||
|
local rc_file
|
||||||
|
if [[ "${SHELL:-}" == */zsh ]]; then
|
||||||
|
rc_file="$HOME/.zshrc"
|
||||||
|
else
|
||||||
|
rc_file="$HOME/.bashrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Remove any previously written OCP LAN lines
|
||||||
|
if [[ -f "$rc_file" ]]; then
|
||||||
|
local tmp_rc
|
||||||
|
tmp_rc=$(mktemp)
|
||||||
|
if python3 - "$rc_file" "$tmp_rc" <<'PYEOF'
|
||||||
|
import sys
|
||||||
|
src, dst = sys.argv[1], sys.argv[2]
|
||||||
|
lines = open(src).readlines()
|
||||||
|
out = []
|
||||||
|
skip = False
|
||||||
|
for line in lines:
|
||||||
|
s = line.rstrip('\n')
|
||||||
|
if s == '# OCP LAN (added by ocp connect)':
|
||||||
|
skip = True
|
||||||
|
continue
|
||||||
|
if skip and (s == '' or s.startswith('export OPENAI_BASE_URL=') or s.startswith('export OPENAI_API_KEY=')):
|
||||||
|
continue
|
||||||
|
skip = False
|
||||||
|
out.append(line)
|
||||||
|
open(dst, 'w').writelines(out)
|
||||||
|
PYEOF
|
||||||
|
then
|
||||||
|
cp "$tmp_rc" "$rc_file"
|
||||||
|
fi
|
||||||
|
rm -f "$tmp_rc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 7: Append new config
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "# OCP LAN (added by ocp connect)"
|
||||||
|
echo "export OPENAI_BASE_URL=$base_url/v1"
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
echo "export OPENAI_API_KEY=$key"
|
||||||
|
fi
|
||||||
|
} >> "$rc_file"
|
||||||
|
|
||||||
|
echo " Written to $rc_file:"
|
||||||
|
echo " OPENAI_BASE_URL=$base_url/v1"
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
echo " OPENAI_API_KEY=${key:0:8}..."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 8: Quick smoke test — send a minimal chat completion
|
||||||
|
echo " Running smoke test..."
|
||||||
|
local chat_payload='{"model":"claude-haiku-4-5-20251001","messages":[{"role":"user","content":"Reply with OK only."}],"max_tokens":10}'
|
||||||
|
local chat_out chat_ok=0
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
chat_out=$(curl -sf --max-time 30 \
|
||||||
|
-H "Authorization: Bearer $key" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$chat_payload" \
|
||||||
|
"$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
|
||||||
|
else
|
||||||
|
chat_out=$(curl -sf --max-time 30 \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$chat_payload" \
|
||||||
|
"$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $chat_ok -eq 1 ]]; then
|
||||||
|
local reply
|
||||||
|
reply=$(echo "$chat_out" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['choices'][0]['message']['content'].strip())" 2>/dev/null || echo "(response received)")
|
||||||
|
echo " ✓ Smoke test passed: $reply"
|
||||||
|
else
|
||||||
|
echo " ⚠ Smoke test failed (proxy is reachable but chat completion did not succeed)."
|
||||||
|
echo " The env vars have still been written. Check the remote OCP logs."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Done. Reload your shell to apply:"
|
||||||
|
echo " source $rc_file"
|
||||||
|
}
|
||||||
|
|
||||||
# ── lan ─────────────────────────────────────────────────────────────────
|
# ── lan ─────────────────────────────────────────────────────────────────
|
||||||
cmd_lan_help() {
|
cmd_lan_help() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -609,6 +806,7 @@ Commands:
|
|||||||
clear Clear all sessions
|
clear Clear all sessions
|
||||||
keys Manage API keys (add/list/revoke)
|
keys Manage API keys (add/list/revoke)
|
||||||
lan LAN mode setup guide
|
lan LAN mode setup guide
|
||||||
|
connect <ip> Connect to a remote OCP (sets env vars in rc file)
|
||||||
restart Restart proxy
|
restart Restart proxy
|
||||||
restart gateway Restart gateway
|
restart gateway Restart gateway
|
||||||
update Update OCP to latest version
|
update Update OCP to latest version
|
||||||
@@ -653,6 +851,7 @@ case "$subcmd" in
|
|||||||
clear) cmd_clear ;;
|
clear) cmd_clear ;;
|
||||||
keys) cmd_keys "${1:-}" "${2:-}" ;;
|
keys) cmd_keys "${1:-}" "${2:-}" ;;
|
||||||
lan) cmd_lan ;;
|
lan) cmd_lan ;;
|
||||||
|
connect) cmd_connect "$@" ;;
|
||||||
restart) cmd_restart "${1:-}" ;;
|
restart) cmd_restart "${1:-}" ;;
|
||||||
update) cmd_update "${1:-}" ;;
|
update) cmd_update "${1:-}" ;;
|
||||||
*) echo "Unknown command: $subcmd"; echo ""; cmd_help; exit 1 ;;
|
*) echo "Unknown command: $subcmd"; echo ""; cmd_help; exit 1 ;;
|
||||||
|
|||||||
Executable
+227
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ocp-connect — Lightweight client script to connect to a remote OCP instance
|
||||||
|
# No dependencies beyond curl and python3 (available on most Linux/Mac systems)
|
||||||
|
#
|
||||||
|
# Install:
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect -o ocp-connect
|
||||||
|
# chmod +x ocp-connect
|
||||||
|
#
|
||||||
|
# Or run directly:
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect | bash -s -- <host-ip> --key <key>
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
cat <<'EOF'
|
||||||
|
ocp-connect — Connect this machine to a remote OCP (Open Claude Proxy)
|
||||||
|
|
||||||
|
Configures OPENAI_BASE_URL and OPENAI_API_KEY in your shell rc file
|
||||||
|
so tools like Claude Code, Cline, Aider, etc. point to the remote OCP.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ocp-connect <host-ip> [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--port PORT Port OCP listens on (default: 3456)
|
||||||
|
--key API_KEY API key (prompted interactively if remote requires auth)
|
||||||
|
--help, -h Show this help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
ocp-connect 192.168.1.10
|
||||||
|
ocp-connect 192.168.1.10 --port 8080
|
||||||
|
ocp-connect 192.168.1.10 --key ocp_abc123
|
||||||
|
|
||||||
|
What it does:
|
||||||
|
1. Tests connectivity to the remote OCP
|
||||||
|
2. Verifies your API key (if auth is enabled)
|
||||||
|
3. Writes OPENAI_BASE_URL and OPENAI_API_KEY to ~/.bashrc or ~/.zshrc
|
||||||
|
4. Runs a smoke test to confirm everything works
|
||||||
|
|
||||||
|
After running, reload your shell: source ~/.bashrc (or ~/.zshrc)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local host="" port=3456 key=""
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--port) port="${2:?'--port requires a value'}"; shift 2 ;;
|
||||||
|
--key) key="${2:?'--key requires a value'}"; shift 2 ;;
|
||||||
|
--help|-h) show_help; exit 0 ;;
|
||||||
|
-*) echo "Unknown option: $1"; show_help; exit 1 ;;
|
||||||
|
*) host="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$host" ]]; then
|
||||||
|
echo "Error: host IP is required."
|
||||||
|
echo ""
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
||||||
|
echo "Error: invalid host '$host'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
for cmd in curl python3; do
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
echo "Error: '$cmd' is required but not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
local base_url="http://$host:$port"
|
||||||
|
|
||||||
|
echo "OCP Connect"
|
||||||
|
echo "─────────────────────────────────────"
|
||||||
|
echo " Remote: $base_url"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Test connectivity via /health
|
||||||
|
echo " Checking connectivity..."
|
||||||
|
local health_json
|
||||||
|
health_json=$(curl -sf --max-time 10 "$base_url/health" 2>/dev/null) || {
|
||||||
|
echo " ✗ Cannot reach $base_url/health"
|
||||||
|
echo " Make sure OCP is running on $host and bound to 0.0.0.0 (LAN mode)."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo " ✓ Connected"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 2: Show remote info
|
||||||
|
local remote_version auth_mode
|
||||||
|
remote_version=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('version','?'))" 2>/dev/null || echo "?")
|
||||||
|
auth_mode=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('authMode','none'))" 2>/dev/null || echo "none")
|
||||||
|
|
||||||
|
echo " Remote OCP v$remote_version (auth: $auth_mode)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Determine if key is needed
|
||||||
|
if [[ "$auth_mode" != "none" && -z "$key" ]]; then
|
||||||
|
echo " Remote requires authentication."
|
||||||
|
echo " Ask the OCP admin to run: ocp keys add <name>"
|
||||||
|
printf " Enter API key (or press Enter to skip): "
|
||||||
|
read -rs key </dev/tty
|
||||||
|
echo
|
||||||
|
if [[ -z "$key" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo " ✗ No key provided — cannot connect to an auth-required remote without a key."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 4: Test API access via /v1/models
|
||||||
|
echo " Testing API access..."
|
||||||
|
local models_out models_ok=0
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
models_out=$(curl -sf --max-time 10 \
|
||||||
|
-H "Authorization: Bearer $key" \
|
||||||
|
"$base_url/v1/models" 2>/dev/null) && models_ok=1
|
||||||
|
else
|
||||||
|
models_out=$(curl -sf --max-time 10 "$base_url/v1/models" 2>/dev/null) && models_ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $models_ok -eq 0 ]]; then
|
||||||
|
echo " ✗ API access failed — key may be invalid or revoked."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local model_count
|
||||||
|
model_count=$(echo "$models_out" | python3 -c "import sys,json; print(len(json.loads(sys.stdin.read()).get('data',[])))" 2>/dev/null || echo "?")
|
||||||
|
echo " ✓ API accessible ($model_count models available)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 5: Detect shell rc file
|
||||||
|
local rc_file
|
||||||
|
if [[ "${SHELL:-}" == */zsh ]]; then
|
||||||
|
rc_file="$HOME/.zshrc"
|
||||||
|
elif [[ "${SHELL:-}" == */fish ]]; then
|
||||||
|
echo " Note: fish shell detected. Writing to ~/.bashrc — add to fish config manually."
|
||||||
|
rc_file="$HOME/.bashrc"
|
||||||
|
else
|
||||||
|
rc_file="$HOME/.bashrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Remove any previously written OCP LAN lines (idempotent)
|
||||||
|
if [[ -f "$rc_file" ]]; then
|
||||||
|
local tmp_rc
|
||||||
|
tmp_rc=$(mktemp)
|
||||||
|
if python3 - "$rc_file" "$tmp_rc" <<'PYEOF'
|
||||||
|
import sys
|
||||||
|
src, dst = sys.argv[1], sys.argv[2]
|
||||||
|
lines = open(src).readlines()
|
||||||
|
out = []
|
||||||
|
skip = False
|
||||||
|
for line in lines:
|
||||||
|
s = line.rstrip('\n')
|
||||||
|
if s == '# OCP LAN (added by ocp connect)':
|
||||||
|
skip = True
|
||||||
|
continue
|
||||||
|
if skip and (s == '' or s.startswith('export OPENAI_BASE_URL=') or s.startswith('export OPENAI_API_KEY=')):
|
||||||
|
continue
|
||||||
|
skip = False
|
||||||
|
out.append(line)
|
||||||
|
open(dst, 'w').writelines(out)
|
||||||
|
PYEOF
|
||||||
|
then
|
||||||
|
cp "$tmp_rc" "$rc_file"
|
||||||
|
fi
|
||||||
|
rm -f "$tmp_rc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 7: Append new config
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "# OCP LAN (added by ocp connect)"
|
||||||
|
echo "export OPENAI_BASE_URL=$base_url/v1"
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
echo "export OPENAI_API_KEY=$key"
|
||||||
|
fi
|
||||||
|
} >> "$rc_file"
|
||||||
|
|
||||||
|
echo " Written to $rc_file:"
|
||||||
|
echo " OPENAI_BASE_URL=$base_url/v1"
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
echo " OPENAI_API_KEY=${key:0:8}..."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 8: Quick smoke test
|
||||||
|
echo " Running smoke test..."
|
||||||
|
# Pick the first available model from /v1/models
|
||||||
|
local smoke_model
|
||||||
|
smoke_model=$(echo "$models_out" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['data'][0]['id'])" 2>/dev/null || echo "claude-haiku-4-5-20251001")
|
||||||
|
local chat_payload="{\"model\":\"$smoke_model\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply with OK only.\"}],\"max_tokens\":10}"
|
||||||
|
local chat_out chat_ok=0
|
||||||
|
if [[ -n "$key" ]]; then
|
||||||
|
chat_out=$(curl -sf --max-time 30 \
|
||||||
|
-H "Authorization: Bearer $key" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$chat_payload" \
|
||||||
|
"$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
|
||||||
|
else
|
||||||
|
chat_out=$(curl -sf --max-time 30 \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$chat_payload" \
|
||||||
|
"$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $chat_ok -eq 1 ]]; then
|
||||||
|
local reply
|
||||||
|
reply=$(echo "$chat_out" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['choices'][0]['message']['content'].strip())" 2>/dev/null || echo "(response received)")
|
||||||
|
echo " ✓ Smoke test passed: $reply"
|
||||||
|
else
|
||||||
|
echo " ⚠ Smoke test failed (proxy is reachable but chat completion did not succeed)."
|
||||||
|
echo " The env vars have still been written. Check the remote OCP logs."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Done. Reload your shell to apply:"
|
||||||
|
echo " source $rc_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openclaw-claude-proxy",
|
"name": "openclaw-claude-proxy",
|
||||||
"version": "3.4.0",
|
"version": "3.5.0",
|
||||||
"description": "OCP (Open Claude Proxy) — use your Claude Pro/Max subscription as an OpenAI-compatible API for any IDE. Works with Cline, OpenCode, Aider, Continue.dev, OpenClaw, and more.",
|
"description": "OCP (Open Claude Proxy) — use your Claude Pro/Max subscription as an OpenAI-compatible API for any IDE. Works with Cline, OpenCode, Aider, Continue.dev, OpenClaw, and more.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1123,6 +1123,7 @@ const server = createServer(async (req, res) => {
|
|||||||
uptimeHuman: `${Math.floor(uptimeMs / 3600000)}h ${Math.floor((uptimeMs % 3600000) / 60000)}m`,
|
uptimeHuman: `${Math.floor(uptimeMs / 3600000)}h ${Math.floor((uptimeMs % 3600000) / 60000)}m`,
|
||||||
claudeBinary: CLAUDE,
|
claudeBinary: CLAUDE,
|
||||||
claudeBinaryOk: binaryOk,
|
claudeBinaryOk: binaryOk,
|
||||||
|
authMode: AUTH_MODE,
|
||||||
auth: authStatus,
|
auth: authStatus,
|
||||||
config: {
|
config: {
|
||||||
timeout: TIMEOUT,
|
timeout: TIMEOUT,
|
||||||
|
|||||||
Reference in New Issue
Block a user