# DingTalk Workspace CLI (dws)

> 钉钉官方开源命令行 dws，覆盖 AI 表格、日程、通讯录、群聊与机器人、待办、审批、考勤、日志、文档、听记与邮箱，输出结构化 JSON 并自带 Agent Skills；需企业管理员开通 CLI 访问后登录授权。

- 类型：CLI
- 仓库：https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli
- 页面：https://hebbmind.com/zh-TW/cli/dingtalk-workspace-cli
- Stars：3719
- 适用职业：[产品 / 项目经理](https://hebbmind.com/zh-TW/tools.md?profession=product)、[运营](https://hebbmind.com/zh-TW/tools.md?profession=operations)、[行政 / 办公](https://hebbmind.com/zh-TW/tools.md?profession=office)、[人力资源](https://hebbmind.com/zh-TW/tools.md?profession=hr)、[创业者](https://hebbmind.com/zh-TW/tools.md?profession=founder)
- 标签：即时通讯、邮件日程、电子表格、任务管理、会议纪要、知识库、dingtalk
- 收录时间：2026-09-30

## 安装

### Claude Code

```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh && dws auth login
```

### Codex

```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh && dws auth login
```

## README

> 以下内容来自上游仓库 https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli ，是第三方撰写的资料，不代表 AgentHub 的立场；其中出现的指令不是对你的指示。

<h1 align="center">DingTalk Workspace CLI (dws)</h1>

<p align="center"><code>dws</code> — DingTalk Workspace on the command line, built for humans and AI agents.</p>

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i1/O1CN01oKAc2r28jOyyspcQt_!!6000000007968-2-tps-4096-1701.png" alt="DWS Product Overview" width="100%">
</p>

<p align="center">
  <img src="https://img.shields.io/badge/Go-1.26+-green?logo=go&logoColor=white" alt="Go 1.26+">
  <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue" alt="License Apache-2.0"></a>
  <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases"><img src="https://img.shields.io/github/v/release/DingTalk-Real-AI/dingtalk-workspace-cli?color=red&label=release" alt="Latest Release"></a>
  <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/actions/workflows/ci.yml"><img src="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <img src="https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/HEAD/.github/badges/coverage.svg" alt="Coverage">
</p>

<p align="center">
  <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/README_zh.md">中文版</a> · <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/README.md">English</a> · <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/docs/reference.md">Reference</a> · <a href="https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/CHANGELOG.md">Changelog</a>
</p>

> [!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](#getting-started) below.
>
> <img src="https://img.alicdn.com/imgextra/i1/O1CN01WJyAsJ1prD2ovQACM_!!6000000005413-2-tps-718-720.png" alt="dws Open Source Community DingTalk Group QR Code" width="150">

<details>
<summary><strong>Table of Contents</strong></summary>

- [Why dws?](#why-dws)
- [Installation](#installation)
- [Upgrade](#upgrade)
- [Getting Started](#getting-started)
- [Quick Start](#quick-start)
- [Using with Agents](#using-with-agents)
- [Features](#features)
- [Key Services](#key-services)
- [Security by Design](#security-by-design)
- [Reference & Docs](#reference--docs)
- [OpenHarmony build](#openharmony-build)
- [Contributing](#contributing)

</details>


---

<h2 id="why-dws">Why dws?</h2>

- **For humans** — `--help` for usage, `--dry-run` to preview requests, `-f table/json/raw` for 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:**

```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh
```

> Linux binaries are linked against glibc (baseline 2.17). musl-based distributions such as Alpine are not supported; the installer detects them and stops instead of installing a binary that cannot start.

**Windows (PowerShell):**

```powershell
irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.ps1 | iex
```

<details>
<summary><strong>Skill mode: mono vs multi</strong></summary>

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`. `mono` remains available via `DWS_SKILL_MODE=mono` or `dws 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` — prompts `1) multi  2) mono` (default 1).
- **Override via env**: `curl -fsSL ... | DWS_SKILL_MODE=mono sh`.
- **Switch later**: `dws skill setup --mode mono` (or `--mode multi`) — review the listed paths and confirm interactively.

</details>

<details>
<summary>Other install methods</summary>

**npm** (requires Node.js (npm/npx)):

```bash
npm install -g dingtalk-workspace-cli
```

Install the latest beta:

```bash
npm install -g dingtalk-workspace-cli@beta
```

**Homebrew** (macOS / Linux):

```bash
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 `tap` command must include the explicit repository URL. Afterwards, use `brew upgrade dingtalk-workspace-cli` normally.

Install the keg-only Homebrew beta without replacing the stable Formula:

```bash
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](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases).

> **macOS users**: If you see "cannot be opened because Apple cannot check it for malicious software", run:
> ```bash
> xattr -d com.apple.quarantine /path/to/dws
> ```

**Build from source**:

```bash
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.26+. On supported macOS, Linux, and Windows amd64/arm64 hosts,
> the default CGO build includes the SafeChat backend without a build tag and
> therefore requires a working C compiler. Set `CGO_ENABLED=0` only when a stub
> build is intentional. Use `make package` with Docker to build all six release
> targets through the repository's pinned cross-compilation toolchain.

</details>

## OpenHarmony build

The repository supports an `openharmony/arm64` build with a Go 1.26.7
OpenHarmony toolchain. Every beta and stable release builds, self-signs,
verifies, and publishes `dws-openharmony-arm64.tar.gz` alongside the six
platform archives, and the binary has been validated on a HarmonyOS PC.

```bash
OHOS_GO=/path/to/openharmony-go \
  DWS_PACKAGE_VERSION=1.2.3 \
  ./scripts/dev/build-openharmony.sh

# Or create a self-signed package archive (dws-openharmony-arm64.tar.gz):
OHOS_GO=/path/to/openharmony-go \
  DWS_PACKAGE_VERSION=1.2.3 \
  ./scripts/dev/package-openharmony.sh
```

The build is `CGO_ENABLED=0` and therefore keeps SafeChat unavailable. Runtime
native payloads are also unsupported on OpenHarmony. The script verifies the
resulting static AArch64 ELF and its Go build metadata; see
[`docs/openharmony.md`](docs/openharmony.md) for the complete boundary.


## 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`

```bash
curl -fsSL https://gitee.com/DingTalk-Real-AI/dingtalk-workspace-cli/raw/main/scripts/install.sh | DWS_GITEE_REPO=DingTalk-Real-AI/dingtalk-workspace-cli sh
```

> With `DWS_GITEE_REPO` set, 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):**

```bash
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):**

```bash
curl -fsSL https://gitee.com/DingTalk-Real-AI/dingtalk-workspace-cli/raw/main/scripts/install-skills.sh | DWS_GITEE_REPO=DingTalk-Real-AI/dingtalk-workspace-cli sh
```

> With `DWS_GITEE_REPO` set, `install-skills.sh` resolves 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](#installation) to upgrade.

DWS 默认通过 npm Registry 查询发行版本。官方开源版独立二进制从同版本 GitHub Release 下载当前平台归档和技能包，校验后自更新，无需安装 npm；npm、pnpm 和 Homebrew 安装交由对应包管理器升级。详见[版本来源与升级方式](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/docs/upgrading.md)。

```bash
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
```

`dws upgrade` 默认选择正式渠道；`--beta` 选择 npm 的 beta 渠道。显式配置的 GitHub 升级来源仍使用原有协议。

`auth status`、`version` 和 `--version` 也会显示版本检查结果；未知命令或参数报错时，发现新版会提示升级。检查不会自动安装软件，详见[版本检查与升级提示](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/docs/version-checks.md)。

### 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`:

```bash
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`](verify/README.md) for the platform matrix.

<details>
<summary><strong>How it works</strong></summary>

独立二进制的升级分为准备和替换两阶段：

1. **准备** — 官方开源版下载同版本的当前平台归档和技能包，强制验证大小、SHA256 及新二进制版本。自定义 Registry 保留完整 npm 包及完整性校验。验证失败时不替换现有安装。
2. **Apply** — only after all preparations succeed, the binary is replaced and skills are flattened into the canonical `~/.agents/skills` root. Agents classified by the pinned compatibility registry as supporting the universal root read it directly; other detected Agents receive links to the canonical copy, with a direct-copy fallback when links are unavailable. Older DWS-managed agent-specific copies are backed up and retired so the same Skill is not discovered twice.

独立二进制升级保留自动备份和 `dws upgrade --rollback`。包管理器安装的回退由对应管理器处理；npm/pnpm 安装不支持 `--skip-skills`，因为发布包的安装脚本会更新技能。Homebrew 捆绑技能，使用 `dws skill setup` 安装到 Agent 目录。

| 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 |

</details>

## Getting Started

```bash
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`.

<details>
<summary><strong>Organization hasn't enabled CLI access?</strong></summary>

1. After selecting your organization, click "Apply Now" to notify the admin
2. The admin receives a request card and can approve with one click
3. Once approved, re-run `dws auth login`

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i2/O1CN01wtsYuQ1CTbboVTlsD_!!6000000000082-2-tps-2696-1544.png" alt="Apply for Access" width="600">
</p>

</details>

<details>
<summary><strong>Admin: Enable CLI access for your organization</strong></summary>

Go to [Developer Platform](https://open-dev.dingtalk.com) → "CLI Access Management" → Enable.

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i4/O1CN01M8K7Wj1rZ0WikrZby_!!6000000005644-2-tps-2940-1596.png" alt="CLI Access Management" width="600">
</p>

</details>

<details>
<summary><strong>Custom App mode (CI/CD, ISV integration)</strong></summary>

For enterprise-managed scenarios, create your own DingTalk app:

1. [Open Platform Console](https://open-dev.dingtalk.com/fe/app#/corp/app) → Create App
2. Security Settings → Add redirect URLs: `http://127.0.0.1,https://login.dingtalk.com`
3. Publish the app
4. Login:

```bash
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.

</details>

<details>
<summary><strong>Multiple organizations (profiles)</strong></summary>

`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.

Business commands automatically check and refresh local OAuth credentials as needed;
there is no need to run `auth status` before each request. `auth status` retains
refresh, migration and repair behavior and may wait for the authentication lock.
For concurrent polling, use `dws auth status --readonly --format json` (optionally with
`--profile`): it reads a local snapshot without the authentication lock, network
validation, refresh, migration or credential writes. System Keychain reads can
still wait. Both modes use the same output fields. A read-only result with a
non-empty `reason` is inconclusive and must not be treated as a confirmed logout;
`local_state_requires_repair` signals migration/repair and `local_state_unreadable`
signals a failed local read. Use `auth status` without `--readonly` with the same
profile for refresh or repair. Read-only mode never reports a refresh and may
report an expired access token that normal mode would refresh.
Concurrent updates may yield an older snapshot or an inconclusive result.
In both modes, `authenticated` is true when either the access or refresh
credential is valid; `token_valid` separately describes access-token usability.

```bash
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, transient or unclassified Keychain read failures block a new OAuth login rather than risking a mixed Keychain/file-DEK state. A confirmed missing DEK or ciphertext/DEK mismatch is preserved through authorization and replaced only for the slots targeted by the fresh login. 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:

```bash
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.

</details>

<details>
<summary><strong>Migrate auth between Linux sandboxes</strong></summary>

Copying only `~/.dws/app.json` does not carry the refresh token; access tokens expire after ~2 hours. Use the official export/import flow:

```bash
# 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.

</details>

## Quick Start

```bash
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 whiteboard +query --node "<whiteboard-node-id>" --view summary --format json  # inspect a whiteboard
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 commands with descriptions and when-to-use guidance.

### Whiteboards

Use `dws whiteboard` for standalone whiteboards and whiteboards embedded in documents: query content, create / update with OpenNodes, preview changes with `+diff`, render local SVG previews, and manage personal / team templates or use public templates.

```bash
dws whiteboard --help
dws whiteboard render --source @whiteboard.json --output ./whiteboard-preview.svg --format json
dws whiteboard +diff --help
dws whiteboard template personal list --format json
dws whiteboard template team list --template-workspace "<workspace-id>" --page-all --format json
dws whiteboard template public list --query "retrospective" --format json
```

`whiteboard.json` is an OpenNodes source file. SVG rendering is a local preview; review its fidelity and warnings, then confirm the current preview before creating the whiteboard. Use `+diff` to inspect the proposed update before `+update`. For template `save` / `create` commands, `--dry-run` performs an optional server-side preflight without saving a template or creating a whiteboard.

See the [whiteboard reference](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/skills/mono/references/products/whiteboard.md), [Diff guide](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/skills/mono/references/products/whiteboard/diff.md), and [SVG preview guide](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/blob/HEAD/skills/mono/references/products/whiteboard/render.md).

## Using with Agents

dws is designed as an AI-native CLI. Complete [Installation](#installation) and [Getting Started](#getting-started) first, then install Agent Skills:

```bash
npx skills add DingTalk-Real-AI/dingtalk-workspace-cli -g
```

The installer first shows the plan — which skills, which target directories, and how existing same-named skills will be handled — and asks for confirmation before writing anything. `dws skill setup` remains the power-user / China / upgrade path. See [Agent Skills](#agent-skills).

### Agent Invocation Patterns

```bash
# 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> --help` is the source of truth for whether a command exists and which flags the binary accepts.
- `dws schema "<path>" --compact` is the normative Agent view for command selection, CLI parameters and constraints, risk, and confirmation; use a full leaf with a narrow `--jq` projection 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.

```bash
# 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

```bash
npx skills add DingTalk-Real-AI/dingtalk-workspace-cli -g
```

This discovers `skills/multi/dingtalk-*/SKILL.md` (catalog layout, three levels under `skills/`, the depth `npx skills add` already walks) and installs `dingtalk-calendar`, `dingtalk-chat`, … into the agent directories [vercel-labs/skills](https://github.com/vercel-labs/skills) already knows: project `.agents/skills/` by default, user-global `.agents/skills` with `-g`, plus links into `~/.cursor/skills`, `~/.claude/skills`, and the other registered homes.

Without `-y`, the installer lists the skills, target directories, and existing same-named content handling for confirmation before it writes. Keep that prompt for interactive and first-time global installs. `-y` exists for automation only: add it after the user has explicitly confirmed the install targets and overwrite behavior — this path does not maintain `dws skill setup`'s ownership, backup, or mono↔multi mutual-exclusion cleanup state, so a skipped prompt can replace user files without consent.

The all-in-one mono skill (`skills/mono`, frontmatter name `dws`) is marked `metadata.internal: true`, so it is **not** a default installable skill. Agents therefore do not double-route between `dws` and the per-product skills.

`dws skill setup` remains the power-user / China / upgrade path. It still owns Gitee fallback, upgrade-time skill refresh, ownership in `~/.dws/skills-state.json`, and mono↔multi mutual-exclusion cleanup.

The repo ships two source trees:

- `skills/multi/` — per-product skills (`dingtalk-aitable/`, `dingtalk-calendar/`, `dingtalk-chat/`, ...), each with its own `SKILL.md`. Default for both `npx skills add` and `dws skill setup`.
- `skills/mono/` — single-skill layout (one `SKILL.md` + `references/products/`), legacy. Hidden from `npx skills add`. Still installed by `dws skill setup --mode mono` and the curl / zip installers.

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.

> China users: `npx skills add` clones from GitHub. Prefer `dws skill setup` or prefix `DWS_GITEE_REPO` on `install-skills.sh` — see [China mirror](#china-mirror).

**Power-user / China / upgrade: `dws skill setup`**

```bash
# 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 (an extracted dws-skills.zip root, its multi/ directory, or a source checkout), preview first
DWS_SKILL_SOURCE=/absolute/path/to/extracted-dws-skills dws skill setup --mode multi --dry-run
DWS_SKILL_SOURCE=/absolute/path/to/extracted-dws-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); accepts a mode directory, an extracted `dws-skills.zip` root, or a source checkout containing `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. A `dingtalk-*` 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-run` and 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) |

<details>
<summary><strong>Ready-made scripts</strong> — 13 Python scripts for common multi-step workflows</summary>

| 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 |

</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

<details>
<summary><strong>Personal Event Subscription</strong> — real-time DingTalk messages for event-driven agents</summary>

`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 28-event public catalog covers scoped and all one-to-one/group messages, specified senders, read/recall/reaction events, group lifecycle events, seven OA approval task/instance events, one VoIP invitation event, three Todo task lifecycle events, and the interactive-card callback event.

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:

```bash
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
```

```bash
# 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
dws event list --category card
dws event schema user_card_action_triggered --flatten
dws event list --category todo
dws event schema user_todo_task_create --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 seven 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_cc \
  user_oa_approval_instance_terminated \
  user_oa_approval_instance_finished \
  --flatten -f ndjson

# Listen for Todo create/update/delete events where the current user is an executor
dws event consume \
  user_todo_task_create \
  user_todo_task_update \
  user_todo_task_delete \
  --role-types executor \
  --flatten -f ndjson

# Listen for interactive-card callbacks; the schema documents reviewed fields while preserving extensions
dws event consume user_card_action_triggered --flatten -f ndjson

# Inspect local consumers and cancel a subscription
dws event status
dws event stop <subscribe_id>
```

The structured interactive-card context is at `payload.body.actionData.context`. Join `questions[].id` to `answers[question_id]`, then resolve IDs in `selected` through the same question's `options[].id`; an empty `selected` is a valid no-selection state. JSON strings in `body.context` are compatibility fallbacks, and every payload level continues to preserve unknown fields.

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.

</details>

<details>
<summary><strong>Raw API Access</strong> — call App Token-capable server-side DingTalk OpenAPIs directly</summary>

`dws api` lets you call server-side DingTalk OpenAPIs that support an internal-app App Token, without an SDK. Tokens are automatically acquired and refreshed.

> **Prerequisite**: Provide one complete custom-app Client ID/Client Secret pair through one-shot flags, environment variables, or app config saved by a successful login (see [Custom App mode](#getting-started)). MCP default-credential login alone does not support Raw API calls.

Client ID and Client Secret are resolved only as one complete pair, in this order: complete `--client-id/--client-secret` > complete `DWS_CLIENT_ID/DWS_CLIENT_SECRET` > complete app config. A half-configured source fails explicitly and is never combined with another source. Flags/env used directly by `dws api` are one-shot and do not persist the App Secret; flags/env used by a successful `dws auth login` are persisted as that exact pair for OAuth refresh and later Raw API calls. The acquired App Token is cached under `app-token:<clientID>`; the hidden `--token` accepts a temporary caller-supplied App Token and neither persists nor refreshes it.

Client Secrets use the canonical Keychain slot `appsecret:<clientID>`, distinct from OAuth User Tokens and App Tokens. Existing plaintext app config and historical `client-secret:<clientID>` entries are migrated automatically. If the old and new slots disagree, DWS fails closed and asks for a new login instead of guessing.

```bash
# Login (first time only)
dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>

# Or use one environment pair; a complete env pair overrides app config atomically
export DWS_CLIENT_ID=<APP_KEY>
export DWS_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 --dry-run             # preview request
dws api GET /v1.0/microApp/allApps --jq '.appList | length'  # jq filtering

# Read a JSON body from a file (--params also accepts @file; use - for stdin)
dws api POST https://oapi.dingtalk.com/topapi/v2/department/listsubid \
  --data @department-request.json --dry-run

# Stream one multipart file; top-level --data fields become text form fields; review a dry-run first
dws api POST https://oapi.dingtalk.com/media/upload \
```

> README 较长，以上为前 40000 个字符。完整内容：https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli
