写完 OpsPilot CLI 后,我对命令行工具的几个判断
写完 OpsPilot CLI 后,我对命令行工具的几个判断
这次写 OpsPilot CLI,我最大的感受是:真正有价值的 CLI 不是把函数包一层命令,而是把一个系统的操作模型固定下来。
一开始我看这个项目时,CLI 还只是计划。那时最关心的问题是边界:命令层到底应该直接调用内部模块,还是先定义一个更稳定的运行时接口?如果只是为了尽快有一个opspilot xxx能跑,直接 import dispatcher、session manager、LangGraph 节点都可以。但那样写出来的 CLI 会很脆弱,底层一重构,命令也跟着重写。
现在回头看,先把OpsPilot运行时收口出来,是 CLI 能写顺的前提。
先有运行时,再有 CLI
我以前也写过一些命令行工具,很多时候会从 Typer 或 argparse 开始:先列命令,再往每个 command 里塞实现。
这次不太一样。OpsPilot 不是普通脚本,它有后台 worker、会话状态、审批等待、通知渠道、持久化和 HTTP 宿主。如果 CLI 直接拼这些模块,命令层很快会变成第二个应用层,而且是更难测试的应用层。
所以真正的第一步不是写命令,而是让系统有一个可以被命令行驱动的对象:
op=OpsPilot(config)awaitop.start()awaitop.ingest(events)awaitop.decide_approval(approval_id,"approve",message)awaitop.stop()有了这个对象,CLI 才能保持薄:参数解析、输出格式、错误处理和远程请求属于 CLI;告警、会话、审批、工具、通知属于运行时。
这个分界一旦清楚,后面的代码会自然很多。
CLI 不应该暴露工程内部词汇
写命令时我一直提醒自己:用户不是来操作代码结构的。
用户想看的不是SessionManager,而是 sessions。不是ToolRegistry,而是 tools。不是AlertHandler,而是 alerts。不是某个内部 future,而是 approvals。
所以命令组最后长这样:
config projects tools server alerts sessions approvals notifications reports这其实是产品设计,不只是代码组织。
如果一个 CLI 的命令名称和内部模块名高度一致,通常说明它还没有站在使用者视角整理过。OpsPilot 面向的是运维和开发值班人员,他们在终端里需要的是“现在有什么告警”“哪个会话卡住了”“哪些操作要审批”“能不能看报告”,而不是“某个 manager 的某个方法返回什么”。
远程模式比本地模式更重要
最开始想 CLI 时,我容易先想到本地命令:读配置、加载工具、启动 runtime,然后执行动作。
但 OpsPilot 这种系统不适合只做本地模式。真正运行时通常是一个 server 进程,CLI 很可能是另一个进程,甚至在另一台机器上。它不能靠读内存里的SessionManager来看状态。
所以这次 CLI 把运行时操作放在控制 API 后面:
CLI -> /api/control/v1 -> OpsPilot runtime本地命令仍然存在,但主要用于配置检查和工具列表。涉及告警、会话、审批、通知、报告的命令,默认走控制 API。
这让命令行工具更像一个真正的运维客户端,而不是开发机上的调试脚本。
安全细节不能等以后再补
CLI 开发里有几个细节很容易被低估。
第一个是控制 API token。Webhook secret 和 control token 必须分开。告警源只应该能投递告警,不应该能查会话、取消任务或审批工具调用。
第二个是脱敏输出。config show很方便,但它如果把 API key、token、password、webhook URL 原样打出来,就会变成泄密入口。所以脱敏不是体验优化,而是底线。
第三个是审批确认。钉钉卡片上的按钮不能直接改变系统状态。聊天软件里的按钮可能被转发、误触或过期,真正的 approve/reject 必须回到 OpsPilot server,由 server 校验签名和有效期,再展示确认页。
这些东西如果等“功能稳定后再补”,后面通常会补得很别扭。因为它们会影响接口形态,而不是只影响界面文案。
输出要同时服务人和脚本
CLI 输出也有取舍。
面向值班人员,表格最好读:
opspilot sessions list opspilot approvals list面向自动化,JSON 才可靠:
opspilot sessions list --format json这次我不想把输出格式和业务逻辑混在一起。运行时和控制 API 返回结构化数据,CLI rendering 层再决定是打印表格还是 JSON。这样以后加字段、改表格列、接脚本都比较稳。
同样,错误也应该清楚。配置文件错了、项目不存在、token 不对、审批已经不存在,这些都要有明确退出码和信息。命令行工具最怕失败时只给一段内部 traceback,用户不知道下一步该改什么。
测试让我敢说它是控制面
写 CLI 时我不太愿意只测“命令能启动”。那种测试只能证明 Typer 没写炸,不能证明系统可用。
这次更关键的测试在运行时和控制 API 上:
- 告警能进入 dispatcher。
- session 能创建并写入持久化。
- 危险工具调用会进入 pending approval。
- approve 后工具能继续执行。
- reject 后工具会被阻断。
- 审批超时会自动拒绝。
- 取消会话会清理 pending approval。
- 控制 API 必须鉴权。
- 钉钉审批确认路由能被挂载。
这些测试让我对 CLI 的信心不是来自“我试过一次命令”,而是来自核心路径被自动验证过。
我最后得到的判断
这次写完以后,我对运维类 CLI 有几个更明确的判断。
第一,CLI 应该是产品操作模型,不是内部 API 目录。
第二,运行时要先收口。没有稳定运行时,CLI 会变成散落的胶水代码。
第三,远程控制能力要尽早设计。只会本地读内存的 CLI,很难进入真实使用场景。
第四,安全和脱敏不是后期 polish,它们会决定接口边界。
第五,表格和 JSON 都要有。人和脚本都是 CLI 的用户。
OpsPilot CLI 写到现在,我觉得它最有价值的地方不是命令数量,而是它把系统从“代码里能跑”推进到了“操作者可以控制”。
这一步完成后,后续不管是接前端、补更多通知渠道,还是扩展报告和审计,都有了一个已经验证过的操作面可以复用。
