Day 02 / 共 20 天 · 第 1 周 入门与全景

入口与启动流程

昨天看了 OpenClaw 的全景地图。今天钻进第一格——启动:你敲 openclaw gateway 回车,到底发生了什么?追一遍启动链:openclaw.mjs(bin)→ dist/entry.jsrunCli(Commander)→ 对应命令。以及 openclaw onboard 配置向导。搞懂它,明天讲的 Gateway 才知道"是被谁、怎么拉起来的"。

📍 你在整门课的位置(第 1 周 · 入门与全景)
Day1 全景 Day2 入口启动 Day3 Gateway Day4 消息旅程 Day5 配置· W2 大脑· W3 技能/渠道· W4 安全/部署
L01

bin 入口

🤔 痛点:一个命令行工具,凭什么敲 openclaw 就能跑? 你从没写过 /usr/bin/openclaw 这个文件,为什么装完就能敲 openclaw gateway?如果没有一套约定,每个工具都得手动配 PATH、写启动脚本,乱成一团。
💡 本质:npm 的 bin 字段 = "命令名 → 文件"的登记表 就像手机把"张三"这个名字映射到一串号码。package.jsonbin 声明 openclaw → openclaw.mjs,安装时 npm 自动在系统 PATH 里建一个软链。于是敲 openclaw 就等于 node openclaw.mjs
// package.json:16
"bin": { "openclaw": "openclaw.mjs" }
"main": "dist/index.js"
"bin" 是什么? package.jsonbin 字段声明"这个包提供哪些命令行工具"。npm install -g openclaw 后,系统里就有了 openclaw 命令,它指向 openclaw.mjs 这个文件。所以你敲 openclaw gateway,实际就是 node openclaw.mjs gateway.mjs 是 ES Module 格式的 JS 文件。这是所有 Node CLI 工具的标准入口机制。
L02

openclaw.mjs(薄壳引导)

openclaw.mjs 是个"薄壳",只做三件事:

// 1. 检查 Node 版本(要求 >= 22.12)
const MIN_NODE_MAJOR = 22, MIN_NODE_MINOR = 12;
ensureSupportedNodeVersion();   // 不满足 → 打印 nvm 升级指引并退出

// 2. 开启 Node 编译缓存(加速后续启动)
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) { ... }

// 3. 加载真正的入口 dist/entry.js(构建产物)
if (await tryImport("./dist/entry.js")) { /* OK */ }
else if (await tryImport("./dist/entry.mjs")) { /* OK */ }
else throw new Error("openclaw: missing dist/entry.(m)js (build output).");
💬 小白 vs 老师 👶 小白:检查 Node 版本这种事,直接写在主程序 entry.ts 里不就行了?
👨‍🏫 老师:不行。如果你用的是老版本 Node(比如 v18),主程序里的新语法会让它直接语法报错——连"请升级到 v22"这句话都打印不出来,用户一脸懵。所以要一层"用最老最保守语法写的薄壳"先跑,先验版本、给出友好指引,再去加载新语法写的主程序。
👶 小白:哦!所以薄壳是"翻译不好但一定能开口说话的门童"。
👨‍🏫 老师:正是这个意思。
读法:为什么要这层薄壳?因为它必须能在任何 Node 版本下先跑起来、检查版本——如果直接用新语法写在主代码里,老 Node 会直接语法报错、连"请升级"都提示不了。薄壳用最保守的语法,先验版本、开缓存,再加载真正的(编译过的)主程序 dist/entry.js
L03

entry.ts → runCli

dist/entry.jssrc/entry.ts 编译而来。它做启动准备后调 runClisrc/cli/run-main.ts:74):

// src/entry.ts(简化)
process.argv = normalizeWindowsArgv(process.argv);   // Windows argv 归一
// 处理只读 auth store、--no-color 等早期开关
// 若非 version/help 快速路径 →
import("./cli/run-main.js").then(({ runCli }) => runCli(process.argv));
读法:entry.ts 是"启动准备层":处理平台差异(Windows argv)、进程级开关、决定是否 respawn(重启进程以加实验性 flag),最后把控制权交给 runCli把"进程级准备"和"命令分发"分开,各司其职。
openclaw.mjs薄壳·验Node版本·开缓存 dist/entry.js启动准备·argv归一·快速路径 runClicli/run-main.ts · Commander gateway 命令 onboard 命令
启动链:薄壳先验版本 → 编译产物做准备 → Commander 分发到你敲的那个命令。
🚶 第一人称之旅:现在你就是命令 openclaw gateway 你被系统敲进终端 → 顺着 bin 登记表找到 openclaw.mjs,门童拦住你量了量身高(Node 版本够不够 22.12)→ 放行,把你交给 dist/entry.js,它帮你整理行李(argv 归一、开缓存)→ 到了 runCli 前台,前台看你手里写着"gateway",就把你领进 gateway 命令的办公室 → 网关服务器被拉起,开始收发消息。
L04

快速路径(fast path)

entry.ts 里有 tryHandleRootVersionFastPath / tryHandleRootHelpFastPath:128/:148):如果只是 openclaw --version--help,直接处理、不加载全套 CLI。

💡 如果让你自己实现,你会怎么做? 朴素做法:不管用户敲什么,都先把整个 CLI 加载完再看要干嘛——简单,但 openclaw --version 也得等几百毫秒,像"进门先把全屋灯都打开只为拿个快递"。问题:高频轻量操作被重加载拖累。源码方案:在加载全套之前先瞄一眼 argv,是 --version/--help 就地打印、立刻返回。这就是"快速路径"。
为什么要"快速路径"? 加载完整的 CLI(几十个命令、一堆依赖)要花时间。openclaw --version 只想打印个版本号,没必要把整个程序加载起来。快速路径就是"抄近道":一眼看出你只要版本/帮助,就立刻返回,跳过重加载。这是启动性能优化——高频轻量操作走捷径,别为它付全量启动的代价。
L05

Commander 命令分发

runCliCommander(Node 流行的 CLI 框架)构建命令树,各命令在 src/cli/ 注册(如 gateway-cli/register.tsexec-approvals-cli.tslogs-cli.tsupdate-cli.ts)。

读法:Commander 把 openclaw <命令> <子命令> --选项 解析成对应的处理函数。每个功能域一个 register 文件(gateway/onboard/doctor/skills…),保持 CLI 模块化。你敲的 gateway 就匹配到 gateway 命令的处理器。
L06

gateway 命令(最常用)

src/cli/gateway-cli/register.ts 注册 gateway 命令。它启动 Gateway 服务器(Day 03):

openclaw gateway --bind lan --port 18789   # 启动网关,监听 LAN
openclaw gateway --allow-unconfigured      # Docker 默认(未配置也先起来)
读法:这是生产运行的主命令——Docker/compose/fly 的 CMD 都是它(Day 20)。启动后网关拉起各渠道连接、开始收发消息。--bind 控制监听范围(loopback/lan,Day 03/20 的安全点),--port 默认 18789。
L07

onboard 向导

首次使用推荐跑 openclaw onboard(README 首推)。相关代码在 src/commands/onboard-*.ts(实测 ls src/commands/onboard-*.ts 有一大票:onboard-auth.config-*.ts 按不同鉴权网关拆分、onboard-auth.credentials.ts 等)。

📝 举个例子:onboard 一问一答长这样openclaw onboard → 向导依次问:选哪个模型?(Anthropic / OpenAI / …)怎么登录?(OAuth 还是 API key)接哪些渠道?(Telegram / Slack …)装哪些技能?。你只管选,它把答案写进配置文件(Day 05)。每一类问题对应一个 onboard-*.ts
向导(wizard)解决什么? OpenClaw 配置项很多(选哪个模型、连哪些渠道、怎么鉴权、装哪些技能)。让新手直接写配置文件太劝退。onboard 向导用一问一答的交互,一步步领你:配网关 → 选模型 + 登录(OAuth/API key)→ 接渠道 → 选技能。每一步对应一个 onboard-*.ts 文件。这是"降低上手门槛"的产品化投入——又一个"框架"和"产品"的区别:产品必须管好第一次使用体验。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • openclaw 命令指向哪个文件?(bin)
  • openclaw.mjs 这层薄壳为什么必要?做哪三件事?
  • entry.ts → runCli 的职责分工?
  • 快速路径优化什么?
  • gateway 命令干什么?onboard 向导解决什么?
🗣️ 一句话复述openclaw gateway = bin 登记表找到 openclaw.mjs(门童验版本)→ 加载编译好的 entry.js(做启动准备)→ Commander 按你敲的词分发到 gateway 命令。一句话:"门童放行 → 管家备场 → 前台派单"

✋ 动手

cd /Users/bitmart/work/codes/github/openclaw
head -60 openclaw.mjs
grep -n 'runCli\|version.*FastPath\|help.*FastPath' src/entry.ts
ls src/cli/ | grep -iE 'register|gateway|run-main'
ls src/commands/onboard-*.ts
明天预告 · Day 03Gateway 控制平面——网关服务器怎么起、gateway.bind 的安全含义、WebSocket JSON-RPC、server-methods,以及它怎么协调渠道 ↔ Agent。
← Day 01 全景 Day 03 · Gateway 控制平面 →