Metadata-Version: 2.4
Name: opsai-cli
Version: 0.1.0
Summary: Command-line client for the OpsAI Open API
License-Expression: LicenseRef-Proprietary
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1,>=0.27
Requires-Dist: keyring<26,>=25
Requires-Dist: pydantic<3,>=2.7
Dynamic: license-file

# OpsAI CLI

独立的 OpsAI Open API 命令行客户端。当前已实现 M1–M5，包括业务调用、任务等待、重试、SSE 与停止，尚未发布到 PyPI。

交付文档：[快速开始](docs/quickstart.md)、[完整命令参考](docs/commands.md)、[兼容性与发布验证](docs/release-validation.md)、[版本记录](CHANGELOG.md)。M6 的本地 QA 和跨平台 CI 配置已实现，未运行的系统/版本仍需验收，不能将通用 wheel 标签当作全部平台已通过实测。

要求 Python 3.11 或以上。运行时依赖 HTTPX、keyring 和 Pydantic，不加载后端、数据库或平台 SDK。许可证暂保留专有权利，正式客户使用和分发条款须在公开发布前确认。

2026-10-31 已在 Windows / Python 3.12.11 完成本地构建和安装验证；其他系统及 Python 版本的实际验证纳入 M6。该日 PyPI JSON 查询返回 404，未发现同名项目，但这不代表名称已保留或保证发布时可用。

## 本地构建

在本目录下运行（Windows PowerShell）：

```powershell
py -m venv .venv-build
.\.venv-build\Scripts\python.exe -m pip install build
.\.venv-build\Scripts\python.exe -m build
```

产物为 `dist/opsai_cli-0.1.0-py3-none-any.whl` 和 `dist/opsai_cli-0.1.0.tar.gz`。

## 在干净环境中安装

```powershell
py -m venv .venv-smoke
.\.venv-smoke\Scripts\python.exe -m pip install dist/opsai_cli-0.1.0-py3-none-any.whl
.\.venv-smoke\Scripts\opsai.exe --help
.\.venv-smoke\Scripts\opsai.exe --version
.\.venv-smoke\Scripts\python.exe -m opsai_cli --help
.\.venv-smoke\Scripts\python.exe -m opsai_cli --version
```

Linux/macOS 使用 `python3` 创建虚拟环境，并将 `Scripts/python.exe`、`Scripts/opsai.exe` 替换为 `bin/python`、`bin/opsai`。

安装环境的命令目录在 PATH 中时，可以直接运行 `opsai`。无参数显示帮助，未知参数/命令返回退出码 2；帮助和版本返回 0。

## M2 配置和凭证

CLI 内置官网地址 `https://www.krossray.com`，无需配置或指定服务地址。

```powershell
# 默认账号：直接隐藏输入并保存 API Key
opsai auth configure

# 可选：使用命名 profile 区分多个账号的凭证
opsai auth configure --profile customer-a
```

环境变量 `OPSAI_API_KEY` 可直接用于 CI，无需配置文件或系统凭证；`OPSAI_PROFILE` 可选账号，显式 `--profile` 优先。环境变量 Key 优先于系统凭证，空值视为错误。配置成功不表示服务端权限已经验证。

密钥保存到 Windows Credential Locker、macOS Keychain 或 Linux Secret Service。只使用这些内置系统后端，不使用明文、空实现或链式回退；后端不可用时使用环境变量，不会自动写入明文文件。无交互终端时认证命令直接报错，不从 stdin 读取 Key。

`config set-profile <name>` 仅登记可选账号名，`config profiles` 列出已登记账号，未登记时显示默认官网入口；不支持 --base-url。配置文件不保存 Key。历史配置中的自定义 base_url 被忽略，旧地址绑定的凭证不会发送到官网，需要重新配置官网 Key。

非敏感配置位于 Windows `%APPDATA%/opsai/config.json`，Linux/macOS `$XDG_CONFIG_HOME/opsai/config.json`（默认 `~/.config/opsai/config.json`）。`OPSAI_CONFIG_DIR` 可指定目录。无需配置文件即可使用默认账号。多进程修改可选配置应串行执行。

## M2 HTTP 基础层

`ApiClient.from_profile()` 使用固定官网地址并读取所选账号凭证，`request_json(method, path, params=..., json=...)` 接受 `/listing/v1/stores` 等相对路径，统一拼接 `/api/open`，携带 Bearer Key。该层是后续命令使用的内部接口，当前不提供任意 URL 请求命令。

证书校验始终开启，不跟随重定向，不读取代理和证书相关环境变量（HTTPX trust_env=False）；需要代理或企业 CA 的场景待独立评估。默认连接超时最多 10 秒，读写/连接池超时 30 秒，可通过构造参数 request_timeout 调整；HTTPX 超时是各网络阶段限制，不是任务等待总时长。

HTTP 原始传输层不重试；业务命令通过任务编排层进行受控重试和轮询。401/403 映射退出码 3，API 拒绝映射 4，网络/超时/协议/服务故障映射 5，本地配置错误映射 2。HTTP 错误响应不回显服务端原文或底层异常，避免响应或异常中包含 Key；成功响应保留 API 数据。

所有命令支持 table/json/ndjson 输出，结果写 stdout，错误及进度写 stderr。没有交互终端时 auth configure 直接报错，提示使用 OPSAI_API_KEY，不读取 stdin 中的密钥。

## 测试

测试针对安装后的包，可从任意目录执行以下测试文件；不需要 pytest 或后端测试配置：

```powershell
.\.venv-smoke\Scripts\python.exe -I -m unittest discover -s tests -v
```

不要在当前阶段使用 `pip install opsai-cli` 验证本地成果：名称尚未保留，PyPI 上的同名包可能不属于本项目。M7/M8 才进行经批准的远程发布。

## M3 基础业务命令

服务地址固定为官网，配置 API Key 后可调用以下命令。普通 submit 提交并返回 task_id，task get 查询一次；可用 --wait 或 task watch 等待，临时故障按下述规则重试。JSON 输出保留 API 响应结构。

```bash
opsai auth configure
opsai listing stores --platform 9
opsai activity stores
opsai optimization stores

opsai listing template --out listing.json
# 修改示例店铺、站点、价格、SKU、图片及稳定 request_id 后提交
opsai listing validate --file listing.json
opsai listing submit --file listing.json
opsai listing task get 12345

opsai activity actions --store-id 1001 --listing-sku L-0001
opsai activity template --kind prices --out prices.json
opsai activity prices --file prices.json
opsai activity template --out activity.json
opsai activity submit --file activity.json
opsai activity task get 12345

opsai optimization submit --store-id 1001 --listing-sku L-0001 --mode update --request-id your-stable-request-id
opsai optimization task get 12345
```

所有实际查询/提交命令均可在命令末尾添加 --profile，也可在业务组之前指定全局 --profile。三个业务组均提供 template、validate；默认操作为 submit。activity 的 --kind 另支持 actions、prices：

```bash
opsai activity template --kind actions --out actions.json
opsai activity validate --kind actions --file actions.json
opsai activity actions --file actions.json
opsai activity validate --kind prices --file prices.json
opsai optimization template --out optimization.json
opsai optimization validate --file optimization.json
```

标准输入使用 --file -。Linux/macOS 可用 `cat listing.json | opsai listing submit --file -`；PowerShell 可用 `Get-Content -Raw -Encoding utf8 listing.json | opsai listing submit --file -`，管道输出须采用 UTF-8。文件和管道读取均接受 UTF-8 BOM，单次最多 10 MiB，拒绝重复 JSON 字段和非有限金额。

- 文件输入与快捷业务参数互斥，不自动覆盖或合并。上架/活动填报使用文件；优化提交和活动 actions 可使用快捷参数。
- request_id 必须明确提供，CLI 不自动生成或更换。重试应保持请求号和内容；跨三个业务族也不能复用请求号。
- 本地校验与服务端 API v1 请求模型保持一致，包括严格类型、额外字段拒绝、图片 URL、重复项及数量限制。校验成功不代表店铺授权、商品归属或平台受理已通过。
- template/validate 无需凭证、不发 HTTP 请求；模板目标已存在时必须显式 --force 才覆盖。模板是结构示例，提交前务必修改示例值；不保证示例图片实际可下载。
- 三类 stores 的平台筛选：上架/优化当前为 1、2、3、9、10、11、12，活动仅 9。任务 ID 必须为正整数。
- 查询或受理返回 code=0 时 CLI 返回 0；task get 查询成功，即使任务结果是 failed，也返回 0，调用方需检查业务状态。等待模式检查最终状态和逐项结果，部分失败返回 1。
- 活动不提供 --stream 或 task stop；上架和优化均支持。

完整请求示例见 examples/。独立客户端 contracts.py 是版本化服务端模型快照；维护时更新快照并运行仓库契约比对测试，运行和安装时均不导入后端。

## M4/M5 任务、实时日志及停止

```bash
# 三类业务均支持等待；只输出最终 JSON，诊断和任务号写 stderr
opsai listing submit --file listing.json --wait --output json
opsai activity submit --file activity.json --wait
opsai optimization submit --file optimization.json --wait
opsai listing task watch 12345 --poll-interval 5 --wait-timeout 1800

# 上架和优化支持流式提交；不能与 --wait 同时使用
opsai listing submit --file listing.json --stream --output ndjson
opsai optimization submit --file optimization.json --stream --output json

# 显式停止，返回实际终态，不回滚平台已接受的操作
opsai listing task stop 12345
opsai optimization task stop 12345
```

| 参数 | 默认值 / 约束 |
| --- | --- |
| --request-timeout | 30 秒，有限正数；HTTPX 各阶段超时，连接最多 10 秒 |
| --wait-timeout | 1800 秒，有限正数；等待、流式提交和停止的本地预算，包含受理和退避时间 |
| --poll-interval | 5 秒，至少 1 秒 |
| --max-retries | 2 次额外尝试，范围 0～10；每次独立 HTTP 请求的上限 |
| --output | table / json / ndjson；交互终端默认 table，重定向默认 json；支持全局或命令末尾指定 |

普通查询/受理的重试预算为 120 秒。等待预算以单调时钟计时；下一次轮询或 Retry-After 将超出预算时提前结束，不发出不合时限的请求。预算在请求前后和流读取时检查，阻塞网络调用仍按阶段超时生效，不是精确到毫秒的硬中断。SSE 读取空闲超时至少 30 秒，最后剩余预算更短时按剩余值收紧。

网络故障、408/425/429/500/502/503/504 可重试；参数、认证、权限、资源及冲突错误不自动重试。遵循 Retry-After 秒数或带时区 HTTP 日期，无有效头的 429 等待 60 秒；其余退避为 1、2、4…秒，上限 30 秒。请求重试始终复用原规范化请求体及 request_id。不会在业务失败后自动换请求号或重新创建任务。

SSE 是提交接口，task watch 只轮询状态。流式事件包含 accepted、log、result，心跳不输出为结果。收到 task_id 后断流、流内 error 或协议异常会关闭原连接并转为轮询，不再 POST 提交；未取得 task_id 的可重试断流只能用同一请求体受控重试。无法恢复时提示保留原请求号。无 Last-Event-ID 续传；重复订阅可能重放日志。

显式 stop 的网络超时/5xx 响应不确定时，改为查询任务，避免反复发送停止请求；服务端明确 408/425/429 拒绝时可以按上限重试。已结束任务返回原终态，不能假设一定 cancelled。Ctrl+C 只结束本地命令，未显式 stop 时不自动停止任务；显示 task get 恢复命令，未知受理结果时提示复用 request_id。

### 输出和退出码

- json：普通调用保留 API envelope；等待和流式只在 stdout 输出一次最终 envelope。日志、受理 task_id、重试及恢复提示在 stderr。
- ndjson：每行 {event,data}；提交输出 accepted，SSE 输出 accepted/log，轮询可输出 status，等待最终 result 的 data 是任务详情。普通查询的 result data 是 API envelope。心跳不输出。
- table：制表符分列并标注业务时区 Asia/Shanghai；JSON 保留原时间，平台源时间不转换。
- 错误记录输出 stderr：机器模式为 {error:{source:"cli",exit_code,message}}；已输出的 NDJSON 事件保留，但失败时不伪造最终结果。当前 Key 和 Bearer 文本会在日志及结果中脱敏。
- 退出码：0 请求/受理成功或等待后全部成功；1 业务失败、部分失败或取消；2 本地输入/配置错误；3 认证/权限错误；4 API 拒绝；5 网络/协议/服务故障或普通请求重试预算耗尽；6 本地等待预算耗尽；130 用户中断。
- task get 只依据查询是否成功返回退出码。上架 completed 仍检查 items：PUBLISH_FAILED 等失败项导致等待命令返回 1；SUBMITTED 只表示提交审核。活动 partial_failed 返回 1。显式 stop 成功确认终态返回 0，包括确认取消或原任务已结束。

测试采用假时钟及 HTTPX MockTransport，不依赖真实时间等待、真实店铺写入或业务数据库。实际平台联调、跨平台凭证和发布验证继续由 M6–M8 完成。
