doc

package
v1.0.57 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package doc declares the high-fidelity `dws doc +<command>` shortcuts. Tool names and parameter keys are lifted verbatim from internal/helpers/doc.go (the single source of truth for DingTalk MCP tools).

Index

Constants

This section is empty.

Variables

View Source
var BackgroundDelete = shortcut.Shortcut{
	Service: "doc", Command: "+background-delete", Product: productDoc,
	Description: "清除文档背景色",
	Intent:      "当用户明确要恢复文档默认背景、移除当前背景色时使用;执行 background clear 并要求确认。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "idempotent"},
	Contract: docContract("+background-delete", "清除文档背景色",
		"当用户明确要恢复文档默认背景、移除当前背景色时使用;执行 background clear 并要求确认。",
		[]string{`dws doc +background-delete --node <DOC_ID>`}),
	Flags: []shortcut.Flag{{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true}},
	Tips:  []string{`dws doc +background-delete --node <DOC_ID>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node"), "background": map[string]any{"action": "clear"}}
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.background_delete", map[string]any{"executed": false, "params": params}))
		}
		return rt.CallMCP("update_document_style", params)
	},
}
View Source
var BackgroundUpdate = shortcut.Shortcut{
	Service: "doc", Command: "+background-update", Product: productDoc,
	Description: "设置文档 #RRGGBB 背景纯色",
	Intent:      "当用户要设置在线文档背景纯色时使用;只接受 #RRGGBB,不支持背景图片。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+background-update", "设置文档 #RRGGBB 背景纯色",
		"当用户要设置在线文档背景纯色时使用;只接受 #RRGGBB,不支持背景图片。",
		[]string{`dws doc +background-update --node <DOC_ID> --color "#E8F2FE"`}),
	Flags: []shortcut.Flag{{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true}, {Name: "color", Type: shortcut.FlagString, Desc: "#RRGGBB 背景色", Required: true}},
	Tips:  []string{`dws doc +background-update --node <DOC_ID> --color "#E8F2FE"`},
	Validate: func(rt *shortcut.RuntimeContext) error {
		color := rt.Str("color")
		if len(color) != 7 || color[0] != '#' {
			return apperrors.NewValidation("--color 必须是 #RRGGBB")
		}
		for _, char := range color[1:] {
			if !strings.ContainsRune("0123456789abcdefABCDEF", char) {
				return apperrors.NewValidation("--color 必须是 #RRGGBB")
			}
		}
		return nil
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"color"}, Description: "#RRGGBB"}},
	Execute: func(rt *shortcut.RuntimeContext) error {
		return rt.CallMCP("update_document_style", map[string]any{"nodeId": rt.Str("node"), "background": map[string]any{"action": "set", "backgroundColor": rt.Str("color")}})
	},
}
View Source
var CheckpointUpdate = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+checkpoint-update",
	Product:     productDoc,
	Description: "先保存可回滚版本,再更新并读回验证",
	Intent:      "当用户要进行重要追加或整篇覆盖,并希望自动创建恢复点、执行更新、再读回确认时使用;任一步失败都会返回已经完成的步骤。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "unknown"},
	Contract: docContract("+checkpoint-update", "先保存可回滚版本,再更新并读回验证",
		"当用户要进行重要追加或整篇覆盖,并希望自动创建恢复点、执行更新、再读回确认时使用;任一步失败都会返回已经完成的步骤。",
		[]string{`dws doc +checkpoint-update --node <DOC_ID> --mode append --content @section.md`, `dws doc +checkpoint-update --node <DOC_ID> --mode overwrite --content @document.md`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "mode", Type: shortcut.FlagString, Default: "append", Desc: "更新模式", Enum: []string{"append", "overwrite"}},
		{Name: "content", Type: shortcut.FlagString, Desc: "内容字面量、@相对文件或 - 表示 stdin", Required: true},
	},
	Tips: []string{`dws doc +checkpoint-update --node <DOC_ID> --mode append --content @section.md`, `dws doc +checkpoint-update --node <DOC_ID> --mode overwrite --content @document.md`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		content, err := readShortcutContent(rt, "content")
		if err != nil {
			return err
		}
		plan := map[string]any{"nodeId": rt.Str("node"), "mode": rt.Str("mode"), "contentBytes": len(content), "steps": []string{"save_doc_version", "update_document", "get_document_content"}}
		if rt.DryRun() {
			plan["executed"] = false
			return rt.Output(docEnvelope("doc.checkpoint_update", plan))
		}
		steps := []map[string]any{}
		checkpoint, err := rt.CallMCPWriteData(productDoc, "save_doc_version", map[string]any{"nodeId": rt.Str("node")})
		if err != nil {
			return err
		}
		steps = append(steps, map[string]any{"name": "checkpoint", "status": "success"})
		if _, err := rt.CallMCPWriteData(productDoc, "update_document", map[string]any{"nodeId": rt.Str("node"), "markdown": content, "mode": rt.Str("mode")}); err != nil {
			return checkpointPartialWriteError(rt.Str("node"), checkpoint, "update", "doc_checkpoint_update_failed", err,
				append(steps, map[string]any{"name": "update", "status": "failed"}, map[string]any{"name": "verify", "status": "not_started"}))
		}
		steps = append(steps, map[string]any{"name": "update", "status": "success"})
		verified, err := rt.CallMCPData(productDoc, "get_document_content", map[string]any{"nodeId": rt.Str("node"), "format": "markdown"})
		if err != nil {
			return checkpointPartialWriteError(rt.Str("node"), checkpoint, "verify", "doc_checkpoint_verification_failed", err,
				append(steps, map[string]any{"name": "verify", "status": "failed"}))
		}
		steps = append(steps, map[string]any{"name": "verify", "status": "success"})
		return rt.Output(docEnvelope("doc.checkpoint_update", map[string]any{"verified": verified}, steps...))
	},
}
View Source
var CommentCreate = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+comment-create",
	Product:     productComment,
	Description: "在文档上创建一条评论",
	Intent:      "当你想对整篇文档留一条全文评论、给出反馈或 @ 相关同事时使用;输入 node 与评论 content(可带 mention),会实际在文档上发布一条新评论。",
	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_comment_create",
			CanonicalPath:  "doc.shortcut_comment_create",
			CLIPath:        "doc +comment-create",
			PrimaryCLIPath: "doc +comment-create",
		},
		Description: "在文档上创建一条评论",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "在文档上创建一条评论",
			UseWhen:      []string{"当你想对整篇文档留一条全文评论、给出反馈或 @ 相关同事时使用;输入 node 与评论 content(可带 mention),会实际在文档上发布一条新评论。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +comment-create --node DOC_ID --content \"这里需要修改\""},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "content", Type: shortcut.FlagString, Desc: "评论文字内容 (纯文本)", Required: true},
		{Name: "mention", Type: shortcut.FlagStringSlice, Desc: "被 @ 的用户 uid 列表"},
	},
	Tips: []string{`dws doc +comment-create --node DOC_ID --content "这里需要修改"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{
			"nodeId":  rt.Str("node"),
			"content": rt.Str("content"),
		}
		if rt.Changed("mention") {
			params["mentionedUserIds"] = rt.StrSlice("mention")
		}
		return rt.CallMCP("create_comment", params)
	},
}
View Source
var CommentCreateInline = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+comment-create-inline",
	Product:     productComment,
	Description: "在文档选中文本区域上创建划词评论",
	Intent:      "当你想针对文档里某段具体文字(而非整篇)留评论、做精确批注时使用;需先用 +block-list 定位块,再输入 node、block-id 及该块内的 start/end 字符偏移量,会实际在选中文本上创建划词评论。",
	Risk:        shortcut.RiskWrite,
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "content", Type: shortcut.FlagString, Desc: "评论文字内容 (纯文本)", Required: true},
		{Name: "block-id", Type: shortcut.FlagString, Desc: "评论标记所在的块 ID (通过 +block-list 获取)", Required: true},
		{Name: "start", Type: shortcut.FlagInt, Desc: "块内文本起始字符偏移量 (从 0 开始)", Required: true},
		{Name: "end", Type: shortcut.FlagInt, Desc: "块内文本结束字符偏移量 (须大于 start)", Required: true},
		{Name: "selected-text", Type: shortcut.FlagString, Desc: "选中文本内容 (展示引用原文)"},
		{Name: "mention", Type: shortcut.FlagStringSlice, Desc: "被 @ 的用户 uid 列表"},
	},
	Tips: []string{`dws doc +comment-create-inline --node DOC_ID --block-id BLOCK_ID --start 0 --end 10 --content "这里需要修改"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{
			"nodeId":  rt.Str("node"),
			"content": rt.Str("content"),
			"blockId": rt.Str("block-id"),
			"start":   rt.Int("start"),
			"end":     rt.Int("end"),
		}
		if v := rt.Str("selected-text"); v != "" {
			params["selectedText"] = v
		}
		if rt.Changed("mention") {
			params["mentionedUserIds"] = rt.StrSlice("mention")
		}
		return rt.CallMCP("create_inline_comment", params)
	},
}
View Source
var CommentDelete = shortcut.Shortcut{
	Service: "doc", Command: "+comment-delete", Product: productComment,
	Description: "永久删除指定文档评论",
	Intent:      "当用户明确要求永久删除某条文档评论,且已核对 node 与 commentKey 时使用;不可用于标记 resolved。",
	Risk:        shortcut.RiskHighWrite,
	Safety:      contract.SafetySpec{Effect: "destructive", Risk: "high", Confirmation: "user_required", Idempotency: "unknown"},
	Contract: docContract("+comment-delete", "永久删除指定文档评论",
		"当用户明确要求永久删除某条文档评论,且已核对 node 与 commentKey 时使用;不可用于标记 resolved。",
		[]string{`dws doc +comment-delete --node <DOC_ID> --comment-key <COMMENT_KEY>`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "comment-key", Type: shortcut.FlagString, Desc: "评论 commentKey", Required: true},
	},
	Tips: []string{`dws doc +comment-delete --node <DOC_ID> --comment-key <COMMENT_KEY>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node"), "commentKey": rt.Str("comment-key")}
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.comment_delete", map[string]any{"executed": false, "params": params}))
		}
		return rt.CallMCP("delete_comment", params)
	},
}
View Source
var CommentList = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+comment-list",
	Product:     productComment,
	Description: "查询文档评论列表",
	Intent:      "当你想查看某篇文档上已有的评论、了解有哪些反馈或待处理意见(可按全文/划词、已解决/未解决过滤)时使用;输入 node,返回评论列表及其 commentKey 以便后续回复。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_comment_list",
			CanonicalPath:  "doc.shortcut_comment_list",
			CLIPath:        "doc +comment-list",
			PrimaryCLIPath: "doc +comment-list",
		},
		Description: "查询文档评论列表",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "查询文档评论列表",
			UseWhen:      []string{"当你想查看某篇文档上已有的评论、了解有哪些反馈或待处理意见(可按全文/划词、已解决/未解决过滤)时使用;输入 node,返回评论列表及其 commentKey 以便后续回复。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples: []string{
				"dws doc +comment-list --node DOC_ID",
				"dws doc +comment-list --node DOC_ID --type inline --resolve-status unresolved",
			},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "每页数量 (默认 50,最大 50)"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标 (上一页返回的 nextToken)"},
		{Name: "type", Type: shortcut.FlagString, Desc: "评论类型: global (全文) / inline (划词)", Enum: []string{"global", "inline"}},
		{Name: "resolve-status", Type: shortcut.FlagString, Desc: "解决状态: resolved / unresolved", Enum: []string{"resolved", "unresolved"}},
	},
	Tips: []string{`dws doc +comment-list --node DOC_ID`, `dws doc +comment-list --node DOC_ID --type inline --resolve-status unresolved`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node")}
		if rt.Changed("limit") {
			params["pageSize"] = rt.Int("limit")
		}
		if v := rt.Str("cursor"); v != "" {
			params["nextToken"] = v
		}
		if v := rt.Str("type"); v != "" {
			params["commentType"] = v
		}
		if v := rt.Str("resolve-status"); v != "" {
			params["resolveStatus"] = v
		}
		return rt.CallMCP("list_comments", params)
	},
}
View Source
var CommentReply = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+comment-reply",
	Product:     productComment,
	Description: "回复文档中的一条评论",
	Intent:      "当你要针对某条已有评论进行回复、参与讨论或用表情贴图回应时使用;先从评论列表拿到 comment-key,再输入 node、comment-key 与 content(--emoji 则作为表情回复),会实际发布一条回复。",
	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_comment_reply",
			CanonicalPath:  "doc.shortcut_comment_reply",
			CLIPath:        "doc +comment-reply",
			PrimaryCLIPath: "doc +comment-reply",
		},
		Description: "回复文档中的一条评论",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "回复文档中的一条评论",
			UseWhen:      []string{"当你要针对某条已有评论进行回复、参与讨论或用表情贴图回应时使用;先从评论列表拿到 comment-key,再输入 node、comment-key 与 content(--emoji 则作为表情回复),会实际发布一条回复。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +comment-reply --node DOC_ID --comment-key COMMENT_KEY --content \"同意\""},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "content", Type: shortcut.FlagString, Desc: "回复文字内容 (表情回复时填表情名称)", Required: true},
		{Name: "comment-key", Type: shortcut.FlagString, Desc: "被回复评论的 commentKey (从 list/create 获取)", Required: true},
		{Name: "emoji", Type: shortcut.FlagBool, Desc: "作为表情贴图回复"},
		{Name: "mention", Type: shortcut.FlagStringSlice, Desc: "被 @ 的用户 uid 列表"},
	},
	Tips: []string{`dws doc +comment-reply --node DOC_ID --comment-key COMMENT_KEY --content "同意"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{
			"nodeId":          rt.Str("node"),
			"content":         rt.Str("content"),
			"replyCommentKey": rt.Str("comment-key"),
		}
		if rt.Bool("emoji") {
			params["emoji"] = true
		}
		if rt.Changed("mention") {
			params["mentionedUserIds"] = rt.StrSlice("mention")
		}
		return rt.CallMCP("reply_comment", params)
	},
}
View Source
var CommentUpdate = shortcut.Shortcut{
	Service: "doc", Command: "+comment-update", Product: productComment,
	Description: "更新指定文档评论正文和 mention",
	Intent:      "当用户要修改一条已有评论的文字内容或 @ 用户列表,且已知 commentKey 时使用;不会创建回复或改变解决状态。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown"},
	Contract: docContract("+comment-update", "更新指定文档评论正文和 mention",
		"当用户要修改一条已有评论的文字内容或 @ 用户列表,且已知 commentKey 时使用;不会创建回复或改变解决状态。",
		[]string{`dws doc +comment-update --node <DOC_ID> --comment-key <COMMENT_KEY> --content "已按最新数据修正"`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "comment-key", Type: shortcut.FlagString, Desc: "评论 commentKey", Required: true},
		{Name: "content", Type: shortcut.FlagString, Desc: "更新后的评论正文", Required: true},
		{Name: "mention", Type: shortcut.FlagStringSlice, Desc: "被 @ 的用户 uid 列表"},
	},
	Tips: []string{`dws doc +comment-update --node <DOC_ID> --comment-key <COMMENT_KEY> --content "已按最新数据修正"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node"), "commentKey": rt.Str("comment-key"), "content": rt.Str("content")}
		if rt.Changed("mention") {
			params["mentionedUserIds"] = rt.StrSlice("mention")
		}
		return rt.CallMCP("update_comment", params)
	},
}
View Source
var Copy = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+copy",
	Product:     productDoc,
	Description: "复制文档/文件到指定文件夹或知识库",
	Intent:      "当你想保留原件、在另一个文件夹或知识库里生成一份文档/文件副本(例如以某篇文档为模板另存)时使用;输入源 node 与目标 folder/workspace,会实际创建一个副本。",
	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_copy",
			CanonicalPath:  "doc.shortcut_copy",
			CLIPath:        "doc +copy",
			PrimaryCLIPath: "doc +copy",
		},
		Description: "复制文档/文件到指定文件夹或知识库",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "复制文档/文件到指定文件夹或知识库",
			UseWhen:      []string{"当你想保留原件、在另一个文件夹或知识库里生成一份文档/文件副本(例如以某篇文档为模板另存)时使用;输入源 node 与目标 folder/workspace,会实际创建一个副本。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +copy --node DOC_ID --folder TARGET_FOLDER_NODE_ID"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档/文件 ID 或 URL", Required: true},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文档文件夹 nodeId 或 alidocs 文件夹 URL"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "目标知识库 ID"},
	},
	Tips: []string{`dws doc +copy --node DOC_ID --folder TARGET_FOLDER_NODE_ID`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node")}
		if rt.Changed("folder") {
			params["targetFolderId"] = rt.Str("folder")
		}
		if rt.Changed("workspace") {
			params["workspaceId"] = rt.Str("workspace")
		}
		return rt.CallMCP("copy_document", params)
	},
}
View Source
var Create = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+create",
	Product:     productDoc,
	Description: "从 Markdown 或 JSONML 创建在线文字文档",
	Intent:      "当用户要新建钉钉在线文字文档,并可同时写入 Markdown/JSONML 初始内容、指定文件夹或知识库位置时使用;不会用于普通文件上传或其他在线对象类型。",
	Risk:        shortcut.RiskWrite,
	Safety: contract.SafetySpec{
		Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown",
	},
	Contract: docContract(
		"+create", "从 Markdown 或 JSONML 创建在线文字文档",
		"当用户要新建钉钉在线文字文档,并可同时写入 Markdown/JSONML 初始内容、指定文件夹或知识库位置时使用;不会用于普通文件上传或其他在线对象类型。",
		[]string{`dws doc +create --name "项目周报" --content "# 本周进展"`, `dws doc +create --name "模板" --content @body.json --doc-format jsonml`},
		contract.ParamDecl{Name: "folder", Property: "folderId"},
		contract.ParamDecl{Name: "workspace", Property: "workspaceId"},
	),
	Flags: []shortcut.Flag{
		{Name: "name", Type: shortcut.FlagString, Desc: "新文档名称", Required: true},
		{Name: "content", Type: shortcut.FlagString, Desc: "内容字面量、@工作目录相对文件或 - 表示 stdin"},
		{Name: "doc-format", Type: shortcut.FlagString, Default: "markdown", Desc: "内容格式", Enum: []string{"markdown", "jsonml"}},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文档文件夹 ID"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "目标知识库 ID"},
	},
	Tips: []string{`dws doc +create --name "项目周报" --content "# 本周进展"`, `dws doc +create --name "模板" --content @body.json --doc-format jsonml`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		content, err := readShortcutContent(rt, "content")
		if err != nil {
			return err
		}
		format := rt.Str("doc-format")
		if format == "jsonml" && content != "" {
			content, err = validateJSONML(content)
			if err != nil {
				return err
			}
		}
		params := map[string]any{"name": rt.Str("name")}
		if rt.Str("folder") != "" {
			params["folderId"] = rt.Str("folder")
		}
		if rt.Str("workspace") != "" {
			params["workspaceId"] = rt.Str("workspace")
		}
		if format == "markdown" && content != "" {
			params["markdown"] = content
		}
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.create", map[string]any{"executed": false, "previewKind": "plan", "create": params, "docFormat": format, "contentBytes": len(content)}))
		}
		created, err := rt.CallMCPWriteData(productDoc, "create_document", params)
		if err != nil {
			return err
		}
		nodeID := nestedString(created, "nodeId", "documentId", "id")
		steps := []map[string]any{{"name": "create_document", "status": "success"}}
		if format == "jsonml" && content != "" {
			if nodeID == "" {
				return docPartialWriteError(
					"doc.create", "doc_create_missing_node_id", "resolve_created_document",
					"创建文档成功但响应缺少 nodeId;JSONML 尚未写入,请先在钉钉中定位新文档,不要直接重试",
					nil,
					map[string]any{"nodeId": "", "docFormat": format},
					append(steps, map[string]any{"name": "write_jsonml", "status": "not_started"}),
					map[string]any{"available": false, "reason": "create_document did not return nodeId; locate the new document in DingTalk"},
				)
			}
			if _, err := rt.CallMCPWriteData(productDoc, "update_document", map[string]any{"nodeId": nodeID, "format": "jsonml", "jsonml": content, "mode": "overwrite"}); err != nil {
				return docPartialWriteError(
					"doc.create", "doc_create_initial_content_failed", "write_jsonml",
					fmt.Sprintf("文档已创建但 JSONML 写入失败(nodeId=%s);不要直接重试创建", nodeID),
					err,
					map[string]any{"nodeId": nodeID, "docFormat": format},
					append(steps, map[string]any{"name": "write_jsonml", "status": "failed"}),
					map[string]any{"available": true, "action": "delete_created_document", "nodeId": nodeID, "reason": "remove the empty document before retrying create"},
				)
			}
			steps = append(steps, map[string]any{"name": "write_jsonml", "status": "success"})
		}
		return rt.Output(docEnvelope("doc.create", map[string]any{"nodeId": nodeID, "result": created}, steps...))
	},
}
View Source
var CreateFromTemplate = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+create-from-template",
	Product:     productDoc,
	Description: "按 templateId 直达或搜索消歧后创建文档",
	Intent:      "当用户要基于文档模板创建新文档时使用;可直接给 template-id,或给 query 搜索且只在唯一命中时继续创建。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown"},
	Contract: docContract("+create-from-template", "按 templateId 直达或搜索消歧后创建文档",
		"当用户要基于文档模板创建新文档时使用;可直接给 template-id,或给 query 搜索且只在唯一命中时继续创建。",
		[]string{`dws doc +create-from-template --template-id <TEMPLATE_ID> --name "我的周报"`, `dws doc +create-from-template --query "会议纪要" --name "项目例会"`}),
	Flags: []shortcut.Flag{
		{Name: "template-id", Type: shortcut.FlagString, Desc: "模板 ID"},
		{Name: "query", Type: shortcut.FlagString, Desc: "模板搜索名称"},
		{Name: "source", Type: shortcut.FlagString, Desc: "模板来源", Enum: []string{"MY", "PUBLIC"}},
		{Name: "name", Type: shortcut.FlagString, Desc: "新文档名称"},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文件夹 ID"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "目标知识库 ID"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintExactlyOne, Flags: []string{"template-id", "query"}, Description: "--template-id 与 --query 必须且只能提供一个"}},
	Tips:        []string{`dws doc +create-from-template --template-id <TEMPLATE_ID> --name "我的周报"`, `dws doc +create-from-template --query "会议纪要" --name "项目例会"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		templateID := rt.Str("template-id")
		if templateID == "" {
			params := map[string]any{"searchName": rt.Str("query")}
			if rt.Str("source") != "" {
				params["templateSource"] = rt.Str("source")
			}
			found, err := rt.CallMCPData(productDoc, "search_doc_templates", params)
			if err != nil {
				return err
			}
			ids := collectTemplateIDs(found)
			if len(ids) != 1 {
				return apperrors.NewValidation(fmt.Sprintf("模板搜索需要唯一命中,实际 %d 个候选: %v", len(ids), ids))
			}
			templateID = ids[0]
		}
		params := map[string]any{"templateId": templateID}
		for flag, property := range map[string]string{"name": "name", "folder": "folderId", "workspace": "workspaceId"} {
			if value := rt.Str(flag); value != "" {
				params[property] = value
			}
		}
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.create_from_template", map[string]any{"executed": false, "params": params}))
		}
		result, err := rt.CallMCPWriteData(productDoc, "apply_doc_template", params)
		if err != nil {
			return err
		}
		return rt.Output(docEnvelope("doc.create_from_template", result, map[string]any{"name": "apply_template", "status": "success"}))
	},
}
View Source
var Export = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+export",
	Product:     productDoc,
	Description: "提交、轮询并安全下载在线文档导出文件",
	Intent:      "当用户要把在线文档导出成 docx、markdown 或 PDF 并保存到工作目录时使用;自动完成 job 提交、轮询与 no-clobber 原子下载。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+export", "提交、轮询并安全下载在线文档导出文件",
		"当用户要把在线文档导出成 docx、markdown 或 PDF 并保存到工作目录时使用;自动完成 job 提交、轮询与 no-clobber 原子下载。",
		[]string{`dws doc +export --node <DOC_ID> --export-format docx --output ./exports/`, `dws doc +export --node <DOC_ID> --export-format markdown --output ./document.md`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "export-format", Type: shortcut.FlagString, Default: "docx", Desc: "导出格式", Enum: []string{"docx", "markdown", "pdf"}},
		{Name: "output", Type: shortcut.FlagString, Default: ".", Desc: "工作目录内相对路径(文件或目录)"},
		{Name: "max-polls", Type: shortcut.FlagInt, Default: "30", Desc: "最大轮询次数"},
	},
	Tips:        []string{`dws doc +export --node <DOC_ID> --export-format docx --output ./exports/`, `dws doc +export --node <DOC_ID> --export-format markdown --output ./document.md`},
	Validate:    func(rt *shortcut.RuntimeContext) error { return localio.ValidateOutput(rt.Str("output")) },
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"output"}, Description: "--output 必须是工作目录内相对路径;默认 no-clobber"}},
	Execute:     executeExport,
}
View Source
var ExportGet = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+export-get",
	Product:     productDoc,
	Description: "根据 jobId 查询文档导出任务结果",
	Intent:      "当你已用 +export-submit 提交了导出任务、想查询它是否完成并拿到导出文件的下载链接时使用;输入上一步返回的 job-id,返回任务状态与结果。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_export_get",
			CanonicalPath:  "doc.shortcut_export_get",
			CLIPath:        "doc +export-get",
			PrimaryCLIPath: "doc +export-get",
		},
		Description: "根据 jobId 查询文档导出任务结果",
		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: "根据 jobId 查询文档导出任务结果",
			UseWhen:      []string{"当你已用 +export-submit 提交了导出任务、想查询它是否完成并拿到导出文件的下载链接时使用;输入上一步返回的 job-id,返回任务状态与结果。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +export-get --job-id JOB_ID"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "job-id", Type: shortcut.FlagString, Desc: "导出任务 ID", Required: true},
	},
	Tips: []string{`dws doc +export-get --job-id JOB_ID`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		return rt.CallMCP("query_export_job", map[string]any{"jobId": rt.Str("job-id")})
	},
}
View Source
var ExportSubmit = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+export-submit",
	Product:     productDoc,
	Description: "提交在线文档导出任务 (docx/markdown/pdf),返回 jobId",
	Intent:      "当你想把在线文档导出成 docx/markdown/pdf 文件(例如离线保存或外发)时使用;这是异步任务的第一步,输入 node 与 export-format 提交导出,返回 jobId,随后用 +export-get 轮询结果。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_export_submit",
			CanonicalPath:  "doc.shortcut_export_submit",
			CLIPath:        "doc +export-submit",
			PrimaryCLIPath: "doc +export-submit",
		},
		Description: "提交在线文档导出任务 (docx/markdown/pdf),返回 jobId",
		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: "提交在线文档导出任务 (docx/markdown/pdf),返回 jobId",
			UseWhen:      []string{"当你想把在线文档导出成 docx/markdown/pdf 文件(例如离线保存或外发)时使用;这是异步任务的第一步,输入 node 与 export-format 提交导出,返回 jobId,随后用 +export-get 轮询结果。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +export-submit --node DOC_ID --export-format markdown"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "要导出的文档 ID 或 URL", Required: true},
		{Name: "export-format", Type: shortcut.FlagString, Default: "docx", Desc: "导出格式", Enum: []string{"docx", "markdown", "pdf"}},
	},
	Tips: []string{`dws doc +export-submit --node DOC_ID --export-format markdown`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		format := rt.Str("export-format")
		if format == "" {
			format = "docx"
		}
		return rt.CallMCP("submit_export_job", map[string]any{
			"nodeId":       rt.Str("node"),
			"exportFormat": format,
		})
	},
}
View Source
var Fetch = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+fetch",
	Product:     productDoc,
	Description: "读取完整或局部文档内容,并按 detail 控制保真度",
	Intent:      "当用户要读取在线文字文档正文,或需要 block ID、JSONML、outline/range/section/keyword/tags 局部内容用于精确编辑和评论时使用;非最新历史 revision 会明确拒绝。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract(
		"+fetch", "读取完整或局部文档内容,并按 detail 控制保真度",
		"当用户要读取在线文字文档正文,或需要 block ID、JSONML、outline/range/section/keyword/tags 局部内容用于精确编辑和评论时使用;非最新历史 revision 会明确拒绝。",
		[]string{`dws doc +fetch --node <DOC_ID>`, `dws doc +fetch --node <DOC_ID> --detail with-ids --scope keyword --keyword "结论"`},
		contract.ParamDecl{Name: "node", Property: "nodeId"},
	),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "detail", Type: shortcut.FlagString, Default: "simple", Desc: "输出细节", Enum: []string{"simple", "with-ids", "full"}},
		{Name: "scope", Type: shortcut.FlagString, Default: "full", Desc: "读取范围;keyword 时 --keyword 不能为空", Enum: []string{"full", "outline", "range", "section", "keyword", "tags"}},
		{Name: "start-block-id", Type: shortcut.FlagString, Desc: "range/section 起始块 ID"},
		{Name: "end-block-id", Type: shortcut.FlagString, Desc: "range 结束块 ID"},
		{Name: "keyword", Type: shortcut.FlagString, Desc: "keyword 范围搜索词,不能为空,支持 foo|bar"},
		{Name: "tags", Type: shortcut.FlagStringSlice, Desc: "tags 范围的 JSONML tag"},
		{Name: "context-before", Type: shortcut.FlagInt, Desc: "关键词命中前的上下文字符数"},
		{Name: "context-after", Type: shortcut.FlagInt, Desc: "关键词命中后的上下文字符数"},
		{Name: "max-depth", Type: shortcut.FlagInt, Desc: "outline/section 最大深度"},
		{Name: "revision", Type: shortcut.FlagInt, Desc: "只接受当前最新版;历史 revision 暂不支持"},
	},
	Tips: []string{`dws doc +fetch --node <DOC_ID>`, `dws doc +fetch --node <DOC_ID> --detail with-ids --scope keyword --keyword "结论"`},
	Validate: func(rt *shortcut.RuntimeContext) error {
		if rt.Changed("revision") {
			return apperrors.NewValidation("HISTORICAL_READ_UNSUPPORTED: 当前接口不能读取指定历史 revision")
		}
		if rt.Str("scope") == "keyword" && rt.Str("keyword") == "" {
			return apperrors.NewValidation("--scope keyword 时必须提供 --keyword")
		}
		return nil
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"scope", "keyword"}, Description: "--scope keyword 时 --keyword 不能为空"}},
	Execute: func(rt *shortcut.RuntimeContext) error {
		format := "markdown"
		if rt.Str("detail") != "simple" || rt.Str("scope") != "full" {
			format = "jsonml"
		}
		params := map[string]any{"nodeId": rt.Str("node"), "format": format}
		scope := rt.Str("scope")
		if scope != "keyword" && scope != "full" {
			params["scope"] = scope
		}
		if value := rt.Str("start-block-id"); value != "" {
			params["startBlockId"] = value
		}
		if value := rt.Str("end-block-id"); value != "" {
			params["endBlockId"] = value
		}
		if rt.Changed("tags") {
			params["tags"] = rt.StrSlice("tags")
		}
		if rt.Changed("max-depth") {
			params["maxDepth"] = rt.Int("max-depth")
		}
		data, err := rt.CallMCPData(productDoc, "get_document_content", params)
		if err != nil {
			return err
		}
		if scope == "keyword" {
			return rt.Output(projectKeywordMatches(data, rt.Str("keyword"), rt.Int("context-before"), rt.Int("context-after")))
		}
		return rt.Output(data)
	},
}
View Source
var Import = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+import",
	Product:     productDoc,
	Description: "上传本地文件并等待转换成在线文档对象",
	Intent:      "当用户要把工作区内的 doc/docx/xls/xlsx/md/txt/xmind/mark 文件导入为钉钉在线对象,并可指定目标文件夹或知识库时使用。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "not_required", Idempotency: "unknown"},
	Contract: docContract("+import", "上传本地文件并等待转换成在线文档对象",
		"当用户要把工作区内的 doc/docx/xls/xlsx/md/txt/xmind/mark 文件导入为钉钉在线对象,并可指定目标文件夹或知识库时使用。",
		[]string{`dws doc +import --file ./report.docx --folder <FOLDER_ID>`, `dws doc +import --file ./notes.md --workspace <WORKSPACE_ID> --name "会议纪要"`}),
	Flags: []shortcut.Flag{
		{Name: "file", Type: shortcut.FlagString, Desc: "本地文件路径", Required: true},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文件夹 ID"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "目标知识库 ID"},
		{Name: "name", Type: shortcut.FlagString, Desc: "导入后名称"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"folder", "workspace"}, Description: "--folder 与 --workspace 至少提供一个导入目标"}},
	Tips:        []string{`dws doc +import --file ./report.docx --folder <FOLDER_ID>`, `dws doc +import --file ./notes.md --workspace <WORKSPACE_ID> --name "会议纪要"`},
	Execute:     func(rt *shortcut.RuntimeContext) error { return helpers.RunDocImportShortcut(rt.Command()) },
}
View Source
var Inspect = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+inspect",
	Product:     productDoc,
	Description: "聚合文档元信息,并按需附带样式、权限、历史、媒体和评论",
	Intent:      "当用户需要在一次调用中了解文档类型、标题、链接和可选的协作/样式/历史/媒体/评论状态,而不是读取正文时使用。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+inspect", "聚合文档元信息,并按需附带样式、权限、历史、媒体和评论",
		"当用户需要在一次调用中了解文档类型、标题、链接和可选的协作/样式/历史/媒体/评论状态,而不是读取正文时使用。",
		[]string{`dws doc +inspect --node <DOC_ID>`, `dws doc +inspect --node <DOC_ID> --include-style --include-permissions --include-comments`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "include-style", Type: shortcut.FlagBool, Desc: "附带封面和背景"},
		{Name: "include-permissions", Type: shortcut.FlagBool, Desc: "附带权限列表"},
		{Name: "include-history", Type: shortcut.FlagBool, Desc: "附带最近历史版本"},
		{Name: "include-media", Type: shortcut.FlagBool, Desc: "附带正文媒体列表"},
		{Name: "include-comments", Type: shortcut.FlagBool, Desc: "附带评论列表"},
	},
	Tips: []string{`dws doc +inspect --node <DOC_ID>`, `dws doc +inspect --node <DOC_ID> --include-style --include-permissions --include-comments`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		node := rt.Str("node")
		result := map[string]any{}
		info, err := rt.CallMCPData(productDoc, "get_document_info", map[string]any{"nodeId": node})
		if err != nil {
			return err
		}
		result["document"] = info
		reads := []struct {
			flag, key, product, tool string
			params                   map[string]any
		}{
			{"include-style", "style", productDoc, "get_document_style", map[string]any{"nodeId": node}},
			{"include-permissions", "permissions", productDoc, "list_permission", map[string]any{"nodeId": node}},
			{"include-history", "history", productDoc, "list_doc_versions", map[string]any{"nodeId": node}},
			{"include-media", "media", productDoc, "list_document_blocks", map[string]any{"nodeId": node, "format": "jsonml"}},
			{"include-comments", "comments", productComment, "list_comments", map[string]any{"nodeId": node}},
		}
		for _, read := range reads {
			if !rt.Bool(read.flag) {
				continue
			}
			value, callErr := rt.CallMCPData(read.product, read.tool, read.params)
			if callErr != nil {
				return callErr
			}
			result[read.key] = value
		}
		return rt.Output(docEnvelope("doc.inspect", result, map[string]any{"name": "inspect", "status": "success"}))
	},
}
View Source
var List = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+list",
	Product:     productDoc,
	Description: "列出文件夹或知识库下的直接子节点",
	Intent:      "当你已知某个文档文件夹或知识库的 ID、想浏览它下面直接包含的文档与子文件夹(不递归深层)以便逐层导航时使用;输入 folder 或 workspace,返回该层级的子节点列表。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_list",
			CanonicalPath:  "doc.shortcut_list",
			CLIPath:        "doc +list",
			PrimaryCLIPath: "doc +list",
		},
		Description: "列出文件夹或知识库下的直接子节点",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出文件夹或知识库下的直接子节点",
			UseWhen:      []string{"当你已知某个文档文件夹或知识库的 ID、想浏览它下面直接包含的文档与子文件夹(不递归深层)以便逐层导航时使用;输入 folder 或 workspace,返回该层级的子节点列表。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples: []string{
				"dws doc +list --folder DOC_FOLDER_NODE_ID",
				"dws doc +list --workspace WS_ID --limit 20",
			},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "folder", Type: shortcut.FlagString, Desc: "文档文件夹 nodeId 或 alidocs 文件夹 URL"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "知识库 ID"},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "每页数量 (默认 50,最大 50)"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标 (上次结果的 nextPageToken)"},
	},
	Tips: []string{`dws doc +list --folder DOC_FOLDER_NODE_ID`, `dws doc +list --workspace WS_ID --limit 20`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{}
		if rt.Changed("folder") {
			params["folderId"] = rt.Str("folder")
		}
		if rt.Changed("workspace") {
			params["workspaceId"] = rt.Str("workspace")
		}
		if rt.Changed("limit") {
			params["pageSize"] = rt.Int("limit")
		}
		if rt.Changed("cursor") {
			params["pageToken"] = rt.Str("cursor")
		}
		data, err := rt.CallMCPData(productDoc, "list_nodes", params)
		if err != nil {
			return err
		}
		nodes := listNodesProject(data)
		return rt.Output(map[string]any{"count": len(nodes), "nodes": nodes})
	},
}
View Source
var MediaDownload = shortcut.Shortcut{
	Service: "doc", Command: "+media-download", Product: productDoc,
	Description: "安全下载文档正文附件到工作目录",
	Intent:      "当用户已从 media-list 或 block 数据拿到 resourceId,要把正文附件保存到本地时使用;默认拒绝覆盖并原子发布文件。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+media-download", "安全下载文档正文附件到工作目录",
		"当用户已从 media-list 或 block 数据拿到 resourceId,要把正文附件保存到本地时使用;默认拒绝覆盖并原子发布文件。",
		[]string{`dws doc +media-download --node <DOC_ID> --resource-id <RESOURCE_ID> --output ./downloads/`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "resource-id", Type: shortcut.FlagString, Desc: "附件 resourceId", Required: true},
		{Name: "output", Type: shortcut.FlagString, Default: ".", Desc: "工作目录内相对路径(文件或目录)"},
	},
	Validate:    func(rt *shortcut.RuntimeContext) error { return localio.ValidateOutput(rt.Str("output")) },
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"output"}, Description: "--output 必须是工作目录内相对路径;默认 no-clobber"}},
	Tips:        []string{`dws doc +media-download --node <DOC_ID> --resource-id <RESOURCE_ID> --output ./downloads/`},
	Execute:     executeMediaDownload,
}
View Source
var MediaInsert = shortcut.Shortcut{
	Service: "doc", Command: "+media-insert", Product: productDoc,
	Description: "上传本地图片或文件并插入文档正文",
	Intent:      "当用户要把本地图片或附件作为正文 block 插入在线文档时使用;组合本地校验、上传凭证、OSS PUT 和插块,失败时不会伪造完整回滚。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "unknown"},
	Contract: withDryRun(docContract("+media-insert", "上传本地图片或文件并插入文档正文",
		"当用户要把本地图片或附件作为正文 block 插入在线文档时使用;组合本地校验、上传凭证、OSS PUT 和插块,失败时不会伪造完整回滚。",
		[]string{`dws doc +media-insert --node <DOC_ID> --file ./report.pdf`, `dws doc +media-insert --node <DOC_ID> --file ./image.png --ref-block <BLOCK_ID> --where after`}), contract.DryRunPreviewPlan, false),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "file", Type: shortcut.FlagString, Desc: "本地文件路径", Required: true},
		{Name: "name", Type: shortcut.FlagString, Desc: "显示名称"},
		{Name: "mime-type", Type: shortcut.FlagString, Desc: "MIME 类型"},
		{Name: "index", Type: shortcut.FlagInt, Desc: "顶层插入索引"},
		{Name: "where", Type: shortcut.FlagString, Desc: "相对参考块的位置", Enum: []string{"before", "after"}},
		{Name: "ref-block", Type: shortcut.FlagString, Desc: "参考 block ID"},
	},
	Tips:    []string{`dws doc +media-insert --node <DOC_ID> --file ./report.pdf`, `dws doc +media-insert --node <DOC_ID> --file ./image.png --ref-block <BLOCK_ID> --where after`},
	Execute: func(rt *shortcut.RuntimeContext) error { return helpers.RunDocMediaInsertShortcut(rt.Command()) },
}
View Source
var MediaList = shortcut.Shortcut{
	Service: "doc", Command: "+media-list", Product: productDoc,
	Description: "列出文档正文中的图片和附件资源",
	Intent:      "当用户要发现文档内可下载或可定位的图片、附件及其 blockId/resourceId 时使用;只读取并投影媒体节点。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+media-list", "列出文档正文中的图片和附件资源",
		"当用户要发现文档内可下载或可定位的图片、附件及其 blockId/resourceId 时使用;只读取并投影媒体节点。",
		[]string{`dws doc +media-list --node <DOC_ID>`}),
	Flags: []shortcut.Flag{{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true}},
	Tips:  []string{`dws doc +media-list --node <DOC_ID>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		data, err := rt.CallMCPData(productDoc, "list_document_blocks", map[string]any{"nodeId": rt.Str("node"), "format": "element"})
		if err != nil {
			return err
		}
		items := collectMediaItems(data)
		return rt.Output(map[string]any{"count": len(items), "media": items})
	},
}
View Source
var MediaPreview = shortcut.Shortcut{
	Service: "doc", Command: "+media-preview", Product: productDoc,
	Description: "下载正文媒体到受控临时目录并返回预览路径",
	Intent:      "当用户要临时查看文档附件或图片内容而不指定持久保存路径时使用;下载到独立临时目录并返回 artifact 路径。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+media-preview", "下载正文媒体到受控临时目录并返回预览路径",
		"当用户要临时查看文档附件或图片内容而不指定持久保存路径时使用;下载到独立临时目录并返回 artifact 路径。",
		[]string{`dws doc +media-preview --node <DOC_ID> --resource-id <RESOURCE_ID>`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "resource-id", Type: shortcut.FlagString, Desc: "附件 resourceId", Required: true},
	},
	Tips: []string{`dws doc +media-preview --node <DOC_ID> --resource-id <RESOURCE_ID>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.media_preview", map[string]any{"executed": false, "nodeId": rt.Str("node"), "resourceId": rt.Str("resource-id"), "output": "managed_temp_dir"}))
		}
		data, err := rt.CallMCPData(productDoc, "download_doc_attachment", map[string]any{"nodeId": rt.Str("node"), "resourceId": rt.Str("resource-id")})
		if err != nil {
			return err
		}
		dir, err := docMkdirTemp("", "dws-doc-preview-*")
		if err != nil {
			return err
		}
		result, err := downloadResolvedResource(rt, data, dir, ".")
		if err != nil {
			_ = docRemoveAll(dir)
			return err
		}
		return rt.Output(docEnvelope("doc.media_preview", map[string]any{"previewPath": result.AbsolutePath, "sizeBytes": result.SizeBytes}))
	},
}
View Source
var Move = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+move",
	Product:     productDoc,
	Description: "移动文档/文件到指定文件夹或知识库",
	Intent:      "当你要整理文档归属、把某篇文档/文件从当前位置挪到另一个文件夹或知识库(原位置不再保留)时使用;输入 node 与目标 folder/workspace,会实际改变文件的存放位置。",
	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_move",
			CanonicalPath:  "doc.shortcut_move",
			CLIPath:        "doc +move",
			PrimaryCLIPath: "doc +move",
		},
		Description: "移动文档/文件到指定文件夹或知识库",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "移动文档/文件到指定文件夹或知识库",
			UseWhen:      []string{"当你要整理文档归属、把某篇文档/文件从当前位置挪到另一个文件夹或知识库(原位置不再保留)时使用;输入 node 与目标 folder/workspace,会实际改变文件的存放位置。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +move --node DOC_ID --folder TARGET_FOLDER_NODE_ID"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档/文件 ID 或 URL", Required: true},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文档文件夹 nodeId 或 alidocs 文件夹 URL"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "目标知识库 ID"},
	},
	Tips: []string{`dws doc +move --node DOC_ID --folder TARGET_FOLDER_NODE_ID`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node")}
		if rt.Changed("folder") {
			params["targetFolderId"] = rt.Str("folder")
		}
		if rt.Changed("workspace") {
			params["workspaceId"] = rt.Str("workspace")
		}
		return rt.CallMCP("move_document", params)
	},
}
View Source
var ResourceDelete = shortcut.Shortcut{
	Service: "doc", Command: "+resource-delete", Product: productDoc,
	Description: "幂等清除文档封面",
	Intent:      "当用户明确要移除文档当前封面时使用;发送 cover clear,重复执行保持无封面状态。",
	Risk:        shortcut.RiskHighWrite,
	Safety:      contract.SafetySpec{Effect: "destructive", Risk: "high", Confirmation: "user_required", Idempotency: "idempotent"},
	Contract: docContract("+resource-delete", "幂等清除文档封面",
		"当用户明确要移除文档当前封面时使用;发送 cover clear,重复执行保持无封面状态。",
		[]string{`dws doc +resource-delete --node <DOC_ID>`}),
	Flags: []shortcut.Flag{{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true}},
	Tips:  []string{`dws doc +resource-delete --node <DOC_ID>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node"), "cover": map[string]any{"action": "clear"}}
		if rt.DryRun() {
			return rt.Output(docEnvelope("doc.resource_delete", map[string]any{"executed": false, "params": params}))
		}
		return rt.CallMCP("update_document_style", params)
	},
}
View Source
var ResourceDownload = shortcut.Shortcut{
	Service: "doc", Command: "+resource-download", Product: productDoc,
	Description: "读取并安全下载当前文档封面",
	Intent:      "当用户要把当前文档封面保存到本地时使用;先读 style,必要时用 resourceId 换临时链接,再按安全本地下载策略保存。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+resource-download", "读取并安全下载当前文档封面",
		"当用户要把当前文档封面保存到本地时使用;先读 style,必要时用 resourceId 换临时链接,再按安全本地下载策略保存。",
		[]string{`dws doc +resource-download --node <DOC_ID> --output ./cover.png`}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "output", Type: shortcut.FlagString, Default: ".", Desc: "工作目录内相对路径(文件或目录)"},
	},
	Validate:    func(rt *shortcut.RuntimeContext) error { return localio.ValidateOutput(rt.Str("output")) },
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"output"}, Description: "--output 必须是工作目录内相对路径;默认 no-clobber"}},
	Tips:        []string{`dws doc +resource-download --node <DOC_ID> --output ./cover.png`},
	Execute:     executeResourceDownload,
}
View Source
var ResourceUpdate = shortcut.Shortcut{
	Service: "doc", Command: "+resource-update", Product: productDoc,
	Description: "从本地图片或 HTTPS URL 设置文档封面",
	Intent:      "当用户要设置或替换文档顶部封面图时使用;本地图片会先上传,HTTPS URL 由服务端转存。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "idempotent"},
	Contract: withDryRun(docContract("+resource-update", "从本地图片或 HTTPS URL 设置文档封面",
		"当用户要设置或替换文档顶部封面图时使用;本地图片会先上传,HTTPS URL 由服务端转存。",
		[]string{`dws doc +resource-update --node <DOC_ID> --image https://example.com/cover.png`, `dws doc +resource-update --node <DOC_ID> --file ./cover.png`}), contract.DryRunPreviewRequest, false),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "image", Type: shortcut.FlagString, Desc: "HTTPS 封面图片 URL"},
		{Name: "file", Type: shortcut.FlagString, Desc: "本地封面图片"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintExactlyOne, Flags: []string{"image", "file"}, Description: "--image 与 --file 必须且只能提供一个"}},
	Tips:        []string{`dws doc +resource-update --node <DOC_ID> --image https://example.com/cover.png`, `dws doc +resource-update --node <DOC_ID> --file ./cover.png`},
	Execute:     func(rt *shortcut.RuntimeContext) error { return helpers.RunDocResourceUpdateShortcut(rt.Command()) },
}
View Source
var Review = shortcut.Shortcut{
	Service: "doc", Command: "+review", Product: productComment,
	Description: "聚合未解决评论、引用原文和块上下文",
	Intent:      "当用户要确定性查看一篇文档仍待处理的 review 意见时使用;聚合 unresolved 评论与 block 上下文,不调用模型生成总结。",
	Risk:        shortcut.RiskRead,
	Safety:      contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"},
	Contract: docContract("+review", "聚合未解决评论、引用原文和块上下文",
		"当用户要确定性查看一篇文档仍待处理的 review 意见时使用;聚合 unresolved 评论与 block 上下文,不调用模型生成总结。",
		[]string{`dws doc +review --node <DOC_ID>`}),
	Flags: []shortcut.Flag{{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true}},
	Tips:  []string{`dws doc +review --node <DOC_ID>`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		node := rt.Str("node")
		comments, err := rt.CallMCPData(productComment, "list_comments", map[string]any{"nodeId": node, "resolveStatus": "unresolved"})
		if err != nil {
			return err
		}
		blocks, err := rt.CallMCPData(productDoc, "list_document_blocks", map[string]any{"nodeId": node, "format": "element"})
		if err != nil {
			return err
		}
		items := projectReviewComments(comments, blocks)
		global, inline := 0, 0
		for _, item := range items {
			if item["blockId"] == "" {
				global++
			} else {
				inline++
			}
		}
		return rt.Output(map[string]any{"status": "unresolved", "counts": map[string]any{"total": len(items), "global": global, "inline": inline}, "comments": items})
	},
}
View Source
var Search = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+search",
	Product:     productDoc,
	Description: "按关键词搜索有权限的文档 (不传则返回最近访问)",
	Intent:      "当你只记得文档的标题或主题词、需要先定位到某篇钉钉文档拿到它的 nodeId/URL 以便后续阅读或编辑时使用;可按关键词、扩展名、创建/访问时间、创建者等条件过滤,不传关键词则返回最近访问的文档,返回匹配的文档列表。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_search",
			CanonicalPath:  "doc.shortcut_search",
			CLIPath:        "doc +search",
			PrimaryCLIPath: "doc +search",
		},
		Description: "按关键词搜索有权限的文档 (不传则返回最近访问)",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "按关键词搜索有权限的文档 (不传则返回最近访问)",
			UseWhen:      []string{"当你只记得文档的标题或主题词、需要先定位到某篇钉钉文档拿到它的 nodeId/URL 以便后续阅读或编辑时使用;可按关键词、扩展名、创建/访问时间、创建者等条件过滤,不传关键词则返回最近访问的文档,返回匹配的文档列表。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples: []string{
				"dws doc +search --query \"会议纪要\"",
				"dws doc +search --extensions pdf,docx",
			},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词,不传返回最近访问的文档"},
		{Name: "extensions", Type: shortcut.FlagStringSlice, Desc: "按文件扩展名过滤 (如 adoc,axls,pdf)"},
		{Name: "created-from", Type: shortcut.FlagInt, Desc: "创建时间起始 (毫秒时间戳)"},
		{Name: "created-to", Type: shortcut.FlagInt, Desc: "创建时间截止 (毫秒时间戳)"},
		{Name: "visited-from", Type: shortcut.FlagInt, Desc: "访问时间起始 (毫秒时间戳)"},
		{Name: "visited-to", Type: shortcut.FlagInt, Desc: "访问时间截止 (毫秒时间戳)"},
		{Name: "creator-uids", Type: shortcut.FlagStringSlice, Desc: "按创建者用户 ID 过滤"},
		{Name: "editor-uids", Type: shortcut.FlagStringSlice, Desc: "按编辑者用户 ID 过滤"},
		{Name: "mentioned-uids", Type: shortcut.FlagStringSlice, Desc: "按 @提及的用户 ID 过滤"},
		{Name: "workspace-ids", Type: shortcut.FlagStringSlice, Desc: "按知识库 ID 过滤"},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "每页数量 (默认 10,最大 30)"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标 (上次结果的 nextPageToken)"},
	},
	Tips: []string{`dws doc +search --query "会议纪要"`, `dws doc +search --extensions pdf,docx`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{}
		if v := rt.Str("query"); v != "" {
			params["keyword"] = v
		}
		if rt.Changed("extensions") {
			params["extensions"] = rt.StrSlice("extensions")
		}
		if rt.Changed("created-from") {
			params["createdTimeFrom"] = rt.Int("created-from")
		}
		if rt.Changed("created-to") {
			params["createdTimeTo"] = rt.Int("created-to")
		}
		if rt.Changed("visited-from") {
			params["visitedTimeFrom"] = rt.Int("visited-from")
		}
		if rt.Changed("visited-to") {
			params["visitedTimeTo"] = rt.Int("visited-to")
		}
		if rt.Changed("creator-uids") {
			params["creatorUserIds"] = rt.StrSlice("creator-uids")
		}
		if rt.Changed("editor-uids") {
			params["editorUserIds"] = rt.StrSlice("editor-uids")
		}
		if rt.Changed("mentioned-uids") {
			params["mentionedUserIds"] = rt.StrSlice("mentioned-uids")
		}
		if rt.Changed("workspace-ids") {
			params["workspaceIds"] = rt.StrSlice("workspace-ids")
		}
		if rt.Changed("limit") {
			params["pageSize"] = rt.Int("limit")
		}
		if rt.Changed("cursor") {
			params["pageToken"] = rt.Str("cursor")
		}
		data, err := rt.CallMCPData(productDoc, "search_documents", params)
		if err != nil {
			return err
		}
		docs := searchDocsProject(data)
		return rt.Output(map[string]any{"count": len(docs), "documents": docs})
	},
}
View Source
var TemplateApply = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+template-apply",
	Product:     productDoc,
	Description: "使用指定模板创建新文档",
	Intent:      "当你已选定某个模板、想据此快速生成一篇带预设结构的新文档时使用;输入 template-id(可选 name/folder/workspace),会实际按模板创建一篇新文档并返回其 ID。",
	Risk:        shortcut.RiskWrite,
	Flags: []shortcut.Flag{
		{Name: "template-id", Type: shortcut.FlagString, Desc: "模板 ID", Required: true},
		{Name: "name", Type: shortcut.FlagString, Desc: "新文档名称 (可选)"},
		{Name: "folder", Type: shortcut.FlagString, Desc: "目标文件夹 ID (可选)"},
		{Name: "workspace", Type: shortcut.FlagString, Desc: "知识库 ID (可选)"},
	},
	Tips: []string{`dws doc +template-apply --template-id TPL_ID --name "我的周报"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"templateId": rt.Str("template-id")}
		if v := rt.Str("name"); v != "" {
			params["name"] = v
		}
		if rt.Changed("folder") {
			params["folderId"] = rt.Str("folder")
		}
		if rt.Changed("workspace") {
			params["workspaceId"] = rt.Str("workspace")
		}
		return rt.CallMCP("apply_doc_template", params)
	},
}
View Source
var TemplateList = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+template-list",
	Product:     productDoc,
	Description: "获取文档模板列表",
	Intent:      "当你想基于模板新建文档、需要先浏览可用的模板(自己的 MY 或公共 PUBLIC)并拿到 templateId 时使用;返回模板列表,随后可配合 +template-apply 套用。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_template_list",
			CanonicalPath:  "doc.shortcut_template_list",
			CLIPath:        "doc +template-list",
			PrimaryCLIPath: "doc +template-list",
		},
		Description: "获取文档模板列表",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "获取文档模板列表",
			UseWhen:      []string{"当你想基于模板新建文档、需要先浏览可用的模板(自己的 MY 或公共 PUBLIC)并拿到 templateId 时使用;返回模板列表,随后可配合 +template-apply 套用。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +template-list --source PUBLIC"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "source", Type: shortcut.FlagString, Desc: "模板来源: MY / PUBLIC (默认 MY)", Enum: []string{"MY", "PUBLIC"}},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "返回数量上限"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标"},
	},
	Tips: []string{`dws doc +template-list --source PUBLIC`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{}
		if v := rt.Str("source"); v != "" {
			params["templateSource"] = v
		}
		if rt.Changed("limit") {
			params["maxResults"] = rt.Int("limit")
		}
		if v := rt.Str("cursor"); v != "" {
			params["nextCursor"] = v
		}
		return rt.CallMCP("list_doc_templates", params)
	},
}
View Source
var TemplateSearch = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+template-search",
	Product:     productDoc,
	Description: "根据关键词搜索文档模板",
	Intent:      "当模板较多、你想按关键词(如“周报”“合同”)快速找到合适的模板并拿到 templateId 时使用;输入 query,返回匹配的模板列表,随后可配合 +template-apply 套用。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_template_search",
			CanonicalPath:  "doc.shortcut_template_search",
			CLIPath:        "doc +template-search",
			PrimaryCLIPath: "doc +template-search",
		},
		Description: "根据关键词搜索文档模板",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "根据关键词搜索文档模板",
			UseWhen:      []string{"当模板较多、你想按关键词(如“周报”“合同”)快速找到合适的模板并拿到 templateId 时使用;输入 query,返回匹配的模板列表,随后可配合 +template-apply 套用。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +template-search --query \"周报\""},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词", Required: true},
		{Name: "source", Type: shortcut.FlagString, Desc: "模板来源: MY / PUBLIC (默认 MY)", Enum: []string{"MY", "PUBLIC"}},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "返回数量上限"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标"},
	},
	Tips: []string{`dws doc +template-search --query "周报"`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"searchName": rt.Str("query")}
		if v := rt.Str("source"); v != "" {
			params["templateSource"] = v
		}
		if rt.Changed("limit") {
			params["maxResults"] = rt.Int("limit")
		}
		if v := rt.Str("cursor"); v != "" {
			params["nextCursor"] = v
		}
		return rt.CallMCP("search_doc_templates", params)
	},
}
View Source
var Update = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+update",
	Product:     productDoc,
	Description: "追加、覆盖或按 block 精确更新文档内容",
	Intent:      "当用户要修改已有在线文字文档时使用;支持整篇 append/overwrite、block 插入/替换/删除,以及受限的唯一纯文本 str_replace,所有模式统一经过静态确认门禁。",
	Risk:        shortcut.RiskWrite,
	Safety:      contract.SafetySpec{Effect: "write", Risk: "medium", Confirmation: "user_required", Idempotency: "unknown"},
	Contract: docContract("+update", "追加、覆盖或按 block 精确更新文档内容",
		"当用户要修改已有在线文字文档时使用;支持整篇 append/overwrite、block 插入/替换/删除,以及受限的唯一纯文本 str_replace,所有模式统一经过静态确认门禁。",
		[]string{`dws doc +update --node <DOC_ID> --command append --content "补充说明"`, `dws doc +update --node <DOC_ID> --command block_replace --block-id <BLOCK_ID> --content "新内容"`},
		contract.ParamDecl{Name: "doc", Property: "node"},
		contract.ParamDecl{Name: "text", Property: "content"}),
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true, Aliases: []string{"doc"}, AliasesVisible: true},
		{Name: "command", Type: shortcut.FlagString, Desc: "更新动作;不能为空", Enum: []string{"append", "overwrite", "block_insert_after", "block_replace", "block_delete", "str_replace", "block_copy_insert_after"}},
		{Name: "content", Type: shortcut.FlagString, Desc: "内容字面量、@相对文件或 - 表示 stdin;相关动作要求时不能为空", Aliases: []string{"text"}, AliasesVisible: true},
		{Name: "doc-format", Type: shortcut.FlagString, Default: "markdown", Desc: "内容格式", Enum: []string{"markdown", "jsonml"}},
		{Name: "block-id", Type: shortcut.FlagString, Desc: "目标或源 block ID;相关动作要求时不能为空"},
		{Name: "after-block-id", Type: shortcut.FlagString, Desc: "插入位置参考 block ID"},
		{Name: "old", Type: shortcut.FlagString, Desc: "str_replace 原文字,不能为空"},
		{Name: "new", Type: shortcut.FlagString, Desc: "str_replace 新文字;--old 不能为空,新值可为空但参数必须显式提供"},
		{Name: "expected-revision", Type: shortcut.FlagInt, Desc: "best-effort 乐观 revision 检查"},
	},
	Tips: []string{`dws doc +update --node <DOC_ID> --command append --content "补充说明"`, `dws doc +update --node <DOC_ID> --command block_replace --block-id <BLOCK_ID> --content "新内容"`},
	Validate: func(rt *shortcut.RuntimeContext) error {
		command := rt.Str("command")
		if command == "" {
			return apperrors.NewValidation("缺少 --command")
		}
		if rt.StrFirst("node", "doc") == "" {
			return apperrors.NewValidation("缺少 --node")
		}
		if command == "append" || command == "overwrite" || command == "block_insert_after" || command == "block_replace" {
			if rt.StrFirst("content", "text") == "" {
				return apperrors.NewValidation("该更新动作的 --content 不能为空")
			}
		}
		if strings.HasPrefix(command, "block_") && command != "block_insert_after" && rt.Str("block-id") == "" {
			return apperrors.NewValidation("该 block 操作必须提供 --block-id")
		}
		if command == "str_replace" && (rt.Str("old") == "" || !rt.Changed("new")) {
			return apperrors.NewValidation("--command str_replace 必须同时提供 --old 和 --new")
		}
		return nil
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"command", "content", "block-id", "old", "new"}, Description: "依 command 校验,所需文本参数不能为空"}},
	Execute:     executeUpdate,
}
View Source
var VersionList = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+version-list",
	Product:     productDoc,
	Description: "查看文档历史版本列表",
	Intent:      "当你想查看某篇文档有哪些历史版本、以便挑一个版本号用于回滚时使用;输入 node,返回历史版本列表及其版本号。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_version_list",
			CanonicalPath:  "doc.shortcut_version_list",
			CLIPath:        "doc +version-list",
			PrimaryCLIPath: "doc +version-list",
		},
		Description: "查看文档历史版本列表",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "查看文档历史版本列表",
			UseWhen:      []string{"当你想查看某篇文档有哪些历史版本、以便挑一个版本号用于回滚时使用;输入 node,返回历史版本列表及其版本号。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +version-list --node DOC_ID"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "limit", Type: shortcut.FlagInt, Desc: "返回版本数量上限"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标"},
	},
	Tips: []string{`dws doc +version-list --node DOC_ID`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"nodeId": rt.Str("node")}
		if rt.Changed("limit") {
			params["maxResults"] = rt.Int("limit")
		}
		if v := rt.Str("cursor"); v != "" {
			params["nextCursor"] = v
		}
		return rt.CallMCP("list_doc_versions", params)
	},
}
View Source
var VersionRevert = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+version-revert",
	Product:     productDoc,
	Description: "回滚文档到指定历史版本",
	Intent:      "当文档被误改、你想把它整体恢复到某个历史版本时使用;先用 +version-list 找到目标版本号,再输入 node 与 version,会实际把文档内容覆盖回该版本,属于高风险写操作,需谨慎确认。",
	Risk:        shortcut.RiskHighWrite,
	Safety: contract.SafetySpec{
		Effect: "destructive", Risk: "high",
		Confirmation: "user_required", Idempotency: "unknown",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "doc",
			Name:           "shortcut_version_revert",
			CanonicalPath:  "doc.shortcut_version_revert",
			CLIPath:        "doc +version-revert",
			PrimaryCLIPath: "doc +version-revert",
		},
		Description: "回滚文档到指定历史版本",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "回滚文档到指定历史版本",
			UseWhen:      []string{"当文档被误改、你想把它整体恢复到某个历史版本时使用;先用 +version-list 找到目标版本号,再输入 node 与 version,会实际把文档内容覆盖回该版本,属于高风险写操作,需谨慎确认。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +version-revert --node DOC_ID --version 3"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
		{Name: "version", Type: shortcut.FlagInt, Desc: "目标版本号 (从 +version-list 获取)", Required: true},
	},
	Tips: []string{`dws doc +version-revert --node DOC_ID --version 3 --yes`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		return rt.CallMCP("revert_doc_version", map[string]any{
			"nodeId":  rt.Str("node"),
			"version": rt.Int("version"),
		})
	},
}
View Source
var VersionSave = shortcut.Shortcut{
	Service:     "doc",
	Command:     "+version-save",
	Product:     productDoc,
	Description: "手动保存文档版本快照",
	Intent:      "当你在做重大改动前后、想手动打一个可回滚的版本存档点时使用;输入 node,会实际为该文档保存一个当前内容的历史版本快照。",
	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_version_save",
			CanonicalPath:  "doc.shortcut_version_save",
			CLIPath:        "doc +version-save",
			PrimaryCLIPath: "doc +version-save",
		},
		Description: "手动保存文档版本快照",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "手动保存文档版本快照",
			UseWhen:      []string{"当你在做重大改动前后、想手动打一个可回滚的版本存档点时使用;输入 node,会实际为该文档保存一个当前内容的历史版本快照。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws doc +version-save --node DOC_ID"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "node", Type: shortcut.FlagString, Desc: "文档 ID 或 URL", Required: true},
	},
	Tips: []string{`dws doc +version-save --node DOC_ID`},
	Execute: func(rt *shortcut.RuntimeContext) error {
		return rt.CallMCP("save_doc_version", map[string]any{"nodeId": rt.Str("node")})
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL