Engineering Diary, Day 19: We Built a CLI Because AI Agents Hate Browsers
The Thesis: CLI Is the New API
Something shifted in 2026. The AI agent ecosystem stopped trying to make agents use browsers and started meeting them where they actually work — the terminal. Devin, Cursor, Claude Code, Windsurf, Cline — every serious agent runtime has first-class shell access. And the data backs it up:
- Task completion: CLI commands succeed 28% more often than equivalent MCP tool calls in benchmarks
- Token efficiency: A
volo search "NYC to London"command costs 4-32x fewer tokens than navigating a web UI or parsing HTML - Composability:
volo fleet list | jq '.[] | select(.range_km > 10000)'— Unix pipes are the original function composition - Determinism: No DOM to break, no CSS selectors to rot, no cookie banners to dismiss
E2B, Stripe, Vercel, Supabase, Neon — every developer-facing company now treats their CLI as a first-class agent interface. VOLO already had REST, GraphQL, MCP, and OpenAI Plugin. The missing piece was obvious.
Architecture: Four Dependencies and a Principle
The design constraint was simple: stdout is for machines, stderr is for humans.
By default, every command outputs clean JSON to stdout. No spinners, no colors, no progress bars. An AI agent can pipe the output directly to jq or parse it with JSON.parse(). Add --pretty and you get colored tables, loading spinners, and formatted output — but only on stderr, never polluting the data stream.
# Agent mode (default) — clean JSON
volo search "NYC to London 4 pax" | jq '.data.flights[0].price'
# Human mode — colored tables
volo fleet list --pretty --category heavy
Production dependencies: exactly four.
| Package | Purpose | Size |
|---|---|---|
| commander | CLI framework | ~50KB |
| chalk | Terminal colors (--pretty only) | ~16KB |
| cli-table3 | Table formatting (--pretty only) | ~30KB |
| ora | Loading spinners (--pretty only) | ~20KB |
HTTP? Node 18+ native fetch. Zero dependency. Config storage? fs.writeFileSync with 0600 permissions. No keychain libraries, no credential managers — just a JSON file only the owner can read.
16 Commands, One API Surface
The CLI maps directly to VOLO's REST API. Every command is a thin wrapper around an HTTP call:
volo search "Shanghai to Tokyo next Friday, 4 passengers"
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 bot@example.com
volo agent dashboard --pretty
volo chat "find me a heavy jet for 8 passengers"
volo api GET /v1/openapi.json
volo config list
Commands that don't need authentication (search, weather, fleet, airports, track) work instantly with npx volo-cli. No signup, no API key, no configuration. Agent commands (dashboard, referrals) require a key obtained via volo agent register.
The Security Architecture
This was the most important design decision and the one that required the most discipline. VOLO integrates with multiple upstream aviation data providers. The CLI must never expose any of them.
The principle: the CLI is a pure API client that only knows about flyvolo.ai. It has no awareness that upstream providers exist.
User/Agent ──> volo-cli ──> flyvolo.ai/api/v1/* ──> [VOLO Backend] ──> upstream providers
│ ↑
│ All provider calls
│ happen server-side only
└── CLI only knows flyvolo.ai, nothing else
What this means in practice:
- No upstream URLs: The CLI source code contains exactly one domain —
flyvolo.ai. No provider API addresses, no proxy URLs, no CDN endpoints. - No internal IDs: The embedded fleet data contains public information only — aircraft names, seat counts, range, price ranges. No supplier-internal identifiers.
- No leaked error details: When an upstream API fails, the CLI shows "Service temporarily unavailable" — never the upstream error message or URL.
- No provider imports: The CLI package has zero imports from internal packages. It doesn't depend on
@volo/ai,@volo/database, or any provider client library.
The auth chain follows defense-in-depth:
Priority: VOLO_API_KEY env > --api-key flag > ~/.volo/config.json > anonymous
Credentials in ~/.volo/config.json are stored with 0600 permissions (owner-only read/write). volo config list masks API keys in display (volo_abc...xyz). volo auth logout securely deletes the file.
Offline Capability
Two commands work without any network connection:
volo fleet list: Embedded data for 15 aircraft across 4 categories (light, midsize, super-midsize, heavy/ultra-long-range). Complete specs: name, manufacturer, seats, range, speed, cabin dimensions, price range.volo fleet compare: Side-by-side comparison from embedded data. Tryvolo fleet compare "Citation CJ4" "Phenom 300E" --pretty.
The embedded data mirrors what's publicly displayed on the website — no internal pricing models, no supplier margins, no confidential data. It's the same information any visitor to flyvolo.ai can see.
Interactive Chat: The Agent Concierge in Your Terminal
volo chat brings VOLO's 14-tool AI concierge to the command line. Two modes:
# Single-shot (great for agents)
volo chat "find empty legs from London this week under $30k"
# Interactive REPL (great for humans)
volo chat
volo> I need a jet from Shanghai to Tokyo for 6 people next month
volo> what about a Global 7500 instead?
volo> /quit
The REPL maintains conversation history across turns, so the concierge remembers context. Single-shot mode returns JSON — perfect for agent pipelines.
Frontend Integration: CLI as Fifth Protocol
Building the CLI was half the work. The other half was making it visible. We integrated CLI into every section of the Agent homepage:
| Component | Change |
|---|---|
| CodeTabs | Added 8th "CLI" tab with installation and command examples |
| AgentHero | Added npx volo-cli --help copyable install card below stats |
| EndpointCards | Each API endpoint now shows its CLI equivalent in the expanded view |
| ProtocolStatus | Added 5th "CLI" protocol card (status: LIVE) |
| AgentHome | Added CLI entry point in Quick Start section |
CLI is now a first-class citizen alongside REST, GraphQL, MCP, and OpenAI Plugin — visible from the moment an agent or developer lands on /for-agents.
The Numbers
| Metric | Value |
|---|---|
| Commands | 16 (+ config/auth) |
| Source files | 28 |
| Lines of code | ~2,500 |
| Production dependencies | 4 |
| Tests | 17 (all passing) |
| Upstream APIs exposed | 0 |
| Offline commands | 2 (fleet list, fleet compare) |
| Frontend components modified | 5 |
| Time from plan to deploy | 1 session |
What I Learned
1. JSON-first is non-negotiable for agent interfaces
The moment you mix human-readable formatting into stdout, you break every downstream consumer. Agents can't parse tables. They can't strip ANSI color codes reliably. The stdout/stderr split isn't a nice-to-have — it's the fundamental contract.
2. Security by architecture, not by policy
We didn't add a "don't expose upstream APIs" lint rule. We made it structurally impossible — the CLI package has no dependency on any internal package, no import path to any provider client, and exactly one hardcoded domain. The safest code is code that can't reach the thing it shouldn't touch.
3. Offline capability earns trust
volo fleet compare G650 "Global 7500" works on an airplane with no WiFi. That's not a gimmick — it's proof that your tool respects the user's context. When an agent is running in a restricted sandbox, offline commands still work.
4. The monorepo pattern matters
Adding a new package to a Turborepo monorepo is almost frictionless. packages/cli/ slots in next to packages/mcp-server/. Same build pattern, same tsconfig base, same CI pipeline. The MCP server was the template — we matched its structure exactly.
5. CLI is the fastest path from zero to value
npx volo-cli search "NYC to Aspen" — that's it. No browser, no signup, no API key, no SDK installation. One command, instant results. For an AI agent evaluating whether to integrate with VOLO, the friction is essentially zero.
What's Next
- npm publish: Push
volo-clito the public npm registry sonpx volo-cliworks globally - Shell completions: Tab completion for bash/zsh/fish
- Plugin system: Let agent developers extend volo-cli with custom commands
- Streaming output: Real-time flight tracking with
--watchflag - CI/CD integration: GitHub Actions for automated aviation data pipelines
비행 준비가 되셨나요? 몇 초 안에 맞춤 전세 견적을 받으세요.
최신 소식 받기
엠프티 레그 특가, 신규 노선, 항공 인사이트를 이메일로 받아보세요.