P1-a sortTime 泄露进输出契约 —— 采集端无条件写内部排序字段 sortTime,而 emit 仅在单层(reqDepth==1)经 stripDriveDepthDecorations 整体剥离。depth>1 的所有路径都把 sortTime 漏进 stdout;#971 引入的 --type/时间区间过滤同样读该字段,泄露面随之扩大。修法:在 emitDriveDepthResult 尾部无条件 delete,一处覆盖正常 emit / SIGINT 取消 / unrecoverable partial 三条路径。采集端保持不动(内部字段,排序与筛选时才读)。 P1-b 不完整扫描仍以退出码 0 产出「Top-N」 —— 尾部拒绝 guard 只拦全局截断,不拦递归途中目录读取失败;后者把可恢复失败记进 errs[] 后照常 emit,Top-N 落在漏扫子树的不完整集合上却冒充全局最新。修法:guard 扩为 latest>0 && (truncated || len(errs)>0),走新增 driveLatestIncompleteError(LATEST_SCAN_TRUNCATED / LATEST_SCAN_INCOMPLETE 双 token,二者同真时都带,目录失败详情排在截断之前);unrecoverable 分支在 latest>0 时不吐 partial,直接回根因错误。 恢复命令必须能原样复现原候选集:driveLatestScope 快照查询域(--workspace / --space-id)、扫描根(--folder)与全部过滤条件(--pattern / --type / --start / --end),缺任一项,用户照抄后就在另一个集合上取 Top-N,看起来成功却答非所问。扫描根取 runDriveListDepth 实际使用的 rootFolderID 而非重读 flag:用户可能传 URL,解析后的 ID 才是真正被扫的目标。「按原范围重跑」原样带回原 --folder,原调用在空间根时不带。 拒绝产出后 errors[] 不再进 stdout,目录名与服务端错误文本从 JSON(编码会转义)挪进纯文本 stderr —— 原样透传会让 ANSI/OSC 序列被终端执行,可清屏、伪造彩色成功、隐藏后续输出、改窗口标题,Agent 场景还会污染上下文。改为复用仓库既有的 output.SanitizeForTerminal(canonical 实现在 pkg/validate),再把它按设计保留的换行与制表符折成空格。Reason 无需处理:它是 classifyDriveDepthReason 的固定三值映射。latest=0 的既有路径仍把原值放进 errors[] JSON,不受影响。 Windows 下恢复命令的注入面:POSIX 单引号在 cmd.exe 里不是引用,--space-id 传入 sp-7 加 & 加 whoami 时,单引号包裹后的片段粘贴进 cmd 仍会执行 whoami;而唯一做真 shell 往返验证的测试被 build tag 排除在 Windows 之外。不采用「按目标 shell 生成引用」的路线:cmd.exe 的双引号挡不住 %VAR% 展开,PowerShell 的内嵌单引号写法又与 POSIX 不同,且生成命令时无法知道用户会粘贴进哪个 shell。改为平台分流 —— POSIX 构建继续单引号内联;Windows 构建只内联全部由白名单字符组成的值,含元字符的值不进命令,降级为占位符加 strconv.Quote 展示行并标注非可执行(与 internal/auth 展示 profile 标识的既有做法同一思路)。安全性由此不再依赖引用是否正确,而依赖「不受信任的值不进入可执行命令」这个更强的不变量。 顺带修掉白名单里的一个漏洞:% 原本免引用(当初为 URL 的 %20),但 cmd.exe 会无条件展开 %VAR%,于是 %PATH% 这类值会被判为安全并原样内联。% 已移除,POSIX 侧只是多一对无害引号;并新增逐字符断言,锁定白名单不含 POSIX sh / PowerShell / cmd.exe 三套元字符,同时作为该缺陷的回归锁。 两条平台策略写成与构建平台无关的纯函数,平台文件只做一行编译期绑定,因此 Windows 形态能在 POSIX 机器上端到端验证 —— 否则该分支在 POSIX 上永不可达,平台覆盖率门禁会直接报未覆盖(第一版实测 97.3451%)。另做了一次本地全量模拟:临时把 POSIX 绑定切到 Windows 策略后跑全部测试,唯一失败的是专门断言绑定的那条,据此确认没有断言会在 Windows runner 误报,并借此修掉两条原本只在 POSIX 下成立的断言。 SIGINT 取消路径刻意不套用该防线:取消由用户主动发起、退出码 130 已明确告知结果不完整,partial 是用户的预期产物。已加注释说明并补测试锁定该契约。 skill 文档(mono/multi 两份 drive.md)原在过滤章节声明「触顶截断 truncated=true、退出码 0」,同章节又说明可与 --latest 组合 —— 组合后该描述不再成立,故补一条拒绝产出的说明,并注明 Windows 下的占位符形态,避免 agent 按旧契约预期退出码或误解析。 测试命名统一 TestCrossPlatformCoverage 前缀:平台覆盖率门禁 run-platform-coverage-gate.sh 只跑匹配 ^(TestAllShortcuts|TestCrossPlatformCoverage) 的测试。本 PR 因新增带平台名的 go:build 文件被判定 platform_sensitive,Coverage (macOS) / (Windows) 由 SKIPPED 转为实跑;不带该前缀时新增语句在平台 profile 里是零覆盖,实测 69.0476%,改名后 100.0000%(当前 114 条语句仍为 100%)。已在测试文件头写明该前缀是门禁约定而非命名风格。 发布说明按 .changes fragment 机制落在 .changes/899-drive-latest-incomplete-scan.md,不改 CHANGELOG.md。
DingTalk Workspace CLI (dws)
dws — DingTalk Workspace on the command line, built for humans and AI agents.
中文版 · English · Reference · Changelog
Important
Co-creation Phase: This project accesses DingTalk enterprise data and requires enterprise admin authorization. Join the DingTalk DWS co-creation group for support and updates. See Getting Started below.
![]()
Table of Contents
Why dws?
- For humans —
--helpfor usage,--dry-runto preview requests,-f table/json/rawfor output formats. - For AI agents — structured JSON responses + built-in Agent Skills, ready out of the box.
- For enterprise admins — zero-trust architecture: OAuth device-flow auth + domain allowlisting + least-privilege scoping. Not a single byte can bypass authentication and audit.
Installation
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh
Windows (PowerShell):
irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.ps1 | iex
Skill mode: mono vs multi
The installer ships skills in one of two layouts. CLI commands (dws aitable ..., dws calendar ...) are identical in both modes — only the agent-side skill layout differs.
| Mode | What gets installed | Best for |
|---|---|---|
| multi (default) | Per-product skills (dingtalk-aitable, dingtalk-calendar, dingtalk-chat, ...) |
Single-product tasks; smaller context per call |
| mono (legacy) | One dws skill covering all products |
Cross-product workflows; single entry point |
Installs and upgrades default to
multi.monoremains available viaDWS_SKILL_MODE=monoordws skill setup --mode mono. File issues if you hit problems.
How to pick:
- Quick install (one-liner above): non-interactive, installs
multi. - TTY install (download then run):
curl -O .../install.sh && bash install.sh— prompts1) multi 2) mono(default 1). - Override via env:
DWS_SKILL_MODE=mono curl -fsSL ... | sh. - Switch later:
dws skill setup --mode mono(or--mode multi) — review the listed paths and confirm interactively.
Other install methods
npm (requires Node.js (npm/npx)):
npm install -g dingtalk-workspace-cli
Install the latest beta:
npm install -g dingtalk-workspace-cli@beta
Homebrew (macOS / Linux):
brew tap DingTalk-Real-AI/dingtalk-workspace-cli https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
brew install dingtalk-workspace-cli
The Formula lives in this repository, so the first
tapcommand must include the explicit repository URL. Afterwards, usebrew upgrade dingtalk-workspace-clinormally.
Install the keg-only Homebrew beta without replacing the stable Formula:
brew install dingtalk-workspace-cli-beta
$(brew --prefix dingtalk-workspace-cli-beta)/bin/dws version
To make the beta dws the default for the current shell, prepend $(brew --prefix dingtalk-workspace-cli-beta)/bin to PATH.
Pre-built binary: download from GitHub Releases.
macOS users: If you see "cannot be opened because Apple cannot check it for malicious software", run:
xattr -d com.apple.quarantine /path/to/dws
Build from source:
git clone https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
cd dingtalk-workspace-cli
go build -o dws ./cmd # build to current directory
cp dws ~/.local/bin/ # install to PATH
Static endpoint data is generated from the Wukong baseline and committed in this
repository under internal/syncdata, so source builds do not require a sibling
data checkout.
Requires Go 1.25+. Use
make packageto cross-compile for all platforms (macOS / Linux / Windows x amd64 / arm64).
China mirror
For users in mainland China, the following channels avoid GitHub network issues. By default (without setting these environment variables) the installer pulls from GitHub.
1. Install script + pre-built binary (Gitee mirror):
Repository mirror: https://gitee.com/DingTalk-Real-AI/dingtalk-workspace-cli
DWS_GITEE_REPO=DingTalk-Real-AI/dingtalk-workspace-cli curl -fsSL https://gitee.com/DingTalk-Real-AI/dingtalk-workspace-cli/raw/main/scripts/install.sh | sh
With
DWS_GITEE_REPOset, the installer resolves the latest version and every release asset (binary, checksums, skills) from the Gitee API instead of GitHub. If it is unset, installation defaults to GitHub.
2. npm package (npmmirror mirror):
npm install -g dingtalk-workspace-cli --registry=https://registry.npmmirror.com
npmmirror automatically syncs public packages from the public npm registry, so this works directly in China.
3. Skills only (Gitee mirror):
DWS_GITEE_REPO=DingTalk-Real-AI/dingtalk-workspace-cli curl -fsSL https://gitee.com/DingTalk-Real-AI/dingtalk-workspace-cli/raw/main/scripts/install-skills.sh | sh
With
DWS_GITEE_REPOset,install-skills.shresolves the version and skills package from Gitee; it also auto-falls back to the Gitee mirror when GitHub is unreachable.
Upgrade
Requires v1.0.7 or later. For earlier versions, please re-run the install script to upgrade.
dws has built-in self-upgrade capability. Updates are pulled directly from GitHub Releases with SHA256 integrity verification and automatic backup.
dws upgrade # interactive upgrade to latest version
dws upgrade --check # check for new versions without installing
dws upgrade --list # list stable release versions
dws upgrade --beta # upgrade to the latest beta pre-release
dws upgrade --check --beta # check the beta track without installing
dws upgrade --list --beta # list beta pre-release versions
dws upgrade --version v1.0.7 # upgrade to a specific version
dws upgrade --version v1.0.8-beta.1 # upgrade to a specific beta version
dws upgrade --rollback # rollback to the previous version
dws upgrade -y # skip confirmation prompt
By default, dws upgrade follows the stable release track. Use --beta only when you explicitly want the newest GitHub pre-release build.
Six-channel post-release verification
Maintainers and release validators can run the release-quality smoke checks for curl, PowerShell, npm stable, npm beta, Homebrew, and dws upgrade:
git clone https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git /tmp/dws-verify
cd /tmp/dws-verify/verify
bash verify-all-channels.sh
The verifier uses isolated directories and does not replace the dws on the current PATH. It reports PASS, FAIL, and SKIP; a platform skip is not a pass and must be covered on the matching host. See verify/README.md for the platform matrix.
How it works
The upgrade process follows a two-phase atomic flow to ensure consistency:
- Prepare — downloads the platform-specific binary and skill packages to a temporary directory, verifies SHA256 checksums, and extracts/validates all files. If any step fails, the upgrade aborts without modifying the existing installation.
- Apply — only after all preparations succeed, the binary is replaced and skills are flattened into detected agent-specific roots (for example
~/.codex/skills/dingtalk-chat).~/.agents/skillsis used only when no specific Agent is detected; once a specific root is active, older DWS-managed generic copies are backed up and retired so the same Skill is not discovered twice.
A backup of the current version is automatically created before each upgrade. Use dws upgrade --rollback to restore the previous version if needed.
| Flag | Description |
|---|---|
--check |
Check for updates without installing |
--list |
List available stable release versions with changelogs |
--beta |
Use the beta pre-release track for upgrade, --check, or --list |
--version |
Upgrade to a specific version (e.g. v1.0.7 or v1.0.8-beta.1) |
--rollback |
Rollback to the previous backed-up version |
--force |
Force reinstall even if already on the latest version |
--skip-skills |
Skip skill package update |
-y |
Skip confirmation prompt |
Getting Started
dws auth login # browser opens automatically
dws auth login --device # for headless environments (Docker, SSH, CI)
Select your organization and authorize. That's it.
If your organization hasn't enabled CLI access, you'll be prompted to send an access request to your admin. Once approved, re-run
dws auth login.
Organization hasn't enabled CLI access?
- After selecting your organization, click "Apply Now" to notify the admin
- The admin receives a request card and can approve with one click
- Once approved, re-run
dws auth login
Admin: Enable CLI access for your organization
Go to Developer Platform → "CLI Access Management" → Enable.
Custom App mode (CI/CD, ISV integration)
For enterprise-managed scenarios, create your own DingTalk app:
- Open Platform Console → Create App
- Security Settings → Add redirect URLs:
http://127.0.0.1,https://login.dingtalk.com - Publish the app
- Login:
dws auth login --client-id <your-app-key> --client-secret <your-app-secret>
Credentials are securely persisted after first login (Keychain). Subsequent runs auto-refresh tokens.
Multiple organizations (profiles)
dws can stay logged in to several DingTalk accounts at once, including multiple accounts in the same organization. A profile is uniquely identified by corpId:userId; the current profile decides which identity a command runs as.
dws auth login # add or refresh one account
dws profile list # list every logged-in account
dws profile switch <corpId:userId> # persistently switch; use - to toggle back
dws profile switch "<corpName>:<userName>" # friendly input; names must be unique
dws --profile <corpId> contact user search --query "..." # use that org's explicitly recorded current account
dws --profile <corpId:userId> contact user search --query "..." # use one exact account without changing the default
Selectors support corpId:userId, corpId:userName, corpName:userId, and corpName:userName. Friendly names are input aliases only; use the stable profile value returned by profile list for automation. Duplicate organization or account names fail with explicit corpId:userId candidates. If an organization has multiple accounts but no recorded current account, --profile <corpId> fails instead of choosing the first or most recently used account.
currentProfile, previousProfile, and per-organization defaults are stored as exact identities. primaryProfile remains in JSON only for compatibility and is not used for selection. profile list reads status and expiry from each real identity Token without refreshing it. auth logout --profile <corpId> removes all local accounts in that organization; an exact selector or local profile name removes one account.
Cross-org reads are orchestrated by the agent rather than a built-in --all-orgs: list profiles, group by corpId, and use the unique isOrgCurrent=true account for each organization. If a multi-account organization has no default, ask the user to choose an account first. Writes default to the current account — confirm both organization and account before cross-org writes.
On macOS, an unreadable registered token slot blocks a new OAuth login rather than risking a mixed Keychain/file-DEK state. If normal terminal commands can still read the login while a sandbox using DWS_DISABLE_KEYCHAIN=1 cannot, migrate the legacy and profile auth entries without exposing tokens:
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --dry-run --format json
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --yes --format json
DWS_DISABLE_KEYCHAIN=1 dws auth status --format json
The migration validates every selected auth ciphertext before writing, ignores unrelated application secrets, and can be rerun after an interrupted commit. If validation identifies genuinely damaged ciphertext, remove only the affected account with dws auth logout --profile <corpId:userId>, or all accounts in one organization with --profile <corpId>, then log in again. Use dws auth reset only when you intend to discard every local profile.
Migrate auth between Linux sandboxes
Copying only ~/.dws/app.json does not carry the refresh token; access tokens expire after ~2 hours. Use the official export/import flow:
# Sandbox A (already logged in)
dws auth export -o /tmp/dws-auth.tar.gz
# Or for copy/paste: dws auth export --base64 -o /tmp/dws-auth.b64
# Sandbox B
dws auth import -i /tmp/dws-auth.tar.gz
# Or: dws auth import -i /tmp/dws-auth.b64 --base64
dws auth status # confirm "Refresh Token: valid"
The bundle includes the encrypted keychain under ~/.local/share/dws-cli (with auth-token.enc and dek) plus required ~/.dws config files.
Windows export and import are intentionally rejected before credentials or
bundles are read: Windows stores credentials as DPAPI-protected HKCU Registry
values, and the current file-DEK bundle has no safe DPAPI-to-portable conversion.
Quick Start
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— all commands with descriptions and when-to-use guidance.
Using with Agents
dws is designed as an AI-native CLI. Complete Installation and Getting Started first, then configure your agent:
Agent Invocation Patterns
# Use --yes to skip confirmation prompts (required for agents)
dws todo task create --title "Review PR" --executors "<your-userId>" --yes
# Use --dry-run to preview operations (safe execution)
dws contact user search --query "engineering" --dry-run
# Use --jq to extract precisely (save tokens)
dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}'
Command Help and Schema
Use Cobra help and Schema for different parts of the command contract:
dws <path> --helpis the source of truth for whether a command exists and which flags the binary accepts.dws schema "<path>" --compactis the normative Agent view for command selection, CLI parameters and constraints, risk, and confirmation; use a full leaf with a narrow--jqprojection for mapping or provenance audits.- If Help and Schema disagree, treat it as contract drift: pass only flags accepted by Cobra and use the more conservative safety semantics.
- Schema describes commands; it does not read or search DingTalk business data. Execute the real product command after discovery.
# Confirm that the command exists and inspect accepted flags
dws aitable record query --help
# Discover within a product, then inspect the selected leaf contract
dws schema aitable --compact
dws schema "aitable record query" --compact
# Execute the real business query
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
dws schema --all exports the complete contract for tooling, CI, audits, and compatibility baselines. Agents should query progressively with --compact; its positive field allowlist prevents new full/audit fields from silently expanding Agent context.
Agent Skills
The repo ships a complete Agent Skill system under skills/, organized into two layouts:
skills/mono/— single-skill layout (oneSKILL.md+references/products/), legacy.skills/multi/— per-product skills (dingtalk-aitable/,dingtalk-calendar/,dingtalk-chat/, ...), each with its ownSKILL.md. Default layout.
Leaf safety/parameters/selection prose for Schema generation come from ProductDecl / ContractFinal declarations in Go. The former internal/cli/schema_hints/ HintFile tree is fully retired and must not reappear.
After installing, AI tools like Claude Code / Cursor can operate DingTalk directly through natural language:
# Install skills into current project (defaults to multi; DWS_SKILL_MODE=mono switches back)
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-skills.sh | sh
Installers prefer detected agent-specific roots such as
$HOME/.codex/skills/. They use.agents/skills/only as the generic fallback when no specific Agent is detected; multi layout is per-product siblings, while mono uses thedws/subdirectory.China users: prefix
DWS_GITEE_REPOto use the Gitee mirror — see China mirror.
Switching or re-installing with dws skill setup:
# Interactive: prompts for mode + target agents
dws skill setup
# Preview the exact directories that mono setup would back up and replace
dws skill setup --mode mono --target all --dry-run
# Run interactively and confirm the listed directories
dws skill setup --mode mono --target all
# Preview, then install multi skills to a single agent home with interactive confirmation
dws skill setup --mode multi --target cursor --dry-run
dws skill setup --mode multi --target cursor
# Point at a local source tree (e.g. a fork or work-in-progress), preview first
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi --dry-run
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
| Flag | Values | Description |
|---|---|---|
--mode |
mono | multi |
Skill layout; defaults to interactive prompt |
--target |
all | claude | cursor | codex | zcode | opencode | qoder |
Where to install; all covers every detected agent home, including ZCode at ~/.zcode/skills |
--source |
path | Local source directory (overrides bundled skills) |
--yes |
— | Scripting-only: skip the confirmation prompt. Removals are still backed up to ~/.dws/skill-backups/ first |
The setup command can remove the opposite-mode layout (
dws/for multi, DWS-managed multi Skills for mono) and stale managed Skills not in the bundle. DWS records ownership, installer version, source, and content digest centrally in~/.dws/skills-state.json(or$DWS_CONFIG_DIR/skills-state.json). Exact official names shipped before the centralized state remain a frozen migration list. Adingtalk-*prefix alone never authorizes cleanup, so other same-prefix market/user Skills are preserved. Every removal is previewed before confirmation and preserved under~/.dws/skill-backups/<timestamp>/; a directory that cannot be backed up is never removed. In a non-interactive shell, first run--dry-runand inspect its output; only then may the caller explicitly choose the scripting-only confirmation bypass.
After a multi setup or upgrade, DWS stores the official bundle snapshot and centralized ownership metadata in ~/.dws/skills-state.json (or $DWS_CONFIG_DIR/skills-state.json). Every upgrade installs and overwrites the complete bundled Skill set from that release. Deleting or excluding a bundled Skill is not sticky: the next upgrade restores it. dws upgrade --force additionally allows reinstalling the current CLI version when no newer version is available.
Env vars: DWS_SKILL_MODE=mono|multi (also honored by install.sh / install.ps1), DWS_SKILL_SOURCE=<path>.
What's included (mono layout):
| Component | Path | Description |
|---|---|---|
| Master Skill | skills/mono/SKILL.md |
Intent routing, decision tree, safety rules, error handling |
| Product references | skills/mono/references/products/*.md |
Per-product command reference (aitable, chat, calendar, etc.) |
| Intent guide | skills/mono/references/intent-guide.md |
Disambiguation for confusing scenarios (e.g. report vs todo) |
| Global reference | skills/mono/references/global-reference.md |
Auth, output formats, global flags |
| Error codes | skills/mono/references/error-codes.md |
Error codes + debugging workflows |
| Ready-made scripts | skills/mono/scripts/*.py |
13 batch operation scripts (see below) |
Ready-made scripts — 13 Python scripts for common multi-step workflows
| Script | Description |
|---|---|
calendar_schedule_meeting.py |
Create event + add participants + find & book available meeting room |
calendar_free_slot_finder.py |
Find common free slots across multiple people, recommend best meeting time |
calendar_today_agenda.py |
View today/tomorrow/this week's schedule |
import_records.py |
Batch import records from CSV/JSON into AITable |
bulk_add_fields.py |
Batch add fields to an AITable data table |
upload_attachment.py |
Upload attachment to AITable attachment field |
todo_batch_create.py |
Batch create todos from JSON (with priority, due date, executors) |
todo_daily_summary.py |
Summarize today/this week's incomplete todos |
todo_overdue_check.py |
Scan overdue todos and output overdue list |
contact_dept_members.py |
Search department by name and list all members |
attendance_my_record.py |
View my attendance records for today/this week/specific date |
attendance_team_shift.py |
Query team shift schedules and attendance statistics |
report_inbox_today.py |
View today's received reports with details |
ISV Integration: Author your own Agent Skills and orchestrate them with dws skills for cross-product workflows: ISV Skill → dws Skill → DingTalk Open Platform API (enforced auth + full audit).
Features
Personal Event Subscription — real-time DingTalk messages for event-driven agents
dws event consume subscribes as the currently logged-in user over a managed Stream WebSocket and emits each event as one NDJSON line on stdout. The public catalog covers scoped and all one-to-one/group messages, specified senders, read/recall/reaction events, group lifecycle events, and six OA approval task/instance events.
The default ndjson, json, and pretty output preserves the transport envelope (type, event_type, string data, and headers) for existing scripts; compact retains its existing processor. Add --flatten to emit the stable top-level business fields used by Agent workflows. --format controls JSON serialization; --flatten controls the data structure and cannot be combined with -f raw or --debug-raw-events.
Prerequisite: run
dws auth login. Personal identity is resolved from the OAuth token and cannot be supplied through command-line identity flags.
For an event-focused installation, use the official convenience installer:
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-event.sh | sh
# Or install the standalone multi skill from an existing dws installation
dws skill setup --mode multi -s event
# Inspect the public personal event catalog and schema
dws event list
dws event schema user_im_message_receive_o2o --flatten
dws event list --category oa
dws event schema user_oa_approval_task_created --flatten
# Listen for messages that mention the current user
dws event +listen-im --kind at-me -f ndjson
# Listen for messages from a specified sender
dws event +listen-im --kind sender --user <userId> -f ndjson
# Listen by openDingtalkId (external contact, bot, or cross-organization identity)
dws event +listen-im --kind sender --open-dingtalk-id <openDingtalkId> -f ndjson
# Listen for messages in a specified group
dws event +listen-im --kind group --chat-id <openConversationId> -f ndjson
# Listen for all one-to-one or all group messages
dws event +listen-im --kind all-direct -f ndjson
dws event +listen-im --kind all-group -f ndjson
# Listen for a specified group's title changes, member changes, or disband event
dws event consume user_im_group_updated --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_added --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_exited --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_disbanded --group <openConversationId> --flatten -f ndjson
# Listen for messages, reads, and recalls from the same sender in one process
dws event +listen-im --kind sender --user <userId> \
--events message,read,recall -f ndjson
# Listen for all six public OA approval events in one process
dws event consume \
user_oa_approval_task_created \
user_oa_approval_task_finished \
user_oa_approval_task_redirected \
user_oa_approval_instance_started \
user_oa_approval_instance_terminated \
user_oa_approval_instance_finished \
--flatten -f ndjson
# Inspect local consumers and cancel a subscription
dws event status
dws event stop <subscribe_id>
For one-to-one and specified-sender events, use exactly one target identity: --user for an internal userId, or --open-dingtalk-id for an openDingtalkId. The CLI does not infer or convert between these identity types.
| Feature | Details |
|---|---|
| Managed lifecycle | consume creates or reuses the personal subscription; stop cancels it and cleans local state |
| Shared connection | Consumers for the same user share one local bus and cloud connection |
| Multi-event process | One consume process can listen for compatible events for the same target while retaining one subscription per event |
| Subscription isolation | Normal consumers match both event type and subscribe_id |
| Agent-friendly output | Stream events are written to stdout as NDJSON; status and diagnostics use stderr |
| Observability | status shows remote subscriptions, the personal bus, and local consumers |
| Cross-platform | Unix Socket on macOS/Linux, Windows Named Pipe on Windows |
See skills/multi/dingtalk-event/SKILL.md for the Agent workflow and supported event parameters.
Raw API Access — call any DingTalk OpenAPI directly
dws api lets you call any DingTalk OpenAPI without an SDK. Tokens are automatically acquired and refreshed.
Prerequisite: Must login with your own app credentials (see Custom App mode). Encrypted tokens from MCP default-credential login are not supported for raw API calls.
# Login (first time only)
dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>
# === api.dingtalk.com ===
# List all enterprise apps
dws api GET /v1.0/microApp/allApps
# Search users (POST + JSON body)
dws api POST /v1.0/contact/users/search \
--data '{"queryWord":"engineering","offset":0,"size":10}'
# === oapi.dingtalk.com ===
# Get user details (use --base-url to specify domain)
dws api POST /topapi/v2/user/get \
--base-url https://oapi.dingtalk.com \
--data '{"userid":"<USER_ID>"}'
# Or use the full URL directly
dws api POST https://oapi.dingtalk.com/topapi/v2/user/get \
--data '{"userid":"<USER_ID>"}'
# === General ===
dws api GET /v1.0/microApp/allApps --page-all # auto-paginate
dws api GET /v1.0/microApp/allApps --dry-run # preview request
dws api GET /v1.0/microApp/allApps --jq '.agentId' # jq filtering
| Feature | Details |
|---|---|
| Dual-form auto-detection | Automatically selects api.dingtalk.com (header auth) or oapi.dingtalk.com (query-param auth) based on URL |
| Automatic token management | App-level accessToken is fetched on first call, cached while valid, auto-refreshed on expiry |
| Domain allowlist | Only api.dingtalk.com and oapi.dingtalk.com permitted — prevents token leakage |
| Auto-pagination | --page-all iterates all pages. --page-limit caps the maximum (default 10, set to 0 for unlimited, hard cap at 500 to prevent infinite loops) |
Smart Input Correction — auto-corrects common AI model parameter mistakes
Built-in pipeline engine that normalizes flag names, splits sticky arguments, and fuzzy-matches typos:
# Naming convention auto-conversion (camelCase / snake_case / UPPER -> kebab-case)
dws aitable record query --baseId BASE_ID --tableId TABLE_ID # auto-corrected to --base-id --table-id
# Sticky argument splitting
dws contact user search --query "engineering" --timeout30 # auto-split to --timeout 30
# Fuzzy flag name matching
dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-id -> --table-id
# Value normalization (boolean / number / date / enum)
# "yes" -> true, "1,000" -> 1000, "2024/03/29" -> "2024-03-29", "ACTIVE" -> "active"
| Agent Output | dws Auto-Corrects To |
|---|---|
--userId |
--user-id |
--limit100 |
--limit 100 |
--tabel-id |
--table-id |
--USER-ID |
--user-id |
--user_name |
--user-name |
jq Filtering & Field Selection — fine-grained output control to reduce token consumption
# Built-in jq expressions
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
dws schema "dev app create" --jq '.parameters'
# Return only specific fields
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
Schema Introspection — Agent command discovery and execution contracts
dws schema aitable --compact # discover product commands
dws schema "aitable record query" --compact # view the selected Agent leaf contract
dws schema "aitable record query" --jq '[.parameters | to_entries[] | select(.value.required)]' # view required fields
dws schema --all # full export for CI/audit/baselines
Pipe & File Input — read flag values from files or stdin
# Read message body from a file
dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
--title "Weekly Report" --text @report.md
# Pipe content via stdin
cat report.md | dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
--title "Weekly Report"
# Read from stdin explicitly
dws chat message send-by-bot --robot-code BOT_CODE --group GROUP_ID \
--title "Weekly Report" --text @-
Note
:
@is treated as the@<path>file-injection prefix only when the next character is an ASCII path-shaped character (A-Z/a-z/0-9/.///~/_/-), or@-for stdin. Chat-bot payloads like--text "@所有人 周报"or--text "@张三 看一下"pass through unchanged, so literal mentions reach the API as-is.
DingTalk bot — connect a robot to your local AI
dws dev connect bridges a DingTalk robot to a local AI CLI (Claude Code / Codex / opencode / Qoder / Gemini, or any tool via --agent-cmd): @-mention the bot in a chat and it answers using your local agent, keeping per-conversation multi-turn memory.
dws dev connect --channel auto --unified-app-id <unifiedAppId>
--unified-app-idresolvesclientSecretat runtime viadev app credentials get, so the secret never appears in argv (ps/ journald / shell history). The legacy--robot-client-id <id> --robot-client-secret <secret>still works but the CLI will warn you.
In-chat session commands (send the bare command as the whole message — no agent turn, no tokens):
| Command | Effect |
|---|---|
/new (aliases /start, /reset) |
Start a fresh session; the previous one is left intact (resumable where the agent supports it) |
/clear |
Wipe the current session — disposed through the agent's real session op (opencode issues DELETE /session/:id); channels whose agent exposes no delete primitive fall back to a reset |
See docs/robot-quickstart.md for the full 4-step walkthrough (install → create robot → connect → add to a group).
Key Services
| Service | Command | Capabilities |
|---|---|---|
| Contact | contact |
Look up users, departments, labels, roster profiles and dismissals; create enterprises and enterprise accounts; invite employees |
| Chat / IM | chat (im) |
Send / reply / search messages, group & member management, bot & webhook messaging, reactions, recall |
| Calendar | calendar |
Events CRUD, attendees, meeting rooms, free/busy & time suggestions |
| Todo | todo |
Create / list / update / complete tasks and comments |
| Approval | oa |
Approve / reject / revoke / transfer; query pending / initiated / CC instances and forms |
| Attendance | attendance |
Clock-in records, shifts, summaries, group rules (read-only) |
| Ding | ding |
Send / recall DING messages |
| Report | report |
Create / submit logs, inbox & outbox, templates, statistics |
| AI Tables | aitable |
Bases / tables / records / fields / views, permissions & roles, automation, charts & dashboards, import / export |
| Doc | doc |
Search / read / write docs, block-level editing, comments, permissions, media, up / download |
| Drive | drive |
List / search / download, folders, upload, copy / move / rename, permissions |
| Minutes | minutes |
AI meeting notes: list, summary / keywords / transcription / todos, mind map, speakers, tags |
mail |
Mailboxes, KQL search, read / send, drafts, folders, templates, contacts | |
| Sheet | sheet |
Online spreadsheets: worksheet & range read / write, filters, conditional format, images, CSV |
| Wiki | wiki |
Knowledge bases: spaces, members, node tree, docs & files |
| DevDoc | devdoc |
Search the Open Platform docs and diagnose API errors |
| AI Search | aisearch |
Enterprise people search by name / dept / role / duty / supervisor / phone / job-number |
| Live | live |
List my live streams |
| Raw API | api |
Call any DingTalk OpenAPI directly, with managed app-level token |
Full command listing with usage scenarios:
docs/command-index.md. Rundws --helpfor the top-level tree, ordws <service> --helpfor any service's subcommands.
Note on
chat bot: bot capabilities (send-by-bot/recall-by-bot/add-bot/send-by-webhook/ bot search) are merged into the relevantchatsubtrees (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-levelbotproduct.
Coming soon
conference(video meetings)- Multi-skill mode (default) — per-product skills under
skills/multi/; installs and upgrades default to it,dws skill setup --mode monoswitches back after interactive confirmation
Security by Design
dws treats security as a first-class architectural concern, not an afterthought. Credentials never touch disk, tokens never leave trusted domains, permissions never exceed grants, operations never escape audit — every API call must pass through DingTalk Open Platform's authentication and audit chain, no exceptions.
For Developers
| Mechanism | Details |
|---|---|
| Encrypted token storage | PBKDF2 + AES-256-GCM encryption, keyed by device physical MAC address; cross-platform Keychain/DPAPI integration provides additional protection — tokens cannot be decrypted on another machine |
| Input security | Path traversal protection (symlink resolution + working directory containment), CRLF injection blocking, Unicode visual spoofing filtering — prevents AI Agents from being tricked by malicious instructions |
| Domain allowlist | DWS_TRUSTED_DOMAINS defaults to *.dingtalk.com; bearer tokens are never sent to non-allowlisted domains |
| HTTPS enforced | All requests require TLS; HTTP only permitted for loopback during development |
| Dry-run preview | --dry-run shows call parameters without executing, preventing accidental mutations |
| Zero credential persistence | Client ID / Secret used in memory only — never written to config files or logs |
For Enterprise Admins
| Mechanism | Details |
|---|---|
| OAuth device-flow auth | Users must authenticate through an admin-authorized DingTalk application |
| Least-privilege scoping | CLI can only invoke APIs granted to the application — no privilege escalation |
| Allowlist gating | Admin confirmation required during co-creation phase; self-service approval planned |
| Full-chain audit | Every data read/write passes through the DingTalk Open Platform API — enterprise admins can trace complete call logs in real time; no anomalous operation can hide |
For ISVs
| Mechanism | Details |
|---|---|
| Tenant data isolation | Operates under authorized app identity; cross-tenant access is impossible |
| Skill sandbox | Agent Skills are Markdown documents (SKILL.md) — prompt descriptions only, no arbitrary code execution |
| Zero blind spots | Every API call during ISV–dws skill orchestration is forced through DingTalk Open Platform authentication — full call chain is traceable with no bypass path |
Found a vulnerability? Report via GitHub Security Advisories. See SECURITY.md.
Reference & Docs
- Command Index — every runtime command with description and when-to-use guidance
- Reference — environment variables, exit codes, output formats, shell completion
- Architecture — static endpoint pipeline, command surface, transport layer
- Open Platform App Command Routing — yulan dev app command design, MCP overlay, permission flow, and Agent routing
- Changelog — release history and migration notes
Contributing
See CONTRIBUTING.md for build instructions, testing, and development workflow.
License
Apache-2.0



