Provider Key Manager 一条命令更换供应商 API Key,全员生效,零遗漏。 痛点 OpenClaw 多 agent 架构下,每个 agent 都有独立的 ,更换一个 provider key 需要: 1. 修改 全局配置 2. 修改 13+ 个 agent 的 3. 更新 存储 4. 重启 Gateway 5. 逐个验证 一个 key 要改 15+ 处,漏改一个就报错。 方案:环境变量统一引用 OpenClaw 原生支持 语法引用环境变量: 核心原则 : - API Key 只存在 中(单一真相源) - 所有 provider 的 用 引用 - 各 agent 的 删除 apiKey 字段 ,继承全局 provider - 换 key = 改一个 env var + 重启,全员自动生效 触发词 , , , , , , 命令 1. 审计当前配置 扫描所有 agent 的 models.json,报告: - ✅ 使用 引用的 provider - ❌ 硬编码 apiKey 的 provider - ⚠️ key 值不一致的 agent 2. 迁移到环境变量模式 自动执行: 1. 将 中的硬编码 key 移入 2. 替换 provider apiKey 为 3. 从各 agent models.json 中 删除 apiKey 字段(继承全局) 4. 更新 存储…

, value.strip()))\n if isinstance(value, dict):\n return value.get(\"source\") in (\"env\", \"file\", \"exec\")\n return False\n\n\ndef extract_env_var_name(value: str) -> str | None:\n \"\"\"从 ${VAR_NAME} 中提取变量名\"\"\"\n m = re.match(r'^\\$\\{([A-Z][A-Z0-9_]*)\\}

Provider Key Manager 一条命令更换供应商 API Key,全员生效,零遗漏。 痛点 OpenClaw 多 agent 架构下,每个 agent 都有独立的 ,更换一个 provider key 需要: 1. 修改 全局配置 2. 修改 13+ 个 agent 的 3. 更新 存储 4. 重启 Gateway 5. 逐个验证 一个 key 要改 15+ 处,漏改一个就报错。 方案:环境变量统一引用 OpenClaw 原生支持 语法引用环境变量: 核心原则 : - API Key 只存在 中(单一真相源) - 所有 provider 的 用 引用 - 各 agent 的 删除 apiKey 字段 ,继承全局 provider - 换 key = 改一个 env var + 重启,全员自动生效 触发词 , , , , , , 命令 1. 审计当前配置 扫描所有 agent 的 models.json,报告: - ✅ 使用 引用的 provider - ❌ 硬编码 apiKey 的 provider - ⚠️ key 值不一致的 agent 2. 迁移到环境变量模式 自动执行: 1. 将 中的硬编码 key 移入 2. 替换 provider apiKey 为 3. 从各 agent models.json 中 删除 apiKey 字段(继承全局) 4. 更新 存储…

, value.strip())\n return m.group(1) if m else None\n\n\n# ── 命令:audit ──────────────────────────────────────\ndef cmd_audit():\n \"\"\"审计所有 agent 的 provider key 配置\"\"\"\n print(\"🔍 Provider Key 配置审计\")\n print(\"=\" * 60)\n\n config = load_json(OPENCLAW_CONFIG)\n global_providers = config.get(\"models\", {}).get(\"providers\", {})\n env_vars = config.get(\"env\", {}).get(\"vars\", {})\n agents = get_all_agents()\n\n # 1. 全局 provider 检查\n print(\"\\n📋 全局 Provider (openclaw.json)\")\n print(\"-\" * 40)\n for pid, pconf in sorted(global_providers.items()):\n api_key = pconf.get(\"apiKey\", \"\")\n base_url = pconf.get(\"baseUrl\", \"N/A\")\n if is_env_ref(api_key):\n env_name = extract_env_var_name(api_key) if isinstance(api_key, str) else api_key.get(\"id\", \"?\")\n actual = env_vars.get(env_name, \"❌ 未设置\")\n masked = actual[:6] + \"...\" + actual[-4:] if len(str(actual)) > 10 else actual\n print(f\" ✅ {pid}: apiKey=${{{env_name}}} → {masked}\")\n elif api_key:\n masked = api_key[:6] + \"...\" + api_key[-4:] if len(api_key) > 10 else api_key\n print(f\" ❌ {pid}: 硬编码 apiKey={masked}\")\n else:\n print(f\" ⚠️ {pid}: 无 apiKey\")\n print(f\" baseUrl: {base_url}\")\n\n # 2. 各 agent 检查\n print(f\"\\n📋 各 Agent models.json ({len(agents)} 个)\")\n print(\"-\" * 40)\n\n issues = []\n for agent_id in agents:\n models_path = AGENTS_DIR / agent_id / \"agent\" / \"models.json\"\n models = load_json(models_path)\n agent_providers = models.get(\"providers\", {})\n\n agent_issues = []\n for pid, pconf in agent_providers.items():\n api_key = pconf.get(\"apiKey\")\n if api_key is None:\n continue # 无 apiKey = 继承全局 ✅\n if is_env_ref(api_key):\n continue # 使用环境变量引用 ✅\n # 硬编码\n global_key = global_providers.get(pid, {}).get(\"apiKey\", \"\")\n # 解析全局 key 的实际值\n if is_env_ref(global_key) and isinstance(global_key, str):\n env_name = extract_env_var_name(global_key)\n global_actual = env_vars.get(env_name, \"\") if env_name else \"\"\n else:\n global_actual = global_key\n\n if api_key == global_actual:\n agent_issues.append(f\" ⚠️ {pid}: 硬编码但值正确(建议迁移为继承)\")\n else:\n agent_issues.append(f\" ❌ {pid}: 硬编码且值不一致!\")\n issues.append((agent_id, pid))\n\n if agent_issues:\n print(f\" 🤖 {agent_id}:\")\n for issue in agent_issues:\n print(f\" {issue}\")\n else:\n has_keys = any(\n p.get(\"apiKey\") is not None\n for p in agent_providers.values()\n )\n if not has_keys and agent_providers:\n print(f\" ✅ {agent_id}: 全部继承全局(理想状态)\")\n elif not agent_providers:\n print(f\" ✅ {agent_id}: 无自定义 provider\")\n else:\n print(f\" ✅ {agent_id}: 配置正确\")\n\n # 3. 总结\n print(f\"\\n{'=' * 60}\")\n if issues:\n print(f\"⚠️ 发现 {len(issues)} 个不一致问题:\")\n for agent_id, pid in issues:\n print(f\" - {agent_id}/{pid}\")\n print(f\"\\n💡 建议运行: python3 {__file__} migrate\")\n else:\n print(\"✅ 所有配置一致\")\n\n # 4. env.vars 覆盖度\n print(f\"\\n📋 env.vars 环境变量 ({len(env_vars)} 个)\")\n print(\"-\" * 40)\n for provider_id, mapping in sorted(PROVIDER_ENV_MAP.items()):\n env_name = mapping[\"env\"]\n if env_name in env_vars:\n val = env_vars[env_name]\n masked = val[:6] + \"...\" + val[-4:] if len(val) > 10 else val\n print(f\" ✅ {env_name} = {masked}\")\n elif provider_id in global_providers:\n print(f\" ❌ {env_name} 未设置(provider {provider_id} 存在但无 env var)\")\n\n\n# ── 命令:migrate ────────────────────────────────────\ndef cmd_migrate(provider_filter: str = None, dry_run: bool = False):\n \"\"\"迁移硬编码 key 到环境变量模式\"\"\"\n print(\"🔄 迁移到环境变量模式\")\n if dry_run:\n print(\" (DRY RUN - 不会实际修改文件)\")\n print(\"=\" * 60)\n\n config = load_json(OPENCLAW_CONFIG)\n global_providers = config.get(\"models\", {}).get(\"providers\", {})\n env_vars = config.setdefault(\"env\", {}).setdefault(\"vars\", {})\n agents = get_all_agents()\n\n # 备份\n if not dry_run:\n backup_dir = BACKUP_DIR / datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n backup_dir.mkdir(parents=True, exist_ok=True)\n shutil.copy2(OPENCLAW_CONFIG, backup_dir / \"openclaw.json\")\n for agent_id in agents:\n src = AGENTS_DIR / agent_id / \"agent\" / \"models.json\"\n dst = backup_dir / f\"{agent_id}-models.json\"\n shutil.copy2(src, dst)\n print(f\" 📦 备份到: {backup_dir}\")\n\n changes = []\n\n # Step 1: 全局 provider apiKey → ${ENV_VAR}\n print(\"\\n📌 Step 1: 全局 provider apiKey → ${ENV_VAR}\")\n for pid, pconf in global_providers.items():\n if provider_filter and pid != provider_filter:\n continue\n api_key = pconf.get(\"apiKey\", \"\")\n if not api_key or is_env_ref(api_key):\n print(f\" ⏭️ {pid}: 已是环境变量引用或无 key\")\n continue\n\n mapping = PROVIDER_ENV_MAP.get(pid)\n if not mapping:\n # 自动生成 env var name\n env_name = pid.upper().replace(\"-\", \"_\") + \"_API_KEY\"\n pass_path = f\"api/{pid}\"\n print(f\" ⚠️ {pid}: 未预定义映射,自动使用 {env_name}\")\n else:\n env_name = mapping[\"env\"]\n pass_path = mapping[\"pass\"]\n\n print(f\" 🔄 {pid}: 硬编码 → ${{{env_name}}}\")\n if not dry_run:\n env_vars[env_name] = api_key # 保存实际值到 env.vars\n pconf[\"apiKey\"] = f\"${{{env_name}}}\" # 替换为引用\n changes.append(f\"全局 {pid}: apiKey → ${{{env_name}}}\")\n\n # Step 2: 各 agent models.json 删除 apiKey\n print(\"\\n📌 Step 2: 各 agent models.json 删除 apiKey(继承全局)\")\n for agent_id in agents:\n models_path = AGENTS_DIR / agent_id / \"agent\" / \"models.json\"\n models = load_json(models_path)\n agent_providers = models.get(\"providers\", {})\n modified = False\n\n for pid, pconf in agent_providers.items():\n if provider_filter and pid != provider_filter:\n continue\n if \"apiKey\" in pconf:\n print(f\" 🗑️ {agent_id}/{pid}: 删除 apiKey(将继承全局)\")\n if not dry_run:\n del pconf[\"apiKey\"]\n modified = True\n changes.append(f\"{agent_id}/{pid}: 删除 apiKey\")\n\n if modified and not dry_run:\n save_json(models_path, models)\n\n # Step 3: 保存全局配置\n if not dry_run and changes:\n save_json(OPENCLAW_CONFIG, config)\n\n # Step 4: 更新 pass\n if not dry_run:\n print(\"\\n📌 Step 3: 同步 pass 存储\")\n for pid in global_providers:\n if provider_filter and pid != provider_filter:\n continue\n mapping = PROVIDER_ENV_MAP.get(pid)\n if not mapping:\n continue\n env_name = mapping[\"env\"]\n pass_path = mapping[\"pass\"]\n actual_key = env_vars.get(env_name, \"\")\n if actual_key:\n try:\n subprocess.run(\n [\"pass\", \"insert\", \"-f\", pass_path],\n input=actual_key.encode(),\n capture_output=True, timeout=10\n )\n print(f\" ✅ pass insert {pass_path}\")\n except Exception as e:\n print(f\" ⚠️ pass insert {pass_path} 失败: {e}\")\n\n # 总结\n print(f\"\\n{'=' * 60}\")\n print(f\"📊 迁移{'(DRY RUN)' if dry_run else ''}完成: {len(changes)} 处变更\")\n for c in changes:\n print(f\" - {c}\")\n if not dry_run and changes:\n print(f\"\\n⚠️ 需要重启 Gateway 生效\")\n print(f\" 运行: openclaw gateway restart\")\n\n\n# ── 命令:update ─────────────────────────────────────\ndef cmd_update(provider: str, new_key: str, base_url: str = None):\n \"\"\"更换指定 provider 的 API Key\"\"\"\n print(f\"🔑 更换 {provider} API Key\")\n print(\"=\" * 60)\n\n config = load_json(OPENCLAW_CONFIG)\n global_providers = config.get(\"models\", {}).get(\"providers\", {})\n env_vars = config.setdefault(\"env\", {}).setdefault(\"vars\", {})\n agents = get_all_agents()\n\n if provider not in global_providers:\n print(f\"❌ Provider '{provider}' 不存在于全局配置\")\n print(f\" 可用: {', '.join(global_providers.keys())}\")\n return\n\n mapping = PROVIDER_ENV_MAP.get(provider, {})\n env_name = mapping.get(\"env\", f\"{provider.upper()}_API_KEY\")\n pass_path = mapping.get(\"pass\", f\"api/{provider}\")\n\n pconf = global_providers[provider]\n changes = 0\n\n # 1. 更新 env.vars\n old_key = env_vars.get(env_name, \"\")\n env_vars[env_name] = new_key\n print(f\" ✅ env.vars.{env_name} 已更新\")\n changes += 1\n\n # 2. 确保全局 apiKey 是 ${ENV_VAR} 引用\n if not is_env_ref(pconf.get(\"apiKey\", \"\")):\n pconf[\"apiKey\"] = f\"${{{env_name}}}\"\n print(f\" ✅ 全局 provider apiKey → ${{{env_name}}}\")\n changes += 1\n else:\n print(f\" ⏭️ 全局 provider apiKey 已是 ${{{env_name}}}\")\n\n # 3. 更新 baseUrl(如果提供)\n if base_url:\n pconf[\"baseUrl\"] = base_url\n print(f\" ✅ baseUrl → {base_url}\")\n changes += 1\n\n # 4. 同步各 agent(如果还有硬编码的)\n agent_fixes = 0\n for agent_id in agents:\n models_path = AGENTS_DIR / agent_id / \"agent\" / \"models.json\"\n models = load_json(models_path)\n agent_pconf = models.get(\"providers\", {}).get(provider)\n if agent_pconf and \"apiKey\" in agent_pconf:\n del agent_pconf[\"apiKey\"] # 删除硬编码,继承全局\n save_json(models_path, models)\n print(f\" 🗑️ {agent_id}: 删除硬编码 apiKey(继承全局)\")\n agent_fixes += 1\n\n # 5. 保存全局配置\n save_json(OPENCLAW_CONFIG, config)\n\n # 6. 更新 pass\n try:\n subprocess.run(\n [\"pass\", \"insert\", \"-f\", pass_path],\n input=new_key.encode(),\n capture_output=True, timeout=10\n )\n print(f\" ✅ pass insert {pass_path}\")\n except Exception as e:\n print(f\" ⚠️ pass 更新失败: {e}\")\n\n # 7. 测试\n print(f\"\\n🧪 测试 {provider} key...\")\n test_ok = _test_provider(provider, new_key, pconf.get(\"baseUrl\", \"\"))\n\n # 总结\n print(f\"\\n{'=' * 60}\")\n print(f\"📊 更新完成:\")\n print(f\" - env.vars: ✅\")\n print(f\" - 全局 provider: ✅\")\n print(f\" - agent 硬编码清理: {agent_fixes} 个\")\n print(f\" - pass: ✅\")\n print(f\" - 可用性测试: {'✅ 通过' if test_ok else '❌ 失败'}\")\n print(f\"\\n⚠️ 需要重启 Gateway 生效\")\n print(f\" 运行: openclaw gateway restart\")\n\n\n# ── 命令:test ───────────────────────────────────────\ndef cmd_test(provider: str):\n \"\"\"测试 provider key 可用性\"\"\"\n config = load_json(OPENCLAW_CONFIG)\n global_providers = config.get(\"models\", {}).get(\"providers\", {})\n env_vars = config.get(\"env\", {}).get(\"vars\", {})\n\n if provider not in global_providers:\n print(f\"❌ Provider '{provider}' 不存在\")\n return\n\n pconf = global_providers[provider]\n api_key = pconf.get(\"apiKey\", \"\")\n\n # 解析环境变量引用\n if isinstance(api_key, str) and is_env_ref(api_key):\n env_name = extract_env_var_name(api_key)\n api_key = env_vars.get(env_name, \"\") if env_name else \"\"\n print(f\" 📎 Key 来源: ${{{env_name}}}\")\n\n base_url = pconf.get(\"baseUrl\", \"\")\n _test_provider(provider, api_key, base_url)\n\n\ndef _test_provider(provider: str, api_key: str, base_url: str) -> bool:\n \"\"\"实际测试 provider\"\"\"\n import urllib.request\n import urllib.error\n\n test_conf = PROVIDER_TEST_CONFIG.get(provider)\n if not test_conf:\n print(f\" ⚠️ 无测试配置 for {provider}\")\n return True # 跳过\n\n url = base_url.rstrip(\"/\") + test_conf[\"url_suffix\"]\n model = test_conf[\"model\"]\n is_anthropic = test_conf.get(\"api\") == \"anthropic\"\n\n if is_anthropic:\n headers = {\n \"x-api-key\": api_key,\n \"anthropic-version\": \"2023-06-01\",\n \"Content-Type\": \"application/json\"\n }\n body = json.dumps({\n \"model\": model,\n \"max_tokens\": 5,\n \"messages\": [{\"role\": \"user\", \"content\": \"hi\"}]\n })\n else:\n headers = {\n \"Authorization\": f\"Bearer {api_key}\",\n \"Content-Type\": \"application/json\"\n }\n body = json.dumps({\n \"model\": model,\n \"messages\": [{\"role\": \"user\", \"content\": \"hi\"}],\n \"max_tokens\": 5\n })\n\n try:\n req = urllib.request.Request(url, data=body.encode(), headers=headers, method=\"POST\")\n # 绕过代理\n proxy_handler = urllib.request.ProxyHandler({})\n opener = urllib.request.build_opener(proxy_handler)\n resp = opener.open(req, timeout=15)\n result = json.loads(resp.read())\n if \"choices\" in result or \"content\" in result:\n print(f\" ✅ {provider}: 测试通过 (model={model})\")\n return True\n elif \"error\" in result:\n print(f\" ❌ {provider}: {result['error']}\")\n return False\n else:\n print(f\" ✅ {provider}: 有响应 (可能正常)\")\n return True\n except urllib.error.HTTPError as e:\n body = e.read().decode()[:200]\n print(f\" ❌ {provider}: HTTP {e.code} - {body}\")\n return False\n except Exception as e:\n print(f\" ❌ {provider}: {e}\")\n return False\n\n\n# ── 命令:status ─────────────────────────────────────\ndef cmd_status():\n \"\"\"显示所有 provider 状态总览\"\"\"\n print(\"📊 Provider 状态总览\")\n print(\"=\" * 60)\n\n config = load_json(OPENCLAW_CONFIG)\n global_providers = config.get(\"models\", {}).get(\"providers\", {})\n env_vars = config.get(\"env\", {}).get(\"vars\", {})\n\n for pid, pconf in sorted(global_providers.items()):\n api_key = pconf.get(\"apiKey\", \"\")\n base_url = pconf.get(\"baseUrl\", \"N/A\")\n api_type = pconf.get(\"api\", \"openai-completions\")\n models = [m.get(\"id\", \"?\") for m in pconf.get(\"models\", [])]\n\n # Key 来源\n if is_env_ref(api_key) and isinstance(api_key, str):\n env_name = extract_env_var_name(api_key)\n actual = env_vars.get(env_name, \"\")\n key_source = f\"${{{env_name}}}\"\n key_masked = actual[:6] + \"...\" + actual[-4:] if len(actual) > 10 else \"(empty)\"\n elif api_key:\n key_source = \"硬编码\"\n key_masked = api_key[:6] + \"...\" + api_key[-4:] if len(api_key) > 10 else api_key\n else:\n key_source = \"无\"\n key_masked = \"-\"\n\n print(f\"\\n🏷️ {pid}\")\n print(f\" Key: {key_source} = {key_masked}\")\n print(f\" URL: {base_url}\")\n print(f\" API: {api_type}\")\n print(f\" Models: {', '.join(models[:5])}{'...' if len(models) > 5 else ''}\")\n\n\n# ── 主入口 ───────────────────────────────────────────\ndef main():\n if len(sys.argv) \u003c 2:\n print(\"Usage:\")\n print(f\" {sys.argv[0]} audit — 审计配置\")\n print(f\" {sys.argv[0]} migrate [--provider X] [--dry-run] — 迁移到环境变量模式\")\n print(f\" {sys.argv[0]} update \u003cprovider> \u003ckey> [--base-url URL] — 更换 key\")\n print(f\" {sys.argv[0]} test \u003cprovider> — 测试 key\")\n print(f\" {sys.argv[0]} status — 状态总览\")\n return\n\n cmd = sys.argv[1]\n\n if cmd == \"audit\":\n cmd_audit()\n elif cmd == \"migrate\":\n provider = None\n dry_run = False\n i = 2\n while i \u003c len(sys.argv):\n if sys.argv[i] == \"--provider\" and i + 1 \u003c len(sys.argv):\n provider = sys.argv[i + 1]\n i += 2\n elif sys.argv[i] == \"--dry-run\":\n dry_run = True\n i += 1\n else:\n i += 1\n cmd_migrate(provider, dry_run)\n elif cmd == \"update\":\n if len(sys.argv) \u003c 4:\n print(\"Usage: manager.py update \u003cprovider> \u003cnew-key> [--base-url URL]\")\n return\n provider = sys.argv[2]\n new_key = sys.argv[3]\n base_url = None\n if \"--base-url\" in sys.argv:\n idx = sys.argv.index(\"--base-url\")\n if idx + 1 \u003c len(sys.argv):\n base_url = sys.argv[idx + 1]\n cmd_update(provider, new_key, base_url)\n elif cmd == \"test\":\n if len(sys.argv) \u003c 3:\n print(\"Usage: manager.py test \u003cprovider>\")\n return\n cmd_test(sys.argv[2])\n elif cmd == \"status\":\n cmd_status()\n else:\n print(f\"❌ 未知命令: {cmd}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":21148,"content_sha256":"a2a685689aee5d419e76875714e537f6272841b19e4bf503c3bfbaf522238ee6"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Provider Key Manager","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"一条命令更换供应商 API Key,全员生效,零遗漏。","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"痛点","type":"text"}]},{"type":"paragraph","content":[{"text":"OpenClaw 多 agent 架构下,每个 agent 都有独立的 ","type":"text"},{"text":"models.json","type":"text","marks":[{"type":"code_inline"}]},{"text":",更换一个 provider key 需要:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"修改 ","type":"text"},{"text":"openclaw.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 全局配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"修改 13+ 个 agent 的 ","type":"text"},{"text":"models.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"更新 ","type":"text"},{"text":"pass","type":"text","marks":[{"type":"code_inline"}]},{"text":" 存储","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"重启 Gateway","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"逐个验证","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"一个 key 要改 15+ 处,漏改一个就报错。","type":"text","marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"方案:环境变量统一引用","type":"text"}]},{"type":"paragraph","content":[{"text":"OpenClaw 原生支持 ","type":"text"},{"text":"${ENV_VAR}","type":"text","marks":[{"type":"code_inline"}]},{"text":" 语法引用环境变量:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"// openclaw.json\n{\n \"env\": {\n \"vars\": {\n \"ZAI_API_KEY\": \"actual-key-value-here\"\n }\n },\n \"models\": {\n \"providers\": {\n \"zai\": {\n \"apiKey\": \"${ZAI_API_KEY}\", // ← 引用环境变量,不硬编码\n \"baseUrl\": \"https://open.bigmodel.cn/api/coding/paas/v4\"\n }\n }\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"核心原则","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API Key 只存在 ","type":"text"},{"text":"env.vars","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中(单一真相源)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"所有 provider 的 ","type":"text"},{"text":"apiKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用 ","type":"text"},{"text":"\"${ENV_VAR}\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" 引用","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"各 agent 的 ","type":"text"},{"text":"models.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"删除 apiKey 字段","type":"text","marks":[{"type":"strong"}]},{"text":",继承全局 provider","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"换 key = 改一个 env var + 重启,全员自动生效","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"触发词","type":"text"}]},{"type":"paragraph","content":[{"text":"换key","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"更换key","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"provider key","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"API key 更换","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"模型key","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"key管理","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"供应商密钥","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"命令","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. 审计当前配置","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 ~/clawd/skills/provider-key-manager/scripts/manager.py audit","type":"text"}]},{"type":"paragraph","content":[{"text":"扫描所有 agent 的 models.json,报告:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ 使用 ","type":"text"},{"text":"${ENV_VAR}","type":"text","marks":[{"type":"code_inline"}]},{"text":" 引用的 provider","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"❌ 硬编码 apiKey 的 provider","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"⚠️ key 值不一致的 agent","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. 迁移到环境变量模式","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 ~/clawd/skills/provider-key-manager/scripts/manager.py migrate [--provider zai] [--dry-run]","type":"text"}]},{"type":"paragraph","content":[{"text":"自动执行:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"将 ","type":"text"},{"text":"openclaw.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中的硬编码 key 移入 ","type":"text"},{"text":"env.vars","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"替换 provider apiKey 为 ","type":"text"},{"text":"\"${ENV_VAR}\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"从各 agent models.json 中","type":"text"},{"text":"删除","type":"text","marks":[{"type":"strong"}]},{"text":" apiKey 字段(继承全局)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"更新 ","type":"text"},{"text":"pass","type":"text","marks":[{"type":"code_inline"}]},{"text":" 存储","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"生成迁移报告","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. 更换 API Key","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 ~/clawd/skills/provider-key-manager/scripts/manager.py update \u003cprovider> \u003cnew-key> [--base-url \u003curl>]","type":"text"}]},{"type":"paragraph","content":[{"text":"一条命令完成:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"更新 ","type":"text"},{"text":"env.vars","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中的 key","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"如有 agent 仍硬编码,同步更新","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"更新 ","type":"text"},{"text":"pass","type":"text","marks":[{"type":"code_inline"}]},{"text":" 存储","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"测试 key 可用性","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"输出重启命令","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. 测试 Key 可用性","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 ~/clawd/skills/provider-key-manager/scripts/manager.py test \u003cprovider>","type":"text"}]},{"type":"paragraph","content":[{"text":"对指定 provider 发送最小请求,验证 key 有效。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. 查看 Provider 总览","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 ~/clawd/skills/provider-key-manager/scripts/manager.py status","type":"text"}]},{"type":"paragraph","content":[{"text":"显示所有 provider 的 key 来源、baseUrl、模型列表。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Provider ↔ 环境变量映射","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provider","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"环境变量","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pass 路径","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"zai","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ZAI_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/zai","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"xingjiabiapi","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"XINGJIABIAPI_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/xingjiabiapi","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"xai","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"XAI_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/xai","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"xingsuancode","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"XINGSUANCODE_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/xingsuancode","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"moonshot","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MOONSHOT_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/kimi","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"minimax","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MINIMAX_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/minimax","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"xinyuan","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"XINYUAN_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/xinyuan","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"boluobao","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BOLUOBAO_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/boluobao","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"google","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GOOGLE_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"api/google-ai-studio","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"架构图","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"┌─────────────────────────────────┐\n│ openclaw.json │\n│ ┌───────────────────────┐ │\n│ │ env.vars │ │\n│ │ ZAI_API_KEY: \"xxx\" │ ← 单一真相源 (Single Source of Truth)\n│ │ XAI_API_KEY: \"yyy\" │ │\n│ └───────────┬───────────┘ │\n│ │ ${ZAI_API_KEY} │\n│ ┌───────────▼───────────┐ │\n│ │ models.providers.zai │ │\n│ │ apiKey: \"${ZAI_API_KEY}\" │\n│ │ baseUrl: \"https://...\" │ │\n│ └───────────────────────┘ │\n└─────────────────────────────────┘\n │ 继承\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n agent/ agent/ agent/\n main/ ops/ code/ ...\n models.json models.json\n (无 apiKey,继承全局)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"注意事项","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"env.vars 中的 key 是明文","type":"text","marks":[{"type":"strong"}]},{"text":" — 但 openclaw.json 已在 .gitignore,不会被 commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"per-agent models.json 仍需保留 provider 结构","type":"text","marks":[{"type":"strong"}]},{"text":" — 只是删除 apiKey 字段","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"迁移后换 key 流程","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"manager.py update zai \u003cnew-key>","type":"text","marks":[{"type":"code_inline"}]},{"text":" → Gateway 重启 → 完成","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"回退方案","type":"text","marks":[{"type":"strong"}]},{"text":":迁移前自动备份所有 models.json 到 ","type":"text"},{"text":"/tmp/provider-key-backup/","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"provider-key-manager","author":"@skillopedia","source":{"stars":65,"repo_name":"claude-code-skills","origin_url":"https://github.com/aaaaqwq/claude-code-skills/blob/HEAD/skills/provider-key-manager/SKILL.md","repo_owner":"aaaaqwq","body_sha256":"11c4f3c94efaa2292c72c7aedc415f577878aaa78f8c3d2120ae39a3366354ae","cluster_key":"228437dbdfe4984abfbcd280bb69a181488e3cc998ec2a974189d209b7eea117","clean_bundle":{"format":"clean-skill-bundle-v1","source":"aaaaqwq/claude-code-skills/skills/provider-key-manager/SKILL.md","attachments":[{"id":"f38a659a-0936-5aa9-a0b4-781bffde9d62","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f38a659a-0936-5aa9-a0b4-781bffde9d62/attachment.py","path":"scripts/manager.py","size":21148,"sha256":"a2a685689aee5d419e76875714e537f6272841b19e4bf503c3bfbaf522238ee6","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"b29726f6bce5511949648ba6fa9139b2d668ebf72500541092e7476f4365af33","attachment_count":1,"text_attachments":1,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/provider-key-manager/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"clean-skills-v1","description":"Provider key manager — rotate and sync API keys across multi-agent workspaces"}},"renderedAt":1782987602468}

Provider Key Manager 一条命令更换供应商 API Key,全员生效,零遗漏。 痛点 OpenClaw 多 agent 架构下,每个 agent 都有独立的 ,更换一个 provider key 需要: 1. 修改 全局配置 2. 修改 13+ 个 agent 的 3. 更新 存储 4. 重启 Gateway 5. 逐个验证 一个 key 要改 15+ 处,漏改一个就报错。 方案:环境变量统一引用 OpenClaw 原生支持 语法引用环境变量: 核心原则 : - API Key 只存在 中(单一真相源) - 所有 provider 的 用 引用 - 各 agent 的 删除 apiKey 字段 ,继承全局 provider - 换 key = 改一个 env var + 重启,全员自动生效 触发词 , , , , , , 命令 1. 审计当前配置 扫描所有 agent 的 models.json,报告: - ✅ 使用 引用的 provider - ❌ 硬编码 apiKey 的 provider - ⚠️ key 值不一致的 agent 2. 迁移到环境变量模式 自动执行: 1. 将 中的硬编码 key 移入 2. 替换 provider apiKey 为 3. 从各 agent models.json 中 删除 apiKey 字段(继承全局) 4. 更新 存储…