cnb-rs api
cnb-rs api <path> [flags]直接调用 CNB OpenAPI 的「逃生通道」,对齐 gh api 设计。给 extension、shell 脚本、调试场景一个零 Rust 依赖的入口,等价于 curl -H "Authorization: Bearer $CNB_TOKEN" ... 但额外提供模板变量替换、自动 base URL 拼接、自动 auth header、表单字段 → JSON body 转换。
参数
<path>: API 路径。以/开头时自动 prependhttps://{CNB_DOMAIN}/api;含{owner}/{repo}时自动替换为当前仓库(取自全局--repo或 git remote);完整 URL(https://...)直接透传
选项
-X, --method <STRING>: HTTP method(默认有 body 时POST,否则GET)-H, --header <K: V>: 追加 HTTP header,按K: V格式拆分(首个:之前为 key)。可多次使用-f, --raw-field <K=V>: 字符串表单字段。多个-f合并为 JSON object body。可多次-F, --field <K=V>: 带类型推断的表单字段。true/false→ bool,null→ null,纯数字 → number,其他 → string。可多次--input <FILE>: 请求 body 来源。文件路径,或-从 stdin 读。与-f/-F互斥--jq <EXPR>: 仅保留指定 JSON 字段(mini-jq 子集,见下方「mini-jq 语法」)。复杂表达式请 pipe 到外部jq--paginate: 自动翻页 GET 请求,合并 JSON array 响应为一个大 array。响应非 array 时报错
继承的全局选项:
--repo <REPO>: 指定仓库路径(覆盖{owner}/{repo}模板的来源)--domain <DOMAIN>: 指定目标域名(默认:cnb.cool)
行为细节
Method 默认值
- 没传
-X+ 没传 body →GET - 没传
-X+ 传-f/-F/--input→POST - 显式
-X优先
Body 构造
-f和-F字段合并为 JSON object body(对齐gh api)--input优先级最高,但不能与-f/-F同时使用- 自动设置
Content-Type: application/json(除非-H "Content-Type: ..."显式覆盖)
Auth Header
- 自动加
Authorization: Bearer $CNB_TOKEN(来自配置 /keyring/ 环境变量) - 自动加
Accept: application/vnd.cnb.api+json
模板变量
仅支持 {owner} 和 {repo},其余 {...} 会报错。
{repo}→ 完整<group>/<subgroup>/<name>三级路径{owner}→ 路径首段
输出
- 响应 body 以原始字节写到 stdout,不解析 JSON、不重格式化
- 退出码:HTTP 2xx → 0;4xx / 5xx → 1(便于
if ! cnb api ...判断) - 错误mini-jq 语法
--jq 接受最简版 jq 子集,cover 90% 字段提取场景:
| 表达式 | 行为 |
|---|---|
. 或空字符串 | identity,保留原 value |
.field | object 取字段(结果是含此单字段的 object) |
.field1,.field2 | object 多字段(输出 object subset) |
.parent.child | object 嵌套字段(保留 {parent: {child: ...}} 结构) |
| 上述任一应用于 array | 对每个元素递归过滤 |
遇到信支持的语法(管道 |、select(...)、length、.[] 等)立刻报错,避免 silent 返回空。复杂查询请直接 pipe 到外部 jq:
bash
$ cnb-rs api /{repo}/-/issues \
| jq -r '.[] | select(.state=="open") | .title'--paginate 行为
- 仅适用于
GET请求;其他 method 报错 - 在 URL 上自动追加
?page=N&page_size=100,循环page=1,2,3,… - 每页响应必须是 JSON array;满 100 条继续,不满则停止
- 合并所有页为一个大 JSON array 输出
- 任一页 HTTP 非 2xx → 报错,不继续后续页
- 与
--jq组合:先合并再过滤
CNB 的
wrapped pagination(如/{repo}/-/star-users返回{users: [...], total: N})不被--paginate自动支持,请 pipe 到jq自己拼。
不息(包括 4xx body)写到 stderr
不做什么(MVP 边界)
- ❌ 不做 retry(用户用 shell
while自处理)) - ❌
--jq不内置完整 jq(复杂表达式 pipe 到外部jq - ❌ 不做 GraphQL 包装(直接走 REST endpoint)
- ❌ 不做缓存(未来
--cache <duration>增加)
示例
基本 GET
bash
# 拉当前用户信息
$ cnb-rs api /user
# 拉当前仓库的 issue 列表({repo} 自动从 git remote 推断为 group/subgroup/name 三级路径)
$ cnb-rs api /{repo}/-/issues
# 显式 --repo(不依赖 git remote)
$ cnb-rs --repo wwvo/cnb-rs/cnb-rs api /{repo}/-/releases/latestPOST + 表单字段(自动 JSON 化)
bash
# 创建 Issue
$ cnb-rs api -X POST /{repo}/-/issues \
-f title="bug found" \
-f body="repro: ..."
# 类型推断字段(true → JSON bool,123 → JSON number)
$ cnb-rs api -X PATCH /{repo}/-/issues/123 \
-F state="closed" \
-F priority=2请求 body 从文件 / stdin
bash
# 从文件读
$ cnb-rs api -X POST /{repo}/-/issues --input data.json
# 从 stdin 读
$ echo '{"title":"x"}' | cnb-rs api -X POST /{repo}/-/issues --input -
# 与 heredoc 配合
$ cnb-rs api -X POST /{repo}/-/issues --input - <<EOF
{
"title": "Hello",
"body": "world"
}
EOF自定义 header
bash
# 追加自定义 trace id
$ cnb-rs api -H "X-Trace-Id: my-trace-001" /{repo}/-/issues
# 覆盖默认 Accept
$ cnb-rs api -H "Accept: application/json" /{repo}shell 包装:5 行 bash 写一个 cnb-whoami
bash
#!/usr/bin/env bash
set -e
cnb-rs api /user把这 3 行存为 cnb-whoami,赋可执行权限,就可以用作 extension 入口。Phase 3+ 后可直接 cnb-rs ext install <owner>/cnb-whoami 调用。
内置 mini-jq 过滤
bash
# 单字段
$ cnb-rs api /user --jq .username
{
"username": "alice"
}
# 多字段
$ cnb-rs api /user --jq .username,.nickname,.repo_count
{
"nickname": "Alice",
"repo_count": 318,
"username": "alice"
}
# 嵌套字段(保留 wrapper 结构)
$ cnb-rs api /{repo} --jq .owner.username
# 应用到 array(对每个 item 过滤)
$ cnb-rs api /{repo}/-/issues --jq .number,.title,.state自动翻页
bash
# 拉所有 release(自动翻页)
$ cnb-rs --repo wwvo/cnb-rs/cnb-rs api /{repo}/-/releases --paginate
# --paginate + --jq 组合:列出所有 release 的 tag 名
$ cnb-rs --repo wwvo/cnb-rs/cnb-rs api /{repo}/-/releases --paginate --jq .tag_name复杂场景 pipe 到外部 jq
cnb api 输出原始 JSON,复杂表达式可直接 pipe 给外部 jq:
bash
# 拉所有 open issue 的 title
$ cnb-rs api /{repo}/-/issues --paginate \
| jq -r '.[] | select(.state=="open") | .title'
# 拉前 10 个 star 用户的 username(wrapped 分页,--paginate 不直接支持)
$ cnb-rs api /{repo}/-/star-users \
| jq -r '.users[:10] | .[].username'与 gh api 的差异
| 维度 | gh api | cnb-rs api |
|---|---|---|
| 模板变量 | {owner} / {repo} / {branch} | 仅 {owner} / {repo}(CNB endpoint 多为 /{repo}/-/...,故 {owner} 用得较少) |
| 输出 | 默认 JSON 美化 | 原始字节(用户自己 pipe 到 jq) |
--jq flag | 内置 gojq(完整 jq) | 内置 mini-jq 子集(.field / .a.b / .x,.y),复杂表达式 pipe 到外部 jq |
--paginate | 自动 Link header 翻页 | 自动 ?page=N&page_size=100 翻页,仅支持 JSON array 响应 |
| 退出码 | 2xx → 0;4xx/5xx → 1 | 同 |
| 默认 method | 有 body → POST | 同 |
实现注意
- 复用
cnb-apicrate 的CnbClient::request_raw公开方法,自动带Authorization+Acceptheader + reqwest connection pool - 不复用
send_with_retry(cnb-api 内部的 GET 重试机制),保持「用户传什么 method 就发什么 method、不偷偷重试」的语义 - HTTP 4xx / 5xx 不映射为
ApiError,而是把 status code + body 原样返回 + 退出码 1