Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/aihubmix_platfrom-cli.json
T

1 line
7.9 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "aihubmix/platfrom-cli", "files": {"CLAUDE.md": {"d83dbc139a82619333d5b0f557a8350b42b03583": {"t": 1785768987.4376402, "n": 4845, "h": "0085af8926fe551b", "c": "# CLAUDE.md\n\n面向在本仓库协作的 Claude 的工程笔记。**用中文**(项目注释、文档、提交均为中文)。\n\n## 项目是什么\n\n`aihubmix` CLI:用 Go + cobra 把 AIHubMix **CliEndpoints**(账户/余额查询、API Key 管理、可用模型查询)这组 REST 接口封装成命令行工具。接口文档在 `docs/`(已被 `.gitignore` 忽略,本地可读,不提交)。\n\n## 常用命令\n\n- 构建当前平台:`go build -o aihubmix.exe .`(Win)/ `go build -o aihubmix .`(\\*nix)\n- 交叉编译全平台:`bash scripts/build.sh`(产物 + `checksums.txt` 落到 `dist/`;可传 `linux/amd64` 等只编指定平台)\n- 单元测试:`go test ./...`\n- 直接运行:`go run . <command>` 或 `./aihubmix.exe <command>`\n- 手测/冒烟:`bash scripts/smoke.sh <TOKEN>`(一次输入 token 把所有命令跑一遍;`--read-only` 跳过写操作,`--json` 全 JSON 输出)\n- 发布:push `vX.Y.Z` tag 触发 `.github/workflows/release.yml`(注入 `VERSION` 交叉编译并建 GitHub Release)\n- Go 1.25。\n\n## 凭证与运行时\n\n- 凭证优先级:`--token/-t` > `AIHUBMIX_TOKEN` 环境变量 > `~/.aihubmix/config.json`(`cmd/root.go:setup`)。\n- Base URL:`--base-url/-b`,默认 `https://aihubmix.com`,备用 `https://api.aihubmix.com`。\n- 鉴权头是**裸 token**:`Authorization: <token>`,**无 `Bearer` 前缀**(`internal/api/client.go`)。\n- 全局 flag:`-t/--token`、`-b/--base-url`、`-j/--json`、`--no-color`、`--config`。\n- 不登录、直连真实 API 调试:`./aihubmix.exe me --token <KEY> --base-url <URL> --json`。\n\n## 架构(包职责)\n\n- `main.go` → `cmd.Execute()`;错误统一经 `cmd.PrintError` 映射退出码后 `os.Exit`。\n- `cmd/`:cobra 命令树。`root.go` 装配全局 flag + `setup()` 解析凭证;顶层 `login`/`logout`/`whoami`(status)/`me`(account);`keys/` 子命令组(list/search/get/token/create/update/delete);`models/`。\n- `internal/api`:HTTP 客户端。`client.go` 做统一 envelope 解析与错误映射(**先看 HTTP 状态码,2xx 再看 `success` 字段**);`endpoints.go` 是各接口方法;`errors.go` 定义退出码与错误 Kind。\n- `internal/auth`:保存解析后的运行时凭证,并据此构造 client。\n- `internal/config`:`~/.aihubmix/config.json` 加载/保存 + 平台相关权限收紧。\n- `internal/models`:数据结构与自定义反序列化。\n- `internal/output`:人类可读输出(表格/详情)、`--json` 输出、额度/时间格式化。\n\n## 关键约定 / 不变量\n\n- 统一响应:`{success, message, data}`。业务失败时 `success=false` + `message`。\n- **额度换算**:原始整数 `/ 500000 = 美元`(`api.QuotaPerDollar = 500000`);反向 `美元 × 500000`。`quota`=余额,`used_quota`=已用,`remain_quota`=Key 限额余量(`-1`=无限额)。\n- `--json`:**stdout 永远是干净 JSON**(`me`/`keys`/`models` 透传原始 envelope;`whoami` 输出会话状态对象);**错误一律走 stderr**,带稳定 `code` 字段(`jq`/agent 友好)。\n- **类型漂移兼容**:`expired_time`/`remain_quota` 在不同端点可能是 number 或 string,故用 `models.FlexInt64`;`get-user-token` 的 data 可能是裸字符串或对象,故 `UserToken.UnmarshalJSON` 两种都吃。**新增可能类型不一致的字段时沿用这套,别直接上 `int64`/`string`,否则反序列化会崩或被中断的 `me` 报错。**\n- Windows 配置权限:`config.Save` 收紧 DACL(等效 `0600`);非 NTFS 卷收紧失败会返回包 `ErrHardenDegraded` 的错误,文件仍可用,调用方降级为警告。\n\n## 排查笔记(领域知识,非显而易见)\n\n- **无限额 Key 的 `used_quota` 恒为 0,这不是 CLI 的 bug。** `keys list`/`keys get` 的数据源 `/api/token/` 对 `unlimited_quota: true` 的 Key,上游返回的 `used_quota` 字段**本身就是 `0`**——服务端不需要扣额度,就不回写这个计数。CLI 只是如实透传。\n - 真实用量看**账户维度**:`/api/user/self` 的 `used_quota`(`me` 命令的 `Used` 行)与 `request_count`。官网「用量」页也是从账户级 / 请求日志取数,所以会出现「官网有用量、`keys list` 的 USED 列全是 `$0.00`」。\n - 即:账户级用量(`me`)准确可信;per-key 用量(`keys list`)对无限额 Key 不可信。排查「用量为 0」类问题,先确认该 Key 是不是无限额。\n - 对比验证脚本:`scripts/usage-probe.sh`(同一个 key 同时打账户接口和 token 接口,并和 CLI 输出对照)。\n\n- **完整可用的 Key 只在 create-key 返回一次,字段是 `full_key`,不是 `key`。**(实测,非文档原述)create-key 的 `data` 里 `key` 是**掩码值**(`sk-xxxx****xxxx`,仅供识别、不可调用),`full_key` 才是**完整明文**;而 get-key **只返回掩码 `key`、没有 `full_key`**,事后无法还原。\n - 故 `models.ApiKey` 同时有 `Key`(掩码)与 `FullKey`(完整)。**`keys create` 人类可读输出默认把 `full_key` 写入系统剪贴板**(`atotto/clipboard`),终端只显示掩码 + 绿色「已复制、仅此一次」提示,避免明文残留在回滚缓冲/录屏里;剪贴板不可用时回退到终端明文展示 + 黄色告警(见 `output.PrintCreatedKey`)。`output.PrintKeyDetail`(get/update)只显示掩码 `Key`,不再明文展示 `FullKey`。\n - **`--json` 不变,仍原样透传含明文 `full_key` 的 envelope**(不写剪贴板),脚本照旧取完整 key:`keys create --json | jq -r '.data.full_key'`。别把剪贴板逻辑塞进 `--json` 分支,否则污染 stdout 破坏脚本。验证脚本:`scripts/create-key-probe.sh`。\n\n- **`PUT /api/token/` 是「整体覆盖」而非「局部更新」,且改状态必须带 `?status_only=true`。**(实测,与「只发改动字段,避免误置零」的常规约定正好相反,极易踩坑)上游 update-token 分两支:\n - **不带 `status_only`** → 用请求体**整体覆盖** name/expired_time/remain_quota/unlimited_quota/models/subnet:**请求体里缺的字段会被置零**(name→`\"\"`、expired_time→`0`、unlimited→`false`…),且这条分支**根本不读取 `status`**。所以「只发改动的那个字段」会把其余字段全部清空,而 `--status` 在这条分支下完全不生效。\n - **带 `?status_only=true`** → **只**改 `status`,其它字段原样保留。\n - 故 `keys update` 的正确姿势(见 `cmd/keys/update.go`):改非状态字段时先 `GetKey` 读当前完整状态、把所有字段回填、叠加用户改动后整体 PUT(read-modify-write);改状态走 `client.UpdateKeyStatus`(`status_only=true`);两者同时改则拆成两次请求(整体 PUT + status_only PUT)。\n - `api.UpdateKey` 负责整体覆盖分支(doc 里有⚠️),`api.UpdateKeyStatus` 负责 status_only 分支——**别再把 update 退化成「只发改动字段的单条 PUT」**,否则会重演「改名清空过期/额度、`--status` 改不动还把名字清空」的 bug。回归测试见 `cmd/keys/update_test.go`。\n - **改状态的取值:上游禁用是 `2` 不是 `0`。**(实测)`1`=启用、`2`=禁用,`0` 是无效默认值,直接下发会被拒为 `Invalid token status`。`docs/` 文档与 `SPEC` 里「0=禁用」是错的。CLI 对用户仍保留直觉写法 `--status 0|1`(0=禁用、1=启用),在 `cmd/keys/update.go` 内部映射 `0→2`、`1→1`(非 0/1 直接报错)后再下发;常量见 `api.TokenStatusEnabled`/`TokenStatusDisabled`。正因有这层映射,README/SPEC 的 `--status 0` 示例无需改。\n\n## 注意\n\n- `CLAUDE.md` 会被提交,且 `scripts/` 不在 `.gitignore`:**任何脚本/文档里都不要硬编码真实 token**,用 `<KEY>` 占位或要求从环境变量传入。\n"}}}}