@zhin.js/plugin-calendar 0.0.0-stage → 0.1.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.md +189 -2
- package/commands/calendar/auth/index.js +26 -0
- package/commands/calendar/auth/index.ts +29 -0
- package/commands/calendar/logout/index.js +18 -0
- package/commands/calendar/logout/index.ts +18 -0
- package/commands/calendar/status/index.js +10 -0
- package/commands/calendar/status/index.ts +10 -0
- package/lib/caldav.d.ts +18 -0
- package/lib/caldav.d.ts.map +1 -0
- package/lib/caldav.js +167 -0
- package/lib/caldav.js.map +1 -0
- package/lib/chat-auth.d.ts +49 -0
- package/lib/chat-auth.d.ts.map +1 -0
- package/lib/chat-auth.js +233 -0
- package/lib/chat-auth.js.map +1 -0
- package/lib/cli/auth.d.ts +3 -0
- package/lib/cli/auth.d.ts.map +1 -0
- package/lib/cli/auth.js +182 -0
- package/lib/cli/auth.js.map +1 -0
- package/lib/config.d.ts +87 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +95 -0
- package/lib/config.js.map +1 -0
- package/lib/credential-store.d.ts +30 -0
- package/lib/credential-store.d.ts.map +1 -0
- package/lib/credential-store.js +42 -0
- package/lib/credential-store.js.map +1 -0
- package/lib/free-time.d.ts +13 -0
- package/lib/free-time.d.ts.map +1 -0
- package/lib/free-time.js +96 -0
- package/lib/free-time.js.map +1 -0
- package/lib/http-callback.d.ts +13 -0
- package/lib/http-callback.d.ts.map +1 -0
- package/lib/http-callback.js +26 -0
- package/lib/http-callback.js.map +1 -0
- package/lib/index.d.ts +32 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +122 -0
- package/lib/index.js.map +1 -0
- package/lib/oauth-auth.d.ts +37 -0
- package/lib/oauth-auth.d.ts.map +1 -0
- package/lib/oauth-auth.js +91 -0
- package/lib/oauth-auth.js.map +1 -0
- package/lib/oauth-session.d.ts +27 -0
- package/lib/oauth-session.d.ts.map +1 -0
- package/lib/oauth-session.js +43 -0
- package/lib/oauth-session.js.map +1 -0
- package/lib/oauth.d.ts +28 -0
- package/lib/oauth.d.ts.map +1 -0
- package/lib/oauth.js +200 -0
- package/lib/oauth.js.map +1 -0
- package/lib/runtime.d.ts +11 -0
- package/lib/runtime.d.ts.map +1 -0
- package/lib/runtime.js +3 -0
- package/lib/runtime.js.map +1 -0
- package/lib/secrets.d.ts +3 -0
- package/lib/secrets.d.ts.map +1 -0
- package/lib/secrets.js +6 -0
- package/lib/secrets.js.map +1 -0
- package/lib/time.d.ts +37 -0
- package/lib/time.d.ts.map +1 -0
- package/lib/time.js +118 -0
- package/lib/time.js.map +1 -0
- package/lib/token-manager.d.ts +71 -0
- package/lib/token-manager.d.ts.map +1 -0
- package/lib/token-manager.js +239 -0
- package/lib/token-manager.js.map +1 -0
- package/lib/tool-errors.d.ts +6 -0
- package/lib/tool-errors.d.ts.map +1 -0
- package/lib/tool-errors.js +17 -0
- package/lib/tool-errors.js.map +1 -0
- package/lib/types.d.ts +28 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +112 -3
- package/plugin.js +72 -0
- package/skills/calendar-scheduling/SKILL.md +38 -0
- package/skills/calendar-scheduling/tools/create_event/index.js +38 -0
- package/skills/calendar-scheduling/tools/create_event/index.ts +41 -0
- package/skills/calendar-scheduling/tools/find_free_time/index.js +46 -0
- package/skills/calendar-scheduling/tools/find_free_time/index.ts +50 -0
- package/skills/calendar-scheduling/tools/list_events/index.js +32 -0
- package/skills/calendar-scheduling/tools/list_events/index.ts +42 -0
- package/src/caldav.ts +198 -0
- package/src/chat-auth.ts +336 -0
- package/src/cli/auth.ts +235 -0
- package/src/config.ts +108 -0
- package/src/credential-store.ts +79 -0
- package/src/free-time.ts +128 -0
- package/src/http-callback.ts +55 -0
- package/src/index.ts +193 -0
- package/src/oauth-auth.ts +145 -0
- package/src/oauth-session.ts +65 -0
- package/src/oauth.ts +298 -0
- package/src/runtime.ts +16 -0
- package/src/secrets.ts +5 -0
- package/src/time.ts +167 -0
- package/src/token-manager.ts +358 -0
- package/src/tool-errors.ts +18 -0
- package/src/types.ts +37 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory OAuth access-token cache with proactive refresh, single-flight,
|
|
3
|
+
* refresh-token rotation handling, and clear invalid_grant errors.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
/** Chat command that (re-)authorizes a provider; shown in user-facing errors. */
|
|
8
|
+
export function authCommandFor(provider) {
|
|
9
|
+
return `calendar auth ${provider}`;
|
|
10
|
+
}
|
|
11
|
+
export class OAuthTokenError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "OAuthTokenError";
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_SKEW_MS = 5 * 60 * 1000;
|
|
20
|
+
export class OAuthTokenManager {
|
|
21
|
+
#provider;
|
|
22
|
+
#clientId;
|
|
23
|
+
#clientSecret;
|
|
24
|
+
#tenant;
|
|
25
|
+
#tokenFile;
|
|
26
|
+
#fetch;
|
|
27
|
+
#now;
|
|
28
|
+
#logger;
|
|
29
|
+
#skewMs;
|
|
30
|
+
#refreshToken;
|
|
31
|
+
#accessToken = "";
|
|
32
|
+
#expiresAt = 0;
|
|
33
|
+
#inflight;
|
|
34
|
+
#tokenStore;
|
|
35
|
+
#onAuthorizationLost;
|
|
36
|
+
#authorizationLostMessage = "";
|
|
37
|
+
constructor(options) {
|
|
38
|
+
if (!options.clientId.trim()) {
|
|
39
|
+
throw new OAuthTokenError("config", `${options.provider}: 缺少 client_id`);
|
|
40
|
+
}
|
|
41
|
+
if (options.provider === "google" && !options.clientSecret?.trim()) {
|
|
42
|
+
throw new OAuthTokenError("config", "google: 缺少 client_secret");
|
|
43
|
+
}
|
|
44
|
+
this.#provider = options.provider;
|
|
45
|
+
this.#clientId = options.clientId.trim();
|
|
46
|
+
this.#clientSecret = (options.clientSecret ?? "").trim();
|
|
47
|
+
this.#tenant = (options.tenant ?? "common").trim() || "common";
|
|
48
|
+
this.#tokenFile = (options.tokenFile ?? "").trim();
|
|
49
|
+
this.#fetch = options.fetchImpl ?? fetch;
|
|
50
|
+
this.#now = options.now ?? Date.now;
|
|
51
|
+
this.#logger = options.logger ?? {
|
|
52
|
+
warn: (message) => console.warn(message),
|
|
53
|
+
};
|
|
54
|
+
this.#skewMs = options.skewMs ?? DEFAULT_SKEW_MS;
|
|
55
|
+
this.#refreshToken = (options.initialRefreshToken ?? "").trim();
|
|
56
|
+
this.#tokenStore = options.tokenStore;
|
|
57
|
+
this.#onAuthorizationLost = options.onAuthorizationLost;
|
|
58
|
+
}
|
|
59
|
+
get provider() {
|
|
60
|
+
return this.#provider;
|
|
61
|
+
}
|
|
62
|
+
/** Whether a refresh token is available (stored or from config). */
|
|
63
|
+
get hasRefreshToken() {
|
|
64
|
+
return this.#refreshToken !== "";
|
|
65
|
+
}
|
|
66
|
+
/** Whether the last refresh attempt was rejected with invalid_grant. */
|
|
67
|
+
get authorizationLost() {
|
|
68
|
+
return this.#authorizationLostMessage !== "";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Loads persisted tokens: token_file overlay first, then the token store,
|
|
72
|
+
* which takes precedence over config/env because it is written by chat auth.
|
|
73
|
+
*/
|
|
74
|
+
async hydrate() {
|
|
75
|
+
await this.hydrateFromFile();
|
|
76
|
+
if (!this.#tokenStore) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const storedRefreshToken = (await this.#tokenStore.load())?.trim() ?? "";
|
|
80
|
+
if (storedRefreshToken) {
|
|
81
|
+
this.#refreshToken = storedRefreshToken;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Replaces the refresh token after a successful chat authorization. */
|
|
85
|
+
setRefreshToken(refreshToken) {
|
|
86
|
+
this.#refreshToken = refreshToken.trim();
|
|
87
|
+
this.#accessToken = "";
|
|
88
|
+
this.#expiresAt = 0;
|
|
89
|
+
this.#authorizationLostMessage = "";
|
|
90
|
+
}
|
|
91
|
+
/** Forgets all tokens held in memory (used by `calendar logout`). */
|
|
92
|
+
clearTokens() {
|
|
93
|
+
this.setRefreshToken("");
|
|
94
|
+
}
|
|
95
|
+
/** Current refresh token (may change after Microsoft rotation). */
|
|
96
|
+
get refreshToken() {
|
|
97
|
+
return this.#refreshToken;
|
|
98
|
+
}
|
|
99
|
+
/** Load optional token file overlay (rotated refresh / cached access). */
|
|
100
|
+
async hydrateFromFile() {
|
|
101
|
+
if (!this.#tokenFile)
|
|
102
|
+
return;
|
|
103
|
+
try {
|
|
104
|
+
const raw = await readFile(this.#tokenFile, "utf8");
|
|
105
|
+
const data = JSON.parse(raw);
|
|
106
|
+
if (typeof data.refresh_token === "string" && data.refresh_token.trim()) {
|
|
107
|
+
this.#refreshToken = data.refresh_token.trim();
|
|
108
|
+
}
|
|
109
|
+
if (typeof data.access_token === "string" &&
|
|
110
|
+
data.access_token.trim() &&
|
|
111
|
+
typeof data.expires_at === "number" &&
|
|
112
|
+
data.expires_at > this.#now() + this.#skewMs) {
|
|
113
|
+
this.#accessToken = data.access_token.trim();
|
|
114
|
+
this.#expiresAt = data.expires_at;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const err = error;
|
|
119
|
+
if (err.code !== "ENOENT") {
|
|
120
|
+
this.#logger.warn(`[calendar] 读取 token_file 失败:${this.#tokenFile}(${err.message})`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async getAccessToken() {
|
|
125
|
+
if (this.#accessToken && this.#now() < this.#expiresAt - this.#skewMs) {
|
|
126
|
+
return this.#accessToken;
|
|
127
|
+
}
|
|
128
|
+
return this.#refreshSingleFlight();
|
|
129
|
+
}
|
|
130
|
+
/** Bypass cache (e.g. after HTTP 401). Still single-flight. */
|
|
131
|
+
async forceRefresh() {
|
|
132
|
+
this.#expiresAt = 0;
|
|
133
|
+
this.#accessToken = "";
|
|
134
|
+
return this.#refreshSingleFlight();
|
|
135
|
+
}
|
|
136
|
+
#refreshSingleFlight() {
|
|
137
|
+
if (this.#inflight)
|
|
138
|
+
return this.#inflight;
|
|
139
|
+
this.#inflight = this.#refresh().finally(() => {
|
|
140
|
+
this.#inflight = undefined;
|
|
141
|
+
});
|
|
142
|
+
return this.#inflight;
|
|
143
|
+
}
|
|
144
|
+
async #refresh() {
|
|
145
|
+
if (this.#authorizationLostMessage) {
|
|
146
|
+
throw new OAuthTokenError("invalid_grant", this.#authorizationLostMessage);
|
|
147
|
+
}
|
|
148
|
+
if (!this.#refreshToken) {
|
|
149
|
+
throw new OAuthTokenError("not_authorized", `${this.#provider}: 日历尚未授权。请 master 在聊天中发送「${authCommandFor(this.#provider)}」完成授权。`);
|
|
150
|
+
}
|
|
151
|
+
const body = new URLSearchParams({
|
|
152
|
+
client_id: this.#clientId,
|
|
153
|
+
refresh_token: this.#refreshToken,
|
|
154
|
+
grant_type: "refresh_token",
|
|
155
|
+
});
|
|
156
|
+
if (this.#clientSecret)
|
|
157
|
+
body.set("client_secret", this.#clientSecret);
|
|
158
|
+
if (this.#provider === "microsoft") {
|
|
159
|
+
body.set("scope", "offline_access Calendars.ReadWrite User.Read");
|
|
160
|
+
}
|
|
161
|
+
const url = this.#provider === "google"
|
|
162
|
+
? "https://oauth2.googleapis.com/token"
|
|
163
|
+
: `https://login.microsoftonline.com/${this.#tenant}/oauth2/v2.0/token`;
|
|
164
|
+
const response = await this.#fetch(url, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
167
|
+
body,
|
|
168
|
+
});
|
|
169
|
+
const text = await response.text();
|
|
170
|
+
let data = {};
|
|
171
|
+
try {
|
|
172
|
+
data = text ? JSON.parse(text) : {};
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
data = {};
|
|
176
|
+
}
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const errCode = String(data.error ?? "");
|
|
179
|
+
const errDesc = String(data.error_description ?? text.slice(0, 200));
|
|
180
|
+
if (errCode === "invalid_grant" || /invalid_grant/i.test(errDesc)) {
|
|
181
|
+
const message = `${this.#provider}: 日历授权已失效或被撤销(invalid_grant)。请 master 在聊天中发送「${authCommandFor(this.#provider)}」重新授权。详情:${errDesc}`;
|
|
182
|
+
this.#authorizationLostMessage = message;
|
|
183
|
+
await this.#notifyAuthorizationLost(message);
|
|
184
|
+
throw new OAuthTokenError("invalid_grant", message);
|
|
185
|
+
}
|
|
186
|
+
throw new OAuthTokenError(errCode || "http_error", `${this.#provider}: token 刷新失败 HTTP ${response.status}:${errDesc || response.statusText}`);
|
|
187
|
+
}
|
|
188
|
+
const accessToken = String(data.access_token ?? "");
|
|
189
|
+
if (!accessToken) {
|
|
190
|
+
throw new OAuthTokenError("response", `${this.#provider}: 刷新响应缺少 access_token`);
|
|
191
|
+
}
|
|
192
|
+
const expiresIn = Number(data.expires_in ?? 3600);
|
|
193
|
+
this.#accessToken = accessToken;
|
|
194
|
+
this.#expiresAt = this.#now() + Math.max(60, expiresIn) * 1000;
|
|
195
|
+
const rotated = typeof data.refresh_token === "string" ? data.refresh_token.trim() : "";
|
|
196
|
+
if (rotated && rotated !== this.#refreshToken) {
|
|
197
|
+
this.#refreshToken = rotated;
|
|
198
|
+
if (this.#tokenStore) {
|
|
199
|
+
await this.#tokenStore.save(rotated);
|
|
200
|
+
this.#logger.warn(`[calendar] ${this.#provider}: 提供方返回了新的 refresh_token,已写入插件数据库`);
|
|
201
|
+
}
|
|
202
|
+
else if (this.#tokenFile) {
|
|
203
|
+
await this.#persist();
|
|
204
|
+
this.#logger.warn(`[calendar] ${this.#provider}: 提供方返回了新的 refresh_token,已写入 token_file:${this.#tokenFile}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.#logger.warn(`[calendar] ${this.#provider}: 提供方返回了新的 refresh_token。请立刻更新环境变量中的 refresh_token(当前未配置 token_file,内存中的新值进程重启后会丢失):\n${rotated}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (this.#tokenFile) {
|
|
211
|
+
await this.#persist();
|
|
212
|
+
}
|
|
213
|
+
return this.#accessToken;
|
|
214
|
+
}
|
|
215
|
+
async #notifyAuthorizationLost(message) {
|
|
216
|
+
if (!this.#onAuthorizationLost) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await this.#onAuthorizationLost(this.#provider, message);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
224
|
+
this.#logger.warn(`[calendar] 授权失效通知发送失败:${reason}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async #persist() {
|
|
228
|
+
if (!this.#tokenFile)
|
|
229
|
+
return;
|
|
230
|
+
const payload = {
|
|
231
|
+
refresh_token: this.#refreshToken,
|
|
232
|
+
access_token: this.#accessToken || undefined,
|
|
233
|
+
expires_at: this.#expiresAt || undefined,
|
|
234
|
+
};
|
|
235
|
+
await mkdir(path.dirname(this.#tokenFile), { recursive: true });
|
|
236
|
+
await writeFile(this.#tokenFile, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=token-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../src/token-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs a calendar tool body and turns OAuth authorization failures into a
|
|
3
|
+
* readable reply (e.g. "请 master 发送 calendar auth google") instead of a crash.
|
|
4
|
+
*/
|
|
5
|
+
export declare function runCalendarTool(operation: () => Promise<string>): Promise<string>;
|
|
6
|
+
//# sourceMappingURL=tool-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-errors.d.ts","sourceRoot":"","sources":["../src/tool-errors.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CASjB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OAuthTokenError } from "./token-manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Runs a calendar tool body and turns OAuth authorization failures into a
|
|
4
|
+
* readable reply (e.g. "请 master 发送 calendar auth google") instead of a crash.
|
|
5
|
+
*/
|
|
6
|
+
export async function runCalendarTool(operation) {
|
|
7
|
+
try {
|
|
8
|
+
return await operation();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error instanceof OAuthTokenError) {
|
|
12
|
+
return error.message;
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=tool-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-errors.js","sourceRoot":"","sources":["../src/tool-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAgC;IAEhC,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;YACrC,OAAO,KAAK,CAAC,OAAO,CAAC;QACvB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Calendar event as returned by every provider. */
|
|
2
|
+
export interface CalendarEvent {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly start: string;
|
|
6
|
+
readonly end: string;
|
|
7
|
+
readonly location?: string;
|
|
8
|
+
readonly description?: string;
|
|
9
|
+
readonly all_day?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateEventInput {
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly start: Date;
|
|
14
|
+
readonly end: Date;
|
|
15
|
+
readonly location?: string;
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly all_day?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface FreeSlot {
|
|
20
|
+
readonly start: string;
|
|
21
|
+
readonly end: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CalendarProvider {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
listEvents(from: Date, to: Date, signal?: AbortSignal): Promise<CalendarEvent[]>;
|
|
26
|
+
createEvent(input: CreateEventInput, signal?: AbortSignal): Promise<CalendarEvent>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,UAAU,CACR,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,IAAI,EACR,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5B,WAAW,CACT,KAAK,EAAE,gBAAgB,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAAC;CAC3B"}
|
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,115 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-calendar",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CalDAV / Google / Microsoft calendar tools for Zhin Agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"zhin-calendar-auth": "./lib/cli/auth.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"development": "./src/index.ts",
|
|
15
|
+
"import": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"plugin.js",
|
|
21
|
+
"commands",
|
|
22
|
+
"src",
|
|
23
|
+
"lib",
|
|
24
|
+
"skills",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"zhin",
|
|
30
|
+
"zhin.js",
|
|
31
|
+
"plugin",
|
|
32
|
+
"calendar",
|
|
33
|
+
"caldav"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"author": {
|
|
37
|
+
"name": "lc-cn",
|
|
38
|
+
"email": "admin@liucl.cn",
|
|
39
|
+
"url": "https://github.com/lc-cn"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@zhin.js/host-http": "1.1.4",
|
|
43
|
+
"@zhin.js/im-contract": "1.1.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"vitest": "^4.1.10",
|
|
48
|
+
"zod": "^4.4.3",
|
|
49
|
+
"@zhin.js/command": "1.1.5",
|
|
50
|
+
"@zhin.js/skill": "1.1.4",
|
|
51
|
+
"@zhin.js/tool": "1.1.4",
|
|
52
|
+
"zhin.js": "1.1.7"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"zod": "^4.0.0",
|
|
56
|
+
"@zhin.js/skill": "^1.1.4",
|
|
57
|
+
"@zhin.js/command": "^1.1.5",
|
|
58
|
+
"@zhin.js/tool": "^1.1.4",
|
|
59
|
+
"zhin.js": "^1.1.7"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@zhin.js/command": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@zhin.js/skill": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@zhin.js/tool": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"zod": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "git+https://github.com/zhinjs/zhin.git",
|
|
78
|
+
"directory": "plugins/utils/calendar"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public",
|
|
82
|
+
"registry": "https://registry.npmjs.org"
|
|
83
|
+
},
|
|
84
|
+
"engines": {
|
|
85
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
86
|
+
},
|
|
87
|
+
"zhin": {
|
|
88
|
+
"protocol": 1,
|
|
89
|
+
"type": "plugin",
|
|
90
|
+
"entry": "./plugin.js",
|
|
91
|
+
"engine": "^1.0.0",
|
|
92
|
+
"runtime": "trusted",
|
|
93
|
+
"features": [
|
|
94
|
+
{
|
|
95
|
+
"package": "@zhin.js/command",
|
|
96
|
+
"api": "^1.0.0"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"package": "@zhin.js/tool",
|
|
100
|
+
"api": "^1.0.0"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"package": "@zhin.js/skill",
|
|
104
|
+
"api": "^1.0.0"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"plugins": []
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "tsc",
|
|
111
|
+
"check": "tsc -p tsconfig.check.json",
|
|
112
|
+
"test": "vitest run --root ../../.. plugins/utils/calendar/tests",
|
|
113
|
+
"auth": "tsx src/cli/auth.ts"
|
|
114
|
+
}
|
|
6
115
|
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { databaseHostToken, definePlugin, outboundHostToken, } from "zhin.js";
|
|
3
|
+
import { httpHostToken } from "@zhin.js/host-http";
|
|
4
|
+
import { assertCalendarTimezone, calendarConfig, createCalendarProvider, createOAuthTokenManagers, } from "./lib/index.js";
|
|
5
|
+
import { buildRedirectUri, CalendarAuthService, DEFAULT_OAUTH_CALLBACK_PATH, localOriginFromAddress, } from "./lib/chat-auth.js";
|
|
6
|
+
import { CalendarCredentialStore, } from "./lib/credential-store.js";
|
|
7
|
+
import { registerOAuthCallbackRoute } from "./lib/http-callback.js";
|
|
8
|
+
import { calendarRuntimeToken } from "./lib/runtime.js";
|
|
9
|
+
export default definePlugin({
|
|
10
|
+
name: "calendar",
|
|
11
|
+
config: calendarConfig,
|
|
12
|
+
metadata: { displayName: "日历" },
|
|
13
|
+
setup(context) {
|
|
14
|
+
const config = context.config.get();
|
|
15
|
+
assertCalendarTimezone(config.timezone);
|
|
16
|
+
const resources = context.resources;
|
|
17
|
+
const credentials = resources.has(databaseHostToken)
|
|
18
|
+
? new CalendarCredentialStore(resources.use(databaseHostToken)
|
|
19
|
+
.state)
|
|
20
|
+
: undefined;
|
|
21
|
+
const outbound = resources.has(outboundHostToken)
|
|
22
|
+
? resources.use(outboundHostToken)
|
|
23
|
+
: undefined;
|
|
24
|
+
const http = resources.has(httpHostToken)
|
|
25
|
+
? resources.use(httpHostToken)
|
|
26
|
+
: undefined;
|
|
27
|
+
const notify = outbound?.sendTarget
|
|
28
|
+
? async (target, text) => {
|
|
29
|
+
await outbound.sendTarget(target, text);
|
|
30
|
+
}
|
|
31
|
+
: undefined;
|
|
32
|
+
const callbackPath = config.oauth_callback_path?.trim() || DEFAULT_OAUTH_CALLBACK_PATH;
|
|
33
|
+
const resolveRedirectUri = () => {
|
|
34
|
+
const configuredBase = config.oauth_redirect_base_url?.trim() ?? "";
|
|
35
|
+
if (configuredBase) {
|
|
36
|
+
return buildRedirectUri(configuredBase, callbackPath);
|
|
37
|
+
}
|
|
38
|
+
const address = http?.address;
|
|
39
|
+
if (!address) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return buildRedirectUri(localOriginFromAddress(address), callbackPath);
|
|
43
|
+
};
|
|
44
|
+
let auth;
|
|
45
|
+
const managers = createOAuthTokenManagers(config, {
|
|
46
|
+
credentials,
|
|
47
|
+
onAuthorizationLost: (provider, message) => auth?.notifyAuthorizationLost(provider, message),
|
|
48
|
+
});
|
|
49
|
+
auth = new CalendarAuthService({
|
|
50
|
+
config,
|
|
51
|
+
managers,
|
|
52
|
+
credentials,
|
|
53
|
+
notify,
|
|
54
|
+
resolveRedirectUri,
|
|
55
|
+
});
|
|
56
|
+
const provider = createCalendarProvider(config, { managers });
|
|
57
|
+
context.resources.provide(calendarRuntimeToken, {
|
|
58
|
+
config,
|
|
59
|
+
provider,
|
|
60
|
+
auth: auth,
|
|
61
|
+
});
|
|
62
|
+
if (config.provider !== "none" && !provider) {
|
|
63
|
+
console.warn("[calendar] provider 已配置但凭据为空,日历工具将不可用;Google/Microsoft 请先配置 client_id,再在聊天中发送 calendar auth <provider>");
|
|
64
|
+
}
|
|
65
|
+
const releaseRoute = http
|
|
66
|
+
? registerOAuthCallbackRoute(http, callbackPath, (searchParams) => auth.handleCallback(searchParams))
|
|
67
|
+
: undefined;
|
|
68
|
+
return () => {
|
|
69
|
+
releaseRoute?.();
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: calendar-scheduling
|
|
3
|
+
description: >-
|
|
4
|
+
查询日历空闲、列出事件并在确认后创建日程。默认时区来自插件配置(默认 Asia/Manila)。
|
|
5
|
+
Use when the user mentions meetings, availability, calendar, 约时间, 日程.
|
|
6
|
+
keywords:
|
|
7
|
+
- 日历
|
|
8
|
+
- 日程
|
|
9
|
+
- calendar
|
|
10
|
+
- 约时间
|
|
11
|
+
- free time
|
|
12
|
+
- 有空
|
|
13
|
+
tags:
|
|
14
|
+
- calendar
|
|
15
|
+
tools:
|
|
16
|
+
- list_events
|
|
17
|
+
- find_free_time
|
|
18
|
+
- create_event
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# 日历安排
|
|
22
|
+
|
|
23
|
+
## 何时使用
|
|
24
|
+
|
|
25
|
+
- 查某段时间有没有会、找空档、创建日程
|
|
26
|
+
|
|
27
|
+
## 工作流
|
|
28
|
+
|
|
29
|
+
1. 先确认时区(配置项 `timezone`,默认 `Asia/Manila`)和日期范围。
|
|
30
|
+
2. `list_events` 看已有安排;需要空档时用 `find_free_time`(尊重 `work_day_start` / `work_day_end`)。
|
|
31
|
+
3. **创建前复述**:标题、开始、结束、地点、时区,等用户明确同意后再调 `create_event`(仅 master,且始终要求批准)。
|
|
32
|
+
4. 若日历未启用,告诉用户去配置 CalDAV / Google / Microsoft,不要假装已创建。
|
|
33
|
+
|
|
34
|
+
## 注意
|
|
35
|
+
|
|
36
|
+
- 没有时区偏移的时间按配置时区解释。
|
|
37
|
+
- Google / Microsoft 使用 refresh_token(环境变量);用 `pnpm --filter @zhin.js/plugin-calendar auth google|microsoft` 获取。access token 仅内存缓存。
|
|
38
|
+
- 操作哪本日历由配置 `calendar_id` 决定(Google 默认 `primary`,Outlook 默认账户主日历)。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from "@zhin.js/tool";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { calendarRuntimeToken } from "../../../../lib/runtime.js";
|
|
5
|
+
import { runCalendarTool } from "../../../../lib/tool-errors.js";
|
|
6
|
+
import { parseDateTimeInZone } from "../../../../lib/time.js";
|
|
7
|
+
export default defineAgentTool({
|
|
8
|
+
description: "Create a calendar event. Always confirm title, start, end and timezone with the user first.",
|
|
9
|
+
requiresApproval: "always",
|
|
10
|
+
permissions: ["role(master)"],
|
|
11
|
+
tags: ["calendar"],
|
|
12
|
+
keywords: ["创建日程", "约时间", "create event"],
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
title: z.string().min(1).max(200),
|
|
15
|
+
start: z.string().describe("Start YYYY-MM-DDTHH:mm"),
|
|
16
|
+
end: z.string().describe("End YYYY-MM-DDTHH:mm"),
|
|
17
|
+
location: z.string().max(200).optional(),
|
|
18
|
+
description: z.string().max(2000).optional(),
|
|
19
|
+
all_day: z.boolean().optional(),
|
|
20
|
+
}),
|
|
21
|
+
async execute(input, context) {
|
|
22
|
+
return runCalendarTool(async () => {
|
|
23
|
+
const runtime = context.use(calendarRuntimeToken);
|
|
24
|
+
if (!runtime.provider)
|
|
25
|
+
return "日历未启用:请先配置 provider 与凭据";
|
|
26
|
+
const timezone = runtime.config.timezone;
|
|
27
|
+
const event = await runtime.provider.createEvent({
|
|
28
|
+
title: input.title,
|
|
29
|
+
start: parseDateTimeInZone(input.start, timezone),
|
|
30
|
+
end: parseDateTimeInZone(input.end, timezone),
|
|
31
|
+
location: input.location,
|
|
32
|
+
description: input.description,
|
|
33
|
+
all_day: input.all_day,
|
|
34
|
+
}, context.signal);
|
|
35
|
+
return `已创建事件 [${event.id}]\n${event.title}\n${event.start} → ${event.end}(${timezone})`;
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineAgentTool } from "@zhin.js/tool";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { calendarRuntimeToken } from "../../../../src/runtime.js";
|
|
4
|
+
import { runCalendarTool } from "../../../../src/tool-errors.js";
|
|
5
|
+
import { parseDateTimeInZone } from "../../../../src/time.js";
|
|
6
|
+
|
|
7
|
+
export default defineAgentTool({
|
|
8
|
+
description:
|
|
9
|
+
"Create a calendar event. Always confirm title, start, end and timezone with the user first.",
|
|
10
|
+
requiresApproval: "always",
|
|
11
|
+
permissions: ["role(master)"],
|
|
12
|
+
tags: ["calendar"],
|
|
13
|
+
keywords: ["创建日程", "约时间", "create event"],
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
title: z.string().min(1).max(200),
|
|
16
|
+
start: z.string().describe("Start YYYY-MM-DDTHH:mm"),
|
|
17
|
+
end: z.string().describe("End YYYY-MM-DDTHH:mm"),
|
|
18
|
+
location: z.string().max(200).optional(),
|
|
19
|
+
description: z.string().max(2000).optional(),
|
|
20
|
+
all_day: z.boolean().optional(),
|
|
21
|
+
}),
|
|
22
|
+
async execute(input, context) {
|
|
23
|
+
return runCalendarTool(async () => {
|
|
24
|
+
const runtime = context.use(calendarRuntimeToken);
|
|
25
|
+
if (!runtime.provider) return "日历未启用:请先配置 provider 与凭据";
|
|
26
|
+
const timezone = runtime.config.timezone;
|
|
27
|
+
const event = await runtime.provider.createEvent(
|
|
28
|
+
{
|
|
29
|
+
title: input.title,
|
|
30
|
+
start: parseDateTimeInZone(input.start, timezone),
|
|
31
|
+
end: parseDateTimeInZone(input.end, timezone),
|
|
32
|
+
location: input.location,
|
|
33
|
+
description: input.description,
|
|
34
|
+
all_day: input.all_day,
|
|
35
|
+
},
|
|
36
|
+
context.signal
|
|
37
|
+
);
|
|
38
|
+
return `已创建事件 [${event.id}]\n${event.title}\n${event.start} → ${event.end}(${timezone})`;
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|