cli-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Agent-First CLI Design

以Agent为先的CLI设计

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
jq
.
本系统中的CLI遵循Agent优先,人类次之的原则。每个命令都会返回结构化JSON,供Agent解析、执行和跟进。人类用户可通过
jq
工具处理输出。

Core Principles

核心原则

1. JSON always

1. 始终返回JSON

Every command returns JSON. No plain text. No tables. No color codes. Agents parse JSON; they don't parse prose.
bash
undefined
每个命令都返回JSON格式输出,不支持纯文本、表格或颜色代码。Agent仅能解析JSON,无法处理自然语言文本。
bash
undefined

This is the ONLY output format

这是唯一的输出格式

joelclaw status
joelclaw status

→ { "ok": true, "command": "joelclaw status", "result": {...}, "next_actions": [...] }

→ { "ok": true, "command": "joelclaw status", "result": {...}, "next_actions": [...] }


No `--json` flag. No `--human` flag. JSON is the default and only format.

无需`--json`或`--human`参数,JSON是默认且唯一的输出格式。

2. HATEOAS — every response tells you what to do next

2. HATEOAS — 每个响应都告知后续操作

Every response includes
next_actions
— an array of commands the agent can run next, with descriptions. The agent never has to guess what's available.
json
{
  "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_actions
are contextual — they change based on what just happened. A failed command suggests different next steps than a successful one.
每个响应都包含
next_actions
字段——一个数组,列出Agent接下来可执行的命令及描述。Agent无需猜测可用操作。
json
{
  "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_actions
具备上下文关联性——会根据当前操作结果动态变化。失败命令的后续建议与成功命令不同。

3. Self-documenting command tree

3. 自文档化命令树

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": [...]
}
根命令(无参数)会返回完整的命令树,Agent可通过一次调用发现所有可用命令:
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

4. 上下文保护输出

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" }
  ]
}
Agent的上下文窗口有限,CLI输出不能超出其承载范围。
规则:
  • 默认简洁输出——仅返回必要信息
  • 自动截断大型输出(日志、列表)至合理长度
  • 截断时提供完整输出的文件路径
  • 绝不输出原始日志、完整记录或无限制列表
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

5. 错误提示包含修复建议

When something fails, the response includes a
fix
field — plain language telling the agent what to do about it.
json
{
  "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" }
  ]
}
当操作失败时,响应包含
fix
字段——用自然语言告知Agent解决方法。
json
{
  "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
  }>
}
typescript
{
  ok: true,
  command: string,          // 执行的命令
  result: object,           // 命令专属返回数据
  next_actions: Array<{
    command: string,        // 可直接复制执行的命令
    description: string     // 命令说明
  }>
}

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
  }>
}
typescript
{
  ok: false,
  command: string,
  error: {
    message: string,        // 错误信息
    code: string            // 机器可读的错误码
  },
  fix: string,              // 自然语言描述的修复建议
  next_actions: Array<{
    command: string,
    description: string
  }>
}

Reference implementations

参考实现

  • slog
    ~/Code/system-bus/
    (Effect CLI, system log)
  • igs
    ~/Code/system-bus/
    (Effect CLI, Inngest operations)
Both follow this exact envelope. Copy their patterns.
  • slog
    ~/Code/system-bus/
    (Effect CLI,系统日志工具)
  • igs
    ~/Code/system-bus/
    (Effect CLI,Inngest操作工具)
两者均严格遵循上述响应格式,可直接参考其实现模式。

Implementation

实现方案

Framework: Effect CLI (@effect/cli)

框架:Effect CLI (@effect/cli)

All CLIs use
@effect/cli
with Bun. This is non-negotiable — consistency across the system matters more than framework preference.
typescript
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]))
所有CLI均使用
@effect/cli
搭配Bun构建。这是硬性要求——系统一致性优先于框架偏好。
typescript
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 }) => {
  // ... 执行逻辑,返回JSON信封格式
})

const root = Command.make("joelclaw", {}, () => {
  // 根命令:返回健康状态 + 命令树
}).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/
使用Bun构建,安装至
~/.bun/bin/
bash
bun build src/cli.ts --compile --outfile joelclaw
cp joelclaw ~/.bun/bin/

Adding a new command

添加新命令

  1. Define the command with
    Command.make
  2. Return the standard JSON envelope (ok, command, result, next_actions)
  3. Include contextual
    next_actions
    — what makes sense AFTER this specific command
  4. Handle errors with the error envelope (ok: false, error, fix, next_actions)
  5. Add to the root command's subcommands
  6. Add to the root command's
    commands
    array in the self-documenting output
  7. Rebuild and install
  1. 使用
    Command.make
    定义命令
  2. 返回标准JSON信封格式(ok、command、result、next_actions)
  3. 包含上下文相关的
    next_actions
    ——即当前命令执行后合理的后续操作
  4. 使用错误信封格式处理错误(ok: false、error、fix、next_actions)
  5. 将命令添加至根命令的子命令列表
  6. 在根命令的自文档化输出中,将该命令加入
    commands
    数组
  7. 重新构建并安装

Anti-Patterns

反模式

Don'tDo
Plain text outputJSON envelope
Tables with ANSI colorsJSON arrays
--json
flag to opt into JSON
JSON is the only format
Dump 10,000 linesTruncate + file pointer
Error: something went wrong
{ ok: false, error: {...}, fix: "..." }
Undiscoverable commandsRoot returns full command tree
Static help textHATEOAS next_actions
console.log("Success!")
{ ok: true, result: {...} }
Exit code as the only error signalError in JSON + exit code
Require the agent to read --helpRoot command self-documents
禁止做法正确做法
纯文本输出JSON信封格式
带ANSI颜色的表格JSON数组
通过
--json
参数启用JSON输出
JSON是唯一输出格式
输出10000行内容截断输出并提供文件路径
Error: something went wrong
{ ok: false, error: {...}, fix: "..." }
命令不可被发现根命令返回完整命令树
静态帮助文本HATEOAS风格的next_actions
console.log("Success!")
{ ok: true, result: {...} }
仅通过退出码传递错误信号JSON中包含错误信息 + 退出码
要求Agent阅读--help内容根命令提供自文档化信息

Naming Conventions

命名规范

  • Commands are nouns or verbs, lowercase, no hyphens:
    send
    ,
    status
    ,
    health
    ,
    logs
  • Subcommands follow naturally:
    joelclaw memory search
    ,
    joelclaw loop start
  • Flags use
    --kebab-case
    :
    --max-quality
    ,
    --follow
  • Short flags for common options:
    -d
    for
    --data
    ,
    -f
    for
    --follow
  • Event names use
    domain/action
    :
    pipeline/video.download
    ,
    content/summarize
  • 命令使用名词或动词,小写,无连字符:
    send
    status
    health
    logs
  • 子命令自然延伸:
    joelclaw memory search
    joelclaw loop start
  • 标志使用
    --kebab-case
    --max-quality
    --follow
  • 常用选项使用短标志:
    -d
    对应
    --data
    -f
    对应
    --follow
  • 事件名称使用
    domain/action
    格式:
    pipeline/video.download
    content/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/
  • 返回JSON信封格式(ok、command、result、next_actions)
  • 错误响应包含fix字段
  • 根命令的命令树中包含该命令
  • 输出具备上下文安全性(可能过大时自动截断)
  • next_actions与当前操作上下文相关
  • 无任何纯文本输出
  • 无ANSI颜色或格式化内容
  • 支持管道操作(无TTY检测)
  • 可构建并安装至~/.bun/bin/