From c22b0dd074197f2d53163f4704e3c3134bd6ffba Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Sun, 12 Apr 2026 13:27:07 +1000 Subject: [PATCH] feat: interactive IDE configuration in ocp-connect ocp-connect now detects installed IDEs and offers to configure them automatically. OpenClaw gets full interactive setup (provider name, primary/backup priority, model aliases). Other IDEs (Cline, Continue.dev, Cursor) get manual config instructions printed. Co-Authored-By: Claude Opus 4.6 --- README.md | 43 +++++++-- ocp-connect | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 288 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7f825f1..397f4cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OCP — Open Claude Proxy -> **Status: Stable (v3.5.1)** — Feature-complete. Bug fixes only. +> **Status: Stable (v3.5.2)** — Feature-complete. Bug fixes only. > **Already paying for Claude Pro/Max? Use your subscription as an OpenAI-compatible API — $0 extra cost.** @@ -135,24 +135,57 @@ OCP Connect Checking connectivity... ✓ Connected - Remote OCP v3.5.1 (auth: multi) + Remote OCP v3.5.2 (auth: multi) Server allows anonymous access — no key needed. Testing API access... ✓ API accessible (3 models available) - Written to /home/user/.bashrc: + Shell config: + ✓ .bashrc + ✓ .zshrc OPENAI_BASE_URL=http://192.168.1.100:3456/v1 + System-level (launchctl): + ✓ OPENAI_BASE_URL set for GUI apps and daemons + + IDE Configuration + ───────────────────────────────────── + Detected: OpenClaw (~/.openclaw/openclaw.json) + + Configure OpenClaw to use this OCP? [Y/n] y + Provider name (models show as /model-id) [ocp]: ocp + + How should OCP models be configured? + 1) Primary — use OCP by default, keep existing models as backup + 2) Backup — keep current primary, add OCP as additional option + + Choice [1]: 1 + + ✓ OpenClaw configured + Provider: ocp + Models: + • ocp/claude-opus-4-6 + • ocp/claude-sonnet-4-6 + • ocp/claude-haiku-4 + Priority: PRIMARY (default model) + + Restart OpenClaw to apply: openclaw gateway restart + Running smoke test... ✓ Smoke test passed: OK Done. Reload your shell to apply: - source /home/user/.bashrc + source ~/.zshrc ``` -After running, reload your shell (`source ~/.bashrc` or `source ~/.zshrc`). On macOS, the script also sets `launchctl setenv` so GUI apps and daemons can see the env vars immediately (resets on reboot — re-run `ocp-connect` after restart). +The script automatically: +- Writes env vars to all relevant shell rc files (`.bashrc`, `.zshrc`) +- Sets system-level env vars (`launchctl setenv` on macOS, `environment.d` on Linux) +- Detects and configures IDEs (OpenClaw, Cline, Continue.dev, Cursor) + +On macOS, `launchctl setenv` vars reset on reboot — re-run `ocp-connect` after restart. **Manual setup** — if you prefer not to use the script: ```bash diff --git a/ocp-connect b/ocp-connect index f3cc47c..0467464 100755 --- a/ocp-connect +++ b/ocp-connect @@ -35,12 +35,258 @@ 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 + 4. Sets system-level env vars (launchctl on macOS, systemd on Linux) + 5. Detects and configures IDEs (OpenClaw, Cline, Continue.dev, Cursor) + 6. Runs a smoke test to confirm everything works After running, reload your shell: source ~/.bashrc (or ~/.zshrc) EOF } +configure_ides() { + local base_url="$1" key="$2" models_out="$3" + + # --- OpenClaw --- + local oc_config="$HOME/.openclaw/openclaw.json" + local oc_found=false + if command -v openclaw &>/dev/null || [[ -f "$oc_config" ]]; then + oc_found=true + fi + + if $oc_found; then + echo " IDE Configuration" + echo " ─────────────────────────────────────" + echo " Detected: OpenClaw ($oc_config)" + echo "" + printf " Configure OpenClaw to use this OCP? [Y/n] " + local oc_answer + read -r oc_answer /dev/null || oc_answer="y" + oc_answer="${oc_answer:-y}" + + if [[ "$oc_answer" =~ ^[Yy]$ ]]; then + # Ask for provider name + printf " Provider name (models show as /model-id) [ocp]: " + local provider_name + read -r provider_name /dev/null || provider_name="" + provider_name="${provider_name:-ocp}" + # Sanitize: only allow alphanumeric, dash, underscore + provider_name=$(echo "$provider_name" | tr -cd 'a-zA-Z0-9_-') + [[ -z "$provider_name" ]] && provider_name="ocp" + + # Ask for priority + echo "" + echo " How should OCP models be configured?" + echo " 1) Primary — use OCP by default, keep existing models as backup" + echo " 2) Backup — keep current primary, add OCP as additional option" + echo "" + printf " Choice [1]: " + local priority_choice + read -r priority_choice /dev/null || priority_choice="1" + priority_choice="${priority_choice:-1}" + + echo "" + echo " Writing OpenClaw config..." + + # Use python3 to safely manipulate JSON + local py_ok=0 + python3 - "$oc_config" "$base_url" "$key" "$provider_name" "$priority_choice" "$models_out" <<'PYEOF' && py_ok=1 +import sys, json, os + +config_path = sys.argv[1] +base_url = sys.argv[2] +api_key = sys.argv[3] +provider_name = sys.argv[4] +priority = sys.argv[5] # "1" = primary, "2" = backup +models_json_str = sys.argv[6] + +# Parse models from OCP /v1/models response +try: + models_data = json.loads(models_json_str) + model_ids = [m["id"] for m in models_data.get("data", [])] +except: + model_ids = ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4"] + +# Build provider entry +provider = { + "baseUrl": base_url + "/v1", + "api": "openai-completions", + "authHeader": bool(api_key), + "models": [] +} + +# Model metadata mapping (prefix match for versioned IDs like claude-haiku-4-5-20251001) +model_meta = { + "claude-opus-4": {"name": "Claude Opus (OCP)", "reasoning": True, "maxTokens": 16384}, + "claude-sonnet-4": {"name": "Claude Sonnet (OCP)", "reasoning": True, "maxTokens": 16384}, + "claude-haiku-4": {"name": "Claude Haiku (OCP)", "reasoning": False, "maxTokens": 8192}, +} + +def get_model_meta(mid): + """Match model metadata by prefix.""" + for prefix, meta in sorted(model_meta.items(), key=lambda x: -len(x[0])): + if mid.startswith(prefix): + return meta + return {"name": mid + " (OCP)", "reasoning": False, "maxTokens": 8192} + +for mid in model_ids: + meta = get_model_meta(mid) + provider["models"].append({ + "id": mid, + "name": meta["name"], + "reasoning": meta["reasoning"], + "input": ["text"], + "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, + "contextWindow": 200000, + "maxTokens": meta["maxTokens"] + }) + +# Load or create config +if os.path.exists(config_path): + with open(config_path, "r") as f: + config = json.load(f) +else: + os.makedirs(os.path.dirname(config_path), exist_ok=True) + config = {} + +# Ensure models.providers exists +config.setdefault("models", {}) +config["models"].setdefault("mode", "merge") +config["models"].setdefault("providers", {}) + +# Remove any previous OCP provider with the same name +config["models"]["providers"][provider_name] = provider + +# Set up auth profile if key is provided +if api_key: + config.setdefault("auth", {}) + config["auth"].setdefault("profiles", {}) + config["auth"]["profiles"][provider_name + ":default"] = { + "provider": provider_name, + "mode": "api_key" + } + +# Configure agent defaults — add model aliases +config.setdefault("agents", {}) +config["agents"].setdefault("defaults", {}) +config["agents"]["defaults"].setdefault("models", {}) + +# Build alias map (prefix match) +alias_prefixes = { + "claude-opus-4": "Claude Opus", + "claude-sonnet-4": "Claude Sonnet", + "claude-haiku-4": "Claude Haiku", +} + +for mid in model_ids: + full_id = provider_name + "/" + mid + alias = mid # fallback + for prefix, name in sorted(alias_prefixes.items(), key=lambda x: -len(x[0])): + if mid.startswith(prefix): + alias = name + break + config["agents"]["defaults"]["models"][full_id] = {"alias": alias} + +# Handle primary/backup +if priority == "1": + # OCP as primary — pick the best model (prefer sonnet for daily use) + primary_model = provider_name + "/claude-sonnet-4-6" if "claude-sonnet-4-6" in model_ids else provider_name + "/" + model_ids[0] + config["agents"]["defaults"].setdefault("model", {}) + config["agents"]["defaults"]["model"]["primary"] = primary_model + # Keep existing fallbacks + config["agents"]["defaults"]["model"].setdefault("fallbacks", []) + +# If backup (priority == "2"), don't change the primary — just add models to the list + +# Update agent list entries that use old provider name patterns +# (only update if agents.list exists and has entries using old OCP-like providers) +if "list" in config.get("agents", {}): + for agent in config["agents"]["list"]: + agent_model = agent.get("model", {}) + if priority == "1" and agent_model.get("primary", "").startswith("claude-local/"): + # Migrate from claude-local to new provider + old_model_id = agent_model["primary"].split("/", 1)[1] + if old_model_id in model_ids: + agent_model["primary"] = provider_name + "/" + old_model_id + # Also update subagents if they use claude-local + sub = agent.get("subagents", config["agents"]["defaults"].get("subagents", {})) + # Don't modify subagents in agent entries — they inherit from defaults + +# Update defaults subagents model if using claude-local +if priority == "1": + sub = config["agents"]["defaults"].get("subagents", {}) + if sub.get("model", "").startswith("claude-local/"): + old_id = sub["model"].split("/", 1)[1] + if old_id in model_ids: + sub["model"] = provider_name + "/" + old_id + +with open(config_path, "w") as f: + json.dump(config, f, indent=2, ensure_ascii=False) + f.write("\n") +PYEOF + + if [[ $py_ok -eq 1 ]]; then + echo " ✓ OpenClaw configured" + echo " Provider: $provider_name" + echo " Models:" + # List models from the already-fetched models_out + echo "$models_out" | python3 -c " +import sys,json +d=json.loads(sys.stdin.read()) +pn='$provider_name' +for m in d.get('data',[]): + print(' • ' + pn + '/' + m['id']) +" 2>/dev/null + if [[ "$priority_choice" == "1" ]]; then + echo " Priority: PRIMARY (default model)" + else + echo " Priority: BACKUP (available in model selector)" + fi + echo "" + echo " Restart OpenClaw to apply: openclaw gateway restart" + else + echo " ⚠ Failed to write OpenClaw config. You can configure it manually." + fi + else + echo " Skipped OpenClaw configuration." + fi + echo "" + fi + + # --- Other IDEs: print manual instructions --- + local other_ides_shown=false + + # Detect Cline (VS Code extension) + if [[ -d "$HOME/.vscode/extensions" ]] && ls "$HOME/.vscode/extensions/" 2>/dev/null | grep -qi cline; then + if ! $other_ides_shown; then + echo " Other IDEs detected:" + other_ides_shown=true + fi + echo " • Cline: Settings → OPENAI_BASE_URL = $base_url/v1" + fi + + # Detect Continue.dev + if [[ -f "$HOME/.continue/config.json" ]]; then + if ! $other_ides_shown; then + echo " Other IDEs detected:" + other_ides_shown=true + fi + echo " • Continue.dev: ~/.continue/config.json → apiBase: \"$base_url/v1\"" + fi + + # Detect Cursor + if command -v cursor &>/dev/null || [[ -d "$HOME/.cursor" ]]; then + if ! $other_ides_shown; then + echo " Other IDEs detected:" + other_ides_shown=true + fi + echo " • Cursor: Settings → OpenAI Base URL = $base_url/v1" + fi + + if $other_ides_shown; then + echo "" + fi +} + main() { local host="" port=3456 key="" @@ -236,6 +482,9 @@ PYEOF fi echo "" + # Step 7c: Interactive IDE configuration + configure_ides "$base_url" "$key" "$models_out" + # Step 8: Quick smoke test echo " Running smoke test..." # Pick the first available model from /v1/models