| name | dingtalk-workspace-cli |
| description | DingTalk Workspace CLI (dws) — cross-platform tool for managing DingTalk enterprise data (contacts, calendars, docs, todos, AI tables, chat) via command line and AI agents |
| triggers | ["search DingTalk contacts or users","create a DingTalk calendar event or meeting","query DingTalk AI table records","search DingTalk documents","create or list DingTalk todos","send a DingTalk message","manage DingTalk drive files","get DingTalk attendance records"] |
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
dws upgrade --check
dws upgrade --version v1.0.7
dws upgrade --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
-f, --format table|json|raw
--jq '.result[0].name'
--dry-run
--yes, -y
--debug
Key Commands & Patterns
Contact Management
Search users:
dws contact user search --query "engineering"
dws contact user search --query "zhang" -f json --jq '.result[] | {name: .name, userId: .userId, mobile: .mobile}'
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:
dws contact dept list
dws contact dept list --dept-id "DEPT_ID"
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:
dws calendar event list
dws calendar event list --start-time "2026-05-20T00:00:00+08:00" --end-time "2026-05-21T00:00:00+08:00"
dws calendar event list --jq '.result[] | {title: .summary, start: .start.dateTime, attendees: [.attendees[].displayName]}'
Create 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
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:
dws aitable record query --base-id "BASE_ID" --table-id "TABLE_ID"
dws aitable record query \
--base-id "BASE_ID" \
--table-id "TABLE_ID" \
--filter-by-formula '{Status}="Done"' \
--limit 10
dws aitable record query \
--base-id "BASE_ID" \
--table-id "TABLE_ID" \
--sort '[{"field":"CreatedTime","order":"desc"}]' \
--page-size 20
dws aitable record query \
--base-id "BASE_ID" \
--table-id "TABLE_ID" \
--jq '.result.records[] | {id: .recordId, fields: .fields}'
Create records:
dws aitable record create \
--base-id "BASE_ID" \
--table-id "TABLE_ID" \
--records '[{"fields":{"Name":"Test Item","Status":"In Progress","Priority":1}}]' \
--yes
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:
dws todo task list
dws todo task list --is-done false --jq '.result[] | {id: .taskId, title: .subject, due: .dueTime}'
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
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
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:
dws doc search --query "quarterly report"
dws doc search --query "roadmap" --doc-type "doc" --jq '.result[] | {title: .title, url: .url, author: .creator.name}'
dws doc search --query "API" --max-results 50
Chat & IM
Send message:
dws chat send \
--receiver-id "USER_ID" \
--msg-type "text" \
--content "Hello from dws!" \
--yes
dws chat send \
--receiver-id "GROUP_ID" \
--msg-type "text" \
--content "Team update" \
--yes
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:
dws drive list
dws drive list --space-id "SPACE_ID"
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:
dws attendance record get
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:
dws report list inbox --start-time 1715990400000 --end-time 1716076799000
dws report list sent --start-time 1715990400000 --end-time 1716076799000
dws report list mine --start-time 1715990400000 --end-time 1716076799000
AI Meeting Minutes
List minutes:
dws minutes list
dws minutes list mine
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:
dws schema aitable.query_records --jq '.tool.parameters'
dws schema --jq '.products[] | select(.id=="aitable") | .tools[] | {name: .name, description: .description}'
Example discovery workflow:
dws schema --jq '.products[] | {id, description}'
dws schema --jq '.products[] | select(.id=="calendar") | .tools[] | .name'
dws schema calendar.create_event --jq '.tool.parameters.properties | keys'
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
dws todo task create --title "Review PR" --executors "USER_ID"
dws todo task create --title "Review PR" --executors "USER_ID" --yes
Use --dry-run for Safety
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
dws contact user search --query "zhang" -f json
dws contact user search --query "zhang" -f json --jq '.result[] | {name: .name, userId: .userId}'
Handle Pagination
dws aitable record query --base-id "BASE_ID" --table-id "TABLE_ID" --page-size 100
dws calendar event list --start-time "2026-05-01T00:00:00+08:00" --end-time "2026-06-01T00:00:00+08:00"
Error Handling
dws contact user search --query "nonexistent"
echo $?
dws todo task create --title "Test" --executors "INVALID_ID" --yes 2>&1 | grep "error"
Common Patterns
Multi-Step Workflows
Schedule meeting with available room:
ATTENDEES=$(dws contact user search --query "engineering" -f json --jq '[.result[].userId] | join(",")')
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:
cat > records.json <<EOF
[
{"fields": {"Name": "Item 1", "Status": "Todo", "Priority": 1}},
{"fields": {"Name": "Item 2", "Status": "In Progress", "Priority": 2}}
]
EOF
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:
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(",")')
dws aitable record delete \
--base-id "$BASE_ID" \
--table-id "$TABLE_ID" \
--record-ids "$RECORD_IDS" \
--yes
Send notification to department:
USER_IDS=$(dws contact dept members --dept-id "$DEPT_ID" -f json --jq '[.result[].userId]')
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:
dws auth login
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
dws auth status -f json --jq '.result | {userId: .userId, corpId: .corpId, scopes: .scopes}'
dws auth login --scope "Contact.User.Read,Calendar.Event.Write"
Rate Limiting
for i in {1..100}; do
dws todo task create --title "Task $i" --executors "USER_ID" --yes
sleep 0.5
done
Debug Mode
dws --debug contact user search --query "test"
dws contact user search --query "test" --dry-run
Invalid Timestamps
DingTalk expects RFC3339 format with timezone:
--start-time "2026-05-20T14:00:00+08:00"
--start-time "2026-05-20 14:00:00"
--start-time "1716192000"
For attendance, use date format:
--start-date "2026-05-20"
--start-date "2026-05-20T00:00:00+08:00"
AITable filterByFormula Syntax
--filter-by-formula '{Status}="Done"'
--filter-by-formula 'AND({Priority}>5, {Status}="In Progress")'
--filter-by-formula {"Status":"Done"}
Empty Results
dws aitable record query --base-id "$BASE_ID" --table-id "$TABLE_ID" --limit 1
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
set -e
MY_USER_ID=$(dws contact user get-self -f json --jq '.result[0].orgEmployeeModel.userId' | tr -d '"')
ATTENDEES=$(dws contact user search --query "engineering" -f json --jq '[.result[].userId] | join(",")')
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/')
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"
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: " \
--