diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e54347b3..f024c98a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -92,10 +92,18 @@ jobs: node-version: "20" registry-url: "https://registry.npmjs.org" - - name: Publish to npm + - name: Publish stable to npm # 只有官方仓库发 npm;fork(dev 预览)没有 NPM_TOKEN,跳过以免红叉 - if: ${{ github.repository_owner == 'DingTalk-Real-AI' }} + if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(github.ref_name, '-') }} working-directory: dist/npm/dingtalk-workspace-cli run: npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish prerelease to npm beta + # 预发布版本不能更新 npm latest,避免普通 npm 安装链路拿到 beta。 + if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(github.ref_name, '-') }} + working-directory: dist/npm/dingtalk-workspace-cli + run: npm publish --access public --tag beta + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aab1e93..016cf466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ## [Unreleased] +### Delivery Beta + +- **Remove-discovery delivery seal** — pre-release validation line for the static-endpoint delivery branch. It removes dynamic service discovery, keeps legacy CLI compatibility aliases, syncs the open command/help/skill surface with the dws-wukong baseline, and includes the `dev connect` default-yolo behavior plus the beta upgrade track. +- **Official beta packages stay out of normal `dws upgrade`** — publish the sealed beta as an upstream GitHub pre-release after merge, using a low pre-release tag such as `v0.0.48-beta.1`. Old clients' plain `dws upgrade` / `dws upgrade --check` follows GitHub `releases/latest`, so it does not install pre-releases. New clients' plain `dws upgrade` stays on the stable track, and the low numeric version prevents the sealed beta from being treated as newer than `v1.0.47`. Testers install this sealed beta only by specifying the exact tag. +- **Pinned curl install for the official beta package after the upstream pre-release exists**: + + ```sh + curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | DWS_VERSION=v0.0.48-beta.1 sh + ``` + ## [1.0.47] - 2026-07-05 This release adds **connector supervision & health monitoring** (`dev connect list/status/restart/stop`) and fixes **bot-to-bot @-mention** delivery end-to-end. diff --git a/README.md b/README.md index 1d40d0ca..f644f2d0 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,10 @@ go build -o dws ./cmd # build to current directory cp dws ~/.local/bin/ # install to PATH ``` +Static endpoint data is generated from the Wukong baseline and committed in this +repository under `internal/syncdata`, so source builds do not require a sibling +data checkout. + > Requires Go 1.25+. Use `make package` to cross-compile for all platforms (macOS / Linux / Windows x amd64 / arm64). @@ -152,12 +156,18 @@ dws has built-in self-upgrade capability. Updates are pulled directly from [GitH ```bash dws upgrade # interactive upgrade to latest version dws upgrade --check # check for new versions without installing -dws upgrade --list # list all available versions +dws upgrade --list # list stable release versions +dws upgrade --beta # upgrade to the latest beta pre-release +dws upgrade --check --beta # check the beta track without installing +dws upgrade --list --beta # list beta pre-release versions dws upgrade --version v1.0.7 # upgrade to a specific version +dws upgrade --version v1.0.8-beta.1 # upgrade to a specific beta version dws upgrade --rollback # rollback to the previous version dws upgrade -y # skip confirmation prompt ``` +By default, `dws upgrade` follows the stable release track. Use `--beta` only when you explicitly want the newest GitHub pre-release build. +
How it works @@ -171,8 +181,9 @@ A backup of the current version is automatically created before each upgrade. Us | Flag | Description | |------|-------------| | `--check` | Check for updates without installing | -| `--list` | List all available versions with changelogs | -| `--version` | Upgrade to a specific version (e.g. `v1.0.7`) | +| `--list` | List available stable release versions with changelogs | +| `--beta` | Use the beta pre-release track for `upgrade`, `--check`, or `--list` | +| `--version` | Upgrade to a specific version (e.g. `v1.0.7` or `v1.0.8-beta.1`) | | `--rollback` | Rollback to the previous backed-up version | | `--force` | Force reinstall even if already on the latest version | | `--skip-skills` | Skip skill package update | @@ -300,21 +311,18 @@ dws contact user search --query "engineering" --dry-run dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}' ``` -### Schema Discovery +### Command Help and Schema -Agents don't need pre-built knowledge of every command. Use `dws schema` to dynamically discover capabilities: +Product commands are compiled into the binary in static endpoint mode. Use `--help` and the bundled Agent Skills as the source of truth; `dws schema` is retained for helper-only schemas such as `dev.*`. ```bash -# Step 1: Discover all available products -dws schema --jq '.products[] | {id, tool_count: (.tools | length)}' +# Inspect the current compiled command surface +dws aitable record query --help -# Step 2: Inspect target tool's parameter schema -dws schema aitable.query_records --jq '.tool.parameters' +# Helper-only schema introspection +dws schema "dev app create" -# Optional: inspect DingTalk authorization metadata for PAT planning -dws schema aitable.query_records --jq '.tool.auth' - -# Step 3: Construct the correct call +# Construct the call dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10 ``` @@ -479,7 +487,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i ```bash # Built-in jq expressions dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params' -dws schema --jq '.products[] | {id, tools: (.tools | length)}' +dws schema "dev app create" --jq '.tool.required' # Return only specific fields dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response @@ -488,14 +496,12 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
-Schema Introspection — query parameter schemas before making calls +Schema Introspection — helper-only schemas in static endpoint mode ```bash -dws schema # list all products and tools -dws schema aitable.query_records # view parameter schema -dws schema aitable.query_records --jq '.tool.required' # view required fields -dws schema aitable.query_records --jq '.tool.auth' # view authorization metadata -dws schema --jq '.products[].id' # extract all product IDs +dws schema # static endpoint mode note +dws schema "dev app create" # view helper-only schema +dws schema "dev app create" --jq '.tool.required' # view required fields ```
@@ -626,7 +632,7 @@ See [`docs/robot-quickstart.md`](./docs/robot-quickstart.md) for the full 4-step - [Command Index](./docs/command-index.md) — every runtime command with description and when-to-use guidance - [Reference](./docs/reference.md) — environment variables, exit codes, output formats, shell completion -- [Architecture](./docs/architecture.md) — discovery-driven pipeline, IR, transport layer +- [Architecture](./docs/architecture.md) — static endpoint pipeline, command surface, transport layer - [Open Platform App Command Routing](./docs/dev-yulan-command-routing.md) — yulan dev app command design, MCP overlay, permission flow, and Agent routing - [Changelog](./CHANGELOG.md) — release history and migration notes diff --git a/README_zh.md b/README_zh.md index 7238988e..72f1ea22 100644 --- a/README_zh.md +++ b/README_zh.md @@ -110,6 +110,7 @@ cp dws ~/.local/bin/ # 安装到 PATH ``` > 需要 Go 1.25+。也可以用 `make package` 构建所有平台产物(macOS / Linux / Windows × amd64 / arm64)。 +> 静态端点数据由悟空基线生成并提交在本仓库 `internal/syncdata`,源码构建不需要额外 checkout 数据仓库。 @@ -152,12 +153,18 @@ dws 内置自升级能力,直接从 [GitHub Releases](https://github.com/DingT ```bash dws upgrade # 交互式升级到最新版本 dws upgrade --check # 仅检查是否有新版本 -dws upgrade --list # 列出所有可用版本 +dws upgrade --list # 列出正式 release 版本 +dws upgrade --beta # 升级到最新 beta 预发布版本 +dws upgrade --check --beta # 仅检查 beta 轨道是否有新版本 +dws upgrade --list --beta # 列出 beta 预发布版本 dws upgrade --version v1.0.7 # 升级到指定版本 +dws upgrade --version v1.0.8-beta.1 # 升级到指定 beta 版本 dws upgrade --rollback # 回滚到上一版本 dws upgrade -y # 跳过确认直接升级 ``` +默认情况下,`dws upgrade` 只跟随正式 release 轨道。只有显式传入 `--beta` 时,才会选择 GitHub pre-release 里的 beta 构建。 +
工作原理 @@ -171,8 +178,9 @@ dws upgrade -y # 跳过确认直接升级 | Flag | 说明 | |------|------| | `--check` | 仅检查更新,不安装 | -| `--list` | 列出所有可用版本及更新日志 | -| `--version` | 升级到指定版本(如 `v1.0.7`) | +| `--list` | 列出正式 release 版本及更新日志 | +| `--beta` | 对 `upgrade`、`--check`、`--list` 使用 beta 预发布轨道 | +| `--version` | 升级到指定版本(如 `v1.0.7` 或 `v1.0.8-beta.1`) | | `--rollback` | 回滚到上一个备份版本 | | `--force` | 强制重新安装,即使已是最新版本 | | `--skip-skills` | 跳过技能包更新 | @@ -300,18 +308,18 @@ dws contact user search --query "张三" --dry-run dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}' ``` -### Schema 发现 +### 命令帮助与 Schema -Agent 无需预置所有命令知识,通过 `dws schema` 动态发现可用能力: +产品命令在静态端点模式下已经编译进二进制。Agent 以 `--help` 和内置 Skill 为事实源;`dws schema` 仅保留给 `dev.*` 等 helper-only schema 查询。 ```bash -# 第一步:发现所有可用产品 -dws schema --jq '.products[] | {id, tool_count: (.tools | length)}' +# 查看当前编译出的命令面 +dws aitable record query --help -# 第二步:查看目标工具的参数结构 -dws schema aitable.query_records --jq '.tool.parameters' +# helper-only schema 自省 +dws schema "dev app create" -# 第三步:构造正确的调用 +# 构造正确的调用 dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10 ``` @@ -476,7 +484,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i ```bash # 内置 jq 表达式 dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params' -dws schema --jq '.products[] | {id, tools: (.tools | length)}' +dws schema "dev app create" --jq '.tool.required' # 只返回指定字段 dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response @@ -485,13 +493,12 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
-Schema 自省 — 调用前查询任意工具的参数结构 +Schema 自省 — 静态端点模式下的 helper-only schema ```bash -dws schema # 列出所有产品和工具 -dws schema aitable.query_records # 查看参数 Schema -dws schema aitable.query_records --jq '.tool.required' # 查看必填字段 -dws schema --jq '.products[].id' # 提取所有产品 ID +dws schema # 静态端点模式提示 +dws schema "dev app create" # 查看 helper-only schema +dws schema "dev app create" --jq '.tool.required' # 查看必填字段 ```
@@ -619,7 +626,7 @@ dws dev connect --channel auto --robot-client-id --robot-client-secret Auto-generated. Edit `pre/config.json`, not this file. +> Auto-generated. Update plugin descriptors in `internal/plugin/`, not this file. ## Global flags diff --git a/docs/connect-daemon-service.md b/docs/connect-daemon-service.md index eb4bf0c0..8d5fce85 100644 --- a/docs/connect-daemon-service.md +++ b/docs/connect-daemon-service.md @@ -1,6 +1,6 @@ # Running the connector as a 7x24 service -`dws devapp robot connect` keeps a DingTalk robot wired to a local agent over a +`dws dev connect` keeps a DingTalk robot wired to a local agent over a Stream long-connection. By default it runs in the foreground and dies when the terminal closes. For an unattended "digital employee" you have two options. @@ -15,14 +15,14 @@ terminal closes. For an unattended "digital employee" you have two options. ```bash # Detach into a background supervisor that restarts the connector if it crashes. -dws devapp robot connect --daemon \ +dws dev connect --daemon \ --channel claudecode \ --unified-app-id # Inspect / stop / restart it (locate the daemon by unifiedAppId). -dws devapp robot connect status --unified-app-id -dws devapp robot connect stop --unified-app-id -dws devapp robot connect restart --unified-app-id +dws dev connect status --unified-app-id +dws dev connect stop --unified-app-id +dws dev connect restart --unified-app-id ``` - The parent prints the daemon pid and the log path, then exits. @@ -60,8 +60,7 @@ and `REPLACE_UNIFIED_APP_ID`, then `launchctl load -w `. ProgramArguments /usr/local/bin/dws - devapp - robot + dev connect --channel claudecode @@ -110,7 +109,7 @@ Wants=network-online.target [Service] Type=simple -ExecStart=/usr/local/bin/dws devapp robot connect \ +ExecStart=/usr/local/bin/dws dev connect \ --channel claudecode \ --unified-app-id REPLACE_UNIFIED_APP_ID Restart=always @@ -136,7 +135,7 @@ security warning to stderr. This form: - exposes `clientSecret` to every user on the box via `ps -ef`; - gets baked into launchd `ProgramArguments` / systemd `ExecStart`, which makes rotation harder; -- means `dws devapp robot connect restart` cannot re-fetch credentials — you +- means `dws dev connect restart` cannot re-fetch credentials — you must re-run the full command yourself. Prefer `--unified-app-id`. Only fall back to the pair when you understand the diff --git a/docs/reference.md b/docs/reference.md index 5b3cdce8..76b60ba6 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -5,7 +5,7 @@ | Variable | Purpose / 用途 | |---------|---------| | `DWS_CONFIG_DIR` | Override default config directory / 覆盖默认配置目录 | -| `DWS_SERVERS_URL` | Point discovery at a custom server registry endpoint / 将服务发现指向自定义端点 | +| `DWS__MCP_URL` | Override a product MCP endpoint for local development / 本地开发时覆盖指定产品 MCP endpoint | | `DWS_CLIENT_ID` | OAuth client ID (DingTalk AppKey) | | `DWS_CLIENT_SECRET` | OAuth client secret (DingTalk AppSecret) | | `DWS_TRUSTED_DOMAINS` | Comma-separated trusted domains for bearer token (default: `*.dingtalk.com`). `*` for dev only / Bearer token 允许发送的域名白名单,默认 `*.dingtalk.com`,仅开发环境可设为 `*` | @@ -22,7 +22,7 @@ | 3 | Validation | Invalid input, flags, or parameter schema mismatch / 输入参数校验失败 | | 4 | PAT | PAT authorization interception; stderr carries raw machine-readable PAT JSON / PAT 授权拦截;stderr 返回原始机器可解析 JSON | | 5 | Internal | Unexpected internal error / 未预期的内部错误 | -| 6 | Discovery | Server discovery, cache, or protocol negotiation failure / 服务发现、缓存或协议协商失败 | +| 6 | Discovery | Static endpoint resolution or protocol negotiation failure / 静态端点解析或协议协商失败 | With `-f json`, error responses include structured payloads: `category`, `reason`, `hint`, `actions`. @@ -34,7 +34,7 @@ With `-f json`, error responses include structured payloads: `category`, `reason dws contact user search --query "Alice" -f table # Table (default, human-friendly / 表格,默认) dws contact user search --query "Alice" -f json # JSON (for agents and piping / 适合 agent) dws contact user search --query "Alice" -f raw # Raw API response / 原始响应 -dws schema -f pretty ding.send_ding_message # Pretty (ANSI-colored, schema-aware / 彩色分区,专为 schema 设计) +dws schema -f pretty "dev app create" # Pretty helper-only schema view / helper-only schema 彩色分区展示 ``` ## Dry Run / 试运行 @@ -51,26 +51,24 @@ dws contact user search --query "Alice" -o result.json ## Schema Introspection / Schema 查询 -`dws schema` 查询已发现的 MCP 产品和工具元数据。不带参数列出所有产品,带路径输出单个工具的完整 schema。 +静态端点模式下,产品命令和 flag 以当前二进制的 `--help` 与内置 Skill 为准。`dws schema` 仅保留 helper-only 子树(如 `dev.*`)的 schema 查询。 ### 路径写法 ```bash -dws schema # 列出所有产品 + 工具名 -dws schema ding.send_ding_message # canonical: product.rpc_name -dws schema ding.message.send # CLI 点路径: product.group.cli_name -dws schema "ding message send" # CLI 空格路径(同上) -dws schema --cli-path "ding message send" # 显式 flag(脚本友好,免转义) -dws schema -f pretty ding.send_ding_message # ANSI 着色分区展示(人肉查看最舒服) +dws schema # 静态端点模式提示 +dws schema "dev app create" # CLI 空格路径 +dws schema --cli-path "dev app create" # 显式 flag(脚本友好,免转义) +dws schema -f pretty "dev app create" # ANSI 着色分区展示(人肉查看最舒服) ``` -Canonical 路径先匹配;落空后走 CLI 路径(product → group.. → cli_name)。 +helper-only schema 以 CLI 路径为准;普通产品命令请使用 `dws --help` 查看参数。 ### 单工具输出字段 | 字段 | 说明 | |------|------| -| `name` / `cli_name` / `canonical_path` | MCP RPC 名 / CLI 叶子名 / `product.rpc_name` | +| `name` / `cli_name` / `canonical_path` | MCP RPC 名 / CLI 叶子名 / helper-only canonical path | | `group` | CLI 父级 group 路径(dot-separated) | | `title` / `description` | 工具名/说明(overlay 优先) | | `parameters` / `required` | MCP 输入 JSON Schema 的 properties / required | @@ -85,10 +83,8 @@ Canonical 路径先匹配;落空后走 CLI 路径(product → group.. → cl ### 筛选输出 ```bash -dws schema ding.send_ding_message --jq '.tool.flag_overlay' # 只看 overlay -dws schema calendar.create_event --jq '.tool.auth' # 只看授权元数据 -dws schema --jq '.products[] | {id, count: (.tools|length)}' # 各产品工具数 -dws schema aitable.delete_base --jq '.tool.annotations' # 敏感操作提示 +dws schema "dev app create" --jq '.tool.parameters' # 只看参数 schema +dws schema "dev app create" --jq '.tool.required' # 只看必填字段 ``` ## Shell Completion / 自动补全 diff --git a/skills/mono/SKILL.md b/skills/mono/SKILL.md index 4f8dc473..da1d2772 100644 --- a/skills/mono/SKILL.md +++ b/skills/mono/SKILL.md @@ -9,50 +9,35 @@ cli_version: ">=1.0.15" 通过 `dws` 命令管理钉钉产品能力。 -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 ## 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 -- 不要编造 URL、Email、手机号等结构化信息,必须从命令返回中提取或由用户明确提供 - 不要猜测字段名/参数值,操作前必须先查询确认 -- 禁止编造命令路径、子命令或 flag;产品参考缺失、路径/flag 不确定,或报 `unknown command` / `unknown flag` 时,必须先运行对应层级的 `dws --help` 查证后再执行或重试 ## 严格要求 (MUST DO) -- DWS 命令合法性协议:执行 `dws` 前必须用当前 skill 资料确认命令;产品参考已覆盖时直接按参考执行,缺失或不确定时必须先用 `--help` 查证 - 所有命令必须加 `--format json` 以获取可解析输出 - 危险操作必须先向用户确认,用户同意后才加 `--yes` 执行 - 单次批量操作不超过 30 条记录 - 所有命令必须**严格遵循**对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) - **脚本优先**:[scripts/](./scripts/) 下的 `python scripts/.py` 已封装翻页/轮询/批量逻辑,遇到对应场景(如 AI 表格批量导入导出、AI 应用创建轮询、文档创建后写内容、钉盘目录树等)**优先调用脚本**而非手写多步命令。脚本均支持 `--dry-run` 预览、`--format json` 输出,失败时回退到手动步骤 -- **业务域最佳实践优先**:文档类多步任务先读 [04-document.md](./references/best_practices/04-document.md);AI 表格读取/统计/写入/导入导出先读 [06-data-analytics.md](./references/best_practices/06-data-analytics.md)。本仓库只迁入这些业务域 best practices,不引入其它产品行动指南。 -- 知识库容器只用 `dws wiki space/member`;知识库内文件/文档的浏览、搜索、读取、创建、移动、复制统一切到 `dws doc`。`workspaceId` 只能传给 `wiki --workspace`、`doc --workspace` 或 `doc search --workspace-ids`,禁止传给 `doc list --folder`,也不要使用不存在的 `--space-id`。 -- 找群 / 找人 / 找数据在当前组织没命中、且 `dws profile list` 显示 ≥2 个组织时,对每个组织带一次性 `--profile ` 各搜一遍;命中即用,全部组织都没有才追问用户。禁止在当前组织搜不到就判定「不存在」或直接甩给用户选。 - -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按本文件「错误处理」执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 ## 产品总览 -> 若用户意图涉及多步操作、汇总/整理/归纳/分析、文档创建后写入内容、知识库内文档处理、AI 表格批量读写/统计/导入导出,**先匹配下方「行动指南」**;仅当行动指南无匹配且明确是单一产品单步操作时,按本表路由。 - | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| +| `aiapp` | AI应用:创建/查询/修改AI应用 | [aiapp.md](./references/products/aiapp.md) | | `aisearch` | AI搜问(搜人首选):按姓名/部门/职位/职责/上级/下级/手机号/工号维度找人,"谁负责 XX/XX 的负责人/某事项/某项目的人"统一走本产品 | [aisearch.md](./references/products/aisearch.md) | | `aitable` | AI表格:Base/数据表/字段/记录/视图/附件/图表/仪表盘/导入导出/模板搜索 | [aitable.md](./references/products/aitable.md) | -| `attendance` | 考勤:考勤组与规则查询(rules)/个人打卡详情(record get)/批量班次查询(shift list)/考勤统计摘要(summary),仅此 4 个命令组 | [attendance.md](./references/products/attendance.md) | +| `attendance` | 考勤:打卡结果/打卡流水/考勤组查询/考勤规则/汇总统计/假期类型/假期余额(P0 已落地,部分管理类命令仍属 P1) | [attendance.md](./references/products/attendance.md) | | `calendar` | 日历:日历列表/日程/参与者/附件/响应/会议室/闲忙查询/时间建议 | [calendar.md](./references/products/calendar.md) | -| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/消息发送(文本/Markdown/图片/文件)/拉取消息/@我/特别关注/机器人群发/单聊/撤回/转发/引用回复/Webhook/**查询**已有机器人 | [chat.md](./references/products/chat.md) | -| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/花名册档案(学历/家庭/银行卡/合同)/离职员工查询(姓名/时间范围/部门)/部门查询(搜索/详情/子部门/成员)/特别关注列表(按角色/职责找人请走 `aisearch`) | [contact.md](./references/products/contact.md) | -| `dev` | 开放平台开发者:**新建/配置机器人(建号)**、建联调试(把机器人接到本地 agent 的 Stream)、应用生命周期(创建/更新/删除/凭证/权限/成员/事件订阅)、开放平台文档搜索 | [dev.md](./references/products/dev.md) | +| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/消息发送(文本/Markdown/图片/文件)/拉取消息/@我/特别关注/机器人群发/单聊/撤回/转发/引用回复/Webhook/机器人搜索 | [chat.md](./references/products/chat.md) | +| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/花名册档案(学历/家庭/银行卡/合同)/离职员工查询(姓名/时间范围/部门)/部门查询(搜索/详情/子部门/成员)/角色查询(主管/管理员/财务/HR 等 label)/特别关注列表 | [contact.md](./references/products/contact.md) | | `devdoc` | 开放平台文档:搜索开发文档 | [devdoc.md](./references/products/devdoc.md) | | `ding` | DING消息:发送/撤回(应用内/短信/电话) | [ding.md](./references/products/ding.md) | -| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名/**删除/导出 docx/权限管理/媒体上传下载**;创建/编辑先按渐进式 doc 子文档与 JSONML 工作流决策 | [doc.md](./references/products/doc.md) | +| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名/**删除/导出 docx/权限管理/媒体上传下载** | [doc.md](./references/products/doc.md) | | `drive` | 钉钉云盘:文件列表/元数据/文件夹/上传(两步)/下载 | [drive.md](./references/products/drive.md) | | `minutes` | AI听记:听记列表/摘要/关键词/转写/待办/思维导图/发言人/发言人段落总结/热词/录音控制/成员权限/上传 | [minutes.md](./references/products/minutes.md) | | `oa` | OA审批:待处理/详情/同意/拒绝/撤销/记录/已发起/任务/转交/评论/抄送 | [oa.md](./references/products/oa.md) | @@ -62,73 +47,18 @@ cli_version: ">=1.0.15" | `todo` | 待办:创建(含优先级/截止时间/循环)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) | | `wiki` | 知识库:空间创建/详情/列表/搜索 + 成员管理 | [wiki.md](./references/products/wiki.md) | -## 核心流程(每次请求必须执行,不得跳过) - -作为一个智能助手,你的首要任务是**理解用户的真实、完整意图**,不是简单执行第一条看起来相关的命令。在选择 `dws` 产品命令前,必须按以下流程执行: - -0. **URL 预检**:输入含 `alidocs.dingtalk.com` URL 时,必须先读取 [url-patterns.md](./references/url-patterns.md) 的「alidocs URL 分流决策」,识别 URL 是文档、文件夹、知识库、表格、分享短链还是其它格式,再选择对应产品。含 `shanji.dingtalk.com` URL 时直接路由到 `minutes`。 -1. **意图拆解**:判断用户请求是否包含多个时序步骤(如“创建文件夹,然后创建文档并写入内容”“读文档,然后总结并评估”“建 AI 表格,再加字段和记录”)。若是,拆成多个子意图,按顺序执行,前一步产出的 `workspaceId` / `nodeId` / `baseId` / `tableId` 必须作为后一步输入。 -2. **行动指南优先匹配**:将用户意图或拆解后的子意图与下方「行动指南」逐行做语义比对。命中文档知识或 AI 表格数据任一行时,必须先读取对应 best practice,再读取对应产品参考文件执行;文档知识场景还必须进入 [doc.md](./references/products/doc.md) 的渐进式文档索引按需加载子文件。 -3. **recipe 分级执行**:当前开源版只迁入业务域 full recipe(`04-document.md`、`06-data-analytics.md`),未迁入悟空完整 `lite-recipes.md`。因此命中下方任一业务域行动指南时,一律按 full recipe 处理:先读行动指南,再读产品参考,不要只凭主 skill 摘要直接执行。文档创建/更新/块级编辑按 `doc.md` 前置条件继续读取 `doc/style/doc-create-workflow.md`、`doc/style/doc-update-workflow.md`、`doc/format/doc-jsonml-schema.md` 或 `doc/format/doc-jsonml-cookbook.md`,优先保真使用 JSONML。 -4. **Fallback 单产品路由**:仅当行动指南未命中,且用户意图明确是单一产品单步操作时,才按「产品总览」和「意图判断决策树」选择产品,并读取对应 `references/products/*.md`。 -5. **追问**:以上步骤都无法判断时,主动追问用户澄清,严禁猜测命令、flag、URL、ID 或字段名。 - -## 多组织处理 -dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织(corp)。当前 profile 决定本次命令用哪个组织的身份(corpId / userId 按当前 profile 自动注入,不是只支持单组织)。 - -**触发条件(命中任一即进入本节)**: -- 显式:用户提到 切换 / 换 / 跨组织、另一个钉钉、别的公司、看登录了哪些组织、当前是哪个组织、某人 / 某群 / 某数据在别的组织 -- 隐式(最常见、易漏):在当前组织读 / 搜没找到目标(群 / 人 / 数据),且 `dws profile list` 显示已登录 ≥2 个组织 —— 别急着判「不存在」,按下方跨组织铁律去其他组织找 -- 需要跨多个组织汇总 / 对比数据 -- 用户问认证状态 / 登录了哪些组织 / 主组织是哪个 - -**不触发**:只登录 1 个组织时,按当前组织正常处理,不带 `--profile`,不进本节。 - -命令: -- `dws profile list` — 列出已登录组织(主 / 当前标记、状态、有效期),只读元数据 -- `dws profile switch <名称|corpId|->` — 持久切换当前组织;`-` 切回上一个;无参数在交互终端弹选择器(非交互须显式传参)。`dws profile use` 是其别名 -- 全局 `--profile <名称|corpId>` — 单次指定本命令用哪个组织,一次性、不改当前组织 -- `dws auth login` — 再登一个组织即新增 profile(自动从授权账号取 corpId / corpName);同组织重复 login = 刷新 -- `dws auth status [--profile <名称>]` — 查看认证状态 - -多组织数据聚合步骤:`dws profile list` 拿到所有已登录组织,对每个组织带 `--profile ` 各取一次数,合并并标注来源组织;某组织失败则标「该组织暂不可用」并继续返回其余。 -安全护栏: -- 只有 `dws profile list` 显示 ≥2 个组织才启用上面的跨组织逻辑;单组织直接按当前组织走,不带 `--profile`。 -- 自动跨组织只对「读 / 搜」。写 / 发 / 删 / 撤回等操作默认只在当前组织做;确需带 `--profile` 跨组织写时,必须先与用户确认目标组织。 -- 持久切换 `dws profile switch`(改默认组织)按写操作对待:未经用户明确要求不得执行。跨组织找数一律用一次性 `--profile`,不改当前组织。 - -## 行动指南(优先匹配) - -> 将用户意图与下表做**语义比对**,不要求字面包含关键词。命中后必须读取该行动指南文件,并按其中固定路线执行;多个场景同时命中时,按下方「消歧规则」选择。 - -| # | 场景 | 触发关键词 / 能力范围 | 行动指南 | -|---|------|----------------------|----------| -| 4 | 文档知识 | 搜索/浏览/读取/创建/更新/迁移/模板复用/导出钉钉文档;知识库内文档处理;文件夹下创建文档;块级编辑;JSONML 保真改写;图片/附件/PDF/Excel/PPT 嵌入正文;读文档后总结/评估/对照 | [04-document.md](./references/best_practices/04-document.md) | -| 6 | AI 表格数据 | AI 表格读取/统计/写入记录/更新记录/字段/视图/导入导出/模板建表/主文档读取 | [06-data-analytics.md](./references/best_practices/06-data-analytics.md) | - -### 消歧规则 - -- "工作台应用/app001/appXYZ/钉钉工作台上有哪些应用" → `workbench app`。 -- "开放平台接口文档/API 怎么调用/错误码/字段说明" → `devdoc`。 -- "MCP 服务/connector/创建工具/HSF 映射/上架 MCP" → MCP 平台配置流程。 -- 只说"应用"且缺少上下文时,先结合当前对话判断;仍不明确时追问是工作台应用、文档能力还是 MCP 工具。 -- "知识库空间/成员管理" → `wiki` 产品参考;"知识库里的文档/文件/内容" → #4 文档知识,再切 `doc`。 -- "创建表格/在线电子表格/单元格" → `sheet`;"AI 表格/多维表/base/记录/字段/视图" → #6 AI 表格数据。 -- "写文档/读文档/总结文档/插入图片附件/块级编辑/JSONML 保真改写" → #4 文档知识,不要停在 `doc create` 或 `doc block --help`;创建/编辑必须继续按 [doc.md](./references/products/doc.md) 加载渐进式子文档和 JSONML workflow。 -- "导出文档后归档/上传" → 先 #4 完成 `doc export`,再按产品路由继续 `drive upload` 等后续动作。 - ## 意图判断决策树 -用户提到"找人/搜人/谁负责 XX/某事项的负责人/某项目的人/某职责/某角色(主管/管理员/财务/HR 等)由谁担任/团队成员/上级/下级/按工号找人/按手机号找人" → `aisearch`(按角色或职责找人用 `aisearch person --dimension duty`) +用户提到"AI应用/创建应用/生成系统/做工具/管理后台/低代码" → `aiapp` +用户提到"找人/搜人/谁负责 XX/某事项的负责人/某项目的人/团队成员/上级/下级/按工号找人/按手机号找人" → `aisearch` 用户提到"表格/多维表/AI表格/记录/数据/视图/图表/仪表盘" → `aitable` 用户提到"考勤/打卡/排班" → `attendance` 用户提到"日程/日历/会议室/约会/时间建议" → `calendar` -用户提到"群聊/建群/群成员/群管理/发消息/发图片消息/发文件消息/发 Markdown 消息/截图发钉钉/转发消息/引用回复/@我/特别关注消息/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat`(仅 IM 操作;创建/配置/建联机器人走 `dev`,见下) -用户提到"创建机器人/新建机器人/建机器人/配置机器人/机器人建号/建联/把机器人连到/接入 agent/opencode/claude/qoder/连接 agent/开放平台应用/开发者应用/app 创建/应用凭证/事件订阅/dws dev" → `dev` -用户提到"通讯录/同事/部门/组织架构/子部门/部门多少人/离职员工/离职名单/离职花名册/花名册/员工档案/学历/家庭/银行卡/紧急联系人/合同/特别关注/星标联系人" → `contact`(按角色/职责找人不在此,走 `aisearch`) +用户提到"群聊/建群/群成员/群管理/发消息/发图片消息/发文件消息/发 Markdown 消息/截图发钉钉/转发消息/引用回复/@我/特别关注消息/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat` +用户提到"通讯录/同事/部门/组织架构/子部门/部门多少人/离职员工/离职名单/离职花名册/花名册/员工档案/学历/家庭/银行卡/紧急联系人/合同/角色/主管角色/管理员角色/财务/HR/特别关注/星标联系人" → `contact` 用户提到"开发/API/调用错误 文档" → `devdoc` 用户提到"DING/紧急消息/电话提醒" → `ding` -用户提到"钉钉文档/云文档/读写文档/知识库里的文档/浏览知识库内容/知识库内搜索文档/块级编辑/文档评论/文档复制移动" → `doc` +用户提到"钉钉文档/云文档/知识库/读写文档/块级编辑/文档评论/文档复制移动" → `doc` 用户提到"云盘/文件存储/文件上传下载/文件夹" → `drive` 用户提到"听记/AI听记/会议纪要/转写/摘要/思维导图/发言人/热词" → `minutes` 用户提到"邮箱/邮件/发邮件/收邮件/搜邮件/查邮件/邮件草稿/转发邮件/回复邮件/邮件附件/抄送" → `mail` @@ -136,15 +66,12 @@ dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织 用户提到"日志/日报/周报/日志统计/写日报/提交周报/发日志/填日志" → `report` 用户提到"在线电子表格/钉钉表格/axls/工作表/单元格读写/合并单元格/筛选视图/导出 xlsx" → `sheet` 用户提到"待办/TODO/任务提醒/循环待办" → `todo` -用户提到"创建知识库/知识库列表/搜索知识库空间/wiki/团队空间/知识库成员管理/我的文档个人空间" → `wiki` -用户提到"切换组织/换组织/跨组织/另一个钉钉/别的公司/多组织/看所有组织/profile/登录了哪些组织" → `profile`(见「多组织 / profile」节) +用户提到"知识库/wiki/团队空间/知识库成员管理" → `wiki` -关键区分: **dev(创建/配置/建联机器人)** vs **chat(查询/发消息已有机器人)**。`dws chat bot search/find` 只查询机器人;**建号**(创建钉钉智能体机器人)走 `dws dev app robot submit`;**建联**(把机器人接到本地 agent 的 Stream)走 `dws dev connect`。凡是"创建机器人""建机器人""接入 agent""建联"一律路由到 `dev`,禁止走 `chat`。 关键区分: aitable(数据表格) vs todo(待办任务) 关键区分: report(钉钉日志/日报周报) vs todo(待办任务) 关键区分: chat send-by-bot(机器人身份发消息) vs send-by-webhook(自定义机器人Webhook告警) 关键区分: doc(钉钉文档/富文本协同) vs drive(钉钉云盘/二进制文件) -关键区分: wiki(知识库空间/成员管理) vs doc(知识库内文档内容读写)。用户要读/搜/列知识库里的文档时:先 `wiki space list/search` 拿 `workspaceId`,再 `doc list --workspace` / `doc search --workspace-ids` / `doc read --node`。 关键区分: oa tasks(审批 taskId,审批/拒绝用) vs oa list-pending(收件箱 processInstanceId,查看用) @@ -175,7 +102,6 @@ dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织 | `oa` | `approval reject` | 拒绝待审批(需加明确理由) | | `todo` | `task delete` | 删除待办 | | `minutes` | `replace-text` | 全文批量替换转写与摘要 | -| `auth` | `logout` | **默认退出所有已登录组织**;只退一个加 `--profile <名称\|corpId>`。注意:退主组织不会被拦,会静默把「主」改选为剩下第一个组织,退主前必须向用户确认 | ### 确认流程 ``` @@ -184,40 +110,43 @@ Step 2 → 用户明确回复确认(如 "确认" / "好的") Step 3 → 加 --yes 执行命令 ``` +## 核心流程 +作为一个智能助手,你的首要任务是**理解用户的真实、完整的意图**,而不是简单地执行命令。在选择 `dws` 的产品命令前,必须严格遵循以下四步流程: + +0. **URL 预检**:输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式(`/i/nodes/...`、`/i/p/...`、`/spreadsheetv2/...`、`/document/edit|preview?dentryKey=...` 等),每种的处理流程不同。**必须先读取 [url-patterns.md](./references/url-patterns.md) 中的「alidocs URL 分流决策」**,按其中规则识别 URL 类型后再选择对应产品。含 `shanji.dingtalk.com` URL 时直接路由到 `minutes`。URL 已识别后直接进入对应产品流程,无需后续步骤。 +1. 意图分类:首先,判断用户指令的核心 动词/动作 属于哪一类。这比关注名词更重要。 +2. 歧义处理与信息追问:如果用户指令模糊或包含多个产品的关键字,严禁猜测。必须主动向用户追问以澄清意图。这是你作为智能助手而非命令执行器的核心价值。 +3. 精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。 +4. 充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。 + ## 命令发现(flag / 参数以 binary 为准) -产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束随服务发现动态变化,**以下两个命令的输出才是调用的事实源**: +产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束以当前二进制编译出的 Cobra 命令为准,**`--help` 是产品命令调用的事实源**: ```bash -# 1) 人读视图:看 Usage / Example / Flags +# 人读视图:看 Usage / Example / Flags dws --help # 例:dws calendar event list --help -# 2) 机读视图:JSON Schema + flag 别名映射 + 必填字段 -dws schema # 列出所有产品及工具 -dws schema . # 规范路径(如 calendar.list_suggested_event_times) -dws schema " " # CLI 路径(如 "calendar event list") -dws schema --jq '.tool.flag_overlay' # 只看 flag 别名 -dws schema --jq '.tool.required' # 只看必填字段 +# helper-only schema 查询(如 dev.*),普通产品命令不要依赖 schema 推断参数 +dws schema "dev app create" +dws schema "dev app create" --jq '.tool.required' ``` **何时用哪条路径:** - 只需看某个命令怎么调用 → `dws --help` -- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → `dws schema ` -- 参考文档和 `--help` 冲突时 → **以 `--help` / `dws schema` 为准**,文档视为过期 +- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → 先看 `dws --help`,helper-only 命令再看 `dws schema` +- 参考文档和 `--help` 冲突时 → **以 `--help` 为准**,文档视为过期 -`dws schema` 输出的 `flag_overlay[key].alias` 就是实际生效的 flag 名(如 `attendeeUserIds → --attendee-user-ids`);`parameters[key]` 是原始 JSON Schema;`required` 是必填字段数组;`sensitive: true` 表示写/删操作,须先向用户确认再加 `--yes`。 +`dws schema` 在静态端点模式下只保留 helper-only 子树;普通产品命令和 flag 不再通过远程 schema 动态发现。写/删操作须先向用户确认再加 `--yes`。 ## 错误处理 -1. 遇到错误,加 `--verbose` 重试**一次** +1. 遇到错误,加 `--verbose` 重试一次 2. 若 stderr 出现 `RECOVERY_EVENT_ID=`,优先按 [recovery-guide.md](./references/recovery-guide.md) 执行 recovery 闭环 -3. 仍然失败,**立即停止**并报告完整错误信息,禁止自行尝试替代方案或反复变通 -4. **严禁**连续重试超过 3 次相同或类似的命令;如果 3 次仍失败,必须停止并报告 -5. 报 `unknown command` / `unknown flag` 时,先运行对应层级 `dws --help` 查证,再修正一次;不要把自然语言同义词直接当命令或 flag -6. 逐条命令多次失败时,检查 [scripts/](./scripts/) 是否有对应业务域脚本可降级使用 -7. 认证失败时,参考 [global-reference.md](./references/global-reference.md) 中的认证章节处理 -8. 各产品高频错误及排查流程见 [error-codes.md](./references/error-codes.md) -9. 遇到 [capability-limits.md](./references/capability-limits.md) 中列出的「已知不支持操作」时,**直接告知用户不支持并建议在钉钉客户端操作**,不要重试或变通 +3. 仍然失败,报告完整错误信息给用户,禁止自行尝试替代方案 +4. 认证失败时,参考 [global-reference.md](./references/global-reference.md) 中的认证章节处理 +5. 各产品高频错误及排查流程见 [error-codes.md](./references/error-codes.md) +6. 遇到 [capability-limits.md](./references/capability-limits.md) 中列出的「已知不支持操作」时,**直接告知用户不支持并建议在钉钉客户端操作**,不要重试或变通 ## 详细参考 (按需读取) @@ -230,6 +159,21 @@ dws schema --jq '.tool.required' # 只看必填字段 - [references/error-codes.md](./references/error-codes.md) — 错误码 + 调试流程 - [references/recovery-guide.md](./references/recovery-guide.md) — recovery 闭环、`RECOVERY_EVENT_ID`、`execute/finalize` 规范 - [scripts/](./scripts/) — 各产品批量/复合操作脚本(AI表格批量导入导出、AI应用创建轮询、日历、机器人消息、通讯录、考勤、日志、待办、文档创建并写入、钉盘目录树等) -- [references/products/aitable/](./references/products/aitable/) — AI表格细分章节(单元格值/字段属性/公式/筛选排序/导入导出/仪表盘/记录增删改查/错误恢复) +- [references/products/aitable/](./references/products/aitable/) — AI表格细分章节(单元格值/字段属性/公式/筛选排序/导入导出/仪表盘/记录增删改查/错误恢复/最佳实践) - [references/products/aitable-record-ops.md](./references/products/aitable-record-ops.md) — AI表格记录操作专项说明 - [references/capability-limits.md](./references/capability-limits.md) — 已知能力限制(doc/aitable/chat/minutes,遇到时直接告知用户不支持) +- [references/best_practices/](./references/best_practices/) — 全场景 recipe 行动指南(11 个编号场景 + lite 速查) + - [01-messaging.md](./references/best_practices/01-messaging.md) — 消息沟通 + - [02-task.md](./references/best_practices/02-task.md) — 任务管理(todo) + - [03-meeting.md](./references/best_practices/03-meeting.md) — 会议日程(日历 + 会议室) + - [04-document.md](./references/best_practices/04-document.md) — 文档场景(write-doc / search-docs / migrate-doc / update-doc-section / doc-to-message / delete-old-doc / export-doc-as-docx / grant-doc-access / insert-image-to-doc / template-based-generation 等) + - [05-reporting.md](./references/best_practices/05-reporting.md) — 工作汇报(钉钉日志 / 文档周报选路) + - [06-data-analytics.md](./references/best_practices/06-data-analytics.md) — AI表格数据分析(read-aitable / generate-data-report / create-aitable-record / update-aitable-record / export-aitable-to-xlsx / primary-doc-from-record 等) + - [07-minutes.md](./references/best_practices/07-minutes.md) — 听记与会后 + - [08-directory.md](./references/best_practices/08-directory.md) — 通讯录(组织架构) + - [09-mail.md](./references/best_practices/09-mail.md) — 邮件 + - [10-minutes-speaker-match.md](./references/best_practices/10-minutes-speaker-match.md) — 听记发言人智能匹配 + - [11-minutes-speaker-correct.md](./references/best_practices/11-minutes-speaker-correct.md) — 听记发言人识别与标注 + - [lite-recipes.md](./references/best_practices/lite-recipes.md) — Lite Recipe 速查(核心流程判定为 lite 后直接执行) + - [_common/conventions.md](./references/best_practices/_common/conventions.md) — 批量查询、多源并行采集、字段术语等通用规范 + - [_common/recipe-conventions.md](./references/best_practices/_common/recipe-conventions.md) — recipe 元规范 diff --git a/skills/mono/references/best_practices/01-messaging.md b/skills/mono/references/best_practices/01-messaging.md index 966a79ee..fb95e817 100644 --- a/skills/mono/references/best_practices/01-messaging.md +++ b/skills/mono/references/best_practices/01-messaging.md @@ -5,7 +5,7 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| | query-group-chat | **优先**:`chat_export_messages.py`(开源版未引入;可手动用 `dws chat message list` 翻页后写入文件)(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--forward=false` 拉给定时间**之前**的消息
5. 合并全部消息后总结 | -| query-private-chat | **优先**:`chat_history_with_user.py`(开源版未引入;可手动用 `aisearch person` + `dws chat message list-direct` 组合)(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list-direct --user --time ""` → 取消息列表(单聊专用;旧版 `list --user` 已停用)
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | +| query-private-chat | **优先**:`chat_history_with_user.py`(开源版未引入;可手动用 `dws chat search` + `dws chat message list` 组合)(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 | | escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 [ding.md](../products/ding.md))
2. `chat message send --group --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](../products/chat.md))
3. `todo task create --title "<标题>" --executors --priority 40` 建紧急待办
前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` | | send-by-bot | **多群批量优先**:`bot_broadcast.py`(开源版未引入;可手动用 `dws chat message send-by-bot` 多次调用)
单群:1. `chat bot search` → 取 `robotCode`
2. `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send-by-bot --robot-code --group --title "<标题>" --text "<内容>"` | | forward-message | 1. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message list --group --time "<起始时间>"` 拉源消息
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取目标 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | diff --git a/skills/mono/references/best_practices/04-document.md b/skills/mono/references/best_practices/04-document.md index f2cf2686..c884341e 100644 --- a/skills/mono/references/best_practices/04-document.md +++ b/skills/mono/references/best_practices/04-document.md @@ -1,18 +1,81 @@ # 文档知识 -> lite recipe 见 [SKILL.md 速查表](../../../../SKILL.md#常用-recipe-速查表lite-recipe--可直接执行无需读行动指南文件)。 +> 通用规范见 [_common/conventions.md](_common/conventions.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| write-doc | 0. 阅读 [doc-create-workflow.md](../products/doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)
1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)
2. `doc create --content-file /tmp/.json --content-format jsonml`(或 `.md` + `--content-format markdown`)
3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片
4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node `,校验关键标题/段落/表格是否完整写入 | +| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. **先把内容写入临时文件**(Linux/Mac `/tmp/.md`,Windows `%TEMP%\.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串
3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file [--folder ] [--workspace ]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)
4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node --content-file --mode append`
5. **回读校验**(必须):所有写入完成后,执行 `doc read --node ` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)
备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` | | search-docs-and-share | 1. `doc search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) | | create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `doc create --name "<文档名>" --workspace ` → 取 `nodeId`
3. `doc list --workspace ` 确认 | | migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `.md`
2. `doc create --name "<文档名>" --folder --content-file .md` → 取新 `nodeId`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId;正文 <200KB 单步到位)
2a. 若正文 >200KB:**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" --folder ` → `nodeId` → 按段落切片 → 每片 `doc update --node --content-file --mode append`
3. **回读校验**:`doc read --node ` 校验内容完整性 | -| update-doc-section | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. **形态选择(按 [doc-update-workflow.md §1.3](../products/doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe
3. `doc read --node ` 定位目标章节
4. `doc update --node --content "<替换内容>" --mode overwrite`
5. **回读校验**:`doc read --node ` 确认 overwrite 未被降级为 append、内容完整无截断
**overwrite 须用户确认**;完整改写流程见 [doc-update-workflow.md](../products/doc/style/doc-update-workflow.md) | -| rewrite-doc | 1. 阅读并执行 [doc-update-workflow.md](../products/doc/style/doc-update-workflow.md):先看 §1.3 编辑形态优先级(**JSONML 首选**),再按 §3 速查表选路径,跳 §4 对应小节执行
2. 单块改写 / 含富结构 → §4.4 路径 B;多处保真改写或改 root → §4.4 路径 A;纯文本骨架重写 → §4.5 markdown
3. 整篇 overwrite 前必须按 workflow §4.5 向用户提示风险并等待确认
4. **回读校验(必须)**:按 workflow §6 的校验要点逐项核查;@人、附件、图片等保真要素必须原样保留
**适用场景**:用户提供已有 nodeId/链接,需要改写、润色、章节补充、段落形态转换、整篇重写 | +| update-doc-section | **优先块级精修(保形)**:1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc block list --node --format json` 定位目标块取 `blockId`
3. `doc block update --node --block-id --text "<替换内容>" --format json` 局部修改(不破坏富格式)
4. **回读校验**:`doc read --node ` 确认目标章节已更新
**兜底**:仅当无法定位块且用户确认整篇替换时,先 `doc update --node --content-file .md --mode overwrite --dry-run` 预览,再加 `--yes` 执行;overwrite 会丢富格式(颜色/字号/合并单元格等),详见下方 template-based-generation §"为什么避免 overwrite" | +| template-based-generation | **触发**:用户给已有 alidocs URL + "参照/按模板/复刻/同样模板生成 X 月份的" → **保形复制,禁止重写**。固定走下方 [template-based-generation](#template-based-generation) 章节:`doc copy` 保形复制 → `doc rename` → `doc block list/update` 局部改副本。禁止 `doc read + doc create` 重写链。 | | doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | -| lossless-doc-edit | 1. `doc read --node --content-format jsonml --output /tmp/doc.json` → 获取完整 JSONML 结构(输出含 `revision`,并发敏感时记下来;默认改写不需要)
2. 解析 JSON 文件,修改 `jsonml` 数组中的目标节点(节点结构参见 [doc-jsonml-schema.md](../products/doc/format/doc-jsonml-schema.md))
3. 将修改后的内容写回临时文件 `/tmp/doc_modified.json`,格式为 `{"jsonml": [...]}`
4. `doc update --node --content-file /tmp/doc_modified.json --content-format jsonml --mode overwrite`(默认不做并发检查;担心多 agent 同时改时加 `--revision <第 1 步拿到的 N>` 触发并发检查,版本不一致返回 `VersionConflict` 时回到第 1 步重读重写)
5. **回读校验**:`doc read --node --content-format jsonml` 确认写入成功
**适用场景**:保留样式、精准插入特定节点类型、改属性不动文本;普通文本编辑仍优先用 markdown 模式。完整 JSONML 改写流程见 [doc-update-workflow.md §4.4](../products/doc/style/doc-update-workflow.md) | -| jsonml-node-edit | 1. `doc block list --node --content-format jsonml` → 获取 JSONML 节点列表(含 uuid)
2. 根据 uuid 定位目标节点
3. `doc block list --node --content-format jsonml --block-id ` → 读取完整子树
4. 修改 JSONML 节点内容(节点结构参见 [doc-jsonml-schema.md](../products/doc/format/doc-jsonml-schema.md),可复制范例见 [doc-jsonml-cookbook.md](../products/doc/format/doc-jsonml-cookbook.md))
5. `doc block update --node --block-id --content-format jsonml --element '<修改后的 JSONML>'` → 写回
**适用场景**:只改一个 block 的结构/样式,无需全文回写;写入端默认 normalize(自动补 uuid、裸字符串包成 canonical 文本),可用 `--no-fix-jsonml` 关闭全部修复;`--fix-jsonml` 额外启用 JSON 语法修复(推荐 agent 调用) | +| delete-old-doc | 1. `doc search --query "<关键词>"` 或 `doc list --folder ` 取 `nodeId`
2. **必须先向用户展示要删除的文档标题/路径**,等用户确认
3. `doc delete --node --yes`(注意:是 `doc delete`,不是 `doc block delete`;前者删整篇,后者删块)
4. 验证:`doc info --node ` 应返回 not-found | +| export-doc-as-docx | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc export --node --output /tmp/.docx --timeout-sec 600`(CLI 内置渐进式退避轮询,**不要自己拼 GET downloadUrl**)
3. 落盘失败兜底:`doc export get --job-id --output /tmp/.docx` 续等
4. (可选)`doc-to-message` 把 docx 路径作为附件发给用户 | +| grant-doc-access | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `contact user search --query "<姓名>"` → 取 `userId`(**注意**: doc permission 用 `userId`,不是 `openDingTalkId`)
3. **节点级**授权:`doc permission add --node --user --role EDITOR`
  role 取值: MANAGER / EDITOR / DOWNLOADER / READER(**不要传 OWNER**)
  单次最多 30 个 userId
4. 查权限:`doc permission list --node ` 确认
5. **不要混淆**: 给"知识库"加成员用 `wiki member add --workspace`(容器级);给"单个文档"加权限用 `doc permission add --node`(节点级)| +| insert-image-to-doc | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc media insert --node --file ./image.png`(3 步一体化:获取凭证 → PUT OSS → 插入块;图片 ≤20MB 自动作为内联图片,其他作为附件块)
3. (可选)`doc read --node ` 回读确认
**禁止**: 自己拿 uploadUrl 写 PUT —— 90%+ 会因 Content-Type 未清空触发 SignatureDoesNotMatch | +| download-doc-attachment | 1. `doc block list --node ` → 找到 `blockType=attachment` 的块取 `resourceId`
2. `doc media download --node --resource-id ` → 返回 OSS 临时 URL(**注意 expirationSeconds**)
3. 调用方自行 GET 该 URL 落盘 | + +--- + +## template-based-generation + +### 触发条件 + +用户给已有 alidocs URL,并要求: +- "参照这个生成同样的" +- "按模板生成" +- "复刻这个" +- "同样的模板,X 月份的" +- "仿照这个文档" + +### 核心原则 + +**保形优于重写**。adoc 富格式(行高、单元格背景色、字号、渐变文字色、表格列宽、合并单元格)在 markdown 层无表达,`doc read → create_file → doc create` 会做两次 lossy projection(先 adoc→markdown,再 markdown→adoc),必然丢失这些属性。 + +正确做法:**在 adoc 层保形复制(`doc copy`)**,再局部改写需要替换的块(`doc block update`),保留所有富格式。 + +### 固定路线 + +```bash +# 1. probe 模板节点,确认是 ALIDOC(adoc 富文档) +dws doc info --node <模板NODE或URL> --format json + +# 2. 在 adoc 层保形复制(不经过 markdown 投影) +dws doc copy --node <模板NODE或URL> --folder <目标文件夹NODE或URL> --format json +# → 取返回的副本 nodeId + +# 3. 重命名副本 +dws doc rename --node <副本nodeId> --name "<新文档名>" --format json + +# 4. 列出所有块定位要改的内容 +dws doc block list --node <副本nodeId> --format json +# → 记录 blockId 和现有文本 + +# 5. 逐块局部改写(只改月份/日期/姓名/指标等局部变量,不动结构) +dws doc block update --node <副本nodeId> --block-id --text "<新内容>" --format json + +# 6. 回读确认 +dws doc read --node <副本nodeId> +``` + +### 为什么避免 overwrite + +| 场景 | overwrite 后果 | +|------|---------------| +| 模板带表格背景色 | 全部变白 | +| 标题字号 24pt 加粗渐变 | 退化为普通 H1 | +| 合并单元格的表格 | 拆成独立单元格 | +| 嵌入图片 | 链接保留但样式重置 | + +**用 copy+block update 全部可保留**。 + +### 禁止反模式 + +- ❌ doc read + doc create 重写模板文档(两次 lossy projection) +- ❌ 先 `dws doc copy`,再删除副本退回 create 重写链路 +- ❌ 用 `dws doc update --mode overwrite --yes` 把含富格式的 adoc 整篇覆盖成 markdown --- @@ -43,15 +106,32 @@ ## doc update 回读校验规范 -**所有 `doc update`(含 overwrite 和 append)执行后都必须回读校验**——返回 `success=true` 不等于内容真的写入完整。 +### 为什么需要回读校验 -完整规范(静默失败场景、校验流程、异常处理路径、"先清空再重建"命令样板)见 [doc-update-workflow.md §6「回读验收」](../products/doc/style/doc-update-workflow.md)。 +`doc update` 在以下场景可能产生**静默失败**,返回 `success=true` 但实际写入不完整: -最小流程: +- **overwrite 降级为 append**:大文档 overwrite 模式被后端静默降级,导致旧内容未清除、新内容追加在末尾 +- **分块 append 内容截断**:超长文档分片写入时,部分片段丢失或顺序错乱 +- **编码/通道问题**:PowerShell 等特殊终端下 UTF-8 内容传输乱码 + +### 校验流程 + +在 **每次 `doc update` 完成后**(无论 overwrite 还是 append 模式),必须执行以下步骤: + +1. **回读文档**:`doc read --node ` +2. **关键内容校验**:检查回读结果是否包含预期的关键标题、段落首句、表格标记等 +3. **异常处理**: + - 若内容缺失或截断 → 向用户报告具体差异,建议重试或手动修复 + - 若 overwrite 后仍残留旧内容 → 提示用户 overwrite 可能被降级,建议先清空再 append + - **禁止**在未回读的情况下直接向用户报告"已完成" + +### 示例 -```bash -dws doc update --node --content-file /tmp/new-content.md --mode overwrite -dws doc read --node # 校验关键标题、段落首句、表格、@人、附件 ``` +# 更新文档 +doc update --node abc123 --content-file /tmp/new-content.md --mode overwrite -**禁止**在未回读的情况下向用户报告「已完成」。 +# 回读校验 +doc read --node abc123 +# → 检查输出是否包含预期内容的关键标题和段落 +``` diff --git a/skills/mono/references/best_practices/06-data-analytics.md b/skills/mono/references/best_practices/06-data-analytics.md index 213cc7ae..6b089723 100644 --- a/skills/mono/references/best_practices/06-data-analytics.md +++ b/skills/mono/references/best_practices/06-data-analytics.md @@ -9,5 +9,5 @@ | create-aitable-record | **写入路径分流**(关键决策):
  • **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)
  • **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id --file ./x.xlsx` + `dws aitable import data --import-id `(CLI 已内置 OSS PUT 清空 Content-Type + 同步轮询,**禁止自己写 PUT**)
  • **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"":"值"}}]'` | | update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. **取目标 record**:
  • 已知少量 recordId → `aitable record query --record-ids `
  • 按条件批量改 → `aitable record query --base-id --table-id --filters '' --all`(**用 --all 防止漏改**)
3. **先展示让用户确认要改的 record 列表**
4. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'`(单次 ≤30 条) | | search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`
2. 用户选定
3. `aitable base create --name "<表格名>" --template-id ` → 取 `baseId` | -| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --base-id --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | +| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | | primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:
1. `aitable record query` 取 `recordId`
2. `aitable base get-primary-doc-id --base-id --table-id --record-id ` → 取主键文档 `dentryUuid`
3. 凭 `dentryUuid` 调 `doc read --node ` 拿文档内容 | diff --git a/skills/mono/references/best_practices/08-directory.md b/skills/mono/references/best_practices/08-directory.md index 16ab9fc0..95bfc5d9 100644 --- a/skills/mono/references/best_practices/08-directory.md +++ b/skills/mono/references/best_practices/08-directory.md @@ -5,7 +5,7 @@ ## 专用规则(#8 非 lite 步骤必守) -- **角色/职责类查人优先 aisearch**:用户说"角色为XX的员工/XX角色的人员""所有主管/财务/HR/总经理""谁负责 XX"等角色/职责类查询时,**优先** `aisearch person --keyword "<角色或职责>" --dimension duty`(`contact label` 角色查询命令已下线,不再可用)。 +- **角色类查人优先 label**:用户说"角色为XX的员工/XX角色的员工/XX角色的人员""所有主管/主管理员/财务/HR/总经理"等角色类型人员时,**优先** `contact label list` 获取全部角色 → 匹配目标角色 → `contact label list-members --id `;若用户明确指定了角色名称(如"角色为总经理"),则先用 `contact label get --names ` 精确匹配,**若精确匹配无结果,降级 `label list` 模糊匹配**(如用户说"管理员"可匹配到"主管理员"和"子管理员")。 - **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --depts`。 - **详情链路**:用户要子部门、职位、联系方式、汇报关系等,在 `user search` 之后**必须**再 `contact user get --ids `;禁止仅用 search 的浅表字段交差。 - **`user get` 后部门仍空**:不得过早结束或只建议用户去 App;须在 CLI 能力内尝试 **用户点名的部门** `dept search` + `dept list-members` 等与 `userId` 交叉核对,再结构化汇总「返回中有哪些字段 / 哪些为空及可能原因」。 @@ -20,7 +20,7 @@ ## 与其他场景消歧 -- **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `aisearch person --dimension duty`(按职责维度找人);`contact label` 角色查询命令已下线。 +- **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `contact label list` + `label list-members`;label 精确命中角色维度,返回完整名单;aisearch 是语义模糊搜索不保证完整性。 - **搜人/找人/找同事/查工号/查手机号** → 首选 **`aisearch person`**(AI 语义搜索,支持姓名/部门/职责/上下级/手机号/工号维度),见 `aisearch`(开源版未引入,悟空内部产品)。 - **需要 userId 做后续操作 / 按手机号查 / 按 userId 查详情** → `contact`(精确查询)。 - **纯查部门与子部门成员 / 验证归属 / 组织关系** → `contact`。 @@ -31,7 +31,7 @@ | Recipe | 步骤 | |--------|------| -| `lookup-role-members` | 1. `aisearch person --keyword "<角色或职责>" --dimension duty` → 按职责维度找人(`contact label` 已下线) | +| `lookup-label-members` | 1. `contact label list` → 浏览全部角色,匹配目标角色的 labelId
2. `contact label list-members --id ` → 该角色下的成员列表 | | `search-user-by-mobile` | 1. `contact user search-mobile --mobile "<手机号>"` → 按需 `contact user get --ids ` | | `lookup-dept-id` | 1. `contact dept search --query "<部门关键词>"` → 回显 `deptId`(多命中须消歧) | | `list-subdepts` | 1. 已有父 `deptId` → `contact dept list-children --dept <父deptId>` 直接取直属子部门列表
2. 只有部门名 → 先 `lookup-dept-id` 取 `deptId`,再 `list-children` | diff --git a/skills/mono/references/best_practices/_common/conventions.md b/skills/mono/references/best_practices/_common/conventions.md index 8125a295..502df690 100644 --- a/skills/mono/references/best_practices/_common/conventions.md +++ b/skills/mono/references/best_practices/_common/conventions.md @@ -1,6 +1,7 @@ -# 业务域通用规范 +# 通用规范 -> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 [SKILL.md](../../../SKILL.md) 中定义,此处不重复。 +> full recipe 执行时的共享规范。安全门控、危险操作确认、`--format json` 等已在根 [SKILL.md](../../../SKILL.md) 中定义,此处不重复。 +> Recipe 元规范(YAML frontmatter、命名、三层架构)见 [recipe-conventions.md](recipe-conventions.md)。 ## 批量查询规范 @@ -9,7 +10,8 @@ | 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** | | 2 | **翻页**:分页接口须拉全直至无更多 | | 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 | -| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | +| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 | +| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` | ## 多源并行采集(公共模式) @@ -18,7 +20,7 @@ - 同条 Shell:`&` 并行 + `wait`;分页须采全。 - 只保留与主题相关的数据,无关丢弃。 - 有批量详情接口优先;否则并行拉详情(见上表 #1)。 -- 具体采哪些产品列表由对应 **行动指南 recipe** 与 [SKILL 产品参考](../../../SKILL.md) 决定;不要引入本文档未覆盖的产品路线。 +- 具体采哪些产品列表由对应 **行动指南 recipe** 与 [SKILL 产品参考](../../../SKILL.md) 决定。 ## 字段术语与 ID 传递 @@ -26,10 +28,18 @@ | 字段 | 来源 | 传递给 | |------|------|--------| +| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` | +| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` | +| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id | | `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` | | `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` | +| `eventId` | `calendar event list` | `calendar event get/update --id` | +| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` | +| `openConversationId` | `chat search` | `chat message list/send --group` | +| `todoTaskId` | `todo task list` | `todo task update/done --task-id` | +| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` | | `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` | | `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --file-id`、`drive list/mkdir/upload --parent-id` | -| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` | +| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` | -**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 +**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。 diff --git a/skills/mono/references/best_practices/lite-recipes.md b/skills/mono/references/best_practices/lite-recipes.md index 3ab28f22..49caffb0 100644 --- a/skills/mono/references/best_practices/lite-recipes.md +++ b/skills/mono/references/best_practices/lite-recipes.md @@ -146,6 +146,17 @@ dws minutes list shared --query "ROI" --format json - **热词管理**:`minutes hot-word add --words "词1,词2"` / `minutes hot-word list` - **思维导图**:`minutes mind-graph create --id ` → `mind-graph status --id ` 轮询至完成 +### minutes-tag(标签/分组查询) + +- 查询标签列表:`minutes tag list` → 返回用户在听记页面创建的所有标签/分组(含 tagId 和名称) +- 按标签查听记:`minutes tag query --tag-id [--limit 20] [--cursor ]` + - tagId 来自 `tag list` 返回值,不可编造 + - 支持分页,`--cursor` 传入上一次返回的 nextToken + +**典型链路**:用户说"帮我看看'周会'标签下的听记" → +1. `dws minutes tag list --format json` → 按名称匹配找到 tagId +2. `dws minutes tag query --tag-id --format json` + ### minutes-permission(权限管理) - 添加成员:`minutes permission add --ids --member-uids --policy 4` @@ -176,6 +187,17 @@ dws minutes upload cancel --session-id --format json | 案例 6 | 通讯录+部门+转写三路印证 | Step 3 画像 + Step 4 `contact user search` 并发 → 置信度 ≥70% → 确认 → 替换 | | 案例 7 | grep 花名误判未参会 | **禁止**在转写文本里 grep 人名判参会;**必须**调 `contact user search` | | 案例 8 | 听记类 query 不走 dws | **禁止**用 session_search/browser_use/activity:search 替代 dws;模糊请求先 `list mine` | +| 案例 9 | 按标签筛选听记 | `tag list` → 按名称匹配 tagId → `tag query --tag-id `;**禁止**编造 tagId | + +### 听记取数深度约束(0609 点踩 case 提炼) + +> 详细说明见 [minutes.md](../products/minutes.md)。 + +- **转写原文硬约束**:用户诉求含「聚焦原话/逐字/沟通细节/具体讨论了什么」等词时,**必须先调 `get transcription` 翻页拉全**,禁止仅凭 summary 出稿 +- **数据源下钻**:听记维度**必须 `get summary`(或 `get transcription`)读正文**,严禁只取标题列表;scope 用 `all`;空时换窗重试或标注 +- **听记链接解析**:聊天消息中遇到听记链接(`flash_minutes_detail`/`SHANJI`)→ 解析 `minutesId` → 调 `minutes get summary/transcription`,禁止把链接降级为关键词 +- **忠实性约束**:源数据无某要素(行动项/责任人/数字)时禁止生成;统计字段基于实际取数计数,不得编造 +- **多源全覆盖**:用户枚举多数据源时每个来源都必须调对应工具;瞬时错误重试;如实声明缺失来源,禁编无来源数字 ### 间接意图识别铁律 diff --git a/skills/mono/references/global-reference.md b/skills/mono/references/global-reference.md index 8089ba7f..edfc7374 100644 --- a/skills/mono/references/global-reference.md +++ b/skills/mono/references/global-reference.md @@ -9,10 +9,6 @@ dws auth login # 查看状态 dws auth status -# 沙箱间迁移登录态(Linux,含 refresh token) -dws auth export -o dws-auth.tar.gz -dws auth import -i dws-auth.tar.gz - # 退出 dws auth logout @@ -101,7 +97,7 @@ dws recovery finalize --event-id --outcome recovered|failed|handoff - | 变量 | 说明 | |------|------| | `DWS_CONFIG_DIR` | 覆盖默认配置目录 | -| `DWS_SERVERS_URL` | 自定义服务发现端点 | +| `DWS__MCP_URL` | 本地开发时覆盖指定产品 MCP endpoint | | `DWS_CLIENT_ID` | 覆盖 OAuth Client ID (DingTalk AppKey) | | `DWS_CLIENT_SECRET` | 覆盖 OAuth Client Secret (DingTalk AppSecret) | diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md index a572736b..532434e0 100644 --- a/skills/mono/references/intent-guide.md +++ b/skills/mono/references/intent-guide.md @@ -6,29 +6,121 @@ | 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | |-----------|----------|--------|--------|------| -| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | — | 搜索开放平台技术文档,不是钉钉内部内容 | -| "查询开放平台 API 错误码/字段说明" | 搜索开放平台接口文档 | `devdoc` | — | 查 API 用法和错误码 | -| "查工作台应用 appXYZ 的详情" | 查询钉钉工作台应用 | `workbench app` | — | `appXYZ/app001` 是工作台 app id | -| "帮我创建一个应用" | 应用类型不明确 | 先追问 | — | 缺少工作台/AI/MCP 等上下文,不能默认 | -| "做一个 AI 应用" | AI 应用/智能体需求不明确 | 先追问或转对应能力 | — | 需求不明确时不默认走任何创建命令 | -| "创建 MCP 服务并配置 HSF tool" | MCP 平台工具配置 | OpenDev MCP 平台流程 | — | 这是 connector/tool 配置 | -| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `todo` | 涉及结构化数据/行列操作,不是个人待办 | -| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `aitable` | 个人待办提醒,非数据表 | -| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室)| -| "帮我看看收到的日报" | 日志收件箱 | `report` | `todo` | 钉钉日志系统(日报/周报),不是待办 | -| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | -| "帮我提交请假审批" | 发起审批 | `oa` | — | 审批流程,不是待办或日志 | +| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 | +| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 | +| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | +| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `doc copy + doc rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](./best_practices/04-document.md#template-based-generation) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | +| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 | +| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 | +| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws doc info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 | +| "读一下这个 xlsx 的数据" / xlsx 节点链接 | 下载本地表格文件 | `dws drive download --node` | `sheet range read` | xlsx / xls / xlsm / csv 是上传的本地文件(`contentType=DOCUMENT`),sheet 命令只支持在线表格,必须下载后本地解析 | +| "把这个在线表格导出为 xlsx 文件" | 在线表格格式转换 | `dws sheet export` | `dws drive download` | `export` 是 axls → xlsx 的导出转换;`download` 只能下载已有的 xlsx 节点 | +| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` / `aitable` | 个人待办提醒,非文档内容或数据表 | +| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 | +| "帮我把这个文件传到网盘" | 钉盘上传 | `drive upload` | — | 文件上传是存储层操作,归 drive | +| "上传文件到钉盘/我的文件" | 钉盘上传 | `drive upload` | — | 提到"钉盘/网盘/我的文件"→ drive | +| "上传文件"(未指定目标) | 默认钉盘 | `drive upload` | — | 未明确目标时默认上传到钉盘 | +| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list | +| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated | +| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search | +| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | +| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | +| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议(conference)当前开源 CLI 不支持 | +| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder | | "帮我建一个项目群" | 创建群聊 | `chat group create` | — | 群聊管理,不是日历日程 | | "把张三拉进群" | 添加群成员 | `chat group members add` | — | 先查 userId,再添加 | -| "让机器人在群里发个通知" | 机器人群发 | `chat message send-by-bot` | `chat message send-by-webhook` | 企业内部机器人发消息,需 robotCode | -| "通过 Webhook 发告警到群里" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 自定义机器人 Webhook,需 token | +| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 | +| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 | | "给张三发一条机器人单聊消息" | 机器人单聊 | `chat message send-by-bot --users` | — | 机器人批量单聊,先查 userId | +| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook | +| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | +| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 | +| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 | +| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) | +| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user | +| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 | +| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list | +| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) | +| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 | +| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId | +| "撤回我发的消息/撤回消息" | 撤回个人消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息,recall-by-bot 撤回机器人消息 | +| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 | +| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 | +| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 | +| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 | +| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID | +| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 | +| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id | +| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title | +| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 | +| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 | +| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId | +| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 | +| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 | +| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 | +| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 | +| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 | +| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 | +| "设管理员/取消管理员" | 设置群管理员 | `chat group set-admin` | `chat group members add` | set-admin 设置或取消管理员角色;加人入群用 members add | +| "退群/退出群聊/离开群" | 退出群聊 | `chat group quit` | `chat group members remove` | quit 是当前用户自己退群;members remove 是管理员踢别人 | +| "改群头像/更新群头像" | 更新群头像 | `chat group update-icon` | `chat group rename` | update-icon 改群头像;rename 改群名称 | +| "改群设置/群设置开关/群权限/入群许可/禁止私聊" | 更新群设置 | `chat group update-settings` | `chat group set-admin` | update-settings 改群功能开关;set-admin 是管理员角色操作 | +| "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `chat group update-nick` | `chat group rename` | update-nick 改自己的群昵称;rename 改群名称 | +| "群备注/给群加备注/修改群备注" | 设置群备注 | `chat group update-alias` | `chat group rename` | update-alias 设置仅自己可见的备注;rename 改群名称全员可见 | +| "隐藏会话/隐藏群聊/隐藏对话" | 隐藏会话 | `chat hide` | `chat mute` | hide 隐藏会话不显示;mute 是免打扰但仍显示 | +| "关闭@所有人通知/屏蔽@all/不接收@所有人" | 关闭 @所有人提醒 | `chat mute-at-all` | `chat mute` | mute-at-all 仅屏蔽 @所有人;mute 是整个会话免打扰 | +| "关闭红包通知/屏蔽红包/不接收红包提醒" | 关闭红包提醒 | `chat mute-red-envelope` | `chat mute` | mute-red-envelope 仅屏蔽红包;mute 是整个会话免打扰 | +| "解散群/解散群聊" | 解散群聊 | `chat group dismiss` | `chat group quit` | dismiss 是群主解散整个群(不可逆);quit 是当前用户自己退群 | +| "新成员看历史/历史消息可见范围" | 设置新成员可见历史消息 | `chat group set-history` | `chat group update-settings` | set-history 控制新成员入群后可见历史消息范围;update-settings 是其他群功能开关 | +| "群里有哪些机器人/查看群机器人/列出群机器人" | 查看群内机器人列表 | `chat group bots` | `chat group members` | bots 只列机器人;members 列普通群成员 | +| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 | +| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 | +| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) | +| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) | +| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 | +| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 | +| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 | +| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 | +| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 | +| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 | +| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 | +| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 | +| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 | +| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 | +| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 | +| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 | +| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 | +| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 | +| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 | +| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 | +| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 | +| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 | +| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 | +| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 | +| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 | +| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat | +| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 | +| "以我的名义发DING/个人发DING/用户身份DING" | 以用户身份发 DING | `ding message send-personal` | `ding message send` | send-personal 以用户身份发送,无需 robot-code;send 以机器人身份发送 | +| "以我的名义撤回DING/个人撤回DING" | 以用户身份撤回 DING | `ding message recall-personal` | `ding message recall` | recall-personal 以用户身份撤回;recall 以机器人身份撤回 | +| "消息转DING/把这条消息DING给某人/转发为DING" | 消息转 DING | `ding message send-by-message` | `ding message send-personal` | send-by-message 是将已有消息转为 DING,需指定原消息;send-personal 是直接发新 DING | +| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | [05-reporting.md `generate-topic-report`](./best_practices/05-reporting.md) | [07-minutes.md `meeting-followup`](./best_practices/07-minutes.md) | meeting-followup 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 | +| "整理一下XX项目的所有讨论" | 跨源主题归档 | [05-reporting.md `generate-topic-report`](./best_practices/05-reporting.md) | [04-document.md `write-doc`](./best_practices/04-document.md) | write-doc 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 | +| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | `contact`(行动指南 [08-directory.md](./best_practices/08-directory.md)) | 汇报 / 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact | +| "研发部的详细信息/部门信息" | 查部门详情 | `contact dept get-info` | `contact dept list-members` | 查部门属性(ID、名称、人数)用 get-info;查成员列表用 list-members | +| "研发部有多少人" | 查部门人数 | `contact dept get-info` | `contact dept list-members` | 问人数用 get-info(返回 memberCount);问有哪些人用 list-members | +| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact | +| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch | +| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber | +| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone | +| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | +| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注"谁对什么做过什么",走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | +| "查/提交 请假/加班/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\|overtime\|repair-check`) | `oa approval list-pending` / `oa approval records` | 请假/加班/补卡/出差/外出 属于考勤业务审批单,按业务类型查询;`oa approval` 是通用 OA 审批中心,覆盖范围不同 | --- ## 典型场景详解 -### 1. aitable vs todo — 表格数据 vs 待办任务 +### 1. aitable vs doc vs sheet — 数据表格 vs 文档内容 vs 电子表格 **用 `aitable` 的场景**: - "创建一个表格记录团队成员信息" — 结构化数据,有行列 @@ -37,106 +129,344 @@ - "用项目管理模板建一个表" — 模板创建 - 用户提到"多维表"、"Base"、"数据表"、"记录" -**用 `todo` 的场景**: -- "帮我记一下这周要做的事" — 个人任务管理 -- "创建一个待办提醒" — 任务提醒 +**用 `doc` 的场景**: +- "帮我写个会议纪要" — 富文本内容创作 +- "看一下这个文档链接的内容" — 阅读文档 +- 用户提到"文档"、"知识库"、"写文档" -**判断关键**:有没有行列/字段/记录概念?有→ `aitable`;个人任务清单 → `todo` +**用 `sheet` 的场景**: +- "创建一个电子表格" — 创建 Excel 式在线表格 +- "帮我读一下这个表格 A1 到 D10 的数据" — 按单元格区域读取 +- "在 B2 写入一个 SUM 公式" — 写入公式/值到单元格 +- "帮我看看这个表格有哪些工作表" — 工作表管理 +- 用户提到"电子表格"、"Excel"、"工作表"、"Sheet"、"单元格"、"公式" + +**三者判断关键**: +- 有字段定义/记录增删改查/数据筛选 → `aitable` +- 纯文本/Markdown/富文本编辑 → `doc` +- 单元格区域读写/公式/多工作表 → `sheet` +- 个人任务清单(无行列/字段概念)→ `todo` + +**易误判场景**: +- "在知识库中新建一个表格" — 指在钉钉文档空间创建表格类型节点 → `wiki node create --type axls`(不是 `aitable`) +- "帮我建个表记录项目进度" — 指创建结构化数据表 → `aitable` --- -### 2. devdoc vs workbench — 应用泛词消歧 +### 1.1 xlsx vs axls — 本地表格文件 vs 在线电子表格 -`应用` 是泛词,不能直接等价为某个产品。 +alidocs 链接表面长得一样(`https://alidocs.dingtalk.com/i/nodes/{id}`),但节点类型完全不同。sheet 产品线只服务 axls(在线电子表格),xlsx / xls / xlsm / csv 等本地表格文件必须走 `dws drive download`,严禁错路由。 -**用 `devdoc` 的场景**: -- "开放平台 API 怎么调用" — 接口文档 -- "错误码/字段说明/接口参数" — 文档检索 +用 `sheet` 的场景(axls,钉钉在线电子表格): +- `dws doc info --node ` 返回 `contentType=ALIDOC` + `extension=axls` +- 用户在钉钉文档空间直接"新建电子表格"得到的节点 +- 所有 sheet 子命令(`list` / `range read` / `range write` / `export` 等)仅服务这类节点 -**用 `workbench app` 的场景**: -- "工作台应用/钉钉工作台上的应用" -- "应用 app001/appXYZ 的详情" +用 `dws drive download` 的场景(xlsx / xls / xlsm / csv 本地表格文件): +- `dws doc info --node ` 返回 `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv` +- 用户把本地 Excel 文件上传到文档空间得到的节点,本质是"文件 + 预览",非在线表格 +- sheet 命令直接调用会报错,必须先 `dws drive download --node ` 下载到本地再解析处理 -**先追问的场景**: -- "创建应用/做个应用/查应用" 但没有工作台、文档、AI、MCP 等上下文 -- "AI 应用/智能体" 但没有说明是哪种产品能力 -- "MCP 服务/connector/HSF tool" 属于平台配置流程,不要转成任何创建命令 +判断关键: +- 未知 alidocs URL → 必须先 `dws doc info --node --format json` 探测 `contentType` 与 `extension` +- `contentType=ALIDOC` + `extension=axls` → `sheet` +- `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv` → `dws drive download` +- 用户说"把在线表格导出为 xlsx 文件" → `dws sheet export`(axls → xlsx 的格式转换,不是读取 xlsx) -**判断关键**:是否在查 API 文档?是 → `devdoc`。是否在查钉钉工作台入口应用?是 → `workbench app`。仍不明确 → 追问,不调用写命令。 +易误判场景: +- 用户粘贴一个 alidocs 链接说"读一下这个表格" — 不能直接调 `sheet range read`,必须先 probe 再按 `extension` 路由 +- 用户说"读一下这个 xlsx 文件里的数据" — 走 `dws drive download` 下载后本地解析,不要走 `sheet` +- 用户说"把这个在线表格导出为 xlsx" — 走 `dws sheet export`,不要走 `dws drive download`(后者只能下载已有的 xlsx 节点,无法从 axls 生成) + +详见 [url-patterns.md](./url-patterns.md) 和 [sheet.md 适用范围](./products/sheet.md)。 --- -### 3. devdoc — 开发文档搜索 +### 2. devdoc vs drive search / wiki node search — 两种搜索 **用 `devdoc` 的场景**: -- "API 调用报错 403 怎么解决" — 走 `devdoc error diagnose` -- "requestId 15r6h45w0muec 为什么失败" — 走 `devdoc error diagnose --request-id ...` +- "API 调用报错 403 怎么解决" — 开发调试问题 - "搜一下 OAuth2 接入文档" — 开放平台技术文档 - "CLI 命令出错了怎么办" — CLI 使用错误 -- 用户提到"开发"、"API"、"接口文档" → `devdoc article search` -- 用户提到"调用错误"、"错误码"、"requestId"、"traceId" → `devdoc error diagnose` +- 用户提到"开发"、"API"、"调用错误" + +**用 `drive search` / `wiki node search` 的场景**: +- "在我的文档里搜一下'项目方案'" — 全局搜索用 `drive search` +- 用户明确说"某个知识库里搜" — 空间内搜索用 `wiki node search --workspace` + +**判断关键**:搜开发文档→ `devdoc`;搜用户自己的文档→ `drive search`(全局)或 `wiki node search`(空间内) --- -### 4. report vs todo — 日志 vs 待办 +### 3. drive vs doc vs wiki — 存储层 vs 内容层 vs 空间管理层 -**用 `report` 的场景**: -- "帮我看看收到的日报" — 日志收件箱 -- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template get`,再 `report entry submit --contents-file .json` -- "有什么日志模版" — 查看模版 -- "看看这个日志的已读统计" — 阅读状态 -- "我发过的日志有哪些" — 已发送列表 (`report outbox list`) -- 用户提到"日报"、"周报"、"日志" +> **两层模型判定口诀**:如果操作换一种文件类型还能成立,就是存储层(→ drive);操作只对特定格式有意义,就是内容层(→ doc/sheet);操作是对空间/节点的组织管理,就是空间管理层(→ wiki)。 -**用 `todo` 的场景**: -- "记一下这周要做的事" — 个人任务管理 +**用 `drive`(存储层)的场景**: +- "把这个 PDF 传到钉盘" — 上传文件(不关心格式) +- "下载那个 Excel 附件" — 下载文件(不关心格式) +- "看一下钉盘根目录有什么文件" — 浏览文件列表 +- "搜一下有没有叫季度汇报的文件" — 全局搜索文件实体 (`drive search`) +- "把这个文档复制一份" — 复制文件实体 +- "把文件移到另一个文件夹" — 移动文件实体 +- "改一下文件名" — 重命名文件实体 +- "给张三加个编辑权限" — 权限管理 +- 用户提到"钉盘"、"网盘"、"上传"、"下载"、"搜文件"、"找文件"、"复制"、"移动"、"重命名"、"权限" -**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;任务清单→ `todo` +**用 `doc`(内容层)的场景**: +- "读一下这个文档的内容" — 读取文档 Markdown(仅 adoc 有意义) +- "帮我写入一段话到文档里" — 编辑文档内容(仅 adoc 有意义) +- "在第三段后面插入一个表格" — 块级编辑(仅 adoc 有意义) +- "给这段内容加个评论" — 文档评论(仅 adoc 有意义) +- "把这个文档导出为 docx" — 文档导出(当前仅 adoc 支持) +- 用户提到"读文档内容"、"写文档"、"编辑文档"、"块级编辑"、"文档评论"、"导出文档" + +**用 `wiki`(空间管理层)的场景**: +- "列出所有知识库" — 空间列表 (`wiki space list`) +- "列出钉盘团队空间" — 钉盘空间列表 (`wiki space list --type orgSpace`) +- "在产品知识库里搜一下方案" — 空间内搜索 (`wiki node search --workspace`) +- "在知识库里创建一个空白文档" — 创建节点 (`wiki node create --type adoc`) +- "把这个文件移到另一个知识库" — 跨知识库移动 (`drive move`) +- "给知识库加个成员" — 成员管理 (`wiki member add`) +- 用户提到"知识库"、"团队空间"、"空间成员"、"空间内搜索"、"列出空间" + +**判断关键(两层模型)**: +- 操作不关心文件格式 → `drive`(存储层) +- 操作仅对特定文档格式有意义 → `doc` / `sheet`(内容层) +- 操作是对空间/节点的组织管理 → `wiki`(空间管理层) + +**搜索场景路由**: +- "搜文件" / "找文件"(不指定空间) → `drive search`(全局聚合搜索) +- "在某个知识库里搜" → `wiki node search --workspace `(空间内搜索) + +**创建场景路由**: +- "在知识库里创建一个文档" → `wiki node create --type adoc`(创建空文件实体) +- "帮我写一篇项目周报" → `doc create`(创建并写入内容) +- "创建一篇文档并写入内容" → 先 `wiki node create` 再 `doc update`(先创建实体,再写内容) + +**列表场景路由**: +- "列出钉盘文件" → `drive list` +- "列出知识库里的文件" → `drive list --workspace ` 或 `wiki node list --workspace ` +- "列出所有空间" → `wiki space list` --- -### 5. chat 内部 — 两种消息发送方式 +### 4. 视频会议 vs 日历日程 — conference 不在开源 CLI + +当前开源 CLI **不提供**视频会议(conference)命令。"预约视频会议/开个会/发起会议/会中控制(静音/共享屏幕/录制)/呼叫入会"等诉求,直接告知用户请在钉钉客户端完成,不要试探 `dws conference` 或用其他命令变通。 + +**用 `calendar` 的场景**(这些都不是视频会议): +- "明天下午安排个会" — 日程管理(可含会议室) +- "给自己留两个小时写方案/建个个人日程" — 个人日历事件,仍用 `calendar event create` +- "帮我约几个人开会" — 创建日程 + 添加参会人 +- "看看下午有没有空闲会议室" — 会议室管理 +- "帮我查一下同事有空吗" — 闲忙查询 +- 用户提到"日程"、"会议室"、"约会" + +--- + +### 5. chat 内部 — 消息发送与撤回 + +**用 `chat message send` 的场景**: +- "帮我在群里发个消息提醒大家" — **个人身份**发群消息 +- "发个单聊消息给某人" — 个人身份发单聊: + - 已有 userId 时直接使用 `--user`;已有 openDingTalkId 时使用 `--open-dingtalk-id` + - 纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId + - 富媒体消息(image/file)单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送 +- "发张图片/截图/语音/视频/文件到群里" / "发张图给某某" — **统一一条命令**:`dws chat message send ... --msg-type file --file-path <本地路径>`,CLI 内部自动上传并发送,**任意扩展名(png/jpg/pdf/mp4/zip…)都走这条** +- "发图片+文字说明" — 不要硬塞进一条命令;先发文件消息再补一条 `--text "..."` 即可 + +```bash +dws chat message send --group --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --msg-type file --file-path ./report.pdf --format json +``` + +> ❌ 反模式:调 `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` 等前置工具再 `--msg-type image --media-id`。这是**旧链路**,仅当上游已持有 mediaId 才用;新场景一律 `--file-path` 直发,避免长链路与"空白图"现象。 +> 富媒体消息单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送。 **用 `chat message send-by-bot` 的场景**: -- "让机器人在群里发一条通知" — **机器人身份**发群消息 -- "给张三发一条机器人单聊消息" — 机器人批量单聊 +- "让机器人在群里发一条通知" — **机器人身份**发消息 +- "给张三发一条机器人单聊消息" — 机器人单聊 **用 `chat message send-by-webhook` 的场景**: - "通过 Webhook 发告警到群里" — 自定义机器人 Webhook - 用户有 Webhook Token -**判断关键**:企业内部机器人→ `send-by-bot`(需 robotCode);有 Webhook Token→ `send-by-webhook` +**用 `chat message recall-by-bot` 的场景**: +- "撤回刚才机器人发的消息" — 需要 robot-code + processQueryKey + +用 `chat message recall` 的场景: +- "撤回我刚发的消息" — 撤回以个人身份发送的消息,需要 openConversationId + openMessageId + +用 `chat message query-send-status` 的场景: +- "消息发没发成功/查询消息发送状态" — 查询个人发送消息的状态,需要 send 返回的 openTaskId + +用 `chat message search-advanced` 的场景(推荐首选): +- "按发送者搜索消息/指定多个群搜索/@我的消息多维度搜" — 支持关键词、发送者、@我、@指定人、多个会话等维度组合搜索 +- 替代关系:完全替代 `chat message search`(严格超集:keyword 可选 vs 必填,支持多群 vs 单群);大部分替代 `chat message list-by-sender`(--user/--users 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 `chat message list-mentions`(--at-me 覆盖核心功能) +- 不能替代:`chat message list-focused`(「特别关注人」是独立维度) +- 默认使用 search-advanced,仅在上述不适用场景才降级到具体命令 + +**不支持的场景**: +- "撤回我刚发的消息"(但不知道消息 ID) — 需先通过消息拉取或搜索接口(如 `chat message list`、`chat message search-advanced` 等)获取 openMessageId,再调用 `chat message recall` + +判断关键:个人发→ `send`;机器人发→ `send-by-bot`;有 Webhook Token→ `send-by-webhook`;个人撤回→ `recall`;机器人撤回→ `recall-by-bot`;查发送状态→ `query-send-status`;消息搜索类意图优先路由到 `search-advanced`(推荐首选),仅在不适用时降级到具体命令 + +--- + +### 6. report vs doc vs todo — 日志 vs 文档 vs 待办 + +**用 `report` 的场景**: +- "帮我看看收到的日报" — 收件箱列表 (`report inbox list`) +- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template get`,再 `report entry submit` +- "有什么日志模版" — 查看模版 (`report template list`) +- "看看这个日志的已读统计" — 阅读状态 (`report entry stats`) +- "我发过的日志有哪些" — 发件箱列表 (`report outbox list`) +- 用户提到"日报"、"周报"、"日志" + +**用 `doc` 的场景**: +- "帮我写个项目总结文档" — 长文本创作(钉钉在线文档,非日志模版) + +**用 `todo` 的场景**: +- "记一下这周要做的事" — 个人任务管理 +- "创建一个待办提醒" — 仍归 `todo`,但要先说明当前只有 dueTime 截止时间,没有独立 reminder schedule + +**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;文档/知识库长文→ `doc`;任务清单→ `todo` + +--- + +### 6.1 attendance approve vs oa approval — 考勤业务审批 vs 通用 OA 审批 + +**用 `attendance approve` 的场景**(考勤业务审批单): +- "上周谁请假了 / 某人近期的加班记录 / 外出出差单 / 补卡审批单" — `attendance approve list --types overtime,leave,trip,patch`(`trip` 同时覆盖出差与外出,两者合并为同一类,查询不再细分) +- "查看考勤审批模板 / 帮我提交考勤审批" — `attendance approve templates --type leave|overtime|repair-check`(repair-check/patch=补卡) +- 用户提到"请假单 / 加班单 / 出差单 / 补卡单 / 考勤审批"等考勤上下文 + +**用 `oa approval` 的场景**(通用 OA 审批中心): +- "我的待审批 / 待办审批 / 已审批记录" — `oa approval list-pending` +- "某审批单详情 / 同意或驳回审批 / 撤销我发起的审批" — `oa approval detail/approve/reject/revoke` +- "查业务审批记录 / 审批转交 / 添加评论与抄送" — `oa approval records/transfer/comment/cc` +- 用户提到"报销 / 采购 / 用印 / 合同 等非考勤类审批" + +**判断关键**: +- 审批主题明确是【请假 / 加班 / 外出 / 出差 / 补卡】 → `attendance approve` +- 不限于考勤业务、面向"我上下游的审批任务"表述 → `oa approval` + +**提交审批单的边界**: +- 提交考勤审批单走 `attendance approve templates --type leave|overtime|repair-check`(当前仅支持请假/加班/补卡三类模板;出差/外出的提交模板暂不支持,查询仍可用 `approve list --types trip` 覆盖),命令会返回审批表单的 submitUrl 跳转链接,由用户点击链接跳转到钉钉客户端的提交页面完成填写与提交。**展示链接时必须用 Markdown 可点击格式 `[表单名称](submitUrl)`,不要裸露 URL**。 +- 提交诉求的辅助查询:可用假期余额走 `attendance vacation balance`、历史已提交记录走 `attendance approve list`。 +- 任何场景下都**不要误用 `oa approval` 代替** —— 该命令组只能查/审/撤已存在的审批单,考勤业务审批单走考勤自己的逻辑便于区分。 + +--- + +### 7. 纯通讯录查询 vs 跨产品 + +**仅查人/部门/成员/归属/组织关系**(没有「发消息、写文档、建待办」等第二动作)→ 属通讯录场景(行动指南 [08-directory.md](./best_practices/08-directory.md)),不要走汇报或文档场景。 + +**多轮对话**:用户先说「搜 userId」再说「要详细资料」→ 仍属通讯录场景;第二步 **必须** 执行 `contact user get --ids`,禁止只用第一次 `user search` 的浅表字段交差。 + +**与消息场景区分**:终点是「把消息发给某人」→ 消息场景([01-messaging.md](./best_practices/01-messaging.md));终点是「某人 userId/部门是什么」→ 通讯录。可先通讯录解析 ID 再发消息。 + +**与发邮件、待办、日程混排**:先后顺序与口径见 [08-directory.md](./best_practices/08-directory.md#与其他场景消歧)。 + +**特别关注列表查询**:用户说"我关注了谁/我的特别关注列表/我的星标联系人/特别关注的人有哪些" → `dws contact relation list-my-followings`(无入参)。 + +- 与 `chat message list-focused` 区分:前者拉"人员列表",后者拉"这些人发的消息"。 +- **可执行判断口径(按顺序扫描)**: + 1. 扫描 query 是否含动词【发/说/聊/讲】或名词【消息/聊天/动态/最新内容】 → 含则**强制**路由到 `chat message list-focused`,**忽略**主语中的"关注/特别关注/星标"。 + 2. 仅含"关注/特别关注/星标"+"人/列表/谁/有哪些/多少" → 路由到 `dws contact relation list-my-followings`。 +- **反例 query(绝不路由到 list-my-followings)**: + - "我特别关注的人最近发了什么消息" → `chat message list-focused` + - "关注的人最近都说了啥" → `chat message list-focused` + - "星标联系人发的群消息" → `chat message list-focused` +- **正例 query(路由到 list-my-followings)**: + - "我特别关注的人有哪些"、"我关注了谁"、"我的星标联系人" --- ## 跨产品工作流路由 -以下场景需要多个产品配合完成,注意上下文传递顺序。 +以下场景需要多个产品配合完成,注意上下文传递顺序。多步骤操作有现成脚本时优先使用脚本。 -### 创建日程并邀请同事(contact → calendar) +### 发邮件给同事(aisearch → contact → mail) + +用户说"给张三发封邮件",但只知道名字不知道邮箱地址: + +```bash +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json + +# 2. 用 userId 查详情获取 email +dws contact user get --ids --format json + +# 3. 用搜索到的邮箱地址作为收件人发送邮件 +dws mail mailbox list --format json # 获取发件人邮箱 +dws mail message send --from my@company.com --to zhangsan@company.com \ + --subject "周报" --body "内容" --format json +``` + +### 创建日程并邀请同事(aisearch → calendar) 用户说"约张三明天下午开会": +> 有脚本: `python scripts/calendar_schedule_meeting.py --title "会议" --start "..." --end "..." --users userId1 --book-room` + ```bash -# 1. 搜索同事 userId -dws contact user search --query "张三" --format json +# 手动流程(脚本不可用时): +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json # 2. 创建日程 dws calendar event create --title "会议" \ --start "2026-03-15T14:00:00+08:00" --end "2026-03-15T15:00:00+08:00" --format json -# 3. 添加参与者 -dws calendar participant add --event --users --format json +# 3. 添加参会人 +dws calendar attendee add --event --attendees --format json ``` -### 创建待办并指派(contact → todo) +### 创建待办并指派(aisearch → todo) 用户说"给张三建个待办": ```bash -# 1. 搜索同事 userId -dws contact user search --query "张三" --format json +# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」) +dws aisearch person --keyword "张三" --dimension name --format json # 2. 创建待办 dws todo task create --title "任务内容" --executors --format json ``` + +--- + +## 图片 / 文件发送 + +### 发送图片 / 本地文件(统一一条命令) + +用户说"发张图/把这张图发给某某/发个 PDF/发个语音/发个视频/发个文件"等任何场景,**统一一条命令**: + +```bash +# 群聊 +dws chat message send --group --msg-type file --file-path <本地路径> --format json + +# 单聊(推荐 --open-dingtalk-id;--user 也支持) +dws chat message send --open-dingtalk-id --msg-type file --file-path <本地路径> --format json +``` + +支持任意扩展名(`.png/.jpg/.gif/.bmp/.webp/.pdf/.doc/.xls/.zip/.mp3/.wav/.mp4/.avi` …),CLI 自动识别并处理。**无需** `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` / `chat conversation-info` / `chat file upload` 等任何前置工具调用。 + +### 图片/文件 + 文字说明 + +不要把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。先发文件再补一条文本消息即可: + +```bash +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --text "这是本周数据汇总" --format json +``` + +### 旧链路(mediaId)— 仅兼容场景 + +仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时使用: + +```bash +dws chat message send --group --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json +``` diff --git a/skills/mono/references/products/aitable.md b/skills/mono/references/products/aitable.md index a2dc9eee..66945081 100644 --- a/skills/mono/references/products/aitable.md +++ b/skills/mono/references/products/aitable.md @@ -7,15 +7,9 @@ | 资源 | URI 格式 | |------|----------| | Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | -| 指定数据表 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | -| 指定数据表+视图 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | | 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | -> **操作后请返回文档 URI**:返回链接时必须带上当前操作的数据表 tableId,让用户点击后直接看到目标数据表,而不是落在空白的默认表。 -> - 已知 tableId + viewId 时(view create 返回、view get 中提取):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` -> - 已知 tableId 时(table create 返回、base get 中提取、record 操作所用的 tableId):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` -> - 仅有 baseId、无明确 tableId 时(如 base list/search):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}` -> +> **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。 > 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。 ## 命令索引表 @@ -27,7 +21,7 @@ | `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` | | `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 | | `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID | -| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` | +| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId | | `base update` | 更新 Base 名称 | `--base-id` `--name` | — | | `base delete` | 删除 Base | `--base-id` | 不可逆 | @@ -37,7 +31,7 @@ |------|------|----------|----------| | `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 | | `table create` | 创建数据表 | `--base-id` `--name` `--fields` | fields 为 JSON 数组,至少 1 个 | -| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` + 三选一(`--name` / `--description` / `--record-name-key`) | `--record-name-key` 是固定枚举(如 task/project/event/customer/ji_lu 等),非字段 ID | +| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — | | `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 | ### field (字段管理) → 详见 [aitable-field.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md) @@ -49,30 +43,6 @@ | `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 | | `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 | -#### 搜索字段选项 -``` -Usage: - dws aitable field search-options [flags] -Example: - dws aitable field search-options --base-id --table-id --field-id - dws aitable field search-options --base-id --table-id --field-id --keyword 已完成 - dws aitable field search-options --base-id --table-id --field-id --limit 100 -Flags: - --base-id string Base ID (必填) - --field-id string 目标字段 ID,必须是 singleSelect / multipleSelect 类型 (必填) - --keyword string 模糊搜索关键词,大小写不敏感、contains 匹配 option name;不传返回全部 - --limit int 返回的最大 option 数量,默认 3000(全量),最大 3000 - --table-id string Table ID (必填) -``` - -仅适用于 **singleSelect / multipleSelect** 字段。其他类型(text/number/date/...)调用会返回错误。 - -适用场景: -- options 较多,只想要含某关键词的子集(避免 `field get` 拉取整个字段配置带回所有 options)。 -- 写入 record 前预览选项 id ↔ name 的映射,确认要使用的选项确实存在。 - -> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 可直接传 option **name**,不需要用本命令。本命令主要用于 **filter** 写法(filters 优先用 option **id**)或选项较多需要精确定位时。 - ### record (记录管理) | 命令 | 用途 | 必读 reference | 路由提醒 | @@ -80,40 +50,28 @@ Flags: | `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference | | `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit | | `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | -| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;`--records` 是 `[{recordId,cells},...]` 数组 | -| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 | +| `record update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag | | `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 | -| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 | -| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) | -| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 | -| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 | -| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` | -| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId | ### view (视图管理) | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| -| `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;**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) | -| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) | +| `view get` | 获取视图配置 | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 | +| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner | +| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 | | `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 | -> **优先用 `view get ` / `view update ` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。 +> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。 -> **属性按 attr 分类,决定该读哪份子文档**: -> - 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 / duplicate → [aitable-view-extras.md](./aitable/aitable-view-extras.md) -> 后一类**不能**塞进 `view update --config '{...}'`,必须用各自专属子命令;如果错传 `flags` / `frozenColCount` / `cellHeight` / `conditionalFormats` 等 key 进 `--config`,CLI 会在 stderr 提示应改用的命令。 - -> **`view update --config` 支持的 9 个 key**: -> `visibleFieldIds` / `filter` / `sort` / `group` / `fieldWidths`(Grid) / `aggregate`(Grid) / `kanbanCard`(Kanban) / `ganttTimebar`(Gantt) / `galleryCard`(Gallery)。 -> filter/sort/group 必须传**数组**格式(与 `record query --filters` 的对象格式不同;CLI 会自动容错)。其他 key 会被服务端忽略并打 warning。 +> **view update --config 支持的 key 白名单**(传入其他 key 会报错): +> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位) +> - `filter` — 筛选规则列表 +> - `sort` — 排序规则列表 +> - `group` — 分组规则列表 +> - `fieldWidths` — 列宽映射(仅 Grid 视图有效) +> +> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。 ### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md) @@ -147,7 +105,6 @@ Flags: |------|------| | `dashboard get/create/update/delete` | 仪表盘管理 | | `dashboard config-example` | 查看仪表盘配置模板 | -| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) | | `chart get/create/update/delete` | 图表管理 | | `chart widgets-example` | 查看图表 widgets 配置模板 | @@ -316,7 +273,7 @@ dws aitable chart get --base-id --dashboard-id --chart- ```bash # 第一步:创建任务(按 scope 传必要参数) -dws aitable export data --base-id --scope table --table-id --export-format excel --timeout-ms 1000 +dws aitable export data --base-id --scope table --table-id --format excel --timeout-ms 1000 # 第二步:拿 taskId 继续轮询,直到返回 downloadUrl dws aitable export data --base-id --task-id --timeout-ms 3000 @@ -340,8 +297,7 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"数据表/子表/table": - 查看 → `table get` - 创建 → `table create` -- 重命名 / 改备注 / 改行命名规则 → `table update`(三选一:`--name` / `--description` / `--record-name-key`) -- 用户说"行命名规则/记录别名/卡片显示成 task/project/event 这种" → `table update --record-name-key <枚举键>`,**中文 → 枚举键**对照见 [aitable-record-name-key.md](./aitable/aitable-record-name-key.md) +- 重命名 → `table update` - 删除 → `table delete` 用户说"字段/列/column": @@ -352,34 +308,10 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"记录/行/数据/row": - 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable/aitable-record-query.md)) -- 找空行 / 没填东西的行 → `record query-empty`(读 [aitable-record-query.md](./aitable/aitable-record-query.md)) - 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`) - 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable/aitable-record-create.md)) -- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) -- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'` +- 修改/更新 → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) - 删除 → `record delete` -- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable/aitable-record-history.md)) -- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable/aitable-record-share.md)) -- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable/aitable-record-upsert.md)) - -用户说"视图/view": -- 列出/查看全部视图 → `view list`(或 `view get` 不传 --view-ids,二者等价) -- 看某个视图详情 → `view get --view-ids ` -- 创建 → `view create` -- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'` -- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update `(读 [aitable-view-config.md](./aitable/aitable-view-config.md)) -- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable/aitable-view-extras.md) -- 删除 → `view delete` - -用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) 用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md) @@ -389,33 +321,14 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)(§5.4 跨表引用) -用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable/aitable-form.md) - -用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable/aitable-workflow.md) -- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`) -- 看某个流程具体配置(触发条件、动作步骤) → `workflow get` -- 启用流程 → `workflow enable` -- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes` -- **新建 / 修改 / 删除流程**:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成 - 用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) -用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)) - 用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable/aitable-attachment.md) 用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable/aitable-export-import.md) 用户说"模板" → `template search` -用户说"高级权限/角色/权限控制/谁能看/谁能改" → 读 [aitable-advperm.md](./aitable/aitable-advperm.md) -- 开/关高级权限 → `advperm enable` / `advperm disable --yes` -- 看角色配置 → `advperm role-list` 或 `advperm role-get` -- 建角色(可同时指定子角色权限) → `advperm role-create --name ... --sub-roles '[...]'` -- 改角色名 / 改子角色权限(PATCH 语义,未传字段不变) → `advperm role-update --role-id ... [--name ...] [--sub-roles '[...]']` -- 删角色 → `advperm role-delete --yes` -- **角色 ↔ 成员绑定**:当前 CLI 不支持,仍需在 AI 表格 Web 端面板手动完成 - 命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable/aitable-error-recovery.md) **关键区分**: base=表格文件, table=数据表, field=列, record=行 @@ -446,8 +359,7 @@ dws aitable record create --base-id --table-id \ |------|-------------|------| | `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI | | `base create` | `baseId` | 后续命令 + 文档 URI | -| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI | -| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI | +| `base get` | `tables[].tableId` | --table-id | | `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete | | `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` | | `template search` | `templateId` | base create --template-id,拼接模板预览 URI | diff --git a/skills/mono/references/products/aitable/aitable-attachment.md b/skills/mono/references/products/aitable/aitable-attachment.md index 45e9c2ae..1648fc87 100644 --- a/skills/mono/references/products/aitable/aitable-attachment.md +++ b/skills/mono/references/products/aitable/aitable-attachment.md @@ -1,8 +1,6 @@ # attachment — 附件上传 > **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。 -> -> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。 ## 准备附件上传 @@ -40,8 +38,8 @@ dws aitable record create --base-id --table-id \ dws aitable attachment upload --base-id --file-name report.pdf --size 204800 --format json # → 返回 uploadUrl、fileToken -# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type) -curl -X PUT "" -H "Content-Type: application/pdf" --data-binary @report.pdf +# 2. PUT 上传(Content-Type 留空) +curl -X PUT "" -H "Content-Type:" --data-binary @report.pdf # 3. 写入记录 dws aitable record update --base-id --table-id \ diff --git a/skills/mono/references/products/aitable/aitable-best-practices.md b/skills/mono/references/products/aitable/aitable-best-practices.md index db63f634..a851af83 100644 --- a/skills/mono/references/products/aitable/aitable-best-practices.md +++ b/skills/mono/references/products/aitable/aitable-best-practices.md @@ -26,7 +26,7 @@ | 全量拉取/统计 | `record query --all` | 不要手动循环 cursor | | 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 | | 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 | -| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) | +| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 | | 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 | ## 4. 创建/修改后回读确认 diff --git a/skills/mono/references/products/aitable/aitable-cell-value.md b/skills/mono/references/products/aitable/aitable-cell-value.md index bc3a8125..9f747bf9 100644 --- a/skills/mono/references/products/aitable/aitable-cell-value.md +++ b/skills/mono/references/products/aitable/aitable-cell-value.md @@ -86,16 +86,11 @@ {"fldDateId": "2026-03-15T09:00+08:00"} ``` -**读取**:RFC3339 字符串(带时区) +**读取**:RFC3339 字符串 ```json {"fldDateId": "2026-03-15T09:00:00+08:00"} ``` -**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。 -- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条; -- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。 -- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。 - --- ### currency(货币) @@ -241,14 +236,15 @@ ### attachment(附件) -**写入**:对象数组,**必须使用 `fileToken`** +**写入**:对象数组,支持 `fileToken` 或 `url` 形式 ```json {"fldAttachId": [{"fileToken": "ft_xxx"}]} +{"fldAttachId": [{"url": "https://example.com/file.pdf"}]} ``` -> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。** -> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。 +> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。 +> URL 形式是 best-effort 异步转存,不保证立即可用。 > 写入会**整体覆盖**原附件列表,不是追加。 **读取**:对象数组(含下载链接、文件名、大小) @@ -339,7 +335,7 @@ |------|----------| | cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | progress 写入 `75` | 写入 `0.75`(范围 0~1) | -| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) | +| attachment 直接传文件路径 | 必须先 upload 获取 fileToken | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | group 字段用 `openConversationId` | 用 `cid` | | singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 | diff --git a/skills/mono/references/products/aitable/aitable-dashboard-chart.md b/skills/mono/references/products/aitable/aitable-dashboard-chart.md index 94d576e2..ec96fb4f 100644 --- a/skills/mono/references/products/aitable/aitable-dashboard-chart.md +++ b/skills/mono/references/products/aitable/aitable-dashboard-chart.md @@ -27,7 +27,6 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 | | `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — | | `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 | -| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` | ## chart 子命令 diff --git a/skills/mono/references/products/aitable/aitable-export-import.md b/skills/mono/references/products/aitable/aitable-export-import.md index fc755451..05e83466 100644 --- a/skills/mono/references/products/aitable/aitable-export-import.md +++ b/skills/mono/references/products/aitable/aitable-export-import.md @@ -111,3 +111,30 @@ dws aitable import data --import-id --table-id --format js > **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此: > - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除 > - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入 + +--- + +## 导入数据:两条链路怎么选 + +用户给文件让导入 AI 表格时,路径选择决定成败: + +| 用户原话 | 链路 | 命令 / 脚本 | 行为 | +|---------|------|------------|------| +| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id B --file ./x.xlsx` + `dws aitable import data --import-id ` | 服务端解析文件,**新建数据表**,自动识别表头 | +| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 | +| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py ` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 | +| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | `dws aitable import upload --base-id B --file ./x.xlsx` → `dws aitable import data --import-id --table-id --field-mapping '{"目标":"源"}'` | 服务端按映射追加 | + +## 大表 / 长任务超时续等 + +默认整体轮询超时 5 分钟。大表导出/导入超时后命令会返回 `taskId` / `importId`,用同命令带 ID 续等: + +```bash +# 续等导出 +dws aitable export data --base-id --task-id --output ./out.xlsx + +# 续等导入 +dws aitable import data --import-id +``` + +或一次性把 timeout 提到 15 分钟:`--timeout-sec 900`。 diff --git a/skills/mono/references/products/aitable/aitable-filter-sort.md b/skills/mono/references/products/aitable/aitable-filter-sort.md index 8fabf8b4..8819a31f 100644 --- a/skills/mono/references/products/aitable/aitable-filter-sort.md +++ b/skills/mono/references/products/aitable/aitable-filter-sort.md @@ -1,7 +1,5 @@ # filters & sort — 筛选排序语法参考 -> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。 - ## filters 结构规范 ### 强制规则 @@ -51,34 +49,11 @@ CLI 同时兼容两种子条件写法(推荐格式 A): | `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) | | `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` | | `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` | -| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` | +| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` | +| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` | +| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` | > **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。 -> -> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。 - -### 日期字段过滤(date / 创建时间 / 修改时间) - -日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则: - -1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。 -2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。 -3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。 -4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。 - -| 需求 | 操作符 | 示例 operands | -|------|--------|--------------| -| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` | -| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` | -| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` | -| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` | - -**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点): - -```bash -dws aitable record query --base-id X --table-id Y \ - --filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}' -``` ### 常见错误拼写(CLI 会自动提示纠正) @@ -129,41 +104,3 @@ dws aitable record query --base-id X --table-id Y \ ```bash --sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]' ``` - ---- - -## view update --config 中的 filter / sort 格式 - -> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**! - -| 场景 | filter 格式 | 说明 | -|------|-------------|------| -| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 | -| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 | -| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 | - -### 正确示例 - -```bash -# view update 设置筛选(filter 是数组) -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}' - -# view update 设置排序(sort 是数组) -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}' - -# 同时设置 filter + sort + visibleFieldIds -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}' -``` - -### CLI 自动容错 - -CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式): - -| 错误写法 | CLI 自动修正为 | -|----------|---------------| -| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) | -| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) | -| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` | diff --git a/skills/mono/references/products/aitable/aitable-record-create.md b/skills/mono/references/products/aitable/aitable-record-create.md index bf605899..eb737228 100644 --- a/skills/mono/references/products/aitable/aitable-record-create.md +++ b/skills/mono/references/products/aitable/aitable-record-create.md @@ -27,7 +27,6 @@ Flags: | cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) | | 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 | | 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 | -| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) | ## 正确流程 diff --git a/skills/mono/references/products/aitable/aitable-record-query.md b/skills/mono/references/products/aitable/aitable-record-query.md index 20ed92da..8d04ab1b 100644 --- a/skills/mono/references/products/aitable/aitable-record-query.md +++ b/skills/mono/references/products/aitable/aitable-record-query.md @@ -72,49 +72,3 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回 - `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"` - 不加 `--field-ids` 拉全字段 → 大表响应体积过大 - 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤 - -## record query-empty — 找空行 - -`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。 - -```bash -dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID -``` - -| flag | 说明 | -|------|------| -| `--base-id` / `--base` | 必填 | -| `--table-id` | 必填 | -| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 | -| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 | - -返回结构: - -```jsonc -{ "data": { "records": [...], "nextCursor": "..." } } -``` - -### 关键语义 - -1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。 -2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。 -3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。 - -### 典型用法 - -```bash -# 扫一页,看本页有没有空行 -dws aitable record query-empty --base-id BASE --table-id TBL - -# 翻页 -dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor> - -# 把整表扫完(手动循环 cursor) -NC="" -while : ; do - R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json) - echo "$R" | jq '.data.records[] | .recordId' - NC=$(echo "$R" | jq -r '.data.nextCursor // empty') - [ -z "$NC" ] && break -done -``` diff --git a/skills/mono/references/products/attendance-report.md b/skills/mono/references/products/attendance-report.md new file mode 100644 index 00000000..e845abf4 --- /dev/null +++ b/skills/mono/references/products/attendance-report.md @@ -0,0 +1,619 @@ +# 考勤报表导出参考 (attendance-report) + +> 本文档由 `attendance.md` 路由调用。当用户提到"考勤报表"、"导出考勤"、"出勤汇总"、"考勤明细"、"迟到早退统计"、"全员考勤数据"、"某月考勤统计"、"考勤表格"、"考勤 Excel" 时,应阅读本文档执行。 +> 不适用于:个人单日打卡查询(用 `attendance check record`)、班次查询(用 `attendance schedule get`)、假期余额(用 `vacation balance`)、审批进度(用 `oa`)。 + +## 强制门禁(必须先读完本文档才能执行) + +**任何调用 `attendance_report_detail.py` / `attendance_report_monthly.py` / `attendance_report_daily.py` 的请求,都必须经过本文档定义的工作流,严禁绕过本文档直接拼脚本命令执行。** + +违反将出现以下任一问题: +1. 未按"阶段 1"做人员解析 → `--users` 传入部门 ID 而非员工 userId,脚本虽内置回退但会浪费一次失败的接口调用 +2. 未按"阶段 0"判断报表类型 → 用户说"汇总"被理解成"明细",导致输出粒度错误 +3. 未按"列选择"判断是否传 `--column-keywords` → 用户要"迟到情况报表"被输出成全字段默认报表 +4. 未按"错误处理"规则处理 403 / `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户 +5. 未按"阶段 4"返回结果 → 把 Excel 内容贴在对话里,或者裸 userId 直接输出 + +**执行前自检(必须能在心中回答)**: +- [ ] 报表类型是?(明细 / 月度汇总 / 每日统计) +- [ ] 人员列表的来源是?(`aisearch person` 还是 `contact dept list-members`?) +- [ ] 列选择方式是?(预设报表关键词 / 自定义 `--column-keywords` / 默认列集合) +- [ ] 报错时如何向用户解释? + +如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。 + +**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回 403 权限错误。 + +## 核心原则 + +Agent 解析用户意图(报表类型、人员范围、时间范围、关注维度),获取 userId 列表后,**直接调用对应的 Python 脚本 CLI 生成 Excel**。 +- **脚本自包含**:数据查询(分批、分段、翻页)、字段解析、聚合计算、Excel 生成全部由脚本内部完成,Agent 不参与数据查询和计算 +- **月度汇总 / 每日统计**:脚本内部调用 `report columns` + `report query-data` +- **明细**:脚本内部调用 `check result` + `check record`(数据源不同) +- 列选择是独立维度:用户未指定关注维度时脚本使用内置默认字段;用户指定了关注维度时 Agent 通过 `--column-keywords` 参数传给脚本 + +## 严格禁止 (NEVER DO) + +- 禁止凭历史记忆复用 userId 等任何 ID,必须从当次命令返回值中提取 +- 禁止用大模型口算/目测做考勤数据聚合(求和、计数、分组),必须通过 Python 脚本完成 +- 禁止 Agent 直接调用 `report query-data` / `report columns` / `check result` / `check record`,这些由脚本内部自动完成 +- 禁止 `dws` 命令缺省 `--format json`(Agent 仅在阶段 1 获取人员时直接调用 dws 命令) +- 禁止编造任何字段值或用户姓名 +- 禁止直接输出裸 userId,脚本已内置 userId → 姓名转换 + +## 严格要求 (MUST DO) + +- 所有 `dws` 命令必须携带 `--format json` +- 时间参数 `--start` / `--end` 格式必须为 `yyyy-MM-dd HH:mm:ss` +- 字段 ID 与字段名的映射必须从 `report columns` 实时建立,禁止硬编码 +- 任何接口失败(含 403)必须向用户清晰报错,禁止静默吞掉 + +## 涉及工具 + +| 工具 | 用途 | 安全等级 | +|------|------|---------| +| `dws attendance report columns` | 获取当前管理员可见的考勤字段清单(字段 ID → 字段名) | 只读 | +| `dws attendance report query-data` | 按字段查询考勤数据(≤20 人/次,≤32 天/次) | 只读 | +| `dws attendance report query-leave` | 按假期名称查询假期数据(≤20 人/次,≤32 天/次),月度汇总/每日统计的"请假"列由脚本自动调用 | 只读 | +| `dws attendance approve list` | 查询审批单记录(考勤记录报表专用),支持类型:leave/trip/out/patch | 只读 | +| `dws oa approval detail` | 获取审批单详情(考勤记录报表专用),解析 formValueVOS / extValue | 只读 | +| `dws attendance check result` | 查询打卡结果(≤100 人/次,≤1 月,明细报表专用) | 只读 | +| `dws attendance check record` | 查询打卡流水(≤1 月,明细报表专用) | 只读 | +| `dws aisearch person` | 按姓名搜索用户获取 userId(搜人首选) | 只读 | +| `dws contact user get` | 批量查询 userId → 用户信息(姓名/部门/工号/职位) | 只读 | +| `dws contact dept search` | 搜索部门获取 deptId | 只读 | +| `dws contact dept list-members` | 获取部门成员 userId 列表 | 只读 | + +## 意图判断 + +### 报表类型(五选一) + +| 用户说 | 报表类型 | 映射脚本 | +|--------|---------|---------| +| "导出研发部 3 月份的考勤明细" / "每条打卡记录" / "明细" / "原始记录" | 明细 | `attendance_report_detail.py` | +| "生成研发部 3 月考勤汇总" / 用户明确说"月度汇总" / 用户未指明类型 | **月度汇总(默认)** | `attendance_report_monthly.py` | +| "导出研发部 3 月每天的出勤情况" / "按天统计" / "每日" / 用户明确说"每日统计" | 每日统计 | `attendance_report_daily.py` | +| "导出研发部 4 月的请假记录" / "补卡记录" / "出差记录" / "外出记录" / "xx记录" | 考勤记录 | `attendance_report_record.py` | +| "导出签到记录" / "签到报表" / "签到数据导出" / "签到明细" / "外勤签到" | 签到报表 | `attendance_report_checkin.py` | + +> **默认报表类型**:用户未指明报表类型时,**默认走月度汇总**,事后告知"已按月度汇总输出,如需明细/每日统计/考勤记录请告知"。 +> +> **考勤记录 vs 其他报表**:当用户明确提到"请假记录"/"补卡记录"/"出差记录"/"外出记录"时,走考勤记录报表(数据源为审批单)。而"请假报表"/"出差时长统计"等走月度汇总(数据源为 report query-data)。区别在于:考勤记录导出的是**审批单维度的原始数据**(含审批单状态、每天明细),月度汇总导出的是**按人按月聚合后的统计数据**。 + +### 列选择(独立维度,与报表类型正交) + +> **"报表类型"与"列选择"是两个独立维度,需分别判断。** +> 例如用户说"帮我出一份加班报表":报表类型未指明 → 默认月度汇总;列选择命中"加班报表" → 使用加班预设关键词。 +> 例如用户说"帮我出每日的异常报表":报表类型命中"每日" → 每日统计脚本;列选择命中"异常报表" → 使用异常预设关键词。 +> 预设报表**不会改变报表类型的判断逻辑**,报表类型始终按下方「报表类型(三选一)」规则判断。 + +| 用户说 | 列选择方式 | +|--------|-----------| +| "帮我出一份考勤报表" / "导出考勤" / 未提及特定关注维度 | 不传 `--column-keywords`,使用脚本内置**默认列集合** | +| "加班报表" / "加班统计" / "加班时长报表" | 传 `--column-keywords`,使用下方「加班报表预设关键词」 | +| "请假报表" / "请假出差报表" / "请假外出统计" | 传 `--column-keywords`,使用下方「请假报表预设关键词」 | +| "异常报表" / "异常考勤" / "迟到早退报表" / "缺卡报表" | 传 `--column-keywords`,使用下方「异常报表预设关键词」 | +| 提及了其他自定义关注维度(如"工作时长报表") | 传 `--column-keywords`,由 Agent 自行拼接关键词 | + +> **预设报表优先级**:当用户提到的关键词同时命中"预设报表"和一般自定义维度时,**优先使用预设报表的完整关键词列表**,确保列不遗漏。 + +### 易混淆场景 + +| 用户说 | 应路由到 | +|--------|---------| +| "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) | +| "帮我排班" | [attendance-schedule.md](./attendance-schedule.md) 排班工作流(排班,非报表) | +| "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) | +| "帮我请假" | `dws oa`(审批流程,非报表) | +| "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) | + +## 工作流 + +### 阶段 0: 参数解析与确认查询范围 + +1. **解析用户输入**(两个独立维度): + - **报表类型**(四选一):明细 / 月度汇总(默认) / 每日统计 / 考勤记录 + - **列选择**(独立维度):预定义列集合(默认) / 用户指定维度筛选(考勤记录不适用) + - **人员维度**:指定员工 / 某个部门 / 多个部门(暂不支持全公司查询) + - **时间维度**:本周 / 本月 / 自定义时间段 + - **记录子类型**(仅考勤记录):leave(请假) / trip(出差) / out(外出) / patch(补卡) +2. **缺失信息处理**: + - **报表类型** 缺失 → 默认走月度汇总(不追问),事后告知 + - **列选择**:用户提及了特定关注维度 → 传 `--column-keywords`;未提及 → 使用脚本内置默认字段集 + - **用户范围 / 时间范围** 缺失 → 追问,禁止猜测 + +### 阶段 1: 获取完整人员列表 + +**场景 A — 指定员工姓名**: +```bash +dws aisearch person --keyword "<员工姓名>" --dimension name --format json +``` + +**场景 B — 按部门查询**: +```bash +dws contact dept search --query "<部门名>" --format json +dws contact dept list-members --ids --format json +``` + +**场景 C — 多个部门**: 对每个部门分别执行 B,汇总去重。 + +**场景 D — 全公司**: 暂不支持,引导用户指定部门。 + +**场景 E — 用户已给 userId 列表**: 直接跳过本步。 + +### 阶段 2: 列选择(决定脚本参数) + +> Agent 不需要手动调用 `report columns`,字段获取由脚本内部完成。Agent 只需根据用户意图决定是否传 `--column-keywords` 参数。 + +**判断顺序**(优先级从高到低): + +1. **明细报表** → 列固定,不支持 `--column-keywords` +2. **用户提到预设报表关键词** → 传 `--column-keywords`,使用本文档「预设报表列集合」中定义的完整关键词列表: + - "加班报表" / "加班统计" / "加班时长" → 使用「加班报表预设关键词」 + - "请假报表" / "请假出差" / "外出统计" → 使用「请假报表预设关键词」 + - "异常报表" / "迟到早退" / "缺卡报表" / "异常考勤" → 使用「异常报表预设关键词」 +3. **用户提及了其他自定义关注维度** → 传 `--column-keywords "..."` +4. **用户未提及特定关注维度** → 不传 `--column-keywords`,脚本使用内置默认字段集 + +### 阶段 3: 调用脚本生成 Excel + +#### 月度汇总 / 每日统计 + +```bash +python scripts/attendance_report_monthly.py \ + --users ,,... \ + --start "" \ + --end "" \ + [--column-keywords "出勤天数,迟到次数,迟到时长,..."] \ + [--out 月度汇总_研发部_202604.xlsx] +``` + +参数说明: +- `--users`(必填):逗号分隔的 userId 列表 +- `--start`(必填):开始日期,支持 `YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss` +- `--end`(必填):结束日期,同上 +- `--column-keywords`(可选):逗号分隔的字段名关键词。不传则使用脚本内置默认字段集。预设报表(加班/请假/异常)也通过本参数传入对应的预设关键词列表 +- `--out`(可选):输出文件名,不传则自动生成 + +脚本内部自动处理:`report columns` 获取字段清单 → 按预设/关键词匹配 → `report query-data` 分批分段查询 → `contact user get` 姓名映射 → 聚合计算 → 生成 Excel + +#### 明细 + +```bash +python scripts/attendance_report_detail.py \ + --users ,,... \ + --start "" \ + --end "" \ + [--out 考勤明细_研发部_202604.xlsx] +``` + +- **没有 `--column-keywords`**,明细列固定 +- 数据来源不同:`check result` + `check record`(非 `report query-data`) +- 分批限制:≤100 人/次(而非 20 人) + +#### 考勤记录 + +```bash +python scripts/attendance_report_record.py \ + --type \ + --users ,,... \ + --start "" \ + --end "" \ + [--out 请假记录_研发部_202604.xlsx] +``` + +参数说明: +- `--type`(必填):记录类型,支持 `leave`(请假) / `trip`(出差) / `out`(外出) / `patch`(补卡) +- `--users`(必填):逗号分隔的 userId 列表 +- `--start`(必填):开始日期 `YYYY-MM-DD` +- `--end`(必填):结束日期 `YYYY-MM-DD` +- `--out`(可选):输出文件名,不传则自动生成 +- **没有 `--column-keywords`**,列由 `--type` 决定 + +脚本内部自动处理:`attendance approve list` 获取审批摘要 → `oa approval detail` 获取详情 → 解析 DDHolidayField / extValue → 按天拆行 → `contact user get` 姓名映射 → 生成 Excel + +**记录类型选择规则**(Agent 需从用户意图中判断): + +| 用户说 | --type 值 | +|--------|----------| +| "请假记录" / "年假记录" / "调休记录" / "病假记录" | `leave` | +| "出差记录" | `trip` | +| "外出记录" | `out` | +| "补卡记录" | `patch` | + +#### 签到报表 + +```bash +python scripts/attendance_report_checkin.py \ + --users ,,... \ + --start "" \ + --end "" \ + [--out 签到报表_研发部_20260401_20260407.xlsx] +``` + +参数说明: +- `--users`(必填):逗号分隔的 userId 列表 +- `--start`(必填):开始日期 `YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss` +- `--end`(必填):结束日期,同上 +- `--out`(可选):输出文件名,不传则自动生成 +- **没有 `--column-keywords`**,签到报表列固定 + +脚本内部自动处理:`attendance checkin records` 分批分段查询(每批 50 人,每段 7 天)→ `contact user get` 姓名/部门映射 → 时间戳转日期+时间 → 图片列展开(最多 9 张)→ 生成 Excel + +> **注意**:签到接口时间限制为 7 天(不同于考勤报表的 32 天),脚本会自动按 7 天分段查询。 + +#### 脚本执行注意事项 + +- 脚本依赖 `openpyxl`,若未安装需先 `pip install openpyxl` +- 脚本摘要输出到 stdout,进度日志输出到 stderr +- 首次调试可加 `--inspect` 参数查看首条记录原始结构 +- 脚本执行失败(exit ≠ 0)时,stderr 中有具体错误信息 + +### 阶段 4: 返回结果给用户 + +- 将脚本 stdout 输出的摘要信息原样转告用户 +- 如果脚本输出 warning,原样转告用户 +- 如果走的是默认月度汇总,追加:"已按月度汇总输出,如需明细/每日统计请告知" +- **不要把 Excel 内容贴在对话里**,只给路径和摘要 + +## 输出文件结构 + +### 月度汇总(双 sheet,自动生成) + +`attendance_report_monthly.py` 输出的 Excel 文件包含 **2 个 sheet**: + +| Sheet 名 | 布局 | 用途 | +|---------|------|------| +| `月度汇总` | 每人 1 行,列为基础信息 + 聚合字段 + 请假展开 + 考勤结果按天展开 | 整月数据汇总速览 | +| `日历表` | 每人 3 行(班次名称/考勤结果/工作时长),列为基础信息 + 指标 + 1日~N日 | 钉钉日历视图,逐日查看 | + +**日历表结构示意**: + +| 姓名 | 考勤组 | 部门 | 指标 | 1日 | 2日 | ... | 30日 | +|------|--------|------|------|------|------|------|------| +| 张三 | 研发组 | 技术部 | 班次名称 | 早班 | 早班 | ... | 休息 | +| | | | 考勤结果 | 正常 | 迟到 | ... | — | +| | | | 工作时长 | 8 | 7.5 | ... | 0 | +| 李四 | 研发组 | 技术部 | 班次名称 | 晚班 | 晚班 | ... | 早班 | +| ... | ... | ... | ... | ... | ... | ... | ... | + +- 基础列(姓名/考勤组/部门)已纵向 3 行合并 +- 日历表的 3 个指标字段(`班次名称`/`考勤结果`/`工作时长`)由脚本**强制**追加到 `report query-data` 查询字段中(即使用户的 `--column-keywords` 没包含),确保日历表非空 +- 日历表数据来源与月度汇总相同(同一次 `report query-data` 调用),不会增加接口次数 + +## 预定义列集合 + +### 月度汇总(3 个基础信息列 + 18 个考勤数据列) + +**基础信息列**(脚本自动从 `contact user get` 和原始记录中提取): + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 1 | 姓名 | `contact user get --ids` | +| 2 | 考勤组 | `attendance group search` + `filtered-get --member` 反向映射 | +| 3 | 部门 | `contact user get --ids` | + +**考勤数据列**(从 `report columns` 中按名称精确匹配): + +| 序号 | 字段名称 | +|------|---------| +| 4 | 出勤天数 | +| 5 | 休息天数 | +| 6 | 工作时长 | +| 7 | 迟到次数 | +| 8 | 迟到时长 | +| 9 | 严重迟到次数 | +| 10 | 严重迟到时长 | +| 11 | 旷工迟到次数 | +| 12 | 早退次数 | +| 13 | 早退时长 | +| 14 | 上班缺卡次数 | +| 15 | 下班缺卡次数 | +| 16 | 旷工天数 | +| 17 | 出差时长 | +| 18 | 外出时长 | +| 19 | 请假(按假期类型展开为 4 列:`请假-事假`、`请假-调休`、`请假-病假`、`请假-年假`,值为月度求和;数据由脚本通过 `report query-leave` 单独查询) | +| 20 | 加班-审批单统计 | +| 21 | 考勤结果(按天展开为多列:1日/2日/.../31日,每列显示当天考勤状态) | + +### 每日统计(4 个基础信息列 + 31 个考勤数据列) + +**基础信息列**: + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 1 | 姓名 | `contact user get --ids` | +| 2 | 考勤组 | `attendance group search` + `filtered-get --member` 反向映射 | +| 3 | 部门 | `contact user get --ids` | +| 4 | 日期 | 查询日期 | + +**考勤数据列**: + +| 序号 | 字段名称 | +|------|---------| +| 5 | 班次 | +| 6 | 上班1打卡时间 | +| 7 | 上班1打卡结果 | +| 8 | 下班1打卡时间 | +| 9 | 下班1打卡结果 | +| 10 | 上班2打卡时间 | +| 11 | 上班2打卡结果 | +| 12 | 下班2打卡时间 | +| 13 | 下班2打卡结果 | +| 14 | 上班3打卡时间 | +| 15 | 上班3打卡结果 | +| 16 | 下班3打卡时间 | +| 17 | 下班3打卡结果 | +| 18 | 关联的审批单 | +| 19 | 出勤天数 | +| 20 | 休息天数 | +| 21 | 工作时长 | +| 22 | 迟到次数 | +| 23 | 迟到时长 | +| 24 | 严重迟到次数 | +| 25 | 严重迟到时长 | +| 26 | 旷工迟到次数 | +| 27 | 早退次数 | +| 28 | 早退时长 | +| 29 | 上班缺卡次数 | +| 30 | 下班缺卡次数 | +| 31 | 旷工天数 | +| 32 | 出差时长 | +| 33 | 外出时长 | +| 34 | 请假(按假期类型展开为 4 列:`请假-事假`、`请假-调休`、`请假-病假`、`请假-年假`;数据由脚本通过 `report query-leave` 单独查询) | +| 35 | 加班-审批单统计 | + +### 预设报表:加班报表 + +3 个基础信息列(姓名/考勤组/部门)+ 以下考勤数据列: + +> 原始配置中的 TITLE_COLUMN(如"加班时长(转调休)")为分组标题,脚本不支持父子列结构,已打平为叶子字段。 + +| 序号 | 字段名称 | 说明 | +|------|---------|------| +| 4 | 加班-审批单统计 | — | +| 5 | 加班总时长 | — | +| 6 | 考勤结果 | 按天展开 | + +**对应 `--column-keywords`**:`加班-审批单统计,加班总时长,考勤结果` + +### 预设报表:请假报表 + +3 个基础信息列(姓名/考勤组/部门)+ 以下考勤数据列: + +| 序号 | 字段名称 | 说明 | +|------|---------|------| +| 4 | 请假 | 按假期类型自动展开(事假/调休/病假/年假等),数据由脚本通过 `report query-leave` 单独查询 | +| 5 | 出差时长 | — | +| 6 | 外出时长 | — | +| 7 | 考勤结果 | 按天展开 | + +**对应 `--column-keywords`**:`请假,出差时长,外出时长,考勤结果` + +### 预设报表:异常报表 + +3 个基础信息列(姓名/考勤组/部门)+ 以下考勤数据列: + +> 原始配置中的 TITLE_COLUMN(如"迟到"、"早退"、"缺卡")为分组标题,脚本不支持父子列结构,已打平为叶子字段。 + +| 序号 | 字段名称 | 说明 | +|------|---------|------| +| 4 | 迟到次数 | 原属分组「迟到」 | +| 5 | 迟到时长 | 同上 | +| 6 | 严重迟到次数 | 同上 | +| 7 | 严重迟到时长 | 同上 | +| 8 | 旷工迟到次数 | 同上 | +| 9 | 早退次数 | 原属分组「早退」 | +| 10 | 早退时长 | 同上 | +| 11 | 上班缺卡次数 | 原属分组「缺卡」 | +| 12 | 下班缺卡次数 | 同上 | +| 13 | 旷工天数 | — | +| 14 | 考勤结果 | 按天展开 | + +**对应 `--column-keywords`**:`迟到次数,迟到时长,严重迟到次数,严重迟到时长,旷工迟到次数,早退次数,早退时长,上班缺卡次数,下班缺卡次数,旷工天数,考勤结果` + +### 明细(3 个基础信息列 + 10 个打卡字段列) + +**基础信息列**: + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 1 | 姓名 | `contact user get --ids` | +| 2 | 考勤组 | `attendance group search` + `filtered-get --member` 反向映射 | +| 3 | 部门 | `contact user get --ids` | + +**打卡字段列**(以打卡流水为主表,每条流水一行;通过打卡时间关联 `check result` 获取考勤时间和打卡结果): + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 4 | 考勤日期 | `check record` | +| 5 | 考勤时间 | `check result`(班次规定的上/下班时间,按打卡时间关联) | +| 6 | 打卡时间 | `check record`(实际打卡时间) | +| 7 | 打卡结果 | `check result`(正常/迟到/早退/缺卡等,按打卡时间关联) | +| 8 | 打卡地址 | `check record` | +| 9 | 打卡备注 | `check record` | +| 10 | 异常打卡原因 | `check record` | +| 11 | 打卡图片 | `check record` | +| 12 | 打卡设备 | `check record` | +| 13 | 管理员修改备注 | `check record` | + +### 签到报表(3 个基础信息列 + 11 个签到字段列 + 9 个图片列) + +**基础信息列**: + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 1 | 姓名 | `contact user get --ids` | +| 2 | 部门 | `contact user get --ids` | +| 3 | 完整部门 | `contact user get --ids` | + +**签到字段列**(每条签到记录一行,数据来源均为 `attendance checkin records`): + +| 序号 | 字段名称 | 数据来源 | +|------|---------|---------| +| 4 | 签到来源 | `checkinType` | +| 5 | 日期 | `timestamp`(毫秒时间戳转日期) | +| 6 | 时间 | `timestamp`(毫秒时间戳转时间) | +| 7 | 经度 | `longitude` | +| 8 | 纬度 | `latitude` | +| 9 | 地点 | `place` | +| 10 | 详细地址 | `detailPlace` | +| 11 | 拜访客户 | `customers` | +| 12 | 客户部门名称 | 预留(签到接口暂无此字段) | +| 13 | 工作内容 | `remark` | +| 14 | 手机标识 | `mobileId` | + +**图片列**(从 `imageList` 数组展开,最多 9 列): + +| 序号 | 字段名称 | +|------|---------| +| 15 | 图片1 | +| 16 | 图片2 | +| ... | ... | +| 23 | 图片9 | + +## 分批查询规则(脚本内部自动处理) + +| 维度 | 限制 | 脚本自动处理方式 | +|------|------|---------| +| 人数超限(月度/每日) | `query-data` 最多 20 人/次 | 自动按 5 人一批分批 | +| 人数超限(明细) | `check result` 最多 100 人/次 | 自动按 100 人一批分批 | +| 人数超限(签到) | `checkin records` 最多 100 人/次 | 自动按 50 人一批分批 | +| 时间超限(月度/每日) | `--start` 到 `--end` 不超过 32 天 | 自动按月分段 | +| 时间超限(明细) | `--start` 到 `--end` 不超过 1 个月 | 自动按月分段 | +| 时间超限(签到) | `--start` 到 `--end` 不超过 7 天 | 自动按 7 天分段 | +| 分页(明细打卡结果) | `check result` 单次最多 1000 条 | 自动翻页 | + +## 错误处理 + +| 错误 | 原因 | 处理方式 | +|------|------|---------| +| 权限错误(403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | +| userId 无效 | 用户 ID 错误或已离职 | 脚本跳过并在摘要中标注 | +| 时间区间超长 | 接口可能性能不佳 | 提示"超过 1 年的数据建议分阶段导出" | +| openpyxl 未安装 | 环境缺包 | 输出 `pip install openpyxl` 安装提示 | +| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试 | + +## 使用示例 + +### 示例 1: 团队月度汇总(默认) +**用户说**: "帮我生成研发组 4 月的考勤报表" + +```bash +# 1. 获取部门成员 +dws contact dept search --query "研发组" --format json +dws contact dept list-members --ids --format json + +# 2. 调用脚本(默认月度汇总,不传 --column-keywords) +python scripts/attendance_report_monthly.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" +``` + +### 示例 2: 加班报表(预设) +**用户说**: "帮我出一份研发组 4 月的加班报表" + +```bash +python scripts/attendance_report_monthly.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" \ + --column-keywords "加班-审批单统计,加班总时长,考勤结果" +``` + +### 示例 3: 请假报表(预设) +**用户说**: "帮我导出研发组 4 月的请假出差情况" + +```bash +python scripts/attendance_report_monthly.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" \ + --column-keywords "请假,出差时长,外出时长,考勤结果" +``` + +### 示例 4: 异常报表(预设) +**用户说**: "帮我出研发组 4 月的异常考勤报表" + +```bash +python scripts/attendance_report_monthly.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" \ + --column-keywords "迟到次数,迟到时长,严重迟到次数,严重迟到时长,旷工迟到次数,早退次数,早退时长,上班缺卡次数,下班缺卡次数,旷工天数,考勤结果" +``` + +### 示例 5: 自定义维度筛选 +**用户说**: "帮我出一份研发组 4 月的工作时长报表" + +```bash +python scripts/attendance_report_monthly.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" \ + --column-keywords "工作时长" +``` + +### 示例 6: 每日统计 +**用户说**: "帮我出一份研发组 4 月每天的出勤情况" + +```bash +python scripts/attendance_report_daily.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" +``` + +### 示例 7: 明细报表 +**用户说**: "帮我导出研发组 4 月的考勤明细" + +```bash +python scripts/attendance_report_detail.py \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" +``` + +### 示例 8: 请假记录 +**用户说**: "帮我导出研发组 4 月的请假记录" + +```bash +python scripts/attendance_report_record.py \ + --type leave \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" +``` + +### 示例 9: 出差记录 +**用户说**: "帮我导出研发组 4 月的出差记录" + +```bash +python scripts/attendance_report_record.py \ + --type trip \ + --users userId1,userId2,... \ + --start "2026-04-01" --end "2026-04-30" +``` + +### 示例 10: 补卡记录 +**用户说**: "帮我导出研发组 5 月的补卡记录" + +```bash +python scripts/attendance_report_record.py \ + --type patch \ + --users userId1,userId2,... \ + --start "2026-05-01" --end "2026-05-31" +``` + +### 示例 11: 签到报表 +**用户说**: "帮我导出研发组上周的签到记录" + +```bash +python scripts/attendance_report_checkin.py \ + --users userId1,userId2,... \ + --start "2026-05-26" --end "2026-06-01" +``` + +## 配套脚本 + +| 脚本 | 报表类型 | 数据来源 | CLI 参数 | +|------|---------|---------|---------| +| [attendance_report_detail.py](../../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` | +| [attendance_report_monthly.py](../../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_daily.py](../../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` | +| [attendance_report_record.py](../../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` | +| [attendance_report_checkin.py](../../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` | +| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — | diff --git a/skills/mono/references/products/attendance-schedule.md b/skills/mono/references/products/attendance-schedule.md new file mode 100644 index 00000000..05bcc8a3 --- /dev/null +++ b/skills/mono/references/products/attendance-schedule.md @@ -0,0 +1,590 @@ +# 考勤排班操作参考 (attendance-schedule) + +> 本文档由 `attendance.md` 路由调用。覆盖两类排班操作: +> 1. **排班导入**(写操作):当用户提到"排班"、"导入排班"、"安排班次"、"设置排班"、"调班"、"换班"、"排休"时 +> 2. **排班查询导出**(只读操作):当用户提到"查看排班"、"排班表"、"导出排班"、"排班记录"时 +> +> 不适用于:班次定义查询(用 `attendance class search`)、考勤组配置(用 `attendance group get`)。 + +## 强制门禁(必须先读完本文档才能执行) + +**任何排班操作都必须经过本文档定义的工作流,严禁绕过本文档直接调用 `dws attendance schedule import` 命令。** + +违反将出现以下任一问题: +1. 未按"阶段 1"确认考勤组 → 把固定班制考勤组当排班制操作,接口报错 +2. 未按"阶段 3"校验班次 → 传入不属于该考勤组的班次 ID,导致排班数据错乱 +3. 未按"阶段 4"回显确认 → 用户未看到排班内容就直接执行,排错了无法回退 +4. 未按"阶段 2"解析人员 → 传入错误的 userId,导致排班到错误的人 +5. 未经用户确认就执行排班 → 排班是写操作,一旦执行就会覆盖原有排班 + +**执行前自检(必须能在心中回答)**: +- [ ] 考勤组是排班制(TURN)吗? +- [ ] 员工都属于该考勤组吗? +- [ ] 班次都属于该考勤组可用的班次吗? +- [ ] 用户已经确认了排班内容吗? + +如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。 + +**前提**:当前用户必须是钉钉考勤管理员,否则排班接口返回权限错误。 + +## 业务约束(必须深刻理解) + +> **这两条约束是排班的根基,贯穿整个工作流的每一步。** + +1. **用户只能属于一个考勤组**:每个员工有且只有一个考勤组,不存在"选择考勤组"的场景。直接通过 `dws attendance rules` 查询即可唯一确定。 +2. **排班只能排考勤组关联的班次**:考勤组绑定了固定的班次列表(`shiftVOList`),排班时只能从这些班次中选择,不能使用企业其他考勤组的班次,更不能编造班次。 + +**由此推导出的执行顺序**:必须先查清考勤组和它关联的班次,再去收集日期、人员等其他参数。 + +## 核心原则 + +Agent 解析用户意图(考勤组、员工、日期范围、班次安排),完成校验后,**调用 Python 脚本执行排班**。 +- **先查后排**:任何排班操作的第一步都是查询考勤组及其关联班次,拿到真实数据后再进行后续参数收集 +- **脚本自包含**:考勤组校验、班次校验、员工校验、回显确认、调用排班 API 全部由脚本内部完成 +- **Agent 职责**:先查考勤组和关联班次,再解析用户意图、获取必要的 ID(员工 userId)、组装脚本参数 +- **脚本职责**:二次校验、回显排班表格、等待用户确认、执行排班、输出结果摘要 +- 排班是**写操作**,必须经过回显确认后才能执行 + +## 严格禁止 (NEVER DO) + +- **禁止直接调用 `dws attendance schedule import`**,必须通过脚本执行 +- 禁止凭历史记忆复用任何 ID(考勤组 ID、班次 ID、userId),必须从当次命令返回值中提取 +- 禁止在未确认考勤组类型为排班制(TURN)的情况下执行排班 +- 禁止在班次未经校验的情况下执行排班 +- 禁止跳过用户确认直接执行排班 +- **禁止在未向用户展示完整排班明细表格(含每天的班次名称)的情况下弹出确认卡片**。用户必须先看到"谁、哪天、上什么班"才能做出确认决策 +- 禁止编造任何字段值或用户姓名 +- 禁止直接输出裸 userId,脚本已内置 userId → 姓名转换 +- **禁止直接输出裸 classId 数字**,必须展示班次名称(如"早班"),用户不理解 classId 是什么 + +## 严格要求 (MUST DO) + +- 所有 `dws` 命令必须携带 `--format json` +- 必须先确认考勤组类型为 TURN(排班制),否则拒绝执行 +- 必须通过考勤组详情获取绑定班次列表,校验班次 ID 属于该考勤组 +- 必须在执行排班前向用户回显排班内容并获得确认 +- 任何接口失败必须向用户清晰报错,禁止静默吞掉 + +## 涉及工具 + +| 工具 | 用途 | 安全等级 | +|------|------|---------| +| `dws attendance group search` | 搜索考勤组(按名称/类型) | 只读 | +| `dws attendance group get` | 查询考勤组全量信息(含绑定班次列表) | 只读 | +| `dws attendance group filtered-get` | 查询考勤组详情(成员列表) | 只读 | +| `dws attendance class search` | 查询班次列表(ID→名称映射) | 只读 | +| `dws attendance class get` | 查询班次详情 | 只读 | +| `dws attendance schedule import` | 导入排班记录(**仅由脚本内部调用**) | 写操作(危险) | +| `dws attendance schedule get` | 查询现有排班记录 | 只读 | +| `dws aisearch person` | 按姓名搜索用户获取 userId | 只读 | +| `dws contact user get` | 批量查询 userId → 用户信息 | 只读 | +| `dws contact dept search` | 搜索部门获取 deptId | 只读 | +| `dws contact dept list-members` | 获取部门成员 userId 列表 | 只读 | + +## 意图判断 + +### 排班操作类型 + +| 用户说 | 操作类型 | 处理方式 | +|--------|---------|---------| +| "帮我给研发组排下周的班" / "排班" / "安排班次" | 批量排班 | 走本文档工作流 | +| "帮我把张三下周一改成早班" / "调班" / "换班" | 单人调班 | 走本文档工作流(单人模式) | +| "帮我把李四下周三排休" | 排休 | 走本文档工作流(isRest=Y) | + +### 易混淆场景 + +| 用户说 | 应路由到 | +|--------|---------| +| "查看下周的排班" / "排班表" / "导出排班" / "导出排班表" / "XX考勤组的排班" | **本文档「排班查询导出工作流」**(走脚本) | +| "有哪些班次" / "班次列表" | `dws attendance class search`(查询班次定义) | +| "我属于哪个考勤组" | `dws attendance rules`(查询考勤规则) | +| "导出考勤报表" / "导出考勤" / "考勤明细" / "出勤汇总" (**不含"排班"二字**) | `attendance-report.md`(报表 skill) | + +> **关键区分**:"导出排班表" ≠ "导出考勤报表"。判断标准:句中含"排班"→ 本文档;不含"排班"且说的是"考勤报表/考勤数据/出勤统计" → `attendance-report.md`。 + +## 工作流 + +### 阶段 0: 先查考勤组和关联班次,再收集缺失参数 + +> **核心逻辑:先查后问。** 用户只属于一个考勤组,排班只能排该考勤组关联的班次。所以第一步永远是查清考勤组和它的班次,拿到真实数据后再向用户收集其他信息。 + +**步骤 0a — 查询考勤组(必须最先执行)**: + +```bash +# 自动获取当前用户的考勤组(用户只属于一个考勤组,无需选择) +dws attendance rules --date <今天日期> --format json +# → 从返回中提取 groupId +``` + +如果是给指定员工排班,先查该员工的 userId,再查其考勤组。 + +**步骤 0b — 查询考勤组详情和关联班次(必须在 ask_question 之前完成)**: + +```bash +# 获取考勤组详情(含绑定的班次列表) +dws attendance group get --group-id --format json +# → 校验 groupVO.type 必须为 TURN(排班制) +# → 从 groupVO.shiftVOList 提取关联的班次 +``` + +**提取结果**: +- 考勤组名称:`groupVO.name` +- 考勤组类型:`groupVO.type`(必须为 TURN) +- 关联班次列表:`groupVO.shiftVOList[].shiftSetting.{shiftId, shiftName}` + +**步骤 0c — 收集缺失参数(ask_question 卡片交互)**: + +拿到考勤组和关联班次后,再向用户收集缺失的参数。排班所需的四个参数: +- **考勤组**:已在 0a 自动获取,无需询问 +- **班次**:已在 0b 获取关联班次列表,展示给用户选择 +- **员工范围**(必填):指定员工姓名 / 部门 / 考勤组全员 / "给我排班" +- **日期范围**(必填):具体日期 / 日期范围(如"下周"、"5月19日到5月23日") + +**只收集真正缺失的参数**,用户已经提供的不要重复询问。将缺失参数**合并到一次 `ask_question` 调用中**。 + +示例:用户说"帮我排班",Agent 应先自动查询考勤组和关联班次(步骤 0a + 0b),然后只询问日期范围和班次: + + ``` + // ===== 步骤 0a + 0b 已完成,此时你已经拿到了以下真实数据 ===== + // groupId = 实际的考勤组ID + // groupName = 实际的考勤组名称 + // shiftVOList = 考勤组关联的班次列表(来自 dws attendance group get 的返回) + + // 从 shiftVOList 构建班次选项(伪代码): + shiftOptions = [] + for each shift in groupVO.shiftVOList: + shiftOptions.append({ id: String(shift.shiftSetting.shiftId), label: shift.shiftSetting.shiftName }) + shiftOptions.append({ id: "rest", label: "排休" }) + + // 如果考勤组只关联了一个班次,直接使用,不需要询问用户 + if shiftVOList.length == 1: + selectedShift = shiftVOList[0] // 自动选定,跳过班次选择 + + // 构建日期选项(必须填入实际计算的日期): + todayStr = 当天日期(YYYY-MM-DD) + thisWeekEnd = 本周日日期 + nextWeekStart = 下周一日期 + nextWeekEnd = 下周日日期 + + ask_question({ + title: "排班参数确认", + questions: [ + { + id: "date_range", + prompt: "请选择排班日期范围", + options: [ + { id: "this_week", label: "本周剩余时间(" + todayStr + "-" + thisWeekEnd + ")" }, + { id: "next_week", label: "下周(" + nextWeekStart + "-" + nextWeekEnd + ")" }, + { id: "custom", label: "自定义日期范围" } + ] + }, + { + id: "shift", + prompt: "请选择班次(以下为您考勤组「" + groupName + "」关联的班次)", + options: shiftOptions // 直接使用上面从 shiftVOList 动态构建的选项,严禁替换为任何硬编码值 + } + ] + }) + ``` + + **⚠️ 班次选项严禁硬编码**:上述伪代码中的 `shiftOptions` 必须在运行时从 `shiftVOList` 动态生成。禁止在 `ask_question` 中写入任何固定的班次名称(如"早班"、"晚班"、"正常班"、"全部排XX班"等)。如果你发现自己在 options 里手写班次名,说明你做错了。 + + **动态选项原则(严格执行)**: + - 班次选项**必须且只能**来自考勤组的 `shiftVOList`,**严禁编造任何班次名称**(如"正常班"、"早班"、"晚班"、"全部排XX班"等都是编造) + - 每个班次选项的 `id` 必须是 `shiftVOList` 中的真实 `shiftId`,`label` 必须是真实的 `shiftName` + - 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class search` 按 ID 精确查询(不是全局搜索) + - **只收集缺失的参数**:用户已经提供的参数不要重复询问 + - **如果考勤组只有一个班次,直接使用该班次,不需要询问用户选择** + - **禁止用 `class search` 全局搜索来给用户展示班次选项**——全局班次列表包含不属于该考勤组的班次,用户选了也会被校验拒绝 + +### 阶段 1: 确认考勤组(必须为排班制) + +> **业务事实:用户只属于一个考勤组。** 不存在"选考勤组"的场景,直接通过 `dws attendance rules` 自动获取即可。只有在用户明确指定了一个考勤组名称时,才用 `group search` 按名称确认。 + +**方式 A — 自动获取(默认方式,适用于绝大多数场景)**: +```bash +# 用户只属于一个考勤组,直接查询即可确定 +dws attendance rules --date <今天日期> --format json +# → 返回中提取 groupId,然后用 group get 获取详情 +dws attendance group get --group-id --format json +``` + +**方式 B — 用户明确指定考勤组名称时**: +```bash +dws attendance group search --query "<考勤组名称>" --type TURN --format json +``` + +**校验规则**: +1. 考勤组类型必须为 **TURN(排班制)**,如果是 FIXED(固定班制)或 NONE(自由工时),拒绝并提示"该考勤组不是排班制,无法进行排班操作" +2. 确认考勤组后,**必须立即获取其关联班次**(`groupVO.shiftVOList`),后续所有班次选项都从这里取 + +**提取信息**:考勤组 ID(`groupId`)、考勤组名称(`name`)、**关联班次列表(`shiftVOList`)** + +### 阶段 2: 获取员工列表 + +**场景 A — 指定员工姓名**: +```bash +dws aisearch person --keyword "<员工姓名>" --dimension name --format json +``` + +**场景 B — 按部门查询**: +```bash +dws contact dept search --query "<部门名>" --format json +dws contact dept list-members --ids --format json +``` + +**场景 C — 考勤组全员**: +```bash +dws attendance group filtered-get --group-id --member --format json +``` + +**场景 D — 用户已给 userId 列表**:直接跳过本步。 + +### 阶段 3: 校验班次(必须是考勤组关联的班次) + +> **业务约束:排班只能排考勤组关联的班次。** 阶段 0/1 已经通过 `dws attendance group get` 拿到了 `shiftVOList`,本阶段直接使用该数据校验,**不需要也不应该再调用 `class search` 全局搜索**。 + +**班次数据来源**(已在阶段 0 或阶段 1 获取): +- `groupVO.shiftVOList[].shiftSetting.shiftId` — 班次 ID +- `groupVO.shiftVOList[].shiftSetting.shiftName` — 班次名称 + +**禁止调用 `dws attendance class search` 全局搜索班次**——全局搜索会返回不属于该考勤组的班次,即使用户指定了班次名称,也必须在 `shiftVOList` 中匹配,而不是全局搜索。 + +**校验规则**: +1. 用户在 `ask_question` 卡片中选择的班次,其 `shiftId` 必须存在于 `shiftVOList` 中(卡片选项本身就是从 `shiftVOList` 构建的,所以天然满足) +2. 如果用户通过自然语言指定了班次名称(如"排早班"),必须在 `shiftVOList` 中**按名称模糊匹配**,找到对应的 `shiftId` +3. 如果用户指定的班次不在 `shiftVOList` 中,**必须拒绝**,并列出该考勤组关联的全部班次让用户重新选择 +4. 仅当 `shiftVOList` 为空时,才降级从 `groupVO.classIds` + `dws attendance class get` 按 ID 精确查询(仍然不是全局搜索) + +**提取信息**:班次 ID(`classId` / `shiftId`)、班次名称(`shiftName`) + +### 阶段 4: 回显排班内容并使用 ask_question 卡片确认 + +> **[硬性门禁]** 必须先用普通文本向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),用户看到排班明细后,才能弹出 `ask_question` 确认卡片。 +> **禁止在用户还不知道"谁、哪天、上什么班"的情况下就弹确认卡片**——这等于让用户盲签,体验极差。 + +**步骤 4a — 展示排班明细(必须在确认卡片之前)**: + +在调用 `ask_question` 之前,**必须先**用普通文本向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字: + +``` +排班预览 + +考勤组: <考勤组名称>(ID: ) +排班日期: ~ + +| 员工姓名 | 日期 | 星期 | 班次 | 是否排休 | +|---------|------|------|------|---------| +| 张三 | 2026-05-19 | 周一 | 早班 09:00-18:00 | 否 | +| 张三 | 2026-05-20 | 周二 | 早班 09:00-18:00 | 否 | +| 张三 | 2026-05-21 | 周三 | 排休 | 是 | +| 李四 | 2026-05-19 | 周一 | 晚班 18:00-02:00 | 否 | +| ... | ... | ... | ... | ... | + +共 条排班记录 +``` + +**自查清单(展示表格前必须确认)**: +- [ ] 表格中有员工姓名(不是 userId) +- [ ] 表格中有具体日期和星期几 +- [ ] 表格中有班次名称(不是 classId 数字) +- [ ] 排休的记录标注了"排休" +- [ ] 表格涵盖了所有待排班的员工和日期 + +**步骤 4b — 使用 `ask_question` 卡片确认(必须在展示表格之后)**: + +``` +ask_question({ + title: "排班执行确认", + questions: [ + { + id: "confirm_execute", + prompt: "以上排班将覆盖所选日期的现有排班记录,确认执行吗?", + options: [ + { id: "yes", label: "确认执行" }, + { id: "no", label: "取消" } + ] + } + ] +}) +``` + +**处理用户选择**: +- 用户选择 **"确认执行"** → 进入阶段 5 +- 用户选择 **"取消"** → 终止流程,提示"已取消排班操作" + +### 阶段 5: 调用脚本执行排班 + +```bash +python scripts/attendance_schedule_import.py \ + --group-id \ + --schedules '' \ + --confirm +``` + +参数说明: +- `--group-id`(必填):考勤组 ID +- `--schedules`(必填):排班记录 JSON 数组,每条记录包含 `userId`、`workDate`、`classId`、`isRest` +- `--confirm`(必填):表示用户已确认,脚本收到此标志才会执行排班 + +脚本内部自动处理: +1. 二次校验考勤组类型(必须为 TURN) +2. 从考勤组详情提取绑定班次,二次校验班次 ID 属于该考勤组 +3. 格式化 workDate 为 `yyyy-MM-dd HH:mm:ss` +4. 调用 `dws attendance schedule import` 执行排班 +5. 输出执行结果摘要(含全部排班明细) + +### 阶段 6: 返回结果给用户 + +- 将脚本 stdout 输出的摘要信息原样转告用户 +- 如果脚本输出 warning,原样转告用户 +- 如果执行失败,将 stderr 错误信息转告用户 + +## 排班记录 JSON 格式 + +每条排班记录的字段说明: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `userId` | string | 是 | 员工的 userId | +| `workDate` | string | 是 | 排班日期,格式 YYYY-MM-DD | +| `classId` | int | 是 | 班次 ID(从 `class search` 获取) | +| `isRest` | string | 是 | 是否排休,`Y`=排休 / `N`=正常上班 | + +排休时 `classId` 传 0,`isRest` 传 `Y`。 + +## API 返回结构注意事项(Agent 必读) + +> 以下是实际执行中多次踩坑的关键数据结构说明。**禁止凭直觉假设字段在顶层**,必须按本节描述的嵌套路径提取。 + +### `dws attendance group get` 返回结构 + +``` +run_dws 解包后的结构(unwrap_result 去掉 success/result 包装后): +{ + "groupVO": { ← 关键!type/name/classIds 等字段在这一层 + "type": "TURN", ← 考勤组类型 + "name": "研发组", + "classIds": [1290384739, ...], ← 绑定的班次 ID 列表 + "shiftVOList": [ ← 排班制特有,班次详情 + { + "shiftSetting": { + "shiftId": 1290384739, ← 班次 ID(与 classIds 对应) + "shiftName": "早班 09:00-18:00" + } + } + ], + "selectedClass": [...], ← 部分环境使用此字段 + ... + }, + ...其他顶层字段... +} +``` + +**提取规则**: +- 考勤组类型:`result["groupVO"]["type"]` +- 考勤组名称:`result["groupVO"]["name"]` +- 绑定班次 ID 列表:`result["groupVO"]["classIds"]` +- 班次名称:`result["groupVO"]["shiftVOList"][N]["shiftSetting"]["shiftName"]` +- **禁止从 result 顶层直接取 type/name/classIds,那里没有这些字段** + +### `dws attendance class search` 返回结构 + +``` +run_dws 解包后可能为以下之一: +1. 直接 list[dict]: [{id, name, ...}, ...] +2. {"data": [...]} 或 {"items": [...]} 或 {"classList": [...]} +``` + +**注意**:如果 `class search` 返回 0 条记录,不一定是错误——可能是当前账号没有班次管理权限。此时从 `group get` 的 `shiftVOList` 中也可获取班次名称。 + +### `dws aisearch person` 搜索同名问题 + +同一个姓名可能返回**多个不同 userId**(如主管理员账号 vs 子管理员账号)。必须通过以下方式确认正确的 userId: +1. 检查目标考勤组的成员列表:`dws attendance group filtered-get --group-id --member` +2. 取成员列表中存在的那个 userId + +**禁止直接取搜索结果的第一条 userId,必须与考勤组成员列表交叉验证。** + +### `dws contact user get` 可能的权限错误 + +`resolve_user_names`(userId→姓名转换)可能遇到 `SECURITY_CHECK_INVOKE_FAILED` 错误。这只影响**展示层**,不影响排班数据的正确性: +- 脚本已内置降级处理:权限失败时直接使用 userId 替代姓名 +- **不要因为姓名获取失败就中止排班流程** + +### Agent 常见错误模式(严禁) + +| 错误做法 | 正确做法 | +|------------|------------| +| `result.get("type")` 从顶层取类型 | `result["groupVO"]["type"]` | +| `result.get("classIds")` 从顶层取班次 | `result["groupVO"]["classIds"]` 或 `result["groupVO"]["shiftVOList"]` | +| 用 `python3 -c "..."` inline 脚本解析 JSON | 调用已有的 Python 脚本(`attendance_schedule_import.py`) | +| 人名搜到多个结果直接取第一个 | 与考勤组成员列表交叉验证 | +| 姓名获取失败就中止流程 | 降级用 userId 展示,继续执行排班 | +| 直接调用 `dws attendance schedule import` | 必须通过 `attendance_schedule_import.py` 脚本 | + +## 错误处理 + +| 错误 | 原因 | 处理方式 | +|------|------|---------| +| 权限错误(403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 | +| 考勤组不是排班制 | 考勤组类型为 FIXED 或 NONE | 提示"该考勤组不是排班制,无法排班" | +| 班次不在可用列表中 | classId 无效 | 列出可用班次让用户重新选择 | +| userId 无效 | 用户 ID 错误或已离职 | 提示具体哪个用户无效 | +| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户 | +| SECURITY_CHECK_INVOKE_FAILED | userId→姓名转换权限不足 | 仅影响展示,降级用 userId,不中止流程 | +| class search 返回空列表 | 账号无班次管理权限 | 从 `group get` 的 `shiftVOList` 提取班次名称 | + +## 使用示例 + +### 示例 1: 给指定员工排班 +**用户说**: "帮我给张三下周一到周五排早班,考勤组是研发组" + +```bash +# 1. 确认考勤组并获取关联班次(先查后排) +dws attendance group search --query "研发组" --type TURN --format json +# → 拿到 groupId +dws attendance group get --group-id --format json +# → 从 groupVO.shiftVOList 获取关联班次列表 +# → 在 shiftVOList 中匹配"早班",拿到对应的 shiftId 作为 classId +# ⚠️ 禁止用 class search 全局搜索班次 + +# 2. 获取员工 userId +dws aisearch person --keyword "张三" --dimension name --format json + +# 3. 回显确认(Agent 向用户展示排班表格) +# ... 用户确认 ... + +# 4. 调用脚本执行 +python scripts/attendance_schedule_import.py \ + --group-id 123456 \ + --schedules '[{"userId":"user001","workDate":"2026-05-19","classId":789,"isRest":"N"},{"userId":"user001","workDate":"2026-05-20","classId":789,"isRest":"N"},{"userId":"user001","workDate":"2026-05-21","classId":789,"isRest":"N"},{"userId":"user001","workDate":"2026-05-22","classId":789,"isRest":"N"},{"userId":"user001","workDate":"2026-05-23","classId":789,"isRest":"N"}]' \ + --confirm +``` + +### 示例 2: 给员工排休 +**用户说**: "帮我把李四下周三排休" + +```bash +# 1. 先查考勤组和关联班次(即使排休也需要确认考勤组) +dws attendance rules --date 2026-05-15 --format json +# → 拿到 groupId +dws attendance group get --group-id --format json +# → 确认是排班制(TURN) + +# 2. 获取员工 userId +dws aisearch person --keyword "李四" --dimension name --format json + +# 3. 回显确认 → 用户确认 → 执行 +python scripts/attendance_schedule_import.py \ + --group-id 123456 \ + --schedules '[{"userId":"user002","workDate":"2026-05-21","classId":0,"isRest":"Y"}]' \ + --confirm +``` + +### 示例 3: 部门批量排班 +**用户说**: "帮我给研发部全员下周排早班" + +```bash +# 1. 先查考勤组并获取关联班次(先查后排) +dws attendance rules --date 2026-05-15 --format json +# → 拿到 groupId +dws attendance group get --group-id --format json +# → 从 groupVO.shiftVOList 中匹配"早班",拿到 shiftId +# ⚠️ 禁止用 class search 全局搜索班次 + +# 2. 获取部门成员 +dws contact dept search --query "研发部" --format json +dws contact dept list-members --ids --format json + +# 3. 回显确认 → 用户确认 → 执行 +python scripts/attendance_schedule_import.py \ + --group-id 123456 \ + --schedules '[...]' \ + --confirm +``` + +## 配套脚本 + +| 脚本 | 用途 | CLI 参数 | +|------|------|---------| +| [attendance_schedule_import.py](../../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` | +| [attendance_schedule_export.py](../../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` | + +--- + +## 排班查询导出工作流 + +> 当用户提到"查看排班"、"排班表"、"导出排班"、"排班记录"时,走此工作流。 +> **禁止直接调用 `dws attendance schedule get`**,必须通过脚本执行,脚本自动处理分批、姓名转换、班次名称转换、排班表格式输出。 + +### 查询导出 — 阶段 1: 参数收集 + +1. **员工范围**(必填):需获取 userId 列表 + - 指定员工姓名 → `dws aisearch person` 获取 userId + - 指定部门 → `dws contact dept search` + `dws contact dept list-members` + - 指定考勤组全员 → `dws attendance group filtered-get --member` + - 用户已给 userId 列表 → 直接使用 +2. **日期范围**(必填):开始日期 ~ 结束日期(YYYY-MM-DD) + - 用户说"下周" → 计算下周一到周日 + - 用户说"本月" → 计算本月 1 日到月末 + - 任何缺失信息必须追问 + +### 查询导出 — 阶段 2: 调用脚本 + +```bash +python scripts/attendance_schedule_export.py \ + --users \ + --start \ + --end \ + [--output ] +``` + +参数说明: +- `--users`(必填):userId 列表,逗号分隔 +- `--start`(必填):开始日期,格式 YYYY-MM-DD +- `--end`(必填):结束日期,格式 YYYY-MM-DD +- `--output`(可选):输出文件路径,默认 `attendance_schedule__.xlsx` + +脚本内部自动处理: +1. **分批查询**:超过 20 人自动分批调用 `dws attendance schedule get` +2. **班次名称转换**:classId → className(优先从记录中提取,缺失时回退 class search) +3. **姓名转换**:userId → 员工姓名 +4. **排班表格式**:日历表(行=员工,列=日期,单元格=班次名称) +5. **Excel 输出**:钉钉风格美化排版 + +### 查询导出 — 阶段 3: 返回结果给用户 + +- 将脚本 stdout 输出的摘要信息(人数、日期、记录数、预览表格)原样转告用户 +- 提醒用户完整排班表已导出到 Excel 文件 +- 如果执行失败,将 stderr 错误信息转告用户 + +### 查询导出示例 + +**用户说**: "帮我导出研发组下周的排班表" + +```bash +# 1. 获取考勤组成员 +dws attendance group search --query "研发组" --format json +dws attendance group filtered-get --group-id --member --format json + +# 2. 调用脚本导出 +python scripts/attendance_schedule_export.py \ + --users user001,user002,user003 \ + --start 2026-05-18 \ + --end 2026-05-24 +``` + +**用户说**: "帮我查看张三和李四本月的排班" + +```bash +# 1. 获取 userId +dws aisearch person --keyword "张三" --dimension name --format json +dws aisearch person --keyword "李四" --dimension name --format json + +# 2. 调用脚本导出 +python scripts/attendance_schedule_export.py \ + --users user001,user002 \ + --start 2026-05-01 \ + --end 2026-05-31 +``` diff --git a/skills/mono/references/products/attendance-vacation.md b/skills/mono/references/products/attendance-vacation.md new file mode 100644 index 00000000..6c59e739 --- /dev/null +++ b/skills/mono/references/products/attendance-vacation.md @@ -0,0 +1,140 @@ +# 假期余额导出参考 (attendance-vacation) + +> 本文档由 `attendance.md` 路由调用。当用户提到“导出假期余额”、“假期余额列表”、“所有假期规则余额”、“假期余额 Excel”、“年假/病假/调休余额导出”等诉求时,必须先阅读本文档,再调用配套脚本。 + +## 强制门禁 + +**任何调用 `attendance_vacation_balance.py` 的请求,都必须经过本文档定义的工作流,禁止只凭脚本路径或 `--help` 自行拼命令。** + +执行前必须确认: +- 人员范围:指定员工 / 部门 / 多部门;缺失时必须追问 +- 导出范围:默认导出所有假期规则余额;用户指定“年假/病假/调休”等时才传 `--leave-keywords` +- 输出形式:生成 Excel,不在对话中粘贴完整表格 + +## 核心原则 + +Agent 只负责解析人员范围并获取 userId 列表;假期规则查询、`leaveCode` 解析、假期规则单位解析、余额查询、字段解析、用户信息补齐、Excel 生成都由脚本完成。 + +`--leave-keywords` 是 `attendance_vacation_balance.py` 的脚本入参,只用于按假期名称筛选导出列;**不是** `dws attendance vacation balance` 的入参。`vacation balance` 当前只支持批量 `--users` 和单个 `--leave-code`,因此脚本必须先获取假期规则列表,再按每个匹配到的 `leaveCode` 分别查询余额。 + +脚本输出结构参考钉钉假期余额列表: +- 每名员工一行 +- 基础列固定:`姓名`、`部门`、`入职时间`、`首次工作时间` +- 假期规则横向动态展开为多列,表头必须携带 `vacation types` 返回的规则单位,例如 `年假(天)`、`病假(天)`、`调休(小时)` +- 特殊值统一展示为 `不限制余额`、`不适用`、`未设置` +- 当某个假期规则 `leaveCode` 查询余额时接口返回“假期类型没有余额”类业务错误,表示该规则不限制余额,脚本应为该规则列填充 `不限制余额` +- 当余额记录中返回 `visible=false`,表示该员工不适用该假期规则,脚本应为该员工 + 该规则单元格填充 `不适用` +- 当接口返回“员工未设置入职时间”或“员工未设置首次参加工作时间”类业务错误,表示该假期规则依赖员工时间字段且当前员工缺失配置,脚本应为该员工 + 该规则单元格填充 `不适用` +- 当 `vacation types` 返回假期规则 `source=external`,表示该规则由开放接口写入;若这类外部规则调用余额接口失败且不是权限错误,脚本不应阻断导出,应为该员工 + 该规则单元格填充 `外部规则暂无余额,需通过接口初始化更新余额` + +## 严格禁止 (NEVER DO) + +- 禁止凭历史记忆复用 userId、deptId、leaveCode +- 禁止 Agent 手工汇总、转置或目测假期余额 +- 禁止 Agent 自行只查单个 `leave-code` 再声称是“所有假期规则余额”;如需导出所有假期规则余额,必须交由脚本按假期规则列表逐个 `leaveCode` 查询并汇总 +- 禁止直接输出裸 userId;脚本会通过 `contact user get` 转换姓名和部门 +- 禁止把 Excel 明细内容完整贴在对话里,只返回路径和摘要 + +## 严格要求 (MUST DO) + +- 所有 `dws` 命令必须携带 `--format json` +- 假期规则名称`--leave-keywords`与假期规则`leaveCode`的映射必须从 `vacation types` 实时建立,禁止硬编码 +- 假期规则单位必须从 `vacation types` 实时读取,优先使用 `leaveViewUnit` 等展示单位字段,并在 Excel 表头中展示为 `假期名称(单位)` +- 假期规则来源必须从 `vacation types` 实时读取;当 `source=external` 时按外部接口写入规则处理 +- 任何接口失败必须向用户清晰报错,禁止静默吞掉 +- `vacation balance` 返回“假期类型没有余额”时不作为致命错误处理,应按该假期规则 `leaveCode` 生成 `不限制余额` +- `vacation balance` 返回员工维度 `visible=false` 时不作为查询失败处理,应按该员工 + 假期规则 `leaveCode` 生成 `不适用` +- `vacation balance` 返回“员工未设置入职时间”或“员工未设置首次参加工作时间”时不作为查询失败处理,应按该员工 + 假期规则 `leaveCode` 生成 `不适用` +- 对 `source=external` 的外部假期规则,`vacation balance` 查询失败且不是权限错误时不作为导出失败处理,应按该员工 + 假期规则 `leaveCode` 生成 `外部规则暂无余额,需通过接口初始化更新余额` + +## 涉及工具 + +| 工具 | 用途 | 安全等级 | +|------|------|---------| +| `dws attendance vacation types` | 获取当前可见的假期规则清单,用于建立假期名称/关键词 → `leaveCode` 的映射,读取 `leaveViewUnit` 等规则单位、`source` 规则来源,并决定 Excel 假期列顺序 | 只读 | +| `dws attendance vacation balance` | 按批量 `--users` + 单个 `--leave-code` 查询员工假期余额;由脚本按匹配到的 `leaveCode` 逐个调用 | 只读 | +| `dws aisearch person` | 按姓名搜索用户获取 userId(搜人首选) | 只读 | +| `dws contact user get` | 批量查询 userId → 用户信息(姓名/部门/入职时间等),用于 Excel 基础列补齐 | 只读 | +| `dws contact dept search` | 搜索部门获取 deptId | 只读 | +| `dws contact dept list-members` | 获取部门成员 userId 列表 | 只读 | + +## 工作流 + +### 阶段 0:参数解析 + +1. 识别人员范围: + - 指定员工姓名:用 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 获取 userId + - 指定部门:用 `dws contact dept search --query "<部门名>" --format json`,再用 `dws contact dept list-members --ids --format json` 获取成员 + - 已提供 userId:直接使用 +2. 识别假期列范围: + - 未指定假期类型:导出所有假期规则余额,不传 `--leave-keywords` + - 指定假期类型:传 `--leave-keywords "年假,病假"` + +### 阶段 1: 获取完整人员列表 + +**场景 A — 指定员工姓名**: +```bash +dws aisearch person --keyword "<员工姓名>" --dimension name --format json +``` + +**场景 B — 按部门查询**: +```bash +dws contact dept search --query "<部门名>" --format json +dws contact dept list-members --ids --format json +``` + +**场景 C — 多个部门**: 对每个部门分别执行 B,汇总去重。 + +**场景 D — 全公司**: 暂不支持,引导用户指定部门。 + +**场景 E — 用户已给 userId 列表**: 直接跳过本步。 + +### 阶段 2:调用脚本生成 Excel + +```bash +python scripts/attendance_vacation_balance.py \ + --users ,,... \ + [--leave-keywords "年假,病假,调休"] \ + [--out 假期余额列表.xlsx] +``` + +脚本内部自动执行: +1. `dws attendance vacation types --format json` 获取假期规则列表、`leaveCode`、展示单位、规则来源 `source` 和列顺序 +2. 对匹配到的每个假期规则,调用 `dws attendance vacation balance --users <批量用户> --leave-code <单个leaveCode> --format json` 查询余额;`vacation balance` 不支持 `--leave-keywords` +3. 处理特殊业务返回: + - 返回“假期类型没有余额”类业务错误:该 `leaveCode` 对应列填充 `不限制余额` + - 返回员工维度 `visible=false`:该员工 + 该 `leaveCode` 单元格填充 `不适用` + - 返回“员工未设置入职时间”或“员工未设置首次参加工作时间”:该员工 + 该 `leaveCode` 单元格填充 `不适用` + - `source=external` 的外部假期规则查询失败且不是权限错误:该员工 + 该 `leaveCode` 单元格填充 `外部规则暂无余额,需通过接口初始化更新余额` +4. `dws contact user get --ids <批量用户> --format json` 获取姓名、部门等信息 +5. 生成 Excel:`attendance_vacation_balance_.xlsx` + +### 阶段 3:返回结果 + +向用户返回脚本 stdout 摘要即可,必须包含: +- 输出文件路径 +- 员工数量 +- 假期规则列数 +- 如传了 `--leave-keywords`,说明筛选关键词 + +不要粘贴 Excel 全量内容。 + +## 错误处理 + +| 错误 | 处理方式 | +|------|---------| +| 权限不足 | 提示当前账号无权查询目标员工假期余额,需管理员或管理范围权限 | +| 人员范围缺失 | 追问员工或部门,禁止猜测 | +| 无假期规则 | 提示未匹配到假期规则,建议先执行 `dws attendance vacation types --format json` 验证 | +| 假期类型没有余额 | 不作为失败返回;按对应假期规则 `leaveCode` 填充 `不限制余额` | +| `visible=false` | 不作为失败返回;按对应员工 + 假期规则 `leaveCode` 填充 `不适用` | +| 员工未设置入职时间 / 首次参加工作时间 | 不作为失败返回;说明该规则依赖员工时间字段,按对应员工 + 假期规则 `leaveCode` 填充 `不适用` | +| 外部假期规则查询失败 | 当假期规则 `source=external` 且失败不是权限错误时,不作为导出失败;按对应员工 + 假期规则 `leaveCode` 填充 `外部规则暂无余额,需通过接口初始化更新余额` | +| openpyxl 缺失 | 提示执行 `pip install openpyxl` | +| 接口返回结构不确定 | 使用脚本 `--inspect` 重新执行一次,查看首条原始结构 | + +## 配套脚本 + +| 脚本 | 场景 | CLI 参数 | +|------|------|---------| +| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` | diff --git a/skills/mono/references/products/attendance.md b/skills/mono/references/products/attendance.md index c4098d41..c826339e 100644 --- a/skills/mono/references/products/attendance.md +++ b/skills/mono/references/products/attendance.md @@ -1,28 +1,591 @@ # 考勤 (attendance) 命令参考 -> **【命令合法性 — 必读】** 当前 dws 只提供以下 4 个考勤命令组,其余命令(check / checkin / class / group settings / adjustment / overtime / schedule / approve / selfsetting / globalsetting / report / vacation / boss-check 等)均**不存在**,调用会返回 `unknown command`: -> -> | 命令 | 用途 | -> |------|------| -> | `attendance rules` | 查询考勤组与考勤规则(我属于哪个考勤组、打卡范围、弹性工时) | -> | `attendance record get` | 查询某个人某天的考勤打卡详情 | -> | `attendance shift list` | 批量查询多名员工在某日期范围内的班次 | -> | `attendance summary` | 查询某个人的考勤统计摘要(周/月) | +> **【开源版命令可用性提示】** 当前开源 dws 已落地 P0 5 条命令:`attendance check result`、`attendance check record`、`attendance group search`、`attendance vacation balance`、`attendance vacation types`。文档中其余 P1 阶段命令(`class` / `overtime` / `adjustment` / `group settings` / `report` / `schedule` / `boss-check` 等共 28 条)暂未在当前开源二进制暴露,调用会返回 `unknown command`,将在后续批次落地。 -> **【日期计算规则】** 所有含日期参数的命令均适用,禁止随意猜测日期范围,必须按下表精确计算: +> **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用** > -> | 用户表达 | 起始(含)| 结束(含)| 说明 | +> **禁止随意猜测日期范围,必须按以下规则精确计算后再组装命令参数:** +> +> | 用户表达 | --start(含)| --end(含)| 说明 | > |---------|------------|------------|------| -> | 本周 / 这周 | 本周**周一** | 本周**周日** | 周一为一周第一天 | -> | 上周 | 上周**周一** | 上周**周日** | 往前推一周 | -> | 本月 / 这个月 | 本月 **1 日** | 本月**最后一天** | 必须计算当月实际天数 | -> | 上月 | 上月 **1 日** | 上月**最后一天** | 往前推一个月 | -> | 今天 / 昨天 | 当天 | 当天 | start == end | +> | 本周 / 这周 / 当周 | 本周**周一** | 本周**周日** | 周一为一周的第一天,周日为最后一天 | +> | 上周 / 上一周 | 上周**周一** | 上周**周日** | 同上,往前推一周 | +> | 本月 / 这个月 / 当月 | 本月 **1 日** | 本月**最后一天**(28/29/30/31)| 必须计算当月实际天数 | +> | 上月 / 上个月 | 上月 **1 日** | 上月**最后一天** | 同上,往前推一个月 | +> | 今天 / 今日 | **今天** | **今天** | start == end | +> | 昨天 | **昨天** | **昨天** | start == end | > -> 计算日期必须参考当前系统时间,不得硬编码或模糊估算;用户给定具体日期范围时直接采用。 +> **必须遵守:** +> 1. **本周一定是周一到周日**,严禁将"本周"解释为周一到周五或其他任意范围。 +> 2. **本月一定是 1 日到该月实际最后一天**,严禁将"本月"截断为 1 日到 28 日或其他固定天数。 +> 3. 计算日期时必须参考当前系统时间,不得使用硬编码或模糊估算。 +> 4. 如果用户指定了具体日期范围(如"4月1日到4月15日"),直接使用用户给定的范围,不套用上述规则。 +> 5. 日期格式统一按各命令要求:`shift list` / `check result` 等使用 `YYYY-MM-DD`;`report query-data` / `report query-leave` 等使用 `yyyy-MM-dd HH:mm:ss`(start 取当天 `00:00:00`,end 取当天 `23:59:59`)。 + +## 签到(checkin)— 优先路由 + +> **只要用户句中含"签到"二字(签到记录/签到数据/签到明细/外勤签到/导出签到/签到报表),就走本节,不要走 `check record`(打卡流水)。"签到" ≠ "打卡",签到是外勤场景的独立功能。** + +### 导出 vs 查询 判断规则(必须先判断) + +| 用户说 | 路由 | +|--------|------| +| 含"导出"/"报表"/"Excel"/"表格"/"生成"中**任何一个** + 含"签到" | → **导出签到报表**(走脚本) | +| 不含上述导出关键词,仅"查签到"/"签到记录"/"看看签到" | → **查询签到记录**(走命令) | + +> **示例**:"帮我导出最近一个月的签到记录" → 含"导出" → 走报表脚本 +> "查一下张三的签到记录" → 不含导出关键词 → 走查询命令 + +- **导出签到报表** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"签到报表")**,对应脚本 `attendance_report_checkin.py` + - **严禁**绕过 `attendance-report.md` 直接调用脚本 + - **严禁**自己手动分段调用 `dws attendance checkin records` 来拼数据,必须用脚本 +- **查询签到记录**(无需导出,仅查看) → `dws attendance checkin records`(数据源为 MCP 工具 `get_checkin_record`) + +签到记录返回字段(`result.list` 数组,按 sortedProps 顺序): +- `corpId`(string):企业 ID +- `name`(string):用户名称 +- `userId`(string):用户 ID +- `timestamp`(number):签到时间 +- `place`(string):签到地点 +- `detailPlace`(string):签到详细地点 +- `longitude`(number):签到地点经度 +- `latitude`(number):签到地点纬度 +- `remark`(string):备注信息 +- `imageList`(string[]):图片列表 +- `customers`(string):拜访客户 +- `checkinType`(string):签到类型 +- `mobileId`(string):设备 ID ## 命令总览 + +### 查询打卡结果 +``` +Usage: + dws attendance check result [flags] +Example: + dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) + --limit int 分页大小, 默认 100, 范围 1-1000 (可选) + --offset int 分页偏移量, 默认 0 (可选) + --users string 用户 ID 列表, 逗号分隔, 最多 100 个 (必填) +``` + +返回每条记录含:用户 ID、工作日期、时间结果(Normal/Late/Early/Absenteeism/NotSigned)、位置结果、计划打卡时间、实际打卡时间、打卡流水 ID。时间跨度不超过 1 个月,最多 100 人。 + +### 查询打卡流水 +``` +Usage: + dws attendance check record [flags] +Example: + dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填) + --users string 用户 ID 列表, 逗号分隔 (必填) +``` + +返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。 + +### 查询审批单(补卡/加班/请假/出差外出) +``` +Usage: + dws attendance approve list [flags] +Example: + dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30 + dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30 # 同时返回出差与外出 + dws attendance approve list --users userId1 --types 加班,请假,补卡 --start 2026-04-01 --end 2026-04-30 +Flags: + --start string 起始日期, 格式 YYYY-MM-DD (必填) + --end string 结束日期, 格式 YYYY-MM-DD (必填) + --types string 审批类型, 逗号分隔: overtime/加班、trip/travel/business_trip/出差/外出、leave/请假、patch/repair-check/补卡 (必填) + --users string 用户 ID 列表, 逗号分隔 (必填) +``` + +审批类型映射(关键词 → bizType): +- `overtime` / `加班` → `1` +- `trip` / `travel` / `business_trip` / `business-trip` / `出差` / `外出` → `2`(**服务端查询接口 bizType=2 同时覆盖出差与外出,两者合并为同一类、不再细分**;传入任一别名都会返回这两类记录) +- `leave` / `请假` → `3` +- `patch` / `repair-check` / `repair_check` / `补卡` → `4` + +> 查询不区分外出与出差,如果需要在提交入口区分外出(`TRAVEL`)与出差(`OUT`),请改用 `dws attendance approve templates --type travel|out`。 + +返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。 + +### 查询补卡/请假/加班/外出/出差审批提交链接 (必须走引导流程) +``` +Usage: + dws attendance approve templates [flags] +Example: + dws attendance approve templates --type leave + dws attendance approve templates --type REPAIR_CHECK + dws attendance approve templates --type 加班 + dws attendance approve templates --type travel # 外出,等价 --type TRAVEL + dws attendance approve templates --type 出差 # 出差,等价 --type OUT +Flags: + --type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,或 REPAIR_CHECK/LEAVE/OVERTIME/TRAVEL/OUT(必填) +``` + +当用户提到需要提交补卡、请假、加班、外出或出差时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。 +`corpId` 和 `opUserId` 由系统参数自动注入,无需通过命令参数传入。 +审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`,外出=`TRAVEL`,出差=`OUT`(`trip` / `business_trip` / `business-trip` 亦映射为 `OUT`)。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。 +#### 引导用户自主选择合适的表单模板流程 +如果返回多个表单模板,必须将多个可用模板都返回给用户,并引导用户根据实际场景自主选择合适的模板提交: +- 请假场景:可根据 `formName` 将与用户请假类型更匹配的模板放在前面展示。例如用户明确说年假、事假、病假、调休时,将名称中包含对应假期类型的模板靠前;如果用户只泛化表达“请假”,将名称最通用的请假模板靠前,例如“请假”“员工请假”“通用请假”等,避免把专项或特殊场景模板放在最前。 +- 补卡/加班场景:可将名称与“补卡”或“加班”最直接匹配的模板放在前面展示。 +- 回复用户时不要直接裸露任何 `submitUrl`,所有返回的表单模板都必须使用 Markdown 可点击链接格式展示:`[formName](submitUrl)`,例如 `[员工请假](https://...)`。如存在更匹配的模板,可以放在列表前面,但不要只返回推荐模板,必须同时返回其它可用模板供用户选择,且每个模板都应是用户可直接点击的 Markdown 链接。 + +### 导入排班记录(排班 = 为员工安排工作日期和班次, 写场景接口,必须走二次确认流程) +``` +Usage: + dws attendance schedule import [flags] +Example: + dws attendance schedule import --group-id 123456 \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ + --yes +Flags: + --group-id string 考勤组(必填,传入考勤组ID) + --schedules string 排班记录 JSON 数组(必填) + --yes 跳过确认提示 +``` + +为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含: +- `userId`: 员工ID +- `classId`: 班次ID +- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22 +- `checkBeginTime`: 开始打卡时间 +- `checkEndTime`: 结束打卡时间 +- `isRest`: 是否休息日 Y/N(可选) + +#### AI 调用 `schedule import` 的二次确认流程 + +`schedule import` 是写操作,会为考勤组导入或变更员工排班。AI 调用时必须按以下流程执行,不得在未确认的情况下直接导入: + +1. **识别写操作**:用户表达“导入排班 / 设置排班 / 安排排班 / 给员工排班 / 批量排班”等意图时,命中 `schedule import`。 +2. **收集必要参数**:必须明确 `--group-id` 和 `--schedules`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。 +3. **展示导入摘要并反问确认**:向用户展示考勤组 ID、导入员工数量、涉及日期范围、班次 ID 列表,以及排班记录明细摘要,并询问是否确认执行导入。 +4. **用户确认后再执行导入**:只有用户明确确认后,才可以执行 `dws attendance schedule import ... --format json`。 + +确认话术示例: + +```text +即将导入排班记录,请确认: +- 考勤组 ID: +- 员工数量: +- 日期范围: ~ +- 班次 ID: +- 排班明细: + - : -,班次 + +是否确认执行导入? +``` + +如用户明确要求跳过确认,或命令中明确包含全局 `--yes`,可跳过二次确认。 + +### 获取排班记录 + +**禁止直接调用 `dws attendance schedule get`。必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行。** + +- 任何"查询排班"、"查看排班"、"XX考勤组的排班"、"X月份的排班"场景,**一律走 attendance-schedule.md 工作流**,由脚本 `attendance_schedule_export.py` 统一处理 +- 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 +- 违反后果:人数多时接口超时/报错、输出裸 userId 和 classId 用户看不懂、无排班表格式 +- **此处不提供 `schedule get` 的 Usage/Flags,防止绕过工作流直接拼命令。完整参数由 attendance-schedule.md 工作流中的脚本内部使用。** + +### 查询当前用户可管理的所有班次详情 +``` +Usage: + dws attendance class search [flags] +Example: + dws attendance class search + dws attendance class search --query "早班" --filter-type MINE_OWN + dws attendance class search --page 1 --limit 50 +Flags: + --filter-type string 班次类型: ALL 全部班次 / MINE_OWN 我负责的 (可选) + --query string 班次名称关键字, 模糊搜索 (可选) + --page int 页码, 从 1 开始 (可选, 默认 1) + --limit int 每页条数, 最大 200 (可选, 默认 20) +``` + +### 查询班次详情 +``` +Usage: + dws attendance class get [flags] +Example: + dws attendance class get --class-id 1170996821 +Flags: + --class-id int 班次 ID (必填) +``` + +根据班次 ID 查询该班次的完整详细信息。班次 ID 可从 `class search` 返回结果中提取,也有可能来源于用户手动输入。 + +### 创建班次 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括班次名称、上下班时间、休息时段等 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance class create [flags] +Example: + dws attendance class create --name "早班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:00","across":0},{"checkType":"OffDuty","checkTime":"17:00","across":0}]}]}' --timeout 10 + # 带休息时段(12:00-13:00 午休) + dws attendance class create --name "测试CLI" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 +Flags: + --name string 班次名称 (必填) + --owner string 班次负责人 userId (可选) + --class-vo string 完整 TopAtClassVO JSON 字符串, 包含 sections 等复杂子对象 (必填) +``` + +创建一个新班次。`--name` 和 `--class-vo`(包含 `sections`)必填。`sections` 定义班次的上下班时间段,支持多段上下班,每段包含 `times` 数组(有且只能有两个对象:上班+下班)。由于保存班次耗时较久,建议加 `--timeout 10`。 + +`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 + +`--class-vo` 支持字段: +- `name`(string, 必填) `owner`(string, 可选) +- `sections`([]object, 必填): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty, 必填) `checkTime`("HH:mm", 必填) `across`(0/1, 必填) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) +- `setting`(object, 可选): `seriousLateMinutes`(严重迟到分钟) `absenteeismLateMinutes`(旷工迟到分钟) `attendDays`(出勤天数) `topRestTimeList`([]object, 仅单段上下班时可用,最多3段: checkType/checkTime("HH:mm")/across) + +### 更新班次 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括班次 ID、要修改的字段含义及新值 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance class update [flags] +Example: + dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 + dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 + # 带休息时段(12:00-13:00 午休) + dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10 +Flags: + --class-id int 班次 ID (必填) + --name string 班次名称 (可选,不传则保持原值) + --owner string 班次负责人 userId (可选,不传则保持原值) + --class-vo string 完整 TopAtClassVO JSON 字符串,用于修改复杂子对象 (可选) +``` + +更新班次配置。`--class-id` 必填,其余均可选,仅需对要修改的字段进行赋值,未传字段会自动从已有配置补充。小改用单字段 flag(如 `--name`);修改上下班时间、休息时段等复杂子对象时用 `--class-vo` 传入完整 JSON。`--class-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 + +`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。 + +`--class-vo` 支持字段(均可选,只需包含要修改的字段): +- `name`(string) `owner`(string) +- `sections`([]object): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty) `checkTime`("HH:mm") `across`(0/1) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制) +- `setting`(object): `seriousLateMinutes` `absenteeismLateMinutes` `attendDays` `topRestTimeList`([]object: checkType/checkTime("HH:mm")/across) + +由于保存班次耗时较久,建议加 `--timeout 10`。 + +### 分页查询补卡规则,支持按名称搜素 +``` +Usage: + dws attendance adjustment search [flags] +Example: + dws attendance adjustment search --page 1 --limit 20 + dws attendance adjustment search --query "标准" --page 1 --limit 50 +Flags: + --page int 页码, 从 1 开始 (必填, 默认 1) + --query string 补卡规则名称关键字, 模糊搜索 (可选) + --limit int 每页条数, 200 以内 (必填, 默认 20) +``` + +### 查询补卡规则详情 +``` +Usage: + dws attendance adjustment get [flags] +Example: + dws attendance adjustment get --adjustment-id 12345 +Flags: + --adjustment-id int 补卡规则主键 ID (必填) +``` + +根据补卡规则主键 ID 查询对应的补卡规则详情。主键 ID 可从 `adjustment search` 返回结果中提取,也有可能来源于用户手动输入。**注意:已被删除或被更新覆盖的补卡规则无法查询到。** + +### 分页查询加班规则,支持按名称搜素 +``` +Usage: + dws attendance overtime search [flags] +Example: + dws attendance overtime search --page 1 --limit 20 + dws attendance overtime search --query "节假日" --page 1 --limit 50 +Flags: + --page int 页码, 从 1 开始 (必填, 默认 1) + --query string 加班规则名称关键字, 模糊搜索 (可选) + --limit int 每页条数, 200 以内 (必填, 默认 20) +``` + +### 查询加班规则详情 +``` +Usage: + dws attendance overtime get [flags] +Example: + dws attendance overtime get --overtime-id 12345 +Flags: + --overtime-id int 加班规则主键 ID (必填) +``` + +根据加班规则主键 ID 查询对应的加班规则详情。主键 ID 可从 `overtime search` 返回结果中提取,也有可能来源于用户手动输入。**已被删除或更新覆盖的加班规则也可以查到。** + +### 查询考勤组列表 +``` +Usage: + dws attendance group search [flags] +Example: + dws attendance group search --query "研发" + dws attendance group search --type FIXED --limit 50 + dws attendance group search --page 1 --limit 20 +Flags: + --query string 考勤组名称关键字, 模糊搜索 (可选) + --page int 页码, 从 1 开始 (必填, 默认 1) + --limit int 每页条数, 200 以内 (必填, 默认 20) + --query-ble 是否查询蓝牙设备列表 (可选, 默认 false) + --query-position 是否查询地理定位和 Wifi 名称 (可选, 默认 false) + --type string 考勤组类型: FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) +``` + +### 查询考勤组全量信息 +``` +Usage: + dws attendance group get [flags] +Example: + dws attendance group get --group-id 123456 +Flags: + --group-id int 考勤组 ID (必填) +``` + +根据考勤组 ID 查询该考勤组的全量信息。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。如果只需查询成员、打卡地址、蓝牙、Wifi 子集,请使用 `group filtered-get` 以节省查询成本。 +返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId + +### 按需查询考勤组部分信息 +``` +Usage: + dws attendance group filtered-get [flags] +Example: + dws attendance group filtered-get --group-id 123456 --member + dws attendance group filtered-get --group-id 123456 --position --wifi +Flags: + --group-id int 考勤组 ID (必填) + --member 是否查询考勤组成员信息 (可选, 默认 false) + --position 是否查询打卡地址 (可选, 默认 false) + --wifi 是否查询打卡 Wifi (可选, 默认 false) + --bles 是否查询打卡蓝牙 (可选, 默认 false) +``` + +强烈建议在仅需查询成员、打卡地址、蓝牙、Wifi 时调用该命令,避免全量查询带来的性能开销。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。 +返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId + +### 更新考勤组成员 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要添加/移除的成员列表 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** + +``` +Usage: + dws attendance group update-members [flags] +Example: + dws attendance group update-members --group-id 123456 --add-users userId1,userId2 + dws attendance group update-members --group-id 123456 --remove-users userId1 + dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 +Flags: + --group-id int 考勤组 ID (必填) + --add-users string 添加考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --remove-users string 删除考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --add-extra-users string 添加无需考勤的人员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --remove-extra-users string 删除无需考勤的成员 userId 列表, 逗号分隔, 最多 20 个 (可选) + --add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) + --remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选) +``` + +对指定考勤组的成员进行增删操作。--group-id 必填,其余参数均为可选,但至少需要传入一个变更项,否则命令拒绝执行。每次调用各参数最多传 20 个 ID。"无需考勤"人员指考勤组内豁免打卡的成员(如高管)。 + +### 更新考勤组配置 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要修改的字段含义及新值 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance group update [flags] +Example: + dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 + dws attendance group update --group-id 123456 --owner userId1 --timeout 10 + dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 + dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市...","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 +Flags: + --group-id int 考勤组 ID (必填) + --name string 考勤组名称 (可选) + --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选) + --owner string 考勤组主负责人 userId (可选) + --enable-outside-check 是否允许外勤打卡 true/false (可选) + --classIds string 所选班次 id 列表, JSON 数组格式, 如 '[123,456]' (可选) + --group-vo string 完整 groupVO JSON 字符串, 用于修改复杂子对象 (可选) +``` + +更新考勤组配置。--group-id 必填,其余均可选,但至少需指定一个修改项。仅需对要修改的字段进行赋値,其余字段会自动从已有配置补充。小改用单字段 flag;修改打卡地址、wifi、蓝牙设备、循环排班等复杂子对象时用 `--group-vo` 传入完整 JSON。`--group-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。 + +`--group-vo` 支持字段(均可选,只需包含要修改的字段): +- 基础:`name`(名称) `type`(FIXED/TURN/NONE) `owner`(主负责人 userId) `managerList`([]string 子负责人) `skipHolidays`(bool,只在固定班制和自由工时生效) `defaultGroup`(bool) `classIds`([]number,所选班次 id,只有固定班制和排班制才有班次,自由工时没有) +- 打卡范围:`trimDistance`(微调距离) `enablePositionOfGps/Wifi/Ble`(bool) +- 打卡地址:`positions`([]对象: title/address/latitude/longitude/offset,其中 offset 为该地址允许的打卡范围米) +- Wifi:`wifis`([]对象: ssid/macAddr/groupId) +- 蓝牙:`bleDeviceVOList`([]对象: name/deviceUid/sn/productType/devServId) +- 外勤:`enableOutsideCheck`(bool) `enableOutsideCameraCheck/Remark/Apply`(bool) `outsideCheckApproveMode`(NO_NEED_APPROVE/APPROVE_FIRST/CHECK_FIRST/APPROVE_EVERYTIME) `outSideCheckApplyType`(1全天/2上班/3下班) `forbidHideOutSideAddress`(bool) `enableOutSideUpdateNormalCheck`(下班时允许外勤卡更新内勤卡) `enableOnDutyNormalUpdateOutsideCheck`(上班时允许内勤卡更新外勤卡) +- 打卡方式:`enableCameraCheck/openCameraCheck` `openFaceCheck` `enableFaceStrictMode` `enableFaceBeauty`(bool) `onlyMachineCheck`(bool) `permitMaxBeaconCount`(number) `disableCheckWhenRest`(bool,休息日打卡需审批,只在固定班制和排班制生效) +- 固定班制设置(FIXED):`defaultClassId`(number) `workDayClassList`([]number,共7个值代表周日到周六每天的班次id,为0表示当天休息,如[0,1279240003,0,0,0,0,0]表示只有周一上班) +- 排班制设置(TURN):`disableCheckWithoutSchedule`(bool,true=未排班时禁止打卡) `enableEmpSelectClass`(未排班时员工可选班次) `enableScheduleAutoMatch`(未排班时系统自动匹配) + - 循环排班(非必填,不设置则由管理员手动排班):`cycleDays`(number) `startCycleDate`(时间戳,毫秒) `cycleScheduleList`([]对象: cycleName/groupId/isValid(Y/N)/itemList[{classId/className/isValid}]) +- 自由工时设置(NONE):`workDays`([1-7],1=周一7=周日) `freeCheckDayStartMinOffset`(number,距0点分钟数) `freeCheckCoreTime`(最短工作时长,分钟) `freeCheckDemandWorkMinutes`(要求打卡时长,分钟) `freeCheckSettingVO`(对象: freeCheckType(CYCLE上下班交替/MAX_TIME_UPDATE最大时间打卡)/freeWorkDayLackSwitch/freeOnDutyLackMinOffset/freeOffDutyLackMinOffset/delimitOffsetMinutesBetweenDays/freeCheckGapVO{onOffCheckGapMinutes/offOnCheckGapMinutes}) `freeGroupSpecialDayVO`(对象: specialOnDutyDays[]/specialOffDutyDays[]) + +### 创建考勤组 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括考勤组名称、类型、班次列表等 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** +``` +Usage: + dws attendance group create [flags] +Example: + dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 + dws attendance group create --name "自由工时分组" --type NONE --timeout 10 +Flags: + --name string 考勤组名称 (必填) + --type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (必填) + --owner string 考勤组主负责人 userId (可选) + --group-vo string 完整 groupVO JSON 字符串,用于传入复杂子对象 (可选) +``` + +创建一个新的考勤组。`--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一。 + +**条件必填(type=FIXED 固定班制时)**:`--group-vo` 必须包含 `workDayClassList`(工作日班次列表,不能为空,共 7 个值,代表周日到周六每天的班次 ID,为 0 表示当天休息)和 `defaultClassId`(默认班次 ID,不能为 null)。 + +`--group-vo` 可用字段说明(与 `group update` 的 `--group-vo` 一致,均可选,只需包含要设置的字段): + +【基础信息】 + id number 考勤组 id(创建时无需传入,由服务端分配) + name string 考勤组名称(必填) + type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填) + owner string 考勤组主负责人 userId + managerList []string 考勤组子负责人 userId 列表 + skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效) + defaultGroup bool 是否默认考勤组 + classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有) + +【打卡范围与定位】 + trimDistance number 定位允许微调距离(米) + enablePositionOfGps bool 打卡是否允许开启 GPS 定位 + enablePositionOfWifi bool 打卡是否允许开启 Wifi 定位 + enablePositionOfBle bool 打卡是否允许开启蓝牙定位 + enableMacCheck bool 开启 MAC 地址校验 + checkDistanceType string 打卡距离类型:NORMAL(正常) / OUTER_ADDRESS(仅允许在外勤地址打卡) + wifiCompanyId number 允许打卡的 Wifi 公司 ID + +【打卡地址】 + positions []object 打卡地址列表,每个对象字段: + title string 地址名称 + address string 详细地址 + latitude number 纬度 + longitude number 经度 + offset number 该地址允许的打卡范围(米) + +【打卡 Wifi】 + wifis []object 打卡 Wifi 列表,每个对象字段: + ssid string Wifi 名称 + macAddr string MAC 地址 + groupId number 所属考勤组 ID + +【蓝牙设备】 + bleDeviceVOList []object 蓝牙设备列表,每个对象字段: + name string 设备名称 + deviceUid string 设备 ID + sn string 序列号 + productType string 产品类型 + devServId number 设备服务 ID + +【外勤打卡设置】 + enableOutsideCheck bool 是否允许外勤打卡 + openOutsideCameraCheck bool 外勤打卡是否开启拍照 + enableOutsideRemark bool 是否允许外勤备注 + enableOutsideApply bool 外勤打卡是否需审批 + forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址 + enableOutSideUpdateNormalCheck bool 下班时允许外勤卡更新内勤卡 + enableOnDutyNormalUpdateOutsideCheck bool 上班时允许内勤卡更新外勤卡 + outsideCheckApproveMode string 外勤审批模式:NO_NEED_APPROVE / APPROVE_FIRST / CHECK_FIRST / APPROVE_EVERYTIME + outSideCheckApplyType number 外勤打卡申请类型:1 全天 / 2 上班 / 3 下班 + +【打卡方式】 + openCameraCheck bool 是否开启拍照打卡 + enableFaceStrictMode bool 是否开启人脸严格模式 + enableFaceBeauty bool 是否开启人脸美颜 + onlyMachineCheck bool 是否仅允许考勤机打卡 + permitMaxBeaconCount number 允许最大蓝牙信标数量 + disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效) + +【固定班制设置(FIXED)】 + defaultClassId number 默认班次 ID + workDayClassList []number 工作日班次列表,共 7 个值代表周日到周六每天的班次 id,为 0 表示当天休息。 + 例如 [0,1279240003,0,0,0,0,0] 表示只有周一上班 + +【排班制设置(TURN)】 + disableCheckWithoutSchedule bool 未排班时禁止打卡 + enableEmpSelectClass bool 未排班时员工可选班次 + enableScheduleAutoMatch bool 未排班时系统自动匹配 + cycleDays number 循环排班周期天数 + startCycleDate number 循环排班开始日期(时间戳,毫秒) + cycleScheduleList []object 循环排班列表: + cycleName string 循环排班名称 + groupId number 考勤组 ID + isValid string 是否生效:Y/N + itemList []object 循环排班明细: + classId number 班次 ID + className string 班次名称 + isValid string 是否生效:Y/N + +【自由工时设置(NONE)】 + workDays []number 工作日,[1-7],1=周一,7=周日 + freeCheckDayStartMinOffset number 距 0 点分钟数 + freeCheckCoreTime number 最短工作时长(分钟) + freeCheckDemandWorkMinutes number 要求打卡时长(分钟) + freeCheckSettingVO object 自由打卡设置: + freeCheckType string 打卡类型:CYCLE(上下班交替)/ MAX_TIME_UPDATE(最大时间打卡) + freeWorkDayLackSwitch bool 工作日缺卡开关 + freeOnDutyLackMinOffset number 上班缺卡分钟数偏移 + freeOffDutyLackMinOffset number 下班缺卡分钟数偏移 + delimitOffsetMinutesBetweenDays number 跨天切割分钟数偏移 + freeCheckGapVO object 打卡间隔: + onOffCheckGapMinutes number 上班到下班最小间隔(分钟) + offOnCheckGapMinutes number 下班到上班最小间隔(分钟) + freeGroupSpecialDayVO object 特殊日期设置: + specialOnDutyDays []number 特殊上班日期 + specialOffDutyDays []number 特殊休息日期 + +当使用 `--group-vo` 传入完整 JSON 时,`--name`、`--type`、`--owner` 仍可覆写 JSON 中的同名字段。由于保存考勤组耗时较久,建议加 `--timeout 10`。 + +> **新建考勤组并同时添加成员的工作流**:`group create` 不支持在创建时直接传入成员列表。若用户需要在新建考勤组后立即添加成员,必须先执行 `group create` 创建考勤组,从返回结果中提取 `groupId`,再执行 `group update-members --group-id --add-users ...` 完成成员添加。 + +### 查询某个人的考勤统计摘要 +``` +Usage: + dws attendance summary [flags] +Example: + dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" +Flags: + --date string 工作日期, 格式 yyyy-MM-dd HH:mm:ss (必填) + --user string 钉钉用户 ID (必填) +``` + ### 查询考勤组与考勤规则 ``` Usage: @@ -31,91 +594,754 @@ Example: dws attendance rules --date 2026-03-14 dws attendance rules --date "2026-03-14 09:00:00" Flags: - --date string 考勤日期,格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填) -Notes: - - 用于回答:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算 - - 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 + --date string 考勤日期, 格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填) ``` -### 查询个人考勤详情 +查询考勤组/考勤规则。例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。 + +### 查询个人规则设置 ``` Usage: - dws attendance record get [flags] + dws attendance selfsetting get [flags] Example: - dws attendance record get --user USER_ID --date 2026-03-08 + dws attendance selfsetting get --setting-scene checkRemind --user --format json + dws attendance selfsetting get --setting-scene fastCheck --user --format json Flags: - --date string 查询日期,格式 YYYY-MM-DD (必填) - --user string 钉钉用户 ID (必填) + --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --user string 查询用户 ID (必填) ``` -### 批量查询员工班次信息 +调用 MCP 工具 query_self_setting 查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。MCP 入参 `userId` 必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 + +`--setting-scene` 枚举值: +- `checkRemind`: 打卡提醒 +- `fastCheck`: 极速打卡 +- `checkResultNotify`: 打卡结果通知 +- `lackRemind`: 缺卡提醒 +- `personalAttendStatNotify`: 个人考勤统计通知 +- `bossAttendStatNotify`: 团队考勤统计通知 + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 可能根据 `--setting-scene` 仅返回对应设置项相关字段。常见字段包括: +- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` +- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` +- `checkResultNotify`: `checkResultMsg`, 取值 0 表示关闭, 1 表示开启 +- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser`, 取值 0 表示关闭, `null` 或 1 表示开启 +- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` +- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` + +其中周报/月报通知渠道枚举值:0 表示全关闭,1 表示工作通知,2 表示钉邮,3 表示工作通知和钉邮。 + +### 更新保存个人规则设置 (写场景接口,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要,包括目标用户、设置场景、当前值、新值和最终命令参数 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传全局 `--yes` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--yes`。** + ``` Usage: - dws attendance shift list [flags] + dws attendance selfsetting save [flags] Example: - dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07 + # 开启打卡结果通知(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) + dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --yes --format json + # 更新极速打卡设置(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) + dws attendance selfsetting save --setting-scene fastCheck --user --onduty-check-type 3 --voice-remind-switch=true --yes --format json + # 更新打卡提醒设置(Agent 调用时,必须先完成 ask_human 二次确认,确认后再追加 --yes 执行) + dws attendance selfsetting save --setting-scene checkRemind --user --check-remind-user-on-duty=false \ + --check-remind-setting '{"onDutyRemind":{"openRemind":true,"remindMinutes":10}}' --yes --format json Flags: - --users string 员工 ID 列表,逗号分隔,最多 50 人 (必填) - --start string 起始日期,格式 YYYY-MM-DD (必填) - --end string 结束日期,格式 YYYY-MM-DD (必填) -Notes: - - 单次查询最多 7 天、最多 50 人 + --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --user string 更新用户 ID (必填) + --check-remind-setting string 打卡提醒 DING 渠道设置 JSON + --check-remind-user-on-duty 打卡提醒工作通知渠道:用户个人上班打卡提醒开关 + --check-remind-user-off-duty 打卡提醒工作通知渠道:用户个人下班打卡提醒开关 + --enable-onduty-check-remind-of-pc PC 端弹窗渠道:上班打卡提醒开关 + --enable-offduty-check-remind-of-pc PC 端弹窗渠道:下班打卡提醒开关 + --onduty-check-type int 上班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 + --offduty-check-type int 下班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡 + --onduty-remind-start-min int 上班打卡提醒开始时间,单位:分钟 + --onduty-remind-end-min int 上班打卡提醒结束时间,单位:分钟 + --offduty-remind-start-min int 下班打卡提醒开始时间,单位:分钟 + --offduty-remind-end-min int 下班打卡提醒结束时间,单位:分钟 + --fast-check-late-need-confirm 迟到时是否需要二次确认 + --can-update-off-duty 是否允许用户更新下班打卡设置 + --voice-remind-switch 极速打卡提示音开关 + --vibration-remind-switch 极速打卡震动提醒开关 + --check-result-msg int 打卡结果通知开关:0 关闭,1 开启 + --lack-send-todo-msg int 缺卡提醒待办渠道:0 关闭,null 或 1 开启 + --lack-remind-user int 缺卡提醒工作通知渠道:0 关闭,null 或 1 开启 + --person-daily-report-switch int 个人考勤统计日报推送开关:0 关闭,1 开启 + --person-week-report-type int 个人考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --person-month-report-type int 个人考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-push-start-min int 团队考勤统计日报推送开始时间,单位:分钟;-1 表示关闭日报推送 + --boss-week-report-type int 团队考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-month-report-type int 团队考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --yes 用户已确认,跳过交互式确认提示 + Agent 调用时传入前必须已完成 ask_human 二次确认 ``` -### 查询某个人的考勤统计摘要 +调用 MCP 工具 save_self_setting 更新保存个人规则设置,请求体封装在 `RuleMcpSaveSelfSettingRequest` 中。`settingScene` 必填;MCP 入参 `userId` 必填,CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。 + +`selfsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: +- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc` +- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch` +- `checkResultNotify`: `checkResultMsg` +- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser` +- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType` +- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType` + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。其中 `result` 为 boolean,表示保存是否成功。 + +#### 强制执行流程:Agent 调用 `selfsetting save` + +`selfsetting save` 是写操作,会修改用户个人规则设置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达“更新个人规则设置 / 保存打卡提醒 / 修改极速打卡 / 关闭缺卡提醒 / 开启打卡结果通知 / 设置个人考勤统计通知 / 设置团队考勤统计通知”等意图时,命中 `selfsetting save`。 +2. **收集必要参数**:必须明确 `--user`、`--setting-scene`,以及对应场景下将要修改的字段和值。 +3. **前置查询当前设置**:执行 `dws attendance selfsetting get --setting-scene --user --format json`,获取当前配置,用于确认目标用户和当前值。 +4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要。必须调用 `ask_human` 或返回待确认状态,并等待用户明确确认。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以追加全局 `--yes` 执行 `dws attendance selfsetting save ... --yes --format json`。 + +确认话术示例: + +```text +即将更新个人考勤规则设置,请确认: +- 用户 ID: +- 设置场景:checkResultNotify +- 修改内容: + - 打卡结果通知(checkResultMsg):关闭 → 开启 + +是否确认执行更新? +``` + +禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,Agent 也必须先通过 `ask_human` 或待确认状态展示完整参数摘要并获得明确确认后,才允许追加 `--yes` 执行。 + +### 查询全局规则设置(仅管理员) ``` Usage: - dws attendance summary [flags] + dws attendance globalsetting get [flags] Example: - dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type month - dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type week + dws attendance globalsetting get --scope 企业 --setting-scene checkRemind --format json + dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify --format json Flags: - --user string 钉钉用户 ID (必填) - --date string 工作日期,格式 yyyy-MM-dd HH:mm:ss (必填) - --stats-type string 统计类型:week(周统计)或 month(月统计)(必填) -Notes: - - --stats-type 必填,不填会返回 C0002 统计类型错误(钉钉服务端业务层强制要求) + --setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) ``` +调用 MCP 工具 `query_global_setting` 查询全局规则设置,请求体封装在 `RuleMcpQueryGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认查询的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 + +返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 常见字段包括: +- `checkRemindCorp`: 打卡提醒企业总开关 +- `checkRemindPcCorp`: 打卡提醒 PC 端企业总开关 +- `fastCheckCorp`: 极速打卡企业总开关 +- `enableCheckCertPush`: 打卡结果通知企业总开关 +- `lackRemindCorp`: 缺卡提醒企业总开关 +- `enablePersonalDailyReport`: 个人考勤统计通知日报企业总开关 +- `enablePersonalWeeklyReport`: 个人考勤统计通知周报企业开关,钉邮渠道 +- `enablePersonalWeeklyReportCard`: 个人考勤统计通知周报企业开关,工作通知渠道 +- `enablePersonalMonthlyReport`: 个人考勤统计通知月报企业总开关 +- `bossDailyReportType`: 团队考勤统计通知日报发送渠道类型,0 全关闭,1 开启 +- `bossWeeklyReportType`: 团队考勤统计通知周报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 +- `bossMonthlyReportType`: 团队考勤统计通知月报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + +### 更新保存全局规则设置(写场景接口,仅管理员,必须走二次确认流程) +**强制执行流程**:此命令为写操作,Agent 调用时必须先向用户展示待执行操作的完整参数摘要,等待用户明确确认后,才能追加 `--yes` 执行。 + +``` +Usage: + dws attendance globalsetting save [flags] +Example: + dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true --yes --format json + dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false --yes --format json + dws attendance globalsetting save --scope 所有人 --setting-scene bossAttendStatNotify --boss-daily-report-type 1 --boss-weekly-report-type 3 --yes --format json +Flags: + --setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填) + --scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填) + --check-remind-corp 打卡提醒企业总开关 + --check-remind-pc-corp 打卡提醒 PC 端弹窗企业总开关 + --fast-check-corp 极速打卡企业总开关 + --enable-check-cert-push 打卡结果通知企业总开关 + --lack-remind-corp 缺卡提醒企业总开关 + --enable-personal-daily-report 个人考勤统计通知日报企业总开关 + --enable-personal-weekly-report 个人考勤统计通知周报企业开关,钉邮渠道 + --enable-personal-weekly-report-card 个人考勤统计通知周报企业开关,工作通知渠道 + --enable-personal-monthly-report 个人考勤统计通知月报企业总开关 + --boss-daily-report-type int 团队考勤统计通知日报发送渠道类型:0 全关闭,1 开启 + --boss-weekly-report-type int 团队考勤统计通知周报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --boss-monthly-report-type int 团队考勤统计通知月报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮 + --yes 用户已确认,跳过交互式确认提示 +``` + +调用 MCP 工具 `save_global_setting` 更新保存全局规则设置,请求体封装在 `RuleMcpSaveGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认更新的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。 + +`globalsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值: +- `checkRemind`: `checkRemindCorp`、`checkRemindPcCorp` +- `fastCheck`: `fastCheckCorp` +- `checkResultNotify`: `enableCheckCertPush` +- `lackRemind`: `lackRemindCorp` +- `personalAttendStatNotify`: `enablePersonalDailyReport`、`enablePersonalWeeklyReport`、`enablePersonalWeeklyReportCard`、`enablePersonalMonthlyReport` +- `bossAttendStatNotify`: `bossDailyReportType`、`bossWeeklyReportType`、`bossMonthlyReportType` + +返回 `ServiceResult`,其中 `result` 为 boolean,表示保存是否成功。 + +### 获取企业考勤字段列表(仅管理员) +``` +Usage: + dws attendance report columns +Example: + dws attendance report columns +``` + +根据操作者的列权限,过滤并返回其有权查看的考勤字段列表。操作者必须是管理员,否则返回权限错误。 + +### 根据字段查询考勤数据(仅管理员) +``` +Usage: + dws attendance report query-data [flags] +Example: + dws attendance report query-data \ + --users userId1,userId2 --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" +Flags: + --columns string 字段 ID 列表, 逗号分隔, 可通过 report columns 获取(必填) + --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) +``` + +根据字段查询考勤数据,含列权限过滤和用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 + +### 查询用户假期数据(仅管理员) +``` +Usage: + dws attendance report query-leave [flags] +Example: + dws attendance report query-leave \ + --users userId1,userId2 --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" +Flags: + --end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --leave-names string 假期类型名称列表, 逗号分隔, 不填则查询所有假期类型(选填) + --start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填) + --users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填) +``` + +查询用户假期数据,含用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。 + +### 查询当前用户假期规则列表 +``` +Usage: + dws attendance vacation types +Example: + dws attendance vacation types +Flags: + 无 +``` + +调用 MCP 工具 get_leave_types 查询当前用户可用的假期规则列表。例如:年假、事假、病假等假期类型及对应规则。请求体封装在 McpLeaveTypeRequest 中,认证信息(corpId、opUserId)由系统自动注入,无需手动传入。 + +### 查询指定员工假期余额 +``` +Usage: + dws attendance vacation balance [flags] +Example: + dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Flags: + --users string 目标员工 ID 列表, 逗号分隔 (必填) + --leave-code string 假期规则 code (选填,不传则查询所有假期规则余额) +``` + +调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取;不传 `--leave-code` 时查询所有假期规则余额。认证信息(corpId、opUserId)由系统自动注入。 + +如用户需要“所有假期规则余额 / 导出假期余额列表 / 所有假期规则余额 Excel / 按截图样式导出假期余额”,必须先读取 [attendance-vacation.md](./attendance-vacation.md),再按其中工作流调用脚本生成 Excel。 + +### 查询指定员工假期余额变更记录 +``` +Usage: + dws attendance vacation records [flags] +Example: + dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22 +Flags: + --user string 指定查询员工 ID (必填) + --leave-code string 假期规则 code (必填, 不传则无法查询) + --start string 查询开始日期, 格式 YYYY-MM-DD (必填) + --end string 查询结束日期, 格式 YYYY-MM-DD (必填) +``` + +调用 MCP 工具 get_leave_balance_records 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。 + +### 更新假期规则(写场景接口,必须走二次确认流程) + +**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传 `--user-say-yes=true` 执行命令 + +**禁止未经用户确认直接执行或自动添加 `--user-say-yes=true`。** + +``` +Usage: + dws attendance vacation update-type [flags] +Example: + # 更新假期规则名称 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name "事假(修改版)" + + # 更新假期单位 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8 + + # 更新适用范围 + dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' +Flags: + --leave-code string 假期编码(必填) + --name string 假期名称(可选) + --unit string 假期单位:day/halfDay/hour(可选) + --paid bool 是否带薪假期(可选,默认 false) + --per-hours int 一天折算小时数(可选) + --when-can-leave string 新员工请假规则:entry/formal(可选) + --visibility-rules string 适用范围规则 JSON 数组(可选) + --user-say-yes 用户已确认,跳过交互式确认提示 + Agent 调用时传 true 前必须完成用户二次确认 +``` + +调用 MCP 工具 save_leave_type 更新已有假期规则。`--leave-code` 必填,指定要更新的假期规则编码。其他字段均为可选,仅需传入要修改的字段。除 `--leave-code` 外,必须至少传入一个更新字段。 + +#### 强制执行流程:Agent 调用 `vacation update-type` + +`vacation update-type` 是写操作,会修改假期规则配置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达"更新假期规则 / 修改假期类型 / 编辑假期规则"等意图时,命中 `vacation update-type`。 +2. **收集必要参数**:必须明确 `--leave-code`,以及至少一个更新字段(`--name`、`--unit`、`--paid`、`--per-hours`、`--when-can-leave`、`--visibility-rules`)。 +3. **前置查询当前规则**:需先调用 `vacation types` 确认该规则是否存在及当前配置。 +4. **展示待写入数据并等待确认**:向用户展示假期编码、要更新的字段及新值,并询问是否确认执行。**必须等待用户明确确认**。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation update-type ... --format json`。 + +确认话术示例: + +```text +即将更新假期规则,请确认: +- 假期编码:a1b2c3d4-e5f6-7890-abcd-ef1234567890 +- 更新内容: + - 名称:事假 → 事假(修改版) + +是否确认执行更新? +``` + +如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 + +#### 强制执行流程:Agent 调用 `vacation save-balance` + +`vacation save-balance` 是写操作,会直接替换员工的假期余额(SET 接口,而非 ADD)。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交: + +1. **识别写操作**:用户表达"设置假期余额 / 调整假期额度 / 更新假期余额 / 增加假期余额 / 发放年假 / 给员工加年假"等意图时,命中 `vacation save-balance`。 +2. **前置查询当前余额**:必须先调用 `vacation balance --users --leave-code ` 获取当前余额,因为这是 SET 接口,传入值会直接替换而非累加。 +3. **收集必要参数**:必须明确 `--target`(目标员工)、`--leave-code`(假期编码)、`--num`(新余额数量)、`--reason`(变更原因),以及可选参数 `--start/--end`(有效期)。 +4. **计算变更并展示确认**:向用户展示目标员工、假期类型、当前余额、新余额、差额(增加或减少)、变更原因、有效期等,并询问是否确认执行。**必须等待用户明确确认**。 +5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation save-balance ... --format json`。 + +确认话术示例: + +**设置余额场景**: +```text +即将设置员工假期余额,请确认: +- 目标员工:张三(user001) +- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) +- 当前余额:5 天 +- 新余额:8 天 +- 变更差额:+3 天(增加) +- 变更原因:年度发放 +- 有效期:2024-01-01 至 2024-12-31 + +是否确认执行设置? +``` + +**减少余额场景**: +```text +即将设置员工假期余额,请确认: +- 目标员工:李四(user002) +- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890) +- 当前余额:10 天 +- 新余额:2 天 +- 变更差额:-8 天(减少) +- 变更原因:请假扣减 + +注意:此操作将大幅减少余额,请确认是否继续? + +是否确认执行设置? +``` + +如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。 + +### 设置员工假期余额 +``` +Usage: + dws attendance vacation save-balance [flags] +Example: + # 设置员工年假余额为8天 + dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" + + # 设置带有效期的假期余额 + dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" \ + --start 2024-01-01 --end 2024-12-31 +Flags: + --target string 目标员工工号(必填) + --leave-code string 假期编码(必填) + --num string 余额数量,如8天传8,7.5天传7.5(必填) + --reason string 变更原因,最长100字符(必填) + --start string 有效期开始日期 YYYY-MM-DD(可选) + --end string 有效期结束日期 YYYY-MM-DD(可选) + --user-say-yes 用户已确认,跳过交互式确认提示 + Agent 调用时传 true 前必须完成用户二次确认 +``` + +**重要:这是设置(SET)接口,传入的值会替换当前余额,而非增加(ADD)。**余额数量在传递给 MCP 时会自动乘以 100(如 8 天传 800)。执行前会展示待写入数据,需用户确认后提交。 + +### 查询指定员工的签到记录 +``` +Usage: + dws attendance checkin records [flags] +Example: + dws attendance checkin records \ + --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" +Flags: + --end string 结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填) + --operator-corp-id string 操作者企业 ID(必填) + --operator-staff-id string 操作者员userID(必填) + --staff-ids string 目标员工userID 列表, 逗号分隔(必填),员工数最多100个人 + --start string 开始时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束时间限制在7天 +``` + +调用 MCP 工具 get_checkin_record 查询指定员工在一段时间内的签到记录。权限说明:Boss/超级管理员可查看全公司员工,子管理员可查看管理范围内员工,部门主管可查看所管理部门员工,普通员工只能查询自己。接口单次最多返回100条签到记录。 + ## 意图判断 -用户说"我属于哪个考勤组/打卡范围/弹性工时/考勤规则" → `attendance rules --date <日期>` -用户说"某人某天的打卡记录/打卡详情/几点上下班" → `attendance record get --user --date <日期>` -用户说"某些人的班次/排了什么班/某段时间的班次" → `attendance shift list --users --start <开始> --end <结束>` -用户说"某人的考勤统计/本周/本月出勤/迟到早退汇总" → `attendance summary --user --date <日期> --stats-type week|month` - -关键区分: -- `record get` = 某天的逐次打卡明细(单人单日) -- `summary` = 一段时间的统计汇总(单人,周/月) -- `shift list` = 排班(谁哪天上什么班,可批量多人) -- `rules` = 考勤组与规则配置(不查打卡数据) +用户说"打卡记录/出勤/考勤" → `check record` +用户说"指定用户打卡结果/考勤结果/迟到早退/缺卡异常" → `check result` +用户说"指定用户打卡流水/打卡详情/打卡时间地点/打卡记录详情" → `check record` +用户说"审批单/请假记录/加班记录/出差记录/补卡记录" → `approve list` +用户说"查询排班记录/获取排班详情/查看排班/排班表/导出排班/导出排班表/排班导出/XX考勤组的排班" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行**。 + - **优先级**:只要用户句中含"排班"二字且意图是查看/导出,就走本条,不要走 `attendance-report.md`。"导出排班表" ≠ "导出考勤报表" + - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule get` 命令 + - 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出 +用户说"排班/导入排班/安排排班/设置排班/安排班次/调班/换班/排休" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班导入工作流」执行**。 + - **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule import` 命令 + - **严禁**仅凭命令 `--help` 或本文件中的命令参考自行组装排班命令 + - 该文档定义了:考勤组类型校验(必须为 TURN)、班次校验(必须属于该考勤组)、排班回显确认、错误处理等约束,缺一不可 + - 违反约束的后果:排错班次、排错人员、排班数据覆盖无法回退 +用户说"班次定义/班次列表/有哪些班次/我负责的班次" → `class search`(返回结果已包含全量属性,无需再调 get) +用户说"班次详情/某个班次的具体信息" → `class search --name "..."`(search 直出,直接返回详情)。`class get` 仅在需要按已知 classId 精确查询时使用 +用户说"更新班次/修改班次/班次改名/修改上下班时间" → `class update` +用户说"补卡规则/补卡设置" → `adjustment search`(返回结果已包含全量属性,无需再调 get) +用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出)。`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用 +用户说"加班规则/加班设置/加班计算" → `overtime search`(返回结果已包含全量属性,无需再调 get) +用户说"加班规则详情/某条加班规则的具体信息" → `overtime search --name "..."`(search 直出)。如需查已删除/被覆盖的历史记录 → `overtime get` +用户说"考勤组列表/有哪些考勤组" → `group search` +用户说"考勤组详情/全量考勤组信息" → `group get`,若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId +用户说"考勤组成员/打卡地址/打卡wifi/打卡蓝牙" → `group filtered-get`(按需查询,节省成本),若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids `(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId +用户说"更新考勤组成员/添加考勤人员/删除考勤人员/添加考勤部门/删除考勤部门/加入考勤组/移出考勤组/设置无需考勤/取消无需考勤" → `group update-members` +用户说"修改考勤组/更新考勤组配置/考勤组改名/改变考勤组绑定的班次/修改打卡范围/设置考勤组负责人" → `group update` +用户说"创建考勤组/新建考勤组/添加考勤组" → `group create` +用户说"考勤汇总/统计" → `summary` +用户说"考勤组/考勤规则/打卡规则" → `rules` +用户说"查询个人规则设置/查看打卡提醒/查看极速打卡/查看缺卡提醒/查看打卡结果通知/查看个人考勤统计通知/查看团队考勤统计通知" → `selfsetting get` +用户说"更新个人规则设置/保存打卡提醒/修改极速打卡/关闭缺卡提醒/开启打卡结果通知/设置个人考勤统计通知/设置团队考勤统计通知" → `selfsetting save` +用户说"考勤字段/考勤列" → `report columns` +用户说"考勤数据/查询考勤报表数据" → `report query-data`(单次查询场景,非导出) + **导出考勤/导出报表/生成考勤报表/出勤汇总导出/考勤明细导出/迟到早退统计导出/全员考勤数据导出/月度考勤报表/考勤表格/考勤 Excel** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行**。 + - **排除**:如果用户说的是"导出**排班**表"/"导出**排班**"/"**排班**导出",这属于**排班查询导出**,应路由到 [attendance-schedule.md](./attendance-schedule.md),而非本条。判断标准:句中含"排班"二字 → 走排班;不含"排班"或明确说"考勤报表/考勤数据/出勤统计" → 走报表。 + - **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_*.py` 任何脚本 + - **严禁**仅凭脚本 `--help` 或本文件"自动化脚本"表格里的脚本路径就推断参数自行组装命令 + - 该文档定义了:报表类型默认值、列选择策略(`--column-keywords`)、阶段 1 人员获取流程、错误处理、输出摘要规范,缺一不可 + - 违反约束的后果:报表数据不全、列错位、人员遗漏、用户得到错误结果 +用户说"假期数据/年假/病假/请假记录" → `report query-leave` +用户说"假期/我的假期/假期规则" → `vacation types` +用户说"病假余额/年假余额/事假剩余假期"等查询指定假期规则的余额 → `vacation balance` +用户说"导出假期余额/假期余额列表/所有假期规则余额/假期余额 Excel/年假病假调休余额导出"等全部假期规则余额的查询 → **必须先 `read_file` 读取 [attendance-vacation.md](./attendance-vacation.md) 后按其中工作流执行** +用户说"假期变更/假期记录/请假扣减" → `vacation records` +用户说"更新假期规则/修改假期类型/编辑假期规则" → `vacation update-type --leave-code ` +用户说"设置假期余额/调整假期额度/更新假期余额" → 先调用 `vacation balance` 获取当前余额,计算修改后的值,再调用 `vacation save-balance` +用户说"增加假期余额/发放年假/给员工加年假" → 先调用 `vacation balance` 获取当前余额,加上要增加的天数,再调用 `vacation save-balance` 设置新总额度 +用户说"签到/签到记录" → `checkin records` ## 核心工作流 ```bash +# 导入排班记录 +dws attendance schedule import --group-id 123456 \ + --schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \ + --yes --format json + +# 获取排班记录 — 禁止直接调用,必须走 attendance-schedule.md 排班查询导出工作流 +# python scripts/attendance_schedule_export.py --users user001,user002 --start 2026-04-01 --end 2026-04-30 + +# 查询可管理的班次列表 +dws attendance class search --format json +dws attendance class search --query "早班" --filter-type MINE_OWN --format json + +# 查询班次详情 +dws attendance class get --class-id 1170996821 --format json + +# 更新班次 +dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 --format json +dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 --format json + +# 查询补卡规则 +dws attendance adjustment search --page 1 --limit 20 --format json +dws attendance adjustment search --query "标准" --page 1 --limit 20 --format json + +# 查询补卡规则详情 +dws attendance adjustment get --adjustment-id 12345 --format json + +# 查询加班规则 +dws attendance overtime search --page 1 --limit 20 --format json + +# 查询加班规则详情 +dws attendance overtime get --overtime-id 12345 --format json + +# 查询考勤组列表 +dws attendance group search --query "研发" --page 1 --limit 20 --format json +dws attendance group search --type FIXED --page 1 --limit 20 --format json + +# 查询考勤组全量信息 +dws attendance group get --group-id 123456 --format json + +# 按需查询考勤组成员/地址/蓝牙/Wifi +dws attendance group filtered-get --group-id 123456 --member --format json +dws attendance group filtered-get --group-id 123456 --position --wifi --format json + +# 更新考勤组成员 +dws attendance group update-members --group-id 123456 --add-users userId1,userId2 --timeout 10 --format json +dws attendance group update-members --group-id 123456 --remove-users userId1 --timeout 10 --format json +dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 --timeout 10 --format json + +# 更新考勤组配置 +dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 --format json +dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 --format json +dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 --format json + +# 创建考勤组 +dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 --format json +dws attendance group create --name "自由工时分组" --type NONE --timeout 10 --format json + +# 查看考勤统计摘要 +dws attendance summary --user --date "2026-03-12 15:00:00" --format json + # 查看考勤组和规则 dws attendance rules --date 2026-03-14 --format json -# 查询某人某天的打卡详情(先用 contact 拿 userId) -dws contact user search --query "张三" --format json -dws attendance record get --user --date 2026-03-08 --format json +# 查看指定用户的打卡提醒设置 +dws attendance selfsetting get --setting-scene checkRemind --user --format json -# 批量查询多人班次(最多 7 天、50 人) -dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07 --format json +# 查看指定用户的极速打卡设置 +dws attendance selfsetting get --setting-scene fastCheck --user --format json -# 查看某人本月考勤统计摘要 -dws attendance summary --user --date "2026-03-12 15:00:00" --stats-type month --format json +# 开启指定用户的打卡结果通知 +dws attendance selfsetting save --setting-scene checkResultNotify --user --check-result-msg 1 --format json + +# 更新指定用户的极速打卡设置 +dws attendance selfsetting save --setting-scene fastCheck --user \ + --onduty-check-type 3 --voice-remind-switch=true --format json + +# 获取考勤字段列表(管理员) +dws attendance report columns --format json + +# 根据字段查询考勤数据(管理员) +dws attendance report query-data --users userId1,userId2 \ + --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json + +# 查询用户假期数据(管理员) +dws attendance report query-leave --users userId1,userId2 \ + --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json + +# 查看假期规则列表 +dws attendance vacation types --format json + +# 查看指定员工假期余额 +dws attendance vacation balance --users userId1,userId2 --format json + +# 查看指定员工某类假期余额 +dws attendance vacation balance --users userId1 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json + +# 查看指定员工假期余额变更记录 +dws attendance vacation records --user USER_ID --start 2026-04-01 --end 2026-04-22 --format json + +# 更新假期规则名称 +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --name "事假(修改版)" --format json + +# 更新假期单位 +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --unit hour --per-hours 8 --format json + +# 更新适用范围 +dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' --format json + +# 设置员工假期余额完整流程 +# 1. 查询当前余额 +dws attendance vacation balance --users user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json + +# 2. 根据查询结果计算新值(如当前5天,要设置为8天) + +# 3. 执行设置(SET操作,会替换当前余额) +dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --num 8 --reason "年度发放" --start 2024-01-01 --end 2024-12-31 --format json + +# 增加员工假期余额完整流程(ADD场景) +# 1. 查询当前余额(假设返回5天) +dws attendance vacation balance --users user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json + +# 2. 计算增加后的新值(5 + 3 = 8天) + +# 3. 设置新总额度 +dws attendance vacation save-balance --target user001 \ + --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + --num 8 --reason "绩效奖励发放3天" --format json + +# 查询签到记录 +dws attendance checkin records --operator-staff-id op001 --staff-ids user001,user002 \ + --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" --format json ``` ## 上下文传递表 - | 操作 | 提取 | 用于 | |------|------|------| -| `contact user search/get-self` | `userId` | record get / summary 的 --user、shift list 的 --users | +| `contact user get-self` | `userId` | summary 的 --user | +| `rules` | `groupId` | schedule import 的 --group-id | +| `schedule import` | `classId` | schedule import 的 schedules 中的 classId | +| `contact user search` | `userId` | schedule import/get 的 userId | +| `contact user get-self` / `contact user search` | `userId` | summary 的 --user, vacation records 的 --user;selfsetting get/save 的 --user(必填) | +| 当前登录上下文 | `corpId`, `opUserId` | selfsetting get/save 自动补齐 MCP 入参, CLI 不需要传 `--corp-id` / `--op-user` | +| `vacation types` | `leaveCode` | vacation balance 的 --leave-code, vacation records 的 --leave-code | ## 注意事项 +**Agent 使用引导**: +- 执行 vacation 子命令前,**必须先调用** `dws attendance vacation --help` 查看完整子命令列表和参数说明 +- 新增命令可能不在 Agent 缓存中,直接猜测命令会失败 +- 正确流程:查看帮助 → 选择命令 → 查看命令详细参数(如 `dws attendance vacation update-type --help`)→ 执行 -- `--user` / `--users` 需要 userId,可先用 `contact user search`、`contact user get-self` 或 `aisearch person` 获取 -- `shift list` 单次最多 7 天、50 人,跨度更大需分批 -- `summary` 的 `--stats-type` 必填(week / month),缺省会被钉钉服务端拒绝 -- 认证信息(corpId、optUserId)由系统自动注入,无需手动传入 +- `record get` 的 `--date` 格式: YYYY-MM-DD(如 `2026-03-08`),CLI 自动转换为毫秒时间戳 +- `shift list` 查询班次信息,`--start/--end` 使用 YYYY-MM-DD 格式,间隔不超过 7 天 +- `schedule import` 导入排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流执行 +- `schedule get` 查询排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流执行(脚本自动分批、姓名转换、排班表格式导出) +- `schedule import` 是写操作,AI 调用时必须先展示导入摘要并引导用户二次确认;用户明确确认后才允许执行。用户明确要求跳过确认或命令包含全局 `--yes` 时,可跳过二次确认 +- `class search` 所有参数均为可选,不填时返回全部可管理班次(默认第 1 页,每页 20 条) +- **概念区分**:班次是员工当天打卡安排;排班是为排班制考勤组导入的排班记录;班次定义是考勤管理员创建的工作时间规则 +- `class get` 的 `--class-id` 必填,班次 ID 可从 `class search` 结果中提取 +- `class search` 返回结果已包含全量属性,无需再调用 `class get`;`class get` 仅在需要按已知 classId 精确查询时使用 +- `class update` 的 `--class-id` 必填,其余均可选,仅需对要修改的字段赋值,未传字段会自动从已有配置补充;由于保存班次耗时较久,建议加 `--timeout 10` +- `adjustment search` 返回结果已包含全量属性,无需再调用 `adjustment get`;`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用 +- `overtime search` 返回结果已包含全量属性,无需再调用 `overtime get`;`overtime get` 仅在需要按已知 overtimeId 查询时使用(包括已删除/被覆盖的历史记录) +- `adjustment search` / `overtime search` 分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 +- `group search` 的分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20 +- `group get` 的 `--group-id` 必填,返回考勤组全量字段;如仅需成员/地址/蓝牙/Wifi,优先使用 `group filtered-get` 节省成本。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** +- `group update-members` 的 --group-id 必填,其余参数均可选,但至少需传一个变更项;各参数每次最多 20 个 ID;`--add-extra-users` 和 `--remove-extra-users` 操作的是"无需考勤"豁免名单,不影响考勤组主成员列表 +- `group update` 的 --group-id 必填,其余均可选,至少需指定一个修改项;仅需对要修改的字段赋値,未传字段会从已有配置自动补充;修改打卡地址/wifi/蓝牙等复杂子对象时用 `--group-vo` 传入完整 JSON;`--group-vo` 与单字段 flag 同时传入时单字段 flag 优先级更高 +- `group create` 的 `--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一;type=FIXED 时 `--group-vo` 必须包含 `workDayClassList`(非空)和 `defaultClassId`(非 null);由于保存考勤组耗时较久,建议加 `--timeout 10` +- `group filtered-get` 的 `--group-id` 必填,`--member/--position/--wifi/--bles` 均可选,默认 false。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids ,,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。** +- `summary` 的 `--date` 格式: yyyy-MM-dd HH:mm:ss(如 `2026-03-12 15:00:00`) +- `rules` 的 `--date` 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss 两种格式 +- `selfsetting get/save` 的 `--setting-scene` 必须是 `checkRemind`、`fastCheck`、`checkResultNotify`、`lackRemind`、`personalAttendStatNotify`、`bossAttendStatNotify` 之一 +- `selfsetting get/save` 的 MCP 入参 `userId` 为必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID +- `selfsetting save` 必须传入与 `--setting-scene` 对应的至少一个设置字段;不同场景的字段不能混用 +- `selfsetting save` 是敏感写操作,AI 调用时必须先执行 `selfsetting get` 查询当前值,并向用户展示目标用户、设置场景、修改字段、“当前值 → 新值”和最终命令参数摘要;必须调用 `ask_human` 或返回待确认状态等待用户明确确认;用户确认后才允许追加全局 `--yes` 执行保存。禁止未经确认直接执行或自动添加 `--yes` +- `selfsetting get/save` 不需要传 `--corp-id` / `--op-user`,`corpId` 和 `opUserId` 由当前登录上下文自动补齐 +- `report columns` 无需额外参数,corpId 和 operatorId 由系统自动传入 +- `report query-data` 和 `report query-leave` 的 `--start/--end` 格式: yyyy-MM-dd HH:mm:ss,间隔不超过 32 天,最多 20 人 +- report 系列接口仅对管理员开放 +- 用户 ID 需从 `contact user get-self` 或 `aisearch person` 获取 +- 考勤组 ID 需从 `rules` 命令返回结果中获取 +- `vacation types` 无需任何参数,认证信息自动注入 +- `vacation balance` 的 `--users` 为目标员工 ID 列表,逗号分隔;`--leave-code` 选填,可通过 `vacation types` 获取 +- `vacation records` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳;`--leave-code` 选填 +- `vacation balance` 和 `vacation records` 的认证参数(corpId、opUserId)由系统自动注入,无需手动传入 +- `vacation update-type` 的 `--leave-code` 必填;其他字段均为可选,但至少需传一个更新字段 +- `vacation update-type` 的 `--visibility-rules` 为 JSON 数组字符串,格式:`[{"type":"dept","visible":["1","2","3"]}]`,type 可取值 staff/label/dept +- `vacation save-balance` 是 **SET 接口**而非 ADD 接口:传入值会直接替换当前余额,而非累加 +- `vacation save-balance` 的 `--num` 输入为实际天数(如 8 或 7.5),内部会乘以 100 传给 MCP(如 800 或 750) +- `vacation save-balance` 执行前需先调用 `vacation balance` 查询当前余额,再计算新值,避免误操作 +- `vacation save-balance` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳 +- `vacation update-type` 和 `vacation save-balance` 执行前会展示待写入数据,需用户输入 yes/y 确认后提交 +- 假期编码为 UUID 格式字符串,可通过 `vacation types` 命令查询获取 + +### 改签打卡记录 + +**命令**: `dws attendance boss-check` + +**功能**: 改签打卡记录,管理员可修改员工的打卡时间、打卡结果等信息。 + +**强制执行流程**: 此命令为写操作,Agent 调用时必须遵守以下流程: +1. 先向用户展示待执行操作的完整参数摘要 +2. 使用 `ask_human` 或返回待确认状态,等待用户明确确认 +3. 用户确认后,再传 `--user-say-yes=true` 执行命令 +4. **禁止**未经用户确认直接执行或自动添加 `--user-say-yes=true` + +**参数**: +| 参数 | 必填 | 说明 | 来源 | +|------|------|------|------| +| --plan-id | y* | 排班ID(与 --result-id 二选一) | `dws attendance schedule get` 返回的 `id` 字段 | +| --result-id | y* | 打卡结果ID(与 --plan-id 二选一,优先使用) | **暂不支持**(record get 未返回此字段) | +| --time | n | 新打卡时间,格式 yyyy-MM-dd HH:mm | - | +| --result | n | 打卡结果枚举值 | - | +| --absent-min | n | 缺勤时长(分钟) | - | +| --remark | n | 备注,最长500字符 | - | +| --user-say-yes | n | 用户已确认,跳过交互式确认提示 | - | + +**获取 planId 步骤**: +1. 查询排班记录:`dws attendance schedule get --userIdList USER_ID --workDateBegin DATE --workDateEnd DATE` +2. 从返回结果中找到对应打卡类型(OnDuty=上班,OffDuty=下班)的记录 +3. 使用该记录的 `id` 字段作为 `--plan-id` 参数 +4. 示例返回:`{"id": 948964045503, "checkType": "OffDuty", ...}` → `--plan-id 948964045503` + +**打卡结果枚举值**: +- Normal: 正常 +- TimesResultA: 迟到 +- TimesResultB: 早退 +- TimesResultC: 缺卡 +- TimesResultD: 迟到+早退 +- TimesResultE: 缺卡+早退 +- TimesResultF: 迟到+缺卡 + +**示例**: +```bash +# 步骤1:获取排班记录的 planId +dws attendance schedule get --userIdList 03642229451220076 --workDateBegin 2026-05-13 --workDateEnd 2026-05-13 -f json + +# 步骤2:使用返回的 id 作为 --plan-id 改签 +# 假设返回 id: 948964045503 (OffDuty 下班打卡) +dws attendance boss-check --plan-id 948964045503 --result Normal --user-say-yes + +# 同时修改打卡时间 +dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --result Normal --yes +``` + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [attendance_my_record.py](../../scripts/attendance_my_record.py) | 查看我今天/指定日期的考勤记录 | `python attendance_my_record.py today` | +| [attendance_team_shift.py](../../scripts/attendance_team_shift.py) | 查询团队成员本周排班 | `python attendance_team_shift.py --users userId1,userId2` | +| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 考勤报表导出公共模块(不可单独执行) | — | +| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出 | **禁止直接调用**,必须先读 [attendance-vacation.md](./attendance-vacation.md) 按工作流执行 | +| attendance_report_detail.py | 考勤报表 — **明细粒度** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_report_monthly.py | 考勤报表 — **月度汇总** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_report_daily.py | 考勤报表 — **每日统计** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 | +| attendance_schedule_import.py | 排班导入(含校验、回显、执行) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | +| attendance_schedule_export.py | 排班查询导出(分批查询、排班表 Excel) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 | + +> 说明: +> - `attendance_report_*.py` 三个脚本由 [attendance-report.md](./attendance-report.md) 工作流编排使用,自动处理 `--users` 超过 20 人分批、`--start/--end` 超过 32 天按月切片,输出 `attendance_report___<粒度>.xlsx` +> - `attendance_schedule_import.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流编排使用,自动处理考勤组校验、班次校验、排班回显确认 +> - `attendance_schedule_export.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流编排使用,自动处理分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、输出排班表格式 Excel + +## 严格约束 +- 不要凭历史记忆复用 userId / classId / leaveCode / groupId / instanceId 等任何 ID,每次必须从当次命令返回值中提取 +- 不要猜测命令,先查询明确命令,不要使用记忆中未经证实(即不存在于本.md文件中的指令)或执行失败(指令传参有误或flag不正确)的的命令 +- 制定 plan 并自我审查,严格按 plan 执行 +- 涉及超过 3 条记录的聚合(求和、分组、计数、排序、跨字段计算)时必须落 Python 脚本处理,禁止用大模型口算或目测。脚本里如果用到 mcp,先提前看下 mcp 返回的结构,避免执行异常 +- 遇到时长字段时,注意区分单位是秒、分钟还是小时 +- 遇到意图不清晰的场景不要猜测,主动询问用户明确意图 +- 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。 \ No newline at end of file diff --git a/skills/mono/references/products/calendar.md b/skills/mono/references/products/calendar.md index 3e0f6950..52dca8cd 100644 --- a/skills/mono/references/products/calendar.md +++ b/skills/mono/references/products/calendar.md @@ -2,7 +2,7 @@ ## CLI 命令树与黄金路径 -- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(用户日历列表)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 +- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(我能看哪些日历本)、`acl`(我的日历共享给了谁)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。 - **个人日程 / 给自己留时间块 / 专注时段**:统一走 **`dws calendar event create`**。当前**没有**单独的 `personal schedule create` / `calendar create` 命令。 - **查日程列表**:`dws calendar event list --start "" --end "" --format json`,或优先使用脚本 `python scripts/calendar_today_agenda.py [today|tomorrow|week]`(见文末「自动化脚本」)。 - **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。 @@ -10,7 +10,7 @@ - **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。 ## 反模式(禁止) -1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book`。 +1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book` / `acl`。 2. **禁止**使用不存在的子命令试探(如臆造 `dws calendar list`);需要日程列表时一律使用 **`dws calendar event list`**(带 `--start` / `--end`,见下文「查询日程列表」示例);需要日历本列表时使用 **`dws calendar book list`**。 3. **禁止**将完整 `--help`/Usage 输出作为「观察」重复提交给模型;若误触,应直接改用本节黄金路径中的合法命令并重试。 4. **禁止**继续使用已弃用的 `--max-results` 参数 —— 它们仍可被解析但**会被丢弃**,不再透传到 MCP,模型生成命令时也不要再带。 @@ -30,6 +30,9 @@ 响应状态(response):参会人对日程的回应,包括:未响应、接受、待定、拒绝。 忙闲时间(busy):查询用户在指定时间段的忙闲状态,查询会议室在指定时间段的预定状态,用于会议时间协调。 会议室(room):room是 会议室 ,room可视为日程的资源类参会人,需要加入日程完成预订。注意和location区分,location只是地点,和room不同。 +访问控制 (acl):用户可通过设置acl将自己的日历访问权限授予给他人(即:共享日历),授予后,他人就可以查看日历下的日程信息。通过 `dws calendar acl list` 可查看当前要用户已经授权出去的权限。若 privilege >= reader,可查询此日历下的日程数据。若 privilege >= writer ,可操作(创建/修改/删除/响应等)此日历下的日程数据。 +共享日历:将自己日历的访问权限授予给他人,用于协作。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `shared` 即他人共享给当前用户的日历。 +订阅日历:创建一个公共日历供他人订阅,他人订阅后即可查询日历下的日程数据。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `subscribed` 即当前用户已订阅的日历。注意:订阅日历下的日程无参会人概念,无法执行 attendee 相关指令,也无法添加会议室。 ## 命令概览 @@ -74,10 +77,26 @@ dws calendar busy search [flags] dws calendar attachment add [flags] ``` -### book 相关三级子命令 +### book 相关三级子命令(查"我能看哪些日历本") ``` -# 查询当前用户的所有日历,结果范围:用户自己的日历、已订阅的公共/团队日历、他人共享的日历。 +# 查询我拥有和可访问的所有日历本(含他人共享给我的) dws calendar book list [flags] +# 查询指定日历本信息 +dws calendar book get [flags] +# 按名称模糊搜索日历本 +dws calendar book search [flags] +# 更新日历本信息(需 owner 权限) +dws calendar book update [flags] +``` + +### acl 相关三级子命令(查"我的日历共享给了谁") +``` +# 查询我的日历共享给了哪些人、各自什么权限 +dws calendar acl list [flags] +# 把我的日历共享给某人 +dws calendar acl add [flags] +# 取消我的日历对某人的共享 +dws calendar acl delete [flags] ``` > **说明**: 可以通过 --help 进一步查看指令明细,也可以继续查看下一节 命令总览 @@ -217,13 +236,12 @@ Flags: Usage: dws calendar attendee add [flags] Example: - dws calendar attendee add --event --users , - dws calendar attendee add --event --users --optional true + dws calendar attendee add --event --attendees , + dws calendar attendee add --event --attendees --optional Flags: - --event string 日程 eventId (必填) - --users string 参会人 userId 列表,逗号分隔 (必填) - --optional string 是否可选参会人 (可选) - --calendar-id string 日历 ID (可选) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人) + --optional 参会人可选 (默认必选参会人) ``` ### 移除参会人 @@ -234,11 +252,10 @@ Flags: Usage: dws calendar attendee delete [flags] Example: - dws calendar attendee delete --event --users --yes + dws calendar attendee delete --event --attendees --yes Flags: - --event string 日程 eventId (必填) - --users string 参会人 userId 列表,逗号分隔 (必填) - --calendar-id string 日历 ID (可选) + --event string 日程 ID (必填) + --attendees string 参会人 userId 列表,逗号分隔 (必填) ``` ### 搜索会议室 @@ -317,10 +334,10 @@ Usage: dws calendar room list-groups [flags] Example: dws calendar room list-groups - dws calendar room list-groups --page-size 20 --page-index 0 + dws calendar room list-groups --limit 20 --page 0 Flags: - --page-size string 页大小 (可选,默认 100,上限 100) - --page-index string 分页起始位置 (可选,默认 0) + --limit string 页大小 (可选,不填默认 100,超过 100 按 100 处理) + --page string 分页起始位置 (可选,不填默认 0) ``` ### 添加日程附件 @@ -343,10 +360,88 @@ Usage: Example: dws calendar book list ``` -> 通过此接口可查询当前用户的日历列表,包含 主日历本、他人共享的日历、订阅的公共/团队日历。 +> 通过此接口可查询当前用户的日历列表,包含 主日历本、他人共享的日历、订阅的公共/团队日历。查的是"我能看哪些日历本";注意区分:`acl list` 是查"我的日历共享**给了谁**",方向相反。 > 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id `可查到xxx完整的日程安排 > 主日历 `id` 固定为 `primary`,绝大多数日程操作都默认走主日历,只有当用户明确要求查/写其他日历本时才需要带 `--calendar-id`。 +### 查询指定日历本 +``` +Usage: + dws calendar book get [flags] +Example: + dws calendar book get --id primary + dws calendar book get --id CALENDAR_ID +Flags: + --id string 日历 ID (必填,主日历固定为 primary) +``` + +> **说明**:根据日历 id 查询指定日历的详细信息。用户主日历本 id 固定为 `primary`。 + +### 搜索日历本 +``` +Usage: + dws calendar book search [flags] +Example: + dws calendar book search --query "项目" + dws calendar book search --query "团队周报" +Flags: + --query string 按日历本名称模糊检索 (必填) +``` + +> **说明**:搜索当前用户拥有的日历本,支持按日历本名模糊搜索。获取全部日历请使用 `book list`。 + +### 更新日历本 +``` +Usage: + dws calendar book update [flags] +Example: + dws calendar book update --id CALENDAR_ID --summary "新日历名" + dws calendar book update --id CALENDAR_ID --desc "日历描述" +Flags: + --id string 日历 ID (必填) + --summary string 日历标题 + --desc string 日历描述 +``` + +> **说明**:更新日历信息,最低权限要求:privilege == "owner"。注意:用户主日历本 以及 他人共享的日历本 **不支持更新**。 + +### 查询我的日历共享给了谁 +``` +Usage: + dws calendar acl list [flags] +Example: + dws calendar acl list +``` + +> **说明**:查询"我的日历共享给了哪些人、各自什么权限"(即主日历的访问控制列表)。注意区分:`book list` 是查"我能看哪些日历本",方向相反。 + +### 把我的日历共享给某人 +``` +Usage: + dws calendar acl add [flags] +Example: + dws calendar acl add --user USER_ID --privilege reader + dws calendar acl add --user USER_ID --privilege writer --no-notification +Flags: + --user string 授予权限的目标用户 ID (必填) + --privilege string 授予的日历权限 (必填): free_busy_reader(查看忙闲)|title_reader(查看标题)|reader(查看详情)|writer(创建和编辑) + --no-notification 不向被授权用户发送提醒 (默认发送) +``` + +> **说明**:把我的日历共享给指定用户,授予对方相应权限。`--privilege` 可选值:`free_busy_reader`(查看忙闲)、`title_reader`(查看标题)、`reader`(查看详情)、`writer`(创建和编辑)。 + +### 取消我的日历对某人的共享 +``` +Usage: + dws calendar acl delete [flags] +Example: + dws calendar acl delete --acl-id ACL_ID +Flags: + --acl-id string 已授予权限的 ID (必填,可通过 acl list 查询) +``` + +> **说明**:取消我的日历对某人的共享(撤回已授予的访问权限)。aclId 可通过 `acl list` 获取。 + ### 查询用户 / 会议室闲忙状态 ``` Usage: @@ -419,8 +514,8 @@ Flags: 用户说"参会人/与会者": - 查看 → `attendee list` -- 邀请/添加 → `attendee add --users `(可选参会人加 `--optional true`) -- 移除 → `attendee delete --users ` +- 邀请/添加 → `attendee add --attendees `(可选参会人加 `--optional`) +- 移除 → `attendee delete --attendees ` 用户说"会议室/订会议室": - 哪个空闲 → `room search` @@ -439,8 +534,18 @@ Flags: 用户说"日程附件/给会议加文件/上传日程材料": - 添加 → `attachment add`(先用钉盘上传得 fileId,再 `attachment add --files :`) -用户说"我有几个日历/查所有日历/共享日历本": -- 列表 → `book list`(主日历 id 固定为 `primary`) +用户说"我有几个日历/查所有日历/别人共享给我的日历/他人共享给我的日历本": +- 列表 → `book list`(返回用户拥有和订阅的所有日历本,包括他人共享给自己的;主日历 id 固定为 `primary`) +- 查指定日历本 → `book get --id ` +- 按名称搜索日历本 → `book search --query "关键词"` +- 修改日历本名称/描述 → `book update --id --summary "新名"` + +用户说"我的日历共享给了谁/谁能看我日历/日历权限/取消共享/把日历分享给xxx": +- 查看我共享出去的情况(即谁有权访问我的日历) → `acl list` +- 把我的日历共享给他人 → `acl add --user --privilege reader` +- 取消我的日历对某人的共享 → `acl delete --acl-id `(aclId 来自 `acl list`) + +> **易混淆辨析**:`book list` 查的是"我能看哪些日历本"(包含别人共享**给我**的);`acl list` 查的是"我的日历共享**给了谁**"(我的主日历的访问控制列表)。两者方向相反,不可混用。 用户说"查下xxx的日程安排": - 查询是否有共享关系 -> `book list` @@ -479,7 +584,7 @@ dws calendar event create --title "Q1 复盘会" \ --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json # Step 2: 添加参会人(必须用 Step 1 返回的 eventId) -dws calendar attendee add --event --users userId1,userId2 --format json +dws calendar attendee add --event --attendees userId1,userId2 --format json # Step 3: 搜索空闲会议室 dws calendar room search --start ... --end ... --format json @@ -512,20 +617,24 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 | `event respond` | 响应结果 | — | | `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms | | `room list-groups` | `groups[].groupId` | room search 的 --group-id | -| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id | +| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id | +| `book get` | 日历详细信息 | — | +| `book search` | 匹配的日历列表 | book get/update 的 --id | +| `acl list` | `aclId` | acl delete 的 --acl-id | +| `acl add` | 新增的权限记录 | — | | 钉盘上传 | 文件 `fileId` | attachment add 的 --files `:` | ## 注意事项 - 时间格式: `event create/update`、`event list`、`busy search` 和 `event suggest` 用 ISO-8601 - 时区: `event create/update` 和 `event suggest` 支持 `--timezone` 指定 IANA 时区(如 `Asia/Shanghai`、`America/New_York`),不传默认 `Asia/Shanghai` -- 创建日程时可通过 `--attendees` 直接指定参会人(最多500人),也可创建后用 `attendee add --users ...` 单独添加 -- `event create` 的 `--attendees` 和 `--open-dingtalk-ids` 至少传一个(如果需要指定参会人) -- `attendee add` 可通过 `--optional true` 设为可选参会人(默认必选) +- 创建日程时可通过 `--attendees` 直接指定参会人(最多500人),也可创建后用 `attendee add --attendees ...` 单独添加 +- `--attendees` 和 `--open-dingtalk-ids` 至少传一个(如果需要指定参会人) +- 添加参会人时可通过 `--optional` 设为可选参会人(默认必选) - `event suggest` 根据参会人闲忙自动推荐合适时间,适合会议时间未确定时使用 - 创建日程**支持**通过 `--rooms` 一步预定会议室(`event create --rooms roomId1,roomId2`);若创建后再加,仍可用 `room add` - `room search` 不带 `--group-id` 时查根目录;企业会议室超过 100 条会报错,此时需先 `room list-groups` 获取分组,再按分组逐一查询 -- `room list-groups` 支持 `--page-size` / `--page-index` 分页(schema 类型为字符串) +- `room list-groups` 支持 `--limit` / `--page` 分页(schema 类型为字符串) - **`event create --rooms` / `room add --rooms` 的唯一合法来源**:最近一次(同一会话、同一时段窗口)`room search` 返回体中的 `roomId`;禁止把用户自然语言会议室名当 `roomId` 传入(否则会 `roomId invalid` 等错误) - **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId - **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` @@ -544,4 +653,4 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 ## 相关产品 - [conference](./simple.md) — 仅视频会议预约(返回入会链接),不含参会人/会议室管理 -- [contact](./contact.md) — 搜索同事 userId,用于 attendee add --users +- [contact](./contact.md) — 搜索同事 userId,用于 attendee add --attendees diff --git a/skills/mono/references/products/chat.md b/skills/mono/references/products/chat.md index b7b1ff34..ad5491b2 100644 --- a/skills/mono/references/products/chat.md +++ b/skills/mono/references/products/chat.md @@ -18,9 +18,9 @@ Flags: #### 查看群成员列表 — 分页查询指定群聊的成员 ``` Usage: - dws chat group members list [flags] + dws chat group members [flags] Example: - dws chat group members list --id + dws chat group members --id Flags: --cursor string 分页游标,首次从 0 开始 --id string 群 ID / openconversation_id (必填) @@ -47,6 +47,7 @@ Flags: --id string 群 ID / openconversation_id (必填) --users string 要移除的用户 userId 列表,逗号分隔 (必填) ``` +> 移除对象是群主时 CLI 会拦截并报错,提示先用 `chat group transfer-owner` 转让群主(防止产生无主群);查不到群主时放行。 #### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错 ``` @@ -100,11 +101,13 @@ Usage: dws chat group transfer-owner [flags] Example: dws chat group transfer-owner --group --new-owner + dws chat group transfer-owner --group --user # 查询群 ID: dws chat search --query "群名" # 查询人员: dws aisearch person --keyword "姓名" --dimension name Flags: --group string 群聊 openConversationId (必填) - --new-owner string 新群主 openDingTalkId (必填) + --new-owner string 新群主 openDingTalkId + --user string 新群主 userId ``` #### 获取群邀请链接 — 获取指定群聊的邀请加入链接 @@ -145,6 +148,7 @@ Flags: --group string 群聊 openConversationId (必填) --icon-media-id string 群头像 mediaId (必填) ``` +> `--icon-media-id` 有本地格式校验:必须是 `@` 开头的媒体 ID(如 `dt_media_upload` 的返回值),非法格式会在本地直接报错。 #### 更新群设置 — 更新指定群聊的设置项 @@ -334,74 +338,84 @@ Flags: --cursor string 分页游标(默认 "0",翻页传 nextCursor) ``` +### data-auth (数据授权) + +#### 跨组织数据授权 — 授予 chat 跨组织消息拉取权限 + +用于跨组织消息拉取等数据访问场景,不用于发送、撤回、群管理等操作。该命令调用 `chat_permission_grant`,固定使用数据授权类别:`scope=chat.data:cross-org`、`grantCategory=data`。 + +``` +Usage: + dws chat data-auth cross-org [flags] + +Example: + dws chat data-auth cross-org --target-org-id 439446171 --format json + dws chat data-auth cross-org --target-org-id 439446171 --agentCode wukong --grant-type timed --ttl 24h --format json + dws chat data-auth cross-org --all --grant-type timed --ttl 24h --format json + +Flags: + --target-org-id string 目标组织 ID(与 --all 二选一) + --all 授权所有目标组织 + --agentCode string Agent 标识,默认 wukong + --grant-type string 授权策略: once|session|timed|permanent (default "timed") + --ttl string timed 授权有效期,如 1h/4h/24h/7d (default "24h") + --session-id string session 授权的会话标识 + +注意: + - --target-org-id 和 --all 必须且只能指定一个。 + - --grant-type=session 时必须传 --session-id。 + - --grant-type=timed 时必须传 --ttl。 +``` + ### message (会话消息管理) -#### 拉取群聊会话消息内容 — 拉取指定群聊的会话消息内容(仅群聊) +#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容 ---group 指定群聊 openConversationId(**本命令仅支持群聊**;拉取单聊/私聊消息请改用 `chat message list-direct`)。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 +--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 ``` Usage: dws chat message list [flags] Example: dws chat message list --group --time "2025-03-01 00:00:00" - dws chat message list --group --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --user --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 50 dws chat message list --group --time "2025-03-01 00:00:00" --forward=false - # 拉单聊改用 list-direct: dws chat message list-direct --user --time "2025-03-01 00:00:00" Flags: --forward true=拉给定时间之后的消息,false=拉给定时间之前的消息 (default true) - --group string 群聊 openconversation_id(必填,**仅支持群聊**;查单聊用 chat message list-direct --user ) + --group string 群聊 openconversation_id(群聊时必填) --limit int 返回数量,不传则不限制 --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) + --user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一) + --open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景) 注意: - - 本命令**仅支持群聊**,必须指定 --group;拉取单聊(私聊)消息请改用 `chat message list-direct`(旧版的 `list --user` / `list --open-dingtalk-id` 已不再支持) + - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id + - --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识: + - --user 传 userId(企业内部应用常用) + - --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group --topic-id 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。 ``` -#### 拉取单聊消息内容 — 按对方 userId 拉取与某同事的单聊(私聊)历史消息 - -按对方 userId(或 openDingTalkId)拉取与该同事的单聊会话消息,**专用于私聊**;查群聊请用 `chat message list --group`。同组织内同事用 --user,非同组织好友用 --open-dingtalk-id,二者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 -``` -Usage: - dws chat message list-direct [flags] -Example: - dws chat message list-direct --user <对方userId> --time "2025-03-01 00:00:00" --limit 50 - dws chat message list-direct --user <对方userId> --time "2025-03-01 00:00:00" --forward=false - dws chat message list-direct --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 20 - # 查询对方 userId: dws contact user search --keyword "姓名" 或 dws aisearch person --keyword "姓名" --dimension name -Flags: - --forward true=从老往新(给定时间之后),false=从新往老(给定时间之前) (default true) - --user string 对方 userId(同组织内同事,与 --open-dingtalk-id 二选一) - --open-dingtalk-id string 对方 openDingTalkId(非同组织普通好友场景,与 --user 二选一) - --limit int 每页返回数量(默认 50) - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - -注意: - - --user 与 --open-dingtalk-id 二选一,必须且只能指定其一;同组织同事优先用 --user - - --time 必填;翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - - 本命令是 `chat message list` 拆分出的单聊专用命令;查群聊消息请用 `chat message list --group` -``` - #### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊 **重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** -**`--ai-tag` 默认开(默认 true)**:dws 发送的消息默认带「通过AI发送」角标,正常发无需特意加;仅当用户要求按本人发、不带角标时传 `--ai-tag=false`。仅 send / reply 支持。 - --group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 --群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 ---富媒体消息:通过 --msg-type 指定类型(image/file),必须根据文件扩展名判断 msgType 后再发送。 +--富媒体消息:通过 --msg-type 指定类型(image/file/audio/video),audio/video 是 file 的语义别名,发给服务端仍按 file 发送;必须根据文件扩展名判断 msgType 后再发送。 ``` Usage: dws chat message send [flags] [] 富媒体消息 msgType 决策(必须按此规则判断,不可跳过): 文件扩展名 → msgType → 发送参数 - .jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py ` 提取 mediaId → --msg-type image --media-id - 其他所有(.mp3/.wav/.mp4/.avi/.pdf/.doc/.xls/.zip 等) → file → conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size + .jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py ` 提取 mediaId → --msg-type image --media-id + .mp3/.wav/.m4a/.aac/.flac → audio(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type audio + .mp4/.mov/.avi/.mkv/.webm → video(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type video + 其他所有(.pdf/.doc/.xls/.zip 等) → file → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type file Example: dws chat message send --group --text "hello" @@ -413,17 +427,15 @@ Example: dws chat message send --group --text "hello" --uuid "unique-id-123" dws chat message send --group --at-all "@all 请大家注意" dws chat message send --group --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收" - # 默认即带「通过AI发送」角标,无需特意加 --ai-tag - dws chat message send --user --text "已处理好了" - # 仅当用户要求按本人发送、不带角标时 - dws chat message send --user --text "已处理好了" --ai-tag=false # 发送图片 dws chat message send --group --msg-type image --media-id - # 发送文件(音频/视频/文档等非图片文件统一走钉盘上传) + # 发送文件/音频/视频(audio/video 是 file 的语义别名) # 先 dws chat conversation-info --group 获取 spaceId(取 newCSpaceIdIM) # 再 dws drive upload --file <文件> --space-id 上传 # 再 dws drive info --file-id --space-id 获取 dentryId dws chat message send --group --msg-type file --dentry-id --space-id 24557356340 --file-name "report.pdf" --file-type "pdf" --file-path "/report.pdf" --file-size 234724 + dws chat message send --group --msg-type audio --file-path ./recording.mp3 + dws chat message send --group --msg-type video --file-path ./demo.mp4 Flags: --text string 消息内容(推荐使用,也可用位置参数) --group string 群聊 openconversation_id(群聊时必填) @@ -432,9 +444,8 @@ Flags: --title string 消息标题(可选,默认「消息」) --at-all @所有人(仅群聊时生效,可选,默认 false) --at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选) - --ai-tag 标记为「通过AI发送」角标,默认 true(默认带上);传 --ai-tag=false 关闭(按本人发送) --media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py ` 提取,仅 msgType=image) - --msg-type string 消息类型: image/file(image 用 mediaId,file 用钉盘上传) + --msg-type string 消息类型: image/file/audio/video(audio/video 是 file 别名;image 用 mediaId,file/audio/video 用钉盘上传) --dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取) --space-id int64 钉盘空间 ID(msgType=file 时必填) --file-name string 文件名(msgType=file 时必填) @@ -449,21 +460,57 @@ Flags: - 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力 - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符@all;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2> - - **@ 群内机器人**:务必使用 `dws chat group bots --group ` 返回的 openDingtalkId(群级别 ID,与全局搜索结果不同);全局 `chat bot find` 返回的 ID 无法正确 @ 机器人 - **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可: 1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行 2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `
`),或直接写 HTML 的 `
` 标签 - 富媒体消息类型与参数对应关系: - image(图片):--msg-type image --media-id - - file(音频/视频/文档等所有非图片文件):先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size + - audio/video:file 的语义别名,发给服务端仍是 file;可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type audio 或 --msg-type video --dentry-id --space-id --file-name --file-type --file-path --file-size + - file(文档/压缩包等其他非图片文件):可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size - mediaId 通过 dt_media_upload 上传获得,必须用脚本提取:`python scripts/extract_media_id.py ""`(输出如 @lQLPxxx,直接用于 --media-id)。禁止手动从 URL 中截取或拼接 mediaId,手动解析会因 URL 格式不稳定导致尺寸后缀残留 - --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持) - 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送 - - 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,其他所有→file;不可跳过此判断步骤 + - 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,音频(.mp3/.wav/.m4a/.aac/.flac)→audio,视频(.mp4/.mov/.avi/.mkv/.webm)→video,其他所有→file;不可跳过此判断步骤。audio/video 是 file 别名,不代表不支持音频/视频 - 20MB 降级:图片超过 20MB 时 dt_media_upload 会失败,必须降级走钉盘上传 + Markdown 嵌入方式发送(参见「发送图片+文字消息」章节)。音频/视频/文件走钉盘上传无 20MB 限制 - 发送文字 + 文件混合消息时的完整流程:除了将文件以 Markdown 链接内嵌到文字消息中发送一条 md 消息外,还必须额外逐个发送独立的文件消息(--msg-type file),确保接收方可以直接下载原始文件。即:先发一条包含文字和文件链接的 md 消息,再对每个涉及的文件各发一条 --msg-type file 的文件消息 ``` +### file (会话文件上传) + +#### 上传本地文件或 URL 文件到会话文件空间 — 不暴露 spaceId + +上传文件到指定会话关联的文件空间。调用方只需要提供会话和文件来源,不需要先调用 conversation-info,也不需要传递 spaceId。若只是发送本地文件,优先使用 `dws chat message send --msg-type file --file-path <本地文件>`。 +``` +Usage: + dws chat file upload [flags] +Example: + # 本地文件:CLI 会初始化上传、直传文件内容并提交 + dws chat file upload --group --file ./report.pdf --format json + dws chat file upload --user --file ./report.pdf --format json + dws chat file upload --open-dingtalk-id --file ./report.pdf --format json + + # URL 文件:服务端拉取 URL 并上传到会话文件空间 + dws chat file upload --group --url https://example.com/report.pdf --file-name report.pdf --format json +Flags: + --group string 群聊 openConversationId(群聊时使用) + --user string 单聊对方 userId(单聊时使用) + --open-dingtalk-id string 单聊对方 openDingTalkId(单聊时使用) + --file string 本地文件路径(与 --url 二选一) + --url string 远程文件 URL(与 --file 二选一,服务端代传) + --file-name string 文件名(可选,本地文件默认取文件名,URL 默认从 URL 推断) + --md5 string 文件 MD5(可选,本地文件不传时自动计算) + --uuid string 幂等 UUID(可选) + +注意: + - --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一 + - --file 和 --url 互斥,必须且只能指定其一 + - 本地文件由一个命令内部完成:获取上传链接 → CLI 直传文件内容 → 提交上传 + - URL 文件走服务端代传:服务端自行解析会话空间并上传到会话文件空间 + - 若只是发送本地文件,直接使用 `dws chat message send --msg-type file --file-path <本地文件>`,CLI 会复用同一套上传逻辑 + - 发图片/文件优先走 `chat message send --msg-type file --file-path <本地路径>`,无需调用 `chat file upload`;本节只在以下场景使用:(a) URL 文件由服务端代传 (`--url`);(b) 业务需要先拿到下载链接再以 Markdown 形式内嵌到文字消息中 + - **文字 + 文件双消息**(仅适用于非图片文件;图片直接 `--msg-type file --file-path` 单条消息即可,不要走双发):先发一条 Markdown 文字消息引用下载链接,再对每个文件各补发一条 `--msg-type file` 文件消息,确保接收方既看到文字说明又能直接下载原始文件 +``` + #### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态 查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 openTaskId。 @@ -517,17 +564,28 @@ Usage: Example: dws chat message send-by-bot --robot-code --group --title "日报" --text "## 今日完成..." dws chat message send-by-bot --robot-code --users userId1,userId2 --title "提醒" --text "请提交周报" + dws chat message send-by-bot --robot-code --open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "请提交周报" + dws chat message send-by-bot --robot-code --group --at-user-ids userId1,userId2 --title "提醒" --text "@userId1 @userId2 请查收本周报告" + dws chat message send-by-bot --robot-code --group --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" + dws chat message send-by-bot --robot-code --group --at-all --title "通知" --text "请所有人注意" Flags: --group string 群聊 openConversationId(群聊时必填) --robot-code string 机器人 Code (必填) --text string 消息内容 Markdown (必填) --title string 消息标题 (必填) - --users string 接收者 userId 列表,逗号分隔,最多20个(单聊时必填) + --users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填) + --open-dingtalk-ids string 用户 openDingtalkId 列表,逗号分隔(单聊时可替代 --users,可选) + --at-user-ids string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选) + --at-open-dingtalk-ids string @指定成员的 openDingtalkId 列表,逗号分隔(仅群聊时生效,可选) + --at-all @所有人(可选),服务端接收字符串 true/false 注意: - 用户明确要求机器人发送时,必须使用 `chat message send-by-bot`;严禁使用 `chat message send` 以用户身份代发 - - --group(群聊)与 --users(单聊)互斥,必须且只能指定其一 - - send-by-bot 不支持 @成员/@所有人 参数(无 --at-user-ids/--at-open-dingtalk-ids/--at-all);如需在群里 @人,用 `chat message send --group` 走用户身份发送 + - --group 与 --users/--open-dingtalk-ids 互斥,必须且只能指定其一 + - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) + - --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本 + - --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本 + - --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理 - userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: 1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行 @@ -606,7 +664,7 @@ Flags: 注意: - 四个参数每次请求都会传递给服务端,cursor 首页传 "0" - - 与 chat message list 的区别:list 拉取指定群聊会话的消息(单聊用 list-direct),list-all 拉取当前用户所有会话的消息 + - 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息 - 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页 - 时间格式统一为 yyyy-MM-dd HH:mm:ss ``` @@ -935,21 +993,6 @@ Flags: - --output 如果指定目录,文件名会从下载 URL 中自动推断 ``` -#### 资源链接形态分流 — 按 content 里的链接 host 选下载方式 - -`message list` / `message list-all` 拉到的消息,`content` 里的资源**不一定都是 mediaId**,下载方式由链接的 host 决定、不由文件扩展名决定;**首选对应 dws 命令**,裸 curl 仅对明确的公开直链有效。按下表分流、不要混用: - -| `content` 里的形态 | 资源性质 | 下载方式 | -|---|---|---| -| `mediaId=...`(图片 / 视频 / 语音,扩展名任意) | 钉钉消息媒体;**host 不唯一**:`down.dingtalk.com/media`(公开直链)或 `*.trans.dingtalk.com/...?Expires=&Signature=`(签名 + 会过期) | **首选 `dws chat message download-media`**(内部取最新下载 URL,不受过期影响,需 `--message-id` + `--open-conversation-id`)。仅当链接是 `down.dingtalk.com/media` 公开直链时,可 `curl -sL -o` 快捷下载;**签名/过期链(`trans.dingtalk.com`、带 `Signature`/`Expires`)不要裸 curl,过期会 403** | -| `[文件] xxx fileId: `(钉盘文件) | 钉盘临时签名链接 | `dws drive download --node --output <路径>`(裸 curl 不通用,需签名头) | -| `https://alidocs.dingtalk.com/i/nodes/`(钉钉文档 / .adoc / 视频 .mov 等节点) | 文档 / 钉盘节点 | 读内容用 `dws doc`,下文件用 `dws drive download`(裸 curl 只得 HTML 预览页) | - -> - 下载到本地后用 `file <文件>` 判断真实类型:部分链接扩展名是 `.unknown`、服务端按 `application/octet-stream` 返回,仍可正常下载。 -> - 图片消息还会附带 AI 识别的内容描述(`...`),不下载也能理解图意。 -> - 钉盘文件在 `content` 里给的是 `fileId`、不是 mediaId,必须走 `dws drive download`(下载链接是带签名头的临时链接,裸 curl 不通用)。 -> - alidocs 节点裸 curl 只会拿到 HTML 预览页、不是文件本体,需走 `dws doc`(读内容)或 `dws drive download`(下文件)。 - ### search-common (搜索共同群) #### 搜索共同群 — 查询指定人共同所在的群聊 @@ -1157,11 +1200,255 @@ Flags: - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 ``` +### hide (隐藏会话) + +#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现 +``` +Usage: + dws chat hide [flags] +Example: + dws chat hide --conversation-id + dws chat hide --id + # 查询群 ID: dws chat search --query "群名" + # 查询单聊会话 ID: dws chat conversation-info --user +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 隐藏后会话不再显示在列表中,收到新消息时会重新出现 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mute-at-all (关闭@所有人通知) + +#### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 +``` +Usage: + dws chat mute-at-all [flags] +Example: + dws chat mute-at-all --conversation-id + dws chat mute-at-all --conversation-id --off + # 查询群 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --off 恢复接收 @所有人通知(不传则关闭通知) + +注意: + - 默认行为是关闭 @所有人通知,传 --off 则恢复接收通知 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mute-red-envelope (关闭红包通知) + +#### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 +``` +Usage: + dws chat mute-red-envelope [flags] +Example: + dws chat mute-red-envelope --conversation-id + dws chat mute-red-envelope --conversation-id --off + # 查询群 ID: dws chat search --query "群名" +Flags: + --conversation-id string 会话 openConversationId (必填,支持单聊/群聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --off 恢复接收红包通知(不传则关闭通知) + +注意: + - 默认行为是关闭红包通知,传 --off 则恢复接收通知 + - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mark-unread (标记会话为未读) + +#### 标记会话为未读 — 将指定会话标记为未读状态 +``` +Usage: + dws chat mark-unread [flags] +Example: + dws chat mark-unread --conversation-id + dws chat mark-unread --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - 标记未读后会话列表中会显示未读状态 +``` + +### clear-red-point (清除会话红点) + +#### 清除会话红点 — 清除指定会话的未读红点 +``` +Usage: + dws chat clear-red-point [flags] +Example: + dws chat clear-red-point --conversation-id + dws chat clear-red-point --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - 清除红点后该会话不再显示未读标记 +``` + +### clear-all-red-point (红点清零) + +#### 清除所有会话红点 — 一键全部已读 +``` +Usage: + dws chat clear-all-red-point +Example: + dws chat clear-all-red-point + +注意: + - 无需任何参数,直接清除当前用户所有会话的未读红点 + - 等效于"全部已读"操作 +``` + +### list-all-conversations (全部会话列表) + +#### 分页获取全部会话列表 — 获取当前用户的所有会话 +``` +Usage: + dws chat list-all-conversations [flags] +Example: + dws chat list-all-conversations + dws chat list-all-conversations --limit 50 + dws chat list-all-conversations --limit 100 --cursor + dws chat list-all-conversations --exclude-muted +Flags: + --limit int 每页数量(默认 1000) + --cursor int 分页游标(首次不传或传 0,翻页传 nextCursor) + --exclude-muted 是否排除已免打扰会话(默认 false) + +注意: + - 返回结果包含单聊和群聊,不区分会话类型 + - 翻页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor + - 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话 +``` + +### clear-messages (清空会话聊天记录) + +#### 清空会话聊天记录 — 清空当前用户指定会话的消息 +``` +Usage: + dws chat clear-messages [flags] +Example: + dws chat clear-messages --conversation-id + dws chat clear-messages --id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + +注意: + - 仅清空当前用户视角的消息,不影响其他成员 + - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 +``` + +### mark-read (标记消息已读) + +#### 标记消息已读 — 将指定消息及之前的消息标记为已读 +``` +Usage: + dws chat mark-read [flags] +Example: + dws chat mark-read --conversation-id --message-id + dws chat mark-read --id --message-id +Flags: + --conversation-id string 会话 openConversationId (必填,支持群聊/单聊) + --id string --conversation-id 的别名 + --chat string --conversation-id 的别名 + --message-id string 消息 openMessageId (必填) + +注意: + - 标记该消息及之前的所有消息为已读 + - openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 + - openMessageId 可通过 chat message list 获取 +``` + +### group list-all (分页拉取所有群) + +#### 分页拉取我所有群列表 — 获取当前用户加入的所有群聊 +``` +Usage: + dws chat group list-all [flags] +Example: + dws chat group list-all + dws chat group list-all --limit 50 + dws chat group list-all --limit 100 --cursor +Flags: + --limit int 每页返回数量(默认 100,最大 200) + --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) + +注意: + - 与 `chat group list-my-groups` 区别: list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 + - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor +``` + +### group list-join-validations (分页拉取入群验证记录) + +#### 分页拉取入群验证记录 — 获取当前用户的所有入群验证记录 + +包括自己被拒绝的记录以及作为审批者的记录。 + +``` +Usage: + dws chat group list-join-validations [flags] +Example: + dws chat group list-join-validations + dws chat group list-join-validations --limit 30 + dws chat group list-join-validations --limit 20 --cursor +Flags: + --limit int 单页数量(默认 20,最大 50) + --cursor string 分页游标(首次不传,翻页传返回的 nextCursor) + +注意: + - 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor + - cursor 首次拉取不传或传 null 时从当前时间开始拉 +``` + +### group audit-join-validation (审批入群验证) + +#### 审批入群验证 — 通过、拒绝、删除单个审核 + +支持通过、拒绝、删除、忽略、拒绝并拉黑等操作。 + +``` +Usage: + dws chat group audit-join-validation [flags] +Example: + dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditApprove + dws chat group audit-join-validation --group --record-id 123456 --applicant --inviter --status AuditRefuse --description "不符合入群条件" + # 查询入群验证记录: dws chat group list-join-validations +Flags: + --group string 群 openConversationId (必填) + --record-id string 申请记录 ID (必填) + --applicant string 申请人 openDingTalkId (必填) + --inviter string 邀请人 openDingTalkId (必填) + --status string 审批动作: AuditApprove/AuditDelete/AuditIgnore/AuditRefuse/AuditBlock (必填) + --description string 审批说明(可选) + +注意: + - status 可选值: AuditApprove(通过), AuditDelete(删除), AuditIgnore(忽略), AuditRefuse(拒绝), AuditBlock(拒绝且拉黑) + - record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得 +``` + ## 意图判断 用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `chat message list-focused`(零参数一行命令) 用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id ` 或 `--sender-open-dingtalk-id `(跨单聊+群聊) -用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list-direct --user ` 或 `--open-dingtalk-id ` +用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user ` 或 `--open-dingtalk-id ` 用户说"某个群的聊天记录" → `chat message list --group ` 用户说"我最近所有消息/我今天的消息" → `chat message list-all --start --end ` 用户说"@我的消息/提及我的" → `chat message list-mentions --start --end ` @@ -1174,14 +1461,14 @@ Flags: 用户说"建群/创建群聊" → `chat group create` 用户说"搜索群/找群" → `chat search` 用户说"我创建的群/我管理的群/我是群主的群/我当管理员的群" → `chat group list-my-groups` -用户说"群成员/看群里有谁" → `chat group members list` +用户说"群成员/看群里有谁" → `chat group members` 用户说"拉人进群/加群成员" → `chat group members add` 用户说"踢人/移除群成员" → `chat group members remove` 用户说"加机器人到群" → `chat group members add-bot` 用户说"改群名" → `chat group rename` 用户说"聊天记录/会话消息/拉取会话" → `chat message list` 用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊) -用户说"拉取和某人的单聊记录/单聊消息" → `chat message list-direct --user`(单聊专用;用户明确说"单聊"时使用) +用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用) 用户说"@我的消息/at我的/提及我的" → `chat message list-mentions` 用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations` 用户说"发群消息(以个人身份)" → `chat message send --group` @@ -1219,6 +1506,20 @@ Flags: 用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion` 用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion` 用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute` +用户说"隐藏会话/隐藏群聊/隐藏对话" → `chat hide` +用户说"关闭@所有人通知/屏蔽@所有人/不接收@all" → `chat mute-at-all` +用户说"开启@所有人通知/恢复@所有人提醒" → `chat mute-at-all --off` +用户说"关闭红包通知/屏蔽红包/不接收红包提醒" → `chat mute-red-envelope` +用户说"开启红包通知/恢复红包提醒" → `chat mute-red-envelope --off` +用户说"标记会话未读/标为未读" → `chat mark-unread` +用户说"标记已读/把消息标成已读" → `chat mark-read` +用户说"清除红点/去掉某个会话的未读红点" → `chat clear-red-point` +用户说"全部已读/一键清除红点/红点清零" → `chat clear-all-red-point` +用户说"我的所有会话/全部会话列表" → `chat list-all-conversations` +用户说"清空聊天记录/清空会话消息" → `chat clear-messages` +用户说"我加入的所有群/我的全部群列表" → `chat group list-all` +用户说"入群验证记录/谁申请进群" → `chat group list-join-validations` +用户说"审批入群/通过入群申请/拒绝入群申请" → `chat group audit-join-validation` 用户说"引用回复/回复消息/引用消息回复" → `chat message reply` 用户说"转发消息/转发一条消息/把消息转发到另一个群" → `chat message forward` 用户说"合并转发/批量转发/合并转发多条消息" → `chat message combine-forward` @@ -1236,8 +1537,8 @@ Flags: 关键区分: - `chat search` — 搜**群/会话名**返回 `openConversationId`,**不**搜消息内容;要搜消息内容请用 `chat message search-advanced`(首选)/ `chat message search` / `list-by-sender` / `list-all`,**勿混淆** -- `chat message list` — 拉取指定**群聊**的消息(需指定 --group,**仅群聊**),按时间点 + 方向翻页 -- `chat message list-direct` — 单聊专用,拉取与指定用户的单聊(私聊)记录(--user / --open-dingtalk-id;用户明确说"单聊""私聊"时使用) +- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页 +- `chat message list --user` — list 的单聊模式,拉取与指定用户的单聊记录(用户明确说"单聊""私聊"时使用) - `chat message list-by-sender` — 搜索指定发送者发给我的消息,跨所有会话(单聊+群聊均包含,用户只说"某人发的消息"时优先使用) - `chat message list-mentions` — 拉取 @我 的消息(跨单聊/群聊,可选指定群) - `chat message list-unread-conversations` — 拉取当前用户存在未读消息的会话列表(可选 `--count`) @@ -1246,7 +1547,7 @@ Flags: - `chat message list-topic-replies` — 拉取群话题的回复消息列表 - `chat message list-focused` — 拉取特别关注人的消息,cursor 分页 - `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页 -- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file(音频/视频/文档等所有非图片文件),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件需先获取会话共享空间再上传钉盘 +- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file/audio/video(audio/video 是 file 别名),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件可直接用 --file-path 或先获取会话共享空间再上传钉盘 - `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群) - `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在) - `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag @@ -1262,6 +1563,16 @@ Flags: - `chat category list` — 获取用户自定义会话分组列表 - `chat category list-conversations` — 拉取指定分组下的会话列表 - `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭) +- `chat hide` — 在会话列表中隐藏会话(支持单聊/群聊,收到新消息时重新出现) +- `chat mute-at-all` — 关闭/开启 @所有人消息提醒(默认关闭,--off 恢复) +- `chat mute-red-envelope` — 关闭/开启红包消息提醒(默认关闭,--off 恢复) +- `chat mark-unread` / `chat mark-read` — 标记会话未读 / 标记指定消息及之前的消息已读 +- `chat clear-red-point` / `chat clear-all-red-point` — 清除单个会话红点 / 一键清除所有会话红点(全部已读) +- `chat list-all-conversations` — 分页拉取当前用户全部会话列表(单聊+群聊,与 list-top-conversations 的区别是不限置顶) +- `chat clear-messages` — 清空当前用户视角下指定会话的聊天记录(不影响其他成员) +- `chat group list-all` — 分页拉取当前用户加入的所有群(list-my-groups 仅返回群主/管理员的群) +- `chat group list-join-validations` / `chat group audit-join-validation` — 拉取入群验证记录 / 审批入群验证(通过/拒绝/删除/忽略/拉黑) +- `chat file upload` — 上传本地文件或 URL 文件到会话文件空间(不暴露 spaceId;纯发文件优先用 `chat message send --msg-type file --file-path`) - `chat group transfer-owner` — 转让群主 - `chat group invite-url` — 获取群邀请链接 - `chat message reply` — 引用回复消息(在群聊中引用某条消息并回复文字) @@ -1346,7 +1657,7 @@ dws chat message send-by-bot --robot-code --group --robot-code +dws chat group members add-bot --group --robot-code # Step 4: 重新发送 dws chat message send-by-bot --robot-code --group \ @@ -1365,22 +1676,27 @@ dws chat bot find --query "玉澜" --format json dws chat message send --open-dingtalk-id --text "你好" --format json ``` -### @指定人发群消息(用户身份) +### 机器人 @指定人发群消息 -`send-by-bot` 不支持 @ 参数。群里 @人/@所有人请用 `chat message send --group`(当前用户身份):通过 `--at-open-dingtalk-ids` 传入 openDingTalkId 列表 @指定成员(多个逗号分隔),`--text` 中需包含对应 `@openDingTalkId` 文本;`--at-all` @所有人。 +通过 `--at-user-ids` 传入 userId 列表或 `--at-open-dingtalk-ids` 传入 openDingtalkId 列表来 @指定成员,多个用逗号分隔。`--text` 中需包含 `@userId` 或 `@openDingtalkId` 文本(不要用尖括号,不要用姓名)。通过 `--at-all` @所有人。 ```bash -# Step 1: 搜人获取 openDingTalkId +# Step 1: 搜人获取 userId dws contact user search --query "张三" --format json -# Step 2: @指定成员发群消息(注意 text 中带 @openDingTalkId) -dws chat message send --group \ +# Step 2: 用 userId 发送并 @(注意 text 中 @userId) +dws chat message send-by-bot --robot-code --group \ + --at-user-ids userId1,userId2 \ + --title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json + +# 或者用 openDingtalkId 发送并 @ +dws chat message send-by-bot --robot-code --group \ --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 \ - --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json + --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json # @所有人 -dws chat message send --group \ - --at-all --text "请所有人注意" --format json +dws chat message send-by-bot --robot-code --group \ + --at-all --title "通知" --text "请所有人注意" --format json ``` @@ -1413,6 +1729,32 @@ dws chat message send --group \ --text "[报告.pdf](下载链接) 这是季度报告" --format json ``` +### 发送图片 / 文件(统一一条命令) + +**`dws chat message send --msg-type file --file-path <本地路径>`** 适用于所有发图片/文件场景,任意扩展名。CLI 内部完成上传与发送,无需任何前置工具调用。 + +```bash +# 群聊 +dws chat message send --group --msg-type file --file-path ./screenshot.png --format json +dws chat message send --group --msg-type file --file-path ./report.pdf --format json + +# 单聊(推荐 --open-dingtalk-id) +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +``` + +**带文字说明**:在上一步发完文件后,再补一条文本消息即可。不要尝试把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。 + +```bash +dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json +dws chat message send --open-dingtalk-id --text "这是本周数据汇总" --format json +``` + +**旧链路(仅当上游已经持有 `dt_media_upload` 返回的 mediaId 时才用)**: + +```bash +dws chat message send --group --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json +``` + #### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息 群聊传 --group,单聊传 --receiver,二者互斥。 @@ -1457,8 +1799,8 @@ Flags: | `chat search` | `openConversationId` | message send/list、group members 等的 --group | | `chat group create` | `openConversationId` | 同上 | | `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor | -| `aisearch person` | `userId` | message send 的 --user、list-direct 的 --user、send-by-bot 的 --users、list-by-sender 的 --sender-user-id | -| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、list-direct 的 --open-dingtalk-id、list-by-sender 的 --sender-open-dingtalk-id | +| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | +| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id | | `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) | | `chat bot find` | `openDingTalkId` | 给机器人发单聊消息(全部可用机器人,额外返回 openDingTalkId) | | `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys | @@ -1468,6 +1810,7 @@ Flags: | `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor | | `chat search-common` | `openConversationId` | message send/list 等的 --group | | `chat conversation-info` | `newCSpaceIdIM` | drive upload 的 --space-id(发送文件消息前获取共享空间) | +| `chat file upload` | 下载链接字段 | message send 的 Markdown 链接文本(仅文字+非图片文件双发场景;常规发图/发文件用 `--msg-type file --file-path`) | | `chat message list` | `openMsgId` | message read-status 的 --message-id | | `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id | | `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id | @@ -1491,9 +1834,9 @@ Flags: - 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送 - 此参数适用于 `chat message send`(群聊和单聊均支持) - `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取 -- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,其他所有→file,不可跳过此判断 +- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file/audio/video),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,音频→audio,视频→video,其他→file,不可跳过此判断 - `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor -- `chat message list` **仅支持群聊**,必须指定 `--group`;拉取单聊用 `chat message list-direct`(`--user` / `--open-dingtalk-id` 二选一) +- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一 - `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` - `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` - `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数 @@ -1504,12 +1847,12 @@ Flags: - `--user` 和 `--open-dingtalk-id` 本质上都是发起单聊操作,只是用户标识格式不同:userId 为企业内部应用常用标识,openDingTalkId 为三方应用或跨组织场景下的用户标识,服务端对两种 ID 的解析逻辑不同 - `--time` 格式: `yyyy-MM-dd HH:mm:ss`,为拉取消息的起始时间点;`--forward` 控制方向(默认 true,拉给定时间之后的消息),`--limit` 控制数量 - `chat search` 挂在 `chat` 下(非 `chat group` 下),路径为 `dws chat search` -- `send-by-bot` 群聊传 `--group`,单聊传 `--users`,二者互斥且必选其一;**不支持 @成员/@所有人**(无 `--at-user-ids`/`--at-open-dingtalk-ids`/`--at-all`),群里 @人请用 `chat message send --group` 走用户身份;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --id --robot-code ` 将机器人邀请进群后再发送 +- `send-by-bot` 群聊传 `--group`,单聊传 `--users` 或 `--open-dingtalk-ids`,与 `--group` 互斥且必选其一;群聊时可选 `--at-user-ids` @指定成员(传 userId 列表)或 `--at-open-dingtalk-ids` @指定成员(传 openDingtalkId 列表),content 中需包含对应 @标识;`--at-all` @所有人;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --group --robot-code ` 将机器人邀请进群后再发送 - `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回) - `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效;`--at-all` @所有人时需在 `--text` 中包含 `@10` - `chat group-role` 系列命令用于管理群的自定义身份标签:`list` 查列表,`add` 创建,`update` 改名,`remove` 删除;`set-user` 覆盖某人全部身份(传空 --role-ids 则清除),`remove-user` 仅移除指定身份,`query-user` 查询某人当前身份;用户用 `--user ` - 消息**换行符**(`send` / `send-by-bot` / `send-by-webhook` 的 `--text`)有两层要求:(1) 必须是**真实换行符** `U+000A`,不是字面量 `\n`;(2) Markdown 规范下单换行不生效,需用空行 `\n\n`(段落分隔)或行尾两空格 + 换行 / `
`(硬换行) -- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主用 `--new-owner`(传 openDingTalkId) +- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner` - `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久) - `chat group quit` 退出群聊,需传 --group(openConversationId) - `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId) @@ -1522,15 +1865,18 @@ Flags: - `chat message create-text-emotion` 创建文字表情模板,返回 emotionId;--background-id 可选,不传由服务端默认分配 - `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取) - `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID +- `chat hide` 隐藏会话,需传 --conversation-id(openConversationId,支持单聊/群聊),隐藏后不显示在列表中,收到新消息时重新出现 +- `chat mute-at-all` 关闭/开启 @所有人消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 +- `chat mute-red-envelope` 关闭/开启红包消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 - `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text - `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) - `chat set-top` 设置/取消会话置顶(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 -- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致;**同样支持 `--ai-tag`(默认 true,默认带「通过AI发送」角标,传 `--ai-tag=false` 关闭)**(详见上文 send 段的「AI 代发标记」规则) +- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致 - **如何获取 openConversationId**(如果上层已有则直接使用,不必再查): - 群聊:`dws chat search --query "群名"` - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取) - `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消 -- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言 +- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言;CLI 会自动把 userId 解析成 openDingTalkId 再调用,直接传 userId 即可;禁言群主会被服务端拒绝 - `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消 ## 自动化脚本 @@ -1543,5 +1889,5 @@ Flags: ## 相关产品 -- [contact](./contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、list-by-sender 的 --sender-open-dingtalk-id +- [contact](./contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id - [drive](./drive.md) — 上传文件获取下载链接,用于 Markdown 图片/文件消息 diff --git a/skills/mono/references/products/contact.md b/skills/mono/references/products/contact.md index aca24680..dbc7846e 100644 --- a/skills/mono/references/products/contact.md +++ b/skills/mono/references/products/contact.md @@ -1,8 +1,7 @@ # 通讯录 (contact) 命令参考 -> **CRITICAL — 命令合法性**:contact 只有 `user` / `dept` / `relation` 三个可操作的二级子命令。 +> **CRITICAL — 命令合法性**:contact 只有 `user` / `dept` / `label` / `relation` 四个二级子命令。 > 不存在 `contact search`、`contact find`、`contact list`、`contact get`、`contact user find/list`。 -> 角色/标签(label)查询命令已下线,不再提供 `contact label list/get/list-members`。 > 构造命令前必须确认路径在下方「命令总览」中存在;不确定时,**根据意图对照下方「意图判断」选择正确命令**。 > > **CRITICAL — 根部门**:钉钉根部门 `deptId=1`。`dept` 系列命令查根部门统一传 `--dept 1` 或 `--depts 1`,不要传 `self / me / root / 0`。 @@ -188,6 +187,52 @@ Notes: - 跨层级成员展开见 [08-directory.md](../best_practices/08-directory.md) 的 `cross-level-dept-members` recipe ``` +### label (角色查询) + +> **角色ID = labelId**:用户提到"角色ID"时,均指通讯录 label 系统中的角色ID,**不是部门ID也不是userId**。查角色成员用 `label list-members --id <角色ID>`,不要传给 `dept get-info` 或 `user get`。 + +#### 获取企业所有角色列表 +``` +Usage: + dws contact label list +Example: + dws contact label list +Flags: + 无 +Notes: + - 无需参数,返回当前企业全部角色列表(labelId、labelName等) + - 用于不知道准确角色名称时先浏览全部角色 + - 典型场景:用户说“企业所有主管/查所有管理员/财务人员有哪些”→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员 +``` + +#### 根据角色名称查询角色 +``` +Usage: + dws contact label get [flags] +Example: + dws contact label get --names "管理员" + dws contact label get --names "管理员,财务" +Flags: + --names string 角色名称,逗号分隔 (必填) +Notes: + - 精确匹配角色名称,不支持模糊搜索 + - 支持同时查询多个角色名称,逗号分隔 + - 无需分页 +``` + +#### 查询角色下的成员 +``` +Usage: + dws contact label list-members [flags] +Example: + dws contact label list-members --id 12345 +Flags: + --id string 角色 ID (必填) +Notes: + - 根据角色ID直接查询成员列表;已有角色ID时直接用 `--id ` + - 不知道角色ID时:先 `dws contact label get --names "角色名"` 或 `dws contact label list` 获取 labelId +``` + ## 意图判断 > **搜人首选 `aisearch person`**:凡是“找人/搜人/找同事/谁负责/上级/下级”均优先用 [aisearch person](./aisearch.md),以下场景才用 contact。 @@ -202,19 +247,32 @@ Notes: 用户说"部门详情/部门信息/部门多少人" → `dept get-info`(返回部门ID、部门名称、部门人数;需 deptId,若只有部门名称需先 `dept search`) 用户说"子部门/下设部门/部门有哪些下级部门/枚举二级部门" → `dept list-children`(需父 deptId;只有部门名先 `dept search`) 用户说"部门有谁/部门成员/人员名单" → `dept list-members`(需 deptId;**仅本部门不含下级**,含下级先 `dept list-children` 再合并查) -用户查询涵盖"角色/职责"(主管/管理员/财务/HR/总经理/谁负责 XX 等)→ 优先用 [`aisearch person`](./aisearch.md) 按职责维度找人(`dws aisearch person --keyword "<角色或职责>" --dimension duty`)。 +用户查询涵盖"角色"(主管/管理员/财务/HR/总经理等任意角色名)→ 统一走 `contact label` 链路,按下方决策树选命令: +- 不知道角色名 / 枚举所有角色 → `label list` +- 已知角色名,查ID或成员 → 先 `label get --names <名>` 拿ID,查成员再调 `label list-members --id `;**精确匹配无结果时降级 `label list` 模糊匹配** +- 已知角色ID 查成员 → `label list-members --id ` > [!IMPORTANT] -> **角色查人 vs 查某人的属性 — 判断口径**:先判断用户的终点是"人"还是"属性": -> - 终点是**人**("管理员有哪些人""谁负责财务""找 XX 角色的成员")→ 走 [`aisearch person`](./aisearch.md)(职责维度找人) +> **角色查询 3 步决策树**(不依赖字串匹配,按语义判定): +> 1. 不知道角色名 / 要枚举所有角色(列出企业有哪些角色、每个角色的名称和ID、不确定叫什么角色、负责XX的人有哪些等) → `label list` +> 2. 已知角色名 要查角色ID → `label get --names` +> 3. 已知角色ID 要查成员 → `label list-members --id` +> +> 任何含"角色"一词的查询默认走 `contact label` 链路。**唯一例外**:终点是"某个人是否具备某权限"(如"张三是不是管理员")→ `user get`。禁止路由到 `user profile fields`(那是花名册字段)、`dept list-members` 筛选、或 OA/chat 模块。 + +> [!IMPORTANT] +> **角色查人 vs 查某人的角色信息 — 判断口径**:先判断用户的终点是"人"还是"属性": +> - 终点是**人**("管理员角色有哪些人""管理员下都有谁""查XX角色的成员")→ 角色维度查人,**必须**走 `contact label` 链路(`label get`/`label list` → `label list-members`),**禁止**通过 `dept list-members` 筛选 `isAdmin` 等字段替代 > - 终点是**属性**("张三是不是管理员""查某人的主管/管理员权限")→ 已知 userId 查个人详情,走 `user get`(返回 isAdmin/leader 等字段) > > 反例对照: -> - "管理员都有哪些人" → `aisearch person --keyword 管理员 --dimension duty`(终点=人员列表) +> - "管理员角色下都有哪些人" → `label get --names 管理员` → `label list-members`(终点=角色下的**人员列表**) +> - "我想知道管理员这个角色下都有谁" → `label get --names 管理员` → `label list-members`(终点=角色下的**人员列表**) > - "张三是不是管理员" → `user get --ids `(终点=某个人的**属性**) > - "查一下张三的管理员权限" → `user get --ids `(终点=某个人的**属性**) +> - "角色ID为55808858的角色下有哪些成员" → `label list-members --id 55808858`(已有角色ID直接查成员) > -> 注意:`contact label` 角色查询命令已下线,不要再构造 `contact label list/get/list-members`。OA 审批只管审批流程(待审批/同意/拒绝),**不支持**查询角色成员;群角色(chat group-role)只管群内身份,不涉及企业组织角色。 +> **角色 = 通讯录 label**:用户提到"角色"(角色ID/角色成员/角色名称/企业角色/查角色下的人)时,均指通讯录组织角色,应走 `contact label` 链路。OA 审批只管审批流程(待审批/同意/拒绝),**不支持**查询角色成员;群角色(chat group-role)只管群内身份,不涉及企业组织角色。 用户说"我关注了谁/我的特别关注列表/我的星标联系人/特别关注的人有哪些" → `relation list-my-followings` > [!IMPORTANT] @@ -254,19 +312,28 @@ dws contact dept list-children --dept <父deptId> --format json # 6. 查看部门成员 dws contact dept list-members --depts --format json -# 7. 查询花名册有权限的字段列表 +# 7. 获取企业所有角色列表 — 不知道角色名时先浏览 +dws contact label list --format json + +# 8. 根据角色名称查询角色 +dws contact label get --names "管理员" --format json + +# 9. 查询角色下的成员 +dws contact label list-members --id --format json + +# 10. 查询花名册有权限的字段列表 dws contact user profile fields --format json -# 8. 根据字段 code 查询指定员工的花名册信息 +# 11. 根据字段 code 查询指定员工的花名册信息 dws contact user profile get --staff-id --fields fieldCode1,fieldCode2 --format json -# 9. 查询所有可见字段的花名册信息 +# 12. 查询所有可见字段的花名册信息 dws contact user profile get --staff-id --format json -# 10. 查询全部离职员工 +# 13. 查询全部离职员工 dws contact user dismission search --format json -# 11. 按姓名/时间范围/部门筛选离职员工 +# 14. 按姓名/时间范围/部门筛选离职员工 dws contact user dismission search --name "张三" --format json dws contact user dismission search --start 2026-01-01 --end 2026-03-31 --format json dws contact user dismission search --depts 123456,789012 --hide-retirement=false --format json @@ -280,6 +347,8 @@ dws contact user dismission search --depts 123456,789012 --hide-retirement=false | `user get-self/search` | `orgAuthEmail` | mail message send 的 --to/--cc (跨产品) | | `user get-self/search` | `userId` | profile get 的 --staff-id | | `user profile fields` | `fieldCode` | profile get 的 --fields | +| `label list` | `labelId` / `labelName` | `label get --names` 或 `label list-members --id` | +| `label get` | `labelId` | `label list-members` 的 --id | | `dept search/list-children` | `deptId` | dept get-info/list-children/list-members 的 --dept/--depts | | `dept search/list-children` | `deptId` | dismission search 的 --depts | @@ -293,7 +362,11 @@ dws contact user dismission search --depts 123456,789012 --hide-retirement=false - 建议先执行 `user profile fields` 获取可用字段列表,再根据需要的字段 code 执行 `user profile get` - `user dismission search` 的 `--start`/`--end` 必须同时设置或同时不设置,不允许只传其中一个 - `user dismission search` 默认隐藏退休人员(`--hide-retirement` 默认 true),默认展示合作伙伴(`--hide-partner` 默认 false) -- 角色/职责类查询(主管、管理员、财务、HR 等任意角色,或"谁负责 XX")优先走 [`aisearch person`](./aisearch.md) 的职责维度(`--dimension duty`);`contact label` 角色查询命令已下线,不要再构造 +- `label list` 无需参数,适用于不知道准确角色名称的场景;当用户说“查所有主管/主管理员/财务”等角色类型人员时,优先 `label list` 列出企业全部角色,LLM 灵活匹配目标角色后调用 `label list-members` +- 角色类查询(主管、管理员、财务、HR 等任意角色)优先走 label 链路,而非 dept list-members 或 aisearch person;label 精确命中角色维度,返回完整名单 +- `label get` 是精确匹配角色名称,不支持模糊搜索;支持逗号分隔同时查询多个角色名称 +- **`label get` 精确匹配无结果时的降级策略**:若 `label get --names "XX"` 返回空结果,必须降级调用 `label list` 获取全部角色列表,从中模糊匹配包含XX关键词的角色(如用户说"管理员"可匹配到"主管理员"和"子管理员"),再对匹配到的角色调用 `label list-members` +- `label list-members` 需要先通过 `label list` 或 `label get` 获取 labelId,再用 --id 查询角色下的成员 ## 自动化脚本 diff --git a/skills/mono/references/products/devdoc.md b/skills/mono/references/products/devdoc.md index 4614911e..e6e3e239 100644 --- a/skills/mono/references/products/devdoc.md +++ b/skills/mono/references/products/devdoc.md @@ -9,57 +9,23 @@ Usage: dws devdoc article search [flags] Example: - dws devdoc article search "MCP" dws devdoc article search --query "OAuth2 接入" - dws devdoc article search --keyword "机器人" --size 10 dws devdoc article search --query "消息卡片" --page 2 --size 5 - dws devdoc article search --query "消息卡片" --cursor "" --size 5 + dws devdoc article search --query "机器人" --size 10 Flags: --query string 搜索关键词 (必填) - --keyword string 搜索关键词 (--query 的别名) --page int 分页页码 (从 1 开始,默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page --size int 分页大小 (默认 10) ``` -### 错误排查 -``` -Usage: - dws devdoc error diagnose [flags] - dws devdoc error troubleshoot [flags] -Example: - dws devdoc error diagnose --request-id 15r6h45w0muec - dws devdoc error diagnose --trace-id 15r6h45w0muec --api "创建日程" - dws devdoc error diagnose --error-code 33012 --error-message "missing scope" - dws devdoc error diagnose --query "机器人回调失败" --context "HTTP 403" -Flags: - --query string 原始排查问题 - --request-id string 开放平台 requestId - --trace-id string requestId 的兼容别名 - --error-code string 错误码 - --error-message string 错误描述,会合并进原始问题 - --api string API 名称,会合并进原始问题作为补充检索词 - --context string 额外排查上下文,会合并进原始问题 - --page int 分页页码 (从 1 开始,默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page - --size int 分页大小 (默认 10) -``` - ## 意图判断 用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节: -- 走 `devdoc article search`,把用户问的关键短语作为位置参数或 `--query` -- 用户已经给出错误码、错误描述或 requestId: -- 先走 `devdoc error diagnose`;若后端返回 `PARAM_ERROR - 未找到指定工具` / `unknown tool`,降级 `devdoc article search`,同时把该结果标记为 `needs_gateway_tool_registration` - -用户已经提供 requestId / traceId / 错误码 / 错误描述 / 失败上下文: -- 走 `devdoc error diagnose`,优先传 `--request-id`,没有 requestId 时传 `--error-code`、`--error-message`、`--query` 或 `--context` +- 走 `devdoc article search`,把用户问的关键短语作为 `--query` 关键区分: - devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容) -- devdoc 只做搜索/诊断,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 -- `devdoc error diagnose` 只返回诊断事实、参考资料和链接,不生成 AI 分析结论 -- `--api`、`--error-message`、`--context` 是 CLI 侧易用参数,调用 MCP 时会合并到 `query`;MCP 入参只发送 `query`、`requestId`、`errorCode`、`page`/`cursor`、`size` +- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 ## 核心工作流 @@ -67,48 +33,16 @@ Flags: # 开发者问"OAuth2 怎么接" dws devdoc article search --query "OAuth2 接入" --format json -# 简短关键词可直接作为位置参数 -dws devdoc article search "MCP" --format json - # 命中结果多时翻页 dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json -# 推荐动态翻页:优先使用上一页返回的 nextCursor -dws devdoc article search --query "消息卡片" --cursor "" --size 5 --format json - # 查错误码 / 字段含义 dws devdoc article search --query "errcode 40078" --format json - -# 已经有 requestId 时排查 -dws devdoc error diagnose --request-id 15r6h45w0muec --format json - -# 只有 traceId 时按 requestId 兼容处理 -dws devdoc error diagnose --trace-id 15r6h45w0muec --api "创建日程" --format json - -# 只有错误码和错误描述时排查 -dws devdoc error diagnose --error-code 33012 --error-message "missing scope" --format json - -# 错误排查结果继续翻页 -dws devdoc error diagnose --error-code "40014" --query "access_token" --cursor "" --format json ``` -## 上线/注入验收 - -```bash -dws devdoc --help -dws devdoc article search --query "OAuth2 接入" --dry-run --format json -dws devdoc error diagnose --error-code "40014" --query "access_token" --dry-run --format json -dws schema devdoc.search_open_platform_docs_rag --format json || true -dws schema devdoc.search_open_error_code_rag --format json || true -``` - -- `--help` / `--dry-run` 只能证明 CLI 命令面和映射存在,不能证明后端工具已注册 -- 真实调用若返回 `PARAM_ERROR - 未找到指定工具`,不是用户参数问题;记录为网关/工具注册待闭环,并降级到 article search 或本地已挂载 Markdown - ## 注意事项 -- 关键词必填;可用位置参数、`--query` 或兼容别名 `--keyword`。建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 -- 错误排查至少提供 `--query`、`--request-id`、`--error-code`、`--error-message`、`--context` 之一;单独 `--api` 只作为补充上下文,不足以发起排查 -- 返回按相关性排序,默认 `--size 10`;响应有 `hasMore=true` 时,用 `nextCursor` 传给 `--cursor` 继续翻页,不要手写猜测下一页 +- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 +- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词 - 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考 - 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档** diff --git a/skills/mono/references/products/ding.md b/skills/mono/references/products/ding.md index f9849bbb..fb53c856 100644 --- a/skills/mono/references/products/ding.md +++ b/skills/mono/references/products/ding.md @@ -26,32 +26,138 @@ Flags: --robot-code string 机器人 ID (必填, 或设 DINGTALK_DING_ROBOT_CODE) ``` +### 查询 DING 消息历史 +``` +Usage: + dws ding message list [flags] +Example: + dws ding message list + dws ding message list --type UNREAD + dws ding message list --type SEND --cursor 10 +Flags: + --cursor int 分页游标 (首次传 0, 翻页传返回的 nextCursor) + --type string 消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED (可选, 不传返回全部) +``` + +### 查看 DING 接收状态 +``` +Usage: + dws ding message receiver-status [flags] +Example: + dws ding message receiver-status --ding-id + # 查询 dingId: dws ding message list +Flags: + --ding-id string DING 消息 openDingId (必填) +``` + +### 以用户身份发送 DING — 以当前用户身份(非机器人)发送 DING 消息 +``` +Usage: + dws ding message send-personal [flags] +Example: + dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" + dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" + # 查询 openDingTalkId: dws contact user search --query "姓名" +Flags: + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --content string DING 内容 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `ding message send`(机器人身份)不同:send-personal 以当前用户身份发送,无需 --robot-code + - 接收者使用 openDingTalkId(非 userId),可通过 `dws contact user search --query "姓名"` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + +### 以用户身份撤回 DING — 以当前用户身份撤回已发送的 DING 消息 +``` +Usage: + dws ding message recall-personal [flags] +Example: + dws ding message recall-personal --id + # 查询 openDingId: dws ding message list +Flags: + --id string DING 消息 openDingId (必填) + +注意: + - 与 `ding message recall`(机器人身份)不同:recall-personal 以当前用户身份撤回,无需 --robot-code + - openDingId 可通过 `dws ding message list` 或 `send-personal` 返回值获取 +``` + +### 消息转 DING — 将聊天消息转为 DING 通知发送给指定接收者 +``` +Usage: + dws ding message send-by-message [flags] +Example: + dws ding message send-by-message --group --message-id --users id1,id2 + dws ding message send-by-message --group --message-id --users id1 --type sms + # 查询 openDingTalkId: dws contact user search --query "姓名" + # 查询 openConversationId: dws chat search --query "群名" +Flags: + --group string 原消息所在会话 openConversationId (必填) + --message-id string 原消息 openMessageId (必填) + --users string 接收者 openDingTalkId 列表,逗号分隔 (必填) + --type string 提醒类型: app/sms/call (默认 app) + --uuid string 幂等唯一标识(可选,不传由服务端生成) + +注意: + - 与 `send-personal` 不同: send-by-message 是将已有聊天消息转发为 DING,需要指定原消息的会话和消息 ID + - 接收者使用 openDingTalkId,可通过 `dws contact user search --query "姓名"` 获取 + - sms/call 类型有通信费用,使用前需和用户确认 +``` + ## 意图判断 用户说"DING 一下/紧急通知/电话提醒" → `message send` +用户说"以我的名义 DING/个人发 DING/用户身份 DING" → `message send-personal` +用户说"消息转 DING/把这条消息 DING 给某人/转发为 DING" → `message send-by-message` 用户说"撤回 DING" → `message recall` +用户说"以我的名义撤回 DING/个人撤回 DING" → `message recall-personal` +用户说"DING 消息/查 DING/DING 历史/我的 DING" → `message list` +用户说"DING 接收状态/谁收到了 DING/DING 已读" → `message receiver-status` 关键区分: +- `ding message send`(机器人身份,需 --robot-code) vs `ding message send-personal`(用户身份,无需 robot-code) vs `ding message send-by-message`(消息转 DING,需指定原消息) +- `ding message recall`(机器人身份) vs `ding message recall-personal`(用户身份) - ding(紧急提醒, 支持电话/短信) vs bot(常规群/单聊消息) - sms/call 类型有通信费用 ## 核心工作流 ```bash -# 应用内 DING (免费) +# 机器人身份: 应用内 DING (免费) dws ding message send --robot-code --type app --users userId1,userId2 --content "请查看" --format json -# 电话 DING (紧急, 有成本!) +# 机器人身份: 电话 DING (紧急, 有成本!) dws ding message send --robot-code --type call --users userId1 --content "紧急告警" --format json -# 撤回 +# 机器人身份: 撤回 dws ding message recall --robot-code --id --format json + +# 用户身份: 应用内 DING +dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看" --format json + +# 用户身份: 电话 DING (紧急, 有成本!) +dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警" --format json + +# 用户身份: 消息转 DING +dws ding message send-by-message --group --message-id --users openDingTalkId1,openDingTalkId2 --format json + +# 用户身份: 撤回 +dws ding message recall-personal --id --format json ``` ## 上下文传递表 | 操作 | 提取 | 用于 | |------|------|------| | `message send` | `openDingId` | message recall 的 --id | +| `message send-personal` | `openDingId` | message recall-personal 的 --id | +| `message list` | `openDingId` | message receiver-status 的 --ding-id | +| `message send-by-message` | `openDingId` | message recall-personal 的 --id | ## 注意事项 - `--robot-code` 从钉钉开放平台 **应用管理 → 机器人** 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE` +- `send` / `recall` 是机器人身份,需要 --robot-code;`send-personal` / `recall-personal` / `send-by-message` 是用户身份,无需 robot-code +- `send` 接收者使用 userId;`send-personal` / `send-by-message` 接收者使用 openDingTalkId(可通过 `dws contact user search --query "姓名"` 获取) +- `send-by-message` 是将已有聊天消息转发为 DING,需指定 --group 和 --message-id - sms/call 类型有通信费用,使用前需和用户确认 - 默认 `--type app`(应用内 DING,免费) diff --git a/skills/mono/references/products/doc.md b/skills/mono/references/products/doc.md index 63f87305..04d21a9d 100644 --- a/skills/mono/references/products/doc.md +++ b/skills/mono/references/products/doc.md @@ -1,31 +1,5 @@ # 文档 (doc) 命令参考 -> **渐进式文档**:本文件为路由层(命令索引 + 场景索引 + 意图判断 + 工作流),各命令的详细参数、示例和踩坑说明在 [doc/](./doc/) 目录下按需加载。 - -## 文档地址 (URI) - -| 资源 | URI 格式 | -|------|----------| -| 文档节点 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | -| edit / preview 链接 | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | - -> **操作后请返回文档 URI**:每次执行 create / read / update 等操作后,从返回数据中提取 `docUrl` 直接返回;缺失时用 `doc info --node ` 补查。 - -## 前置条件 — 执行操作前必读 - -**CRITICAL — 执行对应操作前,MUST 先用 Read 工具读取以下子文件:** - -1. **解析 URL / 定位文档**(几乎所有命令都需要先拿 nodeId) - → 必读 [`doc/doc-info.md`](./doc/doc-info.md)(URL/dentryKey 提取规则、ID 边界、contentType 路由、**获取 nodeId 三种方式 A/B/C**) - -2. **创建或编辑文档内容**(`doc create` / `doc update` / `doc block insert|update`) - → 必读 [`doc/style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md)(**形态优先级硬规则:JSONML > element JSON > markdown**;markdown overwrite 会丢富结构) - - 从零创建时加读 [`doc/style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) - - **任何 `doc create` 都必须先读 [`doc/style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) §2.0 类型决策表 + §1 硬规则**(决定骨架 + 全局约束,不读就不知道用哪种骨架) - - 涉及 callout / 分栏 / 富 block 精修时再加读 style-guideline §4-§7 + [`doc/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) - -**未读以上文件就改写已有文档会导致富结构丢失、参数错误或样式不达标。其他命令(阅读 / 评论 / 权限 / 附件 / 下载导出 / 文件操作)按需查下方 §命令索引表跳转对应子文件加载,不必提前加载。** - ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -35,294 +9,1024 @@ dws doc --help # 查看具体命令的完整参数说明 -dws doc read --help +dws doc list --help dws doc create --help dws doc block insert --help # 查看子命令组下的所有命令 dws doc block --help -dws doc media --help +dws doc permission --help ``` 规则: - - 参数名不确定时 → 先 `--help`,再调用 - 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 - 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表 -## 命令索引表 +## 命令总览 -> 命令名 → 单文件,按需加载子文档。复杂任务请优先看下方 §场景索引。 +### 搜索文档 +``` +Usage: + dws doc search [flags] +Example: + dws doc search --query "会议纪要" + dws doc search + dws doc search --extensions pdf,docx + dws doc search --query "方案" --created-from 1700000000000 --created-to 1710000000000 + dws doc search --creator-uids uid1,uid2 + dws doc search --workspace-ids wsId1,wsId2 +Flags: + --query string 搜索关键词 (不传则返回最近访问) + --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,png)。支持的在线文档类型后缀名: adoc=文字, axls=表格, appt=演示文稿, awbd=白板, adraw=画板, amind=脑图, able=多维表格, aform=收集表 + --created-from int 创建时间起始 (毫秒时间戳,含) + --created-to int 创建时间截止 (毫秒时间戳,含) + --visited-from int 访问时间起始 (毫秒时间戳,含) + --visited-to int 访问时间截止 (毫秒时间戳,含) + --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 + --editor-uids strings 按编辑者用户 ID 过滤,逗号分隔 + --mentioned-uids strings 按 @提及的用户 ID 过滤,逗号分隔 + --workspace-ids strings 按知识库 ID 过滤,支持知识库 URL,逗号分隔 + --page-size int 每页数量 (默认 10,最大 30) + --page-token string 分页游标 (从上次结果的 nextPageToken 获取) +``` -### 阅读 / 元信息 +### 遍历文件列表 +``` +Usage: + dws doc list [flags] +Example: + dws doc list + dws doc list --folder + dws doc list --workspace --page-size 20 +Flags: + --folder string 文件夹 ID 或 URL + --workspace string 知识库 ID + --page-size int 每页数量 (默认 50,最大 50) + --page-token string 分页游标 (从上次结果的 nextPageToken 获取) +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc info` | 文档元信息(含 contentType / extension) | `--node` | [`doc/doc-info.md`](./doc/doc-info.md) | -| `doc read` | 读取正文(markdown 或 jsonml) | `--node` | [`doc/doc-read.md`](./doc/doc-read.md) | +### 获取文档元信息 +``` +Usage: + dws doc info [flags] +Example: + dws doc info --node + dws doc info --node "https://alidocs.dingtalk.com/i/nodes/" + dws doc info --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" + dws doc info --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" +Flags: + --node string 文档 ID 或 URL (必填) +``` -### 创建 / 写入 / 块编辑 +### 读取文档内容 +``` +Usage: + dws doc read [flags] +Example: + dws doc read --node + dws doc read --node "https://alidocs.dingtalk.com/i/nodes/" + dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" + dws doc read --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" +Flags: + --node string 文档 ID 或 URL (必填) +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc create` | 创建文字文档(adoc) | `--name` | [`doc/doc-create.md`](./doc/doc-create.md) | -| `doc update` | 整篇 / 段落级更新(markdown / jsonml) | `--node` `--mode` | [`doc/doc-update.md`](./doc/doc-update.md) | -| `doc block list/insert/update/delete` | 块级精细编辑(含 JSONML 节点操作) | `--node` (+ `--block-id`) | [`doc/doc-block.md`](./doc/doc-block.md) | +### 创建文档 +``` +Usage: + dws doc create [flags] +Example: + dws doc create --name "项目周报" + dws doc create --name "Q1 总结" --content "# Q1 总结" --folder + dws doc create --name "知识库文档" --workspace +Flags: + --name string 文档名称 (必填) + --folder string 父文件夹 nodeId / URL + --workspace string Workspace ID + --content string 初始 Markdown 内容 + --content-file string 从文件读取初始 Markdown +``` -### 附件 / 评论 / 导出 +### 创建其他类型文件 (表格/脑图/白板/多维表/画板) +``` +Usage: + dws doc file create [flags] +Example: + dws doc file create --name "项目周报" --type adoc + dws doc file create --name "数据统计" --type axls --folder + dws doc file create --name "思维导图" --type amind --workspace + dws doc file create --name "子文件夹" --type folder +Flags: + --name string 文件名称 (必填) + --type string 文件类型 (必填): adoc=文档, axls=表格, appt=演示, adraw=白板, amind=脑图, able=多维表, folder=文件夹 + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc media insert/download` | 附件 / 图片插入与下载 | `--node` `--file` 或 `--resource-id` | [`doc/doc-media.md`](./doc/doc-media.md) | -| `doc comment list/create/reply/create-inline` | 文档评论与划词评论 | `--node` (+ ...) | [`doc/doc-comment.md`](./doc/doc-comment.md) | -| `doc export` / `doc export get` | 在线文档导出 docx | `--node` `--output` | [`doc/doc-export.md`](./doc/doc-export.md) | +### 更新文档内容 +``` +Usage: + dws doc update [flags] +Example: + dws doc update --node --content "# 追加内容" --mode append + dws doc update --node --content "# 完整替换" --mode overwrite +Flags: + --node string 文档 nodeId / URL + --content string Markdown 内容 + --content-file string 从文件读取 Markdown + --mode string 更新模式: append / overwrite +``` -### 文件操作(doc 与 drive 均提供,按场景选用) +### 上传文件到钉钉文档或钉钉知识库 +``` +Usage: + dws doc upload [flags] +Example: + dws doc upload --file ./report.pdf + dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder + dws doc upload --file ./data.xlsx --workspace --convert +Flags: + --file string 本地文件路径 (必填) + --name string 文件显示名称 (默认使用文件名) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID + --convert 是否转换为钉钉在线文档 +``` -> **说明**:以下命令在 `doc` 下均为可用命令;`drive` 下也有等价命令(聚合钉盘 + 文档空间)。需要纯文件/钉盘语义时用 `drive`,需要在文档空间内操作时用 `doc`,两者都不会报 deprecated。 +### 下载文件到本地 +``` +Usage: + dws doc download [flags] +Example: + dws doc download --node + dws doc download --node --output ./report.pdf + dws doc download --node "https://alidocs.dingtalk.com/i/nodes/" --output ~/downloads/ +Flags: + --node string 文件节点 ID 或 URL (必填) + --output string 本地保存路径 (文件路径或目录,必填) +``` -| doc 命令 | drive 等价命令 | 详见 | -|----------|----------------|------| -| `doc upload` | `dws drive upload` | [`drive.md`](./drive.md) | -| `doc download` | `dws drive download` | [`drive.md`](./drive.md) | -| `doc copy` | `dws drive copy` | [`drive.md`](./drive.md) | -| `doc move` | `dws drive move` | [`drive.md`](./drive.md) | -| `doc rename` | `dws drive rename` | [`drive.md`](./drive.md) | -| `doc delete` | `dws drive delete` | [`drive.md`](./drive.md) | -| `doc folder create` | `dws drive mkdir` | [`drive.md`](./drive.md) | -| `doc file create` | `dws wiki node create --type ` | [`wiki.md`](./wiki.md) | -| `doc permission *` | `dws drive permission *` | [`drive.md`](./drive.md) | -| `doc list` | `dws drive list --workspace` / `dws wiki node list` | [`drive.md`](./drive.md) / [`wiki.md`](./wiki.md) | -| `doc search` | `dws drive search` / `dws wiki node search` | [`drive.md`](./drive.md) / [`wiki.md`](./wiki.md) | +### 创建文件夹 +``` +Usage: + dws doc folder create [flags] +Example: + dws doc folder create --name "项目资料" + dws doc folder create --name "子文件夹" --folder +Flags: + --name string 文件夹名称 (必填) + --folder string 父文件夹 ID 或 URL + --workspace string 目标知识库 ID +``` -### 排版规范 / JSONML 参考 +### 复制文档/文件 +``` +Usage: + dws doc copy [flags] +Example: + dws doc copy --node --folder + dws doc copy --node --workspace + dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/" --folder +Flags: + --node string 源文档/文件 ID 或 URL (必填) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录) +``` -| 资源 | 用途 | 详见 | -|------|------|------| -| 创建工作流 | 标题、位置、骨架、回读校验 | [`doc/style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) | -| 改写工作流 | 编辑形态优先级、分片 append、回读验收 | [`doc/style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | -| 排版规范 | 文档类型 / 骨架 / 元素边界 / 颜色语义 | [`doc/style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | -| JSONML 范例 | 所有节点类型的可复制命令 | [`doc/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) | -| JSONML 节点结构 | 字段定义 + JSON Schema | [`doc/format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) | +### 移动文档/文件 +``` +Usage: + dws doc move [flags] +Example: + dws doc move --node --folder + dws doc move --node --workspace +Flags: + --node string 源文档/文件 ID 或 URL (必填) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录) +``` -## 场景索引 +### 重命名文档/文件 +``` +Usage: + dws doc rename [flags] +Example: + dws doc rename --node --name "新名称" + dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/" --name "项目周报 v2" +Flags: + --node string 文档/文件 ID 或 URL (必填) + --name string 新名称 (必填) +``` -> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 Read 文件组**,不要逐个跳。 +### 查询块元素 +``` +Usage: + dws doc block list [flags] +Example: + dws doc block list --node + dws doc block list --node --start-index 0 --end-index 5 + dws doc block list --node --block-type heading +Flags: + --node string 文档 ID 或 URL (必填) + --start-index int 起始位置 (从 0 开始) + --end-index int 终止位置 (含) + --block-type string 按块类型过滤 +``` -| 任务场景 | 一次性读取 | 主命令 | -|---------|-----------|--------| -| 定位 nodeId / URL 解析 | [`doc-info.md`](./doc/doc-info.md)(搜索请用 `dws drive search` / `dws wiki node search`;遍历请用 `dws drive list` / `dws wiki node list`) | `drive search` / `wiki node search` | -| 阅读已有文档 | [`doc-info.md`](./doc/doc-info.md) + [`doc-read.md`](./doc/doc-read.md) | read | -| 创建新文档 | [`doc-create.md`](./doc/doc-create.md) + [`doc-update.md`](./doc/doc-update.md)(写入管道)+ [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create | -| 创建文档且包含图片/截图/图文并茂 | [`doc-create.md`](./doc/doc-create.md) + [`doc-media.md`](./doc/doc-media.md) + [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create → media insert | -| 局部改写 / 段落替换(保真) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | block update | -| 整篇 overwrite(可选并发检查) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | update | -| 插入富 block(callout / 分栏 / 表格) | [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | block insert | -| 上传图片 / 附件 | [`doc-media.md`](./doc/doc-media.md) | media insert | -| 评论 / 划词评论(含 @人) | [`doc-comment.md`](./doc/doc-comment.md)(+ `dws contact user search` 取 mention 用 userId) | comment create | -| 文档分享 / 节点级权限 | [`drive.md`](./drive.md)(推荐 `dws drive permission add/update/list/remove`;`doc permission *` 亦可用) | `drive permission` | -| 导出 PDF / DOCX | [`doc-info.md`](./doc/doc-info.md) + [`doc-export.md`](./doc/doc-export.md) | export | -| 文件下载 / 上传 / 移动 / 重命名 / 复制 | [`drive.md`](./drive.md)(推荐 `dws drive upload/download/copy/move/rename/delete`;同名 `doc` 命令亦可用) | `drive *` | +### 插入块元素 +``` +Usage: + dws doc block insert [flags] +Example: + dws doc block insert --node --text "这是一段文字" + dws doc block insert --node --heading "二级标题" --level 2 + dws doc block insert --node --element '{"blockType":"paragraph","paragraph":{"text":"内容"}}' + dws doc block insert --node --text "在此处之前插入" --ref-block --where before +Flags: + --node string 文档 ID 或 URL (必填) + --text string 快捷: 段落文本内容 + --heading string 快捷: 标题文本 + --level int 标题级别 1-6 (配合 --heading,默认 1) + --element string 块元素 JSON (高级) + --index int 参照位置索引 (从 0 开始) + --where string 插入方向: before / after (默认 after) + --ref-block string 参照块 ID (优先级高于 --index) +``` + +### 更新块元素 +``` +Usage: + dws doc block update [flags] +Example: + dws doc block update --node --block-id --text "新内容" + dws doc block update --node --block-id --element '{"blockType":"heading","heading":{"text":"新标题","level":1}}' +Flags: + --node string 文档 ID 或 URL (必填) + --block-id string 目标块 ID (必填) + --text string 快捷: 段落文本内容 + --heading string 快捷: 标题文本 + --level int 标题级别 1-6 (配合 --heading,默认 1) + --element string 块元素 JSON (高级) +``` + +### 删除块元素 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc block delete [flags] +Example: + dws doc block delete --node --block-id --yes +Flags: + --node string 文档 ID 或 URL (必填) + --block-id string 目标块 ID (必填) +``` + +### 查询文档评论列表 +``` +Usage: + dws doc comment list [flags] +Example: + dws doc comment list --node + dws doc comment list --node --type inline --resolve-status unresolved + dws doc comment list --node --page-size 20 --next-token +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --page-size int 每页返回的评论数量,默认 50,最大 50 + --next-token string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) + --type string 按评论类型过滤: global (全文评论) / inline (划词评论) + --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) +``` + +### 创建文档评论 +``` +Usage: + dws doc comment create [flags] +Example: + dws doc comment create --node --content "这里需要修改" + dws doc comment create --node --content "请review" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --content string 评论的文字内容,纯文本 (必填) + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 创建划词评论 (内联评论) +``` +Usage: + dws doc comment create-inline [flags] +Example: + dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" + dws doc comment create-inline --node --block-id --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文" + dws doc comment create-inline --node --block-id --start 0 --end 10 --content "请review" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --block-id string 评论锚定所在的块 ID (必填,可通过 dws doc block list 获取) + --start int 选中文本在块内的起始字符偏移量,从 0 开始 (必填) + --end int 选中文本在块内的结束字符偏移量,必须大于 start (必填) + --content string 评论的文字内容,纯文本 (必填) + --selected-text string 选中文本内容,填写后评论列表会展示「引用原文:xxx」 + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 回复文档评论 +``` +Usage: + dws doc comment reply [flags] +Example: + dws doc comment reply --node --comment-key --content "同意" + dws doc comment reply --node --comment-key --content "比心" --emoji + dws doc comment reply --node --comment-key --content "请确认" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --content string 回复的文字内容,表情回复时填写表情名称 (必填) + --comment-key string 被回复评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) + --emoji 设为 true 时作为表情贴图回复 (默认 false) + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 文件内容获取路由规则 + +> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `dws doc info` 获取文件元数据**,再根据返回的 `contentType` 和 `extension` 字段选择对应链路: + +| contentType | extension | 操作 | 命令 | +|-------------|-----------|------|------| +| ALIDOC | adoc | 在线获取 Markdown 内容 | `dws doc read --node ` | +| ALIDOC | axls | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node ` | +| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table get --base-id ` → `dws aitable record query --base-id --table-id ` | +| 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | + +**关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 + +### 格式保留度声明(adoc ↔ markdown lossy projection) + +`dws doc read --format json` 返回的 `markdown` 字段是钉钉 adoc 文档的**有损投影**。源 adoc 中以下富格式属性在 markdown 这一层**没有对应表达,会被丢弃**: + +- 行高(row height) +- 单元格背景色(cell background color) +- 字体大小(font size) +- 字体颜色的**渐变形态**(`radial-gradient` / `linear-gradient` — 文本会以 `` 字面保留,但 CSS `color` 不接受渐变,浏览器无法渲染,**字符串保留但语义已死**) +- 块级缩进 / 表格列宽 +- 合并单元格状态 +- checkbox 视觉样式(颜色、形状) +- 任何 SVG / 嵌入式画板的渲染细节 + +**硬指引(任何 contentType=ALIDOC 文档都适用)**: + +- 当用户要求"按模板生成同形态变体 / 参照这个生成 / 复刻 / 仿照这个文档"时,**禁止**走 doc read → create_file → doc create 链路——这条链路会做两次 lossy projection(adoc → markdown 丢一次,markdown → adoc 重建再丢一次),最终交付物只有文本内容,富格式全部失真。 +- 正确路径:`dws doc copy`(adoc 层面整文档保形复制)+ `dws doc rename` + `dws doc block list / dws doc block update`(在副本上做局部修改)。详见 [best_practices/04-document.md](../best_practices/04-document.md#template-based-generation) 的 `template-based-generation` recipe。 +- 当用户提到 `行高 / 颜色 / 字号 / 表格样式 / 周末高亮` 等富格式诉求时,agent 必须在动手前显式声明能力边界:"这些属性 markdown 无法表达,需走 copy + block update 路径",避免静默走破坏链。 + +### 内容写入管道(create / update 共用) + +> **关键原则**:CLI 内置自动分片。超长内容(>10000 字符)自动按 markdown 结构切分后逐片写入,对调用方透明。写入完成后由调用方自行决定是否回读确认。 + +#### 输入方式选择 + +| 场景 | 推荐方式 | 说明 | +|------|---------|------| +| 短文本(<2KB,无换行/表格/特殊字符) | `--content "..."` | 字面量传入,最简单 | +| 长文本(≥2KB)、含换行、含表格 | `--content-file ./file.md` | **必须**用文件路径,避免 shell escape 和截断 | +| 含特殊字符(`"`、`\`、`$`、`` ` ``) | `--content-file ./file.md` | 字面量传入会被 shell 转义破坏 | +| 管道/heredoc 输入 | `--content -` 或 `cat file \| dws doc ...` | 从 stdin 读取 | + +#### 自动分片行为 + +当内容超过 10000 字符时,CLI 自动执行: +1. **create**: 先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append +2. **update (overwrite)**: 第一片用 overwrite,后续片用 append +3. **update (append)**: 所有片段用 append + +分片策略按优先级:H1 标题 → H2 标题 → H3 标题 → 空行(段落边界)→ 硬切(保留表格/代码块完整性) + +如果某片写入超时,自动将分片大小减半重试(最小 5000 字符,低于此值报错)。 + +#### 输出格式 + +写入成功后输出 JSON(混合 `[INFO]` 进度行): + +```json +{"success": true, "nodeId": "xxx", "chunksWritten": 3} +``` + +| 字段 | 说明 | +|------|------| +| `nodeId` | 文档节点 ID,可用于后续读取或追加 | +| `chunksWritten` | 实际写入的分片数(1 = 单次写入) | + +#### 内容完整性验证(必读) + +CLI **不会**自动执行回读验证。**Agent 必须在文档写入完成后主动回读确认**——这是硬约束,不是建议: + +1. 使用 `dws doc read --node ` 读取写入后的文档内容 +2. 检查关键段落是否完整、顺序是否正确 +3. 如发现内容缺失或异常,使用 `dws doc update --mode append` 补写缺失部分 + +> **何时回读**:每次 create / update 操作完成后**必须**回读。 +> - 单次写入(≤10000 字符):写完立即回读一次 +> - 分片写入(>10000 字符):所有分片写完后回读一次全文,校验关键标题与段落完整性 +> - 破坏性 overwrite(`--mode overwrite --yes`):**必须**回读,确认 overwrite 未被后端静默降级为 append(详见 [best_practices/04-document.md "doc update 回读校验规范"](../best_practices/04-document.md#doc-update-回读校验规范)) +> - 连续多次编辑同一文档:可在全部编辑完成后统一回读一次 +> +> **禁止**在未回读的情况下直接向用户报告"已完成 / 更新成功"。Agent 自陈"完成"必须基于回读结果,而非工具调用的 `success=true` 字段。 + +#### 进度输出示例 + +``` +[INFO] 内容较长 (25000 字符),自动分片写入... +[INFO] 已创建空文档 (nodeId=abc123),开始分片写入... +[INFO] 写入分片 (1/3),10000 字符... +[INFO] 写入分片 (2/3),10000 字符... +[INFO] 写入分片 (3/3),5000 字符... +[INFO] 全部 3 个分片写入完成 +{"success": true, "nodeId": "abc123", "chunksWritten": 3} +``` + +#### CONTENT_TRUNCATED 错误 + +当分片写入持续超时且减半到最小阈值仍失败时,返回 `CONTENT_TRUNCATED` 错误码。应对策略: +1. 检查网络和后端服务状态 +2. 已写入的部分内容可通过 `dws doc read --node ` 查看 +3. 从断点处手动用 `dws doc update --mode append` 继续追加 + +### 删除文档/文件到回收站 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc delete [flags] +Example: + dws doc delete --node --format json # 查询 nodeId: dws doc search --query "..." 或 dws doc list +Flags: + --node string 文档/文件 ID 或 URL (必填) +``` + +权限要求: 对文档有"管理"权限。 + +### 下载文档附件 +``` +Usage: + dws doc media download [flags] +Example: + dws doc media download --node --resource-id + dws doc media download --node "https://alidocs.dingtalk.com/i/nodes/xxx" --resource-id +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --resource-id string 附件资源 ID,可通过 dws doc block list 获取 (必填) +``` + +### 上传附件并插入文档 +``` +Usage: + dws doc media insert [flags] +Example: + dws doc media insert --node --file ./report.pdf + dws doc media insert --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream + dws doc media insert --node --file ./image.png --ref-block --where before +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --file string 本地文件路径 (必填) + --name string 附件显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) + --index int 插入位置索引 + --where string 相对位置: before / after (配合 --ref-block) + --ref-block string 参考块 ID (配合 --where) +``` + +### 添加文档权限(节点级授权) +``` +Usage: + dws doc permission add [flags] +Example: + dws doc permission add --node --users uid1 --role READER + dws doc permission add --node --users uid1,uid2 --role EDITOR + dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --users uid1 --role MANAGER +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --users strings 被授权的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) + --workspace string 所属知识库 ID (选填,仅用于辅助构造返回的 docUrl,业务实际依赖 nodeId) +``` + +> **重要约束**: +> - 仅支持 USER 类型授权。 +> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加)。 +> - 操作者需在该节点具备「可编辑(EDITOR)」及以上角色(OWNER / MANAGER / EDITOR)。 +> - 授权对象是文档节点本身,不需要也不应该用 `wiki member add`(那个是知识库容器级授权)。 + +### 修改文档权限(节点级) +``` +Usage: + dws doc permission update [flags] +Example: + dws doc permission update --node --users uid1 --role EDITOR + dws doc permission update --node --users uid1,uid2 --role READER +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER + --workspace string 所属知识库 ID (选填) +``` + +### 列出文档权限(节点级) +``` +Usage: + dws doc permission list [flags] +Example: + dws doc permission list --node + dws doc permission list --node --limit 50 + dws doc permission list --node --filter-role EDITOR +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --workspace string 所属知识库 ID (选填) + --limit int 返回数量上限,最大 200 (默认 50) + --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) +``` + +> 接口不支持游标分页,使用 `--limit` 一次性拉取。 + +### 导出在线文档为 docx(一体化命令) +``` +Usage: + dws doc export [flags] +Example: + dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx + dws doc export --node --output ~/downloads/ +Flags: + --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) + --output string 本地保存路径,文件路径或目录 (必填) + --export-format string 导出格式,当前仅支持 docx (默认) +``` + +CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 +**只需一条命令,无需手动轮询。** + +### 查询导出任务结果(手动兜底) +``` +Usage: + dws doc export get [flags] +Example: + dws doc export get --job-id +Flags: + --job-id string 导出任务 ID (必填) +``` + +仅在 `dws doc export` 超时或中断后,用于手动查询任务状态。通常不需要调用。 + + +## URL 识别与 DOC_ID 提取 + +当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 + +补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 + +### 支持的 URL 格式 + +| 格式 | 示例 | DOC_ID 提取方式 | +|------|------|----------------| +| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段:`9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | +| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段:`abc123` | +| `alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | `https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE` | **不要提取 `dentryKey` 单独使用**,必须将完整 URL 原样传给 `--node` | + +### 提取规则 + +1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 +2. 路径为 `/i/nodes/{id}` 时,取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment) +3. 路径为 `/document/edit` 或 `/document/preview` 且 query 含 `dentryKey` 时,**禁止**提取 `dentryKey` 当 DOC_ID;将整段 URL 原样传给 `--node`,CLI 会自动解析(追踪参数如 `utm_source`、`chInfo` 也不必清理) +4. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) + +### ID 边界与参数映射 + +- `nodeId` 是 `doc` 命令的统一节点标识;文档、文件夹、文件都通过 `nodeId` 或完整 `alidocs` URL 传给 `--node` / `--folder`。 +- `dentryUuid` 是 `alidocs` URL `/i/nodes/{dentryUuid}` 的最后一段,在 `doc` 场景中等价于可传入 CLI 的 `nodeId`;不要把它改写成数字 ID。 +- `dentryId` 通常是纯数字,**不是** `doc` 的 `nodeId`,也不是 `doc --folder` 的目标文件夹 ID;不要把数字 `dentryId` 当作 `--node`、`--folder`,也不要当作隐藏兼容 alias(如 `--parent-id` / `--parent-folder-id`)的取值。 +- 目标父文件夹的 canonical flag 是 `--folder `,目标知识库使用 `--workspace `。CLI 可能接受 `--parent-id` / `--parent-folder-id` 作为隐藏兼容 alias,但 skill 正文和示例应统一教 `--folder`,不要把 drive/chat 链路里的数字 parentId 搬到 doc 命令。 +- 如果上下文只有数字 `dentryId`,但用户要读、改、移动、复制、重命名文档,先通过 `doc search` / `doc list` / 用户提供的 `alidocs` URL 获取 `nodeId` / `dentryUuid`,不要用数字 `dentryId` 重试为父目录参数。 + +### 处理流程 + +``` +用户输入含 alidocs.dingtalk.com URL + → 若是用户直接提供的原始 URL,先按链接规范做 probe + → 提取 DOC_ID(URL 路径最后一段) + → 结合用户意图选择命令(doc 默认 read,folder 默认 list,file 默认 download) + → 将 DOC_ID 传给 --node 参数 +``` ## 意图判断 用户说"找文档/搜文档/最近文档": - -- 全局搜索 → `dws drive search --query "<关键词>"`(聚合钉盘+文档空间) -- 空间内搜索 → `dws wiki node search --workspace --keyword "<关键词>"` -- 遍历文件夹 → `dws drive list --workspace ` 或 `dws wiki node list --workspace ` +- 搜索 → `search` +- 浏览 → `list` 用户说"看文档/读内容/文档内容": - -- 读取 → `read`(需文档 ID 或 URL) +- 读取 → `read` (需文档 ID 或 URL) - 元信息 → `info` 用户说"写文档/创建文档": - - 新建文字文档(adoc)→ `doc create` - 追加内容 → `update --mode append` -- 覆盖替换 → `update --mode overwrite` +- 整篇覆盖替换 → 先 `update --mode overwrite --dry-run` 预览,用户确认后再 `update --mode overwrite --yes` - 指定父目录时,只有明确的文档文件夹 `nodeId` / `alidocs` 文件夹 URL 才能放入 `--folder`;上一步若只返回了纯数字 `dentryId`、`spaceId` 或 drive `parent-id`,不要把它传给 doc 的 `--folder` > 严禁把「创建表格」路由到 `doc create`: -> -> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md#创建钉钉表格文档)(axls 在线电子表格) -> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md#创建-ai-表格)(able 多维表格) +> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md)(axls 在线电子表格) +> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md)(able 多维表格) + +用户说"新建脑图/白板/多维表/演示文稿": +- 用 `file create --type` 指定类型 (axls/amind/adraw/able/appt/adraw) 用户说"建文件夹/新建目录": - -- 创建 → `dws drive folder create` +- 创建 → `folder create` 或 `file create --type folder` 用户说"上传文件/传文件/上传到文档/上传到知识库": - -- 上传 → `dws drive upload`(需本地文件路径) +- 上传 → `upload`(需本地文件路径) +- 上传并转换 → `upload --convert` 用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": - - **必须先判断目标文件类型**,再决定走 `export` 还是 `download`: - - 在线文档 (alidocs/adoc) → **`doc export`**(导出是内容层操作,仅对 adoc 有意义) - - 已有文件(PDF、图片、附件、视频等非在线文档) → **`dws drive download`** + - 在线文档(alidocs/adoc)→ **`export`**(格式转换后导出为 docx) + - 已有文件(PDF、图片、附件、视频等非在线文档)→ **`download`**(直接下载原始文件) - 判断方法: - 1. 如果用户明确说了"导出文档"、"导出为Word/docx" → 直接走 `doc export` - 2. 如果用户明确说了"下载PDF/图片/附件" → 直接走 `drive download` - 3. 不确定时,先用 `drive info --node ` 查询节点信息,根据返回的 `contentType` 字段判断: - - `contentType` 为 `ALIDOC` → 走 `doc export` - - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `drive download` + 1. 用户明确说"导出文档"/"导出为 Word/docx" → 直接走 `export` + 2. 用户明确说"下载 PDF/图片/附件" → 直接走 `download` + 3. 不确定时,先用 `info --node ` 查询节点信息,根据 `contentType` 判断: + - `contentType` = `ALIDOC` → 走 `export` + - `contentType` = `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `download` > **严禁将"导出文档"直接路由到 `download`**。`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx,两者完全不同。 -用户说"复制文档/拷贝文件/复制到": - -- 复制 → `dws drive copy` +用户说"复制文档/拷贝一份/复制到": +- 复制 → `copy` (需源 --node 和目标 --folder/--workspace) 用户说"移动文档/搬到/移到/转移文件": - -- 移动 → `dws drive move` +- 移动 → `move` (需源 --node 和目标 --folder/--workspace) 用户说"重命名/rename/改名/改文档名/修改文档名称/修改文档标题/把这个文档叫做...": - -- 重命名 → `dws drive rename --node --name "新名称"` -- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update`。 +- 重命名 → `doc rename`(需文档 ID 或 URL + 新名称) +- 只要意图是修改文档在列表和链接中展示的名称,统一路由到 `dws doc rename --node --name "新名称"`;不要走 `drive`、`doc update` 或重新 `doc create` +- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update` 用户说"删除文档/删掉这个文件/移到回收站/丢掉这篇文档": - -- 删除节点 → `dws drive delete` +- 删除节点 → `delete`(危险操作,需 `--yes` 确认;需文档 ID 或 URL) 用户说"插入附件/上传附件到文档/往文档里加文件/加附件": - - 插入附件 → `media insert`(需文档 ID 或 URL + 本地文件路径) 用户说"插入图片/加图片/放张图/嵌入图片/往文档里插图": - - 插入图片 → `media insert`(需文档 ID 或 URL + 本地图片文件路径) - 注意:图片也是通过 `media insert` 作为附件块插入文档,不是通过 `block insert` 用户说"下载附件/获取附件/取出文档里的附件": - - 下载附件 → `media download`(需文档 ID 或 URL + 资源 ID) 用户说"编辑块/改段落/插入标题/删除块": - - 查看结构 → `block list` - 插入 → `block insert` - 修改 → `block update` - 删除 → `block delete` 用户说"给某人开权限/分享给某人/授权某文档/把这篇文档给 xxx 看": - -- 新增权限 → `dws drive permission add` -- 修改权限 → `dws drive permission update` -- 查看谁有权限 → `dws drive permission list` -- 移除权限 → `dws drive permission remove` +- 新增权限 → `permission add`(需 `--node` + `--user` + `--role`) +- 修改权限 → `permission update` +- 查看谁有权限 → `permission list` > **关键区分**: -> -> - "把**某篇文档**授权给某人" → `drive permission add`(节点级,包括「我的文档」下的文档都支持) +> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) > - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) > 补充:如果用户直接粘贴的是原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认是 `adoc` / `file` / `folder` 后,才继续按下列意图执行。 **用户直接粘贴文档 URL(无其他指令)**: - - 默认 → `read`(读取文档内容) - 如 URL 明显是文件夹 → `list`(列出文件夹内容) **用户粘贴 URL + 附加指令**: - - "帮我看看这个文档" → `read` - "这个文档的信息" → `info` - "往这个文档追加内容" → `update --mode append` -- "把这个文档标题改成 X" / "这个文档改名为 X" → `dws drive rename` +- "把这个文档标题改成 X" / "这个文档改名为 X" → `rename` - "把正文里的一级标题/章节标题改成 X" → `block update` 关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) +用户说"上传文件/传文件/上传到文档/上传到知识库": +- 上传 → `upload`(需本地文件路径) +- 上传并转换 → `upload --convert` + +用户说"下载文件/导出文件/下载到本地": +- 下载 → `download`(需文件节点 ID 或 URL) + +用户说"编辑块/改段落/插入标题/删除块": +- 查看结构 → `block list` +- 插入 → `block insert` +- 修改 → `block update` +- 删除 → `block delete` + +**用户直接粘贴文档 URL(无其他指令)**: +- 默认 → `read`(读取文档内容) +- 如 URL 明显是文件夹 → `list`(列出文件夹内容) + +**用户粘贴 URL + 附加指令**: +- "帮我看看这个文档" → `read` +- "这个文档的信息" → `info` +- "往这个文档追加内容" → `update --mode append` +- "编辑这个文档的标题" → `block update` + +关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) + ## 核心工作流 -> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 - ```bash -# ── 工作流 1: 定位并阅读文档 ── -dws drive search --query "<关键词>" --format json # 1. 搜索定位 nodeId(或 wiki node search) -dws doc info --node --format json # 2. 元信息(含 contentType) -dws doc read --node --format json # 3. 读 markdown 正文 +# ── 工作流 1: 浏览并阅读文档 ── -# ── 工作流 2: 创建文档(含分片自动写入)── -dws drive folder create --name "项目资料" --format json # 1. (可选) 创建文件夹 -dws doc create --name "项目周报" --content-file /tmp/x.md \ - --folder --format json # 2. 创建 + 写入 -dws doc read --node --format json # 3. 回读校验(必须) +# 1. 浏览我的文档根目录 +dws doc list --format json -# ── 工作流 3: 局部改写(保真,首选 JSONML)── -dws doc block list --node --content-format jsonml # 1. 取 uuid -dws doc block list --node --content-format jsonml --block-id # 2. 读子树 -dws doc block update --node --block-id \ - --content-format jsonml --element '[...]' # 3. 写回(uuid 必须 == --block-id) -dws doc read --node --content-format jsonml # 4. 回读 +# 2. 浏览子文件夹 +dws doc list --folder --format json -# ── 工作流 4: 整篇 overwrite(仅在新骨架重写时)── -dws doc read --node --content-format jsonml --output /tmp/doc.json # 1. 读出当前 JSONML -# 修改 /tmp/doc.json 中的 jsonml 数组 -dws doc update --node --content-file /tmp/doc.json \ - --content-format jsonml --mode overwrite # 2. 写回(默认不做并发检查) -dws doc read --node --content-format jsonml # 3. 回读 -# 担心被并发覆盖时,可加 --revision 触发并发检查(详见 doc-update.md) +# 3. 获取文档元信息 (标题、类型、权限) +dws doc info --node --format json -# ── 工作流 5: 上传独立文件 vs 插入附件到文档正文 ── -dws drive upload --file ./report.pdf --folder # 上传作为独立文件(存储层) -dws doc media insert --node --file ./report.pdf # 上传并作为附件块插入正文(内容层) +# 4. 读取文档内容 (Markdown 格式) +dws doc read --node --format json -# ── 工作流 6: 下载 vs 导出(先 info 判 contentType)── -dws doc info --node --format json # 必须先查 contentType -dws doc export --node --output ~/downloads/ # contentType=ALIDOC 走 export(内容层) -dws drive download --node --output ~/downloads/ # contentType≠ALIDOC 走 drive download(存储层) +# ── 工作流 2: 创建文档并写入内容 ── -# ── 工作流 7: 评论 + 划词(@人 需 userId)── -dws contact user search --query "张三" --format json # 取 userId -dws doc comment create --node --content "请确认" --mention --format json -dws doc block list --node --format json # 划词需先取 blockId / paragraph.text -dws doc comment create-inline --node --block-id \ - --start 0 --end 10 --content "建议调整" --selected-text "原文" --format json +# 1. (可选) 创建文件夹 — 提取 nodeId +dws doc folder create --name "项目资料" --format json -# ── 工作流 8: 权限授予(节点级,推荐用 drive,doc permission 亦可用)── -dws contact user search --query "张三" --format json # 取 userId -dws drive permission add --node --user , --role EDITOR --format json -dws drive permission list --node --format json # 校验 +# 2. 创建文档 — 提取 nodeId +dws doc create --name "项目周报" --folder --format json -# ── 工作流 9: 文件操作(推荐用 drive,同名 doc 命令亦可用)── -# 第一步:获取 nodeId -# 方式 A(优先):用户直接提供 URL / nodeId → 直接传 --node -# 方式 B:按关键字找:dws drive search --query "项目周报" --format json -# 方式 C:按文件夹遍历:dws drive list --workspace --format json -# 第二步:执行 -dws drive copy --node --folder --format json -dws drive move --node --folder --format json -dws drive rename --node --name "新名称" --format json -dws drive delete --node --yes --format json +# 3. 写入内容 (追加模式) +dws doc update --node --content "# 本周总结\n\n- 完成了 A\n- 推进了 B" --mode append --format json + +# ── 工作流 3: 一步创建带内容的文档 ── + +dws doc create --name "会议纪要" --content "# 会议纪要\n\n## 议题\n\n1. ..." --format json + +# ── 工作流 4: 上传本地文件到钉钉文档/知识库 ── + +# 1. 上传到"我的文档"根目录 +dws doc upload --file ./report.pdf + +# 2. 上传到指定文件夹 +dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder + +# 3. 上传到知识库并转换为在线文档 +dws doc upload --file ./data.xlsx --workspace --convert + +# ── 工作流 5: 下载/导出文件到本地 ── +# 必须先用 info 判断文件类型,再决定用 download 还是 export: +# - contentType 为 ALIDOC(在线文档)→ 用 export +# - contentType 为 DOCUMENT/IMAGE/VIDEO 等(已有文件)→ 用 download + +# 步骤 1: 查询文件类型 +dws doc info --node --format json +# 根据返回的 contentType 字段判断: + +# 如果是已有文件 (非 ALIDOC),用 download: +dws doc download --node --output ~/downloads/ + +# 如果是在线文档 (ALIDOC),用 export: +dws doc export --node --output ~/downloads/ + +# ── 工作流 6: 上传附件并插入文档 ── + +# media insert 自动完成三步流程: +# 1. 获取附件上传凭证 (get_doc_attachment_upload_info) +# 2. HTTP PUT 上传文件到 OSS +# 3. 插入附件块到文档 (insert_document_block) + +# 1. 基本用法: 插入本地文件到文档 +dws doc media insert --node --file ./report.pdf + +# 2. 指定附件显示名称 +dws doc media insert --node --file ./data.xlsx --name "Q1数据报表.xlsx" + +# 3. 指定 MIME 类型 (文件扩展名无法推断时) +dws doc media insert --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream + +# 4. 在指定块之前插入附件 +dws doc media insert --node --file ./image.png --ref-block --where before + +# 5. 完整流程: 创建文档 → 写入内容 → 插入附件 +dws doc create --name "项目报告" --content "# 项目报告\n\n以下为相关附件:" --format json +# 提取 nodeId 后: +dws doc media insert --node --file ./design.pdf +dws doc media insert --node --file ./timeline.xlsx --name "项目时间线.xlsx" + +# ── 工作流 7: 块级精细编辑 ── + +# 1. 查看文档块结构 — 获取 blockId +dws doc block list --node --format json + +# 2. 在文档末尾插入段落 +dws doc block insert --node --text "新增内容" + +# 3. 在指定块之前插入标题 +dws doc block insert --node --heading "新章节" --level 2 --ref-block --where before + +# 4. 更新某个块的内容 +dws doc block update --node --block-id --text "修改后的内容" + +# 5. 删除块 +dws doc block delete --node --block-id --yes + +# ── 工作流 8: 复制/移动/重命名文档 ── + +# 获取 nodeId 的三种方式(按场景选择,无需全部执行): +# 方式 A: 用户直接提供文档 URL — 直接传给 --node,无需额外查询 +# 方式 B: 搜索文档 — 从返回中提取 nodeId +dws doc search --query "项目周报" --format json +# 方式 C: 浏览文件夹 — 从返回中提取 nodeId +dws doc list --folder --format json +# 注意: 这里的 是文件夹 nodeId/dentryUuid 或文件夹 URL,不是数字 dentryId;示例统一使用 canonical --folder。 + +# 复制文档到指定文件夹(--node 支持 ID 或 URL) +dws doc copy --node --folder --format json + +# 复制到目标知识库根目录(不传 --folder 时) +dws doc copy --node --workspace --format json + +# 移动文档到指定文件夹 +dws doc move --node --folder --format json + +# 移动到目标知识库根目录(不传 --folder 时) +dws doc move --node --workspace --format json + +# 重命名文档 +dws doc rename --node --name "新名称" --format json + +# 删除文档到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) +# 正确流程:1.向用户展示"即将删除「文档名」到回收站" → 2.等用户确认 → 3.执行下面命令 +dws doc delete --node --yes --format json + +# ── 工作流 9: 文档评论管理 ── + +# 1. 查看文档的所有评论 +dws doc comment list --node --format json + +# 2. 在文档上创建评论 +dws doc comment create --node --content "这里需要补充数据来源" --format json + +# 3. 创建评论并 @ 相关人 +# 先搜索用户: dws contact user search --query "张三" --format json → 提取 userId +# 再将 userId 传入 --mention +dws doc comment create --node --content "请确认这部分内容" --mention , --format json + +# 4. 回复某条评论(commentKey 从 list 或 create 返回中获取) +dws doc comment reply --node --comment-key --content "已修改" --format json + +# 5. 用表情回复评论 +dws doc comment reply --node --comment-key --content "比心" --emoji --format json + +# 6. 创建划词评论(针对文档中某段选中文本) +# 先获取块列表: dws doc block list --node --format json → 提取 blockId 和文本内容 +# 确定选中文本在块内的起始偏移量 (start) 和结束偏移量 (end) +dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" --format json + +# 7. 创建划词评论并附带引用原文 + @ 相关人 +dws doc comment create-inline --node --block-id --start 5 --end 20 --content "请确认这部分" --selected-text "被选中的原文内容" --mention , --format json + +# ── 工作流 10: 导出在线文档为 docx ── + +# 一条命令自动完成(提交→轮询→下载),无需手动编排 +dws doc export --node --output ./exported.docx + +# 如果导出命令超时或中断,可用 export get 手动查询任务状态: +# dws doc export get --job-id ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `drive search` / `wiki node search` | 文档 `nodeId` / URL | doc read / info / update 等所有 `--node` 入参 | -| `drive list` / `wiki node list` | `nodes[].nodeId` | doc read / info / update / block 操作的 --node | +| `list` | `nodes[].nodeId` | read / info / update / block 操作的 --node | +| `list` | folder 类型的 `nodeId` | list 的 --folder, create 的 --folder | +| `search` | 文档 `nodeId` / URL / `createTime` / `creatorUid` | read / info / update 的 --node;创建时间与创建者信息 | | `create` | `nodeId` | update / block 操作的 --node | +| `folder create` | `nodeId` | create / list / upload 的 --folder | | `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id | -| `read --content-format jsonml` | `revision` | update --content-format jsonml 的 --revision(可选,并发检查时使用) | -| `media insert` | `resourceId` | 附件已插入文档,可通过 block list 查看附件块 | -| `media download` | 附件下载链接 `downloadUrl` | 下载文档中的附件资源 | -| `block list` | attachment 块的 `resourceId` | media download 的 --resource-id | +| `upload` | `nodeId` / URL | 上传后文件的访问链接 | +| `download` | 本地文件路径 | 下载后的文件保存位置 | | `comment list` | `commentList[].commentKey` | comment reply 的 --comment-key | -| `comment create` | `commentKey` | comment reply 的 --comment-key | -| `comment create-inline` | `commentKey` | comment reply 的 --comment-key | -| `block list` | `blocks[].element.id` | comment create-inline 的 --block-id | -| `block list` | `blocks[].element.paragraph.text` | 计算 create-inline 的 --start / --end 偏移量 | -| `contact user search` | `userId` | comment create/reply/create-inline 的 --mention;drive permission 的 --user | +| `comment create` / `comment create-inline` | `commentKey` | comment reply 的 --comment-key | +| `block list` | `blockId` + 文本内容 | comment create-inline 的 --block-id 及 --start/--end 计算 | +| `contact user search` | `userId` | comment create / create-inline / reply 的 --mention | +| `file create` | `nodeId` | 后续 read / update / block 操作的 --node(仅 adoc 支持 read/update,axls/amind 等类型用各自产品的命令) | +| `copy` / `move` | 新 `nodeId`(copy)或原 nodeId(move) | 后续 read / info 等的 --node | + +## nodeId 多格式说明 + +所有 `--node` 参数同时支持以下格式,系统自动识别: +- **文档 ID**: 字母数字字符串,如 `9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` +- **文档 URL**: `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}`,如 `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` +- **文档链接(edit/preview)**: `https://alidocs.dingtalk.com/document/{edit|preview}?...&dentryKey={key}`(必须传入完整 URL,不要提取其中的 query 参数单独使用) + +以下命令效果相同: +```bash +dws doc read --node 9E05BDRVQePjzLkZt2p2vE7kV63zgkYA +dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA" +dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE" +dws doc read --node "https://alidocs.dingtalk.com/document/preview?cid=74993670680&type=d&docKey=Pd6l2Z7V8ZWydl7M&dentryKey=rBGBr2r1HmwanAGW" +``` + +> **注意**:`document/edit` 和 `document/preview` 格式 URL 中的 `dentryKey` 参数值不是合法的独立 nodeId,禁止提取后单独使用,必须传入完整 URL。URL 中可能包含 `utm_source`、`chInfo` 等追踪参数,无需手动去除,直接传入完整 URL 即可。 + +`--folder` 参数同样支持文件夹 URL 或 ID。 + +## 长 Markdown 写入 + +**核心规则**:含多行、表格、`\n` 或长度 >2KB 的 Markdown **必须**通过 `--content-file` 或 `--content -`(stdin)传入,禁止直接作为 `--content` 命令行字符串——shell escape 会破坏换行和表格,且命令行长度受限。 + +`dws doc create` 和 `dws doc update` 支持两种内容来源(`--content-file` 优先于 `--content`): + +| 形式 | 说明 | +|------|------| +| `--content "..."` | 字面量(仅推荐短文本 <2KB 且无换行/表格) | +| `--content -` | 从 stdin 读取(可配合 heredoc/pipe) | +| `--content-file path` | 从文件读取(UTF-8),推荐 | + +### 短/中等长度(< 200KB)— 单步创建 + +```bash +# 1. 把内容写入 UTF-8 文本文件: +# Linux/Mac: /tmp/.md;Windows: %TEMP%\.md +# 2. 一步创建+写入: +dws doc create --name "<文档名>" --content-file [--folder ] [--workspace ] +``` + +### 超长(> 200KB 兜底)— 创建空文档 + 分片追加 + +```bash +# 1. 创建空文档拿 nodeId +dws doc create --name "<文档名>" [--folder/--workspace] # → nodeId + +# 2. 按 markdown 标题或段落边界切成 ≤200KB 的片段(不要切断表格) +# 3. 逐个追加: +dws doc update --node --content-file --mode append +``` + +### stdin 变体 + +```bash +# pipe +cat report.md | dws doc update --node --content - --mode append + +# heredoc(真实换行,含表格) +dws doc update --node --mode append --content - <<'EOF' +## 追加段落 + +| 列1 | 列2 | +|---|---| +| a | b | +EOF +``` + +## 注意事项 + +- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟) +- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态 +- `export` 当前仅支持钉钉在线文档 (alidocs) 导出为 `docx`;当前开源 CLI 未暴露在线表格导出命令,在线表格数据读取走 `dws sheet range read --node ` +- `update --mode overwrite` 会**清空原内容后重写**,⚠️ 谨慎使用;必须先 `--dry-run` 预览并在用户确认后加 `--yes`;默认 `--mode append`(追加)更安全 +- `read` 返回 Markdown 格式的文档内容,仅限有"下载"权限的文档 +- `read` 返回的内容中,文档里的附件会以 OSS 临时下载链接形式给出(如 `https://alidocs2.oss-cn-zhangjiakou.aliyuncs.com/res/.../att/.ext?Expires=...`),该链接会过期。链接过期后,可从 URL 路径中提取 ``(即 `/att/` 后、扩展名前的 UUID 部分),然后使用 `media download --node --resource-id ` 重新获取下载链接 +- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 +- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示/文件夹,用 `file create --type` +- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append` +- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON +- `--content` 参数中的换行必须使用**真实换行符**(即实际的换行字符,Unicode `U+000A`),而不是字面量字符串 `\n`(反斜杠加字母 n)。在通过程序或大模型构造此参数时,请确保字符串在发送前已正确反转义。如果传入的是两个字符的字面量 `\n`,所有内容将渲染在同一行,导致标题、段落和表格格式全部错乱。**含多行/表格/长文本时优先用 `--content-file path.md` 或 `--content -`(stdin),不经过 shell escape,换行和表格都保持原样**(详见下方「长 Markdown 写入」)。 +- 块类型包括: paragraph, heading, blockquote, callout, columns, orderedList, unorderedList, table, sheet, attachment, slot +- 关键区分: doc(文档内容级操作) vs wiki(知识库空间级管理) vs aitable(数据表格操作) vs drive(钉盘文件管理) +- wiki 是知识库容器,doc 是知识库中的文档内容;需要 `workspaceId` 时,先用 `dws wiki space list/search` 获取,再传给 doc 的 `--workspace` 参数 +- `doc upload vs drive upload`:用户提到"知识库/文档空间/workspace" → `doc upload`;提到"钉盘/网盘/我的文件" → `drive upload`;未明确目标时默认 `drive upload` +- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档 +- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作 +- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录 +- `media insert` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 插入附件块到文档),无需手动分步操作 +- `media insert` 的 `--mime-type` 可选,不指定时根据文件扩展名自动推断;支持常见文件类型 (PDF、Office、图片、视频、压缩包等) +- `media insert` 与 `upload` 的区别:`upload` 将文件上传到文档空间/知识库作为独立文件;`media insert` 将文件作为附件块插入到文档正文中 +- `media download` 用于获取文档正文中附件的临时下载链接,`--resource-id` 可通过 `block list` 返回的 attachment 块获取 +- `copy` 需要对源节点有"阅读"权限,且对目标文件夹有"编辑"权限 +- `move` 需要对源节点有"管理"权限,且对目标文件夹有"编辑"权限;移动后原位置的文档将不再存在 +- `rename` 需要对文档有"编辑"权限 +- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档" +- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始) + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [doc_create_and_write.py](../../scripts/doc_create_and_write.py) | 创建文档并写入 Markdown 内容 | `python doc_create_and_write.py --name "周报" --content "# 本周总结"` | ## 相关产品 -- [wiki](./wiki.md) — 知识库空间级管理(创建/查询/列出/搜索知识库),doc 中的文档存储在 wiki 知识库中 - [aitable](./aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 - [drive](./drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 - [report](./report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 diff --git a/skills/mono/references/products/drive.md b/skills/mono/references/products/drive.md index 6304f2c1..a84e2a7c 100644 --- a/skills/mono/references/products/drive.md +++ b/skills/mono/references/products/drive.md @@ -1,5 +1,7 @@ # 钉盘 (drive) 命令参考 +钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。 + ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -33,26 +35,33 @@ Example: Flags: --limit int 每页返回数量,默认 20,最大 50 (可选) --cursor string 分页游标,首次不传 (可选) - --order string 排序方向: asc|desc,默认 desc (可选) - --order-by string 排序字段: createTime|modifyTime|name (可选) + --order string 排序方向: asc|desc,默认 desc (可选,仅钉盘) + --order-by string 排序字段: createTime|modifyTime|name (可选,仅钉盘) --folder string 父节点 ID (dentryUuid),不传则列出空间根目录 (可选) - --space-id string 空间 ID,不传则使用「我的文件」对应 spaceId (可选) - --thumbnail 是否返回缩略图信息 (可选) + --space-id string 钉盘空间 ID (纯数字),不传则使用「我的文件」对应 spaceId (可选) + --workspace string 文档空间/知识库 ID (加密 string 或 URL),传入则路由到文档空间 (可选) + --thumbnail 是否返回缩略图信息 (可选,仅钉盘) ``` +> 统一入口:默认列钉盘空间(`--space-id` 纯数字);传 `--workspace` 时路由到文档空间/知识库列表。 + ### 获取钉盘空间列表 +> **Deprecated**:推荐改用 `dws wiki space list --type orgSpace` / `--type mySpace`(见 [wiki.md](./wiki.md))。本命令仍可用,仅作兼容保留。 +> 适用场景:复制/移动文件到「我的文件」或团队空间根目录时,先取 `rootFolderId`;或者枚举用户可访问的团队空间。 + ``` Usage: dws drive list-spaces [flags] Example: - dws drive list-spaces - dws drive list-spaces --space-type mySpace + dws wiki space list --type orgSpace # 推荐 + dws wiki space list --type mySpace # 推荐 + dws drive list-spaces # deprecated dws drive list-spaces --space-type orgSpace --limit 20 --cursor Flags: --space-type string 空间类型: orgSpace=企业空间(默认), mySpace=我的文件 (可选) --limit int 每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效 - --cursor string 分页游标,仅企业空间支持分页 (可选) + --cursor string 分页游标,仅企业空间支持分页 (可选) ``` spaceType 筛选规则: @@ -62,13 +71,13 @@ spaceType 筛选规则: 返回字段说明: - `spaceId` — 空间 ID,用于 `list`/`info`/`upload` 等命令的 `--space-id` - `spaceName` — 空间名称(如"全员文件夹"、"我的文件") -- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `doc copy/move` 的 `--folder` 参数 +- `rootFolderId` — 空间根目录的 dentryUuid,可作为 `drive copy/move` 的 `--folder` 参数 - `spaceType` — 空间类型(如 `orgSpace`) - `nextToken` — 若不为空,表示还有更多空间可查询(仅企业空间) ### 搜索钉盘文件/文件夹/空间 -按关键词在钉盘中搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 +按关键词全局搜索文件,默认同时搜索钉盘和文档空间,合并返回结果。不同于 `list`(需要明确的 spaceId/folder 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 ``` Usage: @@ -82,7 +91,7 @@ Example: dws drive search --query "报告" --limit 30 --cursor Flags: --query string 搜索关键词 (必填) - --target string 搜索目标: all(默认) | file | space (可选) + --target string 搜索范围: all(默认,聚合钉盘+文档空间) | file(仅钉盘文件) | space(仅钉盘空间) (可选) --file-types strings 按文件内容类型过滤,逗号分隔: alidoc,document,image,video,audio,archive (仅 target=file/all 生效) --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,adoc) --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 @@ -94,15 +103,47 @@ Flags: --cursor string 分页游标,从上次返回的 nextCursor 获取 (可选) ``` -搜索目标 (`--target`) 选择规则: -- `all`(默认):同时搜文件与空间,返回混合结果 — 不确定目标是文件还是空间时使用 -- `file`:只搜文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找文件时使用 -- `space`:只搜团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 +搜索范围 (`--target`) 选择规则: +- `all`(默认):同时搜钉盘文件与文档空间,聚合返回 — 不确定目标位置时使用 +- `file`:只搜钉盘文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找钉盘文件时使用 +- `space`:只搜钉盘团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 -返回结果中 `type` 字段区分:`SPACE`(空间)、`FILE`(普通文件)、`FOLDER`(文件夹)、`ALIDOC`(钉钉在线文档)。 +结果中 `source` 字段区分来源:`drive` / `doc`。如果需要在某个知识库内搜索,请使用 `dws wiki node search --workspace `。 > **提示**:结果按相关性排序,首页未命中时优先调整关键词 / 补充 `--file-types`/`--extensions` 缩小范围 / 加上时间范围,而非反复翻页。 +### 获取最近访问/编辑的文档列表 + +``` +Usage: + dws drive recent [flags] +Example: + dws drive recent + dws drive recent --operate-type 1 + dws drive recent --creator-type 1 --limit 10 + dws drive recent --file-types 0,1 --operate-type 0 +Flags: + --file-types ints 按文档类型过滤,逗号分隔 (参考 RecentAccessType 枚举) (可选) + --operate-type ints 按操作类型过滤: 0=最近访问(默认), 1=最近编辑; 不传默认仅返回最近访问(0) (可选) + --creator-type int 按创建人过滤: 0=全部(默认), 1=我创建, 2=他人创建 (可选) + --org-ids ints 按资源所属组织 ID 过滤,逗号分隔 (可选) + --limit int 每页数量 (默认 20,最大 20) (可选) + --cursor string 分页游标,从上次返回的 nextCursor 获取 (可选) +``` + +返回字段说明: +- `recentItems[]` — 最近访问/编辑的文档列表 + - `nodeId` — 文档节点 ID,可用于 `doc read/info/update` 的 `--node` + - `name` — 文档名称 + - `contentType` — 内容类型(如 ALIDOC) + - `extension` — 扩展名(如 adoc、axls、able) + - `docUrl` — 文档在线访问 URL + - `operateType` — 操作类型:LAUNCH=访问,EDIT=编辑 + - `accessTime` — 最近访问时间 + - `createTime` / `updateTime` — 创建/更新时间 +- `nextCursor` — 翻页游标,传入 `--cursor` 获取下一页 +- `hasMore` — 是否还有更多数据 + ### 获取文件元数据信息 ``` @@ -111,7 +152,7 @@ Usage: Example: dws drive info --node Flags: - --node string 节点 ID (dentryUuid) (必填) + --node string 节点 ID (dentryUuid) (必填) --space-id string 节点所属空间 ID (可选) ``` @@ -122,12 +163,12 @@ Flags: | extension | 文件类型 | 操作 | 命令 | |-----------|---------|------|------| -| adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | -| axls | 在线表格 | 在线读取表格数据 | `dws sheet get-all-sheets` → `dws sheet get-range` | -| able | 多维表格 | 在线查询记录 | `dws aitable get-tables` → `dws aitable query-records` | -| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download` | +| adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | +| axls | 在线表格 | 在线读取表格数据 | `dws sheet list` → `dws sheet range read` | +| able | 多维表格 | 在线查询记录 | `dws aitable base get` → `dws aitable record query` | +| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download --node --output ` | -### 下载文件到本地 +### 下载钉盘文件到本地 下载流程一步到位:获取下载 URL → HTTP GET 下载文件二进制内容到本地。 @@ -138,7 +179,7 @@ Example: dws drive download --node --output ./report.pdf dws drive download --node --output ~/downloads/ Flags: - --node string 文件 ID (dentryUuid) (必填) + --node string 文件 ID (dentryUuid) (必填) --output string 本地保存路径 (必填),可以是文件路径或目录;如果指定目录,文件名从下载 URL 中自动推断 --space-id string 文件所属空间 ID (可选) ``` @@ -154,14 +195,16 @@ Example: dws drive mkdir --name "项目资料" dws drive mkdir --name "子目录" --folder Flags: - --name string 文件夹名称,最长 50 字符 (必填) - --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --name string 文件夹名称,最长 50 字符 (必填) + --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) + --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) ``` +> `mkdir` 在钉盘空间创建文件夹;要在文档空间/知识库中创建文件夹,用 `dws wiki node create --type folder --workspace `(见 [wiki.md](./wiki.md))。 + ### 上传本地文件到钉盘 -> **注意:** 上传文件必须使用 `dws drive upload` 命令,禁止使用 `upload-info` + `curl` + `commit` 三步流程。 +> **注意:** 上传文件首选 `dws drive upload` 一条命令(内部自动完成三步流程),不要手动走 `upload-info` + `curl` + `commit` 三步。 ``` Usage: @@ -173,12 +216,47 @@ Example: Flags: --file string 本地文件路径 (必填) --file-name string 文件显示名称 (默认使用文件名) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) + --space-id string 目标钉盘空间 ID,不传则使用「我的文件」 (可选) + --workspace string 目标知识库 ID,传入时路由到文档空间上传 (可选) + --convert 是否转换为钉钉在线文档 (仅文档空间上传时生效) --mime-type string 文件 MIME 类型,不传则自动推断 (可选) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) ``` -`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。 +`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。上传到知识库/文档空间时加 `--workspace` 参数。 + +### 获取上传凭证 (手动三步·仅特殊场景) + +> 仅当需要自定义流式上传、无法使用 `upload` 一条命令时才走手动三步:`upload-info` → HTTP PUT → `commit`。 + +``` +Usage: + dws drive upload-info [flags] +Example: + dws drive upload-info --file-name "report.pdf" --file-size 102400 + dws drive upload-info --file-name "slides.pptx" --file-size 512000 --folder +Flags: + --file-name string 文件名含后缀 (必填) + --file-size int 文件大小,单位字节 (必填) + --mime-type string MIME 类型 (可选,服务端会自动推断) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) + --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) +``` + +### 提交上传 (手动三步·第三步) + +``` +Usage: + dws drive commit [flags] +Example: + dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id +Flags: + --file-name string 文件名含后缀 (必填,须与 upload-info 一致) + --file-size int 文件大小,单位字节 (必填,须与 upload-info 一致) + --upload-id string upload-info 返回的 uploadId (必填) + --folder string 父节点 ID (dentryUuid),须与 upload-info 一致 (可选) + --space-id string 空间 ID,不传则使用「我的文件」 (可选) +``` ### 删除文件/文件夹到回收站 @@ -190,30 +268,68 @@ Usage: Example: dws drive delete --node --format json # 查询 fileId: dws drive list Flags: - --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) + --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) + +Global Flags: + --yes 跳过二次确认 (危险操作,建议先与用户确认) ``` +> 由当前二进制静态注册(路由到 doc 服务的 `delete_document` tool);如果当前版本未暴露,调用前可用 `dws drive delete --help` 验证。 注意:`--node` 使用的是 `drive list` 返回结果中的 `fileId` 字段(即 `dentryUuid`),**不是** `dentryId` 字段。 +删除是软删除(进回收站),但仍需用户明确确认;不要在自动化脚本里默认带 `--yes`。 + +### 查看回收站文件列表 + +``` +Usage: + dws drive recycle list [flags] +Example: + dws drive recycle list + dws drive recycle list --space-id 12345 --limit 10 +Flags: + --space-id string 钉盘空间 ID (选填,不传则返回所有空间) + --limit int 返回条数上限 (默认 20,最大 50) + --cursor string 分页游标 (选填) +``` + +### 还原回收站文件 + +``` +Usage: + dws drive recycle restore [flags] +Example: + dws drive recycle restore --id +Flags: + --id string 回收项 ID (必填,从 recycle list 获取) +``` + +> **注意**:还原操作可能是异步的(返回 `async=true` 和 `taskId`)。 ## 意图判断 用户说"我的文件/钉盘/网盘/云盘" → `list` -用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `list-spaces` +用户说"最近访问/最近打开/最近编辑/最近文档" → `recent`(默认仅最近访问,`--operate-type 1` 仅最近编辑,`--operate-type 0,1` 全部) +用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `wiki space list --type orgSpace`(`drive list-spaces` 已 deprecated) 用户说"搜索钉盘文件/钉盘里找个文件/查找某个钉盘文件/钉盘中搜索" → `search` 用户说"文件详情/文件信息" → `info` -用户说"下载文件" → `download` -用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `folder create`(文档空间) -用户说"上传文件/传文件到钉盘" → `upload`(必须使用此命令,自动完成三步流程) +用户说"下载文件" → `download` 指定 `--output` 保存到本地 +用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `wiki node create --type folder`(文档空间) +用户说"上传文件/传文件到钉盘" → `upload`(首选此命令,自动完成三步流程) 用户说"复制文件/移动文件/搬到/移到" → `copy` / `move` 用户说"重命名/改名" → `rename` 用户说"删除文件/删除文件夹/移到回收站" → `delete`(危险操作,需确认) +用户说"回收站/查看回收站/回收站列表/回收站里有什么" → `recycle list` +用户说"恢复文件/还原删除的文件/从回收站恢复/还原回收站文件" → `recycle restore` 用户说"给文档授权/分享权限" → `permission add` +用户说"公开文件/互联网公开/设置公开/让互联网所有人可访问" → `publish set` +用户说"关闭公开/取消公开/取消互联网访问" → `publish unset` +用户说"查看公开状态/是否公开/发布状态" → `publish get` 关键区分: drive(文件管理) vs doc(文档内容读写) vs wiki(空间管理) -**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时优先问明。 +**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时 `drive search`(全局聚合搜索)。 -**drive upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 +**drive upload vs doc upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 **drive permission vs wiki member**: "给某篇文档/文件授权" → `drive permission add`(节点级);"给某个知识库整体加成员" → `wiki member add`(空间级) @@ -221,6 +337,8 @@ Flags: **导出文档/导出为Word**: 导出是内容层操作,走 `doc export`,不属于 drive。 +把图片/文件发到群里一般直接用 `chat message send --msg-type file --file-path <本地路径>`(见 [chat.md](./chat.md)),无需先经 drive 上传。 + ## 核心工作流 ```bash @@ -239,18 +357,22 @@ dws drive download --node --output /tmp/ --format json # 5. 创建文件夹 dws drive mkdir --name "项目资料" --format json -# 6. 上传文件(必须使用 upload 命令,禁止手动分步操作) +# 6. 上传文件(首选 upload 命令,自动完成三步流程) dws drive upload --file ./报告.pdf --format json dws drive upload --file ./报告.pdf --folder --format json # 7. 删除文件/文件夹到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) # 正确流程:1.向用户展示"即将删除「文件名」到回收站" → 2.等用户确认 → 3.执行下面命令 dws drive delete --node --yes --format json + +# 8. 查看回收站并还原文件 +dws drive recycle list --format json +dws drive recycle restore --id --format json ``` ## 文档空间管理命令 -> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc MCP server。 +> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc 服务。 > 与钉盘命令(list / mkdir / upload 等)的区别:钉盘命令操作钉盘空间(spaceId 纯数字),文档空间命令操作知识库/我的文档(workspaceId 加密 string)。 ### 复制/移动/重命名文件 @@ -267,18 +389,19 @@ Flags: --name string 新名称 (仅 rename 必填) ``` +权限要求:copy 需对源文档有"阅读"权限且对目标文件夹有"编辑"权限;move 需对源文档有"管理"权限且对目标文件夹有"编辑"权限;rename 需对文档有"编辑"权限。 + > **字段选择**:`drive list` 返回中有 `dentryId`(数字格式)和 `fileId`(UUID 格式),**必须使用 `fileId`(UUID 格式)**作为 `--node` 和 `--folder` 参数值。 ### 创建文件夹(文档空间) +drive 没有独立的文档空间建文件夹命令,在知识库/文档空间中创建文件夹走: + +```bash +dws wiki node create --type folder --name "文件夹名" --workspace --format json ``` -Usage: - dws drive folder create --name "文件夹名" -Flags: - --name string 名称 (必填) - --folder string 父文件夹 nodeId - --workspace string 目标知识库 ID -``` + +详见 [wiki.md](./wiki.md)。 ### 权限管理(文档节点级) @@ -292,79 +415,118 @@ Usage: dws drive permission remove --node --users uid1 Flags: --node string 目标节点 ID 或 URL (必填) - --users string 用户 userId 列表,逗号分隔 - --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER + --users string 用户 userId 列表,逗号分隔 (add/update/remove 必填) + --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER (add/update 必填) + --workspace string 知识库 ID (选填) --limit int 返回成员数上限 (仅 list,默认 30,最大 200) - --filter-role string 按角色过滤 (仅 list) + --filter-role string 按角色过滤: OWNER / MANAGER / EDITOR / DOWNLOADER / READER (仅 list) ``` +### 文件互联网公开发布 + +管理文件的互联网公开发布状态。公开后任何人通过链接即可访问,无需登录钉钉。操作者需要是文件的管理员或拥有者。 + +> **`publish set` 和 `publish unset` 为 [危险] 操作,执行前需要向用户确认。确认后传入 `--yes` 跳过交互式确认。** + +``` +Usage: + dws drive publish set --node [--permission READER|DOWNLOADER|EDITOR] + dws drive publish unset --node + dws drive publish get --node +Example: + dws drive publish set --node + dws drive publish set --node --permission READER + dws drive publish get --node + dws drive publish unset --node +Flags: + --node string 目标文件 ID (dentryUuid) 或 URL (必填) + --permission string 公开后的权限: READER(仅可查看) / DOWNLOADER(可查看和下载,默认) / EDITOR(可编辑),仅 set 有效 +``` + +子命令说明: +- `publish set` — [危险] 设置文件为互联网公开,可选指定公开权限 +- `publish unset` — [危险] 关闭文件互联网公开 +- `publish get` — 查询文件当前的公开发布状态 + +返回字段说明: +- `published` — true=已公开,false=未公开 +- `publishPermission` — 当前公开权限(READER/DOWNLOADER/EDITOR) +- `pendingApproval` — true=已提交审批待生效,false/null=无需审批或已直接生效 +- `docUrl` — 文件访问链接 + > **注意**:`drive export` 不存在。导出仅对自研文档 (adoc) 有意义,属于内容层操作,应使用 `doc export`。 ### 目标位置参数规则 | 目标位置 | 参数传递方式 | 前置步骤 | |---------|-----------|---------| -| 未指定目标(默认) | `--folder ` | 先 `dws drive list-spaces --space-type mySpace` 获取「我的文件」的 `rootFolderId` | -| 知识库空间根目录 | `--workspace ` | 无需额外步骤 | -| 钉盘 space 根目录 | `--folder ` | 先 `dws drive list-spaces` 获取目标 space 的 `rootFolderId` | -| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览 | +| 未指定目标(默认) | `--folder ` | 先 `dws wiki space list --type mySpace` 获取「我的文件」的 `rootFolderId` | +| 知识库空间根目录 | `--workspace ` | 无需额外步骤,直接传入 workspaceId | +| 钉盘 space 根目录 | `--folder ` | 先 `dws wiki space list --type orgSpace` 获取目标 space 的 `rootFolderId` | +| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览,获取目标文件夹的 `fileId`(dentryUuid 格式) | ### 工作流示例 ```bash -# ── 场景 默认: 复制/移动到「我的文件」根目录 ── -dws drive list --space-id --format json -dws drive list-spaces --space-type mySpace --format json +# ── 场景默认: 用户未指定目标位置 → 复制/移动到「我的文件」根目录 ── +dws drive list --space-id --format json # 获取源文件 dentryUuid +dws wiki space list --type mySpace --format json # 获取「我的文件」rootFolderId dws drive copy --node <源文件dentryUuid> --folder <我的文件rootFolderId> --format json -# ── 场景 A: 复制到知识库空间根目录 ── +# ── 场景 A: 复制钉盘文件到知识库空间根目录 ── dws drive copy --node <源文件dentryUuid> --workspace --format json -# ── 场景 B: 移动到另一个钉盘 space 根目录 ── -dws drive list-spaces --format json +# ── 场景 B: 移动钉盘文件到另一个钉盘 space 根目录 ── +dws wiki space list --type orgSpace --format json dws drive move --node <源文件dentryUuid> --folder <目标space的rootFolderId> --format json +# 注意:移动到其他 space 时只传 --folder,不传 --workspace -# ── 场景 C: 复制到钉盘子文件夹 ── +# ── 场景 C: 复制钉盘文件到钉盘 space 下的子文件夹 ── dws drive list --space-id --format json dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --format json ``` ## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -| ------------- | ---------------------------- | -------------------------------------------------------- | -| `list` | **`fileId`**(UUID 格式,注意:不是 `dentryId`) | info / download / mkdir / delete / list 的 --node 或 --folder;`drive copy/move` 的 --node 或 --folder | -| `list` | `spaceId` | info / download / mkdir / commit 的 --space-id | -| `list-spaces` | `rootFolderId` | `drive copy/move` 的 --folder(复制/移动到钉盘 space 根目录时) | -| `list-spaces` | `spaceId` | list / info / download / mkdir / upload 的 --space-id | -| `search` | **`fileId`**(文件/文件夹结果) | info / download / delete 的 --node;list 的 --folder | -| `search` | `spaceId` / `rootFolderId`(空间结果) | list 的 --space-id;`drive copy/move` 的 --folder | -| `search` | `nextCursor` | search 的 --cursor(翻页) | -| `mkdir` | `fileId`(UUID 格式) | list 的 --folder | +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `list` | **`fileId`**(UUID 格式,注意:不是 `dentryId`) | info / download / delete 的 --node;list / mkdir 的 --folder;`drive copy/move` 的 --node 或 --folder | +| `list` | `spaceId` | info / download / mkdir / upload 的 --space-id | +| `list` | `nextCursor` | 下次 list 的 --cursor | +| `list-spaces` / `wiki space list` | `rootFolderId` | `drive copy/move` 的 --folder(复制/移动到钉盘 space 根目录时) | +| `list-spaces` / `wiki space list` | `spaceId` | list / info / download / mkdir / upload 的 --space-id | +| `search` | **`fileId`**(文件/文件夹结果) | info / download / delete 的 --node;list 的 --folder | +| `search` | `spaceId` / `rootFolderId`(空间结果) | list 的 --space-id;`drive copy/move` 的 --folder | +| `search` | `nextCursor` | search 的 --cursor(翻页) | +| `mkdir` | `fileId`(UUID 格式) | list / upload 的 --folder | +| `upload` | `dentryUuid` | download / info 的 --node | +| `recycle list` | `id`(回收项 ID) | recycle restore 的 --id | +| `recycle list` | `name`(原始文件名) | 供用户确认还原目标 | +| `recent` | `recentItems[].nodeId` / `docUrl` | doc read / info / update / block 操作的 --node | +| `recent` | `nextCursor` | recent 的 --cursor(翻页) | > **重要**:`drive list` 返回结果中同时包含 `dentryId` 和 `fileId` 两个字段。所有需要传 `--node` 的命令(info / download / delete)必须使用 `fileId`(即 dentryUuid),**不要使用** `dentryId`。 - ## 注意事项 - 不传 `--space-id` 时默认使用「我的文件」空间 - 不传 `--folder` 时默认操作空间根目录 -- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录;`dentryId` 只用于 `chat message send --dentry-id` -- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询,不要直接传大于 50 的值 +- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录 +- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询 - `--order-by` 支持: `createTime`、`modifyTime`、`name` -- **上传文件必须使用 `dws drive upload` 命令**,禁止使用 `upload-info` + `curl` + `commit` 三步手动流程 +- **上传文件首选 `dws drive upload` 命令**;手动三步(`upload-info` → HTTP PUT → `commit`)仅用于自定义流式上传等特殊场景。手动三步时 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;PUT 返回 200 后才能调 `commit`;`uploadId` 有过期时间,过期需重新 `upload-info`;`--folder` 在 upload-info / commit 中要保持一致 - `--file-name` 必须包含扩展名(如 `report.pdf`) +- `download` 需要指定 `--output`,CLI 会把文件保存到本地路径或目录 +- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾 ## 自动化脚本 - -| 脚本 | 场景 | 用法 | -| ------------------------------------------------------ | ----------- | ------------------------------------- | +| 脚本 | 场景 | 用法 | +|------|------|------| | [drive_tree_list.py](../../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | - ## 相关产品 -- [doc](./doc.md) — 文档内容读写/知识库空间,不是文件存储 -- [chat](./chat.md) — 上传文件到 drive 后可通过 Markdown 语法发送图片/文件消息 +- [doc](./doc.md) — 文档内容读写(Markdown/块级编辑/导出),不是文件存储 +- [wiki](./wiki.md) — 知识库/空间管理层(空间列表、节点创建、空间内搜索、成员管理) +- [chat](./chat.md) — 发送图片/文件消息用 `chat message send --msg-type file --file-path` diff --git a/skills/mono/references/products/mail.md b/skills/mono/references/products/mail.md index 4abb021e..c0d84259 100644 --- a/skills/mono/references/products/mail.md +++ b/skills/mono/references/products/mail.md @@ -5,6 +5,7 @@ | 命令 | 功能简述 | |------|----------| | `dws mail mailbox list` | 查询**当前用户自己**的可用邮箱列表 | +| `dws mail message list` | 列出指定文件夹中的邮件(默认收件箱) | | `dws mail message search` | 搜索邮件(KQL 语法,按主题/发件人/日期等) | | `dws mail message get` | 查看邮件完整内容(含正文) | | `dws mail message send` | 发送邮件(支持附件/内联图片) | @@ -13,15 +14,43 @@ | `dws mail message forward` | 转发邮件(支持附件/内联图片) | | `dws mail message batch-move` | 批量移动邮件到指定文件夹 | | `dws mail message batch-delete` | 批量删除邮件 | +| `dws mail message batch-update` | 批量修改邮件状态(标记已读/未读/添加标签/移除标签) | +| `dws mail message verify` | 根据 internetMessageId 查询邮件发送状态 | | `dws mail draft create` | 创建草稿(保留在草稿箱,不发送) | | `dws mail draft update` | 更新草稿内容(保留在草稿箱,不发送) | | `dws mail draft send` | 发送草稿箱中已有的草稿 | | `dws mail folder list` | 列举邮件文件夹 | +| `dws mail folder create` | 创建邮件文件夹 | +| `dws mail folder delete` | 删除邮件文件夹 | +| `dws mail folder update` | 更新邮件文件夹名称 | | `dws mail attachment list` | 列举指定邮件的所有附件 | | `dws mail attachment download` | 下载邮件附件到本地(**仅支持逐个下载,不支持批量下载**) | | `dws mail tag list` | 列举邮件标签 | +| `dws mail tag create` | 创建邮件标签 | +| `dws mail tag delete` | 删除邮件标签 | +| `dws mail tag update` | 更新邮件标签名称 | +| `dws mail thread list` | 列出指定邮箱、指定文件夹下的邮件会话 | | `dws mail thread get` | 获取会话详情 | +| `dws mail thread update` | 修改单个邮件会话的状态或标签(标记已读/未读/添加标签/移除标签) | +| `dws mail thread batch-update` | 批量修改邮件会话的状态或标签(单次最多 100 个) | +| `dws mail thread trash` | 将单个邮件会话移动到已删除文件夹(不会永久删除) | +| `dws mail thread batch-trash` | 将多个邮件会话批量移动到已删除文件夹(单次最多 100 个,不会永久删除) | | `dws mail user search` | 搜索通讯录用户(**按姓名查他人邮箱**,不是搜邮件) | +| `dws mail template create` | 创建邮件模板 | +| `dws mail template list` | 列举邮件模板 | +| `dws mail template get` | 获取邮件模板详情 | +| `dws mail template update` | 更新邮件模板 | +| `dws mail template delete` | 删除邮件模板 | +| `dws mail contact create` | 创建个人邮件联系人(添加到自己的联系人列表) | +| `dws mail contact list` | 列举个人邮件联系人(查看自己保存的联系人,**不是搜索通讯录用户**) | +| `dws mail contact update` | 更新个人邮件联系人信息 | +| `dws mail contact batch-delete` | 批量删除个人邮件联系人 | +| `dws mail auto-reply get` | 获取用户的自动回复配置 | +| `dws mail rule list` | 列出个人收信规则 | +| `dws mail rule create` | 创建个人收信规则 | +| `dws mail rule update` | 更新个人收信规则 | +| `dws mail rule delete` | 删除个人收信规则 | +| `dws mail rule adjust` | 调整收信规则排序 | > **查找他人邮箱**(如「获取严龙的邮箱」)→ **不要用 `mailbox list`**,应走三路并发查询,详见「查找他人邮箱地址」章节。 @@ -91,18 +120,43 @@ dws contact user search --keyword "姓名" --format json 若三路均无有效邮箱,必须 `ask_human` 请用户手动提供,**严禁臆测**。 +### 列出文件夹中的邮件 +> **注意:** `message list` 用于按文件夹列出邮件;若需根据主题/发件人/日期等条件精确搜索,请使用 `message search`。 +``` +Usage: + dws mail message list [flags] +Example: + dws mail message list --email user@company.com + dws mail message list --email user@company.com --folder-id 1 + dws mail message list --email user@company.com --folder-id 2 --limit 50 + dws mail message list --email user@company.com --cursor +Flags: + --email string 邮件所属邮箱地址 (必填) + --folder-id string 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除),默认为收件箱 + --limit string 每页返回数量(最大限制 100, 默认 20) + --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) | +| `total` | `int32` | 符合条件的总邮件数 | +| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 | + ### 搜索邮件 (KQL 语法) ``` Usage: dws mail message search [flags] Example: - dws mail message search --email user@company.com --query "subject:\"周报\"" --limit 20 - dws mail message search --email user@company.com --query "from:alice AND date>2025-06-01T00:00:00Z" --limit 10 + dws mail message search --email user@company.com --query "subject:\"周报\"" --size 20 + dws mail message search --email user@company.com --query "from:alice AND date>2025-06-01T00:00:00Z" --size 10 Flags: --cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始 --email string 搜索目标邮箱地址 (必填) --query string KQL 查询表达式 (必填), 其中 date 格式需遵循 ISO8601 规范 - --limit string 每页返回数量(最大限制 100, 默认 20) + --size string 每页返回数量(最大限制 100, 默认 20),别名: --limit, --page-size ``` KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, attachname, body, from, to @@ -140,9 +194,9 @@ KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, at **翻页示例:** ```bash # 第一页 -dws mail message search --email user@company.com --query "folderId:2" --limit 20 --format json +dws mail message search --email user@company.com --query "folderId:2" --size 20 --format json # 取返回中的 nextCursor,传入下一次请求(nextCursor="$" 时停止) -dws mail message search --email user@company.com --query "folderId:2" --limit 20 --cursor --format json +dws mail message search --email user@company.com --query "folderId:2" --size 20 --cursor --format json ``` ### 查看邮件完整内容 @@ -168,17 +222,17 @@ Usage: dws mail message send [flags] Example: dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "本周完成任务A和任务B" + --subject "周报" --body "本周完成任务A和任务B" dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./report.pdf + --subject "周报" --body "见附件" --attachment ./report.pdf dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./a.pdf --attachment ./b.xlsx + --subject "周报" --body "见附件" --attachment ./a.pdf --attachment ./b.xlsx dws mail message send --from user@company.com --to colleague@company.com \ - --subject "图表周报" --content "图表如下:[inline:chart.png]" --inline-attachment ./chart.png + --subject "图表周报" --body "图表如下:[inline:chart.png]" --inline-attachment ./chart.png dws mail message send --from user@company.com --to colleague@company.com \ - --subject "带图文档" --content "见附件,图表:[inline:img.png]" --attachment ./doc.pdf --inline-attachment ./img.png + --subject "带图文档" --body "见附件,图表:[inline:img.png]" --attachment ./doc.pdf --inline-attachment ./img.png Flags: - --content string 邮件正文 (必填) + --body string 邮件正文 (必填) --cc string 抄送人列表 --from string 发件人邮箱 (必填),别名: --sender --subject string 邮件标题 (必填) @@ -202,7 +256,7 @@ Flags: - 仅支持图片类型:`jpg` / `jpeg` / `png` / `gif` / `webp` / `bmp` / `svg` - CLI 自动生成 contentId,格式:`inline-{文件名(不含扩展名)}-{序号}@alimail.com`,例:`inline-chart-1@alimail.com` -- 在 `--content` 中使用占位符 `[inline:文件名]` 引用图片,CLI 自动替换为 `` 标签 +- 在 `--body` 中使用占位符 `[inline:文件名]` 引用图片,CLI 自动替换为 `` 标签 - 若 body 中没有对应占位符,内联图片会自动追加到正文末尾 - 非图片类型(PDF、视频、音频等)请改用 `--attachment` @@ -231,6 +285,100 @@ Flags: | `totalItemCount` | `int` | 邮件总数 | | `unreadItemCount` | `int` | 未读邮件数量 | +### 创建邮件文件夹 +``` +Usage: + dws mail folder create [flags] +Example: + dws mail folder create --email user@company.com --name "项目资料" + dws mail folder create --email user@company.com --name "子文件夹" --folder +Flags: + --email string 邮件所属邮箱地址 (必填) + --name string 新建邮件文件夹名称 (必填) + --folder string 父文件夹 ID,不传则创建顶层文件夹 (可选) +``` + +不传 `--folder` 创建顶层文件夹;传入 `--folder` 时创建指定父文件夹下的子文件夹。 + +> **重要:** `--folder` 必须填写父文件夹 ID,不是文件夹名称。父文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "folder": { + "id": "104", + "displayName": "项目资料", + "parentFolderId": "0", + "childFolderCount": 0, + "totalItemCount": 0, + "unreadItemCount": 0, + "extensions": {} + } + } +} +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | `boolean` | 是否创建成功 | +| `result.folder.id` | `string` | 文件夹唯一标识 | +| `result.folder.displayName` | `string` | 文件夹显示名称 | +| `result.folder.parentFolderId` | `string` | 父文件夹 ID | +| `result.folder.childFolderCount` | `number` | 子文件夹数量 | +| `result.folder.totalItemCount` | `number` | 邮件总数 | +| `result.folder.unreadItemCount` | `number` | 未读邮件数量 | +| `result.folder.extensions` | `object` | 文件夹扩展信息 | + +### 删除邮件文件夹 +``` +Usage: + dws mail folder delete [flags] +Example: + dws mail folder delete --email user@company.com --id +Flags: + --email string 邮件所属邮箱地址 (必填) + --id string 要删除的邮件文件夹 ID (必填) +``` + +`--id` 必须填写要删除的文件夹 ID,不是文件夹名称。文件夹 ID 来自 `dws mail folder list --email <邮箱>` 返回的 `folders[].id`,或来自 `folder create` 返回的 `result.folder.id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + +### 更新邮件文件夹 +``` +Usage: + dws mail folder update [flags] +Example: + dws mail folder update --email user@company.com --id --name "新文件夹名" +Flags: + --email string 邮件所属邮箱地址 (必填) + --id string 要更新的邮件文件夹 ID (必填) + --name string 更新后的邮件文件夹名称 (必填) +``` + +`--id` 必须填写要更新的文件夹 ID,不是文件夹名称;`--name` 是更新后的文件夹名称。若用户只给出原文件夹名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 update。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + ### 列举邮件附件 > **重要:** 不存在 `attachment download_batch` / `download_all` 等批量下载命令。如需下载多封邮件的所有附件,必须按以下流程逐个下载:1) `message search` 搜索邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。 @@ -307,6 +455,154 @@ Flags: | `totalItemCount` | `int` | 标签下邮件总数 | | `unreadItemCount` | `int` | 标签下未读邮件数量 | +### 创建邮件标签 +``` +Usage: + dws mail tag create [flags] +Example: + dws mail tag create --email user@company.com --name "项目资料" + dws mail tag create --email user@company.com --name "子标签" --parent-id +Flags: + --email string 用户的邮箱地址 (必填) + --name string 新建邮件标签名称 (必填) + --parent-id string 父标签 ID,不传则创建顶层标签 (可选) +``` + +不传 `--parent-id` 创建顶层标签;传入 `--parent-id` 时创建指定父标签下的子标签。 + +> **重要:** `--parent-id` 必须填写父标签 ID,不是标签名称。父标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "tag": { + "id": "tag-001", + "name": "项目资料", + "parentId": "0", + "totalItemCount": 0, + "unreadItemCount": 0, + "extensions": {} + } + } +} +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | `boolean` | 是否创建成功 | +| `result.tag.id` | `string` | 标签唯一标识 | +| `result.tag.name` | `string` | 标签显示名称 | +| `result.tag.parentId` | `string` | 父标签 ID | +| `result.tag.totalItemCount` | `number` | 标签下邮件总数 | +| `result.tag.unreadItemCount` | `number` | 标签下未读邮件数量 | +| `result.tag.extensions` | `object` | 标签扩展信息 | + +### 删除邮件标签 +``` +Usage: + dws mail tag delete [flags] +Example: + dws mail tag delete --email user@company.com --id +Flags: + --email string 用户的邮箱地址 (必填) + --id string 要删除的邮件标签 ID (必填) +``` + +`--id` 必须填写要删除的标签 ID,不是标签名称。标签 ID 来自 `dws mail tag list --email <邮箱>` 返回的 `tags[].id`,或来自 `tag create` 返回的 `result.tag.id`。 + +只能删除用户自定义标签,系统标签不能删除。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + +### 更新邮件标签 +``` +Usage: + dws mail tag update [flags] +Example: + dws mail tag update --email user@company.com --id --name "新标签名" +Flags: + --email string 用户的邮箱地址 (必填) + --id string 要更新的邮件标签 ID (必填) + --name string 更新后的邮件标签名称 (必填) +``` + +`--id` 必须填写要更新的标签 ID,不是标签名称;`--name` 是更新后的标签名称。若用户只给出原标签名称,必须先调用 `tag list` 找到对应 `tags[].id`,再执行 update。 + +只能更新用户自定义标签,系统标签不能更新。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": {} +} +``` + +### 列出邮件会话 + +``` +Usage: + dws mail thread list [flags] +Example: + dws mail thread list --email user@company.com --folder --limit 10 + dws mail thread list --email user@company.com --folder 104 --limit 20 --cursor +Flags: + --email string 会话所属邮箱地址 (必填) + --folder string 邮件文件夹 ID,不是文件夹名称 (必填) + --limit int 本次列出的会话数,最大 100 (必填) + --cursor string 分页游标,首次请求可不传 (可选) + --start string 开始 UTC 时间字符串,如 2024-01-01T00:00:00Z (可选) + --end string 结束 UTC 时间字符串,如 2024-12-31T23:59:59Z (可选) + --ascending 是否按时间升序;不传由服务端默认排序 (可选) +``` + +`--folder` 必须填写文件夹 ID,不是文件夹名称。若用户只给出“收件箱/已删除/某个自定义文件夹”这类名称,必须先调用 `folder list` 找到对应 `folders[].id`,再执行 `thread list`。 + +**返回 JSON:** + +```json +{ + "success": true, + "result": { + "conversations": [ + { + "id": "conversationId", + "subject": "会话主题", + "summary": "会话摘要", + "lastModifiedDateTime": "2024-02-06T01:05:07Z", + "messageCount": 1, + "tags": [], + "senders": [ + { + "email": "sender@example.com", + "name": "发件人" + } + ], + "isRead": true, + "priority": "PRY_NORMAL", + "flag": "FLAG_NONE", + "hasAttachments": false + } + ], + "nextCursor": "", + "hasMore": false + } +} +``` + ### 获取会话详情 ``` Usage: @@ -333,19 +629,133 @@ Flags: | `priority` | `string` | 会话重要性,取会话内邮件最高优先级(`PRY_HIGH` / `PRY_NORMAL`) | | `flag` | `string` | 会话标识,取会话内最近邮件的标识(`FLAG_NONE` / `FLAG_REPLY` / `FLAG_FORWARD`) | | `hasAttachments` | `boolean` | 会话是否包含附件(不含 inline 资源) | + +### 修改邮件会话状态 + +``` +Usage: + dws mail thread update [flags] +Example: + dws mail thread update --email user@company.com --id --action markRead + dws mail thread update --email user@company.com --id --action addTags --tag-ids 1,2 +Flags: + --email string 会话所属邮箱地址 (必填) + --id string 会话唯一标识 conversationId (必填) + --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) + --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) +``` + +`--id` 必须填写会话 ID,不是邮件 ID。会话 ID 可通过 `thread list` 的 `conversations[].id` 获取。 + +支持的 `--action`: + +| action | 说明 | 是否需要 `--tag-ids` | +|--------|------|----------------------| +| `markRead` | 标记会话为已读 | 否 | +| `markUnread` | 标记会话为未读 | 否 | +| `addTags` | 给会话增加标签 | 是 | +| `removeTags` | 从会话移除标签 | 是 | + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### 批量修改邮件会话状态 + +``` +Usage: + dws mail thread batch-update [flags] +Example: + dws mail thread batch-update --email user@company.com --ids , --action markUnread + dws mail thread batch-update --email user@company.com --ids , --action removeTags --tag-ids 11 +Flags: + --email string 会话所属邮箱地址 (必填) + --ids string 会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) + --action string 操作类型:markRead、markUnread、addTags、removeTags (必填) + --tag-ids string 标签 ID 列表,多个用英文逗号分隔;addTags/removeTags 时必填 (可选) +``` + +`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表,最多 100 个。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### [危险] 删除邮件会话 + +``` +Usage: + dws mail thread trash [flags] +Example: + dws mail thread trash --email user@company.com --id --yes +Flags: + --email string 会话所属邮箱地址 (必填) + --id string 要删除的会话 ID (必填) + --yes 确认执行此危险操作 (必填) +``` + +> ⚠️ **危险操作**:此命令会将邮件会话移动到已删除文件夹。建议先通过 `thread get` 确认目标会话后再执行。 + +将指定邮件会话移动到已删除文件夹,不会永久删除邮件。`--id` 必须填写会话 ID,不是邮件 ID。执行前需向用户确认,确认后传入 `--yes` 执行。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + +### [危险] 批量删除邮件会话 + +``` +Usage: + dws mail thread batch-trash [flags] +Example: + dws mail thread batch-trash --email user@company.com --ids , --yes +Flags: + --email string 会话所属邮箱地址 (必填) + --ids string 要删除的会话 ID 列表,多个用英文逗号分隔,最多 100 个 (必填) + --yes 确认执行此危险操作 (必填) +``` + +> ⚠️ **危险操作**:此命令会批量将邮件会话移动到已删除文件夹。建议先通过 `thread list` 确认目标会话后再执行。 + +将指定邮件会话批量移动到已删除文件夹,单次最多 100 个会话。不会永久删除邮件。`--ids` 必须填写会话 ID 列表,不是邮件 ID 列表。执行前需向用户确认,确认后传入 `--yes` 执行。 + +成功时返回: + +```json +{ + "success": true, + "result": {} +} +``` + ### 回复邮件 ``` Usage: dws mail message reply [flags] Example: dws mail message reply --from user@company.com --id - dws mail message reply --from user@company.com --id --subject "Re: 周报" --content "已收到,谢谢!" + dws mail message reply --from user@company.com --id --subject "Re: 周报" --body "已收到,谢谢!" Flags: --from string 发件人邮箱 (必填),别名: --sender --to string 收件人列表(可选) --id string 要回复的邮件 ID (必填) --subject string 回复邮件标题(可选) - --content string 回复正文(可选) + --body string 回复正文(可选) --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -371,13 +781,13 @@ Usage: dws mail message reply-all [flags] Example: dws mail message reply-all --from user@company.com --id - dws mail message reply-all --from user@company.com --id --subject "Re: 周报" --content "感谢大家的参与!" + dws mail message reply-all --from user@company.com --id --subject "Re: 周报" --body "感谢大家的参与!" Flags: --from string 发件人邮箱 (必填),别名: --sender --to string 收件人列表(可选,包含发件人及所有原始收件人) --id string 要回复的邮件 ID (必填) --subject string 回复邮件标题(可选) - --content string 回复正文(可选) + --body string 回复正文(可选) --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -409,7 +819,7 @@ Flags: --to string 转发收件人列表(可选) --id string 要转发的邮件 ID (必填) --subject string 转发邮件标题(可选) - --content string 转发附言(可选) + --body string 转发附言(可选) --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` @@ -455,33 +865,103 @@ Flags: --ids string 要删除的邮件 ID 列表,逗号分隔 (必填) ``` +### 批量修改邮件状态 + +批量修改邮件的已读状态或标签,通过 `--action` 指定操作类型。 + +**支持的操作类型(--action):** + +| action | 说明 | 需要的额外参数 | +|--------|------|---------------| +| `markRead` | 标记邮件为已读 | 无 | +| `markUnread` | 标记邮件为未读 | 无 | +| `addTags` | 给邮件增加标签 | `--tags`(标签 ID 列表,必填) | +| `removeTags` | 从邮件移除标签 | `--tags`(标签 ID 列表,必填) | + +**常用标签 ID:** + +| 标签 ID | 名称 | 图标 | +|---------|------|------| +| `1` | 跟进事项 | 小红旗 | +| `2` | 完成事项 | 绿色小勾 | +| `11` | 重要 | 星标 | + +``` +Usage: + dws mail message batch-update [flags] +Example: + dws mail message batch-update --email user@company.com --ids , --action markRead + dws mail message batch-update --email user@company.com --ids , --action addTags --tags 1,2 + dws mail message batch-update --email user@company.com --ids , --action removeTags --tags 11 +Flags: + --email string 邮件所属邮箱地址 (必填) + --ids string 要修改的邮件 ID 列表,逗号分隔 (必填) + --action string 操作类型: markRead/markUnread/addTags/removeTags (必填) + --tags string 标签 ID 列表,逗号分隔 (action 为 addTags/removeTags 时必填) +``` + +> **查询标签 ID:** 使用 `dws mail tag list --email <邮箱>` 可查看所有可用标签及其 ID。 + +### 查询邮件发送状态 +``` +Usage: + dws mail message verify [flags] +Example: + dws mail message verify --email user@company.com --internet-message-id +Flags: + --email string 邮件所属邮箱地址 (必填) + --internet-message-id string 邮件的 internetMessageId (必填),取自发送类命令返回值 +``` + +根据 `internetMessageId` 查询某封邮件当前的发送投递状态。 + +> **internetMessageId 来源:** `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` 等发送类命令的返回值中均会带 `internetMessageId` 字段,可直接传入此命令查询发送结果。 + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `message` | `object` | 邮件完整信息 | +| `sendStatus` | `string` | 发送状态,取值见下表 | + +**`sendStatus` 取值说明:** + +| 值 | 含义 | +|------|------| +| `none` | 未发送 | +| `posting` | 投递中 | +| `partial_success` | 部分成功(部分收件人投递成功) | +| `success` | 发送成功 | +| `failed` | 发送失败 | +| `unknown` | 未知状态 | + ### 创建草稿 ``` Usage: dws mail draft create [flags] Example: dws mail draft create --from user@company.com --to colleague@company.com \ - --subject "草稿标题" --content "草稿正文" + --subject "草稿标题" --body "草稿正文" dws mail draft create --from user@company.com --subject "草稿标题" dws mail draft create --from user@company.com --subject "带附件草稿" \ - --content "见附件" --attachment ./report.pdf + --body "见附件" --attachment ./report.pdf dws mail draft create --from user@company.com --subject "带图片草稿" \ - --content "图表:[inline:chart.png]" --inline-attachment ./chart.png + --body "图表:[inline:chart.png]" --inline-attachment ./chart.png Flags: --from string 发件人邮箱 (必填),别名: --sender --subject string 邮件标题 (必填) --to string 收件人列表(可选,有确定收件人时才传) --cc string 抄送人列表(可选,有确定抄送人时才传) - --content string 邮件正文(可选,有正文内容时才传) + --body string 邮件正文(可选,有正文内容时才传) --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` -> **注意:** `--to`、`--cc`、`--content` 均为可选参数,**仅在用户明确提供对应信息时才传入**。若用户未指定收件人,不要传 `--to ""`(空字符串)。 +> **注意:** `--to`、`--cc`、`--body` 均为可选参数,**仅在用户明确提供对应信息时才传入**。若用户未指定收件人,不要传 `--to ""`(空字符串)。 **附件说明:** -指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿创建和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 +指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿创建和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--body` 中使用 `[inline:文件名]` 占位符)。 **返回字段:** @@ -494,24 +974,24 @@ Flags: Usage: dws mail draft update [flags] Example: - dws mail draft update --from user@company.com --id --subject "新标题" --content "新正文" - dws mail draft update --from user@company.com --id --content "见附件" --attachment ./report.pdf + dws mail draft update --from user@company.com --id --subject "新标题" --body "新正文" + dws mail draft update --from user@company.com --id --body "见附件" --attachment ./report.pdf dws mail draft update --from user@company.com --id \ - --content "图表:[inline:chart.png]" --inline-attachment ./chart.png + --body "图表:[inline:chart.png]" --inline-attachment ./chart.png Flags: --from string 发件人邮箱 (必填),别名: --sender --id string 草稿邮件 ID (必填) --to string 收件人列表(可选) --cc string 抄送人列表(可选) --subject string 邮件标题(可选) - --content string 邮件正文(可选) + --body string 邮件正文(可选) --attachment stringArray 附件文件路径,可多次指定 (可选) --inline-attachment stringArray 内联图片路径,可多次指定,cid 自动生成 (可选) ``` **附件说明:** -指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿更新和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--content` 中使用 `[inline:文件名]` 占位符)。 +指定 `--attachment` 或 `--inline-attachment` 时,CLI 自动完成草稿更新和附件上传,**草稿保留在草稿箱,不会发送**。内联图片用法同 `message send`(`--body` 中使用 `[inline:文件名]` 占位符)。 ### 发送草稿 ``` @@ -533,13 +1013,13 @@ Usage: Example: dws mail user search --keyword "张三" dws mail user search --email user@company.com --keyword "张三" - dws mail user search --email user@company.com --keyword "alice" --limit 10 + dws mail user search --email user@company.com --keyword "alice" --size 10 dws mail user search --email user@company.com --keyword "alice" --cursor Flags: --email string 搜索目标邮箱地址 (可选) --keyword string 搜索关键词 (必填) --cursor string 分页游标,取自响应中的 nextCursor 字段(可选) - --limit string 每页返回数量(可选) + --size string 每页返回数量(可选) ``` > **重要区别:** @@ -570,6 +1050,409 @@ Flags: | `jobTitle` | `string` | 职位 | | `workLocation` | `string` | 工作地 | +### 创建邮件模板 +``` +Usage: + dws mail template create [flags] +Example: + dws mail template create --email user@company.com --name "周报模板" --subject "周报" --content "本周工作总结..." + dws mail template create --email user@company.com --from user@company.com --name "通知模板" --subject "通知" --content "..." --to a@x.com,b@x.com --cc c@x.com +Flags: + --email string 用户邮箱地址 (必填) + --from string 模板发件人邮箱 (可选) + --subject string 模板邮件标题 (必填) + --content string 模板邮件正文 (必填) + --name string 模板名称 (必填) + --to string 模板收件人列表,逗号分隔 (可选) + --cc string 模板抄送人列表,逗号分隔 (可选) + --is-draft 是否为草稿模板 (可选,默认 false) +``` + +> **草稿模板说明:** 传入 `--is-draft` 创建的模板为草稿模板,草稿模板支持后续通过 `template update` 修改内容。**非草稿模板创建后不可修改**(`template update` 仅对草稿模板有效)。 + +### 列举邮件模板 +``` +Usage: + dws mail template list [flags] +Example: + dws mail template list --email user@company.com --limit 20 + dws mail template list --email user@company.com --limit 20 --cursor +Flags: + --email string 用户邮箱地址 (必填) + --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) + --limit string 每页返回数量 (必填) +``` + +### 获取邮件模板详情 +``` +Usage: + dws mail template get [flags] +Example: + dws mail template get --email user@company.com --id +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填) +``` + +### 更新邮件模板 + +> ⚠️ 服务端限制:仅草稿模板(创建时带 `--is-draft`)可更新;非草稿模板更新会返回 1001 Invalid parameter。需要修改非草稿模板时,建议删除后用 `--is-draft` 重建。 + +> **重要限制:** `template update` **仅对草稿模板有效**。只有通过 `template create --is-draft` 创建的草稿模板才支持更新,非草稿模板调用 update 会返回 `Invalid parameter` 错误。 + +``` +Usage: + dws mail template update [flags] +Example: + dws mail template update --email user@company.com --id --subject "新标题" --content "新正文" + dws mail template update --email user@company.com --id --name "新模板名" +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填,必须是草稿模板的 ID) + --from string 模板发件人邮箱 (可选) + --subject string 模板邮件标题 (可选) + --content string 模板邮件正文 (可选) + --name string 模板名称 (可选) + --to string 模板收件人列表,逗号分隔 (可选) + --cc string 模板抄送人列表,逗号分隔 (可选) +``` + +### 删除邮件模板 +``` +Usage: + dws mail template delete [flags] +Example: + dws mail template delete --email user@company.com --id +Flags: + --email string 用户邮箱地址 (必填) + --id string 模板唯一标识 (必填) +``` + +> **特殊字符注意:** `--contact-id` 等 ID 参数的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-id '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 + +### 创建邮件联系人 +``` +Usage: + dws mail contact create [flags] +Example: + dws mail contact create --email user@company.com --contact-email colleague@company.com --display-name "张三" + dws mail contact create --email user@company.com --contact-email colleague@company.com --first-name "三" --last-name "张" +Flags: + --email string 用户邮箱地址 (必填) + --contact-email string 联系人邮箱地址 (必填) + --first-name string 联系人名 (可选) + --middle-name string 联系人中间名 (可选) + --last-name string 联系人姓 (可选) + --display-name string 联系人显示名称 (可选) +``` + +### 列举邮件联系人 +``` +Usage: + dws mail contact list [flags] +Example: + dws mail contact list --email user@company.com --limit 20 + dws mail contact list --email user@company.com --limit 20 --cursor +Flags: + --email string 用户邮箱地址 (必填) + --cursor string 分页游标,取自响应中的 nextCursor 字段 (可选) + --limit string 每页返回数量 (必填) +``` + +> **重要区别:** `contact list` 列举的是**个人联系人**(用户自己保存/创建的联系人列表),不需要关键词;搜索**企业通讯录用户**(按姓名找人查邮箱)请用 `user search`。 + +### 更新邮件联系人 +``` +Usage: + dws mail contact update [flags] +Example: + dws mail contact update --email user@company.com --contact-id --display-name "李四" + dws mail contact update --email user@company.com --contact-id --contact-email new@company.com --first-name "四" --last-name "李" +Flags: + --email string 用户邮箱地址 (必填) + --contact-id string 联系人唯一标识 (必填) + --contact-email string 联系人邮箱地址 (可选) + --first-name string 联系人名 (可选) + --middle-name string 联系人中间名 (可选) + --last-name string 联系人姓 (可选) + --display-name string 联系人显示名称 (可选) +``` + +### 批量删除邮件联系人 + +> **特殊字符注意:** `--contact-ids` 的值可能包含 `$`、`!` 等 shell 特殊字符。在终端手动执行时,**必须用单引号**包裹这类参数值(如 `--contact-ids '101_0:DzzzzyJqO10$---.hp5uBuR'`),双引号会导致 `$` 被 shell 变量展开,使 ID 值被篡改从而报错。通过 MCP 协议(JSON 传参)调用时无此问题。 + +``` +Usage: + dws mail contact batch-delete [flags] +Example: + dws mail contact batch-delete --email user@company.com --contact-ids , +Flags: + --email string 用户邮箱地址 (必填) + --contact-ids string 要删除的联系人 ID 列表,逗号分隔 (必填) +``` + +### 获取自动回复配置 + +获取当前用户的邮件自动回复配置,包括是否启用、生效时间、回复范围和回复内容。 + +``` +Usage: + dws mail auto-reply get [flags] +Example: + dws mail auto-reply get --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `enabled` | bool | 是否启用自动回复 (true=启用, false=禁用) | +| `startTime` | string | 自动回复开始时间 | +| `endTime` | string | 自动回复结束时间 | +| `scope` | string | 回复范围: "contact"(仅联系人) 或 "all"(所有人) | +| `content` | string | 自动回复内容 | + +### 收信规则管理 + +#### 列出收信规则 + +列出当前用户的所有收信规则,包括规则名称、启用状态、条件、动作和排序。 + +``` +Usage: + dws mail rule list [flags] +Example: + dws mail rule list --email user@company.com +Flags: + --email string 用户的邮箱地址 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | int | 规则总数 | +| `rules` | List[] | 规则列表 | +| `rules[].id` | string | 规则 ID | +| `rules[].name` | string | 规则名称 | +| `rules[].enabled` | bool | 是否启用 | +| `rules[].conditions` | List[] | 规则条件列表 | +| `rules[].actions` | List[] | 规则动作列表 | +| `rules[].order` | int | 规则排序 | + +#### 创建收信规则 + +创建一条新的收信规则。支持设置规则名称、启用状态、匹配条件和执行动作。 + +> **`--conditions` 和 `--actions`** 为 JSON 数组字符串。 + +**界面与参数对应关系:** + +| 界面元素 | CLI 参数 / JSON 字段 | 说明 | +|----------|---------------------|------| +| 规则名称 | `--name` | 必填,规则的显示名称 | +| 如果满足以下「全部」条件 | `--conditions` | 多个条件之间为 **AND** 关系(即所有条件都满足才触发) | +| ├ 对象下拉(发件人) | `object: "from"` | 条件匹配的对象,可选值见下方 | +| ├ 操作下拉(包含) | `operation: "include"` | 匹配方式,可选值见下方 | +| └ 关键词输入框 | `keyword: "a@test.com"` | 匹配的具体值 | +| 执行以下操作 | `--actions` | 条件满足后执行的动作列表 | +| ├ 动作下拉(移动到文件夹) | `action: "ActSavetoFolder"` | 动作类型,可选值见下方 | +| └ 参数选择(收件箱) | `parameters: ["2"]` | 动作的参数,如目标文件夹 ID | + +**条件逻辑说明:** + +- `--conditions` 数组中的多个条件之间为 **AND(且)** 关系,即所有条件都满足才触发规则 +- 同一个条件对象(如 `from`)内部的 `or` 数组中多个表达式之间为 **OR(或)** 关系 +- 同一个 `and` 数组中的多个子条件之间为 **AND(且)** 关系 + +**条件对象 (object) 与合法操作类型 (operation) 组合:** + +| object | 合法 operation | 说明 | +|--------|---------------|------| +| `from` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配发件人地址或名称 | +| `to` | `include`(包含), `exclude`(不包含), `oneof`(是联系人之一), `noneof`(不是联系人之一) | 匹配收件人地址或名称 | +| `subject` | `include`(包含), `exclude`(不包含) | 匹配邮件主题 | +| `attachment` | `exist`(是否存在附件) | keyword="1" 表示有附件,keyword="0" 表示无附件 | +| `x-aliyun-size` | `greater`(大于), `less`(小于) | 邮件大小,单位为 **字节(Bytes)**(1KB=1024, 1MB=1048576);可组合使用表示范围区间 | + +**操作类型 (operation) 详细说明:** + +| 值 | 界面显示 | 适用 object | 说明 | +|----|---------|------------|------| +| `include` | 包含 | from, to, subject | 字段包含关键词 | +| `exclude` | 不包含 | from, to, subject | 字段不包含关键词 | +| `oneof` | 是联系人之一 | from, to | 字段值在给定联系人列表中 | +| `noneof` | 不是联系人之一 | from, to | 字段值不在给定联系人列表中 | +| `greater` | 大于 | x-aliyun-size | 数值大于阈值,单位字节(Bytes) | +| `less` | 小于 | x-aliyun-size | 数值小于阈值,单位字节(Bytes) | +| `exist` | 存在 | attachment | keyword="1" 表示有附件,keyword="0" 表示无附件 | + +**动作类型 (action) 可选值:** + +| 值 | 界面显示 | parameters 说明 | 前置依赖 | +|----|---------|----------------|----------| +| `ActSavetoFolder` | 移动到文件夹 | 目标文件夹 ID,如 `["2"]`(2=收件箱) | 需先通过 `dws mail folder list` 获取文件夹 ID | +| `ActFlagMail` | 标记标签 | 标签 ID 列表,逗号分隔,如 `["102,11,1"]` | 需先通过 `dws mail tag list` 获取标签 ID | +| `ActFlagMail2` | 标记已读 | `"asread"`(标记已读),服务端仅支持标记已读,不支持标记未读 | 无 | +| `ActReply` | 自动回复 | 回复内容文本,如 `["感谢您的来信"]` | 无 | + +**条件 JSON 结构说明:** + +每个条件由 `object`(匹配对象)和 `or`(OR 表达式列表)组成,`or` 内嵌 `and`(AND 条件列表)。 + +| 字段 | 说明 | +|------|------| +| `object` | 条件对象,取值及合法 operation 见上方组合表 | +| `or` | OR 表达式列表,同一 object 下多个 or 项之间为 **OR** 关系 | +| `and` | AND 条件列表,同一 or 项内多个 and 子条件之间为 **AND** 关系 | +| `operation` | 操作类型,必须与 object 合法组合(见上方组合表) | +| `keyword` | 关键词/阈值;attachment+exist 时 "1"=有附件/"0"=无附件;x-aliyun-size 时单位为字节(Bytes),如 1KB=1024, 1MB=1048576 | +| `ignoreCase` | 是否忽略大小写(布尔值,仅 from/to/subject + include/exclude 时需要) | + +**完整 conditions JSON 示例:** + +```json +[ + {"object":"from","or":[ + {"and":[{"operation":"oneof","keyword":"a@test.com","ignoreCase":true}]}, + {"and":[{"operation":"oneof","keyword":"b@test.com","ignoreCase":true}]} + ]}, + {"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}, + {"object":"attachment","or":[{"and":[{"operation":"exist","keyword":"1"}]}]}, + {"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"1024"},{"operation":"less","keyword":"10240"}]}]} +] +``` + +> 上例表示:发件人是 a@test.com **或** b@test.com **且** 主题包含"报告" **且** 有附件 **且** 大小在 1KB(1024字节)~10KB(10240字节) 之间。 +> +> **同一 object 下匹配多个值的 OR 写法:** 在 `or` 数组中放多个 `and` 项(每个 `and` 对应一个匹配值),而非在一个 `and` 中放多个条件。例如上方 `from` 条件中,两个邮箱地址分别作为独立的 `and` 项放在 `or` 数组中,表示"满足任一即可"。 + +**完整 actions JSON 示例:** + +```json +[ + {"action":"ActSavetoFolder","parameters":["2"]}, + {"action":"ActFlagMail","parameters":["102,11,1"]}, + {"action":"ActFlagMail2","parameters":["asread"]}, + {"action":"ActReply","parameters":["感谢您的来信,我将尽快回复"]} +] +``` + +> **注意:** 使用 `ActSavetoFolder` 前需先通过 `dws mail folder list` 获取文件夹 ID;使用 `ActFlagMail` 前需先通过 `dws mail tag list` 获取标签 ID。 + +``` +Usage: + dws mail rule create [flags] +Example: + dws mail rule create --email user@company.com --name "VIP邮件标记" --enabled true \ + --conditions '[{"object":"from","or":[{"and":[{"operation":"include","keyword":"vip@company.com","ignoreCase":true}]}]}]' \ + --actions '[{"action":"ActFlagMail2","parameters":["asread"]}]' + dws mail rule create --email user@company.com --name "大附件归档" --enabled true \ + --conditions '[{"object":"x-aliyun-size","or":[{"and":[{"operation":"greater","keyword":"10485760"}]}]}]' \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' +Flags: + --email string 用户的邮箱地址 (必填) + --name string 规则名称 (必填) + --enabled string 是否启用: true/false (必填) + --conditions string 规则条件 JSON 数组 (可选) + --actions string 规则动作 JSON 数组 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 创建是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | +| `id` | string | 新建规则 ID | + +#### 更新收信规则 + +更新已有的收信规则。**除 `--conditions` 外所有参数均为必填**。 + +> **建议工作流:** 先通过 `dws mail rule list` 获取当前规则的完整配置,再传入需要修改的字段值。 +> +> `--conditions` 为空或不传表示命中所有邮件(无条件匹配)。`--actions` 格式同 create 命令。 + +``` +Usage: + dws mail rule update [flags] +Example: + dws mail rule update --email user@company.com --id --name "新规则名" --enabled true \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' + dws mail rule update --email user@company.com --id --name "全量归档" --enabled false \ + --conditions '[{"object":"subject","or":[{"and":[{"operation":"include","keyword":"报告","ignoreCase":true}]}]}]' \ + --actions '[{"action":"ActSavetoFolder","parameters":["6"]}]' +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) + --name string 规则名称 (必填) + --enabled string 是否启用: true/false (必填) + --conditions string 规则条件 JSON 数组 (可选,为空表示命中所有邮件) + --actions string 规则动作 JSON 数组 (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 更新是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 更新结果 | + +#### 删除收信规则 + +删除指定的收信规则。 + +``` +Usage: + dws mail rule delete [flags] +Example: + dws mail rule delete --email user@company.com --id +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 删除是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误信息 | +| `result` | object | 删除结果 | + +#### 调整收信规则排序 + +调整指定收信规则的排序位置,向上(up)或向下(down)移动。 + +``` +Usage: + dws mail rule adjust [flags] +Example: + dws mail rule adjust --email user@company.com --id --direction up + dws mail rule adjust --email user@company.com --id --direction down +Flags: + --email string 用户的邮箱地址 (必填) + --id string 规则 ID (必填) + --direction string 调整方向: up/down (必填) +``` + +**返回字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | bool | 调整是否成功 | +| `errorCode` | string | 错误码 | +| `errorMsg` | string | 错误消息 | +| `result` | object | 调整结果 | + ## 通用错误说明 以下错误适用于所有 mail 命令。 @@ -596,6 +1479,20 @@ Flags: 用户说"查看附件/邮件附件/有什么附件" → 先 `message search` 获取 messageId,再 `attachment list` 用户说"下载附件/保存附件/把附件存到本地/把所有附件下载到..." → 先 `message search` 获取 messageId,再 `attachment list` 获取 attachmentId 和 name,最后逐个 `attachment download`(**不支持批量下载,不存在 download_batch/download_all 命令,必须逐个下载**) 用户说"把XX邮件的所有附件都下载" / "批量下载附件" / "下载4月所有发票邮件的附件" → **不存在批量下载命令**,必须按以下流程循环执行:1) `message search` 搜索匹配邮件获取 messageId 列表 → 2) 对每封邮件 `attachment list` 获取 attachmentId + name → 3) 对每个附件逐个调用 `attachment download`。不要编造 `download_batch` / `download_all` / `batch_download` 等不存在的命令 +用户说"创建邮件文件夹/新建邮箱文件夹/新建邮件目录" → `folder create` +用户说"在某个邮件文件夹下创建子文件夹" → 先 `folder list` 找到父文件夹 ID,再 `folder create --folder `;禁止把父文件夹名称直接填给 `--folder` +用户说"删除邮件文件夹/删除邮箱文件夹/删除邮件目录" → 先确认要删除的文件夹 ID;如果用户只给名称,先 `folder list` 找到 `folders[].id`,再 `folder delete --id ` +用户说"重命名邮件文件夹/修改邮箱文件夹名称/更新邮件目录名称" → 先确认要更新的文件夹 ID;如果用户只给原名称,先 `folder list` 找到 `folders[].id`,再 `folder update --id --name <新名称>` +用户说"查看邮件标签/列出邮箱标签/查看邮箱 label" → `tag list` +用户说"创建邮件标签/新建邮箱标签/新增 label" → `tag create` +用户说"在某个邮件标签下创建子标签" → 先 `tag list` 找到父标签 ID,再 `tag create --parent-id `;禁止把父标签名称直接填给 `--parent-id` +用户说"删除邮件标签/删除邮箱标签/删除 label" → 先确认要删除的标签 ID;如果用户只给名称,先 `tag list` 找到 `tags[].id`,再 `tag delete --id ` +用户说"重命名邮件标签/修改邮箱标签名称/更新 label 名称" → 先确认要更新的标签 ID;如果用户只给原名称,先 `tag list` 找到 `tags[].id`,再 `tag update --id --name <新名称>` +用户说"列出邮件会话/查看会话列表/查看某个文件夹里的邮件会话" → 先确认邮箱地址和文件夹 ID;如果只有文件夹名称,先 `folder list` 找到 `folders[].id`,再 `thread list --folder ` +用户说"标记会话已读/未读/给会话加标签/移除会话标签" → 用 `thread update`;如果是多条会话,用 `thread batch-update`;标签操作必须先有标签 ID +用户说"删除会话/把会话放入已删除/批量删除会话" → 单条用 `thread trash`,多条用 `thread batch-trash`;传入的是会话 ID,不是邮件 ID +用户说"批量标记邮件已读/未读/给邮件加标签/移除邮件标签" → `message batch-update --action markRead/markUnread/addTags/removeTags`(操作对象是邮件 ID;会话级操作用 `thread update/batch-update`) +用户说"邮件发出去了吗/查邮件发送状态/确认邮件是否发送成功/邮件投递结果" → 用发送类命令返回的 `internetMessageId`,调用 `message verify` 查询 `sendStatus` 用户说"查看会话/获取会话/看这封邮件的会话" → 先 `message search` 或 `message get` 获取邮件中的 `conversationId`,再 `thread get` 用户说"搜索/查找/联系 邮箱用户/联系人/某人的邮箱地址" → `user search`(搜索通讯录人员,不是搜邮件内容) 用户说"发送草稿/把草稿发出去/发这封草稿" → 先 `message search --query "folderId:5"` 找到草稿 messageId,再 `draft send` @@ -612,6 +1509,13 @@ Flags: - **严禁在用户未明确指定使用个人邮箱时,默认选择 `@dingtalk.com` 个人邮箱作为 `--email` / `--from`**;默认必须从 `mailbox list` 中挑选企业邮箱 - **涉及带附件的邮件操作时,严禁上传到钉钉媒体存储(media upload)**;必须使用对应命令的 `--attachment` / `--inline-attachment` 参数,由 CLI 内部完成附件处理 - **严禁编造不存在的批量下载命令**(如 `attachment download_batch`、`attachment download_all`、`attachment batch_download` 等)。下载附件只有 `attachment download` 一条命令,每次只能下载一个附件;需要批量下载时必须循环调用 +- **严禁把文件夹名称当作 `folder create --folder` 的值**;`--folder` 只能填父文件夹 ID,父文件夹 ID 必须来自 `folder list` 的 `folders[].id` +- **严禁把文件夹名称当作 `folder delete/update --id` 的值**;`--id` 只能填要操作的文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` 或 `folder create` 的 `result.folder.id` +- **严禁把标签名称当作 `tag create --parent-id` 的值**;`--parent-id` 只能填父标签 ID,父标签 ID 必须来自 `tag list` 的 `tags[].id` +- **严禁把标签名称当作 `tag delete/update --id` 的值**;`--id` 只能填要操作的标签 ID,标签 ID 必须来自 `tag list` 的 `tags[].id` 或 `tag create` 的 `result.tag.id` +- **严禁更新或删除系统标签**;`tag update/delete` 只适用于用户自定义标签 +- **严禁把文件夹名称当作 `thread list --folder` 的值**;`--folder` 只能填文件夹 ID,文件夹 ID 必须来自 `folder list` 的 `folders[].id` +- **严禁把邮件 ID 当作 `thread get/update/trash/batch-update/batch-trash` 的会话 ID**;这些命令需要 conversationId,可来自 `thread list` 的 `conversations[].id` 或邮件结果中的 `conversationId` ## 核心工作流 @@ -619,31 +1523,65 @@ Flags: # 1. 查看可用邮箱 — 提取邮箱地址 dws mail mailbox list --format json +# 1b. 创建顶层邮件文件夹 +dws mail folder create --email user@company.com --name "项目资料" --format json + +# 1c. 创建子文件夹 — 先通过 folder list 获取父文件夹 id,再传给 --folder +dws mail folder list --email user@company.com --format json +dws mail folder create --email user@company.com --name "子文件夹" --folder --format json + +# 1d. 更新或删除邮件文件夹 — 先通过 folder list 获取目标文件夹 id,再传给 --id +dws mail folder list --email user@company.com --format json +dws mail folder update --email user@company.com --id --name "新文件夹名" --format json +dws mail folder delete --email user@company.com --id --format json + +# 1e. 创建顶层邮件标签 +dws mail tag create --email user@company.com --name "项目资料" --format json + +# 1f. 创建子标签 — 先通过 tag list 获取父标签 id,再传给 --parent-id +dws mail tag list --email user@company.com --format json +dws mail tag create --email user@company.com --name "子标签" --parent-id --format json + +# 1g. 更新或删除邮件标签 — 先通过 tag list 获取目标标签 id,再传给 --id +dws mail tag list --email user@company.com --format json +dws mail tag update --email user@company.com --id --name "新标签名" --format json +dws mail tag delete --email user@company.com --id --format json + +# 1h. 列出文件夹中的邮件会话 — 先通过 folder list 获取文件夹 id,再传给 --folder +dws mail folder list --email user@company.com --format json +dws mail thread list --email user@company.com --folder --limit 10 --format json + +# 1i. 修改或删除邮件会话 — 先通过 thread list 获取 conversationId +dws mail thread update --email user@company.com --id --action markRead --format json +dws mail thread batch-update --email user@company.com --ids , --action markUnread --format json +dws mail thread trash --email user@company.com --id --yes --format json +dws mail thread batch-trash --email user@company.com --ids , --yes --format json + # 2. 搜索邮件 — 提取 messageId dws mail message search --email user@company.com \ - --query "subject:\"周报\" AND date>2025-06-01T00:00:00Z" --limit 10 --format json + --query "subject:\"周报\" AND date>2025-06-01T00:00:00Z" --size 10 --format json # 3. 查看邮件详情 dws mail message get --email user@company.com --id --format json # 4. 发送邮件(纯文本) dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "本周完成…" --format json + --subject "周报" --body "本周完成…" --format json # 4b. 发送带附件的邮件(自动编排:创建草稿→上传附件→发送草稿) dws mail message send --from user@company.com --to colleague@company.com \ - --subject "周报" --content "见附件" --attachment ./report.pdf --format json + --subject "周报" --body "见附件" --attachment ./report.pdf --format json # 4c. 发送带内联图片的邮件(正文自动转 HTML, 标签自动注入) dws mail message send --from user@company.com --to colleague@company.com \ - --subject "图表周报" --content "本周图表如下:[inline:chart.png]" \ + --subject "图表周报" --body "本周图表如下:[inline:chart.png]" \ --inline-attachment ./chart.png --format json # 5. 下载邮件附件到本地(每次只能下载一个附件,不支持批量下载) # 步骤 5.1:搜索匹配的邮件,获取 messageId 列表 # 示例:下载4月所有发票邮件的附件 dws mail message search --email user@company.com \ - --query "subject:发票 AND date>2025-04-01T00:00:00Z AND date<2025-05-01T00:00:00Z AND hasAttachments:true" --limit 50 --format json + --query "subject:发票 AND date>2025-04-01T00:00:00Z AND date<2025-05-01T00:00:00Z AND hasAttachments:true" --size 50 --format json # 步骤 5.2:对每封邮件,列出附件获取 attachmentId 和 name # (对搜索结果中的每封邮件都要执行一次) @@ -656,7 +1594,7 @@ dws mail attachment download --email user@company.com \ # 5. 获取邮件所属会话详情(thread) # 步骤 5.1:先通过 message search 或 message get 获取邮件中的 conversationId dws mail message search --email user@company.com \ - --query "subject:\"周报\"" --limit 5 --format json + --query "subject:\"周报\"" --size 5 --format json # 从返回的邮件列表中提取 conversationId 字段 # 步骤 5.2:用 conversationId 获取会话详情 @@ -676,8 +1614,16 @@ dws mail thread get --email user@company.com --id --select mess | `message search` | `messageId` | attachment list 的 --id | | `attachment list` | `attachments[].id` / `attachments[].name` | attachment download 的 --attachment-id / --name | | `message get` | `conversationId` | thread get 的 --id | +| `folder list` | `folders[].id` | folder create 的 --folder;folder delete/update 的 --id | +| `folder create` | `result.folder.id` | 后续创建子文件夹或移动邮件时作为 --folder;更新/删除该文件夹时作为 --id | +| `folder list` | `folders[].id` | thread list 的 --folder | +| `thread list` | `conversations[].id` | thread get/update/trash/batch-update/batch-trash 的 --id/--ids | +| `tag list` | `tags[].id` | message batch-update 的 --tags;thread update/batch-update 的 --tag-ids | +| `tag list` | `tags[].id` | tag create 的 --parent-id;tag delete/update 的 --id | +| `tag create` | `result.tag.id` | 后续创建子标签时作为 --parent-id;更新/删除该标签时作为 --id | | `aisearch person` → `contact user get` / `contact user search` / `mail user search` | 用户邮箱 (orgAuthEmail / email) | message send 的 --to/--cc(三路并发,取先到结果) | | `user search` | 用户邮箱 (email) | message send 的 --to/--cc | +| `message send` / `draft send` / `message reply` / `message reply-all` / `message forward` | `internetMessageId` | `message verify` 的 --internet-message-id | ## 注意事项 @@ -694,3 +1640,8 @@ dws mail thread get --email user@company.com --id --select mess - `thread get` 默认不返回邮件列表,如需查看会话内所有邮件,需加 `--select messages`;如需同时返回多个可选字段,用英文逗号分隔,如 `--select messages,internetMessageId` - `thread get` 返回的 `messages` 列表中,邮件正文(`body`)、收件人(`toRecipients`)等字段默认不包含,需在 `--select` 中额外指定 - `user search` 仅支持企业邮箱(非 `@dingtalk.com` 个人邮箱),使用个人邮箱将因无权限报错;搜到的用户邮箱(`email` 字段)可直接用于 `message send` 的 `--to`/`--cc` 参数 +- `thread list --folder` 的值必须是文件夹 ID,不是文件夹显示名称;不知道文件夹 ID 时,先调用 `folder list` 查 `folders[].id` +- `thread get/update/trash/batch-update/batch-trash` 使用的是会话 ID(conversationId),不是邮件 ID;会话 ID 可来自 `thread list` 的 `conversations[].id`,也可来自 `message search` 或 `message get` 返回的 `conversationId` +- `thread update` / `thread batch-update` 仅支持 `markRead`、`markUnread`、`addTags`、`removeTags`;标签操作必须传 `--tag-ids` +- `folder create --folder` 的值必须是父文件夹 ID,不是文件夹显示名称;不知道父文件夹 ID 时,先调用 `folder list` 查 `folders[].id` +- `folder delete/update --id` 的值必须是目标文件夹 ID,不是文件夹显示名称;不知道目标文件夹 ID 时,先调用 `folder list` 查 `folders[].id` diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index 8ba2acd6..0d83f20d 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -22,9 +22,9 @@ minutes 模块的命令是**两级或三级结构**,不同层级之间不能 ``` dws minutes ├── list # 查询听记列表(list 后必须跟 mine / shared / all) -│ ├── mine [--query] [--start] [--end] [--max] [--cursor] # 仅查询我自己创建/发起的听记(不含他人共享给我的) -│ ├── shared [--query] [--start] [--end] [--max] [--cursor] # 仅查询他人共享给我的听记(不含我自己创建的) -│ └── all [--query] [--start] [--end] [--max] [--cursor] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) +│ ├── mine [--query] [--start] [--end] [--max] [--next-token] # 仅查询我自己创建/发起的听记(不含他人共享给我的) +│ ├── shared [--query] [--start] [--end] [--max] [--next-token] # 仅查询他人共享给我的听记(不含我自己创建的) +│ └── all [--query] [--start] [--end] [--max] [--next-token] # 查询我可访问的所有听记(= mine ∪ shared,覆盖面最广,默认 scope) ├── get # 获取听记详情(get 后必须跟子命令名) │ ├── info --id # 获取听记基础信息(标题、时长、参与人、状态等) │ ├── summary --id # 获取听记 AI 摘要(结构化纪要) @@ -102,15 +102,15 @@ dws minutes > > | 语义 | 规约 Primary | minutes 当前 CLI 实际参数 | alias 注册状态 | 说明 | > |------|-------------|------------------------|--------------|------| -> | 单页大小 (Group 13) | `--limit` | `--limit`(别名 `--max`) | 已对齐 | `list mine/shared/all` 用 `--limit`,`--max` 为别名 | -> | 续页标识 (Group 14) | `--cursor` | `list`: `--cursor`;`get transcription`: `--next-token` | 部分对齐 | **list 系列用 `--cursor`,get transcription 用 `--next-token`,不要混用** | +> | 单页大小 (Group 13) | `--limit` | `--max` | `--limit` 未注册 | minutes 用 `--max`,其他模块用 `--limit`。**跨模块切换时注意差异** | +> | 续页标识 (Group 14) | `--cursor` | `--next-token` | `--next-token` 在 alias 池中 | minutes 用 `--next-token`,合法 alias | > | 搜索关键词 (Group 11) | `--query` | `--query` | 已对齐 | 无差异 | > | 起始时间 (Group 15) | `--start` | `--start` | 已对齐 | 无差异 | > | 结束时间 (Group 16) | `--end` | `--end` | 已对齐 | 无差异 | > > **跨模块参数名差异速查(防止混用):** -> - 分页大小:minutes `list` 用 `--limit`(别名 `--max`),与其他模块一致 -> - 续页标识:minutes `list mine/shared/all` 用 `--cursor`;minutes `get transcription` 用 `--next-token`;aitable/drive/doc 用 `--cursor` +> - 分页大小:minutes 用 `--max`,aitable/calendar/chat/drive 用 `--limit` +> - 续页标识:minutes 用 `--next-token`,aitable/drive/doc 用 `--cursor` > - 起止时间:minutes/report 用 `--start`/`--end`,calendar 用 `--start-time`/`--end-time`(跨模块最高频错误) **铁律:禁止编造 taskUuid(0512 P0 Golden Case 提炼)** @@ -271,9 +271,9 @@ dws minutes ``` dws minutes ├── list # 二级:list 后必须跟 mine / shared / all -│ ├── mine [--query] [--start] [--end] [--max] [--cursor] -│ ├── shared [--query] [--start] [--end] [--max] [--cursor] -│ └── all [--query] [--start] [--end] [--max] [--cursor] +│ ├── mine [--query] [--start] [--end] [--max] [--next-token] +│ ├── shared [--query] [--start] [--end] [--max] [--next-token] +│ └── all [--query] [--start] [--end] [--max] [--next-token] ├── get # 二级:get 后必须跟子命令名 │ ├── info --id │ ├── summary --id @@ -306,9 +306,12 @@ dws minutes │ ├── create --file-name "..." --file-size │ ├── complete --session-id │ └── cancel --session-id -└── permission - ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] - └── remove --ids --member-uids +├── permission +│ ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] +│ └── remove --ids --member-uids +└── tag # 听记标签/分组管理(标签在听记页面手动创建) + ├── list # 查询我的听记标签/分组列表(返回 tagId 和标签名称) + └── query --tag-id [--limit] [--cursor] # 根据标签ID查询该标签下的听记列表 ``` ## 命令总览 @@ -320,17 +323,17 @@ Usage: Example: dws minutes list mine dws minutes list mine --max 10 - dws minutes list mine --max 10 --cursor + dws minutes list mine --max 10 --next-token dws minutes list mine --query "周会" Flags: - --max float 查询的听记篇数 (默认 10,--limit 的别名) - --cursor string 分页游标 (首页留空,后续填写前次返回的 nextCursor) + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) --query string 关键字筛选 (可选) --start string 开始时间 ISO-8601 (可选) --end string 结束时间 ISO-8601 (可选) ``` -查询我创建的听记列表,支持 `--max` 和 `--cursor` 分页,支持按关键字和时间范围筛选。 +查询我创建的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 ### 查询他人共享给我的听记列表 ``` @@ -339,16 +342,16 @@ Usage: Example: dws minutes list shared dws minutes list shared --max 20 - dws minutes list shared --max 5 --cursor + dws minutes list shared --max 5 --next-token Flags: - --max float 查询的听记篇数 (默认 10,--limit 的别名) - --cursor string 分页游标 (首页留空,后续填写前次返回的 nextCursor) + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) --query string 关键字筛选 (可选) --start string 开始时间 ISO-8601 (可选) --end string 结束时间 ISO-8601 (可选) ``` -查询他人共享给我的听记列表,支持 `--max` 和 `--cursor` 分页,支持按关键字和时间范围筛选。 +查询他人共享给我的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。 ### 查询我有权限访问的所有听记列表 ``` @@ -359,16 +362,16 @@ Example: dws minutes list all --max 20 dws minutes list all --query "周会" --max 20 dws minutes list all --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" - dws minutes list all --max 10 --cursor + dws minutes list all --max 10 --next-token Flags: --end string 结束时间 ISO-8601 (可选) --query string 关键字筛选 (可选) - --max float 查询的听记篇数 (默认 10,--limit 的别名) - --cursor string 分页游标 (首页留空,后续填写前次返回的 nextCursor) + --max float 查询的听记篇数 (默认 10) + --next-token string 分页 token (首页留空,后续填写前次返回的 nextToken) --start string 开始时间 ISO-8601 (可选) ``` -查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--max` 和 `--cursor` 进行分页查询。 +查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--max` 和 `--next-token` 进行分页查询。 ### 获取听记基础信息 ``` @@ -824,6 +827,7 @@ Flags: ``` ### 发起听记(开始录音) + ``` Usage: dws minutes record start [flags] @@ -1207,6 +1211,41 @@ Flags: - 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 - 建议在移除前先确认成员的当前权限,避免误操作 +### 查询我的听记标签/分组列表 +``` +Usage: + dws minutes tag list +``` + +查询当前用户在听记页面手动创建的所有标签或分组列表。无需传入额外参数,系统自动识别当前用户身份。 +返回所有标签/分组的列表,每条记录包含 tagId 和标签名称。获取到 tagId 后,可使用 `dws minutes tag query --tag-id ` 查询该标签下的听记列表。 + +**注意事项:** +- 标签/分组在听记页面手动创建,此命令仅提供查询能力 +- 返回的 tagId 用于后续 `tag query` 命令的 `--tag-id` 参数 + +### 根据标签ID查询听记列表 +``` +Usage: + dws minutes tag query [flags] +Example: + dws minutes tag query --tag-id + dws minutes tag query --tag-id --limit 20 + dws minutes tag query --tag-id --limit 10 --cursor +Flags: + --tag-id string 标签/分组 ID,可通过 tag list 获取 (必填) + --limit float 每页数据条数 (默认 10) + --cursor string 分页 token (首页留空) +``` + +根据用户的标签或分组 ID 查询该标签下的听记列表。支持分页查询。 +tagId 可通过 `dws minutes tag list` 获取。 + +**注意事项:** +- `--tag-id` 必填,值来自 `dws minutes tag list` 返回的 tagId +- 支持分页,使用 `--cursor` 传入上一次返回的 nextToken +- 标签/分组在听记页面手动创建,不支持通过 CLI 创建标签 + ## 意图判断 ### 发起听记 @@ -1587,6 +1626,27 @@ Flags: 2. 调用 `dws contact user search --query "张三" --format json` 获取 UID 3. 调用 `dws minutes permission add --ids --member-uids --policy 4 --format json` +### 听记标签/分组查询 +用户说"我的听记标签/听记分组/听记有哪些标签/有哪些分组" → `tag list` +用户说"查看某个标签下的听记/这个标签下有哪些听记/按标签查听记" → `tag query` + +**自然语言示例:** +- "我的听记有哪些标签" +- "帮我看看听记的分组列表" +- "查一下'周会'标签下有哪些听记" +- "按标签筛选听记" + +**路由规则:** +- 用户提到"标签列表/分组列表/有哪些标签/有哪些分组" → `tag list` +- 用户提到"按标签查/某个标签下的听记/标签筛选" → 先 `tag list` 获取 tagId,再 `tag query --tag-id ` +- 如果用户直接提供了 tagId → 直接 `tag query --tag-id ` +- 如果用户提供标签名称而非 tagId → 先 `tag list` 查找对应 tagId,再 `tag query` + +**典型执行链路:** +1. 用户说"帮我看看'周会'标签下的听记" → AI 先获取标签列表 +2. 调用 `dws minutes tag list --format json` → 从返回中按名称匹配找到 tagId +3. 调用 `dws minutes tag query --tag-id --format json` + ### 修改听记标题 用户说"改听记标题/重命名听记/修改标题" → `update title` @@ -1607,7 +1667,7 @@ dws minutes record start --format json # 1. 查看我的听记列表 — 提取 taskUuid dws minutes list mine --format json -dws minutes list mine --max 10 --cursor --format json +dws minutes list mine --max 10 --next-token --format json dws minutes list mine --query "周会" --format json # 1b. 查看共享给我的听记 @@ -1678,9 +1738,9 @@ dws minutes upload cancel --session-id --format json | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `list mine` | `taskUuid`、`nextCursor` | get/update 的 --id;翻页时 --cursor | -| `list shared` | `taskUuid`、`nextCursor` | get/update 的 --id;翻页时 --cursor | -| `list all` | `taskUuid`、`nextCursor` | get/update 的 --id;翻页时 --cursor | +| `list mine` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | +| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | +| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token | | `get batch` | 各听记 `taskUuid` | 进一步查询详情 | | `get audio` | 音频/视频 OSS 地址 | 用 HTTP GET 下载录音文件 / 在浏览器播放 | | `record start` | `taskUuid`/`uuid` | record pause/resume/stop 的 --id | @@ -1841,7 +1901,7 @@ loop: - `record pause` / `record resume` / `record stop` 对应 `cmd=pause/resume/end`,需要传入 `--id`(映射 MCP 入参 `uuid`) - 如果用户传入听记 URL(格式: `https://shanji.dingtalk.com/app/transcribes/`),直接从路径末段提取 taskUuid 作为 `--id` 参数,无需再调用 list 查询 - `list mine`、`list shared`、`list all` 统一走 `list_by_keyword_and_time_range` 链路,通过 `belongingConditionId` 区分(`created` / `shared` / `noLimit`) -- 三个 list 命令均支持 `--max`(=--limit)、`--cursor` 分页及 `--query`、`--start`、`--end` 筛选 +- 三个 list 命令均支持 `--max`、`--next-token` 分页及 `--query`、`--start`、`--end` 筛选 - `list mine`、`list shared` 默认每页 20 条,`list all` 默认每页 10 条 - `get summary` 返回 AI 生成的结构化 Markdown 摘要 - `get transcription` 的 `--direction` 控制时间排序: 0=正序(默认), 1=倒序;当用户明确要求查看/分析转写原文时,默认自动翻页拉取全部原文(不需要用户手动说"拉第一页"),如果用户意图不是专门看原文(如查列表、看摘要),则不应主动调用此命令 @@ -1860,7 +1920,7 @@ loop: - 当用户提供关键词或时间范围时,必须使用 --query / --start / --end 在服务端筛选,严禁全量拉取后本地过滤。**严禁在 dws 命令后拼接 shell 管道做本地过滤**(如 `| grep "关键词"` / `| head -50` / `python -c "import json;..."` / `> /tmp/xxx.txt && grep ...`),这些写法在 Windows 沙箱里 100% 失败(`head`/`grep`/`wait` 不是 Windows 内置命令),即使在 macOS 上也会因 list 本身失败导致整个管道链路断掉。正确做法是始终使用 `--query` / `--start` / `--end` / `--max` 等 cli 内置参数在服务端完成筛选 - `dws minutes list` 后面**必须跟 `mine` / `shared` / `all` 子命令**,不能直接 `dws minutes list --start ... --end ...`(会报 `unknown flag: --start`)。正确写法:`dws minutes list mine --start ... --end ...` 或 `dws minutes list all --query "关键词" --start ... --end ...` - get transcription 翻页时,同一个 next-token 连续返回空 2 次即终止翻页,不再重试 -- **跨平台兼容性**:悟空运行环境可能是 Windows cmd / PowerShell / macOS bash,**严禁在 dws 命令中使用任何依赖特定 shell 的工具或管道**,包括但不限于:`| head`、`| grep`、`| tail`、`| wc`、`& wait`、`> %TEMP%\xxx`、`timeout /t 5`、`Start-Sleep`、`python -c "..."` 等。所有数据筛选、截断、过滤都必须通过 dws cli 自身的参数完成(`--query`/`--start`/`--end`/`--max`/`--cursor`,get transcription 翻页用 `--next-token`) +- **跨平台兼容性**:悟空运行环境可能是 Windows cmd / PowerShell / macOS bash,**严禁在 dws 命令中使用任何依赖特定 shell 的工具或管道**,包括但不限于:`| head`、`| grep`、`| tail`、`| wc`、`& wait`、`> %TEMP%\xxx`、`timeout /t 5`、`Start-Sleep`、`python -c "..."` 等。所有数据筛选、截断、过滤都必须通过 dws cli 自身的参数完成(`--query`/`--start`/`--end`/`--max`/`--next-token`) ### 错误恢复策略(AI 遇到以下错误时必须自动执行对应恢复动作) @@ -2392,3 +2452,64 @@ Step 2 **[禁止]** 看到全是匿名编号 → 没有继续走 Step 3-4,反 3. 用户给了钉钉 URL 时,我有没有从 URL 提取 taskUuid/dentryUuid 后走 dws,而不是 browser_use? 4. 用户的请求模糊(如"总结下我的会议")时,我是先 `dws minutes list mine` 列出来,还是反问要细节? 5. 用户要写报告/总结/日报但没提"听记"时,我有没有判断"任务产出是否依赖会议讨论内容"?如果依赖 → 听记采集不能省略(参见间接意图识别三条铁律) + +### 案例 9:长听记按时间区间总结,转写没翻页到目标区间就硬总结 → 答非所问(202606 P0 实锤复盘) + +> 这是一次**有 SLS 日志实锤根因**的真实事故:同一篇 1 小时+ 的长听记、同一个"总结某时间区间"的诉求,弱档位答错、强档位答对,但根因**不是模型能力,而是转写没拉到用户要的时间区间**。本案例用于固化"转写覆盖校验"铁律(详见案例 3 末尾的铁律)。 + +**用户输入:** +``` +总结里面 57:52 开始到 01:05:44 的内容给我,形成关键的三件事及拆解 +https://shanji.dingtalk.com/app/transcribes/ (一篇时长 1 小时+ 的会议听记) +``` + +**[错误] 真实失败链路(SLS 日志实锤):** + +``` +Step 1 [完成] 从 URL 提取 taskUuid → get transcription 拉第 1 页(50 段,到 8:59) + ↓ +Step 2 [完成] 续翻第 2 页(到 19:23)、第 3 页(到 26:34)、第 4 页(到 31:09) + ↓ +Step 3 [禁止] 翻到 31:09 时撞上字符上限 / 步数预算 → 停止翻页 + ↓ +错误动作:手里只有 0:02 ~ 31:09 的转写,但用户要的是 57:52 ~ 01:05:44 + ↓ +错误结论:在"前 31 分钟"的内容里硬找"57 分钟那段",拼出一个"关键三件事" + ↓ +后果:总结的内容和用户要的那段完全对不上,用户评"总结的一般",被迫换更强模型重做 +``` + +> **强档位为什么"答对了"**:换档位重做时,翻页继续推进到了 **63:53**,**覆盖了 57:52~01:05:44 整段**,所以答对了——**对的是"把转写翻到了目标区间",不是"模型更聪明"**。SLS 日志清晰记录了两轮的翻页进度:弱档位止于 31:09(漏掉目标区间),强档位推进到 63:53(覆盖目标区间)。 + +**这条链路的核心错误:** + +| 反模式 | 后果 | +|--------|------| +| 用户给了明确时间区间(57:52~01:05:44),却没翻页到该区间就停 | 目标区间的转写**根本没进上下文**,总结无源可依 | +| 拿"前 31 分钟"的内容冒充"用户要的 57 分钟那段" | 答非所问,且用户不知道 AI 其实只拉到了 31:09 | +| 撞字符上限时只想着"是否继续",没意识到"目标区间还没拉到" | 即使提示了"是否继续"也没说清"为什么必须继续",提示无效 | +| 答错后归因为"模型不行、换强模型" | 真正要修的是取数覆盖校验,归因错了优化方向就错了 | + +**[正确] 应该这样执行:** + +1. **解析目标区间**:`57:52` → 3472000ms,`01:05:44` → 3944000ms。 +2. **从 URL 提取 taskUuid** → `dws minutes get transcription --id --format json` 拉第一页,拿 `nextToken` 和本页段落 `endTime` 最大值。 +3. **按 `endTime` 校验覆盖**:每翻一页,检查"已拉取段落的 `endTime` 最大值"是否 ≥ 3944000ms(目标区间上界)。 + - 未达到 且 `hasNext=true` → **继续翻页**(即使中途超过 12000 字符,也要明确告知用户"目标区间还在后面、需继续翻页",而不是停下硬总结)。 + - 已达到 / `hasNext=false` → 停止翻页,进入分析。 +4. **截取目标区间**:从拉取到的全部段落里,筛出 `startTime`/`endTime` 落在 [3472000, 3944000] 的段落,**只对这一段**做"关键三件事 + 拆解"。 +5. **若翻页耗尽仍没到 57:52**(听记总时长 < 57:52)→ 准确提示:"这篇听记总时长约 X 分钟,你要的 57:52 超出了实际长度,是不是时间点记错了?" +6. **若因字符上限暂停且尚未覆盖目标区间** → 准确提示(见案例 3 铁律第 4 条的话术),让用户在"继续翻页 / 缩小范围"间选择,**绝不拿没覆盖的内容硬总结**。 + +**本案例固化的关键经验(强制吸收):** + +1. **用户给了时间区间 = 必须翻页到该区间才能总结**:`get transcription` 是从头顺序分页的,区间靠后就得一直翻到那里。中途停 = 目标区间没进上下文。 +2. **"撞字符上限"不是停止翻页的理由,"已覆盖目标区间"才是**:用户明确指定区间时,覆盖校验优先于字符上限——没覆盖就得继续翻(或准确提示用户),不能闷头停。 +3. **答错后先查"取数是否拉全",再怀疑模型**:本案例换强模型答对是假象,真因是取数覆盖。遇到"总结不准"的长听记 case,第一反应应是"目标区间的转写拉到了吗"。 +4. **提示必须准确到"已拉到哪 / 目标区间在不在"**:模糊的"是否继续拉取"对用户无意义;必须告诉用户"已拉到 31:09,你要的 57:52 还没拉到"。 + +**绝对禁止:** +- **[禁止]** 用户指定了时间区间,却没翻页到该区间(按 `endTime` 校验)就开始总结 +- **[禁止]** 拿"已拉到的前半段"冒充"用户要的后半段区间"硬凑答案,且不告知用户实际只拉到了哪里 +- **[禁止]** 因字符上限/步数预算停止翻页时,只说"已达上限是否继续"而不说明"目标区间尚未拉到" +- **[禁止]** 把"总结不准"归因为模型能力、直接建议换模型——先核对取数是否覆盖了用户请求的时间区间 diff --git a/skills/mono/references/products/oa.md b/skills/mono/references/products/oa.md index e9984282..de477154 100644 --- a/skills/mono/references/products/oa.md +++ b/skills/mono/references/products/oa.md @@ -80,16 +80,16 @@ Flags: --instance-id string 审批实例 ID (必填) ``` -### 查询已发起的审批实例列表 +### 查询我已发起的审批实例记录 ``` Usage: dws oa approval list-initiated [flags] Example: - dws oa approval list-initiated --process-code --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --next-token 0 --max-results 20 + dws oa approval list-initiated --process-code --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --limit 20 Flags: --end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填) - --max-results string 每页大小,最大 20 (必填) - --next-token string 分页游标,首次传 0 (必填) + --limit string 每页大小,最大 20 (必填) + --cursor string 分页游标,首次传 0 (必填) --process-code string 表单 processCode (必填) --start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填) ``` @@ -106,6 +106,80 @@ Flags: ``` MCP 工具: `list_user_visible_process`;参数: cursor, pageSize(对应 --cursor/--size)。返回结果含 processCode,可用于 list-initiated 的 --process-code。 +### 按关键字模糊搜索审批表单 +``` +Usage: + dws oa approval search-forms [flags] +Example: + dws oa approval search-forms --query AI + dws oa approval search-forms --query 报销 +Flags: + --query string 关键字,匹配 processCode 或表单名称 (必填) +``` + +### 获取审批任务的被催办人 userId + +> **催办必须两步串联:** ① `ding-info` 获取被催办人 `userId` → ② `ding message send` 发送催办消息。禁止跳过第一步直接猜测 userId。 + +``` +Usage: + dws oa approval ding-info [flags] +Example: + dws oa approval ding-info --task-id +Flags: + --task-id string 审批任务 ID (必填),来自 list-pending 或 tasks +``` + +返回值字段: +- `userId` — 被催办人用户 ID(必取),作为 `ding message send` 的 `--users` 入参,多个以逗号拼接 + +**不返回** robotCode 和 content,需由 agent 自行处理: +- `--robot-code`:优先取环境变量 `$DINGTALK_DING_ROBOT_CODE`;若无则向用户确认 +- `--content`:由 agent 根据审批上下文撰写催办文案,建议格式 `"请尽快审批《{表单名}》(提交人:{发起人},提交时间:{时间})"` +- `--users`:取本接口返回的 `userId` + +**叮消息类型(`--type`):** +- 默认不发 `--type` → 应用内 DING 提醒(免费,推荐) +- `--type sms` → 短信提醒(有成本,需向用户确认) +- `--type call` → 电话提醒(有成本,需向用户确认) + +**完整催办流程:** +```bash +# Step 1: 获取被催办人 userId +dws oa approval ding-info --task-id --format json +# Step 2: 发送催办消息(robotCode 优先走环境变量,content 由 agent 撰写) +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --format json +# Step 3 (可选): 如需短信/电话提醒,加 --type sms 或 --type call +dws ding message send --robot-code $DINGTALK_DING_ROBOT_CODE --users --content "请尽快审批《XXX》" --type sms --format json +``` + +### 获取任务可回退的节点信息 + +> **IMPORTANT:** 退回任务前**必须先调用此命令**获取可回退节点列表,从中提取 `activityId` 和 `revertAction` 作为 `revert-task` 的入参。若无返回值,明确告知用户"当前任务无可回退节点"。 + +``` +Usage: + dws oa approval revert-activities [flags] +Example: + dws oa approval revert-activities --task-id +Flags: + --task-id string 审批任务 ID (必填) +``` + +返回字段说明: +- `instRevertActivities[]` — 可回退的节点列表 + - `activityId` — 节点 ID,即 `revert-task` 的 `--target-activity-id` + - `activityName` — 节点名称(如"发起人"、"审批人"),用于向用户展示 + - `revertAction` — 退回方式,即 `revert-task` 的 `--action` + - `REVERT_FOR_RESUBMIT` → 退回到发起人重交(此时 `activityId` 为 `sid-startevent`) + - `REVERT_FOR_APPROVAL` → 退回到某审批节点重新审批 + - `activityActioners[]` — 该节点的审批人列表 + - `actualActioners[]` — 该节点的实际处理人列表 + - `approvalIndex` — 审批节点序号(仅审批节点有) + - `actType` — 审批类型(如 `one_by_one` 依次审批) + +**无返回值处理:** 若 `instRevertActivities` 为空或不存在,必须明确告知用户"当前任务无可回退节点",不得继续执行退回操作。 + ### 查询待我审批的任务 ID ``` Usage: @@ -191,6 +265,45 @@ Flags: ``` MCP 工具: `oa_cc_noticer`;参数: processInstanceId, userList(对应 --instance-id/--user-list)。processInstanceId 可通过 `list-pending` 或 `detail` 获取,抄送用户 ID 可通过 `dws contact user search` 获取。 +### 对审批任务进行加签 + +> **CAUTION:** 加签操作不可撤回 — 执行前必须向用户确认加签类型、被加签人和激活方式。 +``` +Usage: + dws oa approval append-task [flags] +Example: + dws oa approval append-task --instance-id --task-id --type before --appender-user-ids "userId1,userId2" --activate-type ALL --agree-all true + dws oa approval append-task --instance-id --task-id --type after --appender-user-ids "userId1" --activate-type ONE_BY_ONE --agree-all false +Flags: + --instance-id string 审批实例 ID (必填) + --task-id string 审批任务 ID (必填) + --type string 加签类型:before(前加签),after(后加签),Parallel(并加签)(必填) + --appender-user-ids string 被加签用户 ID 列表,多个用逗号分隔 (必填) + --activate-type string 任务激活类型:ALL(或签),ONE_BY_ONE(依次审批)(必填) + --agree-all string 是否需要全部同意 (必填) 是 true 否 false +``` + +### 退回审批任务 + +> **CAUTION:** 退回操作不可撤回 — 执行前必须向用户确认退回方式及目标节点。 +> **前置步骤:** 必须先调用 `revert-activities` 获取可回退节点列表,从中提取 `activityId` 和 `revertAction`。若无返回值,明确告知用户"当前任务无可回退节点"。 + +``` +Usage: + dws oa approval revert-task [flags] +Example: + # 退回到发起人(targetActivityId 固定传 sid-startevent) + dws oa approval revert-task --instance-id --task-id --target-activity-id sid-startevent --action REVERT_FOR_RESUBMIT --remark "补充说明后重提" + # 退回到某个审批节点(targetActivityId 从 revert-activities 返回中获取 activityId) + dws oa approval revert-task --instance-id --task-id --target-activity-id --action REVERT_FOR_APPROVAL --remark "重新审批" +Flags: + --instance-id string 审批实例 ID (必填) + --task-id string 审批任务 ID (必填) + --target-activity-id string 退回到的节点 ID;退回发起人时固定传 sid-startevent (必填) + --action string 退回方式:REVERT_FOR_APPROVAL(退回到审批人)/ REVERT_FOR_RESUBMIT(退回到发起人)(必填) + --remark string 退回说明 (可选) +``` + ## 意图判断 diff --git a/skills/mono/references/products/sheet.md b/skills/mono/references/products/sheet.md index e3fb21ab..edc0bdb3 100644 --- a/skills/mono/references/products/sheet.md +++ b/skills/mono/references/products/sheet.md @@ -1,93 +1,1994 @@ # 电子表格 (sheet) 命令参考 -> **渐进式文档**:本文件为路由层(索引 + 意图判断 + 全局约束),各命令的详细参数、示例和注意事项在 [sheet/](./sheet/) 目录下按需加载。 +## 适用范围(重要) -## 适用范围 - -`sheet` 仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。 +`sheet` 产品线仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`),不支持上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。 | 文件类型 | 处理方式 | |---------|---------| -| 在线电子表格(`axls`) | 走 `sheet` 全部命令 | -| `xlsx` / `xls` / `xlsm` / `csv` | `dws drive download --node --output <路径>` 下载到本地处理,禁止调用任何 `sheet` 子命令 | -| 本地 xlsx 导入为在线表格 | `dws drive upload --file <路径> --convert`(上传并转换为在线电子表格,转换后可用 `sheet` 命令操作) | -| 在线表格导出为 xlsx | `dws sheet export`(axls → xlsx 格式转换) | +| 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) | +| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node --output <路径>` 先下载到本地再解析处理,禁止调用任何 `sheet` 子命令(sheet 底层 MCP 工具仅认 `axls`,传入 xlsx 节点会直接报错) | +| 想把在线表格导出为 xlsx | 用 `dws sheet export` ——输入是 `axls`,输出是 xlsx(axls → xlsx 的格式转换) | -用户贴原始 `alidocs` URL 时必须先 probe:`dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验: +> 用户直接粘贴原始 `alidocs` URL 时必须先 probe:先执行 `dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验 `contentType` 和 `extension`: +> - 仅当 `contentType=ALIDOC` 且 `extension=axls` 时,才继续走 `sheet` +> - 如果是 `xlsx` / `xls` / `xlsm` / `csv`,立即转向 `dws doc download`,并告知用户"这是本地表格文件,已为你下载到本地处理" + +## URL 识别与 NODE_ID 提取 + +当用户输入包含钉钉文档 URL 时,必须先识别并提取 NODE_ID,再判断意图。 + +硬性规则:对用户直接给出的原始 `alidocs` URL,不允许直接走 `sheet` 命令,必须先执行: + +```bash +dws doc info --node "" --format json +``` + +根据返回路由: - `contentType=ALIDOC` + `extension=axls` → 继续走 `sheet` -- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws drive download`,告知用户"这是本地表格文件,已为你下载到本地处理" +- `contentType≠ALIDOC` + `extension=xlsx` / `xls` / `xlsm` / `csv` → 转向 `dws doc download --node --output <路径>`,禁止调用任何 sheet 子命令 +- 其他类型 → 按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 路由 -## URL → NODE_ID +补充:如果这是用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `sheet`。 -| URL 格式 | 提取方式 | -|----------|---------| -| `.../i/nodes/{id}` 或 `.../i/nodes/{id}?query` | 取路径末段作 NODE_ID(忽略 query) | -| `.../spreadsheetv2/{key}/...` | **完整 URL 原样传 `--node`**,禁止提取 path segment | +### 支持的 URL 格式 -参数不确定时先 `dws sheet <命令> --help`。 +| 格式 | 示例 | NODE_ID 提取方式 | +|------|------|----------------| +| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段 | +| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段 | +| `alidocs.dingtalk.com/spreadsheetv2/{key}/...` | `https://alidocs.dingtalk.com/spreadsheetv2/vKJngl50tJN1v3a3/...?dentryKey=vKJngl50tJN1v3a3&type=s` | **不要提取 path segment**,将完整 URL 直接传给 `--node` 参数,由 MCP 服务端解析 | -## Reference 索引 +### 提取规则 -| Reference | 描述 | -|-----------|------| -| [sheet-workbook](./sheet/sheet-workbook.md) | 管理表格文档与工作表。当用户说"创建表格"、"有哪些工作表"、"新建/重命名/隐藏/冻结/复制/删除工作表"时使用。命令:`create`/`list`/`info`/`new`/`update`/`copy`/`delete-sheet` | -| [sheet-read-data](./sheet/sheet-read-data.md) | 读取工作表数据。当用户说"读数据"、"看表格内容"、"查看数据"时使用。推荐 `csv-get`(CSV 格式、token 低、防爆保护);需 value + dataValidation / hyperlink / richText / cellStyles 等 per-cell 元数据时用 `range read`。大范围数据建议分页读取(单次 ≤5000 单元格)。命令:`csv-get`/`range read` | -| [sheet-write-data](./sheet/sheet-write-data.md) | 写入数据到工作表。当用户说"写数据"、"填表"、"更新单元格"、"写公式"、"超链接"、"写值同时设样式/数据验证"、"追加数据"、"导入CSV"时使用。大批量纯值(>5行或>20单元格)必须用 `csv-put` 而非 `range update`。命令:`range update`/`append`/`csv-put` | -| [sheet-search-replace](./sheet/sheet-search-replace.md) | 搜索和替换文本。当用户说"搜索"、"查找"、"替换"、"把A改成B"时使用。禁止用 `range read` 全量读取后客户端过滤代替 `find`,禁止用 `range update` 模拟 `replace`。命令:`find`/`replace` | -| [sheet-range-operations](./sheet/sheet-range-operations.md) | 区域结构性操作。当用户说"清空"、"排序"、"自动填充"、"复制区域到"、"移动数据到"时使用。均为服务端原子操作,禁止 `range read`+`range update` 组合模拟。排序前必须先读前几行判断表头。命令:`range clear`/`range sort`/`range fill`/`range copy-to`/`range move-to` | -| [sheet-dimension-operations](./sheet/sheet-dimension-operations.md) | 行列增删移动与属性设置。当用户说"插入行/列"、"删除行/列"、"隐藏/显示行列"、"设行高/列宽"、"移动行/列"、"追加空行/空列"时使用。命令:`insert-dimension`/`delete-dimension`/`update-dimension`/`move-dimension`/`add-dimension` | -| [sheet-style-format](./sheet/sheet-style-format.md) | 单元格样式与合并。当用户说"设样式"、"改颜色/字体/对齐"、"数字格式(百分比/货币/日期)"、"合并/取消合并"时使用。纯样式/批量样式走 `set-style`;写值同时设置少量 cell 样式可用 `range update` 的 `cellStyles`。命令:`range set-style`/`range batch-set-style`/`merge-cells`/`unmerge-cells` | -| [sheet-dropdown](./sheet/sheet-dropdown.md) | 下拉列表管理。当用户说"设置下拉"、"下拉选项"、"删除下拉"时使用。命令:`set-dropdown`/`get-dropdown`/`delete-dropdown` | -| [sheet-media-image](./sheet/sheet-media-image.md) | 附件上传与图片。当用户说"上传附件"、"写入图片到单元格"、"浮动图片"时使用。单元格图片用 `write-image`(禁止 `range update`);浮动图片需先 `media-upload` 再 `create-float-image`。命令:`media-upload`/`write-image`/`create-float-image`/`get-float-image`/`list-float-images`/`update-float-image`/`delete-float-image` | -| [sheet-filter](./sheet/sheet-filter.md) | 全局筛选。当用户说"筛选"、"过滤"、"只看某些行"(未说"筛选视图")时使用。禁止用"删除不符合条件的行"代替筛选。命令:`filter get`/`create`/`delete`/`update`/`clear-criteria`/`sort` | -| [sheet-filter-view](./sheet/sheet-filter-view.md) | 筛选视图(个人化,不影响协作者)。当用户明确说"筛选视图"时使用,与全局筛选相互独立。命令:`filter-view list`/`create`/`update`/`delete`/`info`/`update-criteria`/`delete-criteria`/`list-criteria`/`get-criteria` | -| [sheet-conditional-format](./sheet/sheet-conditional-format.md) | 条件格式规则。触发词:标红/标黄/高亮/突出/标记/数据条/色阶/颜色随数据变 → **强制**走条件格式,禁止 `range set-style` 静态样式替代。命令:`cond-format list`/`create`/`update`/`delete` | -| [sheet-export](./sheet/sheet-export.md) | 导出表格为 xlsx。当用户说"导出"、"下载xlsx"、"存为Excel"时使用。单命令一站式,CLI 内部自动轮询,禁止 Agent 侧重试。命令:`export` | +1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 +2. **判断 URL 路径格式**: + - 路径包含 `/i/nodes/` → 取 URL path 的最后一段作为 NODE_ID(去掉 query string 和 fragment) + - 路径包含 `/spreadsheetv2/` → **不要提取 path segment**,必须将完整 URL 原样传给 `--node` 参数(因为 path 中的短 ID 不是合法的 nodeId,MCP 服务端会自行解析完整 URL) +3. 对于 `/i/nodes/` 格式,提取出的 NODE_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) +4. 对用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认 `contentType=ALIDOC` 且 `extension=axls` 时,才继续留在 `sheet`;如果 `extension=xlsx` / `xls` / `xlsm` / `csv`,必须转向 `dws doc download`,不能走任何 sheet 命令 -## 全局硬约束 +## 查询命令帮助 -1. **`--sheet-id` 禁止臆测**:未知时必须 `dws sheet list --node --format json` 查询,禁止编造 `Sheet1`/`sheet1`/`0`/`default` -2. **合并单元格是结构信息**:`dws sheet info --node --sheet-id --format json` 返回 `mergedRanges`(如 `["C7:D11"]`);不要在 `range read` / `csv-get` 里寻找合并信息 -3. **最后非空坐标只用 A1 语义**:`sheet info` 通过 `nonEmptyRange` 返回 A1/UI 边界;优先使用 `nonEmptyRange.range`,需要追加行/列时使用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn`。不要依赖底层 0-based 的 `lastNonEmptyRow` / `lastNonEmptyColumn` -4. **`range update` 维度校验**:`--values` 行列数必须与 `--range` 完全一致;只接 `--values` 一个数据参数,cell `type` 仅支持 `text` / `richText`;整格超链接通过 cell-level `hyperlink` 表达,富文本片段链接才使用 `richText.texts[].type="link"` -5. **dataValidation 三语义**:不传 `dataValidation` 字段=保留原 DV;`dataValidation:{type:"none"}`=显式清除;`dataValidation:{type:"dropdown"/"checkbox",...}`=覆盖。`{}` 跳过亦保留原 DV -6. **hyperlink 三语义**:不传 `hyperlink` 字段=保留原整格超链接;`hyperlink:{type:"none"}`=显式清除;`hyperlink:{type:"path"/"sheet"/"range",link,...}`=覆盖。Agent 调用不要用 `hyperlink:null`,避免网关/Schema 过滤 null 字段 -7. **样式写法**:cell-level 样式用 `cellStyles` 或 `range set-style`;richText 片段级样式才用子项 `style`。不要在 `type:"text"` 顶层使用旧 `style` 字段 -8. **用专用命令不用组合模拟**:搜索→`find`、替换→`replace`、清空→`range clear`、排序→`range sort`、填充→`range fill`、复制区域→`range copy-to`、移动区域→`range move-to`、移动行列→`move-dimension` -9. **大批量纯值用 `csv-put`**(>5 行或 >20 单元格),不用 `range update` -10. **单元格图片用 `write-image`**(`range update` 不支持图片参数) -11. **`export` 禁止自行轮询**(CLI 内部已完成渐进式退避,最多 30 次约 5 分钟) -12. **单次调用上限**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) -13. **关键区分**:sheet(电子表格/单元格读写)vs aitable(AI多维表/结构化记录)vs doc(文档) +当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 -## URL 粘贴场景 +```bash +# 查看 sheet 下所有子命令 +dws sheet --help + +# 查看具体命令的完整参数说明 +dws sheet range update --help +dws sheet filter-view create --help +dws sheet insert-dimension --help + +# 查看子命令组下的所有命令 +dws sheet range --help +dws sheet filter-view --help +``` + +规则: +- 参数名不确定时 → 先 `--help`,再调用 +- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 +- 不确定某个功能是否存在时 → `dws sheet --help` 查看命令列表 + +## 命令速查表 + +| 命令 | 用途 | +|------|------| +| `sheet create` | 创建钉钉表格文档 | +| `sheet list` | 获取全部工作表列表 | +| `sheet info` | 获取指定工作表详情 | +| `sheet new` | 新建工作表 | +| `sheet update` | 更新工作表属性(标题/位置/隐藏/冻结) | +| `sheet copy` | 复制工作表 | +| `sheet range read` | 读取工作表数据(别名: range get) | +| `sheet range update` | 更新指定区域内容(值/公式/超链接) | +| `sheet range set-style` | 设置单元格样式 | +| `sheet range batch-set-style` | 按配置文件批量设置样式 | +| `sheet find` | 搜索单元格内容 | +| `sheet append` | 在末尾追加数据行 | +| `sheet csv-put` | 将 CSV 数据写入指定位置(纯值,自动扩容) | +| `sheet replace` | 全局查找替换文本 | +| `sheet merge-cells` | 合并单元格 | +| `sheet unmerge-cells` | 取消合并单元格 | +| `sheet set-dropdown` | 设置下拉列表 | +| `sheet get-dropdown` | 获取下拉列表配置 | +| `sheet delete-dropdown` | 删除下拉列表 | +| `sheet insert-dimension` | 在指定位置插入行或列 | +| `sheet delete-dimension` | 删除指定位置的行或列 | +| `sheet update-dimension` | 更新行/列属性(显隐/行高/列宽) | +| `sheet move-dimension` | 移动行或列到指定位置 | +| `sheet add-dimension` | 在末尾追加空行或空列 | +| `sheet media-upload` | 上传附件到表格 | +| `sheet write-image` | 上传图片并写入单元格 | +| `sheet create-float-image` | 创建浮动图片 | +| `sheet get-float-image` | 获取浮动图片详情 | +| `sheet list-float-images` | 列出工作表所有浮动图片 | +| `sheet update-float-image` | 更新浮动图片属性 | +| `sheet delete-float-image` | 删除浮动图片 | +| `sheet export` | 导出表格为 xlsx | +| `sheet filter get` | 获取全局筛选信息 | +| `sheet filter create` | 创建全局筛选 | +| `sheet filter delete` | 删除全局筛选 | +| `sheet filter update` | 批量更新筛选条件 | +| `sheet filter clear-criteria` | 清除单列筛选条件 | +| `sheet filter sort` | 筛选排序 | +| `sheet filter-view list` | 获取所有筛选视图 | +| `sheet filter-view create` | 创建筛选视图 | +| `sheet filter-view update` | 更新筛选视图属性 | +| `sheet filter-view delete` | 删除筛选视图 | +| `sheet filter-view update-criteria` | 更新筛选视图列条件 | +| `sheet filter-view delete-criteria` | 删除筛选视图列条件 | +| `sheet filter-view info` | 获取单个筛选视图详情 | +| `sheet filter-view list-criteria` | 列出筛选视图所有列条件 | +| `sheet filter-view get-criteria` | 获取单列筛选条件详情 | + +> 不确定参数?对任意命令执行 `dws sheet <命令> --help` 查看完整用法。 + +## 意图判断 + +### 表格与工作表管理 + +用户说"创建表格/新建电子表格": +- 创建表格文档 → `create` + +用户说"看工作表/有哪些工作表/表格结构": +- 列出工作表 → `list` +- 工作表详情 → `info` + +用户说"加工作表/新增Sheet": +- 新建工作表 → `new` + +用户说"修改工作表名称/重命名工作表/移动工作表位置/隐藏工作表/显示工作表/冻结行/冻结列/取消冻结/更新工作表属性": +- 更新工作表属性 → `update` +- 重命名工作表 → `update --title "新名称"` +- 移动工作表位置 → `update --index N` +- 隐藏工作表 → `update --hidden` +- 显示工作表 → `update --hidden=false` +- 冻结行列 → `update --frozen-row-count N --frozen-column-count M` +- 取消冻结 → `update --frozen-row-count 0 --frozen-column-count 0` + +用户说"复制工作表/拷贝工作表/克隆工作表/工作表副本": +- 复制工作表 → `copy` +- 复制并指定名称 → `copy --title "副本名称"` +- 复制并指定位置 → `copy --index N` + +### 数据读写 + +用户说"读数据/看表格内容": +- 读取数据 → `range read` + +用户说"写数据/填表/更新单元格/写入公式": +- 更新数据 → `range update` +- 【强制】`--sheet-id` 必填:即使是单工作表也不能省略,不要参照 `range read` 的默认行为;未知时先执行 `dws sheet list --node --format json` 获取 `sheetId`,禁止凭空臆测为 `Sheet1`、`sheet1`、`0`、`default` 等 +- 注意:如果用户的目的是替换文本、移动行列或追加空行空列,请勿使用 `range update`,必须使用对应的专用命令(`replace`/`move-dimension`/`add-dimension`) +- **批量纯值写入优先用 `csv-put`**:当写入场景同时满足以下条件时,必须优先使用 `csv-put` 而非 `range update`:(1) 写入的是纯值(不含公式、超链接);(2) 数据量较大(超过 5 行或超过 20 个单元格);(3) 数据来源为表格/CSV 文本/结构化文本。`csv-put` 无需手动构造二维 JSON 数组,直接传 CSV 文本即可,更简洁高效且支持自动扩容 + +用户说"追加数据/添加行/在末尾加数据/新增记录": +- 追加数据 → `append` + +用户说"批量写入CSV/导入CSV/CSV写入表格/把CSV贴到表格里": +- 写入 CSV → `csv-put` +- 与 `range update` 的区别:`csv-put` 接受 CSV 文本直接写入,无需手动构造二维 JSON 数组;适合大批量纯值写入 +- 与 `append` 的区别:`csv-put` 写入指定位置(--start-cell),`append` 在末尾追加 + +用户说"搜索/查找/找单元格/搜内容/精确搜索/精确匹配/完全匹配/全字匹配": +- 搜索单元格 → `find` +- 精确匹配(只匹配完全等于的,不匹配包含的) → `find --match-entire-cell` +- 正则搜索 → `find --use-regexp` +- 搜索公式 → `find --match-formula` +- 不要用 `range read` 读取全量数据后在客户端过滤来替代 `find`,必须使用 `find` 命令的服务端搜索能力 + +用户说"替换/查找替换/全局替换/批量替换/把A替换成B/把所有的X改成Y": +- 查找替换 → `replace` +- 精确匹配后替换(只替换内容完全等于的单元格) → `replace --match-entire-cell` +- 正则替换 → `replace --use-regexp` +- 删除匹配内容 → `replace --replacement ""` +- 请勿用 `find` + `range update`、`range read` + `range update` 等组合来模拟替换,`replace` 是服务端原子操作,效率更高且返回替换计数 + +### 行列操作 + +用户说"插入行/插入列/在某行前插入/在某列前插入": +- 插入行或列 → `insert-dimension` +- 在末尾追加 → `append`(insert-dimension 不支持末尾追加) + +用户说"删除行/删除列/删掉第几行/删掉某列/移除行/移除列": +- 删除行或列 → `delete-dimension` +- 仅清空内容但保留行/列 → `range update --values` 写入空字符串 `""` + +用户说"隐藏行/隐藏列/显示行/显示列/设置行高/设置列宽/调整行高/调整列宽/行列属性": +- 隐藏/显示行或列 → `update-dimension --hidden` / `--hidden=false` +- 设置行高/列宽 → `update-dimension --pixel-size` +- 同时修改尺寸与显隐 → `update-dimension --pixel-size --hidden` + +用户说"移动行/移动列/调整行顺序/调整列顺序/行列拖拽/把第N行移到第M行": +- 移动行或列 → `move-dimension` +- 请勿用 `range read` + `range update` 读取再重写来模拟移动,`move-dimension` 是原子操作,能保留格式和合并状态 + +用户说"追加空行/追加空列/增加行数/增加列数/扩展表格/在末尾加空行": +- 追加空行/空列 → `add-dimension` +- 注意与 `append`(追加数据行)区分:`add-dimension` 追加的是空行/空列,`append` 追加的是带数据的行 +- 请勿用 `range update` 写空数据来模拟追加,`add-dimension` 直接扩展表格维度 + +### 单元格格式 + +用户说"设置样式/改颜色/设背景色/加粗/居中/换行/字体颜色/字号": +- 设置单元格样式 → `range set-style` +- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(内部顺序循环调 `update_range`) +- 请勿用 `range update --values` 写空/重写来模拟样式变更;也请勿把样式变更混在 `range update` 里、再故意清空 `--values` + +用户说"设置数字格式/改成百分比/用人民币显示/按日期显示/文本格式/保留几位小数": +- 设置数字格式 → `range set-style --number-format <格式代码>`(如 `0%` / `¥#,##0.00` / `yyyy/m/d` / `@`) +- 请勿用 `range update` 传递数字格式,`range update` 仅负责写入值与超链接;数字格式属于单元格样式,统一走 `range set-style` + +用户说"合并单元格/合并/合并区域/按行合并/按列合并": +- 合并所有单元格 → `merge-cells`(默认 mergeAll) +- 按行合并 → `merge-cells --merge-type mergeRows` +- 按列合并 → `merge-cells --merge-type mergeColumns` + +用户说"取消合并/拆分单元格/还原合并": +- 取消合并单元格 → `unmerge-cells` + +### 下拉列表 + +用户说"设置下拉列表/下拉选项/下拉菜单/添加下拉/配置下拉": +- 设置下拉列表 → `set-dropdown` +- 设置多选下拉 → `set-dropdown --multi-select` + +用户说"查看下拉列表/获取下拉配置/下拉列表有哪些选项": +- 获取下拉列表配置 → `get-dropdown` + +用户说"删除下拉列表/移除下拉/取消下拉/清除下拉": +- 删除下拉列表 → `delete-dropdown` + +### 媒体上传 + +用户说"上传附件/传文件到表格/上传文件到表格/上传到表格": +- 上传附件 → `media-upload`(需表格 ID 或 URL + 本地文件路径) +- 用户指定了上传后的名称 → `media-upload --name "自定义名称"` +- `media-upload` 的 `--name` 参数用于指定附件在表格中显示的名称(不改变本地文件名);不传时默认使用本地文件名 + +用户说"写入图片/插入图片/加图片/放图片到单元格/嵌入图片到表格": +- 写入图片 → `write-image`(需表格 ID + 工作表 ID + 单元格范围 + 本地图片路径) +- 禁止使用 `range update` 写入图片,因为 `update_range` 的 MCP 工具不支持图片类型参数,调用必定失败。必须使用 `write-image` 命令 +- 用户指定了图片尺寸 → `write-image --width N --height M` + +### 浮动图片 + +用户说"浮动图片/悬浮图片/在表格上放一张图/加个浮动的图": +- 创建浮动图片 → 先 `media-upload` 上传图片获取 `resourceUrl`,再 `create-float-image` +- 浮动图片悬浮于单元格之上,不占用单元格内容,与 `write-image`(写入单元格内部的图片)不同 + +用户说"查看浮动图片/有哪些浮动图片/浮动图片列表": +- 列出所有浮动图片 → `list-float-images` +- 查看某个浮动图片详情 → `get-float-image` + +用户说"移动浮动图片/调整浮动图片大小/修改浮动图片/更新浮动图片": +- 更新浮动图片属性 → `update-float-image`(可更新锚点位置、尺寸、偏移量、图片资源路径) + +用户说"删除浮动图片/移除浮动图片": +- 删除浮动图片 → `delete-float-image` + +关键区分:`write-image`(单元格内嵌图片,占据单元格内容)vs `create-float-image`(浮动图片,悬浮于单元格之上,不占内容) + +### 筛选视图 + +用户说"筛选/过滤/只看某些值/只显示满足条件的行/筛选数据/创建筛选/删除筛选/设置筛选条件/清除筛选/排序": +- 查看当前筛选 → `filter get` +- 创建筛选 → `filter create` +- 删除筛选 → `filter delete` +- 批量设置多列条件 → `filter update` +- 清除某一列条件 → `filter clear-criteria` +- 按列排序 → `filter sort` +- **区分全局筛选与筛选视图**:如果用户说"筛选视图"则走 `filter-view` 系列;如果只说"筛选/过滤/只看"则默认走全局 `filter` 系列 +- **禁止替代方案**:当用户要求"筛选/只看/仅保留某些行"时,必须通过 `filter create` / `filter update` 创建真实的筛选器。禁止用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替——这些做法会让原数据丢失或不可恢复 + +用户说"筛选视图/查看筛选视图/有哪些筛选视图/筛选视图列表": +- 获取所有筛选视图 → `filter-view list` + +用户说"筛选视图详情/查看某个筛选视图/筛选视图信息/筛选视图配置": +- 获取单个筛选视图详情 → `filter-view info` + +用户说"创建筛选视图/新建筛选视图/添加筛选视图": +- 创建筛选视图 → `filter-view create` + +用户说"更新筛选视图/修改筛选视图/改筛选视图名称/改筛选视图范围": +- 更新筛选视图属性 → `filter-view update` + +用户说"删除筛选视图/移除筛选视图": +- 删除筛选视图 → `filter-view delete` + +用户说"设置筛选条件/添加筛选条件/配置筛选视图条件/按值筛选/按条件筛选/按颜色筛选": +- 设置筛选视图列条件 → `filter-view update-criteria` + +用户说"查看筛选条件/有哪些筛选条件/筛选视图设了什么条件/列出筛选条件": +- 列出所有列条件 → `filter-view list-criteria` +- 查看某一列的条件 → `filter-view get-criteria --column N` + +用户说"清除筛选条件/移除筛选条件/取消筛选条件": +- 清除筛选视图列条件 → `filter-view delete-criteria` +- 注意与 `filter-view delete`(删除整个筛选视图)区分:`delete-criteria` 仅清除指定列的条件,不删除筛选视图本身 + +### 导出 + +用户说"导出/下载xlsx/存为Excel/存成表格文件/把表格变成xlsx/导出表格/下载表格/导出为 excel": +- 导出表格 → `export`(单命令一站式,内部自动完成提交、轮询、可选下载) +- 仅需传 `--node`,可选 `--output` 指定本地文件/目录(不传则返回 downloadUrl) +- 需要落盘到本地 → `dws sheet export --node --output `,命令自动下载 xlsx +- 禁止用 `range read` 全量读取后自行拼接 xlsx 来模拟导出,必须使用 `export` 命令(服务端原子导出,保留格式/合并/公式等属性) +- 禁止在 AI Agent 侧实现轮询或重试,CLI 内部已按渐进式退避策略完成(最多 30 次约 5 分钟) + +### URL 粘贴场景 用户直接粘贴表格 URL(无其他指令): -- 先 probe:`dws doc info --node --format json` -- `extension=axls` → `list` + `range read`(读取第一个工作表数据) -- `extension=xlsx`/`xls`/`xlsm`/`csv` → 转 `dws drive download --node --output ./` +- 先 probe:`dws doc info --node --format json` 校验 `contentType` 和 `extension` +- `extension=axls` → `list`(列出工作表)+ `range read`(读取第一个工作表数据) +- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws doc download --node --output ./`,告知用户"这是本地表格文件,已为你下载到本地",然后基于本地文件继续后续处理 用户粘贴 URL + 附加指令: -- probe 为 `axls` → 按 Reference 索引路由到对应命令 -- probe 为 xlsx/csv → 先 `dws drive download` 下载到本地,严禁调用 sheet 命令 +- 已 probe 为 `axls` 时: + - "帮我看看这个表格有什么数据" → `range read` + - "这个表格有哪些工作表" → `list` + - "往这个表格写入数据" → `range update` + - "帮我找一下表格里的XXX" → `find` +- probe 为 xlsx/xls/xlsm/csv 时:无论用户说"读数据/查看/分析",先走 `dws doc download` 下载到本地,由用户或后续步骤对本地 xlsx 进行解析,严禁调用 `sheet list` / `range read` 等命令 -## 导入本地表格 +关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录) vs doc(文档编辑/阅读) -用户说"导入Excel/把xlsx转为在线表格/上传表格并在线编辑"时: -```bash -# 上传并转换为在线电子表格(转换后返回 nodeId,可用 sheet 命令操作) -dws drive upload --file ./data.xlsx --convert +## 关键注意事项 -# 指定上传到某个文件夹 -dws drive upload --file ./data.xlsx --folder --convert +以下是最容易出错的规则,**必须严格遵守**: -# 指定上传到知识库 -dws drive upload --file ./data.xlsx --workspace --convert +- ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- ★ **`range update` 维度校验(强制)**:`--values` / `--hyperlinks` 的行列数必须与 `--range` 完全一致。例如 `--range "A1:C3"` → `--values` 必须是 3×3 数组 +- ★ **`range update` 清空规范(强制)**:清空单元格用空字符串 `""`,禁止用 `null`(全 null 会被跳过无效果) +- ★ **单次调用上限(强制)**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) +- ★ **大批量纯值写入用 `csv-put` 不用 `range update`**:当写入纯值(无公式/超链接)且数据量较大时(>5 行或 >20 单元格),必须使用 `csv-put`。`csv-put` 接受 CSV 文本直接写入,无需构造二维 JSON 数组,支持自动扩容,更简洁高效。仅在需要写入公式、超链接、或仅更新少量单元格时才使用 `range update` +- ★ **搜索用 `find` 不用 `range read`**:`find` 是服务端搜索,禁止用 `range read` 全量读取后客户端过滤 +- ★ **替换用 `replace` 不用 `range update`**:`replace` 是服务端原子操作,返回替换计数 +- ★ **移动用 `move-dimension` 不用 `range update`**:原子操作,保留格式和合并状态 +- ★ **单元格图片用 `write-image` 不用 `range update`**:`update_range` MCP 不支持图片参数,调用必失败 +- ★ **浮动图片用 `create-float-image` 不用 `write-image`**:两者用途不同——`write-image` 写入单元格内部,`create-float-image` 创建悬浮于单元格之上的浮动图片;`--src` 必须来自 `media-upload` 的 `resourceUrl` +- ★ **`export` 禁止自行轮询/重试**:CLI 内部已完成渐进式退避轮询(最多 30 次约 5 分钟),失败时直接告知用户稍后再试 +- ★ **关键区分**:sheet(在线电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) + +> 完整注意事项请参见本文档末尾「注意事项(完整版)」章节。 + +## 命令详细参考 + +> 以下为各命令的完整 Usage、Flags 和示例。参数不确定时也可直接执行 `dws sheet <命令> --help` 在线查看。 + +### 创建钉钉表格文档 +``` +Usage: + dws sheet create [flags] +Example: + dws sheet create --name "销售数据" + dws sheet create --name "Q1 数据" --folder + dws sheet create --name "知识库表格" --workspace +Flags: + --name string 表格名称 (必填) + --folder string 目标文件夹 ID (dentryUuid 格式) 或 URL;禁止传入纯数字 dentryId + --workspace string 目标知识库 ID ``` -- `--convert` 是关键参数,不加则仅上传为附件,不会转换为在线电子表格 -- 转换后的文档为 `axls` 格式,可用 `sheet` 全部命令操作 -- 支持 `.xlsx` / `.xls` / `.csv` 等格式 -## nodeId 说明 +> **ID 格式约束**:`--folder` 只接受 UUID 格式的 `fileId`(如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)或 alidocs 文件夹 URL。`drive list` 返回中有 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式)两个字段,**必须使用 `fileId`,禁止使用 `dentryId`**,传入纯数字会导致命令失败。 -`--node` 同时支持文档 ID、URL、分享链接。`drive list` 返回中必须用 `fileId`(UUID 格式),禁止用 `dentryId`(纯数字)。 +### 获取全部工作表列表 +``` +Usage: + dws sheet list [flags] +Example: + dws sheet list --node + dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/" +Flags: + --node string 表格文档 ID 或 URL (必填) +``` + +### 获取指定工作表详情 +``` +Usage: + dws sheet info [flags] +Example: + dws sheet info --node + dws sheet info --node --sheet-id + dws sheet info --node --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (不传则返回第一个工作表) +``` + +### 新建工作表 +``` +Usage: + dws sheet new [flags] +Example: + dws sheet new --node --name "Sheet2" + dws sheet new --node --name "数据汇总" +Flags: + --node string 表格文档 ID (必填) + --name string 工作表名称 (必填) +``` + +### 更新工作表属性 +``` +Usage: + dws sheet update [flags] +Example: + # 改名 + 调整冻结 + dws sheet update --node --sheet-id --title "汇总表" --frozen-row-count 2 --frozen-column-count 1 + + # 隐藏工作表 + dws sheet update --node --sheet-id --hidden=true + + # 显示工作表 + dws sheet update --node --sheet-id --hidden=false + + # 移动工作表到第一个位置 + dws sheet update --node --sheet-id --index 0 + + # 取消冻结 + dws sheet update --node --sheet-id --frozen-row-count 0 --frozen-column-count 0 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --title string 新标题,最长 100 字符,不能包含 / \ ? * [ ] : + --index int 新位置(从 0 开始) + --hidden --hidden=true 隐藏,--hidden=false 取消隐藏 + --frozen-row-count int 冻结行数,0 表示取消冻结 + --frozen-column-count int 冻结列数,0 表示取消冻结 +``` + +更新工作表标题、位置、隐藏状态、冻结行列。 +`--title` / `--index` / `--hidden` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 + +注意: +- 至少需要保留一个可见的工作表,不能将所有工作表都隐藏 +- 冻结行数/列数不能超过工作表的总行数/列数 + +### 复制工作表 +``` +Usage: + dws sheet copy [flags] +Example: + # 按默认位置复制 + dws sheet copy --node --sheet-id + + # 指定副本名称和位置 + dws sheet copy --node --sheet-id --title "销售副本" --index 2 + + # 只指定名称 + dws sheet copy --node --sheet-id --title "备份" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 源工作表 ID 或名称 (必填) + --title string 副本名称,最长 100 字符,不能包含 / \ ? * [ ] : (不传则系统自动生成) + --index int 副本位置(从 0 开始)(不传则放在源工作表之后) +``` + +复制指定工作表,在同一表格中创建一个副本。 +复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表中。 +传 `--index` 时,CLI 会先复制,再追加一次位置更新,把副本移动到目标索引。 +名称与已有工作表重复时系统会自动重命名。 + +### 读取工作表数据 +``` +Usage: + dws sheet range read [flags] # 别名: dws sheet range get +Example: + dws sheet range read --node + dws sheet range read --node --sheet-id + dws sheet range read --node --sheet-id "Sheet1" --range "A1:D10" + dws sheet range read --node --range "Sheet1!A1:D10" + + # 使用 get 别名,与 read 等价 + dws sheet range get --node --sheet-id --range "A1:D10" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (不传则默认第一个工作表) + --range string 读取范围,A1 表示法 (如 A1:D10,不传则读取全部数据) +``` + +**超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: +- 先通过 `info` 获取 `rowCount` / `lastNonEmptyRow` / `columnCount` 确定数据边界 +- 按行分批读取,如 `A1:J500`、`A501:J1000`、`A1001:J1500` …… +- 避免不传 `--range` 直接读取整个大工作表 + +### 更新工作表指定区域内容 +``` +Usage: + dws sheet range update [flags] +Example: + # 写入值 + dws sheet range update --node --sheet-id --range "A1:B2" \ + --values '[["姓名","分数"],["张三",90]]' + + # 写入公式 + dws sheet range update --node --sheet-id --range "C2" \ + --values '[["=A2&B2"]]' + + # 写入超链接 + dws sheet range update --node --sheet-id --range "A1" \ + --hyperlinks '[[{"type":"path","link":"https://dingtalk.com","text":"钉钉"}]]' + + # 清空区域(使用空字符串 "") + dws sheet range update --node --sheet-id --range "A1:B3" \ + --values '[["",""],["",""],["",""]]' +Flags: + --node string 表格文档 ID (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:B3 (必填) + --values string 单元格值,二维 JSON 数组 (与 --hyperlinks 至少传一项) + --hyperlinks string 超链接,二维 JSON 数组 (与 --values 至少传一项) +``` + +**单次调用建议**:行数 ≤ 1000,单元格总数(行×列)≤ 5000;超过时请拆分多次调用。 + +**何时该用 `csv-put` 替代**:如果你准备用 `range update` 写入纯值(不含公式和超链接),且数据量超过 5 行或 20 个单元格,应改用 `csv-put`——它接受 CSV 文本直接写入,无需手动拼装二维 JSON 数组,且支持自动扩容行列。仅在需要写入公式(`=SUM(...)`)、超链接(`--hyperlinks`)、或修改少量单元格时才使用 `range update`。 + +**范围职责**:`range update` 仅负责写入单元格的值与超链接,不接受任何样式参数。如需设置数字格式(百分比 / 货币 / 日期 / 文本等)请使用 `dws sheet range set-style --number-format <格式代码>`,可与其他样式参数同时传入。 + +### 设置单元格样式 +``` +Usage: + dws sheet range set-style [flags] +Example: + # 给 A1:B3 打上黄底粗体居中 + dws sheet range set-style --node --sheet-id --range "A1:B3" \ + --bg-color "#FFF2CC" --font-weight bold --h-align center + + # 给 C1:C5 逐单元格设置不同背景色 + dws sheet range set-style --node --sheet-id --range "C1:C5" \ + --bg-colors-json '[["#FF0000"],["#00FF00"],["#0000FF"],["#FFFF00"],["#FF00FF"]]' + + # 整片 range 启用自动换行 + dws sheet range set-style --node --sheet-id --range "A1:E10" --word-wrap autoWrap +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标区域,如 A1:B3 (必填) + --bg-color string 背景色(#RRGGBB),一键刷整个 range;与 --bg-colors-json 二选一 + --bg-colors-json string 背景色二维 JSON 数组,维度需与 --range 一致 + --font-size int 字号,一键刷整个 range;与 --font-sizes-json 二选一 + --font-sizes-json string 字号二维 JSON 数组 + --h-align string 水平对齐:left/center/right/general + --h-aligns-json string 水平对齐二维 JSON 数组 + --v-align string 垂直对齐:top/middle/bottom + --v-aligns-json string 垂直对齐二维 JSON 数组 + --font-color string 字体颜色(#RRGGBB) + --font-colors-json string 字体颜色二维 JSON 数组 + --font-weight string 字体粗细:bold/normal + --font-weights-json string 字体粗细二维 JSON 数组 + --word-wrap string 换行方式:overflow/clip/autoWrap(整个 range 共用) + --number-format string 数字格式,如 General/@/#,##0/0%/yyyy/m/d +``` + +**特性说明**: +- 每个样式维度提供两种写法,二选一:`--xxx`(单值刷整个 range,CLI 本地展开为二维数组)vs `--xxx-json`(逐单元格指定,维度需与 `--range` 完全一致) +- 至少需传入一个样式参数。单次调用建议:行数 ≤ 1000,单元格总数 ≤ 5000 +- 枚举值按驼峰书写:`autoWrap`、`bold`、`normal`、`center` 等 + +### 批量设置单元格样式 +``` +Usage: + dws sheet range batch-set-style [flags] +Example: + dws sheet range batch-set-style --node --batch ./styles.json + dws sheet range batch-set-style --node --batch ./styles.json --continue-on-error +Flags: + --node string 表格文档 ID 或 URL (必填) + --batch string 批次配置 JSON 文件路径 (必填) + --continue-on-error 遇到失败时继续执行后续条目(默认遇错即停) +``` + +配置文件格式(JSON 数组,每个元素一条批次项): +```json +[ + { + "sheetId": "Sheet1", + "range": "A1:B3", + "bgColor": "#FFF2CC", + "fontSize": 12, + "hAlign": "center", + "vAlign": "middle", + "fontColor": "#333333", + "fontWeight": "bold", + "wordWrap": "autoWrap", + "numberFormat": "General" + }, + { + "sheetId": "Sheet1", + "range": "C1:C5", + "bgColorsJson": "[[\"#FF0000\"],[\"#00FF00\"],[\"#0000FF\"],[\"#FFFF00\"],[\"#FF00FF\"]]" + } +] +``` + +**特性说明**: +- CLI 侧顺序循环逐条调用 `update_range`(非服务端批量),运行时输出 `[N/M]` 进度 +- 每条记录执行与 `set-style` 一致的校验:至少一项样式字段 + rows ≤ 1000 + rows×cols ≤ 30000 + 枚举合法 +- 默认遇错即停(返回非 0),`--continue-on-error` 时所有条目跑完再返回首个错误 + +### 在工作表中搜索单元格内容 +``` +Usage: + dws sheet find [flags] +Example: + # 基本搜索 + dws sheet find --node --sheet-id --find "销售额" + + # 在指定范围内搜索 + dws sheet find --node --sheet-id --find "合计" --range "A1:D100" + + # 正则表达式搜索(不区分大小写) + dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false + + # 精确匹配整个单元格内容 + dws sheet find --node --sheet-id --find "完成" --match-entire-cell + + # 搜索公式文本 + dws sheet find --node --sheet-id --find "SUM" --match-formula +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --find string 搜索文本 (必填) + --range string 搜索范围,A1 表示法 (如 A1:D10) + --match-case 区分大小写 (默认 true) + --match-entire-cell 精确匹配整个单元格内容 + --use-regexp 启用正则表达式搜索 + --match-formula 搜索公式文本而非显示值 + --include-hidden 包含隐藏单元格 +``` + +### 在工作表末尾追加数据 +``` +Usage: + dws sheet append [flags] +Example: + dws sheet append --node --sheet-id --values '[["张三","销售部",50000]]' + dws sheet append --node --sheet-id "Sheet1" \ + --values '[["李四","市场部",38000],["王五","销售部",62000]]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --values string 追加数据,二维 JSON 数组 (必填) +``` + +`--values` 为二维 JSON 数组,外层每个元素代表一行,内层每个元素代表一个单元格值。 +追加的数据列数应与工作表已有数据的列数保持一致。 + +### 将 CSV 数据写入指定位置 +``` +Usage: + dws sheet csv-put [flags] +Example: + dws sheet csv-put --node --sheet-id --start-cell A1 \ + --csv 'name,score\nAlice,95\nBob,87' + + dws sheet csv-put --node --sheet-id --start-cell B2 \ + --csv @data.csv --allow-overwrite + + cat data.csv | dws sheet csv-put --node --sheet-id \ + --start-cell A1 --csv - + + dws sheet csv-put --node --sheet-id --start-cell A1 \ + --csv @data.csv --dry-run +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --csv string CSV 文本、@文件路径 或 - 表示 stdin (必填) + --start-cell string 起始单元格,A1 表示法 (必填) + --allow-overwrite 允许覆盖已有数据 (默认 false) +``` + +将 RFC 4180 格式的 CSV 文本写入指定工作表的指定单元格位置。 +- 只写纯值,不支持公式/样式/批注。`=` 开头的内容当文本处理,不会被解析为公式 +- 数字/日期/百分数由表格引擎自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) +- 自动扩容行列:CSV 数据超出当前工作表维度时自动追加行/列 +- 目标区域如含合并单元格,合并将被打散,值正常写入 +- `--allow-overwrite` 默认 false,目标区域有数据时需显式传 `--allow-overwrite` 才能覆盖 +- `--csv` 支持三种输入:直接传文本、`@filepath` 从本地文件读取、`-` 从 stdin 管道读取 +- CSV 文本上限 2M 字符,单元格总数上限 30000 +- 特殊字符处理:CLI 会自动过滤 `\r`(Windows 换行符)和 BOM(UTF-8 文件头标记),Excel/Windows 导出的 CSV 可直接使用;如 CSV 数据中含零宽字符(U+200B 等)或 Bidi 控制符,CLI 会拒绝并报错 + +### 在指定位置插入行或列 +``` +Usage: + dws sheet insert-dimension [flags] +Example: + # 在第 3 行之前插入 2 行 + dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 + + # 在 A 列之前插入 1 列 + dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 + + # 在 AB 列之前插入 3 列 + dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 插入维度: ROWS 或 COLUMNS (必填) + --position string 插入位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 插入数量,正整数 (必填),最大 5000 +``` + +在钉钉表格指定工作表的指定位置之前插入若干空行或空列。 +`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 +支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +若需要在末尾追加行/列,请使用 `append` 命令。 + +### 删除指定位置的行或列 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet delete-dimension [flags] +Example: + # 从第 3 行开始删除 2 行 + dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 + + # 从 A 列开始删除 1 列 + dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 + + # 从 AB 列开始删除 3 列 + dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 删除维度: ROWS 或 COLUMNS (必填) + --position string 删除起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 删除数量,正整数 (必填),最大 5000 +``` + +在钉钉表格指定工作表中,从指定位置起删除若干连续的行或列。 +`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 +支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +删除后后续的行/列会向前移动填补空位;若需要仅清空内容但保留行/列占位,请使用 `range update` 将目标区域写入空字符串 `""`。 + +### 更新指定范围行/列属性 +``` +Usage: + dws sheet update-dimension [flags] +Example: + # 隐藏第 3~4 行 + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "3" --length 2 --hidden + + # 显示 A~B 列 + dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "A" --length 2 --hidden=false + + # 设置第 1~5 行行高为 40px + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "1" --length 5 --pixel-size 40 + + # 设置 C 列列宽为 200px 并隐藏 + dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "Sheet1!3" --length 2 --hidden +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 更新维度: ROWS 或 COLUMNS (必填) + --start-index string 起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 更新数量,正整数 (必填),最大 5000 + --hidden 是否隐藏 (true=隐藏, false=显示),与 --pixel-size 至少填其一 + --pixel-size int 行高或列宽(像素),ROWS 时为行高,COLUMNS 时为列宽,与 --hidden 至少填其一 +``` + +批量更新钉钉表格指定工作表中连续多行/多列的属性,支持设置显隐状态(hidden)与行高/列宽(pixelSize)。 +`--dimension ROWS` 时,`--start-index` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--start-index` 为列字母。 +支持在 `--start-index` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +`--hidden` 与 `--pixel-size` 至少必须提供一个。当同时提供时,将先应用尺寸再应用显隐,任一失败整体失败。 +`--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽。 + +### 合并单元格 +``` +Usage: + dws sheet merge-cells [flags] +Example: + # 合并所有单元格(默认) + dws sheet merge-cells --node --sheet-id --range "A1:B3" + + # 按行合并 + dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows + + # 按列合并 + dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns + + # 使用带工作表前缀的范围(忽略 --sheet-id) + dws sheet merge-cells --node --sheet-id --range "Sheet1!A1:B3" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:B3 (必填) + --merge-type string 合并方式: mergeAll(默认)/mergeRows/mergeColumns +``` + +支持三种合并方式: +- `mergeAll`(默认):合并所有单元格,将选定区域内的所有单元格合并成一个 +- `mergeRows`:按行合并,在选定区域内将同一行相邻的单元格合并 +- `mergeColumns`:按列合并,在选定区域内将同一列相邻的单元格合并 + +注意:合并时只保留左上角单元格的值,其他单元格的值会被丢弃。 +`--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时将优先使用前缀解析出的工作表,忽略 `--sheet-id`。 + +### 上传附件到表格 +``` +Usage: + dws sheet media-upload [flags] +Example: + dws sheet media-upload --node --file ./report.pdf + dws sheet media-upload --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream +Flags: + --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) + --file string 本地文件路径 (必填) + --name string 附件显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) +``` + +### 上传图片并写入表格单元格 +``` +Usage: + dws sheet write-image [flags] +Example: + dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png + dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 +Flags: + --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:A1 (必填) + --file string 本地图片文件路径 (必填) + --name string 图片显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) + --width int 图片显示宽度 (可选) + --height int 图片显示高度 (可选) +``` + +### 创建浮动图片 +``` +Usage: + dws sheet create-float-image [flags] +Example: + # 先上传图片获取 resourceUrl + dws sheet media-upload --node --file ./chart.png + # 输出: resourceUrl: /core/api/resources/img/xxxx... + + # 再创建浮动图片 + dws sheet create-float-image --node --sheet-id \ + --src "/core/api/resources/img/xxxx..." --range A1 --width 400 --height 300 + + # 带偏移量 + dws sheet create-float-image --node --sheet-id \ + --src "/core/api/resources/img/xxxx..." --range B2 --width 200 --height 150 --offset-x 10 --offset-y 20 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --src string 图片资源路径,通过 media-upload 获取的 resourceUrl (必填) + --range string 锚点单元格,A1 表示法,如 A1、B3 (必填) + --width int 图片宽度,像素,正整数 (必填) + --height int 图片高度,像素,正整数 (必填) + --offset-x int 水平偏移量,像素 (默认 0) + --offset-y int 垂直偏移量,像素 (默认 0) +``` + +浮动图片悬浮于单元格之上,不占用单元格内容,可自由定位和调整大小。 +- `--src` 必须是 `media-upload` 返回的 `resourceUrl`(格式为 `/core/api/resources/img/...`),不能直接传外部 URL +- `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) +- `--width` / `--height` 为必填,单位像素,必须为正整数 +- `--offset-x` / `--offset-y` 表示相对锚点单元格左上角的偏移量(像素),默认 0,不能为负数 + +### 获取浮动图片详情 +``` +Usage: + dws sheet get-float-image [flags] +Example: + dws sheet get-float-image --node --sheet-id --float-image-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) +``` + +获取单个浮动图片的详细信息,包括 ID、图片资源路径、锚点位置、尺寸和偏移量。 +`--float-image-id` 可通过 `list-float-images` 获取。 + +### 列出工作表所有浮动图片 +``` +Usage: + dws sheet list-float-images [flags] +Example: + dws sheet list-float-images --node --sheet-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +列出指定工作表中所有浮动图片,返回 `floatImages` 数组和 `totalCount`。 + +### 更新浮动图片属性 +``` +Usage: + dws sheet update-float-image [flags] +Example: + # 移动浮动图片到新位置 + dws sheet update-float-image --node --sheet-id --float-image-id --range C5 + + # 调整尺寸 + dws sheet update-float-image --node --sheet-id --float-image-id --width 600 --height 400 + + # 替换图片(需先 media-upload 新图片获取 resourceUrl) + dws sheet update-float-image --node --sheet-id --float-image-id \ + --src "/core/api/resources/img/xxxx..." +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) + --src string 新的图片资源路径,通过 media-upload 获取的 resourceUrl + --range string 新的锚点单元格,A1 表示法 + --width int 新的图片宽度,像素 + --height int 新的图片高度,像素 + --offset-x int 新的水平偏移量,像素 + --offset-y int 新的垂直偏移量,像素 +``` + +更新浮动图片的属性,`--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少传入一个。 +`--float-image-id` 可通过 `list-float-images` 获取。 + +### 删除浮动图片 +``` +Usage: + dws sheet delete-float-image [flags] +Example: + dws sheet delete-float-image --node --sheet-id --float-image-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) +``` + +删除指定的浮动图片,操作不可恢复。`--float-image-id` 可通过 `list-float-images` 获取。 + +### 全局查找替换 +``` +Usage: + dws sheet replace [flags] +Example: + dws sheet replace --node --sheet-id --find "旧文本" --replacement "新文本" + dws sheet replace --node --sheet-id --find "待处理" --replacement "已完成" --match-entire-cell + dws sheet replace --node --sheet-id --find "\\d{4}" --replacement "****" --use-regexp + dws sheet replace --node --sheet-id --find "旧" --replacement "新" --range "A1:D100" + dws sheet replace --node --sheet-id --find "临时" --replacement "" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --find string 查找文本 (必填) + --replacement string 替换文本 (必填,可为空字符串表示删除) + --range string 替换范围,A1 表示法 (如 A1:D100) + --match-case 区分大小写 (默认 false) + --match-entire-cell 完整单元格匹配 + --use-regexp 启用正则表达式匹配 + --include-hidden 包含隐藏行/列 +``` + +返回被替换的单元格数量。`--replacement` 可以为空字符串,表示删除匹配内容。 + +### 移动行或列 +``` +Usage: + dws sheet move-dimension [flags] +Example: + # 将第 2 行移动到第 5 行的位置(索引从 0 开始) + dws sheet move-dimension --node --sheet-id \ + --dimension ROWS --start-index 1 --end-index 1 --destination-index 4 + + # 将第 2~4 行移动到第 1 行之前 + dws sheet move-dimension --node --sheet-id \ + --dimension ROWS --start-index 1 --end-index 3 --destination-index 0 + + # 将 B~C 列移动到 E 列的位置 + dws sheet move-dimension --node --sheet-id \ + --dimension COLUMNS --start-index 1 --end-index 2 --destination-index 4 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 维度类型: ROWS 或 COLUMNS (必填) + --start-index int 源起始索引,0-based (必填) + --end-index int 源结束索引,0-based,包含 (必填) + --destination-index int 目标位置索引,0-based (必填) +``` + +索引均为 0-based(第 1 行/列的索引为 0)。destination-index 不能在 [start-index, end-index] 范围内。 + +**destination-index 计算规则:** +destination-index 是目标位置的 0-based 索引,即移动到第 n 行/列则传 n-1: +- 通用公式:`destination-index = 目标行号(1-based) - 1` +- 例如:将第 2 行移到第 5 行位置 → `destination-index = 5 - 1 = 4`,即 `start-index=1, end-index=1, destination-index=4` +- 例如:将第 4 行移到第 1 行(最前面)→ `destination-index = 1 - 1 = 0`,即 `start-index=3, end-index=3, destination-index=0` + +### 追加空行或空列 +``` +Usage: + dws sheet add-dimension [flags] +Example: + dws sheet add-dimension --node --sheet-id --dimension ROWS --length 5 + dws sheet add-dimension --node --sheet-id --dimension COLUMNS --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 维度类型: ROWS 或 COLUMNS (必填) + --length int 追加数量,正整数,最多 5000 (必填) +``` + +在工作表末尾追加指定数量的空行或空列。 + +### 取消合并单元格 +``` +Usage: + dws sheet unmerge-cells [flags] +Example: + dws sheet unmerge-cells --node --sheet-id --range "A1:D5" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 取消合并的范围,A1 表示法 (必填) +``` + +取消指定范围内所有合并的单元格,恢复为独立单元格。 + +### 设置下拉列表 +``` +Usage: + dws sheet set-dropdown [flags] +Example: + # 设置单选下拉列表 + dws sheet set-dropdown --node --sheet-id --range "A2:A100" \ + --options '[{"value":"选项1"},{"value":"选项2"},{"value":"选项3"}]' + + # 设置带颜色的多选下拉列表 + dws sheet set-dropdown --node --sheet-id --range "B2:B50" \ + --options '[{"value":"高","color":"#ff0000"},{"value":"中","color":"#ffaa00"},{"value":"低","color":"#00ff00"}]' \ + --multi-select +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格范围,A1 表示法,如 A2:A100 (必填) + --options string 下拉选项 JSON 数组 (必填),如 '[{"value":"选项1","color":"#ff0000"}]' + --multi-select 是否允许多选(默认单选) +``` + +在指定单元格范围内设置下拉列表。设置后用户可从预定义选项中选择值。 +- **用途**:为单元格配置下拉列表,支持自定义选项颜色和多选。 +- **场景**:规范数据输入,如状态选择(完成/进行中/待处理)、优先级(高/中/低)等。 +- **注意**:选项值不能包含英文逗号;如果目标范围已存在下拉列表,会被新配置覆盖。 + +### 获取下拉列表配置 +``` +Usage: + dws sheet get-dropdown [flags] +Example: + dws sheet get-dropdown --node --sheet-id --range "A2:A100" + dws sheet get-dropdown --node --sheet-id --range "A1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 查询范围,A1 表示法,如 A1:A100 (必填) +``` + +查询指定范围内的下拉列表配置信息,包括选项值、颜色和是否多选。 +- **用途**:查看单元格已设置的下拉列表选项和配置。 +- **场景**:在修改下拉列表前先查询现有配置;确认下拉列表是否设置成功。 +- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `enableMultiSelect` 和 `colorValueMap`)。范围内无下拉列表时 `hasDropdown` 为 false。 + +### 删除下拉列表 +``` +Usage: + dws sheet delete-dropdown [flags] +Example: + dws sheet delete-dropdown --node --sheet-id --range "A2:A100" + dws sheet delete-dropdown --node --sheet-id --range "B1:D10" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 要删除下拉列表的范围,A1 表示法 (必填) +``` + +删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。 +- **用途**:移除不再需要的下拉列表约束。 +- **注意**:已填写的单元格值不会被清除;目标范围不存在下拉列表时操作仍返回成功。 + +### 获取筛选信息 +``` +Usage: + dws sheet filter get [flags] +Example: + dws sheet filter get --node --sheet-id + dws sheet filter get --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +获取指定工作表的全局筛选信息,返回筛选范围和各列的筛选条件详情。 +- **用途**:查看当前工作表上是否存在全局筛选及其配置。 +- **场景**:在修改或删除筛选前,先读取当前筛选配置;创建筛选前先确认是否已存在(每个工作表只能有一个筛选)。 +- **区分**:全局筛选(filter)影响所有协作者看到的数据展示;筛选视图(filter-view)是个人化的。 +- **返回**:`range`(筛选范围,A1 表示法)和 `columnFilterCriteria`(各列条件,key 为列偏移量)。如果未设置筛选,返回筛选信息为空。 + +### 创建筛选 +``` +Usage: + dws sheet filter create [flags] +Example: + # 创建筛选框架(不设条件) + dws sheet filter create --node --sheet-id --range "A1:E100" + + # 创建筛选并同时设置条件(按值筛选) + dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":1,"filterType":"values","visibleValues":["北京","上海"]}]' + + # 创建筛选并设置条件筛选 + dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 筛选范围,A1 表示法,须包含表头行 (必填) + --criteria string 筛选条件 JSON 数组 (可选) +``` + +在工作表中创建全局筛选。 +- **用途**:为工作表建立筛选器,使数据可按条件过滤展示。 +- **约束**:每个工作表只能有一个全局筛选,已存在时会报错。应先 `filter get` 确认不存在后再创建。 +- **range 规范**:必须包含表头行(如 `A1:E100`),不能只包含数据行。 +- **criteria 格式**:JSON 数组,每个元素含 `column`(列偏移量,从 0 开始)和筛选条件字段。不传则仅创建空筛选框架,后续可通过 `filter update` 设置条件。 + +### 删除筛选 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet filter delete [flags] +Example: + dws sheet filter delete --node --sheet-id --yes +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +删除工作表的全局筛选。 +- **用途**:移除筛选器,所有被隐藏的行将重新显示。 +- **不可逆**:删除后所有筛选条件丢失,需重新创建。 +- **前置**:工作表没有筛选时调用会报错,应先 `filter get` 确认存在。 + +### 批量更新筛选条件 +``` +Usage: + dws sheet filter update [flags] +Example: + # 同时设置多列的筛选条件 + dws sheet filter update --node --sheet-id --criteria '[{"column":0,"filterType":"values","visibleValues":["已完成","进行中"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50"}]}]' + + # 按颜色筛选 + dws sheet filter update --node --sheet-id --criteria '[{"column":1,"filterType":"color","backgroundColor":"#FF0000"}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --criteria string 筛选条件 JSON 数组 (必填) +``` + +批量更新筛选条件,可同时设置多列的筛选条件。 +- **用途**:一次性设置或替换多列的筛选条件。 +- **前置**:工作表必须已创建筛选(通过 `filter create`)。 +- **覆盖式**:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置。 +- **criteria 格式**:JSON 数组,支持三种 `filterType`: + - `values`:按值筛选,指定 `visibleValues` 数组 + - `condition`:按条件筛选,指定 `conditions` 数组(最多 2 个)和可选的 `conditionOperator`(`and`/`or`) + - `color`:按颜色筛选,指定 `backgroundColor` 或 `fontColor`(二选一) + +### 清除单列筛选条件 +``` +Usage: + dws sheet filter clear-criteria [flags] +Example: + # 清除第 2 列(B 列)的筛选条件 + dws sheet filter clear-criteria --node --sheet-id --column 1 + + # 清除第 1 列(A 列)的筛选条件 + dws sheet filter clear-criteria --node --sheet-id --column 0 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --column number 列偏移量,从 0 开始 (必填) +``` + +清除筛选中某一列的筛选条件。 +- **用途**:移除某列的筛选条件,该列不再参与筛选计算。 +- **区分**:仅清除指定列的条件,不删除整个筛选。如需删除整个筛选,使用 `filter delete`。 +- **幂等**:指定列没有设置筛选条件时调用不会报错。 + +### 筛选排序 +``` +Usage: + dws sheet filter sort [flags] +Example: + # 按第 1 列(A 列)升序排序 + dws sheet filter sort --node --sheet-id --column 0 --ascending + + # 按第 3 列(C 列)降序排序 + dws sheet filter sort --node --sheet-id --column 2 --ascending=false +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --column number 排序列偏移量,从 0 开始 (必填) + --ascending 是否升序,默认 true (可选) +``` + +对筛选范围内的数据按指定列排序。 +- **用途**:对数据行按某一列的值进行升序或降序排列。 +- **前置**:工作表必须已创建筛选(通过 `filter create`)。 +- **注意**:排序会实际改变工作表中数据行的物理顺序,不可撤销。 +- **column**:列偏移量从 0 开始,相对于筛选范围首列。 + +### 获取所有筛选视图 +``` +Usage: + dws sheet filter-view list [flags] +Example: + dws sheet filter-view list --node --sheet-id + dws sheet filter-view list --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +获取指定工作表的所有筛选视图列表,返回每个筛选视图的 ID、名称和范围信息。 +- **用途**:查看当前工作表上已创建的所有筛选视图,获取视图 ID、名称和范围。 +- **场景**:在对筛选视图进行 update / delete / update-criteria 等操作前,先用 list 获取可用的 filterViewId。 +- **区分**:筛选视图(filter-view)是个人化的数据过滤方式,与全局筛选不同。每个用户可以创建自己的筛选视图,互不影响原始数据。如果没有筛选视图,返回空列表。 + +### 创建筛选视图 +``` +Usage: + dws sheet filter-view create [flags] +Example: + # 创建不带筛选条件的筛选视图 + dws sheet filter-view create --node --sheet-id --name "我的视图" --range "A1:E10" + + # 创建带按值筛选条件的筛选视图 + dws sheet filter-view create --node --sheet-id --name "销售筛选" --range "A1:E10" \ + --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]}]' + + # 创建带按条件筛选的筛选视图(大于等于 200000) + dws sheet filter-view create --node --sheet-id --name "高预算" --range "A1:C10" \ + --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --name string 筛选视图名称 (必填) + --range string 筛选视图范围,A1 表示法,如 A1:E10 (必填) + --criteria string 筛选条件,JSON 数组 (可选) +``` + +在指定工作表中创建一个筛选视图。 +- **用途**:为指定数据区域创建一个可命名的个人化筛选视图,可选同时设置筛选条件。 +- **场景**:用户需要针对某个数据区域建立固定的筛选视角(如"高绩效员工""研发部数据"),方便反复查看。 +- **区分**:与全局筛选不同,筛选视图是个人化的,不影响其他用户看到的数据。如果只需创建视图不设条件,后续可通过 `update-criteria` 单独设置;如果要一步到位,可通过 `--criteria` 在创建时直接设置。 +`--criteria` 为 JSON 数组,每个元素包含 `column`(列偏移量,从 0 开始)和筛选条件字段。支持三种筛选类型: +- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 +- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符(kebab-case):`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) +- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 + +### 更新筛选视图属性 +``` +Usage: + dws sheet filter-view update [flags] +Example: + # 更新筛选视图名称 + dws sheet filter-view update --node --sheet-id --filter-view-id --name "新名称" + + # 更新筛选视图范围 + dws sheet filter-view update --node --sheet-id --filter-view-id --range "A1:F20" + + # 更新筛选条件 + dws sheet filter-view update --node --sheet-id --filter-view-id \ + --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --name string 筛选视图新名称 + --range string 筛选视图新范围,A1 表示法 + --criteria string 筛选条件,JSON 数组 +``` + +更新筛选视图的名称、范围和/或筛选条件,`--name`、`--range`、`--criteria` 至少传入一个。 +- **用途**:修改已有筛选视图的名称、数据范围或筛选条件。 +- **场景**:数据区域扩展后需要扩大筛选视图范围,或重命名视图,或通过 `--criteria` 一次性批量更新多列筛选条件。 +- **区分**:`update` 可同时修改名称、范围和条件,适合批量更新;`update-criteria` 只能设置单列条件,适合精确控制某一列的筛选逻辑。`--criteria` 指定列的条件会被替换,未指定的列保持不变。 + +`--criteria` 为 JSON 数组,格式与 `filter-view create` 的 `--criteria` 相同,支持的筛选类型和操作符参见「创建筛选视图」说明。 + +### 删除筛选视图 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet filter-view delete [flags] +Example: + dws sheet filter-view delete --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +删除指定的筛选视图。 +- **用途**:永久删除一个不再需要的筛选视图及其所有筛选条件。 +- **场景**:筛选视图已过时或不再需要时,清理无用的视图。 +- **区分**:`delete` 删除整个筛选视图(包括所有列的条件),操作不可恢复;`delete-criteria` 只删除某一列的筛选条件,视图本身保留。此操作不影响全局筛选或其他筛选视图,也不影响原始数据。 + +### 更新筛选视图列条件 +``` +Usage: + dws sheet filter-view update-criteria [flags] +Example: + # 按值筛选:只显示"销售部"和"市场部" + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' + + # 按条件筛选:大于 100 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' + + # 按条件筛选:大于等于 200000 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}' + + # 按条件筛选:小于 100 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"less","value":"100"}]}' + + # 多条件筛选:大于等于 60 且 小于等于 90 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditionOperator":"and","conditions":[{"operator":"greater-equal","value":"60"},{"operator":"less-equal","value":"90"}]}' + + # 按颜色筛选:背景色为红色 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"color","backgroundColor":"#FF0000"}' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) + --filter-criteria string 筛选条件,JSON 对象 (必填) +``` + +更新筛选视图中某一列的筛选条件。 +- **用途**:为筛选视图的指定列创建或更新筛选条件,控制该列哪些数据行可见。 +- **场景**:只显示某些特定值的行(如"只看研发部")→ `filterType: values`;按数值条件筛选(如"绩效 ≥ 85")→ `filterType: condition` + `operator: greater-equal`;按文本条件筛选(如"名称包含关键字")→ `filterType: condition` + `operator: contains`。 +- **区分**:`update-criteria` 精确控制单列条件,适合逐列设置不同的筛选逻辑;`filter-view update --criteria` 可以批量更新多列条件;`delete-criteria` 是 `update-criteria` 的逆操作,删除指定列的条件。 + +`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 +例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列,`--column 1` 代表 C 列。 + +`--filter-criteria` 为 JSON 对象,支持三种筛选类型: +- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 +- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符:`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) +- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 + +### 删除筛选视图列条件 +``` +Usage: + dws sheet filter-view delete-criteria [flags] +Example: + # 删除第 1 列(A 列)的筛选条件 + dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 0 + + # 删除第 3 列(C 列)的筛选条件 + dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 2 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) +``` + +清除筛选视图中指定列的筛选条件。 +- **用途**:移除筛选视图中指定列的筛选条件,使该列不再参与过滤。 +- **场景**:之前通过 `update-criteria` 设置了某列的筛选条件,现在需要取消该列的筛选以显示全部数据。 +- **区分**:`delete-criteria` 只清除指定列的条件,筛选视图本身和其他列的条件保持不变;`delete` 会删除整个筛选视图。如果指定列没有设置筛选条件,调用此命令不会报错(幂等操作)。 + +### 获取单个筛选视图详情 +``` +Usage: + dws sheet filter-view info [flags] +Example: + # 查看指定筛选视图的详情 + dws sheet filter-view info --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +获取指定筛选视图的完整信息,包括 ID、名称、范围和筛选条件。 +- **用途**:查看某个筛选视图的当前配置,包括已设置的所有筛选条件详情。 +- **场景**:在修改或删除筛选视图前,先确认其当前状态;或在 `update-criteria` 后验证条件是否生效。 +- **区分**:`info` 返回单个视图的完整信息(含 criteria);`list` 返回所有视图的列表概要。`info` 需要指定 `--filter-view-id`,ID 可通过 `list` 获取。 +- **实现**:内部调用 `get_filter_views` 获取全部列表后按 ID 过滤。 + +### 列出筛选视图所有列条件 +``` +Usage: + dws sheet filter-view list-criteria [flags] +Example: + # 列出筛选视图的所有条件 + dws sheet filter-view list-criteria --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +列出指定筛选视图中已设置的所有列筛选条件。 +- **用途**:查看某个筛选视图当前设置了哪些列的筛选条件,包括每列的条件类型和具体规则。 +- **场景**:在管理筛选条件(修改/删除特定列条件)前,先了解当前视图有哪些条件;或排查筛选结果不符合预期时检查条件配置。 +- **区分**:`list-criteria` 返回所有列的条件(按列偏移量为 key 的对象);`get-criteria` 只返回指定列的条件。如果没有设置任何条件,返回空对象 `{}`。 +- **实现**:内部调用 `get_filter_views` 获取视图详情后提取 `criteria` 字段。 + +### 获取单列筛选条件 +``` +Usage: + dws sheet filter-view get-criteria [flags] +Example: + # 查看第 1 列(偏移量 0)的筛选条件 + dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 0 + + # 查看第 3 列(偏移量 2)的筛选条件 + dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 2 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) +``` + +获取指定筛选视图中某一列的筛选条件详情。 +- **用途**:查看某个筛选视图中指定列当前设置的筛选条件,包括条件类型、运算符和比较值。 +- **场景**:在修改某列条件前,先查看其当前配置;或验证 `update-criteria` 后该列条件是否正确。 +- **区分**:`get-criteria` 只返回指定列的条件;`list-criteria` 返回所有列的条件。`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 +- **实现**:内部调用 `get_filter_views` 获取视图详情后按列偏移量过滤 `criteria` 中的对应条件。 + +### 导出表格为 xlsx(异步任务一站式) +``` +Usage: + dws sheet export [flags] # 一站式:提交 → 轮询 → 可选下载 +Example: + # 仅导出,返回 downloadUrl(链接有时效性,请尽快下载) + dws sheet export --node + dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" + + # 导出并自动下载为本地文件 + dws sheet export --node --output ./report.xlsx + + # --output 为目录时,自动按下载链接中的文件名保存 + dws sheet export --node --output ./ + +Flags: + --node string 表格文档 ID 或 URL (必填) + --output string 本地保存路径(可选,支持文件路径或目录) +``` + +将钉钉在线电子表格导出为 Office xlsx 格式。**单命令一站式**:命令内部自动完成「提交任务 → 渐进式退避轮询 → (可选)下载文件」全流程,AI Agent 无需自行拆分步骤或实现轮询。 + +**内部流程**: +1. 调 `submit_export_job` 获取 `jobId` +2. 按渐进式退避策略轮询 `query_export_job` 直至任务终态或超时 +3. 任务成功后取得 `downloadUrl`;若指定了 `--output`,自动 HTTP GET 下载 xlsx 到本地文件 + +**内置轮询策略(CLI 内实现,无需关心)**: +- 第 1~5 次:每次间隔 2 秒 +- 第 6~10 次:每次间隔 5 秒 +- 第 11~20 次:每次间隔 10 秒 +- 第 21~30 次:每次间隔 15 秒 +- **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令返回错误 + +**命令返回**: +- `--output` 未指定:进度日志 + 末尾输出 `jobId` 和 `downloadUrl`(链接有时效性,请尽快下载) +- `--output` 指定为文件路径:下载到该路径并输出 `导出完成: ` +- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名并保存到该目录下 + +**失败处理(命令内部已处理,Agent 仅需转述)**: +- MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 +- 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 + +**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 + +## 核心工作流 + +```bash +# ── 工作流 1: 创建表格并写入数据 ── + +# 1. 创建表格文档 — 提取 nodeId +dws sheet create --name "销售数据" --format json + +# 2. 查看工作表列表 — 提取 sheetId +dws sheet list --node --format json + +# 3. 写入表头和数据 +dws sheet range update --node --sheet-id --range "A1:C1" \ + --values '[["姓名","部门","销售额"]]' --format json + +dws sheet range update --node --sheet-id --range "A2:C4" \ + --values '[["张三","销售部",50000],["李四","市场部",38000],["王五","销售部",62000]]' --format json + +# ── 工作流 2: 读取已有表格数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 查看工作表详情(行列数、最后非空位置等) +dws sheet info --node --sheet-id --format json + +# 3. 读取全部数据 +dws sheet range read --node --sheet-id --format json + +# 4. 读取指定区域 +dws sheet range read --node --sheet-id --range "A1:D10" --format json + +# ── 工作流 3: 多工作表管理 ── + +# 1. 新建工作表 +dws sheet new --node --name "汇总" --format json + +# 2. 在新工作表中写入汇总公式 +dws sheet range update --node --sheet-id --range "A1:B1" \ + --values '[["指标","数值"]]' --format json + +dws sheet range update --node --sheet-id --range "A2:B2" \ + --values '[["总销售额","=SUM(Sheet1!C2:C100)"]]' --format json + +# ── 工作流 4: 写入数据并设置样式 ── + +# 1. 写入数据 +dws sheet range update --node --sheet-id --range "A1:C3" \ + --values '[["商品","单价","数量"],["苹果",5.5,100],["香蕉",3.2,200]]' --format json + +# 2. 设置数字格式(人民币)——请走 set-style,不要放到 range update +dws sheet range set-style --node --sheet-id --range "B2:B3" \ + --number-format "¥#,##0.00" --format json + +# 3. 写入超链接 +dws sheet range update --node --sheet-id --range "D1" \ + --hyperlinks '[[{"type":"path","link":"https://dingtalk.com","text":"详情"}]]' --format json + +# ── 工作流 5: 追加数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 查看工作表详情(确认列结构) +dws sheet info --node --sheet-id --format json + +# 3. 追加单行数据 +dws sheet append --node --sheet-id \ + --values '[["张三","销售部",50000]]' --format json + +# 4. 追加多行数据 +dws sheet append --node --sheet-id \ + --values '[["李四","市场部",38000],["王五","销售部",62000]]' --format json +``` + +```bash +# ── 工作流 6: 插入行或列 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 在第 3 行之前插入 2 行 +dws sheet insert-dimension --node --sheet-id \ + --dimension ROWS --position "3" --length 2 --format json + +# 3. 在 A 列之前插入 1 列 +dws sheet insert-dimension --node --sheet-id \ + --dimension COLUMNS --position "A" --length 1 --format json + +# 4. 使用工作表前缀指定位置 +dws sheet insert-dimension --node --sheet-id \ + --dimension ROWS --position "Sheet1!5" --length 3 --format json +``` + +```bash +# ── 工作流 6b: 删除行或列 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 从第 3 行开始删除 2 行 +dws sheet delete-dimension --node --sheet-id \ + --dimension ROWS --position "3" --length 2 --format json + +# 3. 从 A 列开始删除 1 列 +dws sheet delete-dimension --node --sheet-id \ + --dimension COLUMNS --position "A" --length 1 --format json + +# 4. 使用工作表前缀指定位置 +dws sheet delete-dimension --node --sheet-id \ + --dimension ROWS --position "Sheet1!5" --length 3 --format json +``` + +```bash +# ── 工作流 6c: 更新行/列属性(显隐、行高/列宽) ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 隐藏第 3~4 行 +dws sheet update-dimension --node --sheet-id \ + --dimension ROWS --start-index "3" --length 2 --hidden --format json + +# 3. 显示 A~B 列 +dws sheet update-dimension --node --sheet-id \ + --dimension COLUMNS --start-index "A" --length 2 --hidden=false --format json + +# 4. 设置第 1~5 行行高为 40px +dws sheet update-dimension --node --sheet-id \ + --dimension ROWS --start-index "1" --length 5 --pixel-size 40 --format json + +# 5. 设置 C 列列宽为 200px 并隐藏 +dws sheet update-dimension --node --sheet-id \ + --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden --format json +``` + +```bash +# ── 工作流 7: 搜索表格数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 基本搜索 — 在指定工作表中查找文本 +dws sheet find --node --sheet-id --find "销售额" --format json + +# 3. 在指定范围内搜索 +dws sheet find --node --sheet-id --find "合计" --range "A1:D100" --format json + +# 4. 正则搜索(不区分大小写) +dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false --format json + +# 5. 精确匹配整个单元格 +dws sheet find --node --sheet-id --find "完成" --match-entire-cell --format json + +# 6. 搜索公式文本 +dws sheet find --node --sheet-id --find "SUM" --match-formula --format json +``` + +```bash +# ── 工作流 8: 合并单元格 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 合并所有单元格(默认 mergeAll) +dws sheet merge-cells --node --sheet-id --range "A1:B3" --format json + +# 3. 按行合并 +dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows --format json + +# 4. 按列合并 +dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns --format json +``` + +```bash +# ── 工作流 9: 上传附件到表格 ── + +# 1. 基本用法: 上传本地文件到表格 +dws sheet media-upload --node --file ./report.pdf -f json + +# 2. 自定义附件显示名称 (--name 指定上传后在表格中显示的名称) +dws sheet media-upload --node --file ./data.csv --name "销售数据.csv" -f json + +# 3. 指定 MIME 类型 (文件扩展名无法推断时) +dws sheet media-upload --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream -f json + +# 4. 完整流程: 创建表格 → 上传附件 +dws sheet create --name "项目资料" -f json +# 提取 nodeId 后: +dws sheet media-upload --node --file ./design.pdf -f json +dws sheet media-upload --node --file ./timeline.xlsx --name "项目时间线.xlsx" -f json + +# ── 工作流 10: 写入图片到表格单元格 ── + +# 1. 基本用法: 写入图片到指定单元格 +dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png -f json + +# 2. 指定显示尺寸 +dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 -f json + +# 3. 自定义图片名称 +dws sheet write-image --node --sheet-id --range C3:C3 --file ./photo.jpg --name "产品图.jpg" -f json + +# 4. 完整流程: 创建表格 → 写表头 → 写入图片 +dws sheet create --name "产品目录" -f json +# 提取 nodeId 后: +dws sheet range update --node --sheet-id Sheet1 --range "A1:B1" --values '[["产品名称","产品图片"]]' -f json +dws sheet range update --node --sheet-id Sheet1 --range "A2:A2" --values '[["MacBook Pro"]]' -f json +dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json +``` + +```bash +# ── 工作流 11: 筛选视图管理 ── + +# 1. 获取工作表列表 +dws sheet list --node -f json + +# 2. 查看已有筛选视图 +dws sheet filter-view list --node --sheet-id -f json + +# 3. 创建筛选视图(不带条件) +dws sheet filter-view create --node --sheet-id \ + --name "我的筛选" --range "A1:E100" -f json + +# 4. 为筛选视图设置列条件(按值筛选) +dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' -f json + +# 5. 为筛选视图设置列条件(按条件筛选) +dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' -f json + +# 6. 更新筛选视图名称和范围 +dws sheet filter-view update --node --sheet-id --filter-view-id \ + --name "销售数据筛选" --range "A1:F200" -f json + +# 7. 清除某列的筛选条件 +dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id \ + --column 0 -f json + +# 8. 删除筛选视图 +dws sheet filter-view delete --node --sheet-id --filter-view-id -f json +``` + +```bash +# ── 工作流 11b: 创建带条件的筛选视图(一步完成) ── + +# 创建筛选视图时直接指定筛选条件 +dws sheet filter-view create --node --sheet-id \ + --name "高销售额视图" --range "A1:E100" \ + --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50000"}]}]' \ + -f json +``` + +```bash +# ── 工作流 12: 导出表格为 xlsx(单命令一站式)── + +# 场景 A:仅获取下载链接(命令内部自动完成提交+轮询,最终返回 downloadUrl) +dws sheet export --node --format json +# 传入 URL 也可: +# dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" --format json + +# 场景 B:导出并自动下载为本地文件 +dws sheet export --node --output ./report.xlsx + +# 场景 C:下载到目录,自动按链接推断文件名 +dws sheet export --node --output ./ + +# 禁止在 Agent 侧实现任何轮询或重试,CLI 内部已按 2s/5s/10s/15s 渐进式退避自动完成(最多 30 次)。 +# 若命令返回失败或超时,直接告知用户稍后再试,不要自动重调 dws sheet export。 +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `create` | `nodeId` | list / info / new / range read / range update / find 的 --node | +| `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | +| `new` | 新工作表的 `sheetId` | range read / range update / find 的 --sheet-id | +| `info` | `rowCount` / `lastNonEmptyRow` | 确定数据范围、追加写入起始行 | +| `find` | `matchedCells` 中的 `a1Notation` | 定位目标单元格,用于 range read / range update | +| `append` | `a1Notation` 追加数据所在范围 | 确认追加位置 | +| `csv-put` | `a1Notation` 实际写入的单元格范围 | 确认写入位置和范围 | +| `insert-dimension` | `a1Notation` 新插入区域范围 | 确认插入位置和范围 | +| `delete-dimension` | `a1Notation` 被删除区域范围 | 确认删除位置和范围 | +| `update-dimension` | `a1Notation` 被更新区域范围、`hidden` 生效的显隐状态、`pixelSize` 生效的尺寸 | 确认更新结果 | +| `merge-cells` | `a1Notation` 实际被合并的范围、`mergeType` 生效的合并方式 | 确认合并结果 | +| `media-upload` | `resourceId`、`resourceUrl` | 附件已上传到表格;`resourceUrl` 可用于 `create-float-image` 的 `--src` | +| `write-image` | `resourceId` | 图片已写入指定单元格 | +| `create-float-image` | `floatImage`(含 `id`、`src`、`range`、`width`、`height`、`offsetX`、`offsetY`) | `id` 用于后续 get / update / delete 的 `--float-image-id` | +| `get-float-image` | `floatImage`(完整信息) | 查看单个浮动图片详情 | +| `list-float-images` | `floatImages` 数组、`totalCount` | 获取所有浮动图片的 `id`,用于后续操作 | +| `update-float-image` | `floatImage`(更新后的完整信息) | 确认更新结果 | +| `delete-float-image` | `message` | 确认删除完成 | +| `replace` | `replaceCount` 被替换的单元格数量 | 确认替换结果 | +| `move-dimension` | `sheetId` 工作表 ID | 确认操作完成 | +| `add-dimension` | `sheetId` 工作表 ID | 确认操作完成 | +| `unmerge-cells` | `sheetId` 工作表 ID | 确认操作完成 | +| `set-dropdown` | `range` 实际设置范围、`optionCount` 选项数量、`enableMultiSelect` 是否多选 | 确认下拉列表设置成功 | +| `get-dropdown` | `hasDropdown` 是否存在下拉、`dataValidations` 下拉配置列表(含 `conditionValues`、`ranges`、`options`) | 查看已有下拉配置 | +| `delete-dropdown` | `range` 实际删除范围 | 确认下拉列表删除完成 | +| `filter-view list` | `filterViews` 筛选视图列表(含 `id`、`name`、`range`) | 获取 filterViewId 用于 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria | +| `filter-view info` | `id`、`name`、`range`、`criteria` | 查看单个视图完整配置,确认条件是否生效 | +| `filter-view create` | `id` 筛选视图 ID、`name`、`range` | 用于后续 update / delete / update-criteria / delete-criteria 的 --filter-view-id | +| `filter-view update` | `id`、`name`、`range`、`criteria` | 确认更新结果 | +| `filter-view delete` | `id` 被删除的筛选视图 ID | 确认删除完成 | +| `filter-view update-criteria` | `id` 筛选视图 ID | 确认条件设置完成 | +| `filter-view delete-criteria` | `id` 筛选视图 ID | 确认条件清除完成 | +| `filter-view list-criteria` | 所有列条件(按列偏移量为 key 的对象) | 了解当前视图已设置哪些列的条件 | +| `filter-view get-criteria` | 指定列的条件详情(`filterType`、`conditions` 等) | 查看某列的具体筛选规则 | +| `export` | `downloadUrl`(未指定 --output)/ `导出完成: `(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | + +## nodeId 多格式说明 + +所有 `--node` 参数同时支持文档 ID、文档 URL、表格分享链接,系统自动识别。详细格式和提取规则请参见前文「URL 识别与 NODE_ID 提取」章节。 + +> ** 禁止使用 `dentryId`**:`drive list` 返回结果中同时包含 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式,如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)两个字段。sheet 的 `--node` 和 `--folder` 参数**只能使用 `fileId`(UUID 格式)**,不能使用纯数字的 `dentryId`,传入纯数字会导致命令失败。 + +## values 参数格式说明 + +`--values` 为二维 JSON 数组,第一维为行,第二维为列: +- 字符串值: `"文本"` +- 数字值: `100` 或 `3.14` +- 公式: `"=SUM(B2:B4)"`(以 `=` 开头的字符串自动识别为公式) +- 清空单元格: 统一使用空字符串 `""`(不要用 `null` 取代,null 不会保留原值且全 null 会被视为无效调用跳过) + +维度必须与 `--range` 范围一致,例如 `--range "A1:B3"` 需要 3 行 2 列的数组。 + +## hyperlinks 参数格式说明 + +`--hyperlinks` 为二维 JSON 数组,每个元素为对象或 null: +- `type`: 链接类型,可选 `path`(外部链接)、`sheet`(工作表跳转)、`range`(单元格跳转) +- `link`: 链接地址 +- `text`: 显示文本 + +与 `--values` 共存时,hyperlinks 优先级更高。 + +## number-format 常用值 + +适用范围:`number-format` 仅在 `range set-style` / `range batch-set-style` 中接受(CLI 对应 `--number-format`,batch 配置文件对应 `numberFormat`);`range update` 不接受该参数。 + +| 格式代码 | 说明 | 示例 | +|----------|------|------| +| `General` | 常规 | 1234.5 | +| `@` | 文本 | 001234 | +| `#,##0` | 整数千分位 | 1,235 | +| `#,##0.00` | 两位小数 | 1,234.50 | +| `0%` | 百分比 | 85% | +| `yyyy/m/d` | 日期 | 2026/3/15 | +| `hh:mm:ss` | 时间 | 14:30:00 | +| `¥#,##0` | 人民币 | ¥1,235 | + +## 注意事项(完整版) + +> 标 ★ 的条目已在前文「关键注意事项」中列出,此处为完整说明。 + +- ★ `--sheet-id` 获取规范(强制):所有涉及 `--sheet-id` 参数的命令(`info` / `new` / `range read` / `range update` / `find` / `append` / `insert-dimension` / `delete-dimension` / `update-dimension` / `move-dimension` / `add-dimension` / `merge-cells` / `unmerge-cells` / `replace` / `write-image` / `set-dropdown` / `get-dropdown` / `delete-dropdown` / `filter-view *` 等),除非用户主动提供了工作表 ID 或工作表名称,否则在 `sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等);用户仅给出工作表名称时,也应通过 `list` 校验该名称是否存在,避免名称大小写或拼写不一致导致失败 +- ★ `range update` 维度校验(强制):调用 `range update` 写入 `--values` 或 `--hyperlinks` 时,必须严格校验二维 JSON 数组的行数与列数与 `--range` 指定的范围完全一致: + - 例如 `--range "A1:C3"` 表示 3 行 × 3 列,`--values` 必须是 `[[v1,v2,v3],[v4,v5,v6],[v7,v8,v9]]` 这样 3×3 的数组 + - `--range "A1"` 表示 1 行 × 1 列,`--values` 必须是 `[[v]]` + - 行数不足需要用空字符串补齐,列数不足需要补齐到每行相同长度;禁止出现各行列数不一致或与 `--range` 不匹配的情况,否则调用会直接报错 + - 同时传入 `--values` 和 `--hyperlinks` 时,两个二维数组的行列数都必须与 `--range` 严格一致 +- ★ `range update` 清空单元格规范(强制):如需清空单元格内容,统一使用空字符串 `""`。禁止使用 `null`:`null` 不会保留单元格原值,也不存在"选择性保留"场景;且若 `--values` 全部为 `null`,整体调用会被视为无效而跳过,无任何写入效果 +- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 +- `list` 返回所有工作表的 ID 和名称,是后续操作的必要前置步骤 +- `info` 不传 `--sheet-id` 时默认返回第一个工作表的详情 +- `range read` 不传 `--range` 时默认读取整个工作表的全部非空数据 +- `range read` 的 `--range` 支持 `Sheet1!A1:D10` 格式直接指定工作表(此时忽略 `--sheet-id`) +- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 获取边界后分批读取,避免不传 `--range` 直接读取整个大工作表 +- `range update` 的 `--values` 和 `--hyperlinks` 至少传入一项 +- `range update` 职责边界:`range update` 仅写入单元格的值与超链接,不接受任何样式参数(包括但不限于数字格式 / 背景色 / 字体 / 对齐方式等)。如需设置数字格式,请使用 `dws sheet range set-style --number-format <格式代码>`;批量场景走 `dws sheet range batch-set-style --batch `(配置文件中使用 `numberFormat` 字段)。不要在同一次 `range update` 调用里同时完成写值与格式设置 +- ★ `range update` / `range set-style` / `range batch-set-style` 单次调用上限(强制):行数 ≤ 1000,单元格总数(行×列)建议≤ 5000(底层硬限 30000);超限请拆分多次调用。CLI 会在调用前做本地预校验,底层超 30000 会直接报错 +- `range set-style` / `range batch-set-style` 的样式枚举按驼峰书写:`wordWrap` 取 `overflow`/`clip`/`autoWrap`,`fontWeight` 取 `bold`/`normal`,`hAlign` 取 `left`/`center`/`right`/`general`,`vAlign` 取 `top`/`middle`/`bottom`;背景色/字体颜色统一使用 `#RRGGBB` 格式 +- `new` 创建工作表时,如名称与已有工作表重复,系统会自动重命名 +- `find` 返回匹配单元格的地址(A1 表示法)和值,无匹配时返回空数组 +- `find` 的 `--match-entire-cell` 用于精确匹配:只返回单元格内容完全等于搜索文本的结果,不会匹配包含该文本的单元格(例如搜索"苹果"时,只匹配"苹果",不匹配"苹果手机""苹果汁"等)。用户说"精确搜索/完全匹配/只搜等于XX的"时必须使用此参数 +- `find` 的 `--match-case` 默认为 true(区分大小写),设为 false 可忽略大小写 +- `find` 的 `--use-regexp` 启用后,`--find` 参数作为正则表达式处理 +- ★ 当用户要求搜索/查找表格数据时,使用 `find` 命令,不要用 `range read` 读取全量数据后自行过滤——`find` 支持服务端搜索,效率更高、语义更准确 +- `append` 自动定位到最后一行有数据的位置下方插入,无需手动计算行号 +- `append` 的 `--values` 二维数组中每行的列数必须一致,否则会报错。如果用户提供的数据中各行长度不同,必须先将短行用空字符串 `""` 补齐到与最长行相同的列数后再调用。追加的数据列数也应与工作表已有数据列数保持一致 +- `append` vs `range update`:追加新行用 `append`,修改已有单元格用 `range update` +- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append` +- `insert-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `insert-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `insert-dimension` 的 `--length` 最大为 5000 +- `delete-dimension` 从指定位置起删除若干连续的行或列,删除后后续行/列向前移动填补空位 +- `delete-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `delete-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `delete-dimension` 的 `--length` 最大为 5000 +- `delete-dimension` 若需仅清空内容但保留行/列占位,请使用 `range update` 将目标区域写入空字符串 `""`(参见《range update 清空单元格规范》) +- `update-dimension` 批量更新连续行/列的显隐状态与行高/列宽 +- `update-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `update-dimension` 的 `--start-index` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `update-dimension` 的 `--length` 最大为 5000 +- `update-dimension` 的 `--hidden` 与 `--pixel-size` 至少必须提供一个 +- `update-dimension` 的 `--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽 +- `update-dimension` 当同时提供 `--hidden` 与 `--pixel-size` 时,将先应用尺寸再应用显隐,任一失败整体失败 +- `merge-cells` 合并时只保留左上角单元格的值,其他单元格的值会被丢弃 +- `merge-cells` 的 `--merge-type` 不传时默认为 `mergeAll`(合并所有单元格) +- `merge-cells` 的 `--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时忽略 `--sheet-id` +- `merge-cells` 如果目标区域与其他合并单元格、锁定区域或表格区域存在交集,合并将失败 +- `media-upload` 是两步自动完成的流程 (获取附件上传凭证 → OSS 上传),无需手动分步操作 +- `write-image` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 写入图片到单元格),无需手动分步操作 +- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 底层调用的 `update_range` MCP 工具不支持图片类型参数,调用会失败 +- `write-image` 与 `media-upload` 的区别:`media-upload` 仅上传附件到表格获取 resourceId;`write-image` 在上传后还会将图片写入指定单元格 +- `create-float-image` 创建浮动图片前必须先通过 `media-upload` 上传图片获取 `resourceUrl`,再将其作为 `--src` 传入。`--src` 的格式为 `/core/api/resources/img/...`,不能直接传外部 URL +- `create-float-image` 的 `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) +- `create-float-image` 的 `--width` / `--height` 为必填,单位像素,必须为正整数;`--offset-x` / `--offset-y` 可选,默认 0,不能为负数 +- `write-image`(单元格内嵌图片)vs `create-float-image`(浮动图片):`write-image` 将图片写入单元格内部,占据单元格内容;`create-float-image` 创建悬浮于单元格之上的浮动图片,不占用单元格内容,可自由调整位置和大小 +- `update-float-image` 的 `--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少必须提供一个 +- `list-float-images` 返回 `floatImages` 数组和 `totalCount`,每个元素包含 `id`(用于后续 get / update / delete) +- `delete-float-image` 操作不可恢复,删除后图片将从工作表中移除 +- `replace` 的 `--find` 不能为空字符串,`--replace` 可以为空字符串(表示删除匹配内容) +- `replace` 的 `--match-case` 默认为 false(不区分大小写),与 `find` 的默认行为不同 +- ★ `replace` vs `range update`:需要批量替换文本时,必须使用 `replace` 命令,禁止用 `range update` 手动重写单元格来实现替换效果。`replace` 支持服务端全局替换,效率更高且会返回替换计数 +- ★ `move-dimension` vs `range update`:需要移动行或列时,必须使用 `move-dimension` 命令,禁止用 `range update` 读取数据后手动重写来模拟移动效果。`move-dimension` 是原子操作,能保留单元格的格式、合并状态等属性 +- `move-dimension` 的索引均为 0-based(第 1 行/列的索引为 0),`endIndex` 包含在移动范围内 +- `move-dimension` 的 `--destination-index` 不能在 [start-index, end-index] 范围内 +- `move-dimension` 的移动跨度(end-index - start-index + 1)不超过 5000 +- `move-dimension` 的 `--destination-index` 是目标位置的 0-based 索引,即移动到第 n 行/列则传 `n - 1`(通用公式:`destination-index = 目标行号(1-based) - 1`) +- `add-dimension` vs `range update`:需要在末尾追加空行/空列时,必须使用 `add-dimension` 命令,禁止用 `range update` 写空数据来模拟追加效果 +- `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 +- `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 +- `unmerge-cells` 取消指定范围内所有合并单元格,使用 A1 表示法指定范围 +- `set-dropdown` 在指定范围内设置下拉列表,`--options` 为 JSON 数组,每个元素包含 `value`(必填)和 `color`(可选,`#RRGGBB` 格式)。选项值不能包含英文逗号。`--multi-select` 启用多选模式。如果目标范围已存在下拉列表,会被新配置覆盖 +- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。无下拉列表时 `hasDropdown` 为 false +- `delete-dropdown` 删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。已填写的值不会被清除。目标范围不存在下拉列表时操作仍返回成功 +- ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 +- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)和 `columnFilterCriteria`(各列条件)。无筛选时返回空 +- `filter create` 创建全局筛选时 `--range` 必须包含表头行(如 `A1:E100`),不能只包含数据行。每个工作表只能有一个筛选,已存在时报错 +- `filter create` 的 `--criteria` 可选,不传则仅创建空筛选框架,后续通过 `filter update` 设置条件 +- `filter delete` 删除后所有筛选条件丢失且所有被隐藏行重新显示,不可恢复 +- `filter delete` 工作表没有筛选时调用会报错,应先 `filter get` 确认存在 +- `filter update` 是覆盖式:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置再 patch +- `filter update` 前置:工作表必须已创建筛选 +- `filter clear-criteria` 仅清除指定列的条件,不删除整个筛选。指定列无条件时不报错(幂等) +- `filter sort` 会实际改变数据行的物理顺序,不可撤销。前置:工作表必须已创建筛选 +- ★ **筛选操作规范**(参照飞书 core-operations): + - 当用户要求"筛选/只看/仅保留 X"时,**必须**通过 `filter create` / `filter update` 创建真实的筛选器。**禁止**用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替 + - 创建/更新筛选后**必须** `filter get` 回读验证配置正确 + - 更新已有筛选前先 `filter get` 读取当前配置,确认目标存在且了解现有条件后再操作 + - 筛选条件的列索引(`column`)必须与实际数据列精确对应,不要凭猜测填写 + - 筛选不支持正则表达式,传入正则会当成普通文本处理 +- `filter-view list` 获取指定工作表的所有筛选视图列表,返回的 `id` 可用于后续 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria 的 `--filter-view-id` +- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),内部复用 `get_filter_views` MCP 按 ID 过滤 +- `filter-view list-criteria` 列出指定筛选视图已设置的所有列条件,返回按列偏移量为 key 的对象;无条件时返回空对象 `{}` +- `filter-view get-criteria` 获取指定列的条件详情,`--column` 为列偏移量(从 0 开始);该列无条件时返回错误提示 +- `filter-view create` 创建筛选视图时 `--range` 应包含表头行。`--criteria` 可选,不传则创建后无筛选条件,后续可通过 `filter-view update-criteria` 设置 +- `filter-view update` 的 `--name`、`--range`、`--criteria` 至少需要传入一个,未指定的字段保持不变 +- `filter-view update` 的 `--criteria` 中指定列的条件会被替换,未指定的列保持不变 +- `filter-view delete` 删除后该视图及其所有筛选条件将被永久移除,不可恢复 +- `filter-view delete` 不影响全局筛选或其他筛选视图 +- `filter-view update-criteria` 的 `--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列 +- `filter-view update-criteria` 设置条件后立即在该筛选视图中生效,仅影响当前视图,不影响全局筛选或其他筛选视图 +- `filter-view update-criteria` 的 `--filter-criteria` 中 `conditions` 最多 2 个条件,多条件之间通过 `conditionOperator` 指定逻辑关系(`and` 或 `or`) +- `filter-view delete-criteria` 仅清除指定列的条件,不会删除整个筛选视图。如需删除整个筛选视图,请使用 `filter-view delete` +- `filter-view delete-criteria` 如果指定列没有设置筛选条件,调用不会报错 +- 筛选视图相关操作需要"可阅读"权限(list / info / list-criteria / get-criteria)或"可编辑"权限(create / update / delete / update-criteria / delete-criteria),不支持跨组织操作 +- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 +- `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 +- ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 +- `export` 未指定 `--output` 时,返回的 `downloadUrl` 具有时效性,获取后请尽快下载;若用户需要本地文件,优先直接传 `--output` 让 CLI 代为下载 +- `export` 的 `--output` 可为文件路径或已存在目录;为目录时自动从 `downloadUrl` 推断文件名,为文件路径时直接按该路径保存 +- 用户要求"导出表格/下载 xlsx"时,必须使用 `export` 单命令,禁止用 `range read` 读全量数据后自行拼 xlsx 模拟导出(服务端导出会保留格式/合并/公式等完整属性) +- `update` 的 `--title`、`--index`、`--hidden`、`--frozen-row-count`、`--frozen-column-count` 至少必须提供一个 +- `update` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 +- `update` 的 `--index` 为 0-based 非负整数,0 表示移动到最前面 +- `update` 的 `--hidden` 设为 true 时,至少需要保留一个可见的工作表,不能将所有工作表都隐藏 +- `update` 的 `--frozen-row-count` / `--frozen-column-count` 为非负整数,不能超过工作表的总行数/列数,设为 0 表示取消冻结 +- `update` 当同时提供多个属性时,所有属性将在同一次请求中更新 +- `copy` 复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表 +- `copy` 的 `--title` 可选,不传时系统自动生成名称(通常为"源名称 副本"或类似格式) +- `copy` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 +- `copy` 当指定名称与已有工作表重复时,系统会自动重命名为合法值 +- `copy` 的 `--index` 可选,不传时副本将放置在源工作表之后的默认位置 +- ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) +- sheet 产品线仅支持 `axls`(在线电子表格,`contentType=ALIDOC`),不支持 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 +- 遇到未知 `alidocs` URL 时,必须先 probe(`dws doc info --node --format json`)确认 `contentType` 和 `extension`,才能决定是否走 sheet +- 当节点 `extension=xlsx` / `xls` / `xlsm` / `csv`(`contentType≠ALIDOC`)时,必须用 `dws doc download --node --output <路径>` 先下载到本地再处理,禁止调用任何 sheet 子命令(sheet 底层 MCP 工具只识别 axls,调用 xlsx 节点必失败) +- 要把在线表格导出为 xlsx 文件——走 `dws sheet export`(axls → xlsx 的格式转换);要读已有的 xlsx 文件——走 `dws doc download` 后在本地解析,两者方向相反 diff --git a/skills/mono/references/products/sheet/sheet-batch-operations.md b/skills/mono/references/products/sheet/sheet-batch-operations.md new file mode 100644 index 00000000..ce79f719 --- /dev/null +++ b/skills/mono/references/products/sheet/sheet-batch-operations.md @@ -0,0 +1,246 @@ +# 批量操作 + +## 写入边界 + 回读校验 + +`batch-update` 把多次写入打包成单次请求,但每个子操作仍受编辑类任务硬性默认规则约束: + +1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / 工作表。规划 range 时先确认每个子操作的边界。 +2. **批次完成后必须回读校验**:整个 `batch-update` 执行成功后,用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与本地预先计算的预期值对照。 +3. **预期条数前置断言**:涉及"批量填充 N 行"或"对 M 个区域分别写入"时,先把 N、M 硬编码进代码,回读后断言实际等于预期;不一致就再发一轮 `batch-update` 补齐,禁止交付半成品。 + +## 使用场景 + +用户说"批量清除/一次清除多个区域/清空多个 sheet 的数据": +- 批量清除多个区域 → `range batch-clear` +- 每个区域必须带工作表前缀(如 "Sheet1!A1:B3") +- 原子事务:任一区域清除失败则整批回滚 +- 请勿用多次 `range clear` 逐个调用,非原子,中途失败会留下半成品 + +用户说"批量操作/一次执行多个写操作/原子批量/先清除再写入": +- 多个写操作打包 → `batch-update` +- 支持混合操作(清除 + 写入 + 合并 + 调行列尺寸等) +- 原子事务:任一操作失败则整批回滚 +- 传 `--continue-on-error` 可切换为宽松模式(失败继续) + +**何时推荐使用 `batch-update`**: +- 需要对**多个**不同区域执行 `merge-cells` / `unmerge-cells` 时(如按分组合并多列相同内容) +- 需要对**多个**不同区域执行 `update-dimension` 时(如统一调整多列列宽或多行行高,含 hidden + pixelSize) +- 需要先插入行列再写入数据时(`add-dimension` + `range update` / `csv-put`) +- 需要对多个区域执行不同写入操作时(多次 `range update` + `range clear` 等组合) + +当同一工具需要对多个区域重复调用时,**推荐**改用 `batch-update` 合并为单次请求——`batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。 + +**不可放进 `--operations` 的操作**(强行写入会被校验拒或行为未定义): +- `range read` / `csv-get`(只读操作,不属于写入) +- `range sort` / `range move-to`(尚未支持,后续版本补充) +- `write-image` / `media-upload` / `create-float-image` / `update-float-image`(需本地上传或依赖前置上传句柄) +- `set-style` / `range batch-set-style`(自身已是批量入口,不可再嵌套) +- `find` / `replace`(尚未支持,后续版本补充) +- `export`(异步轮询操作) +- `sheet list` / `sheet info`(只读操作) + +这些操作需在 `batch-update` 之外单独调用。 + +## 命令详细参考 + +### 批量清除区域 +``` +Usage: + dws sheet range batch-clear [flags] +Example: + dws sheet range batch-clear --node --ranges '["Sheet1!A1:B3","Sheet2!C1:D5"]' + dws sheet range batch-clear --node --ranges '["Sheet1!A1:Z1000"]' --type all + dws sheet range batch-clear --node --ranges '["Sheet1!A2:A100","Sheet1!C2:C100"]' --type format +Flags: + --node string 表格文档 ID 或 URL (必填) + --ranges string 目标区域 JSON 数组,每项带 sheet 前缀 (必填) + --type string 清除类型: content(仅值,默认) / format(仅格式) / all(全部) +Notes: + - 原子事务:任一区域清除失败则整批回滚 + - 每个 --ranges 项必须包含工作表前缀(格式: "SheetName!A1:B3") + - 不同区域可以属于不同工作表 +``` + +### 批量更新(原子事务) +``` +Usage: + dws sheet batch-update [flags] +Example: + dws sheet batch-update --node --operations '[ + {"toolName":"range clear","input":{"sheet-id":"Sheet1","range":"A1:B3","type":"content"}}, + {"toolName":"range update","input":{"sheet-id":"Sheet1","range":"A1","values":[[{"type":"text","text":"hello"}]]}}, + {"toolName":"merge-cells","input":{"sheet-id":"Sheet1","range":"A1:B1","merge-type":"mergeAll"}}, + {"toolName":"update-dimension","input":{"sheet-id":"Sheet1","dimension":"ROWS","start-index":"1","length":1,"pixel-size":40}} + ]' + dws sheet batch-update --node --continue-on-error --operations '[...]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --operations string 操作数组 JSON (必填,每项 {toolName, input}) + --continue-on-error 遇失败继续执行(默认 false,严格事务) +toolName 用 CLI 命令名,input 的键用 CLI flag 名去掉 --: + range clear / range update / merge-cells / unmerge-cells / update-dimension + range fill / range copy-to / add-dimension / delete-dimension / move-dimension + set-dropdown / delete-dropdown / csv-put / delete-float-image +Notes: + - 默认严格事务模式:任一子操作失败 → 整批回滚到初始状态 + - --continue-on-error: 宽松模式,遇失败继续执行后续操作(已执行的子操作不回滚) + - operations 最多 20 条 + - 当需要对多个区域执行相同清除时,优先使用 `range batch-clear`(更简洁) + - 典型场景:先插入行列再写入数据、先清除再写入、批量合并+调整行高列宽 +``` + +### 子操作定位规则 + +> **`--node` 只在顶层传一次**,每个子操作的 `input` 不含 `node` 字段; +> **子表定位 `sheet-id` 写进各自 `input`**,不同子操作可指向不同工作表。 + +```jsonc +// 正确:node 在顶层,sheet-id 在各自 input +[ + {"toolName":"range clear","input":{"sheet-id":"Sheet1","range":"A1:B3"}}, + {"toolName":"range update","input":{"sheet-id":"Sheet2","range":"A1","values":[[...]]}} // 不同工作表 +] +``` + +## 校验与预览 + +### Validate 阶段 + +CLI 在发送请求前执行以下本地校验(不消耗网络请求): + +| 校验项 | 失败行为 | 示例 | +|--------|---------|------| +| `--node` / `--operations` 必填 | `missing required flag(s)` | 不传 `--node` | +| `--operations` 必须合法 JSON | `--operations JSON 解析失败` | `--operations 'not json'` | +| `--operations` 必须非空数组 | `--operations 不能为空数组` | `--operations '[]'` | +| 每项必须是 object | `operations[N] 不是 object` | `--operations '["string"]'` | +| `toolName` 必须是支持的 CLI 命令名 | `unsupported toolName "xxx"` | 传 `"batch-update"`(禁止嵌套)或拼写错误 | +| 禁止嵌套 `batch-update` | 同上(`batch-update` 不在 dispatch 表中,自动拦截) | `toolName: "batch-update"` | + +> `input` 内的字段(如 `sheet-id`、`range`、`values` 等)由服务端校验,CLI 不提前拦截——传入空值或缺失必填字段时,请求会到达服务端再返回错误。 + +### DryRun 阶段 + +加 `--dry-run` 预览翻译后的完整 MCP 请求,不实际执行任何操作: + +```bash +dws sheet batch-update --node NODE_ID --dry-run --operations '[...]' +``` + +DryRun 输出内容: +- `Tool: batch_update`(最终调用的 MCP 工具名) +- `Arguments:` 完整 JSON(含翻译后的 `operations` 数组,展示每个子操作的 MCP toolName + 参数名 + 参数值) +- 若传了 `--continue-on-error`,输出 JSON 中会包含 `"continueOnError": true` + +DryRun 适用场景: +- 确认 CLI 命令名 → MCP toolName 的翻译是否正确 +- 确认参数名转换(如 `--range` → `rangeAddress`)是否符合预期 +- 检查 `csv-put` 的 `@filepath` 是否被正确解析为文件内容 + +### --continue-on-error 行为 + +| 模式 | flag | 子操作失败时 | 返回格式 | +|------|------|------------|---------| +| 严格事务(默认) | 不传 | 整批回滚到初始状态,后续子操作不执行 | 顶层 `error` 对象 | +| 宽松模式 | `--continue-on-error` | 已执行的子操作不回滚,继续执行后续子操作 | `results` 数组 | + +**严格事务失败的错误信息**由服务端(flex-table-app)生成,包含以下内容: +- 失败操作索引:`operations[N]`(0-based,即 `--operations` JSON 数组中的下标) +- 回滚告知:"All previously executed operations have been rolled back." +- 失败原因:原始错误码和错误消息 + +示例: +``` +Batch update failed at operations[1] (0-based index). +All previously executed operations have been rolled back. +Cause: The requested resource was not found by the identifier 'NonExistentSheet'... +``` + +**宽松模式下**,返回 `results` 数组,逐个标注每个子操作的成功/失败状态: +- 成功的子操作:`success: true` + `message` +- 失败的子操作:`success: false` + `errorCode` + `errorMsg` +- 顶层 `success: true`(batch 本身完成执行,只是部分子操作失败) + +## CLI 命令名 → 原子命令参考 + +`toolName` 使用 CLI 命令名,`input` 的键使用 CLI flag 名(去掉 `--`),与原子命令文档完全一致。各命令的参数详情请查阅对应参考文档或执行 `dws sheet <命令> --help`。 + +| CLI 命令名 | 参考文档 | +|----------|---------| +| `range clear` | [sheet-range-operations](./sheet-range-operations.md) | +| `range update` | [sheet-write-data](./sheet-write-data.md) | +| `merge-cells` | [sheet-style-format](./sheet-style-format.md) | +| `unmerge-cells` | [sheet-style-format](./sheet-style-format.md) | +| `update-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `range fill` | [sheet-range-operations](./sheet-range-operations.md) | +| `range copy-to` | [sheet-range-operations](./sheet-range-operations.md) | +| `add-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `delete-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `move-dimension` | [sheet-dimension-operations](./sheet-dimension-operations.md) | +| `set-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | +| `delete-dropdown` | [sheet-dropdown](./sheet-dropdown.md) | +| `csv-put` | [sheet-write-data](./sheet-write-data.md) | +| `delete-float-image` | [sheet-media-image](./sheet-media-image.md) | + +## 典型组合场景 + +### 插列 + 写表头 + 整列回填 + +一次原子提交,不要拆成多次独立调用。批量回填同一列**只需一次** `range update`(range 写整列范围、values 写 N×1 矩阵),不需要逐行循环。 + +```jsonc +// 在 C 列前插入新列 → 写表头 C1 → 回填 C2:C100 共 99 行 +[ + {"toolName":"add-dimension", + "input":{"sheet-id":"...","dimension":"COLUMNS","length":1}}, + {"toolName":"range update", + "input":{"sheet-id":"...","range":"C1:C100", + "values":[[{"type":"text","text":"score"}],[{"type":"text","text":"95"}],[{"type":"text","text":"87"}]/* ... 97 more rows ... */]}} +] +``` + +### 先清除再写入 + +清除目标区域残留数据后写入新数据,保证目标区域干净。 + +```jsonc +[ + {"toolName":"range clear", + "input":{"sheet-id":"...","range":"A1:D50","type":"all"}}, + {"toolName":"range update", + "input":{"sheet-id":"...","range":"A1:D50", + "values":[[{"type":"text","text":"姓名"},{"type":"text","text":"部门"},{"type":"text","text":"金额"},{"type":"text","text":"日期"}],/* ... */]}} +] +``` + +### 批量合并 + 调整行高列宽 + +统一多列合并和尺寸调整,原子提交避免中途失败留下不一致格式。 + +```jsonc +[ + {"toolName":"merge-cells", + "input":{"sheet-id":"...","range":"A1:B1","merge-type":"mergeAll"}}, + {"toolName":"merge-cells", + "input":{"sheet-id":"...","range":"C1:D1","merge-type":"mergeAll"}}, + {"toolName":"update-dimension", + "input":{"sheet-id":"...","dimension":"ROWS","start-index":"1","length":1,"pixel-size":40}}, + {"toolName":"update-dimension", + "input":{"sheet-id":"...","dimension":"COLUMNS","start-index":"A","length":4,"pixel-size":120}} +] +``` + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `list` | 工作表的 `sheetId` | batch-clear 的 --ranges sheet 前缀 / batch-update 子操作 input 的 sheet-id | +| `batch-update` | 各子操作的执行结果 | 回读校验(range read / csv-get) | + +## 注意事项 + +- ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- ★ **需要对多个区域执行相同清除操作时,用 `range batch-clear`**:一次原子请求清除多个区域(可跨工作表),失败时整批回滚 +- ★ **需要组合多个不同写操作(清除+写入等)时,用 `batch-update`**:原子事务,任一操作失败则整批回滚,避免留下半成品 +- ★ **批次完成后必须回读校验**:用 `range read` 或 `csv-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与预期值对照 +- ★ **`batch-update` 不支持嵌套**:`--operations` 中的 `toolName` 必须是原子操作,不可再嵌套 `batch-update` diff --git a/skills/mono/references/products/sheet/sheet-chart.md b/skills/mono/references/products/sheet/sheet-chart.md new file mode 100644 index 00000000..8aa7fee2 --- /dev/null +++ b/skills/mono/references/products/sheet/sheet-chart.md @@ -0,0 +1,351 @@ +# 浮动图表 (chart) + +## 真对象硬约束 + +当用户要求"画个图 / 数据可视化 / 趋势图 / 对比图 / 占比图"时,**必须**通过 `chart create` 创建真实的图表对象。**禁止**用本地脚本调 matplotlib / seaborn 生成图片再插入到表格代替——静态图片无法随源数据更新,且失去交互能力。判断标准:交付后 `chart list` 必须能返回该对象。 + +## 使用场景 + +读写浮动图表对象。本 reference 覆盖 4 个命令: + +| 操作需求 | 使用命令 | 说明 | +|---------|---------|------| +| 查看已有图表 | `chart list` | 获取图表的类型、数据源和样式配置 | +| 创建图表 | `chart create` | 创建浮动图表对象 | +| 更新图表 | `chart update` | 更新图表配置(PUT 语义整体覆盖) | +| 删除图表 | `chart delete` | 删除浮动图表 | + +典型工作流:先读取现有图表了解配置 → 执行创建/更新/删除 → 再次读取验证结果。 + +## 需求→图表类型映射(创建前必查) + +| 用户说 | 图表类型 | 备注 | +|--------|---------|------| +| "占比"、"比例"、"各XX占多少" | 饼图(pie) | 单维度占比首选 | +| "环形图"、"空心饼图" | 环形图(doughnut) | 与 pie 类似但中心空心 | +| "对比"、"各XX的YY" | 柱形图(column) | 多类别数值对比 | +| "堆叠柱"、"堆积" | 堆积柱形图(columnStacked) | 纵向堆叠 | +| "条形"、"横向对比" | 条形图(bar) | 横向柱形 | +| "横向堆叠" | 堆积条形图(barStacked) | 横向堆叠 | +| "趋势"、"变化"、"走势" | 折线图(line) | 时间序列首选 | +| "堆叠折线" | 堆积折线图(lineStacked) | 堆叠折线 | +| "面积图" | 面积图(area) | 填充折线下方区域 | +| "堆叠面积" | 堆积面积图(areaStacked) | 堆叠面积 | +| "百分比面积" | 百分比堆积面积图(areaPercentStacked) | 占比式面积 | +| "散点图"、"相关性" | 散点图(scatter) | 两变量相关性 | +| "雷达图"、"多维度" | 雷达图(radar) | 多维度对比 | + +**多图表需求**:当用户同时提到多种分析(如"统计占比 + 对比数量"),必须创建多个图表,每个对应一种类型,不要只做一个。 + +## 命令详细参考 + +### 获取浮动图表 +``` +Usage: + dws sheet chart list [flags] +Example: + # 列出所有浮动图表 + dws sheet chart list --node NODE_ID --sheet-id SHEET_ID + + # 获取单个图表详情 + dws sheet chart list --node NODE_ID --sheet-id SHEET_ID --chart-id CHART_ID +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --chart-id string 浮动图表 ID (可选,不传则返回全部) +``` + +### 创建浮动图表 +``` +Usage: + dws sheet chart create [flags] +Example: + dws sheet chart create --node NODE_ID --sheet-id SHEET_ID --properties '{ + "position": {"row": 12, "col": "A"}, + "dimensions": {"width": 600, "height": 400}, + "chart": { + "type": "column", + "series": [{"name": "B1", "value": ["B2:B10"]}], + "category": ["A2:A10"], + "title": {"show": true, "text": "销售数据"} + } + }' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --properties string 图表完整配置 JSON (必填,含 position/dimensions/chart) +``` + +### 更新浮动图表 +``` +Usage: + dws sheet chart update [flags] +Example: + dws sheet chart update --node NODE_ID --sheet-id SHEET_ID --chart-id CHART_ID --properties '{ + "position": {"row": 12, "col": "A"}, + "dimensions": {"width": 800, "height": 500}, + "chart": { + "type": "line", + "series": [{"name": "B1", "value": ["B2:B20"]}], + "category": ["A2:A20"], + "title": {"show": true, "text": "月度趋势(更新)"} + } + }' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --chart-id string 浮动图表 ID (必填,可通过 chart list 获取) + --properties string 图表完整配置 JSON (必填,PUT 语义整体覆盖) +``` + +### 删除浮动图表 +``` +Usage: + dws sheet chart delete [flags] +Example: + dws sheet chart delete --node NODE_ID --sheet-id SHEET_ID --chart-id CHART_ID +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --chart-id string 浮动图表 ID (必填,可通过 chart list 获取) +``` + +> [强制] 危险操作:删除不可恢复。必须先向用户展示操作摘要并获得明确同意,用户同意后才加 `--yes` 执行。 + +## `--properties` JSON Schema 速查 + +**顶层字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `position` | object | 是 | 锚点位置,详见下方 position 字段表 | +| `offset` | object | 否 | 偏移量,详见下方 offset 字段表 | +| `dimensions` | object | 是 | 尺寸,详见下方 dimensions 字段表 | +| `chart` | object | 是 | 图表配置,详见下方 chart 字段表 | + +**position 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `row` | number | 是 | 锚点行索引(0-based),如 0 表示第一行 | +| `col` | string | 是 | 锚点列。使用字母表示法(`"A"`=第1列, `"B"`=第2列, `"Z"`=第26列, `"AA"`=第27列),不支持数字形式 | + +**offset 字段**(可选,不传时默认 0): + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `offsetX` | number | 否 | 相对锚点的水平偏移量,单位像素,默认 0 | +| `offsetY` | number | 否 | 相对锚点的垂直偏移量,单位像素,默认 0 | + +**dimensions 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `width` | number | 是 | 图表宽度,单位像素,正整数 | +| `height` | number | 是 | 图表高度,单位像素,正整数 | + +**chart 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `type` | string | 是 | 图表类型,枚举值:`column` / `bar` / `columnStacked` / `barStacked` / `line` / `lineStacked` / `area` / `areaStacked` / `areaPercentStacked` / `pie` / `doughnut` / `scatter` / `radar` | +| `series` | object[] | 是 | 数据系列数组,至少一项,详见下方 series 字段表 | +| `category` | string[] | 否 | 分类轴(X轴)数据区域,A1 表示法字符串数组(如 `["A2:A10"]`) | +| `title` | object | 否 | 图表标题配置,详见下方 title 字段表 | +| `legend` | object | 否 | 图例配置,详见下方 legend 字段表 | +| `catAx` | object | 否 | 分类轴(X轴)样式配置,详见下方轴配置表 | +| `valAx` | object | 否 | 值轴(Y轴)样式配置,详见下方轴配置表 | + +**series 数组项**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `name` | string | 否 | 系列名称的单元格引用,A1 表示法(如 `"B1"`)。不传时图例显示为"系列1"等默认名 | +| `value` | string[] | 是 | 系列数据区域,A1 表示法字符串数组(如 `["B2:B10"]`)。每个元素为一段连续区域 | + +**title 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `show` | boolean | 否 | 是否显示标题,默认 false | +| `text` | string | 否 | 标题文本内容 | + +**legend 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `show` | boolean | 否 | 是否显示图例,默认 true | +| `pos` | string | 否 | 图例位置,枚举值:`t`(顶部)/ `b`(底部)/ `l`(左侧)/ `r`(右侧)/ `none`(隐藏) | + +**catAx / valAx 轴配置**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `show` | boolean | 否 | 是否显示该轴,默认 true | +| `pos` | string | 否 | 轴位置,枚举值:`l`(左)/ `t`(上)/ `b`(下)/ `r`(右) | +| `titleConfig` | object | 否 | 轴标题配置:`{ show: boolean, title: string }`。show 控制是否显示轴标题,title 为轴标题文本 | +| `axisMin` | number \| null | 否 | 轴最小值,传 null 或不传表示自动计算 | +| `axisMax` | number \| null | 否 | 轴最大值,传 null 或不传表示自动计算 | +| `splitLine` | boolean | 否 | 是否显示网格线,默认 true | +| `minorSplitLine` | boolean | 否 | 是否显示次网格线,默认 false | +| `axisLabel` | boolean | 否 | 是否显示轴标签(刻度文字),默认 true | +| `axisLine` | boolean | 否 | 是否显示轴线,默认 true | + +## A1 区域引用规则 + +- `series[].value`、`series[].name`、`category` 均使用 A1 表示法字符串 +- 格式:`"A2:B10"`(默认使用 `--sheet-id` 对应的工作表) +- 跨 sheet 引用:`"SHEET_ID!A2:B10"`(前缀为工作表 ID) +- 单元格引用:`"B1"`(等同于 `"B1:B1"`) +- MCP 内部自动将 A1 引用转换为 ISheetRange 结构,CLI 直接透传即可 + +## 图表位置选择(创建前必做) + +凭感觉挑行列号可能越界或遮挡数据。按以下四步走: + +1. **查尺寸**:`dws sheet info --node --sheet-id ` 获取工作表行列数信息。 +2. **估跨度**:默认单元格约 **100 px 宽 × 25 px 高**,`needCols = ceil(width/100)`,`needRows = ceil(height/25)`。 +3. **校验**:`position.row + needRows ≤ rowCount` 且 `col + needCols ≤ columnCount`。 +4. **不够就先扩表**:先调 `insert-dimension` 扩行/列,再 create。 + +**图表落点禁止压在已有数据矩形内**——必须落在数据区**右侧或下方的空白**,否则图表浮层会遮挡原始数据。 + +**示例**:数据在 A1:E20,放 600×400 图 → `needCols=6, needRows=16` +- [正确] `{"row": 22, "col": "A"}` — 放数据下方 +- [正确] `{"row": 0, "col": "G"}` — 放数据右侧 +- [错误] `{"row": 5, "col": "B"}` — 压在数据区域上 + +## 核心工作流 + +```bash +# ── 工作流 1: 创建柱形图 ── + +# 1. 先查 sheetId +dws sheet list --node -f json + +# 2. 查看数据范围确认列对应关系 +dws sheet csv-get --node --sheet-id --range "A1:E5" + +# 3. 创建柱形图 +dws sheet chart create --node --sheet-id --properties '{ + "position": {"row": 22, "col": "A"}, + "dimensions": {"width": 600, "height": 400}, + "chart": { + "type": "column", + "series": [ + {"name": "B1", "value": ["B2:B10"]}, + {"name": "C1", "value": ["C2:C10"]} + ], + "category": ["A2:A10"], + "title": {"show": true, "text": "各月销售额对比"}, + "legend": {"show": true, "pos": "t"} + } +}' + +# 4. 验证创建结果 +dws sheet chart list --node --sheet-id + +# ── 工作流 2: 创建折线图 ── + +dws sheet chart create --node --sheet-id --properties '{ + "position": {"row": 22, "col": "H"}, + "dimensions": {"width": 600, "height": 400}, + "chart": { + "type": "line", + "series": [ + {"name": "B1", "value": ["B2:B13"]}, + {"name": "C1", "value": ["C2:C13"]} + ], + "category": ["A2:A13"], + "title": {"show": true, "text": "年度趋势"}, + "legend": {"show": true, "pos": "b"} + } +}' + +# ── 工作流 3: 创建饼图 ── + +dws sheet chart create --node --sheet-id --properties '{ + "position": {"row": 0, "col": "G"}, + "dimensions": {"width": 500, "height": 400}, + "chart": { + "type": "pie", + "series": [{"value": ["B2:B6"]}], + "category": ["A2:A6"], + "title": {"show": true, "text": "各部门人数占比"} + } +}' + +# ── 工作流 4: 多图表组合分析 ── +# 当用户需要"柱+线"等多种图表组合分析时,分别创建对应类型的图表: + +# 创建柱形图 +dws sheet chart create --node --sheet-id --properties '{ + "position": {"row": 40, "col": "A"}, + "dimensions": {"width": 700, "height": 450}, + "chart": { + "type": "column", + "series": [{"name": "B1", "value": ["B2:B10"]}], + "category": ["A2:A10"], + "title": {"show": true, "text": "销售额"}, + "legend": {"show": true, "pos": "t"} + } +}' + +# 创建折线图 +dws sheet chart create --node --sheet-id --properties '{ + "position": {"row": 40, "col": "J"}, + "dimensions": {"width": 700, "height": 450}, + "chart": { + "type": "line", + "series": [{"name": "C1", "value": ["C2:C10"]}], + "category": ["A2:A10"], + "title": {"show": true, "text": "增长率"}, + "legend": {"show": true, "pos": "t"} + } +}' +``` + +## Update 三步法(PUT 语义) + +`chart update` 是 PUT 语义(整体覆盖),不是 PATCH。必须按三步操作: + +1. `chart list --chart-id ` 获取完整配置 +2. 在获取的配置基础上**局部**修改要改的字段,其余保持不变 +3. 把**完整配置**整体回写到 `--properties` + +> 关键:**不能只提交局部配置**,否则未传字段会被还原为默认值。 + +```bash +# 1. 获取现有配置 +dws sheet chart list --node --sheet-id --chart-id -f json +# → 得到完整的 properties 结构(position/dimensions/chart) + +# 2. 基于返回结果修改(如改标题 + 调整尺寸),构造 --properties JSON + +# 3. 整体回写 +dws sheet chart update --node --sheet-id --chart-id \ + --properties '<完整修改后的 JSON>' +``` + +## 上下文传递 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `chart list` | `floatCharts[].id` | 后续 update / delete 的 `--chart-id` | +| `chart list --chart-id` | 完整的 properties(position/dimensions/chart) | update 时作为基础配置修改后回写 | +| `chart create` | `floatChart.id` | 后续 update / delete 的 `--chart-id` | +| `chart update` | 更新后的完整信息 | 确认更新结果 | +| `chart delete` | `message` | 确认删除完成 | +| `sheet list` | 工作表的 `sheetId` | 所有 chart 命令的 `--sheet-id` | + +## 注意事项 + +- [强制] **`--sheet-id` 获取规范**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- [强制] **创建后必须验证**:图表创建后必须调用 `chart list` 验证配置是否正确 +- [强制] **chart-id 禁止臆测**:必须通过 `chart list` 获取真实的图表 ID,不可编造 +- **图表类型映射**:用户说"柱形图"用 `column`,"条形图"(横向)用 `bar`,"散点图"用 `scatter`,"雷达图"用 `radar`,"环形图"用 `doughnut` +- **堆积类型后缀**:堆叠版本的图表在基本类型后加 `Stacked`(如 `columnStacked`、`lineStacked`、`areaStacked`) +- **位置不得压在数据区上**:图表浮层会遮挡原始数据,必须放在空白区域 +- **多图表需求**:多次调用 `chart create`,不要试图用一个图表包含所有分析 +- **跨 sheet 引用数据**:在 A1 区域前加 `SHEET_ID!` 前缀,如 `"sheetXXX!A2:B10"` +- **大 JSON 用 @file**:`--properties` 支持 `@文件路径` 读取本地 JSON 文件 diff --git a/skills/mono/references/products/simple.md b/skills/mono/references/products/simple.md index 4242e769..cdaa9e27 100644 --- a/skills/mono/references/products/simple.md +++ b/skills/mono/references/products/simple.md @@ -15,30 +15,9 @@ Example: Flags: --query string 搜索关键词 (必填) --page string 页码 (默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page --size string 每页数量 (默认 10) ``` -### 错误排查 -``` -Usage: - dws devdoc error diagnose [flags] -Example: - dws devdoc error diagnose --request-id 15r6h45w0muec --format json - dws devdoc error diagnose --error-code 33012 --error-message "missing scope" --format json -Flags: - --query string 原始排查问题 - --request-id string 开放平台 requestId - --trace-id string requestId 的兼容别名 - --error-code string 错误码 - --error-message string 错误描述,会合并进原始问题 - --api string API 名称,会合并进原始问题作为补充检索词 - --context string 额外排查上下文,会合并进原始问题 - --page int 分页页码 (默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page - --size int 分页大小 (默认 10) -``` - --- ## oa — 审批 @@ -120,7 +99,6 @@ Example: ## 意图判断 - 用户说"开发文档/API 文档/接口文档" → `devdoc article search` -- 用户说"调用报错/requestId/traceId/错误码/错误描述" → `devdoc error diagnose`;若返回 `PARAM_ERROR - 未找到指定工具`,降级 `devdoc article search` 并标记后端工具注册待闭环 - 用户说"审批/请假/报销/出差" → `oa approval` - 用户说"同意审批/批准" → `oa approval approve` - 用户说"拒绝审批/驳回" → `oa approval reject` @@ -132,8 +110,7 @@ Example: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `devdoc article search` | 文档链接、nextCursor | 直接展示给用户;hasMore=true 时用 --cursor 翻页 | -| `devdoc error diagnose` | diagnosticInfo、references、materials、nextCursor | 排查开放平台调用错误;hasMore=true 时用 --cursor 翻页;不可用时退回文档搜索 | +| `devdoc article search` | 文档链接 | 直接展示给用户 | | `oa approval list-forms` | processCode | detail / records 等 | | `oa approval tasks` | taskId, instanceId | approve / reject | | `oa approval list-pending` | instanceId | detail / approve / reject | diff --git a/skills/mono/references/products/todo.md b/skills/mono/references/products/todo.md index 45c02851..4f3527cc 100644 --- a/skills/mono/references/products/todo.md +++ b/skills/mono/references/products/todo.md @@ -178,6 +178,63 @@ Flags: --task-id string 待办任务 ID (必填) ``` +### 查询子待办列表 +``` +Usage: + dws todo task list-sub [flags] +Example: + dws todo task list-sub --task-id +Flags: + --task-id string 待办任务 ID (必填) +``` + +### 上传待办附件 + +> ⚠️ 当前不可用:后端未注册该工具,调用报「未找到指定工具」。遇到此需求直接告知用户暂不支持,勿重试或变通。 + +> ⚠️ 重要:该接口会上传文件到附件,不可用于测试或试探性调用。调用前必须确认待办存在。 + +``` +Usage: + dws todo task add-attachment [flags] +Example: + dws todo task add-attachment --task-id --file-path /path/to/file.pdf +Flags: + --file-path string 本地文件路径 (必填) + --task-id string 待办任务 ID (必填) +``` + +### 查询待办附件列表 + +> ⚠️ 当前不可用:后端未注册该工具,调用报「未找到指定工具」。遇到此需求直接告知用户暂不支持,勿重试或变通。 + +``` +Usage: + dws todo task list-attachment [flags] +Example: + dws todo task list-attachment --task-id +Flags: + --task-id string 待办任务 ID (必填) +``` + +### 删除待办附件 + +> ⚠️ 当前不可用:后端未注册该工具,调用报「未找到指定工具」。遇到此需求直接告知用户暂不支持,勿重试或变通。 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws todo task remove-attachment [flags] +Example: + dws todo task remove-attachment --task-id --attachment-id + dws todo task remove-attachment --task-id --attachment-id --yes +Flags: + --attachment-id string 待办附件 ID (必填) + --task-id string 待办任务 ID (必填) +``` +附件 attachmentId 使用 `dws todo task list-attachment` 命令获取。 + ### 添加待办提醒 ``` Usage: diff --git a/skills/mono/references/products/wiki.md b/skills/mono/references/products/wiki.md index c990492d..2467ba8a 100644 --- a/skills/mono/references/products/wiki.md +++ b/skills/mono/references/products/wiki.md @@ -14,6 +14,7 @@ dws wiki member add --help # 查看子命令组下的所有命令 dws wiki space --help +dws wiki node --help dws wiki member --help ``` @@ -32,9 +33,9 @@ Example: dws wiki space create --name "产品文档库" --format json dws wiki space create --name "技术方案" --desc "团队技术方案归档" --format json Flags: - --name string 知识库名称 (必填,不超过 100 字符) + --name string 知识库名称 (必填,不超过 100 字符) --desc string 知识库描述 (选填,不超过 500 字符) - --icon string 知识库图标标识 (选填) + --icon string 知识库图标标识 (选填) ``` ### 删除知识库 @@ -71,22 +72,29 @@ Flags: 支持传入知识库 ID 或知识库 URL,系统自动识别。 知识库 URL 格式:`https://alidocs.dingtalk.com/i/spaces/{workspaceId}/overview` -### 列出知识库 +### 列出空间(知识库 / 钉盘空间) ``` Usage: dws wiki space list [flags] Example: - dws wiki space list --format json - dws wiki space list --type myWikiSpace --format json + dws wiki space list --format json # 组织知识库(默认) + dws wiki space list --type myWikiSpace --format json # 我的文档 + dws wiki space list --type orgSpace --format json # 钉盘企业空间 + dws wiki space list --type mySpace --format json # 钉盘「我的文件」 dws wiki space list --type orgWikiSpace --limit 50 --format json Flags: - --type string 知识库类型: myWikiSpace / orgWikiSpace (默认 orgWikiSpace) - --limit string 每页数量 1-50 (默认 20) + --type string 空间类型: + orgWikiSpace (默认) — 组织知识库 + myWikiSpace — 我的文档个人空间 + orgSpace — 钉盘企业空间 + mySpace — 钉盘「我的文件」 + --limit string 每页数量 1-50 (默认 20) --cursor string 分页游标 (首页留空) ``` - `myWikiSpace`:返回当前用户的「我的文档」个人空间(固定 1 条,不支持分页) - `orgWikiSpace`(默认):返回组织内有权访问的知识库列表,支持分页 +- 钉盘空间类型(`orgSpace` / `mySpace`)会自动路由到钉盘服务,等同于原 `drive list-spaces`(已 deprecated) ### 搜索知识库 ``` @@ -97,9 +105,9 @@ Example: dws wiki space search --query "技术方案" --limit 20 --format json dws wiki space search --type myWikiSpace --format json Flags: - --query string 搜索关键词 (--type myWikiSpace 时可省略) - --type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库 - --limit string 返回数量 1-20 (默认 10) + --query string 搜索关键词 (搜索组织知识库时必填,--type myWikiSpace 时可省略) + --type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库 + --limit string 返回数量 1-20 (默认 10) ``` 当 `--type myWikiSpace` 时,忽略 `--query`,直接返回「我的文档」个人空间。 @@ -113,14 +121,14 @@ Example: dws wiki member add --workspace --users uid1,uid2 --role EDITOR dws wiki member add --workspace "https://alidocs.dingtalk.com/i/spaces//overview" --users uid1 --role MANAGER Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) + --workspace string 目标知识库 ID 或 URL (必填) + --users string 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 授予的角色 (必填,大小写不敏感): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) ``` -> **重要约束**: +> **❗ 重要约束**: > - 仅支持 USER 类型。 -> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。 +> - 角色枚举:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。 > - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 > - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。如果你的目标只是把某篇文档分享给别人,请改用 `dws drive permission add` 在节点级别授权。 @@ -132,8 +140,8 @@ Example: dws wiki member remove --workspace --users uid1 dws wiki member remove --workspace --users uid1,uid2 Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 被移除的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --workspace string 目标知识库 ID 或 URL (必填) + --users string 被移除的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) ``` > **重要约束**: @@ -150,9 +158,9 @@ Example: dws wiki member update --workspace --users uid1 --role EDITOR dws wiki member update --workspace --users uid1,uid2 --role READER Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) - --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER + --workspace string 目标知识库 ID 或 URL (必填) + --users string 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 新角色 (必填,大小写不敏感): MANAGER / EDITOR / DOWNLOADER / READER ``` ### 列出知识库成员 @@ -164,9 +172,9 @@ Example: dws wiki member list --workspace --limit 100 dws wiki member list --workspace --filter-role EDITOR Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --limit int 返回数量上限,最大 200 (默认 50) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) + --workspace string 目标知识库 ID 或 URL (必填) + --limit int 返回成员数上限,默认 30,最大 200 + --filter-role string 按角色过滤,逗号分隔: OWNER / MANAGER / EDITOR / DOWNLOADER / READER (选填) ``` > 接口不支持游标分页,使用 `--limit` 一次性拉取。 @@ -195,14 +203,16 @@ Usage: Example: dws wiki node create --workspace --name "新文档" --format json dws wiki node create --workspace --name "方案目录" --type folder --format json - dws wiki node create --workspace --name "数据表" --type asheet --folder --format json + dws wiki node create --workspace --name "数据表" --type axls --folder --format json Flags: --workspace string 知识库 ID (必填) --name string 节点名称 (必填) - --type string 节点类型: adoc / asheet / folder / axls (默认 adoc) + --type string 节点类型 (默认 adoc),实际支持: adoc(在线文档) / axls(在线表格) / appt(演示文稿) / adraw(白板) / amind(脑图) / able(多维表) / folder(文件夹) --folder string 父节点 nodeId (选填,不传则在根目录创建) ``` +> ⚠️ **type 枚举修正**:`--help` 中列出的 `asheet` 会被后端拒绝,**不要使用**;创建在线表格请用 `axls`。实际可用类型以上方列表为准。 + ### 复制知识库节点 ``` Usage: @@ -253,41 +263,19 @@ Usage: Example: dws wiki node search --workspace --query "方案" --format json dws wiki node search --workspace --query "周报" --limit 10 --format json - dws wiki node search --workspace --query "设计" --extensions adoc,asheet --format json + dws wiki node search --workspace --query "设计" --extensions adoc,axls --format json Flags: - --workspace string 知识库 ID (必填) - --query string 搜索关键词 (必填) - --extensions string 按文件类型过滤,逗号分隔: adoc,asheet 等 (选填) - --limit int 每页数量 (选填) - --cursor string 分页游标 (选填) + --workspace string 知识库 ID (必填) + --query string 搜索关键词 (必填) + --extensions strings 按文件扩展名过滤,逗号分隔 (如 adoc,axls,pdf) (选填) + --limit int 每页数量 (默认 10,最大 30) (选填) + --cursor string 分页游标 (选填) ``` 在指定知识库空间内搜索节点。与 `drive search` 的区别: - `wiki node search` — 限定在某个知识库空间内搜索(需要 `--workspace`) - `drive search` — 全局搜索,聚合钉盘 + 文档空间结果 -### 列出空间(支持钉盘空间类型) - -`wiki space list` 除了支持知识库类型(`orgWikiSpace` / `myWikiSpace`),还支持钉盘空间类型: - -``` -Usage: - dws wiki space list --type orgSpace --format json # 钉盘企业空间 - dws wiki space list --type mySpace --format json # 钉盘「我的文件」 - dws wiki space list --type orgWikiSpace --format json # 知识库(默认) - dws wiki space list --type myWikiSpace --format json # 我的文档 -Flags: - --type string 空间类型: - orgWikiSpace (默认) — 组织知识库 - myWikiSpace — 我的文档个人空间 - orgSpace — 钉盘企业空间 - mySpace — 钉盘「我的文件」 - --limit string 每页数量 1-50 (默认 20) - --cursor string 分页游标 (首页留空) -``` - -> 钉盘空间类型(`orgSpace` / `mySpace`)会自动路由到钉盘 MCP 服务,等同于原 `drive list-spaces`(已 deprecated)。 - ## 意图判断 - 用户说"创建知识库/新建知识库" → `space create` @@ -316,8 +304,8 @@ Flags: - **wiki node**(空间管理层:节点的列出/创建/复制/移动/删除/搜索)vs **doc**(内容层:读写/编辑/块级/评论/导出)vs **drive**(存储层:文件上传/下载/搜索/权限,不关心格式) - **wiki node search**(空间内搜索,需 `--workspace`)vs **drive search**(全局搜索,聚合钉盘+文档空间) - **wiki node create**(在空间中创建空文件实体)vs **doc create**(创建文档并写入内容) -- **wiki member**(容器级,授权整个知识库)vs **doc permission / drive permission**(节点级,授权单篇文档) - - 「我的文档」**只能用** `doc permission` / `drive permission`,不能用 `wiki member` +- **wiki member**(容器级,授权整个知识库)vs **drive permission**(节点级,授权单篇文档) + - 「我的文档」**只能用** `drive permission`,不能用 `wiki member` - **wiki space list --type orgSpace/mySpace**(列出钉盘空间)vs **wiki space list**(默认列出知识库) ## 核心工作流 diff --git a/skills/mono/references/url-patterns.md b/skills/mono/references/url-patterns.md index bf1fc312..4b206ad9 100644 --- a/skills/mono/references/url-patterns.md +++ b/skills/mono/references/url-patterns.md @@ -98,7 +98,7 @@ Step 3 → 按下方路由规则映射到对应产品 > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(envelope schema 里有 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx +> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx ### 示例 @@ -110,10 +110,10 @@ dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json # 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output ./xlsx456.xlsx +dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" # 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output ./def456.bin +dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" # 返回 nodeType=folder → 文件夹,列出子节点 dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json @@ -137,7 +137,7 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso |-------------------------|------|------|------|------|------|------| | **adoc**(在线文档) | `doc read` | `doc update` / `doc block update` | ⚠️ `doc delete` | ⚠️ `doc export` (→ docx) | ⚠️ `doc permission *` | ⚠️ `doc media download/insert` | | **axls**(在线电子表格) | `sheet range read` / `sheet list` | `sheet range write` / `sheet append` | ⚠️ `doc delete`(节点删除) | `sheet submit_export_job` + `sheet query_export_job`(待吴淼 W-01 收敛为单命令 `sheet export`) | ⚠️ `doc permission *`(节点级,跨产品) | 不适用 | -| **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --base-id --scope all --output ./x.xlsx` | ⚠️ `doc permission *`(节点级) | `aitable attachment upload-file` | +| **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --output ./x.xlsx` | ⚠️ `doc permission *`(节点级) | `aitable attachment upload-file` | | **xlsx / xls / xlsm / csv**(本地表格文件) | `doc download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | | **普通文件** (nodeType=file) | `doc download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | | **文件夹** (nodeType=folder) | `doc list --folder ` | `doc create --folder ...` | ⚠️ `doc delete` | 不适用 | ⚠️ `doc permission *` | 不适用 | @@ -158,6 +158,6 @@ Agent 流程: | 用户说 | 路由 | 不要混淆 | |--------|------|---------| | "把这个文档/表格/多维表分享给张三" | **节点级**:`doc permission add --node --user --role EDITOR` | 不是 `wiki member add` | -| "把张三加到这个知识库" | **容器级**:`wiki member add --workspace --users --role ` | 不是 `doc permission add` | +| "把张三加到这个知识库" | **容器级**:`wiki member add --workspace --user --role ` | 不是 `doc permission add` | > 区分依据:**doc permission 作用于单个 node(document / file / folder);wiki member 作用于整个 workspace 容器**。同一用户在 workspace 是 EDITOR、在某个 node 上仍可被单独提升为 MANAGER(节点级覆盖容器级)。 diff --git a/skills/mono/scripts/aitable_export_via_task.py b/skills/mono/scripts/aitable_export_via_task.py index caa66f80..fdb20406 100644 --- a/skills/mono/scripts/aitable_export_via_task.py +++ b/skills/mono/scripts/aitable_export_via_task.py @@ -88,10 +88,10 @@ def build_start_args(args: argparse.Namespace) -> list[str]: args.base_id, "--scope", args.scope, - "--format", + "--export-format", args.export_format, - "--timeout-ms", - str(args.timeout_ms), + "--timeout-sec", + str(args.timeout_sec), ] if args.table_id: cmd.extend(["--table-id", args.table_id]) @@ -107,9 +107,8 @@ def main() -> None: parser.add_argument("--table-id", help="scope=table/view 时必填") parser.add_argument("--view-id", help="scope=view 时必填") parser.add_argument("--export-format", default="excel", choices=sorted(ALLOWED_FORMATS), help="导出格式") - parser.add_argument("--timeout-ms", type=int, default=1000, help="单次等待毫秒数,默认 1000") - parser.add_argument("--poll-timeout-ms", type=int, default=3000, help="轮询等待毫秒数,默认 3000") - parser.add_argument("--max-polls", type=int, default=10, help="最大轮询次数,默认 10") + parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") + parser.add_argument("--max-polls", type=int, default=10, help="(兼容旧参数,开源 CLI 内置轮询不再使用)") parser.add_argument("--output", help="本地保存路径(不传则按 fileName 保存到当前目录)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") parser.add_argument("--no-download", action="store_true", help="仅返回 downloadUrl,不下载文件") @@ -153,10 +152,10 @@ def main() -> None: args.base_id, "--task-id", task_id, - "--timeout-ms", - str(args.poll_timeout_ms), + "--timeout-sec", + str(args.timeout_sec), ], - timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60), + timeout_sec=max(120, args.timeout_sec + 60), ) if rc2 != 0: fail(f"export_data 轮询失败: {err2 or out2}", rc2) diff --git a/skills/mono/scripts/aitable_import_via_task.py b/skills/mono/scripts/aitable_import_via_task.py index fea1ad5e..f3139df2 100644 --- a/skills/mono/scripts/aitable_import_via_task.py +++ b/skills/mono/scripts/aitable_import_via_task.py @@ -77,7 +77,7 @@ def main() -> None: parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格") parser.add_argument("base_id", help="目标 AI 表格 baseId") parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)") - parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30") + parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") args = parser.parse_args() @@ -140,12 +140,12 @@ def main() -> None: "data", "--import-id", import_id, - "--timeout", - str(args.timeout), + "--timeout-sec", + str(args.timeout_sec), "--format", "json", ], - timeout_sec=max(120, args.timeout + 30), + timeout_sec=max(120, args.timeout_sec + 30), ) if rc2 != 0: fail(f"import_data 调用失败: {err2 or out2}", rc2) diff --git a/skills/mono/scripts/attendance_my_record.py b/skills/mono/scripts/attendance_my_record.py index 89898507..88f6cd97 100644 --- a/skills/mono/scripts/attendance_my_record.py +++ b/skills/mono/scripts/attendance_my_record.py @@ -48,7 +48,25 @@ def get_my_user_id(dry_run: bool = False) -> Optional[str]: return '' if not data or not isinstance(data, dict): return None - return data.get('userId') or data.get('userid') + # 兼容两种结构: + # 1) 顶层直接给 userId + # 2) {result: [{orgEmployeeModel: {userId}}]} 包裹 + uid = data.get('userId') or data.get('userid') + if uid: + return uid + inner = data.get('result') + if isinstance(inner, dict): + inner = [inner] + if isinstance(inner, list): + for item in inner: + if not isinstance(item, dict): + continue + emp = item.get('orgEmployeeModel') + if isinstance(emp, dict) and emp.get('userId'): + return emp['userId'] + if item.get('userId'): + return item['userId'] + return None def main(): diff --git a/skills/mono/scripts/attendance_report_2026-07-05_2026-07-07_checkin.xlsx b/skills/mono/scripts/attendance_report_2026-07-05_2026-07-07_checkin.xlsx new file mode 100644 index 00000000..b20f4c3c Binary files /dev/null and b/skills/mono/scripts/attendance_report_2026-07-05_2026-07-07_checkin.xlsx differ diff --git a/skills/mono/scripts/attendance_report_checkin.py b/skills/mono/scripts/attendance_report_checkin.py new file mode 100644 index 00000000..6b362c7c --- /dev/null +++ b/skills/mono/scripts/attendance_report_checkin.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python3 +""" +考勤报表导出 — 签到记录粒度 + +[AI Agent 强制门禁] 调用本脚本前必须先阅读: + dingtalk-workspace/references/products/attendance-report.md + + 本脚本是"签到报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md。 + + [严禁] 仅凭本脚本 docstring 或 --help 输出就直接拼命令执行。 + +导出签到报表:每条签到记录一行,包含签到详情(地点、经纬度、拜访客户、图片等)。 + +前置依赖: + pip install openpyxl + +用法: + python attendance_report_checkin.py \ + --users userId1,userId2,... \ + --start "2026-04-01 00:00:00" \ + --end "2026-04-07 23:59:59" \ + [--out 签到报表_研发部_20260401_20260407.xlsx] + [--inspect] +""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timedelta +from typing import Any + +# ── 前置依赖检查(在任何 dws 调用之前就检测,避免查完数据才报错)─────── +_missing_deps: list[str] = [] +try: + import openpyxl as _openpyxl_check # noqa: F401 +except ImportError: + _missing_deps.append("openpyxl") +try: + import requests as _requests_check # noqa: F401 +except ImportError: + _missing_deps.append("requests") +try: + from PIL import Image as _pil_check # noqa: F401 +except ImportError: + _missing_deps.append("Pillow") + +if _missing_deps: + print( + f"[ERROR] 缺少以下依赖:{', '.join(_missing_deps)}\n" + f" 请先安装:pip install {' '.join(_missing_deps)}\n" + "安装后重新执行本脚本。\n" + "(签到报表需要 openpyxl 生成 Excel、requests + Pillow 下载并嵌入签到图片)", + file=sys.stderr, + ) + sys.exit(2) + +import attendance_report_common as cmn + +# ───────────────────────────────────────────────────────────────────────────── +# 自动获取当前认证的 operator 信息 +# ───────────────────────────────────────────────────────────────────────────── + +def _get_operator_context() -> tuple[str, str]: + """ + 从 `dws auth status --format json` 自动获取当前认证的 corp_id 和 user_id。 + + 签到接口 (checkin records) 必须传 --operator-corp-id 和 --operator-staff-id, + 这两个值来自 dws 的认证上下文(即 `dws auth status` 返回的 corp_id / user_id), + 而非 `dws contact user get-self` 返回的长格式 userId。 + + Returns: + (operator_corp_id, operator_staff_id) 元组 + + Raises: + SystemExit: 未登录或无法获取认证信息时直接退出 + """ + try: + result = subprocess.run( + ["dws", "auth", "status", "--format", "json"], + capture_output=True, + text=True, + timeout=30, + ) + except FileNotFoundError: + cmn.error("未找到 dws 命令,请确认 dws CLI 已安装并在 PATH 中") + sys.exit(2) + except subprocess.TimeoutExpired: + cmn.error("dws auth status 超时,请检查网络或重新登录(dws auth login)") + sys.exit(2) + + if result.returncode != 0: + cmn.error( + "获取认证信息失败,请确保已执行 dws auth login 完成登录。\n" + f" 错误详情:{(result.stderr or result.stdout or '').strip()}" + ) + sys.exit(2) + + try: + auth_data = json.loads(result.stdout) + except json.JSONDecodeError: + cmn.error(f"dws auth status 返回非 JSON:{result.stdout[:200]!r}") + sys.exit(2) + + corp_id = auth_data.get("corp_id") or auth_data.get("corpId") or "" + user_id = auth_data.get("user_id") or auth_data.get("userId") or "" + + if not corp_id or not user_id: + cmn.error( + "无法从认证信息中提取 corp_id / user_id,请重新登录:\n" + " dws auth login\n" + f" 当前返回:{json.dumps(auth_data, ensure_ascii=False)[:300]}" + ) + sys.exit(2) + + cmn.log(f"[auth] 已获取 operator 信息:corp_id={corp_id}, user_id={user_id}") + return str(corp_id), str(user_id) + + +# 签到接口限制:开始到结束最多 7 天 +MAX_DAYS_PER_CHECKIN_SLICE = 7 + +# 签到接口限制:每次最多查 100 人(与 check record 一致) +MAX_USERS_PER_CHECKIN_BATCH = 50 + +# 最多支持 9 张图片列 +MAX_IMAGE_COLUMNS = 9 + +# 报表表头(与用户要求严格对齐) +REPORT_HEADERS = [ + "姓名", "部门", "完整部门", + "日期", "时间", + "经度", "纬度", "地点", "详细地址", + "拜访客户", "客户部门名称", "工作内容", + "手机标识", +] + [f"图片{i}" for i in range(1, MAX_IMAGE_COLUMNS + 1)] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "导出签到报表 — 签到记录粒度。" + "[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本。" + ), + ) + parser.add_argument("--users", required=True, + help="userId 列表,逗号分隔(必填)") + parser.add_argument("--start", required=True, + help='开始时间,YYYY-MM-DD 或 "YYYY-MM-DD HH:mm:ss"(必填)') + parser.add_argument("--end", required=True, + help='结束时间,YYYY-MM-DD 或 "YYYY-MM-DD HH:mm:ss"(必填)') + parser.add_argument("--out", default="", + help="输出 xlsx 文件名;不传则按规范自动生成") + parser.add_argument("--inspect", action="store_true", + help="首次跑时打印首条记录原始结构(用于核对真实字段)") + return parser.parse_args() + + +# ───────────────────────────────────────────────────────────────────────────── +# 签到接口时间切片(7 天一段) +# ───────────────────────────────────────────────────────────────────────────── + +def slice_checkin_date_range( + start: datetime, end: datetime, +) -> list[cmn.DateSlice]: + """将日期范围按 7 天一段切片(签到接口限制开始到结束最多 7 天)。""" + slices: list[cmn.DateSlice] = [] + current = start + while current <= end: + slice_end = min(current + timedelta(days=MAX_DAYS_PER_CHECKIN_SLICE - 1), end) + # 确保 slice_end 的时间部分是当天最后一秒 + slice_end = slice_end.replace(hour=23, minute=59, second=59) + if slice_end > end: + slice_end = end + slices.append(cmn.DateSlice( + start=current, + end=slice_end, + )) + current = slice_end.replace(hour=0, minute=0, second=0) + timedelta(days=1) + return slices + + +def chunk_checkin_users(user_ids: list[str]) -> list[list[str]]: + """将用户列表按 MAX_USERS_PER_CHECKIN_BATCH 分批。""" + batches: list[list[str]] = [] + for i in range(0, len(user_ids), MAX_USERS_PER_CHECKIN_BATCH): + batches.append(user_ids[i:i + MAX_USERS_PER_CHECKIN_BATCH]) + return batches + + +# ───────────────────────────────────────────────────────────────────────────── +# 签到数据查询 +# ───────────────────────────────────────────────────────────────────────────── + +def query_checkin_batch( + user_batch: list[str], + date_slice: cmn.DateSlice, + operator_corp_id: str, + operator_staff_id: str, + stats: cmn.CallStats, + *, + inspect: bool = False, + inspected_flag: list[bool] | None = None, +) -> list[dict]: + """查询一批用户在一个时间片内的签到记录。""" + cmn.log( + f"[checkin] users={len(user_batch)} " + f"slice={date_slice.label}" + ) + try: + payload = cmn.run_dws([ + "attendance", "checkin", "records", + "--operator-corp-id", operator_corp_id, + "--operator-staff-id", operator_staff_id, + "--staff-ids", ",".join(user_batch), + "--start", date_slice.start_str, + "--end", date_slice.end_str, + ]) + stats.total_dws_calls += 1 + except cmn.DwsCallError as exc: + stats.total_dws_calls += 1 + stats.failed_calls += 1 + if exc.is_permission_error: + cmn.error( + "权限错误:当前账号无管理员权限,无法导出签到报表。\n" + "请联系考勤管理员或换号重试。" + ) + raise SystemExit(2) from exc + err_msg = str(exc) + if "missing required flag" in err_msg.lower(): + cmn.error( + "签到接口调用失败:缺少必需参数。\n" + "请确保已执行 dws auth login 完成登录,以便自动获取 operator 参数。\n" + f"当前 operator: corp_id={operator_corp_id}, staff_id={operator_staff_id}\n" + f"原始错误:{err_msg}" + ) + raise SystemExit(2) from exc + stats.add_warning(f"[checkin failed] {date_slice.label}: {exc}") + return [] + + records = cmn.extract_records(payload) + if inspect and records and inspected_flag is not None and not inspected_flag[0]: + cmn.dump_first_record_for_inspection(records, "checkin-records") + inspected_flag[0] = True + return records + + +# ───────────────────────────────────────────────────────────────────────────── +# 签到记录 → 报表行转换 +# ───────────────────────────────────────────────────────────────────────────── + +def _format_timestamp(timestamp_value: Any) -> tuple[str, str]: + """ + 将签到时间戳转换为 (日期字符串, 时间字符串)。 + + 签到接口的 timestamp 为毫秒时间戳。 + """ + if timestamp_value is None: + return "", "" + try: + ts = float(timestamp_value) + # 判断是毫秒还是秒级时间戳 + if ts > 1_000_000_000_000: + ts = ts / 1000 + dt = datetime.fromtimestamp(ts) + return dt.strftime("%Y-%m-%d"), dt.strftime("%H:%M:%S") + except (ValueError, TypeError, OSError, OverflowError): + return str(timestamp_value), "" + + +def transform_records_to_rows( + records: list[dict], + user_info_map: dict[str, cmn.UserInfo], +) -> list[list[Any]]: + """将签到原始记录转换为报表行(与 REPORT_HEADERS 对齐)。""" + rows: list[list[Any]] = [] + for record in records: + uid = cmn._first_nonempty(record, ("userId", "userid", "user_id")) + uid_str = str(uid) if uid is not None else "" + info = user_info_map.get(uid_str, cmn.UserInfo(name=uid_str)) + + # 姓名:优先用 resolve_user_info 的结果,回退到接口返回的 name + name = info.name or record.get("name", uid_str) + dept_name = info.dept_name + # 完整部门:暂用 dept_name(如需更完整的路径可后续扩展) + full_dept = dept_name + + # 日期与时间 + date_str, time_str = _format_timestamp(record.get("timestamp")) + + # 经纬度 + longitude = record.get("longitude", "") + latitude = record.get("latitude", "") + + # 地点 + place = record.get("place", "") + detail_place = record.get("detailPlace", "") + + # 拜访客户 & 客户部门名称 + customers = record.get("customers", "") + # 签到接口暂无客户部门名称字段,预留空值 + customer_dept = "" + + # 工作内容(备注) + remark = record.get("remark", "") + + # 手机标识 + mobile_id = record.get("mobileId", "") + + # 图片列(最多 9 张) + image_list = record.get("imageList") or [] + if isinstance(image_list, str): + # 兼容接口可能返回逗号分隔的字符串 + image_list = [img.strip() for img in image_list.split(",") if img.strip()] + image_cells = [] + for i in range(MAX_IMAGE_COLUMNS): + if i < len(image_list): + image_cells.append(image_list[i]) + else: + image_cells.append("") + + row = [ + name, dept_name, full_dept, + date_str, time_str, + longitude, latitude, place, detail_place, + customers, customer_dept, remark, + mobile_id, + ] + image_cells + rows.append(row) + + return rows + + +# ───────────────────────────────────────────────────────────────────────────── +# main +# ───────────────────────────────────────────────────────────────────────────── + +def main() -> int: + args = parse_args() + + raw_ids = [u.strip() for u in args.users.split(",") if u.strip()] + if not raw_ids: + cmn.error("--users 不能为空") + return 2 + + # 自动识别部门ID并展开为员工userId + user_ids = cmn.resolve_users_from_input(raw_ids) + if not user_ids: + cmn.error("未能解析出任何有效的员工userId") + return 2 + cmn.log(f"[users] 最终用户列表:{len(user_ids)} 人") + + try: + start = cmn.parse_datetime_arg(args.start, end_of_day=False) + end = cmn.parse_datetime_arg(args.end, end_of_day=True) + except ValueError as exc: + cmn.error(str(exc)) + return 2 + + if end < start: + cmn.error(f"--end ({end}) 早于 --start ({start})") + return 2 + + # 获取当前认证的 operator 信息(签到接口必需) + operator_corp_id, operator_staff_id = _get_operator_context() + + # 获取用户基础信息(姓名、部门) + cmn.log(f"[users] 获取 {len(user_ids)} 个用户基础信息") + user_info_map = cmn.resolve_user_info(user_ids) + + # 分批分段查询签到记录 + user_batches = chunk_checkin_users(user_ids) + date_slices = slice_checkin_date_range(start, end) + stats = cmn.CallStats( + user_batches=len(user_batches), + date_slices=len(date_slices), + ) + cmn.log( + f"[plan] 共 {len(user_batches)} 批 × {len(date_slices)} 个时间片 " + f"= {len(user_batches) * len(date_slices)} 次接口调用" + ) + + inspected_flag = [False] + all_records: list[dict] = [] + for batch_idx, batch in enumerate(user_batches, start=1): + for slice_idx, date_slice in enumerate(date_slices, start=1): + cmn.log( + f"[batch {batch_idx}/{len(user_batches)}] " + f"[slice {slice_idx}/{len(date_slices)}]" + ) + records = query_checkin_batch( + batch, date_slice, + operator_corp_id, operator_staff_id, + stats, + inspect=args.inspect, + inspected_flag=inspected_flag, + ) + all_records.extend(records) + + if not all_records: + stats.add_warning("查询完成,但未得到任何签到记录") + + # 转换为报表行 + rows = transform_records_to_rows(all_records, user_info_map) + + # 按日期时间排序(日期列索引=3,时间列索引=4) + rows.sort(key=lambda r: (r[3] or "", r[4] or "")) + + # 生成 Excel + out_name = args.out or cmn.build_output_filename(start, end, suffix="checkin") + title = ( + f"签到报表 统计日期:{start.strftime(cmn.DATE_FMT)} " + f"至 {end.strftime(cmn.DATE_FMT)}" + ) + subtitle = f"报表生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}" + + # 图片列名列表(图片1~图片9),让 write_excel_multi_sheets 自动将 URL 嵌入为缩略图 + image_column_names = [f"图片{i}" for i in range(1, MAX_IMAGE_COLUMNS + 1)] + + checkin_sheet = { + "name": "签到记录", + "headers": REPORT_HEADERS, + "rows": rows, + "title": title, + "subtitle": subtitle, + "image_columns": image_column_names, + "image_size": (60, 60), + } + + try: + cmn.write_excel_multi_sheets(out_name, [checkin_sheet]) + except (RuntimeError, ValueError) as exc: + cmn.error(str(exc)) + return 1 + + cmn.print_summary( + granularity_label="签到报表", + out_path=out_name, + user_count=len(user_ids), + column_names=[h for h in REPORT_HEADERS], + start=start, + end=end, + rows_count=len(rows), + stats=stats, + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/skills/mono/scripts/attendance_report_record.py b/skills/mono/scripts/attendance_report_record.py new file mode 100644 index 00000000..84c57647 --- /dev/null +++ b/skills/mono/scripts/attendance_report_record.py @@ -0,0 +1,947 @@ +#!/usr/bin/env python3 +""" +考勤记录报表导出脚本 — 补卡/出差/外出/请假 + +属于考勤报表导出体系,和 attendance_report_detail.py / attendance_report_monthly.py 平级。 +Agent 负责意图判断和人员获取,本脚本自包含:数据查询 → 解析 → Excel 生成。 + +数据链路: + 1. dws attendance approve list --users --types --start --end + → 获取审批单摘要(含 originId = processInstanceId) + 2. dws oa approval detail --instance-id + → 获取审批单完整表单字段(extValue / detailList) + 3. 解析 formValueVOS 中的 DDHolidayField / extValue → 按天拆分行 + 4. write_excel 输出 + +用法: + python attendance_report_record.py --type leave --users --start 2026-04-01 --end 2026-04-30 + python attendance_report_record.py --type trip --users --start 2026-04-01 --end 2026-04-30 + python attendance_report_record.py --type out --users --start 2026-05-01 --end 2026-05-31 + python attendance_report_record.py --type patch --users --start 2026-05-01 --end 2026-05-31 + +支持类型: leave(请假), trip(出差), out(外出), patch(补卡) +""" + +from __future__ import annotations + +import argparse +import json +import sys +import os +from datetime import datetime +from typing import Any + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from attendance_report_common import ( + run_dws, + write_excel, + resolve_user_names, + resolve_user_info, + UserInfo, + log, + warn, + error, + DwsCallError, + DATE_FMT, +) + +# ───────────────────────────────────────────────────────────────────────────── +# 常量 +# ───────────────────────────────────────────────────────────────────────────── + +SUPPORTED_TYPES = ("leave", "trip", "out", "patch") + +COLUMNS: dict[str, list[str]] = { + "leave": ["姓名", "考勤组", "部门", "工号", "职位", "假期类型", "请假时间", + "请假时长(小时)", "请假时长(天)", "关联审批单", "审批单状态"], + "trip": ["姓名", "考勤组", "部门", "工号", "职位", "出差时间", + "出差时长", "出差单位", "关联审批单", "审批单状态"], + "out": ["姓名", "考勤组", "部门", "工号", "职位", "外出申请时间", + "外出时长(小时)", "外出时长(天)", "关联审批单", "审批单状态"], + "patch": ["姓名", "考勤组", "部门", "工号", "职位", "考勤日期", "考勤时间", + "原打卡时间", "原考勤状态", "补卡时间", "补卡结果", "关联审批单", "审批单状态"], +} + +SHEET_NAMES: dict[str, str] = { + "leave": "请假记录", + "trip": "出差记录", + "out": "外出记录", + "patch": "补卡记录", +} + +STATUS_MAP: dict[str, dict[str, str]] = { + "COMPLETED": {"agree": "审批通过", "refuse": "已拒绝"}, + "RUNNING": {"": "审批中"}, + "TERMINATED": {"": "已撤销"}, +} + +APPROVE_LIST_BATCH_SIZE = 50 # attendance approve list 单次最多用户数 + +# 审批详情页 URL 模板 +# 内层:aflow 审批详情页 +_AFLOW_URL_TEMPLATE = ( + "https://aflow.dingtalk.com/dingtalk/mobile/homepage.htm" + "?corpid={corp_id}&dd_share=false&showmenu=true&back=native" + "#/approval?procInstId={instance_id}" +) +# 外层:dingtalk schema 协议,在钉钉客户端侧边面板打开 +_DINGTALK_SCHEMA_TEMPLATE = ( + "dingtalk://dingtalkclient/action/openapp" + "?corpid={corp_id}&container_type=slide_panel&app_id=-4" + "&&redirect_url={encoded_url}" +) + + +class HyperlinkCell: + """标记单元格为超链接:Excel 中显示 label 文本,点击跳转到 url。""" + + __slots__ = ("label", "url") + + def __init__(self, label: str, url: str): + self.label = label + self.url = url + + def __str__(self) -> str: + return self.label + + +def build_approve_url(corp_id: str, instance_id: str) -> str: + """ + 构建审批单跳转链接(dingtalk:// schema)。 + + 结构:外层 dingtalk schema 打开钉钉侧边面板,内部 redirect 到 aflow 审批详情页。 + """ + from urllib.parse import quote + + inner_url = _AFLOW_URL_TEMPLATE.format(corp_id=corp_id, instance_id=instance_id) + encoded_url = quote(inner_url, safe="") + return _DINGTALK_SCHEMA_TEMPLATE.format(corp_id=corp_id, encoded_url=encoded_url) + + +def build_approve_cell(corp_id: str, instance_id: str, title: str = "") -> HyperlinkCell | str: + """ + 构建"关联审批单"列的单元格值。 + + 如果有 corp_id 和 instance_id,返回 HyperlinkCell(Excel 中为可点击链接)。 + 否则返回纯文本。 + """ + if not instance_id: + return "" + label = title or instance_id + if not corp_id: + return label + url = build_approve_url(corp_id, instance_id) + return HyperlinkCell(label=label, url=url) + + +# ───────────────────────────────────────────────────────────────────────────── +# 工具函数 +# ───────────────────────────────────────────────────────────────────────────── + +def normalize_am_pm(text: str) -> str: + """将时间文本中的 AM/PM 替换为 上午/下午。""" + return text.replace(" PM", " 下午").replace(" AM", " 上午") + + +def format_status(status: str, result: str) -> str: + """将 status + processInstanceResult 转为中文状态。""" + status_upper = (status or "").upper() + result_lower = (result or "").lower() + group = STATUS_MAP.get(status_upper, {}) + return group.get(result_lower, group.get("", f"{status}/{result}")) + + +def ms_to_datetime(ms: int | float | None) -> datetime | None: + """毫秒时间戳转 datetime。""" + if not ms: + return None + try: + return datetime.fromtimestamp(int(ms) / 1000) + except (OSError, ValueError, OverflowError): + return None + + +def ms_to_time_str(ms: int | float | None) -> str: + """毫秒时间戳转 HH:MM。""" + dt = ms_to_datetime(ms) + return dt.strftime("%H:%M") if dt else "" + + +def ms_to_date_str(ms: int | float | None) -> str: + """毫秒时间戳转 YYYY-MM-DD。""" + dt = ms_to_datetime(ms) + return dt.strftime(DATE_FMT) if dt else "" + + +def format_day_type(detail: dict) -> str: + """从 detailList 单条判断日历类型。""" + day_type = detail.get("dayType", "") + is_rest = detail.get("isRest", False) + if day_type == "workDay" or (not is_rest and not day_type): + return "工作日" + if day_type == "restDay" or is_rest: + return "休息日" + if day_type == "holiday": + return "节假日" + return day_type or ("休息日" if is_rest else "工作日") + + +def format_class_time(detail: dict) -> str: + """从 detailList 单条提取上下班时间。""" + class_info = detail.get("classInfo", {}) + sections = class_info.get("sections", []) if class_info else [] + if not sections: + return "未排班" + section = sections[0] + start_time = ms_to_time_str(section.get("startTime")) + end_time = ms_to_time_str(section.get("endTime")) + if start_time and end_time: + return f"{start_time} ~ {end_time}" + return "未排班" + + +# ───────────────────────────────────────────────────────────────────────────── +# 数据查询 +# ───────────────────────────────────────────────────────────────────────────── + +# 钉钉接口把"外出"和"出差"都归类到 trip(bizType=2),out 类型查不到数据。 +# 脚本通过 tagName 区分:tagName="出差" → trip,tagName="外出" → out。 +_API_TYPE_MAP: dict[str, str] = { + "leave": "leave", + "trip": "trip", + "out": "trip", # 外出也用 trip 查询,再按 tagName 过滤 + "patch": "patch", +} + +_TAG_FILTER: dict[str, str | None] = { + "leave": None, + "trip": "出差", + "out": "外出", + "patch": None, +} + + +def fetch_approve_list(user_ids: list[str], record_type: str, start: str, end: str) -> list[dict]: + """ + 分批调用 dws attendance approve list 获取审批单摘要。 + + 返回列表中每条包含: userId, tagName, duration, durationUnit, beginTime, endTime, originId。 + 对于 out 类型,实际用 trip 查询接口,再按 tagName="外出" 过滤; + 对于 trip 类型,按 tagName="出差" 过滤(排除外出记录)。 + """ + api_type = _API_TYPE_MAP.get(record_type, record_type) + tag_filter = _TAG_FILTER.get(record_type) + + all_records: list[dict] = [] + for i in range(0, len(user_ids), APPROVE_LIST_BATCH_SIZE): + batch = user_ids[i:i + APPROVE_LIST_BATCH_SIZE] + users_str = ",".join(batch) + try: + result = run_dws([ + "attendance", "approve", "list", + "--users", users_str, + "--types", api_type, + "--start", start, + "--end", end, + ]) + records: list[dict] = [] + if isinstance(result, list): + records = result + elif isinstance(result, dict): + records = result.get("approveList", result.get("list", [])) + if not isinstance(records, list): + records = [] + # 按 tagName 过滤 + if tag_filter: + records = [r for r in records if r.get("tagName") == tag_filter] + all_records.extend(records) + except DwsCallError as e: + warn(f"查询审批列表失败(batch {i // APPROVE_LIST_BATCH_SIZE + 1}): {e}") + return all_records + + +def fetch_detail(instance_id: str) -> dict | None: + """调用 dws oa approval detail 获取审批单完整详情。""" + try: + result = run_dws([ + "oa", "approval", "detail", + "--instance-id", instance_id, + ]) + return result if isinstance(result, dict) else None + except DwsCallError as e: + warn(f"获取审批详情失败({instance_id[:20]}...): {e}") + return None + + +# ───────────────────────────────────────────────────────────────────────────── +# 解析器 +# ───────────────────────────────────────────────────────────────────────────── + +def find_holiday_field(form_values: list[dict]) -> dict | None: + """从 formValueVOS 中查找 DDHolidayField 组件。""" + for fv in form_values: + if fv.get("componentType") == "DDHolidayField": + return fv + return None + + +def parse_ext_value(field_data: dict) -> dict: + """解析字段的 extValue JSON 字符串。""" + ext_str = field_data.get("extValue") or "" + if not ext_str: + return {} + try: + return json.loads(ext_str) + except (json.JSONDecodeError, TypeError): + return {} + + +def parse_leave_detail(detail: dict, name_map: dict[str, str], *, + user_info_map: dict[str, "UserInfo"] | None = None, + group_map: dict[str, str] | None = None, + corp_id: str = "", + ) -> list[list[str]]: + """解析请假审批单。""" + form_values = detail.get("formValueVOS", []) + user_id = detail.get("originatorUserid", "") + dept_name = detail.get("originatorDeptName", "") + instance_id = detail.get("processInstanceId", "") + status = format_status(detail.get("status", ""), detail.get("processInstanceResult", "")) + + # 用户基础信息 + info = (user_info_map or {}).get(user_id) + user_name = info.name if info else name_map.get(user_id, user_id) + dept = info.dept_name if info and info.dept_name else dept_name + job_number = info.job_number if info else "" + title = info.title if info else "" + group_name = (group_map or {}).get(user_id, "") + approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的请假审批单") + + holiday_field = find_holiday_field(form_values) + if not holiday_field: + return [[user_name, group_name, dept, job_number, title, + "", "", "", "", approve_cell, status]] + + # value: ["开始时间","结束时间",天数,"单位","假期类型","请假类型"] + value_str = holiday_field.get("value", "") + leave_type = "" + leave_time = "" + try: + value_arr = json.loads(value_str) + if isinstance(value_arr, list) and len(value_arr) >= 2: + leave_time = normalize_am_pm(f"{value_arr[0]} ~ {value_arr[1]}") + if len(value_arr) > 4: + leave_type = str(value_arr[4]) + except (json.JSONDecodeError, TypeError): + leave_time = normalize_am_pm(value_str) + + ext = parse_ext_value(holiday_field) + duration_day = str(ext.get("durationInDay", "")) + duration_hour = str(ext.get("durationInHour", "")) + + return [[user_name, group_name, dept, job_number, title, + leave_type, leave_time, duration_hour, duration_day, + approve_cell, status]] + + +def _extract_time_duration_from_fields(form_values: list[dict]) -> tuple[str, str, str, str]: + """ + 从独立表单字段中提取时间范围和时长。 + + 适用于外出/出差表单的非 DDHolidayField 结构: + - startTime (DDDateField) + finishTime (DDDateField) → 时间范围 + - duration (NumberField) → extValue 中含 durationInDay / durationInHour + + Returns: (time_range, duration_hour, duration_day, ext_from_duration) + """ + start_time = "" + end_time = "" + duration_hour = "" + duration_day = "" + + for fv in form_values: + biz_alias = (fv.get("bizAlias") or "").lower() + name = (fv.get("name") or "").lower() + value = fv.get("value") or "" + + # 开始时间 + if biz_alias in ("starttime", "start_time") or "开始时间" in name: + if value and not start_time: + start_time = value + # 结束时间 + if biz_alias in ("finishtime", "finish_time", "endtime", "end_time") or "结束时间" in name: + if value and not end_time: + end_time = value + # 时长字段 — extValue 中有 durationInDay / durationInHour + if biz_alias == "duration" or "时长" in name: + ext = parse_ext_value(fv) + if ext: + duration_day = str(ext.get("durationInDay", "")) + duration_hour = str(ext.get("durationInHour", "")) + + time_range = "" + if start_time and end_time: + time_range = f"{start_time} ~ {end_time}" + elif start_time: + time_range = start_time + + return time_range, duration_hour, duration_day + + +def parse_out_detail(detail: dict, name_map: dict[str, str], *, + user_info_map: dict[str, "UserInfo"] | None = None, + group_map: dict[str, str] | None = None, + corp_id: str = "", + ) -> list[list[str]]: + """解析外出审批单。兼容 DDHolidayField 和独立字段两种表单结构。""" + form_values = detail.get("formValueVOS", []) + user_id = detail.get("originatorUserid", "") + dept_name = detail.get("originatorDeptName", "") + instance_id = detail.get("processInstanceId", "") + status = format_status(detail.get("status", ""), detail.get("processInstanceResult", "")) + + # 用户基础信息 + info = (user_info_map or {}).get(user_id) + user_name = info.name if info else name_map.get(user_id, user_id) + dept = info.dept_name if info and info.dept_name else dept_name + job_number = info.job_number if info else "" + title = info.title if info else "" + group_name = (group_map or {}).get(user_id, "") + approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的外出审批单") + + # 优先尝试 DDHolidayField + holiday_field = find_holiday_field(form_values) + if holiday_field: + value_str = holiday_field.get("value", "") + time_range = "" + try: + value_arr = json.loads(value_str) + if isinstance(value_arr, list) and len(value_arr) >= 2: + time_range = normalize_am_pm(f"{value_arr[0]} ~ {value_arr[1]}") + except (json.JSONDecodeError, TypeError): + time_range = normalize_am_pm(value_str) + ext = parse_ext_value(holiday_field) + duration_day = str(ext.get("durationInDay", "")) + duration_hour = str(ext.get("durationInHour", "")) + else: + # 回退: 从独立字段提取 + time_range, duration_hour, duration_day = _extract_time_duration_from_fields(form_values) + + return [[user_name, group_name, dept, job_number, title, + time_range, duration_hour, duration_day, + approve_cell, status]] + + +def parse_trip_from_approve_record( + record: dict, + name_map: dict[str, str], + *, + user_info_map: dict[str, "UserInfo"] | None = None, + group_map: dict[str, str] | None = None, + corp_id: str = "", +) -> list[str]: + """ + 直接从 attendance approve list 的记录中解析出差行。 + + 不依赖 oa approval detail(该接口对出差单存在 saNode 类型冲突 bug), + 仅使用 approve list 返回的 beginTime/endTime/duration/durationUnit/originId。 + """ + user_id = record.get("userId", "") + info = (user_info_map or {}).get(user_id) + user_name = info.name if info else name_map.get(user_id, user_id) + dept = info.dept_name if info else "" + job_number = info.job_number if info else "" + title = info.title if info else "" + group_name = (group_map or {}).get(user_id, "") + + begin_ms = record.get("beginTime") + end_ms = record.get("endTime") + begin_str = ms_to_date_str(begin_ms) if begin_ms else "" + end_str = ms_to_date_str(end_ms) if end_ms else "" + time_range = f"{begin_str} ~ {end_str}" if begin_str and end_str else begin_str or end_str + + duration = record.get("duration", "") + duration_unit = record.get("durationUnit", "DAY") + unit_str = "天" if duration_unit == "DAY" else "小时" + + instance_id = record.get("originId", "") + effective_corp_id = corp_id or record.get("corpId", "") + approve_cell = build_approve_cell(effective_corp_id, instance_id, f"{user_name}提交的出差审批单") + + # approve list 没有审批状态,有 gmtFinished 说明已完结,视为审批通过 + status = "审批通过" if record.get("gmtFinished") else "审批中" + + return [user_name, group_name, dept, job_number, title, + time_range, str(duration), unit_str, approve_cell, status] + + +def fetch_check_results(user_ids: list[str], start: str, end: str) -> dict[str, list[dict]]: + """ + 批量查询打卡结果,返回 {userId: [records...]} 映射。 + + 每条 record 含: workDate, timeResult, planCheckTime, userCheckTime 等。 + """ + result_map: dict[str, list[dict]] = {} + batch_size = 50 + for i in range(0, len(user_ids), batch_size): + batch = user_ids[i:i + batch_size] + try: + result = run_dws([ + "attendance", "check", "result", + "--users", ",".join(batch), + "--start", start, + "--end", end, + ]) + records = [] + if isinstance(result, list): + records = result + elif isinstance(result, dict): + records = result.get("result", result.get("list", [])) + if not isinstance(records, list): + records = [] + for rec in records: + uid = rec.get("userId", "") + if uid: + result_map.setdefault(uid, []).append(rec) + except DwsCallError as e: + warn(f"查询打卡结果失败(batch {i // batch_size + 1}): {e}") + return result_map + + +def fetch_user_group_map(user_ids: list[str]) -> dict[str, str]: + """ + 查询考勤组列表并建立 userId → 考勤组名称映射。 + + 流程:先 group search 拿到所有考勤组 ID+名称, + 再对有成员的考勤组调用 filtered-get --member 获取成员列表。 + """ + group_map: dict[str, str] = {} + user_id_set = set(user_ids) + + try: + result = run_dws(["attendance", "group", "search"]) + items: list[dict] = [] + if isinstance(result, list): + items = result + elif isinstance(result, dict): + # 适配 {items: [...]} 或 {result: {items: [...]}} + inner = result.get("items", result.get("result", result)) + if isinstance(inner, dict): + items = inner.get("items", []) + elif isinstance(inner, list): + items = inner + + for g in items: + group_name = g.get("name", g.get("groupName", "")) + group_id = g.get("id", g.get("groupId", "")) + member_count = g.get("memberCount", 0) + + if not group_id or not group_name or not member_count: + continue + + # 调用 filtered-get 获取成员 userId 列表 + try: + detail = run_dws([ + "attendance", "group", "filtered-get", + "--group-id", str(group_id), "--member", + ]) + member_users: list[str] = [] + if isinstance(detail, dict): + member_users = detail.get("memberUsers", []) + if not isinstance(member_users, list): + member_users = [] + for uid in member_users: + uid_str = str(uid) + if uid_str in user_id_set: + group_map[uid_str] = group_name + except DwsCallError: + pass + + except DwsCallError as e: + warn(f"查询考勤组失败: {e}") + return group_map + + +CHECK_TIME_RESULT_MAP = { + "Normal": "正常", + "Late": "迟到", + "Early": "早退", + "Absenteeism": "旷工", + "NotSigned": "未打卡", + "SeriousLate": "严重迟到", +} + + +def parse_patch_detail(detail: dict, name_map: dict[str, str], *, + user_info_map: dict[str, "UserInfo"] | None = None, + group_map: dict[str, str] | None = None, + check_result_map: dict[str, list[dict]] | None = None, + corp_id: str = "", + ) -> list[list[str]]: + """解析补卡审批单,输出完整列。""" + form_values = detail.get("formValueVOS", []) + user_id = detail.get("originatorUserid", "") + dept_name = detail.get("originatorDeptName", "") + instance_id = detail.get("processInstanceId", "") + status = format_status(detail.get("status", ""), detail.get("processInstanceResult", "")) + + # 用户基础信息 + info = (user_info_map or {}).get(user_id) + user_name = info.name if info else name_map.get(user_id, user_id) + dept = info.dept_name if info and info.dept_name else dept_name + job_number = info.job_number if info else "" + title = info.title if info else "" + group_name = (group_map or {}).get(user_id, "") + approve_cell = build_approve_cell(corp_id, instance_id, f"{user_name}提交的补卡审批单") + + # 从表单解析补卡时间和原因 + patch_time = "" + patch_reason = "" + work_date = "" + check_time_str = "" + ext_data: dict = {} + + for fv in form_values: + comp_type = fv.get("componentType", "") or "" + biz_alias = (fv.get("bizAlias") or "").lower() + name = fv.get("name") or "" + value = fv.get("value") or "" + + if comp_type == "DDDateField" or "checktime" in biz_alias or "补卡时间" in name: + if value and not patch_time: + patch_time = value + # 解析 extValue 获取考勤日期等 + ext = parse_ext_value(fv) + if ext and not ext_data: + ext_data = ext + if "reason" in biz_alias or "原因" in name or "事由" in name or "理由" in name: + if value and not patch_reason: + patch_reason = value + + # 从 extValue 提取考勤日期、考勤时间、原考勤状态 + plan_tip = "" + plan_text = "" + if ext_data: + work_date_ms = ext_data.get("workDate") + if work_date_ms: + work_date = ms_to_date_str(work_date_ms) + plan_tip = ext_data.get("planTip", "") + plan_text = ext_data.get("planText", "") + + # 从 planText / planTip 提取考勤时间(目标格式:YYYY-MM-DD HH:MM) + # planText 格式: "2026-04-25,星期六,195固定班次,上班时间09:00" + # planTip 格式: "周六上班(04.25 09:00) 缺卡" / "周一上班(04.27 09:00) 缺卡" + import re + plan_time_hhmm = "" + + # 优先从 planTip 提取(更可靠,含具体日期和时间) + # planTip 格式: "周三下班(03.05 01:00) 缺卡" + plan_date_from_tip = "" # MM.DD → 用于跨日场景 + if plan_tip: + # 匹配 "(MM.DD HH:MM)" 格式 + tip_match = re.search(r"\((\d{2})\.(\d{2})\s+(\d{2}:\d{2})\)", plan_tip) + if tip_match: + plan_date_from_tip = f"{tip_match.group(1)}-{tip_match.group(2)}" # "03-05" + plan_time_hhmm = tip_match.group(3) + + # 回退:从 planText 中提取 + if not plan_time_hhmm and plan_text: + # 匹配 "上班时间HH:MM" 或 "下班时间HH:MM" 或 "时间HH:MM" + time_match = re.search(r"时间(\d{2}:\d{2})", plan_text) + if time_match: + plan_time_hhmm = time_match.group(1) + + # 最后回退:任意 HH:MM 格式 + if not plan_time_hhmm: + for source in (plan_tip, plan_text): + if source: + fallback_match = re.search(r"(\d{2}:\d{2})", source) + if fallback_match: + plan_time_hhmm = fallback_match.group(1) + break + + # 拼接考勤时间:优先使用 planTip 中解析的完整日期(处理跨日班次) + if plan_date_from_tip and plan_time_hhmm and work_date: + # 用 work_date 的年份 + planTip 中的 MM-DD + HH:MM + year = work_date[:4] + check_time_str = f"{year}-{plan_date_from_tip} {plan_time_hhmm}" + elif work_date and plan_time_hhmm: + check_time_str = f"{work_date} {plan_time_hhmm}" + elif plan_time_hhmm: + check_time_str = plan_time_hhmm + elif plan_text: + check_time_str = plan_text + elif plan_tip: + check_time_str = plan_tip + + # 如果 work_date 为空,从 patch_time 中提取日期 + if not work_date and patch_time: + work_date = patch_time[:10] if len(patch_time) >= 10 else "" + + # 从 planTip / planText 提取原考勤状态 + # planTip 格式: "周六上班(04.25 09:00) 缺卡" / "Thursday ( 04.23 ) Adjust" + # planText 格式: "2026-04-25,星期六,195固定班次,上班时间09:00" 或 "周一上班(04.27 09:00) 缺卡" + original_check_time = "" + original_status = "" + + tip_status_map = { + "缺卡": "缺卡", "未打卡": "未打卡", + "迟到": "迟到", "早退": "早退", + "旷工": "旷工", "正常": "正常", + "NotSigned": "未打卡", "Adjust": "已调整", + } + # 优先从 planTip 提取,回退到 planText + for source in (plan_tip, plan_text): + if source: + for keyword, label in tip_status_map.items(): + if keyword in source: + original_status = label + break + if original_status: + break + + # 回退: 尝试从 check result 接口获取(如果有数据) + if check_result_map and user_id in check_result_map: + for rec in check_result_map[user_id]: + rec_date = rec.get("workDate", "") + if isinstance(rec_date, (int, float)): + rec_date = ms_to_date_str(rec_date) + if rec_date == work_date: + user_check_ms = rec.get("userCheckTime") + if user_check_ms: + dt = ms_to_datetime(user_check_ms) + original_check_time = dt.strftime("%Y-%m-%d %H:%M") if dt else "" + time_result = rec.get("timeResult", "") + if time_result: + original_status = CHECK_TIME_RESULT_MAP.get(time_result, time_result) + break + + # 补卡结果:审批通过 → 补卡成功 + patch_result = "" + if status == "审批通过": + patch_result = "补卡成功" + elif status == "已拒绝": + patch_result = "补卡失败" + elif status == "审批中": + patch_result = "待审批" + elif status == "已撤销": + patch_result = "已撤销" + + return [[user_name, group_name, dept, job_number, title, work_date, check_time_str, + original_check_time, original_status, patch_time, patch_result, + approve_cell, status]] + + +PARSERS = { + "leave": parse_leave_detail, + "out": parse_out_detail, + "patch": parse_patch_detail, +} + +# 需要额外用户信息(考勤组/工号/职位)的类型 +_TYPES_NEED_USER_INFO = {"leave", "out", "patch", "trip"} + + +# ───────────────────────────────────────────────────────────────────────────── +# 主流程 +# ───────────────────────────────────────────────────────────────────────────── + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="考勤记录报表导出(补卡/出差/外出/请假)", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("--type", required=True, choices=SUPPORTED_TYPES, + help="记录类型: leave(请假)/trip(出差)/out(外出)/patch(补卡)") + parser.add_argument("--users", required=True, + help="用户 ID 列表,逗号分隔(由 Agent 从人员获取阶段提供)") + parser.add_argument("--start", required=True, + help="开始日期 YYYY-MM-DD") + parser.add_argument("--end", required=True, + help="结束日期 YYYY-MM-DD") + parser.add_argument("--out", default="", + help="输出文件路径(不传则自动生成)") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + record_type: str = args.type + user_ids = [u.strip() for u in args.users.split(",") if u.strip()] + start_date: str = args.start + end_date: str = args.end + + if not user_ids: + error("--users 不能为空") + sys.exit(1) + + try: + datetime.strptime(start_date, DATE_FMT) + datetime.strptime(end_date, DATE_FMT) + except ValueError: + error("日期格式错误,请使用 YYYY-MM-DD") + sys.exit(1) + + sheet_name = SHEET_NAMES[record_type] + log(f"开始导出{sheet_name}:{len(user_ids)} 人,{start_date} ~ {end_date}") + + # ── Step 1: 获取审批单列表 ── + log("步骤 1/4:查询审批单列表...") + approve_records = fetch_approve_list(user_ids, record_type, start_date, end_date) + log(f" 获取到 {len(approve_records)} 条审批记录") + + if not approve_records: + log("未查询到任何记录") + print(f"{sheet_name}:0 条记录,无需生成文件") + sys.exit(0) + + # 从 approve list 记录中提取 corpId(用于构建审批单跳转链接) + corp_id = "" + for r in approve_records: + if r.get("corpId"): + corp_id = r["corpId"] + break + + # ── Step 2: 去重提取 instanceId ── + instance_ids = list(dict.fromkeys( + r.get("originId", "") for r in approve_records if r.get("originId") + )) + log(f"步骤 2/4:共 {len(instance_ids)} 个审批实例") + + # ── Step 3: 解析用户信息 ── + log("步骤 3/4:解析用户信息...") + name_map = resolve_user_names(user_ids) + + user_info_map: dict[str, UserInfo] | None = None + group_map: dict[str, str] | None = None + check_result_map: dict[str, list[dict]] | None = None + + if record_type in _TYPES_NEED_USER_INFO: + log(" 获取用户完整信息(工号/职位)...") + user_info_map = resolve_user_info(user_ids) + log(" 查询考勤组映射...") + group_map = fetch_user_group_map(user_ids) + + if record_type == "patch": + log(" 查询原打卡结果...") + check_result_map = fetch_check_results(user_ids, start_date, end_date) + + all_rows: list[list[str]] = [] + + if record_type == "trip": + # 出差记录直接从 approve list 数据生成,不调用 oa approval detail + # (oa approval detail 对出差单存在 saNode result 字段类型冲突 bug) + log("步骤 4/4:从审批列表解析出差记录...") + for record in approve_records: + row = parse_trip_from_approve_record( + record, name_map, + user_info_map=user_info_map, + group_map=group_map, + corp_id=corp_id, + ) + all_rows.append(row) + else: + log("步骤 4/4:查询审批详情并解析...") + for idx, instance_id in enumerate(instance_ids): + if (idx + 1) % 10 == 0: + log(f" 进度: {idx + 1}/{len(instance_ids)}") + + detail = fetch_detail(instance_id) + if not detail: + continue + + # 补充新发现的用户 + originator = detail.get("originatorUserid", "") + if originator and originator not in name_map: + extra = resolve_user_names([originator]) + name_map.update(extra) + if originator and user_info_map and originator not in user_info_map: + extra_info = resolve_user_info([originator]) + user_info_map.update(extra_info) + + if record_type == "patch": + rows = parse_patch_detail( + detail, name_map, + user_info_map=user_info_map, + group_map=group_map, + check_result_map=check_result_map, + corp_id=corp_id, + ) + elif record_type == "leave": + rows = parse_leave_detail( + detail, name_map, + user_info_map=user_info_map, + group_map=group_map, + corp_id=corp_id, + ) + elif record_type == "out": + rows = parse_out_detail( + detail, name_map, + user_info_map=user_info_map, + group_map=group_map, + corp_id=corp_id, + ) + else: + rows = PARSERS[record_type](detail, name_map) + all_rows.extend(rows) + + log(f" 解析完成,共 {len(all_rows)} 行") + + if not all_rows: + log("无有效数据行") + print(f"{sheet_name}:解析后 0 行有效数据,无需生成文件") + sys.exit(0) + + # ── 写入 Excel ── + out_path = args.out or f"attendance_report_record_{record_type}_{start_date}_{end_date}.xlsx" + headers = COLUMNS[record_type] + title = f"{sheet_name} 统计日期:{start_date} 至 {end_date}" + subtitle = f"报表生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}" + + # 将 HyperlinkCell 转为纯文本供 write_excel 写入,之后再补超链接 + plain_rows = [] + hyperlink_cells: list[tuple[int, int, str]] = [] # (row_offset, col_idx, url) + for row_offset, row in enumerate(all_rows): + plain_row = [] + for col_idx, cell in enumerate(row): + if isinstance(cell, HyperlinkCell): + plain_row.append(cell.label) + hyperlink_cells.append((row_offset, col_idx, cell.url)) + else: + plain_row.append(cell) + plain_rows.append(plain_row) + + write_excel( + out_path, + headers, + plain_rows, + sheet_name=sheet_name, + title=title, + subtitle=subtitle, + ) + + # 补充超链接 + if hyperlink_cells: + from openpyxl import load_workbook + from openpyxl.styles import Font + + wb = load_workbook(out_path) + ws = wb.active + # 计算标题行偏移:title + subtitle + header + title_row_count = (1 if title else 0) + (1 if subtitle else 0) + first_data_row = title_row_count + 2 # +1 for header, +1 for 1-indexed + + link_font = Font(color="0563C1", underline="single") + for row_offset, col_idx, url in hyperlink_cells: + cell = ws.cell(row=first_data_row + row_offset, column=col_idx + 1) + cell.hyperlink = url + cell.font = link_font + wb.save(out_path) + + abs_path = os.path.abspath(out_path) + log(f"✅ 导出完成: {abs_path}") + print(f"{sheet_name}导出完成:{abs_path}({len(all_rows)} 行,{len(instance_ids)} 个审批单)") + + +if __name__ == "__main__": + main() diff --git a/skills/mono/scripts/bot_broadcast.py b/skills/mono/scripts/bot_broadcast.py new file mode 100644 index 00000000..5317e222 --- /dev/null +++ b/skills/mono/scripts/bot_broadcast.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +""" +用机器人向多个群批量发送相同消息(如日报提醒) + +用法: + python bot_broadcast.py \ + --robot-code \ + --chats "conv_id1,conv_id2,conv_id3" \ + --title "日报提醒" \ + --text "请大家今天下班前提交日报" + + python bot_broadcast.py \ + --robot-code \ + --chats-file groups.txt \ + --title "周会通知" \ + --text "明天下午3点周会" + + python bot_broadcast.py --dry-run ... +""" + +import sys +import json +import subprocess +import argparse +from pathlib import Path +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return {'dry_run': True} + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f" ✗ 错误:{result.stderr.strip()}") + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f" ✗ 错误:{e}") + return None + + +def main(): + parser = argparse.ArgumentParser( + description='向多个群批量发送机器人消息' + ) + parser.add_argument( + '--robot-code', required=True, help='机器人 Code' + ) + parser.add_argument('--chats', default='', help='会话 ID 列表') + parser.add_argument( + '--chats-file', default='', + help='会话 ID 文件 (每行一个)', + ) + parser.add_argument('--title', required=True, help='消息标题') + parser.add_argument( + '--text', required=True, help='消息内容 Markdown' + ) + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + chat_ids: List[str] = [] + if args.chats: + chat_ids = [c.strip() for c in args.chats.split(',') + if c.strip()] + elif args.chats_file: + p = Path(args.chats_file) + if not p.exists(): + print(f"错误:文件不存在: {p}") + sys.exit(1) + chat_ids = [line.strip() for line in + p.read_text(encoding='utf-8').splitlines() + if line.strip() and not line.startswith('#')] + if not chat_ids: + print('错误:需要 --chats 或 --chats-file') + sys.exit(1) + + print(f"📢 批量发送消息到 {len(chat_ids)} 个群") + print(f" 标题: {args.title}") + print(f" 机器人: {args.robot_code}") + print('=' * 50) + + success, fail = 0, 0 + for i, chat_id in enumerate(chat_ids, 1): + result = run_dws([ + 'chat', 'message', 'send-by-bot', + '--robot-code', args.robot_code, + '--group', chat_id, + '--title', args.title, + '--text', args.text, + '--format', 'json', + ], dry_run=args.dry_run) + if result: + print(f" ✓ [{i}/{len(chat_ids)}] {chat_id}") + success += 1 + else: + print(f" ✗ [{i}/{len(chat_ids)}] {chat_id}") + fail += 1 + + print(f"\n完成: 成功 {success}, 失败 {fail}") + sys.exit(0 if fail == 0 else 1) + + +if __name__ == '__main__': + main() diff --git a/skills/mono/scripts/calendar_free_slot_finder.py b/skills/mono/scripts/calendar_free_slot_finder.py index fbab1296..b4bf20ff 100644 --- a/skills/mono/scripts/calendar_free_slot_finder.py +++ b/skills/mono/scripts/calendar_free_slot_finder.py @@ -51,26 +51,51 @@ def fmt_iso(dt: datetime) -> str: return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00') +def _extract_time_str(value: Any) -> str: + """时间字段兼容字符串与 {dateTime, date} 对象两种形态""" + if isinstance(value, dict): + return value.get('dateTime') or value.get('date') or '' + return value or '' + + def parse_busy_intervals( data: Any, ) -> List[Tuple[datetime, datetime]]: intervals = [] if not data: return intervals - items = [] + # 兼容 {result: [...]} 包裹结构 + if isinstance(data, dict) and 'result' in data: + data = data['result'] + rows = [] if isinstance(data, list): - items = data + rows = data elif isinstance(data, dict): - for user_data in data.values(): - if isinstance(user_data, list): - items.extend(user_data) - elif isinstance(user_data, dict): - items.extend( - user_data.get('busyTimes', []) - ) + rows = list(data.values()) + items = [] + for row in rows: + if isinstance(row, list): + items.extend(row) + elif isinstance(row, dict): + # 新结构: {userId, scheduleItems: [{start, end, status}]} + if isinstance(row.get('scheduleItems'), list): + items.extend(row['scheduleItems']) + elif isinstance(row.get('busyTimes'), list): + items.extend(row['busyTimes']) + elif row.get('startTime') or row.get('start'): + items.append(row) for item in items: - start_str = item.get('startTime') or item.get('start', '') - end_str = item.get('endTime') or item.get('end', '') + if not isinstance(item, dict): + continue + # FREE 时段不算忙 + if str(item.get('status') or '').upper() == 'FREE': + continue + start_str = _extract_time_str( + item.get('startTime') or item.get('start') + ) + end_str = _extract_time_str( + item.get('endTime') or item.get('end') + ) if not start_str or not end_str: continue for fmt in ( @@ -168,6 +193,16 @@ def main(): if args.dry_run: return + # 查询失败必须中止, 否则会把"查不到"当成"全天空闲"给出危险结论 + if data is None: + print('错误:忙闲查询失败,无法给出空闲时段结论', file=sys.stderr) + sys.exit(2) + if isinstance(data, dict) and data.get('success') is False: + print(f"错误:忙闲查询失败: " + f"{data.get('errorMsg') or data.get('errorCode') or data}", + file=sys.stderr) + sys.exit(2) + busy = parse_busy_intervals(data) free = find_free_slots(day_start, day_end, busy, args.duration) @@ -177,6 +212,7 @@ def main(): print(f" 会议时长: {args.duration} 分钟") print(f" 工作时间: {args.start_hour}:00 ~ " f"{args.end_hour}:00") + print(f" 已识别忙时段: {len(busy)} 个") print('=' * 50) if not free: diff --git a/skills/mono/scripts/calendar_today_agenda.py b/skills/mono/scripts/calendar_today_agenda.py index 76dd5501..c96494e1 100644 --- a/skills/mono/scripts/calendar_today_agenda.py +++ b/skills/mono/scripts/calendar_today_agenda.py @@ -91,11 +91,20 @@ def main(): if dry_run: return + # 兼容两种结构: 顶层 events / {result: {events: [...]}} events = [] if isinstance(data, list): events = data elif isinstance(data, dict): - events = data.get('events', data.get('result', [])) + events = data.get('events') + if events is None: + inner = data.get('result') + if isinstance(inner, list): + events = inner + elif isinstance(inner, dict): + events = inner.get('events', []) + if not isinstance(events, list): + events = [] label = {'today': '今天', 'tomorrow': '明天', 'week': '本周' }.get(scope, scope) diff --git a/skills/mono/scripts/chat_export_messages.py b/skills/mono/scripts/chat_export_messages.py index 54324607..2b77d85e 100644 --- a/skills/mono/scripts/chat_export_messages.py +++ b/skills/mono/scripts/chat_export_messages.py @@ -135,12 +135,21 @@ def main(): if not data: break + # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} if isinstance(data, list): page_msgs = data has_more = False else: - page_msgs = data.get('messages', data.get('result', [])) - has_more = data.get('hasMore', False) + container = data + inner = data.get('result') + if isinstance(inner, dict): + container = inner + page_msgs = container.get('messages') + if page_msgs is None and isinstance(inner, list): + page_msgs = inner + if not isinstance(page_msgs, list): + page_msgs = [] + has_more = bool(container.get('hasMore', False)) if not page_msgs: break @@ -153,7 +162,9 @@ def main(): break last_msg = page_msgs[-1] - boundary_time = last_msg.get('createAt') or last_msg.get('time', '') + boundary_time = (last_msg.get('createTime') + or last_msg.get('createAt') + or last_msg.get('time', '')) if not boundary_time or boundary_time == current_time: break current_time = boundary_time @@ -163,28 +174,18 @@ def main(): print('未拉取到消息') return - if isinstance(data, list): - messages = data - elif isinstance(data, dict): - inner = data.get('result', data) - if isinstance(inner, dict): - messages = inner.get('messages', inner.get('records', [])) - elif isinstance(inner, list): - messages = inner - else: - messages = [] - else: - messages = [] - if args.output: with open(args.output, 'w', encoding='utf-8') as f: json.dump(all_messages, f, ensure_ascii=False, indent=2) print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - sender = m.get('senderNick') or m.get('sender', '未知') - text = m.get('text') or m.get('content', '') - time_str = m.get('createAt') or m.get('time', '') + # 实际字段: sender/createTime/content (兼容旧字段名) + sender = (m.get('sender') or m.get('senderNick') + or m.get('senderOpenDingTalkId', '未知')) + text = m.get('content') or m.get('text', '') + time_str = (m.get('createTime') or m.get('createAt') + or m.get('time', '')) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/mono/scripts/chat_history_with_user.py b/skills/mono/scripts/chat_history_with_user.py index 4c92627c..b007c7be 100644 --- a/skills/mono/scripts/chat_history_with_user.py +++ b/skills/mono/scripts/chat_history_with_user.py @@ -9,7 +9,7 @@ 工作流: 1. 通过 --name 搜索通讯录,获取 userId(或直接传 --user) - 2. 调用 chat message list-direct --user 拉取单聊消息 + 2. 调用 chat message list --user 拉取单聊消息 3. 输出到终端或导出为 JSON 文件 """ @@ -116,7 +116,7 @@ def main(): while page < max_pages and remaining > 0: cmd_args = [ - 'chat', 'message', 'list-direct', + 'chat', 'message', 'list', '--user', user_id or '', '--time', current_time, '--format', 'json', @@ -135,16 +135,21 @@ def main(): if not data: break + # 兼容两种结构: 顶层 messages / {result: {messages, hasMore}} if isinstance(data, list): page_msgs = data has_more = False else: - # list-direct 返回 {result: {hasMore, messages: [...]}},先解包 result - result_obj = data.get('result', data) - if not isinstance(result_obj, dict): - result_obj = data - page_msgs = result_obj.get('messages', result_obj.get('records', [])) - has_more = result_obj.get('hasMore', data.get('hasMore', False)) + container = data + inner = data.get('result') + if isinstance(inner, dict): + container = inner + page_msgs = container.get('messages') + if page_msgs is None and isinstance(inner, list): + page_msgs = inner + if not isinstance(page_msgs, list): + page_msgs = [] + has_more = bool(container.get('hasMore', False)) if not page_msgs: break @@ -157,7 +162,8 @@ def main(): break last_msg = page_msgs[-1] - boundary_time = (last_msg.get('createTime') or last_msg.get('createAt') + boundary_time = (last_msg.get('createTime') + or last_msg.get('createAt') or last_msg.get('time', '')) if not boundary_time or boundary_time == current_time: break @@ -169,30 +175,18 @@ def main(): return # 3. 输出结果 - if isinstance(data, list): - messages = data - elif isinstance(data, dict): - inner = data.get('result', data) - if isinstance(inner, dict): - messages = inner.get('messages', inner.get('records', [])) - elif isinstance(inner, list): - messages = inner - else: - messages = [] - else: - messages = [] - if args.output: with open(args.output, 'w', encoding='utf-8') as f: json.dump(all_messages, f, ensure_ascii=False, indent=2) print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - if not isinstance(m, dict): - continue - sender = m.get('senderNick') or m.get('sender', '未知') - text = m.get('text') or m.get('content', '') - time_str = m.get('createTime') or m.get('createAt') or m.get('time', '') + # 实际字段: sender/createTime/content (兼容旧字段名) + sender = (m.get('sender') or m.get('senderNick') + or m.get('senderOpenDingTalkId', '未知')) + text = m.get('content') or m.get('text', '') + time_str = (m.get('createTime') or m.get('createAt') + or m.get('time', '')) print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/mono/scripts/doc_create_and_write.py b/skills/mono/scripts/doc_create_and_write.py index fbf52f8d..367e4b93 100644 --- a/skills/mono/scripts/doc_create_and_write.py +++ b/skills/mono/scripts/doc_create_and_write.py @@ -96,7 +96,7 @@ def main(): if not content: print('错误:需要 --content 或 --content-file') sys.exit(1) - chunk_size = 10000 + chunk_size = 30000 create_args = ['doc', 'create', '--name', args.name, '--format', 'json'] if args.folder: diff --git a/skills/mono/scripts/mail_send_with_cc.py b/skills/mono/scripts/mail_send_with_cc.py new file mode 100644 index 00000000..5df21e1d --- /dev/null +++ b/skills/mono/scripts/mail_send_with_cc.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +发送带抄送的邮件(自动获取发件地址、校验参数) + +用法: + python mail_send_with_cc.py \ + --to colleague@company.com \ + --cc boss@company.com,team@company.com \ + --subject "周报" \ + --body "本周完成任务A和任务B" + + python mail_send_with_cc.py --dry-run \ + --to a@b.com --subject "test" --body "hello" +""" + +import sys +import json +import subprocess +import re +import argparse +from typing import List, Any, Optional + +EMAIL_PATTERN = re.compile( + r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$' +) + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return {'dry_run': True} + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f"错误:{result.stderr.strip()}", file=sys.stderr) + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f"错误:{e}", file=sys.stderr) + return None + + +def validate_emails(emails_str: str) -> bool: + for email in emails_str.split(','): + email = email.strip() + if not EMAIL_PATTERN.match(email): + print(f"错误:无效邮箱地址 '{email}'") + return False + return True + + +def get_my_email(dry_run: bool = False) -> Optional[str]: + data = run_dws([ + 'mail', 'mailbox', 'list', '--format', 'json', + ], dry_run=dry_run) + if dry_run: + return '' + if not data: + return None + if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): + accounts = data['emailAccounts'] + # 优先企业邮箱(type=ORG),否则取第一个 + for acc in accounts: + if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): + return acc['email'] + if accounts and isinstance(accounts[0], dict): + return accounts[0].get('email') + return None + if isinstance(data, list) and data: + item = data[0] + return (item.get('email') or item.get('address') + if isinstance(item, dict) else str(item)) + if isinstance(data, dict): + return data.get('email') or data.get('address') + return None + + +def main(): + parser = argparse.ArgumentParser( + description='发送带抄送的邮件' + ) + parser.add_argument('--to', required=True, help='收件人') + parser.add_argument('--cc', default='', help='抄送人') + parser.add_argument('--subject', required=True, help='标题') + parser.add_argument('--body', required=True, help='正文') + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + if not validate_emails(args.to): + sys.exit(1) + if args.cc and not validate_emails(args.cc): + sys.exit(1) + + print('📬 获取发件邮箱...') + from_email = get_my_email(dry_run=args.dry_run) + if not from_email and not args.dry_run: + print('错误:无法获取发件邮箱') + sys.exit(1) + + cmd_args = [ + 'mail', 'message', 'send', + '--from', from_email or '', + '--to', args.to, + '--subject', args.subject, + '--content', args.body, + '--format', 'json', + ] + if args.cc: + cmd_args.extend(['--cc', args.cc]) + + print('📤 发送邮件...') + result = run_dws(cmd_args, dry_run=args.dry_run) + if result: + print(f" ✓ 邮件已发送") + print(f" 收件人: {args.to}") + if args.cc: + print(f" 抄送: {args.cc}") + print(f" 主题: {args.subject}") + else: + print(' ✗ 发送失败') + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/mono/scripts/mail_unread_summary.py b/skills/mono/scripts/mail_unread_summary.py new file mode 100644 index 00000000..ce2d4507 --- /dev/null +++ b/skills/mono/scripts/mail_unread_summary.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +查询今天未读邮件并汇总(自动获取邮箱地址) + +用法: + python mail_unread_summary.py + python mail_unread_summary.py --size 30 + python mail_unread_summary.py --dry-run +""" + +import sys +import json +import subprocess +import argparse +from datetime import datetime, timezone, timedelta +from typing import List, Any, Optional + +TZ = timezone(timedelta(hours=8)) + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return None + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f"错误:{result.stderr.strip()}", file=sys.stderr) + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f"错误:{e}", file=sys.stderr) + return None + + +def get_my_email(dry_run: bool = False) -> Optional[str]: + data = run_dws([ + 'mail', 'mailbox', 'list', '--format', 'json', + ], dry_run=dry_run) + if dry_run: + return '' + if not data: + return None + if isinstance(data, dict) and isinstance(data.get('emailAccounts'), list): + accounts = data['emailAccounts'] + # 优先企业邮箱(type=ORG),否则取第一个 + for acc in accounts: + if isinstance(acc, dict) and acc.get('type') == 'ORG' and acc.get('email'): + return acc['email'] + if accounts and isinstance(accounts[0], dict): + return accounts[0].get('email') + return None + if isinstance(data, list) and data: + item = data[0] + return (item.get('email') or item.get('address') + if isinstance(item, dict) else str(item)) + if isinstance(data, dict): + return data.get('email') or data.get('address') + return None + + +def main(): + parser = argparse.ArgumentParser( + description='查询今天未读邮件' + ) + parser.add_argument( + '--size', type=int, default=20, help='返回数量' + ) + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + print('📬 获取邮箱地址...') + email = get_my_email(dry_run=args.dry_run) + if not email and not args.dry_run: + print('错误:无法获取邮箱地址') + sys.exit(1) + + today = datetime.now(TZ).strftime('%Y-%m-%dT00:00:00Z') + kql = f'isRead:false AND date>{today}' + + print(f'🔍 搜索未读邮件...\n') + data = run_dws([ + 'mail', 'message', 'search', + '--email', email or '', + '--query', kql, + '--size', str(args.size), + '--format', 'json', + ], dry_run=args.dry_run) + + if args.dry_run: + return + if not data: + print('未查到邮件') + return + + messages = (data if isinstance(data, list) + else data.get('items', data.get('messages', []))) + + print(f"📧 今日未读邮件") + print('=' * 50) + if not messages: + print(' ✅ 收件箱清空,没有未读邮件!') + return + + for m in messages: + subj = m.get('subject', '(无主题)') + sender = m.get('from', {}) + sender_name = (sender.get('name') or sender.get('email', '未知') + if isinstance(sender, dict) else str(sender)) + print(f" 📩 {subj}") + print(f" 发件人: {sender_name}") + + print(f"\n合计: {len(messages)} 封未读邮件") + + +if __name__ == '__main__': + main() diff --git a/skills/mono/scripts/minutes_list_parse.py b/skills/mono/scripts/minutes_list_parse.py index 712327a9..d6aa83ea 100644 --- a/skills/mono/scripts/minutes_list_parse.py +++ b/skills/mono/scripts/minutes_list_parse.py @@ -17,7 +17,7 @@ def _unwrap_rows(payload: Any) -> List[Any]: return value if isinstance(value, dict): for inner_key in ( - 'items', 'list', 'records', 'minutes', + 'itemList', 'items', 'list', 'records', 'minutes', ): inner = value.get(inner_key) if isinstance(inner, list): @@ -30,7 +30,8 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: out: List[Tuple[str, str]] = [] for item in _unwrap_rows(payload): if isinstance(item, dict): - uuid = item.get('taskUuid') or item.get('id') or item.get('task_uuid') + uuid = (item.get('taskUuid') or item.get('uuid') + or item.get('id') or item.get('task_uuid')) if not uuid: continue title = item.get('title') or item.get('name') or '无标题' @@ -53,6 +54,7 @@ def uuid_title_pairs_from_payload(payload: Any) -> List[Tuple[str, str]]: continue uuid = ( parsed.get('taskUuid') + or parsed.get('uuid') or parsed.get('id') or parsed.get('task_uuid') ) diff --git a/skills/mono/scripts/minutes_recent_summary.py b/skills/mono/scripts/minutes_recent_summary.py index 24e8ed44..f136f290 100644 --- a/skills/mono/scripts/minutes_recent_summary.py +++ b/skills/mono/scripts/minutes_recent_summary.py @@ -93,9 +93,16 @@ def main(): if isinstance(summary_data, str): summary_text = summary_data elif isinstance(summary_data, dict): - summary_text = (summary_data.get('summary') - or summary_data.get('content') - or json.dumps(summary_data, + # 兼容 {result: {fullSummary}} 包裹结构 + inner = summary_data.get('result') + container = (inner if isinstance(inner, dict) + else summary_data) + summary_text = (container.get('fullSummary') + or container.get('summary') + or container.get('content') + or (inner if isinstance(inner, str) + else '') + or json.dumps(container, ensure_ascii=False)) output_lines.append(f"## {i}. {title}\n") diff --git a/skills/mono/scripts/report_received_today.py b/skills/mono/scripts/report_received_today.py new file mode 100644 index 00000000..a08e8777 --- /dev/null +++ b/skills/mono/scripts/report_received_today.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +""" +查看今天收到的日志列表及详情 + +用法: + python report_received_today.py + python report_received_today.py --days 3 # 最近 3 天 + python report_received_today.py --dry-run +""" + +import sys +import json +import subprocess +import argparse +from datetime import datetime, timedelta +from typing import List, Any, Optional + + +def run_dws( + args: List[str], dry_run: bool = False, +) -> Optional[Any]: + cmd = ['dws'] + args + if dry_run: + print(f"[dry-run] {' '.join(cmd)}") + return None + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) + if result.returncode != 0: + print(f"错误:{result.stderr.strip()}", file=sys.stderr) + return None + return json.loads(result.stdout) + except (subprocess.TimeoutExpired, json.JSONDecodeError, + FileNotFoundError) as e: + print(f"错误:{e}", file=sys.stderr) + return None + + +def to_iso(dt: datetime) -> str: + return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00') + + +def main(): + parser = argparse.ArgumentParser( + description='查看收到的日志' + ) + parser.add_argument( + '--days', type=int, default=1, help='查询天数 (默认 1)' + ) + parser.add_argument('--dry-run', action='store_true') + args = parser.parse_args() + + now = datetime.now() + start = now - timedelta(days=args.days) + start = start.replace(hour=0, minute=0, second=0) + + label = '今天' if args.days == 1 else f'最近 {args.days} 天' + print(f'📓 查看{label}收到的日志...\n') + + data = run_dws([ + 'report', 'list', + '--start', to_iso(start), + '--end', to_iso(now), + '--cursor', '0', + '--size', '20', + '--format', 'json', + ], dry_run=args.dry_run) + + if args.dry_run: + return + if not data: + print('未查到日志') + return + + if isinstance(data, list): + reports = data + elif isinstance(data, dict): + inner = data.get('result', data) + if isinstance(inner, dict): + reports = inner.get('report_list', + inner.get('reports', [])) + elif isinstance(inner, list): + reports = inner + else: + reports = [] + else: + reports = [] + if not reports: + print(' ✅ 暂无收到的日志') + return + + print(f"📓 {label}日志 ({len(reports)} 条)") + print('=' * 50) + + for r in reports: + if not isinstance(r, dict): + print(f"\n 📝 {r}") + continue + rid = r.get('reportId') or r.get('id', '') + creator = r.get('creatorName') or r.get('creator', '未知') + template = r.get('templateName') or r.get('template', '') + create_time = r.get('createTime', '') + if isinstance(create_time, (int, float)): + create_time = datetime.fromtimestamp( + create_time / 1000 + ).strftime('%Y-%m-%d %H:%M') + + print(f"\n 📝 {template or '日志'} - {creator}") + print(f" 时间: {create_time}") + print(f" ID: {rid}") + + if rid: + detail = run_dws([ + 'report', 'detail', + '--report-id', rid, '--format', 'json', + ]) + if detail and isinstance(detail, dict): + contents = detail.get('contents', []) + for c in contents[:3]: + key = c.get('key') or c.get('title', '') + val = c.get('value') or c.get('content', '') + if key and val: + print(f" {key}: {str(val)[:60]}") + + +if __name__ == '__main__': + main() diff --git a/skills/mono/scripts/todo_daily_summary.py b/skills/mono/scripts/todo_daily_summary.py index 3fb75381..43911912 100644 --- a/skills/mono/scripts/todo_daily_summary.py +++ b/skills/mono/scripts/todo_daily_summary.py @@ -37,7 +37,12 @@ def run_dws(args: List[str], dry_run: bool = False) -> Optional[Any]: except subprocess.TimeoutExpired: print('错误:命令执行超时', file=sys.stderr) return None - except (json.JSONDecodeError, FileNotFoundError) as e: + except json.JSONDecodeError: + # 输出非 JSON 多为底层错误 (如 TOKEN 失效), 透出原文 + print(f"错误:dws 返回非 JSON 输出: " + f"{result.stdout.strip()[:300]}", file=sys.stderr) + return None + except FileNotFoundError as e: print(f"错误:{e}", file=sys.stderr) return None @@ -56,9 +61,32 @@ def get_date_range(scope: str): return today_start, today_start + timedelta(days=1) +def extract_todo_cards(data: Any) -> Optional[List[Dict[str, Any]]]: + """兼容两种结构: 顶层 todoCards / {result: {todoCards: [...]}}。 + 返回 None 表示结构无法识别或调用失败。""" + if isinstance(data, list): + return data + if not isinstance(data, dict): + return None + if data.get('success') is False: + print(f"错误:todo 查询失败: " + f"{data.get('errorMsg') or data.get('errorCode') or data}", + file=sys.stderr) + return None + items = data.get('todoCards') + if items is None: + inner = data.get('result') + if isinstance(inner, dict): + items = inner.get('todoCards') + elif isinstance(inner, list): + items = inner + return items if isinstance(items, list) else None + + def fetch_all_todos( dry_run: bool = False, -) -> List[Dict[str, Any]]: +) -> Optional[List[Dict[str, Any]]]: + """返回 None 表示查询失败 (与"确实没有待办"区分开)""" all_todos: List[Dict[str, Any]] = [] for page in range(1, MAX_PAGES + 1): data = run_dws([ @@ -70,12 +98,12 @@ def fetch_all_todos( ], dry_run=dry_run) if dry_run: return [] - if not data: - break - items = data if isinstance(data, list) else data.get( - 'result', data.get('todoCards', []) - ) - if not items or not isinstance(items, list): + if data is None: + return None if page == 1 else all_todos + items = extract_todo_cards(data) + if items is None: + return None if page == 1 else all_todos + if not items: break all_todos.extend(items) if len(items) < PAGE_SIZE: @@ -161,6 +189,9 @@ def main(): todos = fetch_all_todos(dry_run=dry_run) if dry_run: return + if todos is None: + print('错误:待办查询失败,无法给出汇总结论', file=sys.stderr) + sys.exit(2) filtered = filter_by_due(todos, start, end) print_summary(filtered, scope, start, end) diff --git a/skills/mono/scripts/todo_overdue_check.py b/skills/mono/scripts/todo_overdue_check.py index f25471f8..1e5f33b9 100644 --- a/skills/mono/scripts/todo_overdue_check.py +++ b/skills/mono/scripts/todo_overdue_check.py @@ -33,13 +33,45 @@ def run_dws( print(f"错误:{result.stderr.strip()}", file=sys.stderr) return None return json.loads(result.stdout) - except (subprocess.TimeoutExpired, json.JSONDecodeError, - FileNotFoundError) as e: + except subprocess.TimeoutExpired: + print('错误:命令执行超时', file=sys.stderr) + return None + except json.JSONDecodeError: + # 输出非 JSON 多为底层错误 (如 TOKEN 失效), 透出原文 + print(f"错误:dws 返回非 JSON 输出: " + f"{result.stdout.strip()[:300]}", file=sys.stderr) + return None + except FileNotFoundError as e: print(f"错误:{e}", file=sys.stderr) return None -def fetch_all_undone(dry_run: bool = False) -> List[Dict[str, Any]]: +def extract_todo_cards(data: Any) -> Optional[List[Dict[str, Any]]]: + """兼容两种结构: 顶层 todoCards / {result: {todoCards: [...]}}。 + 返回 None 表示结构无法识别或调用失败。""" + if isinstance(data, list): + return data + if not isinstance(data, dict): + return None + if data.get('success') is False: + print(f"错误:todo 查询失败: " + f"{data.get('errorMsg') or data.get('errorCode') or data}", + file=sys.stderr) + return None + items = data.get('todoCards') + if items is None: + inner = data.get('result') + if isinstance(inner, dict): + items = inner.get('todoCards') + elif isinstance(inner, list): + items = inner + return items if isinstance(items, list) else None + + +def fetch_all_undone( + dry_run: bool = False, +) -> Optional[List[Dict[str, Any]]]: + """返回 None 表示查询失败 (与"确实没有待办"区分开)""" all_todos: List[Dict[str, Any]] = [] for page in range(1, MAX_PAGES + 1): data = run_dws([ @@ -47,11 +79,14 @@ def fetch_all_undone(dry_run: bool = False) -> List[Dict[str, Any]]: '--page', str(page), '--size', str(PAGE_SIZE), '--status', 'false', '--format', 'json', ], dry_run=dry_run) - if dry_run or not data: - break - items = (data if isinstance(data, list) - else data.get('result', data.get('todoCards', []))) - if not items or not isinstance(items, list): + if dry_run: + return [] + if data is None: + return None if page == 1 else all_todos + items = extract_todo_cards(data) + if items is None: + return None if page == 1 else all_todos + if not items: break all_todos.extend(items) if len(items) < PAGE_SIZE: @@ -84,10 +119,16 @@ def days_overdue(due_ms) -> int: def main(): + if '--help' in sys.argv or '-h' in sys.argv: + print(__doc__) + return dry_run = '--dry-run' in sys.argv todos = fetch_all_undone(dry_run=dry_run) if dry_run: return + if todos is None: + print('错误:待办查询失败,无法给出逾期结论', file=sys.stderr) + sys.exit(2) overdue = find_overdue(todos) overdue.sort( diff --git a/skills/multi/dingtalk-aisearch/SKILL.md b/skills/multi/dingtalk-aisearch/SKILL.md index 557db423..298a9c69 100644 --- a/skills/multi/dingtalk-aisearch/SKILL.md +++ b/skills/multi/dingtalk-aisearch/SKILL.md @@ -18,20 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[aisearch.md](references/aisearch.md)。 -> 跨组织:当前组织搜不到人时,别判定「查无此人」——先 `dws profile list` 看有哪些已登录组织,再对每个组织带 `--profile ` 各搜一遍,全无才追问用户。详见 `dingtalk-profile` skill。 - -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-aitable/SKILL.md b/skills/multi/dingtalk-aitable/SKILL.md index 0e5844f4..687d149f 100644 --- a/skills/multi/dingtalk-aitable/SKILL.md +++ b/skills/multi/dingtalk-aitable/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[aitable.md](references/aitable.md);复杂命令按需加载 `references/aitable/*.md`;剧本:[06-data-analytics.md](references/06-data-analytics.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-aitable/references/06-data-analytics.md b/skills/multi/dingtalk-aitable/references/06-data-analytics.md index 501dcb05..6b089723 100644 --- a/skills/multi/dingtalk-aitable/references/06-data-analytics.md +++ b/skills/multi/dingtalk-aitable/references/06-data-analytics.md @@ -4,10 +4,10 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| -| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. **取记录(按场景分流)**:
  • 数据统计/分析/全量汇总 → `aitable record query --base-id --table-id --all`(自动翻页,**禁止凭单页数据做统计**)
  • 大表保险 → 加 `--page-limit 100`(默认 50 页/5000 条,0 = 无限制)
  • 单纯预览前几条 → `aitable record query --base-id --table-id --limit 30`(不加 --all)
  • 筛选时 `--filters` 格式见 [aitable-filter-sort.md](aitable/aitable-filter-sort.md)
4. **检查输出契约**:`hasMore=true` 时数据被截断,必须用 `--cursor ` 续拉;`partial=true` 时表示中途某页失败(保留已拉数据,可重试)
5. 总结数据 | +| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. `aitable field get --base-id --table-id ` → 取 `fieldId`
3. **取记录(按场景分流)**:
  • 数据统计/分析/全量汇总 → `aitable record query --base-id --table-id --all`(自动翻页,**禁止凭单页数据做统计**)
  • 大表保险 → 加 `--page-limit 100`(默认 50 页/5000 条,0 = 无限制)
  • 单纯预览前几条 → `aitable record query --base-id --table-id --limit 30`(不加 --all)
  • 筛选时 `--filters` 格式见 [aitable-filter-sort.md](../products/aitable/aitable-filter-sort.md)
4. **检查输出契约**:`hasMore=true` 时数据被截断,必须用 `--cursor ` 续拉;`partial=true` 时表示中途某页失败(保留已拉数据,可重试)
5. 总结数据 | | generate-data-report | 1. 同 read-aitable 步骤 1-3(**必须用 --all 防漏数据**)
2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景
3. `doc create --name "<报告名>" --content "<分析报告>"` | | create-aitable-record | **写入路径分流**(关键决策):
  • **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)
  • **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id --file ./x.xlsx` + `dws aitable import data --import-id `(CLI 已内置 OSS PUT 清空 Content-Type + 同步轮询,**禁止自己写 PUT**)
  • **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"":"值"}}]'` | | update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. **取目标 record**:
  • 已知少量 recordId → `aitable record query --record-ids `
  • 按条件批量改 → `aitable record query --base-id --table-id --filters '' --all`(**用 --all 防止漏改**)
3. **先展示让用户确认要改的 record 列表**
4. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'`(单次 ≤30 条) | | search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`
2. 用户选定
3. `aitable base create --name "<表格名>" --template-id ` → 取 `baseId` | -| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --base-id --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | +| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
  • 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
  • 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
  • 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` | | primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:
1. `aitable record query` 取 `recordId`
2. `aitable base get-primary-doc-id --base-id --table-id --record-id ` → 取主键文档 `dentryUuid`
3. 凭 `dentryUuid` 调 `doc read --node ` 拿文档内容 | diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index a2dc9eee..85e759fd 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -7,15 +7,9 @@ | 资源 | URI 格式 | |------|----------| | Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | -| 指定数据表 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | -| 指定数据表+视图 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | | 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | -> **操作后请返回文档 URI**:返回链接时必须带上当前操作的数据表 tableId,让用户点击后直接看到目标数据表,而不是落在空白的默认表。 -> - 已知 tableId + viewId 时(view create 返回、view get 中提取):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` -> - 已知 tableId 时(table create 返回、base get 中提取、record 操作所用的 tableId):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` -> - 仅有 baseId、无明确 tableId 时(如 base list/search):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}` -> +> **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。 > 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。 ## 命令索引表 @@ -27,7 +21,7 @@ | `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` | | `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 | | `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID | -| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` | +| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId | | `base update` | 更新 Base 名称 | `--base-id` `--name` | — | | `base delete` | 删除 Base | `--base-id` | 不可逆 | @@ -37,7 +31,7 @@ |------|------|----------|----------| | `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 | | `table create` | 创建数据表 | `--base-id` `--name` `--fields` | fields 为 JSON 数组,至少 1 个 | -| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` + 三选一(`--name` / `--description` / `--record-name-key`) | `--record-name-key` 是固定枚举(如 task/project/event/customer/ji_lu 等),非字段 ID | +| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — | | `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 | ### field (字段管理) → 详见 [aitable-field.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md) @@ -49,30 +43,6 @@ | `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 | | `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 | -#### 搜索字段选项 -``` -Usage: - dws aitable field search-options [flags] -Example: - dws aitable field search-options --base-id --table-id --field-id - dws aitable field search-options --base-id --table-id --field-id --keyword 已完成 - dws aitable field search-options --base-id --table-id --field-id --limit 100 -Flags: - --base-id string Base ID (必填) - --field-id string 目标字段 ID,必须是 singleSelect / multipleSelect 类型 (必填) - --keyword string 模糊搜索关键词,大小写不敏感、contains 匹配 option name;不传返回全部 - --limit int 返回的最大 option 数量,默认 3000(全量),最大 3000 - --table-id string Table ID (必填) -``` - -仅适用于 **singleSelect / multipleSelect** 字段。其他类型(text/number/date/...)调用会返回错误。 - -适用场景: -- options 较多,只想要含某关键词的子集(避免 `field get` 拉取整个字段配置带回所有 options)。 -- 写入 record 前预览选项 id ↔ name 的映射,确认要使用的选项确实存在。 - -> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 可直接传 option **name**,不需要用本命令。本命令主要用于 **filter** 写法(filters 优先用 option **id**)或选项较多需要精确定位时。 - ### record (记录管理) | 命令 | 用途 | 必读 reference | 路由提醒 | @@ -80,66 +50,28 @@ Flags: | `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference | | `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit | | `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | -| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;`--records` 是 `[{recordId,cells},...]` 数组 | -| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 | +| `record update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag | | `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 | -| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 | -| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) | -| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 | -| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 | -| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` | -| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId | ### view (视图管理) | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| -| `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;**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) | -| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) | +| `view get` | 获取视图配置 | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 | +| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner | +| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 | | `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 | -> **优先用 `view get ` / `view update ` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。 +> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。 -> **属性按 attr 分类,决定该读哪份子文档**: -> - 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 / duplicate → [aitable-view-extras.md](./aitable/aitable-view-extras.md) -> 后一类**不能**塞进 `view update --config '{...}'`,必须用各自专属子命令;如果错传 `flags` / `frozenColCount` / `cellHeight` / `conditionalFormats` 等 key 进 `--config`,CLI 会在 stderr 提示应改用的命令。 - -> **`view update --config` 支持的 9 个 key**: -> `visibleFieldIds` / `filter` / `sort` / `group` / `fieldWidths`(Grid) / `aggregate`(Grid) / `kanbanCard`(Kanban) / `ganttTimebar`(Gantt) / `galleryCard`(Gallery)。 -> filter/sort/group 必须传**数组**格式(与 `record query --filters` 的对象格式不同;CLI 会自动容错)。其他 key 会被服务端忽略并打 warning。 - -### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `form list` | 列出表单视图 | `--base-id` `--table-id` | 详情见 [aitable-form.md](./aitable/aitable-form.md) | -| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | — | -| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | — | -| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 | -| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 | -| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 | -| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 | -| `form share get/update` | 表单分享配置 | — | 详情见子文档 | - -> **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。 - -### workflow (自动化工作流) → 详见 [aitable-workflow.md](./aitable/aitable-workflow.md) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` | -| `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) | -| `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` | -| `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP | - -> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。 +> **view update --config 支持的 key 白名单**(传入其他 key 会报错): +> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位) +> - `filter` — 筛选规则列表 +> - `sort` — 排序规则列表 +> - `group` — 分组规则列表 +> - `fieldWidths` — 列宽映射(仅 Grid 视图有效) +> +> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。 ### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) @@ -147,7 +79,6 @@ Flags: |------|------| | `dashboard get/create/update/delete` | 仪表盘管理 | | `dashboard config-example` | 查看仪表盘配置模板 | -| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) | | `chart get/create/update/delete` | 图表管理 | | `chart widgets-example` | 查看图表 widgets 配置模板 | @@ -171,163 +102,6 @@ Flags: |------|------|----------| | `template search` | 搜索模板 | `--query` | -### advperm (高级权限/自定义角色) → 详见 [aitable-advperm.md](./aitable/aitable-advperm.md) - -| 命令 | 用途 | 必填参数 | 路由提醒 | -|------|------|----------|----------| -| `advperm enable` | 开启 Base 高级权限总开关 | `--base-id` | 不开启时角色规则不生效 | -| `advperm disable` | 关闭 Base 高级权限总开关(高危) | `--base-id` `--yes` | 关闭后全员回退默认权限 | -| `advperm role-list` | 列出 Base 下所有角色 | `--base-id` | 同时返回自定义角色和系统角色;`roleType == "custom"` 是自定义,前缀 `system_` 是系统角色 | -| `advperm role-get` | 获取单角色完整配置 | `--base-id` `--role-id` | 含 subRoles 与字段/行级规则 | -| `advperm role-create` | 创建自定义角色 | `--base-id` `--name` | 可选 `--sub-roles` 同时指定子角色权限规则 | -| `advperm role-update` | 增量更新自定义角色(PATCH) | `--base-id` `--role-id` | 未传字段不变;`--sub-roles` 按 (targetId,targetType) 合并 | -| `advperm role-delete` | 删除自定义角色 | `--base-id` `--role-id` `--yes` | 不可逆;系统角色禁删;**调用者必须是该 AI 表格的管理员/Owner**,非管理员会得到 401 AUTH_ERROR | - -> **角色 CRUD 已全支持**:create/get/list/update/delete 都可走 CLI。 -> 所有写命令(enable/disable/role-create/role-update/role-delete)需要 Base 管理员权限;非管理员只能调 `role-list` / `role-get`(只读)。 -> "角色 ↔ 成员"绑定当前 CLI 不支持,仍需在 AI 表格 Web 端 → Base 设置 → 高级权限面板手动完成。 - -### section (文件夹与节点管理) - -> 用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。 -> 操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。 - -#### 创建文件夹 -``` -Usage: - dws aitable section create [flags] -Example: - dws aitable section create --base-id --name 我的文件夹 - dws aitable section create --base-id --name 子文件夹 --parent-section-id --index 0 -Flags: - --base-id string Base ID (必填) - --name string 文件夹名称 (必填) - --parent-section-id string 父文件夹 ID;不传或空字符串表示创建在 Base 根目录下 - --index int 在父文件夹下的目标位置(0-based);不传则追加到末尾 -``` - -返回 `data.sectionId` 与 `data.name`。 - -#### 重命名文件夹 -``` -Usage: - dws aitable section rename [flags] -Example: - dws aitable section rename --base-id --section-id --new-name 新名称 -Flags: - --base-id string Base ID (必填) - --section-id string 目标文件夹 ID (必填) - --new-name string 新的文件夹名称 (必填) -``` - -#### 删除文件夹 -``` -Usage: - dws aitable section delete [flags] -Example: - dws aitable section delete --base-id --section-id -Flags: - --base-id string Base ID (必填) - --section-id string 目标文件夹 ID (必填) -``` - -> **注意**:删除不可逆;删除前可先用 `section list-empty` 确认是否为空文件夹。 - -#### 调整文件夹顺序 -``` -Usage: - dws aitable section reorder [flags] -Example: - dws aitable section reorder --base-id --section-id --target-index 0 -Flags: - --base-id string Base ID (必填) - --section-id string 目标文件夹 ID (必填) - --target-index int 目标位置(0-based)(必填) -``` - -> 在**当前父文件夹下**调整展示顺序。跨父级移动请用 `section move-node`。 - -#### 列出空文件夹 -``` -Usage: - dws aitable section list-empty [flags] -Example: - dws aitable section list-empty --base-id -Flags: - --base-id string Base ID (必填) -``` - -返回 `data.items: [{sectionId, name, parentSectionId}]` 与 `data.total`,用于清理或诊断导航树(parentSectionId 为空串表示在根目录下)。 - -#### 列出全部节点 -``` -Usage: - dws aitable section list-nodes [flags] -Example: - dws aitable section list-nodes --base-id -Flags: - --base-id string Base ID (必填) -``` - -返回 `data.items: [{nodeId, nodeType, parentSectionId, name?}]` 与 `data.total`,涵盖文件夹 / AI 表格 / 表单视图 / 仪表盘 / 文档 / 查询视图。 - -> **与其他命令的关联**:是 `section move-node` / `section reorder` 的前置定位命令——先用它拿到 nodeId 与 parentSectionId。 - -#### 移动节点 -``` -Usage: - dws aitable section move-node [flags] -Example: - dws aitable section move-node --base-id --node-id --new-parent-section-id - dws aitable section move-node --base-id --node-id --new-parent-section-id "" --target-index 0 -Flags: - --base-id string Base ID (必填) - --node-id string 要移动的节点 ID(文件夹/AI表格/表单视图/仪表盘/文档/查询视图)(必填) - --new-parent-section-id string 目标父文件夹 ID;空字符串表示移到 Base 根目录 (必填) - --target-index int Base 内节点的全局位置(0-based);不传则不调整 -``` - -> 服务端自动识别节点类型,无需区分文件夹与非文件夹。返回 `data.nodeId / newParentSectionId / nodeType`。 -> 对文件夹节点带 `--target-index` 时会先 move 再 reorder,中间失败会返回 `MOVE_OK_REORDER_FAILED`,可用 `section reorder` 重试。 - -## 复杂操作 - -### 仪表盘 / 图表(建议顺序) - -```bash -# 1) 先看配置模板(JSONC) -dws aitable dashboard config-example --format json -dws aitable chart widgets-example --format json - -# 2) 先拿 dashboard,再拿 chart 详情 -dws aitable dashboard get --base-id --dashboard-id --format json -dws aitable chart get --base-id --dashboard-id --chart-id --format json -``` - -要点: - -- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。 -- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。 -- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。 - -### 导出数据(两阶段轮询) - -`export data` 常见为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。 - -```bash -# 第一步:创建任务(按 scope 传必要参数) -dws aitable export data --base-id --scope table --table-id --export-format excel --timeout-ms 1000 - -# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl -dws aitable export data --base-id --task-id --timeout-ms 3000 -``` - -参数约束 - -- `scope=all`:只需 `base-id` -- `scope=table`:必须 `table-id` -- `scope=view`:必须同时 `table-id + view-id` - ## 意图判断 用户说"表格/多维表/AI表格": @@ -340,8 +114,7 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"数据表/子表/table": - 查看 → `table get` - 创建 → `table create` -- 重命名 / 改备注 / 改行命名规则 → `table update`(三选一:`--name` / `--description` / `--record-name-key`) -- 用户说"行命名规则/记录别名/卡片显示成 task/project/event 这种" → `table update --record-name-key <枚举键>`,**中文 → 枚举键**对照见 [aitable-record-name-key.md](./aitable/aitable-record-name-key.md) +- 重命名 → `table update` - 删除 → `table delete` 用户说"字段/列/column": @@ -352,34 +125,10 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"记录/行/数据/row": - 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable/aitable-record-query.md)) -- 找空行 / 没填东西的行 → `record query-empty`(读 [aitable-record-query.md](./aitable/aitable-record-query.md)) - 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`) - 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable/aitable-record-create.md)) -- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) -- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'` +- 修改/更新 → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md)) - 删除 → `record delete` -- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable/aitable-record-history.md)) -- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable/aitable-record-share.md)) -- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable/aitable-record-upsert.md)) - -用户说"视图/view": -- 列出/查看全部视图 → `view list`(或 `view get` 不传 --view-ids,二者等价) -- 看某个视图详情 → `view get --view-ids ` -- 创建 → `view create` -- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'` -- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update `(读 [aitable-view-config.md](./aitable/aitable-view-config.md)) -- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable/aitable-view-extras.md) -- 删除 → `view delete` - -用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) - -用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) 用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md) @@ -389,33 +138,14 @@ dws aitable export data --base-id --task-id --timeout-ms 300 用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)(§5.4 跨表引用) -用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable/aitable-form.md) - -用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable/aitable-workflow.md) -- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`) -- 看某个流程具体配置(触发条件、动作步骤) → `workflow get` -- 启用流程 → `workflow enable` -- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes` -- **新建 / 修改 / 删除流程**:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成 - 用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) -用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)) - 用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable/aitable-attachment.md) 用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable/aitable-export-import.md) 用户说"模板" → `template search` -用户说"高级权限/角色/权限控制/谁能看/谁能改" → 读 [aitable-advperm.md](./aitable/aitable-advperm.md) -- 开/关高级权限 → `advperm enable` / `advperm disable --yes` -- 看角色配置 → `advperm role-list` 或 `advperm role-get` -- 建角色(可同时指定子角色权限) → `advperm role-create --name ... --sub-roles '[...]'` -- 改角色名 / 改子角色权限(PATCH 语义,未传字段不变) → `advperm role-update --role-id ... [--name ...] [--sub-roles '[...]']` -- 删角色 → `advperm role-delete --yes` -- **角色 ↔ 成员绑定**:当前 CLI 不支持,仍需在 AI 表格 Web 端面板手动完成 - 命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable/aitable-error-recovery.md) **关键区分**: base=表格文件, table=数据表, field=列, record=行 @@ -446,8 +176,7 @@ dws aitable record create --base-id --table-id \ |------|-------------|------| | `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI | | `base create` | `baseId` | 后续命令 + 文档 URI | -| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI | -| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI | +| `base get` | `tables[].tableId` | --table-id | | `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete | | `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` | | `template search` | `templateId` | base create --template-id,拼接模板预览 URI | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md index 45e9c2ae..1648fc87 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-attachment.md @@ -1,8 +1,6 @@ # attachment — 附件上传 > **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。 -> -> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。 ## 准备附件上传 @@ -40,8 +38,8 @@ dws aitable record create --base-id --table-id \ dws aitable attachment upload --base-id --file-name report.pdf --size 204800 --format json # → 返回 uploadUrl、fileToken -# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type) -curl -X PUT "" -H "Content-Type: application/pdf" --data-binary @report.pdf +# 2. PUT 上传(Content-Type 留空) +curl -X PUT "" -H "Content-Type:" --data-binary @report.pdf # 3. 写入记录 dws aitable record update --base-id --table-id \ diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md index db63f634..a851af83 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-best-practices.md @@ -26,7 +26,7 @@ | 全量拉取/统计 | `record query --all` | 不要手动循环 cursor | | 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 | | 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 | -| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) | +| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 | | 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 | ## 4. 创建/修改后回读确认 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md index bc3a8125..9f747bf9 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-cell-value.md @@ -86,16 +86,11 @@ {"fldDateId": "2026-03-15T09:00+08:00"} ``` -**读取**:RFC3339 字符串(带时区) +**读取**:RFC3339 字符串 ```json {"fldDateId": "2026-03-15T09:00:00+08:00"} ``` -**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。 -- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条; -- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。 -- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。 - --- ### currency(货币) @@ -241,14 +236,15 @@ ### attachment(附件) -**写入**:对象数组,**必须使用 `fileToken`** +**写入**:对象数组,支持 `fileToken` 或 `url` 形式 ```json {"fldAttachId": [{"fileToken": "ft_xxx"}]} +{"fldAttachId": [{"url": "https://example.com/file.pdf"}]} ``` -> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。** -> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。 +> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。 +> URL 形式是 best-effort 异步转存,不保证立即可用。 > 写入会**整体覆盖**原附件列表,不是追加。 **读取**:对象数组(含下载链接、文件名、大小) @@ -339,7 +335,7 @@ |------|----------| | cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | progress 写入 `75` | 写入 `0.75`(范围 0~1) | -| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) | +| attachment 直接传文件路径 | 必须先 upload 获取 fileToken | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | group 字段用 `openConversationId` | 用 `cid` | | singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md index 94d576e2..ec96fb4f 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md @@ -27,7 +27,6 @@ dws aitable chart get --base-id --dashboard-id --chart- | `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 | | `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — | | `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 | -| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` | ## chart 子命令 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md index fc755451..05e83466 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-export-import.md @@ -111,3 +111,30 @@ dws aitable import data --import-id --table-id --format js > **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此: > - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除 > - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入 + +--- + +## 导入数据:两条链路怎么选 + +用户给文件让导入 AI 表格时,路径选择决定成败: + +| 用户原话 | 链路 | 命令 / 脚本 | 行为 | +|---------|------|------------|------| +| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id B --file ./x.xlsx` + `dws aitable import data --import-id ` | 服务端解析文件,**新建数据表**,自动识别表头 | +| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 | +| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py ` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 | +| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | `dws aitable import upload --base-id B --file ./x.xlsx` → `dws aitable import data --import-id --table-id --field-mapping '{"目标":"源"}'` | 服务端按映射追加 | + +## 大表 / 长任务超时续等 + +默认整体轮询超时 5 分钟。大表导出/导入超时后命令会返回 `taskId` / `importId`,用同命令带 ID 续等: + +```bash +# 续等导出 +dws aitable export data --base-id --task-id --output ./out.xlsx + +# 续等导入 +dws aitable import data --import-id +``` + +或一次性把 timeout 提到 15 分钟:`--timeout-sec 900`。 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md index 8fabf8b4..8819a31f 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-filter-sort.md @@ -1,7 +1,5 @@ # filters & sort — 筛选排序语法参考 -> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。 - ## filters 结构规范 ### 强制规则 @@ -51,34 +49,11 @@ CLI 同时兼容两种子条件写法(推荐格式 A): | `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) | | `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` | | `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` | -| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` | +| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` | +| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` | +| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` | > **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。 -> -> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。 - -### 日期字段过滤(date / 创建时间 / 修改时间) - -日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则: - -1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。 -2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。 -3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。 -4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。 - -| 需求 | 操作符 | 示例 operands | -|------|--------|--------------| -| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` | -| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` | -| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` | -| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` | - -**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点): - -```bash -dws aitable record query --base-id X --table-id Y \ - --filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}' -``` ### 常见错误拼写(CLI 会自动提示纠正) @@ -129,41 +104,3 @@ dws aitable record query --base-id X --table-id Y \ ```bash --sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]' ``` - ---- - -## view update --config 中的 filter / sort 格式 - -> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**! - -| 场景 | filter 格式 | 说明 | -|------|-------------|------| -| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 | -| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 | -| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 | - -### 正确示例 - -```bash -# view update 设置筛选(filter 是数组) -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}' - -# view update 设置排序(sort 是数组) -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}' - -# 同时设置 filter + sort + visibleFieldIds -dws aitable view update --base-id X --table-id Y --view-id Z \ - --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}' -``` - -### CLI 自动容错 - -CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式): - -| 错误写法 | CLI 自动修正为 | -|----------|---------------| -| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) | -| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) | -| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` | diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md index bf605899..eb737228 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-create.md @@ -27,7 +27,6 @@ Flags: | cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) | | 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 | | 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 | -| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) | ## 正确流程 diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md index 20ed92da..8d04ab1b 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-record-query.md @@ -72,49 +72,3 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回 - `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"` - 不加 `--field-ids` 拉全字段 → 大表响应体积过大 - 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤 - -## record query-empty — 找空行 - -`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。 - -```bash -dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID -``` - -| flag | 说明 | -|------|------| -| `--base-id` / `--base` | 必填 | -| `--table-id` | 必填 | -| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 | -| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 | - -返回结构: - -```jsonc -{ "data": { "records": [...], "nextCursor": "..." } } -``` - -### 关键语义 - -1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。 -2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。 -3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。 - -### 典型用法 - -```bash -# 扫一页,看本页有没有空行 -dws aitable record query-empty --base-id BASE --table-id TBL - -# 翻页 -dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor> - -# 把整表扫完(手动循环 cursor) -NC="" -while : ; do - R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json) - echo "$R" | jq '.data.records[] | .recordId' - NC=$(echo "$R" | jq -r '.data.nextCursor // empty') - [ -z "$NC" ] && break -done -``` diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py index caa66f80..fdb20406 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_export_via_task.py @@ -88,10 +88,10 @@ def build_start_args(args: argparse.Namespace) -> list[str]: args.base_id, "--scope", args.scope, - "--format", + "--export-format", args.export_format, - "--timeout-ms", - str(args.timeout_ms), + "--timeout-sec", + str(args.timeout_sec), ] if args.table_id: cmd.extend(["--table-id", args.table_id]) @@ -107,9 +107,8 @@ def main() -> None: parser.add_argument("--table-id", help="scope=table/view 时必填") parser.add_argument("--view-id", help="scope=view 时必填") parser.add_argument("--export-format", default="excel", choices=sorted(ALLOWED_FORMATS), help="导出格式") - parser.add_argument("--timeout-ms", type=int, default=1000, help="单次等待毫秒数,默认 1000") - parser.add_argument("--poll-timeout-ms", type=int, default=3000, help="轮询等待毫秒数,默认 3000") - parser.add_argument("--max-polls", type=int, default=10, help="最大轮询次数,默认 10") + parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") + parser.add_argument("--max-polls", type=int, default=10, help="(兼容旧参数,开源 CLI 内置轮询不再使用)") parser.add_argument("--output", help="本地保存路径(不传则按 fileName 保存到当前目录)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") parser.add_argument("--no-download", action="store_true", help="仅返回 downloadUrl,不下载文件") @@ -153,10 +152,10 @@ def main() -> None: args.base_id, "--task-id", task_id, - "--timeout-ms", - str(args.poll_timeout_ms), + "--timeout-sec", + str(args.timeout_sec), ], - timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60), + timeout_sec=max(120, args.timeout_sec + 60), ) if rc2 != 0: fail(f"export_data 轮询失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py index fea1ad5e..f3139df2 100644 --- a/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py +++ b/skills/multi/dingtalk-aitable/scripts/aitable_import_via_task.py @@ -77,7 +77,7 @@ def main() -> None: parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格") parser.add_argument("base_id", help="目标 AI 表格 baseId") parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)") - parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30") + parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)") parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws") args = parser.parse_args() @@ -140,12 +140,12 @@ def main() -> None: "data", "--import-id", import_id, - "--timeout", - str(args.timeout), + "--timeout-sec", + str(args.timeout_sec), "--format", "json", ], - timeout_sec=max(120, args.timeout + 30), + timeout_sec=max(120, args.timeout_sec + 30), ) if rc2 != 0: fail(f"import_data 调用失败: {err2 or out2}", rc2) diff --git a/skills/multi/dingtalk-attendance/SKILL.md b/skills/multi/dingtalk-attendance/SKILL.md index 553cc4bb..c876ddb0 100644 --- a/skills/multi/dingtalk-attendance/SKILL.md +++ b/skills/multi/dingtalk-attendance/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[attendance.md](references/attendance.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-attendance/references/attendance.md b/skills/multi/dingtalk-attendance/references/attendance.md index 8c46578a..dc6bd394 100644 --- a/skills/multi/dingtalk-attendance/references/attendance.md +++ b/skills/multi/dingtalk-attendance/references/attendance.md @@ -1,6 +1,6 @@ # 考勤 (attendance) 命令参考 -> **【开源版命令可用性提示】** 当前开源 dws 已落地 P0 5 条命令:`attendance check result`、`attendance check record`、`attendance group search`、`attendance vacation balance`、`attendance vacation types`。文档中其余 P1 阶段命令(`class` / `overtime` / `adjustment` / `group settings` / `report` / `schedule` / `boss-check` 等共 28 条)暂未通过服务发现推全,调用会返回 `unknown command`,将在后续批次落地。 +> **【开源版命令可用性提示】** 当前开源 dws 已落地 P0 5 条命令:`attendance check result`、`attendance check record`、`attendance group search`、`attendance vacation balance`、`attendance vacation types`。文档中其余 P1 阶段命令(`class` / `overtime` / `adjustment` / `group settings` / `report` / `schedule` / `boss-check` 等共 28 条)暂未在当前开源二进制暴露,调用会返回 `unknown command`,将在后续批次落地。 > **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用** > diff --git a/skills/multi/dingtalk-calendar/SKILL.md b/skills/multi/dingtalk-calendar/SKILL.md index ae2acc63..517abe18 100644 --- a/skills/multi/dingtalk-calendar/SKILL.md +++ b/skills/multi/dingtalk-calendar/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[calendar.md](references/calendar.md);剧本:[03-meeting.md](references/03-meeting.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-calendar/references/calendar.md b/skills/multi/dingtalk-calendar/references/calendar.md index 241868b5..743a4192 100644 --- a/skills/multi/dingtalk-calendar/references/calendar.md +++ b/skills/multi/dingtalk-calendar/references/calendar.md @@ -101,8 +101,6 @@ Flags: **默认行为**:不传 `--start` / `--end` 时,默认返回今天的日程(00:00:00 ~ 23:59:59)。 **权限**:查询共享日历下的日程时,至少要有reader权限。 -补充:当前用户的个人日程也可用 `dws calendar event list-mine` 查询,参数与 `event list` 一致。 - ### 获取日程详情 ``` Usage: @@ -531,7 +529,7 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15 - **评测 / 自动化断言**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId` - **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `:` 元素逗号分隔 - **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历 -- **已弃用入参**:`--max-results`(event list / list-mine)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 +- **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用 ## 自动化脚本 diff --git a/skills/multi/dingtalk-chat/SKILL.md b/skills/multi/dingtalk-chat/SKILL.md index 3fe07faa..198d308e 100644 --- a/skills/multi/dingtalk-chat/SKILL.md +++ b/skills/multi/dingtalk-chat/SKILL.md @@ -18,20 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[chat.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。 -> 跨组织:当前组织搜不到群 / 单聊时,别判定「不存在」——先 `dws profile list` 看有哪些已登录组织,再对每个组织带 `--profile ` 各搜一遍,全无才追问用户。详见 `dingtalk-profile` skill。 - -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | @@ -39,7 +30,7 @@ metadata: | "发消息给张三" | `dws chat message send --open-dingtalk-id --title "<标题>" --text "<内容>"` | | "发到XX群" | `dws chat search --query "<群名>"` → `dws chat message send --group --title "<标题>" --text "<内容>"` | | "建群" / "拉人进群" | `dws chat group create` / `dws chat group members add` | -| "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,确认目标后加 --yes)| +| "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,确认目标后加 --yes;踢群主会被 CLI 拦截,需先 `transfer-owner`)| | "@我消息" / "查群聊记录" | `dws chat message list` | | "用机器人发消息" | `dws chat message send-by-bot --robot-code --group --title "<标题>" --text "<内容>"` | | "Webhook 推一条" | `dws chat message send-by-webhook --token --title "<标题>" --text "<内容>"` | diff --git a/skills/multi/dingtalk-chat/references/chat.md b/skills/multi/dingtalk-chat/references/chat.md index b772dcb4..51875f6d 100644 --- a/skills/multi/dingtalk-chat/references/chat.md +++ b/skills/multi/dingtalk-chat/references/chat.md @@ -48,6 +48,8 @@ Flags: --users string 要移除的用户 userId 列表,逗号分隔 (必填) ``` +> 移除对象是群主时 CLI 会拦截并报错,提示先 `chat group transfer-owner` 转让群主(查不到群主时放行)。 + #### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错 ``` Usage: @@ -148,6 +150,8 @@ Flags: --icon-media-id string 群头像 mediaId (必填) ``` +> `--icon-media-id` 必须是 `@` 开头的媒体 ID(如 dt_media_upload 返回值),非法格式会本地报错。 + #### 更新群设置 — 更新指定群聊的设置项 --setting-key 指定设置项,--status 指定值(0=关闭,1=开启)。 @@ -336,56 +340,66 @@ Flags: --cursor string 分页游标(默认 "0",翻页传 nextCursor) ``` +### data-auth (数据授权) + +#### 跨组织数据授权 — 授予 chat 跨组织消息拉取权限 + +用于跨组织消息拉取等数据访问场景,不用于发送、撤回、群管理等操作。该命令调用 `chat_permission_grant`,固定使用数据授权类别:`scope=chat.data:cross-org`、`grantCategory=data`。 + +``` +Usage: + dws chat data-auth cross-org [flags] + +Example: + dws chat data-auth cross-org --target-org-id 439446171 --format json + dws chat data-auth cross-org --target-org-id 439446171 --agentCode wukong --grant-type timed --ttl 24h --format json + dws chat data-auth cross-org --all --grant-type timed --ttl 24h --format json + +Flags: + --target-org-id string 目标组织 ID(与 --all 二选一) + --all 授权所有目标组织 + --agentCode string Agent 标识,默认 wukong + --grant-type string 授权策略: once|session|timed|permanent (default "timed") + --ttl string timed 授权有效期,如 1h/4h/24h/7d (default "24h") + --session-id string session 授权的会话标识 + +注意: + - --target-org-id 和 --all 必须且只能指定一个。 + - --grant-type=session 时必须传 --session-id。 + - --grant-type=timed 时必须传 --ttl。 +``` + ### message (会话消息管理) -#### 拉取群聊会话消息内容 — 拉取指定群聊的会话消息内容(仅群聊) +#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容 ---group 指定群聊 openConversationId(**本命令仅支持群聊**;拉取单聊/私聊消息请改用 `chat message list-direct`)。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 +--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 ``` Usage: dws chat message list [flags] Example: dws chat message list --group --time "2025-03-01 00:00:00" - dws chat message list --group --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --user --time "2025-03-01 00:00:00" --limit 50 + dws chat message list --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 50 dws chat message list --group --time "2025-03-01 00:00:00" --forward=false - # 拉单聊改用 list-direct: dws chat message list-direct --user --time "2025-03-01 00:00:00" Flags: --forward true=拉给定时间之后的消息,false=拉给定时间之前的消息 (default true) - --group string 群聊 openconversation_id(必填,**仅支持群聊**;查单聊用 chat message list-direct --user ) + --group string 群聊 openconversation_id(群聊时必填) --limit int 返回数量,不传则不限制 --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) + --user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一) + --open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景) 注意: - - 本命令**仅支持群聊**,必须指定 --group;拉取单聊(私聊)消息请改用 `chat message list-direct`(旧版的 `list --user` / `list --open-dingtalk-id` 已不再支持) + - --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id + - --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识: + - --user 传 userId(企业内部应用常用) + - --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用) - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group --topic-id 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。 ``` -#### 拉取单聊消息内容 — 按对方 userId 拉取与某同事的单聊(私聊)历史消息 - -按对方 userId(或 openDingTalkId)拉取与该同事的单聊会话消息,**专用于私聊**;查群聊请用 `chat message list --group`。同组织内同事用 --user,非同组织好友用 --open-dingtalk-id,二者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。 -``` -Usage: - dws chat message list-direct [flags] -Example: - dws chat message list-direct --user <对方userId> --time "2025-03-01 00:00:00" --limit 50 - dws chat message list-direct --user <对方userId> --time "2025-03-01 00:00:00" --forward=false - dws chat message list-direct --open-dingtalk-id --time "2025-03-01 00:00:00" --limit 20 - # 查询对方 userId: dws contact user search --keyword "姓名" 或 dws aisearch person --keyword "姓名" --dimension name -Flags: - --forward true=从老往新(给定时间之后),false=从新往老(给定时间之前) (default true) - --user string 对方 userId(同组织内同事,与 --open-dingtalk-id 二选一) - --open-dingtalk-id string 对方 openDingTalkId(非同组织普通好友场景,与 --user 二选一) - --limit int 每页返回数量(默认 50) - --time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填) - -注意: - - --user 与 --open-dingtalk-id 二选一,必须且只能指定其一;同组织同事优先用 --user - - --time 必填;翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time - - 本命令是 `chat message list` 拆分出的单聊专用命令;查群聊消息请用 `chat message list --group` -``` - #### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊 **重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** @@ -393,15 +407,17 @@ Flags: --group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 --群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 ---富媒体消息:通过 --msg-type 指定类型(image/file),必须根据文件扩展名判断 msgType 后再发送。 +--富媒体消息:通过 --msg-type 指定类型(image/file/audio/video),audio/video 是 file 的语义别名,发给服务端仍按 file 发送;必须根据文件扩展名判断 msgType 后再发送。 ``` Usage: dws chat message send [flags] [] 富媒体消息 msgType 决策(必须按此规则判断,不可跳过): 文件扩展名 → msgType → 发送参数 - .jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py ` 提取 mediaId → --msg-type image --media-id - 其他所有(.mp3/.wav/.mp4/.avi/.pdf/.doc/.xls/.zip 等) → file → conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size + .jpg/.jpeg/.png/.gif/.bmp/.webp → image → dt_media_upload 上传 → `python scripts/extract_media_id.py ` 提取 mediaId → --msg-type image --media-id + .mp3/.wav/.m4a/.aac/.flac → audio(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type audio + .mp4/.mov/.avi/.mkv/.webm → video(file) → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type video + 其他所有(.pdf/.doc/.xls/.zip 等) → file → 本地 --file-path 或 conversation-info/drive 上传 → --msg-type file Example: dws chat message send --group --text "hello" @@ -415,11 +431,13 @@ Example: dws chat message send --group --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收" # 发送图片 dws chat message send --group --msg-type image --media-id - # 发送文件(音频/视频/文档等非图片文件统一走钉盘上传) + # 发送文件/音频/视频(audio/video 是 file 的语义别名) # 先 dws chat conversation-info --group 获取 spaceId(取 newCSpaceIdIM) # 再 dws drive upload --file <文件> --space-id 上传 # 再 dws drive info --file-id --space-id 获取 dentryId dws chat message send --group --msg-type file --dentry-id --space-id 24557356340 --file-name "report.pdf" --file-type "pdf" --file-path "/report.pdf" --file-size 234724 + dws chat message send --group --msg-type audio --file-path ./recording.mp3 + dws chat message send --group --msg-type video --file-path ./demo.mp4 Flags: --text string 消息内容(推荐使用,也可用位置参数) --group string 群聊 openconversation_id(群聊时必填) @@ -429,7 +447,7 @@ Flags: --at-all @所有人(仅群聊时生效,可选,默认 false) --at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选) --media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py ` 提取,仅 msgType=image) - --msg-type string 消息类型: image/file(image 用 mediaId,file 用钉盘上传) + --msg-type string 消息类型: image/file/audio/video(audio/video 是 file 别名;image 用 mediaId,file/audio/video 用钉盘上传) --dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取) --space-id int64 钉盘空间 ID(msgType=file 时必填) --file-name string 文件名(msgType=file 时必填) @@ -444,17 +462,17 @@ Flags: - 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力 - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符@all;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2> - - **@ 群内机器人**:务必使用 `dws chat group bots --group ` 返回的 openDingtalkId(群级别 ID,与全局搜索结果不同);全局 `chat bot find` 返回的 ID 无法正确 @ 机器人 - **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可: 1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行 2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `
`),或直接写 HTML 的 `
` 标签 - 富媒体消息类型与参数对应关系: - image(图片):--msg-type image --media-id - - file(音频/视频/文档等所有非图片文件):先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size + - audio/video:file 的语义别名,发给服务端仍是 file;可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type audio 或 --msg-type video --dentry-id --space-id --file-name --file-type --file-path --file-size + - file(文档/压缩包等其他非图片文件):可直接传本地 --file-path,或先 conversation-info 获取 spaceId → drive upload --space-id 上传 → drive info 获取 dentryId → --msg-type file --dentry-id --space-id --file-name --file-type --file-path --file-size - mediaId 通过 dt_media_upload 上传获得,必须用脚本提取:`python scripts/extract_media_id.py ""`(输出如 @lQLPxxx,直接用于 --media-id)。禁止手动从 URL 中截取或拼接 mediaId,手动解析会因 URL 格式不稳定导致尺寸后缀残留 - --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持) - 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送 - - 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,其他所有→file;不可跳过此判断步骤 + - 发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片(.jpg/.png/.gif/.bmp/.webp)→image,音频(.mp3/.wav/.m4a/.aac/.flac)→audio,视频(.mp4/.mov/.avi/.mkv/.webm)→video,其他所有→file;不可跳过此判断步骤。audio/video 是 file 别名,不代表不支持音频/视频 - 20MB 降级:图片超过 20MB 时 dt_media_upload 会失败,必须降级走钉盘上传 + Markdown 嵌入方式发送(参见「发送图片+文字消息」章节)。音频/视频/文件走钉盘上传无 20MB 限制 - 发送文字 + 文件混合消息时的完整流程:除了将文件以 Markdown 链接内嵌到文字消息中发送一条 md 消息外,还必须额外逐个发送独立的文件消息(--msg-type file),确保接收方可以直接下载原始文件。即:先发一条包含文字和文件链接的 md 消息,再对每个涉及的文件各发一条 --msg-type file 的文件消息 ``` @@ -533,7 +551,6 @@ Flags: - --group 的别名: --id, --chat, --conversation-id (均可替代 --group) - --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本 - --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本 - - **@ 群内机器人**:务必使用 `dws chat group bots --group ` 返回的 openDingtalkId(群级别 ID,与全局搜索结果不同);全局 `chat bot find` 返回的 ID 无法正确 @ 机器人 - --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理 - userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId - **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`: @@ -613,7 +630,7 @@ Flags: 注意: - 四个参数每次请求都会传递给服务端,cursor 首页传 "0" - - 与 chat message list 的区别:list 拉取指定群聊会话的消息(单聊用 list-direct),list-all 拉取当前用户所有会话的消息 + - 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息 - 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页 - 时间格式统一为 yyyy-MM-dd HH:mm:ss ``` @@ -942,21 +959,6 @@ Flags: - --output 如果指定目录,文件名会从下载 URL 中自动推断 ``` -#### 资源链接形态分流 — 按 content 里的链接 host 选下载方式 - -`message list` / `message list-all` 拉到的消息,`content` 里的资源**不一定都是 mediaId**,下载方式由链接的 host 决定、不由文件扩展名决定;**首选对应 dws 命令**,裸 curl 仅对明确的公开直链有效。按下表分流、不要混用: - -| `content` 里的形态 | 资源性质 | 下载方式 | -|---|---|---| -| `mediaId=...`(图片 / 视频 / 语音,扩展名任意) | 钉钉消息媒体;**host 不唯一**:`down.dingtalk.com/media`(公开直链)或 `*.trans.dingtalk.com/...?Expires=&Signature=`(签名 + 会过期) | **首选 `dws chat message download-media`**(内部取最新下载 URL,不受过期影响,需 `--message-id` + `--open-conversation-id`)。仅当链接是 `down.dingtalk.com/media` 公开直链时,可 `curl -sL -o` 快捷下载;**签名/过期链(`trans.dingtalk.com`、带 `Signature`/`Expires`)不要裸 curl,过期会 403** | -| `[文件] xxx fileId: `(钉盘文件) | 钉盘临时签名链接 | `dws drive download --node --output <路径>`(裸 curl 不通用,需签名头) | -| `https://alidocs.dingtalk.com/i/nodes/`(钉钉文档 / .adoc / 视频 .mov 等节点) | 文档 / 钉盘节点 | 读内容用 `dws doc`,下文件用 `dws drive download`(裸 curl 只得 HTML 预览页) | - -> - 下载到本地后用 `file <文件>` 判断真实类型:部分链接扩展名是 `.unknown`、服务端按 `application/octet-stream` 返回,仍可正常下载。 -> - 图片消息还会附带 AI 识别的内容描述(`...`),不下载也能理解图意。 -> - 钉盘文件在 `content` 里给的是 `fileId`、不是 mediaId,必须走 `dws drive download`(下载链接是带签名头的临时链接,裸 curl 不通用)。 -> - alidocs 节点裸 curl 只会拿到 HTML 预览页、不是文件本体,需走 `dws doc`(读内容)或 `dws drive download`(下文件)。 - ### search-common (搜索共同群) #### 搜索共同群 — 查询指定人共同所在的群聊 @@ -1168,7 +1170,7 @@ Flags: 用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `chat message list-focused`(零参数一行命令) 用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id ` 或 `--sender-open-dingtalk-id `(跨单聊+群聊) -用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list-direct --user ` 或 `--open-dingtalk-id ` +用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user ` 或 `--open-dingtalk-id ` 用户说"某个群的聊天记录" → `chat message list --group ` 用户说"我最近所有消息/我今天的消息" → `chat message list-all --start --end ` 用户说"@我的消息/提及我的" → `chat message list-mentions --start --end ` @@ -1188,7 +1190,7 @@ Flags: 用户说"改群名" → `chat group rename` 用户说"聊天记录/会话消息/拉取会话" → `chat message list` 用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊) -用户说"拉取和某人的单聊记录/单聊消息" → `chat message list-direct --user`(单聊专用;用户明确说"单聊"时使用) +用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用) 用户说"@我的消息/at我的/提及我的" → `chat message list-mentions` 用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations` 用户说"发群消息(以个人身份)" → `chat message send --group` @@ -1243,8 +1245,8 @@ Flags: 关键区分: - `chat search` — 搜**群/会话名**返回 `openConversationId`,**不**搜消息内容;要搜消息内容请用 `chat message search-advanced`(首选)/ `chat message search` / `list-by-sender` / `list-all`,**勿混淆** -- `chat message list` — 拉取指定**群聊**的消息(需指定 --group,**仅群聊**),按时间点 + 方向翻页 -- `chat message list-direct` — 单聊专用,拉取与指定用户的单聊(私聊)记录(--user / --open-dingtalk-id;用户明确说"单聊""私聊"时使用) +- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页 +- `chat message list --user` — list 的单聊模式,拉取与指定用户的单聊记录(用户明确说"单聊""私聊"时使用) - `chat message list-by-sender` — 搜索指定发送者发给我的消息,跨所有会话(单聊+群聊均包含,用户只说"某人发的消息"时优先使用) - `chat message list-mentions` — 拉取 @我 的消息(跨单聊/群聊,可选指定群) - `chat message list-unread-conversations` — 拉取当前用户存在未读消息的会话列表(可选 `--count`) @@ -1253,7 +1255,7 @@ Flags: - `chat message list-topic-replies` — 拉取群话题的回复消息列表 - `chat message list-focused` — 拉取特别关注人的消息,cursor 分页 - `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页 -- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file(音频/视频/文档等所有非图片文件),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件需先获取会话共享空间再上传钉盘 +- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;支持 --msg-type 发送富媒体消息:image(图片)、file/audio/video(audio/video 是 file 别名),图片的 mediaId 通过 dt_media_upload 上传获得,其他文件可直接用 --file-path 或先获取会话共享空间再上传钉盘 - `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群) - `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在) - `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag @@ -1469,8 +1471,8 @@ Flags: | `chat search` | `openConversationId` | message send/list、group members 等的 --group | | `chat group create` | `openConversationId` | 同上 | | `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor | -| `aisearch person` | `userId` | message send 的 --user、list-direct 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | -| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、list-direct 的 --open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id | +| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id | +| `aisearch person` → `contact user get` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id | | `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) | | `chat bot find` | `openDingTalkId` | 给机器人发单聊消息(全部可用机器人,额外返回 openDingTalkId) | | `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys | @@ -1503,9 +1505,9 @@ Flags: - 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送 - 此参数适用于 `chat message send`(群聊和单聊均支持) - `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取 -- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,其他所有→file,不可跳过此判断 +- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;富媒体消息通过 `--msg-type` 指定类型(image/file/audio/video),必须显式指定;发送文件/媒体消息时,必须先根据文件扩展名判断 msgType:图片→image,音频→audio,视频→video,其他→file,不可跳过此判断 - `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor -- `chat message list` **仅支持群聊**,必须指定 `--group`;拉取单聊用 `chat message list-direct`(`--user` / `--open-dingtalk-id` 二选一) +- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一 - `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` - `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor` - `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数 @@ -1542,7 +1544,7 @@ Flags: - 群聊:`dws chat search --query "群名"` - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取) - `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消 -- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言 +- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔,CLI 自动解析为 openDingTalkId)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言;禁言群主会被服务端拒绝 - `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消 ## 自动化脚本 diff --git a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py index 4c92627c..8068be38 100644 --- a/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py +++ b/skills/multi/dingtalk-chat/scripts/chat_history_with_user.py @@ -9,7 +9,7 @@ 工作流: 1. 通过 --name 搜索通讯录,获取 userId(或直接传 --user) - 2. 调用 chat message list-direct --user 拉取单聊消息 + 2. 调用 chat message list --user 拉取单聊消息 3. 输出到终端或导出为 JSON 文件 """ @@ -116,7 +116,7 @@ def main(): while page < max_pages and remaining > 0: cmd_args = [ - 'chat', 'message', 'list-direct', + 'chat', 'message', 'list', '--user', user_id or '', '--time', current_time, '--format', 'json', @@ -139,12 +139,8 @@ def main(): page_msgs = data has_more = False else: - # list-direct 返回 {result: {hasMore, messages: [...]}},先解包 result - result_obj = data.get('result', data) - if not isinstance(result_obj, dict): - result_obj = data - page_msgs = result_obj.get('messages', result_obj.get('records', [])) - has_more = result_obj.get('hasMore', data.get('hasMore', False)) + page_msgs = data.get('messages', data.get('result', [])) + has_more = data.get('hasMore', False) if not page_msgs: break @@ -157,8 +153,7 @@ def main(): break last_msg = page_msgs[-1] - boundary_time = (last_msg.get('createTime') or last_msg.get('createAt') - or last_msg.get('time', '')) + boundary_time = last_msg.get('createAt') or last_msg.get('time', '') if not boundary_time or boundary_time == current_time: break current_time = boundary_time @@ -188,11 +183,9 @@ def main(): print(f" ✓ 已导出 {len(all_messages)} 条消息到 {args.output}") else: for m in all_messages: - if not isinstance(m, dict): - continue sender = m.get('senderNick') or m.get('sender', '未知') text = m.get('text') or m.get('content', '') - time_str = m.get('createTime') or m.get('createAt') or m.get('time', '') + time_str = m.get('createAt') or m.get('time', '') print(f" [{time_str}] {sender}: {text[:80]}") print(f"\n合计: {len(all_messages)} 条消息 ({page} 页)") diff --git a/skills/multi/dingtalk-contact/SKILL.md b/skills/multi/dingtalk-contact/SKILL.md index 5e8ad609..b16ab60d 100644 --- a/skills/multi/dingtalk-contact/SKILL.md +++ b/skills/multi/dingtalk-contact/SKILL.md @@ -18,20 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[contact.md](references/contact.md);剧本:[08-directory.md](references/08-directory.md)。 -> 跨组织:当前组织查不到人时,别判定「查无此人」——先 `dws profile list` 看有哪些已登录组织,再对每个组织带 `--profile ` 各查一遍,全无才追问用户。详见 `dingtalk-profile` skill。 - -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-dev/SKILL.md b/skills/multi/dingtalk-dev/SKILL.md index 2c16f207..0cdddf60 100644 --- a/skills/multi/dingtalk-dev/SKILL.md +++ b/skills/multi/dingtalk-dev/SKILL.md @@ -16,7 +16,7 @@ metadata: 每次执行 dev 命令前,先查清楚再敲,别凭记忆或猜: 1. `--help` 看命令树(一个组下有哪些子命令、flag),例 `dws dev app --help` -2. `dws schema dev..` 看某命令参数(类型 / 必填 / 默认),按它构造、别自己拼——flag 名 = schema 参数名,例 `dws schema dev.app.robot.config`(schema 用点号,命令行用空格) +2. `--help` 看叶子命令参数(flag、默认值、示例),按当前二进制输出构造;不要再依赖已下线的动态 schema 3. 全部命令带 `--format json` 4. 写操作:`--dry-run` 看 `invocation.params` 确认无误,再换 `--yes`(`dev connect` 例外见 [connect.md](references/connect.md)) 5. 写完回读确认(`get` / `robot get` / `version status`) diff --git a/skills/multi/dingtalk-dev/references/app.md b/skills/multi/dingtalk-dev/references/app.md index bfdcee5b..57c3ab12 100644 --- a/skills/multi/dingtalk-dev/references/app.md +++ b/skills/multi/dingtalk-dev/references/app.md @@ -2,7 +2,7 @@ > 操作的是「应用」容器本体(见 SKILL.md 概念地图);启停/删除改的是应用 appStatus,不是版本 versionStatus。 -应用列表查询、详情、创建、修改、生命周期启停和删除。参数查 `dws schema dev.app.`(list / get / create / update / delete / disable / enable)。 +应用列表查询、详情、创建、修改、生命周期启停和删除。参数用对应命令的 `--help` 查询。 ## 应用定位 @@ -36,7 +36,7 @@ dws dev app --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/connect.md b/skills/multi/dingtalk-dev/references/connect.md index 490ed72c..459f9850 100644 --- a/skills/multi/dingtalk-dev/references/connect.md +++ b/skills/multi/dingtalk-dev/references/connect.md @@ -102,7 +102,7 @@ dws dev connect --unified-app-id --channel codex --format json dws dev connect --help # 查 connect 的必填参数、类型、默认值 -dws schema dev.connect +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/credentials.md b/skills/multi/dingtalk-dev/references/credentials.md index e0d303de..02607f44 100644 --- a/skills/multi/dingtalk-dev/references/credentials.md +++ b/skills/multi/dingtalk-dev/references/credentials.md @@ -2,7 +2,7 @@ > 凭证=应用调 OpenAPI 的身份(appKey=clientId / appSecret=clientSecret);见 SKILL.md 概念地图。 -`dws dev app credentials get --unified-app-id ` 读取应用凭证。参数查 `dws schema dev.app.credentials.get`。 +`dws dev app credentials get --unified-app-id ` 读取应用凭证。参数用对应命令的 `--help` 查询。 返回字段:`clientId`/`appKey`(同值)、`clientSecret`/`appSecret`(同值)、`currentSecretStatus`、`hasPendingExpireTask`、`unifiedAppId` 等。 @@ -20,7 +20,7 @@ dws dev app credentials --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.credentials.get +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/event.md b/skills/multi/dingtalk-dev/references/event.md index f3e0f74d..38694d18 100644 --- a/skills/multi/dingtalk-dev/references/event.md +++ b/skills/multi/dingtalk-dev/references/event.md @@ -2,7 +2,7 @@ > 把应用关心的事件推到回调地址;见 SKILL.md 概念地图。 -`dws dev app event list/subscribe/unsubscribe`,按 `--unified-app-id` 定位,订阅/退订用 `--event-codes`(逗号分隔,一次多个)。参数查 `dws schema dev.app.event.`。 +`dws dev app event list/subscribe/unsubscribe`,按 `--unified-app-id` 定位,订阅/退订用 `--event-codes`(逗号分隔,一次多个)。参数用对应命令的 `--help` 查询。 规则: - 写操作先 `--dry-run` 预览,确认后 `--yes`。 @@ -40,7 +40,7 @@ dws 当前只消费机器人消息、不消费事件。要自己写事件消费 dws dev app event --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.event. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/member.md b/skills/multi/dingtalk-dev/references/member.md index 00b08720..0e5bc2c9 100644 --- a/skills/multi/dingtalk-dev/references/member.md +++ b/skills/multi/dingtalk-dev/references/member.md @@ -2,7 +2,7 @@ > 成员=谁能改这个应用(DEVELOPER 等角色);见 SKILL.md 概念地图。 -`dws dev app member list/add/remove` 管理应用成员。参数查 `dws schema dev.app.member.`(add/remove 需 `--user-ids` 列表 + `--member-type`,如 DEVELOPER;remove 也必须传 memberType,因为同一用户可能有多个成员身份)。 +`dws dev app member list/add/remove` 管理应用成员。参数用 `dws dev app member --help` 查询。add/remove 需 `--user-ids` 列表 + `--member-type`,如 DEVELOPER;remove 也必须传 memberType,因为同一用户可能有多个成员身份。 ## 发现命令 @@ -13,7 +13,7 @@ dws dev app member --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.member. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/permission.md b/skills/multi/dingtalk-dev/references/permission.md index 3c514737..5ad42ee6 100644 --- a/skills/multi/dingtalk-dev/references/permission.md +++ b/skills/multi/dingtalk-dev/references/permission.md @@ -2,7 +2,7 @@ > 权限点 scopeValue 是授权单元,一个权限点授权一组 OpenAPI;requiredApproval=true 的变更走版本通道生效(见 SKILL.md 生效模型)。 -查询、申请、取消开放平台应用的 APP 应用权限和 SNS 个人权限。参数查 `dws schema dev.app.permission.`。 +查询、申请、取消开放平台应用的 APP 应用权限和 SNS 个人权限。参数用对应命令的 `--help` 查询。 ## 权限列表 @@ -50,7 +50,7 @@ scopeValue 选择顺序: dws dev app permission --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.permission. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/recipes.md b/skills/multi/dingtalk-dev/references/recipes.md index 72887506..fb7c4ab2 100644 --- a/skills/multi/dingtalk-dev/references/recipes.md +++ b/skills/multi/dingtalk-dev/references/recipes.md @@ -1,6 +1,6 @@ # 端到端链路(recipes) -dev 的端到端任务都是「定位应用,改容器某节点,按审批需要走版本生效,最后回读验证」。每步先 `--dry-run` 确认再 `--yes`,参数查 `dws schema`,细节进对应 reference。 +dev 的端到端任务都是「定位应用,改容器某节点,按审批需要走版本生效,最后回读验证」。每步先 `--dry-run` 确认再 `--yes`,参数用对应命令的 `--help` 查询,¼Œ细节进对应 reference。 ## 建一个钉钉里打开的网页应用 diff --git a/skills/multi/dingtalk-dev/references/robot.md b/skills/multi/dingtalk-dev/references/robot.md index 448a8bc5..6793c7cb 100644 --- a/skills/multi/dingtalk-dev/references/robot.md +++ b/skills/multi/dingtalk-dev/references/robot.md @@ -2,7 +2,7 @@ > 机器人是应用的能力扩展之一;建号/配置在此,接到本地 agent 调试用 `dws dev connect`(见 connect.md)。 -为开放平台企业内部应用创建和配置机器人。参数查 `dws schema dev.app.robot.`。分两类场景: +为开放平台企业内部应用创建和配置机器人。参数用对应命令的 `--help` 查询。分两类场景: 1. 新建智能体机器人:异步创建一个新的 Agent 应用 + 承载机器人(`submit` / `result`),当前不绑定已有开放平台应用。 2. 现有应用配置机器人:在已存在的应用上配置/启用/停用机器人(`get` / `config`(upsert) / `enable` / `disable`),用 `--unified-app-id` 定位。 @@ -68,7 +68,7 @@ dws dev app robot --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.robot. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/security.md b/skills/multi/dingtalk-dev/references/security.md index 932303a0..0ea895b1 100644 --- a/skills/multi/dingtalk-dev/references/security.md +++ b/skills/multi/dingtalk-dev/references/security.md @@ -2,7 +2,7 @@ > 安全配置=应用的 IP 白名单 / 登录重定向 / 端内免登 URL;见 SKILL.md 概念地图。 -`dws dev app security config` 配 IP 白名单(`--ip-whitelist`)、登录重定向(`--redirect-urls`)、端内免登(`--sso-urls`)。参数查 `dws schema dev.app.security.config`,至少给一个配置字段。 +`dws dev app security config` 配 IP 白名单(`--ip-whitelist`)、登录重定向(`--redirect-urls`)、端内免登(`--sso-urls`)。参数用 `dws dev app security config --help` 查询;至少给一个配置字段。 覆盖语义:未提供的字段不动;显式提供的列表是整组覆盖(传入即全量替换该项,不是追加)——要保留旧值就把旧值一起带上。 @@ -15,7 +15,7 @@ dws dev app security --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.security.config +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/version.md b/skills/multi/dingtalk-dev/references/version.md index eaf15285..3c6175de 100644 --- a/skills/multi/dingtalk-dev/references/version.md +++ b/skills/multi/dingtalk-dev/references/version.md @@ -2,7 +2,7 @@ > 版本是配置变更生效的唯一通道——改配置不等于上线,发布到 RELEASE 才生效(见 SKILL.md 生效模型)。 -管理应用版本:基于当前配置建版本、查列表/详情、预检审批、发布、查状态。参数查 `dws schema dev.app.version.`。版本用 `--unified-app-id` 定位,单个版本再加 `--version-id`;`corpId`/`userId` 系统注入,CLI 不传。 +管理应用版本:基于当前配置建版本、查列表/详情、预检审批、发布、查状态。参数用对应命令的 `--help` 查询。版本用 `--unified-app-id` 定位,单个版本再加 `--version-id`;`corpId`/`userId` 系统注入,CLI 不传。 ## 典型流程 @@ -93,7 +93,7 @@ permission add(requiredApproval=true 写入版本变更) dws dev app version --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.version. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-dev/references/webapp.md b/skills/multi/dingtalk-dev/references/webapp.md index 0351e1ed..f1841a9e 100644 --- a/skills/multi/dingtalk-dev/references/webapp.md +++ b/skills/multi/dingtalk-dev/references/webapp.md @@ -2,7 +2,7 @@ > 网页应用=应用的能力扩展之一,钉钉内打开的 H5;见 SKILL.md 概念地图。 -`dws dev app webapp get` 查配置,`webapp config` 配移动端/PC 首页和管理后台地址。参数查 `dws schema dev.app.webapp.`,config 至少给一个配置字段。 +`dws dev app webapp get` 查配置,`webapp config` 配移动端/PC 首页和管理后台地址。参数用 `dws dev app webapp get --help` 和 `dws dev app webapp config --help` 查询;config 至少给一个配置字段。 - 未配置网页应用前,`get` 返回空对象 `{}`。拿到 `{}` 就是还没配过,走 `webapp config` 首次配置。 - `h5PageType` 未显式传入时不要假设固定默认值;配置后以 `webapp get` 回读为准。 @@ -16,7 +16,7 @@ dws dev app webapp --help # 查某方法的必填参数、类型、默认值 -dws schema dev.app.webapp. +dws dev --help ``` -按 `dws schema` 输出构造 `--flag`(flag 名 = schema 参数名)。 +按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 diff --git a/skills/multi/dingtalk-devdoc/SKILL.md b/skills/multi/dingtalk-devdoc/SKILL.md index d32080f2..39d5a0ee 100644 --- a/skills/multi/dingtalk-devdoc/SKILL.md +++ b/skills/multi/dingtalk-devdoc/SKILL.md @@ -18,26 +18,17 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[devdoc.md](references/devdoc.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | |--------|------| | "查 OAuth2 接入文档" | `dws devdoc article search --query "OAuth2 接入"` | -| "API 调用报错怎么办" | `dws devdoc error diagnose --query "<报错关键词>"` | -| "requestId 15r6h45w0muec 为什么失败" | `dws devdoc error diagnose --request-id 15r6h45w0muec` | -| "错误码 33012" | `dws devdoc error diagnose --error-code 33012` | +| "API 调用报错怎么办" | `dws devdoc article search --query "<报错关键词>"` | | "开放接口文档" | `dws devdoc article search --query "<接口名或场景>"` | ## 跨产品协作 diff --git a/skills/multi/dingtalk-devdoc/references/devdoc.md b/skills/multi/dingtalk-devdoc/references/devdoc.md index 4614911e..e6e3e239 100644 --- a/skills/multi/dingtalk-devdoc/references/devdoc.md +++ b/skills/multi/dingtalk-devdoc/references/devdoc.md @@ -9,57 +9,23 @@ Usage: dws devdoc article search [flags] Example: - dws devdoc article search "MCP" dws devdoc article search --query "OAuth2 接入" - dws devdoc article search --keyword "机器人" --size 10 dws devdoc article search --query "消息卡片" --page 2 --size 5 - dws devdoc article search --query "消息卡片" --cursor "" --size 5 + dws devdoc article search --query "机器人" --size 10 Flags: --query string 搜索关键词 (必填) - --keyword string 搜索关键词 (--query 的别名) --page int 分页页码 (从 1 开始,默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page --size int 分页大小 (默认 10) ``` -### 错误排查 -``` -Usage: - dws devdoc error diagnose [flags] - dws devdoc error troubleshoot [flags] -Example: - dws devdoc error diagnose --request-id 15r6h45w0muec - dws devdoc error diagnose --trace-id 15r6h45w0muec --api "创建日程" - dws devdoc error diagnose --error-code 33012 --error-message "missing scope" - dws devdoc error diagnose --query "机器人回调失败" --context "HTTP 403" -Flags: - --query string 原始排查问题 - --request-id string 开放平台 requestId - --trace-id string requestId 的兼容别名 - --error-code string 错误码 - --error-message string 错误描述,会合并进原始问题 - --api string API 名称,会合并进原始问题作为补充检索词 - --context string 额外排查上下文,会合并进原始问题 - --page int 分页页码 (从 1 开始,默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page - --size int 分页大小 (默认 10) -``` - ## 意图判断 用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节: -- 走 `devdoc article search`,把用户问的关键短语作为位置参数或 `--query` -- 用户已经给出错误码、错误描述或 requestId: -- 先走 `devdoc error diagnose`;若后端返回 `PARAM_ERROR - 未找到指定工具` / `unknown tool`,降级 `devdoc article search`,同时把该结果标记为 `needs_gateway_tool_registration` - -用户已经提供 requestId / traceId / 错误码 / 错误描述 / 失败上下文: -- 走 `devdoc error diagnose`,优先传 `--request-id`,没有 requestId 时传 `--error-code`、`--error-message`、`--query` 或 `--context` +- 走 `devdoc article search`,把用户问的关键短语作为 `--query` 关键区分: - devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容) -- devdoc 只做搜索/诊断,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 -- `devdoc error diagnose` 只返回诊断事实、参考资料和链接,不生成 AI 分析结论 -- `--api`、`--error-message`、`--context` 是 CLI 侧易用参数,调用 MCP 时会合并到 `query`;MCP 入参只发送 `query`、`requestId`、`errorCode`、`page`/`cursor`、`size` +- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览 ## 核心工作流 @@ -67,48 +33,16 @@ Flags: # 开发者问"OAuth2 怎么接" dws devdoc article search --query "OAuth2 接入" --format json -# 简短关键词可直接作为位置参数 -dws devdoc article search "MCP" --format json - # 命中结果多时翻页 dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json -# 推荐动态翻页:优先使用上一页返回的 nextCursor -dws devdoc article search --query "消息卡片" --cursor "" --size 5 --format json - # 查错误码 / 字段含义 dws devdoc article search --query "errcode 40078" --format json - -# 已经有 requestId 时排查 -dws devdoc error diagnose --request-id 15r6h45w0muec --format json - -# 只有 traceId 时按 requestId 兼容处理 -dws devdoc error diagnose --trace-id 15r6h45w0muec --api "创建日程" --format json - -# 只有错误码和错误描述时排查 -dws devdoc error diagnose --error-code 33012 --error-message "missing scope" --format json - -# 错误排查结果继续翻页 -dws devdoc error diagnose --error-code "40014" --query "access_token" --cursor "" --format json ``` -## 上线/注入验收 - -```bash -dws devdoc --help -dws devdoc article search --query "OAuth2 接入" --dry-run --format json -dws devdoc error diagnose --error-code "40014" --query "access_token" --dry-run --format json -dws schema devdoc.search_open_platform_docs_rag --format json || true -dws schema devdoc.search_open_error_code_rag --format json || true -``` - -- `--help` / `--dry-run` 只能证明 CLI 命令面和映射存在,不能证明后端工具已注册 -- 真实调用若返回 `PARAM_ERROR - 未找到指定工具`,不是用户参数问题;记录为网关/工具注册待闭环,并降级到 article search 或本地已挂载 Markdown - ## 注意事项 -- 关键词必填;可用位置参数、`--query` 或兼容别名 `--keyword`。建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 -- 错误排查至少提供 `--query`、`--request-id`、`--error-code`、`--error-message`、`--context` 之一;单独 `--api` 只作为补充上下文,不足以发起排查 -- 返回按相关性排序,默认 `--size 10`;响应有 `hasMore=true` 时,用 `nextCursor` 传给 `--cursor` 继续翻页,不要手写猜测下一页 +- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写 +- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词 - 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考 - 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档** diff --git a/skills/multi/dingtalk-ding/SKILL.md b/skills/multi/dingtalk-ding/SKILL.md index 9953315d..153e6b72 100644 --- a/skills/multi/dingtalk-ding/SKILL.md +++ b/skills/multi/dingtalk-ding/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[ding.md](references/ding.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-doc/SKILL.md b/skills/multi/dingtalk-doc/SKILL.md index 78f0e5bb..2d0b50ef 100644 --- a/skills/multi/dingtalk-doc/SKILL.md +++ b/skills/multi/dingtalk-doc/SKILL.md @@ -18,80 +18,52 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 -> 渐进式命令参考入口:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。详细参数、示例和踩坑说明按任务读取 `references/doc/` 下对应子文档。 - -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 +> 命令参考:[doc.md](references/doc.md);剧本:[04-document.md](references/04-document.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。 ## URL 预检(含 alidocs URL 时必读) 输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式:`/i/p/...`(分享短链)、`/i/nodes/...`(节点链接,类型需探测)、`/spreadsheetv2/...`(电子表格直链)、`/document/edit|preview?dentryKey=...`(文档链接)等,每种处理流程不同。**必须先读 [url-patterns.md](references/url-patterns.md) 中的「alidocs URL 分流决策」**,按规则识别 URL 类型后再选择对应命令;其中 `/document/edit|preview?dentryKey=...` 直接路由到 `doc`,将完整 URL 原样传给 `--node`,**不要**提取 `dentryKey` 当裸 nodeId。 -## 渐进式文档读取规则 - -执行任何 `dws doc` 操作前,先读 [references/doc.md](references/doc.md) 作为路由层,再按其中「命令索引表 / 场景索引」读取对应子文档。不要只凭本文件或记忆补参数。 - -- URL 解析、定位 nodeId、判断 contentType:读 [doc-info.md](references/doc/doc-info.md),必要时加读 [doc-search.md](references/doc/doc-search.md) / [doc-list.md](references/doc/doc-list.md)。 -- 新建文档:读 [doc-create.md](references/doc/doc-create.md)、[doc-update.md](references/doc/doc-update.md)、[doc-create-workflow.md](references/doc/style/doc-create-workflow.md)、[doc-style-guideline.md](references/doc/style/doc-style-guideline.md)。 -- 改写已有文档或块级编辑:读 [doc-read.md](references/doc/doc-read.md)、[doc-update.md](references/doc/doc-update.md)、[doc-block.md](references/doc/doc-block.md)、[doc-update-workflow.md](references/doc/style/doc-update-workflow.md)。 -- 涉及 callout、分栏、颜色、表格、@人、附件、图片或保真改写:加读 [doc-jsonml-cookbook.md](references/doc/format/doc-jsonml-cookbook.md) 和 [doc-jsonml-schema.md](references/doc/format/doc-jsonml-schema.md)。 -- 评论、权限、附件、导出、上传下载、复制移动重命名删除:按 [doc.md](references/doc.md) 的命令索引读取对应 `doc-*.md` 子文档。 - -## JSONML / Markdown 形态选择 - -创建和改写文档时按 [doc-update-workflow.md](references/doc/style/doc-update-workflow.md) 的编辑形态优先级执行:**JSONML 首选,element JSON 次选,Markdown 兜底**。 - -- 已有文档的局部改写、富结构保真、属性调整、callout / 分栏 / 表格 / @人 / 附件 / 颜色 / 嵌套结构:优先 `--content-format jsonml` 或 `doc block ... --content-format jsonml`。 -- 决策型、含对比的数据沉淀型、用户要求美观/醒目/重点突出的新文档:直接 JSONML 起稿,使用 `.json` + `--content-format jsonml`。 -- 执行型、说明型、纯文本追加等简单内容:可以 Markdown 起稿,使用 `.md` + `--content-format markdown`;若后续需要富结构,再按 JSONML 工作流精修。 -- Markdown overwrite 会丢失部分富结构。整篇 overwrite 前必须提示风险并等待用户确认,写入后必须回读校验。 - ## 参数硬约束 - 创建文档只用 `--name`,不要写 `--title`。 - 目标文件夹只用 `--folder <文档文件夹nodeId或URL>`,不要写 `--parent` / `--parent-node` / `--parent-id`。 - 目标知识库只用 `--workspace `,不要写 `--space-id` / `--spaceId`。 - 文档内容:`create` / `update` 都只接 `--content` / `--content-file`,不要写 `--markdown`。 -- 内容格式显式传 `--content-format jsonml` 或 `--content-format markdown`;不确定时先读对应子文档或 `dws doc --help`。 -- 复杂内容(换行、表格、代码块、长 Markdown / JSONML)先写临时 `.md` 或 `.json`,再用 `--content-file`,不要把大段内容塞进命令行。 -- 每次 `create` / `update` / `block insert` / `block update` / `media insert` 后必须 `dws doc read` 或 `dws doc block list` 回读关键内容。 +- 复杂内容(换行、表格、代码块、长 Markdown)先写临时 `.md`,再用 `--content-file`,不要把大段 Markdown 塞进命令行。 +- 每次 `create` / `update` / `block insert` / `media insert` 后必须 `dws doc read` 或 `dws doc block list` 回读关键内容。 ## 意图表 | 用户说 | 命令 | |--------|------| -| "创建文档" | 先读 `doc-create` + 创建工作流;按类型选择 JSONML 或 Markdown;`dws doc create --name "<标题>" --content-file --content-format ` | -| "搜文档" | 读 `doc-search.md`;`dws doc search --query "<关键词>"` | -| "读文档内容" | 读 `doc-read.md`;`dws doc read --node `,保真改写前用 `--content-format jsonml` | -| "更新文档内容 / 分块追加" | 读 `doc-update.md` + 改写工作流;按形态选择 `doc update` 或 `doc block update` | -| "插入 / 修改富 block" | 读 `doc-block.md` + JSONML cookbook/schema;优先 `dws doc block ... --content-format jsonml` | -| "删除文档/文件" | `dws doc delete`(需用户确认) | +| "创建文档(短内容)" | `dws doc create --name "<标题>" --content "<内容>"` | +| "创建+写入(长内容自动分块)" | `python scripts/doc_create_and_write.py --name "<标题>" --content "<内容>" [--mode append\|overwrite]` | +| "搜文档" | `dws doc search --query "<关键词>"` | +| "读文档内容" | `dws doc read --node ` | +| "更新文档内容 / 分块追加" | `dws doc update --node --content "<分块>" --mode append` | | "删除块" | `dws doc block delete`(需用户确认) | ## 评测/多步文档短路径 - 知识库「评测记录」下按日期文件夹执行:`dws wiki space search --keyword "评测记录" --format json` → `dws doc list --workspace --format json` → 找 `评测-doc-YYYYMMDD`;不存在则 `dws doc folder create --name "评测-doc-YYYYMMDD" --workspace --format json`。 -- 在目标文件夹创建文字文档:按创建工作流选择 `` + `--content-format jsonml` 或 `` + `--content-format markdown`,执行 `dws doc create --name "<标题>" --folder --content-file --content-format --format json`。拿到 `nodeId` 后立即回读。 -- 块级编辑固定顺序:`doc block list --node --content-format jsonml` → 选 `uuid` → `doc block insert/update --content-format jsonml` → `doc block list` 或 `doc read` 验证。删除块必须已有用户明确删除意图或二次确认。 -- 插入引用块、代码块、callout 高亮块、列表、分栏、分隔线、表格等富结构时,先读对应 JSONML 文档并按 cookbook 构造 `--element`;不要默认走旧的 `--type` 快捷路径。 +- 在目标文件夹创建文字文档:`dws doc create --name "<标题>" --folder --content-file --format json`。拿到 `nodeId` 后立即回读。 +- 块级编辑固定顺序:`doc block list --node ` → 选 `blockId` → `doc block insert/update/delete` → `doc block list` 验证。删除块必须已有用户明确删除意图或二次确认。 +- 插入引用块、代码块、表格、分栏、附件、图片时,优先读 [doc.md](references/doc.md) 对应小节,不要只停在"准备查看 help"。说出"我将插入..."后必须立即执行对应 terminal 调用。 - 用户要求多个子文档/附件/块操作时,按 checklist 串行完成;最后一条 assistant 消息不能停在"接下来我要...",必须有实际工具调用或明确失败原因。 - 用户说"下载文件"时用 `doc download --node ... --output `;用户说"导出在线文档为 docx"时用 `doc export --node ... --output `。 - 所有 dws 命令带 `--format json`;仅参数不确定时查 `--help`,不要把完整 help 当成最终结果。 ## 危险操作 -`doc delete` 会删除整篇文档/文件到回收站,`block delete` 会删除文档内部块;两者都必须确认再加 `--yes`。 +`block delete` 不可逆,必须确认再加 `--yes`。 ## 跨产品协作 - 文件存储 / 上传下载 → 切到 `dingtalk-drive` - 知识库空间管理 → 切到 `dingtalk-wiki` - 数据表 → 切到 `dingtalk-aitable` -- 长篇报告生成(多源采集 + 写文档)→ 仍按本文 JSONML / Markdown 形态选择和回读校验规则执行 +- 长篇报告生成(多源采集 + 写文档)→ 此 skill 提供 `doc_create_and_write.py` 脚本 diff --git a/skills/multi/dingtalk-doc/references/04-document.md b/skills/multi/dingtalk-doc/references/04-document.md index f4937a89..c884341e 100644 --- a/skills/multi/dingtalk-doc/references/04-document.md +++ b/skills/multi/dingtalk-doc/references/04-document.md @@ -1,18 +1,81 @@ # 文档知识 -> lite recipe 见 [SKILL.md 速查表](../SKILL.md#常用-recipe-速查表lite-recipe--可直接执行无需读行动指南文件)。 +> 通用规范见 [_common/conventions.md](_common/conventions.md)。 | Recipe | 行动指南(固定路线) | |--------|-------------------| -| write-doc | 0. 阅读 [doc-create-workflow.md](doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)
1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)
2. `doc create --content-file /tmp/.json --content-format jsonml`(或 `.md` + `--content-format markdown`)
3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片
4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node `,校验关键标题/段落/表格是否完整写入 | +| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. **先把内容写入临时文件**(Linux/Mac `/tmp/.md`,Windows `%TEMP%\.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串
3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file [--folder ] [--workspace ]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)
4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node --content-file --mode append`
5. **回读校验**(必须):所有写入完成后,执行 `doc read --node ` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)
备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` | | search-docs-and-share | 1. `doc search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) | | create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `doc create --name "<文档名>" --workspace ` → 取 `nodeId`
3. `doc list --workspace ` 确认 | | migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `.md`
2. `doc create --name "<文档名>" --folder --content-file .md` → 取新 `nodeId`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId;正文 <200KB 单步到位)
2a. 若正文 >200KB:**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" --folder ` → `nodeId` → 按段落切片 → 每片 `doc update --node --content-file --mode append`
3. **回读校验**:`doc read --node ` 校验内容完整性 | -| update-doc-section | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. **形态选择(按 [doc-update-workflow.md §1.3](doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe
3. `doc read --node ` 定位目标章节
4. `doc update --node --content "<替换内容>" --mode overwrite`
5. **回读校验**:`doc read --node ` 确认 overwrite 未被降级为 append、内容完整无截断
**overwrite 须用户确认**;完整改写流程见 [doc-update-workflow.md](doc/style/doc-update-workflow.md) | -| rewrite-doc | 1. 阅读并执行 [doc-update-workflow.md](doc/style/doc-update-workflow.md):先看 §1.3 编辑形态优先级(**JSONML 首选**),再按 §3 速查表选路径,跳 §4 对应小节执行
2. 单块改写 / 含富结构 → §4.4 路径 B;多处保真改写或改 root → §4.4 路径 A;纯文本骨架重写 → §4.5 markdown
3. 整篇 overwrite 前必须按 workflow §4.5 向用户提示风险并等待确认
4. **回读校验(必须)**:按 workflow §6 的校验要点逐项核查;@人、附件、图片等保真要素必须原样保留
**适用场景**:用户提供已有 nodeId/链接,需要改写、润色、章节补充、段落形态转换、整篇重写 | +| update-doc-section | **优先块级精修(保形)**:1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc block list --node --format json` 定位目标块取 `blockId`
3. `doc block update --node --block-id --text "<替换内容>" --format json` 局部修改(不破坏富格式)
4. **回读校验**:`doc read --node ` 确认目标章节已更新
**兜底**:仅当无法定位块且用户确认整篇替换时,先 `doc update --node --content-file .md --mode overwrite --dry-run` 预览,再加 `--yes` 执行;overwrite 会丢富格式(颜色/字号/合并单元格等),详见下方 template-based-generation §"为什么避免 overwrite" | +| template-based-generation | **触发**:用户给已有 alidocs URL + "参照/按模板/复刻/同样模板生成 X 月份的" → **保形复制,禁止重写**。固定走下方 [template-based-generation](#template-based-generation) 章节:`doc copy` 保形复制 → `doc rename` → `doc block list/update` 局部改副本。禁止 `doc read + doc create` 重写链。 | | doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) | -| lossless-doc-edit | 1. `doc read --node --content-format jsonml --output /tmp/doc.json` → 获取完整 JSONML 结构(输出含 `revision`,并发敏感时记下来;默认改写不需要)
2. 解析 JSON 文件,修改 `jsonml` 数组中的目标节点(节点结构参见 [doc-jsonml-schema.md](doc/format/doc-jsonml-schema.md))
3. 将修改后的内容写回临时文件 `/tmp/doc_modified.json`,格式为 `{"jsonml": [...]}`
4. `doc update --node --content-file /tmp/doc_modified.json --content-format jsonml --mode overwrite`(默认不做并发检查;担心多 agent 同时改时加 `--revision <第 1 步拿到的 N>` 触发并发检查,版本不一致返回 `VersionConflict` 时回到第 1 步重读重写)
5. **回读校验**:`doc read --node --content-format jsonml` 确认写入成功
**适用场景**:保留样式、精准插入特定节点类型、改属性不动文本;普通文本编辑仍优先用 markdown 模式。完整 JSONML 改写流程见 [doc-update-workflow.md §4.4](doc/style/doc-update-workflow.md) | -| jsonml-node-edit | 1. `doc block list --node --content-format jsonml` → 获取 JSONML 节点列表(含 uuid)
2. 根据 uuid 定位目标节点
3. `doc block list --node --content-format jsonml --block-id ` → 读取完整子树
4. 修改 JSONML 节点内容(节点结构参见 [doc-jsonml-schema.md](doc/format/doc-jsonml-schema.md),可复制范例见 [doc-jsonml-cookbook.md](doc/format/doc-jsonml-cookbook.md))
5. `doc block update --node --block-id --content-format jsonml --element '<修改后的 JSONML>'` → 写回
**适用场景**:只改一个 block 的结构/样式,无需全文回写;写入端默认 normalize(自动补 uuid、裸字符串包成 canonical 文本),可用 `--no-fix-jsonml` 关闭全部修复;`--fix-jsonml` 额外启用 JSON 语法修复(推荐 agent 调用) | +| delete-old-doc | 1. `doc search --query "<关键词>"` 或 `doc list --folder ` 取 `nodeId`
2. **必须先向用户展示要删除的文档标题/路径**,等用户确认
3. `doc delete --node --yes`(注意:是 `doc delete`,不是 `doc block delete`;前者删整篇,后者删块)
4. 验证:`doc info --node ` 应返回 not-found | +| export-doc-as-docx | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc export --node --output /tmp/.docx --timeout-sec 600`(CLI 内置渐进式退避轮询,**不要自己拼 GET downloadUrl**)
3. 落盘失败兜底:`doc export get --job-id --output /tmp/.docx` 续等
4. (可选)`doc-to-message` 把 docx 路径作为附件发给用户 | +| grant-doc-access | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `contact user search --query "<姓名>"` → 取 `userId`(**注意**: doc permission 用 `userId`,不是 `openDingTalkId`)
3. **节点级**授权:`doc permission add --node --user --role EDITOR`
  role 取值: MANAGER / EDITOR / DOWNLOADER / READER(**不要传 OWNER**)
  单次最多 30 个 userId
4. 查权限:`doc permission list --node ` 确认
5. **不要混淆**: 给"知识库"加成员用 `wiki member add --workspace`(容器级);给"单个文档"加权限用 `doc permission add --node`(节点级)| +| insert-image-to-doc | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc media insert --node --file ./image.png`(3 步一体化:获取凭证 → PUT OSS → 插入块;图片 ≤20MB 自动作为内联图片,其他作为附件块)
3. (可选)`doc read --node ` 回读确认
**禁止**: 自己拿 uploadUrl 写 PUT —— 90%+ 会因 Content-Type 未清空触发 SignatureDoesNotMatch | +| download-doc-attachment | 1. `doc block list --node ` → 找到 `blockType=attachment` 的块取 `resourceId`
2. `doc media download --node --resource-id ` → 返回 OSS 临时 URL(**注意 expirationSeconds**)
3. 调用方自行 GET 该 URL 落盘 | + +--- + +## template-based-generation + +### 触发条件 + +用户给已有 alidocs URL,并要求: +- "参照这个生成同样的" +- "按模板生成" +- "复刻这个" +- "同样的模板,X 月份的" +- "仿照这个文档" + +### 核心原则 + +**保形优于重写**。adoc 富格式(行高、单元格背景色、字号、渐变文字色、表格列宽、合并单元格)在 markdown 层无表达,`doc read → create_file → doc create` 会做两次 lossy projection(先 adoc→markdown,再 markdown→adoc),必然丢失这些属性。 + +正确做法:**在 adoc 层保形复制(`doc copy`)**,再局部改写需要替换的块(`doc block update`),保留所有富格式。 + +### 固定路线 + +```bash +# 1. probe 模板节点,确认是 ALIDOC(adoc 富文档) +dws doc info --node <模板NODE或URL> --format json + +# 2. 在 adoc 层保形复制(不经过 markdown 投影) +dws doc copy --node <模板NODE或URL> --folder <目标文件夹NODE或URL> --format json +# → 取返回的副本 nodeId + +# 3. 重命名副本 +dws doc rename --node <副本nodeId> --name "<新文档名>" --format json + +# 4. 列出所有块定位要改的内容 +dws doc block list --node <副本nodeId> --format json +# → 记录 blockId 和现有文本 + +# 5. 逐块局部改写(只改月份/日期/姓名/指标等局部变量,不动结构) +dws doc block update --node <副本nodeId> --block-id --text "<新内容>" --format json + +# 6. 回读确认 +dws doc read --node <副本nodeId> +``` + +### 为什么避免 overwrite + +| 场景 | overwrite 后果 | +|------|---------------| +| 模板带表格背景色 | 全部变白 | +| 标题字号 24pt 加粗渐变 | 退化为普通 H1 | +| 合并单元格的表格 | 拆成独立单元格 | +| 嵌入图片 | 链接保留但样式重置 | + +**用 copy+block update 全部可保留**。 + +### 禁止反模式 + +- ❌ doc read + doc create 重写模板文档(两次 lossy projection) +- ❌ 先 `dws doc copy`,再删除副本退回 create 重写链路 +- ❌ 用 `dws doc update --mode overwrite --yes` 把含富格式的 adoc 整篇覆盖成 markdown --- @@ -43,15 +106,32 @@ ## doc update 回读校验规范 -**所有 `doc update`(含 overwrite 和 append)执行后都必须回读校验**——返回 `success=true` 不等于内容真的写入完整。 +### 为什么需要回读校验 -完整规范(静默失败场景、校验流程、异常处理路径、"先清空再重建"命令样板)见 [doc-update-workflow.md §6「回读验收」](doc/style/doc-update-workflow.md)。 +`doc update` 在以下场景可能产生**静默失败**,返回 `success=true` 但实际写入不完整: -最小流程: +- **overwrite 降级为 append**:大文档 overwrite 模式被后端静默降级,导致旧内容未清除、新内容追加在末尾 +- **分块 append 内容截断**:超长文档分片写入时,部分片段丢失或顺序错乱 +- **编码/通道问题**:PowerShell 等特殊终端下 UTF-8 内容传输乱码 + +### 校验流程 + +在 **每次 `doc update` 完成后**(无论 overwrite 还是 append 模式),必须执行以下步骤: + +1. **回读文档**:`doc read --node ` +2. **关键内容校验**:检查回读结果是否包含预期的关键标题、段落首句、表格标记等 +3. **异常处理**: + - 若内容缺失或截断 → 向用户报告具体差异,建议重试或手动修复 + - 若 overwrite 后仍残留旧内容 → 提示用户 overwrite 可能被降级,建议先清空再 append + - **禁止**在未回读的情况下直接向用户报告"已完成" + +### 示例 -```bash -dws doc update --node --content-file /tmp/new-content.md --mode overwrite -dws doc read --node # 校验关键标题、段落首句、表格、@人、附件 ``` +# 更新文档 +doc update --node abc123 --content-file /tmp/new-content.md --mode overwrite -**禁止**在未回读的情况下向用户报告「已完成」。 +# 回读校验 +doc read --node abc123 +# → 检查输出是否包含预期内容的关键标题和段落 +``` diff --git a/skills/multi/dingtalk-doc/references/doc.md b/skills/multi/dingtalk-doc/references/doc.md index 218915d8..04d21a9d 100644 --- a/skills/multi/dingtalk-doc/references/doc.md +++ b/skills/multi/dingtalk-doc/references/doc.md @@ -1,31 +1,5 @@ # 文档 (doc) 命令参考 -> **渐进式文档**:本文件为路由层(命令索引 + 场景索引 + 意图判断 + 工作流),各命令的详细参数、示例和踩坑说明在 [doc/](./doc/) 目录下按需加载。 - -## 文档地址 (URI) - -| 资源 | URI 格式 | -|------|----------| -| 文档节点 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | -| edit / preview 链接 | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | - -> **操作后请返回文档 URI**:每次执行 create / read / update 等操作后,从返回数据中提取 `docUrl` 直接返回;缺失时用 `doc info --node ` 补查。 - -## 前置条件 — 执行操作前必读 - -**CRITICAL — 执行对应操作前,MUST 先用 Read 工具读取以下子文件:** - -1. **解析 URL / 定位文档**(几乎所有命令都需要先拿 nodeId) - → 必读 [`doc/doc-info.md`](./doc/doc-info.md)(URL/dentryKey 提取规则、ID 边界、contentType 路由、**获取 nodeId 三种方式 A/B/C**) - -2. **创建或编辑文档内容**(`doc create` / `doc update` / `doc block insert|update`) - → 必读 [`doc/style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md)(**形态优先级硬规则:JSONML > element JSON > markdown**;markdown overwrite 会丢富结构) - - 从零创建时加读 [`doc/style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) - - **任何 `doc create` 都必须先读 [`doc/style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) §2.0 类型决策表 + §1 硬规则**(决定骨架 + 全局约束,不读就不知道用哪种骨架) - - 涉及 callout / 分栏 / 富 block 精修时再加读 style-guideline §4-§7 + [`doc/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) - -**未读以上文件就改写已有文档会导致富结构丢失、参数错误或样式不达标。其他命令(阅读 / 评论 / 权限 / 附件 / 下载导出 / 文件操作)按需查下方 §命令索引表跳转对应子文件加载,不必提前加载。** - ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -35,294 +9,1024 @@ dws doc --help # 查看具体命令的完整参数说明 -dws doc read --help +dws doc list --help dws doc create --help dws doc block insert --help # 查看子命令组下的所有命令 dws doc block --help -dws doc media --help +dws doc permission --help ``` 规则: - - 参数名不确定时 → 先 `--help`,再调用 - 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 - 不确定某个功能是否存在时 → `dws doc --help` 查看命令列表 -## 命令索引表 +## 命令总览 -> 命令名 → 单文件,按需加载子文档。复杂任务请优先看下方 §场景索引。 +### 搜索文档 +``` +Usage: + dws doc search [flags] +Example: + dws doc search --query "会议纪要" + dws doc search + dws doc search --extensions pdf,docx + dws doc search --query "方案" --created-from 1700000000000 --created-to 1710000000000 + dws doc search --creator-uids uid1,uid2 + dws doc search --workspace-ids wsId1,wsId2 +Flags: + --query string 搜索关键词 (不传则返回最近访问) + --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,png)。支持的在线文档类型后缀名: adoc=文字, axls=表格, appt=演示文稿, awbd=白板, adraw=画板, amind=脑图, able=多维表格, aform=收集表 + --created-from int 创建时间起始 (毫秒时间戳,含) + --created-to int 创建时间截止 (毫秒时间戳,含) + --visited-from int 访问时间起始 (毫秒时间戳,含) + --visited-to int 访问时间截止 (毫秒时间戳,含) + --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 + --editor-uids strings 按编辑者用户 ID 过滤,逗号分隔 + --mentioned-uids strings 按 @提及的用户 ID 过滤,逗号分隔 + --workspace-ids strings 按知识库 ID 过滤,支持知识库 URL,逗号分隔 + --page-size int 每页数量 (默认 10,最大 30) + --page-token string 分页游标 (从上次结果的 nextPageToken 获取) +``` -### 阅读 / 元信息 +### 遍历文件列表 +``` +Usage: + dws doc list [flags] +Example: + dws doc list + dws doc list --folder + dws doc list --workspace --page-size 20 +Flags: + --folder string 文件夹 ID 或 URL + --workspace string 知识库 ID + --page-size int 每页数量 (默认 50,最大 50) + --page-token string 分页游标 (从上次结果的 nextPageToken 获取) +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc info` | 文档元信息(含 contentType / extension) | `--node` | [`doc/doc-info.md`](./doc/doc-info.md) | -| `doc read` | 读取正文(markdown 或 jsonml) | `--node` | [`doc/doc-read.md`](./doc/doc-read.md) | +### 获取文档元信息 +``` +Usage: + dws doc info [flags] +Example: + dws doc info --node + dws doc info --node "https://alidocs.dingtalk.com/i/nodes/" + dws doc info --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" + dws doc info --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" +Flags: + --node string 文档 ID 或 URL (必填) +``` -### 创建 / 写入 / 块编辑 +### 读取文档内容 +``` +Usage: + dws doc read [flags] +Example: + dws doc read --node + dws doc read --node "https://alidocs.dingtalk.com/i/nodes/" + dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=" + dws doc read --node "https://alidocs.dingtalk.com/document/preview?dentryKey=" +Flags: + --node string 文档 ID 或 URL (必填) +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc create` | 创建文字文档(adoc) | `--name` | [`doc/doc-create.md`](./doc/doc-create.md) | -| `doc update` | 整篇 / 段落级更新(markdown / jsonml) | `--node` `--mode` | [`doc/doc-update.md`](./doc/doc-update.md) | -| `doc block list/insert/update/delete` | 块级精细编辑(含 JSONML 节点操作) | `--node` (+ `--block-id`) | [`doc/doc-block.md`](./doc/doc-block.md) | +### 创建文档 +``` +Usage: + dws doc create [flags] +Example: + dws doc create --name "项目周报" + dws doc create --name "Q1 总结" --content "# Q1 总结" --folder + dws doc create --name "知识库文档" --workspace +Flags: + --name string 文档名称 (必填) + --folder string 父文件夹 nodeId / URL + --workspace string Workspace ID + --content string 初始 Markdown 内容 + --content-file string 从文件读取初始 Markdown +``` -### 附件 / 评论 / 导出 +### 创建其他类型文件 (表格/脑图/白板/多维表/画板) +``` +Usage: + dws doc file create [flags] +Example: + dws doc file create --name "项目周报" --type adoc + dws doc file create --name "数据统计" --type axls --folder + dws doc file create --name "思维导图" --type amind --workspace + dws doc file create --name "子文件夹" --type folder +Flags: + --name string 文件名称 (必填) + --type string 文件类型 (必填): adoc=文档, axls=表格, appt=演示, adraw=白板, amind=脑图, able=多维表, folder=文件夹 + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL +``` -| 命令 | 用途 | 必填参数 | 详见 | -|------|------|----------|------| -| `doc media insert/download` | 附件 / 图片插入与下载 | `--node` `--file` 或 `--resource-id` | [`doc/doc-media.md`](./doc/doc-media.md) | -| `doc comment list/create/reply/create-inline` | 文档评论与划词评论 | `--node` (+ ...) | [`doc/doc-comment.md`](./doc/doc-comment.md) | -| `doc export` / `doc export get` | 在线文档导出 docx | `--node` `--output` | [`doc/doc-export.md`](./doc/doc-export.md) | +### 更新文档内容 +``` +Usage: + dws doc update [flags] +Example: + dws doc update --node --content "# 追加内容" --mode append + dws doc update --node --content "# 完整替换" --mode overwrite +Flags: + --node string 文档 nodeId / URL + --content string Markdown 内容 + --content-file string 从文件读取 Markdown + --mode string 更新模式: append / overwrite +``` -### 文件操作(已迁移,以下命令 deprecated) +### 上传文件到钉钉文档或钉钉知识库 +``` +Usage: + dws doc upload [flags] +Example: + dws doc upload --file ./report.pdf + dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder + dws doc upload --file ./data.xlsx --workspace --convert +Flags: + --file string 本地文件路径 (必填) + --name string 文件显示名称 (默认使用文件名) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID + --convert 是否转换为钉钉在线文档 +``` -> **迁移提示**:文件管理命令已按产品领域架构重新归属,旧命令在过渡期内仍可使用,运行时会输出 deprecated 警告。 +### 下载文件到本地 +``` +Usage: + dws doc download [flags] +Example: + dws doc download --node + dws doc download --node --output ./report.pdf + dws doc download --node "https://alidocs.dingtalk.com/i/nodes/" --output ~/downloads/ +Flags: + --node string 文件节点 ID 或 URL (必填) + --output string 本地保存路径 (文件路径或目录,必填) +``` -| 旧命令 | 推荐命令 | 详见 | -|--------|----------|------| -| `doc upload` | `dws drive upload` | [`drive.md`](./drive.md) | -| `doc download` | `dws drive download` | [`drive.md`](./drive.md) | -| `doc copy` | `dws drive copy` | [`drive.md`](./drive.md) | -| `doc move` | `dws drive move` | [`drive.md`](./drive.md) | -| `doc rename` | `dws drive rename` | [`drive.md`](./drive.md) | -| `doc delete` | `dws drive delete` | [`drive.md`](./drive.md) | -| `doc folder create` | `dws drive folder create` | [`drive.md`](./drive.md) | -| `doc file create` | `dws wiki node create --type ` | [`wiki.md`](./wiki.md) | -| `doc permission *` | `dws drive permission *` | [`drive.md`](./drive.md) | -| `doc list` | `dws drive list --workspace` / `dws wiki node list` | [`drive.md`](./drive.md) / [`wiki.md`](./wiki.md) | -| `doc search` | `dws drive search` / `dws wiki node search` | [`drive.md`](./drive.md) / [`wiki.md`](./wiki.md) | +### 创建文件夹 +``` +Usage: + dws doc folder create [flags] +Example: + dws doc folder create --name "项目资料" + dws doc folder create --name "子文件夹" --folder +Flags: + --name string 文件夹名称 (必填) + --folder string 父文件夹 ID 或 URL + --workspace string 目标知识库 ID +``` -### 排版规范 / JSONML 参考 +### 复制文档/文件 +``` +Usage: + dws doc copy [flags] +Example: + dws doc copy --node --folder + dws doc copy --node --workspace + dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/" --folder +Flags: + --node string 源文档/文件 ID 或 URL (必填) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录) +``` -| 资源 | 用途 | 详见 | -|------|------|------| -| 创建工作流 | 标题、位置、骨架、回读校验 | [`doc/style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) | -| 改写工作流 | 编辑形态优先级、分片 append、回读验收 | [`doc/style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | -| 排版规范 | 文档类型 / 骨架 / 元素边界 / 颜色语义 | [`doc/style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | -| JSONML 范例 | 所有节点类型的可复制命令 | [`doc/format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) | -| JSONML 节点结构 | 字段定义 + JSON Schema | [`doc/format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) | +### 移动文档/文件 +``` +Usage: + dws doc move [flags] +Example: + dws doc move --node --folder + dws doc move --node --workspace +Flags: + --node string 源文档/文件 ID 或 URL (必填) + --folder string 目标文件夹 ID 或 URL + --workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录) +``` -## 场景索引 +### 重命名文档/文件 +``` +Usage: + dws doc rename [flags] +Example: + dws doc rename --node --name "新名称" + dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/" --name "项目周报 v2" +Flags: + --node string 文档/文件 ID 或 URL (必填) + --name string 新名称 (必填) +``` -> 任务驱动的入口:知道任务但不确定要读哪些命令文件时,按本表**一次性 Read 文件组**,不要逐个跳。 +### 查询块元素 +``` +Usage: + dws doc block list [flags] +Example: + dws doc block list --node + dws doc block list --node --start-index 0 --end-index 5 + dws doc block list --node --block-type heading +Flags: + --node string 文档 ID 或 URL (必填) + --start-index int 起始位置 (从 0 开始) + --end-index int 终止位置 (含) + --block-type string 按块类型过滤 +``` -| 任务场景 | 一次性读取 | 主命令 | -|---------|-----------|--------| -| 定位 nodeId / URL 解析 | [`doc-info.md`](./doc/doc-info.md)(搜索请用 `dws drive search` / `dws wiki node search`;遍历请用 `dws drive list` / `dws wiki node list`) | `drive search` / `wiki node search` | -| 阅读已有文档 | [`doc-info.md`](./doc/doc-info.md) + [`doc-read.md`](./doc/doc-read.md) | read | -| 创建新文档 | [`doc-create.md`](./doc/doc-create.md) + [`doc-update.md`](./doc/doc-update.md)(写入管道)+ [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create | -| 创建文档且包含图片/截图/图文并茂 | [`doc-create.md`](./doc/doc-create.md) + [`doc-media.md`](./doc/doc-media.md) + [`style/doc-create-workflow.md`](./doc/style/doc-create-workflow.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | create → media insert | -| 局部改写 / 段落替换(保真) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | block update | -| 整篇 overwrite(可选并发检查) | [`doc-read.md`](./doc/doc-read.md) + [`doc-update.md`](./doc/doc-update.md) + [`format/doc-jsonml-schema.md`](./doc/format/doc-jsonml-schema.md) + [`style/doc-update-workflow.md`](./doc/style/doc-update-workflow.md) | update | -| 插入富 block(callout / 分栏 / 表格) | [`doc-block.md`](./doc/doc-block.md) + [`format/doc-jsonml-cookbook.md`](./doc/format/doc-jsonml-cookbook.md) + [`style/doc-style-guideline.md`](./doc/style/doc-style-guideline.md) | block insert | -| 上传图片 / 附件 | [`doc-media.md`](./doc/doc-media.md) | media insert | -| 评论 / 划词评论(含 @人) | [`doc-comment.md`](./doc/doc-comment.md)(+ `dws contact user search` 取 mention 用 userId) | comment create | -| 文档分享 / 节点级权限 | [`drive.md`](./drive.md)(已迁移:`dws drive permission add/update/list/remove`) | `drive permission` | -| 导出 PDF / DOCX | [`doc-info.md`](./doc/doc-info.md) + [`doc-export.md`](./doc/doc-export.md) | export | -| 文件下载 / 上传 / 移动 / 重命名 / 复制 | [`drive.md`](./drive.md)(已迁移:`dws drive upload/download/copy/move/rename/delete`) | `drive *` | +### 插入块元素 +``` +Usage: + dws doc block insert [flags] +Example: + dws doc block insert --node --text "这是一段文字" + dws doc block insert --node --heading "二级标题" --level 2 + dws doc block insert --node --element '{"blockType":"paragraph","paragraph":{"text":"内容"}}' + dws doc block insert --node --text "在此处之前插入" --ref-block --where before +Flags: + --node string 文档 ID 或 URL (必填) + --text string 快捷: 段落文本内容 + --heading string 快捷: 标题文本 + --level int 标题级别 1-6 (配合 --heading,默认 1) + --element string 块元素 JSON (高级) + --index int 参照位置索引 (从 0 开始) + --where string 插入方向: before / after (默认 after) + --ref-block string 参照块 ID (优先级高于 --index) +``` + +### 更新块元素 +``` +Usage: + dws doc block update [flags] +Example: + dws doc block update --node --block-id --text "新内容" + dws doc block update --node --block-id --element '{"blockType":"heading","heading":{"text":"新标题","level":1}}' +Flags: + --node string 文档 ID 或 URL (必填) + --block-id string 目标块 ID (必填) + --text string 快捷: 段落文本内容 + --heading string 快捷: 标题文本 + --level int 标题级别 1-6 (配合 --heading,默认 1) + --element string 块元素 JSON (高级) +``` + +### 删除块元素 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc block delete [flags] +Example: + dws doc block delete --node --block-id --yes +Flags: + --node string 文档 ID 或 URL (必填) + --block-id string 目标块 ID (必填) +``` + +### 查询文档评论列表 +``` +Usage: + dws doc comment list [flags] +Example: + dws doc comment list --node + dws doc comment list --node --type inline --resolve-status unresolved + dws doc comment list --node --page-size 20 --next-token +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --page-size int 每页返回的评论数量,默认 50,最大 50 + --next-token string 分页游标,从上一次请求的返回结果中获取 (首次请求不传) + --type string 按评论类型过滤: global (全文评论) / inline (划词评论) + --resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决) +``` + +### 创建文档评论 +``` +Usage: + dws doc comment create [flags] +Example: + dws doc comment create --node --content "这里需要修改" + dws doc comment create --node --content "请review" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --content string 评论的文字内容,纯文本 (必填) + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 创建划词评论 (内联评论) +``` +Usage: + dws doc comment create-inline [flags] +Example: + dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" + dws doc comment create-inline --node --block-id --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文" + dws doc comment create-inline --node --block-id --start 0 --end 10 --content "请review" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --block-id string 评论锚定所在的块 ID (必填,可通过 dws doc block list 获取) + --start int 选中文本在块内的起始字符偏移量,从 0 开始 (必填) + --end int 选中文本在块内的结束字符偏移量,必须大于 start (必填) + --content string 评论的文字内容,纯文本 (必填) + --selected-text string 选中文本内容,填写后评论列表会展示「引用原文:xxx」 + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 回复文档评论 +``` +Usage: + dws doc comment reply [flags] +Example: + dws doc comment reply --node --comment-key --content "同意" + dws doc comment reply --node --comment-key --content "比心" --emoji + dws doc comment reply --node --comment-key --content "请确认" --mention uid1,uid2 +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --content string 回复的文字内容,表情回复时填写表情名称 (必填) + --comment-key string 被回复评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填) + --emoji 设为 true 时作为表情贴图回复 (默认 false) + --mention string 被 @ 的用户 uid 列表,逗号分隔 +``` + +### 文件内容获取路由规则 + +> 当用户请求"分析/查看/读取某个文件内容"时,**必须先调用 `dws doc info` 获取文件元数据**,再根据返回的 `contentType` 和 `extension` 字段选择对应链路: + +| contentType | extension | 操作 | 命令 | +|-------------|-----------|------|------| +| ALIDOC | adoc | 在线获取 Markdown 内容 | `dws doc read --node ` | +| ALIDOC | axls | 在线读取表格数据 | `dws sheet list --node ` → `dws sheet range read --node ` | +| ALIDOC | able | 在线查询多维表格记录 | `dws aitable table get --base-id ` → `dws aitable record query --base-id --table-id ` | +| 非 ALIDOC | — | **不支持在线分析** | 告知用户需下载到本地后查看 | + +**关键规则**:非 ALIDOC 类型文件(PDF/Word/图片/视频等)不支持在线分析,用户可以选择下载后本地查看。 + +### 格式保留度声明(adoc ↔ markdown lossy projection) + +`dws doc read --format json` 返回的 `markdown` 字段是钉钉 adoc 文档的**有损投影**。源 adoc 中以下富格式属性在 markdown 这一层**没有对应表达,会被丢弃**: + +- 行高(row height) +- 单元格背景色(cell background color) +- 字体大小(font size) +- 字体颜色的**渐变形态**(`radial-gradient` / `linear-gradient` — 文本会以 `` 字面保留,但 CSS `color` 不接受渐变,浏览器无法渲染,**字符串保留但语义已死**) +- 块级缩进 / 表格列宽 +- 合并单元格状态 +- checkbox 视觉样式(颜色、形状) +- 任何 SVG / 嵌入式画板的渲染细节 + +**硬指引(任何 contentType=ALIDOC 文档都适用)**: + +- 当用户要求"按模板生成同形态变体 / 参照这个生成 / 复刻 / 仿照这个文档"时,**禁止**走 doc read → create_file → doc create 链路——这条链路会做两次 lossy projection(adoc → markdown 丢一次,markdown → adoc 重建再丢一次),最终交付物只有文本内容,富格式全部失真。 +- 正确路径:`dws doc copy`(adoc 层面整文档保形复制)+ `dws doc rename` + `dws doc block list / dws doc block update`(在副本上做局部修改)。详见 [best_practices/04-document.md](../best_practices/04-document.md#template-based-generation) 的 `template-based-generation` recipe。 +- 当用户提到 `行高 / 颜色 / 字号 / 表格样式 / 周末高亮` 等富格式诉求时,agent 必须在动手前显式声明能力边界:"这些属性 markdown 无法表达,需走 copy + block update 路径",避免静默走破坏链。 + +### 内容写入管道(create / update 共用) + +> **关键原则**:CLI 内置自动分片。超长内容(>10000 字符)自动按 markdown 结构切分后逐片写入,对调用方透明。写入完成后由调用方自行决定是否回读确认。 + +#### 输入方式选择 + +| 场景 | 推荐方式 | 说明 | +|------|---------|------| +| 短文本(<2KB,无换行/表格/特殊字符) | `--content "..."` | 字面量传入,最简单 | +| 长文本(≥2KB)、含换行、含表格 | `--content-file ./file.md` | **必须**用文件路径,避免 shell escape 和截断 | +| 含特殊字符(`"`、`\`、`$`、`` ` ``) | `--content-file ./file.md` | 字面量传入会被 shell 转义破坏 | +| 管道/heredoc 输入 | `--content -` 或 `cat file \| dws doc ...` | 从 stdin 读取 | + +#### 自动分片行为 + +当内容超过 10000 字符时,CLI 自动执行: +1. **create**: 先创建空文档拿 `nodeId`,再按 markdown 标题边界切分后逐片 append +2. **update (overwrite)**: 第一片用 overwrite,后续片用 append +3. **update (append)**: 所有片段用 append + +分片策略按优先级:H1 标题 → H2 标题 → H3 标题 → 空行(段落边界)→ 硬切(保留表格/代码块完整性) + +如果某片写入超时,自动将分片大小减半重试(最小 5000 字符,低于此值报错)。 + +#### 输出格式 + +写入成功后输出 JSON(混合 `[INFO]` 进度行): + +```json +{"success": true, "nodeId": "xxx", "chunksWritten": 3} +``` + +| 字段 | 说明 | +|------|------| +| `nodeId` | 文档节点 ID,可用于后续读取或追加 | +| `chunksWritten` | 实际写入的分片数(1 = 单次写入) | + +#### 内容完整性验证(必读) + +CLI **不会**自动执行回读验证。**Agent 必须在文档写入完成后主动回读确认**——这是硬约束,不是建议: + +1. 使用 `dws doc read --node ` 读取写入后的文档内容 +2. 检查关键段落是否完整、顺序是否正确 +3. 如发现内容缺失或异常,使用 `dws doc update --mode append` 补写缺失部分 + +> **何时回读**:每次 create / update 操作完成后**必须**回读。 +> - 单次写入(≤10000 字符):写完立即回读一次 +> - 分片写入(>10000 字符):所有分片写完后回读一次全文,校验关键标题与段落完整性 +> - 破坏性 overwrite(`--mode overwrite --yes`):**必须**回读,确认 overwrite 未被后端静默降级为 append(详见 [best_practices/04-document.md "doc update 回读校验规范"](../best_practices/04-document.md#doc-update-回读校验规范)) +> - 连续多次编辑同一文档:可在全部编辑完成后统一回读一次 +> +> **禁止**在未回读的情况下直接向用户报告"已完成 / 更新成功"。Agent 自陈"完成"必须基于回读结果,而非工具调用的 `success=true` 字段。 + +#### 进度输出示例 + +``` +[INFO] 内容较长 (25000 字符),自动分片写入... +[INFO] 已创建空文档 (nodeId=abc123),开始分片写入... +[INFO] 写入分片 (1/3),10000 字符... +[INFO] 写入分片 (2/3),10000 字符... +[INFO] 写入分片 (3/3),5000 字符... +[INFO] 全部 3 个分片写入完成 +{"success": true, "nodeId": "abc123", "chunksWritten": 3} +``` + +#### CONTENT_TRUNCATED 错误 + +当分片写入持续超时且减半到最小阈值仍失败时,返回 `CONTENT_TRUNCATED` 错误码。应对策略: +1. 检查网络和后端服务状态 +2. 已写入的部分内容可通过 `dws doc read --node ` 查看 +3. 从断点处手动用 `dws doc update --mode append` 继续追加 + +### 删除文档/文件到回收站 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws doc delete [flags] +Example: + dws doc delete --node --format json # 查询 nodeId: dws doc search --query "..." 或 dws doc list +Flags: + --node string 文档/文件 ID 或 URL (必填) +``` + +权限要求: 对文档有"管理"权限。 + +### 下载文档附件 +``` +Usage: + dws doc media download [flags] +Example: + dws doc media download --node --resource-id + dws doc media download --node "https://alidocs.dingtalk.com/i/nodes/xxx" --resource-id +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --resource-id string 附件资源 ID,可通过 dws doc block list 获取 (必填) +``` + +### 上传附件并插入文档 +``` +Usage: + dws doc media insert [flags] +Example: + dws doc media insert --node --file ./report.pdf + dws doc media insert --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream + dws doc media insert --node --file ./image.png --ref-block --where before +Flags: + --node string 目标文档的标识,支持传入 URL 或 ID (必填) + --file string 本地文件路径 (必填) + --name string 附件显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) + --index int 插入位置索引 + --where string 相对位置: before / after (配合 --ref-block) + --ref-block string 参考块 ID (配合 --where) +``` + +### 添加文档权限(节点级授权) +``` +Usage: + dws doc permission add [flags] +Example: + dws doc permission add --node --users uid1 --role READER + dws doc permission add --node --users uid1,uid2 --role EDITOR + dws doc permission add --node "https://alidocs.dingtalk.com/i/nodes/" --users uid1 --role MANAGER +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --users strings 被授权的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) + --workspace string 所属知识库 ID (选填,仅用于辅助构造返回的 docUrl,业务实际依赖 nodeId) +``` + +> **重要约束**: +> - 仅支持 USER 类型授权。 +> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加)。 +> - 操作者需在该节点具备「可编辑(EDITOR)」及以上角色(OWNER / MANAGER / EDITOR)。 +> - 授权对象是文档节点本身,不需要也不应该用 `wiki member add`(那个是知识库容器级授权)。 + +### 修改文档权限(节点级) +``` +Usage: + dws doc permission update [flags] +Example: + dws doc permission update --node --users uid1 --role EDITOR + dws doc permission update --node --users uid1,uid2 --role READER +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER + --workspace string 所属知识库 ID (选填) +``` + +### 列出文档权限(节点级) +``` +Usage: + dws doc permission list [flags] +Example: + dws doc permission list --node + dws doc permission list --node --limit 50 + dws doc permission list --node --filter-role EDITOR +Flags: + --node string 目标文档/文件夹的 ID 或 URL (必填) + --workspace string 所属知识库 ID (选填) + --limit int 返回数量上限,最大 200 (默认 50) + --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) +``` + +> 接口不支持游标分页,使用 `--limit` 一次性拉取。 + +### 导出在线文档为 docx(一体化命令) +``` +Usage: + dws doc export [flags] +Example: + dws doc export --node "https://alidocs.dingtalk.com/i/nodes/xxx" --output ./exported.docx + dws doc export --node --output ~/downloads/ +Flags: + --node string 要导出的文档标识,支持文档 URL 或 dentryUuid (必填) + --output string 本地保存路径,文件路径或目录 (必填) + --export-format string 导出格式,当前仅支持 docx (默认) +``` + +CLI 内部自动完成:提交导出任务 → 渐进式退避轮询(最多约 5 分钟)→ 成功后自动下载文件。 +**只需一条命令,无需手动轮询。** + +### 查询导出任务结果(手动兜底) +``` +Usage: + dws doc export get [flags] +Example: + dws doc export get --job-id +Flags: + --job-id string 导出任务 ID (必填) +``` + +仅在 `dws doc export` 超时或中断后,用于手动查询任务状态。通常不需要调用。 + + +## URL 识别与 DOC_ID 提取 + +当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。 + +补充:如果这是用户直接提供的原始 `alidocs` URL,必须先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `doc`。 + +### 支持的 URL 格式 + +| 格式 | 示例 | DOC_ID 提取方式 | +|------|------|----------------| +| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段:`9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | +| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段:`abc123` | +| `alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | `https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE` | **不要提取 `dentryKey` 单独使用**,必须将完整 URL 原样传给 `--node` | + +### 提取规则 + +1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 +2. 路径为 `/i/nodes/{id}` 时,取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment) +3. 路径为 `/document/edit` 或 `/document/preview` 且 query 含 `dentryKey` 时,**禁止**提取 `dentryKey` 当 DOC_ID;将整段 URL 原样传给 `--node`,CLI 会自动解析(追踪参数如 `utm_source`、`chInfo` 也不必清理) +4. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) + +### ID 边界与参数映射 + +- `nodeId` 是 `doc` 命令的统一节点标识;文档、文件夹、文件都通过 `nodeId` 或完整 `alidocs` URL 传给 `--node` / `--folder`。 +- `dentryUuid` 是 `alidocs` URL `/i/nodes/{dentryUuid}` 的最后一段,在 `doc` 场景中等价于可传入 CLI 的 `nodeId`;不要把它改写成数字 ID。 +- `dentryId` 通常是纯数字,**不是** `doc` 的 `nodeId`,也不是 `doc --folder` 的目标文件夹 ID;不要把数字 `dentryId` 当作 `--node`、`--folder`,也不要当作隐藏兼容 alias(如 `--parent-id` / `--parent-folder-id`)的取值。 +- 目标父文件夹的 canonical flag 是 `--folder `,目标知识库使用 `--workspace `。CLI 可能接受 `--parent-id` / `--parent-folder-id` 作为隐藏兼容 alias,但 skill 正文和示例应统一教 `--folder`,不要把 drive/chat 链路里的数字 parentId 搬到 doc 命令。 +- 如果上下文只有数字 `dentryId`,但用户要读、改、移动、复制、重命名文档,先通过 `doc search` / `doc list` / 用户提供的 `alidocs` URL 获取 `nodeId` / `dentryUuid`,不要用数字 `dentryId` 重试为父目录参数。 + +### 处理流程 + +``` +用户输入含 alidocs.dingtalk.com URL + → 若是用户直接提供的原始 URL,先按链接规范做 probe + → 提取 DOC_ID(URL 路径最后一段) + → 结合用户意图选择命令(doc 默认 read,folder 默认 list,file 默认 download) + → 将 DOC_ID 传给 --node 参数 +``` ## 意图判断 用户说"找文档/搜文档/最近文档": - -- 全局搜索 → `dws drive search --query "<关键词>"`(聚合钉盘+文档空间) -- 空间内搜索 → `dws wiki node search --workspace --keyword "<关键词>"` -- 遍历文件夹 → `dws drive list --workspace ` 或 `dws wiki node list --workspace ` +- 搜索 → `search` +- 浏览 → `list` 用户说"看文档/读内容/文档内容": - -- 读取 → `read`(需文档 ID 或 URL) +- 读取 → `read` (需文档 ID 或 URL) - 元信息 → `info` 用户说"写文档/创建文档": - - 新建文字文档(adoc)→ `doc create` - 追加内容 → `update --mode append` -- 覆盖替换 → `update --mode overwrite` +- 整篇覆盖替换 → 先 `update --mode overwrite --dry-run` 预览,用户确认后再 `update --mode overwrite --yes` - 指定父目录时,只有明确的文档文件夹 `nodeId` / `alidocs` 文件夹 URL 才能放入 `--folder`;上一步若只返回了纯数字 `dentryId`、`spaceId` 或 drive `parent-id`,不要把它传给 doc 的 `--folder` > 严禁把「创建表格」路由到 `doc create`: -> -> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md#创建钉钉表格文档)(axls 在线电子表格) -> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md#创建-ai-表格)(able 多维表格) +> - 用户说"创建表格/新建表格/建个电子表格/在线表格" → 走 [`dws sheet create`](./sheet.md)(axls 在线电子表格) +> - 用户说"创建多维表格/新建 AI 表格/建 base/数据库表" → 走 [`dws aitable base create`](./aitable.md)(able 多维表格) + +用户说"新建脑图/白板/多维表/演示文稿": +- 用 `file create --type` 指定类型 (axls/amind/adraw/able/appt/adraw) 用户说"建文件夹/新建目录": - -- 创建 → `dws drive folder create` +- 创建 → `folder create` 或 `file create --type folder` 用户说"上传文件/传文件/上传到文档/上传到知识库": - -- 上传 → `dws drive upload`(需本地文件路径) +- 上传 → `upload`(需本地文件路径) +- 上传并转换 → `upload --convert` 用户说"下载/导出/下载到本地/导出文档/导出为Word/导出为docx/把文档导出来": - - **必须先判断目标文件类型**,再决定走 `export` 还是 `download`: - - 在线文档 (alidocs/adoc) → **`doc export`**(导出是内容层操作,仅对 adoc 有意义) - - 已有文件(PDF、图片、附件、视频等非在线文档) → **`dws drive download`** + - 在线文档(alidocs/adoc)→ **`export`**(格式转换后导出为 docx) + - 已有文件(PDF、图片、附件、视频等非在线文档)→ **`download`**(直接下载原始文件) - 判断方法: - 1. 如果用户明确说了"导出文档"、"导出为Word/docx" → 直接走 `doc export` - 2. 如果用户明确说了"下载PDF/图片/附件" → 直接走 `drive download` - 3. 不确定时,先用 `drive info --node ` 查询节点信息,根据返回的 `contentType` 字段判断: - - `contentType` 为 `ALIDOC` → 走 `doc export` - - `contentType` 为 `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `drive download` + 1. 用户明确说"导出文档"/"导出为 Word/docx" → 直接走 `export` + 2. 用户明确说"下载 PDF/图片/附件" → 直接走 `download` + 3. 不确定时,先用 `info --node ` 查询节点信息,根据 `contentType` 判断: + - `contentType` = `ALIDOC` → 走 `export` + - `contentType` = `DOCUMENT`/`IMAGE`/`VIDEO` 等 → 走 `download` > **严禁将"导出文档"直接路由到 `download`**。`download` 只能下载已有文件(原样下载),`export` 是将在线文档格式转换后导出为 docx,两者完全不同。 -用户说"复制文档/拷贝文件/复制到": - -- 复制 → `dws drive copy` +用户说"复制文档/拷贝一份/复制到": +- 复制 → `copy` (需源 --node 和目标 --folder/--workspace) 用户说"移动文档/搬到/移到/转移文件": - -- 移动 → `dws drive move` +- 移动 → `move` (需源 --node 和目标 --folder/--workspace) 用户说"重命名/rename/改名/改文档名/修改文档名称/修改文档标题/把这个文档叫做...": - -- 重命名 → `dws drive rename --node --name "新名称"` -- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update`。 +- 重命名 → `doc rename`(需文档 ID 或 URL + 新名称) +- 只要意图是修改文档在列表和链接中展示的名称,统一路由到 `dws doc rename --node --name "新名称"`;不要走 `drive`、`doc update` 或重新 `doc create` +- 只有用户明确说"正文里的标题/章节标题/段落标题/H1 标题"时,才走 `block update` 用户说"删除文档/删掉这个文件/移到回收站/丢掉这篇文档": - -- 删除节点 → `dws drive delete` +- 删除节点 → `delete`(危险操作,需 `--yes` 确认;需文档 ID 或 URL) 用户说"插入附件/上传附件到文档/往文档里加文件/加附件": - - 插入附件 → `media insert`(需文档 ID 或 URL + 本地文件路径) 用户说"插入图片/加图片/放张图/嵌入图片/往文档里插图": - - 插入图片 → `media insert`(需文档 ID 或 URL + 本地图片文件路径) - 注意:图片也是通过 `media insert` 作为附件块插入文档,不是通过 `block insert` 用户说"下载附件/获取附件/取出文档里的附件": - - 下载附件 → `media download`(需文档 ID 或 URL + 资源 ID) 用户说"编辑块/改段落/插入标题/删除块": - - 查看结构 → `block list` - 插入 → `block insert` - 修改 → `block update` - 删除 → `block delete` 用户说"给某人开权限/分享给某人/授权某文档/把这篇文档给 xxx 看": - -- 新增权限 → `dws drive permission add` -- 修改权限 → `dws drive permission update` -- 查看谁有权限 → `dws drive permission list` -- 移除权限 → `dws drive permission remove` +- 新增权限 → `permission add`(需 `--node` + `--user` + `--role`) +- 修改权限 → `permission update` +- 查看谁有权限 → `permission list` > **关键区分**: -> -> - "把**某篇文档**授权给某人" → `drive permission add`(节点级,包括「我的文档」下的文档都支持) +> - "把**某篇文档**授权给某人" → `doc permission add`(节点级,包括「我的文档」下的文档都支持) > - "把**某个知识库**整体授权给某人" → `wiki member add`(容器级,但**「我的文档」个人空间不支持**) > 补充:如果用户直接粘贴的是原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认是 `adoc` / `file` / `folder` 后,才继续按下列意图执行。 **用户直接粘贴文档 URL(无其他指令)**: - - 默认 → `read`(读取文档内容) - 如 URL 明显是文件夹 → `list`(列出文件夹内容) **用户粘贴 URL + 附加指令**: - - "帮我看看这个文档" → `read` - "这个文档的信息" → `info` - "往这个文档追加内容" → `update --mode append` -- "把这个文档标题改成 X" / "这个文档改名为 X" → `dws drive rename` +- "把这个文档标题改成 X" / "这个文档改名为 X" → `rename` - "把正文里的一级标题/章节标题改成 X" → `block update` 关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) +用户说"上传文件/传文件/上传到文档/上传到知识库": +- 上传 → `upload`(需本地文件路径) +- 上传并转换 → `upload --convert` + +用户说"下载文件/导出文件/下载到本地": +- 下载 → `download`(需文件节点 ID 或 URL) + +用户说"编辑块/改段落/插入标题/删除块": +- 查看结构 → `block list` +- 插入 → `block insert` +- 修改 → `block update` +- 删除 → `block delete` + +**用户直接粘贴文档 URL(无其他指令)**: +- 默认 → `read`(读取文档内容) +- 如 URL 明显是文件夹 → `list`(列出文件夹内容) + +**用户粘贴 URL + 附加指令**: +- "帮我看看这个文档" → `read` +- "这个文档的信息" → `info` +- "往这个文档追加内容" → `update --mode append` +- "编辑这个文档的标题" → `block update` + +关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理) + ## 核心工作流 -> 步骤性指引。"读哪些文件"组合参见上方 §场景索引;命令详细参数参见对应子文件。 - ```bash -# ── 工作流 1: 定位并阅读文档 ── -dws drive search --query "<关键词>" --format json # 1. 搜索定位 nodeId(或 wiki node search) -dws doc info --node --format json # 2. 元信息(含 contentType) -dws doc read --node --format json # 3. 读 markdown 正文 +# ── 工作流 1: 浏览并阅读文档 ── -# ── 工作流 2: 创建文档(含分片自动写入)── -dws drive folder create --name "项目资料" --format json # 1. (可选) 创建文件夹 -dws doc create --name "项目周报" --content-file /tmp/x.md \ - --folder --format json # 2. 创建 + 写入 -dws doc read --node --format json # 3. 回读校验(必须) +# 1. 浏览我的文档根目录 +dws doc list --format json -# ── 工作流 3: 局部改写(保真,首选 JSONML)── -dws doc block list --node --content-format jsonml # 1. 取 uuid -dws doc block list --node --content-format jsonml --block-id # 2. 读子树 -dws doc block update --node --block-id \ - --content-format jsonml --element '[...]' # 3. 写回(uuid 必须 == --block-id) -dws doc read --node --content-format jsonml # 4. 回读 +# 2. 浏览子文件夹 +dws doc list --folder --format json -# ── 工作流 4: 整篇 overwrite(仅在新骨架重写时)── -dws doc read --node --content-format jsonml --output /tmp/doc.json # 1. 读出当前 JSONML -# 修改 /tmp/doc.json 中的 jsonml 数组 -dws doc update --node --content-file /tmp/doc.json \ - --content-format jsonml --mode overwrite # 2. 写回(默认不做并发检查) -dws doc read --node --content-format jsonml # 3. 回读 -# 担心被并发覆盖时,可加 --revision 触发并发检查(详见 doc-update.md) +# 3. 获取文档元信息 (标题、类型、权限) +dws doc info --node --format json -# ── 工作流 5: 上传独立文件 vs 插入附件到文档正文 ── -dws drive upload --file ./report.pdf --folder # 上传作为独立文件(存储层) -dws doc media insert --node --file ./report.pdf # 上传并作为附件块插入正文(内容层) +# 4. 读取文档内容 (Markdown 格式) +dws doc read --node --format json -# ── 工作流 6: 下载 vs 导出(先 info 判 contentType)── -dws doc info --node --format json # 必须先查 contentType -dws doc export --node --output ~/downloads/ # contentType=ALIDOC 走 export(内容层) -dws drive download --node --output ~/downloads/ # contentType≠ALIDOC 走 drive download(存储层) +# ── 工作流 2: 创建文档并写入内容 ── -# ── 工作流 7: 评论 + 划词(@人 需 userId)── -dws contact user search --query "张三" --format json # 取 userId -dws doc comment create --node --content "请确认" --mention --format json -dws doc block list --node --format json # 划词需先取 blockId / paragraph.text -dws doc comment create-inline --node --block-id \ - --start 0 --end 10 --content "建议调整" --selected-text "原文" --format json +# 1. (可选) 创建文件夹 — 提取 nodeId +dws doc folder create --name "项目资料" --format json -# ── 工作流 8: 权限授予(节点级,已迁移到 drive)── -dws contact user search --query "张三" --format json # 取 userId -dws drive permission add --node --user , --role EDITOR --format json -dws drive permission list --node --format json # 校验 +# 2. 创建文档 — 提取 nodeId +dws doc create --name "项目周报" --folder --format json -# ── 工作流 9: 文件操作(已迁移到 drive)── -# 第一步:获取 nodeId -# 方式 A(优先):用户直接提供 URL / nodeId → 直接传 --node -# 方式 B:按关键字找:dws drive search --query "项目周报" --format json -# 方式 C:按文件夹遍历:dws drive list --workspace --format json -# 第二步:执行 -dws drive copy --node --folder --format json -dws drive move --node --folder --format json -dws drive rename --node --name "新名称" --format json -dws drive delete --node --yes --format json +# 3. 写入内容 (追加模式) +dws doc update --node --content "# 本周总结\n\n- 完成了 A\n- 推进了 B" --mode append --format json + +# ── 工作流 3: 一步创建带内容的文档 ── + +dws doc create --name "会议纪要" --content "# 会议纪要\n\n## 议题\n\n1. ..." --format json + +# ── 工作流 4: 上传本地文件到钉钉文档/知识库 ── + +# 1. 上传到"我的文档"根目录 +dws doc upload --file ./report.pdf + +# 2. 上传到指定文件夹 +dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder + +# 3. 上传到知识库并转换为在线文档 +dws doc upload --file ./data.xlsx --workspace --convert + +# ── 工作流 5: 下载/导出文件到本地 ── +# 必须先用 info 判断文件类型,再决定用 download 还是 export: +# - contentType 为 ALIDOC(在线文档)→ 用 export +# - contentType 为 DOCUMENT/IMAGE/VIDEO 等(已有文件)→ 用 download + +# 步骤 1: 查询文件类型 +dws doc info --node --format json +# 根据返回的 contentType 字段判断: + +# 如果是已有文件 (非 ALIDOC),用 download: +dws doc download --node --output ~/downloads/ + +# 如果是在线文档 (ALIDOC),用 export: +dws doc export --node --output ~/downloads/ + +# ── 工作流 6: 上传附件并插入文档 ── + +# media insert 自动完成三步流程: +# 1. 获取附件上传凭证 (get_doc_attachment_upload_info) +# 2. HTTP PUT 上传文件到 OSS +# 3. 插入附件块到文档 (insert_document_block) + +# 1. 基本用法: 插入本地文件到文档 +dws doc media insert --node --file ./report.pdf + +# 2. 指定附件显示名称 +dws doc media insert --node --file ./data.xlsx --name "Q1数据报表.xlsx" + +# 3. 指定 MIME 类型 (文件扩展名无法推断时) +dws doc media insert --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream + +# 4. 在指定块之前插入附件 +dws doc media insert --node --file ./image.png --ref-block --where before + +# 5. 完整流程: 创建文档 → 写入内容 → 插入附件 +dws doc create --name "项目报告" --content "# 项目报告\n\n以下为相关附件:" --format json +# 提取 nodeId 后: +dws doc media insert --node --file ./design.pdf +dws doc media insert --node --file ./timeline.xlsx --name "项目时间线.xlsx" + +# ── 工作流 7: 块级精细编辑 ── + +# 1. 查看文档块结构 — 获取 blockId +dws doc block list --node --format json + +# 2. 在文档末尾插入段落 +dws doc block insert --node --text "新增内容" + +# 3. 在指定块之前插入标题 +dws doc block insert --node --heading "新章节" --level 2 --ref-block --where before + +# 4. 更新某个块的内容 +dws doc block update --node --block-id --text "修改后的内容" + +# 5. 删除块 +dws doc block delete --node --block-id --yes + +# ── 工作流 8: 复制/移动/重命名文档 ── + +# 获取 nodeId 的三种方式(按场景选择,无需全部执行): +# 方式 A: 用户直接提供文档 URL — 直接传给 --node,无需额外查询 +# 方式 B: 搜索文档 — 从返回中提取 nodeId +dws doc search --query "项目周报" --format json +# 方式 C: 浏览文件夹 — 从返回中提取 nodeId +dws doc list --folder --format json +# 注意: 这里的 是文件夹 nodeId/dentryUuid 或文件夹 URL,不是数字 dentryId;示例统一使用 canonical --folder。 + +# 复制文档到指定文件夹(--node 支持 ID 或 URL) +dws doc copy --node --folder --format json + +# 复制到目标知识库根目录(不传 --folder 时) +dws doc copy --node --workspace --format json + +# 移动文档到指定文件夹 +dws doc move --node --folder --format json + +# 移动到目标知识库根目录(不传 --folder 时) +dws doc move --node --workspace --format json + +# 重命名文档 +dws doc rename --node --name "新名称" --format json + +# 删除文档到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) +# 正确流程:1.向用户展示"即将删除「文档名」到回收站" → 2.等用户确认 → 3.执行下面命令 +dws doc delete --node --yes --format json + +# ── 工作流 9: 文档评论管理 ── + +# 1. 查看文档的所有评论 +dws doc comment list --node --format json + +# 2. 在文档上创建评论 +dws doc comment create --node --content "这里需要补充数据来源" --format json + +# 3. 创建评论并 @ 相关人 +# 先搜索用户: dws contact user search --query "张三" --format json → 提取 userId +# 再将 userId 传入 --mention +dws doc comment create --node --content "请确认这部分内容" --mention , --format json + +# 4. 回复某条评论(commentKey 从 list 或 create 返回中获取) +dws doc comment reply --node --comment-key --content "已修改" --format json + +# 5. 用表情回复评论 +dws doc comment reply --node --comment-key --content "比心" --emoji --format json + +# 6. 创建划词评论(针对文档中某段选中文本) +# 先获取块列表: dws doc block list --node --format json → 提取 blockId 和文本内容 +# 确定选中文本在块内的起始偏移量 (start) 和结束偏移量 (end) +dws doc comment create-inline --node --block-id --start 0 --end 10 --content "这里需要修改" --format json + +# 7. 创建划词评论并附带引用原文 + @ 相关人 +dws doc comment create-inline --node --block-id --start 5 --end 20 --content "请确认这部分" --selected-text "被选中的原文内容" --mention , --format json + +# ── 工作流 10: 导出在线文档为 docx ── + +# 一条命令自动完成(提交→轮询→下载),无需手动编排 +dws doc export --node --output ./exported.docx + +# 如果导出命令超时或中断,可用 export get 手动查询任务状态: +# dws doc export get --job-id ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `drive search` / `wiki node search` | 文档 `nodeId` / URL | doc read / info / update 等所有 `--node` 入参 | -| `drive list` / `wiki node list` | `nodes[].nodeId` | doc read / info / update / block 操作的 --node | +| `list` | `nodes[].nodeId` | read / info / update / block 操作的 --node | +| `list` | folder 类型的 `nodeId` | list 的 --folder, create 的 --folder | +| `search` | 文档 `nodeId` / URL / `createTime` / `creatorUid` | read / info / update 的 --node;创建时间与创建者信息 | | `create` | `nodeId` | update / block 操作的 --node | +| `folder create` | `nodeId` | create / list / upload 的 --folder | | `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id | -| `read --content-format jsonml` | `revision` | update --content-format jsonml 的 --revision(可选,并发检查时使用) | -| `media insert` | `resourceId` | 附件已插入文档,可通过 block list 查看附件块 | -| `media download` | 附件下载链接 `downloadUrl` | 下载文档中的附件资源 | -| `block list` | attachment 块的 `resourceId` | media download 的 --resource-id | +| `upload` | `nodeId` / URL | 上传后文件的访问链接 | +| `download` | 本地文件路径 | 下载后的文件保存位置 | | `comment list` | `commentList[].commentKey` | comment reply 的 --comment-key | -| `comment create` | `commentKey` | comment reply 的 --comment-key | -| `comment create-inline` | `commentKey` | comment reply 的 --comment-key | -| `block list` | `blocks[].element.id` | comment create-inline 的 --block-id | -| `block list` | `blocks[].element.paragraph.text` | 计算 create-inline 的 --start / --end 偏移量 | -| `contact user search` | `userId` | comment create/reply/create-inline 的 --mention;drive permission 的 --user | +| `comment create` / `comment create-inline` | `commentKey` | comment reply 的 --comment-key | +| `block list` | `blockId` + 文本内容 | comment create-inline 的 --block-id 及 --start/--end 计算 | +| `contact user search` | `userId` | comment create / create-inline / reply 的 --mention | +| `file create` | `nodeId` | 后续 read / update / block 操作的 --node(仅 adoc 支持 read/update,axls/amind 等类型用各自产品的命令) | +| `copy` / `move` | 新 `nodeId`(copy)或原 nodeId(move) | 后续 read / info 等的 --node | + +## nodeId 多格式说明 + +所有 `--node` 参数同时支持以下格式,系统自动识别: +- **文档 ID**: 字母数字字符串,如 `9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` +- **文档 URL**: `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}`,如 `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` +- **文档链接(edit/preview)**: `https://alidocs.dingtalk.com/document/{edit|preview}?...&dentryKey={key}`(必须传入完整 URL,不要提取其中的 query 参数单独使用) + +以下命令效果相同: +```bash +dws doc read --node 9E05BDRVQePjzLkZt2p2vE7kV63zgkYA +dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA" +dws doc read --node "https://alidocs.dingtalk.com/document/edit?dentryKey=wo1g3x54FzVEJ5yE" +dws doc read --node "https://alidocs.dingtalk.com/document/preview?cid=74993670680&type=d&docKey=Pd6l2Z7V8ZWydl7M&dentryKey=rBGBr2r1HmwanAGW" +``` + +> **注意**:`document/edit` 和 `document/preview` 格式 URL 中的 `dentryKey` 参数值不是合法的独立 nodeId,禁止提取后单独使用,必须传入完整 URL。URL 中可能包含 `utm_source`、`chInfo` 等追踪参数,无需手动去除,直接传入完整 URL 即可。 + +`--folder` 参数同样支持文件夹 URL 或 ID。 + +## 长 Markdown 写入 + +**核心规则**:含多行、表格、`\n` 或长度 >2KB 的 Markdown **必须**通过 `--content-file` 或 `--content -`(stdin)传入,禁止直接作为 `--content` 命令行字符串——shell escape 会破坏换行和表格,且命令行长度受限。 + +`dws doc create` 和 `dws doc update` 支持两种内容来源(`--content-file` 优先于 `--content`): + +| 形式 | 说明 | +|------|------| +| `--content "..."` | 字面量(仅推荐短文本 <2KB 且无换行/表格) | +| `--content -` | 从 stdin 读取(可配合 heredoc/pipe) | +| `--content-file path` | 从文件读取(UTF-8),推荐 | + +### 短/中等长度(< 200KB)— 单步创建 + +```bash +# 1. 把内容写入 UTF-8 文本文件: +# Linux/Mac: /tmp/.md;Windows: %TEMP%\.md +# 2. 一步创建+写入: +dws doc create --name "<文档名>" --content-file [--folder ] [--workspace ] +``` + +### 超长(> 200KB 兜底)— 创建空文档 + 分片追加 + +```bash +# 1. 创建空文档拿 nodeId +dws doc create --name "<文档名>" [--folder/--workspace] # → nodeId + +# 2. 按 markdown 标题或段落边界切成 ≤200KB 的片段(不要切断表格) +# 3. 逐个追加: +dws doc update --node --content-file --mode append +``` + +### stdin 变体 + +```bash +# pipe +cat report.md | dws doc update --node --content - --mode append + +# heredoc(真实换行,含表格) +dws doc update --node --mode append --content - <<'EOF' +## 追加段落 + +| 列1 | 列2 | +|---|---| +| a | b | +EOF +``` + +## 注意事项 + +- `export` 是一体化命令,一条命令自动完成提交→轮询→下载,**无需手动编排轮询**。CLI 内部使用渐进式退避轮询(最多约 5 分钟) +- `export` 超时或中断后,CLI 会输出 `jobId`,可用 `dws doc export get --job-id ` 手动查询任务状态 +- `export` 当前仅支持钉钉在线文档 (alidocs) 导出为 `docx`;当前开源 CLI 未暴露在线表格导出命令,在线表格数据读取走 `dws sheet range read --node ` +- `update --mode overwrite` 会**清空原内容后重写**,⚠️ 谨慎使用;必须先 `--dry-run` 预览并在用户确认后加 `--yes`;默认 `--mode append`(追加)更安全 +- `read` 返回 Markdown 格式的文档内容,仅限有"下载"权限的文档 +- `read` 返回的内容中,文档里的附件会以 OSS 临时下载链接形式给出(如 `https://alidocs2.oss-cn-zhangjiakou.aliyuncs.com/res/.../att/.ext?Expires=...`),该链接会过期。链接过期后,可从 URL 路径中提取 ``(即 `/att/` 后、扩展名前的 UUID 部分),然后使用 `media download --node --resource-id ` 重新获取下载链接 +- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 +- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示/文件夹,用 `file create --type` +- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append` +- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON +- `--content` 参数中的换行必须使用**真实换行符**(即实际的换行字符,Unicode `U+000A`),而不是字面量字符串 `\n`(反斜杠加字母 n)。在通过程序或大模型构造此参数时,请确保字符串在发送前已正确反转义。如果传入的是两个字符的字面量 `\n`,所有内容将渲染在同一行,导致标题、段落和表格格式全部错乱。**含多行/表格/长文本时优先用 `--content-file path.md` 或 `--content -`(stdin),不经过 shell escape,换行和表格都保持原样**(详见下方「长 Markdown 写入」)。 +- 块类型包括: paragraph, heading, blockquote, callout, columns, orderedList, unorderedList, table, sheet, attachment, slot +- 关键区分: doc(文档内容级操作) vs wiki(知识库空间级管理) vs aitable(数据表格操作) vs drive(钉盘文件管理) +- wiki 是知识库容器,doc 是知识库中的文档内容;需要 `workspaceId` 时,先用 `dws wiki space list/search` 获取,再传给 doc 的 `--workspace` 参数 +- `doc upload vs drive upload`:用户提到"知识库/文档空间/workspace" → `doc upload`;提到"钉盘/网盘/我的文件" → `drive upload`;未明确目标时默认 `drive upload` +- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档 +- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作 +- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录 +- `media insert` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 插入附件块到文档),无需手动分步操作 +- `media insert` 的 `--mime-type` 可选,不指定时根据文件扩展名自动推断;支持常见文件类型 (PDF、Office、图片、视频、压缩包等) +- `media insert` 与 `upload` 的区别:`upload` 将文件上传到文档空间/知识库作为独立文件;`media insert` 将文件作为附件块插入到文档正文中 +- `media download` 用于获取文档正文中附件的临时下载链接,`--resource-id` 可通过 `block list` 返回的 attachment 块获取 +- `copy` 需要对源节点有"阅读"权限,且对目标文件夹有"编辑"权限 +- `move` 需要对源节点有"管理"权限,且对目标文件夹有"编辑"权限;移动后原位置的文档将不再存在 +- `rename` 需要对文档有"编辑"权限 +- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档" +- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始) + +## 自动化脚本 + +| 脚本 | 场景 | 用法 | +|------|------|------| +| [doc_create_and_write.py](../../scripts/doc_create_and_write.py) | 创建文档并写入 Markdown 内容 | `python doc_create_and_write.py --name "周报" --content "# 本周总结"` | ## 相关产品 -- [wiki](./wiki.md) — 知识库空间级管理(创建/查询/列出/搜索知识库),doc 中的文档存储在 wiki 知识库中 - [aitable](./aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档 - [drive](./drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑 - [report](./report.md) — 钉钉日志系统(日报/周报模版),不是在线文档 diff --git a/skills/multi/dingtalk-doc/references/url-patterns.md b/skills/multi/dingtalk-doc/references/url-patterns.md index aa679e5f..4b206ad9 100644 --- a/skills/multi/dingtalk-doc/references/url-patterns.md +++ b/skills/multi/dingtalk-doc/references/url-patterns.md @@ -87,9 +87,9 @@ Step 3 → 按下方路由规则映射到对应产品 | 条件 | 路由到产品 | 后续操作 | |------|-----------|---------| -| `contentType=ALIDOC`, `extension=adoc` | `doc` | 按 [doc.md](./doc.md) 操作 | -| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](../../dingtalk-sheet/references/sheet.md) 操作(仅 `axls` 在线电子表格) | -| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](../../dingtalk-aitable/references/aitable.md) 操作 | +| `contentType=ALIDOC`, `extension=adoc` | `doc` | 按 [doc.md](./products/doc.md) 操作 | +| `contentType=ALIDOC`, `extension=axls` | `sheet` | 按 [sheet.md](./products/sheet.md) 操作(仅 `axls` 在线电子表格) | +| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,按 [aitable.md](./products/aitable.md) 操作 | | `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `doc` | 必须用 `dws doc download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) | | `contentType≠ALIDOC`, `nodeType=file` | `doc` | 调用 `dws doc download` 下载,返回文件下载链接 | | `nodeType=folder` | `doc` | 调用 `dws doc list --folder ` 列出指定文件夹直接子节点列表 | @@ -98,7 +98,7 @@ Step 3 → 按下方路由规则映射到对应产品 > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(envelope schema 里有 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx +> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx ### 示例 @@ -110,10 +110,10 @@ dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json # 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output ./xlsx456.xlsx +dws doc download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" # 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载 -dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" --output ./def456.bin +dws doc download --node "https://alidocs.dingtalk.com/i/nodes/def456" # 返回 nodeType=folder → 文件夹,列出子节点 dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format json @@ -137,7 +137,7 @@ dws doc list --folder "https://alidocs.dingtalk.com/i/nodes/ghi789" --format jso |-------------------------|------|------|------|------|------|------| | **adoc**(在线文档) | `doc read` | `doc update` / `doc block update` | ⚠️ `doc delete` | ⚠️ `doc export` (→ docx) | ⚠️ `doc permission *` | ⚠️ `doc media download/insert` | | **axls**(在线电子表格) | `sheet range read` / `sheet list` | `sheet range write` / `sheet append` | ⚠️ `doc delete`(节点删除) | `sheet submit_export_job` + `sheet query_export_job`(待吴淼 W-01 收敛为单命令 `sheet export`) | ⚠️ `doc permission *`(节点级,跨产品) | 不适用 | -| **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --base-id --scope all --output ./x.xlsx` | ⚠️ `doc permission *`(节点级) | `aitable attachment upload-file` | +| **able**(在线多维表) | `aitable base get` / `aitable record query` | `aitable record create/update` | ⚠️ `doc delete`(节点删除)或 `aitable base delete --yes` | `aitable export data --output ./x.xlsx` | ⚠️ `doc permission *`(节点级) | `aitable attachment upload-file` | | **xlsx / xls / xlsm / csv**(本地表格文件) | `doc download` → 本地用 xlsx skill 解析 | 不支持服务端写(先下载改本地再上传) | ⚠️ `doc delete`(节点删除) | 不需要(本身就是 xlsx) | ⚠️ `doc permission *` | 不适用 | | **普通文件** (nodeType=file) | `doc download` | 不支持服务端写 | ⚠️ `doc delete` | 不需要 | ⚠️ `doc permission *` | 不适用 | | **文件夹** (nodeType=folder) | `doc list --folder ` | `doc create --folder ...` | ⚠️ `doc delete` | 不适用 | ⚠️ `doc permission *` | 不适用 | @@ -158,6 +158,6 @@ Agent 流程: | 用户说 | 路由 | 不要混淆 | |--------|------|---------| | "把这个文档/表格/多维表分享给张三" | **节点级**:`doc permission add --node --user --role EDITOR` | 不是 `wiki member add` | -| "把张三加到这个知识库" | **容器级**:`wiki member add --workspace --users --role ` | 不是 `doc permission add` | +| "把张三加到这个知识库" | **容器级**:`wiki member add --workspace --user --role ` | 不是 `doc permission add` | > 区分依据:**doc permission 作用于单个 node(document / file / folder);wiki member 作用于整个 workspace 容器**。同一用户在 workspace 是 EDITOR、在某个 node 上仍可被单独提升为 MANAGER(节点级覆盖容器级)。 diff --git a/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py index fbf52f8d..367e4b93 100644 --- a/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py +++ b/skills/multi/dingtalk-doc/scripts/doc_create_and_write.py @@ -96,7 +96,7 @@ def main(): if not content: print('错误:需要 --content 或 --content-file') sys.exit(1) - chunk_size = 10000 + chunk_size = 30000 create_args = ['doc', 'create', '--name', args.name, '--format', 'json'] if args.folder: diff --git a/skills/multi/dingtalk-drive/SKILL.md b/skills/multi/dingtalk-drive/SKILL.md index 4fdc3145..e1fa4cd9 100644 --- a/skills/multi/dingtalk-drive/SKILL.md +++ b/skills/multi/dingtalk-drive/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[drive.md](references/drive.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 6304f2c1..b13f4d59 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -1,5 +1,7 @@ # 钉盘 (drive) 命令参考 +钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。 + ## 查询命令帮助 当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 @@ -10,7 +12,6 @@ dws drive --help # 查看具体命令的完整参数说明 dws drive list --help -dws drive search --help dws drive upload --help dws drive download --help ``` @@ -22,22 +23,38 @@ dws drive download --help ## 命令总览 -### 获取文件/文件夹列表 +### 列出钉盘空间 +``` +Usage: + dws drive list-spaces [flags] +Example: + dws drive list-spaces --format json + dws drive list-spaces --space-type orgSpace --max 50 --format json +Flags: + --max int 每页数量 (默认 20) + --next-token string 分页游标 (从上次结果的 nextToken 获取) + --space-type string 空间类型过滤 +``` +> 适用场景:复制/移动文件到「我的文件」或团队空间根目录时,先取 `rootFolderId`;或者枚举用户可访问的团队空间。 +> 注意:由当前二进制静态注册;如果当前版本未暴露或后端工具未注册,调用前可用 `dws drive list-spaces --help` / `--dry-run` 验证。 + +### 列出钉盘目录 ``` Usage: dws drive list [flags] Example: - dws drive list --limit 20 - dws drive list --limit 20 --folder --order-by name --order asc + dws drive list + dws drive list --parent-id + dws drive list --parent-id --max 20 --order-by modifiedTime --order desc Flags: - --limit int 每页返回数量,默认 20,最大 50 (可选) - --cursor string 分页游标,首次不传 (可选) - --order string 排序方向: asc|desc,默认 desc (可选) - --order-by string 排序字段: createTime|modifyTime|name (可选) - --folder string 父节点 ID (dentryUuid),不传则列出空间根目录 (可选) - --space-id string 空间 ID,不传则使用「我的文件」对应 spaceId (可选) - --thumbnail 是否返回缩略图信息 (可选) + --parent-id string 父目录 ID (不传则列根目录) + --space-id string 钉盘空间 ID (一般无需指定) + --max int 每页数量 (默认 20) + --next-token string 分页游标 (从上次结果的 nextToken 获取) + --order-by string 排序字段: name / createdTime / modifiedTime + --order string 排序方向: asc / desc + --thumbnail 是否返回缩略图链接 ``` ### 获取钉盘空间列表 @@ -48,11 +65,11 @@ Usage: Example: dws drive list-spaces dws drive list-spaces --space-type mySpace - dws drive list-spaces --space-type orgSpace --limit 20 --cursor + dws drive list-spaces --space-type orgSpace --max 20 --next-token Flags: --space-type string 空间类型: orgSpace=企业空间(默认), mySpace=我的文件 (可选) - --limit int 每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效 - --cursor string 分页游标,仅企业空间支持分页 (可选) + --max int 每页返回数量 (默认 20),仅 spaceType 为 orgSpace 时有效 + --next-token string 分页游标,仅企业空间支持分页 (可选) ``` spaceType 筛选规则: @@ -66,53 +83,28 @@ spaceType 筛选规则: - `spaceType` — 空间类型(如 `orgSpace`) - `nextToken` — 若不为空,表示还有更多空间可查询(仅企业空间) -### 搜索钉盘文件/文件夹/空间 - -按关键词在钉盘中搜索文件、文件夹或团队空间。不同于 `list`(需要明确的 spaceId/parentId 逐层遍历),`search` 用于不知道具体位置、只记得名称/关键词的场景。 - -``` -Usage: - dws drive search [flags] -Example: - dws drive search --query "季度汇报" - dws drive search --query "合同" --target file --extensions pdf,docx - dws drive search --query "项目" --target space - dws drive search --query "方案" --created-from 1700000000000 --created-to 1710000000000 - dws drive search --query "周报" --creator-uids 012345 - dws drive search --query "报告" --limit 30 --cursor -Flags: - --query string 搜索关键词 (必填) - --target string 搜索目标: all(默认) | file | space (可选) - --file-types strings 按文件内容类型过滤,逗号分隔: alidoc,document,image,video,audio,archive (仅 target=file/all 生效) - --extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,adoc) - --creator-uids strings 按创建者用户 ID 过滤,逗号分隔 - --created-from int 创建时间起始 (毫秒时间戳,含) - --created-to int 创建时间截止 (毫秒时间戳,含) - --modified-from int 修改时间起始 (毫秒时间戳,含) - --modified-to int 修改时间截止 (毫秒时间戳,含) - --limit int 每页返回数量(默认 10,最大 30) - --cursor string 分页游标,从上次返回的 nextCursor 获取 (可选) -``` - -搜索目标 (`--target`) 选择规则: -- `all`(默认):同时搜文件与空间,返回混合结果 — 不确定目标是文件还是空间时使用 -- `file`:只搜文件 / 文件夹,支持 `--file-types` / `--extensions` 过滤 — 明确是找文件时使用 -- `space`:只搜团队空间 — 明确知道空间名、需快速定位空间 spaceId/rootFolderId 时使用 - -返回结果中 `type` 字段区分:`SPACE`(空间)、`FILE`(普通文件)、`FOLDER`(文件夹)、`ALIDOC`(钉钉在线文档)。 - -> **提示**:结果按相关性排序,首页未命中时优先调整关键词 / 补充 `--file-types`/`--extensions` 缩小范围 / 加上时间范围,而非反复翻页。 - -### 获取文件元数据信息 - +### 获取文件元信息 ``` Usage: dws drive info [flags] Example: - dws drive info --node + dws drive info --file-id Flags: - --node string 节点 ID (dentryUuid) (必填) - --space-id string 节点所属空间 ID (可选) + --file-id string 文件或文件夹 ID (必填) + --space-id string 钉盘空间 ID (一般无需指定) +``` + +### 创建文件夹 +``` +Usage: + dws drive mkdir [flags] +Example: + dws drive mkdir --name "项目资料" + dws drive mkdir --name "子目录" --parent-id +Flags: + --name string 文件夹名称 (必填) + --parent-id string 父目录 ID (不传则建在根目录) + --space-id string 钉盘空间 ID (一般无需指定) ``` ### 文件内容获取路由规则 @@ -123,62 +115,9 @@ Flags: | extension | 文件类型 | 操作 | 命令 | |-----------|---------|------|------| | adoc | 在线文档 | 在线获取 Markdown 内容 | `dws doc read --node ` | -| axls | 在线表格 | 在线读取表格数据 | `dws sheet get-all-sheets` → `dws sheet get-range` | -| able | 多维表格 | 在线查询记录 | `dws aitable get-tables` → `dws aitable query-records` | -| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download` | - -### 下载文件到本地 - -下载流程一步到位:获取下载 URL → HTTP GET 下载文件二进制内容到本地。 - -``` -Usage: - dws drive download [flags] -Example: - dws drive download --node --output ./report.pdf - dws drive download --node --output ~/downloads/ -Flags: - --node string 文件 ID (dentryUuid) (必填) - --output string 本地保存路径 (必填),可以是文件路径或目录;如果指定目录,文件名从下载 URL 中自动推断 - --space-id string 文件所属空间 ID (可选) -``` - -> **注意**:`--output` 是必填参数,不传会报错。 - -### 创建文件夹 - -``` -Usage: - dws drive mkdir [flags] -Example: - dws drive mkdir --name "项目资料" - dws drive mkdir --name "子目录" --folder -Flags: - --name string 文件夹名称,最长 50 字符 (必填) - --folder string 父节点 ID (dentryUuid),不传则在空间根目录下创建 (可选) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) -``` - -### 上传本地文件到钉盘 - -> **注意:** 上传文件必须使用 `dws drive upload` 命令,禁止使用 `upload-info` + `curl` + `commit` 三步流程。 - -``` -Usage: - dws drive upload [flags] -Example: - dws drive upload --file ./report.pdf - dws drive upload --file ./slides.pptx --file-name "Q1汇报.pptx" - dws drive upload --file ./data.xlsx --folder -Flags: - --file string 本地文件路径 (必填) - --file-name string 文件显示名称 (默认使用文件名) - --space-id string 目标空间 ID,不传则使用「我的文件」 (可选) - --mime-type string 文件 MIME 类型,不传则自动推断 (可选) - --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 (可选) -``` - -`upload` 命令内部自动完成三步流程(获取凭证 → OSS PUT → 提交入库),无需手动分步操作。 +| axls | 在线表格 | 在线读取表格数据 | `dws sheet range read` | +| able | 多维表格 | 在线查询记录 | `dws aitable base get` → `dws aitable record query` | +| 其他(pdf/docx/txt/png 等) | 普通文件 | **不支持在线分析**,需用户主动下载后本地查看 | `dws drive download --file-id --output ` | ### 删除文件/文件夹到回收站 @@ -188,183 +127,230 @@ Flags: Usage: dws drive delete [flags] Example: - dws drive delete --node --format json # 查询 fileId: dws drive list + dws drive delete --file-id --format json # 查询 fileId: dws drive list Flags: - --node string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) + --file-id string 文件/文件夹 ID (dentryUuid),即 drive list 返回的 fileId (必填) ``` -注意:`--node` 使用的是 `drive list` 返回结果中的 `fileId` 字段(即 `dentryUuid`),**不是** `dentryId` 字段。 +注意:`--file-id` 使用的是 `drive list` 返回结果中的 `fileId` 字段(即 `dentryUuid`),**不是** `dentryId` 字段。 + +### 下载钉盘文件到本地 +``` +Usage: + dws drive download [flags] +Example: + dws drive download --file-id --output ./report.pdf +Flags: + --file-id string 文件 ID (必填) + --output string 本地保存路径 (文件路径或目录,必填) + --space-id string 钉盘空间 ID (一般无需指定) +``` + +### 获取上传凭证 (上传第一步) +``` +Usage: + dws drive upload-info [flags] +Example: + dws drive upload-info --file-name "report.pdf" --file-size 102400 + dws drive upload-info --file-name "slides.pptx" --file-size 512000 --parent-id +Flags: + --file-name string 文件名含后缀 (必填) + --file-size int 文件大小,单位字节 (必填) + --mime-type string MIME 类型 (可选,服务端会自动推断) + --parent-id string 父目录 ID (不传则上传到根目录) + --space-id string 钉盘空间 ID (一般无需指定) +``` + +### 提交上传 (上传第三步) +``` +Usage: + dws drive commit [flags] +Example: + dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id +Flags: + --file-name string 文件名含后缀 (必填,须与 upload-info 一致) + --file-size int 文件大小,单位字节 (必填,须与 upload-info 一致) + --upload-id string upload-info 返回的 uploadId (必填) + --parent-id string 父目录 ID (必填时须与 upload-info 一致) + --space-id string 钉盘空间 ID (一般无需指定) + --conflict-handler string 同名冲突策略: AUTO_RENAME / OVERWRITE / RETURN_DENTRY_IF_EXIST (默认 AUTO_RENAME) +``` + +### 一键上传 (三步合成) +``` +Usage: + dws drive upload [flags] +Example: + dws drive upload --file ./report.pdf + dws drive upload --file ./slides.pptx --file-name "Q1汇报.pptx" + dws drive upload --file ./data.xlsx --folder +Flags: + --file string 本地文件路径 (必填) + --file-name string 文件显示名称 (默认使用本地文件名) + --folder string 父节点 ID (dentryUuid),不传则上传到空间根目录 + --space-id string 目标空间 ID,不传则使用「我的文件」 + --mime-type string 文件 MIME 类型,不传则自动推断 +``` + +> 客户端胶水命令:内部自动完成 `upload-info` → HTTP PUT 到 OSS → `commit_upload` 三步。 +> 适合大多数本地文件上传场景,无需 Agent 自己发 HTTP PUT。 +> 注意:`--folder` 接受 `dentryUuid`(UUID 格式),不要传纯数字 `dentryId`。 + +### 删除文件/文件夹到回收站 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws drive delete [flags] +Example: + dws drive delete --file-id --yes --format json +Flags: + --file-id string 文件或文件夹 nodeId(dentryUuid 格式,必填) + +Global Flags: + --yes 跳过二次确认 (危险操作,建议先与用户确认) +``` + +> 由当前二进制静态注册(路由到 doc 服务的 `delete_document` tool);如果当前版本未暴露,调用前可用 `dws drive delete --help` 验证。 +> 删除是软删除(进回收站),但仍需用户明确确认;不要在自动化脚本里默认带 `--yes`。 ## 意图判断 -用户说"我的文件/钉盘/网盘/云盘" → `list` -用户说"钉盘空间/团队文件/有哪些空间/空间列表/团队文件列表" → `list-spaces` -用户说"搜索钉盘文件/钉盘里找个文件/查找某个钉盘文件/钉盘中搜索" → `search` -用户说"文件详情/文件信息" → `info` -用户说"下载文件" → `download` -用户说"新建文件夹/创建目录" → `mkdir`(钉盘空间)/ `folder create`(文档空间) -用户说"上传文件/传文件到钉盘" → `upload`(必须使用此命令,自动完成三步流程) -用户说"复制文件/移动文件/搬到/移到" → `copy` / `move` -用户说"重命名/改名" → `rename` -用户说"删除文件/删除文件夹/移到回收站" → `delete`(危险操作,需确认) -用户说"给文档授权/分享权限" → `permission add` +用户说"钉盘有什么空间/团队文件/列空间" → `list-spaces` +用户说"钉盘有什么文件/列钉盘/看钉盘目录" → `list` +用户说"钉盘文件详情/文件信息" → `info` (需 fileId) +用户说"新建钉盘目录/钉盘里建文件夹" → `mkdir` +用户说"下载钉盘文件/把这个文件拿下来" → `download` 指定 `--output` 保存到本地 +用户说"上传文件到钉盘/把本地文件传钉盘": +- **首选 `upload`**:一条命令自动完成三步(适合本地文件直接上传) +- 也可手动三步:`upload-info` → HTTP PUT 到预签名 URL → `commit`(适合自定义流式上传,或不想在客户端做 HTTP PUT 的场景) +用户说"删除钉盘文件/移到回收站" → `delete`(不可逆,必须确认) -关键区分: drive(文件管理) vs doc(文档内容读写) vs wiki(空间管理) +关键区分: +- drive(钉盘云存储,面向文件二进制) vs doc(在线文档/知识库,面向富文本内容) +- 把图片/文件发到群里一般走 drive 上传拿链接 → chat 发送 Markdown 链接 (见 [chat.md](./chat.md) 的 `drive → chat` 工作流) -**drive search vs wiki node search**: 用户提到"钉盘/网盘/我的文件里搜" → `drive search`;提到"知识库/文档空间/workspace 里搜" → `wiki node search`;未明确目标时优先问明。 +**drive upload vs doc upload**: 用户提到"钉盘/网盘/我的文件"→ drive 三步式;提到"知识库/文档空间/workspace"→ `doc upload`;未明确目标时默认走 drive -**drive upload**: 文件上传统一走 `drive upload`。上传到知识库/文档空间时加 `--workspace` 参数。 - -**drive permission vs wiki member**: "给某篇文档/文件授权" → `drive permission add`(节点级);"给某个知识库整体加成员" → `wiki member add`(空间级) - -**创建在线文档/表格/脑图**: drive 不支持创建文件,需走 `wiki node create --type `(创建空节点)或 `doc create`(创建并写入内容)。 - -**导出文档/导出为Word**: 导出是内容层操作,走 `doc export`,不属于 drive。 +**钉盘文件复制/移动**: drive 本身没有 copy/move 命令,需使用 `dws doc copy` / `dws doc move` 实现(详见下方「复制/移动钉盘文件」工作流) ## 核心工作流 ```bash -# 1. 浏览「我的文件」根目录 -dws drive list --limit 20 --format json +# ── 工作流 1: 浏览钉盘 ── -# 2. 进入子目录 — 提取 dentryUuid 作为 folder -dws drive list --limit 20 --folder --format json +# 1. 看根目录 +dws drive list --format json -# 3. 查看文件元数据 -dws drive info --node --format json +# 2. 进入子目录 (parentId 取自上一步的 dentryUuid) +dws drive list --parent-id --format json -# 4. 下载文件到本地 -dws drive download --node --output /tmp/ --format json +# 3. 看单个文件的元信息 +dws drive info --file-id --format json -# 5. 创建文件夹 -dws drive mkdir --name "项目资料" --format json +# ── 工作流 2: 上传本地文件到钉盘 (三步不能省) ── -# 6. 上传文件(必须使用 upload 命令,禁止手动分步操作) -dws drive upload --file ./报告.pdf --format json -dws drive upload --file ./报告.pdf --folder --format json +# Step 1: 拿上传凭证 +dws drive upload-info --file-name "report.pdf" --file-size 102400 --parent-id --format json +# → 返回: resourceUrl (预签名 URL), headers, uploadId -# 7. 删除文件/文件夹到回收站(危险操作:必须先向用户确认,用户同意后才加 --yes 执行) -# 正确流程:1.向用户展示"即将删除「文件名」到回收站" → 2.等用户确认 → 3.执行下面命令 -dws drive delete --node --yes --format json +# Step 2: Agent 自己发 HTTP PUT 把文件二进制推到 resourceUrl +# 请求头必须携带返回的 headers 全部键值对;期望 HTTP 200 +curl -X PUT -T ./report.pdf -H ": " "" + +# Step 3: 提交入库 +dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id \ + --parent-id --format json +# → 返回: dentryUuid (= 后续 download/info 用的 fileId) + +# ── 工作流 3: 下载钉盘文件到本地 ── +dws drive download --file-id --output ./report.pdf --format json + +# ── 工作流 4: 建目录后批量上传 ── + +# 1. 建目录 → 拿 folderId +dws drive mkdir --name "2026 Q1 归档" --format json + +# 2. 再走 "工作流 2" 把每个文件上传到该目录 ``` -## 文档空间管理命令 +## 复制/移动钉盘文件 -> 以下命令操作的是**文档空间**(知识库 / 我的文档),底层路由到 doc MCP server。 -> 与钉盘命令(list / mkdir / upload 等)的区别:钉盘命令操作钉盘空间(spaceId 纯数字),文档空间命令操作知识库/我的文档(workspaceId 加密 string)。 +钉盘本身没有 copy/move 命令,需使用 `dws doc copy` / `dws doc move` 实现跨场景搬运。 -### 复制/移动/重命名文件 +> **关键陷阱:字段选择** +> `drive list` 返回中有 `dentryId`(数字格式,如 `220335325118`)和 `fileId`(UUID 格式,如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)两个字段。 +> **必须使用 `fileId`(UUID 格式)**作为 `--node` 和 `--folder` 的参数值。 +> **禁止使用 `dentryId`(数字格式)**,传入数字格式会导致命令失败。 -``` -Usage: - dws drive copy --node [--folder ] [--workspace ] - dws drive move --node [--folder ] [--workspace ] - dws drive rename --node --name "新名称" -Flags: - --node string 文档/文件 ID 或 URL (必填) - --folder string 目标文件夹 nodeId - --workspace string 目标知识库 ID - --name string 新名称 (仅 rename 必填) -``` - -> **字段选择**:`drive list` 返回中有 `dentryId`(数字格式)和 `fileId`(UUID 格式),**必须使用 `fileId`(UUID 格式)**作为 `--node` 和 `--folder` 参数值。 - -### 创建文件夹(文档空间) - -``` -Usage: - dws drive folder create --name "文件夹名" -Flags: - --name string 名称 (必填) - --folder string 父文件夹 nodeId - --workspace string 目标知识库 ID -``` - -### 权限管理(文档节点级) - -> 仅适用于文档空间节点,不适用于钉盘文件。 - -``` -Usage: - dws drive permission add --node --users uid1,uid2 --role READER - dws drive permission update --node --users uid1 --role EDITOR - dws drive permission list --node - dws drive permission remove --node --users uid1 -Flags: - --node string 目标节点 ID 或 URL (必填) - --users string 用户 userId 列表,逗号分隔 - --role string 角色: MANAGER / EDITOR / DOWNLOADER / READER - --limit int 返回成员数上限 (仅 list,默认 30,最大 200) - --filter-role string 按角色过滤 (仅 list) -``` - -> **注意**:`drive export` 不存在。导出仅对自研文档 (adoc) 有意义,属于内容层操作,应使用 `doc export`。 +> **能力限制**:钉盘场域下,仅支持将文件复制/移动到文件夹下,**不支持文档下嵌套文档**。 ### 目标位置参数规则 | 目标位置 | 参数传递方式 | 前置步骤 | |---------|-----------|---------| | 未指定目标(默认) | `--folder ` | 先 `dws drive list-spaces --space-type mySpace` 获取「我的文件」的 `rootFolderId` | -| 知识库空间根目录 | `--workspace ` | 无需额外步骤 | +| 知识库空间根目录 | `--workspace ` | 无需额外步骤,直接传入 workspaceId | | 钉盘 space 根目录 | `--folder ` | 先 `dws drive list-spaces` 获取目标 space 的 `rootFolderId` | -| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览 | +| 钉盘 space 下的子文件夹 | `--folder ` | 先 `dws drive list --space-id ` 逐层浏览,获取目标文件夹的 `fileId`(dentryUuid 格式) | ### 工作流示例 ```bash -# ── 场景 默认: 复制/移动到「我的文件」根目录 ── +# ── 场景默认: 用户未指定目标位置 → 复制/移动到「我的文件」根目录 ── +dws drive list --space-id --format json # 获取源文件 dentryUuid +dws drive list-spaces --space-type mySpace --format json # 获取「我的文件」rootFolderId +dws doc copy --node <源文件dentryUuid> --folder <我的文件rootFolderId> --format json + +# ── 场景 A: 复制钉盘文件到知识库空间根目录 ── dws drive list --space-id --format json -dws drive list-spaces --space-type mySpace --format json -dws drive copy --node <源文件dentryUuid> --folder <我的文件rootFolderId> --format json +dws doc copy --node <源文件dentryUuid> --workspace --format json -# ── 场景 A: 复制到知识库空间根目录 ── -dws drive copy --node <源文件dentryUuid> --workspace --format json - -# ── 场景 B: 移动到另一个钉盘 space 根目录 ── +# ── 场景 B: 移动钉盘文件到另一个钉盘 space 根目录 ── +dws drive list --space-id --format json dws drive list-spaces --format json -dws drive move --node <源文件dentryUuid> --folder <目标space的rootFolderId> --format json +dws doc move --node <源文件dentryUuid> --folder <目标space的rootFolderId> --format json +# 注意:移动到其他 space 时只传 --folder,不传 --workspace -# ── 场景 C: 复制到钉盘子文件夹 ── +# ── 场景 C: 复制钉盘文件到钉盘 space 下的子文件夹 ── +dws drive list --space-id --format json dws drive list --space-id --format json -dws drive copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --format json +dws drive list --space-id --parent-id <父文件夹dentryUuid> --format json # 目标深层级时逐层浏览 +dws doc copy --node <源文件dentryUuid> --folder <目标文件夹fileId> --format json ``` ## 上下文传递表 - -| 操作 | 从返回中提取 | 用于 | -| ------------- | ---------------------------- | -------------------------------------------------------- | -| `list` | **`fileId`**(UUID 格式,注意:不是 `dentryId`) | info / download / mkdir / delete / list 的 --node 或 --folder;`drive copy/move` 的 --node 或 --folder | -| `list` | `spaceId` | info / download / mkdir / commit 的 --space-id | -| `list-spaces` | `rootFolderId` | `drive copy/move` 的 --folder(复制/移动到钉盘 space 根目录时) | -| `list-spaces` | `spaceId` | list / info / download / mkdir / upload 的 --space-id | -| `search` | **`fileId`**(文件/文件夹结果) | info / download / delete 的 --node;list 的 --folder | -| `search` | `spaceId` / `rootFolderId`(空间结果) | list 的 --space-id;`drive copy/move` 的 --folder | -| `search` | `nextCursor` | search 的 --cursor(翻页) | -| `mkdir` | `fileId`(UUID 格式) | list 的 --folder | - -> **重要**:`drive list` 返回结果中同时包含 `dentryId` 和 `fileId` 两个字段。所有需要传 `--node` 的命令(info / download / delete)必须使用 `fileId`(即 dentryUuid),**不要使用** `dentryId`。 - +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `list` | `dentryUuid` (folder 类) | 下次 list 的 --parent-id、upload-info / commit 的 --parent-id、mkdir 的 --parent-id | +| `list` | `dentryUuid` (file 类) | info / download 的 --file-id | +| `list` | `nextToken` | 下次 list 的 --next-token | +| `mkdir` | `dentryUuid` | 作为父目录传给 upload-info / commit 的 --parent-id | +| `upload-info` | `resourceUrl` + `headers` | Agent 自行执行 HTTP PUT 上传二进制 | +| `upload-info` | `uploadId` | commit 的 --upload-id | +| `commit` | `dentryUuid` | download / info / chat message 发送图片链接的 --file-id | +| `download` | `--output` 指定的路径 | 本地文件落地位置 | ## 注意事项 -- 不传 `--space-id` 时默认使用「我的文件」空间 -- 不传 `--folder` 时默认操作空间根目录 -- `--folder` 只能使用父文件夹的 `dentryUuid`。不要把 `drive info` 返回的数字型 `dentryId` 当作父目录;`dentryId` 只用于 `chat message send --dentry-id` -- **`--limit` 最大值为 50**,禁止传入超过 50 的值(如 `--limit 100`)。用户要求超过 50 条时,应使用 `--limit 50` 配合 `--cursor` 分页查询,不要直接传大于 50 的值 -- `--order-by` 支持: `createTime`、`modifyTime`、`name` -- **上传文件必须使用 `dws drive upload` 命令**,禁止使用 `upload-info` + `curl` + `commit` 三步手动流程 -- `--file-name` 必须包含扩展名(如 `report.pdf`) +- 上传两条路径:**首选** `upload` 一条命令搞定(内部走三步);如果需要自己控制 HTTP PUT,可走 `upload-info` → 客户端 HTTP PUT → `commit` 三步 +- 用三步流程时,Step 2 的 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;只有 PUT 返回 200 才能调 `commit` +- 上传凭证 (`uploadId`) 有过期时间,拿到后尽快 commit;过期需重新调 `upload-info` +- `download` 需要指定 `--output`,CLI 会把文件保存到本地路径或目录 +- `--parent-id` 在 upload-info / commit 中要保持一致,否则 commit 会报位置不匹配 +- `--conflict-handler` 默认 `AUTO_RENAME` (自动重命名);`OVERWRITE` 覆盖同名文件前必须和用户确认 +- `--space-id` 绝大多数场景不需要传;默认会用用户主钉盘空间 +- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾 ## 自动化脚本 - -| 脚本 | 场景 | 用法 | -| ------------------------------------------------------ | ----------- | ------------------------------------- | +| 脚本 | 场景 | 用法 | +|------|------|------| | [drive_tree_list.py](../../scripts/drive_tree_list.py) | 递归列出钉盘目录树结构 | `python drive_tree_list.py --depth 2` | - ## 相关产品 -- [doc](./doc.md) — 文档内容读写/知识库空间,不是文件存储 -- [chat](./chat.md) — 上传文件到 drive 后可通过 Markdown 语法发送图片/文件消息 +- [doc](./doc.md) — 钉钉在线文档 / 知识库(文字、表格、脑图等富文本节点),和 drive 的裸文件存储不同 +- [chat](./chat.md) — 结合 drive 发送图片 / 附件消息到群聊(Markdown 链接语法) diff --git a/skills/multi/dingtalk-live/SKILL.md b/skills/multi/dingtalk-live/SKILL.md index 26d0f09b..422608eb 100644 --- a/skills/multi/dingtalk-live/SKILL.md +++ b/skills/multi/dingtalk-live/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[live.md](references/live.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-mail/SKILL.md b/skills/multi/dingtalk-mail/SKILL.md index 9c68343e..47c39b2b 100644 --- a/skills/multi/dingtalk-mail/SKILL.md +++ b/skills/multi/dingtalk-mail/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[mail.md](references/mail.md)。复杂搜索、附件、批量处理、草稿等多步邮件场景参考:[09-mail.md](references/09-mail.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-minutes/SKILL.md b/skills/multi/dingtalk-minutes/SKILL.md index b1578be4..4401c096 100644 --- a/skills/multi/dingtalk-minutes/SKILL.md +++ b/skills/multi/dingtalk-minutes/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[minutes.md](references/minutes.md);剧本:[07-minutes.md](references/07-minutes.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-oa/SKILL.md b/skills/multi/dingtalk-oa/SKILL.md index 40219adf..cfa7280e 100644 --- a/skills/multi/dingtalk-oa/SKILL.md +++ b/skills/multi/dingtalk-oa/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[oa.md](references/oa.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-report/SKILL.md b/skills/multi/dingtalk-report/SKILL.md index e5ab1707..bfe05a1c 100644 --- a/skills/multi/dingtalk-report/SKILL.md +++ b/skills/multi/dingtalk-report/SKILL.md @@ -18,34 +18,27 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[report.md](references/report.md);剧本:[05-reporting.md](references/05-reporting.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | |--------|------| | "今天收到的日志" | `python scripts/report_received_today.py` | -| "看日志模版" | `dws report template list` → `dws report template get --name "<模版名>"` | -| "提交日报 / 周报(按模版)" | `dws report entry submit --template-id --contents-file .json` | -| "我已发送的日志" | `dws report outbox list --start --end ` | -| "日志已读统计" | `dws report entry stats --report-id ` | +| "看日志模版" | `dws report template list` → `dws report template detail --name "<模版名>"` | +| "提交日报 / 周报(按模版)" | `dws report create --template-id --contents '[...]' | +| "我已发送的日志" | `dws report sent --start --end ` | +| "日志已读统计" | `dws report stats --report-id ` | | "生成日报 / 周报 / 月报 / 主题报告" | 见 [05-reporting.md](references/05-reporting.md) recipe | ## 日志查询硬约束 -- 查“收到的日志”必须用 `dws report inbox list --start "" --end "" --cursor 0 --size 20 --format json`,并把“今天 / 最近 30 天”等时间词先展开成完整 ISO 起止时间。 -- 列表返回后,后续 `entry get` / `entry stats` 必须复用同一个 `reportId`;不要重新挑选、猜测或改用标题。 -- 用户要正文时用 `dws report entry get --report-id `;用户要已读/统计时用 `dws report entry stats --report-id `。 +- 查“收到的日志”必须用 `dws report list --start "" --end "" --cursor 0 --size 20 --format json`,并把“今天 / 最近 30 天”等时间词先展开成完整 ISO 起止时间。 +- 列表返回后,后续 `detail` / `stats` 必须复用同一个 `reportId`;不要重新挑选、猜测或改用标题。 +- 用户要正文时用 `dws report detail --report-id `;用户要已读/统计时用 `dws report stats --report-id `。 ## 跨产品协作 diff --git a/skills/multi/dingtalk-report/references/05-reporting.md b/skills/multi/dingtalk-report/references/05-reporting.md index 08db7f1d..4f8c3e96 100644 --- a/skills/multi/dingtalk-report/references/05-reporting.md +++ b/skills/multi/dingtalk-report/references/05-reporting.md @@ -8,7 +8,7 @@ | query 中是否含强信号 | 选哪个 recipe | 默认值 | |---------------------|---------------|--------| -| 含「钉钉日志 / OA 周报 / 我的钉钉日志 / 日报模板 / 周报模板 / 提交日志 / 填模版」 | `submit-report`(走 dws report entry submit) | — | +| 含「钉钉日志 / OA 周报 / 我的钉钉日志 / 日报模板 / 周报模板 / 提交日志 / 填模版」 | `submit-report`(走 dws report create) | — | | 含「在线文档 / 写一篇文档 / 整理成文档 / 文档保存」 | `generate-*-report`(走 dws doc create) | — | | **无强信号**(如"写日报"、"写周报"、"整理本周工作")| `generate-*-report`(走 dws doc create) | 默认 | @@ -21,9 +21,9 @@ | Recipe | 行动指南(固定路线) | |--------|-------------------| -| query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;否 → 先按 doc/report 分歧澄清
1. 用户说「我发过 / 我创建」→ `report outbox list --cursor 0 --size 20 --format json`;用户说「收到 / 别人发给我」→ `report inbox list --start "" --end "" --cursor 0 --size 20 --format json`
2. 时间 flag 只允许 `--start` / `--end`,禁止 `--start-date` / `--end-date` / `--date`;不要只传裸日期,必须展开完整 ISO;不要先查 `help`,不要预先登录;只有命令返回认证错误时才处理认证
3. 从列表返回中取 `reportId` 留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表
4. 面向用户展示列表时必须基于 `result[]` 拼 Markdown 表:`日期 | 标题 | 发送人 | 状态 | 钉钉链接`;每条 `result[]` 都带这五个中文字段;不要把日志 ID 作为主列;已读状态字段缺失则不展示,不要编造
5. 用户要看正文时再执行 `report entry get --report-id --format json`;用户要统计 / 已读情况时执行 `report entry stats --report-id --format json`
**不要把 inbox list/outbox list 当正文接口**;查询正文必须补 `entry get`
**不要再生成** `report list` / `report sent` / `report detail` / `report stats`(deprecated alias,仍能跑但会打 stderr 警告) | +| query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;否 → 先按 doc/report 分歧澄清
1. 用户说「我发过 / 我创建」→ `report sent --cursor 0 --size 20 --format json`;用户说「收到 / 别人发给我」→ `report list --start "" --end "" --cursor 0 --size 20 --format json`(注意:`dws report inbox` 当前 CLI 未实现,禁止编造)
2. 时间 flag 只允许 `--start` / `--end`,禁止 `--start-date` / `--end-date` / `--date`;不要只传裸日期,必须展开完整 ISO;不要先查 `help`,不要预先登录;只有命令返回认证错误时才处理认证
3. 从列表返回中取 `reportId` 留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表
4. 面向用户展示列表时必须基于 `result[]` 拼 Markdown 表:`日期 | 标题 | 发送人 | 状态 | 钉钉链接`;每条 `result[]` 都带这五个中文字段;不要把日志 ID 作为主列;已读状态字段缺失则不展示,不要编造
5. 用户要看正文时再执行 `report detail --report-id --format json`;用户要统计 / 已读情况时执行 `report stats --report-id --format json`
**不要把 list/sent 当正文接口**;查询正文必须补 `detail` | | generate-daily-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=今日)
2. 交叉汇总并把日报内容写入临时文件 `.md`(UTF-8,真实换行)
3. **创建文档**:`doc create --name "<日报名>" --content-file .md`(> 200KB 按 `dingtalk-doc/references/04-document.md` 的 write-doc 兜底走 create 空 → 循环 update) | | generate-weekly-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=本周)
2. 交叉对比并把周报内容写入临时文件 `.md`
3. **创建文档**:`doc create --name "<周报名>" --content-file .md`(兜底同上) | -| submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)
1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当日)
2. `report template list --format json` → 取 `report_template_id`
3. `report template get --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`
4. **把 contents 写入临时文件**(避免 shell 引号问题):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`
5. `report entry submit --template-id --contents-file .json --format json` → CLI 会在提交成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接
6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 submit 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report entry get --report-id --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`
**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 submit;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link
**不要再生成** `report template detail` / `report create`(deprecated alias,仍能跑但会打 stderr 警告) | -| generate-monthly-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当月)
2. `report outbox list --start "<月初ISO>" --end "<月末ISO>"` → 取当月已提交日志
3. 按周分段归纳并把月报内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<月报名>" --content-file .md`(兜底同上) | +| submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)
1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当日)
2. `report template list --format json` → 取 `report_template_id`
3. `report template detail --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`
4. **把 contents 写入临时文件**(避免 shell 引号问题):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`
5. `report create --template-id --contents '[...]' (或 echo "$json" | ... --contents -) --format json` → CLI 会在创建成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接
6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 create 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report detail --report-id --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`
**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 create;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link | +| generate-monthly-report | 1. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行(时间=当月)
2. `report sent --start "<月初ISO>" --end "<月末ISO>"` → 取当月已提交日志
3. 按周分段归纳并把月报内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<月报名>" --content-file .md`(兜底同上) | | generate-topic-report | 1. 提取主题关键词;推断时间范围("最近"默认近 30 天)
2. 按「多源并行采集」(见 `dws-shared/references/best_practices/_common/conventions.md`)执行
3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `.md`
4. **创建文档**:`doc create --name "<报告名>" --content-file .md`(兜底同上) | diff --git a/skills/multi/dingtalk-sheet/SKILL.md b/skills/multi/dingtalk-sheet/SKILL.md index e77bf752..95980afc 100644 --- a/skills/multi/dingtalk-sheet/SKILL.md +++ b/skills/multi/dingtalk-sheet/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[sheet.md](references/sheet.md);URL 识别与类型探测:[url-patterns.md](references/url-patterns.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## URL 预检(含 alidocs URL 时必读) 输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式:`/i/p/...`(分享短链)、`/i/nodes/...`(节点链接,类型需探测)、`/spreadsheetv2/...`(电子表格直链,直接路由到 `sheet`)、`/document/edit|preview?dentryKey=...`(文档链接,路由到 `dingtalk-doc`)等。**必须先读 [url-patterns.md](references/url-patterns.md) 中的「alidocs URL 分流决策」**,按规则识别 URL 类型;仅当确认是在线电子表格(`/spreadsheetv2/...` 或 `i/nodes/` 且 probe 出 `contentType=ALIDOC` + `extension=axls`)时,才继续走本 skill 的命令。 diff --git a/skills/multi/dingtalk-sheet/references/sheet.md b/skills/multi/dingtalk-sheet/references/sheet.md index e3fb21ab..edc0bdb3 100644 --- a/skills/multi/dingtalk-sheet/references/sheet.md +++ b/skills/multi/dingtalk-sheet/references/sheet.md @@ -1,93 +1,1994 @@ # 电子表格 (sheet) 命令参考 -> **渐进式文档**:本文件为路由层(索引 + 意图判断 + 全局约束),各命令的详细参数、示例和注意事项在 [sheet/](./sheet/) 目录下按需加载。 +## 适用范围(重要) -## 适用范围 - -`sheet` 仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。 +`sheet` 产品线仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`),不支持上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。 | 文件类型 | 处理方式 | |---------|---------| -| 在线电子表格(`axls`) | 走 `sheet` 全部命令 | -| `xlsx` / `xls` / `xlsm` / `csv` | `dws drive download --node --output <路径>` 下载到本地处理,禁止调用任何 `sheet` 子命令 | -| 本地 xlsx 导入为在线表格 | `dws drive upload --file <路径> --convert`(上传并转换为在线电子表格,转换后可用 `sheet` 命令操作) | -| 在线表格导出为 xlsx | `dws sheet export`(axls → xlsx 格式转换) | +| 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) | +| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node --output <路径>` 先下载到本地再解析处理,禁止调用任何 `sheet` 子命令(sheet 底层 MCP 工具仅认 `axls`,传入 xlsx 节点会直接报错) | +| 想把在线表格导出为 xlsx | 用 `dws sheet export` ——输入是 `axls`,输出是 xlsx(axls → xlsx 的格式转换) | -用户贴原始 `alidocs` URL 时必须先 probe:`dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验: +> 用户直接粘贴原始 `alidocs` URL 时必须先 probe:先执行 `dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验 `contentType` 和 `extension`: +> - 仅当 `contentType=ALIDOC` 且 `extension=axls` 时,才继续走 `sheet` +> - 如果是 `xlsx` / `xls` / `xlsm` / `csv`,立即转向 `dws doc download`,并告知用户"这是本地表格文件,已为你下载到本地处理" + +## URL 识别与 NODE_ID 提取 + +当用户输入包含钉钉文档 URL 时,必须先识别并提取 NODE_ID,再判断意图。 + +硬性规则:对用户直接给出的原始 `alidocs` URL,不允许直接走 `sheet` 命令,必须先执行: + +```bash +dws doc info --node "" --format json +``` + +根据返回路由: - `contentType=ALIDOC` + `extension=axls` → 继续走 `sheet` -- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws drive download`,告知用户"这是本地表格文件,已为你下载到本地处理" +- `contentType≠ALIDOC` + `extension=xlsx` / `xls` / `xlsm` / `csv` → 转向 `dws doc download --node --output <路径>`,禁止调用任何 sheet 子命令 +- 其他类型 → 按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 路由 -## URL → NODE_ID +补充:如果这是用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe 一次确认真实类型,再判断是否继续走 `sheet`。 -| URL 格式 | 提取方式 | -|----------|---------| -| `.../i/nodes/{id}` 或 `.../i/nodes/{id}?query` | 取路径末段作 NODE_ID(忽略 query) | -| `.../spreadsheetv2/{key}/...` | **完整 URL 原样传 `--node`**,禁止提取 path segment | +### 支持的 URL 格式 -参数不确定时先 `dws sheet <命令> --help`。 +| 格式 | 示例 | NODE_ID 提取方式 | +|------|------|----------------| +| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段 | +| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段 | +| `alidocs.dingtalk.com/spreadsheetv2/{key}/...` | `https://alidocs.dingtalk.com/spreadsheetv2/vKJngl50tJN1v3a3/...?dentryKey=vKJngl50tJN1v3a3&type=s` | **不要提取 path segment**,将完整 URL 直接传给 `--node` 参数,由 MCP 服务端解析 | -## Reference 索引 +### 提取规则 -| Reference | 描述 | -|-----------|------| -| [sheet-workbook](./sheet/sheet-workbook.md) | 管理表格文档与工作表。当用户说"创建表格"、"有哪些工作表"、"新建/重命名/隐藏/冻结/复制/删除工作表"时使用。命令:`create`/`list`/`info`/`new`/`update`/`copy`/`delete-sheet` | -| [sheet-read-data](./sheet/sheet-read-data.md) | 读取工作表数据。当用户说"读数据"、"看表格内容"、"查看数据"时使用。推荐 `csv-get`(CSV 格式、token 低、防爆保护);需 value + dataValidation / hyperlink / richText / cellStyles 等 per-cell 元数据时用 `range read`。大范围数据建议分页读取(单次 ≤5000 单元格)。命令:`csv-get`/`range read` | -| [sheet-write-data](./sheet/sheet-write-data.md) | 写入数据到工作表。当用户说"写数据"、"填表"、"更新单元格"、"写公式"、"超链接"、"写值同时设样式/数据验证"、"追加数据"、"导入CSV"时使用。大批量纯值(>5行或>20单元格)必须用 `csv-put` 而非 `range update`。命令:`range update`/`append`/`csv-put` | -| [sheet-search-replace](./sheet/sheet-search-replace.md) | 搜索和替换文本。当用户说"搜索"、"查找"、"替换"、"把A改成B"时使用。禁止用 `range read` 全量读取后客户端过滤代替 `find`,禁止用 `range update` 模拟 `replace`。命令:`find`/`replace` | -| [sheet-range-operations](./sheet/sheet-range-operations.md) | 区域结构性操作。当用户说"清空"、"排序"、"自动填充"、"复制区域到"、"移动数据到"时使用。均为服务端原子操作,禁止 `range read`+`range update` 组合模拟。排序前必须先读前几行判断表头。命令:`range clear`/`range sort`/`range fill`/`range copy-to`/`range move-to` | -| [sheet-dimension-operations](./sheet/sheet-dimension-operations.md) | 行列增删移动与属性设置。当用户说"插入行/列"、"删除行/列"、"隐藏/显示行列"、"设行高/列宽"、"移动行/列"、"追加空行/空列"时使用。命令:`insert-dimension`/`delete-dimension`/`update-dimension`/`move-dimension`/`add-dimension` | -| [sheet-style-format](./sheet/sheet-style-format.md) | 单元格样式与合并。当用户说"设样式"、"改颜色/字体/对齐"、"数字格式(百分比/货币/日期)"、"合并/取消合并"时使用。纯样式/批量样式走 `set-style`;写值同时设置少量 cell 样式可用 `range update` 的 `cellStyles`。命令:`range set-style`/`range batch-set-style`/`merge-cells`/`unmerge-cells` | -| [sheet-dropdown](./sheet/sheet-dropdown.md) | 下拉列表管理。当用户说"设置下拉"、"下拉选项"、"删除下拉"时使用。命令:`set-dropdown`/`get-dropdown`/`delete-dropdown` | -| [sheet-media-image](./sheet/sheet-media-image.md) | 附件上传与图片。当用户说"上传附件"、"写入图片到单元格"、"浮动图片"时使用。单元格图片用 `write-image`(禁止 `range update`);浮动图片需先 `media-upload` 再 `create-float-image`。命令:`media-upload`/`write-image`/`create-float-image`/`get-float-image`/`list-float-images`/`update-float-image`/`delete-float-image` | -| [sheet-filter](./sheet/sheet-filter.md) | 全局筛选。当用户说"筛选"、"过滤"、"只看某些行"(未说"筛选视图")时使用。禁止用"删除不符合条件的行"代替筛选。命令:`filter get`/`create`/`delete`/`update`/`clear-criteria`/`sort` | -| [sheet-filter-view](./sheet/sheet-filter-view.md) | 筛选视图(个人化,不影响协作者)。当用户明确说"筛选视图"时使用,与全局筛选相互独立。命令:`filter-view list`/`create`/`update`/`delete`/`info`/`update-criteria`/`delete-criteria`/`list-criteria`/`get-criteria` | -| [sheet-conditional-format](./sheet/sheet-conditional-format.md) | 条件格式规则。触发词:标红/标黄/高亮/突出/标记/数据条/色阶/颜色随数据变 → **强制**走条件格式,禁止 `range set-style` 静态样式替代。命令:`cond-format list`/`create`/`update`/`delete` | -| [sheet-export](./sheet/sheet-export.md) | 导出表格为 xlsx。当用户说"导出"、"下载xlsx"、"存为Excel"时使用。单命令一站式,CLI 内部自动轮询,禁止 Agent 侧重试。命令:`export` | +1. 匹配 URL 中 `alidocs.dingtalk.com` 域名 +2. **判断 URL 路径格式**: + - 路径包含 `/i/nodes/` → 取 URL path 的最后一段作为 NODE_ID(去掉 query string 和 fragment) + - 路径包含 `/spreadsheetv2/` → **不要提取 path segment**,必须将完整 URL 原样传给 `--node` 参数(因为 path 中的短 ID 不是合法的 nodeId,MCP 服务端会自行解析完整 URL) +3. 对于 `/i/nodes/` 格式,提取出的 NODE_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析) +4. 对用户直接提供的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe;只有 probe 确认 `contentType=ALIDOC` 且 `extension=axls` 时,才继续留在 `sheet`;如果 `extension=xlsx` / `xls` / `xlsm` / `csv`,必须转向 `dws doc download`,不能走任何 sheet 命令 -## 全局硬约束 +## 查询命令帮助 -1. **`--sheet-id` 禁止臆测**:未知时必须 `dws sheet list --node --format json` 查询,禁止编造 `Sheet1`/`sheet1`/`0`/`default` -2. **合并单元格是结构信息**:`dws sheet info --node --sheet-id --format json` 返回 `mergedRanges`(如 `["C7:D11"]`);不要在 `range read` / `csv-get` 里寻找合并信息 -3. **最后非空坐标只用 A1 语义**:`sheet info` 通过 `nonEmptyRange` 返回 A1/UI 边界;优先使用 `nonEmptyRange.range`,需要追加行/列时使用 `nonEmptyRange.lastRow` / `nonEmptyRange.lastColumn`。不要依赖底层 0-based 的 `lastNonEmptyRow` / `lastNonEmptyColumn` -4. **`range update` 维度校验**:`--values` 行列数必须与 `--range` 完全一致;只接 `--values` 一个数据参数,cell `type` 仅支持 `text` / `richText`;整格超链接通过 cell-level `hyperlink` 表达,富文本片段链接才使用 `richText.texts[].type="link"` -5. **dataValidation 三语义**:不传 `dataValidation` 字段=保留原 DV;`dataValidation:{type:"none"}`=显式清除;`dataValidation:{type:"dropdown"/"checkbox",...}`=覆盖。`{}` 跳过亦保留原 DV -6. **hyperlink 三语义**:不传 `hyperlink` 字段=保留原整格超链接;`hyperlink:{type:"none"}`=显式清除;`hyperlink:{type:"path"/"sheet"/"range",link,...}`=覆盖。Agent 调用不要用 `hyperlink:null`,避免网关/Schema 过滤 null 字段 -7. **样式写法**:cell-level 样式用 `cellStyles` 或 `range set-style`;richText 片段级样式才用子项 `style`。不要在 `type:"text"` 顶层使用旧 `style` 字段 -8. **用专用命令不用组合模拟**:搜索→`find`、替换→`replace`、清空→`range clear`、排序→`range sort`、填充→`range fill`、复制区域→`range copy-to`、移动区域→`range move-to`、移动行列→`move-dimension` -9. **大批量纯值用 `csv-put`**(>5 行或 >20 单元格),不用 `range update` -10. **单元格图片用 `write-image`**(`range update` 不支持图片参数) -11. **`export` 禁止自行轮询**(CLI 内部已完成渐进式退避,最多 30 次约 5 分钟) -12. **单次调用上限**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) -13. **关键区分**:sheet(电子表格/单元格读写)vs aitable(AI多维表/结构化记录)vs doc(文档) +当你不确定某个命令的具体参数、格式或可选项时,**优先执行 `--help` 查询**,不要猜测参数名或凭记忆编造。 -## URL 粘贴场景 +```bash +# 查看 sheet 下所有子命令 +dws sheet --help + +# 查看具体命令的完整参数说明 +dws sheet range update --help +dws sheet filter-view create --help +dws sheet insert-dimension --help + +# 查看子命令组下的所有命令 +dws sheet range --help +dws sheet filter-view --help +``` + +规则: +- 参数名不确定时 → 先 `--help`,再调用 +- 报错 "unknown flag" 时 → `--help` 确认正确的 flag 名称 +- 不确定某个功能是否存在时 → `dws sheet --help` 查看命令列表 + +## 命令速查表 + +| 命令 | 用途 | +|------|------| +| `sheet create` | 创建钉钉表格文档 | +| `sheet list` | 获取全部工作表列表 | +| `sheet info` | 获取指定工作表详情 | +| `sheet new` | 新建工作表 | +| `sheet update` | 更新工作表属性(标题/位置/隐藏/冻结) | +| `sheet copy` | 复制工作表 | +| `sheet range read` | 读取工作表数据(别名: range get) | +| `sheet range update` | 更新指定区域内容(值/公式/超链接) | +| `sheet range set-style` | 设置单元格样式 | +| `sheet range batch-set-style` | 按配置文件批量设置样式 | +| `sheet find` | 搜索单元格内容 | +| `sheet append` | 在末尾追加数据行 | +| `sheet csv-put` | 将 CSV 数据写入指定位置(纯值,自动扩容) | +| `sheet replace` | 全局查找替换文本 | +| `sheet merge-cells` | 合并单元格 | +| `sheet unmerge-cells` | 取消合并单元格 | +| `sheet set-dropdown` | 设置下拉列表 | +| `sheet get-dropdown` | 获取下拉列表配置 | +| `sheet delete-dropdown` | 删除下拉列表 | +| `sheet insert-dimension` | 在指定位置插入行或列 | +| `sheet delete-dimension` | 删除指定位置的行或列 | +| `sheet update-dimension` | 更新行/列属性(显隐/行高/列宽) | +| `sheet move-dimension` | 移动行或列到指定位置 | +| `sheet add-dimension` | 在末尾追加空行或空列 | +| `sheet media-upload` | 上传附件到表格 | +| `sheet write-image` | 上传图片并写入单元格 | +| `sheet create-float-image` | 创建浮动图片 | +| `sheet get-float-image` | 获取浮动图片详情 | +| `sheet list-float-images` | 列出工作表所有浮动图片 | +| `sheet update-float-image` | 更新浮动图片属性 | +| `sheet delete-float-image` | 删除浮动图片 | +| `sheet export` | 导出表格为 xlsx | +| `sheet filter get` | 获取全局筛选信息 | +| `sheet filter create` | 创建全局筛选 | +| `sheet filter delete` | 删除全局筛选 | +| `sheet filter update` | 批量更新筛选条件 | +| `sheet filter clear-criteria` | 清除单列筛选条件 | +| `sheet filter sort` | 筛选排序 | +| `sheet filter-view list` | 获取所有筛选视图 | +| `sheet filter-view create` | 创建筛选视图 | +| `sheet filter-view update` | 更新筛选视图属性 | +| `sheet filter-view delete` | 删除筛选视图 | +| `sheet filter-view update-criteria` | 更新筛选视图列条件 | +| `sheet filter-view delete-criteria` | 删除筛选视图列条件 | +| `sheet filter-view info` | 获取单个筛选视图详情 | +| `sheet filter-view list-criteria` | 列出筛选视图所有列条件 | +| `sheet filter-view get-criteria` | 获取单列筛选条件详情 | + +> 不确定参数?对任意命令执行 `dws sheet <命令> --help` 查看完整用法。 + +## 意图判断 + +### 表格与工作表管理 + +用户说"创建表格/新建电子表格": +- 创建表格文档 → `create` + +用户说"看工作表/有哪些工作表/表格结构": +- 列出工作表 → `list` +- 工作表详情 → `info` + +用户说"加工作表/新增Sheet": +- 新建工作表 → `new` + +用户说"修改工作表名称/重命名工作表/移动工作表位置/隐藏工作表/显示工作表/冻结行/冻结列/取消冻结/更新工作表属性": +- 更新工作表属性 → `update` +- 重命名工作表 → `update --title "新名称"` +- 移动工作表位置 → `update --index N` +- 隐藏工作表 → `update --hidden` +- 显示工作表 → `update --hidden=false` +- 冻结行列 → `update --frozen-row-count N --frozen-column-count M` +- 取消冻结 → `update --frozen-row-count 0 --frozen-column-count 0` + +用户说"复制工作表/拷贝工作表/克隆工作表/工作表副本": +- 复制工作表 → `copy` +- 复制并指定名称 → `copy --title "副本名称"` +- 复制并指定位置 → `copy --index N` + +### 数据读写 + +用户说"读数据/看表格内容": +- 读取数据 → `range read` + +用户说"写数据/填表/更新单元格/写入公式": +- 更新数据 → `range update` +- 【强制】`--sheet-id` 必填:即使是单工作表也不能省略,不要参照 `range read` 的默认行为;未知时先执行 `dws sheet list --node --format json` 获取 `sheetId`,禁止凭空臆测为 `Sheet1`、`sheet1`、`0`、`default` 等 +- 注意:如果用户的目的是替换文本、移动行列或追加空行空列,请勿使用 `range update`,必须使用对应的专用命令(`replace`/`move-dimension`/`add-dimension`) +- **批量纯值写入优先用 `csv-put`**:当写入场景同时满足以下条件时,必须优先使用 `csv-put` 而非 `range update`:(1) 写入的是纯值(不含公式、超链接);(2) 数据量较大(超过 5 行或超过 20 个单元格);(3) 数据来源为表格/CSV 文本/结构化文本。`csv-put` 无需手动构造二维 JSON 数组,直接传 CSV 文本即可,更简洁高效且支持自动扩容 + +用户说"追加数据/添加行/在末尾加数据/新增记录": +- 追加数据 → `append` + +用户说"批量写入CSV/导入CSV/CSV写入表格/把CSV贴到表格里": +- 写入 CSV → `csv-put` +- 与 `range update` 的区别:`csv-put` 接受 CSV 文本直接写入,无需手动构造二维 JSON 数组;适合大批量纯值写入 +- 与 `append` 的区别:`csv-put` 写入指定位置(--start-cell),`append` 在末尾追加 + +用户说"搜索/查找/找单元格/搜内容/精确搜索/精确匹配/完全匹配/全字匹配": +- 搜索单元格 → `find` +- 精确匹配(只匹配完全等于的,不匹配包含的) → `find --match-entire-cell` +- 正则搜索 → `find --use-regexp` +- 搜索公式 → `find --match-formula` +- 不要用 `range read` 读取全量数据后在客户端过滤来替代 `find`,必须使用 `find` 命令的服务端搜索能力 + +用户说"替换/查找替换/全局替换/批量替换/把A替换成B/把所有的X改成Y": +- 查找替换 → `replace` +- 精确匹配后替换(只替换内容完全等于的单元格) → `replace --match-entire-cell` +- 正则替换 → `replace --use-regexp` +- 删除匹配内容 → `replace --replacement ""` +- 请勿用 `find` + `range update`、`range read` + `range update` 等组合来模拟替换,`replace` 是服务端原子操作,效率更高且返回替换计数 + +### 行列操作 + +用户说"插入行/插入列/在某行前插入/在某列前插入": +- 插入行或列 → `insert-dimension` +- 在末尾追加 → `append`(insert-dimension 不支持末尾追加) + +用户说"删除行/删除列/删掉第几行/删掉某列/移除行/移除列": +- 删除行或列 → `delete-dimension` +- 仅清空内容但保留行/列 → `range update --values` 写入空字符串 `""` + +用户说"隐藏行/隐藏列/显示行/显示列/设置行高/设置列宽/调整行高/调整列宽/行列属性": +- 隐藏/显示行或列 → `update-dimension --hidden` / `--hidden=false` +- 设置行高/列宽 → `update-dimension --pixel-size` +- 同时修改尺寸与显隐 → `update-dimension --pixel-size --hidden` + +用户说"移动行/移动列/调整行顺序/调整列顺序/行列拖拽/把第N行移到第M行": +- 移动行或列 → `move-dimension` +- 请勿用 `range read` + `range update` 读取再重写来模拟移动,`move-dimension` 是原子操作,能保留格式和合并状态 + +用户说"追加空行/追加空列/增加行数/增加列数/扩展表格/在末尾加空行": +- 追加空行/空列 → `add-dimension` +- 注意与 `append`(追加数据行)区分:`add-dimension` 追加的是空行/空列,`append` 追加的是带数据的行 +- 请勿用 `range update` 写空数据来模拟追加,`add-dimension` 直接扩展表格维度 + +### 单元格格式 + +用户说"设置样式/改颜色/设背景色/加粗/居中/换行/字体颜色/字号": +- 设置单元格样式 → `range set-style` +- 批量设置不同 range 的样式 → `range batch-set-style --batch ./styles.json`(内部顺序循环调 `update_range`) +- 请勿用 `range update --values` 写空/重写来模拟样式变更;也请勿把样式变更混在 `range update` 里、再故意清空 `--values` + +用户说"设置数字格式/改成百分比/用人民币显示/按日期显示/文本格式/保留几位小数": +- 设置数字格式 → `range set-style --number-format <格式代码>`(如 `0%` / `¥#,##0.00` / `yyyy/m/d` / `@`) +- 请勿用 `range update` 传递数字格式,`range update` 仅负责写入值与超链接;数字格式属于单元格样式,统一走 `range set-style` + +用户说"合并单元格/合并/合并区域/按行合并/按列合并": +- 合并所有单元格 → `merge-cells`(默认 mergeAll) +- 按行合并 → `merge-cells --merge-type mergeRows` +- 按列合并 → `merge-cells --merge-type mergeColumns` + +用户说"取消合并/拆分单元格/还原合并": +- 取消合并单元格 → `unmerge-cells` + +### 下拉列表 + +用户说"设置下拉列表/下拉选项/下拉菜单/添加下拉/配置下拉": +- 设置下拉列表 → `set-dropdown` +- 设置多选下拉 → `set-dropdown --multi-select` + +用户说"查看下拉列表/获取下拉配置/下拉列表有哪些选项": +- 获取下拉列表配置 → `get-dropdown` + +用户说"删除下拉列表/移除下拉/取消下拉/清除下拉": +- 删除下拉列表 → `delete-dropdown` + +### 媒体上传 + +用户说"上传附件/传文件到表格/上传文件到表格/上传到表格": +- 上传附件 → `media-upload`(需表格 ID 或 URL + 本地文件路径) +- 用户指定了上传后的名称 → `media-upload --name "自定义名称"` +- `media-upload` 的 `--name` 参数用于指定附件在表格中显示的名称(不改变本地文件名);不传时默认使用本地文件名 + +用户说"写入图片/插入图片/加图片/放图片到单元格/嵌入图片到表格": +- 写入图片 → `write-image`(需表格 ID + 工作表 ID + 单元格范围 + 本地图片路径) +- 禁止使用 `range update` 写入图片,因为 `update_range` 的 MCP 工具不支持图片类型参数,调用必定失败。必须使用 `write-image` 命令 +- 用户指定了图片尺寸 → `write-image --width N --height M` + +### 浮动图片 + +用户说"浮动图片/悬浮图片/在表格上放一张图/加个浮动的图": +- 创建浮动图片 → 先 `media-upload` 上传图片获取 `resourceUrl`,再 `create-float-image` +- 浮动图片悬浮于单元格之上,不占用单元格内容,与 `write-image`(写入单元格内部的图片)不同 + +用户说"查看浮动图片/有哪些浮动图片/浮动图片列表": +- 列出所有浮动图片 → `list-float-images` +- 查看某个浮动图片详情 → `get-float-image` + +用户说"移动浮动图片/调整浮动图片大小/修改浮动图片/更新浮动图片": +- 更新浮动图片属性 → `update-float-image`(可更新锚点位置、尺寸、偏移量、图片资源路径) + +用户说"删除浮动图片/移除浮动图片": +- 删除浮动图片 → `delete-float-image` + +关键区分:`write-image`(单元格内嵌图片,占据单元格内容)vs `create-float-image`(浮动图片,悬浮于单元格之上,不占内容) + +### 筛选视图 + +用户说"筛选/过滤/只看某些值/只显示满足条件的行/筛选数据/创建筛选/删除筛选/设置筛选条件/清除筛选/排序": +- 查看当前筛选 → `filter get` +- 创建筛选 → `filter create` +- 删除筛选 → `filter delete` +- 批量设置多列条件 → `filter update` +- 清除某一列条件 → `filter clear-criteria` +- 按列排序 → `filter sort` +- **区分全局筛选与筛选视图**:如果用户说"筛选视图"则走 `filter-view` 系列;如果只说"筛选/过滤/只看"则默认走全局 `filter` 系列 +- **禁止替代方案**:当用户要求"筛选/只看/仅保留某些行"时,必须通过 `filter create` / `filter update` 创建真实的筛选器。禁止用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替——这些做法会让原数据丢失或不可恢复 + +用户说"筛选视图/查看筛选视图/有哪些筛选视图/筛选视图列表": +- 获取所有筛选视图 → `filter-view list` + +用户说"筛选视图详情/查看某个筛选视图/筛选视图信息/筛选视图配置": +- 获取单个筛选视图详情 → `filter-view info` + +用户说"创建筛选视图/新建筛选视图/添加筛选视图": +- 创建筛选视图 → `filter-view create` + +用户说"更新筛选视图/修改筛选视图/改筛选视图名称/改筛选视图范围": +- 更新筛选视图属性 → `filter-view update` + +用户说"删除筛选视图/移除筛选视图": +- 删除筛选视图 → `filter-view delete` + +用户说"设置筛选条件/添加筛选条件/配置筛选视图条件/按值筛选/按条件筛选/按颜色筛选": +- 设置筛选视图列条件 → `filter-view update-criteria` + +用户说"查看筛选条件/有哪些筛选条件/筛选视图设了什么条件/列出筛选条件": +- 列出所有列条件 → `filter-view list-criteria` +- 查看某一列的条件 → `filter-view get-criteria --column N` + +用户说"清除筛选条件/移除筛选条件/取消筛选条件": +- 清除筛选视图列条件 → `filter-view delete-criteria` +- 注意与 `filter-view delete`(删除整个筛选视图)区分:`delete-criteria` 仅清除指定列的条件,不删除筛选视图本身 + +### 导出 + +用户说"导出/下载xlsx/存为Excel/存成表格文件/把表格变成xlsx/导出表格/下载表格/导出为 excel": +- 导出表格 → `export`(单命令一站式,内部自动完成提交、轮询、可选下载) +- 仅需传 `--node`,可选 `--output` 指定本地文件/目录(不传则返回 downloadUrl) +- 需要落盘到本地 → `dws sheet export --node --output `,命令自动下载 xlsx +- 禁止用 `range read` 全量读取后自行拼接 xlsx 来模拟导出,必须使用 `export` 命令(服务端原子导出,保留格式/合并/公式等属性) +- 禁止在 AI Agent 侧实现轮询或重试,CLI 内部已按渐进式退避策略完成(最多 30 次约 5 分钟) + +### URL 粘贴场景 用户直接粘贴表格 URL(无其他指令): -- 先 probe:`dws doc info --node --format json` -- `extension=axls` → `list` + `range read`(读取第一个工作表数据) -- `extension=xlsx`/`xls`/`xlsm`/`csv` → 转 `dws drive download --node --output ./` +- 先 probe:`dws doc info --node --format json` 校验 `contentType` 和 `extension` +- `extension=axls` → `list`(列出工作表)+ `range read`(读取第一个工作表数据) +- `extension=xlsx` / `xls` / `xlsm` / `csv` → 转 `dws doc download --node --output ./`,告知用户"这是本地表格文件,已为你下载到本地",然后基于本地文件继续后续处理 用户粘贴 URL + 附加指令: -- probe 为 `axls` → 按 Reference 索引路由到对应命令 -- probe 为 xlsx/csv → 先 `dws drive download` 下载到本地,严禁调用 sheet 命令 +- 已 probe 为 `axls` 时: + - "帮我看看这个表格有什么数据" → `range read` + - "这个表格有哪些工作表" → `list` + - "往这个表格写入数据" → `range update` + - "帮我找一下表格里的XXX" → `find` +- probe 为 xlsx/xls/xlsm/csv 时:无论用户说"读数据/查看/分析",先走 `dws doc download` 下载到本地,由用户或后续步骤对本地 xlsx 进行解析,严禁调用 `sheet list` / `range read` 等命令 -## 导入本地表格 +关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录) vs doc(文档编辑/阅读) -用户说"导入Excel/把xlsx转为在线表格/上传表格并在线编辑"时: -```bash -# 上传并转换为在线电子表格(转换后返回 nodeId,可用 sheet 命令操作) -dws drive upload --file ./data.xlsx --convert +## 关键注意事项 -# 指定上传到某个文件夹 -dws drive upload --file ./data.xlsx --folder --convert +以下是最容易出错的规则,**必须严格遵守**: -# 指定上传到知识库 -dws drive upload --file ./data.xlsx --workspace --convert +- ★ **`--sheet-id` 获取规范(强制)**:`sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等) +- ★ **`range update` 维度校验(强制)**:`--values` / `--hyperlinks` 的行列数必须与 `--range` 完全一致。例如 `--range "A1:C3"` → `--values` 必须是 3×3 数组 +- ★ **`range update` 清空规范(强制)**:清空单元格用空字符串 `""`,禁止用 `null`(全 null 会被跳过无效果) +- ★ **单次调用上限(强制)**:`range update` / `set-style` 行数 ≤ 1000,单元格总数建议 ≤ 5000(硬限 30000) +- ★ **大批量纯值写入用 `csv-put` 不用 `range update`**:当写入纯值(无公式/超链接)且数据量较大时(>5 行或 >20 单元格),必须使用 `csv-put`。`csv-put` 接受 CSV 文本直接写入,无需构造二维 JSON 数组,支持自动扩容,更简洁高效。仅在需要写入公式、超链接、或仅更新少量单元格时才使用 `range update` +- ★ **搜索用 `find` 不用 `range read`**:`find` 是服务端搜索,禁止用 `range read` 全量读取后客户端过滤 +- ★ **替换用 `replace` 不用 `range update`**:`replace` 是服务端原子操作,返回替换计数 +- ★ **移动用 `move-dimension` 不用 `range update`**:原子操作,保留格式和合并状态 +- ★ **单元格图片用 `write-image` 不用 `range update`**:`update_range` MCP 不支持图片参数,调用必失败 +- ★ **浮动图片用 `create-float-image` 不用 `write-image`**:两者用途不同——`write-image` 写入单元格内部,`create-float-image` 创建悬浮于单元格之上的浮动图片;`--src` 必须来自 `media-upload` 的 `resourceUrl` +- ★ **`export` 禁止自行轮询/重试**:CLI 内部已完成渐进式退避轮询(最多 30 次约 5 分钟),失败时直接告知用户稍后再试 +- ★ **关键区分**:sheet(在线电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) + +> 完整注意事项请参见本文档末尾「注意事项(完整版)」章节。 + +## 命令详细参考 + +> 以下为各命令的完整 Usage、Flags 和示例。参数不确定时也可直接执行 `dws sheet <命令> --help` 在线查看。 + +### 创建钉钉表格文档 +``` +Usage: + dws sheet create [flags] +Example: + dws sheet create --name "销售数据" + dws sheet create --name "Q1 数据" --folder + dws sheet create --name "知识库表格" --workspace +Flags: + --name string 表格名称 (必填) + --folder string 目标文件夹 ID (dentryUuid 格式) 或 URL;禁止传入纯数字 dentryId + --workspace string 目标知识库 ID ``` -- `--convert` 是关键参数,不加则仅上传为附件,不会转换为在线电子表格 -- 转换后的文档为 `axls` 格式,可用 `sheet` 全部命令操作 -- 支持 `.xlsx` / `.xls` / `.csv` 等格式 -## nodeId 说明 +> **ID 格式约束**:`--folder` 只接受 UUID 格式的 `fileId`(如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)或 alidocs 文件夹 URL。`drive list` 返回中有 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式)两个字段,**必须使用 `fileId`,禁止使用 `dentryId`**,传入纯数字会导致命令失败。 -`--node` 同时支持文档 ID、URL、分享链接。`drive list` 返回中必须用 `fileId`(UUID 格式),禁止用 `dentryId`(纯数字)。 +### 获取全部工作表列表 +``` +Usage: + dws sheet list [flags] +Example: + dws sheet list --node + dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/" +Flags: + --node string 表格文档 ID 或 URL (必填) +``` + +### 获取指定工作表详情 +``` +Usage: + dws sheet info [flags] +Example: + dws sheet info --node + dws sheet info --node --sheet-id + dws sheet info --node --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (不传则返回第一个工作表) +``` + +### 新建工作表 +``` +Usage: + dws sheet new [flags] +Example: + dws sheet new --node --name "Sheet2" + dws sheet new --node --name "数据汇总" +Flags: + --node string 表格文档 ID (必填) + --name string 工作表名称 (必填) +``` + +### 更新工作表属性 +``` +Usage: + dws sheet update [flags] +Example: + # 改名 + 调整冻结 + dws sheet update --node --sheet-id --title "汇总表" --frozen-row-count 2 --frozen-column-count 1 + + # 隐藏工作表 + dws sheet update --node --sheet-id --hidden=true + + # 显示工作表 + dws sheet update --node --sheet-id --hidden=false + + # 移动工作表到第一个位置 + dws sheet update --node --sheet-id --index 0 + + # 取消冻结 + dws sheet update --node --sheet-id --frozen-row-count 0 --frozen-column-count 0 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --title string 新标题,最长 100 字符,不能包含 / \ ? * [ ] : + --index int 新位置(从 0 开始) + --hidden --hidden=true 隐藏,--hidden=false 取消隐藏 + --frozen-row-count int 冻结行数,0 表示取消冻结 + --frozen-column-count int 冻结列数,0 表示取消冻结 +``` + +更新工作表标题、位置、隐藏状态、冻结行列。 +`--title` / `--index` / `--hidden` / `--frozen-row-count` / `--frozen-column-count` 至少提供一个;多个属性可同时传入,将在同一次请求中更新。 + +注意: +- 至少需要保留一个可见的工作表,不能将所有工作表都隐藏 +- 冻结行数/列数不能超过工作表的总行数/列数 + +### 复制工作表 +``` +Usage: + dws sheet copy [flags] +Example: + # 按默认位置复制 + dws sheet copy --node --sheet-id + + # 指定副本名称和位置 + dws sheet copy --node --sheet-id --title "销售副本" --index 2 + + # 只指定名称 + dws sheet copy --node --sheet-id --title "备份" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 源工作表 ID 或名称 (必填) + --title string 副本名称,最长 100 字符,不能包含 / \ ? * [ ] : (不传则系统自动生成) + --index int 副本位置(从 0 开始)(不传则放在源工作表之后) +``` + +复制指定工作表,在同一表格中创建一个副本。 +复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表中。 +传 `--index` 时,CLI 会先复制,再追加一次位置更新,把副本移动到目标索引。 +名称与已有工作表重复时系统会自动重命名。 + +### 读取工作表数据 +``` +Usage: + dws sheet range read [flags] # 别名: dws sheet range get +Example: + dws sheet range read --node + dws sheet range read --node --sheet-id + dws sheet range read --node --sheet-id "Sheet1" --range "A1:D10" + dws sheet range read --node --range "Sheet1!A1:D10" + + # 使用 get 别名,与 read 等价 + dws sheet range get --node --sheet-id --range "A1:D10" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (不传则默认第一个工作表) + --range string 读取范围,A1 表示法 (如 A1:D10,不传则读取全部数据) +``` + +**超时处理建议**:读取大范围数据时若出现超时或响应过慢,请主动缩小 `--range` 查询范围,**建议单次读取的单元格数量控制在 5000 个以内**(例如 50 行 × 100 列、100 行 × 50 列)。对于大表可采用分页读取策略: +- 先通过 `info` 获取 `rowCount` / `lastNonEmptyRow` / `columnCount` 确定数据边界 +- 按行分批读取,如 `A1:J500`、`A501:J1000`、`A1001:J1500` …… +- 避免不传 `--range` 直接读取整个大工作表 + +### 更新工作表指定区域内容 +``` +Usage: + dws sheet range update [flags] +Example: + # 写入值 + dws sheet range update --node --sheet-id --range "A1:B2" \ + --values '[["姓名","分数"],["张三",90]]' + + # 写入公式 + dws sheet range update --node --sheet-id --range "C2" \ + --values '[["=A2&B2"]]' + + # 写入超链接 + dws sheet range update --node --sheet-id --range "A1" \ + --hyperlinks '[[{"type":"path","link":"https://dingtalk.com","text":"钉钉"}]]' + + # 清空区域(使用空字符串 "") + dws sheet range update --node --sheet-id --range "A1:B3" \ + --values '[["",""],["",""],["",""]]' +Flags: + --node string 表格文档 ID (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:B3 (必填) + --values string 单元格值,二维 JSON 数组 (与 --hyperlinks 至少传一项) + --hyperlinks string 超链接,二维 JSON 数组 (与 --values 至少传一项) +``` + +**单次调用建议**:行数 ≤ 1000,单元格总数(行×列)≤ 5000;超过时请拆分多次调用。 + +**何时该用 `csv-put` 替代**:如果你准备用 `range update` 写入纯值(不含公式和超链接),且数据量超过 5 行或 20 个单元格,应改用 `csv-put`——它接受 CSV 文本直接写入,无需手动拼装二维 JSON 数组,且支持自动扩容行列。仅在需要写入公式(`=SUM(...)`)、超链接(`--hyperlinks`)、或修改少量单元格时才使用 `range update`。 + +**范围职责**:`range update` 仅负责写入单元格的值与超链接,不接受任何样式参数。如需设置数字格式(百分比 / 货币 / 日期 / 文本等)请使用 `dws sheet range set-style --number-format <格式代码>`,可与其他样式参数同时传入。 + +### 设置单元格样式 +``` +Usage: + dws sheet range set-style [flags] +Example: + # 给 A1:B3 打上黄底粗体居中 + dws sheet range set-style --node --sheet-id --range "A1:B3" \ + --bg-color "#FFF2CC" --font-weight bold --h-align center + + # 给 C1:C5 逐单元格设置不同背景色 + dws sheet range set-style --node --sheet-id --range "C1:C5" \ + --bg-colors-json '[["#FF0000"],["#00FF00"],["#0000FF"],["#FFFF00"],["#FF00FF"]]' + + # 整片 range 启用自动换行 + dws sheet range set-style --node --sheet-id --range "A1:E10" --word-wrap autoWrap +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标区域,如 A1:B3 (必填) + --bg-color string 背景色(#RRGGBB),一键刷整个 range;与 --bg-colors-json 二选一 + --bg-colors-json string 背景色二维 JSON 数组,维度需与 --range 一致 + --font-size int 字号,一键刷整个 range;与 --font-sizes-json 二选一 + --font-sizes-json string 字号二维 JSON 数组 + --h-align string 水平对齐:left/center/right/general + --h-aligns-json string 水平对齐二维 JSON 数组 + --v-align string 垂直对齐:top/middle/bottom + --v-aligns-json string 垂直对齐二维 JSON 数组 + --font-color string 字体颜色(#RRGGBB) + --font-colors-json string 字体颜色二维 JSON 数组 + --font-weight string 字体粗细:bold/normal + --font-weights-json string 字体粗细二维 JSON 数组 + --word-wrap string 换行方式:overflow/clip/autoWrap(整个 range 共用) + --number-format string 数字格式,如 General/@/#,##0/0%/yyyy/m/d +``` + +**特性说明**: +- 每个样式维度提供两种写法,二选一:`--xxx`(单值刷整个 range,CLI 本地展开为二维数组)vs `--xxx-json`(逐单元格指定,维度需与 `--range` 完全一致) +- 至少需传入一个样式参数。单次调用建议:行数 ≤ 1000,单元格总数 ≤ 5000 +- 枚举值按驼峰书写:`autoWrap`、`bold`、`normal`、`center` 等 + +### 批量设置单元格样式 +``` +Usage: + dws sheet range batch-set-style [flags] +Example: + dws sheet range batch-set-style --node --batch ./styles.json + dws sheet range batch-set-style --node --batch ./styles.json --continue-on-error +Flags: + --node string 表格文档 ID 或 URL (必填) + --batch string 批次配置 JSON 文件路径 (必填) + --continue-on-error 遇到失败时继续执行后续条目(默认遇错即停) +``` + +配置文件格式(JSON 数组,每个元素一条批次项): +```json +[ + { + "sheetId": "Sheet1", + "range": "A1:B3", + "bgColor": "#FFF2CC", + "fontSize": 12, + "hAlign": "center", + "vAlign": "middle", + "fontColor": "#333333", + "fontWeight": "bold", + "wordWrap": "autoWrap", + "numberFormat": "General" + }, + { + "sheetId": "Sheet1", + "range": "C1:C5", + "bgColorsJson": "[[\"#FF0000\"],[\"#00FF00\"],[\"#0000FF\"],[\"#FFFF00\"],[\"#FF00FF\"]]" + } +] +``` + +**特性说明**: +- CLI 侧顺序循环逐条调用 `update_range`(非服务端批量),运行时输出 `[N/M]` 进度 +- 每条记录执行与 `set-style` 一致的校验:至少一项样式字段 + rows ≤ 1000 + rows×cols ≤ 30000 + 枚举合法 +- 默认遇错即停(返回非 0),`--continue-on-error` 时所有条目跑完再返回首个错误 + +### 在工作表中搜索单元格内容 +``` +Usage: + dws sheet find [flags] +Example: + # 基本搜索 + dws sheet find --node --sheet-id --find "销售额" + + # 在指定范围内搜索 + dws sheet find --node --sheet-id --find "合计" --range "A1:D100" + + # 正则表达式搜索(不区分大小写) + dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false + + # 精确匹配整个单元格内容 + dws sheet find --node --sheet-id --find "完成" --match-entire-cell + + # 搜索公式文本 + dws sheet find --node --sheet-id --find "SUM" --match-formula +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --find string 搜索文本 (必填) + --range string 搜索范围,A1 表示法 (如 A1:D10) + --match-case 区分大小写 (默认 true) + --match-entire-cell 精确匹配整个单元格内容 + --use-regexp 启用正则表达式搜索 + --match-formula 搜索公式文本而非显示值 + --include-hidden 包含隐藏单元格 +``` + +### 在工作表末尾追加数据 +``` +Usage: + dws sheet append [flags] +Example: + dws sheet append --node --sheet-id --values '[["张三","销售部",50000]]' + dws sheet append --node --sheet-id "Sheet1" \ + --values '[["李四","市场部",38000],["王五","销售部",62000]]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --values string 追加数据,二维 JSON 数组 (必填) +``` + +`--values` 为二维 JSON 数组,外层每个元素代表一行,内层每个元素代表一个单元格值。 +追加的数据列数应与工作表已有数据的列数保持一致。 + +### 将 CSV 数据写入指定位置 +``` +Usage: + dws sheet csv-put [flags] +Example: + dws sheet csv-put --node --sheet-id --start-cell A1 \ + --csv 'name,score\nAlice,95\nBob,87' + + dws sheet csv-put --node --sheet-id --start-cell B2 \ + --csv @data.csv --allow-overwrite + + cat data.csv | dws sheet csv-put --node --sheet-id \ + --start-cell A1 --csv - + + dws sheet csv-put --node --sheet-id --start-cell A1 \ + --csv @data.csv --dry-run +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --csv string CSV 文本、@文件路径 或 - 表示 stdin (必填) + --start-cell string 起始单元格,A1 表示法 (必填) + --allow-overwrite 允许覆盖已有数据 (默认 false) +``` + +将 RFC 4180 格式的 CSV 文本写入指定工作表的指定单元格位置。 +- 只写纯值,不支持公式/样式/批注。`=` 开头的内容当文本处理,不会被解析为公式 +- 数字/日期/百分数由表格引擎自动识别类型(如 `95` 存为数字,`2025-03-01` 存为日期) +- 自动扩容行列:CSV 数据超出当前工作表维度时自动追加行/列 +- 目标区域如含合并单元格,合并将被打散,值正常写入 +- `--allow-overwrite` 默认 false,目标区域有数据时需显式传 `--allow-overwrite` 才能覆盖 +- `--csv` 支持三种输入:直接传文本、`@filepath` 从本地文件读取、`-` 从 stdin 管道读取 +- CSV 文本上限 2M 字符,单元格总数上限 30000 +- 特殊字符处理:CLI 会自动过滤 `\r`(Windows 换行符)和 BOM(UTF-8 文件头标记),Excel/Windows 导出的 CSV 可直接使用;如 CSV 数据中含零宽字符(U+200B 等)或 Bidi 控制符,CLI 会拒绝并报错 + +### 在指定位置插入行或列 +``` +Usage: + dws sheet insert-dimension [flags] +Example: + # 在第 3 行之前插入 2 行 + dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 + + # 在 A 列之前插入 1 列 + dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet insert-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 + + # 在 AB 列之前插入 3 列 + dws sheet insert-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 插入维度: ROWS 或 COLUMNS (必填) + --position string 插入位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 插入数量,正整数 (必填),最大 5000 +``` + +在钉钉表格指定工作表的指定位置之前插入若干空行或空列。 +`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 +支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +若需要在末尾追加行/列,请使用 `append` 命令。 + +### 删除指定位置的行或列 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet delete-dimension [flags] +Example: + # 从第 3 行开始删除 2 行 + dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "3" --length 2 + + # 从 A 列开始删除 1 列 + dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "A" --length 1 + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet delete-dimension --node --sheet-id --dimension ROWS --position "Sheet1!3" --length 5 + + # 从 AB 列开始删除 3 列 + dws sheet delete-dimension --node --sheet-id --dimension COLUMNS --position "AB" --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 删除维度: ROWS 或 COLUMNS (必填) + --position string 删除起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 删除数量,正整数 (必填),最大 5000 +``` + +在钉钉表格指定工作表中,从指定位置起删除若干连续的行或列。 +`--dimension ROWS` 时,`--position` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--position` 为列字母。 +支持在 `--position` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +删除后后续的行/列会向前移动填补空位;若需要仅清空内容但保留行/列占位,请使用 `range update` 将目标区域写入空字符串 `""`。 + +### 更新指定范围行/列属性 +``` +Usage: + dws sheet update-dimension [flags] +Example: + # 隐藏第 3~4 行 + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "3" --length 2 --hidden + + # 显示 A~B 列 + dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "A" --length 2 --hidden=false + + # 设置第 1~5 行行高为 40px + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "1" --length 5 --pixel-size 40 + + # 设置 C 列列宽为 200px 并隐藏 + dws sheet update-dimension --node --sheet-id --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden + + # 使用工作表前缀(忽略 --sheet-id) + dws sheet update-dimension --node --sheet-id --dimension ROWS --start-index "Sheet1!3" --length 2 --hidden +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 更新维度: ROWS 或 COLUMNS (必填) + --start-index string 起始位置,A1 表示法 (必填)。ROWS 时为行号如 "3";COLUMNS 时为列字母如 "A" + --length string 更新数量,正整数 (必填),最大 5000 + --hidden 是否隐藏 (true=隐藏, false=显示),与 --pixel-size 至少填其一 + --pixel-size int 行高或列宽(像素),ROWS 时为行高,COLUMNS 时为列宽,与 --hidden 至少填其一 +``` + +批量更新钉钉表格指定工作表中连续多行/多列的属性,支持设置显隐状态(hidden)与行高/列宽(pixelSize)。 +`--dimension ROWS` 时,`--start-index` 为 1-based 行号字符串;`--dimension COLUMNS` 时,`--start-index` 为列字母。 +支持在 `--start-index` 中携带工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id`。 +`--hidden` 与 `--pixel-size` 至少必须提供一个。当同时提供时,将先应用尺寸再应用显隐,任一失败整体失败。 +`--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽。 + +### 合并单元格 +``` +Usage: + dws sheet merge-cells [flags] +Example: + # 合并所有单元格(默认) + dws sheet merge-cells --node --sheet-id --range "A1:B3" + + # 按行合并 + dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows + + # 按列合并 + dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns + + # 使用带工作表前缀的范围(忽略 --sheet-id) + dws sheet merge-cells --node --sheet-id --range "Sheet1!A1:B3" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:B3 (必填) + --merge-type string 合并方式: mergeAll(默认)/mergeRows/mergeColumns +``` + +支持三种合并方式: +- `mergeAll`(默认):合并所有单元格,将选定区域内的所有单元格合并成一个 +- `mergeRows`:按行合并,在选定区域内将同一行相邻的单元格合并 +- `mergeColumns`:按列合并,在选定区域内将同一列相邻的单元格合并 + +注意:合并时只保留左上角单元格的值,其他单元格的值会被丢弃。 +`--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时将优先使用前缀解析出的工作表,忽略 `--sheet-id`。 + +### 上传附件到表格 +``` +Usage: + dws sheet media-upload [flags] +Example: + dws sheet media-upload --node --file ./report.pdf + dws sheet media-upload --node --file ./data.bin --name "数据文件.dat" --mime-type application/octet-stream +Flags: + --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) + --file string 本地文件路径 (必填) + --name string 附件显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) +``` + +### 上传图片并写入表格单元格 +``` +Usage: + dws sheet write-image [flags] +Example: + dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png + dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 +Flags: + --node string 目标表格文档的标识,支持传入 URL 或 ID (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格区域地址,如 A1:A1 (必填) + --file string 本地图片文件路径 (必填) + --name string 图片显示名称 (默认使用文件名) + --mime-type string 文件 MIME 类型 (默认根据扩展名推断) + --width int 图片显示宽度 (可选) + --height int 图片显示高度 (可选) +``` + +### 创建浮动图片 +``` +Usage: + dws sheet create-float-image [flags] +Example: + # 先上传图片获取 resourceUrl + dws sheet media-upload --node --file ./chart.png + # 输出: resourceUrl: /core/api/resources/img/xxxx... + + # 再创建浮动图片 + dws sheet create-float-image --node --sheet-id \ + --src "/core/api/resources/img/xxxx..." --range A1 --width 400 --height 300 + + # 带偏移量 + dws sheet create-float-image --node --sheet-id \ + --src "/core/api/resources/img/xxxx..." --range B2 --width 200 --height 150 --offset-x 10 --offset-y 20 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --src string 图片资源路径,通过 media-upload 获取的 resourceUrl (必填) + --range string 锚点单元格,A1 表示法,如 A1、B3 (必填) + --width int 图片宽度,像素,正整数 (必填) + --height int 图片高度,像素,正整数 (必填) + --offset-x int 水平偏移量,像素 (默认 0) + --offset-y int 垂直偏移量,像素 (默认 0) +``` + +浮动图片悬浮于单元格之上,不占用单元格内容,可自由定位和调整大小。 +- `--src` 必须是 `media-upload` 返回的 `resourceUrl`(格式为 `/core/api/resources/img/...`),不能直接传外部 URL +- `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) +- `--width` / `--height` 为必填,单位像素,必须为正整数 +- `--offset-x` / `--offset-y` 表示相对锚点单元格左上角的偏移量(像素),默认 0,不能为负数 + +### 获取浮动图片详情 +``` +Usage: + dws sheet get-float-image [flags] +Example: + dws sheet get-float-image --node --sheet-id --float-image-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) +``` + +获取单个浮动图片的详细信息,包括 ID、图片资源路径、锚点位置、尺寸和偏移量。 +`--float-image-id` 可通过 `list-float-images` 获取。 + +### 列出工作表所有浮动图片 +``` +Usage: + dws sheet list-float-images [flags] +Example: + dws sheet list-float-images --node --sheet-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +列出指定工作表中所有浮动图片,返回 `floatImages` 数组和 `totalCount`。 + +### 更新浮动图片属性 +``` +Usage: + dws sheet update-float-image [flags] +Example: + # 移动浮动图片到新位置 + dws sheet update-float-image --node --sheet-id --float-image-id --range C5 + + # 调整尺寸 + dws sheet update-float-image --node --sheet-id --float-image-id --width 600 --height 400 + + # 替换图片(需先 media-upload 新图片获取 resourceUrl) + dws sheet update-float-image --node --sheet-id --float-image-id \ + --src "/core/api/resources/img/xxxx..." +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) + --src string 新的图片资源路径,通过 media-upload 获取的 resourceUrl + --range string 新的锚点单元格,A1 表示法 + --width int 新的图片宽度,像素 + --height int 新的图片高度,像素 + --offset-x int 新的水平偏移量,像素 + --offset-y int 新的垂直偏移量,像素 +``` + +更新浮动图片的属性,`--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少传入一个。 +`--float-image-id` 可通过 `list-float-images` 获取。 + +### 删除浮动图片 +``` +Usage: + dws sheet delete-float-image [flags] +Example: + dws sheet delete-float-image --node --sheet-id --float-image-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --float-image-id string 浮动图片 ID (必填) +``` + +删除指定的浮动图片,操作不可恢复。`--float-image-id` 可通过 `list-float-images` 获取。 + +### 全局查找替换 +``` +Usage: + dws sheet replace [flags] +Example: + dws sheet replace --node --sheet-id --find "旧文本" --replacement "新文本" + dws sheet replace --node --sheet-id --find "待处理" --replacement "已完成" --match-entire-cell + dws sheet replace --node --sheet-id --find "\\d{4}" --replacement "****" --use-regexp + dws sheet replace --node --sheet-id --find "旧" --replacement "新" --range "A1:D100" + dws sheet replace --node --sheet-id --find "临时" --replacement "" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --find string 查找文本 (必填) + --replacement string 替换文本 (必填,可为空字符串表示删除) + --range string 替换范围,A1 表示法 (如 A1:D100) + --match-case 区分大小写 (默认 false) + --match-entire-cell 完整单元格匹配 + --use-regexp 启用正则表达式匹配 + --include-hidden 包含隐藏行/列 +``` + +返回被替换的单元格数量。`--replacement` 可以为空字符串,表示删除匹配内容。 + +### 移动行或列 +``` +Usage: + dws sheet move-dimension [flags] +Example: + # 将第 2 行移动到第 5 行的位置(索引从 0 开始) + dws sheet move-dimension --node --sheet-id \ + --dimension ROWS --start-index 1 --end-index 1 --destination-index 4 + + # 将第 2~4 行移动到第 1 行之前 + dws sheet move-dimension --node --sheet-id \ + --dimension ROWS --start-index 1 --end-index 3 --destination-index 0 + + # 将 B~C 列移动到 E 列的位置 + dws sheet move-dimension --node --sheet-id \ + --dimension COLUMNS --start-index 1 --end-index 2 --destination-index 4 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 维度类型: ROWS 或 COLUMNS (必填) + --start-index int 源起始索引,0-based (必填) + --end-index int 源结束索引,0-based,包含 (必填) + --destination-index int 目标位置索引,0-based (必填) +``` + +索引均为 0-based(第 1 行/列的索引为 0)。destination-index 不能在 [start-index, end-index] 范围内。 + +**destination-index 计算规则:** +destination-index 是目标位置的 0-based 索引,即移动到第 n 行/列则传 n-1: +- 通用公式:`destination-index = 目标行号(1-based) - 1` +- 例如:将第 2 行移到第 5 行位置 → `destination-index = 5 - 1 = 4`,即 `start-index=1, end-index=1, destination-index=4` +- 例如:将第 4 行移到第 1 行(最前面)→ `destination-index = 1 - 1 = 0`,即 `start-index=3, end-index=3, destination-index=0` + +### 追加空行或空列 +``` +Usage: + dws sheet add-dimension [flags] +Example: + dws sheet add-dimension --node --sheet-id --dimension ROWS --length 5 + dws sheet add-dimension --node --sheet-id --dimension COLUMNS --length 3 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --dimension string 维度类型: ROWS 或 COLUMNS (必填) + --length int 追加数量,正整数,最多 5000 (必填) +``` + +在工作表末尾追加指定数量的空行或空列。 + +### 取消合并单元格 +``` +Usage: + dws sheet unmerge-cells [flags] +Example: + dws sheet unmerge-cells --node --sheet-id --range "A1:D5" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 取消合并的范围,A1 表示法 (必填) +``` + +取消指定范围内所有合并的单元格,恢复为独立单元格。 + +### 设置下拉列表 +``` +Usage: + dws sheet set-dropdown [flags] +Example: + # 设置单选下拉列表 + dws sheet set-dropdown --node --sheet-id --range "A2:A100" \ + --options '[{"value":"选项1"},{"value":"选项2"},{"value":"选项3"}]' + + # 设置带颜色的多选下拉列表 + dws sheet set-dropdown --node --sheet-id --range "B2:B50" \ + --options '[{"value":"高","color":"#ff0000"},{"value":"中","color":"#ffaa00"},{"value":"低","color":"#00ff00"}]' \ + --multi-select +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 目标单元格范围,A1 表示法,如 A2:A100 (必填) + --options string 下拉选项 JSON 数组 (必填),如 '[{"value":"选项1","color":"#ff0000"}]' + --multi-select 是否允许多选(默认单选) +``` + +在指定单元格范围内设置下拉列表。设置后用户可从预定义选项中选择值。 +- **用途**:为单元格配置下拉列表,支持自定义选项颜色和多选。 +- **场景**:规范数据输入,如状态选择(完成/进行中/待处理)、优先级(高/中/低)等。 +- **注意**:选项值不能包含英文逗号;如果目标范围已存在下拉列表,会被新配置覆盖。 + +### 获取下拉列表配置 +``` +Usage: + dws sheet get-dropdown [flags] +Example: + dws sheet get-dropdown --node --sheet-id --range "A2:A100" + dws sheet get-dropdown --node --sheet-id --range "A1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 查询范围,A1 表示法,如 A1:A100 (必填) +``` + +查询指定范围内的下拉列表配置信息,包括选项值、颜色和是否多选。 +- **用途**:查看单元格已设置的下拉列表选项和配置。 +- **场景**:在修改下拉列表前先查询现有配置;确认下拉列表是否设置成功。 +- **返回**:`dataValidations` 数组,相同选项的单元格聚合为一组,每组包含 `conditionValues`(选项值)、`ranges`(覆盖范围)、`options`(含 `enableMultiSelect` 和 `colorValueMap`)。范围内无下拉列表时 `hasDropdown` 为 false。 + +### 删除下拉列表 +``` +Usage: + dws sheet delete-dropdown [flags] +Example: + dws sheet delete-dropdown --node --sheet-id --range "A2:A100" + dws sheet delete-dropdown --node --sheet-id --range "B1:D10" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 要删除下拉列表的范围,A1 表示法 (必填) +``` + +删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。 +- **用途**:移除不再需要的下拉列表约束。 +- **注意**:已填写的单元格值不会被清除;目标范围不存在下拉列表时操作仍返回成功。 + +### 获取筛选信息 +``` +Usage: + dws sheet filter get [flags] +Example: + dws sheet filter get --node --sheet-id + dws sheet filter get --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +获取指定工作表的全局筛选信息,返回筛选范围和各列的筛选条件详情。 +- **用途**:查看当前工作表上是否存在全局筛选及其配置。 +- **场景**:在修改或删除筛选前,先读取当前筛选配置;创建筛选前先确认是否已存在(每个工作表只能有一个筛选)。 +- **区分**:全局筛选(filter)影响所有协作者看到的数据展示;筛选视图(filter-view)是个人化的。 +- **返回**:`range`(筛选范围,A1 表示法)和 `columnFilterCriteria`(各列条件,key 为列偏移量)。如果未设置筛选,返回筛选信息为空。 + +### 创建筛选 +``` +Usage: + dws sheet filter create [flags] +Example: + # 创建筛选框架(不设条件) + dws sheet filter create --node --sheet-id --range "A1:E100" + + # 创建筛选并同时设置条件(按值筛选) + dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":1,"filterType":"values","visibleValues":["北京","上海"]}]' + + # 创建筛选并设置条件筛选 + dws sheet filter create --node --sheet-id --range "A1:E100" --criteria '[{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --range string 筛选范围,A1 表示法,须包含表头行 (必填) + --criteria string 筛选条件 JSON 数组 (可选) +``` + +在工作表中创建全局筛选。 +- **用途**:为工作表建立筛选器,使数据可按条件过滤展示。 +- **约束**:每个工作表只能有一个全局筛选,已存在时会报错。应先 `filter get` 确认不存在后再创建。 +- **range 规范**:必须包含表头行(如 `A1:E100`),不能只包含数据行。 +- **criteria 格式**:JSON 数组,每个元素含 `column`(列偏移量,从 0 开始)和筛选条件字段。不传则仅创建空筛选框架,后续可通过 `filter update` 设置条件。 + +### 删除筛选 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet filter delete [flags] +Example: + dws sheet filter delete --node --sheet-id --yes +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +删除工作表的全局筛选。 +- **用途**:移除筛选器,所有被隐藏的行将重新显示。 +- **不可逆**:删除后所有筛选条件丢失,需重新创建。 +- **前置**:工作表没有筛选时调用会报错,应先 `filter get` 确认存在。 + +### 批量更新筛选条件 +``` +Usage: + dws sheet filter update [flags] +Example: + # 同时设置多列的筛选条件 + dws sheet filter update --node --sheet-id --criteria '[{"column":0,"filterType":"values","visibleValues":["已完成","进行中"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50"}]}]' + + # 按颜色筛选 + dws sheet filter update --node --sheet-id --criteria '[{"column":1,"filterType":"color","backgroundColor":"#FF0000"}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --criteria string 筛选条件 JSON 数组 (必填) +``` + +批量更新筛选条件,可同时设置多列的筛选条件。 +- **用途**:一次性设置或替换多列的筛选条件。 +- **前置**:工作表必须已创建筛选(通过 `filter create`)。 +- **覆盖式**:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置。 +- **criteria 格式**:JSON 数组,支持三种 `filterType`: + - `values`:按值筛选,指定 `visibleValues` 数组 + - `condition`:按条件筛选,指定 `conditions` 数组(最多 2 个)和可选的 `conditionOperator`(`and`/`or`) + - `color`:按颜色筛选,指定 `backgroundColor` 或 `fontColor`(二选一) + +### 清除单列筛选条件 +``` +Usage: + dws sheet filter clear-criteria [flags] +Example: + # 清除第 2 列(B 列)的筛选条件 + dws sheet filter clear-criteria --node --sheet-id --column 1 + + # 清除第 1 列(A 列)的筛选条件 + dws sheet filter clear-criteria --node --sheet-id --column 0 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --column number 列偏移量,从 0 开始 (必填) +``` + +清除筛选中某一列的筛选条件。 +- **用途**:移除某列的筛选条件,该列不再参与筛选计算。 +- **区分**:仅清除指定列的条件,不删除整个筛选。如需删除整个筛选,使用 `filter delete`。 +- **幂等**:指定列没有设置筛选条件时调用不会报错。 + +### 筛选排序 +``` +Usage: + dws sheet filter sort [flags] +Example: + # 按第 1 列(A 列)升序排序 + dws sheet filter sort --node --sheet-id --column 0 --ascending + + # 按第 3 列(C 列)降序排序 + dws sheet filter sort --node --sheet-id --column 2 --ascending=false +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --column number 排序列偏移量,从 0 开始 (必填) + --ascending 是否升序,默认 true (可选) +``` + +对筛选范围内的数据按指定列排序。 +- **用途**:对数据行按某一列的值进行升序或降序排列。 +- **前置**:工作表必须已创建筛选(通过 `filter create`)。 +- **注意**:排序会实际改变工作表中数据行的物理顺序,不可撤销。 +- **column**:列偏移量从 0 开始,相对于筛选范围首列。 + +### 获取所有筛选视图 +``` +Usage: + dws sheet filter-view list [flags] +Example: + dws sheet filter-view list --node --sheet-id + dws sheet filter-view list --node "https://alidocs.dingtalk.com/i/nodes/" --sheet-id "Sheet1" +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) +``` + +获取指定工作表的所有筛选视图列表,返回每个筛选视图的 ID、名称和范围信息。 +- **用途**:查看当前工作表上已创建的所有筛选视图,获取视图 ID、名称和范围。 +- **场景**:在对筛选视图进行 update / delete / update-criteria 等操作前,先用 list 获取可用的 filterViewId。 +- **区分**:筛选视图(filter-view)是个人化的数据过滤方式,与全局筛选不同。每个用户可以创建自己的筛选视图,互不影响原始数据。如果没有筛选视图,返回空列表。 + +### 创建筛选视图 +``` +Usage: + dws sheet filter-view create [flags] +Example: + # 创建不带筛选条件的筛选视图 + dws sheet filter-view create --node --sheet-id --name "我的视图" --range "A1:E10" + + # 创建带按值筛选条件的筛选视图 + dws sheet filter-view create --node --sheet-id --name "销售筛选" --range "A1:E10" \ + --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]}]' + + # 创建带按条件筛选的筛选视图(大于等于 200000) + dws sheet filter-view create --node --sheet-id --name "高预算" --range "A1:C10" \ + --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --name string 筛选视图名称 (必填) + --range string 筛选视图范围,A1 表示法,如 A1:E10 (必填) + --criteria string 筛选条件,JSON 数组 (可选) +``` + +在指定工作表中创建一个筛选视图。 +- **用途**:为指定数据区域创建一个可命名的个人化筛选视图,可选同时设置筛选条件。 +- **场景**:用户需要针对某个数据区域建立固定的筛选视角(如"高绩效员工""研发部数据"),方便反复查看。 +- **区分**:与全局筛选不同,筛选视图是个人化的,不影响其他用户看到的数据。如果只需创建视图不设条件,后续可通过 `update-criteria` 单独设置;如果要一步到位,可通过 `--criteria` 在创建时直接设置。 +`--criteria` 为 JSON 数组,每个元素包含 `column`(列偏移量,从 0 开始)和筛选条件字段。支持三种筛选类型: +- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 +- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符(kebab-case):`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) +- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 + +### 更新筛选视图属性 +``` +Usage: + dws sheet filter-view update [flags] +Example: + # 更新筛选视图名称 + dws sheet filter-view update --node --sheet-id --filter-view-id --name "新名称" + + # 更新筛选视图范围 + dws sheet filter-view update --node --sheet-id --filter-view-id --range "A1:F20" + + # 更新筛选条件 + dws sheet filter-view update --node --sheet-id --filter-view-id \ + --criteria '[{"column":1,"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}]' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --name string 筛选视图新名称 + --range string 筛选视图新范围,A1 表示法 + --criteria string 筛选条件,JSON 数组 +``` + +更新筛选视图的名称、范围和/或筛选条件,`--name`、`--range`、`--criteria` 至少传入一个。 +- **用途**:修改已有筛选视图的名称、数据范围或筛选条件。 +- **场景**:数据区域扩展后需要扩大筛选视图范围,或重命名视图,或通过 `--criteria` 一次性批量更新多列筛选条件。 +- **区分**:`update` 可同时修改名称、范围和条件,适合批量更新;`update-criteria` 只能设置单列条件,适合精确控制某一列的筛选逻辑。`--criteria` 指定列的条件会被替换,未指定的列保持不变。 + +`--criteria` 为 JSON 数组,格式与 `filter-view create` 的 `--criteria` 相同,支持的筛选类型和操作符参见「创建筛选视图」说明。 + +### 删除筛选视图 + +> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 + +``` +Usage: + dws sheet filter-view delete [flags] +Example: + dws sheet filter-view delete --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +删除指定的筛选视图。 +- **用途**:永久删除一个不再需要的筛选视图及其所有筛选条件。 +- **场景**:筛选视图已过时或不再需要时,清理无用的视图。 +- **区分**:`delete` 删除整个筛选视图(包括所有列的条件),操作不可恢复;`delete-criteria` 只删除某一列的筛选条件,视图本身保留。此操作不影响全局筛选或其他筛选视图,也不影响原始数据。 + +### 更新筛选视图列条件 +``` +Usage: + dws sheet filter-view update-criteria [flags] +Example: + # 按值筛选:只显示"销售部"和"市场部" + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' + + # 按条件筛选:大于 100 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' + + # 按条件筛选:大于等于 200000 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater-equal","value":"200000"}]}' + + # 按条件筛选:小于 100 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"less","value":"100"}]}' + + # 多条件筛选:大于等于 60 且 小于等于 90 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditionOperator":"and","conditions":[{"operator":"greater-equal","value":"60"},{"operator":"less-equal","value":"90"}]}' + + # 按颜色筛选:背景色为红色 + dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 1 --filter-criteria '{"filterType":"color","backgroundColor":"#FF0000"}' +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) + --filter-criteria string 筛选条件,JSON 对象 (必填) +``` + +更新筛选视图中某一列的筛选条件。 +- **用途**:为筛选视图的指定列创建或更新筛选条件,控制该列哪些数据行可见。 +- **场景**:只显示某些特定值的行(如"只看研发部")→ `filterType: values`;按数值条件筛选(如"绩效 ≥ 85")→ `filterType: condition` + `operator: greater-equal`;按文本条件筛选(如"名称包含关键字")→ `filterType: condition` + `operator: contains`。 +- **区分**:`update-criteria` 精确控制单列条件,适合逐列设置不同的筛选逻辑;`filter-view update --criteria` 可以批量更新多列条件;`delete-criteria` 是 `update-criteria` 的逆操作,删除指定列的条件。 + +`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 +例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列,`--column 1` 代表 C 列。 + +`--filter-criteria` 为 JSON 对象,支持三种筛选类型: +- `values`:按值筛选,通过 `visibleValues` 指定允许显示的值列表 +- `condition`:按条件筛选,通过 `conditions` 指定条件列表(最多 2 个),每个条件包含 `operator` 和 `value`。支持的操作符:`equal`、`not-equal`、`contains`、`not-contains`、`starts-with`、`not-starts-with`、`ends-with`、`not-ends-with`、`greater`、`greater-equal`、`less`、`less-equal`。多条件之间通过 `conditionOperator` 指定逻辑关系:`and`(且,默认)或 `or`(或) +- `color`:按颜色筛选,通过 `backgroundColor` 或 `fontColor` 指定颜色值(十六进制,如 `#FF0000`),二选一 + +### 删除筛选视图列条件 +``` +Usage: + dws sheet filter-view delete-criteria [flags] +Example: + # 删除第 1 列(A 列)的筛选条件 + dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 0 + + # 删除第 3 列(C 列)的筛选条件 + dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id --column 2 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) +``` + +清除筛选视图中指定列的筛选条件。 +- **用途**:移除筛选视图中指定列的筛选条件,使该列不再参与过滤。 +- **场景**:之前通过 `update-criteria` 设置了某列的筛选条件,现在需要取消该列的筛选以显示全部数据。 +- **区分**:`delete-criteria` 只清除指定列的条件,筛选视图本身和其他列的条件保持不变;`delete` 会删除整个筛选视图。如果指定列没有设置筛选条件,调用此命令不会报错(幂等操作)。 + +### 获取单个筛选视图详情 +``` +Usage: + dws sheet filter-view info [flags] +Example: + # 查看指定筛选视图的详情 + dws sheet filter-view info --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +获取指定筛选视图的完整信息,包括 ID、名称、范围和筛选条件。 +- **用途**:查看某个筛选视图的当前配置,包括已设置的所有筛选条件详情。 +- **场景**:在修改或删除筛选视图前,先确认其当前状态;或在 `update-criteria` 后验证条件是否生效。 +- **区分**:`info` 返回单个视图的完整信息(含 criteria);`list` 返回所有视图的列表概要。`info` 需要指定 `--filter-view-id`,ID 可通过 `list` 获取。 +- **实现**:内部调用 `get_filter_views` 获取全部列表后按 ID 过滤。 + +### 列出筛选视图所有列条件 +``` +Usage: + dws sheet filter-view list-criteria [flags] +Example: + # 列出筛选视图的所有条件 + dws sheet filter-view list-criteria --node --sheet-id --filter-view-id +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) +``` + +列出指定筛选视图中已设置的所有列筛选条件。 +- **用途**:查看某个筛选视图当前设置了哪些列的筛选条件,包括每列的条件类型和具体规则。 +- **场景**:在管理筛选条件(修改/删除特定列条件)前,先了解当前视图有哪些条件;或排查筛选结果不符合预期时检查条件配置。 +- **区分**:`list-criteria` 返回所有列的条件(按列偏移量为 key 的对象);`get-criteria` 只返回指定列的条件。如果没有设置任何条件,返回空对象 `{}`。 +- **实现**:内部调用 `get_filter_views` 获取视图详情后提取 `criteria` 字段。 + +### 获取单列筛选条件 +``` +Usage: + dws sheet filter-view get-criteria [flags] +Example: + # 查看第 1 列(偏移量 0)的筛选条件 + dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 0 + + # 查看第 3 列(偏移量 2)的筛选条件 + dws sheet filter-view get-criteria --node --sheet-id --filter-view-id --column 2 +Flags: + --node string 表格文档 ID 或 URL (必填) + --sheet-id string 工作表 ID 或名称 (必填) + --filter-view-id string 筛选视图 ID (必填) + --column int 列偏移量,从 0 开始 (必填) +``` + +获取指定筛选视图中某一列的筛选条件详情。 +- **用途**:查看某个筛选视图中指定列当前设置的筛选条件,包括条件类型、运算符和比较值。 +- **场景**:在修改某列条件前,先查看其当前配置;或验证 `update-criteria` 后该列条件是否正确。 +- **区分**:`get-criteria` 只返回指定列的条件;`list-criteria` 返回所有列的条件。`--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。 +- **实现**:内部调用 `get_filter_views` 获取视图详情后按列偏移量过滤 `criteria` 中的对应条件。 + +### 导出表格为 xlsx(异步任务一站式) +``` +Usage: + dws sheet export [flags] # 一站式:提交 → 轮询 → 可选下载 +Example: + # 仅导出,返回 downloadUrl(链接有时效性,请尽快下载) + dws sheet export --node + dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" + + # 导出并自动下载为本地文件 + dws sheet export --node --output ./report.xlsx + + # --output 为目录时,自动按下载链接中的文件名保存 + dws sheet export --node --output ./ + +Flags: + --node string 表格文档 ID 或 URL (必填) + --output string 本地保存路径(可选,支持文件路径或目录) +``` + +将钉钉在线电子表格导出为 Office xlsx 格式。**单命令一站式**:命令内部自动完成「提交任务 → 渐进式退避轮询 → (可选)下载文件」全流程,AI Agent 无需自行拆分步骤或实现轮询。 + +**内部流程**: +1. 调 `submit_export_job` 获取 `jobId` +2. 按渐进式退避策略轮询 `query_export_job` 直至任务终态或超时 +3. 任务成功后取得 `downloadUrl`;若指定了 `--output`,自动 HTTP GET 下载 xlsx 到本地文件 + +**内置轮询策略(CLI 内实现,无需关心)**: +- 第 1~5 次:每次间隔 2 秒 +- 第 6~10 次:每次间隔 5 秒 +- 第 11~20 次:每次间隔 10 秒 +- 第 21~30 次:每次间隔 15 秒 +- **硬上限:最多轮询 30 次(约 5 分钟)**,超时后命令返回错误 + +**命令返回**: +- `--output` 未指定:进度日志 + 末尾输出 `jobId` 和 `downloadUrl`(链接有时效性,请尽快下载) +- `--output` 指定为文件路径:下载到该路径并输出 `导出完成: ` +- `--output` 指定为已存在目录:自动从 `downloadUrl` 推断文件名并保存到该目录下 + +**失败处理(命令内部已处理,Agent 仅需转述)**: +- MCP 返回 `FAILED`:命令立即返回错误并附带失败原因,**禁止自动重试 `dws sheet export`**,告知用户稍后再试 +- 轮询 30 次仍 `PROCESSING`:命令返回超时错误,告知用户稍后再试 + +**限制**:仅支持钉钉在线电子表格(alxs)→ xlsx。导出钉钉文字文档请使用 `doc` 产品对应的导出工具。 + +## 核心工作流 + +```bash +# ── 工作流 1: 创建表格并写入数据 ── + +# 1. 创建表格文档 — 提取 nodeId +dws sheet create --name "销售数据" --format json + +# 2. 查看工作表列表 — 提取 sheetId +dws sheet list --node --format json + +# 3. 写入表头和数据 +dws sheet range update --node --sheet-id --range "A1:C1" \ + --values '[["姓名","部门","销售额"]]' --format json + +dws sheet range update --node --sheet-id --range "A2:C4" \ + --values '[["张三","销售部",50000],["李四","市场部",38000],["王五","销售部",62000]]' --format json + +# ── 工作流 2: 读取已有表格数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 查看工作表详情(行列数、最后非空位置等) +dws sheet info --node --sheet-id --format json + +# 3. 读取全部数据 +dws sheet range read --node --sheet-id --format json + +# 4. 读取指定区域 +dws sheet range read --node --sheet-id --range "A1:D10" --format json + +# ── 工作流 3: 多工作表管理 ── + +# 1. 新建工作表 +dws sheet new --node --name "汇总" --format json + +# 2. 在新工作表中写入汇总公式 +dws sheet range update --node --sheet-id --range "A1:B1" \ + --values '[["指标","数值"]]' --format json + +dws sheet range update --node --sheet-id --range "A2:B2" \ + --values '[["总销售额","=SUM(Sheet1!C2:C100)"]]' --format json + +# ── 工作流 4: 写入数据并设置样式 ── + +# 1. 写入数据 +dws sheet range update --node --sheet-id --range "A1:C3" \ + --values '[["商品","单价","数量"],["苹果",5.5,100],["香蕉",3.2,200]]' --format json + +# 2. 设置数字格式(人民币)——请走 set-style,不要放到 range update +dws sheet range set-style --node --sheet-id --range "B2:B3" \ + --number-format "¥#,##0.00" --format json + +# 3. 写入超链接 +dws sheet range update --node --sheet-id --range "D1" \ + --hyperlinks '[[{"type":"path","link":"https://dingtalk.com","text":"详情"}]]' --format json + +# ── 工作流 5: 追加数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 查看工作表详情(确认列结构) +dws sheet info --node --sheet-id --format json + +# 3. 追加单行数据 +dws sheet append --node --sheet-id \ + --values '[["张三","销售部",50000]]' --format json + +# 4. 追加多行数据 +dws sheet append --node --sheet-id \ + --values '[["李四","市场部",38000],["王五","销售部",62000]]' --format json +``` + +```bash +# ── 工作流 6: 插入行或列 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 在第 3 行之前插入 2 行 +dws sheet insert-dimension --node --sheet-id \ + --dimension ROWS --position "3" --length 2 --format json + +# 3. 在 A 列之前插入 1 列 +dws sheet insert-dimension --node --sheet-id \ + --dimension COLUMNS --position "A" --length 1 --format json + +# 4. 使用工作表前缀指定位置 +dws sheet insert-dimension --node --sheet-id \ + --dimension ROWS --position "Sheet1!5" --length 3 --format json +``` + +```bash +# ── 工作流 6b: 删除行或列 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 从第 3 行开始删除 2 行 +dws sheet delete-dimension --node --sheet-id \ + --dimension ROWS --position "3" --length 2 --format json + +# 3. 从 A 列开始删除 1 列 +dws sheet delete-dimension --node --sheet-id \ + --dimension COLUMNS --position "A" --length 1 --format json + +# 4. 使用工作表前缀指定位置 +dws sheet delete-dimension --node --sheet-id \ + --dimension ROWS --position "Sheet1!5" --length 3 --format json +``` + +```bash +# ── 工作流 6c: 更新行/列属性(显隐、行高/列宽) ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 隐藏第 3~4 行 +dws sheet update-dimension --node --sheet-id \ + --dimension ROWS --start-index "3" --length 2 --hidden --format json + +# 3. 显示 A~B 列 +dws sheet update-dimension --node --sheet-id \ + --dimension COLUMNS --start-index "A" --length 2 --hidden=false --format json + +# 4. 设置第 1~5 行行高为 40px +dws sheet update-dimension --node --sheet-id \ + --dimension ROWS --start-index "1" --length 5 --pixel-size 40 --format json + +# 5. 设置 C 列列宽为 200px 并隐藏 +dws sheet update-dimension --node --sheet-id \ + --dimension COLUMNS --start-index "C" --length 1 --pixel-size 200 --hidden --format json +``` + +```bash +# ── 工作流 7: 搜索表格数据 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 基本搜索 — 在指定工作表中查找文本 +dws sheet find --node --sheet-id --find "销售额" --format json + +# 3. 在指定范围内搜索 +dws sheet find --node --sheet-id --find "合计" --range "A1:D100" --format json + +# 4. 正则搜索(不区分大小写) +dws sheet find --node --sheet-id --find "^total" --use-regexp --match-case=false --format json + +# 5. 精确匹配整个单元格 +dws sheet find --node --sheet-id --find "完成" --match-entire-cell --format json + +# 6. 搜索公式文本 +dws sheet find --node --sheet-id --find "SUM" --match-formula --format json +``` + +```bash +# ── 工作流 8: 合并单元格 ── + +# 1. 获取工作表列表 +dws sheet list --node --format json + +# 2. 合并所有单元格(默认 mergeAll) +dws sheet merge-cells --node --sheet-id --range "A1:B3" --format json + +# 3. 按行合并 +dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeRows --format json + +# 4. 按列合并 +dws sheet merge-cells --node --sheet-id --range "A1:C3" --merge-type mergeColumns --format json +``` + +```bash +# ── 工作流 9: 上传附件到表格 ── + +# 1. 基本用法: 上传本地文件到表格 +dws sheet media-upload --node --file ./report.pdf -f json + +# 2. 自定义附件显示名称 (--name 指定上传后在表格中显示的名称) +dws sheet media-upload --node --file ./data.csv --name "销售数据.csv" -f json + +# 3. 指定 MIME 类型 (文件扩展名无法推断时) +dws sheet media-upload --node --file ./data.bin --name "导出数据.dat" --mime-type application/octet-stream -f json + +# 4. 完整流程: 创建表格 → 上传附件 +dws sheet create --name "项目资料" -f json +# 提取 nodeId 后: +dws sheet media-upload --node --file ./design.pdf -f json +dws sheet media-upload --node --file ./timeline.xlsx --name "项目时间线.xlsx" -f json + +# ── 工作流 10: 写入图片到表格单元格 ── + +# 1. 基本用法: 写入图片到指定单元格 +dws sheet write-image --node --sheet-id --range A1:A1 --file ./chart.png -f json + +# 2. 指定显示尺寸 +dws sheet write-image --node --sheet-id --range B2:B2 --file ./logo.png --width 200 --height 100 -f json + +# 3. 自定义图片名称 +dws sheet write-image --node --sheet-id --range C3:C3 --file ./photo.jpg --name "产品图.jpg" -f json + +# 4. 完整流程: 创建表格 → 写表头 → 写入图片 +dws sheet create --name "产品目录" -f json +# 提取 nodeId 后: +dws sheet range update --node --sheet-id Sheet1 --range "A1:B1" --values '[["产品名称","产品图片"]]' -f json +dws sheet range update --node --sheet-id Sheet1 --range "A2:A2" --values '[["MacBook Pro"]]' -f json +dws sheet write-image --node --sheet-id Sheet1 --range B2:B2 --file ./macbook.png --width 150 --height 100 -f json +``` + +```bash +# ── 工作流 11: 筛选视图管理 ── + +# 1. 获取工作表列表 +dws sheet list --node -f json + +# 2. 查看已有筛选视图 +dws sheet filter-view list --node --sheet-id -f json + +# 3. 创建筛选视图(不带条件) +dws sheet filter-view create --node --sheet-id \ + --name "我的筛选" --range "A1:E100" -f json + +# 4. 为筛选视图设置列条件(按值筛选) +dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 0 --filter-criteria '{"filterType":"values","visibleValues":["销售部","市场部"]}' -f json + +# 5. 为筛选视图设置列条件(按条件筛选) +dws sheet filter-view update-criteria --node --sheet-id --filter-view-id \ + --column 2 --filter-criteria '{"filterType":"condition","conditions":[{"operator":"greater","value":"100"}]}' -f json + +# 6. 更新筛选视图名称和范围 +dws sheet filter-view update --node --sheet-id --filter-view-id \ + --name "销售数据筛选" --range "A1:F200" -f json + +# 7. 清除某列的筛选条件 +dws sheet filter-view delete-criteria --node --sheet-id --filter-view-id \ + --column 0 -f json + +# 8. 删除筛选视图 +dws sheet filter-view delete --node --sheet-id --filter-view-id -f json +``` + +```bash +# ── 工作流 11b: 创建带条件的筛选视图(一步完成) ── + +# 创建筛选视图时直接指定筛选条件 +dws sheet filter-view create --node --sheet-id \ + --name "高销售额视图" --range "A1:E100" \ + --criteria '[{"column":0,"filterType":"values","visibleValues":["销售部"]},{"column":2,"filterType":"condition","conditions":[{"operator":"greater","value":"50000"}]}]' \ + -f json +``` + +```bash +# ── 工作流 12: 导出表格为 xlsx(单命令一站式)── + +# 场景 A:仅获取下载链接(命令内部自动完成提交+轮询,最终返回 downloadUrl) +dws sheet export --node --format json +# 传入 URL 也可: +# dws sheet export --node "https://alidocs.dingtalk.com/i/nodes/" --format json + +# 场景 B:导出并自动下载为本地文件 +dws sheet export --node --output ./report.xlsx + +# 场景 C:下载到目录,自动按链接推断文件名 +dws sheet export --node --output ./ + +# 禁止在 Agent 侧实现任何轮询或重试,CLI 内部已按 2s/5s/10s/15s 渐进式退避自动完成(最多 30 次)。 +# 若命令返回失败或超时,直接告知用户稍后再试,不要自动重调 dws sheet export。 +``` + +## 上下文传递表 + +| 操作 | 从返回中提取 | 用于 | +|------|-------------|------| +| `create` | `nodeId` | list / info / new / range read / range update / find 的 --node | +| `list` | 工作表的 `sheetId` | info / range read / range update / find 的 --sheet-id | +| `new` | 新工作表的 `sheetId` | range read / range update / find 的 --sheet-id | +| `info` | `rowCount` / `lastNonEmptyRow` | 确定数据范围、追加写入起始行 | +| `find` | `matchedCells` 中的 `a1Notation` | 定位目标单元格,用于 range read / range update | +| `append` | `a1Notation` 追加数据所在范围 | 确认追加位置 | +| `csv-put` | `a1Notation` 实际写入的单元格范围 | 确认写入位置和范围 | +| `insert-dimension` | `a1Notation` 新插入区域范围 | 确认插入位置和范围 | +| `delete-dimension` | `a1Notation` 被删除区域范围 | 确认删除位置和范围 | +| `update-dimension` | `a1Notation` 被更新区域范围、`hidden` 生效的显隐状态、`pixelSize` 生效的尺寸 | 确认更新结果 | +| `merge-cells` | `a1Notation` 实际被合并的范围、`mergeType` 生效的合并方式 | 确认合并结果 | +| `media-upload` | `resourceId`、`resourceUrl` | 附件已上传到表格;`resourceUrl` 可用于 `create-float-image` 的 `--src` | +| `write-image` | `resourceId` | 图片已写入指定单元格 | +| `create-float-image` | `floatImage`(含 `id`、`src`、`range`、`width`、`height`、`offsetX`、`offsetY`) | `id` 用于后续 get / update / delete 的 `--float-image-id` | +| `get-float-image` | `floatImage`(完整信息) | 查看单个浮动图片详情 | +| `list-float-images` | `floatImages` 数组、`totalCount` | 获取所有浮动图片的 `id`,用于后续操作 | +| `update-float-image` | `floatImage`(更新后的完整信息) | 确认更新结果 | +| `delete-float-image` | `message` | 确认删除完成 | +| `replace` | `replaceCount` 被替换的单元格数量 | 确认替换结果 | +| `move-dimension` | `sheetId` 工作表 ID | 确认操作完成 | +| `add-dimension` | `sheetId` 工作表 ID | 确认操作完成 | +| `unmerge-cells` | `sheetId` 工作表 ID | 确认操作完成 | +| `set-dropdown` | `range` 实际设置范围、`optionCount` 选项数量、`enableMultiSelect` 是否多选 | 确认下拉列表设置成功 | +| `get-dropdown` | `hasDropdown` 是否存在下拉、`dataValidations` 下拉配置列表(含 `conditionValues`、`ranges`、`options`) | 查看已有下拉配置 | +| `delete-dropdown` | `range` 实际删除范围 | 确认下拉列表删除完成 | +| `filter-view list` | `filterViews` 筛选视图列表(含 `id`、`name`、`range`) | 获取 filterViewId 用于 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria | +| `filter-view info` | `id`、`name`、`range`、`criteria` | 查看单个视图完整配置,确认条件是否生效 | +| `filter-view create` | `id` 筛选视图 ID、`name`、`range` | 用于后续 update / delete / update-criteria / delete-criteria 的 --filter-view-id | +| `filter-view update` | `id`、`name`、`range`、`criteria` | 确认更新结果 | +| `filter-view delete` | `id` 被删除的筛选视图 ID | 确认删除完成 | +| `filter-view update-criteria` | `id` 筛选视图 ID | 确认条件设置完成 | +| `filter-view delete-criteria` | `id` 筛选视图 ID | 确认条件清除完成 | +| `filter-view list-criteria` | 所有列条件(按列偏移量为 key 的对象) | 了解当前视图已设置哪些列的条件 | +| `filter-view get-criteria` | 指定列的条件详情(`filterType`、`conditions` 等) | 查看某列的具体筛选规则 | +| `export` | `downloadUrl`(未指定 --output)/ `导出完成: `(指定 --output) | 直接下发给用户或告知文件已保存到本地。命令内部已完成轮询,不要再调用其他 export 相关命令 | + +## nodeId 多格式说明 + +所有 `--node` 参数同时支持文档 ID、文档 URL、表格分享链接,系统自动识别。详细格式和提取规则请参见前文「URL 识别与 NODE_ID 提取」章节。 + +> ** 禁止使用 `dentryId`**:`drive list` 返回结果中同时包含 `dentryId`(纯数字,如 `218595998810`)和 `fileId`(UUID 格式,如 `ZgpG2NdyVXYOR2D5UGDok65MJMwvDqPk`)两个字段。sheet 的 `--node` 和 `--folder` 参数**只能使用 `fileId`(UUID 格式)**,不能使用纯数字的 `dentryId`,传入纯数字会导致命令失败。 + +## values 参数格式说明 + +`--values` 为二维 JSON 数组,第一维为行,第二维为列: +- 字符串值: `"文本"` +- 数字值: `100` 或 `3.14` +- 公式: `"=SUM(B2:B4)"`(以 `=` 开头的字符串自动识别为公式) +- 清空单元格: 统一使用空字符串 `""`(不要用 `null` 取代,null 不会保留原值且全 null 会被视为无效调用跳过) + +维度必须与 `--range` 范围一致,例如 `--range "A1:B3"` 需要 3 行 2 列的数组。 + +## hyperlinks 参数格式说明 + +`--hyperlinks` 为二维 JSON 数组,每个元素为对象或 null: +- `type`: 链接类型,可选 `path`(外部链接)、`sheet`(工作表跳转)、`range`(单元格跳转) +- `link`: 链接地址 +- `text`: 显示文本 + +与 `--values` 共存时,hyperlinks 优先级更高。 + +## number-format 常用值 + +适用范围:`number-format` 仅在 `range set-style` / `range batch-set-style` 中接受(CLI 对应 `--number-format`,batch 配置文件对应 `numberFormat`);`range update` 不接受该参数。 + +| 格式代码 | 说明 | 示例 | +|----------|------|------| +| `General` | 常规 | 1234.5 | +| `@` | 文本 | 001234 | +| `#,##0` | 整数千分位 | 1,235 | +| `#,##0.00` | 两位小数 | 1,234.50 | +| `0%` | 百分比 | 85% | +| `yyyy/m/d` | 日期 | 2026/3/15 | +| `hh:mm:ss` | 时间 | 14:30:00 | +| `¥#,##0` | 人民币 | ¥1,235 | + +## 注意事项(完整版) + +> 标 ★ 的条目已在前文「关键注意事项」中列出,此处为完整说明。 + +- ★ `--sheet-id` 获取规范(强制):所有涉及 `--sheet-id` 参数的命令(`info` / `new` / `range read` / `range update` / `find` / `append` / `insert-dimension` / `delete-dimension` / `update-dimension` / `move-dimension` / `add-dimension` / `merge-cells` / `unmerge-cells` / `replace` / `write-image` / `set-dropdown` / `get-dropdown` / `delete-dropdown` / `filter-view *` 等),除非用户主动提供了工作表 ID 或工作表名称,否则在 `sheetId` 未知时必须先通过 `dws sheet list --node --format json` 查询真实的 `sheetId` / 工作表名称后再调用,禁止凭空编造(如臆测为 `Sheet1`、`sheet1`、`0`、`default` 等);用户仅给出工作表名称时,也应通过 `list` 校验该名称是否存在,避免名称大小写或拼写不一致导致失败 +- ★ `range update` 维度校验(强制):调用 `range update` 写入 `--values` 或 `--hyperlinks` 时,必须严格校验二维 JSON 数组的行数与列数与 `--range` 指定的范围完全一致: + - 例如 `--range "A1:C3"` 表示 3 行 × 3 列,`--values` 必须是 `[[v1,v2,v3],[v4,v5,v6],[v7,v8,v9]]` 这样 3×3 的数组 + - `--range "A1"` 表示 1 行 × 1 列,`--values` 必须是 `[[v]]` + - 行数不足需要用空字符串补齐,列数不足需要补齐到每行相同长度;禁止出现各行列数不一致或与 `--range` 不匹配的情况,否则调用会直接报错 + - 同时传入 `--values` 和 `--hyperlinks` 时,两个二维数组的行列数都必须与 `--range` 严格一致 +- ★ `range update` 清空单元格规范(强制):如需清空单元格内容,统一使用空字符串 `""`。禁止使用 `null`:`null` 不会保留单元格原值,也不存在"选择性保留"场景;且若 `--values` 全部为 `null`,整体调用会被视为无效而跳过,无任何写入效果 +- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录 +- `list` 返回所有工作表的 ID 和名称,是后续操作的必要前置步骤 +- `info` 不传 `--sheet-id` 时默认返回第一个工作表的详情 +- `range read` 不传 `--range` 时默认读取整个工作表的全部非空数据 +- `range read` 的 `--range` 支持 `Sheet1!A1:D10` 格式直接指定工作表(此时忽略 `--sheet-id`) +- `range read` 遇到超时或响应过慢时,应缩小 `--range` 查询范围,**单次读取的单元格数量建议控制在 5000 个以内**;数据量较大时通过 `info` 获取边界后分批读取,避免不传 `--range` 直接读取整个大工作表 +- `range update` 的 `--values` 和 `--hyperlinks` 至少传入一项 +- `range update` 职责边界:`range update` 仅写入单元格的值与超链接,不接受任何样式参数(包括但不限于数字格式 / 背景色 / 字体 / 对齐方式等)。如需设置数字格式,请使用 `dws sheet range set-style --number-format <格式代码>`;批量场景走 `dws sheet range batch-set-style --batch `(配置文件中使用 `numberFormat` 字段)。不要在同一次 `range update` 调用里同时完成写值与格式设置 +- ★ `range update` / `range set-style` / `range batch-set-style` 单次调用上限(强制):行数 ≤ 1000,单元格总数(行×列)建议≤ 5000(底层硬限 30000);超限请拆分多次调用。CLI 会在调用前做本地预校验,底层超 30000 会直接报错 +- `range set-style` / `range batch-set-style` 的样式枚举按驼峰书写:`wordWrap` 取 `overflow`/`clip`/`autoWrap`,`fontWeight` 取 `bold`/`normal`,`hAlign` 取 `left`/`center`/`right`/`general`,`vAlign` 取 `top`/`middle`/`bottom`;背景色/字体颜色统一使用 `#RRGGBB` 格式 +- `new` 创建工作表时,如名称与已有工作表重复,系统会自动重命名 +- `find` 返回匹配单元格的地址(A1 表示法)和值,无匹配时返回空数组 +- `find` 的 `--match-entire-cell` 用于精确匹配:只返回单元格内容完全等于搜索文本的结果,不会匹配包含该文本的单元格(例如搜索"苹果"时,只匹配"苹果",不匹配"苹果手机""苹果汁"等)。用户说"精确搜索/完全匹配/只搜等于XX的"时必须使用此参数 +- `find` 的 `--match-case` 默认为 true(区分大小写),设为 false 可忽略大小写 +- `find` 的 `--use-regexp` 启用后,`--find` 参数作为正则表达式处理 +- ★ 当用户要求搜索/查找表格数据时,使用 `find` 命令,不要用 `range read` 读取全量数据后自行过滤——`find` 支持服务端搜索,效率更高、语义更准确 +- `append` 自动定位到最后一行有数据的位置下方插入,无需手动计算行号 +- `append` 的 `--values` 二维数组中每行的列数必须一致,否则会报错。如果用户提供的数据中各行长度不同,必须先将短行用空字符串 `""` 补齐到与最长行相同的列数后再调用。追加的数据列数也应与工作表已有数据列数保持一致 +- `append` vs `range update`:追加新行用 `append`,修改已有单元格用 `range update` +- `insert-dimension` 在指定位置之前插入空行或空列,不写入数据;如需在末尾追加行/列,使用 `append` +- `insert-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `insert-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `insert-dimension` 的 `--length` 最大为 5000 +- `delete-dimension` 从指定位置起删除若干连续的行或列,删除后后续行/列向前移动填补空位 +- `delete-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `delete-dimension` 的 `--position` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `delete-dimension` 的 `--length` 最大为 5000 +- `delete-dimension` 若需仅清空内容但保留行/列占位,请使用 `range update` 将目标区域写入空字符串 `""`(参见《range update 清空单元格规范》) +- `update-dimension` 批量更新连续行/列的显隐状态与行高/列宽 +- `update-dimension` 的 `--dimension` 只接受 `ROWS` 或 `COLUMNS` +- `update-dimension` 的 `--start-index` 支持工作表前缀(如 `Sheet1!3`),此时忽略 `--sheet-id` +- `update-dimension` 的 `--length` 最大为 5000 +- `update-dimension` 的 `--hidden` 与 `--pixel-size` 至少必须提供一个 +- `update-dimension` 的 `--pixel-size` 单位为像素,`dimension=ROWS` 时表示行高、`dimension=COLUMNS` 时表示列宽 +- `update-dimension` 当同时提供 `--hidden` 与 `--pixel-size` 时,将先应用尺寸再应用显隐,任一失败整体失败 +- `merge-cells` 合并时只保留左上角单元格的值,其他单元格的值会被丢弃 +- `merge-cells` 的 `--merge-type` 不传时默认为 `mergeAll`(合并所有单元格) +- `merge-cells` 的 `--range` 支持带工作表前缀的写法(如 `Sheet1!A1:B3`),此时忽略 `--sheet-id` +- `merge-cells` 如果目标区域与其他合并单元格、锁定区域或表格区域存在交集,合并将失败 +- `media-upload` 是两步自动完成的流程 (获取附件上传凭证 → OSS 上传),无需手动分步操作 +- `write-image` 是三步自动完成的流程 (获取附件上传凭证 → OSS 上传 → 写入图片到单元格),无需手动分步操作 +- ★ 向表格单元格中写入图片必须使用 `write-image`,禁止使用 `range update`。`range update` 底层调用的 `update_range` MCP 工具不支持图片类型参数,调用会失败 +- `write-image` 与 `media-upload` 的区别:`media-upload` 仅上传附件到表格获取 resourceId;`write-image` 在上传后还会将图片写入指定单元格 +- `create-float-image` 创建浮动图片前必须先通过 `media-upload` 上传图片获取 `resourceUrl`,再将其作为 `--src` 传入。`--src` 的格式为 `/core/api/resources/img/...`,不能直接传外部 URL +- `create-float-image` 的 `--range` 使用 A1 表示法指定锚点单元格(如 `A1`、`B3`),支持带工作表前缀(如 `Sheet1!A1`) +- `create-float-image` 的 `--width` / `--height` 为必填,单位像素,必须为正整数;`--offset-x` / `--offset-y` 可选,默认 0,不能为负数 +- `write-image`(单元格内嵌图片)vs `create-float-image`(浮动图片):`write-image` 将图片写入单元格内部,占据单元格内容;`create-float-image` 创建悬浮于单元格之上的浮动图片,不占用单元格内容,可自由调整位置和大小 +- `update-float-image` 的 `--src` / `--range` / `--width` / `--height` / `--offset-x` / `--offset-y` 至少必须提供一个 +- `list-float-images` 返回 `floatImages` 数组和 `totalCount`,每个元素包含 `id`(用于后续 get / update / delete) +- `delete-float-image` 操作不可恢复,删除后图片将从工作表中移除 +- `replace` 的 `--find` 不能为空字符串,`--replace` 可以为空字符串(表示删除匹配内容) +- `replace` 的 `--match-case` 默认为 false(不区分大小写),与 `find` 的默认行为不同 +- ★ `replace` vs `range update`:需要批量替换文本时,必须使用 `replace` 命令,禁止用 `range update` 手动重写单元格来实现替换效果。`replace` 支持服务端全局替换,效率更高且会返回替换计数 +- ★ `move-dimension` vs `range update`:需要移动行或列时,必须使用 `move-dimension` 命令,禁止用 `range update` 读取数据后手动重写来模拟移动效果。`move-dimension` 是原子操作,能保留单元格的格式、合并状态等属性 +- `move-dimension` 的索引均为 0-based(第 1 行/列的索引为 0),`endIndex` 包含在移动范围内 +- `move-dimension` 的 `--destination-index` 不能在 [start-index, end-index] 范围内 +- `move-dimension` 的移动跨度(end-index - start-index + 1)不超过 5000 +- `move-dimension` 的 `--destination-index` 是目标位置的 0-based 索引,即移动到第 n 行/列则传 `n - 1`(通用公式:`destination-index = 目标行号(1-based) - 1`) +- `add-dimension` vs `range update`:需要在末尾追加空行/空列时,必须使用 `add-dimension` 命令,禁止用 `range update` 写空数据来模拟追加效果 +- `add-dimension` 追加的是空行/空列,与 `append`(追加带数据的行)不同 +- `add-dimension` 的 `--length` 必须为正整数(>= 1),行列均不超过 5000 +- `unmerge-cells` 取消指定范围内所有合并单元格,使用 A1 表示法指定范围 +- `set-dropdown` 在指定范围内设置下拉列表,`--options` 为 JSON 数组,每个元素包含 `value`(必填)和 `color`(可选,`#RRGGBB` 格式)。选项值不能包含英文逗号。`--multi-select` 启用多选模式。如果目标范围已存在下拉列表,会被新配置覆盖 +- `get-dropdown` 查询指定范围内的下拉列表配置,返回 `dataValidations` 数组,相同选项的单元格聚合为一组。无下拉列表时 `hasDropdown` 为 false +- `delete-dropdown` 删除指定范围内的下拉列表配置,单元格恢复为普通文本格式。已填写的值不会被清除。目标范围不存在下拉列表时操作仍返回成功 +- ★ **全局筛选(filter)与筛选视图(filter-view)的区别**:全局筛选影响所有协作者看到的数据展示,每个工作表最多一个;筛选视图是个人化的,互不影响。用户只说"筛选"时默认走 `filter` 系列 +- `filter get` 获取工作表的全局筛选信息,返回 `range`(筛选范围)和 `columnFilterCriteria`(各列条件)。无筛选时返回空 +- `filter create` 创建全局筛选时 `--range` 必须包含表头行(如 `A1:E100`),不能只包含数据行。每个工作表只能有一个筛选,已存在时报错 +- `filter create` 的 `--criteria` 可选,不传则仅创建空筛选框架,后续通过 `filter update` 设置条件 +- `filter delete` 删除后所有筛选条件丢失且所有被隐藏行重新显示,不可恢复 +- `filter delete` 工作表没有筛选时调用会报错,应先 `filter get` 确认存在 +- `filter update` 是覆盖式:指定列的条件会被替换,未指定的列保持不变。如只想修改某一列,建议先 `filter get` 读取现有配置再 patch +- `filter update` 前置:工作表必须已创建筛选 +- `filter clear-criteria` 仅清除指定列的条件,不删除整个筛选。指定列无条件时不报错(幂等) +- `filter sort` 会实际改变数据行的物理顺序,不可撤销。前置:工作表必须已创建筛选 +- ★ **筛选操作规范**(参照飞书 core-operations): + - 当用户要求"筛选/只看/仅保留 X"时,**必须**通过 `filter create` / `filter update` 创建真实的筛选器。**禁止**用"删除不符合条件的行"或"新建工作表只放符合条件的行"来代替 + - 创建/更新筛选后**必须** `filter get` 回读验证配置正确 + - 更新已有筛选前先 `filter get` 读取当前配置,确认目标存在且了解现有条件后再操作 + - 筛选条件的列索引(`column`)必须与实际数据列精确对应,不要凭猜测填写 + - 筛选不支持正则表达式,传入正则会当成普通文本处理 +- `filter-view list` 获取指定工作表的所有筛选视图列表,返回的 `id` 可用于后续 info / update / delete / update-criteria / delete-criteria / list-criteria / get-criteria 的 `--filter-view-id` +- `filter-view info` 获取单个筛选视图的完整信息(含 criteria),内部复用 `get_filter_views` MCP 按 ID 过滤 +- `filter-view list-criteria` 列出指定筛选视图已设置的所有列条件,返回按列偏移量为 key 的对象;无条件时返回空对象 `{}` +- `filter-view get-criteria` 获取指定列的条件详情,`--column` 为列偏移量(从 0 开始);该列无条件时返回错误提示 +- `filter-view create` 创建筛选视图时 `--range` 应包含表头行。`--criteria` 可选,不传则创建后无筛选条件,后续可通过 `filter-view update-criteria` 设置 +- `filter-view update` 的 `--name`、`--range`、`--criteria` 至少需要传入一个,未指定的字段保持不变 +- `filter-view update` 的 `--criteria` 中指定列的条件会被替换,未指定的列保持不变 +- `filter-view delete` 删除后该视图及其所有筛选条件将被永久移除,不可恢复 +- `filter-view delete` 不影响全局筛选或其他筛选视图 +- `filter-view update-criteria` 的 `--column` 为列偏移量(从 0 开始),相对于筛选视图范围首列。例如筛选视图范围为 `B1:E10`,则 `--column 0` 代表 B 列 +- `filter-view update-criteria` 设置条件后立即在该筛选视图中生效,仅影响当前视图,不影响全局筛选或其他筛选视图 +- `filter-view update-criteria` 的 `--filter-criteria` 中 `conditions` 最多 2 个条件,多条件之间通过 `conditionOperator` 指定逻辑关系(`and` 或 `or`) +- `filter-view delete-criteria` 仅清除指定列的条件,不会删除整个筛选视图。如需删除整个筛选视图,请使用 `filter-view delete` +- `filter-view delete-criteria` 如果指定列没有设置筛选条件,调用不会报错 +- 筛选视图相关操作需要"可阅读"权限(list / info / list-criteria / get-criteria)或"可编辑"权限(create / update / delete / update-criteria / delete-criteria),不支持跨组织操作 +- ★ `export` 仅支持钉钉在线电子表格(alxs)→ xlsx;传入钉钉文字文档会报 `invalidRequest.document.typeIllegal` +- ★ `export` 为单命令一站式,CLI 内部已自动完成「提交 → 渐进式退避轮询 → 可选下载」,**Agent 不得在外部实现轮询或重试**;命令返回成功后不再调用其他 export 相关命令 +- `export` 内置轮询策略:1~5 次间隔 2s、6~10 次间隔 5s、11~20 次间隔 10s、21~30 次间隔 15s,硬上限 30 次(约 5 分钟);超时后命令返回错误,告知用户稍后再试即可 +- ★ `export` 命令返回失败或超时时,**禁止自动重调 `dws sheet export`**;直接告知用户导出失败并建议稍后再试 +- `export` 未指定 `--output` 时,返回的 `downloadUrl` 具有时效性,获取后请尽快下载;若用户需要本地文件,优先直接传 `--output` 让 CLI 代为下载 +- `export` 的 `--output` 可为文件路径或已存在目录;为目录时自动从 `downloadUrl` 推断文件名,为文件路径时直接按该路径保存 +- 用户要求"导出表格/下载 xlsx"时,必须使用 `export` 单命令,禁止用 `range read` 读全量数据后自行拼 xlsx 模拟导出(服务端导出会保留格式/合并/公式等完整属性) +- `update` 的 `--title`、`--index`、`--hidden`、`--frozen-row-count`、`--frozen-column-count` 至少必须提供一个 +- `update` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 +- `update` 的 `--index` 为 0-based 非负整数,0 表示移动到最前面 +- `update` 的 `--hidden` 设为 true 时,至少需要保留一个可见的工作表,不能将所有工作表都隐藏 +- `update` 的 `--frozen-row-count` / `--frozen-column-count` 为非负整数,不能超过工作表的总行数/列数,设为 0 表示取消冻结 +- `update` 当同时提供多个属性时,所有属性将在同一次请求中更新 +- `copy` 复制操作会将源工作表的所有内容(包括数据、格式、公式等)完整复制到新工作表 +- `copy` 的 `--title` 可选,不传时系统自动生成名称(通常为"源名称 副本"或类似格式) +- `copy` 的 `--title` 最长 100 字符,不能包含 `/ \ ? * [ ] :` 等特殊字符 +- `copy` 当指定名称与已有工作表重复时,系统会自动重命名为合法值 +- `copy` 的 `--index` 可选,不传时副本将放置在源工作表之后的默认位置 +- ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) +- sheet 产品线仅支持 `axls`(在线电子表格,`contentType=ALIDOC`),不支持 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 +- 遇到未知 `alidocs` URL 时,必须先 probe(`dws doc info --node --format json`)确认 `contentType` 和 `extension`,才能决定是否走 sheet +- 当节点 `extension=xlsx` / `xls` / `xlsm` / `csv`(`contentType≠ALIDOC`)时,必须用 `dws doc download --node --output <路径>` 先下载到本地再处理,禁止调用任何 sheet 子命令(sheet 底层 MCP 工具只识别 axls,调用 xlsx 节点必失败) +- 要把在线表格导出为 xlsx 文件——走 `dws sheet export`(axls → xlsx 的格式转换);要读已有的 xlsx 文件——走 `dws doc download` 后在本地解析,两者方向相反 diff --git a/skills/multi/dingtalk-sheet/references/url-patterns.md b/skills/multi/dingtalk-sheet/references/url-patterns.md index c65276e1..908e07ef 100644 --- a/skills/multi/dingtalk-sheet/references/url-patterns.md +++ b/skills/multi/dingtalk-sheet/references/url-patterns.md @@ -98,7 +98,7 @@ Step 3 → 按下方路由规则映射到对应产品 > axls vs xlsx 关键区分: > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws doc download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) -> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(envelope schema 里有 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx +> - 用户想把在线表格导出为 xlsx 文件 → 开源 dws CLI 暂未暴露在线表格导出能力(旧动态 schema 曾包含 `submit_export_job` / `query_export_job`,但当前 cobra 未注册),需要在钉钉客户端手动导出 xlsx ### 示例 diff --git a/skills/multi/dingtalk-skill/SKILL.md b/skills/multi/dingtalk-skill/SKILL.md index 593369b9..3a3d79a7 100644 --- a/skills/multi/dingtalk-skill/SKILL.md +++ b/skills/multi/dingtalk-skill/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[skill.md](references/skill.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | diff --git a/skills/multi/dingtalk-skill/references/skill.md b/skills/multi/dingtalk-skill/references/skill.md index 2b52d8f3..d9a416c3 100644 --- a/skills/multi/dingtalk-skill/references/skill.md +++ b/skills/multi/dingtalk-skill/references/skill.md @@ -15,30 +15,9 @@ Example: Flags: --query string 搜索关键词 (必填) --page string 页码 (默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page --size string 每页数量 (默认 10) ``` -### 错误排查 -``` -Usage: - dws devdoc error diagnose [flags] -Example: - dws devdoc error diagnose --request-id 15r6h45w0muec --format json - dws devdoc error diagnose --error-code 33012 --error-message "missing scope" --format json -Flags: - --query string 原始排查问题 - --request-id string 开放平台 requestId - --trace-id string requestId 的兼容别名 - --error-code string 错误码 - --error-message string 错误描述,会合并进原始问题 - --api string API 名称,会合并进原始问题作为补充检索词 - --context string 额外排查上下文,会合并进原始问题 - --page int 分页页码 (默认 1) - --cursor string 分页游标,翻页传上次返回的 nextCursor;传入后不再使用 --page - --size int 分页大小 (默认 10) -``` - ## live — 直播 ### 查看我的直播列表 @@ -112,8 +91,7 @@ Args: ## 意图判断 -- 用户说"开发文档/API 文档/接口文档" → `devdoc article search` -- 用户说"调用报错/requestId/traceId/错误码/错误描述" → `devdoc error diagnose`;若返回 `PARAM_ERROR - 未找到指定工具`,降级 `devdoc article search` 并标记后端工具注册待闭环 +- 用户说"开发文档/API 文档/接口文档/调用报错" → `devdoc article search` - 用户说"直播/我的直播" → `live stream list` - 用户说"搜索技能/找技能/安装技能/技能市场" → `skill search` / `skill install`(按步骤衔接) @@ -121,8 +99,7 @@ Args: | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `devdoc article search` | 文档链接、nextCursor | 直接展示给用户;hasMore=true 时用 --cursor 翻页 | -| `devdoc error diagnose` | diagnosticInfo、references、materials、nextCursor | 排查开放平台调用错误;hasMore=true 时用 --cursor 翻页;不可用时退回文档搜索 | +| `devdoc article search` | 文档链接 | 直接展示给用户 | | `skill search` | `skillId`、名称、描述 | 用户选型后传给 `skill install ` | | `skill install` | 安装成功/失败信息 | 确认目标 Agent 目录已注册 | | `skill publish` | 发布结果(成功或错误信息) | 确认企业技能库已更新 | diff --git a/skills/multi/dingtalk-todo/SKILL.md b/skills/multi/dingtalk-todo/SKILL.md index d6d7494a..0fc38887 100644 --- a/skills/multi/dingtalk-todo/SKILL.md +++ b/skills/multi/dingtalk-todo/SKILL.md @@ -18,18 +18,11 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[todo.md](references/todo.md);剧本:[02-task.md](references/02-task.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | @@ -53,6 +46,7 @@ metadata: - `--id` / `--ids` 是隐藏兼容别名,文档和生成命令统一写 `--task-id`,减少模型漂移。 - 优先级映射:低=10,普通=20,较高/高/重要=30,紧急/最高/P0/马上处理=40;不要把"较高"写成 40。 - 截止时间必须是 ISO-8601。相对日期按当前日期计算;例如周五说"下周二"就是紧接下一个自然周的周二,不要再加一周。 +- 附件命令 `task add-attachment` / `list-attachment` / `remove-attachment` 服务端无对应工具,当前不可用,不要生成。 - 创建、标记完成、重开、删除后必须 `task get` 或对应 `task list --status ...` 验证,不要只凭创建返回或口头计划结束。 - 所有 dws 命令带 `--format json`。 diff --git a/skills/multi/dingtalk-wiki/SKILL.md b/skills/multi/dingtalk-wiki/SKILL.md index 875d7147..16d82170 100644 --- a/skills/multi/dingtalk-wiki/SKILL.md +++ b/skills/multi/dingtalk-wiki/SKILL.md @@ -18,35 +18,26 @@ metadata: -> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 +> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。 > 命令参考:[wiki.md](references/wiki.md)。 -## 开放平台文档 RAG / 错误码排查 - -- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。 -- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。 -- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按 root `dws` / `dws-shared` 的错误处理执行;`devdoc` 用于开放平台业务错误码和接口语义排查。 -- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。 - ## 意图表 | 用户说 | 命令 | |--------|------| -| "创建知识库" | `dws wiki space create --name "<名称>" [--desc "<描述>"]` | -| "搜索知识库空间" | `dws wiki space search --query "<关键词>" [--limit <1-20>]` | +| "创建知识库" | `dws wiki space create --name "<名称>" [--description "<描述>"]` | +| "搜索知识库空间" | `dws wiki space search --keyword "<关键词>" [--limit <1-20>]` | | "我的文档 / 个人知识库" | `dws wiki space list --type myWikiSpace` | | "列出组织知识库" | `dws wiki space list [--type orgWikiSpace] [--limit <1-50>]` | ## 评测高频硬约束 -- `space search` 用 `--query`;`search` 支持 `--type myWikiSpace` 查询个人知识库,但按类型列出空间优先走 `space list --type myWikiSpace/orgWikiSpace`。 +- `space search` 用 `--keyword`,不要用 `--query`;`search` 支持 `--type myWikiSpace` 查询个人知识库,但按类型列出空间优先走 `space list --type myWikiSpace/orgWikiSpace`。 - 用户说"我的文档/个人空间/my workspace"时优先用 `dws wiki space list --type myWikiSpace --format json`。 -- 用户给空关键词时,不要构造空 `--query ""`;若语义是我的文档则走 `space list --type myWikiSpace`,否则请用户补关键词。 +- 用户给空关键词时,不要构造空 `--keyword ""`;若语义是我的文档则走 `space list --type myWikiSpace`,否则请用户补关键词。 - 搜到空间后复用返回的 `workspaceId/id`,知识库内具体文档的创建、搜索、读写切到 `dingtalk-doc`,不要在 `wiki` 下编造 doc 子命令。 -- `workspaceId` 是知识库空间 ID,只能用于 `wiki space/member --workspace`、`doc --workspace` 或 `doc search --workspace-ids`;不要传给 `doc list --folder`,也不要使用不存在的 `--space-id`。 -- 读取知识库内指定文档固定链路:`wiki space search/list` 取 `workspaceId` → `doc search --query "<文档名>" --workspace-ids ` 取 `nodeId` → `doc read --node `。 - 所有 `dws wiki` 命令加 `--format json`。 ## 跨产品协作 diff --git a/skills/multi/dingtalk-wiki/references/wiki.md b/skills/multi/dingtalk-wiki/references/wiki.md index 7c61c7b0..f6bc47df 100644 --- a/skills/multi/dingtalk-wiki/references/wiki.md +++ b/skills/multi/dingtalk-wiki/references/wiki.md @@ -30,33 +30,13 @@ Usage: dws wiki space create [flags] Example: dws wiki space create --name "产品文档库" --format json - dws wiki space create --name "技术方案" --desc "团队技术方案归档" --format json + dws wiki space create --name "技术方案" --description "团队技术方案归档" --format json Flags: --name string 知识库名称 (必填,不超过 100 字符) - --desc string 知识库描述 (选填,不超过 500 字符) + --description string 知识库描述 (选填,不超过 500 字符) --icon string 知识库图标标识 (选填) ``` -### 删除知识库 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws wiki space delete [flags] -Example: - dws wiki space delete --workspace - dws wiki space delete --workspace "https://alidocs.dingtalk.com/i/spaces/xxx/overview" -Flags: - --workspace string 知识库 ID 或 URL (必填) -``` - -将指定知识库移入回收站。删除后知识库会进入回收站,可在回收站中恢复。 - -> **重要约束**: -> - 操作者必须具备知识库的 OWNER 角色。 -> - 删除操作不可逆(从回收站恢复除外),请确认后再执行。 - ### 查看知识库详情 ``` Usage: @@ -82,7 +62,7 @@ Example: Flags: --type string 知识库类型: myWikiSpace / orgWikiSpace (默认 orgWikiSpace) --limit string 每页数量 1-50 (默认 20) - --cursor string 分页游标 (首页留空) + --page-token string 分页游标 (首页留空) ``` - `myWikiSpace`:返回当前用户的「我的文档」个人空间(固定 1 条,不支持分页) @@ -93,65 +73,47 @@ Flags: Usage: dws wiki space search [flags] Example: - dws wiki space search --query "产品文档" --format json - dws wiki space search --query "技术方案" --limit 20 --format json - dws wiki space search --type myWikiSpace --format json + dws wiki space search --keyword "产品文档" --format json + dws wiki space search --keyword "技术方案" --limit 20 --format json + dws wiki space list --type myWikiSpace --format json Flags: - --query string 搜索关键词 (--type myWikiSpace 时可省略) + --keyword string 搜索关键词 (--type myWikiSpace 时可省略) --type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库 --limit string 返回数量 1-20 (默认 10) ``` -当 `--type myWikiSpace` 时,忽略 `--query`,直接返回「我的文档」个人空间。 +当 `--type myWikiSpace` 时,忽略 `--keyword`,直接返回「我的文档」个人空间。 ### 添加知识库成员(容器级授权) ``` Usage: dws wiki member add [flags] Example: - dws wiki member add --workspace --users uid1 --role READER - dws wiki member add --workspace --users uid1,uid2 --role EDITOR - dws wiki member add --workspace "https://alidocs.dingtalk.com/i/spaces//overview" --users uid1 --role MANAGER + dws wiki member add --workspace --user uid1 --role READER + dws wiki member add --workspace --user uid1,uid2 --role EDITOR + dws wiki member add --workspace "https://alidocs.dingtalk.com/i/spaces//overview" --user uid1 --role MANAGER Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --workspace string 目标知识库 ID 或 URL (必填) + --user string 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) --role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读) ``` -> **重要约束**: +> **❗ 重要约束**: > - 仅支持 USER 类型。 > - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。 > - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 -> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。如果你的目标只是把某篇文档分享给别人,请改用 `dws drive permission add` 在节点级别授权。 - -### 移除知识库成员 -``` -Usage: - dws wiki member remove [flags] -Example: - dws wiki member remove --workspace --users uid1 - dws wiki member remove --workspace --users uid1,uid2 -Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 被移除的用户 userId 列表,逗号分隔 (必填,单次最多 30 个) -``` - -> **重要约束**: -> - OWNER 角色不可通过此接口移除。 -> - 操作者需具备知识库的 OWNER 或 MANAGER 权限。 -> - 移除后相关用户将无法访问该知识库下的内容(除非通过节点级权限另行授权)。 -> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**。 +> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。如果你的目标只是把某篇文档分享给别人,节点级权限授权在开源 dws v1.0.30 暂不支持,请在钉钉客户端文档中通过「分享」设置。 ### 修改知识库成员角色 ``` Usage: dws wiki member update [flags] Example: - dws wiki member update --workspace --users uid1 --role EDITOR - dws wiki member update --workspace --users uid1,uid2 --role READER + dws wiki member update --workspace --user uid1 --role EDITOR + dws wiki member update --workspace --user uid1,uid2 --role READER Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --users strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) + --workspace string 目标知识库 ID 或 URL (必填) + --user string 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个) --role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER ``` @@ -164,161 +126,35 @@ Example: dws wiki member list --workspace --limit 100 dws wiki member list --workspace --filter-role EDITOR Flags: - --workspace string 目标知识库 ID 或 URL (必填) - --limit int 返回数量上限,最大 200 (默认 50) - --filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填) + --workspace string 目标知识库 ID 或 URL (必填) + --limit int 返回成员数上限 + --filter-role string 按角色过滤,逗号分隔: MANAGER / EDITOR / DOWNLOADER / READER (选填) ``` > 接口不支持游标分页,使用 `--limit` 一次性拉取。 -### 列出知识库节点 -``` -Usage: - dws wiki node list [flags] -Aliases: - list, ls -Example: - dws wiki node list --workspace --format json - dws wiki node list --workspace --folder --format json - dws wiki node list --workspace --limit 20 --cursor --format json -Flags: - --workspace string 知识库 ID (必填) - --folder string 父节点 nodeId (选填,不传则列出根目录) - --limit int 每页数量 (默认 50,最大 50) - --cursor string 分页游标 -``` - -### 在知识库中创建节点 -``` -Usage: - dws wiki node create [flags] -Example: - dws wiki node create --workspace --name "新文档" --format json - dws wiki node create --workspace --name "方案目录" --type folder --format json - dws wiki node create --workspace --name "数据表" --type asheet --folder --format json -Flags: - --workspace string 知识库 ID (必填) - --name string 节点名称 (必填) - --type string 节点类型: adoc / asheet / folder / axls (默认 adoc) - --folder string 父节点 nodeId (选填,不传则在根目录创建) -``` - -### 复制知识库节点 -``` -Usage: - dws wiki node copy [flags] -Example: - dws wiki node copy --workspace --node --format json - dws wiki node copy --workspace --node --folder --format json -Flags: - --workspace string 知识库 ID (必填) - --node string 源节点 ID (必填) - --folder string 目标文件夹 nodeId (选填) -``` - -### 移动知识库节点 -``` -Usage: - dws wiki node move [flags] -Example: - dws wiki node move --workspace --node --folder --format json - dws wiki node move --workspace --node --format json -Flags: - --workspace string 知识库 ID (必填) - --node string 源节点 ID (必填) - --folder string 目标文件夹 nodeId (选填) -``` - -### 删除知识库节点 - -> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。 - -``` -Usage: - dws wiki node delete [flags] -Example: - dws wiki node delete --workspace --node - dws wiki node delete --workspace --node --yes -Flags: - --workspace string 知识库 ID (必填,用于权限校验) - --node string 节点 ID (必填) -``` - -将知识库中的节点移入回收站。权限要求: 对节点有"管理"权限。 - -### 在知识库中搜索节点 -``` -Usage: - dws wiki node search [flags] -Example: - dws wiki node search --workspace --query "方案" --format json - dws wiki node search --workspace --query "周报" --limit 10 --format json - dws wiki node search --workspace --query "设计" --extensions adoc,asheet --format json -Flags: - --workspace string 知识库 ID (必填) - --query string 搜索关键词 (必填) - --extensions string 按文件类型过滤,逗号分隔: adoc,asheet 等 (选填) - --limit int 每页数量 (选填) - --cursor string 分页游标 (选填) -``` - -在指定知识库空间内搜索节点。与 `drive search` 的区别: -- `wiki node search` — 限定在某个知识库空间内搜索(需要 `--workspace`) -- `drive search` — 全局搜索,聚合钉盘 + 文档空间结果 - -### 列出空间(支持钉盘空间类型) - -`wiki space list` 除了支持知识库类型(`orgWikiSpace` / `myWikiSpace`),还支持钉盘空间类型: - -``` -Usage: - dws wiki space list --type orgSpace --format json # 钉盘企业空间 - dws wiki space list --type mySpace --format json # 钉盘「我的文件」 - dws wiki space list --type orgWikiSpace --format json # 知识库(默认) - dws wiki space list --type myWikiSpace --format json # 我的文档 -Flags: - --type string 空间类型: - orgWikiSpace (默认) — 组织知识库 - myWikiSpace — 我的文档个人空间 - orgSpace — 钉盘企业空间 - mySpace — 钉盘「我的文件」 - --limit string 每页数量 1-50 (默认 20) - --cursor string 分页游标 (首页留空) -``` - -> 钉盘空间类型(`orgSpace` / `mySpace`)会自动路由到钉盘 MCP 服务,等同于原 `drive list-spaces`(已 deprecated)。 - ## 意图判断 - 用户说"创建知识库/新建知识库" → `space create` - 用户说"查看知识库/知识库详情" → `space get` - 用户说"我的知识库/知识库列表/有哪些知识库" → `space list` -- 用户说"列出钉盘空间/钉盘团队空间" → `space list --type orgSpace` - 用户说"搜索知识库/找知识库" → `space search` - 用户说"我的文档/个人空间" → `space list --type myWikiSpace` -- 用户说"知识库下的文件/知识库里有哪些文档/浏览知识库内容" → `node list`(需 `--workspace`) -- 用户说"在知识库里搜文档/空间内搜索" → `node search`(需 `--workspace` + `--query`) -- 用户说"在知识库里创建文档/新建文件夹" → `node create`(需 `--workspace` + `--name`) -- 用户说"复制知识库里的文档" → `node copy`(需 `--workspace` + `--node`) -- 用户说"移动知识库里的文档" → `node move`(需 `--workspace` + `--node`) -- 用户说"删除知识库里的文档/节点" → `node delete`(需 `--workspace` + `--node`) - 用户说"把知识库分享给某人/给某人加入知识库/邀请进知识库" → `member add`(需 `--workspace` + `--user` + `--role`) - 用户说"修改某人在知识库的权限/调整成员角色" → `member update` -- 用户说"移除知识库成员/把某人从知识库移除/删除知识库成员" → `member remove`(需 `--workspace` + `--users`) - 用户说"知识库有哪些成员/查看知识库成员" → `member list` -- 用户说"删除知识库/移除知识库/把知识库删了" → `space delete`(需 `--workspace`) -> **跨产品路由说明**:知识库节点的**内容操作**(读取/编辑/块级操作)仍由 `dws doc` 承担: ->- 用户说"读某个知识库里的某篇文档" → 先 `node list` 拿到 nodeId,再走 **`dws doc read --node `** ->- 用户说"搜文件"(不指定空间) → 走 **`dws drive search`**(全局聚合搜索) +> **跨产品路由(重要)**:`dws wiki` 只管知识库容器(space/member),**不提供查看知识库文件/文档的能力**。以下意图必须走 `dws doc`,不要在 wiki 下尝试 `node`/`file`/`list` 等子命令: +> - 用户说"知识库下的文件/知识库里有哪些文档/浏览知识库内容" → 先用 `dws wiki space list` 或 `space search` 拿到 `workspaceId`,再走 **`dws doc list --workspace `** +> - 用户说"读某个知识库里的某篇文档" → 先通过上面的方式找到 nodeId,再走 **`dws doc read --node `** +> - 用户说"在知识库里搜文档" → 走 **`dws doc search --workspace-ids `** +> - 用户说"在知识库里创建文档" → 走 **`dws doc create --workspace `** -关键区分(两层模型): -- **wiki node**(空间管理层:节点的列出/创建/复制/移动/删除/搜索)vs **doc**(内容层:读写/编辑/块级/评论/导出)vs **drive**(存储层:文件上传/下载/搜索/权限,不关心格式) -- **wiki node search**(空间内搜索,需 `--workspace`)vs **drive search**(全局搜索,聚合钉盘+文档空间) -- **wiki node create**(在空间中创建空文件实体)vs **doc create**(创建文档并写入内容) -- **wiki member**(容器级,授权整个知识库)vs **doc permission / drive permission**(节点级,授权单篇文档) - - 「我的文档」**只能用** `doc permission` / `drive permission`,不能用 `wiki member` -- **wiki space list --type orgSpace/mySpace**(列出钉盘空间)vs **wiki space list**(默认列出知识库) +关键区分: +- wiki(知识库空间级管理:创建/查询/列出/搜索/成员管理) vs doc(文档内容级操作:搜索/读写/编辑/节点级权限) +- wiki space(知识库容器) vs drive(钉盘文件存储/上传/下载) +- **wiki member**(容器级,授权整个知识库);节点级 `doc permission` 在开源 dws v1.0.30 不可用 + - 「我的文档」当前开源版本不能通过 CLI 改权限;在钉钉客户端用「分享」设置 ## 核心工作流 @@ -330,110 +166,40 @@ dws wiki space list --format json dws wiki space list --type myWikiSpace --format json # 搜索知识库 -dws wiki space search --query "产品" --format json +dws wiki space search --keyword "产品" --format json + +# 搜索「我的文档」 +dws wiki space list --type myWikiSpace --format json # 创建知识库 -dws wiki space create --name "新项目文档" --desc "项目相关文档归档" --format json +dws wiki space create --name "新项目文档" --description "项目相关文档归档" --format json # 查看知识库详情 dws wiki space get --workspace --format json -# ── 工作流: 浏览知识库内容 ── - -# 1. 获取知识库 ID -dws wiki space list --format json - -# 2. 列出根目录节点 -dws wiki node list --workspace --format json - -# 3. 进入子目录 -dws wiki node list --workspace --folder --format json - -# 4. 读取文档内容(跨到 doc) -dws doc read --node --format json - -# ── 工作流: 在知识库中创建文档 ── - -# 1. 创建文档节点 -dws wiki node create --workspace --name "新方案" --format json - -# 2. 创建文件夹 -dws wiki node create --workspace --name "方案归档" --type folder --format json - -# 3. 在指定文件夹下创建 -dws wiki node create --workspace --name "子文档" --folder --format json - -# ── 工作流: 在知识库中搜索 ── - -# 在指定知识库内搜索 -dws wiki node search --workspace --query "方案" --format json - -# 按文件类型过滤 -dws wiki node search --workspace --query "周报" --extensions adoc --format json - -# ── 工作流: 列出钉盘空间 ── - -# 列出钉盘企业空间 -dws wiki space list --type orgSpace --format json - -# 获取钉盘「我的文件」 -dws wiki space list --type mySpace --format json - -# ── 工作流: 复制/移动节点 ── - -# 复制节点到另一个文件夹 -dws wiki node copy --workspace --node --folder --format json - -# 移动节点到另一个文件夹 -dws wiki node move --workspace --node --folder --format json - -# ── 工作流: 删除知识库节点 ── - -# 删除节点(会要求确认) -dws wiki node delete --workspace --node - # ── 工作流: 给知识库加成员 ── # 1. 先确认知识库 ID(避免授权到「我的文档」) dws wiki space list --format json # 注意:不要 --type myWikiSpace # 2. 添加成员 -dws wiki member add --workspace --users --role EDITOR --format json +dws wiki member add --workspace --user --role EDITOR --format json # 3. 查看当前成员 dws wiki member list --workspace --format json - -# ── 工作流: 移除知识库成员 ── - -# 1. 查看当前成员 -dws wiki member list --workspace --format json - -# 2. 移除成员 -dws wiki member remove --workspace --users --format json - -# ── 工作流: 删除知识库 ── - -# 1. 确认知识库信息 -dws wiki space get --workspace --format json - -# 2. 删除知识库 -dws wiki space delete --workspace --format json ``` ## 上下文传递表 | 操作 | 从返回中提取 | 用于 | |------|-------------|------| -| `space create` | `workspaceId` | node list / member add 的 --workspace | -| `space list` | `workspaceId` | node list / member add 的 --workspace | -| `space search` | `workspaceId` | node list / member add 的 --workspace | +| `space create` | `workspaceId` | space get 的 --workspace / member add 的 --workspace | +| `space list` | `workspaceId` | space get 的 --workspace / member add 的 --workspace | +| `space search` | `workspaceId` | space get 的 --workspace / member add 的 --workspace | | `space get` | `spaceUrl` | 分享给用户 | -| `node list` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | -| `node search` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | -| `node create` | `nodeId` | node copy/move/delete 的 --node / `dws doc read` 的 --node | -| `member list` | `userId` | member update 的 --users / member remove 的 --users | +| `member list` | `userId` | member update 的 --user | ## 相关产品 -- [doc](./doc.md) — 内容层:文档读写/编辑/块级操作/评论/导出(仅对自研文档有意义) -- [drive](./drive.md) — 存储层:文件列出/搜索/上传/下载/复制/移动/重命名/删除/权限(不关心文件格式) +- [doc](./doc.md) — 文档内容级操作(搜索/读写/编辑文档、知识库内文档管理) +- [drive](./drive.md) — 钉盘文件存储/上传/下载