6.9 KiB
6.9 KiB
DWS Shortcut 能力 — 总体规划
目标:为 dws 引入一套 声明式高保真命令(Shortcut) 能力,对齐 larksuite/cli 的
+command体验(如lark-cli contact +search-user),并在此之上做 dws 差异化:基于用户高频使用场景, 主动把常用操作沉淀为自定义 shortcut。
1. 背景与动机
dws 当前的命令有三类来源:
- MCP 运行时动态发现 ——
dws mcp <service> <tool> --json '{...}',通用但裸、参数需手拼 JSON。 internal/helpers/产品命令 —— 手写 cobra 命令,体验好但每个都从零写、缺统一框架。internal/registry/recipes.yaml—— 多步工作流的静态描述。
痛点:想新增一个「精选、参数友好、带 dry-run/format/身份」的单命令,只能手写 helper, 没有统一的声明式框架,重复劳动多、一致性差。larksuite 的 shortcut 框架正好解决这一层。
2. 与 larksuite/cli 的架构差异(关键)
| 维度 | larksuite/cli | dws-cli |
|---|---|---|
| 命令来源 | 静态硬编码 Go shortcut | MCP 运行时动态发现 + helpers |
| 调用底座 | Lark SDK 直连 API | MCP JSON-RPC(executor.Runner) |
| 精选命令层 | shortcuts/(200+ 声明式) |
internal/helpers/(手写 cobra) |
| 全局 flag | 框架注入 | root 已内建 --format/--dry-run/--jq/--yes/--fields/--profile |
结论:不能直接搬代码。移植的是 shortcut 的声明式设计,执行底座换成 dws 的
executor.Runner,全局能力复用 dws 已有的 output/safety/auth。
3. 分期目标
P1 — 静态声明式框架(本期,正在做)
- 新建独立模块
internal/shortcut/(零侵入现有 helpers)。 types.go:Shortcut/Flag/RuntimeContext声明层。runner.go:把Shortcut编译成*cobra.Command,串起 flag 注册 → 校验 → dry-run →executor.Runner.Run→output.WriteCommandPayload。register.go:按 service 分组产出命令,在internal/app/legacy.go装配点 merge 进命令树。- 样板命令
contact +search-user:打通 MCP 执行 / format / dry-run / 身份,作为后续命令模板。 - 交付判据:
dws contact +search-user --help、--dry-run正常;go build/go test通过。
P2 — 高频场景自动沉淀(后续,先设计再实现)
- 使用埋点:命令执行入口记录
~/.dws/usage.jsonl(只记参数形状,不记敏感值)。 - 模式挖掘:
dws shortcut suggest聚合高频(service, tool, 固定参数组合)。 - 主动沉淀:命中候选时提示用户,一键写入
~/.dws/shortcuts/*.yaml(声明式,与 P1 结构对应)。 - 运行时加载:
register.go额外扫描~/.dws/shortcuts/*.yaml动态注册,复用internal/plugin/loader.go已验证的「从~/.dws/加载并挂 cobra 命令」模式。
4. 落地方式(P1)
采用独立模块 + 装配点 merge,不改 helpers 内部:
internal/shortcut/
types.go # Shortcut / Flag / RuntimeContext
runner.go # 声明式→cobra 编译 + 执行管道
register.go # Commands(runner) []*cobra.Command,按 service 分组
contact/
search_user.go # 样板:var SearchUser = shortcut.Shortcut{...}
shortcuts.go # Shortcuts() []shortcut.Shortcut
接线:internal/app/legacy.go: newLegacyPublicCommands 里,
helpers.NewPublicCommands(runner) 之后追加 shortcut.Commands(runner),
一起走 mergeTopLevelCommands(同名 service 命令自动合并,+xxx 作为其子命令)。
复用点:
- 执行:
executor.NewHelperInvocation+runner.Run(与 helper 完全一致的调用路径)。 - 输出:
output.WriteCommandPayload(cmd, resp, output.FormatJSON)(自动吃 root 的--format/--jq/--fields)。 - dry-run:读 root
--dry-run,置Invocation.DryRun,由 runner 返回请求预览。 - 身份/安全:复用
--profile、internal/safety(高风险--yes确认)。
5. Shortcut 声明模型(草案)
type Shortcut struct {
Service string // "contact" → 顶层命令
Command string // "+search-user" → 子命令(保留 + 前缀,对齐 larksuite)
Description string
Risk string // read | write | high-risk-write
Flags []Flag
Validate func(*RuntimeContext) error
Execute func(*RuntimeContext) error // 必填;内部调 rt.CallMCP(...)
}
type Flag struct {
Name, Type, Default, Desc string
Required bool
Enum []string
}
RuntimeContext 给 Execute 提供:flag 读取(Str/Bool/Int/StrSlice/Changed)、
CallMCP(product, tool, params)(内部 runner.Run)、Output(payload)、DryRun()。
6. 风险与边界
- 与
dws mcp通道的边界:shortcut 是「人工精选的薄封装」,不替代通用 MCP 通道; 一个 tool 可以既能dws mcp直调,也能有 shortcut。 - 自定义 shortcut 安全(P2):YAML 的
execute若允许任意 MCP 调用,需过internal/security的 endpoint 白名单,且沉淀的参数值要脱敏。 - 命名冲突:
+前缀天然与现有 leaf 命令区分,降低与 helper 命令的冲突面。 - edition 差异:oss / enterprise 的可用 service 不同,注册时按 edition 过滤(后续接入)。
7. 进度看板
- P1-1 types.go —
Shortcut/Flag/Risk声明层 - P1-2 runner.go —
RuntimeContext+mount编译 + 校验/确认/dry-run;CallMCP委托helpers.CallMCPToolOnServer(复用错误分类/输出/dry-run) - P1-3 register.go +
internal/shortcut/contact/search_user.go+builtin聚合包 - P1-4 接线
legacy.go(append 到mergeTopLevelCommands)+ build/test 全绿 - P2 设计文档(进行中)
P1 落地实证(已验证)
dws contact +search-user --help:命令挂载,继承全局--format/--dry-run/--jq/...。- 必填校验:不传
--query→ 结构化 validation 错误。 --dry-run:走 helpers 路径输出[DRY-RUN]预览(tool + 参数)。- 命令树 merge:
+search-user与现有user/dept/label/relation共存,contact user search未受影响。 - 测试:
internal/shortcut单测通过;internal/app全量回归通过。
P1 关键决策记录
- 执行底座复用 helpers 而非裸
runner.Run:CallMCP委托helpers.CallMCPToolOnServer(product, tool, params), 一步获得错误分类(auth/PAT/业务)+ 格式化输出 + dry-run,避免重造劣质输出层。代价是internal/shortcut → internal/helpers的单向依赖(无环)。后续若要 shortcut 做多调用编排/输出重塑, 再补一个返回原始 payload 的CallMCPRaw。 - 避免 import 环:service 包(contact)import 核心
shortcut包并在init()注册;builtin聚合包 blank-import 各 service 包;app只依赖builtin。