cc-lark 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/.github/workflows/ci.yml +47 -0
- package/.github/workflows/release.yml +47 -0
- package/.github/workflows/sync-upstream.yml +127 -0
- package/.prettierrc.json +7 -0
- package/README.md +214 -0
- package/dist/core/api-error.d.ts +193 -0
- package/dist/core/api-error.d.ts.map +1 -0
- package/dist/core/api-error.js +263 -0
- package/dist/core/api-error.js.map +1 -0
- package/dist/core/auth-errors.d.ts +13 -0
- package/dist/core/auth-errors.d.ts.map +1 -0
- package/dist/core/auth-errors.js +14 -0
- package/dist/core/auth-errors.js.map +1 -0
- package/dist/core/config.d.ts +60 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +115 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/device-flow.d.ts +80 -0
- package/dist/core/device-flow.d.ts.map +1 -0
- package/dist/core/device-flow.js +231 -0
- package/dist/core/device-flow.js.map +1 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +16 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lark-client.d.ts +136 -0
- package/dist/core/lark-client.d.ts.map +1 -0
- package/dist/core/lark-client.js +315 -0
- package/dist/core/lark-client.js.map +1 -0
- package/dist/core/token-store.d.ts +67 -0
- package/dist/core/token-store.d.ts.map +1 -0
- package/dist/core/token-store.js +215 -0
- package/dist/core/token-store.js.map +1 -0
- package/dist/core/types.d.ts +286 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +11 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/uat-client.d.ts +64 -0
- package/dist/core/uat-client.d.ts.map +1 -0
- package/dist/core/uat-client.js +227 -0
- package/dist/core/uat-client.js.map +1 -0
- package/dist/core/version.d.ts +26 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +50 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/bitable/app.d.ts +20 -0
- package/dist/tools/bitable/app.d.ts.map +1 -0
- package/dist/tools/bitable/app.js +301 -0
- package/dist/tools/bitable/app.js.map +1 -0
- package/dist/tools/bitable/field.d.ts +19 -0
- package/dist/tools/bitable/field.d.ts.map +1 -0
- package/dist/tools/bitable/field.js +315 -0
- package/dist/tools/bitable/field.js.map +1 -0
- package/dist/tools/bitable/index.d.ts +21 -0
- package/dist/tools/bitable/index.d.ts.map +1 -0
- package/dist/tools/bitable/index.js +39 -0
- package/dist/tools/bitable/index.js.map +1 -0
- package/dist/tools/bitable/record.d.ts +22 -0
- package/dist/tools/bitable/record.d.ts.map +1 -0
- package/dist/tools/bitable/record.js +434 -0
- package/dist/tools/bitable/record.js.map +1 -0
- package/dist/tools/bitable/table.d.ts +21 -0
- package/dist/tools/bitable/table.d.ts.map +1 -0
- package/dist/tools/bitable/table.js +361 -0
- package/dist/tools/bitable/table.js.map +1 -0
- package/dist/tools/calendar/calendar.d.ts +18 -0
- package/dist/tools/calendar/calendar.d.ts.map +1 -0
- package/dist/tools/calendar/calendar.js +192 -0
- package/dist/tools/calendar/calendar.js.map +1 -0
- package/dist/tools/calendar/event.d.ts +20 -0
- package/dist/tools/calendar/event.d.ts.map +1 -0
- package/dist/tools/calendar/event.js +465 -0
- package/dist/tools/calendar/event.js.map +1 -0
- package/dist/tools/calendar/index.d.ts +19 -0
- package/dist/tools/calendar/index.d.ts.map +1 -0
- package/dist/tools/calendar/index.js +37 -0
- package/dist/tools/calendar/index.js.map +1 -0
- package/dist/tools/chat/chat.d.ts +11 -0
- package/dist/tools/chat/chat.d.ts.map +1 -0
- package/dist/tools/chat/chat.js +106 -0
- package/dist/tools/chat/chat.js.map +1 -0
- package/dist/tools/chat/index.d.ts +11 -0
- package/dist/tools/chat/index.d.ts.map +1 -0
- package/dist/tools/chat/index.js +20 -0
- package/dist/tools/chat/index.js.map +1 -0
- package/dist/tools/chat/members.d.ts +9 -0
- package/dist/tools/chat/members.d.ts.map +1 -0
- package/dist/tools/chat/members.js +80 -0
- package/dist/tools/chat/members.js.map +1 -0
- package/dist/tools/common/get-user.d.ts +11 -0
- package/dist/tools/common/get-user.d.ts.map +1 -0
- package/dist/tools/common/get-user.js +112 -0
- package/dist/tools/common/get-user.js.map +1 -0
- package/dist/tools/common/index.d.ts +11 -0
- package/dist/tools/common/index.d.ts.map +1 -0
- package/dist/tools/common/index.js +20 -0
- package/dist/tools/common/index.js.map +1 -0
- package/dist/tools/common/search-user.d.ts +9 -0
- package/dist/tools/common/search-user.d.ts.map +1 -0
- package/dist/tools/common/search-user.js +88 -0
- package/dist/tools/common/search-user.js.map +1 -0
- package/dist/tools/doc/create.d.ts +17 -0
- package/dist/tools/doc/create.d.ts.map +1 -0
- package/dist/tools/doc/create.js +159 -0
- package/dist/tools/doc/create.js.map +1 -0
- package/dist/tools/doc/fetch.d.ts +17 -0
- package/dist/tools/doc/fetch.d.ts.map +1 -0
- package/dist/tools/doc/fetch.js +123 -0
- package/dist/tools/doc/fetch.js.map +1 -0
- package/dist/tools/doc/index.d.ts +21 -0
- package/dist/tools/doc/index.d.ts.map +1 -0
- package/dist/tools/doc/index.js +33 -0
- package/dist/tools/doc/index.js.map +1 -0
- package/dist/tools/doc/shared.d.ts +69 -0
- package/dist/tools/doc/shared.d.ts.map +1 -0
- package/dist/tools/doc/shared.js +172 -0
- package/dist/tools/doc/shared.js.map +1 -0
- package/dist/tools/doc/update.d.ts +25 -0
- package/dist/tools/doc/update.d.ts.map +1 -0
- package/dist/tools/doc/update.js +208 -0
- package/dist/tools/doc/update.js.map +1 -0
- package/dist/tools/drive/file.d.ts +13 -0
- package/dist/tools/drive/file.d.ts.map +1 -0
- package/dist/tools/drive/file.js +212 -0
- package/dist/tools/drive/file.js.map +1 -0
- package/dist/tools/drive/index.d.ts +12 -0
- package/dist/tools/drive/index.d.ts.map +1 -0
- package/dist/tools/drive/index.js +25 -0
- package/dist/tools/drive/index.js.map +1 -0
- package/dist/tools/im/format-messages.d.ts +99 -0
- package/dist/tools/im/format-messages.d.ts.map +1 -0
- package/dist/tools/im/format-messages.js +277 -0
- package/dist/tools/im/format-messages.js.map +1 -0
- package/dist/tools/im/helpers.d.ts +53 -0
- package/dist/tools/im/helpers.d.ts.map +1 -0
- package/dist/tools/im/helpers.js +85 -0
- package/dist/tools/im/helpers.js.map +1 -0
- package/dist/tools/im/index.d.ts +25 -0
- package/dist/tools/im/index.d.ts.map +1 -0
- package/dist/tools/im/index.js +44 -0
- package/dist/tools/im/index.js.map +1 -0
- package/dist/tools/im/message-read.d.ts +19 -0
- package/dist/tools/im/message-read.d.ts.map +1 -0
- package/dist/tools/im/message-read.js +526 -0
- package/dist/tools/im/message-read.js.map +1 -0
- package/dist/tools/im/message.d.ts +22 -0
- package/dist/tools/im/message.d.ts.map +1 -0
- package/dist/tools/im/message.js +233 -0
- package/dist/tools/im/message.js.map +1 -0
- package/dist/tools/im/resource.d.ts +19 -0
- package/dist/tools/im/resource.d.ts.map +1 -0
- package/dist/tools/im/resource.js +185 -0
- package/dist/tools/im/resource.js.map +1 -0
- package/dist/tools/im/time-utils.d.ts +70 -0
- package/dist/tools/im/time-utils.d.ts.map +1 -0
- package/dist/tools/im/time-utils.js +277 -0
- package/dist/tools/im/time-utils.js.map +1 -0
- package/dist/tools/index.d.ts +85 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +135 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/oauth.d.ts +15 -0
- package/dist/tools/oauth.d.ts.map +1 -0
- package/dist/tools/oauth.js +379 -0
- package/dist/tools/oauth.js.map +1 -0
- package/dist/tools/search/doc-search.d.ts +9 -0
- package/dist/tools/search/doc-search.d.ts.map +1 -0
- package/dist/tools/search/doc-search.js +219 -0
- package/dist/tools/search/doc-search.js.map +1 -0
- package/dist/tools/search/index.d.ts +11 -0
- package/dist/tools/search/index.d.ts.map +1 -0
- package/dist/tools/search/index.js +18 -0
- package/dist/tools/search/index.js.map +1 -0
- package/dist/tools/sheets/index.d.ts +11 -0
- package/dist/tools/sheets/index.d.ts.map +1 -0
- package/dist/tools/sheets/index.js +18 -0
- package/dist/tools/sheets/index.js.map +1 -0
- package/dist/tools/sheets/sheet.d.ts +11 -0
- package/dist/tools/sheets/sheet.d.ts.map +1 -0
- package/dist/tools/sheets/sheet.js +332 -0
- package/dist/tools/sheets/sheet.js.map +1 -0
- package/dist/tools/task/index.d.ts +12 -0
- package/dist/tools/task/index.d.ts.map +1 -0
- package/dist/tools/task/index.js +30 -0
- package/dist/tools/task/index.js.map +1 -0
- package/dist/tools/task/task.d.ts +13 -0
- package/dist/tools/task/task.d.ts.map +1 -0
- package/dist/tools/task/task.js +225 -0
- package/dist/tools/task/task.js.map +1 -0
- package/dist/tools/task/tasklist.d.ts +13 -0
- package/dist/tools/task/tasklist.d.ts.map +1 -0
- package/dist/tools/task/tasklist.js +206 -0
- package/dist/tools/task/tasklist.js.map +1 -0
- package/dist/tools/wiki/index.d.ts +11 -0
- package/dist/tools/wiki/index.d.ts.map +1 -0
- package/dist/tools/wiki/index.js +20 -0
- package/dist/tools/wiki/index.js.map +1 -0
- package/dist/tools/wiki/node.d.ts +11 -0
- package/dist/tools/wiki/node.d.ts.map +1 -0
- package/dist/tools/wiki/node.js +112 -0
- package/dist/tools/wiki/node.js.map +1 -0
- package/dist/tools/wiki/space.d.ts +11 -0
- package/dist/tools/wiki/space.d.ts.map +1 -0
- package/dist/tools/wiki/space.js +125 -0
- package/dist/tools/wiki/space.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +101 -0
- package/dist/utils/logger.js.map +1 -0
- package/eslint.config.js +13 -0
- package/package.json +54 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +18 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/core/api-error.ts +342 -0
- package/src/core/auth-errors.ts +27 -0
- package/src/core/config.ts +134 -0
- package/src/core/device-flow.ts +314 -0
- package/src/core/index.ts +16 -0
- package/src/core/lark-client.ts +391 -0
- package/src/core/token-store.ts +249 -0
- package/src/core/types.ts +302 -0
- package/src/core/uat-client.ts +298 -0
- package/src/core/version.ts +53 -0
- package/src/index.ts +138 -0
- package/src/tools/bitable/app.ts +390 -0
- package/src/tools/bitable/field.ts +406 -0
- package/src/tools/bitable/index.ts +43 -0
- package/src/tools/bitable/record.ts +559 -0
- package/src/tools/bitable/table.ts +472 -0
- package/src/tools/calendar/calendar.ts +254 -0
- package/src/tools/calendar/event.ts +606 -0
- package/src/tools/calendar/index.ts +41 -0
- package/src/tools/chat/chat.ts +127 -0
- package/src/tools/chat/index.ts +24 -0
- package/src/tools/chat/members.ts +93 -0
- package/src/tools/common/get-user.ts +127 -0
- package/src/tools/common/index.ts +24 -0
- package/src/tools/common/search-user.ts +99 -0
- package/src/tools/doc/create.ts +184 -0
- package/src/tools/doc/fetch.ts +149 -0
- package/src/tools/doc/index.ts +38 -0
- package/src/tools/doc/shared.ts +228 -0
- package/src/tools/doc/update.ts +240 -0
- package/src/tools/drive/file.ts +265 -0
- package/src/tools/drive/index.ts +29 -0
- package/src/tools/im/format-messages.ts +391 -0
- package/src/tools/im/helpers.ts +109 -0
- package/src/tools/im/index.ts +49 -0
- package/src/tools/im/message-read.ts +676 -0
- package/src/tools/im/message.ts +303 -0
- package/src/tools/im/resource.ts +225 -0
- package/src/tools/im/time-utils.ts +347 -0
- package/src/tools/index.ts +205 -0
- package/src/tools/oauth.ts +460 -0
- package/src/tools/search/doc-search.ts +250 -0
- package/src/tools/search/index.ts +22 -0
- package/src/tools/sheets/index.ts +22 -0
- package/src/tools/sheets/sheet.ts +382 -0
- package/src/tools/task/index.ts +34 -0
- package/src/tools/task/task.ts +265 -0
- package/src/tools/task/tasklist.ts +262 -0
- package/src/tools/wiki/index.ts +24 -0
- package/src/tools/wiki/node.ts +131 -0
- package/src/tools/wiki/space.ts +152 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +132 -0
- package/tests/core/config.test.ts +238 -0
- package/tests/core/device-flow.test.ts +490 -0
- package/tests/core/lark-client.test.ts +378 -0
- package/tests/core/token-store.test.ts +438 -0
- package/tests/index.test.ts +360 -0
- package/tests/tools/doc/create.test.ts +224 -0
- package/tests/tools/doc/fetch.test.ts +182 -0
- package/tests/tools/doc/shared.test.ts +183 -0
- package/tests/tools/doc/update.test.ts +330 -0
- package/tests/tools/im/format-messages.test.ts +184 -0
- package/tests/tools/im/time-utils.test.ts +178 -0
- package/tests/utils/logger.test.ts +140 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Time utilities for IM tools.
|
|
6
|
+
*
|
|
7
|
+
* Provides time range parsing and ISO 8601 conversion utilities.
|
|
8
|
+
* Adapted from openclaw-lark for MCP Server architecture.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const BJ_OFFSET_MS = 8 * 60 * 60 * 1000; // UTC+8
|
|
12
|
+
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
// ISO 8601 ↔ Unix conversion utilities
|
|
15
|
+
// ===========================================================================
|
|
16
|
+
|
|
17
|
+
/** Format a Date as Beijing time ISO 8601 string */
|
|
18
|
+
function formatBeijingISO(d: Date): string {
|
|
19
|
+
const bj = new Date(d.getTime() + BJ_OFFSET_MS);
|
|
20
|
+
const y = bj.getUTCFullYear();
|
|
21
|
+
const mo = String(bj.getUTCMonth() + 1).padStart(2, '0');
|
|
22
|
+
const da = String(bj.getUTCDate()).padStart(2, '0');
|
|
23
|
+
const h = String(bj.getUTCHours()).padStart(2, '0');
|
|
24
|
+
const mi = String(bj.getUTCMinutes()).padStart(2, '0');
|
|
25
|
+
const s = String(bj.getUTCSeconds()).padStart(2, '0');
|
|
26
|
+
return `${y}-${mo}-${da}T${h}:${mi}:${s}+08:00`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Unix seconds → ISO 8601
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/** Convert Unix seconds (number) to ISO 8601 Beijing time */
|
|
34
|
+
export function secondsToDateTime(seconds: number): string {
|
|
35
|
+
return formatBeijingISO(new Date(seconds * 1000));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Convert Unix seconds (string) to ISO 8601 Beijing time */
|
|
39
|
+
export function secondsStringToDateTime(seconds: string): string {
|
|
40
|
+
return secondsToDateTime(parseInt(seconds, 10));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Unix milliseconds → ISO 8601
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/** Convert Unix milliseconds (number) to ISO 8601 Beijing time */
|
|
48
|
+
export function millisToDateTime(millis: number): string {
|
|
49
|
+
return formatBeijingISO(new Date(millis));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Convert Unix milliseconds (string) to ISO 8601 Beijing time */
|
|
53
|
+
export function millisStringToDateTime(millis: string): string {
|
|
54
|
+
return millisToDateTime(parseInt(millis, 10));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// ISO 8601 → Unix
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/** Convert ISO 8601 to Unix seconds (number) */
|
|
62
|
+
export function dateTimeToSeconds(datetime: string): number {
|
|
63
|
+
const d = new Date(datetime);
|
|
64
|
+
if (isNaN(d.getTime())) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Unable to parse ISO 8601 time: "${datetime}". Example format: 2026-02-27T14:30:00+08:00`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return Math.floor(d.getTime() / 1000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Convert ISO 8601 to Unix seconds (string) */
|
|
73
|
+
export function dateTimeToSecondsString(datetime: string): string {
|
|
74
|
+
return dateTimeToSeconds(datetime).toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Convert ISO 8601 to Unix milliseconds (number) */
|
|
78
|
+
export function dateTimeToMillis(datetime: string): number {
|
|
79
|
+
const d = new Date(datetime);
|
|
80
|
+
if (isNaN(d.getTime())) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Unable to parse ISO 8601 time: "${datetime}". Example format: 2026-02-27T14:30:00+08:00`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return d.getTime();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ===========================================================================
|
|
89
|
+
// Time range parsing
|
|
90
|
+
// ===========================================================================
|
|
91
|
+
|
|
92
|
+
/** ISO 8601 time range */
|
|
93
|
+
export interface TimeRange {
|
|
94
|
+
start: string;
|
|
95
|
+
end: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Unix timestamp time range (seconds) */
|
|
99
|
+
export interface TimeRangeSeconds {
|
|
100
|
+
start: string;
|
|
101
|
+
end: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse a time range identifier to ISO 8601 string pair.
|
|
106
|
+
*
|
|
107
|
+
* Supported formats:
|
|
108
|
+
* - `today` / `yesterday` / `day_before_yesterday`
|
|
109
|
+
* - `this_week` / `last_week` / `this_month` / `last_month`
|
|
110
|
+
* - `last_{N}_{unit}` — unit: minutes / hours / days
|
|
111
|
+
*
|
|
112
|
+
* All calculations are based on Beijing time (UTC+8).
|
|
113
|
+
*/
|
|
114
|
+
export function parseTimeRange(input: string): TimeRange {
|
|
115
|
+
const now = new Date();
|
|
116
|
+
const bjNow = toBeijingDate(now);
|
|
117
|
+
|
|
118
|
+
let start: Date;
|
|
119
|
+
let end: Date;
|
|
120
|
+
|
|
121
|
+
switch (input) {
|
|
122
|
+
case 'today':
|
|
123
|
+
start = beijingStartOfDay(bjNow);
|
|
124
|
+
end = now;
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'yesterday': {
|
|
128
|
+
const d = new Date(bjNow);
|
|
129
|
+
d.setUTCDate(d.getUTCDate() - 1);
|
|
130
|
+
start = beijingStartOfDay(d);
|
|
131
|
+
end = beijingEndOfDay(d);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'day_before_yesterday': {
|
|
136
|
+
const d = new Date(bjNow);
|
|
137
|
+
d.setUTCDate(d.getUTCDate() - 2);
|
|
138
|
+
start = beijingStartOfDay(d);
|
|
139
|
+
end = beijingEndOfDay(d);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'this_week': {
|
|
144
|
+
const day = bjNow.getUTCDay(); // 0=Sun .. 6=Sat
|
|
145
|
+
const diffToMon = day === 0 ? 6 : day - 1;
|
|
146
|
+
const monday = new Date(bjNow);
|
|
147
|
+
monday.setUTCDate(monday.getUTCDate() - diffToMon);
|
|
148
|
+
start = beijingStartOfDay(monday);
|
|
149
|
+
end = now;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'last_week': {
|
|
154
|
+
const day = bjNow.getUTCDay();
|
|
155
|
+
const diffToMon = day === 0 ? 6 : day - 1;
|
|
156
|
+
const thisMonday = new Date(bjNow);
|
|
157
|
+
thisMonday.setUTCDate(thisMonday.getUTCDate() - diffToMon);
|
|
158
|
+
const lastMonday = new Date(thisMonday);
|
|
159
|
+
lastMonday.setUTCDate(lastMonday.getUTCDate() - 7);
|
|
160
|
+
const lastSunday = new Date(thisMonday);
|
|
161
|
+
lastSunday.setUTCDate(lastSunday.getUTCDate() - 1);
|
|
162
|
+
start = beijingStartOfDay(lastMonday);
|
|
163
|
+
end = beijingEndOfDay(lastSunday);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case 'this_month': {
|
|
168
|
+
const firstDay = new Date(Date.UTC(bjNow.getUTCFullYear(), bjNow.getUTCMonth(), 1));
|
|
169
|
+
start = beijingStartOfDay(firstDay);
|
|
170
|
+
end = now;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'last_month': {
|
|
175
|
+
const firstDayThisMonth = new Date(Date.UTC(bjNow.getUTCFullYear(), bjNow.getUTCMonth(), 1));
|
|
176
|
+
const lastDayPrevMonth = new Date(firstDayThisMonth);
|
|
177
|
+
lastDayPrevMonth.setUTCDate(lastDayPrevMonth.getUTCDate() - 1);
|
|
178
|
+
const firstDayPrevMonth = new Date(
|
|
179
|
+
Date.UTC(lastDayPrevMonth.getUTCFullYear(), lastDayPrevMonth.getUTCMonth(), 1)
|
|
180
|
+
);
|
|
181
|
+
start = beijingStartOfDay(firstDayPrevMonth);
|
|
182
|
+
end = beijingEndOfDay(lastDayPrevMonth);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
default: {
|
|
187
|
+
// last_{N}_{unit} — only supports minutes / hours / days
|
|
188
|
+
const match = input.match(/^last_(\d+)_(minutes?|hours?|days?)$/);
|
|
189
|
+
if (!match) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Unsupported relative_time format: "${input}". ` +
|
|
192
|
+
'Supported: today, yesterday, day_before_yesterday, this_week, last_week, this_month, last_month, last_{N}_{unit} (unit: minutes/hours/days)'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const n = parseInt(match[1], 10);
|
|
196
|
+
const unit = match[2].replace(/s$/, ''); // normalize plural
|
|
197
|
+
start = subtractFromNow(now, n, unit);
|
|
198
|
+
end = now;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
start: formatBeijingISO(start),
|
|
205
|
+
end: formatBeijingISO(end),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parse a time range identifier to Unix seconds string pair.
|
|
211
|
+
* This is for SDK API calls that require Unix timestamps.
|
|
212
|
+
*/
|
|
213
|
+
export function parseTimeRangeToSeconds(input: string): TimeRangeSeconds {
|
|
214
|
+
const range = parseTimeRange(input);
|
|
215
|
+
return {
|
|
216
|
+
start: dateTimeToSecondsString(range.start),
|
|
217
|
+
end: dateTimeToSecondsString(range.end),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ===========================================================================
|
|
222
|
+
// Internal helpers
|
|
223
|
+
// ===========================================================================
|
|
224
|
+
|
|
225
|
+
/** Convert UTC Date to "Beijing time components stored in UTC fields" Date */
|
|
226
|
+
function toBeijingDate(d: Date): Date {
|
|
227
|
+
return new Date(d.getTime() + BJ_OFFSET_MS);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Beijing time start of day (00:00:00) as real UTC Date */
|
|
231
|
+
function beijingStartOfDay(bjDate: Date): Date {
|
|
232
|
+
return new Date(Date.UTC(bjDate.getUTCFullYear(), bjDate.getUTCMonth(), bjDate.getUTCDate()) - BJ_OFFSET_MS);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Beijing time end of day (23:59:59) as real UTC Date */
|
|
236
|
+
function beijingEndOfDay(bjDate: Date): Date {
|
|
237
|
+
return new Date(
|
|
238
|
+
Date.UTC(bjDate.getUTCFullYear(), bjDate.getUTCMonth(), bjDate.getUTCDate(), 23, 59, 59) - BJ_OFFSET_MS
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function subtractFromNow(now: Date, n: number, unit: string): Date {
|
|
243
|
+
const d = new Date(now);
|
|
244
|
+
switch (unit) {
|
|
245
|
+
case 'minute':
|
|
246
|
+
d.setMinutes(d.getMinutes() - n);
|
|
247
|
+
break;
|
|
248
|
+
case 'hour':
|
|
249
|
+
d.setHours(d.getHours() - n);
|
|
250
|
+
break;
|
|
251
|
+
case 'day':
|
|
252
|
+
d.setDate(d.getDate() - n);
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
throw new Error(`Unsupported time unit: ${unit}`);
|
|
256
|
+
}
|
|
257
|
+
return d;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ===========================================================================
|
|
261
|
+
// Additional utilities for parsing various time formats
|
|
262
|
+
// ===========================================================================
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Parse a time string to Unix timestamp (seconds).
|
|
266
|
+
*
|
|
267
|
+
* Supports:
|
|
268
|
+
* 1. ISO 8601 / RFC 3339 with timezone: "2024-01-01T00:00:00+08:00"
|
|
269
|
+
* 2. Formats without timezone (defaults to Beijing time UTC+8):
|
|
270
|
+
* - "2026-02-25 14:30"
|
|
271
|
+
* - "2026-02-25 14:30:00"
|
|
272
|
+
* - "2026-02-25T14:30:00"
|
|
273
|
+
*
|
|
274
|
+
* Returns null if parsing fails.
|
|
275
|
+
*/
|
|
276
|
+
export function parseTimeToTimestamp(input: string): string | null {
|
|
277
|
+
try {
|
|
278
|
+
const trimmed = input.trim();
|
|
279
|
+
|
|
280
|
+
// Check if timezone info is present (Z or +/- offset)
|
|
281
|
+
const hasTimezone = /[Zz]$|[+-]\d{2}:\d{2}$/.test(trimmed);
|
|
282
|
+
|
|
283
|
+
if (hasTimezone) {
|
|
284
|
+
// Has timezone, parse directly
|
|
285
|
+
const date = new Date(trimmed);
|
|
286
|
+
if (isNaN(date.getTime())) return null;
|
|
287
|
+
return Math.floor(date.getTime() / 1000).toString();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// No timezone, treat as Beijing time
|
|
291
|
+
const normalized = trimmed.replace('T', ' ');
|
|
292
|
+
const match = normalized.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?$/);
|
|
293
|
+
|
|
294
|
+
if (!match) {
|
|
295
|
+
// Try direct parse (might be other ISO 8601 format)
|
|
296
|
+
const date = new Date(trimmed);
|
|
297
|
+
if (isNaN(date.getTime())) return null;
|
|
298
|
+
return Math.floor(date.getTime() / 1000).toString();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const [, year, month, day, hour, minute, second] = match;
|
|
302
|
+
// Treat as Beijing time (UTC+8), convert to UTC
|
|
303
|
+
const utcDate = new Date(
|
|
304
|
+
Date.UTC(
|
|
305
|
+
parseInt(year),
|
|
306
|
+
parseInt(month) - 1,
|
|
307
|
+
parseInt(day),
|
|
308
|
+
parseInt(hour) - 8, // Subtract 8 hours from Beijing time to get UTC
|
|
309
|
+
parseInt(minute),
|
|
310
|
+
parseInt(second ?? '0')
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
return Math.floor(utcDate.getTime() / 1000).toString();
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Convert a Unix timestamp (seconds or milliseconds) to ISO 8601 string
|
|
322
|
+
* in Asia/Shanghai timezone.
|
|
323
|
+
*
|
|
324
|
+
* Auto-detects seconds vs milliseconds based on magnitude.
|
|
325
|
+
*/
|
|
326
|
+
export function unixTimestampToISO8601(raw: string | number | undefined): string | null {
|
|
327
|
+
if (raw === undefined || raw === null) return null;
|
|
328
|
+
|
|
329
|
+
const text = typeof raw === 'number' ? String(raw) : String(raw).trim();
|
|
330
|
+
if (!/^-?\d+$/.test(text)) return null;
|
|
331
|
+
|
|
332
|
+
const num = Number(text);
|
|
333
|
+
if (!Number.isFinite(num)) return null;
|
|
334
|
+
|
|
335
|
+
const utcMs = Math.abs(num) >= 1e12 ? num : num * 1000;
|
|
336
|
+
const beijingDate = new Date(utcMs + BJ_OFFSET_MS);
|
|
337
|
+
if (Number.isNaN(beijingDate.getTime())) return null;
|
|
338
|
+
|
|
339
|
+
const year = beijingDate.getUTCFullYear();
|
|
340
|
+
const month = String(beijingDate.getUTCMonth() + 1).padStart(2, '0');
|
|
341
|
+
const day = String(beijingDate.getUTCDate()).padStart(2, '0');
|
|
342
|
+
const hour = String(beijingDate.getUTCHours()).padStart(2, '0');
|
|
343
|
+
const minute = String(beijingDate.getUTCMinutes()).padStart(2, '0');
|
|
344
|
+
const second = String(beijingDate.getUTCSeconds()).padStart(2, '0');
|
|
345
|
+
|
|
346
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${second}+08:00`;
|
|
347
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Tool registry for the cc-lark MCP Server.
|
|
6
|
+
*
|
|
7
|
+
* Provides a unified interface for registering MCP tools with the server.
|
|
8
|
+
* Each tool defines its schema using Zod and implements a handler function.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import type { ZodRawShapeCompat, ShapeOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
|
13
|
+
import type { FeishuConfig } from '../core/types.js';
|
|
14
|
+
import type { LarkClient } from '../core/lark-client.js';
|
|
15
|
+
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { registerOAuthTool } from './oauth.js';
|
|
17
|
+
import { registerImTools } from './im/index.js';
|
|
18
|
+
import { registerDocTools } from './doc/index.js';
|
|
19
|
+
import { registerBitableTools } from './bitable/index.js';
|
|
20
|
+
import { registerCalendarTools } from './calendar/index.js';
|
|
21
|
+
import { registerTaskTools } from './task/index.js';
|
|
22
|
+
import { registerDriveTools } from './drive/index.js';
|
|
23
|
+
import { registerWikiTools } from './wiki/index.js';
|
|
24
|
+
import { registerSheetsTools } from './sheets/index.js';
|
|
25
|
+
import { registerSearchTools } from './search/index.js';
|
|
26
|
+
import { registerChatTools } from './chat/index.js';
|
|
27
|
+
import { registerCommonTools } from './common/index.js';
|
|
28
|
+
|
|
29
|
+
const log = logger('tools');
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Tool types
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Context passed to tool handlers.
|
|
37
|
+
*/
|
|
38
|
+
export interface ToolContext {
|
|
39
|
+
/** LarkClient instance (may be null if config is invalid) */
|
|
40
|
+
larkClient: LarkClient | null;
|
|
41
|
+
/** Configuration loaded from environment */
|
|
42
|
+
config: FeishuConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handler function type for tool execution.
|
|
47
|
+
*/
|
|
48
|
+
export type ToolHandler<Args extends ZodRawShapeCompat = ZodRawShapeCompat> = (
|
|
49
|
+
args: ShapeOutput<Args>,
|
|
50
|
+
context: ToolContext
|
|
51
|
+
) => Promise<{
|
|
52
|
+
content: Array<{ type: 'text'; text: string }>;
|
|
53
|
+
isError?: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Definition for a single tool.
|
|
58
|
+
*/
|
|
59
|
+
export interface ToolDefinition<Args extends ZodRawShapeCompat = ZodRawShapeCompat> {
|
|
60
|
+
/** Tool name (unique identifier) */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Human-readable tool description */
|
|
63
|
+
description: string;
|
|
64
|
+
/** Zod schema for input parameters (raw shape object) */
|
|
65
|
+
inputSchema: Args;
|
|
66
|
+
/** Tool handler function */
|
|
67
|
+
handler: ToolHandler<Args>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Tool registry
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Registry for MCP tools.
|
|
76
|
+
*
|
|
77
|
+
* Manages tool registration with the MCP server and provides
|
|
78
|
+
* a unified interface for tool handlers.
|
|
79
|
+
*/
|
|
80
|
+
export class ToolRegistry {
|
|
81
|
+
private readonly tools: Map<string, ToolDefinition> = new Map();
|
|
82
|
+
private readonly context: ToolContext;
|
|
83
|
+
|
|
84
|
+
constructor(context: ToolContext) {
|
|
85
|
+
this.context = context;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register a tool with the registry.
|
|
90
|
+
*
|
|
91
|
+
* @param definition - Tool definition
|
|
92
|
+
*/
|
|
93
|
+
register<Args extends ZodRawShapeCompat>(definition: ToolDefinition<Args>): void {
|
|
94
|
+
if (this.tools.has(definition.name)) {
|
|
95
|
+
log.warn(`Tool "${definition.name}" is already registered, overwriting`);
|
|
96
|
+
}
|
|
97
|
+
this.tools.set(definition.name, definition as ToolDefinition);
|
|
98
|
+
log.debug(`Registered tool: ${definition.name}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register all registered tools with the MCP server.
|
|
103
|
+
*
|
|
104
|
+
* @param server - MCP server instance
|
|
105
|
+
*/
|
|
106
|
+
registerWithServer(server: McpServer): void {
|
|
107
|
+
for (const [name, tool] of this.tools) {
|
|
108
|
+
server.tool(name, tool.description, tool.inputSchema, async (args) => {
|
|
109
|
+
try {
|
|
110
|
+
log.debug(`Executing tool: ${name}`, { args: JSON.stringify(args) });
|
|
111
|
+
const result = await tool.handler(args, this.context);
|
|
112
|
+
log.debug(`Tool ${name} completed`, { isError: result.isError });
|
|
113
|
+
return result;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
log.error(`Tool ${name} failed`, {
|
|
116
|
+
error: err instanceof Error ? err.message : String(err),
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text' as const,
|
|
122
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
isError: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get a list of all registered tool names.
|
|
134
|
+
*/
|
|
135
|
+
getToolNames(): string[] {
|
|
136
|
+
return Array.from(this.tools.keys());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the number of registered tools.
|
|
141
|
+
*/
|
|
142
|
+
get size(): number {
|
|
143
|
+
return this.tools.size;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Tool registration
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Register all available tools with the MCP server.
|
|
153
|
+
*
|
|
154
|
+
* @param server - MCP server instance
|
|
155
|
+
* @param larkClient - LarkClient instance (may be null)
|
|
156
|
+
* @param config - Configuration from environment
|
|
157
|
+
*/
|
|
158
|
+
export function registerAllTools(
|
|
159
|
+
server: McpServer,
|
|
160
|
+
larkClient: LarkClient | null,
|
|
161
|
+
config: FeishuConfig
|
|
162
|
+
): void {
|
|
163
|
+
const registry = new ToolRegistry({ larkClient, config });
|
|
164
|
+
|
|
165
|
+
// Register OAuth tool
|
|
166
|
+
registerOAuthTool(registry);
|
|
167
|
+
|
|
168
|
+
// Register IM tools
|
|
169
|
+
registerImTools(registry);
|
|
170
|
+
|
|
171
|
+
// Register Doc tools
|
|
172
|
+
registerDocTools(registry);
|
|
173
|
+
|
|
174
|
+
// Register Bitable tools
|
|
175
|
+
registerBitableTools(registry);
|
|
176
|
+
|
|
177
|
+
// Register Calendar tools
|
|
178
|
+
registerCalendarTools(registry);
|
|
179
|
+
|
|
180
|
+
// Register Task tools
|
|
181
|
+
registerTaskTools(registry);
|
|
182
|
+
|
|
183
|
+
// Register Drive tools
|
|
184
|
+
registerDriveTools(registry);
|
|
185
|
+
|
|
186
|
+
// Register Wiki tools
|
|
187
|
+
registerWikiTools(registry);
|
|
188
|
+
|
|
189
|
+
// Register Sheets tools
|
|
190
|
+
registerSheetsTools(registry);
|
|
191
|
+
|
|
192
|
+
// Register Search tools
|
|
193
|
+
registerSearchTools(registry);
|
|
194
|
+
|
|
195
|
+
// Register Chat tools
|
|
196
|
+
registerChatTools(registry);
|
|
197
|
+
|
|
198
|
+
// Register Common tools
|
|
199
|
+
registerCommonTools(registry);
|
|
200
|
+
|
|
201
|
+
// Register all tools with the server
|
|
202
|
+
registry.registerWithServer(server);
|
|
203
|
+
|
|
204
|
+
log.info(`Registered ${registry.size} tools`, { tools: registry.getToolNames().join(', ') });
|
|
205
|
+
}
|