package helpers import ( "fmt" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" "github.com/spf13/cobra" ) func newFloatImageCmds() []*cobra.Command { createFloatImageCmd := &cobra.Command{ Use: "create-float-image", Short: "创建浮动图片", Long: `在钉钉表格的指定工作表上创建一个浮动图片。 浮动图片悬浮于单元格之上,不占用单元格内容,可自由定位和调整大小。 直接传 --file 可在一个命令中上传本地图片并创建浮动图片。 已经通过 media-upload 获得 resourceUrl 时,也可以改传 --src;--file 与 --src 二选一。 --range 指定浮动图片锚定的单元格位置,使用 A1 表示法(如 A1、B3)。 --width / --height 为必填,单位像素,必须为正整数。 --offset-x / --offset-y 可选,表示相对锚点单元格左上角的偏移量(像素),默认 0。`, Example: ` # 直接从本地文件创建 dws sheet create-float-image --node NODE_ID --sheet-id SHEET_ID \ --file ./chart.png --range A1 --width 400 --height 300 # 高级用法:先上传图片获取 resourceUrl dws sheet media-upload --node NODE_ID --file ./chart.png # 输出: resourceUrl: /core/api/resources/img/xxxx... # 再通过 --src 创建浮动图片 dws sheet create-float-image --node NODE_ID --sheet-id SHEET_ID \ --src "/core/api/resources/img/xxxx..." --range A1 --width 400 --height 300 # 带偏移量 dws sheet create-float-image --node NODE_ID --sheet-id SHEET_ID \ --src "/core/api/resources/img/xxxx..." --range B2 --width 200 --height 150 --offset-x 10 --offset-y 20`, RunE: func(cmd *cobra.Command, args []string) error { if err := validateRequiredFlags(cmd, "node", "sheet-id", "range"); err != nil { return err } filePath, src, err := validateFloatImageCreateInput(cmd) if err != nil { return err } width, err := cmd.Flags().GetInt("width") if err != nil { return fmt.Errorf("--width 解析失败: %w", err) } if width <= 0 { return fmt.Errorf("--width 必须为正整数,当前值: %d", width) } height, err := cmd.Flags().GetInt("height") if err != nil { return fmt.Errorf("--height 解析失败: %w", err) } if height <= 0 { return fmt.Errorf("--height 必须为正整数,当前值: %d", height) } toolArgs := map[string]any{ "nodeId": mustGetFlag(cmd, "node"), "sheetId": mustGetFlag(cmd, "sheet-id"), "range": mustGetFlag(cmd, "range"), "width": width, "height": height, } if cmd.Flags().Changed("offset-x") { ox, _ := cmd.Flags().GetInt("offset-x") if ox < 0 { return fmt.Errorf("--offset-x 不能为负数,当前值: %d", ox) } toolArgs["offsetX"] = ox } if cmd.Flags().Changed("offset-y") { oy, _ := cmd.Flags().GetInt("offset-y") if oy < 0 { return fmt.Errorf("--offset-y 不能为负数,当前值: %d", oy) } toolArgs["offsetY"] = oy } if filePath != "" { return runFloatImageFileMode(cmd, "create_float_image", filePath, toolArgs) } toolArgs["src"] = src return callMCPToolContext(cmd.Context(), "create_float_image", toolArgs) }, } DeclareLeafMetadata(createFloatImageCmd, LeafSpec{ Safety: contract.SafetySpec{ Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown", }, Contract: LeafContract{ Identity: contract.ToolIdentitySpec{ ProductID: "sheet", Name: "create_float_image", CanonicalPath: "sheet.create_float_image", CLIPath: "sheet create-float-image", PrimaryCLIPath: "sheet create-float-image", }, Description: "从本地文件或已上传的 resourceUrl 创建浮动图片。", DryRun: &contract.DryRunSpec{PreviewKind: "request", RemoteReads: false}, Interface: &contract.InterfaceSpec{ Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "sheet", RPCName: "create_float_image"}, }, Selection: contract.SelectionSpec{ AgentSummary: "从本地文件或已上传的 resourceUrl 创建浮动图片。", UseWhen: []string{"需要在单元格上方悬浮图片、不占用单元格内容时"}, AvoidWhen: []string{"单元格内嵌图片用 write-image;更新/删除浮动图用 update/delete-float-image"}, Examples: []string{"dws sheet create-float-image --node --sheet-id --src \"/core/api/resources/img/...\" --range A1 --width 400 --height 300"}, }, Parameters: []contract.ParamDecl{ {Name: "node", Property: "nodeId"}, {Name: "file", Required: boolPtr(false), RequiredWhen: "exactly one of --file or --src must be provided"}, {Name: "src", Property: "src", Required: boolPtr(false)}, }, }, }) createFloatImageCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") createFloatImageCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") createFloatImageCmd.Flags().String("file", "", "本地图片文件路径,与 --src 二选一") createFloatImageCmd.Flags().String("src", "", "通过 media-upload 获取的 resourceUrl,与 --file 二选一") createFloatImageCmd.Flags().String("range", "", "锚点单元格,A1 表示法,如 A1、B3 (必填)") createFloatImageCmd.Flags().Int("width", 0, "图片宽度,像素 (必填)") createFloatImageCmd.Flags().Int("height", 0, "图片高度,像素 (必填)") createFloatImageCmd.Flags().Int("offset-x", 0, "水平偏移量,像素 (默认 0)") createFloatImageCmd.Flags().Int("offset-y", 0, "垂直偏移量,像素 (默认 0)") getFloatImageCmd := &cobra.Command{ Use: "get-float-image", Short: "获取浮动图片详情", Long: `获取钉钉表格指定工作表中某个浮动图片的详细信息。 返回浮动图片的 ID、图片 URL、锚点位置、尺寸和偏移量等信息。 floatImageId 可通过 list-float-images 获取。`, Example: ` dws sheet get-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID`, RunE: func(cmd *cobra.Command, args []string) error { return callMCPTool("get_float_image", map[string]any{ "nodeId": mustGetFlag(cmd, "node"), "sheetId": mustGetFlag(cmd, "sheet-id"), "floatImageId": mustGetFlag(cmd, "float-image-id"), }) }, } DeclareLeafMetadata(getFloatImageCmd, LeafSpec{ Safety: contract.SafetySpec{ Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent", }, Contract: LeafContract{ Identity: contract.ToolIdentitySpec{ ProductID: "sheet", Name: "get_float_image", CanonicalPath: "sheet.get_float_image", CLIPath: "sheet get-float-image", PrimaryCLIPath: "sheet get-float-image", }, Description: "获取单张浮动图片详情。", Interface: &contract.InterfaceSpec{ Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "sheet", RPCName: "get_float_image"}, }, Selection: contract.SelectionSpec{ AgentSummary: "获取单张浮动图片详情。", UseWhen: []string{"已知 float-image-id,需要查看锚点/尺寸/src 时"}, AvoidWhen: []string{"列出全部浮动图用 list-float-images"}, Examples: []string{"dws sheet get-float-image --node --sheet-id --float-image-id "}, }, Parameters: []contract.ParamDecl{ {Name: "node", Property: "nodeId"}, }, }, }) getFloatImageCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") getFloatImageCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") getFloatImageCmd.Flags().String("float-image-id", "", "浮动图片 ID (必填)") listFloatImagesCmd := &cobra.Command{ Use: "list-float-images", Short: "列出工作表所有浮动图片", Long: `列出钉钉表格指定工作表中所有浮动图片。 返回每个浮动图片的 ID、图片 URL、锚点位置、尺寸和偏移量等信息,以及总数。`, Example: ` dws sheet list-float-images --node NODE_ID --sheet-id SHEET_ID`, RunE: func(cmd *cobra.Command, args []string) error { return callMCPTool("list_float_images", map[string]any{ "nodeId": mustGetFlag(cmd, "node"), "sheetId": mustGetFlag(cmd, "sheet-id"), }) }, } DeclareLeafMetadata(listFloatImagesCmd, LeafSpec{ Safety: contract.SafetySpec{ Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent", }, Contract: LeafContract{ Identity: contract.ToolIdentitySpec{ ProductID: "sheet", Name: "list_float_images", CanonicalPath: "sheet.list_float_images", CLIPath: "sheet list-float-images", PrimaryCLIPath: "sheet list-float-images", }, Description: "列出工作表全部浮动图片。", Interface: &contract.InterfaceSpec{ Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "sheet", RPCName: "list_float_images"}, }, Selection: contract.SelectionSpec{ AgentSummary: "列出工作表全部浮动图片。", UseWhen: []string{"需要枚举浮动图 ID 以便后续 get/update/delete 时"}, AvoidWhen: []string{"查单张详情用 get-float-image"}, Examples: []string{"dws sheet list-float-images --node --sheet-id "}, }, Parameters: []contract.ParamDecl{ {Name: "node", Property: "nodeId"}, }, }, }) listFloatImagesCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") listFloatImagesCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") updateFloatImageCmd := &cobra.Command{ Use: "update-float-image", Short: "更新浮动图片属性", Long: `更新钉钉表格指定工作表中浮动图片的属性。 可直接传 --file 上传本地图片并替换浮动图片,也可传已上传图片的 --src。 至少需要传入一个更新字段(--file / --src / --range / --width / --height / --offset-x / --offset-y),且 --file 与 --src 不能同时使用。 floatImageId 可通过 list-float-images 获取。`, Example: ` # 移动浮动图片到新位置 dws sheet update-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID --range C5 # 调整尺寸 dws sheet update-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID --width 600 --height 400 # 直接用本地文件替换图片 dws sheet update-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID \ --file ./replacement.png # 高级用法:用 media-upload 返回的 resourceUrl 替换图片 dws sheet update-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID \ --src "/core/api/resources/img/xxxx..."`, RunE: func(cmd *cobra.Command, args []string) error { fileChanged := cmd.Flags().Changed("file") srcChanged := cmd.Flags().Changed("src") rangeChanged := cmd.Flags().Changed("range") widthChanged := cmd.Flags().Changed("width") heightChanged := cmd.Flags().Changed("height") oxChanged := cmd.Flags().Changed("offset-x") oyChanged := cmd.Flags().Changed("offset-y") if !fileChanged && !srcChanged && !rangeChanged && !widthChanged && !heightChanged && !oxChanged && !oyChanged { return fmt.Errorf("%s", floatImageUpdateFieldsError) } filePath, src, err := validateFloatImageUpdateInput(cmd) if err != nil { return err } if fileChanged { if err := validateRequiredFlags(cmd, "node", "sheet-id", "float-image-id"); err != nil { return err } } toolArgs := map[string]any{ "nodeId": mustGetFlag(cmd, "node"), "sheetId": mustGetFlag(cmd, "sheet-id"), "floatImageId": mustGetFlag(cmd, "float-image-id"), } if srcChanged { toolArgs["src"] = src } if rangeChanged { toolArgs["range"], _ = cmd.Flags().GetString("range") } if widthChanged { w, _ := cmd.Flags().GetInt("width") if w <= 0 { return fmt.Errorf("--width 必须为正整数,当前值: %d", w) } toolArgs["width"] = w } if heightChanged { h, _ := cmd.Flags().GetInt("height") if h <= 0 { return fmt.Errorf("--height 必须为正整数,当前值: %d", h) } toolArgs["height"] = h } if oxChanged { ox, _ := cmd.Flags().GetInt("offset-x") if ox < 0 { return fmt.Errorf("--offset-x 不能为负数,当前值: %d", ox) } toolArgs["offsetX"] = ox } if oyChanged { oy, _ := cmd.Flags().GetInt("offset-y") if oy < 0 { return fmt.Errorf("--offset-y 不能为负数,当前值: %d", oy) } toolArgs["offsetY"] = oy } if fileChanged { return runFloatImageFileMode(cmd, "update_float_image", filePath, toolArgs) } return callMCPToolContext(cmd.Context(), "update_float_image", toolArgs) }, } DeclareLeafMetadata(updateFloatImageCmd, LeafSpec{ Safety: contract.SafetySpec{ Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown", }, Contract: LeafContract{ Identity: contract.ToolIdentitySpec{ ProductID: "sheet", Name: "update_float_image", CanonicalPath: "sheet.update_float_image", CLIPath: "sheet update-float-image", PrimaryCLIPath: "sheet update-float-image", }, Description: "更新浮动图片锚点、尺寸、偏移或资源路径。", DryRun: &contract.DryRunSpec{PreviewKind: "request", RemoteReads: false}, Interface: &contract.InterfaceSpec{ Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "sheet", RPCName: "update_float_image"}, }, Selection: contract.SelectionSpec{ AgentSummary: "更新浮动图片锚点、尺寸、偏移或资源路径。", UseWhen: []string{"需要移动/缩放/替换已有浮动图片时"}, AvoidWhen: []string{"创建用 create-float-image;删除用 delete-float-image"}, Examples: []string{"dws sheet update-float-image --node --sheet-id --float-image-id --range C5"}, }, Parameters: []contract.ParamDecl{ {Name: "node", Property: "nodeId"}, {Name: "file", Required: boolPtr(false)}, {Name: "src", Property: "src", Required: boolPtr(false)}, }, }, }) updateFloatImageCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") updateFloatImageCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") updateFloatImageCmd.Flags().String("float-image-id", "", "浮动图片 ID (必填)") updateFloatImageCmd.Flags().String("file", "", "用于替换浮动图片的本地图片路径,与 --src 不能同时使用") updateFloatImageCmd.Flags().String("src", "", "新的图片资源路径,通过 media-upload 获取的 resourceUrl") updateFloatImageCmd.Flags().String("range", "", "新的锚点单元格,A1 表示法") updateFloatImageCmd.Flags().Int("width", 0, "新的图片宽度,像素") updateFloatImageCmd.Flags().Int("height", 0, "新的图片高度,像素") updateFloatImageCmd.Flags().Int("offset-x", 0, "新的水平偏移量,像素") updateFloatImageCmd.Flags().Int("offset-y", 0, "新的垂直偏移量,像素") deleteFloatImageCmd := &cobra.Command{ Use: "delete-float-image", Short: "删除浮动图片", Long: `删除钉钉表格指定工作表中的浮动图片。 操作不可恢复,删除后图片将从工作表中移除。 floatImageId 可通过 list-float-images 获取。`, Example: ` dws sheet delete-float-image --node NODE_ID --sheet-id SHEET_ID --float-image-id FI_ID --yes`, RunE: func(cmd *cobra.Command, args []string) error { return callMCPTool("delete_float_image", map[string]any{ "nodeId": mustGetFlag(cmd, "node"), "sheetId": mustGetFlag(cmd, "sheet-id"), "floatImageId": mustGetFlag(cmd, "float-image-id"), }) }, } DeclareLeafMetadata(deleteFloatImageCmd, LeafSpec{ Safety: contract.SafetySpec{ Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "unknown", }, Contract: LeafContract{ Identity: contract.ToolIdentitySpec{ ProductID: "sheet", Name: "delete_float_image", CanonicalPath: "sheet.delete_float_image", CLIPath: "sheet delete-float-image", PrimaryCLIPath: "sheet delete-float-image", }, Description: "删除浮动图片(需确认后加 --yes)。", Interface: &contract.InterfaceSpec{ Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "sheet", RPCName: "delete_float_image"}, }, Selection: contract.SelectionSpec{ AgentSummary: "删除浮动图片(需确认后加 --yes)。", UseWhen: []string{"用户明确要求删除某张浮动图片时"}, AvoidWhen: []string{"删除单元格内嵌图需改写单元格;列目录用 list-float-images"}, Examples: []string{"dws sheet delete-float-image --node --sheet-id --float-image-id "}, }, Parameters: []contract.ParamDecl{ {Name: "node", Property: "nodeId"}, }, }, }) deleteFloatImageCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") deleteFloatImageCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (必填)") deleteFloatImageCmd.Flags().String("float-image-id", "", "浮动图片 ID (必填)") return []*cobra.Command{createFloatImageCmd, getFloatImageCmd, listFloatImagesCmd, updateFloatImageCmd, deleteFloatImageCmd} }