← Back to blog

Designing CLIs for AI Agents: The --json Pattern

AI agents are the new power users. Here's how to build CLIs they can actually use.

The best CLI users in 2026 aren't humans. They're AI agents.

Claude Code, Cursor, Copilot agents — they all shell out to CLI tools. And they all hit the same wall: parsing human-readable output.

✓ Server "my-app" forged (65.21.123.45)
  SSH: gibil ssh my-app
  TTL: 60 minutes

A human reads that fine. An agent needs to regex out the IP address and hope the format doesn't change next release.

The fix: --json everywhere

Every command in gibil supports --json. The same create command:

{
  "name": "my-app",
  "ip": "65.21.123.45",
  "ssh_command": "gibil ssh my-app",
  "ttl_minutes": 60,
  "expires_at": "2026-03-28T11:30:00Z"
}

The agent calls gibil create --name my-app --json, parses the JSON, extracts ip, and moves on. No regex. No fragile string matching. No breaking when you add a field to the human output.

The pattern

If you're building a CLI that agents will use, here are the rules we follow:

1. Structured output on every command

Not just the important ones. Every command — create, list, destroy, run. An agent doesn't know which commands are "important."

# Human mode (default)
gibil list
# my-app    65.21.123.45    running    45m remaining

# Agent mode
gibil list --json
# [{"name": "my-app", "ip": "65.21.123.45", "status": "running", "ttl_remaining_minutes": 45}]

2. Meaningful exit codes

0 for success. Non-zero for failure. Agents check exit codes before they parse output.

gibil run my-app "pnpm test" --json
# exit code 0 → tests passed
# exit code 1 → tests failed (output has details)
# exit code 2 → connection failed (different kind of error)

Don't exit 0 when the operation failed but the CLI process "ran successfully." The agent needs the truth.

3. Stderr for logs, stdout for data

When --json is set:

  • stdout: Only the JSON result
  • stderr: Progress messages, warnings, debug info
# stdout (agent parses this)
{"name": "my-app", "ip": "65.21.123.45"}

# stderr (agent ignores this)
 Forging server...
 Waiting for SSH...
 Ready

Agents pipe stdout to jq without garbage. Humans still see progress in the terminal.

4. Idempotent commands

Agents retry. Networks fail. Commands get interrupted. If gibil destroy my-app is called twice, the second call should succeed (or return a clear status), not crash.

{"destroyed": true, "name": "my-app", "was_already_destroyed": false}

5. Error objects, not error strings

When something fails:

{
  "error": true,
  "code": "SERVER_NOT_FOUND",
  "message": "Server 'my-app' not found. Run gibil list to see active servers."
}

The agent switches on code. A human reads message. Nobody parses an error string to figure out what went wrong.

Why this matters now

AI agents are moving from "answer a question" to "complete a task" — and tasks require tools.

The tools that work are the ones agents can use without guessing. --json, meaningful exit codes, clean error objects. None of this is hard to implement. It just needs to be intentional from the start.

Every CLI shipped without structured output is a tool that only humans can use reliably. And the fastest-growing user segment doesn't read terminal output — it parses it.