1746 lines
76 KiB
Go
1746 lines
76 KiB
Go
// 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 devapp declares high-fidelity shortcuts for the DingTalk open-platform
|
||
// developer application ("devapp") MCP service, for the apps
|
||
// command surface. Tool names and parameter keys are copied verbatim from
|
||
// internal/helpers/devapp.go — do not invent tools or params here.
|
||
//
|
||
// Skipped tools (require async orchestration / polling, not a single MCP call):
|
||
// - submit_robot_create_task (异步提交机器人建号任务)
|
||
// - query_robot_create_result (异步轮询建号结果)
|
||
//
|
||
// The robot-config i18n JSON-object params (i18nName/i18nBrief/i18nDescription)
|
||
// are also omitted: they need JSON-object parsing that a flat flag can't express.
|
||
package devapp
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||
)
|
||
|
||
const productDevApp = "devapp"
|
||
|
||
// applyCursor forwards --cursor/--page-size into params, matching the helper's
|
||
// pass-through pagination (pageSize defaults to 20, cursor omitted on page 1).
|
||
func applyCursor(rt *shortcut.RuntimeContext, params map[string]any) {
|
||
if rt.Changed("cursor") {
|
||
if cur := rt.Str("cursor"); cur != "" {
|
||
params["cursor"] = cur
|
||
}
|
||
}
|
||
size := rt.Int("page-size")
|
||
if size < 1 {
|
||
size = 20
|
||
}
|
||
params["pageSize"] = size
|
||
}
|
||
|
||
var cursorFlags = []shortcut.Flag{
|
||
{Name: "cursor", Type: shortcut.FlagString, Desc: "游标令牌:首次查询留空,续翻传上次 meta.pagination.next_token"},
|
||
{Name: "page-size", Type: shortcut.FlagInt, Default: "20", Desc: "单页条数,默认 20"},
|
||
}
|
||
|
||
func devAppObjectResult(outcomes ...contract.ResultOutcome) *contract.ResultSpec {
|
||
return &contract.ResultSpec{
|
||
Outcomes: append([]contract.ResultOutcome(nil), outcomes...),
|
||
DataSchema: json.RawMessage(`{"type":"object","description":"开放平台命令返回的业务对象;具体字段由对应操作定义","additionalProperties":true}`),
|
||
}
|
||
}
|
||
|
||
func devAppPaginatedProjectionResult(collection, description string) *contract.ResultSpec {
|
||
schema, _ := json.Marshal(map[string]any{
|
||
"type": "object",
|
||
"description": description,
|
||
"additionalProperties": false,
|
||
"properties": map[string]any{
|
||
"count": map[string]any{
|
||
"type": "integer",
|
||
"description": "当前页业务记录数量",
|
||
},
|
||
collection: map[string]any{
|
||
"type": "array",
|
||
"description": "当前页业务记录;分页控制信息只读取 meta.pagination",
|
||
"items": map[string]any{
|
||
"type": "object",
|
||
"additionalProperties": true,
|
||
},
|
||
},
|
||
},
|
||
"required": []string{"count", collection},
|
||
})
|
||
return &contract.ResultSpec{
|
||
Outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
|
||
DataSchema: schema,
|
||
}
|
||
}
|
||
|
||
func devAppCursorPagination() *contract.PaginationSpec {
|
||
return &contract.PaginationSpec{
|
||
Kind: contract.PaginationKindCursor,
|
||
CursorParameter: "cursor",
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 应用主体
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// ListApp maps helper `list_dev_app`.
|
||
var ListApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+list",
|
||
Product: productDevApp,
|
||
Description: "查询开放平台企业内部应用列表",
|
||
Intent: "当你要在开发者后台盘点或定位某个企业内部应用(例如按应用名、appKey、创建人或机器人名搜索,拿到其 unifiedAppId 以便后续查看详情、配置或发布)时使用;支持关键词过滤、排序和分页,返回应用列表。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_list",
|
||
CanonicalPath: "devapp.shortcut_list",
|
||
CLIPath: "devapp +list",
|
||
PrimaryCLIPath: "devapp +list",
|
||
},
|
||
Description: "查询开放平台企业内部应用列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询开放平台企业内部应用列表",
|
||
UseWhen: []string{"当你要在开发者后台盘点或定位某个企业内部应用(例如按应用名、appKey、创建人或机器人名搜索,拿到其 unifiedAppId 以便后续查看详情、配置或发布)时使用;支持关键词过滤、排序和分页,返回应用列表。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +list"},
|
||
},
|
||
Result: devAppPaginatedProjectionResult("apps", "当前页开放平台应用查询结果"),
|
||
Pagination: devAppCursorPagination(),
|
||
},
|
||
Flags: append([]shortcut.Flag{
|
||
{Name: "name", Type: shortcut.FlagString, Desc: "应用名称关键词"},
|
||
{Name: "app-key", Type: shortcut.FlagString, Desc: "按 appKey/clientId 过滤"},
|
||
{Name: "app-group-id", Type: shortcut.FlagInt, Desc: "应用分组 ID"},
|
||
{Name: "creator", Type: shortcut.FlagString, Desc: "创建人名称关键词"},
|
||
{Name: "robot-name", Type: shortcut.FlagString, Desc: "机器人名称关键词"},
|
||
{Name: "develop-type", Type: shortcut.FlagInt, Desc: "开发类型枚举;不确定时不要传"},
|
||
{Name: "filter-cool-app", Type: shortcut.FlagInt, Desc: "酷应用过滤枚举;不确定时不要传"},
|
||
{Name: "sort-type", Type: shortcut.FlagString, Desc: "排序字段,如 gmt_modified"},
|
||
{Name: "sort-order", Type: shortcut.FlagString, Desc: "排序方向 asc 或 desc"},
|
||
}, cursorFlags...),
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{}
|
||
applyCursor(rt, params)
|
||
if rt.Changed("name") {
|
||
params["name"] = rt.Str("name")
|
||
}
|
||
if rt.Changed("app-key") {
|
||
params["appKey"] = rt.Str("app-key")
|
||
}
|
||
if rt.Changed("app-group-id") {
|
||
params["appGroupId"] = rt.Int("app-group-id")
|
||
}
|
||
if rt.Changed("creator") {
|
||
params["creator"] = rt.Str("creator")
|
||
}
|
||
if rt.Changed("robot-name") {
|
||
params["robotName"] = rt.Str("robot-name")
|
||
}
|
||
if rt.Changed("develop-type") {
|
||
params["developType"] = rt.Int("develop-type")
|
||
}
|
||
if rt.Changed("filter-cool-app") {
|
||
params["filterCoolApp"] = rt.Int("filter-cool-app")
|
||
}
|
||
if rt.Changed("sort-type") {
|
||
params["sortType"] = rt.Str("sort-type")
|
||
}
|
||
if rt.Changed("sort-order") {
|
||
params["sortOrder"] = rt.Str("sort-order")
|
||
}
|
||
data, err := rt.CallMCPData(productDevApp, "list_dev_app", params)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
apps := listAppProject(data)
|
||
return rt.Output(devAppListProjection(data, "apps", apps))
|
||
},
|
||
}
|
||
|
||
// listAppProject reshapes list_dev_app into a clean app list
|
||
// ({unifiedAppId, name, appKey, agentId, status, gmtModified}) — output-projection
|
||
// clean output projection. The list container and per-item field names are probed
|
||
// defensively across candidate keys, so an unknown/empty shape yields an empty
|
||
// list rather than a crash or fabricated data.
|
||
func listAppProject(data map[string]any) []map[string]any {
|
||
raw := listAppFindList(data)
|
||
out := make([]map[string]any, 0, len(raw))
|
||
for _, item := range raw {
|
||
m, ok := item.(map[string]any)
|
||
if !ok {
|
||
continue
|
||
}
|
||
row := map[string]any{}
|
||
if v, ok := listAppFirst(m, "unifiedAppId", "unified_app_id"); ok {
|
||
row["unifiedAppId"] = v
|
||
}
|
||
if v, ok := listAppFirst(m, "name", "appName", "app_name"); ok {
|
||
row["name"] = v
|
||
}
|
||
if v, ok := listAppFirst(m, "appKey", "clientId", "app_key", "client_id"); ok {
|
||
row["appKey"] = v
|
||
}
|
||
if v, ok := listAppFirst(m, "agentId", "agent_id"); ok {
|
||
row["agentId"] = v
|
||
}
|
||
if v, ok := listAppFirst(m, "status", "appStatus", "app_status"); ok {
|
||
row["status"] = v
|
||
}
|
||
if v, ok := listAppFirst(m, "gmtModified", "gmt_modified", "modifyTime", "modified_time"); ok {
|
||
row["gmtModified"] = v
|
||
}
|
||
if len(row) > 0 {
|
||
out = append(out, row)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// listAppFindList locates the app list payload, tolerating a bare top-level
|
||
// array or nesting one level under a common envelope key.
|
||
func listAppFindList(data map[string]any) []any {
|
||
if data == nil {
|
||
return []any{}
|
||
}
|
||
for _, k := range []string{"list", "items", "apps", "appList", "result", "data"} {
|
||
v, ok := data[k]
|
||
if !ok {
|
||
continue
|
||
}
|
||
if arr, ok := v.([]any); ok {
|
||
return arr
|
||
}
|
||
if inner, ok := v.(map[string]any); ok {
|
||
for _, ik := range []string{"list", "items", "apps", "appList", "result", "data"} {
|
||
if arr, ok := inner[ik].([]any); ok {
|
||
return arr
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return []any{}
|
||
}
|
||
|
||
// listAppFirst returns the first present candidate key's value.
|
||
func listAppFirst(m map[string]any, keys ...string) (any, bool) {
|
||
for _, k := range keys {
|
||
if v, ok := m[k]; ok {
|
||
return v, true
|
||
}
|
||
}
|
||
return nil, false
|
||
}
|
||
|
||
// GetApp maps helper `get_dev_app`.
|
||
var GetApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+get",
|
||
Product: productDevApp,
|
||
Description: "查询开放平台企业内部应用详情",
|
||
Intent: "当你已知某应用的 unifiedAppId、需要查看它的完整配置信息(如名称、描述、图标、能力开关等)以便核对现状或作为修改前的依据时使用;输入 unifiedAppId,返回单个应用的详情。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_get",
|
||
CanonicalPath: "devapp.shortcut_get",
|
||
CLIPath: "devapp +get",
|
||
PrimaryCLIPath: "devapp +get",
|
||
},
|
||
Description: "查询开放平台企业内部应用详情",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询开放平台企业内部应用详情",
|
||
UseWhen: []string{"当你已知某应用的 unifiedAppId、需要查看它的完整配置信息(如名称、描述、图标、能力开关等)以便核对现状或作为修改前的依据时使用;输入 unifiedAppId,返回单个应用的详情。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +get --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("get_dev_app", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// CreateApp maps helper `create_dev_app`.
|
||
var CreateApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+create",
|
||
Product: productDevApp,
|
||
Description: "创建开放平台企业内部应用",
|
||
Intent: "当你要在开放平台从零新建一个企业内部应用(H5/机器人等的载体)时使用;传入应用名称、可选描述与图标 mediaId,会实际创建出一个新应用并返回其 unifiedAppId 供后续配置。",
|
||
Risk: shortcut.RiskWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_create",
|
||
CanonicalPath: "devapp.shortcut_create",
|
||
CLIPath: "devapp +create",
|
||
PrimaryCLIPath: "devapp +create",
|
||
},
|
||
Description: "创建开放平台企业内部应用",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "创建开放平台企业内部应用",
|
||
UseWhen: []string{"当你要在开放平台从零新建一个企业内部应用(H5/机器人等的载体)时使用;传入应用名称、可选描述与图标 mediaId,会实际创建出一个新应用并返回其 unifiedAppId 供后续配置。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +create --name <NAME>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "name", Type: shortcut.FlagString, Desc: "应用名称", Required: true},
|
||
{Name: "desc", Type: shortcut.FlagString, Desc: "应用描述"},
|
||
{Name: "icon-media-id", Type: shortcut.FlagString, Desc: "应用图标 mediaId"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"name": rt.Str("name")}
|
||
if rt.Changed("desc") {
|
||
params["desc"] = rt.Str("desc")
|
||
}
|
||
if rt.Changed("icon-media-id") {
|
||
params["iconMediaId"] = rt.Str("icon-media-id")
|
||
}
|
||
return rt.CallMCP("create_dev_app", params)
|
||
},
|
||
}
|
||
|
||
// UpdateApp maps helper `update_dev_app`.
|
||
var UpdateApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+update",
|
||
Product: productDevApp,
|
||
Description: "修改开放平台企业内部应用基础信息",
|
||
Intent: "当你要改动一个已存在应用的基础信息(更名、改描述或换图标)时使用;指定 unifiedAppId 及要更新的字段,会实际写回并覆盖对应的应用基础资料。",
|
||
Risk: shortcut.RiskWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_update",
|
||
CanonicalPath: "devapp.shortcut_update",
|
||
CLIPath: "devapp +update",
|
||
PrimaryCLIPath: "devapp +update",
|
||
},
|
||
Description: "修改开放平台企业内部应用基础信息",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "修改开放平台企业内部应用基础信息",
|
||
UseWhen: []string{"当你要改动一个已存在应用的基础信息(更名、改描述或换图标)时使用;指定 unifiedAppId 及要更新的字段,会实际写回并覆盖对应的应用基础资料。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +update --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "name", Type: shortcut.FlagString, Desc: "新的应用名称"},
|
||
{Name: "desc", Type: shortcut.FlagString, Desc: "新的应用描述"},
|
||
{Name: "icon-media-id", Type: shortcut.FlagString, Desc: "新的应用图标 mediaId"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("name") {
|
||
params["name"] = rt.Str("name")
|
||
}
|
||
if rt.Changed("desc") {
|
||
params["desc"] = rt.Str("desc")
|
||
}
|
||
if rt.Changed("icon-media-id") {
|
||
params["iconMediaId"] = rt.Str("icon-media-id")
|
||
}
|
||
return rt.CallMCP("update_dev_app", params)
|
||
},
|
||
}
|
||
|
||
// DeleteApp maps helper `delete_dev_app`.
|
||
var DeleteApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+delete",
|
||
Product: productDevApp,
|
||
Description: "删除开放平台企业内部应用(不可逆)",
|
||
Intent: "当你确认要彻底废弃某个企业内部应用时使用;传入 unifiedAppId 会真实且不可逆地删除该应用及其配置,执行前务必确认无误。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "destructive", Risk: "high",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_delete",
|
||
CanonicalPath: "devapp.shortcut_delete",
|
||
CLIPath: "devapp +delete",
|
||
PrimaryCLIPath: "devapp +delete",
|
||
},
|
||
Description: "删除开放平台企业内部应用(不可逆)",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "删除开放平台企业内部应用(不可逆)",
|
||
UseWhen: []string{"当你确认要彻底废弃某个企业内部应用时使用;传入 unifiedAppId 会真实且不可逆地删除该应用及其配置,执行前务必确认无误。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +delete --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("delete_dev_app", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// EnableApp maps helper `enable_dev_app`.
|
||
var EnableApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+enable",
|
||
Product: productDevApp,
|
||
Description: "启用开放平台企业内部应用",
|
||
Intent: "当某个应用处于停用状态、你要让它重新生效可用时使用;传入 unifiedAppId 会实际将应用状态切换为启用。",
|
||
Risk: shortcut.RiskWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_enable",
|
||
CanonicalPath: "devapp.shortcut_enable",
|
||
CLIPath: "devapp +enable",
|
||
PrimaryCLIPath: "devapp +enable",
|
||
},
|
||
Description: "启用开放平台企业内部应用",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "启用开放平台企业内部应用",
|
||
UseWhen: []string{"当某个应用处于停用状态、你要让它重新生效可用时使用;传入 unifiedAppId 会实际将应用状态切换为启用。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +enable --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("enable_dev_app", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// DisableApp maps helper `disable_dev_app`.
|
||
var DisableApp = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+disable",
|
||
Product: productDevApp,
|
||
Description: "停用开放平台企业内部应用",
|
||
Intent: "当你要临时下线某个应用、让它对用户不可用又不删除时使用;传入 unifiedAppId 会实际将应用状态切换为停用,可日后再启用恢复。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "destructive", Risk: "high",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_disable",
|
||
CanonicalPath: "devapp.shortcut_disable",
|
||
CLIPath: "devapp +disable",
|
||
PrimaryCLIPath: "devapp +disable",
|
||
},
|
||
Description: "停用开放平台企业内部应用",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "停用开放平台企业内部应用",
|
||
UseWhen: []string{"当你要临时下线某个应用、让它对用户不可用又不删除时使用;传入 unifiedAppId 会实际将应用状态切换为停用,可日后再启用恢复。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +disable --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("disable_dev_app", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// GetCredentials maps helper `get_dev_app_credentials`.
|
||
var GetCredentials = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+credentials-get",
|
||
Product: productDevApp,
|
||
Description: "读取开放平台应用凭证",
|
||
Intent: "当你需要拿到某应用的鉴权凭证(如 clientId/AppKey、clientSecret/AppSecret)以便在代码或调试中调用开放平台接口时使用;输入 unifiedAppId,返回该应用的凭证信息。",
|
||
Risk: shortcut.RiskRead,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("get_dev_app_credentials", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 网页应用配置
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// WebappGet maps helper `get_extension_webapp_config`.
|
||
var WebappGet = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+webapp-get",
|
||
Product: productDevApp,
|
||
Description: "查询网页应用配置",
|
||
Intent: "当你要查看某应用的网页(H5)能力现状,如移动端/PC 首页地址、管理后台地址等,以便核对或作为改配置前的参考时使用;输入 unifiedAppId,返回当前网页应用配置。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_webapp_get",
|
||
CanonicalPath: "devapp.shortcut_webapp_get",
|
||
CLIPath: "devapp +webapp-get",
|
||
PrimaryCLIPath: "devapp +webapp-get",
|
||
},
|
||
Description: "查询网页应用配置",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询网页应用配置",
|
||
UseWhen: []string{"当你要查看某应用的网页(H5)能力现状,如移动端/PC 首页地址、管理后台地址等,以便核对或作为改配置前的参考时使用;输入 unifiedAppId,返回当前网页应用配置。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +webapp-get --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("get_extension_webapp_config", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// WebappConfig maps helper `set_extension_webapp_config`.
|
||
var WebappConfig = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+webapp-config",
|
||
Product: productDevApp,
|
||
Description: "配置网页应用能力",
|
||
Intent: "当你要为应用开通或调整网页(H5)入口,如设置移动端/PC 端首页 URL、管理后台地址或页面类型时使用;指定 unifiedAppId 及相应地址,会实际写入该应用的网页应用配置。",
|
||
Risk: shortcut.RiskWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_webapp_config",
|
||
CanonicalPath: "devapp.shortcut_webapp_config",
|
||
CLIPath: "devapp +webapp-config",
|
||
PrimaryCLIPath: "devapp +webapp-config",
|
||
},
|
||
Description: "配置网页应用能力",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "配置网页应用能力",
|
||
UseWhen: []string{"当你要为应用开通或调整网页(H5)入口,如设置移动端/PC 端首页 URL、管理后台地址或页面类型时使用;指定 unifiedAppId 及相应地址,会实际写入该应用的网页应用配置。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +webapp-config --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "h5-page-type", Type: shortcut.FlagString, Desc: "网页应用生效端/页面类型"},
|
||
{Name: "homepage-url", Type: shortcut.FlagString, Desc: "移动端首页地址"},
|
||
{Name: "pc-homepage-url", Type: shortcut.FlagString, Desc: "PC 端首页地址"},
|
||
{Name: "omp-url", Type: shortcut.FlagString, Desc: "管理后台地址"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("h5-page-type") {
|
||
params["h5PageType"] = rt.Str("h5-page-type")
|
||
}
|
||
if rt.Changed("homepage-url") {
|
||
params["homepageUrl"] = rt.Str("homepage-url")
|
||
}
|
||
if rt.Changed("pc-homepage-url") {
|
||
params["pcHomepageUrl"] = rt.Str("pc-homepage-url")
|
||
}
|
||
if rt.Changed("omp-url") {
|
||
params["ompUrl"] = rt.Str("omp-url")
|
||
}
|
||
return rt.CallMCP("set_extension_webapp_config", params)
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 权限
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// PermissionList maps helper `list_dev_app_permissions`.
|
||
var PermissionList = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+permission-list",
|
||
Product: productDevApp,
|
||
Description: "查询开放平台应用权限列表",
|
||
Intent: "当你要查看某应用已申请/可申请的 API 权限点及其授权状态(用于排查接口报权限错、或确认某 scopeValue 是否已开通)时使用;可按关键词、scopeValue、授权状态等过滤,返回权限点列表。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_permission_list",
|
||
CanonicalPath: "devapp.shortcut_permission_list",
|
||
CLIPath: "devapp +permission-list",
|
||
PrimaryCLIPath: "devapp +permission-list",
|
||
},
|
||
Description: "查询开放平台应用权限列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询开放平台应用权限列表",
|
||
UseWhen: []string{"当你要查看某应用已申请/可申请的 API 权限点及其授权状态(用于排查接口报权限错、或确认某 scopeValue 是否已开通)时使用;可按关键词、scopeValue、授权状态等过滤,返回权限点列表。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +permission-list --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppPaginatedProjectionResult("permissions", "当前页开放平台应用权限查询结果"),
|
||
Pagination: devAppCursorPagination(),
|
||
},
|
||
Flags: append([]shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "keyword", Type: shortcut.FlagString, Desc: "权限名、权限点、接口名关键词"},
|
||
{Name: "scope-value", Type: shortcut.FlagString, Desc: "精确权限点 scopeValue"},
|
||
{Name: "auth-status", Type: shortcut.FlagString, Default: "ALL", Desc: "权限状态:ALL、AUTHED、UNAUTHED"},
|
||
{Name: "scope-type", Type: shortcut.FlagString, Desc: "权限一级类型:APP 或 SNS"},
|
||
{Name: "api-status", Type: shortcut.FlagString, Desc: "开发者后台 apiStatus 过滤"},
|
||
}, cursorFlags...),
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("keyword") {
|
||
params["keyword"] = rt.Str("keyword")
|
||
}
|
||
if rt.Changed("scope-value") {
|
||
params["scopeValue"] = rt.Str("scope-value")
|
||
}
|
||
if v := strings.ToUpper(rt.Str("auth-status")); v != "" {
|
||
params["authStatus"] = v
|
||
}
|
||
if rt.Changed("scope-type") {
|
||
params["scopeType"] = strings.ToUpper(rt.Str("scope-type"))
|
||
}
|
||
if rt.Changed("api-status") {
|
||
params["apiStatus"] = rt.Str("api-status")
|
||
}
|
||
applyCursor(rt, params)
|
||
data, err := rt.CallMCPData(productDevApp, "list_dev_app_permissions", params)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
permissions := permissionListProject(data)
|
||
return rt.Output(devAppListProjection(data, "permissions", permissions))
|
||
},
|
||
}
|
||
|
||
// permissionListProject reshapes list_dev_app_permissions into a clean
|
||
// permission-point list ({scopeValue, scopeName, apiName, authStatus, scopeType})
|
||
// — clean output projection. The list container and per-item field
|
||
// names are probed defensively across candidate keys, so an unknown/empty shape
|
||
// yields an empty list rather than a crash or fabricated data.
|
||
func permissionListProject(data map[string]any) []map[string]any {
|
||
raw := permissionListFindList(data)
|
||
out := make([]map[string]any, 0, len(raw))
|
||
for _, item := range raw {
|
||
m, ok := item.(map[string]any)
|
||
if !ok {
|
||
continue
|
||
}
|
||
row := map[string]any{}
|
||
if v, ok := permissionListFirst(m, "scopeValue", "scope_value", "permissionCode", "code"); ok {
|
||
row["scopeValue"] = v
|
||
}
|
||
if v, ok := permissionListFirst(m, "scopeName", "scope_name", "permissionName", "name"); ok {
|
||
row["scopeName"] = v
|
||
}
|
||
if v, ok := permissionListFirst(m, "apiName", "api_name", "interfaceName"); ok {
|
||
row["apiName"] = v
|
||
}
|
||
if v, ok := permissionListFirst(m, "authStatus", "auth_status", "status"); ok {
|
||
row["authStatus"] = v
|
||
}
|
||
if v, ok := permissionListFirst(m, "scopeType", "scope_type"); ok {
|
||
row["scopeType"] = v
|
||
}
|
||
if len(row) > 0 {
|
||
out = append(out, row)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// permissionListFindList locates the permission list payload, tolerating a bare
|
||
// top-level array or nesting one level under a common envelope key.
|
||
func permissionListFindList(data map[string]any) []any {
|
||
if data == nil {
|
||
return []any{}
|
||
}
|
||
for _, k := range []string{"list", "items", "permissions", "permissionList", "scopes", "result", "data"} {
|
||
v, ok := data[k]
|
||
if !ok {
|
||
continue
|
||
}
|
||
if arr, ok := v.([]any); ok {
|
||
return arr
|
||
}
|
||
if inner, ok := v.(map[string]any); ok {
|
||
for _, ik := range []string{"list", "items", "permissions", "permissionList", "scopes", "result", "data"} {
|
||
if arr, ok := inner[ik].([]any); ok {
|
||
return arr
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return []any{}
|
||
}
|
||
|
||
// permissionListFirst returns the first present candidate key's value.
|
||
func permissionListFirst(m map[string]any, keys ...string) (any, bool) {
|
||
for _, k := range keys {
|
||
if v, ok := m[k]; ok {
|
||
return v, true
|
||
}
|
||
}
|
||
return nil, false
|
||
}
|
||
|
||
// PermissionAdd maps helper `apply_dev_app_permissions`.
|
||
var PermissionAdd = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+permission-add",
|
||
Product: productDevApp,
|
||
Description: "申请开放平台应用权限点",
|
||
Intent: "当应用调用某接口报缺少权限、你要为它开通一批 API 权限点时使用;传入 unifiedAppId 和 scopeValue 列表,会实际为该应用申请/授予这些权限。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "scope-values", Type: shortcut.FlagStringSlice, Desc: "权限点 scopeValue 列表", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"scopeValues": rt.StrSlice("scope-values"),
|
||
}
|
||
return rt.CallMCP("apply_dev_app_permissions", params)
|
||
},
|
||
}
|
||
|
||
// PermissionRemove maps helper `remove_dev_app_permissions`.
|
||
var PermissionRemove = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+permission-remove",
|
||
Product: productDevApp,
|
||
Description: "取消开放平台应用权限点",
|
||
Intent: "当你要收回应用已开通的某些 API 权限(如安全收敛、下线不再使用的接口)时使用;传入 unifiedAppId 和待取消的 scopeValue 列表,会实际移除这些权限授权。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "scope-values", Type: shortcut.FlagStringSlice, Desc: "待取消权限点 scopeValue 列表", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"scopeValues": rt.StrSlice("scope-values"),
|
||
}
|
||
return rt.CallMCP("remove_dev_app_permissions", params)
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 成员
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// MemberList maps helper `list_dev_app_members`.
|
||
var MemberList = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+member-list",
|
||
Product: productDevApp,
|
||
Description: "查询开放平台应用成员",
|
||
Intent: "当你要查看某应用有哪些成员及其角色(如谁是开发者/管理员),用于核对协作人员或权限归属时使用;输入 unifiedAppId,返回成员列表。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_member_list",
|
||
CanonicalPath: "devapp.shortcut_member_list",
|
||
CLIPath: "devapp +member-list",
|
||
PrimaryCLIPath: "devapp +member-list",
|
||
},
|
||
Description: "查询开放平台应用成员",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询开放平台应用成员",
|
||
UseWhen: []string{"当你要查看某应用有哪些成员及其角色(如谁是开发者/管理员),用于核对协作人员或权限归属时使用;输入 unifiedAppId,返回成员列表。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +member-list --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("list_dev_app_members", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// MemberAdd maps helper `add_dev_app_members`.
|
||
var MemberAdd = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+member-add",
|
||
Product: productDevApp,
|
||
Description: "添加开放平台应用成员",
|
||
Intent: "当你要给某应用增加协作人员(如把某人加为开发者)时使用;传入 unifiedAppId、userId 列表和成员类型(如 DEVELOPER),会实际把这些人加入应用成员并赋予对应角色。",
|
||
Risk: shortcut.RiskWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_member_add",
|
||
CanonicalPath: "devapp.shortcut_member_add",
|
||
CLIPath: "devapp +member-add",
|
||
PrimaryCLIPath: "devapp +member-add",
|
||
},
|
||
Description: "添加开放平台应用成员",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "添加开放平台应用成员",
|
||
UseWhen: []string{"当你要给某应用增加协作人员(如把某人加为开发者)时使用;传入 unifiedAppId、userId 列表和成员类型(如 DEVELOPER),会实际把这些人加入应用成员并赋予对应角色。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +member-add --unified-app-id <UNIFIED_APP_ID> --user-ids <VALUES> --member-type <MEMBER_TYPE>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "user-ids", Type: shortcut.FlagStringSlice, Desc: "成员 userId 列表", Required: true},
|
||
{Name: "member-type", Type: shortcut.FlagString, Desc: "成员类型,如 DEVELOPER", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"userIds": rt.StrSlice("user-ids"),
|
||
"memberType": rt.Str("member-type"),
|
||
}
|
||
return rt.CallMCP("add_dev_app_members", params)
|
||
},
|
||
}
|
||
|
||
// MemberRemove maps helper `remove_dev_app_members`.
|
||
var MemberRemove = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+member-remove",
|
||
Product: productDevApp,
|
||
Description: "移除开放平台应用成员",
|
||
Intent: "当某人离职或不再参与、你要取消其对应用的访问/协作权限时使用;传入 unifiedAppId、userId 列表和成员类型,会实际把这些人从应用成员中移除。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "destructive", Risk: "high",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_member_remove",
|
||
CanonicalPath: "devapp.shortcut_member_remove",
|
||
CLIPath: "devapp +member-remove",
|
||
PrimaryCLIPath: "devapp +member-remove",
|
||
},
|
||
Description: "移除开放平台应用成员",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "移除开放平台应用成员",
|
||
UseWhen: []string{"当某人离职或不再参与、你要取消其对应用的访问/协作权限时使用;传入 unifiedAppId、userId 列表和成员类型,会实际把这些人从应用成员中移除。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +member-remove --unified-app-id <UNIFIED_APP_ID> --user-ids <VALUES> --member-type <MEMBER_TYPE>"},
|
||
},
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "user-ids", Type: shortcut.FlagStringSlice, Desc: "成员 userId 列表", Required: true},
|
||
{Name: "member-type", Type: shortcut.FlagString, Desc: "成员类型,如 DEVELOPER", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"userIds": rt.StrSlice("user-ids"),
|
||
"memberType": rt.Str("member-type"),
|
||
}
|
||
return rt.CallMCP("remove_dev_app_members", params)
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 安全配置
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// SecurityConfig maps helper `update_dev_app_security_config`.
|
||
var SecurityConfig = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+security-config",
|
||
Product: productDevApp,
|
||
Description: "更新开放平台应用安全配置(整组覆盖,非追加)",
|
||
Intent: "当你要设置应用的安全策略,如出口 IP 白名单、登录重定向 URL、端内免登地址时使用;注意每项传入的是整组值会全量覆盖旧配置(非追加),所以要一次性传全,常用于配置 OAuth 回调或加固网络白名单。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "ip-whitelist", Type: shortcut.FlagStringSlice, Desc: "出口 IP 白名单(整组覆盖)"},
|
||
{Name: "redirect-urls", Type: shortcut.FlagStringSlice, Desc: "登录重定向 URL(整组覆盖)"},
|
||
{Name: "sso-urls", Type: shortcut.FlagStringSlice, Desc: "端内免登地址(整组覆盖)"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("ip-whitelist") {
|
||
params["ipWhitelist"] = rt.StrSlice("ip-whitelist")
|
||
}
|
||
if rt.Changed("redirect-urls") {
|
||
params["redirectUrls"] = rt.StrSlice("redirect-urls")
|
||
}
|
||
if rt.Changed("sso-urls") {
|
||
params["ssoUrls"] = rt.StrSlice("sso-urls")
|
||
}
|
||
return rt.CallMCP("update_dev_app_security_config", params)
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 机器人能力
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// RobotGet maps helper `get_extension_robot_config`.
|
||
var RobotGet = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+robot-get",
|
||
Product: productDevApp,
|
||
Description: "查询现有应用的机器人配置",
|
||
Intent: "当你要查看某应用已有的机器人配置(名称、回调地址、模式 HTTPS/STREAM/AISKILL、技能等)以核对现状或作为改配置前的依据时使用;输入 unifiedAppId,返回当前机器人配置。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_robot_get",
|
||
CanonicalPath: "devapp.shortcut_robot_get",
|
||
CLIPath: "devapp +robot-get",
|
||
PrimaryCLIPath: "devapp +robot-get",
|
||
},
|
||
Description: "查询现有应用的机器人配置",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询现有应用的机器人配置",
|
||
UseWhen: []string{"当你要查看某应用已有的机器人配置(名称、回调地址、模式 HTTPS/STREAM/AISKILL、技能等)以核对现状或作为改配置前的依据时使用;输入 unifiedAppId,返回当前机器人配置。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +robot-get --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("get_extension_robot_config", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// RobotConfig maps helper `set_extension_robot_config` (upsert). The i18n
|
||
// JSON-object params are intentionally omitted (see package doc).
|
||
var RobotConfig = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+robot-config",
|
||
Product: productDevApp,
|
||
Description: "创建或更新现有应用的机器人配置(upsert)",
|
||
Intent: "当你要为应用开通机器人或调整其机器人设置(改名称/简介/图标、设消息与事件回调地址、切换 HTTPS/STREAM/AISKILL 模式、配技能、是否自动加权限或关 SSL 校验)时使用;按 unifiedAppId 以 upsert 方式写入机器人配置,会实际生效。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "name", Type: shortcut.FlagString, Desc: "机器人名称"},
|
||
{Name: "brief", Type: shortcut.FlagString, Desc: "机器人简介"},
|
||
{Name: "desc", Type: shortcut.FlagString, Desc: "机器人描述"},
|
||
{Name: "icon-media-id", Type: shortcut.FlagString, Desc: "机器人图标 mediaId"},
|
||
{Name: "outgoing-url", Type: shortcut.FlagString, Desc: "消息回调地址"},
|
||
{Name: "event-callback-url", Type: shortcut.FlagString, Desc: "事件回调地址"},
|
||
{Name: "mode", Type: shortcut.FlagString, Enum: []string{"HTTPS", "STREAM", "AISKILL"}, Desc: "机器人模式:HTTPS / STREAM / AISKILL"},
|
||
{Name: "skills", Type: shortcut.FlagStringSlice, Desc: "技能列表"},
|
||
{Name: "add-scope", Type: shortcut.FlagBool, Desc: "是否自动添加机器人相关权限"},
|
||
{Name: "disable-ssl-verify", Type: shortcut.FlagBool, Desc: "回调地址是否关闭 SSL 校验"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("name") {
|
||
params["name"] = rt.Str("name")
|
||
}
|
||
if rt.Changed("brief") {
|
||
params["brief"] = rt.Str("brief")
|
||
}
|
||
if rt.Changed("desc") {
|
||
params["desc"] = rt.Str("desc")
|
||
}
|
||
if rt.Changed("icon-media-id") {
|
||
params["iconMediaId"] = rt.Str("icon-media-id")
|
||
}
|
||
if rt.Changed("outgoing-url") {
|
||
params["outgoingUrl"] = rt.Str("outgoing-url")
|
||
}
|
||
if rt.Changed("event-callback-url") {
|
||
params["eventCallbackUrl"] = rt.Str("event-callback-url")
|
||
}
|
||
if rt.Changed("mode") {
|
||
params["mode"] = strings.ToUpper(rt.Str("mode"))
|
||
}
|
||
if rt.Changed("skills") {
|
||
params["skills"] = rt.StrSlice("skills")
|
||
}
|
||
if rt.Changed("add-scope") {
|
||
params["addScope"] = rt.Bool("add-scope")
|
||
}
|
||
if rt.Changed("disable-ssl-verify") {
|
||
params["disableSSLVerify"] = rt.Bool("disable-ssl-verify")
|
||
}
|
||
return rt.CallMCP("set_extension_robot_config", params)
|
||
},
|
||
}
|
||
|
||
// RobotEnable maps helper `enable_dev_app_robot`.
|
||
var RobotEnable = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+robot-enable",
|
||
Product: productDevApp,
|
||
Description: "启用现有应用机器人能力(纯启用,无需配置字段)",
|
||
Intent: "当应用的机器人能力已配置但处于关闭状态、你只想把它打开生效时使用;仅传 unifiedAppId 即可,会实际启用该应用的机器人能力,无需再传配置字段。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("enable_dev_app_robot", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// RobotDisable maps helper `disable_dev_app_robot`.
|
||
var RobotDisable = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+robot-disable",
|
||
Product: productDevApp,
|
||
Description: "停用现有应用的机器人能力",
|
||
Intent: "当你要临时关闭某应用的机器人(不再收发机器人消息)但保留其配置时使用;传入 unifiedAppId 会实际停用机器人能力,可日后再启用恢复。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
return rt.CallMCP("disable_dev_app_robot", map[string]any{"unifiedAppId": rt.Str("unified-app-id")})
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 事件订阅
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// EventList maps helper `list_dev_app_events`.
|
||
var EventList = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+event-list",
|
||
Product: productDevApp,
|
||
Description: "查询应用已订阅的事件列表",
|
||
Intent: "当你要确认某应用当前订阅了哪些事件回调(用于排查漏收事件、或退订前先查事件码)时使用;输入 unifiedAppId,可按事件码/名称关键词过滤并分页,返回已订阅事件列表。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_event_list",
|
||
CanonicalPath: "devapp.shortcut_event_list",
|
||
CLIPath: "devapp +event-list",
|
||
PrimaryCLIPath: "devapp +event-list",
|
||
},
|
||
Description: "查询应用已订阅的事件列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询应用已订阅的事件列表",
|
||
UseWhen: []string{"当你要确认某应用当前订阅了哪些事件回调(用于排查漏收事件、或退订前先查事件码)时使用;输入 unifiedAppId,可按事件码/名称关键词过滤并分页,返回已订阅事件列表。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +event-list --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppPaginatedProjectionResult("events", "当前页应用订阅事件查询结果"),
|
||
Pagination: devAppCursorPagination(),
|
||
},
|
||
Flags: append([]shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "keyword", Type: shortcut.FlagString, Desc: "事件搜索关键词,支持按事件码或事件名称模糊匹配"},
|
||
}, cursorFlags...),
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("keyword") {
|
||
params["keyword"] = rt.Str("keyword")
|
||
}
|
||
applyCursor(rt, params)
|
||
data, err := rt.CallMCPData(productDevApp, "list_dev_app_events", params)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
events := eventListProject(data)
|
||
return rt.Output(devAppListProjection(data, "events", events))
|
||
},
|
||
}
|
||
|
||
// eventListProject reshapes list_dev_app_events into a clean subscribed-event
|
||
// list ({eventCode, eventName, status, gmtModified}) — output-projection
|
||
// clean output projection. The list container and per-item field names are probed
|
||
// defensively across candidate keys, so an unknown/empty shape yields an empty
|
||
// list rather than a crash or fabricated data.
|
||
func eventListProject(data map[string]any) []map[string]any {
|
||
raw := eventListFindList(data)
|
||
out := make([]map[string]any, 0, len(raw))
|
||
for _, item := range raw {
|
||
m, ok := item.(map[string]any)
|
||
if !ok {
|
||
continue
|
||
}
|
||
row := map[string]any{}
|
||
if v, ok := eventListFirst(m, "eventCode", "event_code", "code"); ok {
|
||
row["eventCode"] = v
|
||
}
|
||
if v, ok := eventListFirst(m, "eventName", "event_name", "name"); ok {
|
||
row["eventName"] = v
|
||
}
|
||
if v, ok := eventListFirst(m, "status", "subscribeStatus", "subscribe_status"); ok {
|
||
row["status"] = v
|
||
}
|
||
if v, ok := eventListFirst(m, "gmtModified", "gmt_modified", "modifyTime", "modified_time"); ok {
|
||
row["gmtModified"] = v
|
||
}
|
||
if len(row) > 0 {
|
||
out = append(out, row)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// eventListFindList locates the event list payload, tolerating a bare top-level
|
||
// array or nesting one level under a common envelope key.
|
||
func eventListFindList(data map[string]any) []any {
|
||
if data == nil {
|
||
return []any{}
|
||
}
|
||
for _, k := range []string{"list", "items", "events", "eventList", "result", "data"} {
|
||
v, ok := data[k]
|
||
if !ok {
|
||
continue
|
||
}
|
||
if arr, ok := v.([]any); ok {
|
||
return arr
|
||
}
|
||
if inner, ok := v.(map[string]any); ok {
|
||
for _, ik := range []string{"list", "items", "events", "eventList", "result", "data"} {
|
||
if arr, ok := inner[ik].([]any); ok {
|
||
return arr
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return []any{}
|
||
}
|
||
|
||
// eventListFirst returns the first present candidate key's value.
|
||
func eventListFirst(m map[string]any, keys ...string) (any, bool) {
|
||
for _, k := range keys {
|
||
if v, ok := m[k]; ok {
|
||
return v, true
|
||
}
|
||
}
|
||
return nil, false
|
||
}
|
||
|
||
// EventSubscribe maps helper `subscribe_dev_app_events`.
|
||
var EventSubscribe = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+event-subscribe",
|
||
Product: productDevApp,
|
||
Description: "订阅应用事件回调",
|
||
Intent: "当你要让应用开始接收某些事件的回调推送(如通讯录变更、审批事件等)时使用;传入 unifiedAppId 和事件码列表,会实际为该应用登记这些事件订阅。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "event-codes", Type: shortcut.FlagStringSlice, Desc: "事件码列表", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"eventCodes": rt.StrSlice("event-codes"),
|
||
}
|
||
return rt.CallMCP("subscribe_dev_app_events", params)
|
||
},
|
||
}
|
||
|
||
// EventUnsubscribe maps helper `unsubscribe_dev_app_events`.
|
||
var EventUnsubscribe = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+event-unsubscribe",
|
||
Product: productDevApp,
|
||
Description: "取消订阅应用事件",
|
||
Intent: "当你不再需要某些事件的回调推送、要停止接收它们时使用;传入 unifiedAppId 和事件码列表,会实际取消这些事件的订阅。",
|
||
Risk: shortcut.RiskHighWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "event-codes", Type: shortcut.FlagStringSlice, Desc: "事件码列表", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"eventCodes": rt.StrSlice("event-codes"),
|
||
}
|
||
return rt.CallMCP("unsubscribe_dev_app_events", params)
|
||
},
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 版本发布
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// VersionCreate maps helper `create_dev_app_version`.
|
||
var VersionCreate = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-create",
|
||
Product: productDevApp,
|
||
Description: "基于当前配置创建应用新版本",
|
||
Intent: "当你改完应用配置、准备走发布流程前需要先打一个版本快照时使用;传入 unifiedAppId(可选显式版本号与描述,默认服务端自动递增),会实际创建一个新版本并返回 versionId 供后续预检和发布。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "version", Type: shortcut.FlagString, Desc: "高级可选:显式版本号,如 1.0.1;默认由服务端自动递增"},
|
||
{Name: "desc", Type: shortcut.FlagString, Desc: "版本描述"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
if rt.Changed("version") {
|
||
params["version"] = rt.Str("version")
|
||
}
|
||
if rt.Changed("desc") {
|
||
params["desc"] = rt.Str("desc")
|
||
}
|
||
return rt.CallMCP("create_dev_app_version", params)
|
||
},
|
||
}
|
||
|
||
// VersionList maps helper `list_dev_app_versions`.
|
||
var VersionList = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-list",
|
||
Product: productDevApp,
|
||
Description: "分页查询应用版本列表",
|
||
Intent: "当你要查看某应用的历史版本(找某个 versionId、看各版本发布状态或回顾迭代记录)时使用;输入 unifiedAppId 并分页,返回版本列表。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_version_list",
|
||
CanonicalPath: "devapp.shortcut_version_list",
|
||
CLIPath: "devapp +version-list",
|
||
PrimaryCLIPath: "devapp +version-list",
|
||
},
|
||
Description: "分页查询应用版本列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "分页查询应用版本列表",
|
||
UseWhen: []string{"当你要查看某应用的历史版本(找某个 versionId、看各版本发布状态或回顾迭代记录)时使用;输入 unifiedAppId 并分页,返回版本列表。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +version-list --unified-app-id <UNIFIED_APP_ID>"},
|
||
},
|
||
Result: devAppPaginatedProjectionResult("versions", "当前页开放平台应用版本查询结果"),
|
||
Pagination: devAppCursorPagination(),
|
||
},
|
||
Flags: append([]shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
}, cursorFlags...),
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{"unifiedAppId": rt.Str("unified-app-id")}
|
||
applyCursor(rt, params)
|
||
data, err := rt.CallMCPData(productDevApp, "list_dev_app_versions", params)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
versions := versionListProject(data)
|
||
return rt.Output(devAppListProjection(data, "versions", versions))
|
||
},
|
||
}
|
||
|
||
func devAppListProjection(data map[string]any, key string, items []map[string]any) map[string]any {
|
||
out := map[string]any{"count": len(items), key: items}
|
||
for _, candidate := range devAppPaginationCandidates(data) {
|
||
_, hasMore := candidate["hasMore"]
|
||
_, hasCursor := candidate["nextCursor"]
|
||
if !hasMore && !hasCursor {
|
||
continue
|
||
}
|
||
if hasMore {
|
||
out["hasMore"] = candidate["hasMore"]
|
||
}
|
||
if hasCursor {
|
||
out["nextCursor"] = candidate["nextCursor"]
|
||
}
|
||
break
|
||
}
|
||
return out
|
||
}
|
||
|
||
func devAppPaginationCandidates(data map[string]any) []map[string]any {
|
||
if data == nil {
|
||
return nil
|
||
}
|
||
candidates := []map[string]any{data}
|
||
for _, key := range []string{"content", "result", "data"} {
|
||
if nested, ok := data[key].(map[string]any); ok {
|
||
candidates = append(candidates, nested)
|
||
for _, innerKey := range []string{"result", "data"} {
|
||
if inner, ok := nested[innerKey].(map[string]any); ok {
|
||
candidates = append(candidates, inner)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return candidates
|
||
}
|
||
|
||
// versionListProject reshapes list_dev_app_versions into a clean version list
|
||
// ({versionId, version, status, desc, gmtCreate}) — output-projection fidelity
|
||
// for clean output. The list container and per-item field names are probed defensively
|
||
// across candidate keys, so an unknown/empty shape yields an empty list rather
|
||
// than a crash or fabricated data.
|
||
func versionListProject(data map[string]any) []map[string]any {
|
||
raw := versionListFindList(data)
|
||
out := make([]map[string]any, 0, len(raw))
|
||
for _, item := range raw {
|
||
m, ok := item.(map[string]any)
|
||
if !ok {
|
||
continue
|
||
}
|
||
row := map[string]any{}
|
||
if v, ok := versionListFirst(m, "versionId", "version_id", "id"); ok {
|
||
row["versionId"] = v
|
||
}
|
||
if v, ok := versionListFirst(m, "version", "versionName", "version_name"); ok {
|
||
row["version"] = v
|
||
}
|
||
if v, ok := versionListFirst(m, "status", "publishStatus", "publish_status", "versionStatus"); ok {
|
||
row["status"] = v
|
||
}
|
||
if v, ok := versionListFirst(m, "desc", "description", "remark"); ok {
|
||
row["desc"] = v
|
||
}
|
||
if v, ok := versionListFirst(m, "gmtCreate", "gmt_create", "createTime", "create_time"); ok {
|
||
row["gmtCreate"] = v
|
||
}
|
||
if len(row) > 0 {
|
||
out = append(out, row)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// versionListFindList locates the version list payload, tolerating a bare
|
||
// top-level array or nesting one level under a common envelope key.
|
||
func versionListFindList(data map[string]any) []any {
|
||
if data == nil {
|
||
return []any{}
|
||
}
|
||
for _, k := range []string{"list", "items", "versions", "versionList", "result", "data"} {
|
||
v, ok := data[k]
|
||
if !ok {
|
||
continue
|
||
}
|
||
if arr, ok := v.([]any); ok {
|
||
return arr
|
||
}
|
||
if inner, ok := v.(map[string]any); ok {
|
||
for _, ik := range []string{"list", "items", "versions", "versionList", "result", "data"} {
|
||
if arr, ok := inner[ik].([]any); ok {
|
||
return arr
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return []any{}
|
||
}
|
||
|
||
// versionListFirst returns the first present candidate key's value.
|
||
func versionListFirst(m map[string]any, keys ...string) (any, bool) {
|
||
for _, k := range keys {
|
||
if v, ok := m[k]; ok {
|
||
return v, true
|
||
}
|
||
}
|
||
return nil, false
|
||
}
|
||
|
||
// VersionGet maps helper `get_dev_app_version_detail`.
|
||
var VersionGet = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-get",
|
||
Product: productDevApp,
|
||
Description: "查询指定版本详情",
|
||
Intent: "当你已知某个 versionId、要查看该版本的具体内容(版本号、描述、包含的配置等)以核对发布内容时使用;输入 unifiedAppId 和 versionId,返回单个版本的详情。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_version_get",
|
||
CanonicalPath: "devapp.shortcut_version_get",
|
||
CLIPath: "devapp +version-get",
|
||
PrimaryCLIPath: "devapp +version-get",
|
||
},
|
||
Description: "查询指定版本详情",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询指定版本详情",
|
||
UseWhen: []string{"当你已知某个 versionId、要查看该版本的具体内容(版本号、描述、包含的配置等)以核对发布内容时使用;输入 unifiedAppId 和 versionId,返回单个版本的详情。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +version-get --unified-app-id <UNIFIED_APP_ID> --version-id <VERSION_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "version-id", Type: shortcut.FlagString, Desc: "版本 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"versionId": rt.Str("version-id"),
|
||
}
|
||
return rt.CallMCP("get_dev_app_version_detail", params)
|
||
},
|
||
}
|
||
|
||
// VersionCheckApproval maps helper `publish_dev_app_version` in precheck mode
|
||
// (precheckOnly=true): it only returns approval requirements, does not publish.
|
||
var VersionCheckApproval = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-check-approval",
|
||
Product: productDevApp,
|
||
Description: "预检版本发布是否需要审批(不实际发布)",
|
||
Intent: "当你在正式发布某版本前想先确认它是否会触发审批、是否含高敏权限等发布前置要求时使用;传入 unifiedAppId 和 versionId,仅做预检返回审批要求,不会真正发布。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_version_check_approval",
|
||
CanonicalPath: "devapp.shortcut_version_check_approval",
|
||
CLIPath: "devapp +version-check-approval",
|
||
PrimaryCLIPath: "devapp +version-check-approval",
|
||
},
|
||
Description: "预检版本发布是否需要审批(不实际发布)",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "预检版本发布是否需要审批(不实际发布)",
|
||
UseWhen: []string{"当你在正式发布某版本前想先确认它是否会触发审批、是否含高敏权限等发布前置要求时使用;传入 unifiedAppId 和 versionId,仅做预检返回审批要求,不会真正发布。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +version-check-approval --unified-app-id <UNIFIED_APP_ID> --version-id <VERSION_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomePending,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "version-id", Type: shortcut.FlagString, Desc: "版本 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"versionId": rt.Str("version-id"),
|
||
"precheckOnly": true,
|
||
}
|
||
return rt.CallMCP("publish_dev_app_version", params)
|
||
},
|
||
}
|
||
|
||
// VersionPublish maps helper `publish_dev_app_version` (real publish,
|
||
// precheckOnly=false).
|
||
var VersionPublish = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-publish",
|
||
Product: productDevApp,
|
||
Description: "发布指定版本(含高敏权限需 --confirmed-sensitive)",
|
||
Intent: "当你要把某个已创建的版本正式上线到线上环境时使用;传入 unifiedAppId 和 versionId,会实际触发发布(含高敏权限需加 --confirmed-sensitive 确认,灰度选人可指定审批人),可能进入审批流。",
|
||
Risk: shortcut.RiskWrite,
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "version-id", Type: shortcut.FlagString, Desc: "版本 ID", Required: true},
|
||
{Name: "confirmed-sensitive", Type: shortcut.FlagBool, Desc: "确认发布包含高敏权限的版本"},
|
||
{Name: "approver-user-id", Type: shortcut.FlagString, Desc: "灰度选人模式下指定审批人 userId"},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"versionId": rt.Str("version-id"),
|
||
"precheckOnly": false,
|
||
}
|
||
if rt.Changed("confirmed-sensitive") {
|
||
params["confirmedSensitive"] = rt.Bool("confirmed-sensitive")
|
||
}
|
||
if rt.Changed("approver-user-id") {
|
||
params["approverUserId"] = rt.Str("approver-user-id")
|
||
}
|
||
return rt.CallMCP("publish_dev_app_version", params)
|
||
},
|
||
}
|
||
|
||
// VersionStatus maps helper `get_dev_app_version_status`.
|
||
var VersionStatus = shortcut.Shortcut{
|
||
Service: "devapp",
|
||
Command: "+version-status",
|
||
Product: productDevApp,
|
||
Description: "查询版本发布/审批状态",
|
||
Intent: "当你已提交发布、想跟进某版本当前处于什么阶段(审批中、已发布、被驳回等)时使用;输入 unifiedAppId 和 versionId,返回该版本的发布/审批状态。",
|
||
Risk: shortcut.RiskRead,
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: corecmd.ContractDecl{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "devapp",
|
||
Name: "shortcut_version_status",
|
||
CanonicalPath: "devapp.shortcut_version_status",
|
||
CLIPath: "devapp +version-status",
|
||
PrimaryCLIPath: "devapp +version-status",
|
||
},
|
||
Description: "查询版本发布/审批状态",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询版本发布/审批状态",
|
||
UseWhen: []string{"当你已提交发布、想跟进某版本当前处于什么阶段(审批中、已发布、被驳回等)时使用;输入 unifiedAppId 和 versionId,返回该版本的发布/审批状态。"},
|
||
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
||
Examples: []string{"dws devapp +version-status --unified-app-id <UNIFIED_APP_ID> --version-id <VERSION_ID>"},
|
||
},
|
||
Result: devAppObjectResult(
|
||
contract.ResultOutcomeSuccess,
|
||
contract.ResultOutcomePending,
|
||
contract.ResultOutcomeFailure,
|
||
),
|
||
},
|
||
Flags: []shortcut.Flag{
|
||
{Name: "unified-app-id", Type: shortcut.FlagString, Desc: "开放平台统一应用 ID", Required: true},
|
||
{Name: "version-id", Type: shortcut.FlagString, Desc: "版本 ID", Required: true},
|
||
},
|
||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||
params := map[string]any{
|
||
"unifiedAppId": rt.Str("unified-app-id"),
|
||
"versionId": rt.Str("version-id"),
|
||
}
|
||
return rt.CallMCP("get_dev_app_version_status", params)
|
||
},
|
||
}
|
||
|
||
func init() {
|
||
shortcut.Register(
|
||
frameworkUnified(ListApp),
|
||
frameworkUnified(GetApp),
|
||
frameworkDualValidate(CreateApp),
|
||
frameworkDualValidate(UpdateApp),
|
||
frameworkDualValidate(DeleteApp),
|
||
frameworkDualValidate(EnableApp),
|
||
frameworkDualValidate(DisableApp),
|
||
// Credentials contain secrets and still lack a reviewed public result
|
||
// projection. Keep legacy bytes while shadow-validating until Safety,
|
||
// Schema identity, and sensitive-path redaction are declared together.
|
||
frameworkDualValidate(GetCredentials),
|
||
frameworkUnified(WebappGet),
|
||
frameworkDualValidate(WebappConfig),
|
||
frameworkUnified(PermissionList),
|
||
frameworkDualValidate(PermissionAdd),
|
||
frameworkDualValidate(PermissionRemove),
|
||
frameworkDualValidate(MemberList),
|
||
frameworkDualValidate(MemberAdd),
|
||
frameworkDualValidate(MemberRemove),
|
||
frameworkDualValidate(SecurityConfig),
|
||
frameworkUnified(RobotGet),
|
||
frameworkDualValidate(RobotConfig),
|
||
frameworkDualValidate(RobotEnable),
|
||
frameworkDualValidate(RobotDisable),
|
||
frameworkUnified(EventList),
|
||
frameworkDualValidate(EventSubscribe),
|
||
frameworkDualValidate(EventUnsubscribe),
|
||
frameworkDualValidate(VersionCreate),
|
||
frameworkUnified(VersionList),
|
||
frameworkUnified(VersionGet),
|
||
frameworkUnified(VersionCheckApproval),
|
||
frameworkDualValidate(VersionPublish),
|
||
frameworkUnified(VersionStatus),
|
||
)
|
||
}
|
||
|
||
func frameworkUnified(item shortcut.Shortcut) shortcut.Shortcut {
|
||
item.OutputRollout = output.RolloutUnifiedActive
|
||
return item
|
||
}
|
||
|
||
func frameworkDualValidate(item shortcut.Shortcut) shortcut.Shortcut {
|
||
item.OutputRollout = output.RolloutDualValidate
|
||
return item
|
||
}
|