Read and write Apple iCloud Calendar over CalDAV through five tools: list calendars, read a window with recurring events expanded, create an event, and delete behind a dry run that must confirm the exact object count. Credentials come from DPAPI, Keychain or libsecret, or from an environment variable.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:CaiMingshen/dsh-icloud-calendar
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Read and write your Apple iCloud Calendar from DeepSeek Harness (DSH) over plain CalDAV: list calendars, read a time window (recurring events expanded), create events, and delete behind four gates.
中文文档:README.md
Status: compiles, 77 tests green (a DAV-reader suite, an in-process CalDAV server, and a live cordis activation suite; no Apple ID, no network). Not yet verified end-to-end against a real iCloud account — run dsh-icloud-calendar status --probe first, and test writes against a throwaway calendar.
Why CalDAV
Apple publishes no REST API for iCloud Calendar, and EventKit is macOS-only. CalDAV (RFC 4791) is the one cross-platform programmatic route:
DSH plugin → HTTPS → https://caldav.icloud.com/ → your calendars
Auth: Apple ID (email) + an app-specific password
Install
Requires DSH >=0.2.0-rc.2 and Node ^22.19 || >=24.
dsh plugin --profile web add /absolute/path/dsh-icloud-calendar
dsh plugin --profile web add github:<you>/dsh-icloud-calendar
dsh plugin --profile web add dsh-icloud-calendar # once published
dsh --profile web --dump-config | Select-String icloud-calendar
The bundle inserts two rows: dsh-icloud-calendar/host (connection, cache, policy, ctx.icloudCalendar) and dsh-icloud-calendar/tools (the five model-facing tools). Mount the host alone and the calendar stays usable from code but invisible to the model.
lib/ is committed on purpose: pnpm ≥10 refuses to run a git dependency's prepare script until the user allowlists it, so prebuilt artifacts make a github: install work with no build permission and no trust decision.
Credentials
- Create an app-specific password at https://appleid.apple.com (Sign-In and Security → App-Specific Passwords). The Apple ID password is refused once two-factor authentication is on.
- Store it (first match wins, per
secretSource):- OS credential store — DPAPI on Windows, Keychain on macOS, libsecret on Linux;
- environment variable —
secretEnv, defaultICLOUD_APP_PASSWORD; autochecks the store, then the environment.
$p = Read-Host 'app-specific password'
$p | node lib/cli.js login --account you@example.com
The secret never enters profile config, tool arguments, session logs, or error messages. On Windows the plaintext is handed to a PowerShell child process on stdin and only the DPAPI blob is written to disk. Keychain is the one backend where the platform tool requires the value as an argument.
Configuration lives in examples/cordis.patch.example.yml. There is deliberately no password field: a profile patch is a file on disk, and storing the secret there would defeat the credential store. Editing config requires reloading the profile (the fields are not .volatile(), so no settings form is generated).
Tools
| Tool | Purpose |
|---|---|
calendar_status |
Configuration, credential provenance, reachability. Never returns the secret. |
calendar_list_calendars |
Calendars with URL, colour, time zone, component types; iCloud Reminders lists report supportsEvents=false. |
calendar_list_events |
Occurrences in a window, recurring series expanded, cancelled instances skipped, uid returned for deletes. |
calendar_create_event |
Two dates create an all-day event (exclusive end); two offset-bearing timestamps create a timed event. |
calendar_delete_events |
Dry run by default; four gates described below. |
The four delete gates
allowDeletesis false in a default profile: no delete path exists until it is switched on.- A bounded window is mandatory, and the delete window (90 days by default) is smaller than the read window.
- The target is named exactly — a
uidor the completesummaryEqualstitle. Substring and fuzzy matches are refused. - The call is a dry run unless it says
dryRun=false, and thenconfirmCountmust equal the matched object count, capped bymaxDeletesPerCall(1 by default).
Deleting an occurrence of a recurring series removes the whole series; the result says so.
CLI
status, login, logout, calendars, events, add, delete — see dsh-icloud-calendar help. The CLI reuses the same client and the same delete guard, and its delete always runs a dry run before requiring --yes.
Measured pitfalls
| # | Pitfall | Handling |
|---|---|---|
| 1 | Apple ID password → HTTP 401 | App-specific password only; CALENDAR_AUTH_FAILED says so |
| 2 | ~13 s per fresh CalDAV handshake | One long-lived client per service; serial reads to respect iCloud rate limits |
| 3 | DPAPI via PowerShell costs ~9 s per call (measured) | Resolved once per connection, never per step |
| 4 | Deletes scan whole calendars | A date window is mandatory; no full-collection scan |
| 5 | A fuzzy delete removed eight events once | Exact match, dry run, confirm count, cap of 1 |
| 6 | No deadline → hangs | 30 s deadline, backoff retry on 429/5xx, never on 401 |
| 7 | Time zones | Offset-less timestamps are refused; all-day values are read from date fields, not through a local-zone Date |
| 8 | Blocked network | proxyUrl (needs the optional undici) |
| 9 | Hot reload | New bundles may activate via HMR; replacing an installed package needs a restart |
| 10 | Reminders lists | Skipped as event calendars; naming one is an explicit error |
Development
pnpm install
pnpm typecheck
pnpm test # 77 tests: DAV reader, in-process CalDAV mock (401 / 503 / timeout), live cordis activation
pnpm build # tsc → lib/
Beyond the unit suites, two live checks were run: the bundle was installed into a throwaway profile and its layer appeared in dsh --profile <probe> --dump-config, and that profile was booted for real — it reached the model call in 2.2 s and stopped only on the missing LLM credential, with no plugin row failing. A falsification run backs that up: pointing one row at a non-existent module makes the same boot hang in composition instead of proceeding, so "it got to the model call" really does mean both rows activated.
License
MIT. Independently implemented; the design was informed by moziforge/calendar-plugins (Apache-2.0) and STARDUSTLC666/dsh-calendar (MIT).
Links
More in this category
Tencent/WeKnora#dsh-weknora★ 34412
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 5565
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1138
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
gitroomhq/postiz-agent#dsh-postiz★ 520
Connects DeepSeek Harness to Postiz over MCP: list connected social media channels, fetch per-platform posting rules, and schedule, draft, or publish posts to X, LinkedIn, Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Bluesky, Mastodon, Discord, Slack, Telegram and more; adds a postiz workflow skill.
EthanYoQ/Invoice-Downloader#dsh-invoice-downloader★ 509
Local IMAP invoice download, OCR, archive, and Excel reimbursement summaries for DeepSeek Harness.
anysearch-team/anysearch-dsh★ 504
AnySearch-powered real-time web and vertical search provider for DeepSeek Harness.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.