入口与启动流程
昨天看了 OpenClaw 的全景地图。今天钻进第一格——启动:你敲 openclaw gateway 回车,到底发生了什么?追一遍启动链:openclaw.mjs(bin)→ dist/entry.js → runCli(Commander)→ 对应命令。以及 openclaw onboard 配置向导。搞懂它,明天讲的 Gateway 才知道"是被谁、怎么拉起来的"。
bin 入口
/usr/bin/openclaw 这个文件,为什么装完就能敲 openclaw gateway?如果没有一套约定,每个工具都得手动配 PATH、写启动脚本,乱成一团。package.json 的 bin 声明 openclaw → openclaw.mjs,安装时 npm 自动在系统 PATH 里建一个软链。于是敲 openclaw 就等于 node openclaw.mjs。// package.json:16
"bin": { "openclaw": "openclaw.mjs" }
"main": "dist/index.js"
package.json 的 bin 字段声明"这个包提供哪些命令行工具"。你 npm install -g openclaw 后,系统里就有了 openclaw 命令,它指向 openclaw.mjs 这个文件。所以你敲 openclaw gateway,实际就是 node openclaw.mjs gateway。.mjs 是 ES Module 格式的 JS 文件。这是所有 Node CLI 工具的标准入口机制。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).");
entry.ts 里不就行了?👨🏫 老师:不行。如果你用的是老版本 Node(比如 v18),主程序里的新语法会让它直接语法报错——连"请升级到 v22"这句话都打印不出来,用户一脸懵。所以要一层"用最老最保守语法写的薄壳"先跑,先验版本、给出友好指引,再去加载新语法写的主程序。
👶 小白:哦!所以薄壳是"翻译不好但一定能开口说话的门童"。
👨🏫 老师:正是这个意思。
dist/entry.js。entry.ts → runCli
dist/entry.js 由 src/entry.ts 编译而来。它做启动准备后调 runCli(src/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));
runCli。把"进程级准备"和"命令分发"分开,各司其职。openclaw gateway
你被系统敲进终端 → 顺着 bin 登记表找到 openclaw.mjs,门童拦住你量了量身高(Node 版本够不够 22.12)→ 放行,把你交给 dist/entry.js,它帮你整理行李(argv 归一、开缓存)→ 到了 runCli 前台,前台看你手里写着"gateway",就把你领进 gateway 命令的办公室 → 网关服务器被拉起,开始收发消息。快速路径(fast path)
entry.ts 里有 tryHandleRootVersionFastPath / tryHandleRootHelpFastPath(:128/:148):如果只是 openclaw --version 或 --help,直接处理、不加载全套 CLI。
openclaw --version 也得等几百毫秒,像"进门先把全屋灯都打开只为拿个快递"。问题:高频轻量操作被重加载拖累。源码方案:在加载全套之前先瞄一眼 argv,是 --version/--help 就地打印、立刻返回。这就是"快速路径"。openclaw --version 只想打印个版本号,没必要把整个程序加载起来。快速路径就是"抄近道":一眼看出你只要版本/帮助,就立刻返回,跳过重加载。这是启动性能优化——高频轻量操作走捷径,别为它付全量启动的代价。Commander 命令分发
runCli 用 Commander(Node 流行的 CLI 框架)构建命令树,各命令在 src/cli/ 注册(如 gateway-cli/register.ts、exec-approvals-cli.ts、logs-cli.ts、update-cli.ts)。
openclaw <命令> <子命令> --选项 解析成对应的处理函数。每个功能域一个 register 文件(gateway/onboard/doctor/skills…),保持 CLI 模块化。你敲的 gateway 就匹配到 gateway 命令的处理器。gateway 命令(最常用)
src/cli/gateway-cli/register.ts 注册 gateway 命令。它启动 Gateway 服务器(Day 03):
openclaw gateway --bind lan --port 18789 # 启动网关,监听 LAN
openclaw gateway --allow-unconfigured # Docker 默认(未配置也先起来)
CMD 都是它(Day 20)。启动后网关拉起各渠道连接、开始收发消息。--bind 控制监听范围(loopback/lan,Day 03/20 的安全点),--port 默认 18789。onboard 向导
首次使用推荐跑 openclaw onboard(README 首推)。相关代码在 src/commands/onboard-*.ts(实测 ls src/commands/onboard-*.ts 有一大票:onboard-auth.config-*.ts 按不同鉴权网关拆分、onboard-auth.credentials.ts 等)。
openclaw onboard → 向导依次问:选哪个模型?(Anthropic / OpenAI / …) → 怎么登录?(OAuth 还是 API key) → 接哪些渠道?(Telegram / Slack …) → 装哪些技能?。你只管选,它把答案写进配置文件(Day 05)。每一类问题对应一个 onboard-*.ts。onboard-*.ts 文件。这是"降低上手门槛"的产品化投入——又一个"框架"和"产品"的区别:产品必须管好第一次使用体验。今日小结 + 动手
🧠 今天你应该能回答
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
gateway.bind 的安全含义、WebSocket JSON-RPC、server-methods,以及它怎么协调渠道 ↔ Agent。