返回

CLI-Anything 教程 3:第二阶段 Design(命令组与状态模型设计)

CLI-Anything 第二阶段 Design 详解:如何设计 CLI 命令组结构、状态模型、输出格式。包含设计原则、命名规范、状态管理和错误处理策略。

教程概述

系列目录:CLI-Anything 教程系列索引

Design 是 CLI-Anything 7 阶段流程的第二阶段。在 Analyze 阶段识别了软件 API 之后,Design 阶段需要将这些 API 组织成结构化的 CLI 命令

你将学到

  • ✅ Design 阶段的输入输出
  • ✅ 命令组设计原则
  • ✅ 状态模型设计
  • ✅ REPL 交互设计
  • ✅ 输出格式设计

Design 阶段的核心任务

从 API 到 CLI

flowchart TD
    A[API 映射文档] --> B[Design 阶段]
    B --> C[命令组结构]
    B --> D[状态模型]
    B --> E[REPL 设计]
    B --> F[输出格式]
    C --> G[架构设计文档]
    D --> G
    E --> G
    F --> G

    style B fill:#e3f2fd
    style G fill:#e8f5e9

设计输出

flowchart LR
    subgraph Output["Design 阶段输出"]
        A[命令组结构图]
        B[CLI 规范文档]
        C[状态机定义]
        D[REPL 交互流程]
        E[JSON Schema]
    end

    style Output fill:#e8f5e9

命令组设计

设计原则

flowchart TD
    A[命令组设计原则] --> B[语义化分组]
    A --> C[层次化结构]
    A --> D[一致性命名]
    A --> E[避免冲突]

命令组结构示例

GIMP CLI 命令组

flowchart TD
    Root[cli-anything-gimp] --> Project[project]
    Root --> Image[image]
    Root --> Layer[layer]
    Root --> Selection[selection]
    Root --> Filter[filter]
    Root --> Tool[tool]
    Root --> Export[export]

    Project --> P1[new]
    Project --> P2[open]
    Project --> P3[save]
    Project --> P4[close]
    Project --> P5[info]

    Image --> I1[new]
    Image --> I2[scale]
    Image --> I3[crop]
    Image --> I4[flatten]
    Image --> I5[duplicate]

    Layer --> L1[new]
    Layer --> L2[delete]
    Layer --> L3[move]
    Layer --> L4[merge-down]
    Layer --> L5[opacity]

    Filter --> F1[list]
    Filter --> F2[apply]
    Filter --> F3[gaussian-blur]
    Filter --> F4[sharpen]
    Filter --> F5[color-balance]

    Export --> E1[render]
    Export --> E2[batch]

命名规范

类型 规范 示例
命令组 名词,小写 project, image, layer
子命令 动词-名词,kebab-case new, scale, merge-down
参数 名词,下划线 --output-path, --blur-radius
标志 布尔,形容词 --overwrite, --visible-only

命令设计检查清单

## 命令设计检查清单

### 每个命令必须定义:
- [ ] 命令名称(符合命名规范)
- [ ] 命令描述(一句话说明)
- [ ] 参数列表(名称、类型、必需/可选、默认值)
- [ ] 返回值(成功时返回的数据结构)
- [ ] 错误情况(可能的错误及返回码)
- [ ] 示例(至少一个使用示例)

### 命令设计原则:
- [ ] 单一职责:一个命令只做一件事
- [ ] 幂等性:相同输入产生相同结果
- [ ] 可组合性:输出可作为其他命令的输入
- [ ] 一致性:相似命令有相似的参数和输出

状态模型设计

为什么需要状态模型?

flowchart LR
    A[无状态设计] --> B[每次操作重新加载]
    B --> C[性能低下]
    B --> D[上下文丢失]

    E[有状态设计] --> F[保持会话上下文]
    F --> G[高效连续操作]
    F --> H[支持撤销/重做]

    style A fill:#ffcccc
    style B fill:#ffcccc
    style C fill:#ffcccc
    style D fill:#ffcccc
    style E fill:#e8f5e9
    style F fill:#e8f5e9
    style G fill:#e8f5e9
    style H fill:#e8f5e9

状态对象设计

# 状态模型示例(GIMP)
class ProjectState:
    """项目状态对象"""

    def __init__(self):
        self.project_id: str = None
        self.project_path: str = None
        self.images: Dict[str, ImageState] = {}
        self.active_image_id: str = None
        self.modified: bool = False
        self.history: List[Command] = []
        self.history_position: int = -1

    def to_dict(self) -> dict:
        return {
            "project_id": self.project_id,
            "project_path": self.project_path,
            "images": {k: v.to_dict() for k, v in self.images.items()},
            "active_image_id": self.active_image_id,
            "modified": self.modified,
            "history_count": len(self.history),
            "history_position": self.history_position
        }

class ImageState:
    """图像状态对象"""

    def __init__(self):
        self.image_id: str = None
        self.name: str = None
        self.width: int = 0
        self.height: int = 0
        self.layers: List[LayerState] = []
        self.active_layer_id: str = None
        self.selection: SelectionState = None

class LayerState:
    """图层状态对象"""

    def __init__(self):
        self.layer_id: str = None
        self.name: str = None
        self.visible: bool = True
        self.opacity: float = 100.0
        self.position: Tuple[int, int] = (0, 0)

状态持久化

flowchart TD
    A[REPL 会话] --> B[内存状态]
    B --> C[定期保存]
    C --> D[状态文件]
    D --> E[会话恢复]

    style B fill:#e3f2fd
    style D fill:#f3e5f5

状态文件格式

{
  "version": "1.0.0",
  "project_id": "proj_20260403_001",
  "created_at": "2026-04-03T10:30:00Z",
  "modified_at": "2026-04-03T11:45:00Z",
  "active_image_id": "img_001",
  "images": {
    "img_001": {
      "image_id": "img_001",
      "name": "Photo.jpg",
      "width": 1920,
      "height": 1080,
      "active_layer_id": "layer_002",
      "layers": [
        {
          "layer_id": "layer_001",
          "name": "Background",
          "visible": true,
          "opacity": 100.0
        },
        {
          "layer_id": "layer_002",
          "name": "Text",
          "visible": true,
          "opacity": 85.0
        }
      ]
    }
  },
  "history": [
    {"command": "image new", "timestamp": "..."},
    {"command": "layer new --name Text", "timestamp": "..."}
  ]
}

状态转换图

stateDiagram-v2
    [*] --> Empty: 启动
    Empty --> Opened: project open
    Opened --> Modified: 执行编辑操作
    Modified --> Saved: project save
    Saved --> Opened: 继续编辑
    Opened --> Empty: project close
    Modified --> Empty: project close (discard)

REPL 设计

REPL 交互模式

flowchart TD
    A[启动 REPL] --> B[显示 Banner]
    B --> C[显示提示符]
    C --> D{用户输入}
    D --> E[解析命令]
    E --> F[执行命令]
    F --> G[显示结果]
    G --> C
    D --> H[退出]

    style B fill:#e3f2fd
    style C fill:#e3f2fd
    style F fill:#e8f5e9

REPL 提示符设计

# 初始状态
cli-anything-gimp>

# 打开项目后
cli-anything-gimp[ProjectName]>

# 选择图像后
cli-anything-gimp[ProjectName/ImageName]>

# 未保存状态(添加 * 标记)
cli-anything-gimp[ProjectName]*>

REPL 命令集

# 内置 REPL 命令
REPL_COMMANDS = {
    "help": "显示帮助信息",
    "exit": "退出 REPL",
    "quit": "退出 REPL(同 exit)",
    "history": "显示命令历史",
    "save": "保存当前状态",
    "status": "显示当前状态",
    "undo": "撤销上一步操作",
    "redo": "重做上一步操作",
}

REPL 交互示例

$ cli-anything-gimp
╔══════════════════════════════════════════╗
║       cli-anything-gimp v1.0.0          ║
║     GIMP CLI for AI Agents              ║
║     16 commands, 4 command groups        ║
╚══════════════════════════════════════════╝

Type 'help' for available commands, 'exit' to quit.

gimp> help

Available commands:
  project     Project management (new, open, save, close)
  image       Image operations (new, scale, crop, flatten)
  layer       Layer operations (new, delete, merge, move)
  filter      Filters and effects (blur, sharpen, color)
  export      Export and rendering (render, batch)

Built-in commands:
  help        Show this help message
  history     Show command history
  save        Save current state
  status      Show current status
  undo        Undo last operation
  redo        Redo last operation
  exit        Exit REPL

gimp> project new --name "MyProject" --template photo
✓ Created project: MyProject
  Project ID: proj_20260403_abc123
  Template: photo

gimp[MyProject]> image new --width 1920 --height 1080 --name "Photo"
✓ Created image: Photo (1920x1080)
  Image ID: img_001

gimp[MyProject/Photo]> layer new --name "Background" --fill white
✓ Created layer: Background

gimp[MyProject/Photo]> filter gaussian-blur --layer Background --radius 5
✓ Applied Gaussian Blur (radius=5) to layer: Background

gimp[MyProject/Photo]*> undo
✓ Undone: filter gaussian-blur

gimp[MyProject/Photo]> export render output.png --format png
✓ Exported: output.png (1920x1080, 2.3 MB)

gimp[MyProject/Photo]> save
✓ Saved project state

gimp[MyProject/Photo]> exit
Goodbye! 👋

输出格式设计

人类可读格式

# 成功输出
✓ Created project: MyProject
  Project ID: proj_20260403_abc123
  Created at: 2026-04-03 10:30:00

# 错误输出
✗ Failed to open image: invalid_format.jpg
  Error: Unsupported image format
  Supported formats: PNG, JPEG, GIF, TIFF

# 警告输出
⚠ Image dimensions exceed recommended size
  Current: 8000x6000 (48 MP)
  Recommended: 4000x3000 (12 MP)
  Performance may be impacted

JSON 格式(Agent 使用)

# 统一 JSON 输出结构
class CLIResponse:
    """CLI 统一响应格式"""

    def __init__(self, success: bool, command: str):
        self.success = success
        self.command = command
        self.timestamp = datetime.utcnow().isoformat()
        self.data = {}
        self.error = None
        self.warnings = []

    def to_dict(self) -> dict:
        return {
            "success": self.success,
            "command": self.command,
            "timestamp": self.timestamp,
            "data": self.data,
            "error": self.error,
            "warnings": self.warnings
        }

成功响应示例

{
  "success": true,
  "command": "project new",
  "timestamp": "2026-04-03T10:30:00.000Z",
  "data": {
    "project_id": "proj_20260403_abc123",
    "project_name": "MyProject",
    "template": "photo",
    "created_at": "2026-04-03T10:30:00.000Z",
    "state_file": "/home/user/.cli-anything/gimp/proj_20260403_abc123.json"
  },
  "error": null,
  "warnings": []
}

错误响应示例

{
  "success": false,
  "command": "image open",
  "timestamp": "2026-04-03T10:31:00.000Z",
  "data": {
    "requested_path": "/path/to/invalid.xyz"
  },
  "error": {
    "type": "UnsupportedFormatError",
    "message": "File format '.xyz' is not supported",
    "code": "E_FORMAT_UNSUPPORTED",
    "suggestions": [
      "Convert to supported format: PNG, JPEG, GIF, TIFF",
      "Check file extension matches actual content"
    ]
  },
  "warnings": []
}

架构设计文档模板

Design 阶段输出的是一份完整的架构设计文档

# 架构设计文档 - <软件名称> CLI

## 1. CLI 概览

- **CLI 名称**: cli-anything-<software>
- **版本**: 1.0.0
- **目标软件**: <软件名称> <版本>
- **设计目标**: 让 <软件> 可被 AI Agent 自动化调用

## 2. 命令组结构

### 2.1 顶层命令

cli-anything- [GLOBAL_OPTIONS] [ARGS]


### 2.2 全局选项

| 选项 | 类型 | 默认值 | 说明 |
|-----|------|-------|------|
| `--json` | flag | false | 输出 JSON 格式 |
| `--project` | string | - | 指定项目状态文件 |
| `--verbose` | flag | false | 详细输出 |
| `--quiet` | flag | false | 静默模式 |

### 2.3 命令组

#### project - 项目管理

| 子命令 | 描述 | 关键参数 |
|-------|------|---------|
| `new` | 创建新项目 | `--name`, `--template` |
| `open` | 打开现有项目 | `--path` |
| `save` | 保存项目 | `--path` (可选) |
| `close` | 关闭项目 | `--discard` |
| `info` | 显示项目信息 | - |

#### image - 图像操作

| 子命令 | 描述 | 关键参数 |
|-------|------|---------|
| `new` | 创建新图像 | `--width`, `--height`, `--name` |
| `scale` | 缩放图像 | `--width`, `--height`, `--interpolation` |
| `crop` | 裁剪图像 | `--x`, `--y`, `--width`, `--height` |
| `flatten` | 合并所有图层 | `--merge-visible-only` |

... (其他命令组)

## 3. 状态模型

### 3.1 状态对象

```python
class ProjectState:
    project_id: str
    project_name: str
    created_at: datetime
    modified_at: datetime
    images: Dict[str, ImageState]
    active_image_id: Optional[str]
    history: List[Command]
    history_position: int

class ImageState:
    image_id: str
    name: str
    width: int
    height: int
    layers: List[LayerState]
    active_layer_id: Optional[str]

3.2 状态转换

[状态转换图]

4. REPL 设计

4.1 提示符格式

  • 无项目: <software>>
  • 有项目: <software>[ProjectName]>
  • 有修改: <software>[ProjectName]*>

4.2 内置命令

命令 功能
help 显示帮助
history 显示历史
undo 撤销
redo 重做
save 保存状态
status 显示状态
exit 退出

5. 输出格式

5.1 人类可读格式

✓ Operation successful
  Detail 1: ...
  Detail 2: ...

5.2 JSON Schema

{
  "success": boolean,
  "command": string,
  "timestamp": string (ISO8601),
  "data": object,
  "error": {
    "type": string,
    "message": string,
    "code": string,
    "suggestions": [string]
  } | null,
  "warnings": [string]
}

6. 错误处理

6.1 错误类型

错误代码 类型 说明
E_FILE_NOT_FOUND FileNotFoundError 文件不存在
E_FORMAT_UNSUPPORTED UnsupportedFormatError 格式不支持
E_INVALID_PARAMETER ValidationError 参数无效
E_SOFTWARE_NOT_FOUND SoftwareNotFoundError 目标软件未安装

6.2 错误响应格式

{
  "success": false,
  "error": {
    "type": "ValidationError",
    "message": "Width must be positive",
    "code": "E_INVALID_PARAMETER",
    "suggestions": ["Check your input values"]
  }
}

7. 依赖关系

7.1 外部依赖

  • <软件名称> >= <版本>
  • Python >= 3.10
  • Click >= 8.0

7.2 内部依赖

  • cli_anything..core
  • cli_anything..repl
  • cli_anything..utils

8. 实现注意事项

8.1 关键设计决策

  1. 状态管理: 使用 JSON 文件持久化状态
  2. 撤销/重做: 使用命令模式实现
  3. 批处理: 支持从文件读取命令序列
  4. 并发: 单进程模式,避免并发冲突

8.2 性能考虑

  • 大文件使用流式处理
  • 状态文件定期保存,非每次操作
  • 图像数据不保存在状态文件中,只保存路径

## 设计最佳实践

### DO(推荐做法)

```python
# ✅ 语义化的命令名称
@click.command()
def merge_down():  # 向下合并图层
    pass

# ✅ 完善的参数验证
@click.option('--opacity', type=float, default=100.0,
              callback=validate_opacity)
def set_opacity(opacity):
    pass

def validate_opacity(ctx, param, value):
    if not 0 <= value <= 100:
        raise click.BadParameter('Opacity must be between 0 and 100')
    return value

# ✅ 清晰的输出信息
@click.echo(f"✓ Created layer: {layer_name}")

DON’T(避免做法)

# ❌ 模糊的命令名称
@click.command()
def do_something():
    pass

# ❌ 缺少验证
@click.option('--size', default='100')
def resize(size):  # 没有验证 size 格式
    pass

# ❌ 静默失败
def create_layer(name):
    if not name:
        return  # 没有错误信息!

小结

Design 阶段将 Analyze 阶段的 API 映射转化为可实现的 CLI 架构:

  1. 命令组设计 - 合理组织命令,建立层次结构
  2. 状态模型 - 设计状态对象和持久化方案
  3. REPL 设计 - 定义交互模式和提示符
  4. 输出格式 - 统一人类可读和 JSON 输出

关键要点

  • ✅ 命令命名语义化、一致性
  • ✅ 状态模型完整支持 REPL 会话
  • ✅ 输出格式兼顾人类和 Agent
  • ✅ 错误处理完善,提供修复建议
  • ✅ 文档详尽,便于后续实现

系列导航