diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1ad079b --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# YIERP API 连接配置(复制为项目根目录 .env 并填入真实值;.env 禁止入库) +YIERP_URL=https://yierp.example.com +YIERP_APIKEY=你的_api_key +YIERP_APISECRET=你的_api_secret diff --git a/README.zh.md b/README.zh.md index 530d052..6664bb2 100644 --- a/README.zh.md +++ b/README.zh.md @@ -39,8 +39,8 @@ ```bash python tools/yierp_cli.py whoami --config .yierp/config.json - python tools/yierp_cli.py list --doctype ToDo --config .yierp/config.json --limit 10 - python tools/yierp_cli.py create --doctype ToDo --config .yierp/config.json --data '{"description":"测试任务","status":"Open"}' + python tools/yierp_cli.py list --config .yierp/config.json --doctype ToDo --limit 10 + python tools/yierp_cli.py create --config .yierp/config.json --doctype ToDo --data '{"description":"测试任务","status":"Open"}' ``` ## 常用命令 diff --git a/SKILL.md b/SKILL.md index af92362..ea13d66 100644 --- a/SKILL.md +++ b/SKILL.md @@ -7,6 +7,8 @@ description: 适用于 YIERP(源自 ERPNext v16)的数据访问与管理技 这个技能用于访问 YIERP 系统中的业务数据与文档资源。YIERP 继承 ERPNext v16 的 Frappe / REST API 设计,因此可以使用标准的 HTTP API 方式进行 DocType 数据访问、文档操作和后端方法调用。 +> **同步方向**:本技能的工作副本位于项目 `D:\git\giterp\yierp-script\.qoder\skills\yierp-cli\`(自包含技能包,脚本在其 `scripts/`)。变更先更新工作副本,再同步到本仓库(本仓库脚本位于 `tools/`)。 + ## 适用目标 - 通过配置 `url`、`apikey`、`ApiSecret` 建立认证 @@ -14,9 +16,26 @@ description: 适用于 YIERP(源自 ERPNext v16)的数据访问与管理技 - 适合脚本自动化、批量同步、业务查询、报表提取和业务集成 - 所有说明、注释、提示和输出都使用简体中文 + +> **参数顺序**:`--config` / `--url` / `--apikey` / `--apisecret` / `--timeout` / `--no-verify-ssl` 为全局参数,必须写在子命令**之前**,否则会报 `unrecognized arguments`。 + ## 配置方式 -### 1. 配置文件方式 +配置解析优先级:命令行参数 > 环境变量(含 `.env`)> 配置文件。 + +### 1. .env 文件方式(推荐) + +脚本从当前工作目录自动向上查找 `.env`(至含 `.git` 的项目根为止),加载 `YIERP_URL / YIERP_APIKEY / YIERP_APISECRET`,不覆盖已存在的系统环境变量: + +```bash +YIERP_URL=https://yierp.example.com +YIERP_APIKEY=你的_api_key +YIERP_APISECRET=你的_api_secret +``` + +模板见技能包内 `.env.example`。凭证文件 `.env` 必须 gitignore、不入库、不随技能发布。 + +### 2. 配置文件方式 建议在项目中创建 `.yierp/config.json`: @@ -30,7 +49,7 @@ description: 适用于 YIERP(源自 ERPNext v16)的数据访问与管理技 } ``` -### 2. 环境变量方式 +### 3. 环境变量方式 ```bash set YIERP_URL=https://yierp.example.com @@ -38,10 +57,10 @@ set YIERP_APIKEY=你的_api_key set YIERP_APISECRET=你的_api_secret ``` -### 3. 命令行参数方式 +### 4. 命令行参数方式 ```bash -python tools/yierp_cli.py list --doctype ToDo --url https://yierp.example.com --apikey xxx --apisecret yyy +python tools/yierp_cli.py --url https://yierp.example.com --apikey xxx --apisecret yyy list --doctype ToDo ``` ## 关键命令 @@ -49,49 +68,49 @@ python tools/yierp_cli.py list --doctype ToDo --url https://yierp.example.com -- ### 1. 检查当前登录用户 ```bash -python tools/yierp_cli.py whoami --config .yierp/config.json +python tools/yierp_cli.py whoami ``` ### 2. 查询列表 ```bash -python tools/yierp_cli.py list --doctype ToDo --config .yierp/config.json --filters '{"status":"Open"}' --limit 20 +python tools/yierp_cli.py list --doctype ToDo --filters '{"status":"Open"}' --limit 20 ``` ### 3. 获取单条记录 ```bash -python tools/yierp_cli.py get --doctype ToDo --name "TODO-00001" --config .yierp/config.json +python tools/yierp_cli.py get --doctype ToDo --name "TODO-00001" ``` ### 4. 创建记录 ```bash -python tools/yierp_cli.py create --doctype ToDo --config .yierp/config.json --data '{"description":"测试任务","status":"Open"}' +python tools/yierp_cli.py create --doctype ToDo --data '{"description":"测试任务","status":"Open"}' ``` ### 5. 更新记录 ```bash -python tools/yierp_cli.py update --doctype ToDo --name "TODO-00001" --config .yierp/config.json --data '{"status":"Closed"}' +python tools/yierp_cli.py update --doctype ToDo --name "TODO-00001" --data '{"status":"Closed"}' ``` ### 6. 删除记录 ```bash -python tools/yierp_cli.py delete --doctype ToDo --name "TODO-00001" --config .yierp/config.json --yes +python tools/yierp_cli.py delete --doctype ToDo --name "TODO-00001" --yes ``` ### 7. 调用自定义后端方法 ```bash -python tools/yierp_cli.py method --method your_app.api.test --config .yierp/config.json --args '{"message":"hello"}' +python tools/yierp_cli.py method --method your_app.api.test --args '{"message":"hello"}' ``` ### 8. 查看当前配置摘要 ```bash -python tools/yierp_cli.py config --config .yierp/config.json +python tools/yierp_cli.py config ``` ## 认证方式 @@ -144,6 +163,7 @@ Authorization: token {apikey}:{ApiSecret} - 请求体 JSON 是否符合规范 - API 路径是否正确,是否为 `api/resource` 或 `api/method` - SSL 证书错误是否需要使用 `--no-verify-ssl` +- Windows GBK 控制台中文输出乱码:设置环境变量 `PYTHONIOENCODING=utf-8` 后重跑,数据本身不受影响 ## 适用场景 diff --git a/config.example.json b/config.example.json deleted file mode 100644 index 7a52f1f..0000000 --- a/config.example.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "url": "https://yierp.example.com", - "apikey": "你的_api_key", - "ApiSecret": "你的_api_secret", - "timeout": 30, - "verify_ssl": true -} diff --git a/tools/yierp_cli.py b/tools/yierp_cli.py index 3ea1786..649a2c7 100644 --- a/tools/yierp_cli.py +++ b/tools/yierp_cli.py @@ -55,6 +55,49 @@ def _mask_value(value: Optional[str]) -> str: return f"{value[:2]}******{value[-2:]}" +def _parse_env_value(value: str) -> str: + """去除 .env 值两侧空白与包裹的引号。""" + value = value.strip() + if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'): + value = value[1:-1] + return value + + +def _iter_dotenv_candidates() -> list: + """从当前工作目录向上逐级查找 .env,遇到 .git 目录即停止上溯。""" + candidates = [] + cwd = Path.cwd().resolve() + candidates.append(cwd / ".env") + for parent in cwd.parents: + candidates.append(parent / ".env") + if (parent / ".git").exists(): + break + return candidates + + +def load_dotenv() -> None: + """加载 .env 到环境变量(不覆盖已存在的环境变量)。""" + for env_path in _iter_dotenv_candidates(): + if not env_path.is_file(): + continue + try: + text = env_path.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + continue + for line in text.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith("#") or "=" not in stripped: + continue + if stripped.startswith("export "): + stripped = stripped[len("export "):].strip() + key, _, raw_value = stripped.partition("=") + key = key.strip() + if not key or key in os.environ: + continue + os.environ[key] = _parse_env_value(raw_value) + break + + def _load_json_file(file_path: str) -> Dict[str, Any]: """读取 JSON 配置文件。""" path = Path(file_path) @@ -71,7 +114,8 @@ def _load_json_file(file_path: str) -> Dict[str, Any]: def resolve_config(args: argparse.Namespace) -> Dict[str, Any]: - """按优先级解析配置:命令行参数 > 环境变量 > 配置文件。""" + """按优先级解析配置:命令行参数 > 环境变量(含 .env 文件)> 配置文件。""" + load_dotenv() config_path = args.config or os.getenv("YIERP_CONFIG_PATH", ".yierp/config.json") config_data: Dict[str, Any] = _load_json_file(config_path)