# 使用 Humanbased CLI


`hb` 是用于 Campaign API 和交付记录读取的命令行客户端，`humanbased` 是同一程序的另一个命令名。仓库中的当前包版本是 `0.1.0-next.3`，不能据此认定已有公开下载版。请向组织管理员或 [Humanbased 支持](mailto:hello@humanbased.ai)取得获准的程序及适用环境。

如果组织允许你使用 Humanbased monorepo 的源码，可在仓库根目录用 Node.js 20 或更新版本和 pnpm 构建：

```sh
pnpm install --frozen-lockfile
pnpm --filter @humanbased/cli build
node packages/cli/dist/hb.js --version
```

使用源码构建时，把下文命令中的 `hb` 换成 `node packages/cli/dist/hb.js`。这条路径面向已获仓库权限的开发者，不代表公开包已发布。

## 先查看当前版本

```sh
hb --version
hb --help --json
hb config profiles set production --environment production --api-url https://api.humanbased.ai
hb config profiles use production
hb config profiles list --json
```

JSON 帮助和 Profile（目标环境配置）命令不发送网络请求。Profile 记录 API 地址，不保存凭证；CLI 没有默认地址。测试环境应单独设置 `--environment staging --api-url https://api.staging.humanbased.ai`，切换环境前先查看当前 Profile。

可用 `hb auth login` 在浏览器完成该版本支持的登录，也可按下文提供组织授权的 Key。认证后再检查：

```sh
hb auth status
hb auth whoami --json
hb capabilities --json
```

核对环境、API 地址、组织和凭证能力。通过 `HB_API_KEY` 提供的 Key 优先于已保存的会话。未配置目标或地址与环境不符时，CLI 会拒绝请求。

## 选择凭证

人在终端使用受支持版本的 `hb auth login` 时，需要在浏览器完成登录确认。CLI 会话的权限比完整门户会话窄。无人值守的程序应通过运行环境的密钥管理，以 `HB_API_KEY` 或 `--token-stdin` 提供组织 API Key。不要把 Key 放在命令行参数、代码库、Profile 或智能体提示词中。

服务器仍按具体活动核对权限。命令出现在帮助列表里，不代表当前凭证获准执行。遇到 `403` 时，先用 `hb auth whoami --json` 核对身份、当前组织及目标活动，不通过换用他人的凭证扩大范围。

## 先读取，并检查每次结果

从只读操作开始：

```sh
hb campaign list --limit 5 --json
```

标准输出只有一个 JSON 封套，应同时检查 `error.code` 和退出码；进度消息在标准错误输出。`data.items` 为空，表示本次范围没有可见活动。若 `data.has_more=true`，把返回的 `data.next_cursor` 传给下一次的 `--cursor`。`--all` 会连续读取多页，应在确有需要时使用。

| 退出码 | 含义 |
| --- | --- |
| `0` | 该命令按自身规则完成；成功读取回执不表示之前的写入成功。 |
| `1` | 请求已完成，但没有达到命令的完成条件：文件未通过验证、计划无法应用，或 `campaign records pull --all` 的导出提前停止。 |
| `2` | 命令参数无效或请求格式有误。 |
| `3`、`4` | 缺少可用凭证或权限不足。 |
| `5` | 冲突或需要本人决定的结果。 |
| `6` | 暂时性错误，或输出没有送达；按具体提示重试。 |
| `7`–`10` | 服务失败、支出限制、等待超时或契约不匹配；检查 `error.code`。 |
| `130` | 命令被中断。 |

浏览器会话用完后执行 `hb auth logout`。`hb auth sessions list` 和 `hb auth sessions revoke` 用于查看与撤销已保存会话；组织 API Key 另行管理。

## 写入与发布命令

每次写入都按同一顺序进行：先写候选文件，再验证、规划，最后用你自己选定的请求键应用。下表每条命令对应 [Campaign API](/zh/api/campaign-api) 的同一步操作，请求、回执和错误契约见该页。

| 步骤 | 命令 | 所需权限 |
| --- | --- | --- |
| 围绕一个模板生成候选文件 | `hb campaign init --template <template-id> --out campaign.json` | `campaign:read` |
| 从已有版本生成候选文件 | `hb campaign init --from-version <campaign-id> --out campaign.json` | `campaign:read` |
| 验证候选文件 | `hb campaign validate --file campaign.json` | `campaign:author` |
| 规划新建活动 | `hb campaign plan --create --file campaign.json --out plan.json` | `campaign:author` |
| 规划对已有活动的修改 | `hb campaign plan <campaign-id> --file campaign.json --out plan.json` | `campaign:author` 和 `campaign:read` |
| 创建活动 | `hb campaign create --file campaign.json --plan plan.json --idempotency-key <your-key>` | `campaign:author` |
| 应用修改 | `hb campaign apply <campaign-id> --file campaign.json --plan plan.json --idempotency-key <your-key>` | `campaign:author` |
| 提出发布提案 | `hb campaign launch propose <campaign-id>` | `campaign:launch` |
| 执行已确认的发布 | `hb campaign launch execute --proposal <proposal-id> --idempotency-key <your-key>` | `campaign:launch` |

`init --template` 生成的文件把预算、奖励、上限等需要你决定的值留成 `<DECIDE: …>` 占位符。验证前逐项替换。文件里还有占位符时，CLI 拒绝发送，并以退出码 `1` 和 `PLACEHOLDERS_UNANSWERED` 结束。

`validate` 和 `plan` 只检查。候选文件无效或无法应用时，两条命令都以 `1` 退出，脚本可以在这里停下。`plan --out` 保存已复核的更改清单；`create --plan` 和 `apply --plan` 写入前会重新规划，文件或原版本在复核后发生变化时以 `PLAN_DRIFTED` 拒绝。

`--idempotency-key` 标识一次写入。用同一个键重发同一请求，服务端只返回首次尝试的结果，写入只发生一次。另一项更改请换新键。

应用配置的响应丢失时，用原请求键查看回执：

```sh
# Campaign 文件的应用（默认动作）
hb campaign actions inspect --request-key <your-key> --json

# 模板的应用
hb campaign actions inspect --request-key <your-key> --action template.apply --json
```

此查询一次只查一种动作。不带 `--action` 时查的是 `campaignfile.apply`，所以模板的应用必须加 `--action template.apply`，否则会查不到回执，或查到另一次使用同一请求键的写操作的结果。此查询适用于 Campaign 文件和模板的应用操作，不包括 Launch。Launch 响应丢失时，以**同一**请求键重发相同的发布意图，让服务端返回首次操作的结果。`uncertain` 表示结果未定，需要人核对回执步骤和活动状态，再决定后续处理。

发布会锁定活动预算，所以分三步、由两方完成。`hb campaign launch propose` 记录一份提案，并打印 Developer 门户确认页面的地址；带 `--json` 时，地址在 `data.confirmation_url`。对该活动有编辑权限的人登录门户，阅读提案后选择 **Confirm this launch**（确认这次发布）。之后 `hb campaign launch execute` 把已确认的提案执行一次。CLI 没有确认命令；提案和执行都要用组织 API Key，`hb auth login` 的登录会话会被拒绝。

## 确认当前指向哪个环境

每次写入前都确认一遍。

```sh
hb config profiles show --json
```

输出里的 `environment` 和 `api_url` 是**这个 profile 写了什么**。下一条命令的实际去处逐个字段单独算，每个字段取第一个有值的：

| 这个值 | 按顺序取第一个有值的 |
| --- | --- |
| 用哪个 profile | `--profile` → `HB_PROFILE` → 保存的默认 profile |
| `api_url` | `--api-url` → `HB_API_URL` → 上面选中的那个 profile |
| `environment` | `--environment` → `HB_ENVIRONMENT` → 上面选中的那个 profile |

`--profile` 只决定由哪个 profile 兜底，它排在 `HB_API_URL` 和 `HB_ENVIRONMENT` 后面。所以 `--profile staging` 配上一个指向生产的 `HB_API_URL`，走的是环境变量。

`environment` 和 `api_url` 还要彼此对得上：每个环境只认自己那几个 origin，对不上就以 `ORIGIN_MISMATCH` 拒绝执行，而非打一行警告。所以 shell 里单独残留一个 `HB_API_URL` 会当场被拒。会静默跑错环境的是另一种情形：`HB_API_URL` 和 `HB_ENVIRONMENT` 成对指向同一个别的环境 —— 这时两个值自洽、profile 整个不参与，而 `hb config profiles show` 仍然显示 staging。

凭证是另一条线。`hb auth status` 报下一条命令实际会用哪一份凭证，并在 `HB_API_KEY` 盖住已保存会话时明说这一点。

| 作用 | 命令 | 所需权限 |
| --- | --- | --- |
| 列出全部 profile（本机保存的环境配置），并标出当前默认那个 | `hb config profiles list` | — |
| 看一个 profile 的内容；省略名字时看当前默认的 | `hb config profiles show <名字>` | — |
| 新建或替换一个 profile | `hb config profiles set <名字> --environment <production、staging 或 local> --api-url <地址> --org <组织 id>` | — |
| 把某个 profile 设为默认 | `hb config profiles use <名字>` | — |
| 忘掉一个 profile | `hb config profiles remove <名字>` | — |

这五条都不发网络请求，也都不碰凭证。profile 只记录去哪里、预期哪个组织；凭证每次运行时从环境变量或标准输入进来。所以换环境时，profile 和凭证要一起换。

## 登录与会话

这些命令都不指定活动，所以都不要 `campaign:*` 权限；凭证实际能做什么仍由服务端判定。

| 要做的事 | 命令 | 所需权限 |
| --- | --- | --- |
| 在浏览器里登录 | `hb auth login` | — |
| 看下一条命令会用哪份凭证 | `hb auth status` | — |
| 确认服务端核出来的调用者 | `hb auth whoami` | — |
| 这个版本支持什么、这份凭证能做什么 | `hb capabilities` | — |
| 列出以你的身份登录的终端 | `hb auth sessions list` | — |
| 吊销其中一个会话 | `hb auth sessions revoke <session-id>` | — |
| 结束本机的会话 | `hb auth logout` | — |

`hb auth status` 不发网络请求，它读的是本机保存的会话。其余几条都要发请求。

## 读取命令

写入之前、发布之后，用这些命令确认服务端实际记录了什么。它们都不写入。

| 要回答的问题 | 命令 | 所需权限 |
| --- | --- | --- |
| 这个活动现在是什么状态，这份凭证能对它做什么 | `hb campaign get <campaign-id>` | `campaign:read` |
| 它有哪些历史版本 | `hb campaign versions list <campaign-id>` | `campaign:read` |
| 某一版的文档原文 | `hb campaign versions inspect <campaign-id> --version <版本号> --out version.json` | `campaign:read` |
| 两版之间改了什么 | `hb campaign versions diff <campaign-id> --from <版本号> --to <版本号>` | `campaign:read` |
| 一个时间窗内的事件计数，以及它们的分母 | `hb campaign observation <campaign-id> --from <ISO 时间> --to <ISO 时间>` | `campaign:read` |
| 一份候选文件会报出多少奖励 | `hb campaign feasibility --file campaign.json` | `campaign:author` |
| 这个凭证能读到哪些活动 | `hb campaign list --limit 5` | `campaign:read` |
| 一次写入的回执 | `hb campaign actions inspect --request-key <你选的键>` | `campaign:author` |
| 一个订阅里某活动已交付的记录 | `hb campaign records pull <campaign-id> --subscription <subscription-id>` | — |

`records pull` 的所需权限一栏是 `—`，但它要的是带数据权限的组织 API Key，浏览器会话会被拒绝；走法见本页「导出全部交付记录」。

`versions list` 按版本从新到旧返回，分页用 `--limit` 和 `--cursor`，`--all` 走完所有页。

`versions inspect` 省略 `--version` 时取最新那一版。`--raw` 多返回服务端自己的字段，适合排查；要改完发回去的那一份用 `--out` 写出来。

`observation` 的窗口按事件时间算：`--from` 含、`--to` 不含。每个计数都带分母，只看分子得不出比例。

`feasibility` 只报价，不创建任何东西，并且会说明哪些问题它答不了。

## 模板命令

模板决定候选文件里有哪些字段。`hb campaign init --template <template-id>` 引用的就是这里的模板。

| 步骤 | 命令 | 所需权限 |
| --- | --- | --- |
| 看本组织可引用的模板 | `hb template list` | `campaign:read` |
| 看一个模板的字段契约 | `hb template get <template-id>` | `campaign:read` |
| 生成一份起始模板文档 | `hb template init --kind <类型> --name <名字> --out template.json` | — |
| 验证模板文档 | `hb template validate --file template.json` | `campaign:author` |
| 创建或更新模板 | `hb template apply --file template.json --idempotency-key <你选的键>` | `campaign:author` |

`template init` 只在本机写一份文件，不发请求，也不需要任何权限。

更新已有模板要同时给 `--template-id` 和 `--base-hash`，后者来自 `hb template get`；只给 `--template-id` 会被拒绝。少了它，两个人先后改同一个模板时，后写入的会覆盖前一个，而双方都看不出发生过覆盖。

模板的应用也有回执，但查的时候必须带 `--action template.apply`，否则查到的是另一次写操作的结果，或者什么都查不到。

这一页不含 `hb dsa …` 的八条命令（向活动设计助手咨询）和 `hb agent runs`，目前也没有其他页面介绍它们。

## 导出全部交付记录

`hb campaign records pull` 读取一个活动交付到某项订阅的记录。它固定从第一条开始读，订阅的增量读取位置保持原位。活动筛选只会在订阅已授权的范围内缩小结果：收费订阅的范围仍是此前已取得的那一段，读到的始终是已交付记录，而非活动的全部提交。先确认 `hb --help --json` 列出了这条命令；你的版本没有它时，改用 [HTTP 数据接口](/zh/api/api-data)逐页读取。

### 凭证与第一页

这条命令需要一把对该订阅有数据读取权限的完整组织 API Key，`hb auth login` 的登录会话会被拒绝。只有 Data 授权的 Key 也可以运行 `hb capabilities`；它会显示 Campaign API 目录不可用，这对本命令没有影响。按[首次获取已授权数据](/zh/api/api-data)设置 `HB_API_KEY` 和 `HB_SUBSCRIPTION_ID`，再把 Key 只交给这一条命令：

```sh
printf 'Campaign ID: '
read -r HB_CAMPAIGN_ID
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --limit 5 --json
```

不加 `--all` 时只读一页，并返回 `data.next_cursor`。`--limit` 是每页条数，默认 50，最多 200。命令只接受一个 Campaign ID。

### 导出所有页

`--all` 沿游标继续读取，直到服务端报告没有更多记录。把结果写进私有目录：

```sh
HB_EXPORT_DIR="$(mktemp -d)"
printf 'Export directory: %s\n' "$HB_EXPORT_DIR"
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --limit 200 --all --json > "$HB_EXPORT_DIR/campaign-records.json"
printf 'Exit code: %s\n' "$?"
```

记录在 `data.items`。两项检查都通过，才把这个文件当作完整导出：退出码为 `0`，且 `data.complete` 为 `true`。

| `data` 字段 | 含义 |
| --- | --- |
| `items` | 本次命令读到的记录，顺序与服务端返回一致。 |
| `count` | `items` 中的记录数。 |
| `pages_read` | 本次命令请求的页数。 |
| `has_more` | 命令停止时，服务端是否仍报告还有记录。 |
| `next_cursor` | 之后可以续读的位置，或 `null`。 |
| `complete` | 只有未传 `--cursor`、从第一条读到最后一页时才是 `true`。 |
| `pagination_stop_reason` | `null`，或 `--all` 提前停止的原因。 |

### 完整结果与部分结果

完整导出的结果如下。两个示例都省略了 `data.items` 以及封套中的 `requestId`、`context`、`notices`，标识符只是示意。

```json
{
  "apiVersion": "humanbased.cli/v1",
  "command": "campaign.records.pull",
  "requestId": "e9dc2862-d165-44ce-b54a-42111284bd40",
  "context": {
    "profile": null,
    "apiOrigin": "https://api.humanbased.ai",
    "organizationId": "10571000020000000001"
  },
  "data": {
    "subscription_id": "784210003001",
    "campaign_id": "784210001001",
    "count": 1342,
    "pages_read": 7,
    "has_more": false,
    "next_cursor": null,
    "complete": true,
    "pagination_stop_reason": null
  },
  "notices": [],
  "error": null
}
```

部分导出以 `1` 退出。文件里仍保留已读到的记录，并说明停在哪里：

```json
{
  "apiVersion": "humanbased.cli/v1",
  "command": "campaign.records.pull",
  "requestId": "e9dc2862-d165-44ce-b54a-42111284bd40",
  "context": {
    "profile": null,
    "apiOrigin": "https://api.humanbased.ai",
    "organizationId": "10571000020000000001"
  },
  "data": {
    "subscription_id": "784210003001",
    "campaign_id": "784210001001",
    "count": 100000,
    "pages_read": 500,
    "has_more": true,
    "next_cursor": "example-opaque-cursor",
    "complete": false,
    "pagination_stop_reason": "item_limit"
  },
  "notices": [],
  "error": {
    "code": "PAGINATION_INCOMPLETE",
    "httpStatus": null,
    "message": "The --all walk stopped while the server still reported more pages. The partial data and a continuation cursor, when available, are included.",
    "retryable": false,
    "nextAction": null,
    "details": null
  }
}
```

| `pagination_stop_reason` | 发生了什么 | 你现在做什么 |
| --- | --- | --- |
| `page_limit` | 命令已读 1,000 页。按默认每页 50 条，就是 50,000 条记录。 | 从 `next_cursor` 续读。加 `--limit 200` 可让单次命令读到更多记录。 |
| `item_limit` | 命令已读 100,000 条记录。 | 从 `next_cursor` 续读。 |
| `missing_cursor` | 服务端报告还有记录，却没有返回游标。 | `next_cursor` 为 `null`，这个文件没有安全的续读位置。保留它供排查，再从第一条重新导出。 |
| `repeated_cursor` | 服务端返回了本次命令已经用过的游标。 | `next_cursor` 为 `null`。重复的游标只会回到同一页，请从第一条或你此前保存的更早游标重新导出；再次出现时联系 [hello@humanbased.ai](mailto:hello@humanbased.ai)。 |

### 续读部分导出

只有 `data.next_cursor` 是字符串时才续读。复制它的值，去掉 JSON 引号：

```sh
printf 'Part number, 2 for the first continuation: '
read -r HB_PART
printf 'Next cursor: '
read -r HB_CURSOR
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --cursor "$HB_CURSOR" \
  --limit 200 --all --json > "$HB_EXPORT_DIR/campaign-records-$HB_PART.json"
printf 'Exit code: %s\n' "$?"
```

一次运行最多读取 100,000 条记录，较大的导出需要续读多次。每次续读保存为单独的文件：第 2 部分、第 3 部分，依此类推。续读结果覆盖了之前的文件，那一部分记录就丢失了。

续读文件只包含游标之后的记录。即使它读到最后一页并以 `0` 退出，`data.complete` 仍是 `false`。完整导出等于第一个文件的 `data.items` 依次接上每个续读文件的 `data.items`，各次命令使用同一个活动和订阅。

### 保护输出

| 导出的哪一部分 | 对你意味着什么 |
| --- | --- |
| 记录内容 | `data.items[].data` 按交付时的原样写出，外形像凭证的值也照样保留。按源数据的要求保管这个文件。 |
| 数字 | 记录内容里的数字保留原有位数，包括超出 JavaScript 安全整数范围的整数和长小数。数值需要这种精度时，用能保留它的解析器读取。 |
| 临时文件 | 读取期间，CLI 把记录暂存在系统临时目录里仅当前用户可读的文件中，命令结束时删除。请在那里留出整份导出所需的空间。 |
| 输出中断 | 管道关闭或目标位置写满时，命令以非零退出码结束，并在标准错误输出报告原因。已经写出一部分的 JSON 文件是残缺的：丢弃它，重新导出。 |

### 错误

| 退出码与 `error.code` | 原因 | 你现在做什么 |
| --- | --- | --- |
| `1`，`PAGINATION_INCOMPLETE` | `--all` 在还有记录时停止。 | 查看 `data.pagination_stop_reason`，按上文续读。 |
| `2`，`INVALID_ARGUMENT` | Campaign ID 为空或是逗号分隔的列表，或某个 ID、游标外形像凭证。请求没有发出。 | 只传一个 Campaign ID 和真实的标识符。Key 只通过 `HB_API_KEY` 或 `--token-stdin` 提供。 |
| `3` | 没有可用凭证：Key 缺失、格式有误、过期或已撤销，或者用了登录会话。 | 提供完整的组织 API Key。 |
| `4` | Key 没有数据读取权限，或订阅在它的范围或组织之外。 | 按[创建和使用 API Key](/zh/api/api-auth)检查 Key 的 Data 授权。 |
| `6` | 服务暂时不可用，或输出没有送达。 | 重新执行同一条命令；从头读取既不计费，也不移动读取位置。 |

接口操作和权限见 [Campaign API](/zh/api/campaign-api)。把一次限定范围的读取交给智能体，见[通过智能体使用 Humanbased](/zh/api/agent-access)。
