9.4 KiB
安装流程
本文档是 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 安装程序),它必须:
-
从调用方接收一个原始日志路径。 将所有 stdout + stderr 写入那里。不要直接写入终端。
-
在结束时输出单个终端状态块,包含
STATUS: success|skipped|failed和任何步骤特定字段:=== NANOCLAW SETUP: STEP_NAME === STATUS: success KEY: value KEY: value === END ===字段名使用
UPPER_SNAKE_CASE。值是短标量值。 -
如果是一个长时间运行的步骤,可选择在中途发出子状态块。
auto.ts实时解析它们,并可以渲染中间 UI(正如pair-telegram使用PAIR_TELEGRAM_CODE/PAIR_TELEGRAM_ATTEMPT所做的那样)。 -
在硬失败时以非零退出,以便
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的结构化输出。 交互式步骤目前不发出机器可读状态。未来可以在交互后添加一个状态块,注明使用的方法。