dsh-calendar 0.3.2 → 0.4.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/LICENSE ADDED
@@ -0,0 +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.
package/README.en.md CHANGED
@@ -1,7 +1,13 @@
1
1
  # dsh-calendar
2
2
 
3
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
+
3
5
  DSH community plugin: read/write calendar events via CalDAV. Provides 5 model-facing tools (calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search), supporting Google / iCloud / Nextcloud and any CalDAV server. This round is a node half-body with no settings-page UI; all configuration goes through the profile's cordis.patch.yml.
4
6
 
7
+ ## Compatibility
8
+
9
+ Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -10,6 +16,15 @@ dsh plugin --profile web add dsh-calendar
10
16
 
11
17
  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.
12
18
 
19
+ ## Uninstall
20
+
21
+ ```bash
22
+ dsh plugin --profile web remove dsh-calendar
23
+ ```
24
+
25
+ Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
26
+
27
+
13
28
  ## Configuration
14
29
 
15
30
  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:
@@ -92,11 +107,11 @@ If your CalDAV server is not directly reachable from your network (some regional
92
107
 
93
108
  ## Tool reference
94
109
 
95
- - `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`
96
- - `calendar_create`: create an event (summary/start/end required; description/location/allDay optional)
97
- - `calendar_update`: edit an event by uid (summary/start/end/description/location/allDay optional; omitted fields keep their original values)
110
+ - `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.
111
+ - `calendar_create`: create an event (summary/start/end required; description/location/allDay/rrule optional). Validates real calendar dates and `end >= start`.
112
+ - `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).
98
113
  - `calendar_delete`: delete an event by uid
99
- - `calendar_search`: search events by keyword (client-side filter over title/description/location/UID, case-insensitive)
114
+ - `calendar_search`: search events by keyword (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).
100
115
 
101
116
  The stable event identifier `uid` is the CalDAV href (full object URL); `calendar_update` / `calendar_delete` use it.
102
117
 
@@ -104,6 +119,14 @@ The stable event identifier `uid` is the CalDAV href (full object URL); `calenda
104
119
 
105
120
  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.
106
121
 
122
+
123
+ ## v0.3.2 improvements
124
+
125
+ - Fix `calendar_update` dropping `rrule` while updating other fields.
126
+ - Validate `end >= start` and reject impossible dates such as `2025-02-30`.
127
+ - Sort `calendar_list` / `calendar_search` output by start time and clamp search `limit` to 1-200.
128
+ - Reset the cached CalDAV client after creation failure so the next tool call can retry.
129
+
107
130
  ## Known limitations
108
131
 
109
132
  - Recurring event expansion: calendar_list expands RRULE by default via ICAL.RecurExpansion (`expand=true`), capped by `maxOccurrences`; calendar_search still returns the original series (not expanded).
@@ -121,4 +144,4 @@ pnpm install
121
144
  pnpm test # 构建 + node --test
122
145
  ```
123
146
 
124
- Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
147
+ Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
package/README.md CHANGED
@@ -6,9 +6,15 @@
6
6
 
7
7
  ![npm version](https://img.shields.io/npm/v/dsh-calendar?label=npm&color=blue) ![npm downloads](https://img.shields.io/npm/dm/dsh-calendar) ![license](https://img.shields.io/npm/l/dsh-calendar) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-calendar?style=social)
8
8
 
9
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
10
+
9
11
 
10
12
  DSH 社区插件:通过 CalDAV 读写日历事件。提供 5 个面向模型的工具(calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search),支持 Google / iCloud / Nextcloud 及任意 CalDAV 服务器。本轮为 node 半身,不含设置页 UI,配置全部走 profile 的 cordis.patch.yml。
11
13
 
14
+ ## 兼容性
15
+
16
+ 在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
17
+
12
18
  ## 安装
13
19
 
14
20
  ```bash
@@ -29,6 +35,15 @@ dsh plugin --profile web add dsh-calendar
29
35
  - `calendarId`:google 专用,日历 ID(通常是你的邮箱)
30
36
  - `host` / `user` / `calendar`:nextcloud 专用
31
37
 
38
+
39
+ ## 卸载
40
+
41
+ ```bash
42
+ dsh plugin --profile web remove dsh-calendar
43
+ ```
44
+
45
+ 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中的对应插件行。
46
+
32
47
  ## 中国用户:特殊代理配置(Google / iCloud)
33
48
 
34
49
  Google 与 iCloud 的 CalDAV 端点在中国大陆**不可直连**,需要配合你常用的梯子/特殊代理使用。插件内置 `proxyUrl` 配置:把 CalDAV 请求路由到**你本机代理客户端的端口**,不影响其他插件,也无需改任何系统设置。
@@ -119,11 +134,11 @@ iCloud:登录 appleid.apple.com → 登录与安全 → App 专用密码,生
119
134
 
120
135
  ## 工具清单
121
136
 
122
- - `calendar_list`:列出某时间段事件(start/end,ISO 8601,缺省未来 7 天)。默认展开重复事件(`expand` 默认 true,`maxOccurrences` 默认 30、clamp 1-200):每个实例独立成行,带 `isOccurrence: true` 与 `seriesStart`;非重复事件保持 `isOccurrence: false`。`expand=false` 时重复事件按原始单条返回并带 `rrule`
123
- - `calendar_create`:新建事件(summary/start/end 必填,description/location/allDay 可选)
124
- - `calendar_update`:按 uid 改事件(summary/start/end/description/location/allDay 可选,未提供保留原值)
137
+ - `calendar_list`:列出某时间段事件(start/end,ISO 8601,缺省未来 7 天)。默认展开重复事件(`expand` 默认 true,`maxOccurrences` 默认 30、clamp 1-200):每个实例独立成行,带 `isOccurrence: true` 与 `seriesStart`;非重复事件保持 `isOccurrence: false`。`expand=false` 时重复事件按原始单条返回并带 `rrule`。结果按开始时间稳定排序
138
+ - `calendar_create`:新建事件(summary/start/end 必填,description/location/allDay/rrule 可选)。严格校验真实日历日期与 `end >= start`
139
+ - `calendar_update`:按 uid 改事件(summary/start/end/description/location/allDay/rrule 可选,未提供保留原值,重复规则不再丢失)
125
140
  - `calendar_delete`:按 uid 删事件
126
- - `calendar_search`:按关键词搜事件(客户端过滤标题/描述/地点/UID,不区分大小写)
141
+ - `calendar_search`:按关键词搜事件(客户端过滤标题/描述/地点/UID,不区分大小写;`limit` 默认 50、clamp 1-200,结果按开始时间排序)
127
142
 
128
143
  事件稳定标识 `uid` 为 CalDAV href(完整对象 URL),`calendar_update` / `calendar_delete` 使用它。
129
144
 
@@ -131,6 +146,14 @@ iCloud:登录 appleid.apple.com → 登录与安全 → App 专用密码,生
131
146
 
132
147
  输入输出统一 ISO 8601。定时事件输出为 UTC(如 `2025-01-15T01:00:00Z`),全天事件输出 `YYYY-MM-DD`。输入可带时区偏移(如 `2025-01-15T09:00:00+08:00`),插件内部转 UTC 存储。
133
148
 
149
+ ## v0.3.2 优化
150
+
151
+ - 修复 `calendar_update` 更新其他字段时丢失 `rrule` 的问题。
152
+ - 更新与新建都会校验 `end >= start`,并拒绝 `2025-02-30` 这类不存在的日期。
153
+ - `calendar_list` / `calendar_search` 输出按开始时间稳定排序;搜索 `limit` clamp 到 1-200。
154
+ - CalDAV 客户端创建失败后清空缓存,下一次调用可自动重试,不再永久复用 rejected promise。
155
+
156
+
134
157
  ## 已知限制
135
158
 
136
159
  - **国内网络**:Google 与 iCloud 的 CalDAV 端点在中国大陆不可直连;可用 `proxyUrl` 走本机梯子/特殊代理,或改用国内可直连的 CalDAV 端点(自建 Nextcloud/Radicale)。
@@ -157,5 +180,4 @@ pnpm test # 构建 + node --test
157
180
 
158
181
  - [dsh-calendar](https://github.com/STARDUSTLC666/dsh-calendar) — CalDAV 日历五件套
159
182
  - [dsh-slack](https://github.com/STARDUSTLC666/dsh-slack) — Slack 通知/收件箱
160
- - [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
161
-
183
+ - [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
package/lib/ical.js CHANGED
@@ -179,7 +179,12 @@ export function expandEventFromICal(data, href, etag, rangeStart, rangeEnd, maxO
179
179
  const rangeEndMs = isoToEpochMs(rangeEnd);
180
180
  const occurrences = [];
181
181
  try {
182
+ const totalIterationCap = Math.max(100000, maxOccurrences * 1000);
183
+ let iterations = 0;
182
184
  while (occurrences.length < maxOccurrences) {
185
+ iterations += 1;
186
+ if (iterations > totalIterationCap)
187
+ break;
183
188
  const next = expansion.next();
184
189
  if (next === null || next === undefined)
185
190
  break;
package/lib/index.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  import { type CalendarConfig } from './config.js';
11
11
  import { type CalendarToolDefinition } from './tools.js';
12
12
  /** cordis 服务注入:apply 里要用 ctx.tools,必须显式声明注入,否则宿主会抛 cannot get property without inject。 */
13
+ export declare const name = "calendar";
13
14
  export declare const inject: string[];
14
15
  /** 插件所需的最小 ctx 面(社区插件不依赖宿主内部类型)。 */
15
16
  export interface CalendarPluginContext {
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import { resolveConfig } from './config.js';
11
11
  import { buildCalendarTools } from './tools.js';
12
12
  /** cordis 服务注入:apply 里要用 ctx.tools,必须显式声明注入,否则宿主会抛 cannot get property without inject。 */
13
+ export const name = 'calendar';
13
14
  export const inject = ['tools'];
14
15
  /**
15
16
  * 插件入口:惰性解析配置并注册五个日历工具。
package/lib/tools.d.ts CHANGED
@@ -26,5 +26,5 @@ export interface CalendarToolDefinition {
26
26
  execute(args: unknown, exec: unknown): Promise<unknown>;
27
27
  timeoutMs?: number;
28
28
  }
29
- /** 构建五个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
29
+ /** 构建六个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
30
30
  export declare function buildCalendarTools(config: CalendarConfig | undefined): CalendarToolDefinition[];
package/lib/tools.js CHANGED
@@ -96,7 +96,7 @@ function buildSearchFilter(query) {
96
96
  return haystack.includes(needle);
97
97
  };
98
98
  }
99
- /** 构建五个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
99
+ /** 构建六个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
100
100
  export function buildCalendarTools(config) {
101
101
  const service = () => new CalendarService(resolveConfig(config));
102
102
  const list = {
@@ -319,5 +319,48 @@ export function buildCalendarTools(config) {
319
319
  },
320
320
  timeoutMs: TIMEOUT_MS,
321
321
  };
322
- return [list, create, update, remove, search];
322
+ const health = {
323
+ name: 'calendar_health',
324
+ description: 'dsh-calendar 自检:检查 CalDAV 配置完整性(服务商/日历地址/账号/密码),不发起网络连接。遇到问题时先运行本工具定位。',
325
+ parameters: compileParameters({}),
326
+ output: {
327
+ schema: { type: 'object', additionalProperties: true },
328
+ render: (_args, value) => {
329
+ const rec = (value ?? {});
330
+ const rawChecks = Array.isArray(rec.checks) ? rec.checks : [];
331
+ const lines = ['dsh-calendar 自检' + (rec.ok === true ? ':正常。' : ':发现问题。')];
332
+ for (const item of rawChecks) {
333
+ const c = (item ?? {});
334
+ lines.push('- ' + String(c.name) + ':' + (c.ok === true ? '✅ ' + String(c.detail ?? '') : '❌ ' + String(c.detail ?? '')));
335
+ }
336
+ return [{ type: 'text', text: lines.join('\n') }];
337
+ },
338
+ },
339
+ async execute() {
340
+ const checks = [];
341
+ let ok = true;
342
+ const provider = (config?.provider ?? 'custom');
343
+ checks.push({ name: '服务商', ok: true, detail: provider });
344
+ const hasUrl = typeof config?.caldavUrl === 'string' && config.caldavUrl.trim() !== '';
345
+ const derivable = provider === 'google' || provider === 'nextcloud' || provider === 'icloud';
346
+ if (hasUrl || derivable) {
347
+ checks.push({ name: '日历地址', ok: true, detail: hasUrl ? '已配置 caldavUrl' : provider + ' 预设可推导' });
348
+ }
349
+ else {
350
+ ok = false;
351
+ checks.push({ name: '日历地址', ok: false, detail: '未配置:请在 profile 的 cordis.patch.yml 里给 calendar 行填 caldavUrl' });
352
+ }
353
+ const hasUser = typeof config?.username === 'string' && config.username.trim() !== '';
354
+ checks.push({ name: '账号', ok: hasUser, detail: hasUser ? '已配置' : '未配置:请填 username(Google/iCloud 为账号邮箱)' });
355
+ if (!hasUser)
356
+ ok = false;
357
+ const hasPass = typeof config?.password === 'string' && config.password.trim() !== '';
358
+ checks.push({ name: '密码', ok: hasPass, detail: hasPass ? '已配置' : '未配置:请填 password 或环境变量 DSH_CALENDAR_PASSWORD(Google/iCloud 用应用专用密码)' });
359
+ if (!hasPass)
360
+ ok = false;
361
+ return { ok, plugin: 'dsh-calendar', checks };
362
+ },
363
+ timeoutMs: TIMEOUT_MS,
364
+ };
365
+ return [list, create, update, remove, search, health];
323
366
  }
package/package.json CHANGED
@@ -1,61 +1,63 @@
1
- {
2
- "name": "dsh-calendar",
3
- "version": "0.3.2",
4
- "description": "DSH 日历工具插件:通过 CalDAV 读写日历事件(Google / iCloud / Nextcloud / 自定义)",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "types": "lib/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/index.d.ts",
11
- "default": "./lib/index.js"
12
- },
13
- "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
15
- },
16
- "files": [
17
- "lib",
18
- "cordis.patch.yml",
19
- "README.md"
20
- ],
21
- "dsh": {
22
- "bundle": {
23
- "patch": "./cordis.patch.yml"
24
- }
25
- },
26
- "keywords": [
27
- "dsh",
28
- "deepseek-harness",
29
- "plugin",
30
- "calendar",
31
- "caldav",
32
- "ical"
33
- ],
34
- "license": "MIT",
35
- "engines": {
36
- "node": ">=18"
37
- },
38
- "dependencies": {
39
- "ical.js": "^2.1.0",
40
- "tsdav": "^2.3.1",
41
- "undici": "^8.10.0"
42
- },
43
- "devDependencies": {
44
- "@types/node": "^24.0.0",
45
- "typescript": "^5.6.0"
46
- },
47
- "repository": {
48
- "type": "git",
49
- "url": "https://github.com/STARDUSTLC666/dsh-calendar"
50
- },
51
- "bugs": {
52
- "url": "https://github.com/STARDUSTLC666/dsh-calendar/issues"
53
- },
54
- "homepage": "https://github.com/STARDUSTLC666/dsh-calendar#readme",
55
- "author": "stardustlc",
56
- "scripts": {
57
- "build": "tsc -p tsconfig.json",
58
- "typecheck": "tsc -p tsconfig.json --noEmit",
59
- "test": "pnpm run build && node --test \"test/*.test.mjs\""
60
- }
61
- }
1
+ {
2
+ "name": "dsh-calendar",
3
+ "version": "0.4.0",
4
+ "description": "DSH 日历工具插件:通过 CalDAV 读写日历事件(Google / iCloud / Nextcloud / 自定义)",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./cordis.patch.yml": "./cordis.patch.yml",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "cordis.patch.yml",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "pnpm run build && node --test \"test/*.test.mjs\"",
25
+ "prepublishOnly": "pnpm run build"
26
+ },
27
+ "dsh": {
28
+ "bundle": {
29
+ "patch": "./cordis.patch.yml"
30
+ }
31
+ },
32
+ "keywords": [
33
+ "dsh",
34
+ "deepseek-harness",
35
+ "plugin",
36
+ "calendar",
37
+ "caldav",
38
+ "ical"
39
+ ],
40
+ "license": "MIT",
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "dependencies": {
45
+ "ical.js": "^2.1.0",
46
+ "tsdav": "^2.3.1",
47
+ "undici": "^8.10.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^24.0.0",
51
+ "typescript": "^5.6.0"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/STARDUSTLC666/dsh-calendar.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/STARDUSTLC666/dsh-calendar/issues"
59
+ },
60
+ "homepage": "https://github.com/STARDUSTLC666/dsh-calendar#readme",
61
+ "author": "stardustlc",
62
+ "packageManager": "pnpm@11.7.0"
63
+ }