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:
parent
d8bf7754a1
commit
db3e4ba348
|
|
@ -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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 "<dry-run-token>"
|
||||
|
||||
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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue