小龙虾(OpenClaw)源码分析2:CLI启动链路,从一条命令说起

文章目录

上一篇我们把地图铺开了,这一篇开始走主链路第一站:CLI启动。
毕竟无论你是跑openclaw gateway还是openclaw agent,第一步都绕不开CLI入口。

先看两个入口文件

这个项目里和启动强相关的入口,先盯两个就够了:

  • src/entry.ts
  • src/cli/run-main.ts

我自己的理解是:

  • entry.ts负责“程序级起步动作”(环境、参数预处理、快速路径、异常兜底)
  • run-main.ts负责“CLI主流程调度”(解析参数、注册命令、执行命令)

entry.ts在干嘛

entry.ts里有几段很关键的逻辑:

  1. 只在主模块执行(避免被import时重复启动)
  2. 处理--no-color这类环境行为
  3. 尝试respawn(某些场景会子进程重启CLI)
  4. 最终进入runMainOrRootHelp

换句话说,它把“启动姿势”先调整好,再把执行权交给真正的CLI主流程。

run-main.ts才是命令分发核心

src/cli/run-main.ts函数runCli()里,主流程大概是这样:

  1. 规范化argv(含Windows兼容)
  2. 处理--profile/--container这类全局参数
  3. 加载.env和运行时环境
  4. 处理root help/version快速路径
  5. 执行路由tryRouteCli
  6. 构建program并注册命令(核心命令 + 插件命令)
  7. program.parseAsync(...)真正执行

这段代码的设计我觉得很实用:
先做一层“轻路由/快路径”再进入完整命令解析,启动体验会更干脆。

一个典型执行过程

比如你执行:

openclaw gateway --port 18789 --verbose

大致会经历:

  • entry.ts接管进程级初始化
  • 转到runCli()做参数预处理
  • 命令注册阶段识别到gateway
  • Commander解析并进入对应命令处理逻辑
  • 后续才会进入Gateway启动流程(这是下一篇重点)

为什么要先注册核心命令,再按需注册插件命令

run-main.ts里可以看到,它会根据参数判断注册策略,比如只注册主命令、或补充插件命令。
这样做的好处是:

  • help输出更准确
  • 非必要插件命令不会过早拉起
  • 启动性能和可维护性更平衡

从工程角度讲,这是一种“按需装配”的CLI架构思路。

读这部分源码的建议

你可以按下面顺序读,效率更高:

  1. src/entry.ts
  2. src/cli/run-main.ts
  3. src/cli/program/build-program.ts
  4. src/cli/program/command-registry.ts

不要一上来就陷入所有命令实现细节,先把“命令是如何被定位和执行的”这件事跑通。

小结

这一篇我们搞清楚了三件事:

  • OpenClaw CLI不是一层薄壳,而是分层启动
  • entry.ts偏进程和环境,run-main.ts偏命令调度
  • 命令注册是按需策略,不是无脑全量加载

下一篇进入Gateway启动内幕,把控制平面是怎么一点点挂起来的讲清楚。

参考链接