SuperAPI AI Directory

dingtalk-workspace-cli

Office & productivity

View repository

Updated 10-15 22:33

About this skill

DingTalk Workspace CLI (dws)

Skill by ara.so — Devtools Skills collection.

DingTalk Workspace CLI (dws) is an officially open-sourced cross-platform CLI tool from DingTalk that unifies DingTalk's full suite of product capabilities (contacts, calendars, documents, todos, AI tables, chat, drive, attendance, reports, meetings) into a single package. It's designed for both human users and AI agent scenarios, with structured JSON responses, OAuth device-flow authentication, and enterprise-grade security.

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

npm (requires Node.js):

npm install -g dingtalk-workspace-cli

Build from source (Go 1.25+):

git clone https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
cd dingtalk-workspace-cli
go build -o dws ./cmd
cp dws ~/.local/bin/

Upgrade (requires v1.0.7+):

dws upgrade                    # interactive upgrade
dws upgrade --check            # check without installing
dws upgrade --version v1.0.7   # specific version
dws upgrade --rollback         # rollback

Authentication

Interactive login (browser):

dws auth login

Device flow (headless environments):

dws auth login --device

Custom app (CI/CD, ISV integration):

dws auth login --client-id $DINGTALK_CLIENT_ID --client-secret $DINGTALK_CLIENT_SECRET

Check auth status:

dws auth status

Logout:

dws auth logout

Core Architecture

Product Services

DingTalk Workspace is organized into product services:

Service Commands Use Cases
contact user, dept, org Search users, list departments, get org info
calendar event Create/list/update/delete calendar events
aitable space, base, table, record, field, view Query/create AI table records, manage schema
doc search, create Search DingTalk Docs, create documents
todo task, category Create/list/update todos, manage categories
chat send, list Send messages, list conversations
drive list, upload, download Manage DingTalk drive files
attendance record, shift Get attendance records, query shifts
report list List reports (inbox/sent/created)
minutes list, detail Get AI meeting minutes
im group, message Manage IM groups, send messages

Global Flags

# Output formats
-f, --format table|json|raw    # table (human), json (structured), raw (API response)

# JQ filtering (extract specific fields)
--jq '.result[0].name'         # save tokens, extract precisely

# Dry run (preview without executing)
--dry-run                      # see request payload before sending

# Skip confirmation (required for AI agents)
--yes, -y                      # auto-confirm destructive operations

# Debug
--debug                        # verbose logging

Key Commands & Patterns

Contact Management

Search users:

# Basic search
dws contact user search --query "engineering"

# With filtering and formatting
dws contact user search --query "zhang" -f json --jq '.result[] | {name: .name, userId: .userId, mobile: .mobile}'

# Dry run
dws contact user search --query "zhang" --dry-run

Get current user:

dws contact user get-self -f json --jq '.result[0].orgEmployeeModel | {name: .orgUserName, userId: .userId, depts: [.depts[].deptName]}'

Get user by ID:

dws contact user get --user-id "USER_ID"

List departments:

# Root departments
dws contact dept list

# Subdepartments
dws contact dept list --dept-id "DEPT_ID"

# Extract specific fields
dws contact dept list --jq '.result[] | {id: .deptId, name: .name}'

List department members:

dws contact dept members --dept-id "DEPT_ID" -f json

Calendar Events

List events:

# Today's events
dws calendar event list

# Date range
dws calendar event list --start-time "2026-05-20T00:00:00+08:00" --end-time "2026-05-21T00:00:00+08:00"

# Extract fields
dws calendar event list --jq '.result[] | {title: .summary, start: .start.dateTime, attendees: [.attendees[].displayName]}'

Create event:

# Basic event
dws calendar event create \
  --summary "Team Sync" \
  --start-time "2026-05-20T14:00:00+08:00" \
  --end-time "2026-05-20T15:00:00+08:00" \
  --yes

# With attendees
dws calendar event create \
  --summary "Quarterly Review" \
  --start-time "2026-05-25T10:00:00+08:00" \
  --end-time "2026-05-25T11:30:00+08:00" \
  --attendees "USER_ID_1,USER_ID_2" \
  --location "Meeting Room A" \
  --description "Q2 business review" \
  --yes

Update event:

dws calendar event update \
  --event-id "EVENT_ID" \
  --summary "Updated Title" \
  --start-time "2026-05-20T15:00:00+08:00" \
  --end-time "2026-05-20T16:00:00+08:00" \
  --yes

Delete event:

dws calendar event delete --event-id "EVENT_ID" --yes

AI Table (AITable)

List spaces:

dws aitable space list -f json --jq '.result[] | {id: .spaceId, name: .name}'

List bases in space:

dws aitable base list --space-id "SPACE_ID"

List tables in base:

dws aitable table list --base-id "BASE_ID"

Query records:

# All records
dws aitable record query --base-id "BASE_ID" --table-id "TABLE_ID"

# With filters (filterByFormula)
dws aitable record query \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --filter-by-formula '{Status}="Done"' \
  --limit 10

# Sort and paginate
dws aitable record query \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --sort '[{"field":"CreatedTime","order":"desc"}]' \
  --page-size 20

# Extract specific fields
dws aitable record query \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --jq '.result.records[] | {id: .recordId, fields: .fields}'

Create records:

# Single record
dws aitable record create \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --records '[{"fields":{"Name":"Test Item","Status":"In Progress","Priority":1}}]' \
  --yes

# Multiple records
dws aitable record create \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --records '[
    {"fields":{"Name":"Item 1","Status":"Todo"}},
    {"fields":{"Name":"Item 2","Status":"Done"}}
  ]' \
  --yes

Update records:

dws aitable record update \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --records '[{"recordId":"RECORD_ID","fields":{"Status":"Done"}}]' \
  --yes

Delete records:

dws aitable record delete \
  --base-id "BASE_ID" \
  --table-id "TABLE_ID" \
  --record-ids "RECORD_ID_1,RECORD_ID_2" \
  --yes

List fields (schema):

dws aitable field list --base-id "BASE_ID" --table-id "TABLE_ID"

Todo Management

List todos:

# All incomplete todos
dws todo task list

# With filters
dws todo task list --is-done false --jq '.result[] | {id: .taskId, title: .subject, due: .dueTime}'

# Specific category
dws todo task list --category-id "CATEGORY_ID"

Create todo:

dws todo task create \
  --title "Review PR #123" \
  --executors "USER_ID" \
  --due-time "2026-05-22T18:00:00+08:00" \
  --priority 10 \
  --yes

# With description and multiple executors
dws todo task create \
  --title "Quarterly Report" \
  --description "Complete Q2 summary" \
  --executors "USER_ID_1,USER_ID_2" \
  --due-time "2026-05-30T23:59:59+08:00" \
  --yes

Update todo:

dws todo task update \
  --task-id "TASK_ID" \
  --is-done true \
  --yes

# Update fields
dws todo task update \
  --task-id "TASK_ID" \
  --title "Updated Title" \
  --priority 20 \
  --yes

Delete todo:

dws todo task delete --task-id "TASK_ID" --yes

List categories:

dws todo category list -f json

Document Search

Search documents:

# Basic search
dws doc search --query "quarterly report"

# With type filter
dws doc search --query "roadmap" --doc-type "doc" --jq '.result[] | {title: .title, url: .url, author: .creator.name}'

# Pagination
dws doc search --query "API" --max-results 50

Chat & IM

Send message:

# To user
dws chat send \
  --receiver-id "USER_ID" \
  --msg-type "text" \
  --content "Hello from dws!" \
  --yes

# To group
dws chat send \
  --receiver-id "GROUP_ID" \
  --msg-type "text" \
  --content "Team update" \
  --yes

# Markdown message
dws chat send \
  --receiver-id "USER_ID" \
  --msg-type "markdown" \
  --content "**Important**: Please review [this doc](https://example.com)" \
  --yes

List conversations:

dws chat list -f json --jq '.result[] | {id: .openConversationId, title: .title, type: .conversationType}'

List IM groups:

dws im group list --jq '.result[] | {id: .openConversationId, name: .name, memberCount: .memberCount}'

Drive

List files:

# Root directory
dws drive list

# Specific space
dws drive list --space-id "SPACE_ID"

# Extract fields
dws drive list --jq '.result[] | {name: .name, id: .fileId, size: .size, type: .type}'

Upload file:

dws drive upload \
  --space-id "SPACE_ID" \
  --parent-id "PARENT_ID" \
  --file-path "/path/to/file.pdf" \
  --yes

Download file:

dws drive download \
  --space-id "SPACE_ID" \
  --file-id "FILE_ID" \
  --output-path "/path/to/save/file.pdf"

Attendance

Get attendance records:

# Today's records
dws attendance record get

# Specific date range
dws attendance record get \
  --start-date "2026-05-01" \
  --end-date "2026-05-07" \
  -f json --jq '.result[] | {date: .workDate, checkIn: .checkInTime, checkOut: .checkOutTime}'

Query shift schedules:

dws attendance shift query \
  --user-ids "USER_ID" \
  --start-date "2026-05-20" \
  --end-date "2026-05-27"

Reports

List reports:

# Inbox (received)
dws report list inbox --start-time 1715990400000 --end-time 1716076799000

# Sent
dws report list sent --start-time 1715990400000 --end-time 1716076799000

# Created by me
dws report list mine --start-time 1715990400000 --end-time 1716076799000

AI Meeting Minutes

List minutes:

# All minutes
dws minutes list

# Mine
dws minutes list mine

# Extract fields
dws minutes list --jq '.result[] | {id: .minutesId, title: .title, date: .meetingStartTime}'

Get minutes detail:

dws minutes detail --minutes-id "MINUTES_ID" -f json

Schema Discovery (AI Agents)

AI agents don't need pre-built knowledge. Use dws schema to dynamically discover capabilities:

List all products and tool counts:

dws schema --jq '.products[] | {id, tool_count: (.tools | length)}'

Inspect specific tool parameters:

# Get parameter schema for aitable.query_records
dws schema aitable.query_records --jq '.tool.parameters'

# Get all aitable tools
dws schema --jq '.products[] | select(.id=="aitable") | .tools[] | {name: .name, description: .description}'

Example discovery workflow:

# Step 1: Find the right product
dws schema --jq '.products[] | {id, description}'

# Step 2: List tools in that product
dws schema --jq '.products[] | select(.id=="calendar") | .tools[] | .name'

# Step 3: Get parameter schema
dws schema calendar.create_event --jq '.tool.parameters.properties | keys'

# Step 4: Execute
dws calendar event create --summary "Team Sync" --start-time "2026-05-20T14:00:00+08:00" --end-time "2026-05-20T15:00:00+08:00" --yes

Configuration

Config file location:

  • macOS/Linux: ~/.config/dws/config.yaml
  • Windows: %APPDATA%\dws\config.yaml

Example config.yaml:

auth:
  client_id: "your-app-key"
  client_secret: "your-app-secret"
output:
  default_format: "json"
  color: true
debug: false

Override via environment variables:

export DWS_OUTPUT_FORMAT=json
export DWS_DEBUG=true
export DINGTALK_CLIENT_ID=your-app-key
export DINGTALK_CLIENT_SECRET=your-app-secret

AI Agent Best Practices

Always Use --yes for Non-Interactive Execution

# ❌ Bad (will hang waiting for confirmation)
dws todo task create --title "Review PR" --executors "USER_ID"

# ✅ Good
dws todo task create --title "Review PR" --executors "USER_ID" --yes

Use --dry-run for Safety

# Preview before executing
dws contact user search --query "engineering" --dry-run
dws aitable record delete --base-id "BASE_ID" --table-id "TABLE_ID" --record-ids "REC_ID" --dry-run

Use --jq to Save Tokens

# ❌ Returns entire API response (1000+ tokens)
dws contact user search --query "zhang" -f json

# ✅ Extract only needed fields (50 tokens)
dws contact user search --query "zhang" -f json --jq '.result[] | {name: .name, userId: .userId}'

Handle Pagination

# AITable pagination
dws aitable record query --base-id "BASE_ID" --table-id "TABLE_ID" --page-size 100

# Calendar pagination (date ranges)
dws calendar event list --start-time "2026-05-01T00:00:00+08:00" --end-time "2026-06-01T00:00:00+08:00"

Error Handling

# Check exit code
dws contact user search --query "nonexistent"
echo $?  # 0 = success, non-zero = error

# Capture stderr
dws todo task create --title "Test" --executors "INVALID_ID" --yes 2>&1 | grep "error"

Common Patterns

Multi-Step Workflows

Schedule meeting with available room:

# 1. Search attendees
ATTENDEES=$(dws contact user search --query "engineering" -f json --jq '[.result[].userId] | join(",")')

# 2. Create event
EVENT_ID=$(dws calendar event create \
  --summary "Team Sync" \
  --start-time "2026-05-20T14:00:00+08:00" \
  --end-time "2026-05-20T15:00:00+08:00" \
  --attendees "$ATTENDEES" \
  --yes \
  -f json --jq '.result.eventId')

echo "Created event: $EVENT_ID"

Batch import to AITable from JSON:

# Prepare records.json
cat > records.json <<EOF
[
  {"fields": {"Name": "Item 1", "Status": "Todo", "Priority": 1}},
  {"fields": {"Name": "Item 2", "Status": "In Progress", "Priority": 2}}
]
EOF

# Import
dws aitable record create \
  --base-id "$BASE_ID" \
  --table-id "$TABLE_ID" \
  --records "$(cat records.json)" \
  --yes

Find overdue todos:

dws todo task list --is-done false -f json | \
  jq --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
    '.result[] | select(.dueTime != null and .dueTime < $now) | {id: .taskId, title: .subject, due: .dueTime}'

Batch Operations

Delete multiple AITable records:

# Get record IDs to delete
RECORD_IDS=$(dws aitable record query \
  --base-id "$BASE_ID" \
  --table-id "$TABLE_ID" \
  --filter-by-formula '{Status}="Archived"' \
  -f json --jq '[.result.records[].recordId] | join(",")')

# Delete
dws aitable record delete \
  --base-id "$BASE_ID" \
  --table-id "$TABLE_ID" \
  --record-ids "$RECORD_IDS" \
  --yes

Send notification to department:

# Get department members
USER_IDS=$(dws contact dept members --dept-id "$DEPT_ID" -f json --jq '[.result[].userId]')

# Send to each
echo "$USER_IDS" | jq -r '.[]' | while read user_id; do
  dws chat send --receiver-id "$user_id" --msg-type "text" --content "Reminder: Submit timesheet" --yes
done

Troubleshooting

Authentication Issues

Token expired:

# Re-login
dws auth login

# Or use device flow
dws auth login --device

Organization not enabled:

  • Join the DingTalk co-creation group: scan QR in README
  • Ask admin to enable CLI access in Developer Platform

Permission Errors

# Check current auth status and scopes
dws auth status -f json --jq '.result | {userId: .userId, corpId: .corpId, scopes: .scopes}'

# Request required scopes during login
dws auth login --scope "Contact.User.Read,Calendar.Event.Write"

Rate Limiting

# Add delays between bulk operations
for i in {1..100}; do
  dws todo task create --title "Task $i" --executors "USER_ID" --yes
  sleep 0.5
done

Debug Mode

# Enable verbose logging
dws --debug contact user search --query "test"

# Check request/response
dws contact user search --query "test" --dry-run

Invalid Timestamps

DingTalk expects RFC3339 format with timezone:

# ✅ Correct
--start-time "2026-05-20T14:00:00+08:00"

# ❌ Incorrect
--start-time "2026-05-20 14:00:00"
--start-time "1716192000"  # Unix timestamp not supported in event create

For attendance, use date format:

# ✅ Correct
--start-date "2026-05-20"

# ❌ Incorrect
--start-date "2026-05-20T00:00:00+08:00"

AITable filterByFormula Syntax

# ✅ Correct (JSON string escaping)
--filter-by-formula '{Status}="Done"'
--filter-by-formula 'AND({Priority}>5, {Status}="In Progress")'

# ❌ Incorrect (unescaped quotes)
--filter-by-formula {"Status":"Done"}

Empty Results

# Check if data exists
dws aitable record query --base-id "$BASE_ID" --table-id "$TABLE_ID" --limit 1

# Verify IDs are correct
dws aitable space list
dws aitable base list --space-id "$SPACE_ID"
dws aitable table list --base-id "$BASE_ID"

Reference

Security Notes

  • Credentials stored in system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
  • OAuth device-flow for headless environments
  • Zero-trust: every request requires authentication
  • Domain allowlisting and least-privilege scoping enforced server-side
  • Use environment variables for CI/CD: $DINGTALK_CLIENT_ID, $DINGTALK_CLIENT_SECRET

Example: Complete Workflow (AI Agent)

#!/bin/bash
# AI agent script: Create weekly team sync event with engineering team

set -e

# 1. Get current user
MY_USER_ID=$(dws contact user get-self -f json --jq '.result[0].orgEmployeeModel.userId' | tr -d '"')

# 2. Search engineering team
ATTENDEES=$(dws contact user search --query "engineering" -f json --jq '[.result[].userId] | join(",")')

# 3. Find next Monday 2pm
NEXT_MONDAY=$(date -d "next monday 14:00" -Iseconds --utc | sed 's/+00:00/+08:00/')
END_TIME=$(date -d "next monday 15:00" -Iseconds --utc | sed 's/+00:00/+08:00/')

# 4. Create event
EVENT_ID=$(dws calendar event create \
  --summary "Weekly Team Sync" \
  --start-time "$NEXT_MONDAY" \
  --end-time "$END_TIME" \
  --attendees "$ATTENDEES" \
  --location "Meeting Room A" \
  --description "Weekly engineering team sync" \
  --yes \
  -f json --jq '.result.eventId' | tr -d '"')

echo "✅ Created event $EVENT_ID for $NEXT_MONDAY"

# 5. Create reminder todo
dws todo task create \
  --title "Prepare agenda for team sync" \
  --executors "$MY_USER_ID" \
  --due-time "$(date -d "next monday 12:00" -Iseconds --utc | sed 's/+00:00/+08:00/')" \
  --description "Event ID: $EVENT_ID" \
  --yes

echo "✅ Created reminder todo"