// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package helpers import ( "fmt" "os" "strconv" "strings" "github.com/spf13/cobra" ) // leaf.go 是叶子命令的统一构建框架。 // // 现状问题:每个叶子命令手写 cobra.Command,required 校验、别名回退、环境 // 变量回退、值转换、参数装配、派发调用在各个产品文件里各写一份,行为难以 // 保持一致。LeafSpec 把这些共性收敛为声明式结构:命令只声明「flag 集合 + // 绑定关系」,框架统一执行。默认派发走 MCP 直连(callMCPTool);非 MCP // 命令(如 devapp 走 executor.Runner)通过 LeafSpec.Call 注入自己的派发器, // 复用同一套 flag/校验/装配逻辑。复杂命令可通过 LeafSpec.RunE 完全自定义 // (逃生舱),不在框架适用范围内强行套用。 // // 迁移纪律:从手写命令迁移到 LeafSpec 时,flag 名、默认值、usage 文案、 // MarkFlagRequired、required 错误格式、toolArgs 键与值必须逐字保持一致, // 由 check-generated-drift(catalog 零漂移)与命令兼容性检查兜底证明等价。 // LeafFlagKind 是 flag 的值类型。 type LeafFlagKind int const ( // LeafString 字符串 flag(默认)。 LeafString LeafFlagKind = iota // LeafInt 整型 flag(注册为 cobra Int);仅在值 != 0 时进入 toolArgs, // 对应手写「putInt 仅在非零才入参」语义(如 devapp app-group-id)。 LeafInt ) // LeafFlag 声明一个 flag 的注册方式与到 MCP toolArgs 的绑定。 type LeafFlag struct { Name string // flag 名(kebab-case) Usage string // 注册 usage 文案 Kind LeafFlagKind // 值类型,默认 LeafString Default string // 注册默认值(cobra 注册仅 LeafString 使用;回退链链尾对所有 Kind 生效,不遮蔽别名/env) // Required 为 true 时在 RunE 期校验有效值非空。普通 Required 汇聚为 // cmdutil.ValidateRequiredFlags 兼容的统一报错;配置 EnvVar 时回退读 // 环境变量,仍为空则报 RequiredHint(或默认文案)。 Required bool RequiredHint string // MarkRequired 为 true 时调用 cobra MarkFlagRequired(catalog required // 投影的硬下限),cobra 会在 RunE 之前先行报错。 MarkRequired bool Aliases []string // 隐藏别名,按主 flag 的 Kind 注册;主 flag 未显式提供时按序回退 EnvVar string // 有效值为空时回退读取的环境变量(整型 flag 的 env 值必须可解析) // ArgDefault:注册默认值为空、但 toolArgs 需要兜底的场景(如 list type // 注册默认 "ALL" 之外的旧命令);有效值为空时以 ArgDefault 入参。 ArgDefault string // Bind 是 toolArgs 的键;为空时使用 Name。 Bind string // Transform 把字符串有效值转为入参值;nil 时原样入参。返回 // (nil, nil) 表示跳过该键(用于「可空数值:为空或解析失败都不入参」 // 的手写语义)。 Transform func(raw string) (any, error) // OmitEmpty 为 true 时有效值为空则不进入 toolArgs(LeafInt 恒为 // 「非零才入参」,忽略此字段)。 OmitEmpty bool // Trim 为 true 时对有效值做 strings.TrimSpace(主 flag/别名/env 统一), // 对应手写 devAppStringFlag 恒 trim 的语义;亦使「纯空白」值在 required // 校验中视为空。 Trim bool } // LeafSpec 声明一个 MCP 直连叶子命令。 type LeafSpec struct { Use string Short string Long string Example string // Server 非空时走 callMCPToolOnServer(显式 server 路由),否则走 // callMCPTool(按 product 路由)。Call 非空时两者都被忽略。 Server string Tool string Flags []LeafFlag // Call 是可插拔派发函数,非空时替代默认的 callMCPTool/callMCPToolOnServer。 // 供非 MCP 直连命令(如 devapp 走 executor.Runner)复用本框架:调用方用 // 闭包捕获自己的 runner/派发器即可。签名与默认路径一致——收到框架装配好 // 的 toolArgs,自行派发。 Call func(cmd *cobra.Command, tool string, args map[string]any) error // Validate 是跨 flag 校验钩子(如时间区间、互斥关系),在 required // 校验之后、toolArgs 装配之前执行;nil 时跳过。单 flag 的格式转换 // 应放在 LeafFlag.Transform,不要放进 Validate。 Validate func(cmd *cobra.Command, args []string) error // RunE 非空时完全自定义执行体(逃生舱),框架只负责注册 flag。 RunE func(cmd *cobra.Command, args []string) error // PostMount 在 flag 注册完成之后、RunE 设定之前对构建好的 cmd 做最终 // 调整(设置 Args/DisableAutoGenTag、调用 annotate/preferLegacy 等)。 // 无论是否使用 RunE 逃生舱都会执行。对标 lark shortcut 的 PostMount。 PostMount func(cmd *cobra.Command) } // NewLeafCommand 按 LeafSpec 构建叶子命令。 func NewLeafCommand(spec LeafSpec) *cobra.Command { cmd := &cobra.Command{ Use: spec.Use, Short: spec.Short, Long: spec.Long, Example: spec.Example, } for _, flag := range spec.Flags { switch flag.Kind { case LeafInt: cmd.Flags().Int(flag.Name, 0, flag.Usage) default: cmd.Flags().String(flag.Name, flag.Default, flag.Usage) } // 别名按主 flag 的 Kind 注册,否则整型别名的值永远读不到(静默丢弃)。 for _, alias := range flag.Aliases { switch flag.Kind { case LeafInt: cmd.Flags().Int(alias, 0, flag.Usage+" (alias)") default: cmd.Flags().String(alias, "", flag.Usage+" (alias)") } _ = cmd.Flags().MarkHidden(alias) } if flag.MarkRequired { _ = cmd.MarkFlagRequired(flag.Name) } } if spec.PostMount != nil { spec.PostMount(cmd) } if spec.RunE != nil { cmd.RunE = spec.RunE return cmd } cmd.RunE = func(cmd *cobra.Command, args []string) error { if err := leafValidateRequired(cmd, spec); err != nil { return err } if spec.Validate != nil { if err := spec.Validate(cmd, args); err != nil { return err } } toolArgs, err := leafArgs(cmd, spec) if err != nil { return err } if spec.Call != nil { return spec.Call(cmd, spec.Tool, toolArgs) } if spec.Server != "" { return callMCPToolOnServer(spec.Server, spec.Tool, toolArgs) } return callMCPTool(spec.Tool, toolArgs) } return cmd } // leafValidateRequired 复现手写命令的 required 语义:普通 Required 统一报 // 「missing required flag(s)」;带 EnvVar 的 Required 单独报 RequiredHint。 // 普通组先于环境变量组校验,保持与手写顺序一致。两组都按声明的 // 「主 flag → 别名 → env」回退取有效值:只传兼容别名同样视为已提供。 func leafValidateRequired(cmd *cobra.Command, spec LeafSpec) error { var plain []string for _, flag := range spec.Flags { if flag.Required && flag.EnvVar == "" && flag.RequiredHint == "" && !leafHasEffectiveValue(cmd, flag) { plain = append(plain, flag.Name) } } if err := missingRequiredFlagsError(cmd, plain...); err != nil { return err } for _, flag := range spec.Flags { if !flag.Required || (flag.EnvVar == "" && flag.RequiredHint == "") { continue } if !leafHasEffectiveValue(cmd, flag) { hint := flag.RequiredHint if hint == "" { hint = fmt.Sprintf("flag --%s is required", flag.Name) } return fmt.Errorf("%s", hint) } } return nil } // leafHasEffectiveValue 判定 Required 是否满足,标准与 leafArgs 的入参判定 // 一致(LeafInt 需非零、字符串需非空):否则会出现校验声称有效、toolArgs // 却缺参的分裂。整型解析失败视为已提供,让 leafArgs 报出更精确的 // invalid integer 错误。 func leafHasEffectiveValue(cmd *cobra.Command, flag LeafFlag) bool { if flag.Kind == LeafInt { v, err := leafIntegerValue(cmd, flag) if err != nil { return true } return v != 0 } return leafEffectiveValue(cmd, flag) != "" } // leafArgs 按绑定关系装配 toolArgs。 func leafArgs(cmd *cobra.Command, spec LeafSpec) (map[string]any, error) { toolArgs := map[string]any{} for _, flag := range spec.Flags { bind := flag.Bind if bind == "" { bind = flag.Name } if flag.Kind == LeafInt { v, err := leafIntegerValue(cmd, flag) if err != nil { return nil, err } // 保持「非零才入参」(putInt 语义)。 if v != 0 { toolArgs[bind] = int(v) } continue } effective := leafEffectiveValue(cmd, flag) if effective == "" && flag.ArgDefault != "" { effective = flag.ArgDefault } if effective == "" && flag.OmitEmpty { continue } if flag.Transform != nil { value, err := flag.Transform(effective) if err != nil { return nil, err } if value == nil { continue } toolArgs[bind] = value continue } toolArgs[bind] = effective } return toolArgs, nil } // leafEffectiveValue 按「显式主 flag → 别名 → 环境变量 → 注册默认值」顺序取 // 有效值(字符串形态,整型 flag 统一格式化);Trim 为 true 时对结果统一 // TrimSpace。 func leafEffectiveValue(cmd *cobra.Command, flag LeafFlag) string { v := leafRawValue(cmd, flag) if flag.Trim { v = strings.TrimSpace(v) } return v } // leafRawValue 取未 trim 的原始有效值。主 flag 仅在用户显式提供(Changed) // 且非空时命中;注册默认值降级为链尾兜底,不再遮蔽别名与环境变量。Trim 为 // true 时候选值按 trim 后判空,纯空白与空串同样落入下一级回退。 func leafRawValue(cmd *cobra.Command, flag LeafFlag) string { usable := func(v string) bool { if flag.Trim { v = strings.TrimSpace(v) } return v != "" } if cmd.Flags().Changed(flag.Name) { if v := leafFlagString(cmd, flag.Kind, flag.Name); usable(v) { return v } } for _, alias := range flag.Aliases { if !cmd.Flags().Changed(alias) { continue } if v := leafFlagString(cmd, flag.Kind, alias); usable(v) { return v } } if flag.EnvVar != "" { if v := os.Getenv(flag.EnvVar); usable(v) { return v } } return flag.Default } // leafFlagString 按注册类型读取 flag 并统一为字符串形态,使整型 flag 能复用 // 同一条回退链(required 校验、别名、env)。 func leafFlagString(cmd *cobra.Command, kind LeafFlagKind, name string) string { switch kind { case LeafInt: v, _ := cmd.Flags().GetInt(name) return strconv.Itoa(v) default: return mustGetFlag(cmd, name) } } // leafIntegerValue 按回退链取整型 flag 的有效值;环境变量提供的字符串必须 // 可解析,否则报错而非静默丢弃。 func leafIntegerValue(cmd *cobra.Command, flag LeafFlag) (int64, error) { raw := leafEffectiveValue(cmd, flag) if raw == "" { return 0, nil } v, err := strconv.ParseInt(raw, 10, 64) if err != nil { return 0, fmt.Errorf("flag --%s: invalid integer value %q", flag.Name, raw) } return v, nil }