diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f51e827..420c918e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ## [Unreleased] +### Added + +- **Wukong internal command sync** (#621) — ports roughly 30 leaf commands from the internal wukong repository into the open-source CLI: Drive gains `star`, `cover`, `revert`, `download-version`, `permission transfer-owner`/`apply-info`/`apply`, and `list --depth`/`--versions`; Doc gains `style cover`/`style background`; Sheet gains the `comment`, `version`, and `formula-verify` command groups plus `info --include`; Chat gains `message update-text-emotion`, the `location`/`profile` message types, and group `user-settings`/`get-mute-config`. Every new leaf is registered in the Agent-visible Runtime Schema. + ### Fixed - **Name→ID resolution kept external contacts** — the shared contact resolver (`chat +dm`, `+broadcast`, …) no longer drops `search_contact_by_key_word` rows that carry only an `openDingTalkId` (external / cross-org contacts have an empty `userId`), so those people are found instead of reported missing or collapsed into a wrong single match; the display name also falls back through `nick`/`showName`/`flowerName`/`staffName`/`userName`. diff --git a/internal/cli/schema_agent_metadata/chat.json b/internal/cli/schema_agent_metadata/chat.json index 9b1c1025..a4823d97 100644 --- a/internal/cli/schema_agent_metadata/chat.json +++ b/internal/cli/schema_agent_metadata/chat.json @@ -18667,14 +18667,14 @@ ] }, "risk": { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", "candidates": [ { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, @@ -18707,7 +18707,7 @@ "interface_mode": "composite", "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true, - "risk": "low", + "risk": "medium", "source_refs": [ "CommandRegistry:canonical_path=chat.batch_update_group_chat_settings", "cobra-help:dws chat group user-settings set", diff --git a/internal/cli/schema_agent_metadata/drive.json b/internal/cli/schema_agent_metadata/drive.json index ba275593..a46029fa 100644 --- a/internal/cli/schema_agent_metadata/drive.json +++ b/internal/cli/schema_agent_metadata/drive.json @@ -3187,6 +3187,273 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + "drive download-version": { + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "field_provenance": { + "agent_summary": { + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "avoid_when": { + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "examples": { + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/drive.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/drive.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/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + } + ] + }, + "use_when": { + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "candidates": [ + { + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products/drive.go" + ], + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, "drive info": { "agent_summary": "获取文件元数据信息", "agent_summary_source": "dws-agent-selection/drive", @@ -4839,7 +5106,7 @@ "avoid_when": [ "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" ], - "confirmation": "not_required", + "confirmation": "user_required", "effect": "write", "effect_source": "agent-hint", "examples": [ @@ -4899,14 +5166,14 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", "candidates": [ { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "selected": true, diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 64dfa9d9..cf0bd499 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,17 +1,17 @@ { "version": 1, - "source_hash": "sha256:ced34d4510fddb0b0cd02478f0f5051b0de0aab3d6bc7e1e43aaa1a12717d3b7", - "surface_hash": "sha256:674e6c00efb4d7f4c177ec0a1c4c097c8fb48b0045d1787c0ed001c32c51f20a", + "source_hash": "sha256:31a28d1665ebb13fdb131547bda1d84ce1cc3fee0bbff821b76c2fdf6f6904ab", + "surface_hash": "sha256:29d4f5f43688cc01bc2277a4528b597de4fbab89a7b2f13fe2e56df85d1c66c4", "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 839, - "tools_with_metadata": 839, - "tools_with_agent_summary": 839, - "tools_with_use_when": 839, - "tools_with_avoid_when": 839, - "tools_with_examples": 839, - "tools_with_interface_mode": 839, + "surface_tools": 840, + "tools_with_metadata": 840, + "tools_with_agent_summary": 840, + "tools_with_use_when": 840, + "tools_with_avoid_when": 840, + "tools_with_examples": 840, + "tools_with_interface_mode": 840, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index deea4bcc..e3219200 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,11 +1,11 @@ { "version": 1, - "source_hash": "sha256:ced34d4510fddb0b0cd02478f0f5051b0de0aab3d6bc7e1e43aaa1a12717d3b7", - "surface_hash": "sha256:674e6c00efb4d7f4c177ec0a1c4c097c8fb48b0045d1787c0ed001c32c51f20a", + "source_hash": "sha256:31a28d1665ebb13fdb131547bda1d84ce1cc3fee0bbff821b76c2fdf6f6904ab", + "surface_hash": "sha256:29d4f5f43688cc01bc2277a4528b597de4fbab89a7b2f13fe2e56df85d1c66c4", "source_files": 160, "hint_files": 54, "hint_products": 43, - "hint_tools": 1830, + "hint_tools": 1832, "interface_metadata": { "source": "mcp-tools-list+cli-registry", "revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8", @@ -29,13 +29,13 @@ "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 839, - "tools_with_metadata": 839, - "tools_with_agent_summary": 839, - "tools_with_use_when": 839, - "tools_with_avoid_when": 839, - "tools_with_examples": 839, - "tools_with_interface_mode": 839, + "surface_tools": 840, + "tools_with_metadata": 840, + "tools_with_agent_summary": 840, + "tools_with_use_when": 840, + "tools_with_avoid_when": 840, + "tools_with_examples": 840, + "tools_with_interface_mode": 840, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 5b085848..b3ee23ce 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,17 +1,17 @@ { "version": 1, - "surface_hash": "sha256:674e6c00efb4d7f4c177ec0a1c4c097c8fb48b0045d1787c0ed001c32c51f20a", - "source_hash": "sha256:f2cf7fcd3a70748029688ef5653386d9b503ef898bf763acb1da04a0b150d483", + "surface_hash": "sha256:29d4f5f43688cc01bc2277a4528b597de4fbab89a7b2f13fe2e56df85d1c66c4", + "source_hash": "sha256:346ec3ac857b9a740baf03142296b1e4fb0814f713ceab4ed2ff9f46a85d6b4d", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:ced34d4510fddb0b0cd02478f0f5051b0de0aab3d6bc7e1e43aaa1a12717d3b7", - "surface_hash": "sha256:674e6c00efb4d7f4c177ec0a1c4c097c8fb48b0045d1787c0ed001c32c51f20a", + "source_hash": "sha256:31a28d1665ebb13fdb131547bda1d84ce1cc3fee0bbff821b76c2fdf6f6904ab", + "surface_hash": "sha256:29d4f5f43688cc01bc2277a4528b597de4fbab89a7b2f13fe2e56df85d1c66c4", "surface_products": 26, - "surface_tools": 839, - "tools_with_agent_summary": 839, - "tools_with_metadata": 839, + "surface_tools": 840, + "tools_with_agent_summary": 840, + "tools_with_metadata": 840, "unmatched_skill_tools": 122, "version": 1 }, @@ -7955,7 +7955,7 @@ "name": "batch_update_group_chat_settings", "primary_cli_path": "chat group user-settings set", "reviewed": true, - "risk": "low", + "risk": "medium", "title": "批量更新当前用户的群会话设置", "use_when": [ "用户说 把这些群都设为免打扰/置顶" @@ -16172,7 +16172,7 @@ "id": "drive", "name": "钉盘文件管理", "runtime": true, - "tool_count": 40, + "tool_count": 41, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -16219,7 +16219,7 @@ "canonical_path": "drive.apply_permission", "cli_name": "apply", "cli_path": "drive permission apply", - "confirmation": "not_required", + "confirmation": "user_required", "description": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。", "effect": "write", "group": "permission", @@ -16430,6 +16430,35 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "canonical_path": "drive.download_file_version", + "cli_name": "download-version", + "cli_path": "drive download-version", + "confirmation": "not_required", + "description": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "download_file_version", + "primary_cli_path": "drive download-version", + "reviewed": true, + "risk": "low", + "title": "下载文件历史版本到本地", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", @@ -26484,6 +26513,6 @@ } ], "source": "embedded-command-catalog", - "tool_count": 839 + "tool_count": 840 } } diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index d41b1170..7876f2f8 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -5521,14 +5521,14 @@ "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "dws-tool-metadata/chat marks this tool as reviewed", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -5676,7 +5676,7 @@ "primary_cli_path": "chat group user-settings set", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "批量更新当前用户的群会话设置", "use_when": [ @@ -46156,7 +46156,7 @@ "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "send_personal_message", - "parameter_count": 12, + "parameter_count": 17, "parameters": { "ai-tag": { "default": "true", @@ -46492,6 +46492,97 @@ "required": false, "type": "string" }, + "contact-id": { + "description": "联系人名片 openDingTalkId(msgType=profile 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "联系人名片 openDingTalkId(msgType=profile 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "联系人名片 openDingTalkId(msgType=profile 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "contactId" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, "file-path": { "description": "本地文件路径(msgType=file/audio/video 时直接上传并按 file 消息发送)", "field_provenance": { @@ -46714,6 +46805,370 @@ "required": false, "type": "string" }, + "latitude": { + "description": "位置消息纬度(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息纬度(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息纬度(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "latitude" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "location-name": { + "description": "位置消息地址名称(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息地址名称(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息地址名称(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "locationName" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "longitude": { + "description": "位置消息经度(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息经度(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息经度(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "longitude" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, + "map-thumbnail-url": { + "description": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "serialized into the aggregate content payload", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "mapThumbnailUrl" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "serialized into the aggregate content payload", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": false, + "type": "string" + }, "media-id": { "description": "上游已提供的图片 mediaId(仅旧版 msgType=image;CLI 不提供本地上传到 mediaId)", "example": "@lADP_schema_smoke", @@ -46834,7 +47289,7 @@ "type": "string" }, "msg-type": { - "description": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)", + "description": "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)", "enum": [ "image", "file", @@ -46848,7 +47303,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "富媒体消息类型: image/file/audio/video(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" + "value": "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" }, { "precedence": "mcp_metadata", @@ -46866,7 +47321,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/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)" }, "enum": { "candidates": [ diff --git a/internal/cli/schema_catalog/tools/drive.json b/internal/cli/schema_catalog/tools/drive.json index 3b452f21..0581d77a 100644 --- a/internal/cli/schema_catalog/tools/drive.json +++ b/internal/cli/schema_catalog/tools/drive.json @@ -828,7 +828,7 @@ "canonical_path": "drive.apply_permission", "cli_name": "apply", "cli_path": "drive permission apply", - "confirmation": "not_required", + "confirmation": "user_required", "description": "向指定节点的审批人发起权限申请。建议先用 apply-info 获取可申请角色与审批人。\n\n注意: 本命令会真实通知审批人,Agent 必须先获得用户明确同意后再执行。", "display": "钉盘文件管理", "effect": "write", @@ -912,14 +912,14 @@ "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", "selected": true, "source": "internal/cli/schema_hints/metadata/drive.json", - "value": "not_required" + "value": "user_required" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", "source": "internal/cli/schema_hints/metadata/drive.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -5624,6 +5624,625 @@ "已确认 contentType 非 ALIDOC,需要落盘本地查看时" ] }, + "drive.download_file_version": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "internal/cli/schema_hints/metadata/drive.json", + "internal/cli/schema_hints/selection/drive.json", + "wukong-develop:wukong/products/drive.go" + ], + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "agent_summary_source": "dws-agent-selection/drive", + "availability": "available", + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "canonical_path": "drive.download_file_version", + "cli_name": "download-version", + "cli_path": "drive download-version", + "confirmation": "not_required", + "description": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。", + "display": "钉盘文件管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "drive download-version", + "value": "drive.download_file_version" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "drive download-version", + "value": "drive.download_file_version" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "下载钉盘文件的指定历史版本到本地(两步下载流程)。\n\n仅适用于普通文件(如 pdf、docx、xlsx、png 等):\n 钉钉在线文档(adoc)请使用 dws doc version 系列命令\n 钉钉在线表格(axls)请使用 dws sheet version 系列命令\n\n流程:\n 1. 获取历史版本下载 URL 和签名请求头 (download_file_version)\n 2. HTTP GET 下载文件二进制内容到本地\n\n版本号从 dws drive list --node \u003cdentryUuid\u003e --versions 获取。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "dws drive download-version --node \u003cdentryUuid\u003e --version 3 --output ./report_v3.pdf" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "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/drive.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/drive.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": false, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "selected": true, + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "source": "internal/cli/schema_hints/metadata/drive.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "下载文件历史版本到本地" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "下载文件历史版本到本地" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "selected": true, + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source": "internal/cli/schema_hints/selection/drive.json", + "value": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "download_file_version", + "parameter_count": 3, + "parameters": { + "node": { + "description": "文件 ID (dentryUuid) 或 URL (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "文件 ID (dentryUuid) 或 URL (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "node" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "output": { + "description": "本地保存路径 (文件路径或目录,必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "本地保存路径 (文件路径或目录,必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "本地保存路径 (文件路径或目录,必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "output" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "required": true, + "type": "string" + }, + "version": { + "description": "历史版本号 (必填,正整数,从 drive list --versions 获取)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "历史版本号 (必填,正整数,从 drive list --versions 获取)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "历史版本号 (必填,正整数,从 drive list --versions 获取)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "version" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "required": true, + "type": "integer" + } + }, + "path": "drive.download_file_version", + "primary_cli_path": "drive download-version", + "product_id": "drive", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "下载文件历史版本到本地", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ] + }, "drive.get_cover": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_catalog/tools/sheet.json b/internal/cli/schema_catalog/tools/sheet.json index 2bdcb677..b4504f30 100644 --- a/internal/cli/schema_catalog/tools/sheet.json +++ b/internal/cli/schema_catalog/tools/sheet.json @@ -26983,8 +26983,99 @@ "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "is_alias": false, "name": "formula_verify", - "parameter_count": 6, + "parameter_count": 7, "parameters": { + "exit-on-error": { + "description": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "发现公式错误时返回非 0 退出码,便于 CI/自动化使用" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_mapping_exclusion", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "selected": true, + "source": "reviewed_mapping_exclusion", + "value": "" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "exitOnError" + } + ], + "precedence": "reviewed_mapping_exclusion", + "resolution": "highest_precedence", + "review_reason": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "source": "reviewed_mapping_exclusion", + "value": "" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "required": false, + "type": "boolean" + }, "max-cells": { "description": "最多扫描的单元格数", "field_provenance": { @@ -34913,8 +35004,105 @@ "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "info", - "parameter_count": 2, + "parameter_count": 3, "parameters": { + "include": { + "description": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen" + }, + { + "precedence": "mcp_metadata", + "selected": false, + "source": "mcp_metadata", + "value": "可选扩展信息列表。当前支持 groups,用于返回 rowGroups / columnGroups 分组信息。未指定时只返回基础工作表信息、冻结信息和 mergedRanges。" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen" + }, + "property": { + "candidates": [ + { + "precedence": "versioned_binding", + "selected": true, + "source": "versioned_parameter_binding", + "value": "include" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "include" + } + ], + "precedence": "versioned_binding", + "resolution": "highest_precedence", + "source": "versioned_parameter_binding", + "value": "include" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "interface_description": "可选扩展信息列表。当前支持 groups,用于返回 rowGroups / columnGroups 分组信息。未指定时只返回基础工作表信息、冻结信息和 mergedRanges。", + "property": "include", + "required": false, + "type": "array" + }, "node": { "description": "表格文档 ID 或 URL (必填)", "field_provenance": { diff --git a/internal/cli/schema_command_registry/products/drive.json b/internal/cli/schema_command_registry/products/drive.json index 97ae30e0..771e1a18 100644 --- a/internal/cli/schema_command_registry/products/drive.json +++ b/internal/cli/schema_command_registry/products/drive.json @@ -49,6 +49,10 @@ "canonical_path": "drive.download_file", "cli_path": "drive download" }, + { + "canonical_path": "drive.download_file_version", + "cli_path": "drive download-version" + }, { "canonical_path": "drive.get_file_info", "cli_path": "drive info" diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index 0e05bfff..b1f969fa 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -1382,7 +1382,7 @@ }, "chat.batch_update_group_chat_settings": { "effect": "write", - "risk": "low", + "risk": "medium", "confirmation": "not_required", "idempotency": "idempotent", "interface_mode": "composite", diff --git a/internal/cli/schema_hints/metadata/drive.json b/internal/cli/schema_hints/metadata/drive.json index e7410814..3cb923e0 100644 --- a/internal/cli/schema_hints/metadata/drive.json +++ b/internal/cli/schema_hints/metadata/drive.json @@ -17,6 +17,17 @@ "availability": "available", "reviewed": true }, + "drive.apply_permission": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" + }, "drive.commit_upload": { "interface_mode": "mcp", "availability": "available", @@ -64,6 +75,28 @@ "availability": "available", "reviewed": true }, + "drive.download_file_version": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, + "drive.get_cover": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.get_file_info": { "interface_mode": "mcp", "availability": "available", @@ -75,6 +108,17 @@ "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true }, + "drive.get_star_list": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.get_upload_info": { "interface_mode": "mcp", "availability": "available", @@ -100,6 +144,17 @@ "availability": "available", "reviewed": true }, + "drive.mark_star": { + "effect": "write", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.move_document": { "interface_ref": { "product_id": "doc", @@ -164,6 +219,17 @@ "reviewed": true, "runtime_gate": "confirm_dangerous" }, + "drive.query_permission_apply_info": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "none" + }, "drive.recent": { "effect": "read", "risk": "low", @@ -212,28 +278,46 @@ "review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.", "cli_path": "drive rename" }, + "drive.revert_file_version": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "runtime_gate": "confirm_delete" + }, "drive.search_files": { "interface_mode": "mcp", "availability": "available", "reviewed": true }, - "drive.upload": { + "drive.shortcut_copy": { "effect": "write", "risk": "medium", - "confirmation": "not_required", + "confirmation": "user_required", "idempotency": "unknown", "interface_mode": "composite", "availability": "available", - "interface_reason": "命令包含多个 RPC、条件分派或本地 HTTP/文件步骤,不能绑定为单一 interface_ref", + "interface_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.", "reviewed": true, - "parameters": { - "node": { - "property": "nodeId", - "required": false - } - }, - "review_reason": "The reviewed helper preserves the stable command-level Schema contract for ordinary uploads; --node switches to overwrite mode and the runtime still requires explicit confirmation unless --yes is supplied.", - "cli_path": "drive upload", + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +copy", + "runtime_gate": "typed_yes" + }, + "drive.shortcut_find_file": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_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.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +find-file", "runtime_gate": "none" }, "drive.shortcut_info": { @@ -249,6 +333,32 @@ "cli_path": "drive +info", "runtime_gate": "none" }, + "drive.shortcut_move": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", + "interface_mode": "composite", + "availability": "available", + "interface_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.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +move", + "runtime_gate": "typed_yes" + }, + "drive.shortcut_recent": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_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.", + "reviewed": true, + "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "cli_path": "drive +recent", + "runtime_gate": "none" + }, "drive.shortcut_search": { "effect": "read", "risk": "low", @@ -275,68 +385,16 @@ "cli_path": "drive +search-docs", "runtime_gate": "none" }, - "drive.shortcut_copy": { + "drive.transfer_owner": { "effect": "write", - "risk": "medium", + "risk": "high", "confirmation": "user_required", - "idempotency": "unknown", - "interface_mode": "composite", - "availability": "available", - "interface_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.", - "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +copy", - "runtime_gate": "typed_yes" - }, - "drive.shortcut_move": { - "effect": "write", - "risk": "medium", - "confirmation": "user_required", - "idempotency": "unknown", - "interface_mode": "composite", - "availability": "available", - "interface_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.", - "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +move", - "runtime_gate": "typed_yes" - }, - "drive.shortcut_recent": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_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.", - "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +recent", - "runtime_gate": "none" - }, - "drive.shortcut_find_file": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_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.", - "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", - "cli_path": "drive +find-file", - "runtime_gate": "none" - }, - "drive.mark_star": { - "effect": "write", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "idempotency": "non_idempotent", "interface_mode": "composite", "availability": "available", "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", "reviewed": true, - "runtime_gate": "none" + "runtime_gate": "confirm_delete" }, "drive.unmark_star": { "effect": "write", @@ -349,70 +407,23 @@ "reviewed": true, "runtime_gate": "none" }, - "drive.get_star_list": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", - "reviewed": true, - "runtime_gate": "none" - }, - "drive.get_cover": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", - "reviewed": true, - "runtime_gate": "none" - }, - "drive.revert_file_version": { - "effect": "write", - "risk": "medium", - "confirmation": "user_required", - "idempotency": "non_idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", - "reviewed": true, - "runtime_gate": "confirm_delete" - }, - "drive.transfer_owner": { - "effect": "write", - "risk": "high", - "confirmation": "user_required", - "idempotency": "non_idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", - "reviewed": true, - "runtime_gate": "confirm_delete" - }, - "drive.query_permission_apply_info": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", - "interface_mode": "composite", - "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", - "reviewed": true, - "runtime_gate": "none" - }, - "drive.apply_permission": { + "drive.upload": { "effect": "write", "risk": "medium", "confirmation": "not_required", - "idempotency": "non_idempotent", + "idempotency": "unknown", "interface_mode": "composite", "availability": "available", - "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "interface_reason": "命令包含多个 RPC、条件分派或本地 HTTP/文件步骤,不能绑定为单一 interface_ref", "reviewed": true, + "parameters": { + "node": { + "property": "nodeId", + "required": false + } + }, + "review_reason": "The reviewed helper preserves the stable command-level Schema contract for ordinary uploads; --node switches to overwrite mode and the runtime still requires explicit confirmation unless --yes is supplied.", + "cli_path": "drive upload", "runtime_gate": "none" } } diff --git a/internal/cli/schema_hints/runtime-surface-completeness.json b/internal/cli/schema_hints/runtime-surface-completeness.json index ca2c7da7..ecf8253c 100644 --- a/internal/cli/schema_hints/runtime-surface-completeness.json +++ b/internal/cli/schema_hints/runtime-surface-completeness.json @@ -7,7 +7,7 @@ "channel": "open-source" }, "coverage": { - "source_tools": 839, + "source_tools": 840, "matched_tools": 71 }, "tools": { diff --git a/internal/cli/schema_hints/selection/drive.json b/internal/cli/schema_hints/selection/drive.json index bbb97671..f34254e6 100644 --- a/internal/cli/schema_hints/selection/drive.json +++ b/internal/cli/schema_hints/selection/drive.json @@ -33,6 +33,25 @@ "live-mcp:dws schema doc.add_permission" ] }, + "drive.apply_permission": { + "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", + "use_when": [ + "用户确认要为无权限文档发起权限申请" + ], + "avoid_when": [ + "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" + ], + "examples": [ + "dws drive permission apply --node --role READER --users <审批人userId> --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.apply_permission", + "cobra-help:dws drive permission apply", + "wukong-develop:wukong/products" + ] + }, "drive.commit_upload": { "agent_summary": "提交文件上传", "use_when": [ @@ -187,6 +206,47 @@ "live-mcp:dws schema drive.download_file" ] }, + "drive.download_file_version": { + "agent_summary": "下载钉盘普通文件的指定历史版本到本地(两步下载:取签名 URL 后 HTTP GET)", + "use_when": [ + "用户要下载文件的历史版本/旧版本", + "版本号已通过 drive list --versions 获取" + ], + "avoid_when": [ + "下载最新版本用 drive download", + "在线文档(adoc)历史版本用 doc version 系列命令", + "在线表格(axls)历史版本用 sheet version 系列命令" + ], + "examples": [ + "dws drive download-version --node --version 3 --output ./report_v3.pdf" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop drive download --version 分支实现移植为独立命令;单一 download_file_version 远端调用,参数契约与帮助文档一致。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.download_file_version", + "cobra-help:dws drive download-version", + "wukong-develop:wukong/products/drive.go" + ] + }, + "drive.get_cover": { + "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", + "use_when": [ + "用户说 封面/封面图/缩略图/预览图" + ], + "avoid_when": [ + "设置文档封面用 doc style cover set" + ], + "examples": [ + "dws drive cover --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_cover", + "cobra-help:dws drive cover", + "wukong-develop:wukong/products" + ] + }, "drive.get_file_info": { "agent_summary": "获取文件元数据信息", "use_when": [ @@ -235,6 +295,26 @@ "live-mcp:none (Cobra+Skill; composite/unpinned)" ] }, + "drive.get_star_list": { + "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "use_when": [ + "用户说 我的收藏/收藏列表/收藏了哪些文档" + ], + "avoid_when": [ + "操作单个收藏用 mark_star/unmark_star" + ], + "examples": [ + "dws drive star list --format json", + "dws drive star list --content-types doc,sheet --limit 10 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.get_star_list", + "cobra-help:dws drive star list", + "wukong-develop:wukong/products" + ] + }, "drive.get_upload_info": { "agent_summary": "获取文件上传信息", "use_when": [ @@ -334,6 +414,25 @@ "live-mcp:dws schema drive.list_spaces" ] }, + "drive.mark_star": { + "agent_summary": "收藏文档/文件到当前用户收藏列表", + "use_when": [ + "用户说 收藏这个文档/加个收藏/标星" + ], + "avoid_when": [ + "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + ], + "examples": [ + "dws drive star add --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.mark_star", + "cobra-help:dws drive star add", + "wukong-develop:wukong/products" + ] + }, "drive.move_document": { "agent_summary": "将文件或文档移动到目标文件夹或知识库(原位置不再保留)", "use_when": [ @@ -475,6 +574,25 @@ "live-mcp:none (Cobra+Skill; composite/unpinned)" ] }, + "drive.query_permission_apply_info": { + "agent_summary": "查询节点可申请的权限角色列表与审批人列表", + "use_when": [ + "无权限访问文档时,先查可申请角色与审批人" + ], + "avoid_when": [ + "实际发起申请用 apply_permission" + ], + "examples": [ + "dws drive permission apply-info --node --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.query_permission_apply_info", + "cobra-help:dws drive permission apply-info", + "wukong-develop:wukong/products" + ] + }, "drive.recent": { "agent_summary": "获取当前用户最近访问或编辑过的文档列表", "use_when": [ @@ -568,6 +686,25 @@ "live-mcp:dws schema doc.rename_document" ] }, + "drive.revert_file_version": { + "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", + "use_when": [ + "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" + ], + "avoid_when": [ + "在线文档回滚用 doc version revert;在线表格用 sheet version revert" + ], + "examples": [ + "dws drive revert --node --version 3 --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "source_refs": [ + "CommandRegistry:canonical_path=drive.revert_file_version", + "cobra-help:dws drive revert", + "wukong-develop:wukong/products" + ] + }, "drive.search_files": { "agent_summary": "全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果", "use_when": [ @@ -595,32 +732,43 @@ "live-mcp:dws schema drive.search_files" ] }, - "drive.upload": { - "agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件", + "drive.shortcut_copy": { + "agent_summary": "复制文件/文档到指定位置", "use_when": [ - "用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时", - "上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert", - "用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认" + "当你想保留原件、把某个文件/文档拷贝一份到指定文件夹或知识库时使用;输入源节点 node 及目标 folder/workspace,会实际生成一个副本,原文件位置不变。" ], "avoid_when": [ - "常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步", - "要把文件作为文档正文附件插入改用 dws doc media insert", - "用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令", - "用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录" + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], "examples": [ - "dws drive upload --file ./report.pdf --format json", - "dws drive upload --file ./README.md --node --format json" + "dws drive +copy --node --folder " ], "reviewed": true, - "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", "source_refs": [ - "CommandRegistry:canonical_path=drive.upload", - "cobra-help:dws drive upload", - "Skill:skills/mono/references/products/drive.md", - "structured-hint:internal/cli/schema_hints/selection-review.json#drive.upload", - "structured-hint:internal/cli/schema_hints/products/drive.json", - "live-mcp:none (Cobra+Skill; composite/unpinned)" + "internal/cli/schema_command_registry.json#drive.shortcut_copy", + "cobra-help:dws drive +copy", + "ShortcutRegistry:drive +copy" + ] + }, + "drive.shortcut_find_file": { + "agent_summary": "按名称关键词搜索钉盘文件并投影关键字段(只读)", + "use_when": [ + "当你只记得钉盘文件的名字(或其中一部分),想快速按文件名关键词找到它、拿到它的 dentryId 以便后续下载/查看,却不想手动翻目录或写复杂过滤条件时使用;内部调用钉盘的 search_files 工具,把 --query 作为文件名关键词(keyword) 并限定搜索范围为钉盘文件(searchTarget=file),再在本地把每条命中结果精简为「文件名、类型、dentryId、大小」四个字段后打印。这是纯只读操作,只做搜索与本地投影,不会创建、移动或删除任何文件;未命中时返回空列表。" + ], + "avoid_when": [ + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" + ], + "examples": [ + "dws drive +find-file --query 季度汇报", + "dws drive +find-file --query 合同" + ], + "reviewed": true, + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", + "source_refs": [ + "internal/cli/schema_command_registry.json#drive.shortcut_find_file", + "cobra-help:dws drive +find-file", + "ShortcutRegistry:drive +find-file" ] }, "drive.shortcut_info": { @@ -642,64 +790,6 @@ "ShortcutRegistry:drive +info" ] }, - "drive.shortcut_search": { - "agent_summary": "搜索钉盘文件", - "use_when": [ - "当你只记得文件名或内容关键词、不知道它在哪个目录时用它全局检索钉盘文件;输入 query,可按文件类型、扩展名、创建者、创建/修改时间范围过滤,返回匹配文件及其 ID,便于再做下载或整理。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +search --query \"季度汇报\"", - "dws drive +search --query \"合同\" --target file --extensions pdf,docx" - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_search", - "cobra-help:dws drive +search", - "ShortcutRegistry:drive +search" - ] - }, - "drive.shortcut_search_docs": { - "agent_summary": "搜索文档空间文档", - "use_when": [ - "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +search-docs --query \"季度汇报\"" - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_search_docs", - "cobra-help:dws drive +search-docs", - "ShortcutRegistry:drive +search-docs" - ] - }, - "drive.shortcut_copy": { - "agent_summary": "复制文件/文档到指定位置", - "use_when": [ - "当你想保留原件、把某个文件/文档拷贝一份到指定文件夹或知识库时使用;输入源节点 node 及目标 folder/workspace,会实际生成一个副本,原文件位置不变。" - ], - "avoid_when": [ - "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" - ], - "examples": [ - "dws drive +copy --node --folder " - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", - "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_copy", - "cobra-help:dws drive +copy", - "ShortcutRegistry:drive +copy" - ] - }, "drive.shortcut_move": { "agent_summary": "移动文件/文档到指定位置", "use_when": [ @@ -739,42 +829,61 @@ "ShortcutRegistry:drive +recent" ] }, - "drive.shortcut_find_file": { - "agent_summary": "按名称关键词搜索钉盘文件并投影关键字段(只读)", + "drive.shortcut_search": { + "agent_summary": "搜索钉盘文件", "use_when": [ - "当你只记得钉盘文件的名字(或其中一部分),想快速按文件名关键词找到它、拿到它的 dentryId 以便后续下载/查看,却不想手动翻目录或写复杂过滤条件时使用;内部调用钉盘的 search_files 工具,把 --query 作为文件名关键词(keyword) 并限定搜索范围为钉盘文件(searchTarget=file),再在本地把每条命中结果精简为「文件名、类型、dentryId、大小」四个字段后打印。这是纯只读操作,只做搜索与本地投影,不会创建、移动或删除任何文件;未命中时返回空列表。" + "当你只记得文件名或内容关键词、不知道它在哪个目录时用它全局检索钉盘文件;输入 query,可按文件类型、扩展名、创建者、创建/修改时间范围过滤,返回匹配文件及其 ID,便于再做下载或整理。" ], "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], "examples": [ - "dws drive +find-file --query 季度汇报", - "dws drive +find-file --query 合同" + "dws drive +search --query \"季度汇报\"", + "dws drive +search --query \"合同\" --target file --extensions pdf,docx" ], "reviewed": true, "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", "source_refs": [ - "internal/cli/schema_command_registry.json#drive.shortcut_find_file", - "cobra-help:dws drive +find-file", - "ShortcutRegistry:drive +find-file" + "internal/cli/schema_command_registry.json#drive.shortcut_search", + "cobra-help:dws drive +search", + "ShortcutRegistry:drive +search" ] }, - "drive.mark_star": { - "agent_summary": "收藏文档/文件到当前用户收藏列表", + "drive.shortcut_search_docs": { + "agent_summary": "搜索文档空间文档", "use_when": [ - "用户说 收藏这个文档/加个收藏/标星" + "当你只记得文档标题或关键词、想在文档空间/知识库中检索在线文档(区别于 +search 检索钉盘文件)时使用;输入 query 关键词,返回匹配的文档及其节点信息。" ], "avoid_when": [ - "取消收藏用 unmark_star;查看收藏列表用 get_star_list" + "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], "examples": [ - "dws drive star add --node --format json" + "dws drive +search-docs --query \"季度汇报\"" + ], + "reviewed": true, + "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", + "source_refs": [ + "internal/cli/schema_command_registry.json#drive.shortcut_search_docs", + "cobra-help:dws drive +search-docs", + "ShortcutRegistry:drive +search-docs" + ] + }, + "drive.transfer_owner": { + "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", + "use_when": [ + "用户明确要求转交文档/知识库所有权" + ], + "avoid_when": [ + "普通协作权限变更用 permission add/update/remove" + ], + "examples": [ + "dws drive permission transfer-owner --node --new-owner --reserve-role EDITOR --recursive=false --format json" ], "reviewed": true, "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", "source_refs": [ - "CommandRegistry:canonical_path=drive.mark_star", - "cobra-help:dws drive star add", + "CommandRegistry:canonical_path=drive.transfer_owner", + "cobra-help:dws drive permission transfer-owner", "wukong-develop:wukong/products" ] }, @@ -797,119 +906,32 @@ "wukong-develop:wukong/products" ] }, - "drive.get_star_list": { - "agent_summary": "获取当前用户的收藏列表,支持分页与按内容类型筛选", + "drive.upload": { + "agent_summary": "上传本地文件到钉盘或文档空间,或按节点 ID 确认覆盖已有文件", "use_when": [ - "用户说 我的收藏/收藏列表/收藏了哪些文档" + "用户要把本地文件上传到钉盘/我的文件(首选一条命令自动完成凭证+PUT+提交)时", + "上传到知识库/文档空间时加 --workspace;需要转在线文档时加 --convert", + "用户明确要求用本地文件替换已有钉盘/文档空间文件时传 --node;该模式会覆盖远端内容并要求确认" ], "avoid_when": [ - "操作单个收藏用 mark_star/unmark_star" + "常规场景不要拆成 upload-info + 手动 PUT + commit;仅自定义流式上传才用三步", + "要把文件作为文档正文附件插入改用 dws doc media insert", + "用户明确说文档空间且走 doc 兼容入口时可用 dws doc upload,默认仍推荐本命令", + "用户没有明确同意替换目标文件时不要使用 --node;新建上传应使用 --folder 或目标根目录" ], "examples": [ - "dws drive star list --format json", - "dws drive star list --content-types doc,sheet --limit 10 --format json" + "dws drive upload --file ./report.pdf --format json", + "dws drive upload --file ./README.md --node --format json" ], "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", + "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "source_refs": [ - "CommandRegistry:canonical_path=drive.get_star_list", - "cobra-help:dws drive star list", - "wukong-develop:wukong/products" - ] - }, - "drive.get_cover": { - "agent_summary": "获取节点封面图片地址(文档首图/图片缩略图/类型图标)", - "use_when": [ - "用户说 封面/封面图/缩略图/预览图" - ], - "avoid_when": [ - "设置文档封面用 doc style cover set" - ], - "examples": [ - "dws drive cover --node --format json" - ], - "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", - "source_refs": [ - "CommandRegistry:canonical_path=drive.get_cover", - "cobra-help:dws drive cover", - "wukong-develop:wukong/products" - ] - }, - "drive.revert_file_version": { - "agent_summary": "回滚普通文件到指定历史版本(生成新最新版本,历史不丢失)", - "use_when": [ - "用户说 回滚版本/恢复到某个版本/版本回退,且目标是普通文件" - ], - "avoid_when": [ - "在线文档回滚用 doc version revert;在线表格用 sheet version revert" - ], - "examples": [ - "dws drive revert --node --version 3 --format json" - ], - "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", - "source_refs": [ - "CommandRegistry:canonical_path=drive.revert_file_version", - "cobra-help:dws drive revert", - "wukong-develop:wukong/products" - ] - }, - "drive.transfer_owner": { - "agent_summary": "转交文档或知识库所有者给指定用户(不可逆)", - "use_when": [ - "用户明确要求转交文档/知识库所有权" - ], - "avoid_when": [ - "普通协作权限变更用 permission add/update/remove" - ], - "examples": [ - "dws drive permission transfer-owner --node --new-owner --reserve-role EDITOR --recursive=false --format json" - ], - "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", - "source_refs": [ - "CommandRegistry:canonical_path=drive.transfer_owner", - "cobra-help:dws drive permission transfer-owner", - "wukong-develop:wukong/products" - ] - }, - "drive.query_permission_apply_info": { - "agent_summary": "查询节点可申请的权限角色列表与审批人列表", - "use_when": [ - "无权限访问文档时,先查可申请角色与审批人" - ], - "avoid_when": [ - "实际发起申请用 apply_permission" - ], - "examples": [ - "dws drive permission apply-info --node --format json" - ], - "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", - "source_refs": [ - "CommandRegistry:canonical_path=drive.query_permission_apply_info", - "cobra-help:dws drive permission apply-info", - "wukong-develop:wukong/products" - ] - }, - "drive.apply_permission": { - "agent_summary": "向审批人发起文档权限申请(会真实通知审批人)", - "use_when": [ - "用户确认要为无权限文档发起权限申请" - ], - "avoid_when": [ - "先用 apply-info 查可申请角色与审批人;未经用户确认不得自行提交" - ], - "examples": [ - "dws drive permission apply --node --role READER --users <审批人userId> --format json" - ], - "reviewed": true, - "review_reason": "人工审阅:对照悟空 develop 同名命令实现、Cobra Long/Example 与 Runtime 确认门禁移植;不改变命令身份、参数契约或接口绑定。", - "source_refs": [ - "CommandRegistry:canonical_path=drive.apply_permission", - "cobra-help:dws drive permission apply", - "wukong-develop:wukong/products" + "CommandRegistry:canonical_path=drive.upload", + "cobra-help:dws drive upload", + "Skill:skills/mono/references/products/drive.md", + "structured-hint:internal/cli/schema_hints/selection-review.json#drive.upload", + "structured-hint:internal/cli/schema_hints/products/drive.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)" ] } }, diff --git a/internal/cli/schema_parameter_bindings.json b/internal/cli/schema_parameter_bindings.json index 9996ee6c..6a6c7049 100644 --- a/internal/cli/schema_parameter_bindings.json +++ b/internal/cli/schema_parameter_bindings.json @@ -2,8 +2,8 @@ "version": 3, "baseline": { "manifest": "schema-parameter-bindings-v3", - "sha256": "sha256:f48d7b55b6ca9719c466475ee32d678eab0deaee1d79ac4e4790c7ee441ebe7b", - "reason": "Reviewed v3 baseline after exposing the pinned chat.search_messages messageType, onlyRobotMessages, and searchConvType properties through exact Cobra flags.", + "sha256": "sha256:ff0f76145dd27da5429434a348344c9cdf74c44eaf6f4ea5411d29d904a9066f", + "reason": "Reviewed v3 baseline after binding sheet.info --include to the include property while porting the wukong sheet info expansion flag.", "reviewed": true }, "removals": { @@ -1112,7 +1112,8 @@ "node": "nodeId" }, "sheet.info": { - "node": "nodeId" + "node": "nodeId", + "include": "include" }, "sheet.insert_dimension": { "node": "nodeId" @@ -1594,7 +1595,10 @@ "attendance.vacation_update_type --when-can-leave": "Reviewed unpinned adapter: attendance.vacation_update_type has no singular pinned interface_ref; --when-can-leave is a CLI wrapper input and does not publish a direct interface property.", "chat.add_message_favorite --open-conversation-id": "Reviewed unpinned adapter: chat.add_message_favorite has no singular pinned interface_ref; --open-conversation-id is a CLI wrapper input and does not publish a direct interface property.", "chat.add_message_favorite --open-message-id": "Reviewed unpinned adapter: chat.add_message_favorite has no singular pinned interface_ref; --open-message-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.batch_query_group_chat_settings --groups": "Reviewed unpinned adapter: chat.batch_query_group_chat_settings has no singular pinned interface_ref; --groups is a CLI wrapper input and does not publish a direct interface property.", + "chat.batch_update_group_chat_settings --items": "Reviewed unpinned adapter: chat.batch_update_group_chat_settings has no singular pinned interface_ref; --items is a CLI wrapper input and does not publish a direct interface property.", "chat.create_text_emotion --background-id": "Runtime extension: the executable helper forwards backgroundId to im/create_text_emotion, but the pinned source-revision metadata does not declare that optional property; preserve the compatibility flag without advertising it as a pinned RPC field.", + "chat.get_group_mute_config --group": "Reviewed unpinned adapter: chat.get_group_mute_config has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", "chat.list_conversation_message_v2 --open-dingtalk-id": "selects the alternate list_individual_chat_message branch", "chat.list_conversation_message_v2 --user": "selects the alternate list_individual_chat_message branch", "chat.list_message_favorites --cursor": "Reviewed unpinned adapter: chat.list_message_favorites has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", @@ -1611,7 +1615,12 @@ "chat.search_groups --limit": "Reviewed unpinned adapter: chat.search_groups has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", "chat.search_messages --user": "conditional wrapper: parseCSVValues + appendChatIDArgs routes each supplied identifier to senderUserIds or senderOpenDingTakIds according to its runtime ID shape; there is no single RPC property for this flag", "chat.search_messages --users": "conditional wrapper/alias of --user: parseCSVValues + appendChatIDArgs routes each supplied identifier to senderUserIds or senderOpenDingTakIds according to its runtime ID shape; there is no single RPC property for this flag", + "chat.send_personal_message --contact-id": "serialized into the aggregate content payload", "chat.send_personal_message --file-path": "local upload/preprocessing input", + "chat.send_personal_message --latitude": "serialized into the aggregate content payload", + "chat.send_personal_message --location-name": "serialized into the aggregate content payload", + "chat.send_personal_message --longitude": "serialized into the aggregate content payload", + "chat.send_personal_message --map-thumbnail-url": "serialized into the aggregate content payload", "chat.send_personal_message --media-id": "serialized into the aggregate content payload", "chat.send_personal_message --text": "serialized into the aggregate content payload", "chat.send_personal_message --title": "serialized into the aggregate content payload", @@ -1619,6 +1628,16 @@ "chat.set_group_member_mute_list --user": "conditional wrapper/alias of --users: userIds are remotely resolved to openDingTalkIds outside dry-run and otherwise may be sent as uids; openDingTalkId inputs are sent as openDingTalkIds, so one exact property would misdescribe execution", "chat.transfer_group_owner --user": "conditional compatibility input: a userId is sent as newOwnerUid, while an openDingTalkId is sent as newOwnerOpenDingTalkId; the runtime ID-shape branch has no single RPC property", "chat.update_conv_member_roles --user": "conditional wrapper/alias of --users: splitChatIDValues sends userIds as uids and openDingTalkIds as openDingTalkIds; there is no single RPC property for this flag", + "chat.update_text_emotion --background-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --background-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --chat": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --chat is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --conversation-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --conversation-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --emotion-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --emotion-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --emotion-name": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --emotion-name is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --group": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --msg-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --msg-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --old-emotion-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --old-emotion-id is a CLI wrapper input and does not publish a direct interface property.", + "chat.update_text_emotion --text": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --text is a CLI wrapper input and does not publish a direct interface property.", "dev.add_dev_app_members --member-type": "Reviewed unpinned adapter: dev.add_dev_app_members has no singular pinned interface_ref; --member-type is a CLI wrapper input and does not publish a direct interface property.", "dev.add_dev_app_members --unified-app-id": "Reviewed unpinned adapter: dev.add_dev_app_members has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.add_dev_app_members --user-ids": "Reviewed unpinned adapter: dev.add_dev_app_members has no singular pinned interface_ref; --user-ids is a CLI wrapper input and does not publish a direct interface property.", @@ -1636,8 +1655,8 @@ "dev.disable_dev_app_robot --unified-app-id": "Reviewed unpinned adapter: dev.disable_dev_app_robot has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.enable_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.enable_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.enable_dev_app_robot --unified-app-id": "Reviewed unpinned adapter: dev.enable_dev_app_robot has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", - "dev.get_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app --app-key": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --app-key is a CLI wrapper input and does not publish a direct interface property.", + "dev.get_dev_app --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_credentials --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app_credentials has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_version_detail --unified-app-id": "Reviewed unpinned adapter: dev.get_dev_app_version_detail has no singular pinned interface_ref; --unified-app-id is a CLI wrapper input and does not publish a direct interface property.", "dev.get_dev_app_version_detail --version-id": "Reviewed unpinned adapter: dev.get_dev_app_version_detail has no singular pinned interface_ref; --version-id is a CLI wrapper input and does not publish a direct interface property.", @@ -1732,6 +1751,7 @@ "doc.get_document_content --scope": "Runtime extension sends scope for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", "doc.get_document_content --start-block-id": "Runtime extension sends startBlockId for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", "doc.get_document_content --tags": "Runtime extension sends tags for scoped JSONML reads, which is absent from the immutable pinned get_document_content metadata at its declared source revision.", + "doc.get_document_style --node": "Reviewed unpinned adapter: doc.get_document_style has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "doc.import_get --task-id": "Reviewed unpinned adapter: doc.import_get has no singular pinned interface_ref; --task-id is a CLI wrapper input and does not publish a direct interface property.", "doc.insert_document_block --fix-jsonml": "local JSONML normalization control", "doc.insert_document_block --heading": "aggregate convenience input used to build element", @@ -1739,6 +1759,14 @@ "doc.insert_document_block --text": "aggregate convenience input used to build element", "doc.list_document_blocks --block-id": "runtime extension sends blockId, which is absent from the pinned list_document_blocks metadata", "doc.reply_comment --mentioned-open-conversation-id": "Runtime extension sends mentionedOpenConversationIds, which is absent from the immutable pinned reply_comment metadata at its declared source revision.", + "doc.style_background_clear --node": "Reviewed unpinned adapter: doc.style_background_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_background_set --color": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --color is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_background_set --node": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_clear --node": "Reviewed unpinned adapter: doc.style_cover_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --file": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --file is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --image": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --image is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --node": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "doc.style_cover_set --position": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --position is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --folder": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --folder is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --name": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --name is a CLI wrapper input and does not publish a direct interface property.", "doc.template_apply --template-id": "Reviewed unpinned adapter: doc.template_apply has no singular pinned interface_ref; --template-id is a CLI wrapper input and does not publish a direct interface property.", @@ -1767,7 +1795,22 @@ "doc.version_revert --node": "Reviewed unpinned adapter: doc.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "doc.version_revert --version": "Reviewed unpinned adapter: doc.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", "doc.version_save --node": "Reviewed unpinned adapter: doc.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --node": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --notify-mode": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --notify-mode is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --reason is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --role": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --role is a CLI wrapper input and does not publish a direct interface property.", + "drive.apply_permission --users": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --users is a CLI wrapper input and does not publish a direct interface property.", "drive.download_file --output": "local output path", + "drive.download_file_version --node": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.download_file_version --output": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --output is a CLI wrapper input and does not publish a direct interface property.", + "drive.download_file_version --version": "Reviewed unpinned adapter: drive.download_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_cover --node": "Reviewed unpinned adapter: drive.get_cover has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --content-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --content-types is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --cursor": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --limit": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --order-by": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --order-by is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --resource-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --resource-types is a CLI wrapper input and does not publish a direct interface property.", + "drive.get_star_list --sort": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --sort is a CLI wrapper input and does not publish a direct interface property.", "drive.list_files --cursor": "composite route maps to nextToken for drive.list_files or pageToken for doc.list_nodes", "drive.list_files --folder": "composite route maps to parentId for drive.list_files or folderId for doc.list_nodes", "drive.list_files --limit": "composite route maps to maxResults for drive.list_files or pageSize for doc.list_nodes", @@ -1776,10 +1819,12 @@ "drive.list_files --space-id": "drive-branch-only route input on a composite drive/doc command; no singular interface property is advertised", "drive.list_files --thumbnail": "drive-branch-only option on a composite drive/doc command; no singular interface property is advertised", "drive.list_files --workspace": "selects the doc.list_nodes branch of the composite drive/doc command", + "drive.mark_star --node": "Reviewed unpinned adapter: drive.mark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_get --node": "Reviewed unpinned adapter: drive.publish_get has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_set --node": "Reviewed unpinned adapter: drive.publish_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_set --permission": "Reviewed unpinned adapter: drive.publish_set has no singular pinned interface_ref; --permission is a CLI wrapper input and does not publish a direct interface property.", "drive.publish_unset --node": "Reviewed unpinned adapter: drive.publish_unset has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.query_permission_apply_info --node": "Reviewed unpinned adapter: drive.query_permission_apply_info has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --creator-type": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --creator-type is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --cursor": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", "drive.recent --file-types": "Reviewed unpinned adapter: drive.recent has no singular pinned interface_ref; --file-types is a CLI wrapper input and does not publish a direct interface property.", @@ -1790,6 +1835,14 @@ "drive.recycle_list --limit": "Reviewed unpinned adapter: drive.recycle_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", "drive.recycle_list --space-id": "Reviewed unpinned adapter: drive.recycle_list has no singular pinned interface_ref; --space-id is a CLI wrapper input and does not publish a direct interface property.", "drive.recycle_restore --id": "Reviewed unpinned adapter: drive.recycle_restore has no singular pinned interface_ref; --id is a CLI wrapper input and does not publish a direct interface property.", + "drive.revert_file_version --node": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.revert_file_version --version": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --new-owner": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --new-owner is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --node": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --recursive": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --recursive is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --reserve-role": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --reserve-role is a CLI wrapper input and does not publish a direct interface property.", + "drive.transfer_owner --workspace": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --workspace is a CLI wrapper input and does not publish a direct interface property.", + "drive.unmark_star --node": "Reviewed unpinned adapter: drive.unmark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "mail.create_download_session --name": "local download filename", "mail.create_download_session --output": "local output path", "mail.create_draft --attachment": "reviewed local attachment preprocessing input", @@ -1835,9 +1888,16 @@ "sheet.create_pivot_table --source": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --source is a CLI wrapper input and does not publish a direct interface property.", "sheet.create_pivot_table --target-position": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --target-position is a CLI wrapper input and does not publish a direct interface property.", "sheet.create_pivot_table --target-sheet-id": "Reviewed unpinned adapter: sheet.create_pivot_table has no singular pinned interface_ref; --target-sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --content": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --mention": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --node": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --range": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.create_sheet_comment --sheet-id": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.delete_cond_format --yes": "local confirmation control", "sheet.delete_pivot_table --pivot-table-id": "Reviewed unpinned adapter: sheet.delete_pivot_table has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.delete_pivot_table --sheet-id": "Reviewed unpinned adapter: sheet.delete_pivot_table has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.delete_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.delete_sheet_comment --node": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_get_criteria --column": "local post-filter over get_filter_views response", "sheet.filter_view_get_criteria --filter-view-id": "local post-filter over get_filter_views response", "sheet.filter_view_info --filter-view-id": "local post-filter over get_filter_views response", @@ -1847,6 +1907,13 @@ "sheet.filter_view_update_criteria --filter-view-id": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --filter-view-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_update_criteria --node": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.filter_view_update_criteria --sheet-id": "Reviewed unpinned adapter: sheet.filter_view_update_criteria has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --exit-on-error": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --exit-on-error is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --max-cells": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-cells is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --max-locations-per-error": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-locations-per-error is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --node": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --range": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --sheet-id": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.formula_verify --targets": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --targets is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --group-state": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --group-state is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --node": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.group_dimension --range": "Reviewed unpinned adapter: sheet.group_dimension has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", @@ -1854,6 +1921,12 @@ "sheet.hide_gridline --sheet-id": "Reviewed unpinned adapter: sheet.hide_gridline has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.list_pivot_tables --pivot-table-id": "Reviewed unpinned adapter: sheet.list_pivot_tables has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.list_pivot_tables --sheet-id": "Reviewed unpinned adapter: sheet.list_pivot_tables has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --cursor": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --limit": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --node": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --range": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --resolve-status": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --resolve-status is a CLI wrapper input and does not publish a direct interface property.", + "sheet.list_sheet_comments --sheet-id": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --node": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --ranges": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --ranges is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_batch_clear --type": "Reviewed unpinned adapter: sheet.range_batch_clear has no singular pinned interface_ref; --type is a CLI wrapper input and does not publish a direct interface property.", @@ -1861,6 +1934,11 @@ "sheet.range_batch_set_style --continue-on-error": "Composite wrapper consumes this flag in its local multi-call error loop and never sends it to update_range.", "sheet.range_read --range": "Reviewed unpinned adapter: sheet.range_read has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", "sheet.range_read --sheet-id": "Reviewed unpinned adapter: sheet.range_read has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --content": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --emoji": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --emoji is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --mention": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", + "sheet.reply_sheet_comment --node": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.show_gridline --sheet-id": "Reviewed unpinned adapter: sheet.show_gridline has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.submit_export_job --output": "local destination consumed after export job completion", "sheet.table_get --no-header": "Reviewed unpinned adapter: sheet.table_get has no singular pinned interface_ref; --no-header is a CLI wrapper input and does not publish a direct interface property.", @@ -1885,6 +1963,15 @@ "sheet.update_pivot_table --pivot-table-id": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --pivot-table-id is a CLI wrapper input and does not publish a direct interface property.", "sheet.update_pivot_table --properties": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --properties is a CLI wrapper input and does not publish a direct interface property.", "sheet.update_pivot_table --sheet-id": "Reviewed unpinned adapter: sheet.update_pivot_table has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --content": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", + "sheet.update_sheet_comment --node": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --cursor": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --limit": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_list --node": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_revert --node": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_revert --version": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", + "sheet.version_save --node": "Reviewed unpinned adapter: sheet.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", "sheet.write_image --file": "local upload input used to obtain resourceId/resourceUrl", "sheet.write_image --mime-type": "local upload metadata", "sheet.write_image --name": "local upload metadata", @@ -1896,83 +1983,6 @@ "wiki.list_wikiSpaces --cursor": "composite route maps to pageToken for wiki.list_wikiSpaces or nextToken for drive.list_spaces", "wiki.list_wikiSpaces --limit": "composite route maps to pageSize for wiki.list_wikiSpaces or maxResults for drive.list_spaces", "wiki.list_wikiSpaces --type": "route selector maps to wikiSpaceType or spaceType on different composite branches", - "wiki.search_wikiSpaces --type": "local route selector; myWikiSpace invokes list_wikiSpaces instead", - "chat.batch_query_group_chat_settings --groups": "Reviewed unpinned adapter: chat.batch_query_group_chat_settings has no singular pinned interface_ref; --groups is a CLI wrapper input and does not publish a direct interface property.", - "chat.batch_update_group_chat_settings --items": "Reviewed unpinned adapter: chat.batch_update_group_chat_settings has no singular pinned interface_ref; --items is a CLI wrapper input and does not publish a direct interface property.", - "chat.get_group_mute_config --group": "Reviewed unpinned adapter: chat.get_group_mute_config has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --background-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --background-id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --chat": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --chat is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --conversation-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --conversation-id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --emotion-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --emotion-id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --emotion-name": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --emotion-name is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --group": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --group is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --msg-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --msg-id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --old-emotion-id": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --old-emotion-id is a CLI wrapper input and does not publish a direct interface property.", - "chat.update_text_emotion --text": "Reviewed unpinned adapter: chat.update_text_emotion has no singular pinned interface_ref; --text is a CLI wrapper input and does not publish a direct interface property.", - "doc.get_document_style --node": "Reviewed unpinned adapter: doc.get_document_style has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_background_clear --node": "Reviewed unpinned adapter: doc.style_background_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_background_set --color": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --color is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_background_set --node": "Reviewed unpinned adapter: doc.style_background_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_cover_clear --node": "Reviewed unpinned adapter: doc.style_cover_clear has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_cover_set --file": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --file is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_cover_set --image": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --image is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_cover_set --node": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "doc.style_cover_set --position": "Reviewed unpinned adapter: doc.style_cover_set has no singular pinned interface_ref; --position is a CLI wrapper input and does not publish a direct interface property.", - "drive.apply_permission --node": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.apply_permission --notify-mode": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --notify-mode is a CLI wrapper input and does not publish a direct interface property.", - "drive.apply_permission --reason": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --reason is a CLI wrapper input and does not publish a direct interface property.", - "drive.apply_permission --role": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --role is a CLI wrapper input and does not publish a direct interface property.", - "drive.apply_permission --users": "Reviewed unpinned adapter: drive.apply_permission has no singular pinned interface_ref; --users is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_cover --node": "Reviewed unpinned adapter: drive.get_cover has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --content-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --content-types is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --cursor": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --limit": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --order-by": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --order-by is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --resource-types": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --resource-types is a CLI wrapper input and does not publish a direct interface property.", - "drive.get_star_list --sort": "Reviewed unpinned adapter: drive.get_star_list has no singular pinned interface_ref; --sort is a CLI wrapper input and does not publish a direct interface property.", - "drive.mark_star --node": "Reviewed unpinned adapter: drive.mark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.query_permission_apply_info --node": "Reviewed unpinned adapter: drive.query_permission_apply_info has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.revert_file_version --node": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.revert_file_version --version": "Reviewed unpinned adapter: drive.revert_file_version has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", - "drive.transfer_owner --new-owner": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --new-owner is a CLI wrapper input and does not publish a direct interface property.", - "drive.transfer_owner --node": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "drive.transfer_owner --recursive": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --recursive is a CLI wrapper input and does not publish a direct interface property.", - "drive.transfer_owner --reserve-role": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --reserve-role is a CLI wrapper input and does not publish a direct interface property.", - "drive.transfer_owner --workspace": "Reviewed unpinned adapter: drive.transfer_owner has no singular pinned interface_ref; --workspace is a CLI wrapper input and does not publish a direct interface property.", - "drive.unmark_star --node": "Reviewed unpinned adapter: drive.unmark_star has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.create_sheet_comment --content": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", - "sheet.create_sheet_comment --mention": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", - "sheet.create_sheet_comment --node": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.create_sheet_comment --range": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", - "sheet.create_sheet_comment --sheet-id": "Reviewed unpinned adapter: sheet.create_sheet_comment has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", - "sheet.delete_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", - "sheet.delete_sheet_comment --node": "Reviewed unpinned adapter: sheet.delete_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --max-cells": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-cells is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --max-locations-per-error": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --max-locations-per-error is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --node": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --range": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --sheet-id": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", - "sheet.formula_verify --targets": "Reviewed unpinned adapter: sheet.formula_verify has no singular pinned interface_ref; --targets is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --cursor": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --limit": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --node": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --range": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --range is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --resolve-status": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --resolve-status is a CLI wrapper input and does not publish a direct interface property.", - "sheet.list_sheet_comments --sheet-id": "Reviewed unpinned adapter: sheet.list_sheet_comments has no singular pinned interface_ref; --sheet-id is a CLI wrapper input and does not publish a direct interface property.", - "sheet.reply_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", - "sheet.reply_sheet_comment --content": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", - "sheet.reply_sheet_comment --emoji": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --emoji is a CLI wrapper input and does not publish a direct interface property.", - "sheet.reply_sheet_comment --mention": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --mention is a CLI wrapper input and does not publish a direct interface property.", - "sheet.reply_sheet_comment --node": "Reviewed unpinned adapter: sheet.reply_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.update_sheet_comment --comment-key": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --comment-key is a CLI wrapper input and does not publish a direct interface property.", - "sheet.update_sheet_comment --content": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --content is a CLI wrapper input and does not publish a direct interface property.", - "sheet.update_sheet_comment --node": "Reviewed unpinned adapter: sheet.update_sheet_comment has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_list --cursor": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --cursor is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_list --limit": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --limit is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_list --node": "Reviewed unpinned adapter: sheet.version_list has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_revert --node": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_revert --version": "Reviewed unpinned adapter: sheet.version_revert has no singular pinned interface_ref; --version is a CLI wrapper input and does not publish a direct interface property.", - "sheet.version_save --node": "Reviewed unpinned adapter: sheet.version_save has no singular pinned interface_ref; --node is a CLI wrapper input and does not publish a direct interface property." + "wiki.search_wikiSpaces --type": "local route selector; myWikiSpace invokes list_wikiSpaces instead" } } diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index 0e929c16..ef8842ff 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -1582,8 +1582,23 @@ func newChatCommand() *cobra.Command { contentJSON = fmt.Sprintf(`{"dentryId":%d,"spaceId":%d,"fileName":"%s","fileType":"%s","filePath":"%s","fileSize":%d}`, dentryId, spaceId, fileName, fileType, filePath, fileSize) } + case "location": + latitude, _ := cmd.Flags().GetString("latitude") + longitude, _ := cmd.Flags().GetString("longitude") + locationName, _ := cmd.Flags().GetString("location-name") + mapThumbnailUrl, _ := cmd.Flags().GetString("map-thumbnail-url") + if latitude == "" || longitude == "" || locationName == "" || mapThumbnailUrl == "" { + return fmt.Errorf("--latitude, --longitude, --location-name, --map-thumbnail-url are all required for msgType=location") + } + contentJSON = fmt.Sprintf(`{"locationName":"%s","longitude":"%s","latitude":"%s","mapThumbnailUrl":"%s"}`, locationName, longitude, latitude, mapThumbnailUrl) + case "profile": + contactID, _ := cmd.Flags().GetString("contact-id") + if contactID == "" { + return fmt.Errorf("--contact-id is required for msgType=profile") + } + contentJSON = fmt.Sprintf(`{"openDingTalkId":"%s"}`, contactID) default: - return fmt.Errorf("unsupported --msg-type: %s (supported: image, file, audio, video)", msgType) + return fmt.Errorf("unsupported --msg-type: %s (supported: image, file, audio, video, location, profile)", msgType) } params := map[string]any{ @@ -2184,8 +2199,10 @@ func newChatCommand() *cobra.Command { } if cmd.Flags().Changed("only-robot") { toolArgs["onlyRobotMessages"], _ = cmd.Flags().GetBool("only-robot") + } else if cmd.Flags().Changed("only-robot-messages") { + toolArgs["onlyRobotMessages"], _ = cmd.Flags().GetBool("only-robot-messages") } - if v, _ := cmd.Flags().GetString("conversation-type"); v != "" { + if v := flagOrFallback(cmd, "conversation-type", "search-conv-type"); v != "" { toolArgs["searchConvType"] = v } @@ -2492,7 +2509,12 @@ func newChatCommand() *cobra.Command { 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("msg-type", "", "富媒体消息类型: image/file/audio/video/location/profile(本地图片/文件推荐 file --file-path;image 仅接受已有 mediaId)") + chatMessageSendCmd.Flags().String("latitude", "", "位置消息纬度(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("longitude", "", "位置消息经度(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("location-name", "", "位置消息地址名称(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("map-thumbnail-url", "", "位置消息地图缩略图 mediaId,形如 @mediaId(msgType=location 时必填)") + chatMessageSendCmd.Flags().String("contact-id", "", "联系人名片 openDingTalkId(msgType=profile 时必填)") chatMessageSendCmd.Flags().Int64("dentry-id", 0, "文件 dentryId(与 --space-id 成对传入时跳过自动上传)") chatMessageSendCmd.Flags().Int64("space-id", 0, "空间 ID(与 --dentry-id 成对传入时跳过自动上传)") chatMessageSendCmd.Flags().String("file-name", "", "文件名") @@ -2686,7 +2708,11 @@ func newChatCommand() *cobra.Command { _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("group") chatMessageSearchAdvancedCmd.Flags().String("message-type", "", "下层消息类型过滤值(可选,以当前 IM Schema 支持值为准)") chatMessageSearchAdvancedCmd.Flags().Bool("only-robot", false, "只搜索机器人消息(可选;显式传 false 时也会传给下层)") + chatMessageSearchAdvancedCmd.Flags().Bool("only-robot-messages", false, "--only-robot 的别名") + _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("only-robot-messages") chatMessageSearchAdvancedCmd.Flags().String("conversation-type", "", "下层会话类型过滤值(可选,以当前 IM Schema 支持值为准)") + chatMessageSearchAdvancedCmd.Flags().String("search-conv-type", "", "--conversation-type 的别名") + _ = chatMessageSearchAdvancedCmd.Flags().MarkHidden("search-conv-type") chatMessageSearchAdvancedCmd.Flags().String("start", "", "开始时间,ISO-8601 格式(可选)") chatMessageSearchAdvancedCmd.Flags().String("end", "", "结束时间,ISO-8601 格式(可选)") chatMessageSearchAdvancedCmd.Flags().String("cursor", "0", "分页游标(默认 \"0\")") @@ -5409,6 +5435,13 @@ pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH, he_IL, nl_NL, lo_LA, it_IT`, if len(items) > 100 { return fmt.Errorf("--items batch size %d exceeds limit 100", len(items)) } + // 每项必须携带非空 openConversationId,缺失时 fail-fast,避免下发无效批量更新。 + for i, item := range items { + cid, _ := item["openConversationId"].(string) + if strings.TrimSpace(cid) == "" { + return fmt.Errorf("--items[%d] 缺少非空 openConversationId", i) + } + } return callMCPToolOnServer("im", "batch_update_group_chat_settings", map[string]any{ "items": items, }) diff --git a/internal/helpers/chat_ports_coverage_test.go b/internal/helpers/chat_ports_coverage_test.go new file mode 100644 index 00000000..44c7cf03 --- /dev/null +++ b/internal/helpers/chat_ports_coverage_test.go @@ -0,0 +1,209 @@ +package helpers + +import ( + "io" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +func executeChatPortsCoverageCommand(t *testing.T, caller edition.ToolCaller, args ...string) error { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + root := newChatCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + return root.Execute() +} + +func TestCrossPlatformCoverageChatUpdateTextEmotion(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "conversation-id flag", + args: []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--msg-id", "msg-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + "--background-id", "im_bg_5", + }, + want: guardedMutationCall{ + productID: "im", + toolName: "update_text_emotion", + args: map[string]any{ + "openConversationId": "conv-1", + "openMsgId": "msg-1", + "oldEmotionId": "old-1", + "emotionId": "new-1", + "emotionName": "like", + "text": "nice", + "backgroundId": "im_bg_5", + }, + }, + }, + { + name: "group alias for conversation-id", + args: []string{ + "message", "update-text-emotion", + "--group", "conv-2", + "--msg-id", "msg-2", + "--old-emotion-id", "old-2", + "--emotion-id", "new-2", + "--emotion-name", "heart", + "--text", "great", + "--background-id", "im_bg_1", + }, + want: guardedMutationCall{ + productID: "im", + toolName: "update_text_emotion", + args: map[string]any{ + "openConversationId": "conv-2", + "openMsgId": "msg-2", + "oldEmotionId": "old-2", + "emotionId": "new-2", + "emotionName": "heart", + "text": "great", + "backgroundId": "im_bg_1", + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, test.args...) + if err != nil { + t.Fatalf("chat %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageChatUpdateTextEmotionRequiredFlags(t *testing.T) { + fullArgs := []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--msg-id", "msg-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + "--background-id", "im_bg_5", + } + dropFlag := func(name string) []string { + out := make([]string, 0, len(fullArgs)) + for i := 0; i < len(fullArgs); i++ { + if fullArgs[i] == name { + i++ // skip the flag value too + continue + } + out = append(out, fullArgs[i]) + } + return out + } + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "missing conversation-id and aliases", + args: dropFlag("--conversation-id"), + wantErr: "--conversation-id", + }, + { + name: "missing old-emotion-id", + args: dropFlag("--old-emotion-id"), + wantErr: "--old-emotion-id", + }, + { + name: "missing msg-id and background-id", + args: []string{ + "message", "update-text-emotion", + "--conversation-id", "conv-1", + "--old-emotion-id", "old-1", + "--emotion-id", "new-1", + "--emotion-name", "like", + "--text", "nice", + }, + wantErr: "--msg-id, --background-id", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfig(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "get-mute-config", "--group", "conv-1") + if err != nil { + t.Fatalf("get-mute-config returned error: %v", err) + } + want := guardedMutationCall{ + productID: "im", + toolName: "get_group_mute_config", + args: map[string]any{"openConversationId": "conv-1"}, + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, want) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfigRecordsRawArgs(t *testing.T) { + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"muteBlackList":[],"muteWhiteList":[]}`}, + }} + err := executeChatPortsCoverageCommand(t, caller, + "group", "get-mute-config", "--group", "conv-9") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + if caller.calls[0]["openConversationId"] != "conv-9" { + t.Fatalf("raw args = %#v", caller.calls[0]) + } +} + +func TestCrossPlatformCoverageChatGroupGetMuteConfigRequiresGroup(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "get-mute-config") + if err == nil || !strings.Contains(err.Error(), "--group") { + t.Fatalf("err = %v, want message containing --group", err) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } +} diff --git a/internal/helpers/chat_user_settings_test.go b/internal/helpers/chat_user_settings_test.go index 546f547a..69107d78 100644 --- a/internal/helpers/chat_user_settings_test.go +++ b/internal/helpers/chat_user_settings_test.go @@ -44,6 +44,36 @@ func TestCrossPlatformCoverageChatGroupUserSettingsSetValidation(t *testing.T) { } } +func TestCrossPlatformCoverageChatGroupUserSettingsSetItemValidation(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"top":true}]`) + if err == nil || !strings.Contains(err.Error(), "openConversationId") || !strings.Contains(err.Error(), "--items[0]") { + t.Fatalf("err = %v, want missing openConversationId error", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want no MCP call on invalid item", caller.calls) + } + + // 非首项缺失时错误需带正确下标;空白字符串同样视为缺失。 + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"openConversationId":"cid1"},{"openConversationId":" "}]`) + if err == nil || !strings.Contains(err.Error(), "--items[1]") { + t.Fatalf("err = %v, want --items[1] error", err) + } + + // 合法条目仍然成功下发。 + caller = &guardedMutationCaller{} + if err := executeGuardedMutationCommand(t, caller, newChatCommand, + "group", "user-settings", "set", "--items", `[{"openConversationId":"cid1","top":true,"mute":false}]`); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "batch_update_group_chat_settings" { + t.Fatalf("calls = %#v", caller.calls) + } +} + func TestCrossPlatformCoverageChatGroupUserSettingsSetHappyPath(t *testing.T) { caller := &guardedMutationCaller{} err := executeGuardedMutationCommand(t, caller, newChatCommand, @@ -59,3 +89,74 @@ func TestCrossPlatformCoverageChatGroupUserSettingsSetHappyPath(t *testing.T) { t.Fatalf("call = %#v", call) } } + +func TestCrossPlatformCoverageChatMessageSendLocation(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "location", "--latitude", "39.9") + if err == nil || !strings.Contains(err.Error(), "are all required for msgType=location") { + t.Fatalf("err = %v, want missing location params", err) + } + + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "location", + "--latitude", "39.9", "--longitude", "116.4", "--location-name", "国贸", "--map-thumbnail-url", "@media1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.args["msgType"] != "location" { + t.Fatalf("msgType = %#v", call.args["msgType"]) + } + content, _ := call.args["content"].(string) + if !strings.Contains(content, `"latitude":"39.9"`) || !strings.Contains(content, `"mapThumbnailUrl":"@media1"`) { + t.Fatalf("content = %s", content) + } +} + +func TestCrossPlatformCoverageChatMessageSendProfile(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "profile") + if err == nil || !strings.Contains(err.Error(), "--contact-id is required") { + t.Fatalf("err = %v, want contact-id required", err) + } + + caller = &guardedMutationCaller{} + err = executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "send", "--group", "cid1", "--msg-type", "profile", "--contact-id", "od123") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + content, _ := caller.calls[0].args["content"].(string) + if !strings.Contains(content, `"openDingTalkId":"od123"`) { + t.Fatalf("content = %s", content) + } +} + +func TestCrossPlatformCoverageChatSearchAdvancedWukongAliases(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, + "message", "search-advanced", "--query", "通知", + "--only-robot-messages", "--search-conv-type", "group_chat") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + args := caller.calls[0].args + if args["onlyRobotMessages"] != true { + t.Fatalf("onlyRobotMessages = %#v", args["onlyRobotMessages"]) + } + if args["searchConvType"] != "group_chat" { + t.Fatalf("searchConvType = %#v", args["searchConvType"]) + } +} diff --git a/internal/helpers/doc_style.go b/internal/helpers/doc_style.go index 624b8b4b..a807ee33 100644 --- a/internal/helpers/doc_style.go +++ b/internal/helpers/doc_style.go @@ -177,7 +177,12 @@ func runDocStyleCoverSet(cmd *cobra.Command, _ []string) error { if deps.Caller.DryRun() { cover["resourceId"] = fmt.Sprintf("(pending upload from --file %s)", filePath) } else { - resourceID, err := uploadDocStyleImage(context.Background(), nodeID, filePath, filepath.Base(filePath), mimeType, fileSize) + // 使用命令上下文,让 Ctrl-C 能够取消上传凭证获取与 OSS 上传两次网络请求。 + ctx := cmd.Context() + if ctx == nil { + ctx = context.Background() + } + resourceID, err := uploadDocStyleImage(ctx, nodeID, filePath, filepath.Base(filePath), mimeType, fileSize) if err != nil { return err } @@ -247,9 +252,13 @@ func runDocStyleGet(cmd *cobra.Command, _ []string) error { }) } +// docStyleCoverMaxBytes 是 cover set --file 本地图片的大小上限(20 MiB)。 +// 悟空参考实现未设上限,此处补充客户端 fail-fast,避免超大文件被先行上传。 +const docStyleCoverMaxBytes = 20 << 20 + // validateCoverImageFile 对 cover set --file 做本地校验(不上传/不读取文件内容): -// 文件须存在、非目录,且按扩展名推断的 MIME 必须为 image/*。返回 mimeType 与文件大小, -// 供 dry-run 预览与真实上传前 fail-fast,避免非法文件被先行上传。 +// 文件须存在、非目录、不超过大小上限,且按扩展名推断的 MIME 必须为 image/*。 +// 返回 mimeType 与文件大小,供 dry-run 预览与真实上传前 fail-fast,避免非法文件被先行上传。 func validateCoverImageFile(filePath string) (mimeType string, fileSize int64, err error) { fileInfo, err := os.Stat(filePath) if err != nil { @@ -258,6 +267,9 @@ func validateCoverImageFile(filePath string) (mimeType string, fileSize int64, e if fileInfo.IsDir() { return "", 0, fmt.Errorf("%s is a directory, not a file", filePath) } + if fileInfo.Size() > docStyleCoverMaxBytes { + return "", 0, fmt.Errorf("封面图片文件过大:上限 %d 字节 (20 MiB),实际 %d 字节 (%s)", int64(docStyleCoverMaxBytes), fileInfo.Size(), filepath.Base(filePath)) + } mimeType = inferMimeType(filepath.Base(filePath)) if !strings.HasPrefix(mimeType, "image/") { return "", 0, fmt.Errorf("cover --file must be an image (png/jpg/jpeg/gif/svg/webp), got %s", filepath.Base(filePath)) diff --git a/internal/helpers/doc_style_test.go b/internal/helpers/doc_style_test.go index 5eecb0d0..dded4c3f 100644 --- a/internal/helpers/doc_style_test.go +++ b/internal/helpers/doc_style_test.go @@ -135,3 +135,96 @@ func TestCrossPlatformCoverageUploadDocStyleImageFailures(t *testing.T) { t.Fatal("invalid upload info returned nil") } } + +func TestCrossPlatformCoverageValidateCoverImageFileSizeLimit(t *testing.T) { + // 上限以内的正常图片通过校验。 + path := writeTempImage(t, "cover.png") + if _, _, err := validateCoverImageFile(path); err != nil { + t.Fatal(err) + } + + // os.Truncate 生成超过上限 1 字节的稀疏文件(瞬时创建,不占实际磁盘)。 + if err := os.Truncate(path, docStyleCoverMaxBytes+1); err != nil { + t.Fatal(err) + } + _, _, err := validateCoverImageFile(path) + if err == nil || !strings.Contains(err.Error(), "封面图片文件过大") || !strings.Contains(err.Error(), "20 MiB") { + t.Fatalf("err = %v, want size limit error", err) + } + + // 恰好等于上限时仍应通过。 + if err := os.Truncate(path, docStyleCoverMaxBytes); err != nil { + t.Fatal(err) + } + if _, _, err := validateCoverImageFile(path); err != nil { + t.Fatalf("size == limit should pass, got %v", err) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetSizeLimitViaCommand(t *testing.T) { + path := writeTempImage(t, "cover.png") + if err := os.Truncate(path, docStyleCoverMaxBytes+1); err != nil { + t.Fatal(err) + } + caller := &scriptedToolCaller{} + err := executeDocStyleCommand(t, caller, "cover", "set", "--node", "n1", "--file", path) + if err == nil || !strings.Contains(err.Error(), "封面图片文件过大") { + t.Fatalf("err = %v, want size limit error", err) + } + if caller.calls != 0 { + t.Fatalf("calls = %d, want 0 (fail before any network request)", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetNilContextFallback(t *testing.T) { + // 直接调用 RunE(不经 Execute),cmd.Context() 为 nil,覆盖 context.Background() 兜底分支。 + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, nil) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + {text: `{}`}, + }} + installScriptedCaller(t, caller) + root := newDocStyleCommand() + coverSet, _, err := root.Find([]string{"cover", "set"}) + if err != nil { + t.Fatal(err) + } + if coverSet.Context() != nil { + t.Fatal("expected nil context on unexecuted command") + } + if err := coverSet.Flags().Set("node", "n1"); err != nil { + t.Fatal(err) + } + if err := coverSet.Flags().Set("file", path); err != nil { + t.Fatal(err) + } + if err := runDocStyleCoverSet(coverSet, nil); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (upload info + style set)", caller.calls) + } +} + +func TestCrossPlatformCoverageDocStyleCoverSetUsesCommandContext(t *testing.T) { + // 经 ExecuteContext 执行时,上传流程应使用命令上下文;scriptedToolCaller 忽略 ctx, + // 故此处做行为性断言:正常上下文下上传路径成功走通(覆盖 ctx := cmd.Context() 分支)。 + path := writeTempImage(t, "cover.png") + stubHTTPPutFile(t, nil) + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://oss.test/put","resourceId":"res-1"}`}, + {text: `{}`}, + }} + installScriptedCaller(t, caller) + root := newDocStyleCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"cover", "set", "--node", "n1", "--file", path}) + if err := root.ExecuteContext(context.Background()); err != nil { + t.Fatal(err) + } + if caller.calls != 2 { + t.Fatalf("calls = %d, want 2 (upload info + style set)", caller.calls) + } +} diff --git a/internal/helpers/drive.go b/internal/helpers/drive.go index 41332045..87211ed1 100644 --- a/internal/helpers/drive.go +++ b/internal/helpers/drive.go @@ -305,14 +305,23 @@ func newDriveCommand() *cobra.Command { pattern, _ := cmd.Flags().GetString("pattern") depth, _ := cmd.Flags().GetInt("depth") - if cmd.Flags().Changed("depth") { - if err := validateDriveListDepth(cmd, depth); err != nil { - return err - } - } // --versions 模式:列出文件历史版本(仅普通文件) + // 先于 --depth 校验执行:versions 模式合法使用 --limit, + // 不应被「--limit 与 --depth 不兼容」的误导性报错拦截。 if cmd.Flags().Changed("versions") { + if cmd.Flags().Changed("depth") && depth > 1 { + return &CLIError{ + Code: CodeInvalidParam, + Message: "--versions 与 --depth 不能同时使用", + } + } + if pattern != "" { + return &CLIError{ + Code: CodeInvalidParam, + Message: "--versions 与 --pattern 不能同时使用", + } + } nodeID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") if err != nil { return err @@ -327,6 +336,12 @@ func newDriveCommand() *cobra.Command { return callMCPToolOnServer("drive", "list_file_versions", toolArgs) } + if cmd.Flags().Changed("depth") { + if err := validateDriveListDepth(cmd, depth); err != nil { + return err + } + } + // 如果指定了 --workspace,路由到文档空间(doc MCP server) workspaceID := flagOrFallback(cmd, "workspace", "workspace-id") if workspaceID != "" { @@ -519,6 +534,73 @@ func newDriveCommand() *cobra.Command { }, } + driveDownloadVersionCmd := &cobra.Command{ + Use: "download-version", + Short: "下载文件历史版本到本地", + Long: `下载钉盘文件的指定历史版本到本地(两步下载流程)。 + +仅适用于普通文件(如 pdf、docx、xlsx、png 等): + 钉钉在线文档(adoc)请使用 dws doc version 系列命令 + 钉钉在线表格(axls)请使用 dws sheet version 系列命令 + +流程: + 1. 获取历史版本下载 URL 和签名请求头 (download_file_version) + 2. HTTP GET 下载文件二进制内容到本地 + +版本号从 dws drive list --node --versions 获取。`, + Example: ` dws drive download-version --node --version 3 --output ./report_v3.pdf + dws drive download-version --node --version 3 --output ~/downloads/`, + RunE: func(cmd *cobra.Command, args []string) error { + fileID, err := mustFlagOrFallback(cmd, "node", "url", "id", "node-id", "doc-id", "file-id") + if err != nil { + return err + } + versionNum, _ := cmd.Flags().GetInt("version") + if versionNum <= 0 { + return fmt.Errorf("--version 必须为正整数,当前值: %d(版本号从 drive list --versions 获取)", versionNum) + } + outputPath, _ := cmd.Flags().GetString("output") + if outputPath == "" { + return fmt.Errorf("flag --output is required") + } + + if deps.Caller.DryRun() { + deps.Out.PrintKeyValue("操作", "下载文件历史版本") + deps.Out.PrintKeyValue("节点ID", fileID) + deps.Out.PrintKeyValue("版本号", fmt.Sprintf("%d", versionNum)) + deps.Out.PrintKeyValue("输出", outputPath) + return nil + } + + ctx := context.Background() + deps.Out.PrintInfo("[1/2] 获取历史版本下载链接...") + text, err := callMCPToolReturnTextOnServer(ctx, "drive", "download_file_version", map[string]any{ + "nodeId": fileID, + "version": versionNum, + }) + if err != nil { + return err + } + resourceURL, dlHeaders, err := parseDownloadInfo(text) + if err != nil { + return err + } + if fi, statErr := os.Stat(outputPath); statErr == nil && fi.IsDir() { + filename := extractFileNameFromResponse(text) + if filename == "" { + filename = inferFilename(resourceURL) + } + outputPath = filepath.Join(outputPath, filename) + } + deps.Out.PrintInfo(fmt.Sprintf("[2/2] 下载文件到 %s ...", outputPath)) + if err := httpGetFile(ctx, resourceURL, dlHeaders, outputPath); err != nil { + return err + } + deps.Out.PrintInfo(fmt.Sprintf("下载完成: %s", outputPath)) + return nil + }, + } + driveMkdirCmd := &cobra.Command{ Use: "mkdir", Short: "创建文件夹", @@ -628,6 +710,14 @@ func newDriveCommand() *cobra.Command { driveDownloadCmd.Flags().String("space-id", "", "文件所属空间 ID (可选)") driveDownloadCmd.Flags().String("output", "", "本地保存路径 (文件路径或目录,必填)") + driveDownloadVersionCmd.Flags().String("node", "", "文件 ID (dentryUuid) 或 URL (必填)") + driveDownloadVersionCmd.Flags().Int("version", 0, "历史版本号 (必填,正整数,从 drive list --versions 获取)") + driveDownloadVersionCmd.Flags().String("output", "", "本地保存路径 (文件路径或目录,必填)") + for _, alias := range []string{"url", "id", "node-id", "doc-id", "file-id"} { + driveDownloadVersionCmd.Flags().String(alias, "", "") + _ = driveDownloadVersionCmd.Flags().MarkHidden(alias) + } + driveMkdirCmd.Flags().String("name", "", "文件夹名称,最长 50 字符 (必填)") driveMkdirCmd.Flags().String("space-id", "", "目标空间 ID,不传则使用「我的文件」 (可选)") driveMkdirCmd.Flags().String("folder", "", "父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选)") @@ -1211,17 +1301,15 @@ func newDriveCommand() *cobra.Command { if nodeID == "" && workspaceID == "" { return fmt.Errorf("--node or --workspace is required") } + // 不可逆高危操作:帮助文档承诺二选一,两者同传直接失败,绝不静默择一。 + if nodeID != "" && workspaceID != "" { + return fmt.Errorf("--node and --workspace are mutually exclusive; specify exactly one") + } if err := validateRequiredFlags(cmd, "new-owner"); err != nil { return err } newOwnerID := mustGetFlag(cmd, "new-owner") - if commandDryRun(cmd) { - deps.Out.PrintKeyValue("操作", "转交所有者") - deps.Out.PrintKeyValue("新所有者", newOwnerID) - return nil - } - yesMode, _ := cmd.Flags().GetBool("yes") if yesMode { if !cmd.Flags().Changed("reserve-role") { @@ -1232,6 +1320,26 @@ func newDriveCommand() *cobra.Command { } } + if commandDryRun(cmd) { + if deps.Caller.Format() == "json" { + payload := map[string]any{ + "dry_run": true, + "executed": false, + "operation": "转交所有者", + "newOwnerId": newOwnerID, + } + if nodeID != "" { + payload["nodeId"] = nodeID + } else { + payload["workspaceId"] = workspaceID + } + return deps.Out.PrintJSON(payload) + } + deps.Out.PrintKeyValue("操作", "转交所有者") + deps.Out.PrintKeyValue("新所有者", newOwnerID) + return nil + } + reserveRole := mustGetFlag(cmd, "reserve-role") recursive, _ := cmd.Flags().GetBool("recursive") @@ -1309,6 +1417,9 @@ func newDriveCommand() *cobra.Command { if v := mustGetFlag(cmd, "reason"); v != "" { toolArgs["reason"] = v } + if !commandDryRun(cmd) && !confirmDangerousAction(cmd, "发起权限申请", fmt.Sprintf("节点 %s(将真实通知审批人 %s)", nodeID, strings.Join(userIds, ","))) { + return nil + } return callMCPToolOnServer("drive", "apply_permission", toolArgs) }, } @@ -1710,6 +1821,7 @@ func newDriveCommand() *cobra.Command { driveListSpacesCmd, driveInfoCmd, driveDownloadCmd, + driveDownloadVersionCmd, driveMkdirCmd, driveUploadInfoCmd, driveCommitCmd, diff --git a/internal/helpers/drive_depth.go b/internal/helpers/drive_depth.go index 7dfdef04..94dbceb6 100644 --- a/internal/helpers/drive_depth.go +++ b/internal/helpers/drive_depth.go @@ -43,6 +43,9 @@ type driveDepthFolder struct { depth int relPath string retried bool // 已因 rate_limited 重新入队过一次,不二次补偿 + // 限流重入队时保留失败页游标:已成功页的条目已进 collected, + // 从头重扫会产生重复条目并提前耗尽全局 2000 上限。 + pageToken string } type driveDepthError struct { @@ -213,7 +216,7 @@ bfs: start := time.Now() var folderErr error - pageToken := "" + pageToken := folder.pageToken pages := 0 for { if ctx.Err() != nil { @@ -294,8 +297,10 @@ bfs: return folderErr } if driveDepthErrorCode(folderErr) == driveDepthRateLimitedCode && !folder.retried { - // 限流命中在入口处、业务逻辑未执行,重试零副作用;不加 sleep/退避。 + // 限流命中在入口处、失败页本身无副作用;但此前成功页已进 collected, + // 必须从失败页游标续扫而非整目录重扫。不加 sleep/退避。 folder.retried = true + folder.pageToken = pageToken queue = append(queue, folder) continue } diff --git a/internal/helpers/drive_depth_test.go b/internal/helpers/drive_depth_test.go index 9a950d4e..88a72995 100644 --- a/internal/helpers/drive_depth_test.go +++ b/internal/helpers/drive_depth_test.go @@ -534,6 +534,35 @@ func TestCrossPlatformCoverageRunDriveListDepthRateLimitRetry(t *testing.T) { } } +func TestCrossPlatformCoverageRunDriveListDepthRateLimitResumesFromFailedPage(t *testing.T) { + useDriveDepthArgs(t) + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"fileId":"f1","name":"page1.txt","type":"FILE"}],"nextToken":"p2"}`}, + {text: `{"errorCode":"invalidRequest.rateLimited","errorMsg":"slow down"}`}, + {text: `{"items":[{"fileId":"f2","name":"page2.txt","type":"FILE"}]}`}, + }} + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + out := &bytes.Buffer{} + deps.Out.w = out + deps.Out.errW = io.Discard + cmd := &cobra.Command{Use: "list"} + if err := runDriveListDepth(cmd, newDrivePanDepthRoute(), map[string]any{}, "", 3, "", true); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 3 { + t.Fatalf("calls = %d, want 3", len(caller.calls)) + } + if tok, _ := caller.calls[2]["nextToken"].(string); tok != "p2" { + t.Fatalf("retry call args = %#v, want resume with nextToken=p2", caller.calls[2]) + } + items := decodeDepthResult(t, out)["items"].([]any) + if len(items) != 2 { + t.Fatalf("items = %#v, want 2 without duplicates", items) + } +} + func TestCrossPlatformCoverageRunDriveListDepthRateLimitExhausted(t *testing.T) { useDriveDepthArgs(t) caller := &scriptedToolCaller{steps: []scriptedToolStep{ diff --git a/internal/helpers/drive_list_modes_test.go b/internal/helpers/drive_list_modes_test.go index 39b7c6b8..2bc4b1ac 100644 --- a/internal/helpers/drive_list_modes_test.go +++ b/internal/helpers/drive_list_modes_test.go @@ -213,3 +213,15 @@ func TestCrossPlatformCoverageDriveRevertDeclined(t *testing.T) { t.Fatalf("declined revert calls = %#v", caller.calls) } } + +func TestCrossPlatformCoverageDriveTransferOwnerRejectsBothTargets(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--workspace", "ws-1", "--new-owner", "user-1") + if err == nil || !strings.Contains(err.Error(), "mutually exclusive") { + t.Fatalf("err = %v, want mutual exclusion", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} diff --git a/internal/helpers/drive_review_fixes_test.go b/internal/helpers/drive_review_fixes_test.go new file mode 100644 index 00000000..d3cc7648 --- /dev/null +++ b/internal/helpers/drive_review_fixes_test.go @@ -0,0 +1,263 @@ +// 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. + +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "io" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +// executeDriveCommandCapture 执行 drive 命令并捕获 deps.Out 的 stdout 输出, +// 供需要断言 JSON 输出形态的测试使用(等价于 installDepthCaller + Execute)。 +func executeDriveCommandCapture(t *testing.T, caller edition.ToolCaller, args ...string) (*bytes.Buffer, error) { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + buf := &bytes.Buffer{} + deps.Out.w = buf + deps.Out.errW = io.Discard + root := newDriveCommand() + if root.PersistentFlags().Lookup("yes") == nil { + root.PersistentFlags().Bool("yes", false, "confirm high-risk operation") + } + if root.PersistentFlags().Lookup("dry-run") == nil { + root.PersistentFlags().Bool("dry-run", false, "preview without executing") + } + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + err := root.Execute() + return buf, err +} + +// ── drive permission apply:确认门禁与帮助文案契约一致 ── + +func TestCrossPlatformCoverageDrivePermissionApplyDeclined(t *testing.T) { + caller := &guardedMutationCaller{} + root := newDriveCommand() + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("declined apply calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDrivePermissionApplyYesProceeds(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "apply", "--node", "node-1", "--role", "reader", "--users", "u1,u2", "--yes") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want exactly one", caller.calls) + } + call := caller.calls[0] + if call.productID != "drive" || call.toolName != "apply_permission" { + t.Fatalf("call = %#v", call) + } + if call.args["nodeId"] != "node-1" || call.args["roleId"] != "READER" { + t.Fatalf("args = %#v", call.args) + } + if !reflect.DeepEqual(call.args["receivers"], []string{"u1", "u2"}) { + t.Fatalf("receivers = %#v", call.args["receivers"]) + } +} + +func TestCrossPlatformCoverageDrivePermissionApplyDryRunNoPromptNoCall(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + root := newDriveCommand() + promptOut := &bytes.Buffer{} + root.SetErr(promptOut) + root.SetIn(strings.NewReader("no\n")) + err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root }, + "permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run apply calls = %#v, want none", caller.calls) + } + if strings.Contains(promptOut.String(), "Confirm action") { + t.Fatalf("dry-run prompted for confirmation: %q", promptOut.String()) + } +} + +// ── drive permission transfer-owner:dry-run JSON 输出且校验先于 dry-run ── + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONNode(t *testing.T) { + caller := &scriptedToolCaller{format: "json", dry: true} + out, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run tool calls = %d, want none", caller.calls) + } + var payload map[string]any + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String()) + } + if payload["dry_run"] != true || payload["executed"] != false { + t.Fatalf("payload = %#v", payload) + } + if payload["operation"] != "转交所有者" || payload["newOwnerId"] != "user-1" || payload["nodeId"] != "node-1" { + t.Fatalf("payload = %#v", payload) + } + if _, ok := payload["workspaceId"]; ok { + t.Fatalf("unexpected workspaceId in %#v", payload) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONWorkspace(t *testing.T) { + caller := &scriptedToolCaller{format: "json", dry: true} + out, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--workspace", "ws-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + var payload map[string]any + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String()) + } + if payload["workspaceId"] != "ws-1" || payload["newOwnerId"] != "user-1" { + t.Fatalf("payload = %#v", payload) + } + if _, ok := payload["nodeId"]; ok { + t.Fatalf("unexpected nodeId in %#v", payload) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunYesValidatesFirst(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--yes", "--dry-run") + if err == nil || !strings.Contains(err.Error(), "--reserve-role is required") { + t.Fatalf("err = %v, want --reserve-role required even under --dry-run", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveTransferOwnerDryRunNonJSON(t *testing.T) { + caller := &scriptedToolCaller{format: "table", dry: true} + _, err := executeDriveCommandCapture(t, caller, + "permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run") + if err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run tool calls = %d, want none", caller.calls) + } +} + +// ── drive list:--versions 与 --depth/--pattern 的交互 ── + +func TestCrossPlatformCoverageDriveListVersionsRejectsDepth(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--depth", "2", "--limit", "10") + if err == nil || !strings.Contains(err.Error(), "--depth") { + t.Fatalf("err = %v, want explicit --versions/--depth conflict", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsRejectsPattern(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--pattern", "x") + if err == nil || !strings.Contains(err.Error(), "--pattern") { + t.Fatalf("err = %v, want explicit --versions/--pattern conflict", err) + } + if len(caller.calls) != 0 { + t.Fatalf("calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsAllowsDepthOne(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--depth", "1") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 || caller.calls[0].toolName != "list_file_versions" { + t.Fatalf("calls = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveListVersionsWithLimit(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "list", "--versions", "--node", "node-1", "--limit", "10") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want exactly one", caller.calls) + } + call := caller.calls[0] + if call.productID != "drive" || call.toolName != "list_file_versions" { + t.Fatalf("call = %#v", call) + } + if call.args["nodeId"] != "node-1" || call.args["maxResults"] != 10 { + t.Fatalf("args = %#v", call.args) + } +} + +func TestCrossPlatformCoverageDriveDownloadVersionDryRun(t *testing.T) { + caller := &guardedMutationCaller{dryRun: true} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, + "download-version", "--node", "node-1", "--version", "3", "--output", "./x.pdf", "--dry-run") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run calls = %#v, want none", caller.calls) + } +} + +func TestCrossPlatformCoverageDriveDownloadVersionDirectoryOutput(t *testing.T) { + SetHTTPGetFile(func(context.Context, string, map[string]string, string) error { return nil }) + t.Cleanup(func() { SetHTTPGetFile(nil) }) + + dir := t.TempDir() + for _, resp := range []string{ + `{"downloadUrl":"https://oss.test/get/report_v3.pdf","fileName":"报告v3.pdf"}`, + `{"downloadUrl":"https://oss.test/get/inferred_v3.pdf"}`, + } { + caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: resp}}} + installScriptedCaller(t, caller) + root := newDriveCommand() + root.PersistentFlags().Bool("dry-run", false, "") + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"download-version", "--node", "node-1", "--version", "3", "--output", dir}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if caller.calls != 1 { + t.Fatalf("calls = %d, want 1", caller.calls) + } + } +} diff --git a/internal/helpers/drive_star_coverage_test.go b/internal/helpers/drive_star_coverage_test.go new file mode 100644 index 00000000..1264c1a4 --- /dev/null +++ b/internal/helpers/drive_star_coverage_test.go @@ -0,0 +1,168 @@ +package helpers + +import ( + "reflect" + "strings" + "testing" +) + +func TestCrossPlatformCoverageDriveStarCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "add", + args: []string{"star", "add", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "mark_star", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "add with url value passed through", + args: []string{"star", "add", "--node", "https://alidocs.dingtalk.com/i/nodes/node-2"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "mark_star", + args: map[string]any{"nodeId": "https://alidocs.dingtalk.com/i/nodes/node-2"}, + }, + }, + { + name: "remove", + args: []string{"star", "remove", "--node", "node-3"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "unmark_star", + args: map[string]any{"nodeId": "node-3"}, + }, + }, + { + name: "remove rm alias", + args: []string{"star", "rm", "--node", "node-4"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "unmark_star", + args: map[string]any{"nodeId": "node-4"}, + }, + }, + { + name: "list without filters", + args: []string{"star", "list"}, + want: guardedMutationCall{ + productID: "drive", + toolName: "get_star_list", + args: map[string]any{}, + }, + }, + { + name: "list with all filters", + args: []string{ + "star", "list", + "--limit", "10", + "--cursor", "cur-1", + "--order-by", "createTime", + "--sort", "desc", + "--resource-types", "DENTRY,WORKSPACE", + "--content-types", "doc,sheet", + }, + want: guardedMutationCall{ + productID: "drive", + toolName: "get_star_list", + args: map[string]any{ + "limit": 10, + "cursor": "cur-1", + "orderBy": "createTime", + "sortType": "desc", + "supportResourceTypes": []string{"DENTRY", "WORKSPACE"}, + "contentTypes": []string{"doc", "sheet"}, + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, test.args...) + if err != nil { + t.Fatalf("drive %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageDriveStarRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "add without node", + args: []string{"star", "add"}, + wantErr: "--node", + }, + { + name: "remove without node", + args: []string{"star", "remove"}, + wantErr: "--node", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newDriveCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageDriveStarListRecordsRawArgs(t *testing.T) { + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"items":[{"nodeId":"n1","name":"doc1"}],"hasMore":false}`}, + }} + err := executeDriveCommand(t, caller, + "star", "list", "--limit", "5", "--cursor", "cur-9", "--sort", "asc") + if err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v, want 1", caller.calls) + } + args := caller.calls[0] + if args["limit"] != 5 || args["cursor"] != "cur-9" || args["sortType"] != "asc" { + t.Fatalf("star list raw args = %#v", args) + } + if _, ok := args["orderBy"]; ok { + t.Fatalf("unexpected orderBy in %#v", args) + } +} + +func TestCrossPlatformCoverageDriveStarAddScriptedSuccess(t *testing.T) { + caller := &scriptedToolCaller{format: "json", steps: []scriptedToolStep{ + {text: `{"success":true}`}, + }} + installScriptedCaller(t, caller) + root := newDriveCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"star", "add", "--node", "node-1"}) + if err := root.Execute(); err != nil { + t.Fatalf("star add returned error: %v", err) + } + if caller.calls != 1 { + t.Fatalf("tool calls = %d, want 1", caller.calls) + } +} diff --git a/internal/helpers/sheet_formula_verify.go b/internal/helpers/sheet_formula_verify.go index d5bc27da..618608d2 100644 --- a/internal/helpers/sheet_formula_verify.go +++ b/internal/helpers/sheet_formula_verify.go @@ -1,6 +1,7 @@ package helpers import ( + "context" "encoding/json" "fmt" "io" @@ -48,7 +49,8 @@ func newSheetFormulaVerifyCmd() *cobra.Command { } toolArgs["maxCells"] = v } - return callMCPToolOnServer("sheet", "formula_verify", toolArgs) + exitOnError, _ := cmd.Flags().GetBool("exit-on-error") + return callMCPToolFormulaVerify(toolArgs, exitOnError) }, } cmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") @@ -57,15 +59,66 @@ func newSheetFormulaVerifyCmd() *cobra.Command { cmd.Flags().String("targets", "", `扫描目标 JSON 数组、@文件路径 或 - 表示 stdin;每项 {"sheetId":"Sheet1","range":"A1:D100"}`) cmd.Flags().Int("max-locations-per-error", 0, "每种错误类型最多返回的位置数") cmd.Flags().Int("max-cells", 0, "最多扫描的单元格数") + cmd.Flags().Bool("exit-on-error", false, "发现公式错误时返回非 0 退出码,便于 CI/自动化使用") return cmd } -func formulaVerifyTargetsFromFlags(cmd *cobra.Command) ([]map[string]any, error) { - if v, _ := cmd.Flags().GetString("targets"); v != "" { - return parseFormulaVerifyTargets(cmd, v) +// callMCPToolFormulaVerify 与常规打印路径的差异:--exit-on-error 需要读取 +// 返回 payload 判定是否存在公式错误,故走 ReturnText 再自行 PrintJSON。 +func callMCPToolFormulaVerify(toolArgs map[string]any, exitOnError bool) error { + if deps.Caller.DryRun() { + return callMCPToolOnServer("sheet", "formula_verify", toolArgs) } + text, err := callMCPToolReturnTextOnServer(context.Background(), "sheet", "formula_verify", toolArgs) + if err != nil { + return err + } + if text == "" { + return nil + } + var parsed map[string]any + if err := json.Unmarshal([]byte(text), &parsed); err != nil { + deps.Out.PrintRaw(text) + return nil + } + if parsed == nil { + return fmt.Errorf("formula_verify returned empty result") + } + if err := deps.Out.PrintJSON(parsed); err != nil { + return err + } + if exitOnError && formulaVerifyHasErrors(parsed) { + return fmt.Errorf("formula errors found") + } + return nil +} + +func formulaVerifyHasErrors(parsed map[string]any) bool { + result := formulaVerifyResultObject(parsed) + status := strings.ToLower(strings.TrimSpace(fmt.Sprint(result["status"]))) + if status == "errors_found" { + return true + } + totalErrors, ok := nonNegativeJSONInt(result["totalErrors"]) + return ok && totalErrors > 0 +} + +func formulaVerifyResultObject(parsed map[string]any) map[string]any { + if result, ok := parsed["result"].(map[string]any); ok { + return result + } + return parsed +} + +func formulaVerifyTargetsFromFlags(cmd *cobra.Command) ([]map[string]any, error) { sheetID, _ := cmd.Flags().GetString("sheet-id") rangeStr, _ := cmd.Flags().GetString("range") + if v, _ := cmd.Flags().GetString("targets"); v != "" { + if strings.TrimSpace(sheetID) != "" || strings.TrimSpace(rangeStr) != "" { + return nil, fmt.Errorf("--targets 不能与 --sheet-id 或 --range 同时使用") + } + return parseFormulaVerifyTargets(cmd, v) + } if sheetID == "" && rangeStr != "" { return nil, fmt.Errorf("--range 必须与 --sheet-id 配合使用") } diff --git a/internal/helpers/sheet_formula_verify_test.go b/internal/helpers/sheet_formula_verify_test.go index 15b9e489..ab393642 100644 --- a/internal/helpers/sheet_formula_verify_test.go +++ b/internal/helpers/sheet_formula_verify_test.go @@ -89,3 +89,87 @@ func TestCrossPlatformCoverageSheetFormulaVerifyTargetsStdinFailure(t *testing.T t.Fatalf("err = %v, want stdin failure", err) } } + +func TestCrossPlatformCoverageSheetFormulaVerifyTargetsConflict(t *testing.T) { + for _, extra := range [][]string{ + {"--sheet-id", "Sheet1"}, + {"--range", "A1:B2"}, + } { + args := append([]string{"--node", "n1", "--targets", `[{"sheetId":"S1"}]`}, extra...) + err := executeFormulaVerify(t, &scriptedToolCaller{}, nil, args...) + if err == nil || !strings.Contains(err.Error(), "--targets 不能与 --sheet-id 或 --range 同时使用") { + t.Fatalf("args %v err = %v, want conflict error", extra, err) + } + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyExitOnError(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"ERRORS_FOUND","totalErrors":2}`}, + }} + err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error") + if err == nil || !strings.Contains(err.Error(), "formula errors found") { + t.Fatalf("err = %v, want formula errors found", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"result":{"status":"partial","totalErrors":1}}`}, + }} + err = executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error") + if err == nil || !strings.Contains(err.Error(), "formula errors found") { + t.Fatalf("nested totalErrors err = %v, want formula errors found", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"clean","totalErrors":0}`}, + }} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatalf("clean result err = %v", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"status":"errors_found"}`}, + }} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1"); err != nil { + t.Fatalf("errors without --exit-on-error err = %v", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyPayloadEdges(t *testing.T) { + caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `plain text result`}}} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatalf("non-JSON payload err = %v", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `null`}}} + err := executeFormulaVerify(t, caller, nil, "--node", "n1") + if err == nil || !strings.Contains(err.Error(), "empty result") { + t.Fatalf("null payload err = %v, want empty result", err) + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"errorCode":"forbidden.x","errorMsg":"denied"}`}}} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1"); err == nil { + t.Fatal("business error returned nil") + } + + caller = &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"status":"clean"}`}}} + installScriptedCaller(t, caller) + deps.Out.w = failingWriter{} + cmd := newSheetFormulaVerifyCmd() + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetArgs([]string{"--node", "n1"}) + if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "write failed") { + t.Fatalf("print failure err = %v, want write failed", err) + } +} + +func TestCrossPlatformCoverageSheetFormulaVerifyDryRunShortCircuit(t *testing.T) { + caller := &scriptedToolCaller{dry: true} + if err := executeFormulaVerify(t, caller, nil, "--node", "n1", "--exit-on-error"); err != nil { + t.Fatal(err) + } + if caller.calls != 0 { + t.Fatalf("dry-run calls = %d, want 0 (preview only)", caller.calls) + } +} diff --git a/internal/helpers/sheet_info_include_test.go b/internal/helpers/sheet_info_include_test.go new file mode 100644 index 00000000..16d2b7c9 --- /dev/null +++ b/internal/helpers/sheet_info_include_test.go @@ -0,0 +1,40 @@ +package helpers + +import ( + "io" + "os" + "testing" +) + +func TestCrossPlatformCoverageSheetInfoIncludePassthrough(t *testing.T) { + oldArgs := os.Args + os.Args = []string{"dws", "sheet"} + t.Cleanup(func() { os.Args = oldArgs }) + + caller := &depthArgsRecordingCaller{steps: []scriptedToolStep{ + {text: `{"sheetId":"s1"}`}, + }} + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + + root := newSheetCommand() + if root.PersistentFlags().Lookup("dry-run") == nil { + root.PersistentFlags().Bool("dry-run", false, "") + } + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"info", "--node", "n1", "--sheet-id", "Sheet1", "--include", "row_heights,col_widths"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + include, _ := caller.calls[0]["include"].([]string) + if len(include) != 2 || include[0] != "row_heights" || include[1] != "col_widths" { + t.Fatalf("include = %#v", caller.calls[0]["include"]) + } +} diff --git a/internal/helpers/sheet_ports_coverage_test.go b/internal/helpers/sheet_ports_coverage_test.go new file mode 100644 index 00000000..c8aa0fc5 --- /dev/null +++ b/internal/helpers/sheet_ports_coverage_test.go @@ -0,0 +1,298 @@ +package helpers + +import ( + "reflect" + "strings" + "testing" +) + +func TestCrossPlatformCoverageSheetCommentCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "list minimal", + args: []string{"comment", "list", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "list_sheet_comments", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list with cell and status filters", + args: []string{ + "comment", "list", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "A2", + "--resolve-status", "unresolved", + "--limit", "10", + "--cursor", "cur-1", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "list_sheet_comments", + args: map[string]any{ + "nodeId": "node-1", + "sheetId": "Sheet1", + "range": "A2", + "resolveStatus": "unresolved", + "pageSize": 10, + "nextToken": "cur-1", + }, + }, + }, + { + name: "create with mention parsing", + args: []string{ + "comment", "create", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "A2", + "--content", "check this", + "--mention", "uid1, uid2 ,,uid3", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "create_sheet_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "check this", + "sheetId": "Sheet1", + "range": "A2", + "mentionedUserIds": []string{"uid1", "uid2", "uid3"}, + }, + }, + }, + { + name: "create without mention", + args: []string{ + "comment", "create", + "--node", "node-1", + "--sheet-id", "Sheet1", + "--range", "B3", + "--content", "plain", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "create_sheet_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "plain", + "sheetId": "Sheet1", + "range": "B3", + }, + }, + }, + { + name: "reply with emoji and mention", + args: []string{ + "comment", "reply", + "--node", "node-1", + "--comment-key", "ck-1", + "--content", "heart", + "--emoji", + "--mention", "uid1", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "reply_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "heart", + "replyCommentKey": "ck-1", + "emoji": true, + "mentionedUserIds": []string{"uid1"}, + }, + }, + }, + { + name: "reply plain text", + args: []string{ + "comment", "reply", + "--node", "node-1", + "--comment-key", "ck-2", + "--content", "confirmed", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "reply_comment", + args: map[string]any{ + "nodeId": "node-1", + "content": "confirmed", + "replyCommentKey": "ck-2", + }, + }, + }, + { + name: "update", + args: []string{ + "comment", "update", + "--node", "node-1", + "--comment-key", "ck-3", + "--content", "revised", + }, + want: guardedMutationCall{ + productID: "doc-comment", + toolName: "update_comment", + args: map[string]any{ + "nodeId": "node-1", + "commentKey": "ck-3", + "content": "revised", + }, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err != nil { + t.Fatalf("sheet %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageSheetCommentRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "list without node", + args: []string{"comment", "list"}, + wantErr: "--node", + }, + { + name: "create without content sheet-id range", + args: []string{"comment", "create", "--node", "node-1"}, + wantErr: "--content, --sheet-id, --range", + }, + { + name: "reply without comment-key", + args: []string{"comment", "reply", "--node", "node-1", "--content", "text"}, + wantErr: "--comment-key", + }, + { + name: "update without content", + args: []string{"comment", "update", "--node", "node-1", "--comment-key", "ck-1"}, + wantErr: "--content", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} + +func TestCrossPlatformCoverageSheetVersionCommands(t *testing.T) { + tests := []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "save", + args: []string{"version", "save", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "save_doc_version", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list minimal", + args: []string{"version", "list", "--node", "node-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{"nodeId": "node-1"}, + }, + }, + { + name: "list with pagination pass-through", + args: []string{"version", "list", "--node", "node-1", "--limit", "10", "--cursor", "cur-1"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{ + "nodeId": "node-1", + "maxResults": 10, + "nextCursor": "cur-1", + }, + }, + }, + { + name: "list ls alias", + args: []string{"version", "ls", "--node", "node-2"}, + want: guardedMutationCall{ + productID: "doc", + toolName: "list_doc_versions", + args: map[string]any{"nodeId": "node-2"}, + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err != nil { + t.Fatalf("sheet %s returned error: %v", strings.Join(test.args, " "), err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) + } +} + +func TestCrossPlatformCoverageSheetVersionRequiredFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "save without node", + args: []string{"version", "save"}, + wantErr: "--node", + }, + { + name: "list without node", + args: []string{"version", "list"}, + wantErr: "--node", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newSheetCommand, test.args...) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("err = %v, want message containing %q", err, test.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("tool calls = %#v, want none", caller.calls) + } + }) + } +} diff --git a/internal/helpers/sheet_workbook.go b/internal/helpers/sheet_workbook.go index b6f6d966..9f2a9925 100644 --- a/internal/helpers/sheet_workbook.go +++ b/internal/helpers/sheet_workbook.go @@ -69,7 +69,8 @@ sheetId 支持传入工作表 ID 或工作表名称,可通过 sheet list 获 不传 --sheet-id 时默认返回第一个工作表。`, Example: ` dws sheet info --node NODE_ID dws sheet info --node NODE_ID --sheet-id SHEET_ID - dws sheet info --node NODE_ID --sheet-id "Sheet1"`, + dws sheet info --node NODE_ID --sheet-id "Sheet1" + dws sheet info --node NODE_ID --sheet-id SHEET_ID --include row_heights,col_widths`, RunE: func(cmd *cobra.Command, args []string) error { toolArgs := map[string]any{ "nodeId": mustGetFlag(cmd, "node"), @@ -77,11 +78,15 @@ sheetId 支持传入工作表 ID 或工作表名称,可通过 sheet list 获 if v, _ := cmd.Flags().GetString("sheet-id"); v != "" { toolArgs["sheetId"] = v } + if v, _ := cmd.Flags().GetStringSlice("include"); len(v) > 0 { + toolArgs["include"] = v + } return callMCPToolSheetInfo(toolArgs) }, } infoCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)") infoCmd.Flags().String("sheet-id", "", "工作表 ID 或名称 (不传则返回第一个工作表)") + infoCmd.Flags().StringSlice("include", nil, "可选扩展信息,逗号分隔;支持 groups / row_heights / col_widths / hidden_rows / hidden_cols / frozen") newCmd := &cobra.Command{ Use: "new", diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index 18c13b21..890bea3e 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -97,7 +97,7 @@ Flags: | `view get` | 获取视图配置(不传子命令) | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 | | `view get ` | 获取视图某个属性 | `--view-id` | 12 个:card/timebar/aggregate/filter/sort/group/visible-fields/field-widths(详见 [aitable-view-config.md](./aitable/aitable-view-config.md))+ lock/frozen-cols/row-height/fill-color-rule(详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)) | | `view list` | 列出全部视图(`view get` 的别名) | `--base-id` `--table-id` | 与 `view get` 完全等价 | -| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner;可用 `--visible-field-ids`,或用 `--config` 传 visibleFieldIds/filter/sort/group;**Gantt 创建后必须 `view update timebar` 绑定日期字段** | +| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner;用 `--config` 传 visibleFieldIds/filter/sort/group;**Gantt 创建后必须 `view update timebar` 绑定日期字段** | | `view update` | 整体更新视图 / 多属性合并更新 | `--base-id` `--table-id` `--view-id` | 可传 `--name --desc --config '{...}'`,**`--config` 路径继续保留** | | `view update ` | 按属性局部更新(推荐)| `--view-id` + typed flag / `--json` | 12 个:card/timebar/aggregate/field-widths/visible-fields/filter/sort/group/name + frozen-cols/row-height/fill-color-rule | | `view lock [--off]` | 锁定/解锁视图 | `--base-id` `--table-id` `--view-id` | 默认锁定;`--off` 解锁。详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md index 6c9b5feb..b133ac08 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-view-config.md @@ -20,15 +20,10 @@ ## 创建:view create -`--view-type` 支持 `Grid`、`Kanban`、`Gantt`、`Calendar`、`Gallery`、`FormDesigner`。创建时有两种方式设置可见字段: +`--view-type` 支持 `Grid`、`Kanban`、`Gantt`、`Calendar`、`Gallery`、`FormDesigner`。创建时通过 `--config` JSON 设置可见字段: ```bash -# 推荐:CSV typed flag;主字段必须排第一 -dws aitable view create --base-id BASE_ID --table-id TABLE_ID \ - --view-type Grid --name "任务视图" \ - --visible-field-ids fldPrimary,fldStatus,fldOwner - -# JSON 方式:可同时配置可见字段、筛选、排序和分组 +# --config JSON:可同时配置可见字段、筛选、排序和分组;主字段必须排第一 dws aitable view create --base-id BASE_ID --table-id TABLE_ID \ --view-type Grid --name "任务视图" \ --config '{"visibleFieldIds":["fldPrimary","fldStatus","fldOwner"],"sort":[{"fieldId":"fldStatus","direction":"asc"}]}' @@ -43,7 +38,7 @@ dws aitable view create --base-id BASE_ID --table-id TABLE_ID \ | `sort` | `object[]` | 排序规则数组;兼容单个 object,CLI 会自动包装为数组 | | `group` | `object[]` | 分组规则数组;兼容单个 object,CLI 会自动包装为数组 | -`--visible-field-ids` 与 `config.visibleFieldIds` 互斥。描述使用独立的 `--desc '{"content":[]}'`;`description`、`fieldWidths`、`aggregate`、`kanbanCard`、`ganttTimebar`、`galleryCard` 等其他 key 会在调用服务端前被拒绝,并提示对应的 `view update` 子命令。 +描述使用独立的 `--desc '{"content":[]}'`;`description`、`fieldWidths`、`aggregate`、`kanbanCard`、`ganttTimebar`、`galleryCard` 等其他 key 会在调用服务端前被拒绝,并提示对应的 `view update` 子命令。 ## 读取:view get diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 3e2e13bf..b0c3fc20 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -514,18 +514,18 @@ Flags: ``` Usage: - dws drive download [flags] + dws drive download-version [flags] Example: - dws drive download --node --version 3 --output ./report_v1.pdf --format json - dws drive download --node --version 3 --output --format json + dws drive download-version --node --version 3 --output ./report_v3.pdf --format json + dws drive download-version --node --version 3 --output --format json Flags: --node string 文件 ID (dentryUuid) 或 URL (必填) --version int 历史版本号 (必填,正整数,从 list --versions 获取) - --output string 本地保存路径 (文件路径或目录,不传则保存到当前目录) (可选) + --output string 本地保存路径 (文件路径或目录,必填) ``` -> **两步下载流程**:先调用 MCP 工具获取下载 URL 和签名头,再 HTTP GET 下载文件内容到本地。 -> 不传 `--output` 或指定目录时,优先从文件信息中获取原始文件名,获取不到时从下载 URL 推断;均不可用时使用默认名 `download.<扩展名>`。 +> **两步下载流程**:先调用 MCP 工具获取历史版本下载 URL 和签名头,再 HTTP GET 下载文件内容到本地。 +> `--output` 指定目录时,优先从文件信息中获取原始文件名,获取不到时从下载 URL 推断。 #### 回滚文件到指定历史版本 diff --git a/test/skill_static/skill_static_test.go b/test/skill_static/skill_static_test.go index 751758a3..76bcf86a 100644 --- a/test/skill_static/skill_static_test.go +++ b/test/skill_static/skill_static_test.go @@ -56,6 +56,14 @@ var ( "dws skill find": true, // backward-compat stub } + // Full command lines intentionally shown as wrong usage inside fenced + // 「错误示例」 blocks (kept in docs to steer LLMs away from hallucinated + // flags). Matched against the exact extracted command text. + antiPatternExactCommands = map[string]bool{ + "dws report inbox list --start-date 2026-05-04 --end-date 2026-05-11 --format json": true, // report.md: 禁止 --start-date/--end-date + "dws report inbox list --date 2026-05-11 --format json": true, // report.md: 禁止 --date + } + // Commands in this set are runnable leaves whose following token is a // positional argument rather than another Cobra subcommand. positionalLeafCommands = map[string]bool{ @@ -100,7 +108,11 @@ type cmdRef struct { Cmd string } -// extractCommands scans skills/**/*.md for every backtick `dws ...` command. +// extractCommands scans skills/**/*.md for every `dws ...` command reference: +// inline-backtick commands in prose, plus command lines inside fenced ``` code +// blocks (lines whose trimmed text starts with an optional "$ " prefix +// followed by "dws "). Backslash-continuation lines inside fenced blocks are +// joined before parsing so multi-line invocations audit as one command. func extractCommands(root string) ([]cmdRef, error) { skillsDir := filepath.Join(root, "skills") var refs []cmdRef @@ -126,19 +138,58 @@ func extractCommands(root string) ([]cmdRef, error) { if err != nil { return nil, err } - lineNo := 0 + var lines []string scanner := bufio.NewScanner(strings.NewReader(data)) scanner.Buffer(make([]byte, 1024*1024), 1024*1024*4) for scanner.Scan() { - lineNo++ - matches := backtickCmd.FindAllStringSubmatch(scanner.Text(), -1) - for _, m := range matches { - cmd := strings.TrimSpace(m[1]) - if shouldSkip(cmd) { - continue - } - refs = append(refs, cmdRef{File: f, Line: lineNo, Cmd: cmd}) + lines = append(lines, scanner.Text()) + } + inFence := false + for i := 0; i < len(lines); i++ { + lineNo := i + 1 + trimmed := strings.TrimSpace(lines[i]) + if strings.HasPrefix(trimmed, "```") { + inFence = !inFence + continue } + if !inFence { + matches := backtickCmd.FindAllStringSubmatch(lines[i], -1) + for _, m := range matches { + cmd := strings.TrimSpace(m[1]) + if shouldSkip(cmd) { + continue + } + refs = append(refs, cmdRef{File: f, Line: lineNo, Cmd: cmd}) + } + continue + } + // Inside a fenced block: only lines that *start* with an + // (optionally "$ "-prefixed) dws invocation are commands; + // everything else is illustrative output. + cmdText := strings.TrimPrefix(trimmed, "$ ") + if !strings.HasPrefix(cmdText, "dws ") { + continue + } + // Join backslash-continuation lines into one logical command. + for strings.HasSuffix(cmdText, "\\") && i+1 < len(lines) { + next := strings.TrimSpace(lines[i+1]) + if strings.HasPrefix(next, "```") { + break + } + cmdText = strings.TrimSuffix(cmdText, "\\") + cmdText = strings.TrimSpace(cmdText) + " " + next + i++ + } + cmdText = strings.TrimSpace(cmdText) + // Strip a trailing inline shell comment ("dws ... # 说明") so the + // annotation text is not parsed as flags or positional arguments. + if idx := strings.Index(cmdText, " # "); idx >= 0 { + cmdText = strings.TrimSpace(cmdText[:idx]) + } + if shouldSkip(cmdText) { + continue + } + refs = append(refs, cmdRef{File: f, Line: lineNo, Cmd: cmdText}) } } return refs, nil @@ -276,9 +327,15 @@ func exactCommand(root *cobra.Command, sub string) *cobra.Command { } if next == nil { // Once a runnable command is reached, remaining non-flag tokens are - // positional arguments rather than descendants. Validate them with - // the command's Cobra Args contract when one is present. + // positional arguments rather than descendants. A command that has + // registered child commands treats an unknown next token as a + // misspelled subcommand, not a positional argument. For true + // leaves, validate positionals with the command's Cobra Args + // contract when one is present. if current.Runnable() { + if len(current.Commands()) > 0 { + return nil + } args := tokens[i:] if current.Args == nil || current.Args(current, args) == nil { return current @@ -350,6 +407,9 @@ func TestSkillCommandsDispatch(t *testing.T) { seen := map[string]bool{} // dedupe identical sub-path and flag sets for _, r := range refs { + if antiPatternExactCommands[r.Cmd] { + continue + } sub, _, flags := parseSubPath(r.Cmd) if sub == "" { continue