Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bluebirdback_openclaw-without-claude.json
T

1 line
122 KiB
JSON

{"repo": "bluebirdback/openclaw-without-claude", "files": {"scripts/migrate.py": {"0ed891e1b2a89897c147d6404cce435166feb5df": {"t": 1785747506.3169322, "n": 19675, "h": "87f35785df1e5a32", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--config PATH] [--no-restart]\n\nRequirements: Python 3.8+, no external dependencies.\n\nWhat this script does:\n 1. Finds your openclaw.json (auto-detected or via --config)\n 2. Shows a menu of free and cheap model options\n 3. Asks for an API key and validates it live before touching anything\n 4. Backs up your config to openclaw.json.bak-migrate\n 5. Writes two changes to openclaw.json:\n - agents.defaults.model.primary (new model ID)\n - env.OPENROUTER_API_KEY / env.GEMINI_API_KEY (your API key)\n 6. Runs `openclaw gateway restart` to apply changes\n\nWhat it does NOT touch:\n - Channels (Telegram, Discord, etc.)\n - Plugins, workspace, memory files\n - Existing auth profiles (Claude auth is left in place)\n - Everything else in openclaw.json\n\nFlags:\n --dry-run Show what would change, write nothing\n --config Path to openclaw.json (for non-standard installs)\n --no-restart Skip gateway restart after migration\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"google/gemini-3.1-pro-preview\",\n \"name\": \"Gemini 3.1 Pro (direct, free tier available)\",\n \"provider\": \"google\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"google/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"google\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Gemini 2.0 Flash (direct) — $0.10/MTok, 1M context\",\n \"provider\": \"google\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n # Check env var first\n env_path = os.environ.get(\"OPENCLAW_CONFIG_PATH\", \"\")\n if env_path and Path(env_path).exists():\n return Path(env_path)\n\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(\"/home/openclaw/.openclaw/openclaw.json\"),\n Path(\"/opt/openclaw/.openclaw/openclaw.json\"),\n ]\n for p in candidates:\n if p.exists():\n return p\n\n # Last resort: search common locations\n for base in [Path.home(), Path(\"/root\"), Path(\"/home\"), Path(\"/opt\")]:\n try:\n matches = list(base.rglob(\"openclaw.json\"))\n # Exclude backups\n matches = [m for m in matches if \"bak\" not in m.name and \".openclaw\" in str(m)]\n if matches:\n return matches[0]\n except (PermissionError, OSError):\n pass\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw: openrouter/<provider>/<model>\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"OPENROUTER_API_KEY\"] = api_key\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Google Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"GEMINI_API_KEY\"] = api_key\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # IMPORTANT: Never use /v1 — use native Ollama URL only (breaks tool calling)\n config.setdefault(\"models\", {}).setdefault(\"providers\", {})\n if \"ollama\" not in config[\"models\"][\"providers\"]:\n config[\"models\"][\"providers\"][\"ollama\"] = {\n \"baseUrl\": \"http://127.0.0.1:11434\", # no /v1 !\n }\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n\n # Try openclaw CLI first\n result = subprocess.run([\"openclaw\", \"gateway\", \"restart\"], capture_output=True, text=True)\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (openclaw gateway restart)\")\n return\n\n # Try systemd user service\n result = subprocess.run(\n [\"systemctl\", \"--user\", \"restart\", \"openclaw-gateway\"],\n capture_output=True, text=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (systemctl --user restart openclaw-gateway)\")\n return\n\n # Try systemd system service\n result = subprocess.run(\n [\"systemctl\", \"restart\", \"openclaw-gateway\"],\n capture_output=True, text=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (systemctl restart openclaw-gateway)\")\n return\n\n # Nothing worked — config was saved, just tell them\n print()\n print(\" ⚠ Config saved, but could not restart the gateway automatically.\")\n print(\" Please restart it manually with one of:\")\n print(\" openclaw gateway restart\")\n print(\" systemctl --user restart openclaw-gateway\")\n print()\n print(\" Your new model will take effect after restart.\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json automatically.\")\n print()\n print(\" Common locations:\")\n print(\" ~/.openclaw/openclaw.json\")\n print(\" /root/.openclaw/openclaw.json\")\n print()\n # Prompt user to enter path interactively\n entered = prompt(\" Enter the full path to your openclaw.json (or press Enter to abort)\", \"\")\n if not entered:\n print(\" Aborted.\")\n return 1\n config_path = Path(entered).expanduser()\n if not config_path.exists():\n print(f\" ✗ File not found: {config_path}\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "67ae4fe8218a8691506c340a2fb250c63ab6a778": {"t": 1785773657.3218865, "n": 19675, "h": "87f35785df1e5a32", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--config PATH] [--no-restart]\n\nRequirements: Python 3.8+, no external dependencies.\n\nWhat this script does:\n 1. Finds your openclaw.json (auto-detected or via --config)\n 2. Shows a menu of free and cheap model options\n 3. Asks for an API key and validates it live before touching anything\n 4. Backs up your config to openclaw.json.bak-migrate\n 5. Writes two changes to openclaw.json:\n - agents.defaults.model.primary (new model ID)\n - env.OPENROUTER_API_KEY / env.GEMINI_API_KEY (your API key)\n 6. Runs `openclaw gateway restart` to apply changes\n\nWhat it does NOT touch:\n - Channels (Telegram, Discord, etc.)\n - Plugins, workspace, memory files\n - Existing auth profiles (Claude auth is left in place)\n - Everything else in openclaw.json\n\nFlags:\n --dry-run Show what would change, write nothing\n --config Path to openclaw.json (for non-standard installs)\n --no-restart Skip gateway restart after migration\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"google/gemini-3.1-pro-preview\",\n \"name\": \"Gemini 3.1 Pro (direct, free tier available)\",\n \"provider\": \"google\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"google/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"google\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Gemini 2.0 Flash (direct) — $0.10/MTok, 1M context\",\n \"provider\": \"google\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n # Check env var first\n env_path = os.environ.get(\"OPENCLAW_CONFIG_PATH\", \"\")\n if env_path and Path(env_path).exists():\n return Path(env_path)\n\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(\"/home/openclaw/.openclaw/openclaw.json\"),\n Path(\"/opt/openclaw/.openclaw/openclaw.json\"),\n ]\n for p in candidates:\n if p.exists():\n return p\n\n # Last resort: search common locations\n for base in [Path.home(), Path(\"/root\"), Path(\"/home\"), Path(\"/opt\")]:\n try:\n matches = list(base.rglob(\"openclaw.json\"))\n # Exclude backups\n matches = [m for m in matches if \"bak\" not in m.name and \".openclaw\" in str(m)]\n if matches:\n return matches[0]\n except (PermissionError, OSError):\n pass\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw: openrouter/<provider>/<model>\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"OPENROUTER_API_KEY\"] = api_key\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Google Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"GEMINI_API_KEY\"] = api_key\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # IMPORTANT: Never use /v1 — use native Ollama URL only (breaks tool calling)\n config.setdefault(\"models\", {}).setdefault(\"providers\", {})\n if \"ollama\" not in config[\"models\"][\"providers\"]:\n config[\"models\"][\"providers\"][\"ollama\"] = {\n \"baseUrl\": \"http://127.0.0.1:11434\", # no /v1 !\n }\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n\n # Try openclaw CLI first\n result = subprocess.run([\"openclaw\", \"gateway\", \"restart\"], capture_output=True, text=True)\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (openclaw gateway restart)\")\n return\n\n # Try systemd user service\n result = subprocess.run(\n [\"systemctl\", \"--user\", \"restart\", \"openclaw-gateway\"],\n capture_output=True, text=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (systemctl --user restart openclaw-gateway)\")\n return\n\n # Try systemd system service\n result = subprocess.run(\n [\"systemctl\", \"restart\", \"openclaw-gateway\"],\n capture_output=True, text=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (systemctl restart openclaw-gateway)\")\n return\n\n # Nothing worked — config was saved, just tell them\n print()\n print(\" ⚠ Config saved, but could not restart the gateway automatically.\")\n print(\" Please restart it manually with one of:\")\n print(\" openclaw gateway restart\")\n print(\" systemctl --user restart openclaw-gateway\")\n print()\n print(\" Your new model will take effect after restart.\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json automatically.\")\n print()\n print(\" Common locations:\")\n print(\" ~/.openclaw/openclaw.json\")\n print(\" /root/.openclaw/openclaw.json\")\n print()\n # Prompt user to enter path interactively\n entered = prompt(\" Enter the full path to your openclaw.json (or press Enter to abort)\", \"\")\n if not entered:\n print(\" Aborted.\")\n return 1\n config_path = Path(entered).expanduser()\n if not config_path.exists():\n print(f\" ✗ File not found: {config_path}\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "ad613274e6dd8f4b7a3a2e9496ebfe5aa211433f": {"t": 1785773659.6221054, "n": 17796, "h": "f86a571f9eadb8bc", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--config PATH] [--no-restart]\n\nRequirements: Python 3.8+, no external dependencies.\n\nWhat this script does:\n 1. Finds your openclaw.json (auto-detected or via --config)\n 2. Shows a menu of free and cheap model options\n 3. Asks for an API key and validates it live before touching anything\n 4. Backs up your config to openclaw.json.bak-migrate\n 5. Writes two changes to openclaw.json:\n - agents.defaults.model.primary (new model ID)\n - env.OPENROUTER_API_KEY / env.GEMINI_API_KEY (your API key)\n 6. Runs `openclaw gateway restart` to apply changes\n\nWhat it does NOT touch:\n - Channels (Telegram, Discord, etc.)\n - Plugins, workspace, memory files\n - Existing auth profiles (Claude auth is left in place)\n - Everything else in openclaw.json\n\nFlags:\n --dry-run Show what would change, write nothing\n --config Path to openclaw.json (for non-standard installs)\n --no-restart Skip gateway restart after migration\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"google/gemini-3.1-pro-preview\",\n \"name\": \"Gemini 3.1 Pro (direct, free tier available)\",\n \"provider\": \"google\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"google/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"google\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Gemini 2.0 Flash (direct) — $0.10/MTok, 1M context\",\n \"provider\": \"google\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(os.environ.get(\"OPENCLAW_CONFIG_PATH\", \"\")) if os.environ.get(\"OPENCLAW_CONFIG_PATH\") else None,\n ]\n for p in candidates:\n if p and p.exists():\n return p\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw: openrouter/<provider>/<model>\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"OPENROUTER_API_KEY\"] = api_key\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Google Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"GEMINI_API_KEY\"] = api_key\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # IMPORTANT: Never use /v1 — use native Ollama URL only (breaks tool calling)\n config.setdefault(\"models\", {}).setdefault(\"providers\", {})\n if \"ollama\" not in config[\"models\"][\"providers\"]:\n config[\"models\"][\"providers\"][\"ollama\"] = {\n \"baseUrl\": \"http://127.0.0.1:11434\", # no /v1 !\n }\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n result = subprocess.run([\"openclaw\", \"gateway\", \"restart\"], capture_output=True, text=True)\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (openclaw gateway restart)\")\n else:\n print(\" ⚠ Could not restart gateway automatically.\")\n print(\" Please run manually: openclaw gateway restart\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json.\")\n print(\" Tried: ~/.openclaw/openclaw.json\")\n print(\" Use --config /path/to/openclaw.json to specify manually.\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "c9f33b75fe5bf4ec83ab4e0900a9157ba497edf5": {"t": 1785773663.0627508, "n": 16939, "h": "82c6206df8101389", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--config PATH] [--no-restart]\n\nRequirements: Python 3.8+, no external dependencies.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"google/gemini-3.1-pro-preview\",\n \"name\": \"Gemini 3.1 Pro (direct, free tier available)\",\n \"provider\": \"google\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"google/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"google\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Gemini 2.0 Flash (direct) — $0.10/MTok, 1M context\",\n \"provider\": \"google\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(os.environ.get(\"OPENCLAW_CONFIG_PATH\", \"\")) if os.environ.get(\"OPENCLAW_CONFIG_PATH\") else None,\n ]\n for p in candidates:\n if p and p.exists():\n return p\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw: openrouter/<provider>/<model>\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"OPENROUTER_API_KEY\"] = api_key\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Google Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"GEMINI_API_KEY\"] = api_key\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # IMPORTANT: Never use /v1 — use native Ollama URL only (breaks tool calling)\n config.setdefault(\"models\", {}).setdefault(\"providers\", {})\n if \"ollama\" not in config[\"models\"][\"providers\"]:\n config[\"models\"][\"providers\"][\"ollama\"] = {\n \"baseUrl\": \"http://127.0.0.1:11434\", # no /v1 !\n }\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n result = subprocess.run([\"openclaw\", \"gateway\", \"restart\"], capture_output=True, text=True)\n if result.returncode == 0:\n print(\" ✓ Gateway restarted (openclaw gateway restart)\")\n else:\n print(\" ⚠ Could not restart gateway automatically.\")\n print(\" Please run manually: openclaw gateway restart\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json.\")\n print(\" Tried: ~/.openclaw/openclaw.json\")\n print(\" Use --config /path/to/openclaw.json to specify manually.\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "a44b3517584fc78ed1d8dc3fc62ce40d87821ae4": {"t": 1785773664.4427934, "n": 17521, "h": "8d1650c99559efa3", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--provider openrouter] [--model llama-3.3-70b-free]\n\nRequirements: Python 3.8+, no external dependencies.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"google/gemini-3.1-pro-preview\",\n \"name\": \"Gemini 3.1 Pro (direct, free tier available)\",\n \"provider\": \"google\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"google/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"google\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Gemini 2.0 Flash (direct) — $0.10/MTok, 1M context\",\n \"provider\": \"google\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(os.environ.get(\"OPENCLAW_CONFIG\", \"\")) if os.environ.get(\"OPENCLAW_CONFIG\") else None,\n ]\n for p in candidates:\n if p and p.exists():\n return p\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw: openrouter/<provider>/<model>\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"OPENROUTER_API_KEY\"] = api_key\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Google Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # API key goes in env block per OpenClaw docs\n config.setdefault(\"env\", {})\n config[\"env\"][\"GEMINI_API_KEY\"] = api_key\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n # IMPORTANT: Never use /v1 — use native Ollama URL only (breaks tool calling)\n config.setdefault(\"models\", {}).setdefault(\"providers\", {})\n if \"ollama\" not in config[\"models\"][\"providers\"]:\n config[\"models\"][\"providers\"][\"ollama\"] = {\n \"baseUrl\": \"http://127.0.0.1:11434\", # no /v1 !\n }\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n pid = None\n try:\n result = subprocess.run([\"pgrep\", \"-x\", \"openclaw-gateway\"], capture_output=True, text=True)\n if result.returncode == 0:\n pid = result.stdout.strip().split(\"\\n\")[0]\n except Exception:\n pass\n\n if pid:\n print(f\" Sending SIGUSR1 to gateway (PID {pid}) for graceful reload...\")\n subprocess.run([\"kill\", \"-SIGUSR1\", pid])\n print(\" ✓ Gateway reload signal sent (~10s to complete)\")\n else:\n # Try systemd\n result = subprocess.run(\n [\"systemctl\", \"--user\", \"restart\", \"openclaw-gateway\"],\n capture_output=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted via systemd\")\n else:\n print(\" ⚠ Could not restart gateway automatically.\")\n print(\" Please restart it manually: systemctl --user restart openclaw-gateway\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json.\")\n print(\" Tried: ~/.openclaw/openclaw.json\")\n print(\" Use --config /path/to/openclaw.json to specify manually.\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "99e7792063c428214f7f4b091d466105eda4b382": {"t": 1785773665.9596314, "n": 17047, "h": "09b4855bfcdb1920", "c": "#!/usr/bin/env python3\n\"\"\"\nopenclaw-migrate.py — Migrate OpenClaw from Claude subscription OAuth to a free/cheap provider.\n\nUsage:\n python3 migrate.py [--dry-run] [--provider openrouter] [--model llama-3.3-70b-free]\n\nRequirements: Python 3.8+, no external dependencies.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Optional\n\n# ─── Model catalog ─────────────────────────────────────────────────────────────\n\nFREE_MODELS = [\n {\n \"id\": \"openrouter/free\",\n \"name\": \"Free Models Router (auto-picks best free model)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 200,\n \"tags\": [\"free\", \"easy\", \"recommended\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct:free\",\n \"name\": \"Meta: Llama 3.3 70B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 65,\n \"tags\": [\"free\", \"strong\", \"general\"],\n },\n {\n \"id\": \"qwen/qwen3-coder:free\",\n \"name\": \"Qwen3 Coder 480B (free) — best free coding model\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"coding\", \"strong\"],\n },\n {\n \"id\": \"qwen/qwen3.6-plus:free\",\n \"name\": \"Qwen3.6 Plus (free) — 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 1000,\n \"tags\": [\"free\", \"long-context\"],\n },\n {\n \"id\": \"nousresearch/hermes-3-llama-3.1-405b:free\",\n \"name\": \"Nous Hermes 3 405B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-oss-120b:free\",\n \"name\": \"OpenAI: gpt-oss-120b (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"openai\"],\n },\n {\n \"id\": \"nvidia/nemotron-3-super-120b-a12b:free\",\n \"name\": \"NVIDIA: Nemotron 3 Super 120B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 262,\n \"tags\": [\"free\", \"strong\"],\n },\n {\n \"id\": \"google/gemma-3-27b-it:free\",\n \"name\": \"Google: Gemma 3 27B (free)\",\n \"provider\": \"openrouter\",\n \"cost\": \"free\",\n \"context_k\": 131,\n \"tags\": [\"free\", \"google\"],\n },\n]\n\nCHEAP_MODELS = [\n {\n \"id\": \"google/gemini-2.5-flash-lite\",\n \"name\": \"Google: Gemini 2.5 Flash Lite — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\", \"recommended\"],\n },\n {\n \"id\": \"google/gemini-2.0-flash-001\",\n \"name\": \"Google: Gemini 2.0 Flash — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"cheap\", \"google\", \"long-context\"],\n },\n {\n \"id\": \"meta-llama/llama-3.3-70b-instruct\",\n \"name\": \"Meta: Llama 3.3 70B — $0.10/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"strong\"],\n },\n {\n \"id\": \"openai/gpt-4.1-nano\",\n \"name\": \"OpenAI: GPT-4.1 Nano — $0.10/MTok, 1M context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.10/MTok\",\n \"context_k\": 1047,\n \"tags\": [\"cheap\", \"openai\"],\n },\n {\n \"id\": \"mistralai/mistral-small-3.1-24b-instruct\",\n \"name\": \"Mistral Small 3.1 24B — $0.03/MTok\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.03/MTok\",\n \"context_k\": 131,\n \"tags\": [\"cheap\", \"mistral\"],\n },\n {\n \"id\": \"deepseek/deepseek-chat\",\n \"name\": \"DeepSeek V3 — $0.27/MTok, 163k context\",\n \"provider\": \"openrouter\",\n \"cost\": \"$0.27/MTok\",\n \"context_k\": 163,\n \"tags\": [\"cheap\", \"strong\"],\n },\n]\n\nGEMINI_DIRECT = [\n {\n \"id\": \"gemini/gemini-2.5-pro\",\n \"name\": \"Gemini 2.5 Pro (direct, free tier available)\",\n \"provider\": \"gemini\",\n \"cost\": \"free tier / $7/MTok paid\",\n \"context_k\": 1000,\n \"tags\": [\"gemini\", \"strong\", \"free-tier\"],\n },\n {\n \"id\": \"gemini/gemini-2.5-flash\",\n \"name\": \"Gemini 2.5 Flash (direct) — $0.50/MTok\",\n \"provider\": \"gemini\",\n \"cost\": \"$0.50/MTok\",\n \"context_k\": 1048,\n \"tags\": [\"gemini\", \"cheap\"],\n },\n]\n\nOLLAMA_MODELS = [\n {\n \"id\": \"ollama/llama3.2\",\n \"name\": \"Llama 3.2 (local via Ollama) — free, private\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 128,\n \"tags\": [\"local\", \"free\", \"private\"],\n },\n {\n \"id\": \"ollama/qwen2.5-coder:14b\",\n \"name\": \"Qwen2.5 Coder 14B (local via Ollama) — coding\",\n \"provider\": \"ollama\",\n \"cost\": \"free (local)\",\n \"context_k\": 64,\n \"tags\": [\"local\", \"free\", \"coding\"],\n },\n]\n\nALL_MODELS = FREE_MODELS + CHEAP_MODELS + GEMINI_DIRECT + OLLAMA_MODELS\n\n\n# ─── Config helpers ─────────────────────────────────────────────────────────────\n\ndef find_openclaw_config() -> Optional[Path]:\n candidates = [\n Path.home() / \".openclaw\" / \"openclaw.json\",\n Path(\"/root/.openclaw/openclaw.json\"),\n Path(os.environ.get(\"OPENCLAW_CONFIG\", \"\")) if os.environ.get(\"OPENCLAW_CONFIG\") else None,\n ]\n for p in candidates:\n if p and p.exists():\n return p\n return None\n\n\ndef load_config(path: Path) -> dict:\n with open(path) as f:\n return json.load(f)\n\n\ndef save_config(path: Path, config: dict, dry_run: bool = False) -> None:\n if dry_run:\n print(f\"\\n[dry-run] Would write to {path}:\")\n print(json.dumps(config, indent=2)[:500] + \"...\")\n return\n backup = path.with_suffix(\".json.bak-migrate\")\n shutil.copy2(path, backup)\n print(f\" ✓ Backed up original config to {backup}\")\n with open(path, \"w\") as f:\n json.dump(config, f, indent=2)\n print(f\" ✓ Wrote new config to {path}\")\n\n\ndef detect_current_provider(config: dict) -> str:\n auth = config.get(\"auth\", {})\n profiles = auth.get(\"profiles\", {})\n for name, profile in profiles.items():\n if profile.get(\"provider\") == \"anthropic\":\n mode = profile.get(\"mode\", \"unknown\")\n return f\"anthropic ({mode})\"\n model = config.get(\"agents\", {}).get(\"defaults\", {}).get(\"model\", {})\n primary = model.get(\"primary\", \"unknown\")\n return primary\n\n\n# ─── Provider setup ─────────────────────────────────────────────────────────────\n\ndef setup_openrouter(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use OpenRouter.\"\"\"\n model_id = model[\"id\"]\n # OpenRouter model ID format for OpenClaw\n primary = f\"openrouter/{model_id}\" if not model_id.startswith(\"openrouter/\") else model_id\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = primary\n\n config.setdefault(\"auth\", {}).setdefault(\"profiles\", {})\n config[\"auth\"][\"profiles\"][\"openrouter:default\"] = {\n \"provider\": \"openrouter\",\n \"mode\": \"token\",\n \"token\": api_key,\n }\n return config\n\n\ndef setup_gemini(config: dict, model: dict, api_key: str) -> dict:\n \"\"\"Configure OpenClaw to use Gemini directly.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n\n config.setdefault(\"auth\", {}).setdefault(\"profiles\", {})\n config[\"auth\"][\"profiles\"][\"gemini:default\"] = {\n \"provider\": \"gemini\",\n \"mode\": \"token\",\n \"token\": api_key,\n }\n return config\n\n\ndef setup_ollama(config: dict, model: dict) -> dict:\n \"\"\"Configure OpenClaw to use Ollama locally.\"\"\"\n model_id = model[\"id\"]\n\n config.setdefault(\"agents\", {}).setdefault(\"defaults\", {}).setdefault(\"model\", {})\n config[\"agents\"][\"defaults\"][\"model\"][\"primary\"] = model_id\n return config\n\n\n# ─── Connection test ─────────────────────────────────────────────────────────────\n\ndef test_openrouter_key(api_key: str) -> bool:\n try:\n req = urllib.request.Request(\n \"https://openrouter.ai/api/v1/models\",\n headers={\"Authorization\": f\"Bearer {api_key}\"},\n )\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_gemini_key(api_key: str) -> bool:\n try:\n url = f\"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}\"\n with urllib.request.urlopen(url, timeout=10) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\ndef test_ollama() -> bool:\n try:\n with urllib.request.urlopen(\"http://localhost:11434/api/tags\", timeout=5) as resp:\n return resp.status == 200\n except Exception:\n return False\n\n\n# ─── Gateway restart ─────────────────────────────────────────────────────────────\n\ndef restart_gateway(dry_run: bool = False) -> None:\n if dry_run:\n print(\"[dry-run] Would restart gateway\")\n return\n pid = None\n try:\n result = subprocess.run([\"pgrep\", \"-x\", \"openclaw-gateway\"], capture_output=True, text=True)\n if result.returncode == 0:\n pid = result.stdout.strip().split(\"\\n\")[0]\n except Exception:\n pass\n\n if pid:\n print(f\" Sending SIGUSR1 to gateway (PID {pid}) for graceful reload...\")\n subprocess.run([\"kill\", \"-SIGUSR1\", pid])\n print(\" ✓ Gateway reload signal sent (~10s to complete)\")\n else:\n # Try systemd\n result = subprocess.run(\n [\"systemctl\", \"--user\", \"restart\", \"openclaw-gateway\"],\n capture_output=True\n )\n if result.returncode == 0:\n print(\" ✓ Gateway restarted via systemd\")\n else:\n print(\" ⚠ Could not restart gateway automatically.\")\n print(\" Please restart it manually: systemctl --user restart openclaw-gateway\")\n\n\n# ─── Interactive wizard ─────────────────────────────────────────────────────────\n\ndef prompt(msg: str, default: str = \"\") -> str:\n suffix = f\" [{default}]\" if default else \"\"\n try:\n val = input(f\"{msg}{suffix}: \").strip()\n return val or default\n except (KeyboardInterrupt, EOFError):\n print(\"\\nAborted.\")\n sys.exit(0)\n\n\ndef choose_model() -> dict:\n print(\"\\n\" + \"=\" * 60)\n print(\" What do you want to replace Claude with?\")\n print(\"=\" * 60)\n print()\n print(\" 1. Free models via OpenRouter (no cost, needs free account)\")\n print(\" 2. Cheap models via OpenRouter (from $0.03/MTok)\")\n print(\" 3. Gemini direct (free tier, Google account)\")\n print(\" 4. Ollama — local models (free, runs on your machine)\")\n print()\n category = prompt(\"Choose category (1-4)\", \"1\")\n\n if category == \"1\":\n models = FREE_MODELS\n elif category == \"2\":\n models = CHEAP_MODELS\n elif category == \"3\":\n models = GEMINI_DIRECT\n elif category == \"4\":\n models = OLLAMA_MODELS\n else:\n print(\"Invalid choice, defaulting to free models.\")\n models = FREE_MODELS\n\n print()\n for i, m in enumerate(models, 1):\n ctx = f\"{m['context_k']}k ctx\"\n print(f\" {i:2}. {m['name']}\")\n print(f\" cost={m['cost']} context={ctx}\")\n print()\n\n choice = prompt(f\"Choose model (1-{len(models)})\", \"1\")\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice, using first option.\")\n return models[0]\n\n\ndef get_api_key(provider: str) -> Optional[str]:\n if provider == \"openrouter\":\n print()\n print(\" Get a free OpenRouter API key at: https://openrouter.ai/keys\")\n print(\" (Free account, no credit card required for free models)\")\n key = prompt(\" Paste your OpenRouter API key (sk-or-...)\")\n if not key:\n print(\" ⚠ No key entered. Skipping key validation.\")\n return None\n return key\n elif provider == \"gemini\":\n print()\n print(\" Get a free Gemini API key at: https://aistudio.google.com\")\n key = prompt(\" Paste your Gemini API key\")\n if not key:\n return None\n return key\n elif provider == \"ollama\":\n return None\n return None\n\n\n# ─── Main ───────────────────────────────────────────────────────────────────────\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Migrate OpenClaw from Claude to a free/cheap provider.\")\n ap.add_argument(\"--dry-run\", action=\"store_true\", help=\"Show what would change without writing files\")\n ap.add_argument(\"--config\", help=\"Path to openclaw.json (auto-detected if omitted)\")\n ap.add_argument(\"--no-restart\", action=\"store_true\", help=\"Don't restart the gateway after migration\")\n args = ap.parse_args()\n\n print()\n print(\"╔══════════════════════════════════════════════════════╗\")\n print(\"║ openclaw-without-claude — Migration Wizard ║\")\n print(\"╚══════════════════════════════════════════════════════╝\")\n print()\n\n # Find config\n config_path = Path(args.config) if args.config else find_openclaw_config()\n if not config_path or not config_path.exists():\n print(\"✗ Could not find openclaw.json.\")\n print(\" Tried: ~/.openclaw/openclaw.json\")\n print(\" Use --config /path/to/openclaw.json to specify manually.\")\n return 1\n\n config = load_config(config_path)\n current = detect_current_provider(config)\n print(f\" Config found: {config_path}\")\n print(f\" Current provider: {current}\")\n\n if args.dry_run:\n print(\" [DRY RUN] No files will be modified.\\n\")\n\n # Choose model\n model = choose_model()\n provider = model[\"provider\"]\n\n # Get API key if needed\n api_key = get_api_key(provider)\n\n # Validate key\n if api_key and provider == \"openrouter\":\n print(\"\\n Testing OpenRouter API key...\", end=\" \", flush=True)\n if test_openrouter_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif api_key and provider == \"gemini\":\n print(\"\\n Testing Gemini API key...\", end=\" \", flush=True)\n if test_gemini_key(api_key):\n print(\"✓ valid\")\n else:\n print(\"✗ invalid or unreachable\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n elif provider == \"ollama\":\n print(\"\\n Testing Ollama connection...\", end=\" \", flush=True)\n if test_ollama():\n print(\"✓ Ollama is running\")\n else:\n print(\"✗ Ollama not reachable on localhost:11434\")\n print(\" Make sure Ollama is installed and running: https://ollama.com\")\n cont = prompt(\" Continue anyway? (y/n)\", \"n\")\n if cont.lower() != \"y\":\n return 1\n\n # Apply config changes\n print()\n if provider == \"openrouter\":\n config = setup_openrouter(config, model, api_key or \"YOUR_OPENROUTER_KEY\")\n elif provider == \"gemini\":\n config = setup_gemini(config, model, api_key or \"YOUR_GEMINI_KEY\")\n elif provider == \"ollama\":\n config = setup_ollama(config, model)\n\n # Show summary\n new_model = config[\"agents\"][\"defaults\"][\"model\"][\"primary\"]\n print(f\" New primary model: {new_model}\")\n print()\n\n if not args.dry_run:\n confirm = prompt(\" Write config and restart gateway? (y/n)\", \"y\")\n if confirm.lower() != \"y\":\n print(\" Aborted. No changes made.\")\n return 0\n\n save_config(config_path, config, dry_run=args.dry_run)\n\n if not args.no_restart and not args.dry_run:\n print()\n restart_gateway(dry_run=args.dry_run)\n\n print()\n print(\" ✓ Migration complete!\")\n print(f\" Model: {new_model}\")\n if provider == \"openrouter\":\n print(\" Provider: OpenRouter (https://openrouter.ai)\")\n if \"free\" in model[\"id\"]:\n print(\" Cost: free — no credits needed for free-tier models\")\n else:\n print(f\" Cost: {model['cost']}\")\n elif provider == \"gemini\":\n print(\" Provider: Google Gemini (direct)\")\n print(\" Cost: free tier available — check https://aistudio.google.com\")\n elif provider == \"ollama\":\n print(\" Provider: Ollama (local)\")\n print(\" Cost: free — runs on your machine\")\n print()\n print(\" Need help? https://github.com/BlueBirdBack/openclaw-without-claude\")\n print()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}}}}