// 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 smart import ( "encoding/json" "strings" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" ) // ShareDoc: send a document link to a person by NAME, no ID juggling. // // Steps: resolve name → single user (disambiguate on multiple matches) → // build a Markdown message that links to the document → send it to the user's // openDingTalkId as a single-chat message. This is a pure "share a link" flow: // it does not touch any doc tool, it just delivers the URL you already have // straight to the recipient's inbox. // // dws doc +share-doc --to 张三 --url https://docs.dingtalk.com/xxx --note "帮忙过一下" var ShareDoc = shortcut.Shortcut{ Service: "doc", Command: "+share-doc", Product: "chat", Description: "按姓名把文档链接私信发给某人(自动解析 userId)", Intent: "当你手上已经有一个文档链接、想直接私信发给某个人而不必先查 userId 时使用;" + "内部先按姓名搜通讯录解析出唯一用户,再用 openDingTalkId 把链接拼成一条 Markdown 消息发出去," + "姓名匹配到多人时会列出候选让你区分。只发链接、不读取或改动文档本身,会真实发出消息。", Risk: shortcut.RiskWrite, Safety: contract.SafetySpec{ Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "unknown", }, Contract: corecmd.ContractDecl{ Identity: contract.ToolIdentitySpec{ ProductID: "doc", Name: "shortcut_share_doc", CanonicalPath: "doc.shortcut_share_doc", CLIPath: "doc +share-doc", PrimaryCLIPath: "doc +share-doc", }, Description: "按姓名把文档链接私信发给某人(自动解析 userId)", Interface: &contract.InterfaceSpec{ Mode: "composite", Availability: "available", Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", }, Selection: contract.SelectionSpec{ AgentSummary: "按姓名把文档链接私信发给某人(自动解析 userId)", UseWhen: []string{"当你手上已经有一个文档链接、想直接私信发给某个人而不必先查 userId 时使用;内部先按姓名搜通讯录解析出唯一用户,再用 openDingTalkId 把链接拼成一条 Markdown 消息发出去,姓名匹配到多人时会列出候选让你区分。只发链接、不读取或改动文档本身,会真实发出消息。"}, AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws doc +share-doc --to 张三 --url https://docs.dingtalk.com/xxx --note \"帮忙过一下\""}, }, }, Flags: []shortcut.Flag{ {Name: "to", Type: shortcut.FlagString, Desc: "收件人姓名/花名", Required: true}, {Name: "url", Type: shortcut.FlagString, Desc: "文档链接", Required: true}, {Name: "note", Type: shortcut.FlagString, Desc: "附言(可选)"}, shortcut.AIMessageTagFlag(), }, Tips: []string{`dws doc +share-doc --to 张三 --url https://docs.dingtalk.com/xxx --note "帮忙过一下"`}, Execute: func(rt *shortcut.RuntimeContext) error { url := rt.Str("url") note := rt.Str("note") // Step 1 — resolve the recipient name to a unique userId. user, err := resolveOpenDingTalkUser(rt, rt.Str("to")) if err != nil { return err } if user.openDingTalkID == "" { return apperrors.NewValidation("通讯录结果缺少 openDingTalkId,无法发送文档分享消息;请改用 chat +messages-send --open-dingtalk-id") } // Step 2 — build a Markdown message linking to the document. text := shareDocBuildText(url, note) title := "文档分享" content, _ := json.Marshal(map[string]string{"title": title, "text": text}) // Step 3 — deliver it as a single-chat message. return rt.CallMCP("send_personal_message", rt.AddAIMessageTag(map[string]any{ "receiverOpenDingTalkId": user.openDingTalkID, "msgType": "markdown", "content": string(content), })) }, } // shareDocBuildText assembles the Markdown body: a clickable link plus an // optional note on its own line. func shareDocBuildText(url, note string) string { var b strings.Builder b.WriteString("[文档](") b.WriteString(url) b.WriteString(")") if strings.TrimSpace(note) != "" { b.WriteString("\n\n") b.WriteString(note) } return b.String() } func init() { canonicalizeShareDoc() shortcut.Register(legacyShareDoc, ShareDoc) }