Files
dingtalk-workspace-cli/docs/command-framework-comparison.md
T
玉澜andCursor 3e83ac18d1 docs(schema): align description provenance with Long-first assembly
Review Medium fixes: document Cobra Long → cobra_help over ContractDecl
description (title stays declared-first), add assembly regression tests,
and retire LeafSchema/Decl naming leftovers to ContractDecl + contract.*.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 20:51:33 +08:00

237 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 命令框架对比:DWS command vs lark-cli vs GWS
本文档对比 DWS(钉钉工作区 CLI)、lark-cli(飞书 CLI)和 GWS(Google Workspace CLI / gcloud)三套命令框架的设计差异。
## 总览对比
| 维度 | DWS (command) | lark-cli | GWS (gcloud) |
|------|---------------|----------|--------------|
| 语言 | Go | Go | Python (gcloud) / Go (部分) |
| CLI 框架 | cobra | cobra | argparse + calliope |
| 调用底座 | MCP JSON-RPC | Lark REST SDK (`CallAPITyped`) | Google API Client |
| 命令层次 | 2 层:LeafSpec + Shortcut | 3 层:Shortcuts + API Commands + Raw API | 2 层:surface commands + raw |
| Schema 来源 | 代码声明投影 | 代码声明 + 运行时 introspection | API Discovery 文档自动生成 |
| Agent 适配 | 内建 (dws.schema.*) | 内建 (--print-schema) | 外挂 (MCP adapter) |
## 架构对比
### DWS command
```
corecmd.Spec (声明) → corecmd.New() → cobra.Command
│
├── contract.SafetySpec (运行时 + Schema 单一安全来源)
├── FlagSpec[] (参数 + 回退链 + 绑定)
├── Constraint[] (互斥/至少一个)
├── ContractDecl (Agent Selection/DryRun/Interface)
│
└── Invoke / Orchestrate / RunE (执行)
```
**核心特点**:
- 声明与执行严格分离
- SafetySpec 四个独立字段直接对齐 Agent Runtime Schema
- 有效值回退链:flag → alias → env → default
- 框架统一校验、装配、确认、投影
- Schema 从代码声明直接投影,无外部 hint 文件
### lark-cli
```
Shortcut (声明) → runner.Mount() → cobra.Command
│
├── Risk string (确认行为)
├── Scopes / ConditionalScopes (OAuth 权限)
├── Flag[] (参数 + Enum + Input sources)
├── AuthTypes (user/bot)
│
├── DryRun hook → DryRunAPI
├── Validate hook
└── Execute hook → RuntimeContext → CallAPITyped
```
**核心特点**:
- Execute 内直接调 REST API (`CallAPITyped`)
- DryRun 是独立 hook(返回结构化 API 计划)
- 内建 OAuth scope 声明与预检
- `--print-schema --flag-name` 运行时 introspection
- 无 Schema 投影层,Agent 通过 introspection 动态发现
### GWS (gcloud 风格)
```
API Discovery → 代码生成 → surface command
│
├── arguments (从 JSON Schema 自动生成)
├── request/response 映射
└── 自定义 action hook (少量)
```
**核心特点**:
- Schema-first:从 API Discovery 文档自动生成命令
- 参数直接映射 API 字段(flat schema)
- 人工 surface command 是 thin wrapper
- Agent 适配通过 MCP 外部 adapter
## 核心设计差异
### 1. 声明粒度
| 能力 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 参数别名 + 环境变量回退 | ✅ FlagSpec.Aliases + EnvVar | ❌ 无 | ❌ 无 |
| 声明式约束 (互斥/至少一个) | ✅ Constraint[] | ❌ 只有 Validate hook | ✅ argparse group |
| 安全契约 | ✅ SafetySpec(effect/risk/confirmation/idempotency) | Risk | 无 |
| Schema 投影 (Agent metadata) | ✅ ContractDecl 内建 | ⚠️ 运行时 introspection | ❌ 外挂 |
| 参数绑定 (flag name → API key) | ✅ FlagSpec.Bind | ❌ 手写 | ✅ 自动映射 |
| ConstParams (固定载荷) | ✅ | ❌ 手写在 Execute | ✅ 隐式 |
| 确认门顺序可配 (ConfirmFirst) | ✅ | ❌ 固定顺序 | ❌ 无确认机制 |
### 2. 执行模型
| 维度 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 参数装配 | 框架自动 (BuildArgs) | 手写 (`runtime.Str()/Bool()`) | 自动映射 |
| 派发方式 | Invoke(ctx, toolArgs) | Execute(ctx, runtime) | 自动调用 |
| 多步编排 | Orchestrate(ctx) | Execute 内链式 CallAPITyped | 不支持 |
| DryRun | 框架统一 (--dry-run flag) | 独立 DryRun hook 返回 API 计划 | 部分命令支持 |
| 错误分类 | apperrors 类型化 | errs.Problem 类型化 | HTTP status 映射 |
### 3. Agent 适配
| 维度 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 工具发现 | `dws schema --all` (静态 catalog) | `--print-schema` (运行时) | API Discovery |
| 选择指引 | contract.SelectionSpec (UseWhen/AvoidWhen) | Description + Tips | 无 |
| 安全声明 | contract.SafetySpec 直接声明 | Risk string | 无 |
| dry-run 能力声明 | contract.DryRunSpec (reviewed) | DryRun hook 存在性 | 无 |
| 接口模式 | contract.InterfaceSpec (local/mcp/composite) | 隐式 (全部 REST) | 隐式 (全部 REST) |
### 4. Schema 生命周期
```
DWS: 代码声明 → code review → cobra annotation → catalog/metadata JSON
(单一数据源,构建时验证完整性)
lark-cli: 代码声明 → 运行时 introspection → Agent 动态发现
(无离线 catalog,Agent 必须执行命令才能发现)
GWS: API Discovery JSON → 代码生成 → surface command
(Schema-first,但命令行体验受限于 API 形状)
```
## 设计哲学对比
### DWS command 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| 框架装配参数 | 消除 N 个命令各写一份 toolArgs 装配 | lark-cli 每个 Execute 手动取 flag 值 |
| SafetySpec 单一来源 | confirmation 驱动运行时,其余字段原样发布且互不推导 | lark-cli 只有 Risk 一个维度 |
| 声明式约束 | 构建时校验合法性 + 投影到 Schema + 渲染帮助 | lark-cli 约束隐藏在 Validate 逻辑里 |
| Schema 构建时投影 | 离线 catalog 支持 Agent 批量发现 | lark-cli 需要逐个命令 introspection |
| 有效值回退链 | flag → alias → env 统一语义 | lark-cli 别名是独立 Flag 手动关联 |
| ConfirmFirst | 精确建模遗留语义 | lark-cli 确认始终在 Execute 内 |
### lark-cli 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| 直连 REST API | 精确控制请求/响应,可处理分页/重试 | DWS 通过 MCP 间接调用 |
| DryRun 返回 API 计划 | Agent 可预览将要发出的真实 HTTP 请求 | DWS dry-run 只展示参数 |
| OAuth scope 声明 | 框架预检权限,失败提前 | DWS 依赖 MCP 层鉴权 |
| `--print-schema` introspection | 运行时发现,无需维护离线 catalog | DWS 需要 re-generate |
| Input sources (file/@path/stdin) | 丰富的输入方式声明 | DWS 无此抽象 |
| PrintFlagSchema | 单 flag 级别的 JSON Schema 暴露 | DWS 只在 catalog 级别 |
### GWS (gcloud) 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| API Discovery 驱动 | 一份 Schema 生成所有:SDK/CLI/文档 | DWS/lark 手写 |
| Flat parameter 映射 | API 字段 = CLI flag,零转换 | DWS 需要 Bind 映射 |
| 无 shortcut 层 | API 粒度即用户粒度 | DWS/lark 有精选层 |
## 代码量对比
| 框架 | 核心框架代码 | 单命令声明开销 | 备注 |
|------|-------------|---------------|------|
| DWS command | ~1400 行 (command.go + contract_decl.go) | ~20-30 行 (纯声明) | 框架重、单命令轻 |
| lark-cli | ~800 行 (runner.go + types.go + common.go) | ~50-150 行 (声明 + Execute 逻辑) | 框架轻、单命令重 |
| GWS gcloud | ~5000+ 行 (calliope 框架) | ~10 行 (多数自动生成) | 框架最重、单命令最轻 |
## DWS 命令声明示例 vs lark-cli
### DWS (command / LeafSpec)
```go
NewLeafCommand(LeafSpec{
Use: "create",
Short: "创建应用",
Tool: "create_dev_app",
Safety: contract.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
ConfirmFirst: true,
Flags: []LeafFlag{
{Name: "name", Usage: "应用名称", Bind: "name",
Trim: true, Required: true, RequiredHint: "--name 为必填"},
},
Contract: ContractDecl{
Description: "创建开放平台企业内部应用",
DryRun: &contract.DryRunSpec{PreviewKind: "invocation"},
Interface: &contract.InterfaceSpec{Mode: "composite", Availability: "available", Reason: "create then configure"},
Selection: contract.SelectionSpec{
AgentSummary: "创建钉钉开放平台应用",
UseWhen: []string{"需要新建企业内部应用"},
AvoidWhen: []string{"应用已存在时用 update"},
Examples: []string{`dws dev app create --name "Bot" --dry-run`},
},
},
Call: devAppCall(runner),
})
```
### lark-cli (Shortcut)
```go
var CalendarCreate = common.Shortcut{
Service: "calendar",
Command: "+create",
Description: "Create a new calendar event",
Risk: "write",
Scopes: []string{"calendar:calendar"},
Flags: []common.Flag{
{Name: "summary", Desc: "Event title", Required: true},
{Name: "start", Desc: "Start time (RFC3339)", Required: true},
{Name: "end", Desc: "End time (RFC3339)", Required: true},
{Name: "attendees", Type: "string_slice", Desc: "Attendee emails"},
},
DryRun: func(ctx context.Context, rt *common.RuntimeContext) *common.DryRunAPI {
return &common.DryRunAPI{
Method: "POST",
Path: "/open-apis/calendar/v4/calendars/{id}/events",
Body: buildEventBody(rt),
}
},
Execute: func(ctx context.Context, rt *common.RuntimeContext) error {
body := buildEventBody(rt)
data, err := rt.CallAPITyped("POST",
"/open-apis/calendar/v4/calendars/{id}/events", nil, body)
if err != nil { return err }
return rt.Output(data)
},
}
```
## 适用场景总结
| 场景 | 最适合 | 原因 |
|------|--------|------|
| MCP 后端 + Agent Schema 投影 | **DWS command** | 内建 Schema 声明、SafetySpec 契约、离线 catalog |
| REST API 直连 + OAuth scope 管理 | **lark-cli** | CallAPITyped + scope 预检 + DryRun API 计划 |
| API-first 大规模 surface 生成 | **GWS gcloud** | Discovery 驱动,一份 Schema 生成一切 |
| 多步编排 (跨服务链式调用) | **lark-cli** / DWS Orchestrate | lark 的 CallAPITyped 链式 + DWS 的 Orchestrate |
| 遗留系统迁移 (保持行为等价) | **DWS command** | ConfirmFirst + 回退链 + catalog 漂移门禁 |