A command-line interface for Dida365 (滴答清单 / TickTick) task management, designed for automation and scripting workflows.
- Task Management: Full CRUD operations for tasks (create, read, update, delete, complete)
- Kanban Support: List columns per project and move tasks between columns
- Project Access: List and view projects
- JSON Output: All commands output JSON for easy parsing with
jqor other tools - Scripting-Friendly: Clear exit codes and structured error messages
- Secure: Config file stored with user-only permissions
git clone https://github.com/bearzk/dida365-cli.git
cd dida365-cli
go build -o dida365 .
sudo mv dida365 /usr/local/bin/go install github.com/bearzk/dida365-cli@latest- Visit the Dida365 Developer Portal (or TickTick Developer Portal)
- Register an application to get
client_idandclient_secret - Set redirect URL to:
http://localhost:8080/callback
The CLI handles the complete OAuth2 flow automatically:
# For Dida365 (China)
dida365 auth login \
--client-id "your_client_id" \
--client-secret "your_client_secret" \
--service dida365
# For TickTick (International)
dida365 auth login \
--client-id "your_client_id" \
--client-secret "your_client_secret" \
--service ticktick
# Use custom port if 8080 is occupied
dida365 auth login \
--client-id "your_client_id" \
--client-secret "your_client_secret" \
--service dida365 \
--port 9000The CLI will:
- Start a local callback server
- Open your browser to the authorization page
- Handle the callback and exchange the code for tokens
- Save credentials securely to
~/.dida365/config.json
dida365 auth statusList all projects:
dida365 project listGet project details:
dida365 project get <project-id>List Kanban columns in a project:
dida365 project columns <project-id>Get full project data (tasks, columns):
dida365 project data <project-id>Create a task:
dida365 task create \
--title "Deploy to production" \
--project-id "proj123" \
--content "Deploy v2.0.0" \
--due-date "2026-04-30" \
--start-date "2026-05-01"List tasks in a project:
dida365 task list proj123Get task details:
dida365 task get task456 --project-id proj123Update a task:
dida365 task update task456 \
--project-id proj123 \
--title "New title" \
--content "Updated content" \
--due-date "2026-05-01 18:30" \
--start-date "2026-05-01 18:30"Complete a task:
dida365 task complete task456 --project-id proj123Delete a task:
dida365 task delete task456 --project-id proj123Move a task to a different Kanban column:
# First, find the column IDs
dida365 project columns <project-id>
# Then move the task
dida365 task move task456 --project-id proj123 --column-id <column-id>Accepted --start-date and --due-date formats:
# All-day (date only)
dida365 task create --title "Monthly summary" --project-id "proj123" \
--start-date "2026-04-28" --due-date "2026-04-30"
# Specific local time (space separator)
dida365 task create --title "Release window" --project-id "proj123" \
--start-date "2026-04-30 10:00" --due-date "2026-04-30 18:30"
# Specific local time (T separator)
dida365 task create --title "Release window" --project-id "proj123" \
--start-date "2026-04-30T10:00" --due-date "2026-04-30T18:30"
# RFC3339 with timezone
dida365 task update task456 --project-id proj123 \
--start-date "2026-04-30T09:00:00+08:00" --due-date "2026-04-30T23:59:59+08:00"# Get task ID from created task
TASK_ID=$(dida365 task create --title "Test" --project-id "proj123" | jq -r .id)
# Get all project names
dida365 project list | jq '.[].name'
# Filter incomplete tasks
dida365 task list proj123 | jq 'map(select(.status == 0))'#!/bin/bash
set -e
# Check auth status
if ! dida365 auth status >/dev/null 2>&1; then
echo "Error: Not authenticated. Run 'dida365 auth login' to authenticate."
exit 1
fi
# Create task
TASK_JSON=$(dida365 task create --title "Automated task" --project-id "proj123")
if [ $? -ne 0 ]; then
echo "Failed to create task"
exit 1
fi
echo "Task created successfully"
echo "$TASK_JSON" | jq .# GitHub Actions example
- name: Create deployment task
run: |
dida365 task create \
--title "Deployment to ${{ github.ref_name }}" \
--project-id "${{ secrets.DIDA365_PROJECT_ID }}" \
--content "SHA: ${{ github.sha }}"
env:
DIDA365_CONFIG: ${{ secrets.DIDA365_CONFIG }}0- Success1- Configuration error (missing or invalid config)2- Authentication error (invalid token, OAuth2 flow failed)3- API error (resource not found, bad request, server error)4- Network error (connection timeout, DNS failure)5- Validation error (invalid arguments, missing flags)6- OAuth2 server error (callback server failed to start)
Commands output the resource or array directly:
{
"id": "task123",
"projectId": "proj456",
"title": "Task title",
"dueDate": "2026-04-30T15:59:59Z",
"isAllDay": false,
"status": 0,
"sortOrder": 1
}Errors are output to stderr:
{
"error": "human-readable message",
"code": "ERROR_CODE"
}Location: ~/.dida365/config.json
{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"access_token": "your_access_token",
"token_expiry": "2026-08-21T14:30:00Z",
"base_url": "https://dida365.com"
}Token Management:
- Access tokens are long-lived (~6 months)
- Re-authenticate with
dida365 auth loginwhen your token expires
Security: The config file is created with 0600 permissions (user read/write only).
# All tests
go test ./...
# With coverage
go test -cover ./...
# Specific package
go test ./internal/client -vgo build -o dida365 .dida365-cli/
├── cmd/ # Cobra commands
├── internal/
│ ├── client/ # API client
│ ├── config/ # Config management
│ └── models/ # Data models
├── main.go # Entry point
└── README.md
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
go test ./... - Submit a pull request
MIT License - see LICENSE file for details