Compare commits

...
2 Commits
Author SHA1 Message Date
Ari 97390829dd fix(compat,helpers): json_parse YAML fallback + send-by-bot product routing (#161)
* fix(compat): accept YAML flow as a fallback for json_parse transform

Users who run `dws aitable table create --fields [{fieldName: 标题, ...}]`
hit a shell glob error (`zsh: bad pattern: [{fieldName:`) because the
unquoted brackets are interpreted as a zsh pattern. Even when the shell
is placated with outer quotes, the strict-JSON parser still rejected the
natural form that drops quotes around keys and string values.

transformJSONParse now tries strict JSON first (unchanged fast path) and,
on failure, falls back to yaml.Unmarshal. YAML's flow syntax is a
superset of JSON that accepts `{key: value}` without surrounding quotes,
so a single set of outer quotes is all the user needs:

  dws aitable table create --fields '[{fieldName: 标题, type: text}]'

Already-valid strict JSON is unaffected. The @file syntax (`--fields
@path.json`) continues to work through the same transform.

The terminal error message, if both parsers fail, now points users at
the two working forms (quoted YAML flow or @file).

* fix(helpers): route chat send-by-bot to bot product

`chat message send-by-bot` previously stamped its invocation with
CanonicalProduct="chat", routing it through the chat product's auth /
server dispatch. The command is semantically a bot operation — switch
it to "bot" so auth client selection and downstream routing match the
actual MCP server. Extends chat_test.go with single-chat and group-chat
routing assertions.
2026-04-24 01:36:32 +08:00
Ari 7828089081 feat: subcommand merging, schema pretty, IR overlays, v1.0.15 notes (#160)
* feat(compat): merge same-name subcommands under shared parent

When multiple server entries attach to the same parent and their cli.command
collides with a subcommand already in the parent tree, the incoming subcommand's
children are merged recursively into the existing one instead of producing a
duplicate sibling. Leaf-name collisions resolve first-wins.

Fixes the duplicate `group` / `message` rows in `dws chat --help` that surfaced
when bot capabilities were distributed across chat.group.members and
chat.message subtrees.

* feat(ir): carry FlagOverlay and ToolAnnotations into canonical catalog

FlagOverlay mirrors the per-parameter CLI overlay (alias, transform,
transformArgs, env default, default value, hidden) sourced from
market.CLIToolOverride.Flags.

ToolAnnotations mirrors MCP 2025+ tool annotations with nullable hints
(destructive / read-only / idempotent / open-world) so absence means
"unknown" rather than "false".

BuildCatalog now propagates both through the canonical layer, with tests
covering group metadata and flag-overlay passthrough.

* feat(output): add -f pretty for schema-aware colored output

dws schema now supports -f pretty alongside json / raw. Pretty mode
partitions output by product / tool / parameter / enum and applies ANSI
color so humans can browse the catalog without piping through jq.
json / raw behaviour is unchanged.

docs/reference.md gains a Schema Introspection section documenting
dws schema and the new pretty format.

* feat(skills): refresh 13-product references; add devdoc / drive / oa

SKILL.md cli_version bumped to >=1.0.15; product index reordered to match
the v1.0.15 command surface.

New references:
- devdoc.md: Open Platform documentation search
- drive.md: DingTalk drive (promoted out of Coming soon)
- oa.md: OA approval end-to-end flow

Major rewrites for aitable (dashboard / chart share, import/export,
attachment flow), chat (bot capabilities merged into chat.message and
chat.group.members), calendar (event suggest / attachments), doc (comment
subtree, file create, upload/download flow), and minutes (list
mine/shared/all unified, record subcommands split).

Minor alignment touch-ups for contact / report / todo flag names.

* docs(release): sync v1.0.15 notes — 159 commands across 13 products

README Key Services fully refreshed: chat 23, calendar 14, aitable 41,
doc 21, minutes 19; drive promoted out of Coming soon; workbench and
standalone bot rows removed; Quick Start expanded to 7 examples spanning
doc / minutes / drive; Coming soon trimmed to mail / conference / aiapp /
live / wiki.

Adds docs/command-index.md, an auto-generated English listing of all 159
runtime commands with description and when-to-use guidance aimed at AI
agents. Replaces the ad-hoc command-index.pre.* / command-index.full.*
snapshots used during development.

CHANGELOG 1.0.15 records the compat subcommand-merging feature, the new
command index, and a flag-naming cleanup across chat / calendar / drive /
minutes / contact / devdoc.
2026-04-24 00:01:22 +08:00
31 changed files with 3176 additions and 275 deletions
+42
View File
@@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file.
The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and this project follows [Semantic Versioning](https://semver.org/).
## [1.0.15] - 2026-04-23
Compat layer gains **subcommand merging** under shared parents so multiple server entries can contribute into the same `dws <parent> <branch>` subtree without producing duplicate `--help` rows. Ships with a fresh auto-generated command index doc, a README sync to **159 commands across 13 products**, and a wide-ranging flag-naming cleanup that standardises CLI flags across chat, calendar, drive, minutes, contact, and devdoc commands.
### Added
- **`internal/compat` subcommand merging via `attachOrMerge`** — when two or more server entries attach to the same parent (e.g. `parent: "chat"`) and their `cli.command` collides with an existing subcommand in the parent's tree, the new subcommand's children are merged recursively into the existing one instead of creating a duplicate sibling. Leaf-name collisions resolve first-wins. Fixes the "double `group` / `message` rows in `dws chat --help`" symptom when bot capabilities are distributed across `chat.group.members` and `chat.message`.
- **`docs/command-index.md`** — a single, English, auto-generated listing of every runtime command the `dws` CLI exposes under the pre environment (159 total). Each entry carries a description and a "when to use" column aimed at AI agents. Replaces the earlier `command-index.pre.*` / `command-index.full.*` ad-hoc snapshots.
### Changed
- **README Key Services table** (`README.md` + `README_zh.md`) fully synced to the shipped command surface:
- `Chat`: 20 → **23** (bot capabilities merged in; new `list-all` / `list-focused` / `list-unread-conversations` / `conversation-info` exposed)
- `Calendar`: 13 → **14**
- `AI Tables`: 37 → **41** (chart / dashboard public-share config rows)
- `Doc`: 16 → **21** (comment subtree + `file create`)
- `Minutes`: 22 → **19** (single-tool `record`, `list query`, `list-by-keyword-range` pruned)
- New `Drive` row (6 commands) — promoted out of "Coming soon"
- `Workbench` row and standalone `Bot` row removed
- Total revised to **159 commands across 13 products**
- **Quick Start** expanded to 7 examples covering `doc`, `minutes`, `drive` in addition to `contact`, `calendar`, `todo`
- **Coming soon** trimmed to 5: `mail`, `conference`, `aiapp`, `live`, `wiki`
- **Reference & Docs** section now leads with a pointer to the new `docs/command-index.md`
- **Flag naming cleanup** — CLI flags across chat, calendar, drive, minutes, contact, and devdoc have been standardised so the names users type match the product-skill documentation. Notable flags:
- `dws contact user search` / `dws contact dept search` / `dws devdoc article search` now take `--query` (previously `--keyword`)
- `dws chat message list` / `dws chat message search` / `dws chat message list-mentions` / `dws chat conversation-info` / `dws chat message send` now take `--group` for the target conversation (previously `--id`) and `--open-dingtalk-id` (previously `--open-id`)
- `dws chat message list-by-sender` now takes `--sender-user-id` / `--sender-open-dingtalk-id` (previously `--user` / `--open-id`)
- `dws chat message list-topic-replies` now takes `--group` / `--topic-id` / `--limit` / `--time` (previously `--id` / `--topic` / `--size` / `--start`)
- `dws chat search-common` now takes `--match-mode` (previously `--mode`)
- `dws drive list` now takes `--max` / `--thumbnail` (previously `--max-results` / `--with-thumbnail`)
- `dws calendar event suggest` now takes `--users` / `--duration` / `--timezone` (previously `--attendee-user-ids` / `--duration-minutes` / `--time-zone`)
- `dws minutes list mine` / `dws minutes list shared` now take `--max` (previously `--max-results`) and gain `--query` / `--start` / `--end`
- `dws minutes list all` no longer exposes the legacy `--__scope__` internal alias
- **Flag coverage additions** — `dws calendar event create` / `update` gain `--attendees`, `--open-dingtalk-ids`, `--timezone`; `dws chat message send` gains file-message flags (`--dentry-id`, `--file-name`, `--file-size`, `--file-type`, `--media-id`, `--msg-type`, `--space-id`) plus `--open-dingtalk-id` / `--user`; `dws chat message list` gains `--open-dingtalk-id` / `--user`; `dws aitable table delete` gains `--reason`; `dws calendar participant add` gains `--optional`; `dws todo task create` gains `--recurrence`.
### Tests
- 3 new unit tests in `internal/compat/dynamic_commands_test.go`:
- `TestBuildDynamicCommands_ParentMergeSameName` — two servers with identical `command` + `parent` collapse into a single merged subcommand
- `TestBuildDynamicCommands_ParentMergeRecursive` — recursive merge through nested groups (e.g. `chat.group.members`)
- `TestBuildDynamicCommands_ParentMergeLeafCollision` — identical leaf paths resolve first-wins without producing duplicates
## [1.0.13] - 2026-04-22
IM / Messaging capability expansion: the `chat` (aka `im`) product surface grows from "group + bot messaging" into a full conversational layer — user-identity messaging, message reading & search, personal messages, topic replies, mentions, focused contacts, unread/top/common conversations, org-wide group creation, and first-class bot lifecycle.
+21 -14
View File
@@ -185,12 +185,17 @@ Credentials are securely persisted after first login (Keychain). Subsequent runs
## Quick Start
```bash
dws contact user search --keyword "engineering" # search contacts
dws calendar event list # list calendar events
dws contact user search --query "engineering" # search contacts
dws calendar event list # list today's calendar events
dws doc search --query "quarterly" # search DingTalk Docs
dws minutes list mine # list AI meeting notes I created
dws drive list # list DingTalk drive files
dws todo task create --title "Quarterly report" --executors "<your-userId>" # create a todo (replace <your-userId>)
dws todo task list --dry-run # preview without executing
```
> **Full command list**: [`docs/command-index.md`](./docs/command-index.md) — all 159 commands with descriptions and when-to-use guidance.
## Using with Agents
dws is designed as an AI-native CLI. Complete [Installation](#installation) and [Getting Started](#getting-started) first, then configure your agent:
@@ -350,26 +355,27 @@ dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
| Service | Command | Commands | Subcommands | Description |
|---------|---------|:--------:|-------------|-------------|
| Contact | `contact` | 6 | `user` `dept` | Search users by name/mobile, batch query, departments, current user profile |
| Chat / IM | `chat` (alias `im`) | 20 | `message` `group` `search` `list-top-conversations` | User-identity send (group / 1-on-1 / open-dingtalk-id), Markdown + image, @mentions; read & search conversations (list, list-all, topic replies, by-sender, mentions, focused, unread, search, info, top / common groups); group CRUD + member management |
| Bot | `chat bot` | 7 | `bot` `group` `message` `search` `create` `search-groups` | Bot create / search, search bot groups; bot-identity group & batch-1:1 messaging, Webhook, message recall; add bot to group |
| Calendar | `calendar` | 13 | `event` `room` `participant` `busy` | Events CRUD, meeting room booking, free-busy query, participant management |
| Chat / IM | `chat` (alias `im`) | 23 | `message` `group` `bot` `conversation-info` `search` `search-common` `list-top-conversations` | Messages (send / list / list-all / by-sender / mentions / focused / unread / topic replies / search), group CRUD + member management (incl. `add-bot`), bot-identity messaging (`send-by-bot` / `recall-by-bot` / `send-by-webhook`), conversation info, common groups lookup |
| Calendar | `calendar` | 14 | `event` `room` `participant` `busy` | Events CRUD + suggested times + attachments, meeting room booking, free-busy query, participant management |
| Todo | `todo` | 6 | `task` | Create, list, update, done, get detail, delete |
| Approval | `oa` | 9 | `approval` | Approve/reject/revoke, pending tasks, initiated instances, process list |
| Approval | `oa` | 9 | `approval` | Approve / reject / revoke, pending / initiated instances, process list, operation records |
| Attendance | `attendance` | 4 | `record` `shift` `summary` `rules` | Clock-in records, shift schedules, attendance summary, group rules |
| Ding | `ding` | 2 | `message` | Send/recall DING messages |
| Ding | `ding` | 2 | `message` | Send / recall DING messages |
| Report | `report` | 7 | `create` `list` `detail` `template` `stats` `sent` | Create reports, sent/received list, templates, statistics |
| AITable | `aitable` | 37 | `base` `table` `record` `field` `attachment` `template` `chart` `dashboard` `export` `import` `view` | Full CRUD for bases/tables/records/fields; charts/dashboards; data import/export; views; templates |
| Doc | `doc` | 16 | `search` `list` `info` `read` `create` `update` `upload` `download` `folder` `block` `comment` | Search, read, create/update documents; block-level editing; file upload/download; comments |
| Minutes | `minutes` | 22 | `list` `get` `update` `record` `hot-word` `mind-graph` `replace-text` `speaker` `upload` | List/search AI meeting transcripts; summaries, transcriptions, todos, mind-maps; recording control; speaker management, hot-words, file upload |
| Workbench | `workbench` | 2 | `app` | Batch query app details |
| DevDoc | `devdoc` | 1 | `article` | Search platform docs and error codes |
| AI Tables | `aitable` | 41 | `base` `table` `record` `field` `view` `dashboard` `chart` `import` `export` `attachment` `template` | Full CRUD for Bases / datasheets / records / fields / views; charts & dashboards with public-share configs; data import/export; attachments; templates |
| Doc | `doc` | 21 | `search` `list` `info` `read` `create` `update` `upload` `download` `copy` `move` `rename` `file` `folder` `block` `comment` | Search / read / write docs, file & folder create, block-level editing, comments (list / create / reply / create-inline), upload / download |
| Drive | `drive` | 6 | `list` `info` `download` `mkdir` `upload-info` `commit` | DingTalk drive file ops: list, info, download, create folders, two-phase upload |
| Minutes | `minutes` | 19 | `list` `get` `update` `mind-graph` `speaker` `hot-word` `upload` | List AI meeting notes (mine / shared), details (info / summary / keywords / transcription / todos / batch), title/summary updates, mind map, speaker replace, hot-word, upload session |
| DevDoc | `devdoc` | 1 | `article` | Search the DingTalk Open Platform documentation |
> 152 commands across 14 products. Run `dws --help` for the full list, or `dws <service> --help` for subcommands.
> **159 commands across 13 products.** Full listing with descriptions and usage scenarios: [`docs/command-index.md`](./docs/command-index.md). Run `dws --help` for the top-level tree, or `dws <service> --help` for subcommands.
> **Note on `chat bot`**: bot capabilities (`send-by-bot` / `recall-by-bot` / `add-bot` / `send-by-webhook` / bot search) are merged into the relevant `chat` subtrees (e.g. `dws chat message send-by-bot`, `dws chat group members add-bot`) so the agent-facing command surface stays flat and discoverable. There is no longer a separate top-level `bot` product.
<details>
<summary>Coming soon</summary>
`mail` (email) · `drive` (cloud drive) · `conference` (video) · `tb` (Teambition) · `aiapp` (AI apps) · `live` (streaming) · `skill` (marketplace)
`mail` (email) · `conference` (video) · `aiapp` (AI apps) · `live` (streaming) · `wiki` (knowledge base)
</details>
@@ -418,6 +424,7 @@ dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
## Reference & Docs
- [Command Index](./docs/command-index.md) — every runtime command (159 total) 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
- [Changelog](./CHANGELOG.md) — release history and migration notes
+22 -13
View File
@@ -185,12 +185,17 @@ dws auth login --client-id <your-app-key> --client-secret <your-app-secret>
## 快速开始
```bash
dws contact user search --keyword "悟空" # 搜索联系人
dws calendar event list # 查看日历日程
dws contact user search --query "悟空" # 搜索联系人
dws calendar event list # 查看今天的日程
dws doc search --query "季度" # 搜索钉钉文档
dws minutes list mine # 列出我创建的 AI 听记
dws drive list # 列出钉盘文件
dws todo task create --title "季度汇报" --executors "<your-userId>" # 创建待办(请替换为真实 userId)
dws todo task list --dry-run # 预览操作但不执行
```
> **完整命令列表**:[`docs/command-index.md`](./docs/command-index.md) — 全部 159 条命令,带描述和使用场景。
## 在 Agent 中使用
dws 是为 AI Agent 设计的 CLI 工具。请先完成[安装](#安装)和[开始使用](#开始使用),然后配置 Agent 环境:
@@ -350,24 +355,27 @@ dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
| 服务 | 命令 | 命令数 | 子命令 | 描述 |
|------|------|:------:|--------|------|
| 通讯录 | `contact` | 6 | `user` `dept` | 按姓名/手机号搜索、批量查询、部门树、当前用户信息 |
| 群聊 | `chat` | 10 | `message` `group` `search` | 群增删改查、成员管理、机器人消息、Webhook |
| 机器人 | `chat bot` | 6 | `bot` `group` `message` `search` | 机器人创建/搜索、群聊/单聊消息、Webhook、消息撤回 |
| 日历 | `calendar` | 13 | `event` `room` `participant` `busy` | 日程增删改查、会议室预订、闲忙查询、参与者管理 |
| 群聊 | `chat`(别名 `im`)| 23 | `message` `group` `bot` `conversation-info` `search` `search-common` `list-top-conversations` | 消息(发送 / 列表 / list-all / 按发送者 / @我 / 关注 / 未读 / 话题回复 / 搜索)、群增删改 + 成员管理(含 `add-bot`)、机器人身份消息(`send-by-bot` / `recall-by-bot` / `send-by-webhook`)、会话信息查询、共同群聊 |
| 日历 | `calendar` | 14 | `event` `room` `participant` `busy` | 日程 CRUD + 建议时间 + 附件、会议室预订、闲忙查询、参与者管理 |
| 待办 | `todo` | 6 | `task` | 创建、列表、修改、完成、详情、删除 |
| 审批 | `oa` | 9 | `approval` | 同意/拒绝/撤销、待我审批、我发起的、流程列表 |
| 审批 | `oa` | 9 | `approval` | 同意 / 拒绝 / 撤销、待我审批 / 我发起的、流程列表、操作记录 |
| 考勤 | `attendance` | 4 | `record` `shift` `summary` `rules` | 打卡记录、排班查询、考勤摘要、考勤组规则 |
| DING | `ding` | 2 | `message` | 发送/撤回 DING 消息 |
| 日志 | `report` | 7 | `create` `list` `detail` `template` `stats` `sent` | 创建日志、收发列表、模版、统计 |
| 智能表格 | `aitable` | 20 | `base` `table` `record` `field` `attachment` `template` | 多维表/数据表/记录/字段全量 CRUD、模板 |
| 工作台 | `workbench` | 2 | `app` | 批量查询应用详情 |
| 开发者文档 | `devdoc` | 1 | `article` | 搜索开放平台文档与错误码 |
| DING | `ding` | 2 | `message` | 发送 / 撤回 DING 消息 |
| 日志 | `report` | 7 | `create` `list` `detail` `template` `stats` `sent` | 创建日志、收发列表、模版、详情、统计 |
| AI 表格 | `aitable` | 41 | `base` `table` `record` `field` `view` `dashboard` `chart` `import` `export` `attachment` `template` | Base / 数据表 / 记录 / 字段 / 视图 全量 CRUD;图表 + 仪表盘(含分享配置);数据导入导出;附件;模板 |
| 文档 | `doc` | 21 | `search` `list` `info` `read` `create` `update` `upload` `download` `copy` `move` `rename` `file` `folder` `block` `comment` | 搜索 / 读写文档、文件与文件夹创建、块级编辑、评论(list / create / reply / create-inline)、上传 / 下载 |
| 钉盘 | `drive` | 6 | `list` `info` `download` `mkdir` `upload-info` `commit` | 钉盘文件操作:列表、详情、下载、创建文件夹、两阶段上传 |
| AI 听记 | `minutes` | 19 | `list` `get` `update` `mind-graph` `speaker` `hot-word` `upload` | 听记列表(我创建 / 共享给我)、详情(info / summary / keywords / transcription / todos / batch)、标题/摘要更新、思维导图、发言人替换、热词、上传会话 |
| 开发者文档 | `devdoc` | 1 | `article` | 搜索钉钉开放平台文档 |
> 12 个产品,86 个命令。运行 `dws --help` 查看完整列表,或 `dws <service> --help` 查看子命令。
> **13 个产品,159 条命令。** 完整命令清单(带描述与使用场景):[`docs/command-index.md`](./docs/command-index.md)。运行 `dws --help` 查看顶层命令树,或 `dws <service> --help` 查看子命令。
> **关于 `chat bot`**:机器人能力(`send-by-bot` / `recall-by-bot` / `add-bot` / `send-by-webhook` / bot 搜索)已合并到对应的 `chat` 子树下(例如 `dws chat message send-by-bot`、`dws chat group members add-bot`),保持 agent 视角下的命令面扁平易发现。不再有独立的顶层 `bot` 产品。
<details>
<summary>即将推出</summary>
`doc`(文档)· `mail`(邮箱)· `minutes`(AI 听记)· `drive`(钉盘)· `conference`(视频会议)· `tb`(Teambition)· `aiapp`(AI 应用)· `live`(直播)· `skill`(技能市场)
`mail`(邮箱)· `conference`(视频会议)· `aiapp`(AI 应用)· `live`(直播)· `wiki`(知识库)
</details>
@@ -418,6 +426,7 @@ dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
## 参考与文档
- [命令索引](./docs/command-index.md) — 159 条运行时命令,带描述与使用场景
- [参考手册](./docs/reference.md) — 环境变量、退出码、输出格式、Shell 补全
- [架构设计](./docs/architecture.md) — 发现驱动管道、IR、Transport 层
- [更新日志](./CHANGELOG.md) — 版本历史与迁移说明
+323
View File
@@ -0,0 +1,323 @@
# dws Command Index
Every runtime command the `dws` CLI exposes when loaded with the **pre** environment configuration.
- **Source**: `dws-wukong/envelope/channel/open/pre/config.json`
- **Products**: 13
- **Total commands**: 159
- **Generated from**: `internal/compat.BuildDynamicCommands` rendering of the pre config — the same code path the CLI uses at runtime.
> Auto-generated. Edit `pre/config.json`, not this file.
## Global flags
Every command inherits these flags (documented here once, not repeated per command):
| Flag | Purpose |
|---|---|
| `--client-id` | Override OAuth client ID (DingTalk AppKey) |
| `--client-secret` | Override OAuth client secret (DingTalk AppSecret) |
| `--debug` | Enable debug logging |
| `--dry-run` | Preview the request without executing |
| `--fields` | Comma-separated output field projection |
| `-f, --format` | Output format: `json` \| `table` \| `raw` (default `json`) |
| `--jq` | jq expression applied to JSON output |
| `--mock` | Return mock data (developer aid) |
| `-o, --output` | Write output to a file |
| `--timeout` | HTTP request timeout in seconds (default 30) |
| `--token` | Override the configured API token |
| `-v, --verbose` | Verbose logging |
| `-y, --yes` | Skip confirmation prompts (AI-agent mode) |
## Contents
- [`dws aitable` — AI Tables](#dws-aitable) · 41 commands
- [`dws attendance` — Attendance](#dws-attendance) · 4 commands
- [`dws calendar` — Calendar](#dws-calendar) · 14 commands
- [`dws chat` — Group Chat / IM](#dws-chat) · 23 commands
- [`dws contact` — Contact Directory](#dws-contact) · 6 commands
- [`dws devdoc` — Open Platform Docs](#dws-devdoc) · 1 commands
- [`dws ding` — DING Messages](#dws-ding) · 2 commands
- [`dws doc` — DingTalk Doc](#dws-doc) · 21 commands
- [`dws drive` — DingTalk Drive](#dws-drive) · 6 commands
- [`dws minutes` — AI Minutes](#dws-minutes) · 19 commands
- [`dws oa` — OA Approval](#dws-oa) · 9 commands
- [`dws report` — Reports](#dws-report) · 7 commands
- [`dws todo` — Todo Tasks](#dws-todo) · 6 commands
## `dws aitable` — AI Tables
_AI-powered spreadsheet (Base) with datasheets, fields, records, views, dashboards, charts, import/export, attachments, and templates._
**41 commands**
| Command | Description | When to use |
|---|---|---|
| `dws aitable attachment upload` | Request an upload ticket for attaching a file to an AI table attachment-type field. Returns an upload URL and token the caller uses to stream the file. | When the agent needs to attach binary assets (images, PDFs, etc.) to records before creating or updating an attachment field value. |
| `dws aitable base create` | Create a new AI table (Base) under the current user's workspace. Returns the newly-created Base ID. | When an agent needs to provision a fresh Base before populating datasheets, fields, and records. |
| `dws aitable base delete` | Permanently delete an existing AI table (Base) by ID, removing all its datasheets, views, and records. | When the agent is cleaning up a Base that is no longer needed or was created for a one-off task. |
| `dws aitable base get` | Retrieve metadata for a single AI table (Base), including name, owner, and structural summary. | When the agent needs to inspect a specific Base before performing further operations on it. |
| `dws aitable base list` | List AI tables (Bases) accessible to the current user, paginated. | When the agent needs to enumerate the user's Bases to pick one by name or index. |
| `dws aitable base search` | Search AI tables (Bases) the current user can access by keyword against the Base name. | When the agent knows a partial Base name and needs to resolve it to a Base ID. |
| `dws aitable base update` | Update mutable properties of an AI table (Base), such as its name or icon. | When the agent needs to rename or rebrand an existing Base without touching its data. |
| `dws aitable chart create` | Create a new chart inside a Base, bound to a datasheet and view with a given configuration. | When the agent is building analytics on top of a datasheet and needs to materialize a chart visualization. |
| `dws aitable chart delete` | Delete a chart from a Base by chart ID. | When the agent needs to remove an obsolete or mistakenly-created chart. |
| `dws aitable chart get` | Retrieve a chart's full configuration and metadata. | When the agent needs to inspect an existing chart to clone it or adjust its configuration. |
| `dws aitable chart share get` | Retrieve the current public-sharing configuration of a chart, including share link and permissions. | When the agent needs to check whether a chart is already shared externally before issuing a link. |
| `dws aitable chart share update` | Enable, disable, or update the public-sharing configuration of a chart. | When the agent needs to generate or revoke an external share link for a chart. |
| `dws aitable chart update` | Update an existing chart's configuration (type, dimensions, metrics, style). | When the agent iterates on a chart's visualization after reviewing the initial result. |
| `dws aitable chart widgets-example` | Return a reference JSON example of chart widget configuration accepted by chart create/update. | When the agent needs a schema template before composing chart configuration payloads. |
| `dws aitable dashboard config-example` | Return a reference JSON example of dashboard configuration accepted by dashboard create/update. | When the agent needs a schema template before composing dashboard layout payloads. |
| `dws aitable dashboard create` | Create a new dashboard inside a Base with a layout of chart widgets. | When the agent wants to group multiple charts into a single dashboard view for a report or overview page. |
| `dws aitable dashboard delete` | Delete a dashboard from a Base by dashboard ID. | When the agent is removing an outdated dashboard. |
| `dws aitable dashboard get` | Retrieve a dashboard's layout, widget list, and metadata. | When the agent needs to inspect a dashboard before updating it or cloning it. |
| `dws aitable dashboard share get` | Retrieve the current public-sharing configuration of a dashboard. | When the agent needs to verify whether a dashboard has an active external share link. |
| `dws aitable dashboard share update` | Enable, disable, or update the public-sharing configuration of a dashboard. | When the agent needs to generate or revoke an external share link for a dashboard. |
| `dws aitable dashboard update` | Update an existing dashboard's layout, widgets, or metadata. | When the agent adds, removes, or rearranges charts on an existing dashboard. |
| `dws aitable export data` | Export data from a datasheet (optionally scoped to a view) to a downloadable file such as Excel or CSV. | When the agent needs to hand off Base data to an external system or deliver it as an attachment. |
| `dws aitable field create` | Create one or more fields in a datasheet with specified types and options. | When the agent is extending a datasheet's schema to capture new attributes. |
| `dws aitable field delete` | Delete a field from a datasheet by field ID; all values in that column are removed. | When the agent is cleaning up unused or deprecated columns in a datasheet. |
| `dws aitable field get` | Retrieve field definitions for a datasheet, including type, options, and order. | When the agent needs the field schema before constructing record payloads or queries. |
| `dws aitable field update` | Update a field's name, type, or options in a datasheet. | When the agent needs to rename a column or change its type/options without recreating it. |
| `dws aitable import data` | Import previously-uploaded data (e.g. Excel) into a datasheet as records, optionally creating fields. | When the agent is bulk-loading external data into a Base after a successful import upload. |
| `dws aitable import upload` | Request an upload ticket for an import file (Excel/CSV) to be staged before calling import data. | When the agent needs to push a local dataset into a Base and must first stage the file. |
| `dws aitable record create` | Insert one or more records into a datasheet with given field values. | When the agent needs to add new rows to a datasheet, individually or in batches. |
| `dws aitable record delete` | Delete one or more records from a datasheet by record ID. | When the agent removes rows that are obsolete or were created in error. |
| `dws aitable record query` | Query records from a datasheet with optional filters, sort, view scoping, and pagination. | When the agent needs to read row data to reason about it, render it, or feed it into downstream logic. |
| `dws aitable record update` | Update field values on one or more existing records by record ID. | When the agent modifies specific row values after reading or computing new data. |
| `dws aitable table create` | Create a new datasheet (table) inside a Base. | When the agent needs another table alongside existing ones in the same Base. |
| `dws aitable table delete` | Delete a datasheet from a Base by table ID, removing all its records, views, and fields. | When the agent is disposing of a datasheet that is no longer needed. |
| `dws aitable table get` | List datasheets within a Base, returning table IDs and names. | When the agent needs to resolve a table name to an ID inside a known Base. |
| `dws aitable table update` | Update a datasheet's name or other metadata. | When the agent needs to rename a datasheet without altering its contents. |
| `dws aitable template search` | Search the AI table template gallery by keyword. | When the agent needs to suggest or bootstrap from an existing Base template rather than building from scratch. |
| `dws aitable view create` | Create a new view (grid, gallery, kanban, etc.) on a datasheet. | When the agent needs an alternate filtered/sorted presentation of the same datasheet data. |
| `dws aitable view delete` | Delete a view from a datasheet by view ID. | When the agent is cleaning up unused views. |
| `dws aitable view get` | Retrieve view definitions for a datasheet, including filter, sort, and visible-field configuration. | When the agent needs to understand or reuse a view's configuration before querying records through it. |
| `dws aitable view update` | Update a view's name, filter, sort, grouping, or visible fields. | When the agent refines an existing view's configuration after inspection. |
## `dws attendance` — Attendance
_Attendance check-in records, shifts, and aggregate statistics._
**4 commands**
| Command | Description | When to use |
|---|---|---|
| `dws attendance record get` | Query a user's detailed clock-in/clock-out attendance records for a given time range. | When the agent needs to verify punctuality, pull attendance evidence, or build an attendance report for an individual. |
| `dws attendance rules` | Query the attendance group the user belongs to along with its attendance rules (schedule, locations, shifts). | When the agent needs to know the user's expected work schedule or attendance policies before interpreting records. |
| `dws attendance shift list` | Batch-query the assigned shifts for a set of employees over a date range. | When the agent needs to plan around team shifts or compile a shift-based roster. |
| `dws attendance summary` | Retrieve an aggregated attendance summary for a single user (totals of late, early-leave, absence, overtime). | When the agent needs a quick attendance health check without pulling raw records. |
## `dws calendar` — Calendar
_Calendar events, participants, meeting rooms, and busy-status queries._
**14 commands**
| Command | Description | When to use |
|---|---|---|
| `dws calendar busy search` | Query the busy/free time windows of one or more users over a given range. | When the agent is scheduling a meeting and needs to find a slot where all attendees are free. |
| `dws calendar event create` | Create a new calendar event on the user's calendar with title, time, attendees, and optional meeting room. | When the agent schedules a meeting or reminder on behalf of the user. |
| `dws calendar event delete` | Delete an existing calendar event by event ID. | When the agent cancels a previously scheduled event. |
| `dws calendar event get` | Retrieve the full details of a calendar event, including participants, location, and body. | When the agent needs to inspect an event before updating or referencing it. |
| `dws calendar event list` | List calendar events on the user's calendar within a given time range. | When the agent needs an overview of the user's upcoming schedule or a day's agenda. |
| `dws calendar event suggest` | Suggest candidate meeting time slots based on participants' busy/free data and constraints. | When the agent is coordinating a meeting and wants ranked time suggestions rather than raw busy data. |
| `dws calendar event update` | Update an existing calendar event's fields such as time, title, participants, or location. | When the agent needs to reschedule or amend a previously created event. |
| `dws calendar participant add` | Add one or more participants to an existing calendar event. | When the agent invites additional attendees after the event has been created. |
| `dws calendar participant delete` | Remove one or more participants from an existing calendar event. | When the agent drops attendees who no longer need to join the event. |
| `dws calendar participant list` | List current participants of a calendar event along with their response status. | When the agent needs to check who is attending before sending follow-up reminders. |
| `dws calendar room add` | Book a specific meeting room onto an existing calendar event. | When the agent needs to attach a physical meeting room to an already-scheduled event. |
| `dws calendar room delete` | Release a previously booked meeting room from a calendar event. | When the agent cancels or changes the room on an existing event. |
| `dws calendar room list-groups` | List meeting room groups (usually by building or floor) available to the user. | When the agent is narrowing down rooms by location before running an availability search. |
| `dws calendar room search` | Search meeting rooms by keyword within a group, optionally filtering to rooms free during a given window via `--available`. | When the agent needs to find a suitable room, typically free at a specific time, prior to booking. |
## `dws chat` — Group Chat / IM
_Group chats, conversations, messages, and robot/webhook integrations._
**23 commands**
| Command | Description | When to use |
|---|---|---|
| `dws chat bot search` | Search robots (bots) created by the current user by keyword. | When the agent needs to resolve one of its own bots by name to a robot code before sending bot messages. |
| `dws chat conversation-info` | Retrieve basic metadata for a conversation (single chat or group chat) by conversation ID. | When the agent needs context about a conversation (name, type, member count) before operating on it. |
| `dws chat group create` | Create a new internal group chat with a set of initial members. | When the agent needs to spin up a dedicated group for a new project, incident, or discussion thread. |
| `dws chat group members` | List members of a group chat; can also be used against the current user to enumerate their groups' members. | When the agent needs the roster of a group before mentioning, removing, or auditing members. |
| `dws chat group members add` | Add one or more users to an existing group chat. | When the agent expands a group to include additional participants. |
| `dws chat group members add-bot` | Add a robot (bot) to an existing group chat so the bot can post messages there. | When the agent needs to enable bot-driven notifications in a group that does not yet contain the bot. |
| `dws chat group members remove` | Remove one or more members from a group chat. | When the agent kicks users who should no longer have access to the group. |
| `dws chat group rename` | Update the display name of a group chat. | When the agent is rebranding or clarifying the purpose of an existing group. |
| `dws chat list-top-conversations` | Fetch the list of conversations the current user has pinned to the top of their chat list. | When the agent needs to prioritize the user's most important conversations in a summary or dashboard. |
| `dws chat message list` | Pull the recent message history of a specific conversation (v2), paginated. | When the agent needs to read what has recently been said in a conversation to summarize or reason about it. |
| `dws chat message list-all` | Search all messages across the current user's conversations within a time range. | When the agent needs to audit or summarize everything the user saw across chats in a window. |
| `dws chat message list-by-sender` | Fetch messages authored by a specific sender across both single and group chats. | When the agent needs to pull everything a particular colleague said recently. |
| `dws chat message list-focused` | Fetch messages from users the current user has marked as "special focus" (starred contacts). | When the agent builds a priority-inbox view highlighting messages from important people. |
| `dws chat message list-mentions` | Fetch messages where the current user was @-mentioned. | When the agent wants to surface items that explicitly require the user's attention. |
| `dws chat message list-topic-replies` | Pull replies under a specific group topic thread. | When the agent needs the conversation tree of a threaded discussion rather than the flat message list. |
| `dws chat message list-unread-conversations` | Fetch the list of conversations that currently have unread messages for the user. | When the agent builds a "catch me up" triage view of what still needs reading. |
| `dws chat message recall-by-bot` | Recall (retract) a message previously sent by a robot in a group chat. | When the agent sent a bot message in error or with incorrect content and needs to withdraw it. |
| `dws chat message search` | Search messages by keyword across the user's conversations. | When the agent needs to locate a specific statement or link the user remembers from chat history. |
| `dws chat message send` | Send a message into a group chat or single chat as the authenticated user. | When the agent needs to relay a response to a user or notify a group on behalf of the human operator. |
| `dws chat message send-by-bot` | Send a group message as a specific robot (bot) the user owns. | When the agent posts automated notifications under a bot identity rather than as the user. |
| `dws chat message send-by-webhook` | Send a group message via a custom-robot incoming webhook URL. | When the agent needs to post to a group using a webhook without requiring full bot-permission setup. |
| `dws chat search` | Search group conversations the user belongs to by group name keyword. | When the agent needs to resolve a group name to a conversation ID. |
| `dws chat search-common` | Find group chats the current user and a specified other user both belong to. | When the agent needs an existing shared channel to contact another user without creating a new group. |
## `dws contact` — Contact Directory
_Users, departments, and directory lookups._
**6 commands**
| Command | Description | When to use |
|---|---|---|
| `dws contact dept list-members` | List members of a specific department by department ID. | When the agent needs the roster of a department to target communication or build a team overview. |
| `dws contact dept search` | Search departments in the organization's contact directory by keyword. | When the agent needs to resolve a department name to a department ID. |
| `dws contact user get` | Batch-fetch detailed profile information for one or more users by user ID. | When the agent needs names, titles, emails, or departments for a known set of user IDs. |
| `dws contact user get-self` | Retrieve the profile of the currently authenticated user. | When the agent needs to identify who it is acting on behalf of (user ID, name, org). |
| `dws contact user search` | Search users in the contact directory by keyword (name, title, etc.). | When the agent needs to resolve a person's display name to a user ID. |
| `dws contact user search-mobile` | Look up a user by mobile phone number. | When the agent has only a phone number and needs to find the corresponding DingTalk user. |
## `dws devdoc` — Open Platform Docs
_Search the DingTalk Open Platform documentation._
**1 commands**
| Command | Description | When to use |
|---|---|---|
| `dws devdoc article search` | Search the DingTalk Open Platform documentation by keyword. | When the agent needs authoritative API reference or guides to answer a developer question. |
## `dws ding` — DING Messages
_Send and recall DING messages (priority notifications)._
**2 commands**
| Command | Description | When to use |
|---|---|---|
| `dws ding message recall` | Recall (retract) a previously sent DING message. | When the agent sent a DING in error and must withdraw it before recipients act on it. |
| `dws ding message send` | Send a DING message (high-priority notification) to one or more recipients via app/SMS/phone. | When the agent needs to page recipients with urgency beyond a normal chat message. |
## `dws doc` — DingTalk Doc
_DingTalk Doc: search, browse, read/write, upload/download, files, folders, blocks, comments._
**21 commands**
| Command | Description | When to use |
|---|---|---|
| `dws doc block delete` | Delete a block from a DingTalk Doc by block ID. | When the agent is editing a document and needs to remove a specific paragraph, table, or other block. |
| `dws doc block insert` | Insert a new block (paragraph, table, image, etc.) into a DingTalk Doc at a given position. | When the agent is programmatically assembling or editing a document's content. |
| `dws doc block list` | List the blocks of a DingTalk Doc with their IDs, types, and content. | When the agent needs the structured block tree of a doc before modifying specific blocks. |
| `dws doc block update` | Update the content or properties of an existing block in a DingTalk Doc. | When the agent amends a specific paragraph or element without rewriting the whole document. |
| `dws doc comment create` | Create a document-level comment on a DingTalk Doc. | When the agent leaves feedback or follow-up notes that apply to the entire document. |
| `dws doc comment create-inline` | Create an inline (anchored) comment on a specific text range within a DingTalk Doc. | When the agent needs to attach feedback to a particular passage rather than the whole doc. |
| `dws doc comment list` | List comments on a DingTalk Doc, including replies. | When the agent is reviewing outstanding feedback or summarizing comment threads. |
| `dws doc comment reply` | Reply to an existing comment on a DingTalk Doc. | When the agent responds to a reviewer's comment inline rather than starting a new thread. |
| `dws doc copy` | Copy an existing DingTalk Doc or file to a specified destination folder. | When the agent needs to duplicate a template document into a new location for reuse. |
| `dws doc create` | Create a new DingTalk Doc (document type) in a target folder or knowledge base. | When the agent needs a fresh DingTalk Doc to write into. |
| `dws doc download` | Download a DingTalk Doc or file to a local path. | When the agent needs the raw file locally for processing or attachment. |
| `dws doc file create` | Create a new file node of a given type (doc, sheet, mind map, whiteboard, AI table, etc.) in a target folder. | When the agent provisions any non-plain-document file type inside DingTalk Docs. |
| `dws doc folder create` | Create a new folder inside a DingTalk Docs knowledge base or drive location. | When the agent organizes output into a fresh folder before writing files into it. |
| `dws doc info` | Retrieve metadata for a document or file (title, type, owner, path, permissions). | When the agent needs descriptive info about a node without fetching its full content. |
| `dws doc list` | List the child nodes (files and subfolders) of a folder or knowledge base. | When the agent traverses the document hierarchy to find or enumerate items. |
| `dws doc move` | Move a DingTalk Doc or file to a different folder location. | When the agent reorganizes document structure. |
| `dws doc read` | Read the content of a DingTalk Doc as Markdown. | When the agent needs the document body as text for summarization, Q&A, or further editing. |
| `dws doc rename` | Rename a DingTalk Doc or file. | When the agent needs to change a document's title without altering its contents or location. |
| `dws doc search` | Search DingTalk Docs the user can access by keyword. | When the agent needs to locate a document by title or content before reading or editing it. |
| `dws doc update` | Update the content of a DingTalk Doc (bulk content rewrite rather than block-level edit). | When the agent has freshly generated content and needs to overwrite a doc's body. |
| `dws doc upload` | Obtain upload credentials and URL for uploading a local file as an attachment into DingTalk Docs or a knowledge base. | When the agent needs to stage a local file for attachment into the DingTalk Docs system. |
## `dws drive` — DingTalk Drive
_DingTalk Drive file and folder management._
**6 commands**
| Command | Description | When to use |
|---|---|---|
| `dws drive commit` | Commit a file upload to DingTalk Drive after the binary has been pushed to the presigned URL. | When the agent finalizes a Drive upload step; pairs with `drive upload-info`. |
| `dws drive download` | Fetch a temporary download URL for a file stored in DingTalk Drive. | When the agent needs to retrieve a Drive-hosted file for local use or for handing to another service. |
| `dws drive info` | Retrieve metadata for a file or folder in DingTalk Drive. | When the agent inspects a Drive node before downloading, moving, or listing around it. |
| `dws drive list` | List the files and subfolders of a DingTalk Drive folder. | When the agent needs to enumerate Drive contents to find or pick items. |
| `dws drive mkdir` | Create a new folder in DingTalk Drive. | When the agent organizes Drive output into a fresh folder before uploading files. |
| `dws drive upload-info` | Obtain a presigned upload URL and token for pushing a local file into DingTalk Drive. | When the agent starts a Drive upload; pairs with `drive commit` to finalize. |
## `dws minutes` — AI Minutes
_AI meeting notes: listing, summary, todos, transcription, recording control, mind maps, speakers, hot words, uploads._
**19 commands**
| Command | Description | When to use |
|---|---|---|
| `dws minutes get batch` | Batch-fetch detailed metadata for multiple meeting notes (AI minutes) by ID. | When the agent needs to enrich a list of minutes IDs with titles, durations, and participants in one call. |
| `dws minutes get info` | Retrieve basic metadata for a single meeting note (title, owner, time, duration, participants). | When the agent needs a header view of a specific meeting note. |
| `dws minutes get keywords` | Retrieve the extracted keywords of a meeting note. | When the agent needs topical tags for a meeting without pulling the full transcript or summary. |
| `dws minutes get summary` | Retrieve the AI-generated summary of a meeting note. | When the agent needs a concise recap of a meeting for reporting or follow-up. |
| `dws minutes get todos` | Retrieve the action items (todos) extracted from a meeting note. | When the agent needs to convert meeting action items into tasks or follow up on commitments. |
| `dws minutes get transcription` | Retrieve the raw speech-to-text transcription of a meeting note. | When the agent needs the full verbatim transcript for deep analysis or quoting. |
| `dws minutes hot-word add` | Add a custom personal hot word to improve future speech-recognition accuracy on the user's minutes. | When the user has domain-specific jargon or proper nouns that the ASR model mistranscribes. |
| `dws minutes list all` | List all meeting notes the user has access to, filterable by keyword and time range. | When the agent needs a broad search across the user's full minutes library. |
| `dws minutes list mine` | List only the meeting notes the current user created. | When the agent scopes results to the user's own recordings rather than shared ones. |
| `dws minutes list shared` | List meeting notes that have been shared with the current user by others. | When the agent wants to surface meetings the user is an invited viewer of. |
| `dws minutes mind-graph create` | Generate a mind map from a meeting note asynchronously. | When the agent wants a structured mind-map visualization of a meeting's content. |
| `dws minutes mind-graph status` | Query the generation status of a mind-map job and fetch the result when ready. | When the agent polls after `mind-graph create` to retrieve the finished mind map. |
| `dws minutes replace-text` | Find and replace matching text across a meeting note's transcript paragraphs and summary. | When the agent corrects a systemic transcription mistake (e.g. wrong product name) throughout a note. |
| `dws minutes speaker replace` | Reassign speaker labels in a meeting note (e.g. map "Speaker 1" to a specific user). | When the agent cleans up speaker diarization after automatic labels came out wrong. |
| `dws minutes update summary` | Overwrite the summary content of a meeting note. | When the agent refines or replaces the AI-generated summary with a corrected or customized version. |
| `dws minutes update title` | Update the title of a meeting note. | When the agent renames a meeting note for clarity before sharing or archiving. |
| `dws minutes upload cancel` | Cancel an in-progress meeting-note file upload session. | When the agent aborts a multi-step upload due to user cancellation or upstream error. |
| `dws minutes upload complete` | Complete an upload session and create a meeting note from the uploaded audio/video. | When the agent finalizes a minutes upload, triggering transcription and AI processing. |
| `dws minutes upload create` | Create a file upload session for producing a meeting note from a local audio/video file. | When the agent begins uploading a recording to be turned into a meeting note. |
## `dws oa` — OA Approval
_OA approval workflows: list, approve, reject, revoke, records._
**9 commands**
| Command | Description | When to use |
|---|---|---|
| `dws oa approval approve` | Approve a pending approval process instance (task) as the current user. | When the agent acts on a pending approval the user has delegated it to handle. |
| `dws oa approval detail` | Retrieve full details of an approval process instance, including form fields, attachments, and state. | When the agent needs to read the content of an approval ticket before deciding on it or summarizing it. |
| `dws oa approval list-forms` | List approval process templates (forms) the current user is allowed to initiate. | When the agent needs to pick the right approval form before submitting a new request. |
| `dws oa approval list-initiated` | List approval process instances the current user has initiated. | When the agent reviews the status of approvals the user submitted. |
| `dws oa approval list-pending` | List approval process instances currently awaiting action from the current user. | When the agent surfaces "needs your approval" items in the user's inbox. |
| `dws oa approval records` | Retrieve the operation history (who approved/commented/transferred, when) of an approval instance. | When the agent explains an approval's progression or audits who handled it. |
| `dws oa approval reject` | Reject a pending approval process instance as the current user. | When the agent declines an approval on behalf of the user, optionally with a reason. |
| `dws oa approval revoke` | Revoke an approval process instance previously initiated by the current user. | When the agent withdraws an approval request the user no longer wants to pursue. |
| `dws oa approval tasks` | List pending approval task IDs assigned to the current user, used to drive approve/reject actions. | When the agent needs task IDs (not just instance IDs) before calling approve/reject. |
## `dws report` — Reports
_DingTalk Report feature: templates, entries, and statistics._
**7 commands**
| Command | Description | When to use |
|---|---|---|
| `dws report create` | Create a new report (DingTalk "Report" entry) based on a report template with filled-in content. | When the agent submits a daily/weekly report on behalf of the user. |
| `dws report detail` | Retrieve the full details of a specific report entry, including fields and recipients. | When the agent needs to read a report's content for summarization or follow-up. |
| `dws report list` | List reports the current user has received from others. | When the agent digests the user's incoming reports (e.g. team members' weeklies). |
| `dws report sent` | List reports the current user has created and sent out. | When the agent reviews the user's own reporting history. |
| `dws report stats` | Retrieve aggregated statistics for a report entry by ID (views, likes, comments, etc.). | When the agent measures engagement or reach of a report the user sent. |
| `dws report template detail` | Retrieve the detailed schema of a report template by name, including required fields. | When the agent needs to know a template's field structure before calling `report create`. |
| `dws report template list` | List the report templates the current user is allowed to use. | When the agent picks the correct report template (e.g. "weekly", "daily") before creating a report. |
## `dws todo` — Todo Tasks
_Personal todo task management._
**6 commands**
| Command | Description | When to use |
|---|---|---|
| `dws todo task create` | Create a personal todo item for the current user with title, due time, and optional executors. | When the agent captures an action item as a tracked todo in the user's DingTalk todo list. |
| `dws todo task delete` | Delete a todo item by ID. | When the agent removes a todo that is no longer relevant. |
| `dws todo task done` | Update the completion status of a todo's executor (mark done or undone). | When the agent marks an action item as completed after confirming the work is finished. |
| `dws todo task get` | Retrieve the full details of a todo item by ID. | When the agent inspects a specific todo's content, due date, and executors. |
| `dws todo task list` | List todos for the current user within the current organization. | When the agent surfaces the user's outstanding tasks or builds a daily focus list. |
| `dws todo task update` | Update a todo's title, description, due time, or executors. | When the agent edits an existing todo after new information comes in. |
+41
View File
@@ -32,6 +32,7 @@ With `-f json`, error responses include structured payloads: `category`, `reason
dws contact user search --keyword "Alice" -f table # Table (default, human-friendly / 表格,默认)
dws contact user search --keyword "Alice" -f json # JSON (for agents and piping / 适合 agent)
dws contact user search --keyword "Alice" -f raw # Raw API response / 原始响应
dws schema -f pretty ding.send_ding_message # Pretty (ANSI-colored, schema-aware / 彩色分区,专为 schema 设计)
```
## Dry Run / 试运行
@@ -46,6 +47,46 @@ dws todo task list --dry-run # Preview MCP call without executing / 预览但
dws contact user search --keyword "Alice" -o result.json
```
## Schema Introspection / Schema 查询
`dws schema` 查询已发现的 MCP 产品和工具元数据。不带参数列出所有产品,带路径输出单个工具的完整 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 着色分区展示(人肉查看最舒服)
```
Canonical 路径先匹配;落空后走 CLI 路径(product → group.. → cli_name)。
### 单工具输出字段
| 字段 | 说明 |
|------|------|
| `name` / `cli_name` / `canonical_path` | MCP RPC 名 / CLI 叶子名 / `product.rpc_name` |
| `group` | CLI 父级 group 路径(dot-separated) |
| `title` / `description` | 工具名/说明(overlay 优先) |
| `parameters` / `required` | MCP 输入 JSON Schema 的 properties / required |
| `output_schema` | MCP 输出 Schema(上游下发时才有) |
| `sensitive` | 敏感写操作,需 `--yes` 确认 |
| `annotations.destructive_hint` | 对齐 MCP 2025+ annotations,目前从 `sensitive` 映射 |
| `flag_overlay[param]` | CLI 层对 MCP 参数的改写:`alias` / `transform` / `transform_args` / `env_default` / `default` / `hidden` |
**调试 `--flag` 行为的第一站**是 `flag_overlay` —— 比如 `--users 0232...` 能不能直接用,看 `receiverUserIdList.transform == "csv_to_array"` 即可判断。
### 筛选输出
```bash
dws schema ding.send_ding_message --jq '.tool.flag_overlay' # 只看 overlay
dws schema --jq '.products[] | {id, count: (.tools|length)}' # 各产品工具数
dws schema aitable.delete_base --jq '.tool.annotations' # 敏感操作提示
```
## Shell Completion / 自动补全
```bash
+1 -1
View File
@@ -41,7 +41,7 @@ func bindPersistentFlags(cmd *cobra.Command, flags *GlobalFlags) {
cmd.PersistentFlags().BoolVar(&flags.Debug, "debug", false, "显示调试日志")
cmd.PersistentFlags().BoolVar(&flags.DryRun, "dry-run", false, "预览操作内容,不实际执行")
cmd.PersistentFlags().StringVar(&flags.Fields, "fields", "", "筛选输出字段 (逗号分隔, 如: name,id,status)")
cmd.PersistentFlags().StringVarP(&flags.Format, "format", "f", "json", "输出格式: json|table|raw")
cmd.PersistentFlags().StringVarP(&flags.Format, "format", "f", "json", "输出格式: json|table|raw|pretty")
cmd.PersistentFlags().StringVar(&flags.JQ, "jq", "", "jq 表达式过滤输出 (如: '.items[] | .name')")
cmd.PersistentFlags().BoolVar(&flags.Mock, "mock", false, "使用 Mock 数据 (开发调试用)")
cmd.PersistentFlags().StringVarP(&flags.Output, "output", "o", "", "Write command output to a file")
+113 -18
View File
@@ -104,22 +104,38 @@ func NewMCPCommand(ctx context.Context, loader CatalogLoader, runner executor.Ru
}
func NewSchemaCommand(loader CatalogLoader) *cobra.Command {
return &cobra.Command{
Use: "schema [product.tool]",
cmd := &cobra.Command{
Use: "schema [path]",
Short: "查看 MCP 工具 Schema (产品列表 / 工具参数)",
Long: `查看已发现的 MCP 产品和工具的 Schema 元数据。
不带参数时列出所有产品及其工具数量;带 product.tool 路径时
输出该工具的完整输入 Schema(JSON Schema 格式)。
不带参数时列出所有产品及其工具数量;带路径时输出该工具的完整
输入 Schema(JSON Schema 格式)、输出 Schema、MCP 注解和 CLI
层的 flag overlay(alias/transform/env_default)。
路径支持三种写法:
product.rpc_name 规范路径 (e.g. ding.send_ding_message)
product.group.cli_name CLI 点路径 (e.g. ding.message.send)
"product group cli_name" CLI 空格/斜杠路径 (e.g. "ding message send")
示例:
dws schema # 列出所有产品
dws schema aitable.query_records # 查看 aitable query_records 的参数 Schema
dws schema --fields id,tools # 只显示 id 和 tools 字段
dws schema --jq '.products[].id' # 用 jq 提取所有产品 ID`,
dws schema # 列出所有产品
dws schema ding.send_ding_message # 规范路径
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 --jq '.tool.flag_overlay' # 只看 CLI overlay`,
Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
cliPath, _ := cmd.Flags().GetString("cli-path")
cliPath = strings.TrimSpace(cliPath)
if cliPath != "" {
if len(args) > 0 {
return apperrors.NewValidation("--cli-path and positional argument are mutually exclusive")
}
args = []string{cliPath}
}
catalog, err := loader.Load(cmd.Context())
if err != nil {
var degraded *CatalogDegraded
@@ -158,6 +174,8 @@ func NewSchemaCommand(loader CatalogLoader) *cobra.Command {
)
},
}
cmd.Flags().String("cli-path", "", "按 CLI 命令路径查询 (等同于位置参数,便于脚本使用无需转义)")
return cmd
}
func BuildFlagSpecs(schema map[string]any, hints map[string]ir.CLIFlagHint) []FlagSpec {
@@ -664,7 +682,7 @@ func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
}, nil
}
product, tool, ok := catalog.FindTool(args[0])
product, tool, ok := resolveSchemaPath(catalog, args[0])
if !ok {
return nil, apperrors.NewValidation(fmt.Sprintf("unknown canonical schema path %q", args[0]))
}
@@ -676,23 +694,100 @@ func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
}, nil
}
// compactTool returns a lean representation of a tool for schema
// output, keeping only the fields that AI agents and developers
// need: name, description, parameters, and sensitivity flag.
func compactTool(t ir.ToolDescriptor) map[string]any {
tool := map[string]any{
"name": t.RPCName,
"title": t.Title,
"description": t.Description,
"sensitive": t.Sensitive,
// resolveSchemaPath accepts three input forms and maps to (product, tool):
// - "product.rpc_name" (canonical, e.g. "ding.send_ding_message")
// - "product.cli_name" (single-level CLI path, e.g. "doc.create")
// - CLI path with group ("ding message send" or "ding.message.send";
// also accepts "/" and multiple whitespace between tokens)
//
// Canonical form is tried first so existing callers and scripts keep
// working; only when that fails does the CLI-path resolver run.
func resolveSchemaPath(catalog ir.Catalog, raw string) (ir.CanonicalProduct, ir.ToolDescriptor, bool) {
raw = strings.TrimSpace(raw)
if raw == "" {
return ir.CanonicalProduct{}, ir.ToolDescriptor{}, false
}
if product, tool, ok := catalog.FindTool(raw); ok {
return product, tool, true
}
tokens := splitSchemaPathTokens(raw)
if len(tokens) < 2 {
return ir.CanonicalProduct{}, ir.ToolDescriptor{}, false
}
productID := tokens[0]
leaf := tokens[len(tokens)-1]
groupPath := strings.Join(tokens[1:len(tokens)-1], ".")
product, ok := catalog.FindProduct(productID)
if !ok {
return ir.CanonicalProduct{}, ir.ToolDescriptor{}, false
}
for _, tool := range product.Tools {
if tool.CLIName != leaf {
continue
}
if strings.TrimSpace(tool.Group) != groupPath {
continue
}
return product, tool, true
}
return ir.CanonicalProduct{}, ir.ToolDescriptor{}, false
}
// splitSchemaPathTokens splits a CLI path on dots, slashes, and
// whitespace, returning only non-empty tokens. "ding message send",
// "ding.message.send", and "ding/message/send" all yield the same
// three tokens.
func splitSchemaPathTokens(raw string) []string {
fields := strings.FieldsFunc(raw, func(r rune) bool {
return r == '.' || r == '/' || r == ' ' || r == '\t'
})
out := fields[:0]
for _, f := range fields {
if s := strings.TrimSpace(f); s != "" {
out = append(out, s)
}
}
return out
}
// compactTool returns a lean representation of a tool for schema
// output, keeping the fields AI agents and scripts need: RPC + CLI
// identity, input/output schema, sensitivity, MCP annotations, and the
// CLI flag overlay (alias/transform/envDefault/default) that shapes
// how raw MCP parameters appear on the command line.
func compactTool(t ir.ToolDescriptor) map[string]any {
tool := map[string]any{
"name": t.RPCName,
"cli_name": t.CLIName,
"canonical_path": t.CanonicalPath,
"title": t.Title,
"description": t.Description,
"sensitive": t.Sensitive,
}
if strings.TrimSpace(t.Group) != "" {
tool["group"] = t.Group
}
if props, ok := t.InputSchema["properties"]; ok {
tool["parameters"] = props
}
if req := requiredFields(t.InputSchema); len(req) > 0 {
tool["required"] = req
}
if len(t.OutputSchema) > 0 {
tool["output_schema"] = t.OutputSchema
}
if t.Annotations != nil {
tool["annotations"] = t.Annotations
}
if len(t.FlagOverlay) > 0 {
tool["flag_overlay"] = t.FlagOverlay
}
return tool
}
+199
View File
@@ -121,6 +121,205 @@ func TestSchemaPayloadFindsTool(t *testing.T) {
}
}
func TestCompactToolEmitsExtendedFields(t *testing.T) {
t.Parallel()
destructive := true
tool := ir.ToolDescriptor{
RPCName: "send_ding_message",
CLIName: "send",
Group: "message",
CanonicalPath: "ding.send_ding_message",
Title: "发送DING消息",
Description: "desc",
Sensitive: true,
InputSchema: map[string]any{
"type": "object",
"required": []any{"robotCode"},
"properties": map[string]any{
"robotCode": map[string]any{"type": "string"},
},
},
OutputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"openDingId": map[string]any{"type": "string"},
},
},
Annotations: &ir.ToolAnnotations{DestructiveHint: &destructive},
FlagOverlay: map[string]ir.FlagOverlay{
"receiverUserIdList": {Alias: "users", Transform: "csv_to_array"},
},
}
out := compactTool(tool)
if out["name"] != "send_ding_message" {
t.Errorf("name = %v", out["name"])
}
if out["cli_name"] != "send" {
t.Errorf("cli_name = %v", out["cli_name"])
}
if out["canonical_path"] != "ding.send_ding_message" {
t.Errorf("canonical_path = %v", out["canonical_path"])
}
if out["group"] != "message" {
t.Errorf("group = %v", out["group"])
}
if _, ok := out["output_schema"]; !ok {
t.Errorf("output_schema missing, keys = %v", keysOf(out))
}
if _, ok := out["annotations"]; !ok {
t.Errorf("annotations missing, keys = %v", keysOf(out))
}
overlay, ok := out["flag_overlay"].(map[string]ir.FlagOverlay)
if !ok {
t.Fatalf("flag_overlay type = %T", out["flag_overlay"])
}
if overlay["receiverUserIdList"].Alias != "users" {
t.Errorf("overlay alias = %q", overlay["receiverUserIdList"].Alias)
}
}
func TestCompactToolOmitsEmptyExtras(t *testing.T) {
t.Parallel()
tool := ir.ToolDescriptor{
RPCName: "list_documents",
CLIName: "list",
CanonicalPath: "doc.list_documents",
InputSchema: map[string]any{"type": "object"},
}
out := compactTool(tool)
for _, key := range []string{"output_schema", "annotations", "flag_overlay", "group"} {
if _, has := out[key]; has {
t.Errorf("key %q should be omitted when empty, got %#v", key, out[key])
}
}
}
func TestSchemaPayloadResolvesCLIPath(t *testing.T) {
t.Parallel()
catalog := ir.Catalog{
Products: []ir.CanonicalProduct{
{
ID: "ding",
Tools: []ir.ToolDescriptor{
{
RPCName: "send_ding_message",
CLIName: "send",
Group: "message",
CanonicalPath: "ding.send_ding_message",
InputSchema: map[string]any{"type": "object"},
},
{
RPCName: "recall_ding_message",
CLIName: "recall",
Group: "message",
CanonicalPath: "ding.recall_ding_message",
InputSchema: map[string]any{"type": "object"},
},
},
},
},
}
cases := []struct {
name string
input string
wantRPC string
wantErr bool
}{
{"canonical rpc path", "ding.send_ding_message", "send_ding_message", false},
{"dotted cli path", "ding.message.send", "send_ding_message", false},
{"space cli path", "ding message send", "send_ding_message", false},
{"slash cli path", "ding/message/recall", "recall_ding_message", false},
{"unknown leaf", "ding message nope", "", true},
{"unknown group", "ding random send", "", true},
{"unknown product", "nope send", "", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
payload, err := schemaPayload(catalog, []string{tc.input})
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
}
if tc.wantErr {
return
}
tool, ok := payload["tool"].(map[string]any)
if !ok {
t.Fatalf("payload tool missing, got %#v", payload)
}
if tool["name"] != tc.wantRPC {
t.Errorf("tool name = %v, want %s", tool["name"], tc.wantRPC)
}
})
}
}
func TestSchemaCommandCLIPathFlag(t *testing.T) {
t.Parallel()
loader := StaticLoader{Catalog: ir.Catalog{
Products: []ir.CanonicalProduct{{
ID: "ding",
Tools: []ir.ToolDescriptor{{
RPCName: "send_ding_message",
CLIName: "send",
Group: "message",
CanonicalPath: "ding.send_ding_message",
InputSchema: map[string]any{"type": "object"},
}},
}},
}}
t.Run("resolves via --cli-path", func(t *testing.T) {
cmd := NewSchemaCommand(loader)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{"--cli-path", "ding message send"})
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v", err)
}
var payload map[string]any
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("decode: %v", err)
}
tool, ok := payload["tool"].(map[string]any)
if !ok {
t.Fatalf("tool missing: %#v", payload)
}
if tool["name"] != "send_ding_message" {
t.Errorf("tool name = %v", tool["name"])
}
})
t.Run("rejects positional + flag collision", func(t *testing.T) {
cmd := NewSchemaCommand(loader)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{"--cli-path", "ding message send", "ding.send_ding_message"})
err := cmd.Execute()
if err == nil {
t.Fatalf("expected mutual-exclusion error, got nil")
}
if !strings.Contains(err.Error(), "mutually exclusive") {
t.Errorf("err = %v, want mutual-exclusion message", err)
}
})
}
func keysOf(m map[string]any) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
func TestNewMCPCommandReturnsLoaderErrorForInvocations(t *testing.T) {
t.Parallel()
+38 -1
View File
@@ -166,7 +166,7 @@ func BuildDynamicCommands(servers []market.ServerDescriptor, runner executor.Run
}
for _, child := range children {
if parent, ok := topLevel[child.parent]; ok {
parent.AddCommand(child.cmd)
attachOrMerge(parent, child.cmd)
} else {
// Parent not found among dynamic commands; emit as top-level.
name := child.cmd.Name()
@@ -389,6 +389,43 @@ func resolveNestedGroup(root *cobra.Command, groupPath string, registry map[stri
return ensureNestedGroup(root, groupPath, groupPath, registry)
}
// attachOrMerge adds child as a sub-command of parent. If parent already has a
// sub-command with the same Name(), the two are merged recursively: child's
// sub-commands are moved onto the existing one and child itself is discarded.
// Leaf collisions (two commands with the same Name and no further children)
// are resolved first-wins — the incoming one is dropped.
//
// This lets multiple server entries share a cli.command under the same parent,
// e.g. bot-message (command="message", parent="chat") can contribute leaves
// into the same "message" subtree already built from chat's own toolOverrides,
// without creating a duplicate "message" sibling in chat's help output.
func attachOrMerge(parent, child *cobra.Command) {
existing := findSubcommand(parent, child.Name())
if existing == nil {
parent.AddCommand(child)
return
}
// Snapshot child's sub-commands before we start moving them (RemoveCommand
// mutates the slice we'd be iterating).
subs := make([]*cobra.Command, len(child.Commands()))
copy(subs, child.Commands())
for _, sub := range subs {
child.RemoveCommand(sub)
attachOrMerge(existing, sub)
}
}
// findSubcommand returns the first sub-command of parent with the given name,
// or nil if none match.
func findSubcommand(parent *cobra.Command, name string) *cobra.Command {
for _, sub := range parent.Commands() {
if sub.Name() == name {
return sub
}
}
return nil
}
// buildOverrideBindings converts CLIToolOverride flags into FlagBindings and
// constructs a Normalizer that applies transform rules.
// Implements §2.1-§2.5 of the conversion rules.
+209
View File
@@ -16,6 +16,8 @@ package compat
import (
"testing"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/market"
)
@@ -143,3 +145,210 @@ func TestBuildDynamicCommands_NoParent(t *testing.T) {
t.Fatalf("expected 2 top-level commands, got %d", len(cmds))
}
}
// TestBuildDynamicCommands_ParentMergeSameName covers the case where two
// servers share the same cli.command + cli.parent. Instead of producing two
// sibling subcommands with the same Name under the parent (which cobra allows
// but `--help` renders as duplicate rows), the compat layer merges them into
// a single subcommand whose children are the union of both sides.
func TestBuildDynamicCommands_ParentMergeSameName(t *testing.T) {
t.Parallel()
servers := []market.ServerDescriptor{
{
Endpoint: "https://endpoint-chat",
CLI: market.CLIOverlay{
ID: "group-chat",
Command: "chat",
Groups: map[string]market.CLIGroupDef{
"message": {Description: "会话消息管理"},
},
ToolOverrides: map[string]market.CLIToolOverride{
"send_message_as_user": {CLIName: "send", Group: "message"},
"list_conversation_message_v2": {CLIName: "list", Group: "message"},
},
},
},
{
// Second server contributes more leaves into the same "message"
// namespace via command="message" + parent="chat".
Endpoint: "https://endpoint-bot",
CLI: market.CLIOverlay{
ID: "bot-message",
Command: "message",
Parent: "chat",
ToolOverrides: map[string]market.CLIToolOverride{
"send_robot_group_message": {CLIName: "send-by-bot"},
"send_message_by_custom_robot": {CLIName: "send-by-webhook"},
},
},
},
}
cmds := BuildDynamicCommands(servers, executor.EchoRunner{}, nil)
if len(cmds) != 1 || cmds[0].Name() != "chat" {
t.Fatalf("expected single top-level 'chat', got %d cmds", len(cmds))
}
// There must be exactly one child named "message" under chat, not two.
var messageCmds []*cobra.Command
for _, sub := range cmds[0].Commands() {
if sub.Name() == "message" {
messageCmds = append(messageCmds, sub)
}
}
if len(messageCmds) != 1 {
names := make([]string, 0, len(cmds[0].Commands()))
for _, c := range cmds[0].Commands() {
names = append(names, c.Name())
}
t.Fatalf("expected exactly one 'message' under chat, got %d — chat children: %v", len(messageCmds), names)
}
// The merged message subcommand must contain all four leaves.
want := map[string]bool{"send": false, "list": false, "send-by-bot": false, "send-by-webhook": false}
for _, leaf := range messageCmds[0].Commands() {
if _, ok := want[leaf.Name()]; ok {
want[leaf.Name()] = true
}
}
for leaf, seen := range want {
if !seen {
t.Errorf("expected 'chat message %s' after merge, missing", leaf)
}
}
}
// TestBuildDynamicCommands_ParentMergeRecursive covers a multi-level merge:
// chat already has `group.members` (with add/remove), and a separate
// bot-group server contributes `dws chat group members add-bot` via
// command=group + parent=chat + groups.members. The "group" and "members"
// nodes must each be merged, not duplicated.
func TestBuildDynamicCommands_ParentMergeRecursive(t *testing.T) {
t.Parallel()
servers := []market.ServerDescriptor{
{
Endpoint: "https://endpoint-chat",
CLI: market.CLIOverlay{
ID: "group-chat",
Command: "chat",
Groups: map[string]market.CLIGroupDef{
"group": {Description: "群组管理"},
"group.members": {Description: "群成员管理"},
},
ToolOverrides: map[string]market.CLIToolOverride{
"add_group_member": {CLIName: "add", Group: "group.members"},
"remove_group_member": {CLIName: "remove", Group: "group.members"},
},
},
},
{
Endpoint: "https://endpoint-bot",
CLI: market.CLIOverlay{
ID: "bot-group",
Command: "group",
Parent: "chat",
Groups: map[string]market.CLIGroupDef{
"members": {Description: "机器人群成员"},
},
ToolOverrides: map[string]market.CLIToolOverride{
"add_robot_to_group": {CLIName: "add-bot", Group: "members"},
},
},
},
}
cmds := BuildDynamicCommands(servers, executor.EchoRunner{}, nil)
if len(cmds) != 1 || cmds[0].Name() != "chat" {
t.Fatalf("expected single top-level 'chat', got %d", len(cmds))
}
// One 'group' under chat.
var groupCmds []*cobra.Command
for _, sub := range cmds[0].Commands() {
if sub.Name() == "group" {
groupCmds = append(groupCmds, sub)
}
}
if len(groupCmds) != 1 {
t.Fatalf("expected single 'group' under chat, got %d", len(groupCmds))
}
// One 'members' under chat.group.
var membersCmds []*cobra.Command
for _, sub := range groupCmds[0].Commands() {
if sub.Name() == "members" {
membersCmds = append(membersCmds, sub)
}
}
if len(membersCmds) != 1 {
t.Fatalf("expected single 'members' under chat.group, got %d", len(membersCmds))
}
// The merged members subcommand must contain add, remove, add-bot.
want := map[string]bool{"add": false, "remove": false, "add-bot": false}
for _, leaf := range membersCmds[0].Commands() {
if _, ok := want[leaf.Name()]; ok {
want[leaf.Name()] = true
}
}
for leaf, seen := range want {
if !seen {
t.Errorf("expected 'chat group members %s', missing", leaf)
}
}
}
// TestBuildDynamicCommands_ParentMergeLeafCollision verifies that when two
// servers both produce the same leaf path (e.g. both try to register
// `chat message send`), the first one wins and the second is silently
// dropped rather than producing a duplicate cobra command.
func TestBuildDynamicCommands_ParentMergeLeafCollision(t *testing.T) {
t.Parallel()
servers := []market.ServerDescriptor{
{
Endpoint: "https://endpoint-chat",
CLI: market.CLIOverlay{
ID: "group-chat",
Command: "chat",
Groups: map[string]market.CLIGroupDef{"message": {Description: "消息"}},
ToolOverrides: map[string]market.CLIToolOverride{
"send_message_as_user": {CLIName: "send", Group: "message"},
},
},
},
{
Endpoint: "https://endpoint-bot",
CLI: market.CLIOverlay{
ID: "bot-message",
Command: "message",
Parent: "chat",
ToolOverrides: map[string]market.CLIToolOverride{
// Intentional collision: same leaf name.
"send_robot_group_message": {CLIName: "send"},
},
},
},
}
cmds := BuildDynamicCommands(servers, executor.EchoRunner{}, nil)
if len(cmds) != 1 {
t.Fatalf("expected 1 top-level, got %d", len(cmds))
}
messageCmd := findSubcommand(cmds[0], "message")
if messageCmd == nil {
t.Fatal("expected 'message' subcommand under chat")
}
// Exactly one 'send' leaf.
var sendCount int
for _, leaf := range messageCmd.Commands() {
if leaf.Name() == "send" {
sendCount++
}
}
if sendCount != 1 {
t.Fatalf("expected exactly one 'send' leaf, got %d", sendCount)
}
}
+28 -3
View File
@@ -20,6 +20,8 @@ import (
"strings"
"time"
"gopkg.in/yaml.v3"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
)
@@ -110,6 +112,18 @@ func transformCSVToArray(value any) (any, error) {
return result, nil
}
// transformJSONParse parses a CLI string into a structured value so callers can
// pass complex payloads (JSON arrays/objects) through a single flag.
//
// Two input dialects are accepted, in order:
// 1. Strict JSON — `[{"fieldName":"x","type":"text"}]`
// 2. YAML (flow) — `[{fieldName: x, type: text}]`
//
// YAML is a superset of JSON that permits unquoted keys and strings, which
// dramatically reduces the need for shell-level escaping. Users can therefore
// write `--fields '[{fieldName: 标题, type: text}]'` instead of piling quotes
// around every token. The output shape is the same either way; downstream
// consumers see the parsed Go value, not the original dialect.
func transformJSONParse(value any) (any, error) {
s, ok := toString(value)
if !ok {
@@ -119,11 +133,22 @@ func transformJSONParse(value any) (any, error) {
if s == "" {
return value, nil
}
// Strict JSON first — fast path and unambiguous type promotion (numbers
// stay numbers, etc.).
var parsed any
if err := json.Unmarshal([]byte(s), &parsed); err != nil {
return nil, apperrors.NewValidation(fmt.Sprintf("json_parse: invalid JSON: %v", err))
if err := json.Unmarshal([]byte(s), &parsed); err == nil {
return parsed, nil
}
return parsed, nil
// YAML (flow) fallback — accepts `{key: value}` without surrounding
// quotes, which is the natural form when typing at a shell prompt.
if err := yaml.Unmarshal([]byte(s), &parsed); err == nil {
return parsed, nil
}
return nil, apperrors.NewValidation(
"json_parse: input is not valid JSON or YAML; " +
"quote the whole value and use `[{key: value, ...}]` for ad-hoc input, " +
"or pass `@path/to/file.json` to read from a file",
)
}
func transformEnumMap(value any, args map[string]any) (any, error) {
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compat
import (
"reflect"
"testing"
)
// TestJSONParse_StrictJSON covers the primary path: callers passing
// canonical JSON (as generated programmatically or by agents).
func TestJSONParse_StrictJSON(t *testing.T) {
t.Parallel()
input := `[{"fieldName":"title","type":"text"},{"fieldName":"count","type":"number"}]`
got, err := ApplyTransform(input, "json_parse", nil)
if err != nil {
t.Fatalf("strict JSON should parse, got err: %v", err)
}
arr, ok := got.([]any)
if !ok || len(arr) != 2 {
t.Fatalf("expected []any of length 2, got %T %v", got, got)
}
}
// TestJSONParse_YAMLFlowFallback is the motivating case: a user types an
// ad-hoc JSON-shaped array without quoting every key and value. YAML flow
// syntax accepts it and the parsed output is indistinguishable from the
// strict-JSON equivalent.
func TestJSONParse_YAMLFlowFallback(t *testing.T) {
t.Parallel()
// Intentionally unquoted keys, unquoted string values, and Chinese
// identifiers — typical of what humans type at a shell.
input := `[{fieldName: 标题, type: text}, {fieldName: 数量, type: number, config: {formatter: INT}}, {fieldName: 状态, type: singleSelect, config: {options: [{name: 待办}, {name: 进行中}, {name: 已完成}]}}, {fieldName: 已确认, type: checkbox}]`
got, err := ApplyTransform(input, "json_parse", nil)
if err != nil {
t.Fatalf("YAML-flow input should parse, got err: %v", err)
}
arr, ok := got.([]any)
if !ok {
t.Fatalf("expected []any, got %T", got)
}
if len(arr) != 4 {
t.Fatalf("expected 4 field definitions, got %d", len(arr))
}
// Spot-check the third entry, which is the most deeply nested.
third, ok := arr[2].(map[string]any)
if !ok {
t.Fatalf("arr[2] expected map[string]any, got %T", arr[2])
}
if third["fieldName"] != "状态" {
t.Errorf("arr[2].fieldName: want 状态, got %v", third["fieldName"])
}
config, ok := third["config"].(map[string]any)
if !ok {
t.Fatalf("arr[2].config expected map, got %T", third["config"])
}
options, ok := config["options"].([]any)
if !ok || len(options) != 3 {
t.Fatalf("arr[2].config.options: want 3 items, got %v", config["options"])
}
}
// TestJSONParse_EmptyString preserves the legacy behaviour of returning the
// original value untouched when the caller passes an empty / whitespace-only
// string, matching how other transforms treat empty input.
func TestJSONParse_EmptyString(t *testing.T) {
t.Parallel()
cases := []string{"", " ", "\n\t"}
for _, in := range cases {
got, err := ApplyTransform(in, "json_parse", nil)
if err != nil {
t.Errorf("empty input %q should not error: %v", in, err)
continue
}
if !reflect.DeepEqual(got, in) {
t.Errorf("empty input %q should pass through, got %v", in, got)
}
}
}
// TestJSONParse_NonString passes through non-string inputs (already-parsed
// values flowing through the pipeline).
func TestJSONParse_NonString(t *testing.T) {
t.Parallel()
preParsed := []any{map[string]any{"k": "v"}}
got, err := ApplyTransform(preParsed, "json_parse", nil)
if err != nil {
t.Fatalf("non-string should pass through: %v", err)
}
if !reflect.DeepEqual(got, preParsed) {
t.Errorf("non-string should pass through unchanged, got %v", got)
}
}
// TestJSONParse_InvalidInput verifies that genuine garbage is still rejected
// with a user-facing validation error that nudges towards `@file` syntax.
func TestJSONParse_InvalidInput(t *testing.T) {
t.Parallel()
// Unterminated bracket — neither valid JSON nor valid YAML flow.
_, err := ApplyTransform("[{fieldName:", "json_parse", nil)
if err == nil {
t.Fatal("expected error for malformed input")
}
if msg := err.Error(); msg == "" {
t.Fatal("error message should be non-empty")
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ func newChatMessageSendByBotCommand(runner executor.Runner) *cobra.Command {
invocation := executor.NewHelperInvocation(
cobracmd.LegacyCommandPath(cmd),
"chat",
"bot",
tool,
params,
)
+51
View File
@@ -43,4 +43,55 @@ func TestChatMessageSendByBotIgnoresLegacyRealBuildModeEnv(t *testing.T) {
if got := runner.last.Params["robotCode"]; got != "robot-001" {
t.Fatalf("robotCode = %#v, want robot-001", got)
}
if got := runner.last.CanonicalProduct; got != "bot" {
t.Fatalf("CanonicalProduct = %q, want bot", got)
}
}
func TestChatMessageSendByBotRoutesToBotProduct(t *testing.T) {
cases := []struct {
name string
args []string
wantTool string
}{
{
name: "single-chat",
args: []string{
"--users", "user-001",
"--robot-code", "robot-001",
"--title", "t",
"--text", "x",
},
wantTool: "batch_send_robot_msg_to_users",
},
{
name: "group-chat",
args: []string{
"--group", "cid-xyz",
"--robot-code", "robot-001",
"--title", "t",
"--text", "x",
},
wantTool: "send_robot_group_message",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runner := &captureRunner{}
cmd := newChatMessageSendByBotCommand(runner)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&out)
cmd.SetArgs(tc.args)
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v\noutput:\n%s", err, out.String())
}
if got := runner.last.CanonicalProduct; got != "bot" {
t.Fatalf("CanonicalProduct = %q, want bot", got)
}
if got := runner.last.Tool; got != tc.wantTool {
t.Fatalf("Tool = %q, want %q", got, tc.wantTool)
}
})
}
}
+117
View File
@@ -49,6 +49,29 @@ type CLIFlagHint struct {
Alias string `json:"alias,omitempty"`
}
// FlagOverlay carries CLI-layer transformation metadata for a single
// MCP parameter: the flag alias the user types, the transform applied
// before dispatch, env-var fallback, default value, and whether the flag
// is hidden from help. Sourced from market.CLIToolOverride.Flags.
type FlagOverlay struct {
Alias string `json:"alias,omitempty"`
Transform string `json:"transform,omitempty"`
TransformArgs map[string]any `json:"transform_args,omitempty"`
EnvDefault string `json:"env_default,omitempty"`
Default string `json:"default,omitempty"`
Hidden bool `json:"hidden,omitempty"`
}
// ToolAnnotations mirrors MCP 2025+ tool annotations. All hints are
// nullable: absence means "unknown", not "false". Populate only when the
// source has a clear signal — don't guess.
type ToolAnnotations struct {
DestructiveHint *bool `json:"destructive_hint,omitempty"`
ReadOnlyHint *bool `json:"read_only_hint,omitempty"`
IdempotentHint *bool `json:"idempotent_hint,omitempty"`
OpenWorldHint *bool `json:"open_world_hint,omitempty"`
}
type CanonicalProduct struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
@@ -67,13 +90,16 @@ type CanonicalProduct struct {
type ToolDescriptor struct {
RPCName string `json:"rpc_name"`
CLIName string `json:"cli_name,omitempty"`
Group string `json:"group,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
InputSchema map[string]any `json:"input_schema,omitempty"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
Sensitive bool `json:"sensitive"`
Annotations *ToolAnnotations `json:"annotations,omitempty"`
Hidden bool `json:"hidden,omitempty"`
FlagHints map[string]CLIFlagHint `json:"flag_hints,omitempty"`
FlagOverlay map[string]FlagOverlay `json:"flag_overlay,omitempty"`
SourceServerKey string `json:"source_server_key"`
CanonicalPath string `json:"canonical_path"`
}
@@ -105,10 +131,58 @@ func BuildCatalog(runtimeServers []discovery.RuntimeServer) Catalog {
sensitiveByTool := make(map[string]bool, len(runtimeServer.Server.CLI.Tools))
hasSensitiveOverride := make(map[string]bool, len(runtimeServer.Server.CLI.Tools))
toolCLIName := make(map[string]string, len(runtimeServer.Server.CLI.Tools))
toolGroup := make(map[string]string)
toolHidden := make(map[string]bool, len(runtimeServer.Server.CLI.Tools))
toolTitleOverride := make(map[string]string, len(runtimeServer.Server.CLI.Tools))
toolDescriptionOverride := make(map[string]string, len(runtimeServer.Server.CLI.Tools))
toolFlagHints := make(map[string]map[string]CLIFlagHint, len(runtimeServer.Server.CLI.Tools))
toolFlagOverlay := make(map[string]map[string]FlagOverlay)
// Merge richer market.CLIToolOverride data (group, cliName, flag
// transforms/aliases/env-defaults) that the dynamic compat layer
// already consumes but the IR previously dropped.
for name, override := range runtimeServer.Server.CLI.ToolOverrides {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
continue
}
if cn := strings.TrimSpace(override.CLIName); cn != "" {
toolCLIName[trimmed] = cn
}
if gp := strings.TrimSpace(override.Group); gp != "" {
toolGroup[trimmed] = gp
}
if override.Hidden {
toolHidden[trimmed] = true
}
if override.IsSensitive {
sensitiveByTool[trimmed] = true
hasSensitiveOverride[trimmed] = true
}
if desc := strings.TrimSpace(override.Description); desc != "" && toolDescriptionOverride[trimmed] == "" {
toolDescriptionOverride[trimmed] = desc
}
if len(override.Flags) > 0 {
overlay := make(map[string]FlagOverlay, len(override.Flags))
for paramName, flagOverride := range override.Flags {
paramTrim := strings.TrimSpace(paramName)
if paramTrim == "" {
continue
}
overlay[paramTrim] = FlagOverlay{
Alias: strings.TrimSpace(flagOverride.Alias),
Transform: strings.TrimSpace(flagOverride.Transform),
TransformArgs: cloneTransformArgs(flagOverride.TransformArgs),
EnvDefault: strings.TrimSpace(flagOverride.EnvDefault),
Default: strings.TrimSpace(flagOverride.Default),
Hidden: flagOverride.Hidden,
}
}
if len(overlay) > 0 {
toolFlagOverlay[trimmed] = overlay
}
}
}
for _, cliTool := range runtimeServer.Server.CLI.Tools {
name := strings.TrimSpace(cliTool.Name)
if name == "" {
@@ -164,13 +238,16 @@ func BuildCatalog(runtimeServers []discovery.RuntimeServer) Catalog {
tools = append(tools, ToolDescriptor{
RPCName: tool.Name,
CLIName: cliName,
Group: strings.TrimSpace(toolGroup[tool.Name]),
Title: title,
Description: description,
InputSchema: cloneMap(tool.InputSchema),
OutputSchema: cloneMap(tool.OutputSchema),
Sensitive: sensitive,
Annotations: deriveAnnotations(sensitive),
Hidden: toolHidden[tool.Name],
FlagHints: cloneFlagHints(toolFlagHints[tool.Name]),
FlagOverlay: cloneFlagOverlay(toolFlagOverlay[tool.Name]),
SourceServerKey: runtimeServer.Server.Key,
CanonicalPath: fmt.Sprintf("%s.%s", productID, tool.Name),
})
@@ -363,3 +440,43 @@ func cloneFlagHints(value map[string]CLIFlagHint) map[string]CLIFlagHint {
}
return out
}
func cloneFlagOverlay(value map[string]FlagOverlay) map[string]FlagOverlay {
if len(value) == 0 {
return nil
}
out := make(map[string]FlagOverlay, len(value))
for key, overlay := range value {
overlay.TransformArgs = cloneTransformArgs(overlay.TransformArgs)
out[key] = overlay
}
return out
}
func cloneTransformArgs(value map[string]any) map[string]any {
if len(value) == 0 {
return nil
}
data, err := json.Marshal(value)
if err != nil {
return nil
}
var cloned map[string]any
if err := json.Unmarshal(data, &cloned); err != nil {
return nil
}
return cloned
}
// deriveAnnotations maps the coarse-grained Sensitive bool onto MCP 2025+
// annotation hints. Only DestructiveHint is populated — the other hints
// (read_only, idempotent, open_world) stay nil until the upstream tool
// manifest advertises them explicitly. Guessing from name prefixes
// ("list_", "get_") risks false signals for AI agents.
func deriveAnnotations(sensitive bool) *ToolAnnotations {
if !sensitive {
return nil
}
destructive := true
return &ToolAnnotations{DestructiveHint: &destructive}
}
+108
View File
@@ -216,6 +216,114 @@ func TestBuildCatalogFallsBackToRuntimeSensitiveMetadata(t *testing.T) {
}
}
func TestBuildCatalogCarriesToolOverrideGroupAndFlagOverlay(t *testing.T) {
t.Parallel()
catalog := BuildCatalog([]discovery.RuntimeServer{
{
Server: market.ServerDescriptor{
Key: "ding-key",
DisplayName: "DING",
Endpoint: "https://example.com/server/ding",
CLI: market.CLIOverlay{
Command: "ding",
ToolOverrides: map[string]market.CLIToolOverride{
"send_ding_message": {
CLIName: "send",
Group: "message",
IsSensitive: true,
Flags: map[string]market.CLIFlagOverride{
"receiverUserIdList": {
Alias: "users",
Transform: "csv_to_array",
},
"robotCode": {
Alias: "robot-code",
EnvDefault: "DINGTALK_DING_ROBOT_CODE",
},
"remindType": {
Alias: "type",
Transform: "enum_map",
TransformArgs: map[string]any{
"_default": float64(1),
"app": float64(1),
},
},
},
},
},
},
},
Tools: []transport.ToolDescriptor{
{Name: "send_ding_message", Title: "发送DING消息", InputSchema: map[string]any{"type": "object"}},
},
},
})
if len(catalog.Products) != 1 {
t.Fatalf("products len = %d, want 1", len(catalog.Products))
}
product := catalog.Products[0]
if product.ID != "ding" {
t.Fatalf("product ID = %q, want ding", product.ID)
}
tool, ok := product.FindTool("send_ding_message")
if !ok {
t.Fatalf("tool not found")
}
if tool.CLIName != "send" {
t.Fatalf("CLIName = %q, want send", tool.CLIName)
}
if tool.Group != "message" {
t.Fatalf("Group = %q, want message", tool.Group)
}
if !tool.Sensitive {
t.Fatalf("Sensitive = false, want true (from overlay)")
}
if tool.Annotations == nil || tool.Annotations.DestructiveHint == nil || !*tool.Annotations.DestructiveHint {
t.Fatalf("DestructiveHint not propagated, got %#v", tool.Annotations)
}
if tool.Annotations.ReadOnlyHint != nil {
t.Fatalf("ReadOnlyHint should be nil when source is unknown, got %#v", *tool.Annotations.ReadOnlyHint)
}
users := tool.FlagOverlay["receiverUserIdList"]
if users.Alias != "users" || users.Transform != "csv_to_array" {
t.Fatalf("receiverUserIdList overlay = %#v", users)
}
robot := tool.FlagOverlay["robotCode"]
if robot.Alias != "robot-code" || robot.EnvDefault != "DINGTALK_DING_ROBOT_CODE" {
t.Fatalf("robotCode overlay = %#v", robot)
}
remind := tool.FlagOverlay["remindType"]
if remind.Transform != "enum_map" {
t.Fatalf("remindType.Transform = %q, want enum_map", remind.Transform)
}
if v, _ := remind.TransformArgs["app"].(float64); v != 1 {
t.Fatalf("remindType.TransformArgs[app] = %v, want 1", remind.TransformArgs["app"])
}
}
func TestBuildCatalogLeavesAnnotationsNilForNonSensitive(t *testing.T) {
t.Parallel()
catalog := BuildCatalog([]discovery.RuntimeServer{
{
Server: market.ServerDescriptor{
Key: "doc-key",
DisplayName: "文档",
Endpoint: "https://example.com/server/doc",
},
Tools: []transport.ToolDescriptor{
{Name: "list_documents", InputSchema: map[string]any{"type": "object"}},
},
},
})
tool := catalog.Products[0].Tools[0]
if tool.Annotations != nil {
t.Fatalf("Annotations = %#v, want nil for non-sensitive tool", tool.Annotations)
}
}
func TestBuildCatalogConsumesCLIRouteMetadata(t *testing.T) {
t.Parallel()
+8 -3
View File
@@ -29,9 +29,10 @@ import (
type Format string
const (
FormatJSON Format = "json"
FormatTable Format = "table"
FormatRaw Format = "raw"
FormatJSON Format = "json"
FormatTable Format = "table"
FormatRaw Format = "raw"
FormatPretty Format = "pretty"
)
var preferredListKeys = []string{"items", "results", "data", "list", "records", "tools", "servers", "products"}
@@ -75,6 +76,8 @@ func Write(w io.Writer, format Format, payload any) error {
return writeRaw(w, payload)
case FormatTable:
return writeTableish(w, payload)
case FormatPretty:
return writePretty(w, payload)
default:
return WriteJSON(w, payload)
}
@@ -123,6 +126,8 @@ func normalizeFormat(raw string, fallback Format) Format {
return FormatRaw
case string(FormatTable):
return FormatTable
case string(FormatPretty):
return FormatPretty
default:
return fallback
}
+302
View File
@@ -0,0 +1,302 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"encoding/json"
"fmt"
"io"
"sort"
"strings"
"github.com/fatih/color"
)
// writePretty renders the payload as ANSI-colored, human-readable text.
// If the payload looks like a `dws schema` response (has `kind: "schema"`),
// a schema-specific hierarchical renderer runs. Anything else falls back
// to the table renderer so `--format pretty` is safe on any command.
func writePretty(w io.Writer, payload any) error {
normalized, err := normalizePayload(payload)
if err != nil {
return err
}
if m, ok := normalized.(map[string]any); ok {
if kind, _ := m["kind"].(string); kind == "schema" {
return writeSchemaPretty(w, m)
}
}
return writeTableish(w, normalized)
}
// writeSchemaPretty handles both the list-all shape (has `products`) and
// the single-tool shape (has `tool`). Colours are on by default via fatih/color,
// auto-disabled when stdout is not a TTY or when NO_COLOR is set.
func writeSchemaPretty(w io.Writer, payload map[string]any) error {
bold := color.New(color.Bold).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
green := color.New(color.FgGreen).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
red := color.New(color.FgRed, color.Bold).SprintFunc()
dim := color.New(color.Faint).SprintFunc()
if tool, ok := payload["tool"].(map[string]any); ok {
return writeSchemaToolPretty(w, payload, tool, bold, cyan, green, yellow, red, dim)
}
if products, ok := payload["products"].([]any); ok {
return writeSchemaListPretty(w, payload, products, bold, cyan, dim)
}
if degraded, _ := payload["degraded"].(bool); degraded {
reason, _ := payload["reason"].(string)
hint, _ := payload["hint"].(string)
fmt.Fprintf(w, "%s discovery degraded: %s\n", red("!"), yellow(reason))
if hint != "" {
fmt.Fprintf(w, " %s %s\n", dim("hint:"), hint)
}
return nil
}
return writeTableish(w, payload)
}
func writeSchemaListPretty(
w io.Writer,
payload map[string]any,
products []any,
bold, cyan, dim func(...any) string,
) error {
count, _ := payload["count"].(float64)
if count == 0 {
count = float64(len(products))
}
fmt.Fprintf(w, "%s %s products discovered\n", bold("Catalog"), cyan(fmt.Sprintf("%d", int(count))))
for _, raw := range products {
p, ok := raw.(map[string]any)
if !ok {
continue
}
id, _ := p["id"].(string)
name, _ := p["name"].(string)
desc, _ := p["description"].(string)
tools, _ := p["tools"].([]any)
fmt.Fprintf(w, "\n%s %s %s\n", bold("▸"), bold(id), dim(name))
if desc != "" && desc != name {
fmt.Fprintf(w, " %s\n", dim(desc))
}
fmt.Fprintf(w, " %s %s\n", dim("tools:"), cyan(fmt.Sprintf("%d", len(tools))))
shown := 0
for _, t := range tools {
tm, ok := t.(map[string]any)
if !ok {
continue
}
rpc, _ := tm["name"].(string)
cli, _ := tm["cli_name"].(string)
if cli == "" || cli == rpc {
fmt.Fprintf(w, " - %s\n", rpc)
} else {
fmt.Fprintf(w, " - %s %s\n", rpc, dim("→ "+cli))
}
shown++
if shown >= 6 && len(tools) > 8 {
fmt.Fprintf(w, " %s\n", dim(fmt.Sprintf("… %d more", len(tools)-shown)))
break
}
}
}
return nil
}
func writeSchemaToolPretty(
w io.Writer,
payload map[string]any,
tool map[string]any,
bold, cyan, green, yellow, red, dim func(...any) string,
) error {
rpc, _ := tool["name"].(string)
cli, _ := tool["cli_name"].(string)
title, _ := tool["title"].(string)
desc, _ := tool["description"].(string)
group, _ := tool["group"].(string)
canonical, _ := tool["canonical_path"].(string)
header := rpc
if title != "" && title != rpc {
header = fmt.Sprintf("%s %s", rpc, dim(title))
}
fmt.Fprintf(w, "%s %s\n", bold("Tool"), header)
var productID string
if product, ok := payload["product"].(map[string]any); ok {
pid, _ := product["id"].(string)
pname, _ := product["name"].(string)
productID = pid
fmt.Fprintf(w, " %s %s %s\n", dim("product:"), pid, dim(pname))
}
if canonical != "" {
fmt.Fprintf(w, " %s %s\n", dim("canonical:"), canonical)
}
if cli != "" {
parts := []string{}
if productID != "" {
parts = append(parts, productID)
}
if group != "" {
parts = append(parts, strings.Split(group, ".")...)
}
parts = append(parts, cli)
fmt.Fprintf(w, " %s %s\n", dim("cli path:"), cyan(strings.Join(parts, " ")))
}
// Sensitivity + annotations in one line.
sensitive, _ := tool["sensitive"].(bool)
if sensitive {
fmt.Fprintf(w, " %s %s\n", dim("sensitive:"), red("yes (needs --yes)"))
}
if ann, ok := tool["annotations"].(map[string]any); ok && len(ann) > 0 {
var parts []string
for _, key := range sortedMapKeys(ann) {
parts = append(parts, fmt.Sprintf("%s=%v", key, ann[key]))
}
fmt.Fprintf(w, " %s %s\n", dim("annotations:"), yellow(strings.Join(parts, " ")))
}
if desc != "" && desc != title {
fmt.Fprintln(w)
for _, line := range strings.Split(strings.TrimSpace(desc), "\n") {
fmt.Fprintf(w, " %s\n", dim(line))
}
}
// Parameters section.
if params, ok := tool["parameters"].(map[string]any); ok && len(params) > 0 {
required := map[string]bool{}
if req, ok := tool["required"].([]any); ok {
for _, r := range req {
if s, ok := r.(string); ok {
required[s] = true
}
}
}
overlay := map[string]map[string]any{}
if ov, ok := tool["flag_overlay"].(map[string]any); ok {
for name, v := range ov {
if m, ok := v.(map[string]any); ok {
overlay[name] = m
}
}
}
fmt.Fprintln(w)
fmt.Fprintf(w, "%s\n", bold("Parameters"))
for _, name := range sortedMapKeys(params) {
prop, _ := params[name].(map[string]any)
writeParamPretty(w, name, prop, required[name], overlay[name],
bold, cyan, green, yellow, red, dim)
}
}
// Output schema, if any.
if out, ok := tool["output_schema"].(map[string]any); ok && len(out) > 0 {
fmt.Fprintln(w)
fmt.Fprintf(w, "%s\n", bold("Output schema"))
b, _ := json.MarshalIndent(out, " ", " ")
fmt.Fprintf(w, " %s\n", string(b))
}
return nil
}
func writeParamPretty(
w io.Writer,
name string,
prop map[string]any,
required bool,
overlay map[string]any,
bold, cyan, green, yellow, red, dim func(...any) string,
) {
typeStr := describeType(prop)
marker := " "
if required {
marker = red("*")
}
alias, _ := overlay["alias"].(string)
line := fmt.Sprintf(" %s %s", marker, bold(name))
if alias != "" && alias != name {
line += fmt.Sprintf(" %s", cyan("--"+alias))
}
line += fmt.Sprintf(" %s", dim(typeStr))
fmt.Fprintln(w, line)
if d, _ := prop["description"].(string); d != "" {
for _, ln := range strings.Split(strings.TrimSpace(d), "\n") {
fmt.Fprintf(w, " %s\n", dim(ln))
}
}
// enum values — render inline.
if enum, ok := prop["enum"].([]any); ok && len(enum) > 0 {
vals := make([]string, 0, len(enum))
for _, v := range enum {
vals = append(vals, fmt.Sprintf("%v", v))
}
fmt.Fprintf(w, " %s %s\n", dim("enum:"), green(strings.Join(vals, ", ")))
}
// overlay — transform / env default / default.
if transform, _ := overlay["transform"].(string); transform != "" {
extras := transform
if args, ok := overlay["transform_args"].(map[string]any); ok && len(args) > 0 {
kvs := make([]string, 0, len(args))
for _, k := range sortedMapKeys(args) {
kvs = append(kvs, fmt.Sprintf("%s=%v", k, args[k]))
}
extras += "(" + strings.Join(kvs, ", ") + ")"
}
fmt.Fprintf(w, " %s %s\n", dim("transform:"), yellow(extras))
}
if env, _ := overlay["env_default"].(string); env != "" {
fmt.Fprintf(w, " %s %s\n", dim("env default:"), green("$"+env))
}
if def, _ := overlay["default"].(string); def != "" {
fmt.Fprintf(w, " %s %s\n", dim("default:"), green(def))
}
}
func describeType(prop map[string]any) string {
t, _ := prop["type"].(string)
if t == "array" {
if items, ok := prop["items"].(map[string]any); ok {
if inner, _ := items["type"].(string); inner != "" {
return inner + "[]"
}
}
return "array"
}
if t == "" {
return "any"
}
return t
}
func sortedMapKeys(m map[string]any) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
+221
View File
@@ -0,0 +1,221 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bytes"
"strings"
"testing"
"github.com/fatih/color"
)
// forceNoColor disables ANSI codes for assertion simplicity. fatih/color
// would already do this when writing to a non-TTY, but tests use bytes.Buffer
// so we flip the flag explicitly.
func forceNoColor(t *testing.T) {
t.Helper()
prev := color.NoColor
color.NoColor = true
t.Cleanup(func() { color.NoColor = prev })
}
func TestPretty_SchemaListRendersProducts(t *testing.T) {
forceNoColor(t)
payload := map[string]any{
"kind": "schema",
"count": 2,
"products": []any{
map[string]any{
"id": "ding",
"name": "DING消息",
"description": "DING 消息 / 发送 / 撤回",
"tools": []any{
map[string]any{"name": "send_ding_message", "cli_name": "send"},
map[string]any{"name": "recall_ding_message", "cli_name": "recall"},
},
},
map[string]any{
"id": "doc",
"name": "钉钉文档",
"tools": []any{map[string]any{"name": "create_document", "cli_name": "create"}},
},
},
}
var buf bytes.Buffer
if err := writePretty(&buf, payload); err != nil {
t.Fatalf("writePretty() error = %v", err)
}
out := buf.String()
wants := []string{
"Catalog",
"2 products discovered",
"ding",
"send_ding_message",
"→ send",
"recall_ding_message",
"doc",
"create_document",
}
for _, want := range wants {
if !strings.Contains(out, want) {
t.Errorf("pretty list missing %q; got:\n%s", want, out)
}
}
}
func TestPretty_SchemaToolRendersAllSections(t *testing.T) {
forceNoColor(t)
payload := map[string]any{
"kind": "schema",
"path": "ding.send_ding_message",
"product": map[string]any{
"id": "ding",
"name": "DING消息",
},
"tool": map[string]any{
"name": "send_ding_message",
"cli_name": "send",
"canonical_path": "ding.send_ding_message",
"group": "message",
"title": "发送DING消息",
"description": "使用企业内机器人发送DING消息",
"sensitive": true,
"annotations": map[string]any{
"destructive_hint": true,
},
"parameters": map[string]any{
"robotCode": map[string]any{
"type": "string",
"description": "机器人Code",
},
"receiverUserIdList": map[string]any{
"type": "array",
"items": map[string]any{"type": "string"},
"description": "接收者用户ID列表",
},
"remindType": map[string]any{
"type": "number",
"description": "提醒类型",
},
},
"required": []any{"robotCode", "receiverUserIdList", "remindType"},
"flag_overlay": map[string]any{
"receiverUserIdList": map[string]any{
"alias": "users",
"transform": "csv_to_array",
},
"robotCode": map[string]any{
"alias": "robot-code",
"env_default": "DINGTALK_DING_ROBOT_CODE",
},
"remindType": map[string]any{
"alias": "type",
"transform": "enum_map",
"transform_args": map[string]any{
"app": 1,
"sms": 2,
"call": 3,
"_default": 1,
},
},
},
},
}
var buf bytes.Buffer
if err := writePretty(&buf, payload); err != nil {
t.Fatalf("writePretty() error = %v", err)
}
out := buf.String()
wants := []string{
"Tool send_ding_message", // header
"canonical:", // meta section
"ding.send_ding_message",
"cli path:",
"ding message send",
"sensitive:",
"yes (needs --yes)",
"annotations:",
"destructive_hint=true",
"Parameters",
"robotCode",
"--robot-code", // overlay alias rendered
"env default:",
"$DINGTALK_DING_ROBOT_CODE",
"receiverUserIdList",
"--users",
"transform:",
"csv_to_array",
"string[]", // array<string> shorthand
"remindType",
"--type",
"enum_map(", // with args inlined
"app=1",
}
for _, want := range wants {
if !strings.Contains(out, want) {
t.Errorf("pretty tool missing %q; full output:\n%s", want, out)
}
}
// required params must show the red asterisk marker (even with color disabled
// the literal '*' still appears).
for _, req := range []string{"robotCode", "receiverUserIdList", "remindType"} {
// format: " * <name>"
if !strings.Contains(out, "* "+req) {
t.Errorf("required marker missing for %q", req)
}
}
}
func TestPretty_NonSchemaFallsBackToTableish(t *testing.T) {
forceNoColor(t)
// Payload that doesn't have kind="schema" should fall through to
// the tableish renderer, not error or render a schema header.
payload := map[string]any{"items": []any{
map[string]any{"id": "a", "name": "Alice"},
map[string]any{"id": "b", "name": "Bob"},
}}
var buf bytes.Buffer
if err := writePretty(&buf, payload); err != nil {
t.Fatalf("writePretty() error = %v", err)
}
out := buf.String()
if strings.Contains(out, "Catalog") || strings.Contains(out, "Tool ") {
t.Errorf("pretty wrongly ran schema renderer on non-schema payload:\n%s", out)
}
// tableish should produce header + rows
for _, want := range []string{"id", "name", "Alice", "Bob"} {
if !strings.Contains(out, want) {
t.Errorf("tableish fallback missing %q:\n%s", want, out)
}
}
}
func TestNormalizeFormat_RecognisesPretty(t *testing.T) {
if got := normalizeFormat("pretty", FormatJSON); got != FormatPretty {
t.Errorf("normalizeFormat(pretty) = %q, want %q", got, FormatPretty)
}
if got := normalizeFormat("PRETTY", FormatJSON); got != FormatPretty {
t.Errorf("normalizeFormat(PRETTY) = %q, want %q", got, FormatPretty)
}
}
+55 -21
View File
@@ -1,7 +1,7 @@
---
name: dws
description: 管理钉钉产品能力(AI表格/日历/通讯录/群聊与机器人/待办/审批/考勤/日志/DING消息/工作台/开放平台文档/钉钉文档/AI听记等)。当用户需要操作表格数据、管理日程会议、查询通讯录、管理群聊、机器人发消息、创建待办、提交审批、查看考勤、提交日报周报(钉钉日志模版)、读写钉钉文档、查询听记纪要时使用。
cli_version: ">=1.0.6"
description: 管理钉钉产品能力(AI表格/日历/通讯录/群聊与机器人/待办/审批/考勤/日志/DING消息/开放平台文档/钉钉文档/钉钉云盘/AI听记等)。当用户需要操作表格数据、管理日程会议、查询通讯录、管理群聊、机器人发消息、创建待办、提交审批、查看考勤、提交日报周报(钉钉日志模版)、读写钉钉文档、上传下载云盘文件、查询听记纪要时使用。
cli_version: ">=1.0.15"
---
# 钉钉全产品 Skill
@@ -24,39 +24,41 @@ cli_version: ">=1.0.6"
| 产品 | 用途 | 参考文件 |
|-------------------|------------------------------------------------------|----------------------------------------------------------------|
| `aitable` | AI表格:Base/数据表/字段/记录/附件/模板搜索 | [aitable.md](./references/products/aitable.md) |
| `approval` | 审批:审批表单/发起实例/审批/撤销 | [simple.md](./references/products/simple.md) |
| `attendance` | 考勤:打卡记录/排班查询 | [attendance.md](./references/products/attendance.md) |
| `calendar` | 日历:日程/参与者/会议室/闲忙查询 | [calendar.md](./references/products/calendar.md) |
| `aitable` | AI表格:Base/数据表/字段/记录/视图/附件/图表/仪表盘/导入导出/模板搜索 | [aitable.md](./references/products/aitable.md) |
| `attendance` | 考勤:打卡记录/排班查询/考勤规则/汇总统计 | [attendance.md](./references/products/attendance.md) |
| `calendar` | 日历:日程/参与者/会议室/闲忙查询/时间建议 | [calendar.md](./references/products/calendar.md) |
| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/机器人群发/单聊/撤回/Webhook/机器人搜索 | [chat.md](./references/products/chat.md) |
| `contact` | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | [contact.md](./references/products/contact.md) |
| `devdoc` | 开放平台文档:搜索开发文档 | [simple.md](./references/products/simple.md) |
| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/部门查询(搜索/成员列表) | [contact.md](./references/products/contact.md) |
| `devdoc` | 开放平台文档:搜索开发文档 | [devdoc.md](./references/products/devdoc.md) |
| `ding` | DING消息:发送/撤回(应用内/短信/电话) | [ding.md](./references/products/ding.md) |
| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论 | [doc.md](./references/products/doc.md) |
| `minutes` | AI听记:听记列表/摘要/关键词/转写/待办/思维导图/发言人/热词 | [minutes.md](./references/products/minutes.md) |
| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名 | [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) |
| `report` | 日志:按模版创建/收件箱/已发送/模版查看/详情/已读统计 | [report.md](./references/products/report.md) |
| `todo` | 待办:创建(含优先级/截止时间)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) |
| `workbench` | 工作台:应用管理 | [workbench.md](./references/products/workbench.md) |
| `todo` | 待办:创建(含优先级/截止时间/循环)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) |
## 意图判断决策树
用户提到"表格/多维表/AI表格/记录/数据" → `aitable`
用户提到"审批/请假/报销/出差/加班" → `oa`
用户提到"表格/多维表/AI表格/记录/数据/视图/图表/仪表盘" → `aitable`
用户提到"考勤/打卡/排班" → `attendance`
用户提到"日程/日历/会议室/约会" → `calendar`
用户提到"日程/日历/会议室/约会/时间建议" → `calendar`
用户提到"群聊/建群/群成员/群管理/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat`
用户提到"通讯录/同事/部门/组织架构" → `contact`
用户提到"开发/API/调用错误 文档" → `devdoc`
用户提到"DING/紧急消息/电话提醒" → `ding`
用户提到"钉钉文档/云文档/知识库/读写文档/块级编辑/文档评论" → `doc`
用户提到"钉钉文档/云文档/知识库/读写文档/块级编辑/文档评论/文档复制移动" → `doc`
用户提到"云盘/文件存储/文件上传下载/文件夹" → `drive`
用户提到"听记/AI听记/会议纪要/转写/摘要/思维导图/发言人/热词" → `minutes`
用户提到"审批/请假/报销/出差/加班/同意/拒绝/撤销审批" → `oa`
用户提到"日志/日报/周报/日志统计/写日报/提交周报/发日志/填日志" → `report`
用户提到"待办/TODO/任务提醒" → `todo`
用户提到"工作台/应用管理" → `workbench`
用户提到"待办/TODO/任务提醒/循环待办" → `todo`
关键区分: aitable(数据表格) vs todo(待办任务)
关键区分: report(钉钉日志/日报周报) vs todo(待办任务)
关键区分: chat send-by-bot(机器人身份发消息) vs send-by-webhook(自定义机器人Webhook告警)
关键区分: doc(钉钉文档/富文本协同) vs drive(钉钉云盘/二进制文件)
关键区分: oa tasks(审批 taskId,审批/拒绝用) vs oa list-pending(收件箱 processInstanceId,查看用)
> 更多易混淆场景见 [intent-guide.md](./references/intent-guide.md)
@@ -68,14 +70,22 @@ cli_version: ">=1.0.6"
| 产品 | 命令 | 说明 |
|------|------|------|
| `aitable` | `base delete` | 删除整个 AI 表格,含全部数据表和记录 |
| `aitable` | `table delete` | 删除数据表(含全部字段/视图/记录) |
| `aitable` | `field delete` | 删除字段(该列所有值同步清空) |
| `aitable` | `view delete` | 删除视图 |
| `aitable` | `record delete` | 删除记录(支持批量) |
| `aitable` | `chart delete` / `dashboard delete` | 删除图表/仪表盘 |
| `calendar` | `event delete` | 删除日程,所有参与者同步取消 |
| `calendar` | `participant delete` | 移除日程参与者 |
| `calendar` | `room delete` | 取消会议室预定 |
| `chat` | `group members remove` | 移除群成员 |
| `doc` | `delete` | 删除钉钉文档(不可恢复) |
| `doc` | `block delete` | 删除文档块 |
| `chat` | `message recall-by-bot` | 撤回机器人已发消息 |
| `doc` | `block delete` | 删除文档块(不可恢复) |
| `ding` | `message recall` | 撤回已发 DING 消息 |
| `oa` | `approval revoke` | 撤销自己发起的审批实例 |
| `oa` | `approval reject` | 拒绝待审批(需加明确理由) |
| `todo` | `task delete` | 删除待办 |
| `minutes` | `replace-text` | 全文批量替换转写与摘要 |
### 确认流程
```
@@ -92,6 +102,30 @@ Step 3 → 加 --yes 执行命令
3. 精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。
4. 充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。
## 命令发现(flag / 参数以 binary 为准)
产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束随服务发现动态变化,**以下两个命令的输出才是调用的事实源**:
```bash
# 1) 人读视图:看 Usage / Example / Flags
dws <command-path> --help
# 例:dws calendar event list --help
# 2) 机读视图:JSON Schema + flag 别名映射 + 必填字段
dws schema # 列出所有产品及工具
dws schema <product>.<canonical_name> # 规范路径(如 calendar.list_suggested_event_times)
dws schema "<product> <group> <cli_name>" # CLI 路径(如 "calendar event list")
dws schema <path> --jq '.tool.flag_overlay' # 只看 flag 别名
dws schema <path> --jq '.tool.required' # 只看必填字段
```
**何时用哪条路径:**
- 只需看某个命令怎么调用 → `dws <cmd> --help`
- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → `dws schema <path>`
- 参考文档和 `--help` 冲突时 → **以 `--help` / `dws schema` 为准**,文档视为过期
`dws schema` 输出的 `flag_overlay[key].alias` 就是实际生效的 flag 名(如 `attendeeUserIds → --attendee-user-ids`);`parameters[key]` 是原始 JSON Schema;`required` 是必填字段数组;`sensitive: true` 表示写/删操作,须先向用户确认再加 `--yes`。
## 错误处理
1. 遇到错误,加 `--verbose` 重试一次
2. 若 stderr 出现 `RECOVERY_EVENT_ID=<event_id>`,优先按 [recovery-guide.md](./references/recovery-guide.md) 执行 recovery 闭环
@@ -102,7 +136,7 @@ Step 3 → 加 --yes 执行命令
## 详细参考 (按需读取)
- [references/products/](./references/products/) — 各产品命令详细参考
- [references/products/](./references/products/) — 各产品命令详细参考(flag 细节以 `--help` / `dws schema` 为准)
- [references/intent-guide.md](./references/intent-guide.md) — 意图路由指南(易混淆场景对照)
- [references/global-reference.md](./references/global-reference.md) — 全局标志、认证、输出格式
- [references/field-rules.md](./references/field-rules.md) — AI表格字段类型规则
+380 -21
View File
@@ -215,11 +215,12 @@ Example:
dws aitable field update --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --name "新字段名"
dws aitable field update --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --config '{"options":[{"name":"A"},{"name":"B"}]}'
Flags:
--base-id string Base ID (必填)
--config string 字段配置 JSON (不修改时省略)
--field-id string Field ID (必填)
--name string 新字段名称 (不修改时省略)
--table-id string Table ID (必填)
--ai-config string AI 字段配置 JSON(AI 字段类型专用,可选)
--base-id string Base ID (必填)
--config string 字段配置 JSON (不修改时省略)
--field-id string Field ID (必填)
--name string 新字段名称 (不修改时省略)
--table-id string Table ID (必填)
```
不可变更字段类型。更新 singleSelect/multipleSelect 的 options 时需传入完整列表,已有选项应回传原 id。
@@ -362,6 +363,328 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
> ⚠️ `uploadUrl` 有时效性(`expiresAt`),脚本会自动在获取后立即上传。
### view (视图管理)
视图是同一张数据表的筛选/排序/分组/可见字段组合的备用呈现。record query 可以通过视图收敛查询范围。
#### 获取视图
```
Usage:
dws aitable view get [flags]
Example:
dws aitable view get --base-id <BASE_ID> --table-id <TABLE_ID>
dws aitable view get --base-id <BASE_ID> --table-id <TABLE_ID> --view-ids viw1,viw2
Flags:
--base-id string Base ID (必填)
--table-id string Table ID (必填)
--view-ids string View ID 列表,逗号分隔,单次最多 10 个
```
返回视图配置(过滤、排序、可见字段、分组)。不传 view-ids 返回该表全部视图。
#### 创建视图
```
Usage:
dws aitable view create [flags]
Example:
dws aitable view create --base-id <BASE_ID> --table-id <TABLE_ID> --name "进行中看板" --view-type kanban
Flags:
--base-id string Base ID (必填)
--config string 视图配置 JSON:过滤/排序/分组/可见字段(可选,创建后可再 update)
--name string 视图名称 (必填)
--table-id string Table ID (必填)
--view-type string 视图类型:grid/gallery/kanban/gantt/calendar/form 等 (必填)
```
> ⚠️ 视图类型参数是 `--view-type`,不是 `--type`。
#### 更新视图
```
Usage:
dws aitable view update [flags]
Example:
dws aitable view update --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --name "新视图名"
Flags:
--base-id string Base ID (必填)
--config string 视图配置 JSON:过滤/排序/分组/可见字段
--name string 新视图名
--table-id string Table ID (必填)
--view-id string 目标 View ID (必填)
```
调整过滤、排序、分组、可见字段时使用。不重建视图即可替换配置。
#### 删除视图
```
Usage:
dws aitable view delete [flags]
Example:
dws aitable view delete --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --yes
Flags:
--base-id string Base ID (必填)
--table-id string Table ID (必填)
--view-id string 待删除 View ID (必填)
```
不可逆。若是主视图或最后一个视图,删除会失败。
### import (数据导入)
把外部 Excel/CSV 批量写入某张数据表,按两步走:先 upload 拿 importId,再 data 触发导入。
#### 准备导入上传
```
Usage:
dws aitable import upload [flags]
Example:
dws aitable import upload --base-id <BASE_ID> --file-name sales.xlsx --file-size 204800
Flags:
--base-id string Base ID (必填)
--file-name string 文件名,必须含扩展名 (必填)
--file-size int 文件大小(字节),>0 (必填)
```
> ⚠️ 参数是 `--file-size`,不是 `--size`。返回 uploadUrl(PUT 上传)与 importId。
#### 触发导入
```
Usage:
dws aitable import data [flags]
Example:
dws aitable import data --import-id <IMPORT_ID> --timeout 60
Flags:
--import-id string import upload 返回的 importId (必填)
--timeout int 等待导入完成的超时秒数,默认 30
```
把上一步上传到暂存区的文件正式导入为记录;超时未完成时返回处理状态,按 importId 再次查询即可。
### dashboard (仪表盘)
仪表盘 = 多个图表的布局容器;图表绑定数据表/视图。创建仪表盘之前通常先查 `dashboard config-example`,拿到 JSON 骨架,再把图表塞进 `--config` 的布局里。
#### 仪表盘配置示例
```
Usage:
dws aitable dashboard config-example [flags]
Example:
dws aitable dashboard config-example --format json
```
返回仪表盘 `--config` 的 JSON 示例,供 create/update 复制裁剪。
#### 获取仪表盘
```
Usage:
dws aitable dashboard get [flags]
Example:
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID>
Flags:
--base-id string Base ID (必填)
--dashboard-id string Dashboard ID (必填)
```
返回布局和 `charts[].chartId`,可直接喂给 `chart get`。
#### 创建仪表盘
```
Usage:
dws aitable dashboard create [flags]
Example:
dws aitable dashboard create --base-id <BASE_ID> --config '<JSON>'
Flags:
--base-id string Base ID (必填)
--config string 仪表盘配置 JSON(名称、布局、图表列表都在里面) (必填)
```
> ⚠️ 没有独立的 `--name`;名称和布局一起放在 `--config` JSON 中。
#### 更新仪表盘
```
Usage:
dws aitable dashboard update [flags]
Example:
dws aitable dashboard update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --config '<JSON>'
Flags:
--base-id string Base ID (必填)
--config string 更新后的仪表盘配置 JSON (必填)
--dashboard-id string Dashboard ID (必填)
```
调整布局、增删图表一律改 `--config`。
#### 删除仪表盘
```
Usage:
dws aitable dashboard delete [flags]
Example:
dws aitable dashboard delete --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --yes
Flags:
--base-id string Base ID (必填)
--dashboard-id string Dashboard ID (必填)
--reason string 删除原因
```
不可逆。
#### 查看仪表盘分享状态
```
Usage:
dws aitable dashboard share get [flags]
Example:
dws aitable dashboard share get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID>
Flags:
--base-id string Base ID (必填)
--dashboard-id string Dashboard ID (必填)
```
> ⚠️ 可能返回 404(资源不存在或未开通外链),按可重试错误处理,不要误判为参数拼错。
#### 更新仪表盘分享
```
Usage:
dws aitable dashboard share update [flags]
Example:
dws aitable dashboard share update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --enabled true
Flags:
--allow-back-to-doc 是否允许从分享页返回原文档(可选)
--base-id string Base ID (必填)
--dashboard-id string Dashboard ID (必填)
--enabled 是否开启外链分享 (必填)
--share-type string 分享类型(权限/可见范围等,可选)
```
开启后返回 shareUrl;关闭后原链接失效。
### chart (图表)
图表挂在某个仪表盘下,绑定数据表(可进一步绑定视图)。新建前通常先查 `chart widgets-example`。
#### 图表组件示例
```
Usage:
dws aitable chart widgets-example [flags]
Example:
dws aitable chart widgets-example --format json
```
返回图表 widget 的 JSON 示例,供 `chart create/update --config` 参考。
#### 获取图表
```
Usage:
dws aitable chart get [flags]
Example:
dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID>
Flags:
--base-id string Base ID (必填)
--chart-id string Chart ID (必填)
--dashboard-id string Dashboard ID (必填)
```
#### 创建图表
```
Usage:
dws aitable chart create [flags]
Example:
dws aitable chart create --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --config '<JSON>'
Flags:
--base-id string Base ID (必填)
--config string 图表配置 JSON:数据源、维度、度量、样式 (必填)
--dashboard-id string 挂载到的 Dashboard ID (必填)
--layout string 图表布局 JSON(位置、尺寸,可选)
```
> ⚠️ 名称和类型都在 `--config` JSON 里,没有独立 `--name` / `--type`。
#### 更新图表
```
Usage:
dws aitable chart update [flags]
Example:
dws aitable chart update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --config '<JSON>'
Flags:
--base-id string Base ID (必填)
--chart-id string Chart ID (必填)
--config string 图表配置 JSON (必填)
--dashboard-id string Dashboard ID (必填)
--layout string 图表布局 JSON(位置、尺寸,可选)
```
#### 删除图表
```
Usage:
dws aitable chart delete [flags]
Example:
dws aitable chart delete --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --yes
Flags:
--base-id string Base ID (必填)
--chart-id string 待删除 Chart ID (必填)
--dashboard-id string Dashboard ID (必填)
--reason string 删除原因
```
不可逆。
#### 查看图表分享状态
```
Usage:
dws aitable chart share get [flags]
Example:
dws aitable chart share get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID>
Flags:
--base-id string Base ID (必填)
--chart-id string Chart ID (必填)
--dashboard-id string Dashboard ID (必填)
```
返回 `enabled` 与 `shareUrl`,用来判断是否已经对外分享。
#### 更新图表分享
```
Usage:
dws aitable chart share update [flags]
Example:
dws aitable chart share update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --enabled true
Flags:
--allow-back-to-doc 是否允许从分享页返回原文档(可选)
--base-id string Base ID (必填)
--chart-id string Chart ID (必填)
--dashboard-id string Dashboard ID (必填)
--enabled 是否开启外链分享 (必填)
--share-type string 分享类型(权限/可见范围等,可选)
```
### export (数据导出)
把数据表/视图/整张 Base 导出为 Excel/CSV,下发下载链接;常见是异步任务,首次调用可能只返回 `taskId`,需要按 `taskId` 继续轮询直到拿到 `downloadUrl`。
#### 导出数据
```
Usage:
dws aitable export data [flags]
Example:
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
Flags:
--base-id string Base ID (必填)
--scope string 导出范围:all / table / view
--table-id string Table ID(scope=table/view 时必填)
--view-id string View ID(scope=view 时必填)
--format string 导出格式:excel / csv,默认 excel
--task-id string 轮询已有任务的 taskId
--timeout-ms int 单次调用服务端等待时间(毫秒)
```
参数约束:
- `scope=all`:只需 `--base-id`
- `scope=table`:必须同时传 `--table-id`
- `scope=view`:必须同时传 `--table-id + --view-id`
### template (模板搜索)
#### 搜索模板
@@ -392,6 +715,10 @@ dws aitable chart widgets-example --format json
# 2) 先拿 dashboard,再拿 chart 详情
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --format json
dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --format json
# 3) 写入/更新都用 --config JSON(名称、布局、图表类型都在里面)
dws aitable dashboard create --base-id <BASE_ID> --config '<JSON>' --format json
dws aitable chart update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --config '<JSON>' --format json
```
要点:
@@ -399,24 +726,20 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。
- dashboard/chart 的 create/update 只有 `--config`,没有独立的 `--name` / `--type`,名称与布局都在 JSON 里。
### 导出数据(两阶段轮询)
`export data` 常见为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。
### 导入外部数据(两步走)
```bash
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
# 1) 先上传文件拿 importId
dws aitable import upload --base-id <BASE_ID> --file-name sales.xlsx --file-size 204800 --format json
# 把本地文件 PUT 到返回的 uploadUrl(过期前必须上传完成)
# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
# 2) 触发导入
dws aitable import data --import-id <IMPORT_ID> --timeout 60 --format json
```
参数约束
- `scope=all`:只需 `base-id`
- `scope=table`:必须 `table-id`
- `scope=view`:必须同时 `table-id + view-id`
要点:`--file-size` 单位字节,必须与实际文件一致;`--timeout` 单位秒,不是毫秒。
## 意图判断
@@ -446,9 +769,37 @@ dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 300
- 修改/更新 → `record update`(需 recordId,先 `record query`)
- 删除 → `record delete` [危险](需 recordId)
用户说"视图/筛选视图/看板/画廊":
- 查看 → `view get`
- 新建 → `view create`(`--view-type` 指定视图类型)
- 修改(过滤/排序/分组/可见字段) → `view update`
- 删除 → `view delete` [危险]
用户说"导入/上传 Excel/CSV":
- 外部文件导入 → `import upload` → PUT 文件 → `import data`(两步)
用户说"导出/下载表格数据":
- → `export data`(按 scope=all/table/view 传参,异步轮询)
用户说"仪表盘/dashboard":
- 查看布局 → `dashboard get`
- 查看模板 → `dashboard config-example`
- 新建/修改布局 → `dashboard create` / `dashboard update`(配置都在 `--config` JSON)
- 删除 → `dashboard delete` [危险]
- 对外分享 → `dashboard share get` / `dashboard share update`
用户说"图表/chart":
- 查看 → `chart get`(需要 dashboardId)
- 查看组件模板 → `chart widgets-example`
- 新建/修改 → `chart create` / `chart update`(配置都在 `--config` JSON)
- 删除 → `chart delete` [危险]
- 对外分享 → `chart share get` / `chart share update`
用户说"附件/文件字段" → `attachment upload`(不要用钉盘 drive)
用户说"模板" → `template search`
关键区分: base=表格文件, table=数据表, field=列, record=行
关键区分: base=表格文件, table=数据表, field=列, record=行, view=视图, dashboard=仪表盘, chart=图表
## 核心工作流
@@ -476,15 +827,23 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|------|-------------|------|
| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI |
| `base create` | `baseId` | 后续命令 + 文档 URI |
| `base get` | `tables[].tableId` | --table-id |
| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete |
| `base get` | `tables[].tableId`、`dashboards[].dashboardId` | --table-id / --dashboard-id |
| `table get` | `fields[].fieldId`、`views[].viewId` | record 操作的 cells key;field get/update/delete;view 操作 |
| `view get` | `viewId` | record query 按视图收敛;view update/delete |
| `record query` | `recordId` | record update/delete |
| `dashboard get` | `charts[].chartId` | chart get/update/delete/share |
| `import upload` | `importId`、`uploadUrl` | PUT 文件后调 import data |
| `export data` (首次) | `taskId` | 下一轮 export data 轮询 |
| `attachment upload` | `fileToken` | record create/update 写入 attachment 字段 |
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
## 注意事项
- 所有操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称
- 所有操作使用 ID(baseId/tableId/fieldId/recordId/viewId/dashboardId/chartId),不使用名称
- records 的 cells key 是 fieldId,不是字段名称
- dashboard/chart 的 create/update 只有 `--config`(JSON 内含名称/布局/类型),没有独立 `--name`
- view 创建的类型参数是 `--view-type`,不是 `--type`
- import upload 的文件大小参数是 `--file-size`,不是 `--size`
## `--filters` 筛选语法排错与使用规范(极易出错)
+37 -9
View File
@@ -30,11 +30,17 @@ Usage:
Example:
dws calendar event create --title "Q1 复盘会" \
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
dws calendar event create --title "Q1 复盘会" \
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
--attendees userId1,userId2 --timezone "Asia/Shanghai" --desc "Q1 复盘"
Flags:
--desc string 日程描述
--end string 结束时间 ISO-8601 (必填)
--start string 开始时间 ISO-8601 (必填)
--title string 日程标题 (必填)
--attendees string 参会人 userId 列表 (逗号分隔)
--desc string 日程描述
--end string 结束时间 ISO-8601 (必填)
--open-dingtalk-ids string 参会人 openDingTalkId 列表 (逗号分隔)
--start string 开始时间 ISO-8601 (必填)
--timezone string 时区 (如 Asia/Shanghai)
--title string 日程标题 (必填)
```
### 修改日程
@@ -43,11 +49,14 @@ Usage:
dws calendar event update [flags]
Example:
dws calendar event update --id <EVENT_ID> --title "新标题"
dws calendar event update --id <EVENT_ID> --desc "议程调整" --timezone "Asia/Shanghai"
Flags:
--end string 新结束时间
--id string 日程 ID (必填)
--start string 新开始时间
--title string 新标题
--desc string 新描述
--end string 新结束时间
--id string 日程 ID (必填)
--start string 新开始时间
--timezone string 时区 (如 Asia/Shanghai)
--title string 新标题
```
### 删除日程
@@ -60,6 +69,23 @@ Flags:
--id string 日程 ID (必填)
```
### 推荐会议时间
```
Usage:
dws calendar event suggest [flags]
Example:
dws calendar event suggest --users <USER_ID_1>,<USER_ID_2> \
--start "2026-03-10T09:00:00+08:00" --end "2026-03-10T18:00:00+08:00" --duration 30
Flags:
--duration string 会议时长(分钟)
--end string 候选时段结束时间 ISO-8601 (必填)
--start string 候选时段开始时间 ISO-8601 (必填)
--timezone string 时区 (如 Asia/Shanghai)
--users string 参会人 userId 列表 (必填)
```
基于参会人闲忙数据给出排名后的候选时段,比 `busy search` 直接给出原始忙闲更适合"帮我约"场景。
### 查看参与者
```
Usage:
@@ -157,6 +183,7 @@ Flags:
- 创建/约 → `event create`
- 修改/改时间 → `event update`
- 取消/删除 → `event delete`
- 帮我约/找个大家都有空的时间 → `event suggest`
用户说"参会人/与会者":
- 查看 → `participant list`
@@ -170,7 +197,8 @@ Flags:
- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`
用户说"有空吗/忙不忙/闲忙":
- 查询 → `busy search`
- 要原始闲忙块 → `busy search`
- 要直接给时段候选 → `event suggest`
## 核心工作流
+31 -107
View File
@@ -9,21 +9,20 @@
| 子命令 | 用途 |
|-------|------|
| `group create` | 创建内部群 |
| `group create-org` | 创建企业全员群 |
| `group members list` | 查看群成员列表 |
| `group members` | 查看群成员列表 |
| `group members add` | 添加群成员 |
| `group members remove` | 移除群成员(⚠️ 危险操作) |
| `group members add-bot` | 添加机器人到群 |
| `group rename` | 修改群名称 |
| `search` | 搜索群会话 |
| `search-common` | 搜索共同群 |
| `conversation-info` | 获取会话基础信息(单聊/群聊) |
### message (会话消息管理)
| 子命令 | 用途 |
|-------|------|
| `message send` | 以当前用户身份发群消息或单聊消息 |
| `message send-personal` | 发送个人消息(⚠️ 敏感操作) |
| `message list` | 拉取群聊或单聊会话消息 |
| `message list-all` | 按时间范围拉取当前用户所有会话消息 |
| `message list-topic-replies` | 拉取群话题回复消息列表 |
@@ -32,7 +31,6 @@
| `message list-focused` | 拉取特别关注人的消息 |
| `message list-unread-conversations` | 获取未读会话列表 |
| `message search` | 按关键词搜索消息 |
| `message info` | 获取会话信息 |
| `message send-by-bot` | 机器人发消息(群聊或批量单聊) |
| `message recall-by-bot` | 机器人撤回消息 |
| `message send-by-webhook` | 自定义机器人 Webhook 发消息 |
@@ -43,8 +41,6 @@
| 子命令 | 用途 |
|-------|------|
| `bot search` | 搜索我的机器人 |
| `bot create` | 创建企业机器人 |
| `bot search-groups` | 搜索机器人所在群 |
---
@@ -64,31 +60,15 @@ Flags:
---
## group create-org — 创建企业全员群
创建面向企业组织的群,成员通过 userId 列表指定。
```
Usage:
dws chat group create-org [flags]
Example:
dws chat group create-org --name "全员通知群" --users userId1,userId2
Flags:
--name string 群名称 (必填)
--users string 成员 userId 列表,逗号分隔 (必填)
```
---
## group members list — 查看群成员列表
## group members — 查看群成员列表
分页查询指定群聊的成员。
```
Usage:
dws chat group members list [flags]
dws chat group members [flags]
Example:
dws chat group members list --id <openconversation_id>
dws chat group members --id <openconversation_id>
Flags:
--cursor string 分页游标,首次从 0 开始
--id string 群 ID / openconversation_id (必填)
@@ -227,6 +207,12 @@ Flags:
--at-all @所有人(仅群聊时生效,可选,默认 false)
--at-users string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选)
--media-id string 图片 mediaId(通过 dt_media_upload 工具上传获得,需从返回链接中去除 _宽_高.格式 后缀并加上 @ 前缀),指定后发送图片消息,不需要传文本内容
--msg-type string 消息类型(可选,如 text/markdown/image/file;通常由 --text/--media-id/--dentry-id 自动推断)
--dentry-id string 钉盘文件 dentryId(发送钉盘文件消息时使用,需配合 --space-id)
--space-id string 钉盘空间 spaceId(与 --dentry-id 配合使用)
--file-name string 文件消息的文件名
--file-size string 文件消息的文件大小(字节)
--file-type string 文件消息的文件类型(如 pdf / docx / xlsx 等)
注意:
- --text 和位置参数二选一,--text 优先
@@ -234,33 +220,7 @@ Flags:
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- --at-all 和 --at-users 仅在 --group 群聊时生效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-users userId1,userId2时,消息内容中一定要包含对应格式的占位符<@userId1> <@userId2>
- --media-id 指定图片 mediaId 时自动发送图片消息(msgType=image),不需要传 --text;图片单聊仅支持 --open-dingtalk-id,不支持 --user
```
---
## message send-personal — 发送个人消息
> ⚠️ 敏感操作:执行前必须向用户确认,同意后才加 `--yes`。
发送个人消息到指定会话或指定用户。支持 @指定人。
```
Usage:
dws chat message send-personal [flags]
Example:
dws chat message send-personal --id <openConversationId> --content "你好" --type text
dws chat message send-personal --open-id <openDingTalkId> --content "消息内容" --type text
dws chat message send-personal --id <openConversationId> --content "内容" --at-all
Flags:
--content string 消息内容 (必填)
--type string 消息类型,如 text、markdown (必填)
--id string 群聊会话 ID openConversationId(与 --open-id 二选一)
--open-id string 接收人 openDingTalkId(与 --id 二选一)
--at-all @所有人(可选)
--at-users string @指定人的 openDingTalkId 列表,逗号分隔(可选)
注意:
- --id(群聊会话)和 --open-id(指定用户 openDingTalkId)二选一
- 发送钉盘文件消息:传 --dentry-id + --space-id(必要时配合 --file-name / --file-size / --file-type),msg-type 自动推断为 file
```
---
@@ -457,22 +417,22 @@ Flags:
---
## message info — 获取会话信息
## conversation-info — 获取会话基础信息
获取指定群聊或单聊会话的详情信息。
按会话 ID 获取单聊或群聊的基础元数据(名称、类型、成员数等)。在对会话执行操作前用来确认上下文。
```
Usage:
dws chat message info [flags]
dws chat conversation-info [flags]
Example:
dws chat message info --id <openConversationId>
dws chat message info --open-id <openDingTalkId>
dws chat conversation-info --group <openConversationId>
dws chat conversation-info --open-dingtalk-id <openDingTalkId>
Flags:
--id string 群聊会话 ID openConversationId(与 --open-id 二选一)
--open-id string 用户 openDingTalkId(单聊时与 --id 二选一)
--group string 群聊会话 ID openConversationId(与 --open-dingtalk-id 二选一)
--open-dingtalk-id string 用户 openDingTalkId(单聊时与 --group 二选一)
注意:
- --id(群聊)和 --open-id(单聊用户 openDingTalkId)二选一
- --group(群聊 openConversationId)和 --open-dingtalk-id(单聊用户 openDingTalkId)二选一
```
---
@@ -515,37 +475,6 @@ Flags:
---
## bot create — 创建企业机器人
```
Usage:
dws chat bot create [flags]
Example:
dws chat bot create --name "日报提醒机器人" --desc "负责每日日报提醒"
Flags:
--name string 机器人名称 (必填)
--desc string 机器人描述(可选)
```
---
## bot search-groups — 搜索机器人所在群
搜索指定机器人已加入的群列表。
```
Usage:
dws chat bot search-groups [flags]
Example:
dws chat bot search-groups --keyword "项目"
dws chat bot search-groups --keyword "冲刺" --cursor <nextCursor>
Flags:
--keyword string 搜索关键词 (必填)
--cursor string 分页游标(首页留空,翻页传返回的 cursor)
```
---
## message send-by-bot — 机器人发消息
群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。
@@ -612,9 +541,8 @@ Flags:
## 意图判断
用户说"建群/创建群聊" → `chat group create`
用户说"创建企业全员群/组织群" → `chat group create-org`
用户说"搜索群/找群" → `chat search`
用户说"群成员/看群里有谁" → `chat group members list`
用户说"群成员/看群里有谁" → `chat group members`
用户说"拉人进群/加群成员" → `chat group members add`
用户说"踢人/移除群成员" → `chat group members remove`
用户说"加机器人到群" → `chat group members add-bot`
@@ -626,7 +554,6 @@ Flags:
用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations`
用户说"发群消息(以个人身份)" → `chat message send --group`
用户说"发单聊消息(以个人身份)" → `chat message send --user`(有 userId 时)或 `chat message send --open-dingtalk-id`(有 openDingTalkId 时)
用户说"发个人消息/个人通知" → `chat message send-personal`(⚠️ 敏感操作,需确认)
用户说"机器人发消息/机器人群发" → `chat message send-by-bot`
用户说"机器人撤回消息" → `chat message recall-by-bot`
用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook`
@@ -634,12 +561,10 @@ Flags:
用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all`
用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused`
用户说"查看我的机器人" → `chat bot search`
用户说"创建机器人" → `chat bot create`
用户说"搜索消息/查找关键词/搜一下消息里的XX" → `chat message search`
用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common`
用户说"置顶会话/置顶消息/我的置顶/查看置顶" → `chat list-top-conversations`
用户说"获取会话信息/会话详情" → `chat message info`
用户说"机器人在哪些群/机器人的群" → `chat bot search-groups`
用户说"获取会话信息/会话详情/会话元数据" → `chat conversation-info`
关键区分:
- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页
@@ -652,15 +577,13 @@ Flags:
- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页
- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页
- `chat message send` — 以**当前用户**身份发消息(群聊或单聊),text 为位置参数;支持 --media-id 发送图片消息
- `chat message send-personal` — 发送个人消息,支持通过 openConversationId 或 openDingTalkId 指定目标(⚠️ 敏感操作)
- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群)
- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在)
- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag
- `chat message send-by-webhook` — 通过**自定义机器人 Webhook** 发群消息
- `chat message recall-by-bot` — 通过机器人撤回已发送的消息
- `chat message info` — 获取指定会话的详情信息
- `chat bot create` — 创建新的企业机器人
- `chat bot search-groups` — 搜索机器人所在群列表
- `chat conversation-info` — 按会话 ID 获取单聊/群聊的基础元数据(名称、类型、成员数等)
- `chat bot search` — 搜索当前用户名下的机器人,拿到 robotCode 用于 send-by-bot / recall-by-bot / group members add-bot
## 核心工作流
@@ -715,16 +638,18 @@ dws chat message recall-by-bot --robot-code <robot-code> --group <openconversati
--keys <processQueryKey> --format json
```
### 创建并使用机器人(完整流程)
### 将已有机器人加入群并发消息(完整流程)
机器人需先在钉钉开放平台创建好,这里只做"找机器人 → 加入群 → 发消息"。
```bash
# Step 1: 创建机器人
dws chat bot create --name "项目提醒机器人" --desc "项目状态提醒" --format json
# Step 1: 搜索我的机器人 — 提取 robotCode
dws chat bot search --name "项目提醒" --format json
# Step 2: 搜索群 — 提取 openConversationId
dws chat search --query "项目群" --format json
# Step 3: 将机器人添加到群
# Step 3: 将机器人添加到群(需当前用户对该群有管理权限)
dws chat group members add-bot --id <openConversationId> --robot-code <robotCode> --format json
# Step 4: 机器人发消息
@@ -775,8 +700,8 @@ dws chat message send --group <openconversation_id> \
| `aisearch person` | `userId` | message send 的 --user、--at-users、send-by-bot 的 --users、list-by-sender 的 --sender-user-id |
| `aisearch person` → `contact user get` | `openDingTalkId` | list-by-sender 的 --sender-open-dingtalk-id、message send/list 的 --open-dingtalk-id |
| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code、group members add-bot 的 --robot-code |
| `chat bot create` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code |
| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys |
| `chat conversation-info` | `openConversationId` / 成员信息 | 作为 message send/list 等后续操作的会话上下文确认 |
| `chat message search` | `nextCursor` | 下次 message search 的 --cursor |
| `chat search-common` | `openConversationId` | message send/list 等的 --group |
| `drive download` | 下载链接 | message send 的 Markdown 图片/链接语法 |
@@ -785,7 +710,6 @@ dws chat message send --group <openconversation_id> \
- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;`--at-all`、`--at-users` 仅在 `--group` 群聊时生效;发送图片消息用 `--media-id`
- `chat message send-personal` 为敏感操作(isSensitive),执行前需用户明确确认
- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor
- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一
- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识
+6 -7
View File
@@ -17,9 +17,9 @@ Example:
Usage:
dws contact user search [flags]
Example:
dws contact user search --keyword "张三"
dws contact user search --query "张三"
Flags:
--keyword string 搜索关键词 (必填)
--query string 搜索关键词 (必填)
```
#### 按手机号搜索用户
@@ -49,9 +49,9 @@ Flags:
Usage:
dws contact dept search [flags]
Example:
dws contact dept search --keyword "技术部"
dws contact dept search --query "技术部"
Flags:
--keyword string 搜索关键词 (必填)
--query string 搜索关键词 (必填)
```
#### 查看部门成员
@@ -79,10 +79,10 @@ Flags:
dws contact user get-self --format json
# 2. 按名字搜索同事 — 提取 userId
dws contact user search --keyword "张三" --format json
dws contact user search --query "张三" --format json
# 3. 查看部门结构 — 提取 deptId
dws contact dept search --keyword "技术部" --format json
dws contact dept search --query "技术部" --format json
# 4. 查看部门成员
dws contact dept list-members --ids <deptId> --format json
@@ -93,7 +93,6 @@ dws contact dept list-members --ids <deptId> --format json
| 操作 | 提取 | 用于 |
|------|------|------|
| `user get-self/search` | `userId` | 其他产品中的 --users/--executor 参数 |
| `user get-self/search` | `orgAuthEmail` | mail message send 的 --to/--cc (跨产品) |
| `dept search` | `deptId` | dept list-members 的 --ids |
## 注意事项
+48
View File
@@ -0,0 +1,48 @@
# 开放平台文档 (devdoc) 命令参考
搜索钉钉**开放平台**开发文档,用于回答开发者关于 OpenAPI、字段、错误码、接入指南、配额等技术问题。
## 命令总览
### 搜索开发文档
```
Usage:
dws devdoc article search [flags]
Example:
dws devdoc article search --query "OAuth2 接入"
dws devdoc article search --query "消息卡片" --page 2 --size 5
dws devdoc article search --query "机器人" --size 10
Flags:
--query string 搜索关键词 (必填)
--page int 分页页码 (从 1 开始,默认 1)
--size int 分页大小 (默认 10)
```
## 意图判断
用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节:
- 走 `devdoc article search`,把用户问的关键短语作为 `--query`
关键区分:
- devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容)
- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览
## 核心工作流
```bash
# 开发者问"OAuth2 怎么接"
dws devdoc article search --query "OAuth2 接入" --format json
# 命中结果多时翻页
dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json
# 查错误码 / 字段含义
dws devdoc article search --query "errcode 40078" --format json
```
## 注意事项
- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写
- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词
- 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考
- 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档**
+122 -7
View File
@@ -80,6 +80,22 @@ Flags:
--markdown string 文档初始 Markdown 内容
```
### 创建其他类型文件 (表格/脑图/白板/多维表/画板)
```
Usage:
dws doc file create [flags]
Example:
dws doc file create --name "项目周报" --type adoc
dws doc file create --name "数据统计" --type axls --folder <FOLDER_ID>
dws doc file create --name "思维导图" --type amind --workspace <WS_ID>
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
```
### 更新文档内容
```
Usage:
@@ -135,6 +151,45 @@ Flags:
--workspace string 目标知识库 ID
```
### 复制文档/文件
```
Usage:
dws doc copy [flags]
Example:
dws doc copy --node <DOC_ID> --folder <TARGET_FOLDER_ID>
dws doc copy --node <DOC_ID> --workspace <TARGET_WS_ID>
dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>" --folder <FOLDER_ID>
Flags:
--node string 源文档/文件 ID 或 URL (必填)
--folder string 目标文件夹 ID 或 URL
--workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录)
```
### 移动文档/文件
```
Usage:
dws doc move [flags]
Example:
dws doc move --node <DOC_ID> --folder <TARGET_FOLDER_ID>
dws doc move --node <DOC_ID> --workspace <TARGET_WS_ID>
Flags:
--node string 源文档/文件 ID 或 URL (必填)
--folder string 目标文件夹 ID 或 URL
--workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录)
```
### 重命名文档/文件
```
Usage:
dws doc rename [flags]
Example:
dws doc rename --node <DOC_ID> --name "新名称"
dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>" --name "项目周报 v2"
Flags:
--node string 文档/文件 ID 或 URL (必填)
--name string 新名称 (必填)
```
### 查询块元素
```
Usage:
@@ -229,6 +284,24 @@ Flags:
--mention string 被 @ 的用户 uid 列表,逗号分隔
```
### 创建划词评论 (内联评论)
```
Usage:
dws doc comment create-inline [flags]
Example:
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 0 --end 10 --content "这里需要修改"
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文"
dws doc comment create-inline --node <DOC_ID> --block-id <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:
@@ -282,12 +355,24 @@ Flags:
- 元信息 → `info`
用户说"写文档/创建文档":
- 新建 → `create`
- 新建纯文档 (adoc) → `create`
- 追加内容 → `update --mode append`
- 覆盖替换 → `update --mode overwrite`
用户说"新建表格/脑图/白板/多维表/演示文稿":
- 用 `file create --type` 指定类型 (axls/amind/adraw/able/appt/adraw)
用户说"建文件夹/新建目录":
- 创建 → `folder create`
- 创建 → `folder create` 或 `file create --type folder`
用户说"复制文档/拷贝一份":
- 复制 → `copy` (需源 --node 和目标 --folder/--workspace)
用户说"移动文档/换个目录":
- 移动 → `move` (需源 --node 和目标 --folder/--workspace)
用户说"改文档名字/重命名":
- 改名 → `rename` (需 --node 和新 --name)
用户说"上传文件/传文件/上传到文档/上传到知识库":
- 上传 → `upload`(需本地文件路径)
@@ -390,7 +475,7 @@ dws doc block delete --node <DOC_ID> --block-id <BLOCK_ID> --yes
# 1. 查看文档的所有评论
dws doc comment list --node <DOC_ID> --format json
# 2. 在文档上创建评论
# 2. 在文档上创建全文评论
dws doc comment create --node <DOC_ID> --content "这里需要补充数据来源" --format json
# 3. 创建评论并 @ 相关人
@@ -398,11 +483,34 @@ dws doc comment create --node <DOC_ID> --content "这里需要补充数据来源
# 再将 userId 传入 --mention
dws doc comment create --node <DOC_ID> --content "请确认这部分内容" --mention <userId1>,<userId2> --format json
# 4. 回复某条评论(commentKey 从 list 或 create 返回中获取)
# 4. 对某段文字创建划词评论(需先 block list 拿 blockId 和字符偏移)
dws doc block list --node <DOC_ID> --format json
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 0 --end 12 \
--content "这里的数据要复核" --selected-text "被选中的原文片段" --format json
# 5. 回复某条评论(commentKey 从 list 或 create 返回中获取)
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "已修改" --format json
# 5. 用表情回复评论
# 6. 用表情回复评论
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "比心" --emoji --format json
# ── 工作流 8: 创建非文档类型文件 ──
# 创建表格 / 脑图 / 白板 / 多维表 / 演示文稿
dws doc file create --name "销售数据" --type axls --folder <FOLDER_ID> --format json
dws doc file create --name "需求脑图" --type amind --workspace <WS_ID> --format json
dws doc file create --name "Q1 立项会" --type appt --format json
# ── 工作流 9: 整理文档结构 (复制 / 移动 / 重命名) ──
# 1. 把模板复制到目标目录作为新工作件
dws doc copy --node <TEMPLATE_DOC_ID> --folder <TARGET_FOLDER_ID> --format json
# 2. 把文档从个人空间挪到团队知识库
dws doc move --node <DOC_ID> --workspace <WS_ID> --format json
# 3. 重命名
dws doc rename --node <DOC_ID> --name "项目周报 v2" --format json
```
## 上下文传递表
@@ -418,8 +526,11 @@ dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "比
| `upload` | `nodeId` / URL | 上传后文件的访问链接 |
| `download` | 本地文件路径 | 下载后的文件保存位置 |
| `comment list` | `commentList[].commentKey` | comment reply 的 --comment-key |
| `comment create` | `commentKey` | comment reply 的 --comment-key |
| `contact user search` | `userId` | comment create/reply 的 --mention |
| `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 双格式说明
@@ -448,6 +559,10 @@ dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE
- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档
- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作
- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录
- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示/文件夹,用 `file create --type`
- `copy` 需要对源节点有"阅读"权限、对目标目录有"编辑"权限;`move` 需要对源节点有"管理"权限
- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档"
- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始)
## 自动化脚本
+177
View File
@@ -0,0 +1,177 @@
# 钉盘 (drive) 命令参考
钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。
## 命令总览
### 列出钉盘目录
```
Usage:
dws drive list [flags]
Example:
dws drive list
dws drive list --parent-id <FOLDER_ID>
dws drive list --parent-id <FOLDER_ID> --max 20 --order-by modifiedTime --order desc
Flags:
--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 是否返回缩略图链接
```
### 获取文件元信息
```
Usage:
dws drive info [flags]
Example:
dws drive info --file-id <FILE_ID>
Flags:
--file-id string 文件或文件夹 ID (必填)
--space-id string 钉盘空间 ID (一般无需指定)
```
### 创建文件夹
```
Usage:
dws drive mkdir [flags]
Example:
dws drive mkdir --name "项目资料"
dws drive mkdir --name "子目录" --parent-id <PARENT_FOLDER_ID>
Flags:
--name string 文件夹名称 (必填)
--parent-id string 父目录 ID (不传则建在根目录)
--space-id string 钉盘空间 ID (一般无需指定)
```
### 获取下载临时链接
```
Usage:
dws drive download [flags]
Example:
dws drive download --file-id <FILE_ID>
Flags:
--file-id string 文件 ID (必填)
--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 <FOLDER_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 <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)
```
## 意图判断
用户说"钉盘有什么文件/列钉盘/看钉盘目录" → `list`
用户说"钉盘文件详情/文件信息" → `info` (需 fileId)
用户说"新建钉盘目录/钉盘里建文件夹" → `mkdir`
用户说"下载钉盘文件/把这个文件拿下来" → `download` 拿临时 URL,再由 Agent 自行发起 HTTP GET
用户说"上传文件到钉盘/把本地文件传钉盘":
- 三步走: `upload-info` → HTTP PUT 到预签名 URL → `commit`
- **没有**一条聚合命令可以完成;必须完整走完三步
关键区分:
- drive(钉盘云存储,面向文件二进制) vs doc(在线文档/知识库,面向富文本内容)
- 把图片/文件发到群里一般走 drive 上传拿链接 → chat 发送 Markdown 链接 (见 [chat.md](./chat.md) 的 `drive → chat` 工作流)
## 核心工作流
```bash
# ── 工作流 1: 浏览钉盘 ──
# 1. 看根目录
dws drive list --format json
# 2. 进入子目录 (parentId 取自上一步的 dentryUuid)
dws drive list --parent-id <FOLDER_ID> --format json
# 3. 看单个文件的元信息
dws drive info --file-id <FILE_ID> --format json
# ── 工作流 2: 上传本地文件到钉盘 (三步不能省) ──
# Step 1: 拿上传凭证
dws drive upload-info --file-name "report.pdf" --file-size 102400 --parent-id <FOLDER_ID> --format json
# → 返回: resourceUrl (预签名 URL), headers, uploadId
# Step 2: Agent 自己发 HTTP PUT 把文件二进制推到 resourceUrl
# 请求头必须携带返回的 headers 全部键值对;期望 HTTP 200
curl -X PUT -T ./report.pdf -H "<header-from-step-1>: <value>" "<resourceUrl>"
# Step 3: 提交入库
dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id <UPLOAD_ID> \
--parent-id <FOLDER_ID> --format json
# → 返回: dentryUuid (= 后续 download/info 用的 fileId)
# ── 工作流 3: 下载钉盘文件到本地 (两步) ──
# Step 1: 拿临时下载 URL
dws drive download --file-id <FILE_ID> --format json
# → 返回: resourceUrl (带签名的临时下载 URL), expirationSeconds
# Step 2: Agent 自己发 HTTP GET 下载二进制
curl -o ./report.pdf "<resourceUrl>"
# ── 工作流 4: 建目录后批量上传 ──
# 1. 建目录 → 拿 folderId
dws drive mkdir --name "2026 Q1 归档" --format json
# 2. 再走 "工作流 2" 把每个文件上传到该目录
```
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `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` | `resourceUrl` | Agent 自行执行 HTTP GET 下载;也可作为 Markdown 图片/附件链接发送到 chat |
## 注意事项
- 上传是**三步**流程:`upload-info` → 客户端 HTTP PUT 到预签名 URL → `commit`。**没有**自动聚合命令,跳过任何一步都会失败
- Step 2 的 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;只有 PUT 返回 200 才能调 `commit`
- 上传凭证 (`uploadId`) 有过期时间,拿到后尽快 commit;过期需重新调 `upload-info`
- `download` 只返回临时 URL(几分钟级别有效期),不会把文件落地;要真正下载到本地必须再发 HTTP GET
- `--parent-id` 在 upload-info / commit 中要保持一致,否则 commit 会报位置不匹配
- `--conflict-handler` 默认 `AUTO_RENAME` (自动重命名);`OVERWRITE` 覆盖同名文件前必须和用户确认
- `--space-id` 绝大多数场景不需要传;默认会用用户主钉盘空间
- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾
## 相关产品
- [doc](./doc.md) — 钉钉在线文档 / 知识库(文字、表格、脑图等富文本节点),和 drive 的裸文件存储不同
- [chat](./chat.md) — 结合 drive 发送图片 / 附件消息到群聊(Markdown 链接语法)
+138 -43
View File
@@ -1,5 +1,7 @@
# AI听记 (minutes) 命令参考
AI 听记:列表 / 详情 / 摘要 / 待办 / 文字稿 / 思维导图 / 发言人 / 热词 / 文件上传。
## 命令总览
### 查询我创建的听记列表
@@ -143,53 +145,126 @@ Flags:
--title string 新标题 (必填)
```
### 发起听记(开始录音)
### 覆盖听记 AI 摘要
```
Usage:
dws minutes record start [flags]
dws minutes update summary [flags]
Example:
dws minutes record start
dws minutes record start --session-id <sessionId>
dws minutes update summary --id <taskUuid> --content "修订后的摘要 Markdown"
Flags:
--session-id string AI 助理会话 ID (可选)
--id string 听记 taskUuid (必填)
--content string 新的摘要正文 (必填),会整体覆盖原摘要
```
### 暂停听记录音
将 AI 生成的摘要替换成定制版本,适合人工修订或按业务口径重写后回写。
### 全文替换听记文本
```
Usage:
dws minutes record pause [flags]
dws minutes replace-text [flags]
Example:
dws minutes record pause --id <taskUuid>
dws minutes record pause --id <taskUuid> --session-id <sessionId>
dws minutes replace-text --id <taskUuid> --search "小钉" --replace "DingTalk"
Flags:
--id string 听记 taskUuid (必填)
--session-id string AI 助理会话 ID (可选)
--id string 听记 taskUuid (必填)
--search string 需要替换的原文 (必填)
--replace string 替换后的文本 (必填)
```
### 恢复听记录音
在转写段落与摘要中一次性替换所有命中文本,适合纠正系统性的识别错误(如专有名词)。
### 替换发言人标签
```
Usage:
dws minutes record resume [flags]
dws minutes speaker replace [flags]
Example:
dws minutes record resume --id <taskUuid>
dws minutes record resume --id <taskUuid> --session-id <sessionId>
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三"
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三" --target-uid <userId>
Flags:
--id string 听记 taskUuid (必填)
--session-id string AI 助理会话 ID (可选)
--id string 听记 taskUuid (必填)
--from string 原发言人昵称,如 "说话人1" (必填)
--to string 新发言人昵称 (必填)
--target-uid string 绑定到指定钉钉 userId (可选)
```
### 结束听记录音
修正自动分离的发言人标签,可顺带把该说话人对应到真实用户。
### 添加个人热词
```
Usage:
dws minutes record stop [flags]
dws minutes hot-word add [flags]
Example:
dws minutes record stop --id <taskUuid>
dws minutes record stop --id <taskUuid> --session-id <sessionId>
dws minutes hot-word add --words "钉钉,悟空,DWS"
Flags:
--id string 听记 taskUuid (必填)
--session-id string AI 助理会话 ID (可选)
--words string 热词列表,逗号分隔 (必填)
```
把专有名词/业务术语写入个人热词库,后续听记的语音识别会优先匹配,用来兜底高频误识别。
### 生成思维导图
```
Usage:
dws minutes mind-graph create [flags]
Example:
dws minutes mind-graph create --id <taskUuid>
Flags:
--id string 听记 taskUuid (必填)
```
异步任务:提交后返回 jobId,需要用 `mind-graph status` 轮询结果。
### 查询思维导图状态
```
Usage:
dws minutes mind-graph status [flags]
Example:
dws minutes mind-graph status --id <taskUuid>
Flags:
--id string 听记 taskUuid (必填)
```
轮询 `mind-graph create` 的生成任务,就绪后返回思维导图内容。
### 创建文件上传会话
```
Usage:
dws minutes upload create [flags]
Example:
dws minutes upload create --file-name "会议录音.m4a" --file-size 10485760 --title "Q2 复盘"
Flags:
--file-name string 本地文件名 (必填)
--file-size string 文件大小,单位字节 (必填)
--title string 听记标题 (可选)
--template-id string 摘要模板 ID (可选)
--input-language string 语言,如 zh_CN / en_US (可选)
--enable-message-card string 是否推送消息卡片: true/false (可选)
```
三步上传的第 1 步:返回 sessionId 和预签名 URL,由调用方把本地音视频文件 HTTP PUT 到该 URL。
### 完成上传并生成听记
```
Usage:
dws minutes upload complete [flags]
Example:
dws minutes upload complete --session-id <sessionId>
Flags:
--session-id string upload create 返回的 sessionId (必填)
```
三步上传的第 3 步:通知服务端文件已上传完毕,触发转写与 AI 处理,返回新的 taskUuid。
### 取消上传
```
Usage:
dws minutes upload cancel [flags]
Example:
dws minutes upload cancel --session-id <sessionId>
Flags:
--session-id string upload create 返回的 sessionId (必填)
```
上传中途放弃或上游出错时释放会话,避免残留。
## 意图判断
用户说"我的听记/我创建的听记" → `list mine`(可附加 `--query`、`--start`、`--end` 筛选)
@@ -197,23 +272,23 @@ Flags:
用户说"有权限的听记/我能访问的听记/所有听记" → `list all`(可附加 `--query`、`--start`、`--end` 筛选)
用户说"某时间段内的听记/按时间查听记/按关键词查听记" → 根据所属范围选择 `list mine`/`list shared`/`list all`,附加 `--start`、`--end`、`--query` 参数
用户说"听记详情/听记信息" → `get info`
用户说"批量看听记/一次查多篇" → `get batch`(`--ids` 传逗号分隔 taskUuid)
用户说"摘要/总结/会议纪要" → `get summary`
用户说"关键字/关键词" → `get keywords`
用户说"原文/转写/录音文字" → `get transcription`
用户说"会议待办/听记待办" → `get todos`
用户说"改听记标题/重命名听记" → `update title`
用户说"发起听记/开始录音" → `record start`
用户说"暂停听记/暂停录音" → `record pause`
用户说"继续听记/恢复录音" → `record resume`
用户说"结束听记/结束录音" → `record stop`
用户说"改摘要/覆盖摘要/重写总结" → `update summary`
用户说"全文替换/批量改转写里的错字/专有名词搞错了" → `replace-text`
用户说"改发言人/把说话人1改成张三/认领发言人" → `speaker replace`
用户说"加热词/让后续识别更准/把某个专业术语教给系统" → `hot-word add`
用户说"生成思维导图/导图/脑图" → `mind-graph create`(异步),再 `mind-graph status` 轮询
用户说"上传录音生成听记/把本地音视频变听记" → `upload create` → 本地 PUT → `upload complete`;放弃则 `upload cancel`
用户传入听记 URL(如 `https://shanji.dingtalk.com/app/transcribes/xxx`),从 URL 提取 taskUuid,再执行对应的 get/update 操作
## 核心工作流
```bash
# 0. 发起听记(开始录音)
dws minutes record start --format json
# 1. 查看我的听记列表 — 提取 taskUuid
dws minutes list mine --format json
dws minutes list mine --max 10 --next-token <nextToken> --format json
@@ -236,13 +311,28 @@ dws minutes get transcription --id <taskUuid> --format json
# 4. 提取待办事项
dws minutes get todos --id <taskUuid> --format json
# 5. 修改标题
dws minutes update title --id <taskUuid> --title "新标题" --format json
# 5. 批量补齐多个 taskUuid 的详情
dws minutes get batch --ids <taskUuid1>,<taskUuid2>,<taskUuid3> --format json
# 6. 录音控制(基于 start 返回的 taskUuid)
dws minutes record pause --id <taskUuid> --format json
dws minutes record resume --id <taskUuid> --format json
dws minutes record stop --id <taskUuid> --format json
# 6. 修改标题 / 覆盖摘要
dws minutes update title --id <taskUuid> --title "新标题" --format json
dws minutes update summary --id <taskUuid> --content "修订后的摘要 Markdown" --format json
# 7. 修正转写错字 / 发言人标签 / 加热词
dws minutes replace-text --id <taskUuid> --search "小钉" --replace "DingTalk" --format json
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三" --format json
dws minutes hot-word add --words "钉钉,悟空,DWS" --format json
# 8. 异步生成思维导图(create 拿 jobId,status 轮询)
dws minutes mind-graph create --id <taskUuid> --format json
dws minutes mind-graph status --id <taskUuid> --format json
# 9. 从本地音视频上传生成听记(三步走)
dws minutes upload create --file-name "会议录音.m4a" --file-size 10485760 --title "Q2 复盘" --format json
# → 拿到 sessionId 和预签名 URL,调用方自行 HTTP PUT 把文件推上去
dws minutes upload complete --session-id <sessionId> --format json
# 放弃上传
dws minutes upload cancel --session-id <sessionId> --format json
```
## 上下文传递表
@@ -253,18 +343,23 @@ dws minutes record stop --id <taskUuid> --format json
| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token |
| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token |
| `get batch` | 各听记 `taskUuid` | 进一步查询详情 |
| `upload create` | `sessionId`、预签名 URL | 本地 PUT 上传 + `upload complete` / `upload cancel` 的 --session-id |
| `upload complete` | 新听记 `taskUuid` | 后续 get/update/mind-graph 的 --id |
| `mind-graph create` | 异步 jobId(随 taskUuid 关联) | `mind-graph status` 的 --id 查进度与结果 |
## 注意事项
- `taskUuid` 是听记的唯一标识,所有 get/update 操作均以此为入参
- `record start` 对应 MCP 工具 `execute_listening_note_command` 的 `cmd=create`,通常会返回可继续控制录音的 `taskUuid/uuid`
- `record pause` / `record resume` / `record stop` 对应 `cmd=pause/resume/end`,需要传入 `--id`(映射 MCP 入参 `uuid`)
- `taskUuid` 是听记的唯一标识,所有 get/update/mind-graph/speaker/replace-text 操作均以此为入参
- 如果用户传入听记 URL(格式: `https://shanji.dingtalk.com/app/transcribes/<taskUuid>`),直接从路径末段提取 taskUuid 作为 `--id` 参数,无需再调用 list 查询
- `list mine`、`list shared`、`list all` 统一走 `list_by_keyword_and_time_range` 链路,通过 `belongingConditionId` 区分(`created` / `shared` / `noLimit`)
- 三个 list 命令均支持 `--max`、`--next-token` 分页及 `--query`、`--start`、`--end` 筛选
- `list mine`、`list shared`、`list all` 统一走 `list_by_keyword_and_time_range` 链路,通过 `belongingConditionId` 区分(`created` / `shared` / `noLimit`);三者均支持 `--max`、`--next-token` 分页及 `--query`、`--start`、`--end` 筛选
- `list mine`、`list shared` 默认每页 20 条,`list all` 默认每页 10 条
- `get summary` 返回 AI 生成的结构化 Markdown 摘要
- `get info` 是单条查询;`get batch` 是批量补齐 ID 列表(`--ids` 逗号分隔),用在"有一串 taskUuid,想一次拿标题/时长/参与人"的场景
- `get summary` 返回 AI 生成的结构化 Markdown 摘要;`update summary` 会整体覆盖原摘要,不做增量合并
- `get transcription` 的 `--direction` 控制时间排序: 0=正序(默认), 1=倒序
- `get batch` 支持一次查询多个听记,用逗号分隔 taskUuid
- `replace-text` 是系统性纠错:命中即替换,整篇转写与摘要一起改,慎用模糊文本
- `speaker replace` 只改"标签",不重跑说话人分离;`--target-uid` 可顺带把该说话人绑定到真实 userId
- `hot-word add` 作用于**个人**热词库,只对本人后续的听记识别生效
- `mind-graph create` 是异步:调用后立即返回,需要用 `mind-graph status --id <taskUuid>` 轮询到 ready 才能拿到图
- 上传流程严格三步:`upload create`(拿 sessionId + 预签名 URL)→ 客户端自己 HTTP PUT 文件 → `upload complete`(触发转写);中途放弃必须用 `upload cancel` 释放会话,不要只丢 sessionId
## 自动化脚本
+199
View File
@@ -0,0 +1,199 @@
# OA 审批 (oa) 命令参考
钉钉 OA 审批:待办审批列表 / 查看详情 / 同意 / 拒绝 / 撤销 / 流程记录 / 表单模板。
## 命令总览
### 查询待我处理的审批实例
```
Usage:
dws oa approval list-pending [flags]
Example:
dws oa approval list-pending
dws oa approval list-pending --page 1 --size 20
dws oa approval list-pending --start "2026-03-01T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00"
Flags:
--page string 页码 (默认 1)
--size string 每页数量 (默认 10)
--start string 提交开始时间 ISO-8601 (可选)
--end string 提交结束时间 ISO-8601 (可选)
```
"待办收件箱"视图:列出当前用户仍需动作的审批 `processInstanceId`。注意返回的是**实例 ID**,真正驱动 approve/reject 还需要用 `tasks` 再换成 `taskId`。
### 查询我发起的审批实例
```
Usage:
dws oa approval list-initiated [flags]
Example:
dws oa approval list-initiated
dws oa approval list-initiated --process-code <processCode> --max-results 20
dws oa approval list-initiated --start "2026-03-01T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --next-token <nextToken>
Flags:
--process-code string 按指定表单/流程模板过滤 (可选)
--start string 发起开始时间 ISO-8601 (可选)
--end string 发起结束时间 ISO-8601 (可选)
--max-results string 每页条数 (可选)
--next-token string 分页 token (首页留空)
```
查询当前用户作为申请人发起的审批,用来查"我报销到哪一步了"、"我之前提的请假单"。
### 查询可发起的审批表单模板
```
Usage:
dws oa approval list-forms [flags]
Example:
dws oa approval list-forms
dws oa approval list-forms --cursor 0 --size 20
Flags:
--cursor string 分页游标,首次传 0 (默认 0)
--size string 每页大小 (可选)
```
列出当前用户被授权发起的审批表单(如"请假"、"报销"),返回 `processCode`,后续用于提交新的申请或按模板筛选历史实例。
### 获取审批实例详情
```
Usage:
dws oa approval detail [flags]
Example:
dws oa approval detail --instance-id <processInstanceId>
Flags:
--instance-id string 审批实例 processInstanceId (必填)
```
拉取单个实例的完整内容:表单字段、附件、当前状态、参与人。适合让 AI 读懂审批单再决定动作或生成摘要。
### 查询审批实例的操作记录
```
Usage:
dws oa approval records [flags]
Example:
dws oa approval records --instance-id <processInstanceId>
Flags:
--instance-id string 审批实例 processInstanceId (必填)
```
列出实例的流转历史(谁在什么时候同意/加签/转交/评论),用于复盘或解释"这单卡在哪"。
### 查询实例下待处理的任务
```
Usage:
dws oa approval tasks [flags]
Example:
dws oa approval tasks --instance-id <processInstanceId>
Flags:
--instance-id string 审批实例 processInstanceId (必填)
```
针对具体实例拿到当前用户名下的 `taskId`。approve / reject 要的是 `taskId`,不是 `processInstanceId`,这一步不能省。
### 同意审批
```
Usage:
dws oa approval approve [flags]
Example:
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId>
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId> --remark "同意,按流程推进"
Flags:
--instance-id string 审批实例 processInstanceId (必填)
--task-id string 待办任务 taskId (必填),来自 approval tasks
--remark string 审批意见 (可选)
```
以当前用户身份同意某个审批任务。`--task-id` 必须是当前用户的待办 taskId,否则 MCP 会拒绝。
### 拒绝审批
```
Usage:
dws oa approval reject [flags]
Example:
dws oa approval reject --instance-id <processInstanceId> --task-id <taskId> --remark "预算不足,下月再议"
Flags:
--instance-id string 审批实例 processInstanceId (必填)
--task-id string 待办任务 taskId (必填),来自 approval tasks
--remark string 拒绝理由 (可选,但强烈建议填)
```
以当前用户身份驳回审批任务;拒绝后一般由 MCP 端决定流程是打回申请人还是整体终止。
### 撤回已发起的审批
```
Usage:
dws oa approval revoke [flags]
Example:
dws oa approval revoke --instance-id <processInstanceId>
dws oa approval revoke --instance-id <processInstanceId> --remark "金额填错,重新提交" --yes
Flags:
--instance-id string 审批实例 processInstanceId (必填)
--remark string 撤回原因 (可选)
```
只能撤销**自己发起且尚未终态**的实例。命令标了 `isSensitive`,AI 调用前建议与用户再确认一次,可配合全局 `--yes` 跳过确认。
## 意图判断
用户说"我要审的单/待我处理的审批/审批收件箱" → `list-pending` 拿 `processInstanceId`
用户说"这单我同意/批了它/通过" → `approval tasks` 换 `taskId` → `approval approve`
用户说"这单不行/驳回/拒绝" → `approval tasks` 换 `taskId` → `approval reject --remark "<理由>"`
用户说"这单详情/看看内容/这单在说什么" → `approval detail`
用户说"这单卡在谁那/谁还没审/流程走到哪" → `approval records`
用户说"我发起的审批/我的申请" → `list-initiated`(可用 `--process-code` 按模板筛)
用户说"能发起哪些审批/有哪些表单" → `list-forms`
用户说"撤回我的申请/我不提了" → `approval revoke`(仅限自己发起且未终态)
关键区分: oa(钉钉 OA 审批流程) vs todo(个人待办) vs report(日志汇报)
## 核心工作流
```bash
# 1. 看我有哪些待审的单 — 提取 processInstanceId
dws oa approval list-pending --page 1 --size 20 --format json
# 2. 看单子内容(表单字段 / 附件 / 当前状态)
dws oa approval detail --instance-id <processInstanceId> --format json
# 3. 想动作(同意/拒绝)前,先拿 taskId
dws oa approval tasks --instance-id <processInstanceId> --format json
# 4. 同意 / 拒绝(taskId 来自步骤 3)
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId> --remark "同意" --format json
dws oa approval reject --instance-id <processInstanceId> --task-id <taskId> --remark "预算不足" --format json
# 5. 查看流程记录 — 谁何时做了什么
dws oa approval records --instance-id <processInstanceId> --format json
# 6. 查我发起的审批 / 按模板过滤
dws oa approval list-initiated --format json
dws oa approval list-initiated --process-code <processCode> --format json
# 7. 能发起哪些表单
dws oa approval list-forms --cursor 0 --size 20 --format json
# 8. 撤回我发起的单
dws oa approval revoke --instance-id <processInstanceId> --remark "填错了" --yes --format json
```
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `list-pending` | `processInstanceId`、`nextToken` | detail / tasks / records 的 --instance-id |
| `list-initiated` | `processInstanceId`、`nextToken` | detail / records / revoke 的 --instance-id |
| `list-forms` | `processCode` | `list-initiated --process-code` 按模板过滤,或后续发起新审批 |
| `approval tasks` | `taskId` | approve / reject 的 --task-id |
| `approval detail` | 表单字段、参与人、当前状态 | 供 AI 决策是否批准、生成摘要 |
## 注意事项
- `processInstanceId`(实例 ID)与 `taskId`(待办任务 ID)**不是一回事**,不能混用:
- `list-pending` / `list-initiated` / `detail` / `records` / `revoke` 用 `processInstanceId`
- `approve` / `reject` 用 `taskId`,必须先 `approval tasks --instance-id <processInstanceId>` 换一次
- `list-pending` 是"需要我动作"视图;同一个实例如果流转到下一节点就会从这里消失
- `list-initiated` 是"我发起的"视图;和 `list-pending` 是两套语义,不要互相替代
- `--start` / `--end` 使用 ISO-8601 格式,内部会自动转毫秒时间戳
- `revoke` 只能撤销**当前用户发起且未完结**的实例;已流转结束、已作废、或不是自己发起的都会失败
- `approve` / `reject` 的 `--remark` 可选但强烈建议填,尤其是 `reject`——审批流下游会把理由展示给申请人
- 所有敏感动作(`approve` / `reject` / `revoke`)在 AI 调用时应与用户二次确认,可用全局 `--yes` 跳过确认
- `list-forms` 返回的 `processCode` 是未来发起新审批、或筛选历史实例的钩子;目前本命令集只覆盖审批的"处理侧",发起新审批需走业务方自己的流程
+4 -2
View File
@@ -84,6 +84,8 @@ Flags:
--report-id string 日志 ID (必填)
```
返回该日志的互动聚合数据:浏览数 (views)、点赞数 (likes)、评论数 (comments) 等,用于衡量一篇日志的阅读与反馈情况。
### 查询当前人创建的日志列表
```
Usage:
@@ -106,7 +108,7 @@ Flags:
用户说"查日志/看日报" → `list` 获取列表,再 `detail`
用户说"写日报/提交周报/发日志/填日志" → 先 `template list` / `template detail` 取 `templateId` 与各控件 `key`/`sort`/类型,拼 `--contents` JSON,再 `create`
用户说"日志统计/已读统计" → `stats`
用户说"日志数据/互动情况/多少人看了/点赞评论" → `stats`
用户说"有什么日志模版" → `template list` 或 `template detail`
用户说"我发过的日志/我创建的日志" → `sent`
@@ -133,7 +135,7 @@ dws report list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+0
# 4. 查看日志详情
dws report detail --report-id <reportId> --format json
# 5. 查看日志统计(已读/未读)
# 5. 查看日志统计(浏览/点赞/评论聚合)
dws report stats --report-id <reportId> --format json
# 6. 查看当前人创建的日志列表
+9 -4
View File
@@ -9,11 +9,15 @@ Usage:
Example:
dws todo task create --title "修复线上Bug" --executors <USER_ID_1>,<USER_ID_2> --priority 40
dws todo task create --title "提交报告" --executors <USER_ID> --due "2026-03-20T10:00:00+08:00"
dws todo task create --title "每日站会" --executors <USER_ID> \
--due "2026-03-20T10:00:00+08:00" \
--recurrence $'DTSTART:20260320T020000Z\nRRULE:FREQ=DAILY;INTERVAL=1'
Flags:
--due string 截止时间 ISO-8601 (如 2026-03-10T18:00:00+08:00)
--executors string 执行者 userId 列表 (必填)
--priority string 优先级: 10低/20普通/30较高/40紧急
--title string 待办标题 (必填)
--due string 截止时间 ISO-8601 (如 2026-03-10T18:00:00+08:00)
--executors string 执行者 userId 列表 (必填)
--priority string 优先级: 10低/20普通/30较高/40紧急
--recurrence string 循环规则 RFC 5545 (DTSTART+RRULE,仅在同时设置 --due 时生效)
--title string 待办标题 (必填)
```
### 查询待办列表
@@ -122,6 +126,7 @@ dws todo task delete --task-id <taskId> --yes --format json
- 优先级值: 10=低, 20=普通, 30=较高, 40=紧急
- `--due` 截止时间使用 ISO-8601 格式(如 2026-03-10T18:00:00+08:00)
- `--recurrence` 为 RFC 5545 循环规则(`DTSTART:...\nRRULE:FREQ=DAILY;INTERVAL=1` 这种格式),必须与 `--due` 同时设置才生效
- `task list` 的 `--status` 对应 MCP `get_user_todos_in_current_org` 的 `todoStatus` 参数
- todo 是个人待办管理产品
- `task update` 可同时修改标题/优先级/截止时间/完成状态