← 全部文章← All writing

VOLO 工程日记

工程日记・第十九天:我们做了个CLI,因为AI Agent讨厌浏览器

本文原载于 flyvolo.ai 的「VOLO 工程日记」系列(2026-03-18),记录 2026 年 2–5 月 VOLO 私人航空平台的构建过程。2026 年 9 月 VOLO 转向职业自动化导航后,25 篇日记迁至此处归档。正文保持原样;文中提到的产品、页面与链接已不再存在。原文署名:VOLO CTO。

核心论点:CLI是新的API

2026年,有些东西变了。AI Agent生态不再试图让Agent使用浏览器,而是开始在Agent真正工作的地方与它们会面——终端。Devin、Cursor、Claude Code、Windsurf、Cline——每个正经的Agent运行时都有一流的shell访问能力。数据也证实了这一点:

  • 任务完成率:在基准测试中,CLI命令的成功率比等效的MCP工具调用高28%
  • Token效率:一条 volo search "NYC to London" 命令消耗的token比浏览网页UI或解析HTML少4-32倍
  • 可组合性:volo fleet list | jq '.[] | select(.range_km > 10000)' —— Unix管道是最初的函数组合
  • 确定性:没有会崩的DOM,没有会失效的CSS选择器,没有需要关闭的Cookie横幅

E2B、Stripe、Vercel、Supabase、Neon——每个面向开发者的公司现在都把CLI当作一流的Agent接口。VOLO已经有REST、GraphQL、MCP和OpenAI Plugin。缺失的那块拼图很明显。

架构:四个依赖和一个原则

设计约束很简单:stdout给机器,stderr给人类。

默认情况下,每个命令都向stdout输出干净的JSON。没有加载动画,没有颜色,没有进度条。AI Agent可以直接把输出管道到 jq 或用 JSON.parse() 解析。加上 --pretty 就有彩色表格、加载动画和格式化输出——但只在stderr上,永远不会污染数据流。

# Agent模式(默认)—— 干净的JSON
volo search "NYC to London 4 pax" | jq '.data.flights[0].price'
 
# 人类模式 —— 彩色表格
volo fleet list --pretty --category heavy

生产依赖:恰好四个。

包用途大小
commanderCLI框架~50KB
chalk终端颜色(仅--pretty模式)~16KB
cli-table3表格格式化(仅--pretty模式)~30KB
ora加载动画(仅--pretty模式)~20KB

HTTP?Node 18+原生 fetch。零依赖。配置存储?fs.writeFileSync 配合 0600 权限。没有钥匙串库,没有凭据管理器——就是一个只有所有者能读的JSON文件。

16个命令,一个API面

CLI直接映射到VOLO的REST API。每个命令都是HTTP调用的薄封装:

volo search "上海到东京下周五,4位乘客"
volo quote --from ZSPD --to RJTT --pax 4 --date 2026-04-15
volo track CX520
volo weather Singapore --forecast 3
volo fleet list --category heavy
volo fleet compare "Gulfstream G650ER" "Bombardier Global 7500"
volo airports search "changi"
volo empty-legs --region asia-pacific --limit 10
volo routes --tag popular
volo destinations --region europe
volo content --type fleet --locale zh
volo agent register --name MyBot --email [email protected]
volo agent dashboard --pretty
volo chat "帮我找一架重型公务机,8位乘客"
volo api GET /v1/openapi.json
volo config list

不需要认证的命令(search、weather、fleet、airports、track)用 npx volo-cli 立即可用。无需注册,无需API key,无需配置。Agent命令(dashboard、referrals)需要通过 volo agent register 获取的密钥。

安全架构

这是最重要的设计决策,也是需要最多纪律性的决策。VOLO集成了多个上游航空数据供应商。CLI必须永远不暴露它们中的任何一个。

原则:CLI是一个纯API客户端,只知道 flyvolo.ai。它不知道上游供应商的存在。

用户/Agent ──> volo-cli ──> flyvolo.ai/api/v1/* ──> [VOLO后端] ──> 上游供应商
                 │                                          ↑
                 │                                    所有供应商调用
                 │                                    仅在服务端发生
                 └── CLI只知道flyvolo.ai,不知道其他任何东西

实践中的意义:

  • 无上游URL:CLI源码中只包含一个域名——flyvolo.ai。没有供应商API地址,没有代理URL,没有CDN端点。
  • 无内部ID:内嵌的机队数据只包含公开信息——飞机名称、座位数、航程、价格区间。没有供应商内部标识符。
  • 无泄露的错误详情:当上游API失败时,CLI显示"服务暂时不可用"——永远不显示上游错误消息或URL。
  • 无供应商导入:CLI包对内部包的导入为零。不依赖 @volo/ai、@volo/database 或任何供应商客户端库。

认证链遵循纵深防御:

优先级:VOLO_API_KEY 环境变量 > --api-key 参数 > ~/.volo/config.json > 匿名

~/.volo/config.json 中的凭据以 0600 权限存储(仅所有者可读写)。volo config list 在显示时遮蔽API key(volo_abc...xyz)。volo auth logout 安全删除文件。

离线能力

两个命令无需任何网络连接即可工作:

  • volo fleet list:内嵌4个类别15款飞机的数据(轻型、中型、超中型、重型/超远程)。完整规格:名称、制造商、座位数、航程、速度、客舱尺寸、价格区间。
  • volo fleet compare:基于内嵌数据的并排对比。试试 volo fleet compare "Citation CJ4" "Phenom 300E" --pretty。

内嵌数据与网站上公开展示的信息完全一致——没有内部定价模型,没有供应商利润率,没有机密数据。

交互式聊天:终端里的AI管家

volo chat 把VOLO的14工具AI管家带到了命令行。两种模式:

# 单次调用(适合Agent)
volo chat "找找这周从伦敦出发3万美元以下的空腿航班"
 
# 交互式REPL(适合人类)
volo chat
volo> 我需要一架下个月从上海到东京的飞机,6个人
volo> Global 7500怎么样?
volo> /quit

REPL跨对话轮次保持上下文记忆,管家会记住之前聊的内容。单次模式返回JSON——完美适配Agent流水线。

前端集成:CLI成为第五协议

构建CLI只是一半的工作。另一半是让它被看见。我们把CLI集成到了Agent主页的每个部分:

组件变更
CodeTabs添加第8个"CLI"标签页,包含安装和命令示例
AgentHero在统计数据下方添加 npx volo-cli --help 可复制安装卡片
EndpointCards每个API端点在展开视图中显示对应的CLI命令
ProtocolStatus添加第5个"CLI"协议卡片(状态:LIVE)
AgentHome在Quick Start部分添加CLI入口点

CLI现在与REST、GraphQL、MCP和OpenAI Plugin并列——从Agent或开发者访问 /for-agents 的那一刻起就能看到。

核心数据

指标数值
命令数16(+ config/auth)
源文件28
代码行数~2,500
生产依赖4
测试17(全部通过)
暴露的上游API0
离线命令2(fleet list、fleet compare)
修改的前端组件5
从规划到部署1个会话

我学到的

1. JSON-first对Agent接口来说没有商量余地

你在stdout中混入人类可读格式的那一刻,就破坏了每一个下游消费者。Agent无法解析表格,无法可靠地去除ANSI颜色码。stdout/stderr的分离不是"有更好"——它是根本契约。

2. 通过架构而非策略来保证安全

我们没有添加"不要暴露上游API"的lint规则。我们让它在结构上不可能——CLI包不依赖任何内部包,没有通往任何供应商客户端的导入路径,只硬编码了一个域名。最安全的代码是那些根本无法触及不应触及之物的代码。

3. 离线能力赢得信任

volo fleet compare G650 "Global 7500" 在没有WiFi的飞机上也能用。这不是噱头——它证明了你的工具尊重用户的环境。当Agent在受限沙箱中运行时,离线命令依然有效。

4. Monorepo模式很重要

向Turborepo monorepo添加新包几乎没有摩擦。packages/cli/ 自然地放在 packages/mcp-server/ 旁边。相同的构建模式,相同的tsconfig基础,相同的CI管道。MCP server就是模板——我们完全匹配了它的结构。

5. CLI是从零到价值的最快路径

npx volo-cli search "NYC to Aspen" ——就这样。不需要浏览器,不需要注册,不需要API key,不需要安装SDK。一个命令,立即获得结果。对于一个正在评估是否与VOLO集成的AI Agent来说,摩擦基本为零。

下一步

  • npm publish:将 volo-cli 推送到公共npm registry,让 npx volo-cli 全球可用
  • Shell补全:bash/zsh/fish的Tab补全
  • 插件系统:让Agent开发者用自定义命令扩展volo-cli
  • 流式输出:用 --watch 标志实时追踪航班
  • CI/CD集成:用GitHub Actions构建自动化航空数据流水线