From db3e4ba34828a0a2157fffbead8cc481393b9b29 Mon Sep 17 00:00:00 2001 From: ivanberry Date: Wed, 11 Mar 2026 20:34:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=20~/.openclaw/.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新特性: - load_global_env(): 自动加载 ~/.openclaw/.env - 配置加载优先级:环境变量 > 全局配置 > 默认值 - 所有 skill 共享同一份 CLIENT_KEY,无需重复配置 - 支持多 agent skill 配置扩展 文件结构: ~/.openclaw/ ├── .env # 全局配置(手动创建) └── .env.example # 配置模板 优势: - ✅ 一处配置,所有 skill 共享 - ✅ 更换 KEY 只需修改一个文件 - ✅ 新 skill 无需重复配置 - ✅ 支持多 agent skill 扩展 --- python_auth_runtime/README.md | 264 +++++++++++------- .../src/python_auth_runtime/__init__.py | 64 ++++- 2 files changed, 227 insertions(+), 101 deletions(-) diff --git a/python_auth_runtime/README.md b/python_auth_runtime/README.md index b42c761..49bc6f3 100644 --- a/python_auth_runtime/README.md +++ b/python_auth_runtime/README.md @@ -2,71 +2,53 @@ Python 版本的 OpenClaw Auth Runtime。 -**注意**: 本模块**不加载 .env 文件**,只从环境变量读取配置。 +## 全局配置 -加载 `.env.local` 应该在具体的 skill 中实现。 +**所有 skill 共享同一份全局配置**,无需在每个 skill 中重复配置。 -## 项目结构 +### 创建全局配置文件 +```bash +# 复制模板 +cp ~/.openclaw/.env.example ~/.openclaw/.env + +# 编辑配置文件 +vi ~/.openclaw/.env ``` -your-skill/ -├── .env.local # 敏感配置(不提交到 git) -├── scripts/ -│ ├── main.py # 加载 .env.local 并调用 auth_runtime -│ └── load_env.py # 从 python_auth_runtime 复制 -└── pyproject.toml + +### 配置文件内容 + +```bash +# ~/.openclaw/.env + +# Auth Runtime 配置 +CLIENT_KEY=sk_ae28fc4e.xxx +AUTH_BASE=https://api-gw-test.yuanwei-lnc.com + +# Gemini API 配置(可选) +GEMINI_API_KEY=your-gemini-api-key ``` +**注意**: `~/.openclaw/.env` 包含敏感信息,不要提交到 git。 + +--- + ## 安装 ```bash uv pip install /path/to/python_auth_runtime ``` +--- + ## 使用方式 -### 步骤 1: 在具体 skill 中加载 .env.local - -参考 `~/clawd/skills/1688-product-master/scripts/run.ts` 的 `loadEnvLocal()` 实现: - -```python -# your-skill/scripts/main.py -from pathlib import Path -import os - -def load_env_local(): - """加载 .env.local 文件到环境变量""" - script_dir = Path(__file__).parent - env_local_path = script_dir.parent / ".env.local" - - if env_local_path.exists(): - with open(env_local_path, "r", encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line or line.startswith("#"): - continue - eq_index = line.find("=") - if eq_index > 0: - key = line[:eq_index].strip() - value = line[eq_index + 1:].strip() - # 去除引号 - if (value.startswith('"') and value.endswith('"')) or \ - (value.startswith("'") and value.endswith("'")): - value = value[1:-1] - # 只设置尚未存在的环境变量 - if key not in os.environ: - os.environ[key] = value - -# 在 main() 函数开始时调用 -load_env_local() -``` - -### 步骤 2: 使用 auth_runtime +### 最简单的用法 ```python from python_auth_runtime import create_env_config, request_api_with_auto_refresh -# 创建配置(自动从环境变量读取) +# 自动从 ~/.openclaw/.env 加载配置 config = create_env_config() # 调用 API @@ -77,51 +59,49 @@ response = request_api_with_auto_refresh( config=config, body={"url": "https://detail.1688.com/offer/123.html"}, ) - -if response.status == 200: - import json - data = json.loads(response.body) - print("商品价格:", data["price"]) ``` -### 完整示例 +### 配置加载优先级 + +1. **环境变量**(最高优先级) + ```bash + export CLIENT_KEY="override-key" + python script.py + ``` + +2. **全局配置文件** `~/.openclaw/.env` + ```bash + CLIENT_KEY=sk_ae28fc4e.xxx + ``` + +3. **默认值** + - `AUTH_BASE`: `https://api-gw-test.yuanwei-lnc.com` + - `AUTH_CACHE_DIR`: `/tmp/skill-auth-cache` + - `AUTH_MIN_TTL_SEC`: `60` + +--- + +## 完整示例 ```python #!/usr/bin/env python3 # your-skill/scripts/main.py -import os -from pathlib import Path -from python_auth_runtime import create_env_config, request_api_with_auto_refresh -def load_env_local(): - """加载 .env.local 文件到环境变量""" - script_dir = Path(__file__).parent - env_local_path = script_dir.parent / ".env.local" - - if env_local_path.exists(): - with open(env_local_path, "r", encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line or line.startswith("#"): - continue - eq_index = line.find("=") - if eq_index > 0: - key = line[:eq_index].strip() - value = line[eq_index + 1:].strip() - if (value.startswith('"') and value.endswith('"')) or \ - (value.startswith("'") and value.endswith("'")): - value = value[1:-1] - if key not in os.environ: - os.environ[key] = value +from python_auth_runtime import create_env_config, request_api_with_auto_refresh +import json def main(): - # 加载 .env.local - load_env_local() - - # 创建配置 + # 自动从 ~/.openclaw/.env 加载配置 config = create_env_config() - # 调用 API + # 验证配置 + if not config.client_key: + print("❌ CLIENT_KEY not found!") + print("Please create ~/.openclaw/.env with your CLIENT_KEY") + print("See: ~/.openclaw/.env.example") + return 1 + + # 调用 1688 API response = request_api_with_auto_refresh( method="POST", url=f"{config.auth_base}/ecom/tasks/scrape", @@ -130,14 +110,92 @@ def main(): body={"url": "https://detail.1688.com/offer/123.html"}, ) - print(f"状态:{response.status}") - print(f"响应:{response.body}") + if response.status == 200: + data = json.loads(response.body) + print("商品价格:", data.get("price")) + else: + print(f"❌ 失败:{response.status}") + print(response.body) + + return 0 if __name__ == "__main__": - main() + exit(main()) ``` -## 环境变量 +--- + +## API 参考 + +### 配置 + +#### `create_env_config() -> EnvConfig` + +从环境变量创建配置(自动加载 `~/.openclaw/.env`)。 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `AUTH_BASE` | `https://api-gw-test.yuanwei-lnc.com` | 认证基础 URL | +| `CLIENT_KEY` | **必需** | 客户端密钥(从全局配置加载) | +| `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 | +| `AUTH_MIN_TTL_SEC` | `60` | 最小令牌 TTL(秒) | + +### 令牌管理 + +#### `get_access_token(dry_run, config) -> str` + +获取访问令牌(带缓存)。 + +#### `refresh_access_token(dry_run, config) -> str` + +刷新访问令牌(绕过缓存)。 + +### API 请求 + +#### `request_api(method, url, auth_token, body) -> ApiResponse` + +发送 HTTP 请求。 + +#### `request_api_with_auto_refresh(method, url, dry_run, config, body) -> ApiResponse` + +发送 API 请求并自动刷新令牌。 + +--- + +## 多 Agent Skill 配置 + +对于多 Agent skill,可以在全局配置中添加: + +```bash +# ~/.openclaw/.env + +# 主配置 +CLIENT_KEY=sk_ae28fc4e.xxx + +# Agent 特定配置 +AGENT_1_CLIENT_KEY=sk_agent1.xxx +AGENT_2_CLIENT_KEY=sk_agent2.xxx + +# 或者使用统一配置 +# 所有 agent 共享同一个 CLIENT_KEY +``` + +在代码中: + +```python +import os +from python_auth_runtime import create_env_config + +# 主配置 +config = create_env_config() + +# 或者访问特定 agent 的配置 +agent1_key = os.getenv("AGENT_1_CLIENT_KEY", config.client_key) +``` + +--- + +## 环境变量列表 | 变量 | 默认值 | 说明 | |------|--------|------| @@ -145,14 +203,10 @@ if __name__ == "__main__": | `AUTH_BASE` | `https://api-gw-test.yuanwei-lnc.com` | 认证基础 URL | | `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 | | `AUTH_MIN_TTL_SEC` | `60` | 最小令牌 TTL(秒) | +| `GEMINI_API_KEY` | - | Gemini API Key(用于翻译) | +| `ECOM_BASE` | - | 1688 API 基础 URL | -## 命令行参数覆盖 - -可以在命令行设置环境变量,优先级高于 `.env.local`: - -```bash -CLIENT_KEY="override-key" python scripts/main.py -``` +--- ## 测试 @@ -161,11 +215,23 @@ cd /Users/xiaolongxia/Documents/ai-build-app/skills/excel-toolkit/python_auth_ru uv run python scripts/example_usage.py ``` -## 与 TypeScript 版本对比 +--- -| 特性 | TypeScript | Python | -|------|-----------|--------| -| 模块 | `@clawd/auth-runtime` | `python_auth_runtime` | -| .env 加载 | skill 自己实现 (`loadEnvLocal()`) | skill 自己实现 (`load_env_local()`) | -| 环境变量 | `process.env` | `os.getenv()` | -| 缓存 | `/tmp/skill-auth-cache` | `/tmp/skill-auth-cache` | +## 项目结构 + +``` +python_auth_runtime/ +├── src/python_auth_runtime/ +│ └── __init__.py # 核心实现(自动加载全局配置) +├── scripts/ +│ ├── load_env.py # .env 加载工具(可选使用) +│ └── example_usage.py # 使用示例 +├── pyproject.toml +└── README.md +``` + +--- + +## 许可证 + +MIT diff --git a/python_auth_runtime/src/python_auth_runtime/__init__.py b/python_auth_runtime/src/python_auth_runtime/__init__.py index 5a4a689..57de4e3 100644 --- a/python_auth_runtime/src/python_auth_runtime/__init__.py +++ b/python_auth_runtime/src/python_auth_runtime/__init__.py @@ -6,6 +6,11 @@ 基于 ~/clawd/skills/_shared/auth-runtime 的 TypeScript 实现, 提供 Python 版本的客户端密钥认证。 +配置加载优先级: +1. 环境变量(最高优先级) +2. 全局配置文件 ~/.openclaw/.env +3. 默认值 + 使用方式: from python_auth_runtime import create_env_config, get_access_token, request_api_with_auto_refresh @@ -77,17 +82,71 @@ RETRYABLE_BODY_MARKERS = [ 'client key revoked', ] +# 全局配置文件路径 +GLOBAL_ENV_PATHS = [ + Path.home() / ".openclaw" / ".env", + Path.home() / ".clawd" / ".env", + Path.home() / "clawd" / ".env", +] + + +def load_global_env() -> bool: + """ + 从全局配置文件加载环境变量 + + 加载优先级: + 1. ~/.openclaw/.env + 2. ~/.clawd/.env + 3. ~/clawd/.env + + 只加载尚未存在的环境变量(命令行参数优先级更高) + + Returns: + bool: 是否成功加载 + """ + for env_path in GLOBAL_ENV_PATHS: + if env_path.exists(): + print(f"📄 加载全局配置:{env_path}") + with open(env_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + eq_index = line.find("=") + if eq_index > 0: + key = line[:eq_index].strip() + value = line[eq_index + 1:].strip() + # 去除引号 + if (value.startswith('"') and value.endswith('"')) or \ + (value.startswith("'") and value.endswith("'")): + value = value[1:-1] + # 只加载尚未存在的环境变量 + if key not in os.environ: + os.environ[key] = value + print(f" ✓ {key} = {value[:10]}..." if len(str(value)) > 10 else f" ✓ {key} = {value}") + return True + + return False + def create_env_config() -> EnvConfig: """ 从环境变量创建配置 + 加载优先级: + 1. 环境变量(最高优先级) + 2. 全局配置文件 ~/.openclaw/.env + 3. 默认值 + 环境变量: - AUTH_BASE: 认证基础 URL(默认:https://api-gw-test.yuanwei-lnc.com) - CLIENT_KEY: 客户端密钥(必需) - AUTH_CACHE_DIR: 缓存目录(默认:/tmp/skill-auth-cache) - AUTH_MIN_TTL_SEC: 最小令牌 TTL 秒数(默认:60) """ + # 先尝试加载全局配置 + load_global_env() + auth_base = os.getenv("AUTH_BASE", "https://api-gw-test.yuanwei-lnc.com").rstrip("/") client_key = os.getenv("CLIENT_KEY", "") auth_cache_dir = os.getenv("AUTH_CACHE_DIR", "/tmp/skill-auth-cache") @@ -167,7 +226,7 @@ def fetch_session_json(dry_run: bool, config: EnvConfig) -> SessionResponse: ) if not config.client_key: - raise ValueError("CLIENT_KEY is required") + raise ValueError("CLIENT_KEY is required. Please set it in ~/.openclaw/.env or environment variable.") payload = {"clientKey": config.client_key} url = f"{config.auth_base}/auth/skill-credit/session" @@ -198,7 +257,7 @@ def get_access_token(dry_run: bool, config: EnvConfig) -> str: return "" if not config.client_key: - raise ValueError("CLIENT_KEY is required") + raise ValueError("CLIENT_KEY is required. Please set it in ~/.openclaw/.env or environment variable.") cache_file = get_cache_file(config.auth_base, config.client_key, config.auth_cache_dir) cached_token = read_cached_token(cache_file, config.auth_min_ttl_sec) @@ -302,6 +361,7 @@ __all__ = [ "EnvConfig", "SessionResponse", "ApiResponse", + "load_global_env", "create_env_config", "get_access_token", "refresh_access_token",