dsh-calendar 0.4.0 → 0.4.1

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/README.en.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # dsh-calendar
2
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
+
3
5
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
6
 
5
7
  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.
6
8
 
7
9
  ## Compatibility
8
10
 
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.
11
+ Verified against `@deepseek-ai/dsh@0.1.2-alpha.2` on 2026-08-31. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
10
12
 
11
13
  ## Installation
12
14
 
@@ -120,12 +122,14 @@ The stable event identifier `uid` is the CalDAV href (full object URL); `calenda
120
122
  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.
121
123
 
122
124
 
123
- ## v0.3.2 improvements
125
+ ## Changelog
124
126
 
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.
127
+ - **0.4.0**: new `calendar_health` self-check (one-call CalDAV config health: endpoint/username/password/connection).
128
+ - **0.3.2**:
129
+ - Fix `calendar_update` dropping `rrule` while updating other fields.
130
+ - Validate `end >= start` and reject impossible dates such as `2025-02-30`.
131
+ - Sort `calendar_list` / `calendar_search` output by start time and clamp search `limit` to 1-200.
132
+ - Reset the cached CalDAV client after creation failure so the next tool call can retry.
129
133
 
130
134
  ## Known limitations
131
135
 
@@ -144,4 +148,4 @@ pnpm install
144
148
  pnpm test # 构建 + node --test
145
149
  ```
146
150
 
147
- Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
151
+ Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
package/README.md CHANGED
@@ -13,7 +13,7 @@ DSH 社区插件:通过 CalDAV 读写日历事件。提供 5 个面向模型
13
13
 
14
14
  ## 兼容性
15
15
 
16
- 在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
16
+ 在 `@deepseek-ai/dsh@0.1.2-alpha.2` 上验证(2026-08-31)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
17
17
 
18
18
  ## 安装
19
19
 
@@ -146,12 +146,14 @@ iCloud:登录 appleid.apple.com → 登录与安全 → App 专用密码,生
146
146
 
147
147
  输入输出统一 ISO 8601。定时事件输出为 UTC(如 `2025-01-15T01:00:00Z`),全天事件输出 `YYYY-MM-DD`。输入可带时区偏移(如 `2025-01-15T09:00:00+08:00`),插件内部转 UTC 存储。
148
148
 
149
- ## v0.3.2 优化
149
+ ## 版本记录
150
150
 
151
- - 修复 `calendar_update` 更新其他字段时丢失 `rrule` 的问题。
152
- - 更新与新建都会校验 `end >= start`,并拒绝 `2025-02-30` 这类不存在的日期。
153
- - `calendar_list` / `calendar_search` 输出按开始时间稳定排序;搜索 `limit` clamp 到 1-200。
154
- - CalDAV 客户端创建失败后清空缓存,下一次调用可自动重试,不再永久复用 rejected promise。
151
+ - **0.4.0**:新增 `calendar_health` 自检(CalDAV 配置完整性一键体检:端点/账号/密码/连接)。
152
+ - **0.3.2**:
153
+ - 修复 `calendar_update` 更新其他字段时丢失 `rrule` 的问题。
154
+ - 更新与新建都会校验 `end >= start`,并拒绝 `2025-02-30` 这类不存在的日期。
155
+ - `calendar_list` / `calendar_search` 输出按开始时间稳定排序;搜索 `limit` clamp 到 1-200。
156
+ - CalDAV 客户端创建失败后清空缓存,下一次调用可自动重试,不再永久复用 rejected promise。
155
157
 
156
158
 
157
159
  ## 已知限制
package/lib/caldav.d.ts CHANGED
@@ -23,20 +23,20 @@ export declare class CalendarService {
23
23
  list(startIso: string, endIso: string, options?: {
24
24
  expand?: boolean;
25
25
  maxOccurrences?: number;
26
- }): Promise<CalendarEvent[]>;
26
+ }, signal?: AbortSignal): Promise<CalendarEvent[]>;
27
27
  /** 列出全部事件(客户端过滤用)。 */
28
- all(): Promise<CalendarEvent[]>;
28
+ all(signal?: AbortSignal): Promise<CalendarEvent[]>;
29
29
  private toEvents;
30
30
  /** 列出并展开:每个对象经 expandEventFromICal 展开为若干实例行。 */
31
31
  private toExpandedEvents;
32
32
  /** 按 uid(href)找到服务器对象(含 etag 与原始 data)。 */
33
33
  private findObject;
34
34
  /** 新建事件,返回带 href/uid 的事件。 */
35
- create(fields: EventFields): Promise<CalendarEvent>;
35
+ create(fields: EventFields, signal?: AbortSignal): Promise<CalendarEvent>;
36
36
  /** 按 uid 更新事件;未提供的字段保留原值。 */
37
- update(uid: string, changes: Partial<EventFields>): Promise<CalendarEvent>;
37
+ update(uid: string, changes: Partial<EventFields>, signal?: AbortSignal): Promise<CalendarEvent>;
38
38
  /** 按 uid 删除事件。 */
39
- delete(uid: string): Promise<{
39
+ delete(uid: string, signal?: AbortSignal): Promise<{
40
40
  uid: string;
41
41
  href: string;
42
42
  }>;
package/lib/caldav.js CHANGED
@@ -62,35 +62,45 @@ export class CalendarService {
62
62
  return { url: this.collectionUrl };
63
63
  }
64
64
  /** 列出某时间段内的事件;expand 为 true 时在窗口内展开 RRULE。 */
65
- async list(startIso, endIso, options) {
65
+ async list(startIso, endIso, options, signal) {
66
66
  const expand = options?.expand !== false;
67
67
  const maxOccurrences = options?.maxOccurrences ?? 30;
68
+ signal?.throwIfAborted();
68
69
  try {
69
70
  const client = await this.client();
71
+ signal?.throwIfAborted();
70
72
  const objects = await client.fetchCalendarObjects({
71
73
  calendar: this.calendar(),
72
74
  timeRange: { start: startIso, end: endIso },
73
75
  urlFilter: (url) => typeof url === 'string' && url.length > 0,
76
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
74
77
  });
78
+ signal?.throwIfAborted();
75
79
  return expand
76
80
  ? this.toExpandedEvents(objects, startIso, endIso, maxOccurrences)
77
81
  : this.toEvents(objects);
78
82
  }
79
83
  catch (error) {
84
+ signal?.throwIfAborted();
80
85
  throw translateError(error, '读取日历');
81
86
  }
82
87
  }
83
88
  /** 列出全部事件(客户端过滤用)。 */
84
- async all() {
89
+ async all(signal) {
90
+ signal?.throwIfAborted();
85
91
  try {
86
92
  const client = await this.client();
93
+ signal?.throwIfAborted();
87
94
  const objects = await client.fetchCalendarObjects({
88
95
  calendar: this.calendar(),
89
96
  urlFilter: (url) => typeof url === 'string' && url.length > 0,
97
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
90
98
  });
99
+ signal?.throwIfAborted();
91
100
  return this.toEvents(objects);
92
101
  }
93
102
  catch (error) {
103
+ signal?.throwIfAborted();
94
104
  throw translateError(error, '读取日历');
95
105
  }
96
106
  }
@@ -112,29 +122,39 @@ export class CalendarService {
112
122
  return events;
113
123
  }
114
124
  /** 按 uid(href)找到服务器对象(含 etag 与原始 data)。 */
115
- async findObject(uid) {
125
+ async findObject(uid, signal) {
126
+ signal?.throwIfAborted();
116
127
  const client = await this.client();
128
+ signal?.throwIfAborted();
117
129
  const target = normalizeUrl(uid);
118
130
  const objects = await client.fetchCalendarObjects({
119
131
  calendar: this.calendar(),
120
132
  urlFilter: (url) => normalizeUrl(url) === target,
133
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
121
134
  });
135
+ signal?.throwIfAborted();
122
136
  return objects.find((object) => normalizeUrl(object.url) === target);
123
137
  }
124
138
  /** 新建事件,返回带 href/uid 的事件。 */
125
- async create(fields) {
126
- const iCalString = buildICalString(fields);
127
- const filename = (fields.icalUid ?? generateUid()) + '.ics';
139
+ async create(fields, signal) {
140
+ signal?.throwIfAborted();
141
+ const icalUid = fields.icalUid ?? generateUid();
142
+ const iCalString = buildICalString({ ...fields, icalUid });
143
+ const filename = icalUid + '.ics';
128
144
  try {
129
145
  const client = await this.client();
146
+ signal?.throwIfAborted();
130
147
  const response = await client.createCalendarObject({
131
148
  calendar: this.calendar(),
132
149
  iCalString,
133
150
  filename,
151
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
134
152
  });
153
+ signal?.throwIfAborted();
135
154
  assertOk(response, '新建事件');
136
155
  }
137
156
  catch (error) {
157
+ signal?.throwIfAborted();
138
158
  if (error instanceof CalDAVError)
139
159
  throw error;
140
160
  throw translateError(error, '新建事件');
@@ -146,12 +166,14 @@ export class CalendarService {
146
166
  return event;
147
167
  }
148
168
  /** 按 uid 更新事件;未提供的字段保留原值。 */
149
- async update(uid, changes) {
169
+ async update(uid, changes, signal) {
170
+ signal?.throwIfAborted();
150
171
  let object;
151
172
  try {
152
- object = await this.findObject(uid);
173
+ object = await this.findObject(uid, signal);
153
174
  }
154
175
  catch (error) {
176
+ signal?.throwIfAborted();
155
177
  throw translateError(error, '查找事件');
156
178
  }
157
179
  if (object === undefined) {
@@ -182,12 +204,16 @@ export class CalendarService {
182
204
  const iCalString = buildICalString(merged);
183
205
  try {
184
206
  const client = await this.client();
207
+ signal?.throwIfAborted();
185
208
  const response = await client.updateCalendarObject({
186
209
  calendarObject: { url: object.url, etag: object.etag, data: iCalString },
210
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
187
211
  });
212
+ signal?.throwIfAborted();
188
213
  assertOk(response, '更新事件');
189
214
  }
190
215
  catch (error) {
216
+ signal?.throwIfAborted();
191
217
  if (error instanceof CalDAVError)
192
218
  throw error;
193
219
  throw translateError(error, '更新事件');
@@ -198,12 +224,14 @@ export class CalendarService {
198
224
  return event;
199
225
  }
200
226
  /** 按 uid 删除事件。 */
201
- async delete(uid) {
227
+ async delete(uid, signal) {
228
+ signal?.throwIfAborted();
202
229
  let object;
203
230
  try {
204
- object = await this.findObject(uid);
231
+ object = await this.findObject(uid, signal);
205
232
  }
206
233
  catch (error) {
234
+ signal?.throwIfAborted();
207
235
  throw translateError(error, '查找事件');
208
236
  }
209
237
  if (object === undefined) {
@@ -212,12 +240,16 @@ export class CalendarService {
212
240
  }
213
241
  try {
214
242
  const client = await this.client();
243
+ signal?.throwIfAborted();
215
244
  const response = await client.deleteCalendarObject({
216
245
  calendarObject: { url: object.url, etag: object.etag },
246
+ ...(signal !== undefined ? { fetchOptions: { signal } } : {}),
217
247
  });
248
+ signal?.throwIfAborted();
218
249
  assertOk(response, '删除事件');
219
250
  }
220
251
  catch (error) {
252
+ signal?.throwIfAborted();
221
253
  if (error instanceof CalDAVError)
222
254
  throw error;
223
255
  throw translateError(error, '删除事件');
package/lib/config.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare const NEXTCLOUD_PATH = "/remote.php/dav/calendars/";
47
47
  export declare class ConfigError extends Error {
48
48
  constructor(message: string);
49
49
  }
50
+ export declare const CALENDAR_PROVIDERS: readonly CalendarProvider[];
50
51
  /**
51
52
  * 解析配置为可用的 caldavUrl + 凭证。配置缺失抛出 ConfigError(中文指引)。
52
53
  * @param config - 插件 config(可能 undefined)。
package/lib/config.js CHANGED
@@ -15,7 +15,7 @@ export class ConfigError extends Error {
15
15
  this.name = 'ConfigError';
16
16
  }
17
17
  }
18
- const PROVIDERS = ['google', 'icloud', 'nextcloud', 'custom'];
18
+ export const CALENDAR_PROVIDERS = ['google', 'icloud', 'nextcloud', 'custom'];
19
19
  function nonEmpty(value) {
20
20
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
21
21
  }
@@ -25,7 +25,7 @@ function trimTrailingSlash(value) {
25
25
  function normalizeProvider(value) {
26
26
  if (value === undefined || value === null || value === '')
27
27
  return 'custom';
28
- if (typeof value === 'string' && PROVIDERS.includes(value)) {
28
+ if (typeof value === 'string' && CALENDAR_PROVIDERS.includes(value)) {
29
29
  return value;
30
30
  }
31
31
  throw new ConfigError('dsh-calendar 的 provider 取值不合法:只支持 google / icloud / nextcloud / custom。' +
package/lib/tools.d.ts CHANGED
@@ -27,4 +27,4 @@ export interface CalendarToolDefinition {
27
27
  timeoutMs?: number;
28
28
  }
29
29
  /** 构建六个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
30
- export declare function buildCalendarTools(config: CalendarConfig | undefined): CalendarToolDefinition[];
30
+ export declare function buildCalendarTools(config: CalendarConfig | undefined, env?: NodeJS.ProcessEnv): CalendarToolDefinition[];
package/lib/tools.js CHANGED
@@ -5,13 +5,19 @@
5
5
  * @module dsh-calendar/tools
6
6
  */
7
7
  import { CalendarService } from './caldav.js';
8
- import { resolveConfig } from './config.js';
8
+ import { CALENDAR_PROVIDERS, resolveConfig } from './config.js';
9
9
  import { compileParameters } from './parameters.js';
10
10
  const EVENT_SCHEMA = { type: 'object', additionalProperties: true };
11
11
  const TIMEOUT_MS = 60000;
12
12
  function asRecord(args) {
13
13
  return typeof args === 'object' && args !== null ? args : {};
14
14
  }
15
+ function executionSignal(exec) {
16
+ if (typeof exec !== 'object' || exec === null)
17
+ return undefined;
18
+ const signal = exec.signal;
19
+ return signal instanceof AbortSignal ? signal : undefined;
20
+ }
15
21
  function optionalString(args, key) {
16
22
  const value = args[key];
17
23
  return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
@@ -97,8 +103,8 @@ function buildSearchFilter(query) {
97
103
  };
98
104
  }
99
105
  /** 构建六个工具定义;每个 execute 惰性解析配置,缺失时抛出中文指引。 */
100
- export function buildCalendarTools(config) {
101
- const service = () => new CalendarService(resolveConfig(config));
106
+ export function buildCalendarTools(config, env = process.env) {
107
+ const service = () => new CalendarService(resolveConfig(config, env));
102
108
  const list = {
103
109
  name: 'calendar_list',
104
110
  description: '列出某时间段内的日历事件(默认未来 7 天)。start/end 为 ISO 8601 时间(含时区偏移,如 2025-01-01T09:00:00+08:00),全天事件返回 YYYY-MM-DD。默认展开重复事件(RRULE):每个实例作为独立行返回,start/end 为该次发生时间,并带 isOccurrence=true 与 seriesStart(系列原开始时间);非重复事件保持单行且 isOccurrence=false。expand=false 时重复事件按原始单条返回并带 rrule 字段。maxOccurrences 为每个重复事件的展开次数上限。返回每个事件的稳定标识 uid,供 calendar_update / calendar_delete 使用。',
@@ -128,7 +134,7 @@ export function buildCalendarTools(config) {
128
134
  return [{ type: 'text', text: lines.join('\n') }];
129
135
  },
130
136
  },
131
- async execute(args) {
137
+ async execute(args, exec) {
132
138
  const input = asRecord(args);
133
139
  const now = new Date();
134
140
  const defaultEnd = new Date(now.getTime() + 7 * 24 * 3600 * 1000);
@@ -139,7 +145,7 @@ export function buildCalendarTools(config) {
139
145
  assertTimeRange(start, end);
140
146
  const expand = booleanWithDefault(input, 'expand', true);
141
147
  const maxOccurrences = clampedInteger(input, 'maxOccurrences', 30, 1, 200);
142
- const events = sortEvents(await service().list(start, end, { expand, maxOccurrences }));
148
+ const events = sortEvents(await service().list(start, end, { expand, maxOccurrences }, executionSignal(exec)));
143
149
  return { count: events.length, start, end, events };
144
150
  },
145
151
  timeoutMs: TIMEOUT_MS,
@@ -168,7 +174,7 @@ export function buildCalendarTools(config) {
168
174
  return [{ type: 'text', text: '已新建事件:' + formatEvent(event) }];
169
175
  },
170
176
  },
171
- async execute(args) {
177
+ async execute(args, exec) {
172
178
  const input = asRecord(args);
173
179
  const summary = requiredString(input, 'summary', '事件标题');
174
180
  const start = requiredString(input, 'start', '开始时间');
@@ -189,7 +195,7 @@ export function buildCalendarTools(config) {
189
195
  ...(allDay !== undefined ? { allDay } : {}),
190
196
  ...(rrule !== undefined ? { rrule } : {}),
191
197
  };
192
- const created = await service().create(fields);
198
+ const created = await service().create(fields, executionSignal(exec));
193
199
  return { created };
194
200
  },
195
201
  timeoutMs: TIMEOUT_MS,
@@ -219,7 +225,7 @@ export function buildCalendarTools(config) {
219
225
  return [{ type: 'text', text: '已更新事件:' + formatEvent(event) }];
220
226
  },
221
227
  },
222
- async execute(args) {
228
+ async execute(args, exec) {
223
229
  const input = asRecord(args);
224
230
  const uid = requiredString(input, 'uid', '事件 uid');
225
231
  const changes = {};
@@ -248,7 +254,7 @@ export function buildCalendarTools(config) {
248
254
  const rrule = optionalString(input, 'rrule');
249
255
  if (rrule !== undefined)
250
256
  changes.rrule = rrule;
251
- const updated = await service().update(uid, changes);
257
+ const updated = await service().update(uid, changes, executionSignal(exec));
252
258
  return { updated };
253
259
  },
254
260
  timeoutMs: TIMEOUT_MS,
@@ -275,10 +281,10 @@ export function buildCalendarTools(config) {
275
281
  return [{ type: 'text', text: '已删除事件:uid=' + result.uid }];
276
282
  },
277
283
  },
278
- async execute(args) {
284
+ async execute(args, exec) {
279
285
  const input = asRecord(args);
280
286
  const uid = requiredString(input, 'uid', '事件 uid');
281
- const result = await service().delete(uid);
287
+ const result = await service().delete(uid, executionSignal(exec));
282
288
  return { deleted: true, uid: result.uid, href: result.href };
283
289
  },
284
290
  timeoutMs: TIMEOUT_MS,
@@ -309,11 +315,11 @@ export function buildCalendarTools(config) {
309
315
  return [{ type: 'text', text: lines.join('\n') }];
310
316
  },
311
317
  },
312
- async execute(args) {
318
+ async execute(args, exec) {
313
319
  const input = asRecord(args);
314
320
  const query = requiredString(input, 'query', '搜索关键词');
315
321
  const limit = clampedInteger(input, 'limit', 50, 1, 200);
316
- const all = await service().all();
322
+ const all = await service().all(executionSignal(exec));
317
323
  const matched = sortEvents(all).filter(buildSearchFilter(query)).slice(0, limit);
318
324
  return { query, count: matched.length, events: matched };
319
325
  },
@@ -336,29 +342,41 @@ export function buildCalendarTools(config) {
336
342
  return [{ type: 'text', text: lines.join('\n') }];
337
343
  },
338
344
  },
339
- async execute() {
345
+ async execute(_args, exec) {
346
+ executionSignal(exec)?.throwIfAborted();
340
347
  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
+ const rawProvider = config?.provider;
349
+ const provider = rawProvider === undefined || rawProvider === null || rawProvider === '' ? 'custom' : String(rawProvider);
350
+ const providerOk = CALENDAR_PROVIDERS.includes(provider);
351
+ checks.push({ name: '服务商', ok: providerOk, detail: providerOk ? provider : '不支持 ' + provider + ';只支持 ' + CALENDAR_PROVIDERS.join(' / ') });
352
+ let resolved;
353
+ try {
354
+ resolved = resolveConfig(config, env);
348
355
  }
349
- else {
350
- ok = false;
351
- checks.push({ name: '日历地址', ok: false, detail: '未配置:请在 profile 的 cordis.patch.yml 里给 calendar 行填 caldavUrl' });
356
+ catch {
357
+ // Each individual check below provides the actionable detail.
352
358
  }
353
359
  const hasUser = typeof config?.username === 'string' && config.username.trim() !== '';
360
+ const configuredPassword = typeof config?.password === 'string' && config.password.trim() !== '';
361
+ const envPassword = typeof env.DSH_CALENDAR_PASSWORD === 'string' && env.DSH_CALENDAR_PASSWORD.trim() !== '';
362
+ try {
363
+ const endpoint = resolved ?? resolveConfig({
364
+ ...config,
365
+ username: hasUser ? config.username : '__health_check__',
366
+ password: configuredPassword ? config.password : (envPassword ? env.DSH_CALENDAR_PASSWORD : '__health_check__'),
367
+ }, env);
368
+ checks.push({ name: '日历地址', ok: true, detail: '已解析为 ' + endpoint.caldavUrl });
369
+ }
370
+ catch (error) {
371
+ checks.push({ name: '日历地址', ok: false, detail: error instanceof Error ? error.message : String(error) });
372
+ }
354
373
  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 };
374
+ checks.push({
375
+ name: '密码',
376
+ ok: configuredPassword || envPassword,
377
+ detail: configuredPassword ? '已在插件配置中提供' : envPassword ? '已通过环境变量 DSH_CALENDAR_PASSWORD 提供' : '未配置:请填 password 或环境变量 DSH_CALENDAR_PASSWORD(Google/iCloud 用应用专用密码)',
378
+ });
379
+ return { ok: resolved !== undefined, plugin: 'dsh-calendar', checks };
362
380
  },
363
381
  timeoutMs: TIMEOUT_MS,
364
382
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-calendar",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "DSH 日历工具插件:通过 CalDAV 读写日历事件(Google / iCloud / Nextcloud / 自定义)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",