DSH HUB
首页插件商店插件包社区排行榜资源发布指南
插件源码
返回插件目录

CaiMingshen /

CaiMingshen/dsh-icloud-calendar

已验证

Apple iCloud Calendar over CalDAV for DeepSeek Harness (DSH): five tools, cross-platform credential store, request deadlines, and a dry-run delete that must confirm an exact object count.

★ 1 Stars0 Forks0 IssuesN/A 社区评分0 已确认安装
查看 GitHub
README来源: main@ccf780a3

dsh-icloud-calendar

让 DeepSeek Harness(DSH) 通过标准 CalDAV 读写你的 苹果 iCloud 日历:查日历、按时间窗读事件(自动展开重复日程)、建事件、以及带四道闸门的删除。

English: README.en.md

状态:代码可编译、77 项测试全绿(含 DAV 读取器、内存 CalDAV 假服务器与一套真实 cordis 挂载测试,不需要真实账号)。尚未用真实 iCloud 账号做过端到端验收 —— 首次使用请先跑 dsh-icloud-calendar status --probe,并用一个单独的测试日历验证写入。


为什么是 CalDAV

苹果没有公开 iCloud 日历的 REST API,EventKit 只在 macOS 本机可用。跨平台唯一可行的自动化入口就是 CalDAV(RFC 4791):

DSH 插件 → HTTPS → https://caldav.icloud.com/ → 你的 iCloud 日历
认证:Apple ID(邮箱)+ App 专用密码

所以这个插件在 Windows / macOS / Linux 上行为一致。


安装

需要 DSH >=0.2.0-rc.2、Node ^22.19 || >=24。

# 1) 从本地 checkout 安装(开发/自用首选)
dsh plugin --profile web add /绝对路径/dsh-icloud-calendar

# 2) 直接从 GitHub 安装(仓库已提交构建产物 lib/,不需要任何构建授权)
dsh plugin --profile web add github:<你的用户名>/dsh-icloud-calendar

# 3) 从 npm 安装(发布后)
dsh plugin --profile web add dsh-icloud-calendar

验证装载成功:

dsh --profile web --dump-config | Select-String icloud-calendar

应能看到 # == dsh-icloud-calendar 这一层,以及 icloud-calendar-host / icloud-calendar-tools 两行。

为什么把 lib/ 也提交进仓库:pnpm ≥10 默认拒绝执行 git 依赖的 prepare 脚本,源码安装会失败并要求用户写 allowBuilds(等于授权该包在安装期执行代码)。预构建产物让 github: 安装零授权、零构建。


配置

插件是 bundle:dsh.bundle.patch → cordis.patch.yml 插入两行。

行 作用
dsh-icloud-calendar/host 连接、缓存、策略:注册 ctx.icloudCalendar 服务
dsh-icloud-calendar/tools 把服务暴露成 5 个模型可见的工具

只装 host 行不装 tools 行,日历就能被代码使用但对模型不可见 —— 这是一个有用的开关。

完整可复制的配置见 examples/cordis.patch.example.yml。常用项:

- insert:
    - id: icloud-calendar-host
      name: 'dsh-icloud-calendar/host'
      config:
        account: 'you@example.com'      # 也可留空,用 $ICLOUD_ACCOUNT
        secretEnv: ICLOUD_APP_PASSWORD  # 密码所在的环境变量名
        secretSource: auto              # auto | env | store
        allowWrites: true
        allowDeletes: false             # 默认关闭删除
        maxDeletesPerCall: 1
        timeoutMs: 30000
        retries: 2
        proxyUrl: ''                    # 例:http://127.0.0.1:7890
    - id: icloud-calendar-tools
      name: 'dsh-icloud-calendar/tools'

配置里没有密码字段,这是故意的:profile 补丁是磁盘上的文件,把 App 专用密码写进去就等于绕过了系统凭据存储。改配置需要重载 profile(这些字段没有标 .volatile(),因此不会出现在设置页表单里)。

serverUrl 的取值规则:远程主机必须 HTTPS;只有 127.0.0.1 / ::1 / localhost 允许明文 HTTP —— 自建 CalDAV(Radicale、Nextcloud、Baikal)经常就在本机跑明文,而凭据仍走 Authorization 头,所以只对回环开这个口子。想连局域网里的自建服务,请给它配上 HTTPS。


凭据

1. 生成 App 专用密码(必须)

  1. 登录 https://appleid.apple.com
  2. 登录与安全 → App 专用密码 → 生成(形如 abcd-efgh-ijkl-mnop)
  3. 不要用 Apple ID 登录密码 —— 开了双重认证后它必然被拒(HTTP 401)

2. 存放(三种来源,按 secretSource 决定优先级)

来源 说明
OS 凭据存储 Windows → DPAPI(绑定当前用户);macOS → 钥匙串(security);Linux → libsecret(secret-tool)
环境变量 secretEnv 指定的变量,默认 ICLOUD_APP_PASSWORD
都不配 报错点名缺哪个变量、并给出下一步命令,绝不回显密文

auto(默认)先查系统凭据存储,再查环境变量;env / store 可各自锁定。

写入系统凭据存储:

# Windows:密码只经过 stdin,不进 argv、不进 shell 历史
$p = Read-Host 'App 专用密码'
$p | node lib/cli.js login --account you@example.com
# macOS / Linux
read -rs -p 'app-specific password: ' P && printf '%s' "$P" | node lib/cli.js login --account you@example.com

安全边界:

  • 密钥永不进入 profile 配置、工具参数、session log 或错误信息;
  • DPAPI 明文只交给 PowerShell 子进程的 stdin,磁盘上是 DPAPI 密文(%LOCALAPPDATA%\dsh-icloud-calendar,.gitignore 已排除 *.dpapi.xml);
  • Linux 走 secret-tool 时密码走 stdin;macOS 钥匙串是唯一例外,security 只接受命令行参数,argv 会短暂可见(已在代码注释中标注);
  • 环境变量名刻意不叫 *PASSWORD* 之外的花样(DSH_CALENDAR_PASSWORD 那类名字会被部分日志脱敏工具整行打码,反而破坏文件)——本插件用 ICLOUD_APP_PASSWORD,DPAPI 子进程内部用 DSH_ICLOUD_STORE_FILE。

工具(模型可见)

工具 说明
calendar_status 配置/凭据来源/连通性自检,不返回密码
calendar_list_calendars 列出日历(名称、集合 URL、颜色、时区、组件类型);iCloud 提醒事项列表会标 supportsEvents=false
calendar_list_events 按时间窗读事件,重复日程展开为逐条,可取消的实例被跳过;返回 uid 供删除使用
calendar_create_event 建事件;两个日期=全天事件(end 为排他),两个带偏移的时间戳=定时事件
calendar_delete_events 删除,默认干跑,四道闸门见下

删除的四道闸门(针对"一次误删 8 条")

  1. 总开关:allowDeletes: false 是默认值,不打开就没有任何删除路径;
  2. 窗口必填:start / end 必填,且删除窗口上限(默认 90 天)比读窗口(366 天)更小;
  3. 精确指名:只能给 uid(来自 calendar_list_events)或 summaryEquals(完整标题、区分大小写)——子串/模糊匹配一律拒绝;
  4. 二次确认:默认 dryRun=true 只报告匹配结果;真删必须 dryRun=false 且 confirmCount 等于匹配到的对象数,同时受 maxDeletesPerCall(默认 1)封顶。

注意:命中重复日程时"对象数"小于"出现次数",删除会移除整个重复序列,结果里会明确写出这一点。


命令行(不走模型也能用)

dsh-icloud-calendar status [--probe] [--json]
dsh-icloud-calendar login  --account you@example.com      # 密码从 stdin 读
dsh-icloud-calendar logout --account you@example.com
dsh-icloud-calendar calendars
dsh-icloud-calendar events --start 2026-01-05T00:00:00+08:00 --end 2026-01-12T00:00:00+08:00
dsh-icloud-calendar add    --summary "评审" --start 2026-01-05T09:00:00+08:00 --end 2026-01-05T10:00:00+08:00 --time-zone Asia/Shanghai
dsh-icloud-calendar delete --start 2026-01-01T00:00:00+08:00 --end 2026-02-01T00:00:00+08:00 --uid <uid>
dsh-icloud-calendar delete ... --uid <uid> --dry-run=false --yes

CLI 复用同一套客户端与删除闸门;delete 永远先跑一次干跑,再要求 --yes,所以 --yes 单独出现不会删掉任何没人看过的东西。


踩坑清单(实测)

# 坑 现象 处理
1 用登录密码 HTTP 401,看起来像服务器故障 只能 App 专用密码;错误码 CALENDAR_AUTH_FAILED 会直接说明
2 每次调用新建连接 单次操作约 13 秒 host 服务常驻复用连接;CLI 单进程内完成多步;读操作本身按日历串行以免被限流
3 DPAPI 走 PowerShell 本机实测每次读写约 9 秒 只在建立连接时读一次密钥并缓存;不要把凭据读取放进每步
4 删除慢 按日历全扫描,事件越多越慢 删除必须带日期窗口(默认 ≤90 天),不做全库扫描
5 删除过杀 模糊匹配一次删掉 8 条 精确匹配 + 干跑 + confirmCount + 上限 1
6 没有超时 网络卡住就干等 每请求 30s deadline,429/5xx 指数退避重试;401 不重试(避免锁号)
7 时区 全天事件差一天、无偏移时间戳含义不明 无 offset 的时间戳直接拒绝;全天事件按 iCalendar 的日期字段读取,不经过本地时区
8 代理 国内直连 iCloud 可能不通 proxyUrl(需要可选依赖 undici,未装时给出明确提示)
9 热重载预期 换了插件 JS 却没生效 新增 bundle 可经 HMR 生效,替换已安装包必须重启
10 重复日程 以为删的是"这一次" 删除作用于整个对象(整个序列),结果中明确提示
11 Reminders 列表 被当成日历读/写 若服务端公布了组件集合且不含 VEVENT,该集合会被跳过、显式指名则报错。但实测中国区 iCloud 的 19 个日历全部不公布组件集合,所以无法预判:对"提醒"集合查询一个月返回 0 条,代价只是多一次请求。写入该集合的行为未在本插件内验证
12 多日历歧义 不知道写进哪个日历 只有唯一候选时才允许省略 calendar,否则报错并列出候选。实测账号里有两个都叫「家庭」的日历,裸用名字会被拒绝并要求给集合 URL
13 中国区账号的分区主机 以为要连 caldav.icloud.com 入口仍填 https://caldav.icloud.com/ 即可;principal/calendar-home 发现会跟着服务端给出的分区主机走。实测真实账号的日历 URL 落在 p236-caldav.icloud.com.cn,19 个日历全部正常

架构

文件 职责
src/client.ts CalDAV 客户端:principal/home 自动发现、calendar-query、PUT/GET/DELETE、超时与重试、可选代理
src/xml.ts 面向 CalDAV 多状态响应的极小 XML 读取器(命名空间前缀无关)
src/ical.ts iCalendar 语义:时间戳校验、重复日程展开、事件构造与折行、全天事件边界
src/credentials.ts 跨平台凭据存储(DPAPI / 钥匙串 / libsecret / 关闭)
src/config.ts schemastery 配置模式、默认值、凭据解析与来源说明
src/policy.ts 删除闸门(唯一实现,服务与 CLI 共用)
src/host.ts cordis 服务 ctx.icloudCalendar:连接复用、缓存、策略
src/tools.ts defineTool 注册 5 个模型可见工具
src/cli.ts 独立命令行入口
tests/ vitest:DAV 读取器(xml.test.ts,含"只返回第一个自闭合元素"这类回归)、内存 CalDAV 假服务器(401/503/超时)、iCalendar、删除闸门、凭据存储、以及用真实 cordis 上下文挂载两个插件行的激活测试

一个刻意的取舍:不手填日历集合 URL。插件走标准的 current-user-principal → calendar-home-set → 列日历 发现流程,所以换账号/换服务器只要改 account。


开发

pnpm install
pnpm typecheck     # src + tests
pnpm test          # 77 项:DAV 读取器 + mock CalDAV + 真实 cordis 挂载
pnpm build         # tsc → lib/

测试不需要 Apple ID、不需要网络:tests/mock-caldav.ts 是一个进程内 HTTP 服务器,实现了发现、REPORT、PUT、GET、DELETE,并可注入 401、连续 503、慢响应。tests/activation.test.ts 则用真实 @deepseek-ai/cordis 上下文把 host 行与 tools 行挂起来,断言服务注册、inject 满足、5 个工具入册、calendar_status 可执行、以及只读闸门生效。

启动验证(以及为什么"没报错"能算证据)

除单测外,还做了两层真机验证:

  1. 组合层:dsh plugin --profile <probe> add <本包> 后 dsh --profile <probe> --dump-config,能看到 # == dsh-icloud-calendar 层与两行;
  2. 启动层:从官方 headless 模板建一个临时 profile 装上本包并真实启动,进程 2.2 秒内走到 LLM 阶段、只因缺少 LLM 凭据退出(MISSING_CREDENTIAL),没有任何插件行报错。

第 2 条之所以有说服力,是因为做了反证实验:把 cordis.patch.yml 里的模块名改成不存在的路径后,启动会直接卡在组合阶段(600 秒不退出),而不是静默继续。也就是说"启动能走到 LLM 阶段"确实等价于"两行插件成功激活",而不是"错误被吞了"。

  1. 端到端:把测试用的 CalDAV 服务编译成独立进程跑在真实 HTTP 端口上,再用构建产物 lib/ 里的 CLI 走完整流程(结果见下表)。这条路径覆盖了真实的 HTTP 层、iCalendar 构造、策略闸门与退出码,而不只是单测内的函数调用。
命令 结果
status --probe 配置/凭据来源/连接全部正常,退出码 0
calendars 5 个日历:Reminders 标 EVENTS=no、Shared 标 READ-ONLY=yes、Mixed(VTODO+VEVENT)标 EVENTS=yes
events 5 条出现:1 条单次 + 4 条 weekly 展开(RRULE 在窗口内正确展开)
add(09:00+08:00) 建成 2026-01-06T01:00:00Z,时区正确折算
delete(未加 --dry-run=false) DRY RUN — 1 object(s),什么都没删
delete(--dry-run=false 但没 --yes) 拒绝:CALENDAR_UNSAFE_DELETE: Refusing to delete 1 object(s) without --yes
delete --summary-equals=E2E(部分标题) 匹配 0 条 —— 模糊匹配确实不存在
delete --summary-equals=E2E review --dry-run=false --yes DELETED — 1 object(s)
删除后再 events 新建的那条消失,原有 5 条出现完好
退出码契约 成功 0 / 闸门或配置问题 1 / 未知命令 2 / help 0

已知限制 / 路线图

  • 没有设置页表单(配置项未标 .volatile()),改配置需编辑 profile 并重载;
  • 暂不支持更新事件(calendar_update_event)与多账号同时挂载(可用多个 profile 行 + 不同 account 变通);
  • 重复日程的 RRULE 展开在客户端完成,单序列上限 maxOccurrencesPerSeries(默认 400),命中上限会在结果里报告;
  • 未做真实 iCloud 账号的端到端验收(见文首状态说明)。

许可证与致谢

MIT。

设计与实现参考了同一生态里的两个开源项目(代码为独立实现,未复制):

  • moziforge/calendar-plugins(Apache-2.0)—— iCloud CalDAV 的凭据与错误码处理思路;
  • STARDUSTLC666/dsh-calendar(MIT)—— DSH 插件形态与多日历解析实践。

插件形态遵循官方 cordis-plugin-development skill 与 docs/user/develop/basic/publish.md。

DSH HUB

社区维护的 DSH 插件索引。不是 GitHub 或 DeepSeek AI 的官方产品。

社区资源API关于
—/ 5

暂无评分

已验证 DSH bundle

Commit ccf780a32b2a

社区评论

还没有评论,来写第一条。