Files
dingtalk-workspace-cli/internal/helpers/devapp.go
T
修雨 d63f7e5836 fix(devapp): robot create routes to async submit/poll (issue #35)
The synchronous `create_dingtalk_robot` MCP action is broken server-side:
it dispatches to a connect-engine action template (detailId G-ACT-... /
versionId G-ACT-VER-...) that the engine can no longer find, returning
PARAM_ERROR / "找不到执行动作" for every caller (the orgId in the error is
hardcoded server-side, not the caller's). The async path
(submit_robot_create_task + query_robot_create_result) still works.

`dws devapp robot create` now submits the async task and polls the result
until a terminal status, returning the same agentId/clientId/robotCode/
clientSecret payload. User experience is unchanged — one command still
creates the robot. --dry-run previews the submit call.

- runDevAppRobotCreate orchestrates submit + interval-based polling
  (pending set WAITING/PROCESSING/RUNNING/PENDING/INIT/DOING, max 24 polls).
- Tests: create routing now expects submit; add poll-until-terminal and
  status-classification unit tests.
- Docs/skills updated to reflect the new routing.
2026-06-16 09:35:05 +08:00

1407 lines
49 KiB
Go
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.
// 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 (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cobracmd"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
const (
devAppProduct = "devapp"
devAppMemberListTool = "list_open_dev_app_members"
devAppMemberAddTool = "add_open_dev_app_members"
devAppMemberRemoveTool = "remove_open_dev_app_members"
devAppSecurityConfigTool = "update_app_security_config"
// 机器人能力(op-app MCP 工具,硬编码不走服务发现)。
//
// devAppRobotCreateTool(同步 create_dingtalk_robot)的服务端实现引用了一个
// connect-engine 动作模板(detailId G-ACT-...),该模板当前在引擎里已找不到,
// 对所有调用方都返回 PARAM_ERROR / "找不到执行动作"(见 issue #35)。因此
// `dws devapp robot create` 不再调用它,改用 submit + 轮询 result 的异步路径,
// 二者返回的最终结果一致(agentId/clientId/robotCode/clientSecret)。
devAppRobotCreateTool = "create_dingtalk_robot"
devAppRobotSubmitTool = "submit_robot_create_task"
devAppRobotResultTool = "query_robot_create_result"
devAppRobotConfigGetTool = "get_open_dev_app_robot_config"
devAppRobotConfigCreateTool = "create_open_dev_app_robot_config"
devAppRobotConfigUpdateTool = "update_open_dev_app_robot_config"
devAppRobotEnableTool = "enable_open_dev_app_robot"
devAppRobotOfflineTool = "offline_open_dev_app_robot"
// 版本发布能力(op-app MCP 工具,硬编码不走服务发现)。
devAppVersionCreateTool = "create_open_dev_app_version"
devAppVersionListTool = "list_open_dev_app_versions"
devAppVersionDetailTool = "get_open_dev_app_version_detail"
devAppVersionPublishTool = "publish_open_dev_app_version"
devAppVersionStatusTool = "get_open_dev_app_version_status"
)
func init() {
RegisterPublic(func() Handler {
return devAppHandler{}
})
}
type devAppHandler struct{}
func (devAppHandler) Name() string {
return "devapp"
}
func (devAppHandler) Command(runner executor.Runner) *cobra.Command {
return newDevAppCommand(runner)
}
func newDevAppCommand(runner executor.Runner) *cobra.Command {
root := &cobra.Command{
Use: "devapp",
Aliases: []string{"app"},
Short: "开放平台应用",
Long: "管理开放平台开发者应用:查询、详情、创建、更新、启停、删除、权限、网页应用、成员、安全配置、机器人和版本发布。",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
webapp := &cobra.Command{
Use: "webapp",
Short: "开放平台网页应用配置",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
webapp.AddCommand(
newDevAppWebappGetCommand(runner),
newDevAppWebappConfigCommand(runner),
)
permission := &cobra.Command{
Use: "permission",
Short: "开放平台应用权限",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
permission.AddCommand(
newDevAppPermissionListCommand(runner),
newDevAppPermissionAddCommand(runner),
newDevAppPermissionRemoveCommand(runner),
)
credentials := &cobra.Command{
Use: "credentials",
Short: "开放平台应用凭证",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
credentials.AddCommand(newDevAppCredentialsGetCommand(runner))
member := &cobra.Command{
Use: "member",
Short: "开放平台应用成员管理",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
member.AddCommand(
newDevAppMemberListCommand(runner),
newDevAppMemberAddCommand(runner),
newDevAppMemberRemoveCommand(runner),
)
security := &cobra.Command{
Use: "security",
Short: "开放平台应用安全设置",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
security.AddCommand(newDevAppSecurityConfigCommand(runner))
robot := &cobra.Command{
Use: "robot",
Short: "开放平台应用机器人能力",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
robot.AddCommand(
newDevAppRobotCreateCommand(runner),
newDevAppRobotSubmitCommand(runner),
newDevAppRobotResultCommand(runner),
newDevAppRobotConfigGetCommand(runner),
newDevAppRobotConfigCommand(runner, "config", "为现有应用创建机器人配置", devAppRobotConfigCreateTool),
newDevAppRobotConfigCommand(runner, "update", "更新现有应用机器人配置", devAppRobotConfigUpdateTool),
newDevAppRobotConfigCommand(runner, "enable", "启用现有应用机器人能力", devAppRobotEnableTool),
newDevAppRobotOfflineCommand(runner),
newDevAppRobotConnectCommand(runner),
)
version := &cobra.Command{
Use: "version",
Short: "开放平台应用版本发布",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
version.AddCommand(
newDevAppVersionCreateCommand(runner),
newDevAppVersionListCommand(runner),
newDevAppVersionGetCommand(runner),
newDevAppVersionCheckApprovalCommand(runner),
newDevAppVersionPublishCommand(runner),
newDevAppVersionStatusCommand(runner),
)
root.AddCommand(
newDevAppListCommand(runner),
newDevAppGetCommand(runner),
newDevAppCreateCommand(runner),
newDevAppUpdateCommand(runner),
newDevAppLifecycleCommand(runner, "delete", "删除开放平台企业内部应用", "delete_inner_app"),
newDevAppLifecycleCommand(runner, "inactive", "停用开放平台企业内部应用", "inactive_inner_app"),
newDevAppLifecycleCommand(runner, "active", "启用开放平台企业内部应用", "active_inner_app"),
credentials,
webapp,
permission,
member,
security,
robot,
version,
)
return root
}
func newDevAppListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "查询开放平台企业内部应用列表",
Example: " dws devapp list --name DemoApp --page 1 --page-size 20 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params := map[string]any{
"currentPage": devAppIntFlag(cmd, "page"),
"pageSize": devAppIntFlag(cmd, "page-size"),
}
devAppPutString(params, "appName", devAppFlagOrFallback(cmd, "name", "keyword"))
devAppPutInt(params, "agentId", devAppIntFlag(cmd, "agent-id"))
devAppPutInt(params, "appId", devAppIntFlag(cmd, "app-id"))
devAppPutString(params, "appKey", devAppStringFlag(cmd, "app-key"))
devAppPutString(params, "customKey", devAppStringFlag(cmd, "custom-key"))
devAppPutInt(params, "appGroupId", devAppIntFlag(cmd, "app-group-id"))
devAppPutString(params, "creator", devAppStringFlag(cmd, "creator"))
devAppPutString(params, "robotName", devAppStringFlag(cmd, "robot-name"))
devAppPutInt(params, "developType", devAppIntFlag(cmd, "develop-type"))
devAppPutInt(params, "filterCoolApp", devAppIntFlag(cmd, "filter-cool-app"))
devAppPutString(params, "sortType", devAppStringFlag(cmd, "sort"))
devAppPutString(params, "sortOrder", devAppStringFlag(cmd, "order"))
return runDevAppTool(runner, cmd, "list_open_dev_apps_by_condition", params)
},
}
cmd.Flags().Int("page", 1, "分页页码,从 1 开始")
cmd.Flags().Int("page-size", 20, "分页大小")
cmd.Flags().String("name", "", "应用名称关键词")
cmd.Flags().String("keyword", "", "--name 的兼容别名")
_ = cmd.Flags().MarkHidden("keyword")
addDevAppLocatorFlagsWithoutName(cmd)
cmd.Flags().Int("app-group-id", 0, "应用分组 ID")
cmd.Flags().String("creator", "", "创建人名称关键词")
cmd.Flags().String("robot-name", "", "机器人名称关键词")
cmd.Flags().Int("develop-type", 0, "开发类型枚举;不确定时不要传")
cmd.Flags().Int("filter-cool-app", 0, "酷应用过滤枚举;不确定时不要传")
cmd.Flags().String("sort", "", "排序字段,如 gmt_modified")
cmd.Flags().String("order", "", "排序方向 asc 或 desc")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "查询开放平台企业内部应用详情",
Example: " dws devapp get --agent-id 123456 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params := devAppLocatorParams(cmd, true)
if len(params) == 0 {
return devAppLocatorRequired(true)
}
return runDevAppTool(runner, cmd, "get_open_dev_app_detail", params)
},
}
addDevAppLocatorFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "创建开放平台企业内部应用",
Example: " dws devapp create --name DemoApp --desc 内部应用 --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "create"); err != nil {
return err
}
appType := devAppStringFlag(cmd, "type")
if appType != "" && appType != "internal" {
return apperrors.NewValidation("--type currently only supports internal")
}
name := devAppStringFlag(cmd, "name")
if name == "" {
return apperrors.NewValidation("--name is required")
}
params := map[string]any{"appName": name}
devAppPutString(params, "appDesc", devAppStringFlag(cmd, "desc"))
devAppPutString(params, "appIcon", devAppStringFlag(cmd, "icon"))
return runDevAppTool(runner, cmd, "create_inner_app", params)
},
}
cmd.Flags().String("name", "", "应用名称 (必填)")
cmd.Flags().String("desc", "", "应用描述")
cmd.Flags().String("icon", "", "应用图标 mediaId")
cmd.Flags().String("type", "internal", "应用类型;当前仅支持 internal")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppUpdateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "update",
Short: "修改开放平台企业内部应用基础信息",
Example: " dws devapp update --agent-id 123456 --name DemoApp2 --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "update"); err != nil {
return err
}
params := devAppLocatorParams(cmd, false)
if len(params) == 0 {
return devAppLocatorRequired(false)
}
updates := 0
if v := devAppStringFlag(cmd, "name"); v != "" {
params["appName"] = v
updates++
}
if v := devAppStringFlag(cmd, "desc"); v != "" {
params["appDesc"] = v
updates++
}
if v := devAppStringFlag(cmd, "icon"); v != "" {
params["appIcon"] = v
updates++
}
if updates == 0 {
return apperrors.NewValidation("at least one update field is required: --name, --desc, or --icon")
}
return runDevAppTool(runner, cmd, "update_inner_app", params)
},
}
addDevAppLocatorFlagsWithoutName(cmd)
cmd.Flags().String("name", "", "新的应用名称")
cmd.Flags().String("desc", "", "新的应用描述")
cmd.Flags().String("icon", "", "新的应用图标 mediaId")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppCredentialsGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "读取开放平台应用凭证",
Example: " dws devapp credentials get --agent-id 123456 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params := devAppLocatorParams(cmd, false)
if len(params) == 0 {
return devAppLocatorRequired(false)
}
return runDevAppTool(runner, cmd, "get_open_dev_app_credentials", params)
},
}
addDevAppLocatorFlagsWithoutName(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppLifecycleCommand(runner executor.Runner, use, short, tool string) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: short,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, use); err != nil {
return err
}
params := devAppLocatorParams(cmd, false)
if len(params) == 0 {
return devAppLocatorRequired(false)
}
return runDevAppTool(runner, cmd, tool, params)
},
}
addDevAppLocatorFlagsWithoutName(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppWebappGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "查询网页应用配置",
Example: " dws devapp webapp get --agent-id 123456 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params := devAppAppLocatorParams(cmd)
if len(params) == 0 {
return devAppAppLocatorRequired()
}
return runDevAppTool(runner, cmd, "get_webapp_config", params)
},
}
addDevAppAppLocatorFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppWebappConfigCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "配置网页应用能力",
Example: " dws devapp webapp config --agent-id 123456 --homepage-link https://example.com --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "webapp config"); err != nil {
return err
}
params := devAppAppLocatorParams(cmd)
if len(params) == 0 {
return devAppAppLocatorRequired()
}
updates := 0
if v := devAppStringFlag(cmd, "h5-page-type"); v != "" {
params["h5PageType"] = v
updates++
}
if v := devAppStringFlag(cmd, "homepage-link"); v != "" {
params["homepageLink"] = v
updates++
}
if v := devAppStringFlag(cmd, "pc-homepage-link"); v != "" {
params["pcHomepageLink"] = v
updates++
}
if v := devAppStringFlag(cmd, "omp-link"); v != "" {
params["ompLink"] = v
updates++
}
if updates == 0 {
return apperrors.NewValidation("at least one webapp field is required: --h5-page-type, --homepage-link, --pc-homepage-link, or --omp-link")
}
return runDevAppTool(runner, cmd, "set_webapp_config", params)
},
}
addDevAppAppLocatorFlags(cmd)
cmd.Flags().String("h5-page-type", "", "网页应用生效端/页面类型")
cmd.Flags().String("homepage-link", "", "移动端首页地址")
cmd.Flags().String("pc-homepage-link", "", "PC 端首页地址")
cmd.Flags().String("omp-link", "", "管理后台地址")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppPermissionListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"search", "detail"},
Short: "查询开放平台应用权限列表",
Example: " dws devapp permission list --agent-id 123456 --keyword 通讯录 --limit 20 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params := devAppAppLocatorParams(cmd)
if len(params) == 0 {
return devAppAppLocatorRequired()
}
devAppPutString(params, "keyword", devAppStringFlag(cmd, "keyword"))
devAppPutString(params, "scopeValue", devAppFlagOrFallback(cmd, "scope", "permission"))
devAppPutString(params, "authStatus", strings.ToUpper(devAppStringFlag(cmd, "status")))
devAppPutString(params, "firstLevelType", strings.ToUpper(devAppStringFlag(cmd, "scope-type")))
devAppPutString(params, "apiStatus", devAppStringFlag(cmd, "api-status"))
devAppPutInt(params, "limit", devAppIntFlag(cmd, "limit"))
devAppPutInt(params, "offset", devAppIntFlag(cmd, "offset"))
return runDevAppTool(runner, cmd, "list_open_dev_app_permissions", params)
},
}
addDevAppAppLocatorFlags(cmd)
cmd.Flags().String("keyword", "", "权限名、权限点、接口名关键词")
cmd.Flags().String("scope", "", "精确权限点 scopeValue")
cmd.Flags().String("permission", "", "--scope 的兼容别名")
_ = cmd.Flags().MarkHidden("permission")
cmd.Flags().String("status", "ALL", "权限状态:ALL、AUTHED、UNAUTHED")
cmd.Flags().String("scope-type", "", "权限一级类型:APP 或 SNS")
cmd.Flags().String("api-status", "", "开发者后台 apiStatus 过滤")
cmd.Flags().Int("limit", 20, "返回数量上限")
cmd.Flags().Int("offset", 0, "返回偏移量")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppPermissionAddCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "申请开放平台应用权限点",
Example: " dws devapp permission add --agent-id 123456 --permissions Contact.User.mobile --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "permission add"); err != nil {
return err
}
params := devAppAppLocatorParams(cmd)
if len(params) == 0 {
return devAppAppLocatorRequired()
}
scopes := devAppPermissionScopes(cmd)
if len(scopes) == 0 {
return apperrors.NewValidation("--permissions is required")
}
params["scopeValues"] = scopes
return runDevAppTool(runner, cmd, "apply_open_dev_app_permissions", params)
},
}
addDevAppAppLocatorFlags(cmd)
cmd.Flags().StringSlice("permissions", nil, "权限点 scopeValue,多个用逗号分隔")
cmd.Flags().String("scope", "", "--permissions 的兼容别名,单个权限点 scopeValue")
cmd.Flags().String("permission", "", "--permissions 的兼容别名,单个权限点 scopeValue")
_ = cmd.Flags().MarkHidden("scope")
_ = cmd.Flags().MarkHidden("permission")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppPermissionRemoveCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "remove",
Short: "取消开放平台应用权限点",
Example: " dws devapp permission remove --agent-id 123456 --permission Contact.User.mobile --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "permission remove"); err != nil {
return err
}
params := devAppAppLocatorParams(cmd)
if len(params) == 0 {
return devAppAppLocatorRequired()
}
scope := devAppFlagOrFallback(cmd, "permission", "scope")
if scope == "" {
return apperrors.NewValidation("--permission is required")
}
params["scopeValue"] = scope
return runDevAppTool(runner, cmd, "remove_open_dev_app_permission", params)
},
}
addDevAppAppLocatorFlags(cmd)
cmd.Flags().String("permission", "", "待取消权限点 scopeValue")
cmd.Flags().String("scope", "", "--permission 的兼容别名")
_ = cmd.Flags().MarkHidden("scope")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppMemberListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "查询开放平台应用成员",
Example: " dws devapp member list --app-id <unifiedAppId>",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
appID, err := requiredDevAppID(cmd)
if err != nil {
return err
}
return runDevAppTool(runner, cmd, devAppMemberListTool, map[string]any{
"unifiedAppId": appID,
})
},
}
cmd.Flags().String("app-id", "", "开放平台统一应用 ID (必填)")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppMemberAddCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "添加开放平台应用成员",
Example: " dws devapp member add --app-id <unifiedAppId> --users userId1,userId2 --member-type DEVELOPER --dry-run",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDevAppMemberMutation(runner, cmd, devAppMemberAddTool, "member add")
},
}
registerDevAppMemberMutationFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppMemberRemoveCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "remove",
Short: "移除开放平台应用成员",
Example: " dws devapp member remove --app-id <unifiedAppId> --users userId1,userId2 --member-type DEVELOPER --dry-run",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDevAppMemberMutation(runner, cmd, devAppMemberRemoveTool, "member remove")
},
}
registerDevAppMemberMutationFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppSecurityConfigCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "更新开放平台应用安全配置",
Example: " dws devapp security config --app-id <unifiedAppId> " +
"--ip-whitelist 192.0.2.10 --redirect-url https://callback.example.invalid/callback --sso-url https://sso.example.invalid/sso --dry-run",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "security config"); err != nil {
return err
}
appID, err := requiredDevAppID(cmd)
if err != nil {
return err
}
params := map[string]any{"unifiedAppId": appID}
if values := parseDevAppListFlag(cmd, "ip-whitelist"); len(values) > 0 {
params["ipWhiteList"] = values
}
if values := parseDevAppListFlag(cmd, "redirect-url"); len(values) > 0 {
params["redirectUrls"] = values
}
if values := parseDevAppListFlag(cmd, "sso-url"); len(values) > 0 {
params["otherAuthUrls"] = values
}
if len(params) == 1 {
return apperrors.NewValidation("one of --ip-whitelist, --redirect-url, or --sso-url is required")
}
return runDevAppTool(runner, cmd, devAppSecurityConfigTool, params)
},
}
cmd.Flags().String("app-id", "", "开放平台统一应用 ID (必填)")
cmd.Flags().String("ip-whitelist", "", "出口 IP 白名单,多个用逗号或分号分隔")
cmd.Flags().String("redirect-url", "", "登录重定向 URL,多个用逗号或分号分隔")
cmd.Flags().String("sso-url", "", "端内免登地址,多个用逗号或分号分隔")
preferLegacyLeaf(cmd)
return cmd
}
// ---------------------------------------------------------------------------
// 机器人能力
// ---------------------------------------------------------------------------
func newDevAppRobotCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "新建钉钉智能体机器人(提交创建任务并等待完成)",
Example: " dws devapp robot create --app-name 我的智能体 --robot-name 小助手 --desc \"处理审批问答\" --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "robot create"); err != nil {
return err
}
params, err := devAppRobotCreateParams(cmd)
if err != nil {
return err
}
return runDevAppRobotCreate(runner, cmd, params)
},
}
registerDevAppRobotCreateFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppRobotSubmitCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "submit",
Short: "异步提交钉钉智能体机器人创建任务(支持失败重试)",
Example: " dws devapp robot submit --app-name 我的智能体 --robot-name 小助手 --desc \"处理审批问答\" --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "robot submit"); err != nil {
return err
}
params, err := devAppRobotCreateParams(cmd)
if err != nil {
return err
}
// submit_robot_create_task 的 schema 把图标字段标为必填(空值时服务端用默认图标),
// 因此即使用户未提供也补空串占位。
if _, ok := params["robotMediaId"]; !ok {
params["robotMediaId"] = ""
}
if _, ok := params["previewMediaId"]; !ok {
params["previewMediaId"] = ""
}
devAppPutString(params, "taskId", devAppStringFlag(cmd, "task-id"))
return runDevAppTool(runner, cmd, devAppRobotSubmitTool, params)
},
}
registerDevAppRobotCreateFlags(cmd)
cmd.Flags().String("task-id", "", "失败重试时传入原 taskId;为空时服务端自动生成")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppRobotResultCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "result",
Short: "查询机器人异步创建任务结果",
Example: " dws devapp robot result --task-id TASK_ID --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
taskID := devAppStringFlag(cmd, "task-id")
if taskID == "" {
return apperrors.NewValidation("--task-id is required")
}
return runDevAppTool(runner, cmd, devAppRobotResultTool, map[string]any{"taskId": taskID})
},
}
cmd.Flags().String("task-id", "", "提交创建任务时返回的 taskId (必填)")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppRobotConfigGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "查询现有应用的机器人配置",
Example: " dws devapp robot get --unified-app-id UNIFIED_APP_ID --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return err
}
return runDevAppTool(runner, cmd, devAppRobotConfigGetTool, map[string]any{"unifiedAppId": appID})
},
}
addDevAppUnifiedIDFlag(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppRobotConfigCommand(runner executor.Runner, use, short, tool string) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: short,
Example: " dws devapp robot " + use + " --unified-app-id UNIFIED_APP_ID --name 小助手 --brief 审批助手 --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "robot "+use); err != nil {
return err
}
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return err
}
params, updates, err := devAppRobotConfigParams(cmd, appID)
if err != nil {
return err
}
if updates == 0 {
return apperrors.NewValidation("at least one robot config field is required, e.g. --name, --brief, --description, --icon, --outgoing-url, --event-url, --mode, --skills")
}
return runDevAppTool(runner, cmd, tool, params)
},
}
addDevAppUnifiedIDFlag(cmd)
registerDevAppRobotConfigFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppRobotOfflineCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "offline",
Short: "停用现有应用的机器人能力",
Example: " dws devapp robot offline --unified-app-id UNIFIED_APP_ID --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "robot offline"); err != nil {
return err
}
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return err
}
return runDevAppTool(runner, cmd, devAppRobotOfflineTool, map[string]any{"unifiedAppId": appID})
},
}
addDevAppUnifiedIDFlag(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func registerDevAppRobotCreateFlags(cmd *cobra.Command) {
cmd.Flags().String("app-name", "", "智能体应用名称,长度 2-20,企业内唯一 (必填)")
cmd.Flags().String("robot-name", "", "承载机器人名称,用于客户端展示 (必填)")
cmd.Flags().String("desc", "", "机器人功能描述,不超过 200 字 (必填)")
cmd.Flags().String("icon", "", "机器人图标 mediaId;为空时使用默认图标")
cmd.Flags().String("preview", "", "机器人预览图 mediaId;为空时复用图标")
}
func devAppRobotCreateParams(cmd *cobra.Command) (map[string]any, error) {
appName := devAppStringFlag(cmd, "app-name")
if appName == "" {
return nil, apperrors.NewValidation("--app-name is required")
}
robotName := devAppStringFlag(cmd, "robot-name")
if robotName == "" {
return nil, apperrors.NewValidation("--robot-name is required")
}
desc := devAppStringFlag(cmd, "desc")
if desc == "" {
return nil, apperrors.NewValidation("--desc is required")
}
params := map[string]any{
"appName": appName,
"robotName": robotName,
"desc": desc,
}
devAppPutString(params, "robotMediaId", devAppStringFlag(cmd, "icon"))
devAppPutString(params, "previewMediaId", devAppStringFlag(cmd, "preview"))
return params, nil
}
// runDevAppRobotCreate 实现 `dws devapp robot create`。
//
// 同步工具 create_dingtalk_robot 的服务端动作模板已失效(issue #35),因此这里
// 改走异步路径:submit_robot_create_task 拿 taskId,再按服务端给的 interval 轮询
// query_robot_create_result 直到任务进入终态,最后输出与同步接口一致的结果
// (agentId/clientId/robotCode/clientSecret)。用户感知仍是“一条命令建出机器人”。
func runDevAppRobotCreate(runner executor.Runner, cmd *cobra.Command, params map[string]any) error {
// submit_robot_create_task 的 schema 把图标字段标为必填(空值时服务端用默认图标),
// 即使用户未提供也补空串占位。
if _, ok := params["robotMediaId"]; !ok {
params["robotMediaId"] = ""
}
if _, ok := params["previewMediaId"]; !ok {
params["previewMediaId"] = ""
}
// dry-run 直接预览将要提交的 submit 调用(这正是真实会发生的事)。
if commandDryRun(cmd) {
return runDevAppTool(runner, cmd, devAppRobotSubmitTool, params)
}
legacyPath := cobracmd.LegacyCommandPath(cmd)
submitInv := executor.NewHelperInvocation(legacyPath, devAppProduct, devAppRobotSubmitTool, params)
submitRes, err := runner.Run(cmd.Context(), submitInv)
if err != nil {
return err
}
taskID := devAppRobotResultString(submitRes, "taskId")
if taskID == "" {
// 拿不到 taskId 说明服务端返回结构异常,原样回吐 submit 结果由用户判断。
return writeCommandPayload(cmd, submitRes)
}
last := submitRes
interval := devAppRobotResultInterval(submitRes)
const maxAttempts = 24 // 配合 interval(默认 5s)约 2 分钟内收敛
for attempt := 0; attempt < maxAttempts; attempt++ {
if !devAppRobotStatusPending(devAppRobotResultString(last, "status")) {
break
}
select {
case <-cmd.Context().Done():
return cmd.Context().Err()
case <-time.After(interval):
}
pollInv := executor.NewHelperInvocation(legacyPath, devAppProduct, devAppRobotResultTool, map[string]any{"taskId": taskID})
pollRes, pollErr := runner.Run(cmd.Context(), pollInv)
if pollErr != nil {
return pollErr
}
last = pollRes
if next := devAppRobotResultInterval(pollRes); next > 0 {
interval = next
}
}
return writeCommandPayload(cmd, last)
}
// devAppRobotResultBody 取出工具返回里的业务 result 对象
// (形如 {"errorCode":null,"result":{...},"success":true} 中的 result)。
func devAppRobotResultBody(res executor.Result) map[string]any {
content, ok := res.Response["content"].(map[string]any)
if !ok {
return nil
}
body, _ := content["result"].(map[string]any)
return body
}
func devAppRobotResultString(res executor.Result, key string) string {
body := devAppRobotResultBody(res)
if body == nil {
return ""
}
value, _ := body[key].(string)
return strings.TrimSpace(value)
}
// devAppRobotResultInterval 读取服务端建议的轮询间隔(秒),缺省 5 秒。
func devAppRobotResultInterval(res executor.Result) time.Duration {
body := devAppRobotResultBody(res)
if body != nil {
if raw, ok := body["interval"].(float64); ok && raw > 0 {
return time.Duration(raw * float64(time.Second))
}
}
return 5 * time.Second
}
// devAppRobotStatusPending 判断创建任务是否仍在进行中(需要继续轮询)。
// 已知终态:SUCCESS / APPROVAL_REQUIRED / FAILED / ERROR 等;未知或空状态保守按终态处理,避免死等。
func devAppRobotStatusPending(status string) bool {
switch strings.ToUpper(strings.TrimSpace(status)) {
case "WAITING", "PROCESSING", "RUNNING", "PENDING", "INIT", "DOING":
return true
default:
return false
}
}
func registerDevAppRobotConfigFlags(cmd *cobra.Command) {
cmd.Flags().String("name", "", "机器人名称")
cmd.Flags().String("brief", "", "机器人简介")
cmd.Flags().String("description", "", "机器人描述")
cmd.Flags().String("icon", "", "机器人图标 mediaId")
cmd.Flags().String("outgoing-url", "", "消息回调地址 (outgoingUrl)")
cmd.Flags().String("event-url", "", "事件回调地址 (chatBotEventUrl)")
cmd.Flags().Int("mode", 0, "机器人模式枚举")
cmd.Flags().StringSlice("skills", nil, "技能列表,多个用逗号分隔")
cmd.Flags().Bool("add-scope", false, "是否自动添加机器人相关权限")
cmd.Flags().Bool("disable-ssl-verify", false, "回调地址是否关闭 SSL 校验")
cmd.Flags().String("i18n-name", "", "机器人名称国际化 JSON,如 '{\"en_US\":\"Bot\"}'")
cmd.Flags().String("i18n-brief", "", "机器人简介国际化 JSON")
cmd.Flags().String("i18n-description", "", "机器人描述国际化 JSON")
}
func devAppRobotConfigParams(cmd *cobra.Command, appID string) (map[string]any, int, error) {
params := map[string]any{"unifiedAppId": appID}
updates := 0
setString := func(key, flag string) {
if v := devAppStringFlag(cmd, flag); v != "" {
params[key] = v
updates++
}
}
setString("name", "name")
setString("brief", "brief")
setString("description", "description")
setString("iconMediaId", "icon")
setString("outgoingUrl", "outgoing-url")
setString("chatBotEventUrl", "event-url")
if cmd.Flags().Changed("mode") {
params["mode"] = devAppIntFlag(cmd, "mode")
updates++
}
if cmd.Flags().Changed("add-scope") {
value, _ := cmd.Flags().GetBool("add-scope")
params["isAddScope"] = value
updates++
}
if cmd.Flags().Changed("disable-ssl-verify") {
value, _ := cmd.Flags().GetBool("disable-ssl-verify")
params["disableSSLVerify"] = value
updates++
}
if values, _ := cmd.Flags().GetStringSlice("skills"); len(values) > 0 {
params["skillList"] = values
updates++
}
for _, item := range []struct{ key, flag string }{
{"i18nName", "i18n-name"},
{"i18nBrief", "i18n-brief"},
{"i18nDescription", "i18n-description"},
} {
raw := devAppStringFlag(cmd, item.flag)
if raw == "" {
continue
}
parsed := map[string]any{}
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
return nil, 0, apperrors.NewValidation(fmt.Sprintf("--%s must be valid JSON object: %v", item.flag, err))
}
params[item.key] = parsed
updates++
}
return params, updates, nil
}
// ---------------------------------------------------------------------------
// 版本发布能力
// ---------------------------------------------------------------------------
func newDevAppVersionCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "基于当前配置创建应用新版本",
Example: " dws devapp version create --unified-app-id UNIFIED_APP_ID --version 1.0.1 --desc \"新增机器人能力\" --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "version create"); err != nil {
return err
}
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return err
}
params := map[string]any{"unifiedAppId": appID}
devAppPutString(params, "version", devAppStringFlag(cmd, "version"))
devAppPutString(params, "description", devAppStringFlag(cmd, "desc"))
return runDevAppTool(runner, cmd, devAppVersionCreateTool, params)
},
}
addDevAppUnifiedIDFlag(cmd)
cmd.Flags().String("version", "", "版本号,如 1.0.1")
cmd.Flags().String("desc", "", "版本描述")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppVersionListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "分页查询应用版本列表",
Example: " dws devapp version list --unified-app-id UNIFIED_APP_ID --page 1 --page-size 20 --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return err
}
params := map[string]any{
"unifiedAppId": appID,
"currentPage": devAppIntFlag(cmd, "page"),
"pageSize": devAppIntFlag(cmd, "page-size"),
}
return runDevAppTool(runner, cmd, devAppVersionListTool, params)
},
}
addDevAppUnifiedIDFlag(cmd)
cmd.Flags().Int("page", 1, "分页页码,从 1 开始")
cmd.Flags().Int("page-size", 20, "分页大小")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppVersionGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "查询指定版本详情",
Example: " dws devapp version get --unified-app-id UNIFIED_APP_ID --version-id VERSION_ID --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params, err := devAppVersionLocator(cmd)
if err != nil {
return err
}
return runDevAppTool(runner, cmd, devAppVersionDetailTool, params)
},
}
addDevAppVersionLocatorFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppVersionCheckApprovalCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "check-approval",
Short: "预检版本发布是否需要审批(不实际发布)",
Example: " dws devapp version check-approval --unified-app-id UNIFIED_APP_ID --version-id VERSION_ID --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params, err := devAppVersionLocator(cmd)
if err != nil {
return err
}
// 复用 publish 工具的服务端预检模式:dryRun=true 只返回审批要求,不发布。
params["dryRun"] = true
return runDevAppTool(runner, cmd, devAppVersionPublishTool, params)
},
}
addDevAppVersionLocatorFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppVersionPublishCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "publish",
Short: "发布指定版本(含高敏权限需 --confirm-sensitive)",
Example: " dws devapp version publish --unified-app-id UNIFIED_APP_ID --version-id VERSION_ID --dry-run --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := devAppRequireWriteGuard(cmd, "version publish"); err != nil {
return err
}
params, err := devAppVersionLocator(cmd)
if err != nil {
return err
}
params["dryRun"] = false
if cmd.Flags().Changed("confirm-sensitive") {
value, _ := cmd.Flags().GetBool("confirm-sensitive")
params["confirmedSensitive"] = value
}
devAppPutString(params, "approverUserId", devAppStringFlag(cmd, "approver"))
return runDevAppTool(runner, cmd, devAppVersionPublishTool, params)
},
}
addDevAppVersionLocatorFlags(cmd)
cmd.Flags().Bool("confirm-sensitive", false, "确认发布包含高敏权限的版本")
cmd.Flags().String("approver", "", "灰度选人模式下指定审批人 userId")
preferLegacyLeaf(cmd)
return cmd
}
func newDevAppVersionStatusCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "查询版本发布/审批状态",
Example: " dws devapp version status --unified-app-id UNIFIED_APP_ID --version-id VERSION_ID --format json",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params, err := devAppVersionLocator(cmd)
if err != nil {
return err
}
return runDevAppTool(runner, cmd, devAppVersionStatusTool, params)
},
}
addDevAppVersionLocatorFlags(cmd)
preferLegacyLeaf(cmd)
return cmd
}
func addDevAppVersionLocatorFlags(cmd *cobra.Command) {
addDevAppUnifiedIDFlag(cmd)
cmd.Flags().String("version-id", "", "版本 ID (必填)")
}
func devAppVersionLocator(cmd *cobra.Command) (map[string]any, error) {
appID, err := requiredDevAppUnifiedID(cmd)
if err != nil {
return nil, err
}
versionID := devAppStringFlag(cmd, "version-id")
if versionID == "" {
return nil, apperrors.NewValidation("--version-id is required")
}
return map[string]any{"unifiedAppId": appID, "versionId": versionID}, nil
}
func addDevAppUnifiedIDFlag(cmd *cobra.Command) {
cmd.Flags().String("unified-app-id", "", "统一应用 ID (必填)")
cmd.Flags().String("app-id", "", "--unified-app-id 的兼容别名")
_ = cmd.Flags().MarkHidden("app-id")
}
func requiredDevAppUnifiedID(cmd *cobra.Command) (string, error) {
appID := devAppFlagOrFallback(cmd, "unified-app-id", "app-id")
if appID == "" {
return "", apperrors.NewValidation("--unified-app-id is required")
}
return appID, nil
}
func registerDevAppMemberMutationFlags(cmd *cobra.Command) {
cmd.Flags().String("app-id", "", "开放平台统一应用 ID (必填)")
cmd.Flags().String("users", "", "成员 userId 列表,多个用逗号分隔 (必填)")
cmd.Flags().String("member-type", "", "成员类型,如 DEVELOPER (必填)")
}
func runDevAppMemberMutation(runner executor.Runner, cmd *cobra.Command, tool, operation string) error {
if err := devAppRequireWriteGuard(cmd, operation); err != nil {
return err
}
appID, err := requiredDevAppID(cmd)
if err != nil {
return err
}
users, err := requiredDevAppUsers(cmd)
if err != nil {
return err
}
memberType, err := requiredDevAppMemberType(cmd)
if err != nil {
return err
}
params := map[string]any{
"unifiedAppId": appID,
"memberUserIds": users,
"memberType": memberType,
}
return runDevAppTool(runner, cmd, tool, params)
}
func runDevAppTool(runner executor.Runner, cmd *cobra.Command, tool string, params map[string]any) error {
invocation := executor.NewHelperInvocation(
cobracmd.LegacyCommandPath(cmd),
devAppProduct,
tool,
params,
)
invocation.DryRun = commandDryRun(cmd)
result, err := runner.Run(cmd.Context(), invocation)
if err != nil {
return err
}
return writeCommandPayload(cmd, result)
}
func requiredDevAppID(cmd *cobra.Command) (string, error) {
appID, _ := cmd.Flags().GetString("app-id")
appID = strings.TrimSpace(appID)
if appID == "" {
return "", apperrors.NewValidation("--app-id is required")
}
return appID, nil
}
func requiredDevAppUsers(cmd *cobra.Command) ([]string, error) {
usersRaw, _ := cmd.Flags().GetString("users")
if strings.TrimSpace(usersRaw) == "" {
return nil, apperrors.NewValidation("--users is required")
}
users := splitDevAppList(usersRaw)
if len(users) == 0 {
return nil, apperrors.NewValidation("--users must contain at least one userId")
}
return users, nil
}
func requiredDevAppMemberType(cmd *cobra.Command) (string, error) {
memberType, _ := cmd.Flags().GetString("member-type")
memberType = strings.TrimSpace(memberType)
if memberType == "" {
return "", apperrors.NewValidation("--member-type is required")
}
return memberType, nil
}
func parseDevAppListFlag(cmd *cobra.Command, name string) []string {
raw, _ := cmd.Flags().GetString(name)
return splitDevAppList(raw)
}
func splitDevAppList(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
raw = strings.ReplaceAll(raw, ";", ",")
parts := strings.Split(raw, ",")
values := make([]string, 0, len(parts))
for _, part := range parts {
if value := strings.TrimSpace(part); value != "" {
values = append(values, value)
}
}
return values
}
func addDevAppLocatorFlags(cmd *cobra.Command) {
addDevAppLocatorFlagsWithoutName(cmd)
cmd.Flags().String("name", "", "应用名称关键词;写操作前必须唯一命中")
}
func addDevAppLocatorFlagsWithoutName(cmd *cobra.Command) {
addDevAppAppLocatorFlags(cmd)
cmd.Flags().Int("app-id", 0, "兼容 appId")
cmd.Flags().String("app-key", "", "appKey/clientId")
cmd.Flags().String("custom-key", "", "customKey")
}
func addDevAppAppLocatorFlags(cmd *cobra.Command) {
cmd.Flags().String("unified-app-id", "", "统一应用 ID")
cmd.Flags().Int("agent-id", 0, "应用 agentId")
}
func devAppLocatorParams(cmd *cobra.Command, includeName bool) map[string]any {
params := devAppAppLocatorParams(cmd)
devAppPutInt(params, "appId", devAppIntFlag(cmd, "app-id"))
devAppPutString(params, "appKey", devAppStringFlag(cmd, "app-key"))
devAppPutString(params, "customKey", devAppStringFlag(cmd, "custom-key"))
if includeName {
devAppPutString(params, "appName", devAppStringFlag(cmd, "name"))
}
return params
}
func devAppAppLocatorParams(cmd *cobra.Command) map[string]any {
params := map[string]any{}
devAppPutString(params, "unifiedAppId", devAppStringFlag(cmd, "unified-app-id"))
devAppPutInt(params, "agentId", devAppIntFlag(cmd, "agent-id"))
return params
}
func devAppLocatorRequired(includeName bool) error {
if includeName {
return apperrors.NewValidation("one app locator is required: --agent-id, --unified-app-id, --app-key, --custom-key, --app-id, or --name")
}
return apperrors.NewValidation("one app locator is required: --agent-id, --unified-app-id, --app-key, --custom-key, or --app-id")
}
func devAppAppLocatorRequired() error {
return apperrors.NewValidation("one app locator is required: --agent-id or --unified-app-id")
}
func devAppRequireWriteGuard(cmd *cobra.Command, operation string) error {
if commandDryRun(cmd) || devAppYes(cmd) {
return nil
}
return apperrors.NewValidation(fmt.Sprintf("%s is a write operation; rerun with --dry-run to preview or --yes after confirmation", operation))
}
func devAppYes(cmd *cobra.Command) bool {
for _, flags := range []*pflag.FlagSet{cmd.Flags(), cmd.InheritedFlags(), cmd.Root().PersistentFlags()} {
if flags == nil || flags.Lookup("yes") == nil {
continue
}
if value, err := flags.GetBool("yes"); err == nil && value {
return true
}
}
return false
}
func devAppPermissionScopes(cmd *cobra.Command) []string {
values, _ := cmd.Flags().GetStringSlice("permissions")
values = append(values, devAppFlagOrFallback(cmd, "scope", "permission"))
out := make([]string, 0, len(values))
for _, value := range values {
for _, part := range splitDevAppList(value) {
if part != "" {
out = append(out, part)
}
}
}
return out
}
func devAppStringFlag(cmd *cobra.Command, name string) string {
value, _ := cmd.Flags().GetString(name)
return strings.TrimSpace(value)
}
func devAppIntFlag(cmd *cobra.Command, name string) int {
value, _ := cmd.Flags().GetInt(name)
return value
}
func devAppFlagOrFallback(cmd *cobra.Command, primary, fallback string) string {
if value := devAppStringFlag(cmd, primary); value != "" {
return value
}
return devAppStringFlag(cmd, fallback)
}
func devAppPutString(params map[string]any, key, value string) {
if value != "" {
params[key] = value
}
}
func devAppPutInt(params map[string]any, key string, value int) {
if value != 0 {
params[key] = value
}
}