Metadata-Version: 2.4
Name: dida365-sdk
Version: 0.1.0
Summary: Python SDK for the dida365 (TickTick) MCP server.
Project-URL: Homepage, https://github.com/shawn-bluce/dida365-sdk
Project-URL: Repository, https://github.com/shawn-bluce/dida365-sdk
Project-URL: Issues, https://github.com/shawn-bluce/dida365-sdk/issues
Project-URL: Changelog, https://github.com/shawn-bluce/dida365-sdk/blob/main/CHANGELOG.md
Author-email: Hao Zhang <hao.zhang@longbridge-inc.com>
License: MIT License
        
        Copyright (c) 2026 Shawn
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: dida365,mcp,sdk,tasks,ticktick,todo
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Scheduling
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: requests<3,>=2.28
Provides-Extra: async
Requires-Dist: httpx<1,>=0.27; extra == 'async'
Provides-Extra: dev
Requires-Dist: httpx<1,>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: responses>=0.25; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# dida365

[![PyPI version](https://img.shields.io/pypi/v/dida365-sdk.svg)](https://pypi.org/project/dida365-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/dida365-sdk.svg)](https://pypi.org/project/dida365-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

[滴答清单 / dida365](https://www.dida365.com)（TickTick）官方 [MCP 服务](https://help.dida365.com/articles/7438132116019216384) 的 Python SDK。把 32 个 MCP 工具包装成同名方法，同时提供同步与异步接口、完整类型标注、自动重试与凭据隔离。

A Python SDK for the official [dida365 / TickTick MCP server](https://help.dida365.com/articles/7438132116019216384). Wraps all 32 MCP tools as Python methods with sync + async clients, full type stubs, automatic retries, and safe credential handling.

---

## 中文文档

### 安装

```bash
pip install dida365-sdk            # 仅同步客户端
pip install "dida365-sdk[async]"   # 同时安装 httpx，启用 AsyncClient
```

> 安装名是 `dida365-sdk`，导入名仍是 `import dida365`。

### 获取 Token

打开 [滴答清单网页版](https://dida365.com)，进入 **头像 → 设置 → 账户与安全 → API 口令**，新建并复制 token。

设置环境变量（推荐）：

```bash
export DIDA365_TOKEN=dp_xxxxxxxxxxxxxxxxxxxxxxxxxx
```

或者在项目目录创建 `.env` 文件：

```dotenv
DIDA365_TOKEN=dp_xxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 快速开始

```python
import dida365

with dida365.Client() as client:
    # 1. 列出所有清单
    projects = client.list_projects()

    # 2. 创建任务
    task = client.create_task(task={
        "projectId": projects[0]["id"],
        "title": "明天提交周报",
        "priority": 5,                    # 0=无 1=低 3=中 5=高
        "startDate": "2026-05-09T09:00:00+08:00",
        "isAllDay": False,
        "timeZone": "Asia/Shanghai",
        "tags": ["work"],
    })

    # 3. 查询今日待办
    today = client.list_undone_tasks_by_time_query(query_command="today")

    # 4. 完成任务
    client.complete_task(project_id=task["projectId"], task_id=task["id"])
```

### 异步用法

```python
import asyncio
import dida365

async def main() -> None:
    async with dida365.AsyncClient() as client:
        projects, today = await asyncio.gather(
            client.list_projects(),
            client.list_undone_tasks_by_time_query(query_command="today"),
        )
        print(f"{len(projects)} projects, {len(today)} tasks today")

asyncio.run(main())
```

### 错误处理

```python
import dida365

try:
    client = dida365.Client(token="bad-token")
except dida365.AuthenticationError as e:
    print(f"token 失效: {e.code}")          # 401 / 403
except dida365.MCPProtocolError as e:
    print(f"网络/协议错误: {e}")
except dida365.ToolError as e:
    print(f"工具调用失败: {e.tool} -> {e}")
```

### 全部 32 个工具

| 类别 | 工具 |
| --- | --- |
| 清单 | `list_projects` `get_project_by_id` `get_project_with_undone_tasks` `create_project` `update_project` |
| 任务 | `create_task` `update_task` `get_task_in_project` `get_task_by_id` `complete_task` `complete_tasks_in_project` `move_task` `batch_add_tasks` `batch_update_tasks` |
| 查询 | `search` `search_task` `fetch` `filter_tasks` `list_undone_tasks_by_date` `list_undone_tasks_by_time_query` `list_completed_tasks_by_date` |
| 习惯 | `list_habits` `list_habit_sections` `get_habit` `create_habit` `update_habit` `upsert_habit_checkins` `get_habit_checkins` |
| 专注 | `get_focus` `get_focuses_by_time` `delete_focus` |
| 偏好 | `get_user_preference` |

每个工具的参数与返回类型已经在 `dida365/client.pyi` 中声明，IDE / mypy / pyright 都能直接获取补全。

### 高级配置

```python
client = dida365.Client(
    token="dp_xxx",                  # 显式 token（默认从 env 读取）
    url="https://mcp.dida365.com",   # MCP 服务地址
    timeout=(5.0, 30.0),             # (connect, read)
    max_retries=3,                   # 5xx / 429 / 网络错误的重试次数
)
```

`Client` 与 `AsyncClient` 都自动遵循 `Retry-After` 头部、自动透传 `Mcp-Session-Id` 与 `MCP-Protocol-Version` 头部。

### 安全约定

- `repr(client)` 不会泄露 token；只显示 `Client(url=...)`。
- 进程环境变量只接受 `DIDA365_TOKEN`，避免与其他工具的 `TOKEN` 冲突。
- `.env` 中可使用 `DIDA365_TOKEN` 或 `TOKEN`（仅在 `cwd/.env` 中生效）。

### 开发

本仓库使用 [uv](https://docs.astral.sh/uv/) 管理 Python 环境与依赖。

```bash
git clone https://github.com/shawn-bluce/dida365-sdk.git
cd dida365-sdk
uv sync --all-extras           # 创建 .venv 并安装 dev + async 依赖

# 单元测试（不联网）
uv run pytest

# 实测脚本（会创建并关闭一个沙箱清单）
DIDA365_TOKEN=dp_xxx uv run python tests/integration/test_smoke_live.py

# 重新生成类型存根
uv run python scripts/generate_stubs.py --refresh

# 打包与发布
uv build                       # 生成 dist/*.whl 与 dist/*.tar.gz
uv publish                     # 上传到 PyPI（需配置 token）
```

---

## English Documentation

### Install

```bash
pip install dida365-sdk            # sync client only
pip install "dida365-sdk[async]"   # also installs httpx for AsyncClient
```

> The distribution name is `dida365-sdk`; the import name remains `import dida365`.

### Get a token

In the dida365 web app, open **Profile → Settings → Account & Security → API Token**, create and copy a new token. Then either:

```bash
export DIDA365_TOKEN=dp_xxxxxxxxxxxxxxxxxxxxxxxxxx
```

…or drop a `.env` file at the project root with `DIDA365_TOKEN=...`.

### Quickstart

```python
import dida365

with dida365.Client() as client:
    projects = client.list_projects()

    task = client.create_task(task={
        "projectId": projects[0]["id"],
        "title": "Submit weekly report",
        "priority": 5,
        "startDate": "2026-05-09T09:00:00+08:00",
        "isAllDay": False,
        "timeZone": "Asia/Shanghai",
        "tags": ["work"],
    })

    today = client.list_undone_tasks_by_time_query(query_command="today")
    client.complete_task(project_id=task["projectId"], task_id=task["id"])
```

### Async usage

```python
import asyncio
import dida365

async def main() -> None:
    async with dida365.AsyncClient() as client:
        projects, today = await asyncio.gather(
            client.list_projects(),
            client.list_undone_tasks_by_time_query(query_command="today"),
        )
        print(f"{len(projects)} projects, {len(today)} tasks today")

asyncio.run(main())
```

### Error handling

```python
import dida365

try:
    client = dida365.Client(token="bad-token")
except dida365.AuthenticationError as e:
    ...                                  # HTTP 401 / 403
except dida365.MCPProtocolError as e:
    ...                                  # network / HTTP / JSON-RPC error
except dida365.ToolError as e:
    ...                                  # tool reported isError=true
```

### Tool catalogue

All 32 server-advertised tools are dispatched dynamically by name and statically typed via `client.pyi` / `async_client.pyi`. Categories: projects (5), tasks (9), querying (7), habits (7), focus (3), preferences (1).

### Advanced configuration

```python
client = dida365.Client(
    token="dp_xxx",                   # explicit token (defaults to env)
    url="https://mcp.dida365.com",    # MCP endpoint
    timeout=(5.0, 30.0),              # (connect, read)
    max_retries=3,                    # 5xx / 429 / network retries
)
```

Both `Client` and `AsyncClient` honor `Retry-After`, round-trip `Mcp-Session-Id`, and send `MCP-Protocol-Version` on every non-initialize request.

### Security defaults

- `repr(client)` masks the bearer token.
- Only `DIDA365_TOKEN` is read from process env (avoids collisions with `TOKEN` set by other tools).
- `.env` in the current working directory accepts `DIDA365_TOKEN` or `TOKEN`.

### Development

This repo uses [uv](https://docs.astral.sh/uv/) to manage the Python environment and dependencies.

```bash
git clone https://github.com/shawn-bluce/dida365-sdk.git
cd dida365-sdk
uv sync --all-extras           # creates .venv and installs dev + async extras

# Unit tests (offline)
uv run pytest

# Live integration smoke test (creates a sandbox project, closes it after)
DIDA365_TOKEN=dp_xxx uv run python tests/integration/test_smoke_live.py

# Regenerate type stubs from the live MCP catalogue
uv run python scripts/generate_stubs.py --refresh

# Build & publish
uv build                       # produces dist/*.whl and dist/*.tar.gz
uv publish                     # uploads to PyPI (requires token)
```

### License

[MIT](LICENSE) © 2026 Hao Zhang
