# DWS Shortcut 能力 — 总体规划 > 目标:为 dws 引入一套 **声明式高保真命令(Shortcut)** 能力,对齐 larksuite/cli 的 `+command` > 体验(如 `lark-cli contact +search-user`),并在此之上做 dws 差异化:**基于用户高频使用场景, > 主动把常用操作沉淀为自定义 shortcut**。 ## 1. 背景与动机 dws 当前的命令有三类来源: 1. **MCP 运行时动态发现** —— `dws mcp --json '{...}'`,通用但裸、参数需手拼 JSON。 2. **`internal/helpers/` 产品命令** —— 手写 cobra 命令,体验好但每个都从零写、缺统一框架。 3. **`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 声明模型(草案) ```go 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. 进度看板 - [x] P1-1 types.go — `Shortcut` / `Flag` / `Risk` 声明层 - [x] P1-2 runner.go — `RuntimeContext` + `mount` 编译 + 校验/确认/dry-run;`CallMCP` 委托 `helpers.CallMCPToolOnServer`(复用错误分类/输出/dry-run) - [x] P1-3 register.go + `internal/shortcut/contact/search_user.go` + `builtin` 聚合包 - [x] 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`。