cli-design
Original:🇺🇸 English
Translated
Design and build agent-first CLIs with HATEOAS JSON responses, context-protecting output, and self-documenting command trees. Use when creating new CLI tools, adding commands to existing CLIs (joelclaw, slog, igs), or reviewing CLI design for agent-friendliness. Triggers on 'build a CLI', 'add a command', 'CLI design', 'agent-friendly output', or any task involving command-line tool creation.
8installs
Sourcejoelhooks/joelclaw
Added on
NPX Install
npx skill4agent add joelhooks/joelclaw cli-designTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Agent-First CLI Design
CLIs in this system are agent-first, human-distant-second. Every command returns structured JSON that an agent can parse, act on, and follow. Humans are welcome to pipe through .
jqCore Principles
1. JSON always
Every command returns JSON. No plain text. No tables. No color codes. Agents parse JSON; they don't parse prose.
bash
# This is the ONLY output format
joelclaw status
# → { "ok": true, "command": "joelclaw status", "result": {...}, "next_actions": [...] }No flag. No flag. JSON is the default and only format.
--json--human2. HATEOAS — every response tells you what to do next
Every response includes — an array of commands the agent can run next, with descriptions. The agent never has to guess what's available.
next_actionsjson
{
"ok": true,
"command": "joelclaw send pipeline/video.download",
"result": {
"event_id": "01KHF98SKZ7RE6HC2BH8PW2HB2",
"status": "accepted"
},
"next_actions": [
{
"command": "joelclaw run 01KHF98SKZ7RE6HC2BH8PW2HB2",
"description": "Check run status for this event"
},
{
"command": "joelclaw logs --follow",
"description": "Watch worker logs in real-time"
},
{
"command": "joelclaw health",
"description": "Check system health"
}
]
}next_actions3. Self-documenting command tree
The root command (no args) returns the full command tree so an agent can discover everything in one call:
json
{
"ok": true,
"command": "joelclaw",
"result": {
"description": "JoelClaw — personal AI system CLI",
"health": { "server": {...}, "worker": {...} },
"commands": [
{ "name": "send", "description": "Send event to Inngest", "usage": "joelclaw send <event> -d '<json>'" },
{ "name": "status", "description": "System status", "usage": "joelclaw status" },
{ "name": "health", "description": "Health check all services", "usage": "joelclaw health" }
]
},
"next_actions": [...]
}4. Context-protecting output
Agents have finite context windows. CLI output must not blow them up.
Rules:
- Terse by default — minimum viable output
- Auto-truncate large outputs (logs, lists) at a reasonable limit
- When truncated, include a file path to the full output
- Never dump raw logs, full transcripts, or unbounded lists
json
{
"ok": true,
"command": "joelclaw logs",
"result": {
"lines": 20,
"total": 4582,
"truncated": true,
"full_output": "/var/folders/.../joelclaw-logs-abc123.log",
"entries": ["...last 20 lines..."]
},
"next_actions": [
{ "command": "joelclaw logs --tail 100", "description": "Show more log lines" }
]
}5. Errors suggest fixes
When something fails, the response includes a field — plain language telling the agent what to do about it.
fixjson
{
"ok": false,
"command": "joelclaw send pipeline/video.download",
"error": {
"message": "Inngest server not responding",
"code": "SERVER_UNREACHABLE"
},
"fix": "Start the Inngest server: cd ~/Code/system-bus && docker compose up -d",
"next_actions": [
{ "command": "joelclaw health", "description": "Re-check system health after fix" },
{ "command": "docker ps", "description": "Check if Docker containers are running" }
]
}Response Envelope
Every command uses this exact shape:
Success
typescript
{
ok: true,
command: string, // the command that was run
result: object, // command-specific payload
next_actions: Array<{
command: string, // exact command to copy-paste/run
description: string // what it does
}>
}Error
typescript
{
ok: false,
command: string,
error: {
message: string, // what went wrong
code: string // machine-readable error code
},
fix: string, // plain-language suggested fix
next_actions: Array<{
command: string,
description: string
}>
}Reference implementations
- —
slog(Effect CLI, system log)~/Code/system-bus/ - —
igs(Effect CLI, Inngest operations)~/Code/system-bus/
Both follow this exact envelope. Copy their patterns.
Implementation
Framework: Effect CLI (@effect/cli)
All CLIs use with Bun. This is non-negotiable — consistency across the system matters more than framework preference.
@effect/clitypescript
import { Command, Options } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
const send = Command.make("send", {
event: Options.text("event"),
data: Options.optional(Options.text("data").pipe(Options.withAlias("d"))),
}, ({ event, data }) => {
// ... execute, return JSON envelope
})
const root = Command.make("joelclaw", {}, () => {
// Root: return health + command tree
}).pipe(Command.withSubcommands([send, status, health]))Binary distribution
Build with Bun, install to :
~/.bun/bin/bash
bun build src/cli.ts --compile --outfile joelclaw
cp joelclaw ~/.bun/bin/Adding a new command
- Define the command with
Command.make - Return the standard JSON envelope (ok, command, result, next_actions)
- Include contextual — what makes sense AFTER this specific command
next_actions - Handle errors with the error envelope (ok: false, error, fix, next_actions)
- Add to the root command's subcommands
- Add to the root command's array in the self-documenting output
commands - Rebuild and install
Anti-Patterns
| Don't | Do |
|---|---|
| Plain text output | JSON envelope |
| Tables with ANSI colors | JSON arrays |
| JSON is the only format |
| Dump 10,000 lines | Truncate + file pointer |
| |
| Undiscoverable commands | Root returns full command tree |
| Static help text | HATEOAS next_actions |
| |
| Exit code as the only error signal | Error in JSON + exit code |
| Require the agent to read --help | Root command self-documents |
Naming Conventions
- Commands are nouns or verbs, lowercase, no hyphens: ,
send,status,healthlogs - Subcommands follow naturally: ,
joelclaw memory searchjoelclaw loop start - Flags use :
--kebab-case,--max-quality--follow - Short flags for common options: for
-d,--datafor-f--follow - Event names use :
domain/action,pipeline/video.downloadcontent/summarize
Checklist for New Commands
- Returns JSON envelope (ok, command, result, next_actions)
- Error responses include fix field
- Root command lists this command in its tree
- Output is context-safe (truncated if potentially large)
- next_actions are contextual to what just happened
- No plain text output anywhere
- No ANSI colors or formatting
- Works when piped (no TTY detection)
- Builds and installs to ~/.bun/bin/