feat: 支持全局配置文件 ~/.openclaw/.env

新特性:
- load_global_env(): 自动加载 ~/.openclaw/.env
- 配置加载优先级:环境变量 > 全局配置 > 默认值
- 所有 skill 共享同一份 CLIENT_KEY,无需重复配置
- 支持多 agent skill 配置扩展

文件结构:
~/.openclaw/
├── .env          # 全局配置(手动创建)
└── .env.example  # 配置模板

优势:
-  一处配置,所有 skill 共享
-  更换 KEY 只需修改一个文件
-  新 skill 无需重复配置
-  支持多 agent skill 扩展
This commit is contained in:
ivanberry 2026-03-11 20:34:31 +08:00
parent d8bf7754a1
commit db3e4ba348
2 changed files with 227 additions and 101 deletions

View File

@ -2,71 +2,53 @@
Python 版本的 OpenClaw Auth Runtime。 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 ```bash
│ └── load_env.py # 从 python_auth_runtime 复制 # ~/.openclaw/.env
└── pyproject.toml
# 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 ```bash
uv pip install /path/to/python_auth_runtime 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 ```python
from python_auth_runtime import create_env_config, request_api_with_auto_refresh from python_auth_runtime import create_env_config, request_api_with_auto_refresh
# 创建配置(自动从环境变量读取) # 自动从 ~/.openclaw/.env 加载配置
config = create_env_config() config = create_env_config()
# 调用 API # 调用 API
@ -77,51 +59,49 @@ response = request_api_with_auto_refresh(
config=config, config=config,
body={"url": "https://detail.1688.com/offer/123.html"}, 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 ```python
#!/usr/bin/env python3 #!/usr/bin/env python3
# your-skill/scripts/main.py # 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(): from python_auth_runtime import create_env_config, request_api_with_auto_refresh
"""加载 .env.local 文件到环境变量""" import json
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
def main(): def main():
# 加载 .env.local # 自动从 ~/.openclaw/.env 加载配置
load_env_local()
# 创建配置
config = create_env_config() 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( response = request_api_with_auto_refresh(
method="POST", method="POST",
url=f"{config.auth_base}/ecom/tasks/scrape", url=f"{config.auth_base}/ecom/tasks/scrape",
@ -130,14 +110,92 @@ def main():
body={"url": "https://detail.1688.com/offer/123.html"}, body={"url": "https://detail.1688.com/offer/123.html"},
) )
print(f"状态:{response.status}") if response.status == 200:
print(f"响应:{response.body}") data = json.loads(response.body)
print("商品价格:", data.get("price"))
else:
print(f"❌ 失败:{response.status}")
print(response.body)
return 0
if __name__ == "__main__": 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_BASE` | `https://api-gw-test.yuanwei-lnc.com` | 认证基础 URL |
| `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 | | `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 |
| `AUTH_MIN_TTL_SEC` | `60` | 最小令牌 TTL | | `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 uv run python scripts/example_usage.py
``` ```
## 与 TypeScript 版本对比 ---
| 特性 | TypeScript | Python | ## 项目结构
|------|-----------|--------|
| 模块 | `@clawd/auth-runtime` | `python_auth_runtime` | ```
| .env 加载 | skill 自己实现 (`loadEnvLocal()`) | skill 自己实现 (`load_env_local()`) | python_auth_runtime/
| 环境变量 | `process.env` | `os.getenv()` | ├── src/python_auth_runtime/
| 缓存 | `/tmp/skill-auth-cache` | `/tmp/skill-auth-cache` | │ └── __init__.py # 核心实现(自动加载全局配置)
├── scripts/
│ ├── load_env.py # .env 加载工具(可选使用)
│ └── example_usage.py # 使用示例
├── pyproject.toml
└── README.md
```
---
## 许可证
MIT

View File

@ -6,6 +6,11 @@
基于 ~/clawd/skills/_shared/auth-runtime TypeScript 实现 基于 ~/clawd/skills/_shared/auth-runtime TypeScript 实现
提供 Python 版本的客户端密钥认证 提供 Python 版本的客户端密钥认证
配置加载优先级
1. 环境变量最高优先级
2. 全局配置文件 ~/.openclaw/.env
3. 默认值
使用方式 使用方式
from python_auth_runtime import create_env_config, get_access_token, request_api_with_auto_refresh 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', '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: def create_env_config() -> EnvConfig:
""" """
从环境变量创建配置 从环境变量创建配置
加载优先级
1. 环境变量最高优先级
2. 全局配置文件 ~/.openclaw/.env
3. 默认值
环境变量 环境变量
- AUTH_BASE: 认证基础 URL默认https://api-gw-test.yuanwei-lnc.com - AUTH_BASE: 认证基础 URL默认https://api-gw-test.yuanwei-lnc.com
- CLIENT_KEY: 客户端密钥必需 - CLIENT_KEY: 客户端密钥必需
- AUTH_CACHE_DIR: 缓存目录默认/tmp/skill-auth-cache - AUTH_CACHE_DIR: 缓存目录默认/tmp/skill-auth-cache
- AUTH_MIN_TTL_SEC: 最小令牌 TTL 秒数默认60 - AUTH_MIN_TTL_SEC: 最小令牌 TTL 秒数默认60
""" """
# 先尝试加载全局配置
load_global_env()
auth_base = os.getenv("AUTH_BASE", "https://api-gw-test.yuanwei-lnc.com").rstrip("/") auth_base = os.getenv("AUTH_BASE", "https://api-gw-test.yuanwei-lnc.com").rstrip("/")
client_key = os.getenv("CLIENT_KEY", "") client_key = os.getenv("CLIENT_KEY", "")
auth_cache_dir = os.getenv("AUTH_CACHE_DIR", "/tmp/skill-auth-cache") 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: 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} payload = {"clientKey": config.client_key}
url = f"{config.auth_base}/auth/skill-credit/session" url = f"{config.auth_base}/auth/skill-credit/session"
@ -198,7 +257,7 @@ def get_access_token(dry_run: bool, config: EnvConfig) -> str:
return "<dry-run-token>" return "<dry-run-token>"
if not config.client_key: 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) 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) cached_token = read_cached_token(cache_file, config.auth_min_ttl_sec)
@ -302,6 +361,7 @@ __all__ = [
"EnvConfig", "EnvConfig",
"SessionResponse", "SessionResponse",
"ApiResponse", "ApiResponse",
"load_global_env",
"create_env_config", "create_env_config",
"get_access_token", "get_access_token",
"refresh_access_token", "refresh_access_token",