feat(chat): add toolbar commands (list/add/hide/sort)

Add `dws chat toolbar` command group with shared helpers and 4 basic
subcommands for managing conversation shortcut bar visibility and order.

- toolbar_helpers.go: shared utilities (hasIntersection, isSystemBusy,
  parseExtension, toolbarConversationID, toolbarNewSystemBusyError)
- toolbar_helpers_test.go: unit tests for shared helpers
- toolbar.go: command group entry assembling 7 subcommands
- toolbar_list.go: list shortcut entries (read/low)
- toolbar_add.go: add entries to visible area (write/low)
- toolbar_hide.go: hide entries from visible area (write/low)
- toolbar_sort.go: sort entries with intersection validation and
  SYSTEM_BUSY error handling (write/low)
- chat.go: mount newChatToolbarCommand() to chat root
This commit is contained in:
长真 committed 2026-08-05 17:58:09 +08:00
1 parent a37e6e6847
commit 4dbfaa4cef
8 files changed
+601 -1

No files matched your search

+1 -1
View File
@@ -8164,7 +8164,7 @@ pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH, he_IL, nl_NL, lo_LA, it_IT`,
chatCategoryCmd.AddCommand(chatCategoryCreateSmartCmd)
chatMessageCmd.AddCommand(chatMessageListDirectCmd, chatMessageSearchCommonCmd, chatMessageCombineForwardCmd, chatMessageForwardTopicCmd, chatMessageSetPinCmd, chatMessageUnsetPinCmd, chatMessageListPinCmd, chatMessageAddFavoriteCmd, chatMessageRemoveFavoriteCmd, chatMessageListFavoritesCmd, chatMessageSetTopMsgCmd, chatMessageUnsetTopMsgCmd, chatMessageListEmotionRepliesCmd)
root.AddCommand(chatChmodCmd, chatDataAuthCmd, chatGroupCmd, chatSearchCmd, chatSearchCommonCmd, chatMessageCmd, chatFileCmd, newChatMediaGroup(), chatBotCmd, chatMessageListTopConversationsCmd, chatConversationInfoCmd, chatCategoryCmd, chatGroupRoleCmd, chatMuteCmd, chatSetTopCmd, chatGroupMuteCmd, chatGroupMuteMemberCmd, chatHideCmd, chatMuteAtAllCmd, chatMuteRedEnvelopeCmd, chatMarkUnreadCmd, chatClearRedPointCmd, chatClearAllRedPointCmd, chatListAllConversationsCmd, chatClearMessagesCmd, chatMarkReadCmd, chatTextCmd)
root.AddCommand(chatChmodCmd, chatDataAuthCmd, chatGroupCmd, chatSearchCmd, chatSearchCommonCmd, chatMessageCmd, chatFileCmd, newChatMediaGroup(), chatBotCmd, chatMessageListTopConversationsCmd, chatConversationInfoCmd, chatCategoryCmd, chatGroupRoleCmd, chatMuteCmd, chatSetTopCmd, chatGroupMuteCmd, chatGroupMuteMemberCmd, chatHideCmd, chatMuteAtAllCmd, chatMuteRedEnvelopeCmd, chatMarkUnreadCmd, chatClearRedPointCmd, chatClearAllRedPointCmd, chatListAllConversationsCmd, chatClearMessagesCmd, chatMarkReadCmd, chatTextCmd, newChatToolbarCommand())
return root
}
+38
View File
@@ -0,0 +1,38 @@
// 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 "github.com/spf13/cobra"
func newChatToolbarCommand() *cobra.Command {
toolbarCmd := &cobra.Command{
Use: "toolbar",
Short: "快捷栏管理",
Long: "管理会话快捷栏入口:查询、添加、隐藏、排序及自定义入口 CRUD。",
RunE: groupRunE,
}
toolbarCmd.DisableAutoGenTag = true
toolbarCmd.AddCommand(
newToolbarListCommand(),
newToolbarAddCommand(),
newToolbarHideCommand(),
newToolbarSortCommand(),
newToolbarCreateCustomCommand(),
newToolbarRemoveCustomCommand(),
newToolbarUpdateCustomCommand(),
)
return toolbarCmd
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"fmt"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
func newToolbarAddCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "将入口添加到快捷栏可见区",
Long: "将一个或多个快捷入口从隐藏区移至可见区。入口 ID 使用逗号分隔。",
Example: ` dws chat toolbar add --conversation-id <cid> --shortcut-ids 101,102
# 查询入口 ID: dws chat toolbar list --conversation-id <cid>`,
RunE: func(cmd *cobra.Command, args []string) error {
cid, err := toolbarConversationID(cmd)
if err != nil {
return err
}
if err := validateRequiredFlags(cmd, "shortcut-ids"); err != nil {
return err
}
ids, err := parseCSVInt64(mustGetFlag(cmd, "shortcut-ids"))
if err != nil {
return fmt.Errorf("--shortcut-ids: %w", err)
}
return callMCPToolOnServer("im", "add_chat_toolbar_shortcuts", map[string]any{
"openCid": cid,
"shortcutIds": ids,
})
},
}
cmd.Flags().String("conversation-id", "", "会话 openConversationId")
cmd.Flags().String("shortcut-ids", "", "入口 ID 列表(逗号分隔)")
_ = cmd.MarkFlagRequired("conversation-id")
_ = cmd.MarkFlagRequired("shortcut-ids")
cmd.DisableAutoGenTag = true
DeclareLeafMetadata(cmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "chat",
Name: "add_chat_toolbar_shortcuts",
CanonicalPath: "chat.add_chat_toolbar_shortcuts",
CLIPath: "chat toolbar add",
PrimaryCLIPath: "chat toolbar add",
},
Description: "将快捷入口添加到会话快捷栏可见区",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "im", RPCName: "add_chat_toolbar_shortcuts"},
},
Selection: contract.SelectionSpec{
AgentSummary: "将快捷入口添加到会话快捷栏可见区",
UseWhen: []string{"需要将隐藏的快捷入口重新显示在快捷栏中"},
AvoidWhen: []string{"创建新的自定义入口应使用 chat toolbar create-custom"},
Examples: []string{"dws chat toolbar add --conversation-id <cid> --shortcut-ids 101,102"},
},
Parameters: []contract.ParamDecl{
{Name: "conversation-id", Property: "openCid", Required: boolPtr(true)},
{Name: "shortcut-ids", Property: "shortcutIds", Required: boolPtr(true)},
},
},
})
return cmd
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"fmt"
"strings"
"github.com/spf13/cobra"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
)
func hasIntersection(a, b []int64) bool {
if len(a) == 0 || len(b) == 0 {
return false
}
seen := make(map[int64]struct{}, len(a))
for _, v := range a {
seen[v] = struct{}{}
}
for _, v := range b {
if _, ok := seen[v]; ok {
return true
}
}
return false
}
func isSystemBusy(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), "SYSTEM_BUSY")
}
func parseExtension(cmd *cobra.Command) (map[string]string, error) {
raw, _ := cmd.Flags().GetStringArray("extension")
if len(raw) == 0 {
return nil, nil
}
result := make(map[string]string, len(raw))
for _, pair := range raw {
idx := strings.Index(pair, "=")
if idx <= 0 {
return nil, fmt.Errorf("--extension 格式错误,期望 key=value,实际 %q", pair)
}
key := strings.TrimSpace(pair[:idx])
value := strings.TrimSpace(pair[idx+1:])
if key == "" {
return nil, fmt.Errorf("--extension key 不能为空: %q", pair)
}
result[key] = value
}
return result, nil
}
func toolbarConversationID(cmd *cobra.Command) (string, error) {
cid, _ := cmd.Flags().GetString("conversation-id")
cid = strings.TrimSpace(cid)
if cid == "" {
return "", apperrors.NewValidation(
"flag --conversation-id is required",
apperrors.WithReason("missing_required_flag"),
apperrors.WithHint("使用 --conversation-id 指定会话 openConversationId"),
apperrors.WithActions("通过 dws chat search 或 dws chat group list 获取会话 ID"),
)
}
return cid, nil
}
func toolbarNewSystemBusyError() error {
return apperrors.NewValidation(
"服务端繁忙(SYSTEM_BUSY),请稍后重试",
apperrors.WithReason("system_busy"),
apperrors.WithHint("服务端当前负载较高,建议等待几秒后重试"),
apperrors.WithActions("等待后使用相同参数重试"),
)
}
+106
View File
@@ -0,0 +1,106 @@
// 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 (
"errors"
"testing"
"github.com/spf13/cobra"
)
func TestHasIntersection(t *testing.T) {
tests := []struct {
name string
a, b []int64
want bool
}{
{"both empty", nil, nil, false},
{"a empty", nil, []int64{1}, false},
{"b empty", []int64{1}, nil, false},
{"no overlap", []int64{1, 2, 3}, []int64{4, 5, 6}, false},
{"has overlap", []int64{1, 2, 3}, []int64{3, 4, 5}, true},
{"single match", []int64{1}, []int64{1}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hasIntersection(tt.a, tt.b)
if got != tt.want {
t.Errorf("hasIntersection(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestIsSystemBusy(t *testing.T) {
if isSystemBusy(nil) {
t.Error("isSystemBusy(nil) should be false")
}
if !isSystemBusy(errors.New("error code: SYSTEM_BUSY")) {
t.Error("isSystemBusy should detect SYSTEM_BUSY in error message")
}
if isSystemBusy(errors.New("some other error")) {
t.Error("isSystemBusy should return false for non-SYSTEM_BUSY errors")
}
}
func TestParseExtension(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
cmd.Flags().StringArray("extension", nil, "")
ext, err := parseExtension(cmd)
if err != nil {
t.Fatalf("empty extension should not error: %v", err)
}
if ext != nil {
t.Fatalf("empty extension should return nil, got %v", ext)
}
_ = cmd.Flags().Set("extension", "key1=val1")
_ = cmd.Flags().Set("extension", "key2=val2")
ext, err = parseExtension(cmd)
if err != nil {
t.Fatalf("valid extension should not error: %v", err)
}
if ext["key1"] != "val1" || ext["key2"] != "val2" {
t.Fatalf("unexpected extension map: %v", ext)
}
cmd2 := &cobra.Command{Use: "test2"}
cmd2.Flags().StringArray("extension", nil, "")
_ = cmd2.Flags().Set("extension", "badformat")
_, err = parseExtension(cmd2)
if err == nil {
t.Fatal("invalid extension format should error")
}
}
func TestToolbarConversationID(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
cmd.Flags().String("conversation-id", "", "")
_, err := toolbarConversationID(cmd)
if err == nil {
t.Fatal("missing conversation-id should error")
}
_ = cmd.Flags().Set("conversation-id", "cid123")
cid, err := toolbarConversationID(cmd)
if err != nil {
t.Fatalf("valid conversation-id should not error: %v", err)
}
if cid != "cid123" {
t.Fatalf("expected cid123, got %s", cid)
}
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"fmt"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
func newToolbarHideCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "hide",
Short: "将入口从快捷栏可见区隐藏",
Long: "将一个或多个快捷入口从可见区移至隐藏区。入口 ID 使用逗号分隔。",
Example: ` dws chat toolbar hide --conversation-id <cid> --shortcut-ids 101,102
# 查询入口 ID: dws chat toolbar list --conversation-id <cid>`,
RunE: func(cmd *cobra.Command, args []string) error {
cid, err := toolbarConversationID(cmd)
if err != nil {
return err
}
if err := validateRequiredFlags(cmd, "shortcut-ids"); err != nil {
return err
}
ids, err := parseCSVInt64(mustGetFlag(cmd, "shortcut-ids"))
if err != nil {
return fmt.Errorf("--shortcut-ids: %w", err)
}
return callMCPToolOnServer("im", "hide_chat_toolbar_shortcuts", map[string]any{
"openCid": cid,
"shortcutIds": ids,
})
},
}
cmd.Flags().String("conversation-id", "", "会话 openConversationId")
cmd.Flags().String("shortcut-ids", "", "入口 ID 列表(逗号分隔)")
_ = cmd.MarkFlagRequired("conversation-id")
_ = cmd.MarkFlagRequired("shortcut-ids")
cmd.DisableAutoGenTag = true
DeclareLeafMetadata(cmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "chat",
Name: "hide_chat_toolbar_shortcuts",
CanonicalPath: "chat.hide_chat_toolbar_shortcuts",
CLIPath: "chat toolbar hide",
PrimaryCLIPath: "chat toolbar hide",
},
Description: "将快捷入口从会话快捷栏可见区移至隐藏区",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "im", RPCName: "hide_chat_toolbar_shortcuts"},
},
Selection: contract.SelectionSpec{
AgentSummary: "将快捷入口从会话快捷栏可见区隐藏",
UseWhen: []string{"需要将快捷栏中的入口隐藏到隐藏区"},
AvoidWhen: []string{"永久删除自定义入口应使用 chat toolbar remove-custom"},
Examples: []string{"dws chat toolbar hide --conversation-id <cid> --shortcut-ids 101,102"},
},
Parameters: []contract.ParamDecl{
{Name: "conversation-id", Property: "openCid", Required: boolPtr(true)},
{Name: "shortcut-ids", Property: "shortcutIds", Required: boolPtr(true)},
},
},
})
return cmd
}
+75
View File
@@ -0,0 +1,75 @@
// 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 (
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
func newToolbarListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "查询会话快捷栏入口列表",
Long: "查询指定会话的快捷栏入口列表,包括可见区和隐藏区的所有入口。",
Example: ` dws chat toolbar list --conversation-id <cid>
# 查询群 ID: dws chat search --query "群名"`,
RunE: func(cmd *cobra.Command, args []string) error {
cid, err := toolbarConversationID(cmd)
if err != nil {
return err
}
return callMCPToolOnServer("im", "list_chat_toolbar_shortcuts", map[string]any{
"openCid": cid,
})
},
}
cmd.Flags().String("conversation-id", "", "会话 openConversationId")
_ = cmd.MarkFlagRequired("conversation-id")
cmd.DisableAutoGenTag = true
DeclareLeafMetadata(cmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "chat",
Name: "list_chat_toolbar_shortcuts",
CanonicalPath: "chat.list_chat_toolbar_shortcuts",
CLIPath: "chat toolbar list",
PrimaryCLIPath: "chat toolbar list",
},
Description: "查询会话快捷栏入口列表",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "im", RPCName: "list_chat_toolbar_shortcuts"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询会话快捷栏入口列表",
UseWhen: []string{"需要查看会话快捷栏中有哪些入口、获取入口 ID 时"},
AvoidWhen: []string{"修改快捷栏排序使用 chat toolbar sort;创建自定义入口使用 chat toolbar create-custom"},
Examples: []string{"dws chat toolbar list --conversation-id <cid>"},
},
Parameters: []contract.ParamDecl{
{Name: "conversation-id", Property: "openCid", Required: boolPtr(true)},
},
},
})
return cmd
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"fmt"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
)
func newToolbarSortCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "sort",
Short: "排序快捷栏入口",
Long: "对会话快捷栏入口进行排序。sorted-ids 指定排序后的可见区入口 ID 列表,unsorted-ids 指定不参与排序放在末尾的入口 ID 列表。两个列表不能有交集。",
Example: ` dws chat toolbar sort --conversation-id <cid> --sorted-ids 101,102,103
dws chat toolbar sort --conversation-id <cid> --sorted-ids 101,102 --unsorted-ids 103,104
# 查询入口 ID: dws chat toolbar list --conversation-id <cid>`,
RunE: func(cmd *cobra.Command, args []string) error {
cid, err := toolbarConversationID(cmd)
if err != nil {
return err
}
if err := validateRequiredFlags(cmd, "sorted-ids"); err != nil {
return err
}
sortedIds, err := parseCSVInt64(mustGetFlag(cmd, "sorted-ids"))
if err != nil {
return fmt.Errorf("--sorted-ids: %w", err)
}
toolArgs := map[string]any{
"openCid": cid,
"sortedShortcutIds": sortedIds,
}
if unsortedRaw, _ := cmd.Flags().GetString("unsorted-ids"); unsortedRaw != "" {
unsortedIds, err := parseCSVInt64(unsortedRaw)
if err != nil {
return fmt.Errorf("--unsorted-ids: %w", err)
}
if hasIntersection(sortedIds, unsortedIds) {
return apperrors.NewValidation(
"--sorted-ids 与 --unsorted-ids 不能有交集",
apperrors.WithReason("id_intersection"),
apperrors.WithHint("检查两个 ID 列表,确保同一个 ID 不同时出现在两个列表中"),
apperrors.WithActions("修正 ID 列表后重试"),
)
}
toolArgs["unsortedShortcutIds"] = unsortedIds
}
err = callMCPToolOnServer("im", "sort_chat_toolbar_shortcuts", toolArgs)
if isSystemBusy(err) {
return toolbarNewSystemBusyError()
}
return err
},
}
cmd.Flags().String("conversation-id", "", "会话 openConversationId")
cmd.Flags().String("sorted-ids", "", "排序后的入口 ID 列表(逗号分隔)")
cmd.Flags().String("unsorted-ids", "", "不参与排序放在末尾的入口 ID 列表(逗号分隔)")
_ = cmd.MarkFlagRequired("conversation-id")
_ = cmd.MarkFlagRequired("sorted-ids")
cmd.DisableAutoGenTag = true
DeclareLeafMetadata(cmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "chat",
Name: "sort_chat_toolbar_shortcuts",
CanonicalPath: "chat.sort_chat_toolbar_shortcuts",
CLIPath: "chat toolbar sort",
PrimaryCLIPath: "chat toolbar sort",
},
Description: "对会话快捷栏入口进行排序",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "im", RPCName: "sort_chat_toolbar_shortcuts"},
},
Selection: contract.SelectionSpec{
AgentSummary: "对会话快捷栏入口进行排序",
UseWhen: []string{"需要调整快捷栏入口的显示顺序"},
AvoidWhen: []string{"仅添加或隐藏入口使用 chat toolbar add / hide"},
Examples: []string{"dws chat toolbar sort --conversation-id <cid> --sorted-ids 101,102,103"},
},
Parameters: []contract.ParamDecl{
{Name: "conversation-id", Property: "openCid", Required: boolPtr(true)},
{Name: "sorted-ids", Property: "sortedShortcutIds", Required: boolPtr(true)},
{Name: "unsorted-ids", Property: "unsortedShortcutIds", Required: boolPtr(false)},
},
},
})
return cmd
}