Compare commits

..
Author SHA1 Message Date
修雨 fdb461155f Merge branch 'main' into bugfix/shortcut-fixes 2026-07-23 00:08:11 +08:00
修雨 7e38f9fbba test(chat): cover missing download message id 2026-07-22 23:57:16 +08:00
修雨 067b3e6e51 test(chat): cover download media aliases 2026-07-22 23:45:37 +08:00
修雨 bcac84987f fix(chat): preserve message and schema contracts 2026-07-22 23:34:35 +08:00
DennisandClaude Opus 4.8 8dd87108f1 fix(chat): make message-read shortcuts render cards, forwards & ciphertext
The message-list shortcuts projected raw message content, so several message
kinds came out unreadable:

- speaker showed as null (real key is the bare "sender"; forwardMessages carry
  the literal string "null")
- out-of-office auto-replies / cards dumped raw rich-content JSON
- forwarded chat records ("聊天记录") collapsed to a lossy "[卡片]" summary,
  dropping their nested forwardMessages
- encrypted card/robot messages dumped raw base64 ciphertext (dws holds no key)

Extract the projection logic into a shared internal/shortcut/chatmsg package and
fix all five projecting read commands to reuse it (+chat-messages,
+messages-list, +messages-list-direct, +at-me, +search-msg, +thread-replies),
each keeping its own output fields:

- Sender: probe the bare "sender" key first, treat "null"/"" as absent
- CleanText: extract readable text from rich-content JSON, mark ciphertext as
  [加密消息,无法解码] instead of leaking base64
- Forwarded: recursively expand forwardMessages so chat records are readable
- RecoverEncryptedContents: best-effort re-query of search_messages_by_sender
  (which returns server-rendered plaintext) matched back by openMessageId

Also add --msg-id / --open-message-id aliases to `chat message download-media`
so agents that copy the openMessageId/msgId field don't hit "unknown flag".

Verified live: +messages-list-direct expands a 44-message forwarded record;
+messages-list / +at-me render clean text; download-media accepts --msg-id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 20:46:45 +08:00
32 changed files with 877 additions and 1983 deletions
-15
View File
@@ -6,21 +6,6 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
## [1.0.55-beta.1] - 2026-07-23
This beta validates MCP Market URL resolution, the supported Wukong local-file
send path after retiring the legacy credential-based media upload command from
discovery, and reliable message-read rendering for rich content, forwarded
records, encrypted messages, and media-download ID aliases.
### Added
- **MCP URL resolution** — adds `dws mcp url get <mcpId>` for resolving a DingTalk MCP Market ID to the current user and organization scoped Streamable HTTP URL, while keeping the helper-only `mcp-meta` endpoint out of the public product command surface.
### Changed
- **Chat local-file sending** — hides the open-source-only `chat media upload` compatibility command from Help, Schema, and bundled Skills, and removes its legacy AppKey/AppSecret OAPI path. Historical argv still receives an actionable migration error. Send local images and files through `chat message send --msg-type file --file-path`; callers that already hold a mediaId may continue to use `--msg-type image --media-id`.
### Fixed
- **Message-read shortcut projection** (#706) — the message-list shortcuts (`chat +chat-messages` / `+messages-list` / `+messages-list-direct` / `+at-me` / `+search-msg` / `+thread-replies`) now render card and out-of-office rich-content JSON as readable text (without ever rewriting ordinary text that merely embeds a JSON fragment), expand a forwarded chat record's nested `forwardMessages` instead of collapsing to a "[卡片]" summary, and mark undecryptable encrypted card messages as `[加密消息]`; the speaker is read from the bare `sender` key, nested `{name:…}` sender objects yield their display name, and the literal string `"null"` is treated as absent. Shared projection helpers now live in `internal/shortcut/chatmsg`. `chat message download-media` also gains `--msg-id` / `--open-message-id` aliases for its `--message-id` flag so agents copying the `openMessageId`/`msgId` output field no longer hit "unknown flag".
-108
View File
@@ -1,108 +0,0 @@
// 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 app
import (
"encoding/json"
"fmt"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
const (
mcpMetaServerID = "mcp-meta"
mcpMetaURLTool = "get_mcp_server_url"
)
func newMCPURLGroup(caller edition.ToolCaller) *cobra.Command {
group := &cobra.Command{
Use: "url",
Short: "管理 MCP 服务连接地址",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, _ []string) error {
return cmd.Help()
},
}
group.AddCommand(newMCPURLGetCommand(caller))
return group
}
func newMCPURLGetCommand(caller edition.ToolCaller) *cobra.Command {
cmd := &cobra.Command{
Use: "get <mcpId>",
Short: "按 mcpId 获取 MCP 的 Streamable HTTP 服务地址",
Long: "输入 MCP 市场 mcpId,返回以当前用户和组织身份访问该 MCP 的 " +
"Streamable HTTP 服务地址。\n\n" +
"安全提示:返回的 mcpURL 和 mcpJSON 可能包含身份凭据,仅限个人使用," +
"请勿分享到群聊、文档、邮件、代码仓库或日志。",
Example: " dws mcp url get 2480\n" +
" dws mcp url get 2480 --format json",
Args: cobra.ExactArgs(1),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if caller == nil {
return fmt.Errorf("MCP tool caller is not configured")
}
mcpID := strings.TrimSpace(args[0])
if mcpID == "" {
return fmt.Errorf("mcpId 不能为空")
}
result, err := caller.CallTool(cmd.Context(), mcpMetaServerID, mcpMetaURLTool, map[string]any{
"mcpId": mcpID,
})
if err != nil {
return fmt.Errorf("获取 MCP 服务地址: %w", err)
}
return writeMCPURLResult(cmd, result)
},
}
cli.AnnotateRuntimePositionals(cmd, cli.RuntimeSchemaPositional{
Name: "mcp_id",
Type: "string",
Description: "钉钉 MCP 市场中的 mcpId",
Required: true,
Index: 0,
})
return cmd
}
func writeMCPURLResult(cmd *cobra.Command, result *edition.ToolResult) error {
if result == nil {
return fmt.Errorf("MCP 元服务返回空结果")
}
// get_mcp_server_url returns one JSON document in its first non-empty text
// block. Other block types and trailing blocks are intentionally ignored.
for _, block := range result.Content {
if block.Type != "text" || strings.TrimSpace(block.Text) == "" {
continue
}
if err := apperrors.ClassifyMCPResponseText(block.Text); err != nil {
return err
}
var payload any
if err := json.Unmarshal([]byte(block.Text), &payload); err != nil {
return fmt.Errorf("MCP 元服务返回了无效 JSON: %w", err)
}
return output.WriteCommandPayload(cmd, payload, output.FormatJSON)
}
return fmt.Errorf("MCP 元服务返回空结果")
}
-194
View File
@@ -1,194 +0,0 @@
// 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 app
import (
"bytes"
"context"
"encoding/json"
"errors"
"strings"
"testing"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
type mcpURLTestCaller struct {
productID string
toolName string
args map[string]any
result *edition.ToolResult
err error
}
func (c *mcpURLTestCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) {
c.productID = productID
c.toolName = toolName
c.args = args
return c.result, c.err
}
func (*mcpURLTestCaller) Format() string { return "json" }
func (*mcpURLTestCaller) DryRun() bool { return false }
func (*mcpURLTestCaller) Fields() string { return "" }
func (*mcpURLTestCaller) JQ() string { return "" }
func executeMCPURLCommand(t *testing.T, caller edition.ToolCaller, args ...string) (string, error) {
t.Helper()
root := &cobra.Command{Use: "mcp", SilenceErrors: true, SilenceUsage: true}
root.AddCommand(newMCPURLGroup(caller))
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs(args)
err := root.ExecuteContext(t.Context())
return out.String(), err
}
func TestMCPURLGetCallsMetaServerAndPreservesResponse(t *testing.T) {
const response = `{"result":{"mcpURL":"https://example.test/mcp?key=one&token=two","mcpJSON":{"transport":"streamable-http"},"name":"Example"}}`
caller := &mcpURLTestCaller{
result: &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: response}}},
}
out, err := executeMCPURLCommand(t, caller, "url", "get", " 10043 ")
if err != nil {
t.Fatalf("execute mcp url get: %v", err)
}
if caller.productID != mcpMetaServerID {
t.Fatalf("productID = %q, want %q", caller.productID, mcpMetaServerID)
}
if caller.toolName != mcpMetaURLTool {
t.Fatalf("toolName = %q, want %q", caller.toolName, mcpMetaURLTool)
}
if got := caller.args["mcpId"]; got != "10043" {
t.Fatalf("mcpId = %#v, want %q", got, "10043")
}
var payload map[string]any
if err := json.Unmarshal([]byte(out), &payload); err != nil {
t.Fatalf("output is not JSON: %v\n%s", err, out)
}
result, ok := payload["result"].(map[string]any)
if !ok {
t.Fatalf("output result = %#v", payload["result"])
}
if got := result["mcpURL"]; got != "https://example.test/mcp?key=one&token=two" {
t.Fatalf("result.mcpURL = %#v", got)
}
}
func TestMCPURLGetRejectsBlankID(t *testing.T) {
_, err := executeMCPURLCommand(t, &mcpURLTestCaller{}, "url", "get", " ")
if err == nil || !strings.Contains(err.Error(), "mcpId 不能为空") {
t.Fatalf("error = %v, want blank mcpId error", err)
}
}
func TestMCPURLGroupShowsHelp(t *testing.T) {
out, err := executeMCPURLCommand(t, nil, "url")
if err != nil {
t.Fatalf("execute mcp url: %v", err)
}
if !strings.Contains(out, "get") {
t.Fatalf("help output does not list get command:\n%s", out)
}
}
func TestMCPURLGetRejectsMissingCaller(t *testing.T) {
_, err := executeMCPURLCommand(t, nil, "url", "get", "10043")
if err == nil || !strings.Contains(err.Error(), "caller is not configured") {
t.Fatalf("error = %v, want missing caller error", err)
}
}
func TestMCPURLGetPropagatesCallError(t *testing.T) {
caller := &mcpURLTestCaller{err: errors.New("permission denied")}
_, err := executeMCPURLCommand(t, caller, "url", "get", "10043")
if err == nil || !strings.Contains(err.Error(), "permission denied") {
t.Fatalf("error = %v, want call error", err)
}
}
func TestMCPURLGetRejectsInvalidJSON(t *testing.T) {
caller := &mcpURLTestCaller{
result: &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: "not-json"}}},
}
_, err := executeMCPURLCommand(t, caller, "url", "get", "10043")
if err == nil || !strings.Contains(err.Error(), "无效 JSON") {
t.Fatalf("error = %v, want invalid JSON error", err)
}
}
func TestMCPURLGetRejectsEmptyResults(t *testing.T) {
tests := []struct {
name string
result *edition.ToolResult
}{
{name: "nil result"},
{
name: "no usable text content",
result: &edition.ToolResult{Content: []edition.ContentBlock{
{Type: "image", Text: "ignored"},
{Type: "text", Text: " "},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &mcpURLTestCaller{result: tt.result}
_, err := executeMCPURLCommand(t, caller, "url", "get", "10043")
if err == nil || !strings.Contains(err.Error(), "返回空结果") {
t.Fatalf("error = %v, want empty result error", err)
}
})
}
}
func TestMCPURLGetClassifiesBusinessError(t *testing.T) {
caller := &mcpURLTestCaller{
result: &edition.ToolResult{Content: []edition.ContentBlock{{
Type: "text",
Text: `{"success":false,"errorMsg":"搜索内容不能为空"}`,
}}},
}
_, err := executeMCPURLCommand(t, caller, "url", "get", "10043")
if err == nil {
t.Fatal("expected classified business error")
}
var typed *apperrors.Error
if !errors.As(err, &typed) || typed.Reason != "business_error" {
t.Fatalf("error = %#v, want classified business error", err)
}
}
func TestRootRegistersMCPURLGet(t *testing.T) {
root := NewRootCommand(t.Context())
mcp, _, err := root.Find([]string{"mcp"})
if err != nil {
t.Fatalf("find mcp: %v", err)
}
if mcp.Hidden {
t.Fatal("mcp command must be public when it contains reviewed public helpers")
}
cmd, _, err := root.Find([]string{"mcp", "url", "get"})
if err != nil {
t.Fatalf("find mcp url get: %v", err)
}
if got := cmd.CommandPath(); got != "dws mcp url get" {
t.Fatalf("command path = %q, want %q", got, "dws mcp url get")
}
}
+1 -6
View File
@@ -385,16 +385,11 @@ func newRootCommandWithEngine(rootCtx context.Context, engine *pipeline.Engine,
schemaCmd := newSchemaCommand(loader)
mcpCmd := newMCPCommand(rootCtx, loader, runner, engine)
// The legacy dynamic MCP surface remains disabled, but reviewed static MCP
// helpers registered below are part of the public CLI and Schema surface.
mcpCmd.Hidden = false
mcpCmd.Short = "管理 MCP 服务连接信息"
mcpCmd.Long = "管理经过审核并纳入 Schema 的 MCP 服务连接辅助能力。"
mcpCmd.Hidden = true
// Wrap the caller so every MCP tool call's shape is recorded to the local
// usage log (privacy-preserving; see internal/shortcut/usage). Powers
// `dws shortcut stats` and future high-frequency shortcut distillation.
patCaller := newRecordingToolCaller(newToolCallerAdapter(runner, flags))
mcpCmd.AddCommand(newMCPURLGroup(patCaller))
utilityCommands := []*cobra.Command{
newAuthCommand(patCaller),
+1 -118
View File
@@ -15,14 +15,11 @@ package app
import (
"bytes"
stderrors "errors"
"io"
"os"
"path/filepath"
"strings"
"testing"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
@@ -78,14 +75,7 @@ func TestRootKeepsMainBranchChatCompatibilityCommands(t *testing.T) {
}
}
mediaGroup := mustFindCommand(t, root, "chat", "media")
if mediaGroup.Deprecated == "" || mediaGroup.Hidden || !mediaGroup.Runnable() {
t.Fatalf("chat media compatibility contract: deprecated=%q hidden=%v runnable=%v", mediaGroup.Deprecated, mediaGroup.Hidden, mediaGroup.Runnable())
}
mediaUpload := mustFindCommand(t, mediaGroup, "upload")
if mediaUpload.Deprecated == "" || mediaUpload.Hidden || !mediaUpload.Runnable() {
t.Fatalf("chat media upload compatibility contract: deprecated=%q hidden=%v runnable=%v", mediaUpload.Deprecated, mediaUpload.Hidden, mediaUpload.Runnable())
}
mediaUpload := mustFindCommand(t, root, "chat", "media", "upload")
for _, flag := range []string{"file", "type"} {
if mediaUpload.Flags().Lookup(flag) == nil {
t.Fatalf("chat media upload missing --%s", flag)
@@ -98,113 +88,6 @@ func TestRootKeepsMainBranchChatCompatibilityCommands(t *testing.T) {
mustFindCommand(t, root, "conference", "meeting", "reserve")
}
func TestChatHelpAndSchemaHideRetiredMediaUpload(t *testing.T) {
for _, args := range [][]string{
{"chat", "--help"},
{"chat", "media", "--help"},
} {
root := NewRootCommand()
var output bytes.Buffer
root.SetOut(&output)
root.SetErr(&output)
root.SetArgs(args)
if err := root.Execute(); err != nil {
t.Fatalf("dws %s: %v\n%s", strings.Join(args, " "), err, output.String())
}
for _, line := range strings.Split(output.String(), "\n") {
fields := strings.Fields(line)
if len(fields) > 0 && (fields[0] == "media" || fields[0] == "upload") {
t.Fatalf("dws %s exposes retired command in Help line %q:\n%s", strings.Join(args, " "), line, output.String())
}
}
}
root := NewRootCommand()
var output bytes.Buffer
root.SetOut(&output)
root.SetErr(&output)
root.SetArgs([]string{"schema", "--cli-path", "chat media upload", "--format", "json"})
err := root.Execute()
if err == nil {
t.Fatalf("retired chat media upload remains queryable from Schema:\n%s", output.String())
}
if !strings.Contains(err.Error(), "unknown runtime schema path") {
t.Fatalf("retired chat media upload Schema error = %v, want unknown path", err)
}
}
func TestRootChatMediaUploadWithoutAppCredentialsReturnsMigrationValidation(t *testing.T) {
for _, key := range []string{"DWS_CLIENT_ID", "DWS_CLIENT_SECRET"} {
value, existed := os.LookupEnv(key)
if err := os.Unsetenv(key); err != nil {
t.Fatalf("unset %s: %v", key, err)
}
t.Cleanup(func() {
if existed {
_ = os.Setenv(key, value)
return
}
_ = os.Unsetenv(key)
})
if _, exists := os.LookupEnv(key); exists {
t.Fatalf("%s is still set", key)
}
}
t.Setenv("DWS_CONFIG_DIR", filepath.Join(t.TempDir(), "config"))
filePath := filepath.Join(t.TempDir(), "image.png")
if err := os.WriteFile(filePath, []byte("image"), 0o600); err != nil {
t.Fatalf("write image fixture: %v", err)
}
commandArgs := []string{
"chat", "media", "upload",
"--file", filePath,
"--type", "image",
}
previousArgs := os.Args
os.Args = append([]string{"dws"}, commandArgs...)
t.Cleanup(func() { os.Args = previousArgs })
root := NewRootCommand()
var output bytes.Buffer
root.SetOut(&output)
root.SetErr(&output)
root.SetArgs(commandArgs)
err := root.Execute()
if err == nil {
t.Fatalf("chat media upload succeeded without app credentials:\n%s", output.String())
}
var typed *apperrors.Error
if !stderrors.As(err, &typed) {
t.Fatalf("chat media upload error type = %T, want *errors.Error: %v", err, err)
}
if typed.Category != apperrors.CategoryValidation {
t.Fatalf("chat media upload category = %q, want %q", typed.Category, apperrors.CategoryValidation)
}
if exitCode := apperrors.ExitCode(err); exitCode != 3 {
t.Fatalf("chat media upload exit code = %d, want 3", exitCode)
}
got := output.String() + "\n" + err.Error()
for _, want := range []string{"已下线", "chat message send --msg-type file --file-path"} {
if !strings.Contains(got, want) {
t.Fatalf("chat media upload migration output missing %q:\n%s", want, got)
}
}
for _, forbidden := range []string{
"DWS_CLIENT_ID",
"DWS_CLIENT_SECRET",
"缺少应用凭证",
"AppSecret",
"clientSecret",
} {
if strings.Contains(got, forbidden) {
t.Fatalf("chat media upload returned credential error %q:\n%s", forbidden, got)
}
}
}
func TestRootKeepsContactWukongCompatibilityCommands(t *testing.T) {
root := NewRootCommand()
label := mustFindCommand(t, root, "contact", "label")
+12 -81
View File
@@ -1,18 +1,18 @@
{
"version": 1,
"source_hash": "sha256:0977e2b3d4af77d348318fd0cd7ce36d325ba9ca4e7c9ed0d6daeb1be0a0c4ec",
"surface_hash": "sha256:e85148774f65d34b01d50ebdc702b785afdb8ea78663b6f2eeecc35bc3cb3fd5",
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
"coverage": {
"surface_products": 23,
"products_with_metadata": 23,
"surface_tools": 573,
"tools_with_metadata": 573,
"tools_with_agent_summary": 573,
"tools_with_use_when": 573,
"tools_with_avoid_when": 573,
"tools_with_examples": 573,
"tools_with_interface_mode": 573,
"unmatched_skill_tools": 119,
"surface_products": 22,
"products_with_metadata": 22,
"surface_tools": 572,
"tools_with_metadata": 572,
"tools_with_agent_summary": 572,
"tools_with_use_when": 572,
"tools_with_avoid_when": 572,
"tools_with_examples": 572,
"tools_with_interface_mode": 572,
"unmatched_skill_tools": 120,
"unreviewed_skill_tools": 7
},
"products": {
@@ -1089,74 +1089,6 @@
"查收、搜索、阅读、回复、发送或整理邮件"
]
},
"mcp": {
"agent_summary": "解析和管理当前身份可用的 MCP 服务连接信息",
"agent_summary_source": "dws-agent-selection/mcp",
"avoid_when": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
],
"field_provenance": {
"agent_summary": {
"value": "解析和管理当前身份可用的 MCP 服务连接信息",
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"candidates": [
{
"value": "解析和管理当前身份可用的 MCP 服务连接信息",
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true
}
]
},
"avoid_when": {
"value": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"candidates": [
{
"value": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true
}
]
},
"use_when": {
"value": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"candidates": [
{
"value": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true
}
]
}
},
"source_refs": [
"CommandRegistry:product=mcp",
"cobra-help:dws mcp --help",
"internal/cli/schema_command_registry.json#mcp",
"internal/cli/schema_hints/selection/mcp.json"
],
"use_when": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
]
},
"minutes": {
"agent_summary": "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。",
"agent_summary_source": "dws-agent-selection/minutes",
@@ -1673,7 +1605,6 @@
"event",
"live",
"mail",
"mcp",
"minutes",
"oa",
"pat",
-267
View File
@@ -1,267 +0,0 @@
{
"product_id": "mcp",
"tools": {
"mcp url get": {
"agent_summary": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"agent_summary_source": "dws-agent-selection/mcp",
"availability": "available",
"avoid_when": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"confirmation": "not_required",
"effect": "read",
"effect_source": "agent-hint",
"examples": [
"dws mcp url get 10043 --format json"
],
"field_provenance": {
"agent_summary": {
"value": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"candidates": [
{
"value": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。"
}
]
},
"availability": {
"value": "available",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "available",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"avoid_when": {
"value": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"candidates": [
{
"value": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。"
}
]
},
"confirmation": {
"value": "not_required",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "not_required",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"effect": {
"value": "read",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "read",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"examples": {
"value": [
"dws mcp url get 10043 --format json"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"candidates": [
{
"value": [
"dws mcp url get 10043 --format json"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。"
}
]
},
"idempotency": {
"value": "idempotent",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "idempotent",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"interface_mode": {
"value": "composite",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "composite",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"interface_reason": {
"value": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"interface_ref": {
"value": null,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "interface_disposition_matrix",
"review_reason": "final interface mode composite forbids a direct MCP interface_ref",
"candidates": [
{
"value": null,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "final interface mode composite forbids a direct MCP interface_ref"
}
]
},
"reviewed": {
"value": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
},
{
"value": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": false,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。"
}
]
},
"risk": {
"value": "medium",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"candidates": [
{
"value": "medium",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
]
},
"use_when": {
"value": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"candidates": [
{
"value": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
],
"source": "internal/cli/schema_hints/selection/mcp.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。"
}
]
}
},
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"reviewed": true,
"risk": "medium",
"source_refs": [
"cobra-help:dws mcp url get --help",
"internal/app/mcp_url_command.go",
"internal/cli/schema_command_registry.json#mcp.url_get",
"internal/cli/schema_hints/metadata/mcp.json",
"internal/cli/schema_hints/selection/mcp.json",
"pkg/edition/default.go#openSupplementServers"
],
"use_when": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
]
}
}
}
+118 -77
View File
@@ -1,11 +1,11 @@
{
"version": 1,
"source_hash": "sha256:0977e2b3d4af77d348318fd0cd7ce36d325ba9ca4e7c9ed0d6daeb1be0a0c4ec",
"surface_hash": "sha256:e85148774f65d34b01d50ebdc702b785afdb8ea78663b6f2eeecc35bc3cb3fd5",
"source_files": 152,
"hint_files": 48,
"hint_products": 40,
"hint_tools": 1298,
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
"source_files": 150,
"hint_files": 46,
"hint_products": 39,
"hint_tools": 1296,
"interface_metadata": {
"source": "mcp-tools-list+cli-registry",
"revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8",
@@ -27,16 +27,16 @@
]
},
"coverage": {
"surface_products": 23,
"products_with_metadata": 23,
"surface_tools": 573,
"tools_with_metadata": 573,
"tools_with_agent_summary": 573,
"tools_with_use_when": 573,
"tools_with_avoid_when": 573,
"tools_with_examples": 573,
"tools_with_interface_mode": 573,
"unmatched_skill_tools": 119,
"surface_products": 22,
"products_with_metadata": 22,
"surface_tools": 572,
"tools_with_metadata": 572,
"tools_with_agent_summary": 572,
"tools_with_use_when": 572,
"tools_with_avoid_when": 572,
"tools_with_examples": 572,
"tools_with_interface_mode": 572,
"unmatched_skill_tools": 120,
"unreviewed_skill_tools": 7
},
"source_products": [
@@ -60,7 +60,6 @@
"generate-topic-report",
"live",
"mail",
"mcp",
"meeting-followup",
"minutes",
"oa",
@@ -1505,10 +1504,38 @@
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "chat media upload",
"source": "skills/mono/references/products/chat.md",
"line": 610,
"candidates": [
"chat bot find",
"chat bot search",
"chat category create-smart"
],
"review": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "chat media upload",
"source": "skills/mono/references/products/chat.md",
"line": 611,
"candidates": [
"chat bot find",
"chat bot search",
"chat category create-smart"
],
"review": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "chat message list-emotion-replies",
"source": "skills/mono/references/products/chat.md",
"line": 1061,
"line": 1086,
"candidates": [
"chat message add-emoji",
"chat message add-favorite",
@@ -1522,7 +1549,7 @@
{
"tool_path": "chat category create",
"source": "skills/mono/references/products/chat.md",
"line": 1416,
"line": 1441,
"candidates": [
"chat group create",
"chat category create-smart",
@@ -1536,7 +1563,7 @@
{
"tool_path": "chat category delete",
"source": "skills/mono/references/products/chat.md",
"line": 1441,
"line": 1466,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -1550,7 +1577,7 @@
{
"tool_path": "chat category rename",
"source": "skills/mono/references/products/chat.md",
"line": 1452,
"line": 1477,
"candidates": [
"chat group rename",
"chat category create-smart",
@@ -1564,7 +1591,7 @@
{
"tool_path": "chat category add-conv",
"source": "skills/mono/references/products/chat.md",
"line": 1464,
"line": 1489,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -1578,7 +1605,7 @@
{
"tool_path": "chat category remove-conv",
"source": "skills/mono/references/products/chat.md",
"line": 1477,
"line": 1502,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -1592,7 +1619,7 @@
{
"tool_path": "chat text translate",
"source": "skills/mono/references/products/chat.md",
"line": 1492,
"line": 1517,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1606,7 +1633,7 @@
{
"tool_path": "chat text translate",
"source": "skills/mono/references/products/chat.md",
"line": 1493,
"line": 1518,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1620,7 +1647,7 @@
{
"tool_path": "chat text translate",
"source": "skills/mono/references/products/chat.md",
"line": 1494,
"line": 1519,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1634,7 +1661,7 @@
{
"tool_path": "chat hide",
"source": "skills/mono/references/products/chat.md",
"line": 1531,
"line": 1556,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1648,7 +1675,7 @@
{
"tool_path": "chat hide",
"source": "skills/mono/references/products/chat.md",
"line": 1532,
"line": 1557,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1662,7 +1689,7 @@
{
"tool_path": "chat mute-at-all",
"source": "skills/mono/references/products/chat.md",
"line": 1554,
"line": 1579,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1676,7 +1703,7 @@
{
"tool_path": "chat mute-at-all",
"source": "skills/mono/references/products/chat.md",
"line": 1555,
"line": 1580,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1690,7 +1717,7 @@
{
"tool_path": "chat mute-red-envelope",
"source": "skills/mono/references/products/chat.md",
"line": 1577,
"line": 1602,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1704,7 +1731,7 @@
{
"tool_path": "chat mute-red-envelope",
"source": "skills/mono/references/products/chat.md",
"line": 1578,
"line": 1603,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1718,7 +1745,7 @@
{
"tool_path": "chat mark-unread",
"source": "skills/mono/references/products/chat.md",
"line": 1598,
"line": 1623,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1732,7 +1759,7 @@
{
"tool_path": "chat mark-unread",
"source": "skills/mono/references/products/chat.md",
"line": 1599,
"line": 1624,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1746,7 +1773,7 @@
{
"tool_path": "chat clear-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1617,
"line": 1642,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1760,7 +1787,7 @@
{
"tool_path": "chat clear-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1618,
"line": 1643,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1774,7 +1801,7 @@
{
"tool_path": "chat clear-all-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1634,
"line": 1659,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1788,7 +1815,7 @@
{
"tool_path": "chat clear-all-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1636,
"line": 1661,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1802,7 +1829,7 @@
{
"tool_path": "chat list-all-conversations",
"source": "skills/mono/references/products/chat.md",
"line": 1650,
"line": 1675,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1816,7 +1843,7 @@
{
"tool_path": "chat list-all-conversations",
"source": "skills/mono/references/products/chat.md",
"line": 1651,
"line": 1676,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1830,7 +1857,7 @@
{
"tool_path": "chat list-all-conversations",
"source": "skills/mono/references/products/chat.md",
"line": 1652,
"line": 1677,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1844,7 +1871,7 @@
{
"tool_path": "chat list-all-conversations",
"source": "skills/mono/references/products/chat.md",
"line": 1653,
"line": 1678,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1858,7 +1885,7 @@
{
"tool_path": "chat clear-messages",
"source": "skills/mono/references/products/chat.md",
"line": 1673,
"line": 1698,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1872,7 +1899,7 @@
{
"tool_path": "chat clear-messages",
"source": "skills/mono/references/products/chat.md",
"line": 1674,
"line": 1699,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1886,7 +1913,7 @@
{
"tool_path": "chat mark-read",
"source": "skills/mono/references/products/chat.md",
"line": 1692,
"line": 1717,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1900,7 +1927,7 @@
{
"tool_path": "chat mark-read",
"source": "skills/mono/references/products/chat.md",
"line": 1693,
"line": 1718,
"candidates": [
"chat bot find",
"chat bot search",
@@ -1914,7 +1941,7 @@
{
"tool_path": "chat group list-join-validations",
"source": "skills/mono/references/products/chat.md",
"line": 1736,
"line": 1761,
"candidates": [
"chat group bots",
"chat group create",
@@ -1928,7 +1955,7 @@
{
"tool_path": "chat group list-join-validations",
"source": "skills/mono/references/products/chat.md",
"line": 1737,
"line": 1762,
"candidates": [
"chat group bots",
"chat group create",
@@ -1942,7 +1969,7 @@
{
"tool_path": "chat group list-join-validations",
"source": "skills/mono/references/products/chat.md",
"line": 1738,
"line": 1763,
"candidates": [
"chat group bots",
"chat group create",
@@ -1956,7 +1983,7 @@
{
"tool_path": "chat group audit-join-validation",
"source": "skills/mono/references/products/chat.md",
"line": 1758,
"line": 1783,
"candidates": [
"chat group bots",
"chat group create",
@@ -1970,7 +1997,7 @@
{
"tool_path": "chat group audit-join-validation",
"source": "skills/mono/references/products/chat.md",
"line": 1759,
"line": 1784,
"candidates": [
"chat group bots",
"chat group create",
@@ -1984,7 +2011,7 @@
{
"tool_path": "chat group members",
"source": "skills/mono/references/products/chat.md",
"line": 1791,
"line": 1816,
"candidates": [
"chat group members add",
"chat group members add-bot",
@@ -1998,7 +2025,7 @@
{
"tool_path": "chat group update-alias",
"source": "skills/mono/references/products/chat.md",
"line": 1796,
"line": 1821,
"candidates": [
"chat group bots",
"chat group create",
@@ -2012,7 +2039,7 @@
{
"tool_path": "chat group update-nick",
"source": "skills/mono/references/products/chat.md",
"line": 1797,
"line": 1822,
"candidates": [
"chat group bots",
"chat group create",
@@ -2026,7 +2053,7 @@
{
"tool_path": "chat group members list-by-ids",
"source": "skills/mono/references/products/chat.md",
"line": 1798,
"line": 1823,
"candidates": [
"chat message list-by-ids",
"chat group members add",
@@ -2040,7 +2067,7 @@
{
"tool_path": "chat category create",
"source": "skills/mono/references/products/chat.md",
"line": 1822,
"line": 1847,
"candidates": [
"chat group create",
"chat category create-smart",
@@ -2054,7 +2081,7 @@
{
"tool_path": "chat category delete",
"source": "skills/mono/references/products/chat.md",
"line": 1824,
"line": 1849,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -2068,7 +2095,7 @@
{
"tool_path": "chat category rename",
"source": "skills/mono/references/products/chat.md",
"line": 1825,
"line": 1850,
"candidates": [
"chat group rename",
"chat category create-smart",
@@ -2082,7 +2109,7 @@
{
"tool_path": "chat category add-conv",
"source": "skills/mono/references/products/chat.md",
"line": 1826,
"line": 1851,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -2096,7 +2123,7 @@
{
"tool_path": "chat category remove-conv",
"source": "skills/mono/references/products/chat.md",
"line": 1827,
"line": 1852,
"candidates": [
"chat category create-smart",
"chat category list",
@@ -2110,7 +2137,7 @@
{
"tool_path": "chat group share-invite",
"source": "skills/mono/references/products/chat.md",
"line": 1838,
"line": 1863,
"candidates": [
"chat group bots",
"chat group create",
@@ -2124,7 +2151,7 @@
{
"tool_path": "chat group notice create/edit/get/list",
"source": "skills/mono/references/products/chat.md",
"line": 1839,
"line": 1864,
"candidates": [
"chat group bots",
"chat group create",
@@ -2138,7 +2165,7 @@
{
"tool_path": "chat message list-emotion-replies",
"source": "skills/mono/references/products/chat.md",
"line": 1841,
"line": 1866,
"candidates": [
"chat message add-emoji",
"chat message add-favorite",
@@ -2152,7 +2179,7 @@
{
"tool_path": "chat hide",
"source": "skills/mono/references/products/chat.md",
"line": 1848,
"line": 1873,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2166,7 +2193,7 @@
{
"tool_path": "chat mute-at-all",
"source": "skills/mono/references/products/chat.md",
"line": 1849,
"line": 1874,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2180,7 +2207,7 @@
{
"tool_path": "chat mute-at-all",
"source": "skills/mono/references/products/chat.md",
"line": 1850,
"line": 1875,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2194,7 +2221,7 @@
{
"tool_path": "chat mute-red-envelope",
"source": "skills/mono/references/products/chat.md",
"line": 1851,
"line": 1876,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2208,7 +2235,7 @@
{
"tool_path": "chat mute-red-envelope",
"source": "skills/mono/references/products/chat.md",
"line": 1852,
"line": 1877,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2222,7 +2249,7 @@
{
"tool_path": "chat mark-unread",
"source": "skills/mono/references/products/chat.md",
"line": 1853,
"line": 1878,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2236,7 +2263,7 @@
{
"tool_path": "chat mark-read",
"source": "skills/mono/references/products/chat.md",
"line": 1854,
"line": 1879,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2250,7 +2277,7 @@
{
"tool_path": "chat clear-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1855,
"line": 1880,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2264,7 +2291,7 @@
{
"tool_path": "chat clear-all-red-point",
"source": "skills/mono/references/products/chat.md",
"line": 1856,
"line": 1881,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2278,7 +2305,7 @@
{
"tool_path": "chat list-all-conversations",
"source": "skills/mono/references/products/chat.md",
"line": 1857,
"line": 1882,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2292,7 +2319,7 @@
{
"tool_path": "chat clear-messages",
"source": "skills/mono/references/products/chat.md",
"line": 1858,
"line": 1883,
"candidates": [
"chat bot find",
"chat bot search",
@@ -2306,7 +2333,7 @@
{
"tool_path": "chat group list-join-validations",
"source": "skills/mono/references/products/chat.md",
"line": 1860,
"line": 1885,
"candidates": [
"chat group bots",
"chat group create",
@@ -2320,7 +2347,7 @@
{
"tool_path": "chat group audit-join-validation",
"source": "skills/mono/references/products/chat.md",
"line": 1861,
"line": 1886,
"candidates": [
"chat group bots",
"chat group create",
@@ -2331,10 +2358,24 @@
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "chat media upload",
"source": "skills/mono/references/products/chat.md",
"line": 1893,
"candidates": [
"chat bot find",
"chat bot search",
"chat category create-smart"
],
"review": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "chat text translate",
"source": "skills/mono/references/products/chat.md",
"line": 1881,
"line": 1905,
"candidates": [
"chat bot find",
"chat bot search",
+25 -461
View File
@@ -1,21 +1,21 @@
{
"version": 1,
"source_hash": "sha256:360114d03cc3e8dc6cef30c6e617cc40f88e5577c29dd5b5946fe1cde91eb9de",
"surface_hash": "sha256:e85148774f65d34b01d50ebdc702b785afdb8ea78663b6f2eeecc35bc3cb3fd5",
"source_hash": "sha256:14398788381c822f208e8cae059d98cd60da6625023869c74015735650d67e6f",
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
"catalog": {
"agent_metadata": {
"products_with_metadata": 23,
"products_with_metadata": 22,
"source": "embedded-skill-metadata",
"source_hash": "sha256:0977e2b3d4af77d348318fd0cd7ce36d325ba9ca4e7c9ed0d6daeb1be0a0c4ec",
"surface_hash": "sha256:e85148774f65d34b01d50ebdc702b785afdb8ea78663b6f2eeecc35bc3cb3fd5",
"surface_products": 23,
"surface_tools": 573,
"tools_with_agent_summary": 573,
"tools_with_metadata": 573,
"unmatched_skill_tools": 119,
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
"surface_products": 22,
"surface_tools": 572,
"tools_with_agent_summary": 572,
"tools_with_metadata": 572,
"unmatched_skill_tools": 120,
"version": 1
},
"count": 23,
"count": 22,
"interface_metadata": {
"coverage": {
"aliased_tools": 178,
@@ -7660,7 +7660,7 @@
"cli_name": "send",
"cli_path": "chat message send",
"confirmation": "not_required",
"description": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n本地图片 / 文件消息:\n 统一使用 --msg-type file --file-path \u003c本地路径\u003e。CLI 会完成上传并按 file 消息发送;\n .png/.jpg 也会显示为可下载的文件附件,不会生成 mediaId 或渲染成内联图片。\n\n旧版内联图片消息:\n 仅当上游已经提供有效 mediaId 时,使用 --msg-type image --media-id。\n 当前 CLI 不提供本地文件到 mediaId 的上传能力。",
"description": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n富媒体消息(通过 --msg-type 指定类型):\n image — 发送图片:--msg-type image --media-id(通过 dt_media_upload 上传获得)\n file/audio/video — 发送文件、音频、视频:传本地 --file-path,CLI 会上传后按 file 消息发送",
"effect": "write",
"group": "message",
"idempotency": "unknown",
@@ -13476,110 +13476,6 @@
"查收、搜索、阅读、回复、发送或整理邮件"
]
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
"CommandRegistry:product=mcp",
"cobra-help:dws mcp --help",
"internal/cli/schema_command_registry.json#mcp",
"internal/cli/schema_hints/selection/mcp.json"
],
"agent_summary": "解析和管理当前身份可用的 MCP 服务连接信息",
"agent_summary_source": "dws-agent-selection/mcp",
"avoid_when": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
],
"description": "管理 MCP 服务连接信息",
"field_provenance": {
"agent_summary": {
"candidates": [
{
"precedence": "reviewed_explicit",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": "解析和管理当前身份可用的 MCP 服务连接信息"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": "解析和管理当前身份可用的 MCP 服务连接信息"
},
"avoid_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
]
},
"use_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
]
}
},
"id": "mcp",
"name": "管理 MCP 服务连接信息",
"runtime": true,
"tool_count": 1,
"tools": [
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_summary": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"agent_summary_source": "dws-agent-selection/mcp",
"availability": "available",
"avoid_when": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"canonical_path": "mcp.url_get",
"cli_name": "get",
"cli_path": "mcp url get",
"confirmation": "not_required",
"description": "输入 MCP 市场 mcpId,返回以当前用户和组织身份访问该 MCP 的 Streamable HTTP 服务地址。\n\n安全提示:返回的 mcpURL 和 mcpJSON 可能包含身份凭据,仅限个人使用,请勿分享到群聊、文档、邮件、代码仓库或日志。",
"effect": "read",
"group": "url",
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"name": "url_get",
"primary_cli_path": "mcp url get",
"reviewed": true,
"risk": "medium",
"title": "按 mcpId 获取 MCP 的 Streamable HTTP 服务地址",
"use_when": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
]
}
],
"use_when": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
]
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
@@ -19257,7 +19153,7 @@
}
],
"source": "embedded-command-catalog",
"tool_count": 573
"tool_count": 572
},
"tools": {
"aisearch.enterprise_person_search": {
@@ -196587,7 +196483,7 @@
]
]
},
"description": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n本地图片 / 文件消息:\n 统一使用 --msg-type file --file-path \u003c本地路径\u003e。CLI 会完成上传并按 file 消息发送;\n .png/.jpg 也会显示为可下载的文件附件,不会生成 mediaId 或渲染成内联图片。\n\n旧版内联图片消息:\n 仅当上游已经提供有效 mediaId 时,使用 --msg-type image --media-id。\n 当前 CLI 不提供本地文件到 mediaId 的上传能力。",
"description": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n富媒体消息(通过 --msg-type 指定类型):\n image — 发送图片:--msg-type image --media-id(通过 dt_media_upload 上传获得)\n file/audio/video — 发送文件、音频、视频:传本地 --file-path,CLI 会上传后按 file 消息发送",
"display": "群聊 / 消息 / 机器人",
"effect": "write",
"effect_source": "agent-hint",
@@ -196704,7 +196600,7 @@
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n本地图片 / 文件消息:\n 统一使用 --msg-type file --file-path \u003c本地路径\u003e。CLI 会完成上传并按 file 消息发送;\n .png/.jpg 也会显示为可下载的文件附件,不会生成 mediaId 或渲染成内联图片。\n\n旧版内联图片消息:\n 仅当上游已经提供有效 mediaId 时,使用 --msg-type image --media-id。\n 当前 CLI 不提供本地文件到 mediaId 的上传能力。"
"value": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n富媒体消息(通过 --msg-type 指定类型):\n image — 发送图片:--msg-type image --media-id(通过 dt_media_upload 上传获得)\n file/audio/video — 发送文件、音频、视频:传本地 --file-path,CLI 会上传后按 file 消息发送"
},
{
"precedence": "mcp_metadata",
@@ -196716,7 +196612,7 @@
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n本地图片 / 文件消息:\n 统一使用 --msg-type file --file-path \u003c本地路径\u003e。CLI 会完成上传并按 file 消息发送;\n .png/.jpg 也会显示为可下载的文件附件,不会生成 mediaId 或渲染成内联图片。\n\n旧版内联图片消息:\n 仅当上游已经提供有效 mediaId 时,使用 --msg-type image --media-id。\n 当前 CLI 不提供本地文件到 mediaId 的上传能力。"
"value": "以当前用户身份发送消息。\n\n⚠️ 重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。\n\n目标选择(三选一,必填):\n --group 群聊 openconversation_id\n --user 单聊接收人 userId\n --open-dingtalk-id 单聊接收人 openDingTalkId\n\n纯文本 / Markdown 消息(默认):\n 无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。\n\n富媒体消息(通过 --msg-type 指定类型):\n image — 发送图片:--msg-type image --media-id(通过 dt_media_upload 上传获得)\n file/audio/video — 发送文件、音频、视频:传本地 --file-path,CLI 会上传后按 file 消息发送"
},
"effect": {
"candidates": [
@@ -197280,7 +197176,7 @@
"type": "string"
},
"file-path": {
"description": "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)",
"description": "本地文件路径(msgType=file 时可直接上传发送)",
"field_provenance": {
"description": {
"candidates": [
@@ -197288,7 +197184,7 @@
"precedence": "cobra_contract",
"selected": true,
"source": "cobra_usage",
"value": "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)"
"value": "本地文件路径(msgType=file 时可直接上传发送)"
},
{
"precedence": "default",
@@ -197300,7 +197196,7 @@
"precedence": "cobra_contract",
"resolution": "highest_precedence",
"source": "cobra_usage",
"value": "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)"
"value": "本地文件路径(msgType=file 时可直接上传发送)"
},
"format": {
"candidates": [
@@ -197502,7 +197398,7 @@
"type": "string"
},
"media-id": {
"description": "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)",
"description": "图片 mediaId(通过 dt_media_upload 上传后用 extract_media_id.py 提取,仅 msgType=image)",
"example": "@lADP_schema_smoke",
"field_provenance": {
"description": {
@@ -197511,7 +197407,7 @@
"precedence": "cobra_contract",
"selected": true,
"source": "cobra_usage",
"value": "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)"
"value": "图片 mediaId(通过 dt_media_upload 上传后用 extract_media_id.py 提取,仅 msgType=image)"
},
{
"precedence": "default",
@@ -197523,7 +197419,7 @@
"precedence": "cobra_contract",
"resolution": "highest_precedence",
"source": "cobra_usage",
"value": "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)"
"value": "图片 mediaId(通过 dt_media_upload 上传后用 extract_media_id.py 提取,仅 msgType=image)"
},
"example": {
"candidates": [
@@ -197621,7 +197517,7 @@
"type": "string"
},
"msg-type": {
"description": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)",
"description": "富媒体消息类型: image/file/audio/video(audio/video 是 file 别名;纯文本/Markdown 无需指定,直接传内容即可)",
"enum": [
"image",
"file",
@@ -197635,7 +197531,7 @@
"precedence": "cobra_contract",
"selected": true,
"source": "cobra_usage",
"value": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)"
"value": "富媒体消息类型: image/file/audio/video(audio/video 是 file 别名;纯文本/Markdown 无需指定,直接传内容即可)"
},
{
"precedence": "mcp_metadata",
@@ -197653,7 +197549,7 @@
"precedence": "cobra_contract",
"resolution": "highest_precedence",
"source": "cobra_usage",
"value": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)"
"value": "富媒体消息类型: image/file/audio/video(audio/video 是 file 别名;纯文本/Markdown 无需指定,直接传内容即可)"
},
"enum": {
"candidates": [
@@ -319879,338 +319775,6 @@
"已知 templateId 并需要修改名称、主题、正文或收件人时"
]
},
"mcp.url_get": {
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
"cobra-help:dws mcp url get --help",
"internal/app/mcp_url_command.go",
"internal/cli/schema_command_registry.json#mcp.url_get",
"internal/cli/schema_hints/metadata/mcp.json",
"internal/cli/schema_hints/selection/mcp.json",
"pkg/edition/default.go#openSupplementServers"
],
"agent_summary": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"agent_summary_source": "dws-agent-selection/mcp",
"availability": "available",
"avoid_when": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"canonical_path": "mcp.url_get",
"cli_name": "get",
"cli_path": "mcp url get",
"confirmation": "not_required",
"description": "输入 MCP 市场 mcpId,返回以当前用户和组织身份访问该 MCP 的 Streamable HTTP 服务地址。\n\n安全提示:返回的 mcpURL 和 mcpJSON 可能包含身份凭据,仅限个人使用,请勿分享到群聊、文档、邮件、代码仓库或日志。",
"display": "管理 MCP 服务连接信息",
"effect": "read",
"effect_source": "agent-hint",
"examples": [
"dws mcp url get 10043 --format json"
],
"field_provenance": {
"agent_summary": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址"
},
"availability": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "available"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "available"
},
"avoid_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
]
},
"canonical_path": {
"candidates": [
{
"precedence": "command_registry",
"selected": true,
"source": "reviewed_command_registry",
"source_ref": "mcp url get",
"value": "mcp.url_get"
}
],
"precedence": "command_registry",
"resolution": "registry_identity",
"source": "reviewed_command_registry",
"source_ref": "mcp url get",
"value": "mcp.url_get"
},
"confirmation": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "not_required"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "not_required"
},
"description": {
"candidates": [
{
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "输入 MCP 市场 mcpId,返回以当前用户和组织身份访问该 MCP 的 Streamable HTTP 服务地址。\n\n安全提示:返回的 mcpURL 和 mcpJSON 可能包含身份凭据,仅限个人使用,请勿分享到群聊、文档、邮件、代码仓库或日志。"
}
],
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "输入 MCP 市场 mcpId,返回以当前用户和组织身份访问该 MCP 的 Streamable HTTP 服务地址。\n\n安全提示:返回的 mcpURL 和 mcpJSON 可能包含身份凭据,仅限个人使用,请勿分享到群聊、文档、邮件、代码仓库或日志。"
},
"effect": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "read"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "read"
},
"examples": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"dws mcp url get 10043 --format json"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"dws mcp url get 10043 --format json"
]
},
"idempotency": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "idempotent"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "idempotent"
},
"interface_mode": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "composite"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "composite"
},
"interface_reason": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata."
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata."
},
"interface_ref": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "final interface mode composite forbids a direct MCP interface_ref",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": null
}
],
"precedence": "reviewed_explicit",
"resolution": "interface_disposition_matrix",
"review_reason": "final interface mode composite forbids a direct MCP interface_ref",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": null
},
"reviewed": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": true
},
{
"precedence": "reviewed_explicit",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"selected": false,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": true
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": true
},
"risk": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"selected": true,
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "medium"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output.",
"source": "internal/cli/schema_hints/metadata/mcp.json",
"value": "medium"
},
"title": {
"candidates": [
{
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "按 mcpId 获取 MCP 的 Streamable HTTP 服务地址"
}
],
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "按 mcpId 获取 MCP 的 Streamable HTTP 服务地址"
},
"use_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"selected": true,
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"source": "internal/cli/schema_hints/selection/mcp.json",
"value": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
]
}
},
"group": "url",
"has_parameters": false,
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"is_alias": false,
"name": "url_get",
"parameter_count": 0,
"parameters": {},
"path": "mcp.url_get",
"positionals": [
{
"description": "钉钉 MCP 市场中的 mcpId",
"index": 0,
"name": "mcp_id",
"required": true,
"type": "string"
}
],
"primary_cli_path": "mcp url get",
"product_id": "mcp",
"reviewed": true,
"risk": "medium",
"source": "reviewed_command_registry",
"title": "按 mcpId 获取 MCP 的 Streamable HTTP 服务地址",
"use_when": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
]
},
"minutes.add_member_permission": {
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
@@ -321,6 +321,7 @@
"chat list-all-conversations",
"chat mark-read",
"chat mark-unread",
"chat media upload",
"chat message list-emotion-replies",
"chat message set-top-msg",
"chat message unset-top-msg",
@@ -1700,15 +1700,6 @@
}
]
},
{
"id": "mcp",
"tools": [
{
"canonical_path": "mcp.url_get",
"cli_path": "mcp url get"
}
]
},
{
"id": "minutes",
"tools": [
+4 -2
View File
@@ -23,7 +23,6 @@
"event": "metadata/event.json",
"live": "metadata/live.json",
"mail": "metadata/mail.json",
"mcp": "metadata/mcp.json",
"minutes": "metadata/minutes.json",
"oa": "metadata/oa.json",
"pat": "metadata/pat.json",
@@ -48,7 +47,6 @@
"event": "selection/event.json",
"live": "selection/live.json",
"mail": "selection/mail.json",
"mcp": "selection/mcp.json",
"minutes": "selection/minutes.json",
"oa": "selection/oa.json",
"pat": "selection/pat.json",
@@ -436,6 +434,10 @@
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"chat media upload": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"chat message list-emotion-replies": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
@@ -1,24 +0,0 @@
{
"version": 1,
"source": {
"kind": "explicit",
"name": "dws-tool-metadata/mcp",
"repository": "DingTalk-Real-AI/dingtalk-workspace-cli",
"channel": "open-source",
"reviewed": true
},
"tools": {
"mcp.url_get": {
"effect": "read",
"risk": "medium",
"confirmation": "not_required",
"idempotency": "idempotent",
"interface_mode": "composite",
"availability": "available",
"interface_reason": "Reviewed unpinned remote adapter: the public CLI wrapper calls the helper-only mcp-meta/get_mcp_server_url endpoint, which is intentionally absent from the public product catalog and pinned MCP metadata.",
"runtime_gate": "none",
"reviewed": true,
"review_reason": "Expose the reviewed MCP URL resolver through the public CLI and Agent Schema while preserving the helper endpoint as a non-product supplemental server. The returned URL contains user- and organization-scoped credentials and must be treated as sensitive output."
}
}
}
@@ -385,6 +385,10 @@
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"chat media upload": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"chat message list-emotion-replies": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
@@ -7,7 +7,7 @@
"channel": "open-source"
},
"coverage": {
"source_tools": 573,
"source_tools": 572,
"matched_tools": 71
},
"tools": {
@@ -1,51 +0,0 @@
{
"version": 1,
"source": {
"kind": "explicit",
"name": "dws-agent-selection/mcp",
"repository": "DingTalk-Real-AI/dingtalk-workspace-cli",
"channel": "open-source",
"reviewed": true
},
"tools": {
"mcp.url_get": {
"agent_summary": "按 MCP 市场 mcpId 获取当前用户和组织可用的 Streamable HTTP 地址",
"use_when": [
"已知钉钉 MCP 市场 mcpId,需要获得当前身份可用的 Streamable HTTP 连接地址"
],
"avoid_when": [
"只是查询 DWS 已公开命令或参数时使用 dws schema",
"用户要求把返回的凭据 URL 发送到群聊、文档、邮件、日志或代码仓库时不要执行或传播"
],
"examples": [
"dws mcp url get 10043 --format json"
],
"reviewed": true,
"review_reason": "正式公开 MCP URL 解析命令的 Agent 选型文案,并明确凭据 URL 只能返回给当前用户、不得二次传播。",
"source_refs": [
"internal/cli/schema_command_registry.json#mcp.url_get",
"cobra-help:dws mcp url get --help",
"internal/app/mcp_url_command.go",
"pkg/edition/default.go#openSupplementServers"
]
}
},
"products": {
"mcp": {
"agent_summary": "解析和管理当前身份可用的 MCP 服务连接信息",
"use_when": [
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端"
],
"avoid_when": [
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp"
],
"reviewed": true,
"review_reason": "为公开 mcp 命令提供产品级 Agent 路由,并强调连接信息属于敏感凭据。",
"source_refs": [
"internal/cli/schema_command_registry.json#mcp",
"cobra-help:dws mcp --help",
"CommandRegistry:product=mcp"
]
}
}
}
+10 -16
View File
@@ -1433,25 +1433,19 @@ func newChatCommand() *cobra.Command {
纯文本 / Markdown 消息(默认):
无需指定 --msg-type,直接传消息内容即可。推荐使用 --text flag 传递内容(尤其当内容含换行、引号等特殊字符时),也支持位置参数。可选 --title 作为消息标题。
本地图片 / 文件消息:
统一使用 --msg-type file --file-path <本地路径>。CLI 会完成上传并按 file 消息发送;
.png/.jpg 也会显示为可下载的文件附件,不会生成 mediaId 或渲染成内联图片。
旧版内联图片消息:
仅当上游已经提供有效 mediaId 时,使用 --msg-type image --media-id。
当前 CLI 不提供本地文件到 mediaId 的上传能力。`,
富媒体消息(通过 --msg-type 指定类型):
image — 发送图片:--msg-type image --media-id(通过 dt_media_upload 上传获得)
file/audio/video — 发送文件、音频、视频:传本地 --file-path,CLI 会上传后按 file 消息发送`,
Example: ` dws chat message send --group <openconversation_id> "hello"
dws chat message send --user <userId> "请查收"
dws chat message send --open-dingtalk-id <openDingTalkId> "请查收"
dws chat message send --group <openconversation_id> --title "周报提醒" "请大家本周五前提交周报"
# 发送本地图片或文件(图片会作为可下载的 file 附件发送)
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png
# 发送图片
dws chat message send --group <openconversation_id> --msg-type image --media-id <mediaId>
# 发送本地文件/音频/视频(audio/video 是 file 的语义别名)
dws chat message send --group <openconversation_id> --msg-type file --file-path ./report.pdf
# 发送本地音频/视频(audio/video 是 file 的语义别名)
dws chat message send --group <openconversation_id> --msg-type audio --file-path ./recording.mp3
dws chat message send --group <openconversation_id> --msg-type video --file-path ./demo.mp4
# 旧版内联图片:仅当上游已经持有有效 mediaId 时使用
dws chat message send --group <openconversation_id> --msg-type image --media-id <mediaId>
# 查询群 ID: dws chat search --query "群名"
# 查询用户 ID: dws contact user search --query "姓名"`,
Args: cobra.MaximumNArgs(1),
@@ -2406,13 +2400,13 @@ func newChatCommand() *cobra.Command {
_ = chatMessageSendCmd.Flags().MarkHidden("markdown")
chatMessageSendCmd.Flags().Bool("at-all", false, "@所有人(仅群聊时生效,可选),设置时,消息内容中一定要包含对应的占位符<@all>")
chatMessageSendCmd.Flags().String("at-open-dingtalk-ids", "", "@指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选),设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2>")
chatMessageSendCmd.Flags().String("media-id", "", "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)")
chatMessageSendCmd.Flags().String("msg-type", "", "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)")
chatMessageSendCmd.Flags().String("media-id", "", "图片 mediaId(通过 dt_media_upload 上传后用 extract_media_id.py 提取,仅 msgType=image)")
chatMessageSendCmd.Flags().String("msg-type", "", "富媒体消息类型: image/file/audio/video(audio/video 是 file 别名;纯文本/Markdown 无需指定,直接传内容即可)")
chatMessageSendCmd.Flags().Int64("dentry-id", 0, "文件 dentryId(与 --space-id 成对传入时跳过自动上传)")
chatMessageSendCmd.Flags().Int64("space-id", 0, "空间 ID(与 --dentry-id 成对传入时跳过自动上传)")
chatMessageSendCmd.Flags().String("file-name", "", "文件名")
chatMessageSendCmd.Flags().String("file-type", "", "文件类型/扩展名")
chatMessageSendCmd.Flags().String("file-path", "", "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)")
chatMessageSendCmd.Flags().String("file-path", "", "本地文件路径(msgType=file 时可直接上传发送)")
chatMessageSendCmd.Flags().Int64("file-size", 0, "文件大小,单位字节")
_ = chatMessageSendCmd.Flags().MarkHidden("dentry-id")
_ = chatMessageSendCmd.Flags().MarkHidden("space-id")
@@ -3393,7 +3387,7 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
}
iconMediaID := strings.TrimSpace(mustGetFlag(cmd, "icon-media-id"))
if iconMediaID == "" {
return fmt.Errorf("invalid --icon-media-id: mediaId 不能为空\n hint: 请使用上游媒体上传能力返回的有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传命令")
return fmt.Errorf("invalid --icon-media-id: mediaId 不能为空\n hint: 先通过媒体上传命令(dt_media_upload)上传图片,使用返回的 mediaId")
}
return callMCPToolOnServer("im", "update_group_icon", map[string]any{
"openConversationId": mustGetFlag(cmd, "group"),
@@ -1,262 +0,0 @@
package helpers
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
)
type chatFilePathCall struct {
server string
tool string
args map[string]any
}
type chatFilePathCaller struct {
sequence []string
calls []chatFilePathCall
}
func (c *chatFilePathCaller) CallTool(_ context.Context, server, tool string, args map[string]any) (*edition.ToolResult, error) {
c.sequence = append(c.sequence, tool)
copied := make(map[string]any, len(args))
for key, value := range args {
copied[key] = value
}
c.calls = append(c.calls, chatFilePathCall{server: server, tool: tool, args: copied})
switch tool {
case "init_conversation_file_upload":
return textToolResult(`{"resourceUrl":"https://upload.example/file","uploadKey":"upload-key","headers":{"x-upload":"yes"}}`), nil
case "commit_conversation_file_upload":
return textToolResult(`{"result":{"dentryId":123,"spaceId":456}}`), nil
case "send_personal_message":
return textToolResult(`{"success":true}`), nil
default:
return nil, fmt.Errorf("unexpected tool call %s/%s", server, tool)
}
}
func (*chatFilePathCaller) Format() string { return "json" }
func (*chatFilePathCaller) DryRun() bool { return false }
func (*chatFilePathCaller) Fields() string { return "" }
func (*chatFilePathCaller) JQ() string { return "" }
func TestChatMessageSendFilePathUsesWukongUploadSequence(t *testing.T) {
previousDeps, previousPut, previousArgs := deps, httpPutFile, os.Args
t.Cleanup(func() {
deps = previousDeps
httpPutFile = previousPut
os.Args = previousArgs
})
filePath := filepath.Join(t.TempDir(), "image.png")
payload := []byte("png payload")
if err := os.WriteFile(filePath, payload, 0o600); err != nil {
t.Fatal(err)
}
caller := &chatFilePathCaller{}
commandArgs := []string{
"message", "send",
"--group=cid",
"--msg-type=file",
"--file-path=" + filePath,
}
os.Args = append([]string{"dws", "chat"}, commandArgs...)
httpPutFile = func(_ context.Context, resourceURL string, headers map[string]string, localPath string, fileSize int64) error {
caller.sequence = append(caller.sequence, "HTTP PUT")
if resourceURL != "https://upload.example/file" {
t.Fatalf("resourceURL = %q", resourceURL)
}
if headers["x-upload"] != "yes" {
t.Fatalf("headers = %#v", headers)
}
if localPath != filePath || fileSize != int64(len(payload)) {
t.Fatalf("upload file = %q (%d), want %q (%d)", localPath, fileSize, filePath, len(payload))
}
return nil
}
err := runChatCoverageCommand(t, caller, commandArgs...)
if err != nil {
t.Fatalf("chat message send --file-path: %v", err)
}
wantSequence := []string{
"init_conversation_file_upload",
"HTTP PUT",
"commit_conversation_file_upload",
"send_personal_message",
}
if !reflect.DeepEqual(caller.sequence, wantSequence) {
t.Fatalf("call sequence = %#v, want %#v", caller.sequence, wantSequence)
}
if len(caller.calls) != 3 {
t.Fatalf("tool calls = %#v, want init, commit, send", caller.calls)
}
fileMD5 := fmt.Sprintf("%x", md5.Sum(payload))
wantInit := chatFilePathCall{
server: "im",
tool: "init_conversation_file_upload",
args: map[string]any{
"openConversationId": "cid",
"fileName": "image.png",
"fileSize": int64(len(payload)),
"md5": fileMD5,
},
}
if !reflect.DeepEqual(caller.calls[0], wantInit) {
t.Fatalf("init call = %#v, want %#v", caller.calls[0], wantInit)
}
wantCommit := chatFilePathCall{
server: "im",
tool: "commit_conversation_file_upload",
args: map[string]any{
"openConversationId": "cid",
"uploadKey": "upload-key",
"fileName": "image.png",
"fileSize": int64(len(payload)),
"md5": fileMD5,
},
}
if !reflect.DeepEqual(caller.calls[1], wantCommit) {
t.Fatalf("commit call = %#v, want %#v", caller.calls[1], wantCommit)
}
send := caller.calls[len(caller.calls)-1]
if send.server != "chat" || send.tool != "send_personal_message" || send.args["msgType"] != "file" {
t.Fatalf("send call = %#v", send)
}
if send.args["openConversationId"] != "cid" {
t.Fatalf("send target = %#v", send.args["openConversationId"])
}
content, ok := send.args["content"].(string)
if !ok {
t.Fatalf("send content = %#v", send.args["content"])
}
var parsed struct {
DentryID int64 `json:"dentryId"`
SpaceID int64 `json:"spaceId"`
FileName string `json:"fileName"`
FileType string `json:"fileType"`
FilePath string `json:"filePath"`
FileSize int64 `json:"fileSize"`
}
if err := json.Unmarshal([]byte(content), &parsed); err != nil {
t.Fatalf("decode send content %q: %v", content, err)
}
if parsed.DentryID != 123 || parsed.SpaceID != 456 ||
parsed.FileName != "image.png" || parsed.FileType != "png" ||
parsed.FilePath != "/image.png" || parsed.FileSize != int64(len(payload)) {
t.Fatalf("send content = %#v", parsed)
}
}
func TestChatMessageSendFilePathUsesOpenDingTalkIDTarget(t *testing.T) {
previousDeps, previousPut, previousArgs := deps, httpPutFile, os.Args
t.Cleanup(func() {
deps = previousDeps
httpPutFile = previousPut
os.Args = previousArgs
})
filePath := filepath.Join(t.TempDir(), "report.pdf")
payload := []byte("pdf payload")
if err := os.WriteFile(filePath, payload, 0o600); err != nil {
t.Fatal(err)
}
caller := &chatFilePathCaller{}
commandArgs := []string{
"message", "send",
"--open-dingtalk-id=D-target",
"--msg-type=file",
"--file-path=" + filePath,
}
os.Args = append([]string{"dws", "chat"}, commandArgs...)
httpPutFile = func(_ context.Context, resourceURL string, _ map[string]string, localPath string, fileSize int64) error {
caller.sequence = append(caller.sequence, "HTTP PUT")
if resourceURL != "https://upload.example/file" || localPath != filePath || fileSize != int64(len(payload)) {
t.Fatalf("upload = %q, %q (%d)", resourceURL, localPath, fileSize)
}
return nil
}
if err := runChatCoverageCommand(t, caller, commandArgs...); err != nil {
t.Fatalf("chat message send --open-dingtalk-id --file-path: %v", err)
}
if len(caller.calls) != 3 {
t.Fatalf("tool calls = %#v, want init, commit, send", caller.calls)
}
fileMD5 := fmt.Sprintf("%x", md5.Sum(payload))
wantInit := chatFilePathCall{
server: "im",
tool: "init_conversation_file_upload",
args: map[string]any{
"openDingTalkId": "D-target",
"fileName": "report.pdf",
"fileSize": int64(len(payload)),
"md5": fileMD5,
},
}
if !reflect.DeepEqual(caller.calls[0], wantInit) {
t.Fatalf("init direct target call = %#v, want %#v", caller.calls[0], wantInit)
}
wantCommit := chatFilePathCall{
server: "im",
tool: "commit_conversation_file_upload",
args: map[string]any{
"openDingTalkId": "D-target",
"uploadKey": "upload-key",
"fileName": "report.pdf",
"fileSize": int64(len(payload)),
"md5": fileMD5,
},
}
if !reflect.DeepEqual(caller.calls[1], wantCommit) {
t.Fatalf("commit direct target call = %#v, want %#v", caller.calls[1], wantCommit)
}
send := caller.calls[2]
if send.server != "chat" || send.tool != "send_personal_message" || send.args["msgType"] != "file" {
t.Fatalf("send direct target call = %#v", send)
}
if send.args["receiverOpenDingTalkId"] != "D-target" {
t.Fatalf("send direct target = %#v", send.args)
}
if _, ok := send.args["openDingTalkId"]; ok {
t.Fatalf("send direct target leaked upload target key: %#v", send.args)
}
}
func TestChatMessageSendFilePathRequiresFileMessageType(t *testing.T) {
previousDeps := deps
t.Cleanup(func() { deps = previousDeps })
filePath := filepath.Join(t.TempDir(), "image.png")
if err := os.WriteFile(filePath, []byte("png"), 0o600); err != nil {
t.Fatal(err)
}
caller := &chatFilePathCaller{}
err := runChatCoverageCommand(t, caller,
"message", "send",
"--group=cid",
"--file-path="+filePath,
)
if err == nil {
t.Fatal("bare --file-path succeeded without --msg-type=file")
}
if len(caller.calls) != 0 {
t.Fatalf("bare --file-path made remote calls: %#v", caller.calls)
}
}
+170 -28
View File
@@ -14,23 +14,38 @@
package helpers
import (
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/spf13/cobra"
)
const chatMediaUploadReplacement = "dws chat message send --msg-type file --file-path <本地路径>"
var (
chatMediaResolveAppToken = mediaResolveAppToken
chatMediaUploadFile = mediaUploadFile
mediaCreateFormFile = func(w *multipart.Writer, field, name string) (io.Writer, error) { return w.CreateFormFile(field, name) }
mediaOpenFile = os.Open
mediaCopyFile = io.Copy
)
func newChatMediaGroup() *cobra.Command {
media := &cobra.Command{
Use: "media",
Short: "已下线:媒体文件上传兼容入口",
Deprecated: "本地图片和文件请改用 " + chatMediaUploadReplacement,
Short: "媒体文件管理",
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(*cobra.Command, []string) error {
return chatMediaUploadDownlineError()
},
RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() },
}
media.AddCommand(newChatMediaUploadCommand())
return media
@@ -38,33 +53,160 @@ func newChatMediaGroup() *cobra.Command {
func newChatMediaUploadCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "upload",
Short: "已下线:请通过 chat message send 直接发送本地文件",
Deprecated: "请改用 " + chatMediaUploadReplacement,
Long: `此命令仅为 1.x 命令行兼容保留,不再读取应用凭证或调用旧版媒体上传接口。
发送本地图片或文件时,请使用 chat message send --msg-type file --file-path。
该路径会把图片作为可下载的 file 消息发送,不会生成 mediaId,也不会渲染成内联 image 消息。
如果上游已经提供 mediaId,仍可使用 chat message send --msg-type image --media-id。`,
Example: " dws chat message send --group <openConversationId> --msg-type file --file-path ./screenshot.png\n" +
" dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path ./report.pdf",
Use: "upload",
Short: "上传图片获取 mediaId(用于 chat message send --msg-type image)",
Example: " dws chat media upload --file ./screenshot.png\n" +
" dws chat media upload --file ./photo.jpg --type image",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(*cobra.Command, []string) error {
return chatMediaUploadDownlineError()
RunE: func(cmd *cobra.Command, args []string) error {
filePath, _ := cmd.Flags().GetString("file")
filePath = strings.TrimSpace(filePath)
if filePath == "" {
return apperrors.NewValidation("--file is required")
}
fi, err := os.Stat(filePath)
if err != nil {
return apperrors.NewValidation("cannot read file: " + err.Error())
}
if fi.IsDir() {
return apperrors.NewValidation(filePath + " is a directory")
}
mediaType, _ := cmd.Flags().GetString("type")
mediaType = strings.TrimSpace(strings.ToLower(mediaType))
if mediaType == "" {
mediaType = "image"
}
ctx, cancel := context.WithTimeout(cmd.Context(), 2*time.Minute)
defer cancel()
token, err := chatMediaResolveAppToken(ctx)
if err != nil {
return err
}
mediaID, err := chatMediaUploadFile(ctx, token, filePath, mediaType)
if err != nil {
return err
}
return writeCommandPayload(cmd, map[string]any{
"success": true,
"mediaId": mediaID,
})
},
}
// Keep the historical flags so existing argv remains parseable while the
// 1.x compatibility command returns an actionable migration error.
cmd.Flags().String("file", "", "旧版兼容参数;本地文件请改用 chat message send --file-path")
cmd.Flags().String("type", "image", "旧版兼容参数;不再执行媒体上传")
cmd.Flags().String("file", "", "本地文件路径 (必填)")
cmd.Flags().String("type", "image", "媒体类型: image/voice/video/file")
return cmd
}
func chatMediaUploadDownlineError() error {
return apperrors.NewValidation(
"chat media upload 已下线,当前 CLI 不提供本地文件到 mediaId 的上传能力。" +
" 本地图片或文件请改用: " + chatMediaUploadReplacement +
";已有 mediaId 时可使用 dws chat message send --msg-type image --media-id <mediaId>。",
)
func mediaResolveAppToken(ctx context.Context) (string, error) {
return mediaResolveAppTokenWithRequest(ctx, http.NewRequestWithContext)
}
type mediaRequestFactory func(context.Context, string, string, io.Reader) (*http.Request, error)
func mediaResolveAppTokenWithRequest(ctx context.Context, newRequest mediaRequestFactory) (string, error) {
appKey := os.Getenv("DWS_CLIENT_ID")
appSecret := os.Getenv("DWS_CLIENT_SECRET")
if appKey == "" || appSecret == "" {
return "", apperrors.NewAuth(
"缺少应用凭证。chat media upload 需要 DWS_CLIENT_ID / DWS_CLIENT_SECRET 环境变量。\n" +
"请使用 dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET> 登录。")
}
endpoint := url.URL{Scheme: "https", Host: "oapi.dingtalk.com", Path: "/gettoken"}
endpoint.RawQuery = url.Values{
"appkey": []string{appKey},
"appsecret": []string{appSecret},
}.Encode()
req, err := newRequest(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return "", apperrors.NewAuth("构造访问令牌请求失败: " + err.Error())
}
resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
if err != nil {
return "", apperrors.NewAuth("获取访问令牌失败: " + err.Error())
}
defer resp.Body.Close()
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if resp.StatusCode >= 400 {
return "", apperrors.NewAuth(fmt.Sprintf("获取访问令牌 HTTP %d: %s", resp.StatusCode, string(raw)))
}
var parsed struct {
AccessToken string `json:"access_token"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.Unmarshal(raw, &parsed); err != nil {
return "", apperrors.NewAuth("gettoken 响应解析失败: " + string(raw))
}
if parsed.ErrCode != 0 || parsed.AccessToken == "" {
return "", apperrors.NewAuth(fmt.Sprintf("gettoken errcode=%d errmsg=%s", parsed.ErrCode, parsed.ErrMsg))
}
return parsed.AccessToken, nil
}
func mediaUploadFile(ctx context.Context, token, filePath, mediaType string) (string, error) {
return mediaUploadFileWithRequest(ctx, token, filePath, mediaType, http.NewRequestWithContext)
}
func mediaUploadFileWithRequest(ctx context.Context, token, filePath, mediaType string, newRequest mediaRequestFactory) (string, error) {
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)
endpoint := url.URL{Scheme: "https", Host: "oapi.dingtalk.com", Path: "/media/upload"}
endpoint.RawQuery = url.Values{
"access_token": []string{token},
"type": []string{mediaType},
}.Encode()
req, err := newRequest(ctx, http.MethodPost, endpoint.String(), pr)
if err != nil {
_ = pr.CloseWithError(err)
_ = pw.CloseWithError(err)
return "", apperrors.NewAPI("construct media upload request: " + err.Error())
}
req.Header.Set("Content-Type", writer.FormDataContentType())
go func() {
defer pw.Close()
defer writer.Close()
part, err := mediaCreateFormFile(writer, "media", filepath.Base(filePath))
if err != nil {
pw.CloseWithError(err)
return
}
f, err := mediaOpenFile(filePath)
if err != nil {
pw.CloseWithError(err)
return
}
defer f.Close()
if _, err := mediaCopyFile(part, f); err != nil {
pw.CloseWithError(err)
}
}()
resp, err := (&http.Client{Timeout: 2 * time.Minute}).Do(req)
if err != nil {
return "", apperrors.NewAPI("media upload failed: " + err.Error())
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if resp.StatusCode >= 400 {
return "", apperrors.NewAPI(fmt.Sprintf("media upload HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body))))
}
var parsed struct {
MediaID string `json:"media_id"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.Unmarshal(body, &parsed); err != nil {
return "", apperrors.NewAPI("media upload 响应解析失败: " + string(body))
}
if parsed.ErrCode != 0 || strings.TrimSpace(parsed.MediaID) == "" {
return "", apperrors.NewAPI(fmt.Sprintf("media upload errcode=%d errmsg=%s body=%s", parsed.ErrCode, parsed.ErrMsg, string(body)))
}
return strings.TrimSpace(parsed.MediaID), nil
}
@@ -1,36 +1,87 @@
package helpers
import (
"bytes"
"context"
"errors"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
func TestCrossPlatformCoverageChatMediaUploadIsDeprecatedCompatibilityStub(t *testing.T) {
group := newChatMediaGroup()
if group.Deprecated == "" || group.Hidden || !group.Runnable() {
t.Fatalf("media group compatibility contract: deprecated=%q hidden=%v runnable=%v", group.Deprecated, group.Hidden, group.Runnable())
func TestCrossPlatformCoverageChatMediaUploadCommandRemainingCoverage(t *testing.T) {
file := filepath.Join(t.TempDir(), "image.png")
if err := os.WriteFile(file, []byte("image"), 0o600); err != nil {
t.Fatal(err)
}
if err := executeFilterCoverage(t, newChatMediaUploadCommand(), "--file", t.TempDir()); err == nil {
t.Fatal("directory upload returned nil")
}
origResolve, origUpload := chatMediaResolveAppToken, chatMediaUploadFile
t.Cleanup(func() {
chatMediaResolveAppToken = origResolve
chatMediaUploadFile = origUpload
})
chatMediaResolveAppToken = func(context.Context) (string, error) { return "", errors.New("token") }
if err := executeFilterCoverage(t, newChatMediaUploadCommand(), "--file", file); err == nil {
t.Fatal("token failure returned nil")
}
chatMediaResolveAppToken = func(context.Context) (string, error) { return "token", nil }
chatMediaUploadFile = func(context.Context, string, string, string) (string, error) { return "", errors.New("upload") }
if err := executeFilterCoverage(t, newChatMediaUploadCommand(), "--file", file); err == nil {
t.Fatal("upload failure returned nil")
}
var mediaType string
chatMediaUploadFile = func(_ context.Context, _, _, typ string) (string, error) {
mediaType = typ
return "media-id", nil
}
cmd := newChatMediaUploadCommand()
if cmd.Deprecated == "" || cmd.Hidden || !cmd.Runnable() {
t.Fatalf("media upload compatibility contract: deprecated=%q hidden=%v runnable=%v", cmd.Deprecated, cmd.Hidden, cmd.Runnable())
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"--file", file, "--type", " "})
if err := cmd.Execute(); err != nil || mediaType != "image" || !strings.Contains(out.String(), "media-id") {
t.Fatalf("success type=%q output=%q err=%v", mediaType, out.String(), err)
}
for _, flag := range []string{"file", "type"} {
if cmd.Flags().Lookup(flag) == nil {
t.Fatalf("media upload lost historical --%s flag", flag)
}
func TestCrossPlatformCoverageMediaUploadMultipartWriterRemainingFailures(t *testing.T) {
file := filepath.Join(t.TempDir(), "image.png")
if err := os.WriteFile(file, []byte("image"), 0o600); err != nil {
t.Fatal(err)
}
origTransport := http.DefaultTransport
origCreate, origOpen, origCopy := mediaCreateFormFile, mediaOpenFile, mediaCopyFile
t.Cleanup(func() {
http.DefaultTransport = origTransport
mediaCreateFormFile, mediaOpenFile, mediaCopyFile = origCreate, origOpen, origCopy
})
http.DefaultTransport = roundTripFunc(func(req *http.Request) (*http.Response, error) {
_, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(`{"media_id":"id"}`)), Request: req}, nil
})
mediaCreateFormFile = func(*multipart.Writer, string, string) (io.Writer, error) { return nil, errors.New("part") }
if _, err := mediaUploadFile(context.Background(), "token", file, "image"); err == nil {
t.Fatal("form part failure returned nil")
}
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs([]string{"--file", "/path/that/does/not/exist.png", "--type", "image"})
err := cmd.Execute()
if err == nil {
t.Fatal("deprecated media upload returned nil error")
mediaCreateFormFile = origCreate
mediaOpenFile = func(string) (*os.File, error) { return nil, errors.New("open") }
if _, err := mediaUploadFile(context.Background(), "token", file, "image"); err == nil {
t.Fatal("open failure returned nil")
}
for _, want := range []string{"已下线", "chat message send", "--msg-type file", "--file-path", "--media-id"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("media upload migration error missing %q: %v", want, err)
}
mediaOpenFile = origOpen
mediaCopyFile = func(io.Writer, io.Reader) (int64, error) { return 0, errors.New("copy") }
if _, err := mediaUploadFile(context.Background(), "token", file, "image"); err == nil {
t.Fatal("copy failure returned nil")
}
}
+83 -1
View File
@@ -3,11 +3,14 @@ package helpers
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
@@ -114,7 +117,86 @@ func TestCrossPlatformCoverageMailHTTPTransfersCoverage(t *testing.T) {
}
}
func TestCrossPlatformCoverageDocVersionsCoverage(t *testing.T) {
func TestCrossPlatformCoverageChatMediaHTTPAndDocVersionsCoverage(t *testing.T) {
previousTransport := http.DefaultTransport
mode := "success"
var requestQuery url.Values
http.DefaultTransport = roundTripFunc(func(req *http.Request) (*http.Response, error) {
if mode == "transport-error" {
return nil, errors.New("transport")
}
if req.Body != nil {
if _, err := io.ReadAll(req.Body); err != nil {
return nil, err
}
}
requestQuery = req.URL.Query()
status := http.StatusOK
body := `{"access_token":"token","errcode":0}`
if strings.Contains(req.URL.Path, "media/upload") {
body = `{"media_id":"media-id","errcode":0}`
}
switch mode {
case "http-error":
status = http.StatusInternalServerError
body = `failure`
case "invalid-json":
body = `{`
case "api-error":
body = `{"errcode":1,"errmsg":"failed"}`
}
return &http.Response{StatusCode: status, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body)), Request: req}, nil
})
t.Cleanup(func() { http.DefaultTransport = previousTransport })
t.Setenv("DWS_CLIENT_ID", "")
t.Setenv("DWS_CLIENT_SECRET", "")
if _, err := mediaResolveAppToken(context.Background()); err == nil {
t.Fatal("missing media credentials succeeded")
}
t.Setenv("DWS_CLIENT_ID", "client")
t.Setenv("DWS_CLIENT_SECRET", "secret")
for _, current := range []string{"transport-error", "http-error", "invalid-json", "api-error", "success"} {
mode = current
_, _ = mediaResolveAppToken(context.Background())
}
t.Setenv("DWS_CLIENT_ID", "client&scope=chat")
t.Setenv("DWS_CLIENT_SECRET", "secret=with?reserved")
mode = "success"
if _, err := mediaResolveAppToken(context.Background()); err != nil {
t.Fatal(err)
}
if requestQuery.Get("appkey") != "client&scope=chat" || requestQuery.Get("appsecret") != "secret=with?reserved" {
t.Fatalf("token query was not encoded: %v", requestQuery)
}
file := filepath.Join(t.TempDir(), "image.png")
if err := os.WriteFile(file, []byte("image"), 0o600); err != nil {
t.Fatal(err)
}
for _, current := range []string{"transport-error", "http-error", "invalid-json", "api-error", "success"} {
mode = current
_, _ = mediaUploadFile(context.Background(), "token", file, "image")
}
mode = "success"
if _, err := mediaUploadFile(context.Background(), "token&scope=chat", file, "image&type=file"); err != nil {
t.Fatal(err)
}
if requestQuery.Get("access_token") != "token&scope=chat" || requestQuery.Get("type") != "image&type=file" {
t.Fatalf("upload query was not encoded: %v", requestQuery)
}
mode = "success"
_, _ = mediaUploadFile(context.Background(), "token", filepath.Join(t.TempDir(), "missing"), "image")
requestFailure := func(context.Context, string, string, io.Reader) (*http.Request, error) {
return nil, errors.New("request")
}
if _, err := mediaResolveAppTokenWithRequest(context.Background(), requestFailure); err == nil {
t.Fatal("token request construction failure returned nil")
}
if _, err := mediaUploadFileWithRequest(context.Background(), "token", file, "image", requestFailure); err == nil {
t.Fatal("upload request construction failure returned nil")
}
for _, value := range []any{float64(3), float64(3.5), "3", "bad", jsonNumber("3"), jsonNumber("bad"), true} {
_ = docVersionNumberMatches(value, 3)
}
@@ -16,7 +16,6 @@ package chat
import (
"context"
"io"
"reflect"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
@@ -130,48 +129,3 @@ func TestCrossPlatformCoverageCompatibilityAliases(t *testing.T) {
})
}
}
func TestCrossPlatformCoverageChatIDHelpers(t *testing.T) {
t.Run("recognize open DingTalk IDs", func(t *testing.T) {
tests := []struct {
value string
want bool
}{
{value: "DingTalk-open-id", want: true},
{value: " dingtalk-open-id ", want: true},
{value: "user-id", want: false},
{value: " ", want: false},
}
for _, tc := range tests {
if got := isOpenID(tc.value); got != tc.want {
t.Errorf("isOpenID(%q) = %v, want %v", tc.value, got, tc.want)
}
}
})
t.Run("split mixed IDs", func(t *testing.T) {
userIDs, openIDs := splitIDs([]string{" user-1 ", "", "D-open-1", "d-open-2", "user-2"})
if want := []string{"user-1", "user-2"}; !reflect.DeepEqual(userIDs, want) {
t.Fatalf("user IDs = %#v, want %#v", userIDs, want)
}
if want := []string{"D-open-1", "d-open-2"}; !reflect.DeepEqual(openIDs, want) {
t.Fatalf("open IDs = %#v, want %#v", openIDs, want)
}
})
t.Run("parse numeric IDs", func(t *testing.T) {
got, err := toInt64Slice([]string{" 1 ", "", "-2"})
if err != nil {
t.Fatal(err)
}
if want := []int64{1, -2}; !reflect.DeepEqual(got, want) {
t.Fatalf("numeric IDs = %#v, want %#v", got, want)
}
if _, err := toInt64Slice([]string{"not-a-number"}); err == nil {
t.Fatal("invalid integer unexpectedly succeeded")
}
if _, err := toInt64Slice([]string{"", " "}); err == nil {
t.Fatal("empty integer list unexpectedly succeeded")
}
})
}
+2 -18
View File
@@ -39,24 +39,8 @@ func defaultHooks() *Hooks {
base["claw-type"] = DefaultOSSClawType
return base
},
StaticServers: openStaticServers,
SupplementServers: openSupplementServers,
VisibleProducts: openVisibleProducts,
}
}
// openSupplementServers returns helper-only MCP endpoints owned by the open
// CLI. They are callable by explicit server ID but are deliberately excluded
// from VisibleProducts, so no top-level product command is generated. They
// stay separate from syncdata.StaticServers because only explicitly wired CLI
// helpers may call them; they are not public MCP product surfaces.
func openSupplementServers() []ServerInfo {
return []ServerInfo{
{
ID: "mcp-meta",
Name: "MCP 元服务",
Endpoint: "https://mcp-gw.dingtalk.com/server/89833ea5debf30c260a07ffcb5127ffa3bf0c830cd76babadb293d9861485d44",
},
StaticServers: openStaticServers,
VisibleProducts: openVisibleProducts,
}
}
-20
View File
@@ -70,24 +70,4 @@ func TestOpenVisibleProductsExcludesCompatibilityOnlyCommands(t *testing.T) {
t.Fatal("conference must remain compatibility-only and not be added to StaticServers")
}
}
if byID["mcp-meta"] {
t.Fatal("mcp-meta is helper-only and must not appear in VisibleProducts")
}
}
func TestOpenSupplementServersIncludesMCPMeta(t *testing.T) {
servers := openSupplementServers()
for _, server := range servers {
if server.ID != "mcp-meta" {
continue
}
if server.Endpoint == "" {
t.Fatal("mcp-meta has empty endpoint")
}
if len(server.Prefixes) != 0 {
t.Fatal("mcp-meta must remain helper-only without command prefixes")
}
return
}
t.Fatal("openSupplementServers() missing mcp-meta")
}
+6 -6
View File
@@ -273,8 +273,8 @@ alidocs 链接表面长得一样(`https://alidocs.dingtalk.com/i/nodes/{id}`
- 已有 userId 时直接使用 `--user`;已有 openDingTalkId 时使用 `--open-dingtalk-id`
- 纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId
- 富媒体消息(image/file)单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送
- "发本地图片/文件/语音/视频到群里" — `dws chat message send ... --msg-type file --file-path <本地路径>`,CLI 内部自动上传并发送;png/jpg/pdf/mp4/zip 等任意扩展名都走这条,接收方看到的是可下载的文件附件。图片不会内联渲染,也不会生成 mediaId
- "用已有 mediaId 发内联图片" — 仅当上游已经提供有效 mediaId 时,使用 `dws chat message send ... --msg-type image --media-id <mediaId>`;DWS CLI 不能把本地图片转换成 mediaId
- "发文件/语音/视频到群里" — `dws chat message send ... --msg-type file --file-path <本地路径>`,CLI 内部自动上传并发送(png/jpg/pdf/mp4/zip… 任意扩展名都走这条,但**都作为「文件」消息**发出,接收方看到的是可下载的文件条目)
- "发张图片/截图(要在聊天里内联渲染成图,不是文件)" — 走图片消息链路:先 `dt_media_upload` 拿 mediaId,再 `dws chat message send ... --msg-type image --media-id <mediaId>`。**注意**:用 `--msg-type file` 发 .png 只会显示为[文件](fileId),不会渲染成图片;要图片效果必须走 `--msg-type image`
- "发图片+文字说明" — 不要硬塞进一条命令;先发图片/文件消息再补一条 `--text "..."` 即可
```bash
@@ -282,7 +282,7 @@ dws chat message send --group <openConversationId> --msg-type file --file-path .
dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path ./report.pdf --format json
```
> ❌ 反模式:先把本地文件转换成 mediaId,或先经钉盘上传再拼装聊天消息。本地图片/文件一律用 `--msg-type file --file-path` 直发;`--msg-type image --media-id` 只接受上游已经提供的有效 mediaId。
> ❌ 反模式:调 `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` 等前置工具再 `--msg-type image --media-id`。这是**旧链路**,仅当上游已持有 mediaId 才用;新场景一律 `--file-path` 直发,避免长链路与"空白图"现象。
> 富媒体消息单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送。
**用 `chat message send-by-bot` 的场景**:
@@ -453,7 +453,7 @@ dws chat message send --group <openConversationId> --msg-type file --file-path <
dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path <本地路径> --format json
```
支持任意扩展名(`.png/.jpg/.gif/.bmp/.webp/.pdf/.doc/.xls/.zip/.mp3/.wav/.mp4/.avi` …),CLI 自动完成上传与发送,不需要任何前置工具调用。图片文件同样作为可下载的 file 附件发送,不会内联渲染,也不会生成 mediaId。
支持任意扩展名(`.png/.jpg/.gif/.bmp/.webp/.pdf/.doc/.xls/.zip/.mp3/.wav/.mp4/.avi` …),CLI 自动识别并处理。**无需** `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` / `chat conversation-info` / `chat file upload` 等任何前置工具调用。
### 图片/文件 + 文字说明
@@ -464,9 +464,9 @@ dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file
dws chat message send --open-dingtalk-id <openDingTalkId> --text "这是本周数据汇总" --format json
```
### 上游 mediaId — 仅兼容场景
### 旧链路(mediaId)— 仅兼容场景
仅当上游已经提供有效的 `@lQL...` 形式 mediaId 时使用;DWS CLI 不提供本地文件到 mediaId 的转换能力:
仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时使用:
```bash
dws chat message send --group <openConversationId> --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json
+89 -45
View File
@@ -182,8 +182,8 @@ Flags:
--group string 群聊 openConversationId (必填)
--icon-media-id string 群头像 mediaId (必填)
```
> `--icon-media-id` 有本地格式校验:必须是 `@` 开头的、由可信上游提供的媒体 ID,非法格式会在本地直接报错。
> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必使用真实上游媒体上传能力拿到有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传命令。
> `--icon-media-id` 有本地格式校验:必须是 `@` 开头的媒体 ID(如 `dt_media_upload` 的返回值),非法格式会在本地直接报错。
> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必用 `dt_media_upload` / `chat media upload` 上传真实图片拿到的 mediaId。
#### 更新群设置 — 更新指定群聊的设置项
@@ -527,17 +527,17 @@ Flags:
--group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。
若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。
--群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。
--本地图片、文件、音频或视频统一用 --msg-type file --file-path;图片会作为可下载的文件附件发送。--msg-type image --media-id 仅用于上游已经提供有效 mediaId 的场景。
--富媒体消息:通过 --msg-type 指定类型(image/file/audio/video),audio/video 是 file 的语义别名,发给服务端仍按 file 发送;必须根据文件扩展名判断 msgType 后再发送。
```
Usage:
dws chat message send [flags] [<text>]
富媒体消息路由:
场景 → msgType → 发送参数
本地图片/文件/音频/视频 → file → --file-path <本地路径>
上游已提供有效 mediaId 的内联图片 → image → --media-id <mediaId>
注意:本地 .png/.jpg 也按 file 发送,接收方看到的是可下载附件;DWS CLI 不能把本地文件转换成 mediaId。
富媒体消息 msgType 决策(必须按此规则判断,不可跳过):
文件扩展名 → msgType → 发送参数
.jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py <URL>` 提取 mediaId → --msg-type image --media-id
.mp3/.wav/.m4a/.aac/.flac → audio(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type audio
.mp4/.mov/.avi/.mkv/.webm → video(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type video
其他所有(.pdf/.doc/.xls/.zip 等) → file → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type file
Example:
dws chat message send --group <openconversation_id> --text "hello"
@@ -549,13 +549,15 @@ Example:
dws chat message send --group <openconversation_id> --text "hello" --uuid "unique-id-123"
dws chat message send --group <openconversation_id> --at-all "<@all> 请大家注意"
dws chat message send --group <openconversation_id> --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收"
# 本地图片/文件/音频/视频统一作为 file 附件发送
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png
dws chat message send --group <openconversation_id> --msg-type file --file-path ./report.pdf
dws chat message send --group <openconversation_id> --msg-type file --file-path ./recording.mp3
dws chat message send --group <openconversation_id> --msg-type file --file-path ./demo.mp4
# 仅当上游已有有效 mediaId 时发送内联图片
# 发送图片
dws chat message send --group <openconversation_id> --msg-type image --media-id <mediaId>
# 发送文件/音频/视频(audio/video 是 file 的语义别名)
# 先 dws chat conversation-info --group <id> 获取 spaceId(取 newCSpaceIdIM)
# 再 dws drive upload --file <文件> --space-id <spaceId> 上传
# 再 dws drive info --file-id <fileId> --space-id <spaceId> 获取 dentryId
dws chat message send --group <openconversation_id> --msg-type file --dentry-id <dentryId> --space-id 24557356340 --file-name "report.pdf" --file-type "pdf" --file-path "/report.pdf" --file-size 234724
dws chat message send --group <openconversation_id> --msg-type audio --file-path ./recording.mp3
dws chat message send --group <openconversation_id> --msg-type video --file-path ./demo.mp4
Flags:
--text string 消息内容(推荐使用,也可用位置参数)
--group string 群聊 openconversation_id(群聊时必填)
@@ -564,14 +566,14 @@ Flags:
--title string 消息标题(可选,默认「消息」)
--at-all @所有人(仅群聊时生效,可选,默认 false)
--at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选)
--media-id string 上游提供的有效图片 mediaId(仅 msgType=image)
--msg-type string 消息类型: image/file/audio/video(本地文件统一使用 file;image 仅配合已有 mediaId)
--dentry-id int64 已有钉盘文件 dentryId(兼容参数,非本地文件发送的默认路径)
--space-id int64 已有钉盘文件空间 ID(元数据兼容模式)
--file-name string 已有钉盘文件名(元数据兼容模式)
--file-type string 已有钉盘文件类型/扩展名(元数据兼容模式)
--file-path string 本地文件路径(本地图片/文件/音视频配合 msgType=file 使用)
--file-size int64 已有钉盘文件大小,单位字节(元数据兼容模式)
--media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py <URL>` 提取,仅 msgType=image)
--msg-type string 消息类型: image/file/audio/video(audio/video 是 file 别名;image 用 mediaId,file/audio/video 用钉盘上传)
--dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取)
--space-id int64 钉盘空间 ID(msgType=file 时必填)
--file-name string 文件名(msgType=file 时必填)
--file-type string 文件类型/扩展名(msgType=file 时必填)
--file-path string 文件路径(msgType=file 时必填)
--file-size int64 文件大小,单位字节(msgType=file 时必填)
--uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选)
--ai-tag 消息是否带 AI 发送角标(可选,默认 true)
@@ -584,13 +586,36 @@ Flags:
- **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可:
1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行
2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `<br>`),或直接写 HTML 的 `<br>` 标签
- 本地图片、文档、压缩包、音频和视频统一使用 `--msg-type file --file-path <本地路径>`;图片会成为可下载的 file 附件,不会内联渲染,也不会生成 mediaId
- `--msg-type image --media-id` 仅接受上游已经提供的有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传或转换能力
- audio/video 仍是兼容的 file 语义别名,但本地文件的推荐路径保持为 `--msg-type file --file-path`
- dentryId/spaceId 等参数仅用于调用方已经持有钉盘文件元数据的兼容场景,不是发送本地文件的前置步骤
- 富媒体消息类型与参数对应关系:
- image(图片):--msg-type image --media-id
- audio/video:file 的语义别名,发给服务端仍是 file;可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type audio 或 --msg-type video --dentry-id --space-id --file-name --file-type --file-path --file-size
- file(文档/压缩包等其他非图片文件):可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size
- mediaId 通过 dt_media_upload 上传获得,必须用脚本提取:`python scripts/extract_media_id.py "<URL>"`(输出如 @lQLPxxx,直接用于 --media-id)。禁止手动从 URL 中截取或拼接 mediaId,手动解析会因 URL 格式不稳定导致尺寸后缀残留
- --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持)
- 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送
- 发送文字 + 文件时,先发送 `--msg-type file --file-path` 文件消息,再补一条文本或 Markdown 说明;这是两条独立消息
- 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,音频(.mp3/.wav/.m4a/.aac/.flac)→audio,视频(.mp4/.mov/.avi/.mkv/.webm)→video,其他所有→file;不可跳过此判断步骤。audio/video 是 file 别名,不代表不支持音频/视频
- 20MB 降级:图片超过 20MB 时 dt_media_upload 会失败,必须降级走钉盘上传 + Markdown 嵌入方式发送(参见「发送图片+文字消息」章节)。音频/视频/文件走钉盘上传无 20MB 限制
- 发送文字 + 文件混合消息时的完整流程:除了将文件以 Markdown 链接内嵌到文字消息中发送一条 md 消息外,还必须额外逐个发送独立的文件消息(--msg-type file),确保接收方可以直接下载原始文件。即:先发一条包含文字和文件链接的 md 消息,再对每个涉及的文件各发一条 --msg-type file 的文件消息
```
### media (上传媒体获取 mediaId)
#### 上传图片/媒体获取 mediaId — 用于 chat message send --msg-type image 等
⚠️ 前置条件:本命令需要应用凭证。必须已通过 `dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>` 登录,或设置环境变量 `DWS_CLIENT_ID` / `DWS_CLIENT_SECRET`;否则报"缺少应用凭证"。这与其他 chat 命令走用户登录态不同。
```
Usage:
dws chat media upload [flags]
Example:
dws chat media upload --file ./screenshot.png
dws chat media upload --file ./photo.jpg --type image
Flags:
--file string 本地文件路径 (必填)
--type string 媒体类型: image/voice/video/file(默认 image)
注意:
- 返回的 mediaId 可直接用于 chat message send --msg-type image --media-id
- 发图片+文字时,agent 侧一般用独立的 dt_media_upload 工具;本命令是 dws 内置的等价上传入口
```
### file (会话文件上传,已下线)
@@ -1140,7 +1165,7 @@ Flags:
#### 获取会话基础信息 — 含会话关联的钉盘共享空间 ID
获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。该 ID 可用于独立的钉盘存储操作;发送本地图片或文件到聊天不需要先调用本命令,直接使用 `chat message send --msg-type file --file-path`。
获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。发送文件消息前需先调用此命令获取 spaceId,再用 drive upload --space-id 上传文件到共享空间。
```
Usage:
dws chat conversation-info [flags]
@@ -1156,8 +1181,8 @@ Flags:
注意:
- --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 返回值中的 newCSpaceIdIM 为会话共享空间 ID,可用于调用方明确需要的钉盘存储流程
- 该 ID 不是发送本地聊天附件的前置条件;本地附件直接走 `chat message send --msg-type file --file-path`
- 返回值中的 newCSpaceIdIM 为会话共享空间 ID,用于 drive upload --space-id 参数
- 上传到共享空间的文件对方才能打开,上传到个人空间的文件对方无法访问
```
#### 引用回复消息 — 引用某条消息并回复文字(单聊/群聊均可)
@@ -1865,8 +1890,7 @@ Flags:
用户说"转发话题/转发话题消息" → `chat message forward-topic`
用户说"置顶消息/把消息置顶" → `chat message set-top-msg`
用户说"取消置顶消息/撤销消息置顶" → `chat message unset-top-msg`
用户说"发送/上传本地图片或媒体到聊天" → `chat message send --msg-type file --file-path <本地路径>`
用户明确只要 mediaId → DWS CLI 当前不提供本地上传入口;仅在上游已有有效 mediaId 时使用 `chat message send --msg-type image --media-id`
用户说"上传图片拿mediaId/上传媒体" → `chat media upload`
用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots`
用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot`
用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 botOpenDingTalkId 可发单聊)
@@ -1892,7 +1916,7 @@ Flags:
- `chat message list-topic-replies` — 拉取群话题的回复消息列表
- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页
- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页
- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;本地图片/文件/音视频统一用 `--msg-type file --file-path`,其中图片显示为可下载附件而非内联图片;`--msg-type image --media-id` 只用于上游已经提供有效 mediaId 的场景,DWS CLI 不能从本地文件生成 mediaId
- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file/audio/video(audio/video 是 file 别名),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件可直接用 --file-path 或先获取会话共享空间再上传钉盘
- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群)
- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在)
- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag
@@ -2049,16 +2073,34 @@ dws chat message send-by-bot --robot-code <robot-code> --group <openconversation
```
### 发送图片/文件 + 文字说明(两条消息)
### 发送图片+文字 / 文件+文字消息(跨产品: drive → chat)
本地图片和文件先用 `--msg-type file --file-path` 发送,再补一条文本消息说明;这是两条独立消息,不需要媒体上传或钉盘前置步骤。图片会显示为可下载的文件附件,不会内联渲染。
- **图片+文字**:图片**必须**通过 `dt_media_upload` 工具(非 dws 命令,是 agent 可调用的独立 tool)上传获取 mediaId,然后用 Markdown 嵌入方式发送。**禁止**使用钉盘上传图片。
- **文件+文字**:文件通过钉盘上传 + Markdown 嵌入方式发送。
纯发图片/文件(不带文字)的完整流程见 [intent-guide.md](../intent-guide.md) 对应章节。
```bash
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png --format json
dws chat message send --group <openconversation_id> --text "这是本周的数据汇总" --format json
```
# === 图片+文字 ===
# Step 1: 调用 dt_media_upload 工具上传图片(这是一个独立的 tool,不是 dws 命令)
# dt_media_upload 会返回 mediaId(如 @lQLPxxx)
# 提取 mediaId 可使用脚本: python extract_media_id.py "<返回的URL>"
如果调用方已经从上游取得有效 mediaId,可以先用 `--msg-type image --media-id` 发送内联图片,再补一条文本消息;DWS CLI 本身不能把本地图片转换成 mediaId。
# Step 2: 用 Markdown 语法发送(mediaId 作为图片引用)
dws chat message send --group <openconversation_id> \
--text "![截图](mediaId) 这是本周的数据汇总" --format json
# === 文件+文字 ===
# Step 1: 上传文件到钉盘
dws drive upload --file "报告.pdf" --format json
# Step 2: 获取下载链接
dws drive download --file-id <dentryUuid> --format json
# Step 3: 用 Markdown 语法发送
dws chat message send --group <openconversation_id> \
--text "[报告.pdf](下载链接) 这是季度报告" --format json
```
### 发送图片 / 文件(统一一条命令)
@@ -2080,7 +2122,7 @@ dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file
dws chat message send --open-dingtalk-id <openDingTalkId> --text "这是本周数据汇总" --format json
```
**已有 mediaId(仅当上游已经提供有效 mediaId 时才用)**:
**旧链路(仅当上游已经持有 `dt_media_upload` 返回的 mediaId 时才用)**:
```bash
dws chat message send --group <openConversationId> --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json
@@ -2140,7 +2182,7 @@ Flags:
| `chat message search` | `nextCursor` | 下次 message search 的 --cursor |
| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor |
| `chat search-common` | `openConversationId` | message send/list 等的 --group |
| `chat conversation-info` | `newCSpaceIdIM` | 独立钉盘存储流程的共享空间 ID;不是发送本地聊天附件的前置条件 |
| `chat conversation-info` | `newCSpaceIdIM` | drive upload 的 --space-id(发送文件消息前获取共享空间) |
| `chat file upload` | 无(已下线) | 不要调用;常规发图/发文件用 `chat message send --msg-type file --file-path` |
| `chat message list` | `openMsgId` | message read-status 的 --message-id |
| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id |
@@ -2148,6 +2190,7 @@ Flags:
| `chat category list` | `categoryId` | category list-conversations 的 --category-id |
| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId |
| `chat message send-card` | `bizId` | update-card 的 --biz-id |
| `drive download` | 下载链接 | message send 的 Markdown 图片/链接语法 |
| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id |
| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group |
@@ -2164,7 +2207,7 @@ Flags:
- 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送
- 此参数适用于 `chat message send`(群聊和单聊均支持)
- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;本地图片/文件/音视频统一用 `--msg-type file --file-path`,其中图片是可下载附件;`--msg-type image --media-id` 仅用于上游已经提供有效 mediaId 的内联图片
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file/audio/video),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,音频→audio,视频→video,其他→file,不可跳过此判断
- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor
- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一
- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor`
@@ -2185,7 +2228,7 @@ Flags:
- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner`
- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久)
- `chat group quit` 退出群聊,需传 --group(openConversationId)
- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和由可信上游提供的有效 --icon-media-id(mediaId);DWS CLI 不能从本地图片生成该 ID
- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId)
- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启)
- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容
- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status
@@ -2215,8 +2258,9 @@ Flags:
|------|------|------|
| [chat_export_messages.py](../../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` |
| [chat_history_with_user.py](../../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` |
| [extract_media_id.py](../../scripts/extract_media_id.py) | 从 dt_media_upload URL 提取 mediaId | `python extract_media_id.py "<URL>"`(输出如 @lQLPxxx,直接用于 --media-id) |
## 相关产品
- [contact](./contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id
- [drive](./drive.md) — 钉盘文件存储与下载;不是发送本地聊天图片/文件的前置步骤
- [drive](./drive.md) — 上传文件获取下载链接,用于 Markdown 图片/文件消息
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
从 dt_media_upload 返回的 URL 中提取 mediaId。
用法:
python extract_media_id.py <url>
python extract_media_id.py "https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw_960_1280.png"
# 输出: @lQLPD4JNnliqBq3NBQDNA8Cw
依赖: 无(纯 Python 标准库)
典型工作流(发送图片/语音/视频消息):
# 1. 用 dt_media_upload 上传文件,获得 URL
# 2. 用本脚本从 URL 提取 mediaId
python extract_media_id.py "<dt_media_upload 返回的 URL>"
# 输出: @lQLPxxx(直接用于 --media-id 或 --pic-url 参数)
"""
import argparse
import re
import sys
from urllib.parse import urlparse
def extract_media_id(url: str) -> str:
"""从 dt_media_upload 返回的 URL 中提取 mediaId。
支持的 URL 格式示例:
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw_960_1280.png
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw.png
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw
提取规则:
1. 取 URL 路径中 /media/ 之后的部分
2. 去除尾部的 _数字_数字.扩展名 后缀(如 _960_1280.png)
3. 如果没有尺寸后缀,去除尾部的 .扩展名
4. 加上 @ 前缀
返回: @lQLPD4JNnliqBq3NBQDNA8Cw
"""
url = url.strip()
# 提取路径部分
parsed = urlparse(url)
path = parsed.path # e.g. /media/lQLPxxx_960_1280.png
# 取 /media/ 之后的部分
media_prefix = "/media/"
idx = path.find(media_prefix)
if idx == -1:
print(f"错误:URL 中未找到 /media/ 路径: {url}", file=sys.stderr)
sys.exit(1)
raw = path[idx + len(media_prefix):] # e.g. lQLPxxx_960_1280.png
if not raw:
print(f"错误:/media/ 后无内容: {url}", file=sys.stderr)
sys.exit(1)
# 尝试去除 _数字_数字.扩展名 后缀(如 _960_1280.png)
match = re.match(r'^(.+?)(_\d+_\d+\.\w+)$', raw)
if match:
media_id = match.group(1)
else:
# 没有尺寸后缀,尝试去除 .扩展名
match2 = re.match(r'^(.+)\.\w+$', raw)
if match2:
media_id = match2.group(1)
else:
# 无后缀,直接使用
media_id = raw
return f"@{media_id}"
def main():
parser = argparse.ArgumentParser(
description="从 dt_media_upload 返回的 URL 中提取 mediaId"
)
parser.add_argument("url", help="dt_media_upload 返回的文件 URL")
args = parser.parse_args()
media_id = extract_media_id(args.url)
print(media_id)
if __name__ == "__main__":
main()
+1 -1
View File
@@ -96,6 +96,6 @@ metadata:
## 跨产品协作
- 收件人是人名 → 先用 `dingtalk-contact` 或 `dingtalk-aisearch` 拿 `openDingTalkId` / `userId`
- 要发本地图片/文件 → 直接用 `dws chat message send --msg-type file --file-path <本地路径>`;图片会作为可下载的文件附件发送,不会内联渲染。只有上游已提供有效 mediaId 时才用 `--msg-type image --media-id`,DWS CLI 不能把本地文件转换成 mediaId
- 要发图片/文件 → 先 `dt_media_upload` 上传 → `python scripts/extract_media_id.py "<URL>"` 提取 mediaId → 再用 `--media-id`
- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-ding`
- 发邮件 → 切到 `dingtalk-mail`
+87 -44
View File
@@ -184,8 +184,8 @@ Flags:
--icon-media-id string 群头像 mediaId (必填)
```
> `--icon-media-id` 必须是 `@` 开头的、由可信上游提供的媒体 ID,非法格式会本地报错。
> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必使用真实上游媒体上传能力拿到有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传命令。
> `--icon-media-id` 必须是 `@` 开头的媒体 ID(如 dt_media_upload 返回值),非法格式会本地报错。
> ⚠️ 本地格式校验只查前缀。格式合法但不真实存在的 mediaId 服务端仍会静默返回成功,头像并不会真正更新。务必用 `dt_media_upload` / `chat media upload` 上传真实图片拿到的 mediaId。
#### 更新群设置 — 更新指定群聊的设置项
@@ -529,17 +529,17 @@ Flags:
--group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。
若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。
--群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。
--本地图片、文件、音频或视频统一用 --msg-type file --file-path;图片会作为可下载的文件附件发送。--msg-type image --media-id 仅用于上游已经提供有效 mediaId 的场景。
--富媒体消息:通过 --msg-type 指定类型(image/file/audio/video),audio/video 是 file 的语义别名,发给服务端仍按 file 发送;必须根据文件扩展名判断 msgType 后再发送。
```
Usage:
dws chat message send [flags] [<text>]
富媒体消息路由:
场景 → msgType → 发送参数
本地图片/文件/音频/视频 → file → --file-path <本地路径>
上游已提供有效 mediaId 的内联图片 → image → --media-id <mediaId>
注意:本地 .png/.jpg 也按 file 发送,接收方看到的是可下载附件;DWS CLI 不能把本地文件转换成 mediaId。
富媒体消息 msgType 决策(必须按此规则判断,不可跳过):
文件扩展名 → msgType → 发送参数
.jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py <URL>` 提取 mediaId → --msg-type image --media-id
.mp3/.wav/.m4a/.aac/.flac → audio(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type audio
.mp4/.mov/.avi/.mkv/.webm → video(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type video
其他所有(.pdf/.doc/.xls/.zip 等) → file → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type file
Example:
dws chat message send --group <openconversation_id> --text "hello"
@@ -551,13 +551,15 @@ Example:
dws chat message send --group <openconversation_id> --text "hello" --uuid "unique-id-123"
dws chat message send --group <openconversation_id> --at-all "<@all> 请大家注意"
dws chat message send --group <openconversation_id> --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收"
# 本地图片/文件/音频/视频统一作为 file 附件发送
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png
dws chat message send --group <openconversation_id> --msg-type file --file-path ./report.pdf
dws chat message send --group <openconversation_id> --msg-type file --file-path ./recording.mp3
dws chat message send --group <openconversation_id> --msg-type file --file-path ./demo.mp4
# 仅当上游已有有效 mediaId 时发送内联图片
# 发送图片
dws chat message send --group <openconversation_id> --msg-type image --media-id <mediaId>
# 发送文件/音频/视频(audio/video 是 file 的语义别名)
# 先 dws chat conversation-info --group <id> 获取 spaceId(取 newCSpaceIdIM)
# 再 dws drive upload --file <文件> --space-id <spaceId> 上传
# 再 dws drive info --file-id <fileId> --space-id <spaceId> 获取 dentryId
dws chat message send --group <openconversation_id> --msg-type file --dentry-id <dentryId> --space-id 24557356340 --file-name "report.pdf" --file-type "pdf" --file-path "/report.pdf" --file-size 234724
dws chat message send --group <openconversation_id> --msg-type audio --file-path ./recording.mp3
dws chat message send --group <openconversation_id> --msg-type video --file-path ./demo.mp4
Flags:
--text string 消息内容(推荐使用,也可用位置参数)
--group string 群聊 openconversation_id(群聊时必填)
@@ -566,14 +568,14 @@ Flags:
--title string 消息标题(可选,默认「消息」)
--at-all @所有人(仅群聊时生效,可选,默认 false)
--at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选)
--media-id string 上游提供的有效图片 mediaId(仅 msgType=image)
--msg-type string 消息类型: image/file/audio/video(本地文件统一使用 file;image 仅配合已有 mediaId)
--dentry-id int64 已有钉盘文件 dentryId(兼容参数,非本地文件发送的默认路径)
--space-id int64 已有钉盘文件空间 ID(元数据兼容模式)
--file-name string 已有钉盘文件名(元数据兼容模式)
--file-type string 已有钉盘文件类型/扩展名(元数据兼容模式)
--file-path string 本地文件路径(本地图片/文件/音视频配合 msgType=file 使用)
--file-size int64 已有钉盘文件大小,单位字节(元数据兼容模式)
--media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py <URL>` 提取,仅 msgType=image)
--msg-type string 消息类型: image/file/audio/video(audio/video 是 file 别名;image 用 mediaId,file/audio/video 用钉盘上传)
--dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取)
--space-id int64 钉盘空间 ID(msgType=file 时必填)
--file-name string 文件名(msgType=file 时必填)
--file-type string 文件类型/扩展名(msgType=file 时必填)
--file-path string 文件路径(msgType=file 时必填)
--file-size int64 文件大小,单位字节(msgType=file 时必填)
--uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选)
--ai-tag 消息是否带 AI 发送角标(可选,默认 true)
@@ -586,13 +588,16 @@ Flags:
- **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可:
1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行
2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `<br>`),或直接写 HTML 的 `<br>` 标签
- 本地图片、文档、压缩包、音频和视频统一使用 `--msg-type file --file-path <本地路径>`;图片会成为可下载的 file 附件,不会内联渲染,也不会生成 mediaId
- `--msg-type image --media-id` 仅接受上游已经提供的有效 mediaId;DWS CLI 不提供本地文件到 mediaId 的上传或转换能力
- audio/video 仍是兼容的 file 语义别名,但本地文件的推荐路径保持为 `--msg-type file --file-path`
- dentryId/spaceId 等参数仅用于调用方已经持有钉盘文件元数据的兼容场景,不是发送本地文件的前置步骤
- 富媒体消息类型与参数对应关系:
- image(图片):--msg-type image --media-id
- audio/video:file 的语义别名,发给服务端仍是 file;可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type audio 或 --msg-type video --dentry-id --space-id --file-name --file-type --file-path --file-size
- file(文档/压缩包等其他非图片文件):可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size
- mediaId 通过 dt_media_upload 上传获得,必须用脚本提取:`python scripts/extract_media_id.py "<URL>"`(输出如 @lQLPxxx,直接用于 --media-id)。禁止手动从 URL 中截取或拼接 mediaId,手动解析会因 URL 格式不稳定导致尺寸后缀残留
- --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持)
- 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送
- 发送文字 + 文件时,先发送 `--msg-type file --file-path` 文件消息,再补一条文本或 Markdown 说明;这是两条独立消息
- 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,音频(.mp3/.wav/.m4a/.aac/.flac)→audio,视频(.mp4/.mov/.avi/.mkv/.webm)→video,其他所有→file;不可跳过此判断步骤。audio/video 是 file 别名,不代表不支持音频/视频
- 20MB 降级:图片超过 20MB 时 dt_media_upload 会失败,必须降级走钉盘上传 + Markdown 嵌入方式发送(参见「发送图片+文字消息」章节)。音频/视频/文件走钉盘上传无 20MB 限制
- 发送文字 + 文件混合消息时的完整流程:除了将文件以 Markdown 链接内嵌到文字消息中发送一条 md 消息外,还必须额外逐个发送独立的文件消息(--msg-type file),确保接收方可以直接下载原始文件。即:先发一条包含文字和文件链接的 md 消息,再对每个涉及的文件各发一条 --msg-type file 的文件消息
```
#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态
@@ -1125,7 +1130,7 @@ Flags:
#### 获取会话基础信息 — 含会话关联的钉盘共享空间 ID
获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。该 ID 可用于独立的钉盘存储操作;发送本地图片或文件到聊天不需要先调用本命令,直接使用 `chat message send --msg-type file --file-path`。
获取指定会话的基础信息,包含会话关联的钉盘共享空间 ID (newCSpaceIdIM)。发送文件消息前需先调用此命令获取 spaceId,再用 drive upload --space-id 上传文件到共享空间。
```
Usage:
dws chat conversation-info [flags]
@@ -1141,8 +1146,8 @@ Flags:
注意:
- --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 返回值中的 newCSpaceIdIM 为会话共享空间 ID,可用于调用方明确需要的钉盘存储流程
- 该 ID 不是发送本地聊天附件的前置条件;本地附件直接走 `chat message send --msg-type file --file-path`
- 返回值中的 newCSpaceIdIM 为会话共享空间 ID,用于 drive upload --space-id 参数
- 上传到共享空间的文件对方才能打开,上传到个人空间的文件对方无法访问
```
#### 引用回复消息 — 引用某条消息并回复文字(单聊/群聊均可)
@@ -1506,6 +1511,25 @@ Flags:
- 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### media (上传媒体获取 mediaId)
#### 上传图片/媒体获取 mediaId — 用于 chat message send --msg-type image 等
⚠️ 前置条件:本命令需要应用凭证。必须已通过 `dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>` 登录,或设置环境变量 `DWS_CLIENT_ID` / `DWS_CLIENT_SECRET`;否则报"缺少应用凭证"。这与其他 chat 命令走用户登录态不同。
```
Usage:
dws chat media upload [flags]
Example:
dws chat media upload --file ./screenshot.png
dws chat media upload --file ./photo.jpg --type image
Flags:
--file string 本地文件路径 (必填)
--type string 媒体类型: image/voice/video/file(默认 image)
注意:
- 返回的 mediaId 可直接用于 chat message send --msg-type image --media-id
```
### hide (隐藏会话)
#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现
@@ -1831,8 +1855,7 @@ Flags:
用户说"转发话题/转发话题消息" → `chat message forward-topic`
用户说"置顶消息/把消息置顶" → `chat message set-top-msg`
用户说"取消置顶消息/撤销消息置顶" → `chat message unset-top-msg`
用户说"发送/上传本地图片或媒体到聊天" → `chat message send --msg-type file --file-path <本地路径>`
用户明确只要 mediaId → DWS CLI 当前不提供本地上传入口;仅在上游已有有效 mediaId 时使用 `chat message send --msg-type image --media-id`
用户说"上传图片拿mediaId/上传媒体" → `chat media upload`
用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots`
用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot`
用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 botOpenDingTalkId 可发单聊)
@@ -1858,7 +1881,7 @@ Flags:
- `chat message list-topic-replies` — 拉取群话题的回复消息列表
- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页
- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页
- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;本地图片/文件/音视频统一用 `--msg-type file --file-path`,其中图片显示为可下载附件而非内联图片;`--msg-type image --media-id` 只用于上游已经提供有效 mediaId 的场景,DWS CLI 不能从本地文件生成 mediaId
- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file/audio/video(audio/video 是 file 别名),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件可直接用 --file-path 或先获取会话共享空间再上传钉盘
- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群)
- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在)
- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag
@@ -2005,16 +2028,34 @@ dws chat message send-by-bot --robot-code <robot-code> --group <openconversation
```
### 发送图片/文件 + 文字说明(两条消息)
### 发送图片+文字 / 文件+文字消息(跨产品: drive → chat)
本地图片和文件先用 `--msg-type file --file-path` 发送,再补一条文本消息说明;这是两条独立消息,不需要媒体上传或钉盘前置步骤。图片会显示为可下载的文件附件,不会内联渲染。
- **图片+文字**:图片**必须**通过 `dt_media_upload` 工具(非 dws 命令,是 agent 可调用的独立 tool)上传获取 mediaId,然后用 Markdown 嵌入方式发送。**禁止**使用钉盘上传图片。
- **文件+文字**:文件通过钉盘上传 + Markdown 嵌入方式发送。
纯发图片/文件(不带文字)的完整流程见 [intent-guide.md](./01-messaging.md) 对应章节。
```bash
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png --format json
dws chat message send --group <openconversation_id> --text "这是本周的数据汇总" --format json
```
# === 图片+文字 ===
# Step 1: 调用 dt_media_upload 工具上传图片(这是一个独立的 tool,不是 dws 命令)
# dt_media_upload 会返回 mediaId(如 @lQLPxxx)
# 提取 mediaId 可使用脚本: python extract_media_id.py "<返回的URL>"
如果调用方已经从上游取得有效 mediaId,可以先用 `--msg-type image --media-id` 发送内联图片,再补一条文本消息;DWS CLI 本身不能把本地图片转换成 mediaId。
# Step 2: 用 Markdown 语法发送(mediaId 作为图片引用)
dws chat message send --group <openconversation_id> \
--text "![截图](mediaId) 这是本周的数据汇总" --format json
# === 文件+文字 ===
# Step 1: 上传文件到钉盘
dws drive upload --file "报告.pdf" --format json
# Step 2: 获取下载链接
dws drive download --file-id <dentryUuid> --format json
# Step 3: 用 Markdown 语法发送
dws chat message send --group <openconversation_id> \
--text "[报告.pdf](下载链接) 这是季度报告" --format json
```
#### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息
@@ -2070,13 +2111,14 @@ Flags:
| `chat message search` | `nextCursor` | 下次 message search 的 --cursor |
| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor |
| `chat search-common` | `openConversationId` | message send/list 等的 --group |
| `chat conversation-info` | `newCSpaceIdIM` | 独立钉盘存储流程的共享空间 ID;不是发送本地聊天附件的前置条件 |
| `chat conversation-info` | `newCSpaceIdIM` | drive upload 的 --space-id(发送文件消息前获取共享空间) |
| `chat message list` | `openMsgId` | message read-status 的 --message-id |
| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id |
| `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id |
| `chat category list` | `categoryId` | category list-conversations 的 --category-id |
| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId |
| `chat message send-card` | `bizId` | update-card 的 --biz-id |
| `drive download` | 下载链接 | message send 的 Markdown 图片/链接语法 |
| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id |
| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group |
@@ -2093,7 +2135,7 @@ Flags:
- 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送
- 此参数适用于 `chat message send`(群聊和单聊均支持)
- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;本地图片/文件/音视频统一用 `--msg-type file --file-path`,其中图片是可下载附件;`--msg-type image --media-id` 仅用于上游已经提供有效 mediaId 的内联图片
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file/audio/video),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,音频→audio,视频→video,其他→file,不可跳过此判断
- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor
- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一
- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor`
@@ -2114,7 +2156,7 @@ Flags:
- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner`
- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久)
- `chat group quit` 退出群聊,需传 --group(openConversationId)
- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和由可信上游提供的有效 --icon-media-id(mediaId);DWS CLI 不能从本地图片生成该 ID
- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId)
- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启)
- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容
- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status
@@ -2141,8 +2183,9 @@ Flags:
|------|------|------|
| [chat_export_messages.py](../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` |
| [chat_history_with_user.py](../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` |
| [extract_media_id.py](../scripts/extract_media_id.py) | 从 dt_media_upload URL 提取 mediaId | `python extract_media_id.py "<URL>"`(输出如 @lQLPxxx,直接用于 --media-id) |
## 相关产品
- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id
- [drive](../../dingtalk-drive/references/drive.md) — 钉盘文件存储与下载;不是发送本地聊天图片/文件的前置步骤
- [drive](../../dingtalk-drive/references/drive.md) — 上传文件获取下载链接,用于 Markdown 图片/文件消息
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
从 dt_media_upload 返回的 URL 中提取 mediaId。
用法:
python extract_media_id.py <url>
python extract_media_id.py "https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw_960_1280.png"
# 输出: @lQLPD4JNnliqBq3NBQDNA8Cw
依赖: 无(纯 Python 标准库)
典型工作流(发送图片/语音/视频消息):
# 1. 用 dt_media_upload 上传文件,获得 URL
# 2. 用本脚本从 URL 提取 mediaId
python extract_media_id.py "<dt_media_upload 返回的 URL>"
# 输出: @lQLPxxx(直接用于 --media-id 或 --pic-url 参数)
"""
import argparse
import re
import sys
from urllib.parse import urlparse
def extract_media_id(url: str) -> str:
"""从 dt_media_upload 返回的 URL 中提取 mediaId。
支持的 URL 格式示例:
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw_960_1280.png
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw.png
https://down.dingtalk.com/media/lQLPD4JNnliqBq3NBQDNA8Cw
提取规则:
1. 取 URL 路径中 /media/ 之后的部分
2. 去除尾部的 _数字_数字.扩展名 后缀(如 _960_1280.png)
3. 如果没有尺寸后缀,去除尾部的 .扩展名
4. 加上 @ 前缀
返回: @lQLPD4JNnliqBq3NBQDNA8Cw
"""
url = url.strip()
# 提取路径部分
parsed = urlparse(url)
path = parsed.path # e.g. /media/lQLPxxx_960_1280.png
# 取 /media/ 之后的部分
media_prefix = "/media/"
idx = path.find(media_prefix)
if idx == -1:
print(f"错误:URL 中未找到 /media/ 路径: {url}", file=sys.stderr)
sys.exit(1)
raw = path[idx + len(media_prefix):] # e.g. lQLPxxx_960_1280.png
if not raw:
print(f"错误:/media/ 后无内容: {url}", file=sys.stderr)
sys.exit(1)
# 尝试去除 _数字_数字.扩展名 后缀(如 _960_1280.png)
match = re.match(r'^(.+?)(_\d+_\d+\.\w+)$', raw)
if match:
media_id = match.group(1)
else:
# 没有尺寸后缀,尝试去除 .扩展名
match2 = re.match(r'^(.+)\.\w+$', raw)
if match2:
media_id = match2.group(1)
else:
# 无后缀,直接使用
media_id = raw
return f"@{media_id}"
def main():
parser = argparse.ArgumentParser(
description="从 dt_media_upload 返回的 URL 中提取 mediaId"
)
parser.add_argument("url", help="dt_media_upload 返回的文件 URL")
args = parser.parse_args()
media_id = extract_media_id(args.url)
print(media_id)
if __name__ == "__main__":
main()
+2 -3
View File
@@ -13,7 +13,7 @@ import (
// MCP architecture, products are discovered dynamically from MCP servers,
// and their availability depends on the test environment's fixture data.
func TestPublicMCPHelpShowsReviewedURLCommand(t *testing.T) {
func TestHiddenMCPHelpIsReachable(t *testing.T) {
cmd := app.NewRootCommand()
var out strings.Builder
cmd.SetOut(&out)
@@ -25,8 +25,7 @@ func TestPublicMCPHelpShowsReviewedURLCommand(t *testing.T) {
}
got := out.String()
if !strings.Contains(got, "管理经过审核并纳入 Schema 的 MCP 服务连接辅助能力") ||
!strings.Contains(got, "url") {
if !strings.Contains(got, "canonical MCP command surface is disabled") {
t.Fatalf("mcp help missing expected text:\n%s", got)
}
}
+15 -59
View File
@@ -1,7 +1,7 @@
[root]
runnable: true
hidden: false
commands: agoal, aisearch, aitable, api, attendance, audit, auth, calendar, chat, completion, config, contact, dev, devapp, devdoc, ding, doc, doctor, drive, event, help, live, mail, mcp, minutes, oa, pat, plugin, profile, recovery, report, schema, sheet, skill, todo, upgrade, version, wiki
commands: agoal, aisearch, aitable, api, attendance, audit, auth, calendar, chat, completion, config, contact, dev, devapp, devdoc, ding, doc, doctor, drive, event, help, live, mail, minutes, oa, pat, plugin, profile, recovery, report, schema, sheet, skill, todo, upgrade, version, wiki
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=persistent, --client-secret:string|required=false|hidden=false|no-opt=""|scope=persistent, --debug:bool|required=false|hidden=false|no-opt="true"|scope=persistent, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=persistent, --fields:string|required=false|hidden=false|no-opt=""|scope=persistent, -f/--format:string|required=false|hidden=false|no-opt=""|scope=persistent, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=persistent, --mock:bool|required=false|hidden=false|no-opt="true"|scope=persistent, -o/--output:string|required=false|hidden=true|no-opt=""|scope=persistent, --profile:string|required=false|hidden=false|no-opt=""|scope=persistent, --timeout:int|required=false|hidden=false|no-opt=""|scope=persistent, --token:string|required=false|hidden=true|no-opt=""|scope=persistent, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=persistent, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=persistent
[agoal]
@@ -1738,7 +1738,7 @@
[chat.+bot-find]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --keyword:string|required=false|hidden=true|no-opt=""|scope=local, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+bot-search]
runnable: true
@@ -1748,7 +1748,7 @@
[chat.+broadcast]
runnable: true
hidden: false
flags: --ai-tag:bool|required=false|hidden=false|no-opt="true"|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:stringSlice|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:stringSlice|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+category-create]
runnable: true
@@ -1833,7 +1833,7 @@
[chat.+chat-search]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --exclude-muted:bool|required=false|hidden=false|no-opt="true"|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --keyword:string|required=false|hidden=true|no-opt=""|scope=local, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --size:int|required=false|hidden=true|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --exclude-muted:bool|required=false|hidden=false|no-opt="true"|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+chat-set-admin]
runnable: true
@@ -1878,7 +1878,7 @@
[chat.+dm]
runnable: true
hidden: false
flags: --ai-tag:bool|required=false|hidden=false|no-opt="true"|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:string|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:string|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+group-members]
runnable: true
@@ -1888,7 +1888,7 @@
[chat.+messages-list-direct]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --forward:bool|required=false|hidden=false|no-opt="true"|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --open-dingtalk-id:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:int|required=false|hidden=true|no-opt=""|scope=local, --time:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --user:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --forward:bool|required=false|hidden=false|no-opt="true"|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --open-dingtalk-id:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --time:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --user:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+messages-list-pin]
runnable: true
@@ -1913,7 +1913,7 @@
[chat.+messages-read-status]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=true|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=true|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --message-id:string|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --users:stringSlice|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --message-id:string|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --users:stringSlice|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+messages-send-by-webhook]
runnable: true
@@ -1933,7 +1933,7 @@
[chat.+send-to-group]
runnable: true
hidden: false
flags: --ai-tag:bool|required=false|hidden=false|no-opt="true"|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --text:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.+unread-chats]
runnable: true
@@ -2317,7 +2317,7 @@
[chat.message.download-media]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --message-id:string|required=true|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --msg-id:string|required=false|hidden=true|no-opt=""|scope=local, --open-conversation-id:string|required=true|hidden=false|no-opt=""|scope=local, --open-message-id:string|required=false|hidden=true|no-opt=""|scope=local, --output:string|required=true|hidden=false|no-opt=""|scope=local, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --resource-id:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --type:string|required=true|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --message-id:string|required=true|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --open-conversation-id:string|required=true|hidden=false|no-opt=""|scope=local, --output:string|required=true|hidden=false|no-opt=""|scope=local, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --resource-id:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --type:string|required=true|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[chat.message.forward]
runnable: true
@@ -2539,7 +2539,7 @@
[contact]
runnable: true
hidden: false
commands: +by-mobile, +dept-members, +list-dept-members, +list-followings, +list-role-members, +list-roles, +list-sub-depts, +lookup, +me, +org, +resolve-dept, +search-mobile, +search-user, +team, account, dept, label, org, relation, user
commands: +by-mobile, +dept-members, +list-dept-members, +list-followings, +list-role-members, +list-roles, +list-sub-depts, +lookup, +me, +org, +resolve-dept, +search-mobile, +search-user, +team, dept, label, relation, user
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.+by-mobile]
@@ -2612,17 +2612,6 @@
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --name:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.account]
runnable: true
hidden: false
commands: create
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.account.create]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dept-ids:string|required=false|hidden=false|no-opt=""|scope=local, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --email:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --login-id:string|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --org-user-mobile:string|required=false|hidden=false|no-opt=""|scope=local, --org-user-name:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --send-pwd-via-sms:bool|required=false|hidden=false|no-opt="true"|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.dept]
runnable: true
hidden: false
@@ -2671,17 +2660,6 @@
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=false|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --label-id:string|required=false|hidden=true|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --role-id:string|required=false|hidden=true|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.org]
runnable: true
hidden: false
commands: create
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.org.create]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --creator-username:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --org-name:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.relation]
runnable: true
hidden: false
@@ -2696,7 +2674,7 @@
[contact.user]
runnable: true
hidden: false
commands: dismission, get, get-self, invite, profile, search, search-mobile
commands: dismission, get, get-self, profile, search, search-mobile
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.user.dismission]
@@ -2721,11 +2699,6 @@
aliases: current, me, self, whoami
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.user.invite]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --depts:string|required=false|hidden=false|no-opt=""|scope=local, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --org-user-mobile:string|required=false|hidden=false|no-opt=""|scope=local, --org-user-name:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[contact.user.profile]
runnable: true
hidden: false
@@ -2819,7 +2792,7 @@
[dev.app.get]
runnable: true
hidden: false
flags: --app-key:string|required=false|hidden=false|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --unified-app-id:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --unified-app-id:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[dev.app.list]
runnable: true
@@ -3259,7 +3232,7 @@
[doc.+share-doc]
runnable: true
hidden: false
flags: --ai-tag:bool|required=false|hidden=false|no-opt="true"|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --note:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:string|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --url:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --note:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --to:string|required=false|hidden=false|no-opt=""|scope=local, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --url:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[doc.+template-list]
runnable: true
@@ -3664,7 +3637,7 @@
[event.consume]
runnable: true
hidden: false
flags: --as:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --compact:bool|required=false|hidden=false|no-opt="true"|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --debug-raw-events:bool|required=false|hidden=false|no-opt="true"|scope=local, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=local, --duration:duration|required=false|hidden=false|no-opt=""|scope=local, --ephemeral:bool|required=false|hidden=false|no-opt="true"|scope=local, --event-types:stringSlice|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, --filter:string|required=false|hidden=false|no-opt=""|scope=local, --filter-json:string|required=false|hidden=false|no-opt=""|scope=local, --flatten:bool|required=false|hidden=false|no-opt="true"|scope=local, --force:bool|required=false|hidden=false|no-opt="true"|scope=local, --foreground:bool|required=false|hidden=false|no-opt="true"|scope=local, -f/--format:string|required=false|hidden=false|no-opt=""|scope=local, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --max-events:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --name:string|required=false|hidden=false|no-opt=""|scope=local, --open-dingtalk-id:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --output-dir:string|required=false|hidden=false|no-opt=""|scope=local, --personal-event-base-url:string|required=false|hidden=false|no-opt=""|scope=local, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --quiet:bool|required=false|hidden=false|no-opt="true"|scope=local, --route:stringArray|required=false|hidden=false|no-opt=""|scope=local, --rule:string|required=false|hidden=false|no-opt=""|scope=local, --stream-source-id:string|required=false|hidden=false|no-opt=""|scope=local, --stream-ticket-mode:string|required=false|hidden=false|no-opt=""|scope=local, --stream-ticket-url:string|required=false|hidden=false|no-opt=""|scope=local, --subscribe-id:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --ttl:duration|required=false|hidden=false|no-opt=""|scope=local, --user:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --as:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --compact:bool|required=false|hidden=false|no-opt="true"|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --debug-raw-events:bool|required=false|hidden=false|no-opt="true"|scope=local, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=local, --duration:duration|required=false|hidden=false|no-opt=""|scope=local, --ephemeral:bool|required=false|hidden=false|no-opt="true"|scope=local, --event-types:stringSlice|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, --filter:string|required=false|hidden=false|no-opt=""|scope=local, --filter-json:string|required=false|hidden=false|no-opt=""|scope=local, --force:bool|required=false|hidden=false|no-opt="true"|scope=local, --foreground:bool|required=false|hidden=false|no-opt="true"|scope=local, -f/--format:string|required=false|hidden=false|no-opt=""|scope=local, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --max-events:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --name:string|required=false|hidden=false|no-opt=""|scope=local, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --output-dir:string|required=false|hidden=false|no-opt=""|scope=local, --personal-event-base-url:string|required=false|hidden=false|no-opt=""|scope=local, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --quiet:bool|required=false|hidden=false|no-opt="true"|scope=local, --route:stringArray|required=false|hidden=false|no-opt=""|scope=local, --rule:string|required=false|hidden=false|no-opt=""|scope=local, --stream-source-id:string|required=false|hidden=false|no-opt=""|scope=local, --stream-ticket-mode:string|required=false|hidden=false|no-opt=""|scope=local, --stream-ticket-url:string|required=false|hidden=false|no-opt=""|scope=local, --subscribe-id:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --ttl:duration|required=false|hidden=false|no-opt=""|scope=local, --user:string|required=false|hidden=false|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[event.list]
runnable: true
@@ -3674,7 +3647,7 @@
[event.schema]
runnable: true
hidden: false
flags: --as:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, --flatten:bool|required=false|hidden=false|no-opt="true"|scope=local, -f/--format:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
flags: --as:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[event.status]
runnable: true
@@ -4144,23 +4117,6 @@
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --email:string|required=false|hidden=false|no-opt=""|scope=local, --employee-no:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --keyword:string|required=false|hidden=false|no-opt=""|scope=local, --limit:string|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:string|required=false|hidden=true|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[mcp]
runnable: true
hidden: false
commands: url
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[mcp.url]
runnable: true
hidden: false
commands: get
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[mcp.url.get]
runnable: true
hidden: false
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
[minutes]
runnable: true
hidden: false