dsh-calendar 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0(2026-09-27)
4
+
5
+ - 适配 Harness 0.1.7 设置表单与旧配置导入,正确声明设置服务依赖。示例日历改为只读,保留视图切换和详情查看,避免示例操作写入真实日历。
6
+
7
+ - **适配 Harness 0.1.7 的设置接口**:宿主删掉了 `ctx.settings.register(...)`,改成「entry 的 `Config` 就是设置项」(`.volatile()` 字段由宿主投影成表单)。0.8.x 在 0.1.7 上会直接挂载失败(`ctx.settings.register is not a function`);即使不失败,连接设置也只剩插件自己的 JSON 兜底文件,老用户在 `settings.yaml` 里存过的账号一律读不到(启动告警「日历账号未填写」)。
8
+ - **新增 `src/host-config.ts`**:导出 entry 的 `Config` —— 14 个连接字段全部 `.volatile()`(设置页改完即生效,不必重启),`password` / `clientSecret` / `refreshToken` 标 `role('secret')`(不经设置接口回传,只回「有没有」),且**一律不给默认值**:表单默认值会盖掉 `cordis.patch.yml` 与 provider 预设。`liveConfig` 把宿主的活引用摊平成普通值,0.1.6 及更早的字面量配置走同一条路。
9
+ - **新增 `src/host-settings.ts`**:两代宿主接口判定,加上老 `settings.yaml` 里 `dsh-calendar` 段的一次性搬迁。宿主自己也会导入老文件,但它按 **entry id** 找目标,而本插件的行 id 是 `calendar`,对不上就只留一条 warn、值搁在改名后的文件里 —— 所以由插件补这一步:只补当前配置里空缺的键(profile 已写过的值优先)、只搬一次(标记 `legacySettingsImported` 与值写在同一处)、不复制进同插件的自定义实例、读不到或写不进都不抛(原文件原样保留)。
10
+ - **写入语义**:面板保存改用 `mutate` 的逐字段 set/unset。`replace` 会把「本次没提交」的 live 字段重置成下层继承值,顺手抹掉面板不认识的字段(例如上面那个搬迁标记),下次启动就会重搬一遍,用户明确删掉的凭据又回来了。宿主没有 `mutate` 时才退回 `replace`,并显式把标记带上。
11
+ - 自带设置页向宿主登记 `configure({ auto: false })`,并声明 `tools` 与 `settings` 服务依赖,保证设置面板接入宿主存储。
12
+ - **测试 163 → 172**:Config 形状(volatile / secret / 无默认值)、活引用摊平、两代宿主判定、mutate 与 replace 两条写路径、搬迁的一次性与「不串实例」、apply 在 0.1.7 宿主上不再抛错且面板路由照常挂载、面板保存全链路。
13
+
14
+ ## 0.8.4(2026-09-23)
15
+
16
+ - **适配 Harness 0.1.7 的 Web 子路径部署**:面板请求改为跟随应用挂载路径。此前宿主被反向代理挂在子路径下时,设置与事件操作会打到站点根,一律 404。
17
+ - 未配置账号/密码时的提示改为指向面板「连接设置」(不再要求用户去改 `cordis.patch.yml` 并重启)。
18
+ - 新增子路径部署下的请求路径回归(`test/public-mount.test.mjs`)。
19
+
3
20
  ## 0.8.3(2026-09-21)
4
21
 
5
22
  - 已知事件的修改、删除及创建后回读改为直接 CalDAV multiget,省掉整集合索引查询;每次仍读取最新 ETag,保留条件写入。
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 stardustlc
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stardustlc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md CHANGED
@@ -1,214 +1,222 @@
1
- # dsh-calendar
2
-
3
- ![npm](https://img.shields.io/npm/v/dsh-calendar) ![downloads](https://img.shields.io/npm/dm/dsh-calendar) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-calendar) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-calendar?style=social)
4
-
5
- [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
6
-
7
- DSH community plugin: read/write calendar events via CalDAV. Provides 5 calendar tools (calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search) plus the offline `calendar_health` configuration check. Google uses OAuth 2.0; iCloud / Nextcloud / custom servers retain Basic authentication by default. Includes visual week/month views, drag and keyboard rescheduling, connection settings and connection tests. Profile cordis.patch.yml configuration is also supported. Updates and deletes fetch the known event directly, avoiding a collection-wide index request.
8
-
9
- ## Compatibility
10
-
11
- Version **0.8.4** (2026-09-23) supports the Web mount paths introduced in Harness **0.1.7**. The calendar panel keeps its requests under the application path when deployed through a reverse proxy, preserving normal settings and event operations. The current host baseline is official-source 0.1.7-alpha.2 with a local tool-scheduler fix; external CalDAV operations require their own account validation.
12
-
13
- 2026-09-21: the current release package was installed through the official CLI in an isolated profile and co-loaded with the other two most-downloaded plugins on source-built Harness `0.1.6-alpha.2`. All 18 plugin tools registered; calendar/email configuration checks, PPT theme listing and 17-row table generation passed. The host is based on the official alpha.2 release plus the tool-scheduler `Symbol.for` fix (`93badd88`). This run did not connect to live mail or calendar services.
14
-
15
- Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
16
-
17
- On 2026-09-10, npm `dsh-calendar@0.5.2` passed installation through this Harness release's official CLI and registration of all 6 tools. Host execution of `calendar_list` refreshed a real Google token (200), read via CalDAV REPORT (207) and rendered model-facing results. Only Google reads were tested; no writes were performed. This is a historical read-only verification record.
18
-
19
- Follows the official [plugin packaging and installation requirements](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md): an ESM entry point, prebuilt `lib/`, `dsh.bundle.patch` and a `cordis.patch.yml` layer. The plugin explicitly injects `tools` and supplies JSON Schema parameters, canonical output and rendering, with no runtime imports of `@deepseek-ai/*` internals. Use Node 22.19 or later within 22.x, or Node 24 or later. Harness is evolving rapidly; the version above is the tested baseline.
20
-
21
- ## Installation
22
-
23
- ```bash
24
- dsh plugin --profile web add dsh-calendar
25
- ```
26
-
27
- After installing, restart dsh. The plugin inserts a `calendar` config line into the profile (see this package's cordis.patch.yml). The default provider is custom with no credentials filled in; the plugin still loads, but tools throw a Chinese guidance error when called, prompting you to complete the configuration.
28
-
29
- ## Uninstall
30
-
31
- ```bash
32
- dsh plugin --profile web remove dsh-calendar
33
- ```
34
-
35
- Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
36
-
37
-
38
- ## Configuration
39
-
40
- **Recommended: configure it in the panel (0.6.0+)**
41
-
42
- Open「Connection」in the panel toolbar, pick a provider, fill in the address and account, hit **Test connection** (it really lists the next 30 days) and then **Save and enable**. The tools pick the new configuration up on their next call — no restart, no YAML editing.
43
-
44
- What the panel saves lives in your local `settings.yaml` under the `dsh-calendar` namespace (the password is a secret field: no logs, no exports). If the host has no settings service (or the namespace cannot be registered), the connection is stored in the plugin’s own file `$DSH_HOME/data/dsh-calendar/connection.json` (0600) instead, and the panel says which one is in use. **The YAML remains the base layer**: any field the panel never touched still comes from it, so an existing `cordis.patch.yml` setup needs no migration; a headless host without a settings page still configures through YAML only.
45
-
46
- Per-provider notes:
47
-
48
- - **iCloud**: `caldavUrl` looks like `https://caldav.icloud.com/<numeric id>/calendars/<calendar id>/`; use an [app-specific password](https://appleid.apple.com/)
49
- - **Nextcloud / self-hosted**: server URL + username + calendar name (self-hosted: the full collection URL, keep the trailing slash) and an app password
50
- - **Google**: OAuth only — Google rejects every Basic password, including app passwords; needs `clientId` / `clientSecret` / `refreshToken` / `calendarId`
51
-
52
- ### Or: write YAML (advanced / headless)
53
-
54
- The fields below mirror the panel form one-to-one; whatever the panel never set comes from here.
55
- All configuration lives in your profile's cordis.patch.yml; override the `calendar` line by id (overriding replaces that line's config wholesale). Common fields:
56
-
57
- - `provider`: google | icloud | nextcloud | custom
58
- - `caldavUrl`: full calendar collection URL (required for custom / icloud; google / nextcloud can also override the preset manually)
59
- - `authMethod`: `basic` | `oauth`; Google defaults to and requires `oauth`, other providers default to `basic`. Opt in explicitly for another OAuth server. Google credentials in the environment never switch a Basic provider to OAuth automatically.
60
- - `username` / `password`: required only for Basic authentication. Use an app-specific password for iCloud, preferably via `DSH_CALENDAR_PASSWORD`. **Google CalDAV rejects all Basic passwords, including app-specific passwords.**
61
- - `clientId` / `clientSecret` / `refreshToken`: required for OAuth; also read from `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN`. Non-empty config values take precedence over environment variables. Never commit secrets or tokens to Git.
62
- - `tokenUrl`: also read from `DSH_CALENDAR_TOKEN_URL`; defaults to `https://oauth2.googleapis.com/token` for Google and is required for other OAuth providers. OAuth tokenUrl and caldavUrl must use HTTPS without embedded credentials, query parameters or fragments.
63
- - `calendarId`: google-specific, the calendar ID (usually your email)
64
- - `host` / `user` / `calendar`: nextcloud-specific
65
- - `proxyUrl`: optional HTTP proxy (e.g. `http://127.0.0.1:7890`); both OAuth token requests and CalDAV requests use this transport
66
-
67
- ### Google example
68
-
69
- ```yaml
70
- - id: calendar
71
- name: dsh-calendar
72
- config:
73
- provider: google
74
- calendarId: you@gmail.com
75
- # authMethod: oauth # the default for Google
76
- # Provide clientId / clientSecret / refreshToken via environment variables
77
- ```
78
-
79
- Google's CalDAV collection URL is assembled by the plugin: `https://apidata.googleusercontent.com/caldav/v2/<calendarId>/events`.
80
-
81
- Set these placeholder values in the same terminal that starts source-run `pnpm dsh` or ordinary `dsh`:
82
-
83
- ```bash
84
- export DSH_CALENDAR_CLIENT_ID='your OAuth client ID'
85
- export DSH_CALENDAR_CLIENT_SECRET='your OAuth client secret'
86
- export DSH_CALENDAR_REFRESH_TOKEN='your authorized refresh token'
87
- ```
88
-
89
- These credentials come from your own Google Cloud OAuth client and a user authorization, not an email app password. Follow the [Google CalDAV setup guide](https://developers.google.com/workspace/calendar/caldav/v2/guide) to enable the API and configure OAuth. Request `https://www.googleapis.com/auth/calendar` for calendar read/write and offline access (`access_type=offline`) to obtain a refresh token; see [Google's offline authorization documentation](https://developers.google.com/identity/protocols/oauth2/web-server#offline). Supply an already authorized refresh token, or mint one with the helper below.
90
- **Getting a refresh token (one command)**: register the client as a **Desktop app**, then:
91
-
92
- ```bash
93
- cd <plugin directory> # the source checkout, or node_modules/dsh-calendar
94
- node scripts/google-oauth.mjs --client-id <your clientId> --client-secret <your clientSecret>
95
- ```
96
-
97
- The script spins up a **one-shot local callback** (`http://127.0.0.1:<random port>/`), opens the browser for consent, exchanges the code and prints the refresh token — no hand-built URLs, no third-party playground.
98
-
99
- Paste it into the panel’s「Connection」form (provider Google, calendarId = your Gmail address), or use the `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN` environment variables.
100
-
101
- > While the OAuth consent screen is still in **Testing**, the refresh token lasts 7 days: hit **Publish app** on the OAuth consent screen to make it permanent (no review needed for personal use).
102
-
103
- **Google CalDAV scope validation (2026-09-08):** for the same account and calendar, `calendar.readonly` allowed token refresh (200) and collection PROPFIND (207), but the event REPORT returned 403. With the `calendar` scope above, REPORT returned 207; a fresh verification process also refreshed the token and read successfully. Use this CalDAV scope configuration and verify an actual `calendar_list` request: successful token acquisition or collection discovery alone does not prove events are readable. This live test performed reads only; Google write operations were not tested.
104
-
105
- The `calendar` scope permits viewing, editing, sharing and deleting accessible calendars; review that access before granting it. See [Google's scope definitions](https://developers.google.com/workspace/calendar/api/auth). For an external OAuth app in **Testing**, a refresh token with Calendar scopes expires after 7 days. Long-term use needs reauthorization or an appropriate production configuration under Google's requirements; see [refresh token expiration](https://developers.google.com/identity/protocols/oauth2#expiration).
106
-
107
- Access tokens are cached in memory and checked before each DAV request, with refresh before expiry. Both token and DAV requests inherit the tool call's AbortSignal. A 401 invalidates the token for the next call; **writes are never replayed automatically**. OAuth requests do not follow redirects or send Bearer tokens to cross-origin object hrefs, so configure the final calendar collection URL. Runtime tokens are not written to configuration or logs. If another OAuth provider rotates refresh tokens, supply valid credentials again when restarting.
108
-
109
- ### iCloud example
110
-
111
- ```yaml
112
- - id: calendar
113
- name: dsh-calendar
114
- config:
115
- provider: icloud
116
- username: you@icloud.com
117
- caldavUrl: https://caldav.icloud.com/123456789/calendars/<日历ID>/
118
- # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
119
- ```
120
-
121
- iCloud needs the full calendar collection URL (with your user ID and calendar ID); you can find the specific calendar address in the calendar CalDAV settings on icloud.com.
122
-
123
- ### Nextcloud example
124
-
125
- ```yaml
126
- - id: calendar
127
- name: dsh-calendar
128
- config:
129
- provider: nextcloud
130
- username: alice
131
- host: https://cloud.example.com
132
- user: alice
133
- calendar: personal
134
- # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
135
- ```
136
-
137
- The plugin assembles: `https://cloud.example.com/remote.php/dav/calendars/alice/personal/`.
138
-
139
- ### Custom CalDAV example
140
-
141
- ```yaml
142
- - id: calendar
143
- name: dsh-calendar
144
- config:
145
- provider: custom
146
- caldavUrl: https://dav.example.com/calendars/me/work/
147
- username: me
148
- # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
149
- ```
150
-
151
- ## Authentication troubleshooting
152
-
153
- Google: OAuth 2.0 only. On 401/403, check OAuth authorization, calendar scope and calendar permissions. If refresh and PROPFIND succeed but REPORT returns 403, check that the granted scope is the `calendar` scope above; successful discovery with `calendar.readonly` does not prove events are readable. On refresh failure, verify clientId/clientSecret/refreshToken, re-authorize revoked or expired grants, and check the 7-day Testing lifetime. **Generating another app password cannot fix Google CalDAV authentication.**
154
-
155
- iCloud: sign in to appleid.apple.com → Sign-In and Security → App-Specific Passwords, generate one and fill it into `password` or `DSH_CALENDAR_PASSWORD`. You cannot use your Apple ID password.
156
-
157
- Nextcloud / custom Basic servers: check the account, password or provider-issued app token and calendar permissions. `calendar_health` checks configuration only; it neither connects nor proves authorization. Use `calendar_list` to verify the real connection.
158
-
159
- ## Proxy
160
-
161
- If your CalDAV server is not directly reachable from your network (some regional or corporate networks block it), set `proxyUrl` in the plugin config, e.g. `http://127.0.0.1:7890`, and restart. Both token refresh and DAV requests use that HTTP proxy; it does not affect other plugins in the same process.
162
-
163
- ## Tool reference
164
-
165
- - `calendar_health`: offline provider, endpoint and Basic/OAuth credential-completeness checks; never displays secrets or connects to the server.
166
- - `calendar_list`: list events in a time range (start/end, ISO 8601; defaults to the next 7 days). Recurring events are expanded by default (`expand` defaults to true, `maxOccurrences` defaults to 30, clamped to 1-200): each occurrence is a separate row with `isOccurrence: true` and `seriesStart`; non-recurring events keep `isOccurrence: false`. With `expand=false`, recurring events are returned as a single original entry with `rrule`. Results are stably sorted by start time.
167
- - `calendar_create`: create an event (summary/start/end required; description/location/allDay/rrule optional). Validates real calendar dates and `end >= start`.
168
- - `calendar_update`: edit an event by uid (summary/start/end/description/location/allDay/rrule optional; omitted fields keep their original values, including the recurrence rule and original properties such as ATTENDEE / ORGANIZER / VALARM).
169
- - `calendar_delete`: delete an event by uid
170
- - `calendar_search`: search events by keyword within the `start`~`end` window (defaults to one year before/after now; client-side filter over title/description/location/UID, case-insensitive; `limit` defaults to 50, clamped to 1-200, and results are sorted by start time).
171
-
172
- The stable event identifier `uid` is the CalDAV href (full object URL); `calendar_update` / `calendar_delete` use it.
173
-
174
- ## Calendar panel in Settings (0.6.0+)
175
-
176
- Once installed, DSH grows a「Calendar」section in Settings; the chat page also gets a small 📅 button in the bottom-right that opens the same panel (non-modal, Esc closes).
177
-
178
- - **Three views**: month (6×7 grid, today highlighted, click a cell to create), week (24-hour time grid starting at 07:00, overlapping events side by side) and agenda (grouped by day). The panel **remembers the view you used last**.
179
- - **Drag to reschedule**: in the week view drag an event up/down to change the time, left/right to change the day (15-minute snapping), and drag its bottom edge to change the duration. A drop preview shows where it lands and turns yellow when it overlaps something (overlaps are allowed, it is only a warning). Keyboard works too: `↑/↓` 15 minutes, `Shift+↑/↓` an hour, `←/→` a day, `Esc` cancels mid-drag. Every move offers a 5-second undo and rolls back (then re-reads the server) if the request fails. **Recurring events and events longer than 24 hours cannot be dragged yet** — edit those in the details drawer.
180
- - **Click to edit**: open any event in the right-hand drawer — time, duration, a human-readable repeat rule (`FREQ=WEEKLY;COUNT=6` → "weekly (6 times)"), location, notes, one-click uid copy — then edit or delete (deletion asks twice).
181
- - **Create**: title, date, start/end, all-day, location, notes, repeat (daily/weekly/monthly/yearly, or a raw RRULE).
182
- - **Conflict notice**: before saving, overlapping events are listed and you are asked whether to continue.
183
- - **Configurable from the panel itself**: without a CalDAV account the panel shows guidance whose「Connection」button opens the form (test first, then save); a clearly labelled sample is one click away and never pretends to be your real calendar.
184
- - **Theme and locale aware**: colours come from the host design tokens (`--dsw-*`), so light/dark themes follow automatically; copy follows Settings → General.
185
- - **Security**: the panel only talks to the plugin’s own `/_dsh/dsh-calendar/settings`, which is loopback-only and re-checks Host, Origin and Content-Type on writes; credentials are never echoed back.
186
- ## Time and timezone
187
-
188
- Input and output are uniformly ISO 8601. Timed events are output in UTC (e.g. `2025-01-15T01:00:00Z`); all-day events output `YYYY-MM-DD`. Input may carry a timezone offset (e.g. `2025-01-15T09:00:00+08:00`); the plugin converts to UTC internally for storage.
189
-
190
-
191
- ## Changelog
192
-
193
- - **0.5.4 (2026-09-18)**: fixes for `calendar_update` dropping ATTENDEE/VALARM and other original properties, ignored RECURRENCE-ID overrides, `calendar_create` inventing a uid instead of using the server Location, and silent empty expansion results; `calendar_search` gained a time range. 79 tests.
194
- - **0.5.3 (2026-09-11)**: revalidated against official Harness 0.1.5-rc.1 and refreshed co-load / live-service verification; runtime code unchanged.
195
- - **0.5.2 and earlier**: see [CHANGELOG.md](CHANGELOG.md).
196
-
197
- ## Known limitations
198
-
199
- - Recurring event expansion: calendar_list expands RRULE by default via ICAL.RecurExpansion (`expand=true`), capped by `maxOccurrences`, and raises an explicit error when the iteration budget is exhausted; calendar_search only queries the `start`~`end` window (default one year before/after now) and still returns the original series (not expanded).
200
- - Single-instance reads vs edit/delete: calendar_list honors RECURRENCE-ID overrides when expanding (an occurrence separately rescheduled/retitled is returned with the override time and fields, including when the original instant is EXDATE-excluded); calendar_update / calendar_delete still operate on the whole recurring series (by uid) and cannot modify or delete just one occurrence.
201
- - OAuth credentials must be obtained beforehand: refresh-token authentication is supported, but there is no browser login UI / login CLI and runtime tokens are not written back to configuration.
202
- - Timezone rules: events with TZID (named timezone) are output converted to UTC (Z); all-day boundaries, DST, and other complex timezone rules are not handled finely.
203
- - No settings-page UI: this round is a node half-body; config only via cordis.patch.yml, no Web settings page.
204
- - Calendar discovery: iCloud requires manually filling the full calendar collection URL; no principal auto-discovery or multi-calendar selection.
205
- - Cancellation/timeout: tools use `timeoutMs` (60 seconds) and forward the host AbortSignal into token refresh and DAV network requests. Concurrent calls cancel independently.
206
-
207
- ## Development
208
-
209
- ```bash
210
- pnpm install
211
- pnpm test # 构建 + node --test
212
- ```
213
-
214
- Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
1
+ # dsh-calendar
2
+
3
+ ## 0.9.0 update (2026-09-27)
4
+
5
+ Adapts settings forms and legacy configuration import to Harness 0.1.7, with the settings service declared correctly. The sample calendar is read-only; viewing and navigation remain available without writing sample events to a real calendar.
6
+
7
+ Validation host: Harness 0.1.7-rc.2 built from official sources, retaining the local tool-scheduler fix. Build and automated checks pass; interactive coverage and external-service limits are recorded in this release round.
8
+
9
+ ![npm](https://img.shields.io/npm/v/dsh-calendar) ![downloads](https://img.shields.io/npm/dm/dsh-calendar) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-calendar) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-calendar?style=social)
10
+
11
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
12
+
13
+ DSH community plugin: read/write calendar events via CalDAV. Provides 5 calendar tools (calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search) plus the offline `calendar_health` configuration check. Google uses OAuth 2.0; iCloud / Nextcloud / custom servers retain Basic authentication by default. Includes visual week/month views, drag and keyboard rescheduling, connection settings and connection tests. Profile cordis.patch.yml configuration is also supported. Updates and deletes fetch the known event directly, avoiding a collection-wide index request.
14
+
15
+ ## Compatibility
16
+
17
+ Version **0.9.0** (2026-09-23) follows the settings interface introduced in Harness **0.1.7**: the host removed `ctx.settings.register`, so configuration now lives in the entry's own `Config`. Every connection field is declared `volatile` (panel edits apply live, no restart) and the three credentials carry the `secret` role (never returned to the browser, only whether they are set). On the first start after an upgrade the plugin moves the retired `dsh-calendar` section of `settings.yaml` into the profile — filling only absent keys, once, with values already written in the profile winning. Hosts on 0.1.5 / 0.1.6 keep the previous namespace registration unchanged. The verified host baseline is official-source **0.1.7-rc.1** with the local tool-scheduler fix; live CalDAV operations still require their own account validation.
18
+
19
+ Version **0.8.4** (2026-09-23) supports the Web mount paths introduced in Harness **0.1.7**. The calendar panel keeps its requests under the application path when deployed through a reverse proxy, preserving normal settings and event operations. The current host baseline is official-source 0.1.7-alpha.2 with a local tool-scheduler fix; external CalDAV operations require their own account validation.
20
+
21
+ 2026-09-21: the current release package was installed through the official CLI in an isolated profile and co-loaded with the other two most-downloaded plugins on source-built Harness `0.1.6-alpha.2`. All 18 plugin tools registered; calendar/email configuration checks, PPT theme listing and 17-row table generation passed. The host is based on the official alpha.2 release plus the tool-scheduler `Symbol.for` fix (`93badd88`). This run did not connect to live mail or calendar services.
22
+
23
+ Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
24
+
25
+ On 2026-09-10, npm `dsh-calendar@0.5.2` passed installation through this Harness release's official CLI and registration of all 6 tools. Host execution of `calendar_list` refreshed a real Google token (200), read via CalDAV REPORT (207) and rendered model-facing results. Only Google reads were tested; no writes were performed. This is a historical read-only verification record.
26
+
27
+ Follows the official [plugin packaging and installation requirements](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md): an ESM entry point, prebuilt `lib/`, `dsh.bundle.patch` and a `cordis.patch.yml` layer. The plugin explicitly injects `tools` and supplies JSON Schema parameters, canonical output and rendering, with no runtime imports of `@deepseek-ai/*` internals. Use Node 22.19 or later within 22.x, or Node 24 or later. Harness is evolving rapidly; the version above is the tested baseline.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ dsh plugin --profile web add dsh-calendar
33
+ ```
34
+
35
+ After installing, restart dsh. The plugin inserts a `calendar` config line into the profile (see this package's cordis.patch.yml). The default provider is custom with no credentials filled in; the plugin still loads, but tools throw a Chinese guidance error when called, prompting you to complete the configuration.
36
+
37
+ ## Uninstall
38
+
39
+ ```bash
40
+ dsh plugin --profile web remove dsh-calendar
41
+ ```
42
+
43
+ Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
44
+
45
+
46
+ ## Configuration
47
+
48
+ **Recommended: configure it in the panel (0.6.0+)**
49
+
50
+ Open「Connection」in the panel toolbar, pick a provider, fill in the address and account, hit **Test connection** (it really lists the next 30 days) and then **Save and enable**. The tools pick the new configuration up on their next call — no restart, no YAML editing.
51
+
52
+ What the panel saves lives in your local `settings.yaml` under the `dsh-calendar` namespace (the password is a secret field: no logs, no exports). If the host has no settings service (or the namespace cannot be registered), the connection is stored in the plugin’s own file `$DSH_HOME/data/dsh-calendar/connection.json` (0600) instead, and the panel says which one is in use. **The YAML remains the base layer**: any field the panel never touched still comes from it, so an existing `cordis.patch.yml` setup needs no migration; a headless host without a settings page still configures through YAML only.
53
+
54
+ Per-provider notes:
55
+
56
+ - **iCloud**: `caldavUrl` looks like `https://caldav.icloud.com/<numeric id>/calendars/<calendar id>/`; use an [app-specific password](https://appleid.apple.com/)
57
+ - **Nextcloud / self-hosted**: server URL + username + calendar name (self-hosted: the full collection URL, keep the trailing slash) and an app password
58
+ - **Google**: OAuth only — Google rejects every Basic password, including app passwords; needs `clientId` / `clientSecret` / `refreshToken` / `calendarId`
59
+
60
+ ### Or: write YAML (advanced / headless)
61
+
62
+ The fields below mirror the panel form one-to-one; whatever the panel never set comes from here.
63
+ All configuration lives in your profile's cordis.patch.yml; override the `calendar` line by id (overriding replaces that line's config wholesale). Common fields:
64
+
65
+ - `provider`: google | icloud | nextcloud | custom
66
+ - `caldavUrl`: full calendar collection URL (required for custom / icloud; google / nextcloud can also override the preset manually)
67
+ - `authMethod`: `basic` | `oauth`; Google defaults to and requires `oauth`, other providers default to `basic`. Opt in explicitly for another OAuth server. Google credentials in the environment never switch a Basic provider to OAuth automatically.
68
+ - `username` / `password`: required only for Basic authentication. Use an app-specific password for iCloud, preferably via `DSH_CALENDAR_PASSWORD`. **Google CalDAV rejects all Basic passwords, including app-specific passwords.**
69
+ - `clientId` / `clientSecret` / `refreshToken`: required for OAuth; also read from `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN`. Non-empty config values take precedence over environment variables. Never commit secrets or tokens to Git.
70
+ - `tokenUrl`: also read from `DSH_CALENDAR_TOKEN_URL`; defaults to `https://oauth2.googleapis.com/token` for Google and is required for other OAuth providers. OAuth tokenUrl and caldavUrl must use HTTPS without embedded credentials, query parameters or fragments.
71
+ - `calendarId`: google-specific, the calendar ID (usually your email)
72
+ - `host` / `user` / `calendar`: nextcloud-specific
73
+ - `proxyUrl`: optional HTTP proxy (e.g. `http://127.0.0.1:7890`); both OAuth token requests and CalDAV requests use this transport
74
+
75
+ ### Google example
76
+
77
+ ```yaml
78
+ - id: calendar
79
+ name: dsh-calendar
80
+ config:
81
+ provider: google
82
+ calendarId: you@gmail.com
83
+ # authMethod: oauth # the default for Google
84
+ # Provide clientId / clientSecret / refreshToken via environment variables
85
+ ```
86
+
87
+ Google's CalDAV collection URL is assembled by the plugin: `https://apidata.googleusercontent.com/caldav/v2/<calendarId>/events`.
88
+
89
+ Set these placeholder values in the same terminal that starts source-run `pnpm dsh` or ordinary `dsh`:
90
+
91
+ ```bash
92
+ export DSH_CALENDAR_CLIENT_ID='your OAuth client ID'
93
+ export DSH_CALENDAR_CLIENT_SECRET='your OAuth client secret'
94
+ export DSH_CALENDAR_REFRESH_TOKEN='your authorized refresh token'
95
+ ```
96
+
97
+ These credentials come from your own Google Cloud OAuth client and a user authorization, not an email app password. Follow the [Google CalDAV setup guide](https://developers.google.com/workspace/calendar/caldav/v2/guide) to enable the API and configure OAuth. Request `https://www.googleapis.com/auth/calendar` for calendar read/write and offline access (`access_type=offline`) to obtain a refresh token; see [Google's offline authorization documentation](https://developers.google.com/identity/protocols/oauth2/web-server#offline). Supply an already authorized refresh token, or mint one with the helper below.
98
+ **Getting a refresh token (one command)**: register the client as a **Desktop app**, then:
99
+
100
+ ```bash
101
+ cd <plugin directory> # the source checkout, or node_modules/dsh-calendar
102
+ node scripts/google-oauth.mjs --client-id <your clientId> --client-secret <your clientSecret>
103
+ ```
104
+
105
+ The script spins up a **one-shot local callback** (`http://127.0.0.1:<random port>/`), opens the browser for consent, exchanges the code and prints the refresh token — no hand-built URLs, no third-party playground.
106
+
107
+ Paste it into the panel’s「Connection」form (provider Google, calendarId = your Gmail address), or use the `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN` environment variables.
108
+
109
+ > While the OAuth consent screen is still in **Testing**, the refresh token lasts 7 days: hit **Publish app** on the OAuth consent screen to make it permanent (no review needed for personal use).
110
+
111
+ **Google CalDAV scope validation (2026-09-08):** for the same account and calendar, `calendar.readonly` allowed token refresh (200) and collection PROPFIND (207), but the event REPORT returned 403. With the `calendar` scope above, REPORT returned 207; a fresh verification process also refreshed the token and read successfully. Use this CalDAV scope configuration and verify an actual `calendar_list` request: successful token acquisition or collection discovery alone does not prove events are readable. This live test performed reads only; Google write operations were not tested.
112
+
113
+ The `calendar` scope permits viewing, editing, sharing and deleting accessible calendars; review that access before granting it. See [Google's scope definitions](https://developers.google.com/workspace/calendar/api/auth). For an external OAuth app in **Testing**, a refresh token with Calendar scopes expires after 7 days. Long-term use needs reauthorization or an appropriate production configuration under Google's requirements; see [refresh token expiration](https://developers.google.com/identity/protocols/oauth2#expiration).
114
+
115
+ Access tokens are cached in memory and checked before each DAV request, with refresh before expiry. Both token and DAV requests inherit the tool call's AbortSignal. A 401 invalidates the token for the next call; **writes are never replayed automatically**. OAuth requests do not follow redirects or send Bearer tokens to cross-origin object hrefs, so configure the final calendar collection URL. Runtime tokens are not written to configuration or logs. If another OAuth provider rotates refresh tokens, supply valid credentials again when restarting.
116
+
117
+ ### iCloud example
118
+
119
+ ```yaml
120
+ - id: calendar
121
+ name: dsh-calendar
122
+ config:
123
+ provider: icloud
124
+ username: you@icloud.com
125
+ caldavUrl: https://caldav.icloud.com/123456789/calendars/<日历ID>/
126
+ # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
127
+ ```
128
+
129
+ iCloud needs the full calendar collection URL (with your user ID and calendar ID); you can find the specific calendar address in the calendar CalDAV settings on icloud.com.
130
+
131
+ ### Nextcloud example
132
+
133
+ ```yaml
134
+ - id: calendar
135
+ name: dsh-calendar
136
+ config:
137
+ provider: nextcloud
138
+ username: alice
139
+ host: https://cloud.example.com
140
+ user: alice
141
+ calendar: personal
142
+ # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
143
+ ```
144
+
145
+ The plugin assembles: `https://cloud.example.com/remote.php/dav/calendars/alice/personal/`.
146
+
147
+ ### Custom CalDAV example
148
+
149
+ ```yaml
150
+ - id: calendar
151
+ name: dsh-calendar
152
+ config:
153
+ provider: custom
154
+ caldavUrl: https://dav.example.com/calendars/me/work/
155
+ username: me
156
+ # password 推荐用环境变量 DSH_CALENDAR_PASSWORD
157
+ ```
158
+
159
+ ## Authentication troubleshooting
160
+
161
+ Google: OAuth 2.0 only. On 401/403, check OAuth authorization, calendar scope and calendar permissions. If refresh and PROPFIND succeed but REPORT returns 403, check that the granted scope is the `calendar` scope above; successful discovery with `calendar.readonly` does not prove events are readable. On refresh failure, verify clientId/clientSecret/refreshToken, re-authorize revoked or expired grants, and check the 7-day Testing lifetime. **Generating another app password cannot fix Google CalDAV authentication.**
162
+
163
+ iCloud: sign in to appleid.apple.com → Sign-In and Security → App-Specific Passwords, generate one and fill it into `password` or `DSH_CALENDAR_PASSWORD`. You cannot use your Apple ID password.
164
+
165
+ Nextcloud / custom Basic servers: check the account, password or provider-issued app token and calendar permissions. `calendar_health` checks configuration only; it neither connects nor proves authorization. Use `calendar_list` to verify the real connection.
166
+
167
+ ## Proxy
168
+
169
+ If your CalDAV server is not directly reachable from your network (some regional or corporate networks block it), set `proxyUrl` in the plugin config, e.g. `http://127.0.0.1:7890`, and restart. Both token refresh and DAV requests use that HTTP proxy; it does not affect other plugins in the same process.
170
+
171
+ ## Tool reference
172
+
173
+ - `calendar_health`: offline provider, endpoint and Basic/OAuth credential-completeness checks; never displays secrets or connects to the server.
174
+ - `calendar_list`: list events in a time range (start/end, ISO 8601; defaults to the next 7 days). Recurring events are expanded by default (`expand` defaults to true, `maxOccurrences` defaults to 30, clamped to 1-200): each occurrence is a separate row with `isOccurrence: true` and `seriesStart`; non-recurring events keep `isOccurrence: false`. With `expand=false`, recurring events are returned as a single original entry with `rrule`. Results are stably sorted by start time.
175
+ - `calendar_create`: create an event (summary/start/end required; description/location/allDay/rrule optional). Validates real calendar dates and `end >= start`.
176
+ - `calendar_update`: edit an event by uid (summary/start/end/description/location/allDay/rrule optional; omitted fields keep their original values, including the recurrence rule and original properties such as ATTENDEE / ORGANIZER / VALARM).
177
+ - `calendar_delete`: delete an event by uid
178
+ - `calendar_search`: search events by keyword within the `start`~`end` window (defaults to one year before/after now; client-side filter over title/description/location/UID, case-insensitive; `limit` defaults to 50, clamped to 1-200, and results are sorted by start time).
179
+
180
+ The stable event identifier `uid` is the CalDAV href (full object URL); `calendar_update` / `calendar_delete` use it.
181
+
182
+ ## Calendar panel in Settings (0.6.0+)
183
+
184
+ Once installed, DSH grows a「Calendar」section in Settings; the chat page also gets a small 📅 button in the bottom-right that opens the same panel (non-modal, Esc closes).
185
+
186
+ - **Three views**: month (6×7 grid, today highlighted, click a cell to create), week (24-hour time grid starting at 07:00, overlapping events side by side) and agenda (grouped by day). The panel **remembers the view you used last**.
187
+ - **Drag to reschedule**: in the week view drag an event up/down to change the time, left/right to change the day (15-minute snapping), and drag its bottom edge to change the duration. A drop preview shows where it lands and turns yellow when it overlaps something (overlaps are allowed, it is only a warning). Keyboard works too: `↑/↓` 15 minutes, `Shift+↑/↓` an hour, `←/→` a day, `Esc` cancels mid-drag. Every move offers a 5-second undo and rolls back (then re-reads the server) if the request fails. **Recurring events and events longer than 24 hours cannot be dragged yet** — edit those in the details drawer.
188
+ - **Click to edit**: open any event in the right-hand drawer — time, duration, a human-readable repeat rule (`FREQ=WEEKLY;COUNT=6` → "weekly (6 times)"), location, notes, one-click uid copy — then edit or delete (deletion asks twice).
189
+ - **Create**: title, date, start/end, all-day, location, notes, repeat (daily/weekly/monthly/yearly, or a raw RRULE).
190
+ - **Conflict notice**: before saving, overlapping events are listed and you are asked whether to continue.
191
+ - **Configurable from the panel itself**: without a CalDAV account the panel shows guidance whose「Connection」button opens the form (test first, then save); a clearly labelled sample is one click away and never pretends to be your real calendar.
192
+ - **Theme and locale aware**: colours come from the host design tokens (`--dsw-*`), so light/dark themes follow automatically; copy follows Settings → General.
193
+ - **Security**: the panel only talks to the plugin’s own `/_dsh/dsh-calendar/settings`, which is loopback-only and re-checks Host, Origin and Content-Type on writes; credentials are never echoed back.
194
+ ## Time and timezone
195
+
196
+ Input and output are uniformly ISO 8601. Timed events are output in UTC (e.g. `2025-01-15T01:00:00Z`); all-day events output `YYYY-MM-DD`. Input may carry a timezone offset (e.g. `2025-01-15T09:00:00+08:00`); the plugin converts to UTC internally for storage.
197
+
198
+
199
+ ## Changelog
200
+
201
+ - **0.5.4 (2026-09-18)**: fixes for `calendar_update` dropping ATTENDEE/VALARM and other original properties, ignored RECURRENCE-ID overrides, `calendar_create` inventing a uid instead of using the server Location, and silent empty expansion results; `calendar_search` gained a time range. 79 tests.
202
+ - **0.5.3 (2026-09-11)**: revalidated against official Harness 0.1.5-rc.1 and refreshed co-load / live-service verification; runtime code unchanged.
203
+ - **0.5.2 and earlier**: see [CHANGELOG.md](CHANGELOG.md).
204
+
205
+ ## Known limitations
206
+
207
+ - Recurring event expansion: calendar_list expands RRULE by default via ICAL.RecurExpansion (`expand=true`), capped by `maxOccurrences`, and raises an explicit error when the iteration budget is exhausted; calendar_search only queries the `start`~`end` window (default one year before/after now) and still returns the original series (not expanded).
208
+ - Single-instance reads vs edit/delete: calendar_list honors RECURRENCE-ID overrides when expanding (an occurrence separately rescheduled/retitled is returned with the override time and fields, including when the original instant is EXDATE-excluded); calendar_update / calendar_delete still operate on the whole recurring series (by uid) and cannot modify or delete just one occurrence.
209
+ - OAuth credentials must be obtained beforehand: refresh-token authentication is supported, but there is no browser login UI / login CLI and runtime tokens are not written back to configuration.
210
+ - Timezone rules: events with TZID (named timezone) are output converted to UTC (Z); all-day boundaries, DST, and other complex timezone rules are not handled finely.
211
+ - No settings-page UI: this round is a node half-body; config only via cordis.patch.yml, no Web settings page.
212
+ - Calendar discovery: iCloud requires manually filling the full calendar collection URL; no principal auto-discovery or multi-calendar selection.
213
+ - Cancellation/timeout: tools use `timeoutMs` (60 seconds) and forward the host AbortSignal into token refresh and DAV network requests. Concurrent calls cancel independently.
214
+
215
+ ## Development
216
+
217
+ ```bash
218
+ pnpm install
219
+ pnpm test # 构建 + node --test
220
+ ```
221
+
222
+ Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).