175 lines
9.4 KiB
Markdown
175 lines
9.4 KiB
Markdown
# 安装流程
|
||
|
||
本文档是 NanoClaw 端到端脚本化安装的契约
|
||
(`bash nanoclaw.sh` → `pnpm run setup:auto`)。在添加新步骤、修复回退或更改输出渲染方式之前,请阅读本文档。
|
||
|
||
## 三个输出级别
|
||
|
||
每个安装步骤在**三个不同级别**产生输出。它们面向
|
||
不同的受众,输出到不同的位置,并以不同的格式呈现。
|
||
不要混淆它们。
|
||
|
||
| 级别 | 受众 | 目的地 | 格式 |
|
||
|---|---|---|---|
|
||
| 1. 面向用户 | 运行安装的操作者 | 终端(通过 clack) | 品牌化的、简洁的、信息性的——"产品内容" |
|
||
| 2. 进度 | 未来调试者、审查失败运行的 AI agent、发布支持 | `logs/setup.log`(一个文件,仅追加) | 结构化的每个步骤块,线性时间顺序,人类和机器可读 |
|
||
| 3. 原始 | 正在深度调试特定步骤的人 | `logs/setup-steps/NN-step-name.log`(每个步骤一个文件) | 完整的原始子进程 stdout + stderr,逐字记录 |
|
||
|
||
可以这样理解:用户看到的是**摘要**,进度日志是
|
||
**带关键事实的索引**,原始日志是**证据**。
|
||
|
||
### 级别 1:面向用户(clack)
|
||
|
||
由 `setup/auto.ts` 通过 `@clack/prompts` 渲染。这是我们的*产品
|
||
界面*——每行都应读起来像是我们为第一天遇到的陌生人设计的一样。
|
||
|
||
- 使用 clack spinner 表示进行中的工作。显示经过的时间。
|
||
- `p.log.success` / `p.log.step` / `p.log.warn` 用于永久状态标记。
|
||
- `p.note` 用于多行信息(配对码、下一步)。
|
||
- `p.text` / `p.select` / `p.password` 用于提示。
|
||
- 品牌调色板:`setup/auto.ts` 中的 `brand()` / `brandBold()` / `brandChip()` 辅助函数。当终端支持时使用真彩色,否则回退到 16 色青色,管道输出 / `NO_COLOR` 时使用纯文本。
|
||
|
||
规则:
|
||
- **无中断。** 每个子步骤属于同一个视觉流程。唯一的例外是 Anthropic 凭据注册(见下文)。
|
||
- **无原始子进程输出。** 永远不要对输出内容非我们所编写的子进程使用 `stdio: 'inherit'`。捕获它,仅在失败时显示。
|
||
- **无调试样式前缀**(`[add-telegram] …`、`INFO …`、时间戳)。这些属于级别 2 和 3。
|
||
- **无 emoji**,除非 clack 图形需要。
|
||
|
||
### 级别 2:进度日志
|
||
|
||
`logs/setup.log`——每次安装运行一个文件,仅追加,跨多次运行安装累积
|
||
(如果某次运行中途失败并被重新尝试,新条目会追加)。这是你在操作者报告安装 bug 时
|
||
会要求他们粘贴的东西,也是 AI agent 会阅读以了解发生了什么的东西。
|
||
|
||
条目格式:
|
||
|
||
```
|
||
=== [2026-04-22T22:14:12Z] bootstrap [45.1s] → success ===
|
||
platform: linux
|
||
is_wsl: false
|
||
node_version: 22.22.2
|
||
deps_ok: true
|
||
native_ok: true
|
||
raw: logs/setup-steps/01-bootstrap.log
|
||
|
||
=== [2026-04-22T22:14:57Z] environment [2.3s] → success ===
|
||
docker: running
|
||
apple_container: not_found
|
||
raw: logs/setup-steps/02-environment.log
|
||
|
||
=== [2026-04-22T22:15:00Z] container [92.4s] → success ===
|
||
runtime: docker
|
||
image: nanoclaw-agent:latest
|
||
build_ok: true
|
||
raw: logs/setup-steps/03-container.log
|
||
```
|
||
|
||
设计约束:
|
||
- 开始行带有起始时间戳(UTC,ISO-8601),使 `grep` 能给出顺序。
|
||
- 持续时间以秒为单位,保留一位小数——快速步骤读作 "0.5s",而非 "0ms"。
|
||
- 状态为以下之一:`success`、`skipped`、`failed`、`aborted`。
|
||
- 字段是步骤特定的,但**必须**是短标量值。无 JSON,无多行。如果值很长,放在原始日志中并引用它。
|
||
- 始终输出一个 `raw:` 指针,即使在成功时——使调试第二次失败更容易。
|
||
- **用户选择**是其自己的条目,不嵌套在步骤中:
|
||
|
||
```
|
||
=== [2026-04-22T22:17:44Z] user-input → display_name ===
|
||
value: gav
|
||
|
||
=== [2026-04-22T22:17:51Z] user-input → channel_choice ===
|
||
value: telegram
|
||
```
|
||
|
||
这些很重要,因为通过安装流程的路径取决于它们。
|
||
|
||
日志以标识运行的头部块开始,以完成块结束:
|
||
|
||
```
|
||
## 2026-04-22T22:14:12Z · setup:auto 已启动
|
||
user: exedev
|
||
cwd: /home/exedev/nanoclaw
|
||
branch: branded-setup
|
||
commit: 6e0d742
|
||
|
||
… (步骤条目) …
|
||
|
||
## 2026-04-22T22:18:54Z · 已完成 (总计 4m42s)
|
||
```
|
||
|
||
失败时完成块会指出失败的步骤及其错误:
|
||
|
||
```
|
||
## 2026-04-22T22:16:40Z · 在 container 处中止 (err=cache_miss)
|
||
```
|
||
|
||
### 级别 3:原始按步骤日志
|
||
|
||
`logs/setup-steps/NN-step-name.log`——每个步骤一个文件,按执行顺序编号(两位零填充前缀以支持自然排序)。来自子进程的完整逐字 stdout + stderr。每次运行时截断并重写(非追加)。
|
||
|
||
内容为步骤输出的任何内容:apt 输出、docker 构建层、pnpm install 内容、`curl` 响应体等。这是证据层面——"shell 实际看到了什么?"不过滤任何内容。
|
||
|
||
## 新步骤的契约
|
||
|
||
当你添加一个步骤(无论是 `setup/<name>.ts` 中的 TS 步骤还是从 `auto.ts` 调用的 bash 安装程序),它必须:
|
||
|
||
1. **从调用方接收一个原始日志路径。** 将所有 stdout + stderr 写入那里。不要直接写入终端。
|
||
2. **在结束时输出单个终端状态块**,包含 `STATUS: success|skipped|failed` 和任何步骤特定字段:
|
||
|
||
```
|
||
=== NANOCLAW SETUP: STEP_NAME ===
|
||
STATUS: success
|
||
KEY: value
|
||
KEY: value
|
||
=== END ===
|
||
```
|
||
|
||
字段名使用 `UPPER_SNAKE_CASE`。值是短标量值。
|
||
|
||
3. 如果是一个长时间运行的步骤,可选择在中途发出**子状态块**。`auto.ts` 实时解析它们,并可以渲染中间 UI(正如 `pair-telegram` 使用 `PAIR_TELEGRAM_CODE` / `PAIR_TELEGRAM_ATTEMPT` 所做的那样)。
|
||
|
||
4. **在硬失败时以非零退出**,以便 `auto.ts` 可以区分"步骤运行完成并报告失败"与"步骤崩溃"。
|
||
|
||
驱动程序处理其余部分:级别 1 中的 spinner,级别 2 中的结构化追加,级别 3 中的原始捕获。
|
||
|
||
## Anthropic 例外
|
||
|
||
Anthropic 凭据注册(`setup/register-claude-token.sh`)是视觉流程中**唯一**允许的中断。原因:
|
||
|
||
- `claude setup-token` 打开浏览器,运行自己的 OAuth 提示,并打印 token。它通过 `script(1)` 拥有 TTY。
|
||
- 我们不想自己重新实现 OAuth 设备流程。
|
||
- 我们不想拦截/镜像 token(它已经出现在用户终端——镜像它会增加攻击面)。
|
||
|
||
因此在此步骤期间:
|
||
- clack 流程显式暂停(一个 `p.log.step` 标记说"这部分是交互式的,你正在移交给 Anthropic")。
|
||
- 子进程完全继承 stdio。
|
||
- 当控制返回时,clack 在下一行恢复,带有一个成功标记。
|
||
|
||
级别 2 日志仍然获得一个条目(`auth [interactive] → success` 带有方法——subscription / oauth-token / api-key)。级别 3 的捕获在这里是可选的;镜像 `script -q` 输出很棘手,将 token 泄露到磁盘的风险超过了调试价值。
|
||
|
||
## 文件参考
|
||
|
||
| 文件 | 角色 |
|
||
|---|---|
|
||
| `nanoclaw.sh` | 顶层封装。阶段 1(bootstrap)和阶段 2(setup:auto)编排。写入 bootstrap 的原始日志 + 进度条目。 |
|
||
| `setup.sh` | 阶段 1 bootstrap:Node、pnpm、原生模块验证。发出自己的 `BOOTSTRAP` 状态块(历史上打印到 stdout;现在输出到 bootstrap 原始日志)。 |
|
||
| `setup/auto.ts` | 阶段 2 驱动程序。编排 clack UI、步骤执行、用户提示,并为其启动的每个步骤写入所有三个日志级别。 |
|
||
| `setup/logs.ts` | 日志记录原语(`logStep`、`logUserInput`、`logComplete`、`stepRawLog`、`initSetupLog`)。级别 2/3 格式和文件路径的唯一真源。 |
|
||
| `setup/<step>.ts` | 各个步骤的实现。必须输出一个终端状态块;不能直接写入终端。 |
|
||
| `setup/register-claude-token.sh` | Anthropic 例外。继承 stdio,打印自己的 UI,向驱动程序返回状态。 |
|
||
| `setup/add-telegram.sh` | 非交互式适配器安装程序。从 env 读取 `TELEGRAM_BOT_TOKEN`;从不提示。面向用户的部分驻留在 `auto.ts` 中。 |
|
||
| `setup/pair-telegram.ts` | 发出 `PAIR_TELEGRAM_CODE` / `PAIR_TELEGRAM_ATTEMPT` / `PAIR_TELEGRAM` 状态块。从不打印 UI。驱动程序通过 clack notes 渲染。 |
|
||
|
||
## 常见陷阱
|
||
|
||
- **在步骤内部打印调试输出。** 开发时诱人;检入代码时禁止。所有运行时消息都通过状态块(级别 2)或原始日志写入(级别 3)。
|
||
- **添加一个"仅此一次"输出到终端的 `console.log`。** 它会破坏 clack 流程——spinner 行会被撕裂。改用 `src/log.ts` 中的 `log.info` / `log.error`(写入原始日志)。
|
||
- **对非例外子进程使用 `stdio: 'inherit'`。** 见上文 Anthropic。其他任何情况都需要 `pipe` + 显式捕获。
|
||
- **Tee 到 stderr。** Clack 的 spinner 在一个步骤期间拥有终端。即使是 stderr 写入也会撕裂框架。管道传输一切,然后选择要暴露的内容。
|
||
- **bash `$VAR…` 位置中的 UTF-8。** Bash 的词法分析器可能将多字节字符的第一个字节拉入变量名,触发 `set -u`。始终加花括号:`${VAR}…`。
|
||
|
||
## 未来工作(尚未实现)
|
||
|
||
- **进度日志轮转。** 今天的实现在每次运行时截断。未来:将之前的运行滚动到 `logs/setup.log.1`、`.2` 等。
|
||
- **多次运行安装的原始日志轮转。** 目前每次运行会覆盖。目前可以接受;如果支持需要比较连续尝试,则重新审视。
|
||
- **`register-claude-token.sh` 的结构化输出。** 交互式步骤目前不发出机器可读状态。未来可以在交互后添加一个状态块,注明使用的方法。
|