跪拜 Guibai
← All articles
Backend · OpenAI

The 170,000-Line Go CLI That Treats AI Agents as First-Class Users

By 也无风雨也雾晴 · · 148 views · 2 likes
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most CLI tools bolt on `--json` and call it Agent support, but Agents choke on string-based errors, flat command structures, and missing intent routing. lark-cli demonstrates that structured error contracts, trust-layered commands, and embedded Agent manuals directly raise Agent success rates—and that these contracts must be enforced by lint, not documentation, because a silently changed error type causes an Agent to loop and fail in front of a user.

Summary

lark-cli spans 170,000 lines of Go, 200+ commands, and 18 business domains, but its real weight is architectural. Every design decision starts from the premise that an AI Agent is a second-class user, not an afterthought. Commands are split into three trust layers with a `+` prefix isolating stable Shortcuts from raw API calls. A six-phase execution pipeline handles identity resolution, scope pre-checks, and validation before any business logic runs, so the Agent never has to guess about permissions or parameter formats.

The error system is the standout: nine exhaustive, lint-enforced categories return structured JSON on stderr, letting Agents route on `type` and `subtype` fields instead of parsing human-readable strings. Skill files embedded in the binary give Agents intent-routing tables and prerequisite checks that `--help` never could. Security treats every Agent-supplied flag as untrusted input, with a virtual filesystem layer, output scanning, and mandatory dry-run support.

A factory pattern with lazy-loaded function fields makes 200+ commands independently testable without a DI framework. The output envelope unifies data, metadata, and system notifications like version updates into a single JSON structure. Taken together, these patterns form a transferable blueprint for any CLI that will be called by coding agents.

Takeaways
Commands are split into three trust layers: Shortcuts with a `+` prefix (stable, framework-managed), API Commands (1:1 API mapping), and Raw API (escape hatch with no guardrails).
Every Shortcut runs through a six-phase pipeline—Identity, Config, Scope pre-check, RuntimeContext creation, Validate, Execute—so business logic never handles auth or permissions.
A factory struct with lazy-loaded function fields shares dependencies across 200+ commands and lets tests swap in mocks without a DI framework.
All errors return structured JSON on stderr with a stable `type` and `subtype`; nine exhaustive categories (validation, authentication, authorization, config, network, api, policy, internal, confirmation) each map to a specific exit code and Agent action.
Error categories and subtypes are locked by golangci-lint rules and a custom AST checker—returning a bare `fmt.Errorf` at a command boundary fails CI.
Three identities (user, bot, auto) are resolved from `--as`, config, and auto-detection; each Shortcut declares its supported `AuthTypes`, and strict mode can force bot-only operation.
Skill files written per business domain and embedded via `//go:embed` contain intent-routing tables, prerequisite checks, and terminology mappings that `--help` cannot express.
Security treats Agent-supplied flags as untrusted: a virtual filesystem layer blocks path traversal, output scanning prevents malicious content from entering downstream pipes, and tokens live in the OS keychain, not config files.
All output uses a single JSON envelope with `ok`, `data`, `meta`, and `_notice` fields; the `_notice` field pushes system notifications like version updates without breaking the success semantics.
A `--dry-run` flag is automatically available on every Shortcut, and high-risk write operations require `--yes` or return a typed `confirmation` error (exit 10).
Factory-pattern dependency injection uses plain Go structs and function fields—no wire, dig, or third-party libraries needed.
The `+` prefix is a visual signal that lets an Agent distinguish framework-guaranteed stable commands from raw API passthroughs at a glance.
Conclusions

Agent-Native design is not a feature flag; it is a second user persona that reshapes error contracts, command topology, and security posture from the first line of code.

String-based error handling is the single biggest failure point for Agent-facing CLIs—once an Agent routes on error text, any wording change becomes a breaking change.

Lint-enforced error contracts are a higher-reliability mechanism than documentation or code review because they prevent regressions at the CI level, where Agents cannot silently break.

The three-layer command system solves a real coordination problem: Agents need to know which commands carry framework guarantees and which are thin API wrappers with no safety net.

Embedding Skill files in the binary solves version skew between the CLI and the Agent's operating knowledge—an upgraded CLI cannot leave an Agent reading stale instructions.

Treating Agent input as untrusted is not paranoia; prompt injection and looped retries make Agents a fundamentally different threat model than human typists at a terminal.

Factory patterns with function fields are underused in Go CLIs; they give the testability of dependency injection without the complexity of a framework.

The `_notice` envelope field is a quiet but powerful pattern: system notifications piggyback on the success response rather than requiring a separate channel or breaking output parsing.

Most CLI tools stop at 'add a `--json` flag'; lark-cli shows that structured output is table stakes, and the real work is in structured errors, layered commands, and Agent-readable manuals.

An Agent that cannot distinguish a missing-scope error from a network timeout will retry the wrong remediation and erode user trust—error taxonomy is a reliability primitive, not a UX nicety.

Concepts & terms
Shortcut
A stable, framework-managed command prefixed with `+` that handles identity resolution, scope validation, error classification, and pagination automatically, so the Agent only supplies business parameters.
Execution Pipeline
A six-phase sequence (Identity → Config → Scopes → RuntimeContext → Validate → Execute) that runs before every Shortcut's business logic, ensuring the Agent never manually handles auth, permissions, or parameter validation.
Typed Error Envelope
A structured JSON error returned on stderr with stable `type` and `subtype` fields, an exit code, and optional machine-readable fields like `missing_scopes` or `hint`, enabling Agents to route recovery actions without parsing human-readable messages.
Factory Pattern (Go-style)
A struct holding all shared dependencies as function fields (e.g., `Config func() (*CliConfig, error)`), lazily initialized in production and directly replaced with mocks in tests, avoiding global state and DI frameworks.
Skill File
A markdown document per business domain, embedded in the binary via `//go:embed`, containing intent-routing tables, prerequisite checks, and terminology mappings that teach an Agent which command to call for a given user request.
Virtual Filesystem (vfs) Layer
An abstraction over file I/O that validates all paths before opening, rejecting absolute paths, `../` traversal, symlink escapes, and control characters, treating Agent-supplied file paths as untrusted input.
Strict Mode
An administrator-enforced setting (`--strict-mode bot`) that locks all commands to bot identity only, causing any `--as user` call to fail immediately rather than silently degrading.
Output Envelope
A unified JSON structure (`ok`, `data`, `meta`, `_notice`) wrapping every command's output, allowing system notifications like version updates to be delivered without breaking the success/failure contract.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗