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.
Files changed (298) hide show
  1. package/.github/workflows/ci.yml +47 -0
  2. package/.github/workflows/release.yml +47 -0
  3. package/.github/workflows/sync-upstream.yml +127 -0
  4. package/.prettierrc.json +7 -0
  5. package/README.md +214 -0
  6. package/dist/core/api-error.d.ts +193 -0
  7. package/dist/core/api-error.d.ts.map +1 -0
  8. package/dist/core/api-error.js +263 -0
  9. package/dist/core/api-error.js.map +1 -0
  10. package/dist/core/auth-errors.d.ts +13 -0
  11. package/dist/core/auth-errors.d.ts.map +1 -0
  12. package/dist/core/auth-errors.js +14 -0
  13. package/dist/core/auth-errors.js.map +1 -0
  14. package/dist/core/config.d.ts +60 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +115 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/device-flow.d.ts +80 -0
  19. package/dist/core/device-flow.d.ts.map +1 -0
  20. package/dist/core/device-flow.js +231 -0
  21. package/dist/core/device-flow.js.map +1 -0
  22. package/dist/core/index.d.ts +16 -0
  23. package/dist/core/index.d.ts.map +1 -0
  24. package/dist/core/index.js +16 -0
  25. package/dist/core/index.js.map +1 -0
  26. package/dist/core/lark-client.d.ts +136 -0
  27. package/dist/core/lark-client.d.ts.map +1 -0
  28. package/dist/core/lark-client.js +315 -0
  29. package/dist/core/lark-client.js.map +1 -0
  30. package/dist/core/token-store.d.ts +67 -0
  31. package/dist/core/token-store.d.ts.map +1 -0
  32. package/dist/core/token-store.js +215 -0
  33. package/dist/core/token-store.js.map +1 -0
  34. package/dist/core/types.d.ts +286 -0
  35. package/dist/core/types.d.ts.map +1 -0
  36. package/dist/core/types.js +11 -0
  37. package/dist/core/types.js.map +1 -0
  38. package/dist/core/uat-client.d.ts +64 -0
  39. package/dist/core/uat-client.d.ts.map +1 -0
  40. package/dist/core/uat-client.js +227 -0
  41. package/dist/core/uat-client.js.map +1 -0
  42. package/dist/core/version.d.ts +26 -0
  43. package/dist/core/version.d.ts.map +1 -0
  44. package/dist/core/version.js +50 -0
  45. package/dist/core/version.js.map +1 -0
  46. package/dist/index.d.ts +12 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +116 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/tools/bitable/app.d.ts +20 -0
  51. package/dist/tools/bitable/app.d.ts.map +1 -0
  52. package/dist/tools/bitable/app.js +301 -0
  53. package/dist/tools/bitable/app.js.map +1 -0
  54. package/dist/tools/bitable/field.d.ts +19 -0
  55. package/dist/tools/bitable/field.d.ts.map +1 -0
  56. package/dist/tools/bitable/field.js +315 -0
  57. package/dist/tools/bitable/field.js.map +1 -0
  58. package/dist/tools/bitable/index.d.ts +21 -0
  59. package/dist/tools/bitable/index.d.ts.map +1 -0
  60. package/dist/tools/bitable/index.js +39 -0
  61. package/dist/tools/bitable/index.js.map +1 -0
  62. package/dist/tools/bitable/record.d.ts +22 -0
  63. package/dist/tools/bitable/record.d.ts.map +1 -0
  64. package/dist/tools/bitable/record.js +434 -0
  65. package/dist/tools/bitable/record.js.map +1 -0
  66. package/dist/tools/bitable/table.d.ts +21 -0
  67. package/dist/tools/bitable/table.d.ts.map +1 -0
  68. package/dist/tools/bitable/table.js +361 -0
  69. package/dist/tools/bitable/table.js.map +1 -0
  70. package/dist/tools/calendar/calendar.d.ts +18 -0
  71. package/dist/tools/calendar/calendar.d.ts.map +1 -0
  72. package/dist/tools/calendar/calendar.js +192 -0
  73. package/dist/tools/calendar/calendar.js.map +1 -0
  74. package/dist/tools/calendar/event.d.ts +20 -0
  75. package/dist/tools/calendar/event.d.ts.map +1 -0
  76. package/dist/tools/calendar/event.js +465 -0
  77. package/dist/tools/calendar/event.js.map +1 -0
  78. package/dist/tools/calendar/index.d.ts +19 -0
  79. package/dist/tools/calendar/index.d.ts.map +1 -0
  80. package/dist/tools/calendar/index.js +37 -0
  81. package/dist/tools/calendar/index.js.map +1 -0
  82. package/dist/tools/chat/chat.d.ts +11 -0
  83. package/dist/tools/chat/chat.d.ts.map +1 -0
  84. package/dist/tools/chat/chat.js +106 -0
  85. package/dist/tools/chat/chat.js.map +1 -0
  86. package/dist/tools/chat/index.d.ts +11 -0
  87. package/dist/tools/chat/index.d.ts.map +1 -0
  88. package/dist/tools/chat/index.js +20 -0
  89. package/dist/tools/chat/index.js.map +1 -0
  90. package/dist/tools/chat/members.d.ts +9 -0
  91. package/dist/tools/chat/members.d.ts.map +1 -0
  92. package/dist/tools/chat/members.js +80 -0
  93. package/dist/tools/chat/members.js.map +1 -0
  94. package/dist/tools/common/get-user.d.ts +11 -0
  95. package/dist/tools/common/get-user.d.ts.map +1 -0
  96. package/dist/tools/common/get-user.js +112 -0
  97. package/dist/tools/common/get-user.js.map +1 -0
  98. package/dist/tools/common/index.d.ts +11 -0
  99. package/dist/tools/common/index.d.ts.map +1 -0
  100. package/dist/tools/common/index.js +20 -0
  101. package/dist/tools/common/index.js.map +1 -0
  102. package/dist/tools/common/search-user.d.ts +9 -0
  103. package/dist/tools/common/search-user.d.ts.map +1 -0
  104. package/dist/tools/common/search-user.js +88 -0
  105. package/dist/tools/common/search-user.js.map +1 -0
  106. package/dist/tools/doc/create.d.ts +17 -0
  107. package/dist/tools/doc/create.d.ts.map +1 -0
  108. package/dist/tools/doc/create.js +159 -0
  109. package/dist/tools/doc/create.js.map +1 -0
  110. package/dist/tools/doc/fetch.d.ts +17 -0
  111. package/dist/tools/doc/fetch.d.ts.map +1 -0
  112. package/dist/tools/doc/fetch.js +123 -0
  113. package/dist/tools/doc/fetch.js.map +1 -0
  114. package/dist/tools/doc/index.d.ts +21 -0
  115. package/dist/tools/doc/index.d.ts.map +1 -0
  116. package/dist/tools/doc/index.js +33 -0
  117. package/dist/tools/doc/index.js.map +1 -0
  118. package/dist/tools/doc/shared.d.ts +69 -0
  119. package/dist/tools/doc/shared.d.ts.map +1 -0
  120. package/dist/tools/doc/shared.js +172 -0
  121. package/dist/tools/doc/shared.js.map +1 -0
  122. package/dist/tools/doc/update.d.ts +25 -0
  123. package/dist/tools/doc/update.d.ts.map +1 -0
  124. package/dist/tools/doc/update.js +208 -0
  125. package/dist/tools/doc/update.js.map +1 -0
  126. package/dist/tools/drive/file.d.ts +13 -0
  127. package/dist/tools/drive/file.d.ts.map +1 -0
  128. package/dist/tools/drive/file.js +212 -0
  129. package/dist/tools/drive/file.js.map +1 -0
  130. package/dist/tools/drive/index.d.ts +12 -0
  131. package/dist/tools/drive/index.d.ts.map +1 -0
  132. package/dist/tools/drive/index.js +25 -0
  133. package/dist/tools/drive/index.js.map +1 -0
  134. package/dist/tools/im/format-messages.d.ts +99 -0
  135. package/dist/tools/im/format-messages.d.ts.map +1 -0
  136. package/dist/tools/im/format-messages.js +277 -0
  137. package/dist/tools/im/format-messages.js.map +1 -0
  138. package/dist/tools/im/helpers.d.ts +53 -0
  139. package/dist/tools/im/helpers.d.ts.map +1 -0
  140. package/dist/tools/im/helpers.js +85 -0
  141. package/dist/tools/im/helpers.js.map +1 -0
  142. package/dist/tools/im/index.d.ts +25 -0
  143. package/dist/tools/im/index.d.ts.map +1 -0
  144. package/dist/tools/im/index.js +44 -0
  145. package/dist/tools/im/index.js.map +1 -0
  146. package/dist/tools/im/message-read.d.ts +19 -0
  147. package/dist/tools/im/message-read.d.ts.map +1 -0
  148. package/dist/tools/im/message-read.js +526 -0
  149. package/dist/tools/im/message-read.js.map +1 -0
  150. package/dist/tools/im/message.d.ts +22 -0
  151. package/dist/tools/im/message.d.ts.map +1 -0
  152. package/dist/tools/im/message.js +233 -0
  153. package/dist/tools/im/message.js.map +1 -0
  154. package/dist/tools/im/resource.d.ts +19 -0
  155. package/dist/tools/im/resource.d.ts.map +1 -0
  156. package/dist/tools/im/resource.js +185 -0
  157. package/dist/tools/im/resource.js.map +1 -0
  158. package/dist/tools/im/time-utils.d.ts +70 -0
  159. package/dist/tools/im/time-utils.d.ts.map +1 -0
  160. package/dist/tools/im/time-utils.js +277 -0
  161. package/dist/tools/im/time-utils.js.map +1 -0
  162. package/dist/tools/index.d.ts +85 -0
  163. package/dist/tools/index.d.ts.map +1 -0
  164. package/dist/tools/index.js +135 -0
  165. package/dist/tools/index.js.map +1 -0
  166. package/dist/tools/oauth.d.ts +15 -0
  167. package/dist/tools/oauth.d.ts.map +1 -0
  168. package/dist/tools/oauth.js +379 -0
  169. package/dist/tools/oauth.js.map +1 -0
  170. package/dist/tools/search/doc-search.d.ts +9 -0
  171. package/dist/tools/search/doc-search.d.ts.map +1 -0
  172. package/dist/tools/search/doc-search.js +219 -0
  173. package/dist/tools/search/doc-search.js.map +1 -0
  174. package/dist/tools/search/index.d.ts +11 -0
  175. package/dist/tools/search/index.d.ts.map +1 -0
  176. package/dist/tools/search/index.js +18 -0
  177. package/dist/tools/search/index.js.map +1 -0
  178. package/dist/tools/sheets/index.d.ts +11 -0
  179. package/dist/tools/sheets/index.d.ts.map +1 -0
  180. package/dist/tools/sheets/index.js +18 -0
  181. package/dist/tools/sheets/index.js.map +1 -0
  182. package/dist/tools/sheets/sheet.d.ts +11 -0
  183. package/dist/tools/sheets/sheet.d.ts.map +1 -0
  184. package/dist/tools/sheets/sheet.js +332 -0
  185. package/dist/tools/sheets/sheet.js.map +1 -0
  186. package/dist/tools/task/index.d.ts +12 -0
  187. package/dist/tools/task/index.d.ts.map +1 -0
  188. package/dist/tools/task/index.js +30 -0
  189. package/dist/tools/task/index.js.map +1 -0
  190. package/dist/tools/task/task.d.ts +13 -0
  191. package/dist/tools/task/task.d.ts.map +1 -0
  192. package/dist/tools/task/task.js +225 -0
  193. package/dist/tools/task/task.js.map +1 -0
  194. package/dist/tools/task/tasklist.d.ts +13 -0
  195. package/dist/tools/task/tasklist.d.ts.map +1 -0
  196. package/dist/tools/task/tasklist.js +206 -0
  197. package/dist/tools/task/tasklist.js.map +1 -0
  198. package/dist/tools/wiki/index.d.ts +11 -0
  199. package/dist/tools/wiki/index.d.ts.map +1 -0
  200. package/dist/tools/wiki/index.js +20 -0
  201. package/dist/tools/wiki/index.js.map +1 -0
  202. package/dist/tools/wiki/node.d.ts +11 -0
  203. package/dist/tools/wiki/node.d.ts.map +1 -0
  204. package/dist/tools/wiki/node.js +112 -0
  205. package/dist/tools/wiki/node.js.map +1 -0
  206. package/dist/tools/wiki/space.d.ts +11 -0
  207. package/dist/tools/wiki/space.d.ts.map +1 -0
  208. package/dist/tools/wiki/space.js +125 -0
  209. package/dist/tools/wiki/space.js.map +1 -0
  210. package/dist/utils/index.d.ts +8 -0
  211. package/dist/utils/index.d.ts.map +1 -0
  212. package/dist/utils/index.js +8 -0
  213. package/dist/utils/index.js.map +1 -0
  214. package/dist/utils/logger.d.ts +36 -0
  215. package/dist/utils/logger.d.ts.map +1 -0
  216. package/dist/utils/logger.js +101 -0
  217. package/dist/utils/logger.js.map +1 -0
  218. package/eslint.config.js +13 -0
  219. package/package.json +54 -0
  220. package/skills/feishu-bitable/SKILL.md +248 -0
  221. package/skills/feishu-bitable/references/examples.md +813 -0
  222. package/skills/feishu-bitable/references/field-properties.md +763 -0
  223. package/skills/feishu-bitable/references/record-values.md +911 -0
  224. package/skills/feishu-calendar/SKILL.md +244 -0
  225. package/skills/feishu-channel-rules/SKILL.md +18 -0
  226. package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
  227. package/skills/feishu-create-doc/SKILL.md +719 -0
  228. package/skills/feishu-fetch-doc/SKILL.md +93 -0
  229. package/skills/feishu-im-read/SKILL.md +163 -0
  230. package/skills/feishu-task/SKILL.md +293 -0
  231. package/skills/feishu-troubleshoot/SKILL.md +70 -0
  232. package/skills/feishu-update-doc/SKILL.md +285 -0
  233. package/src/core/api-error.ts +342 -0
  234. package/src/core/auth-errors.ts +27 -0
  235. package/src/core/config.ts +134 -0
  236. package/src/core/device-flow.ts +314 -0
  237. package/src/core/index.ts +16 -0
  238. package/src/core/lark-client.ts +391 -0
  239. package/src/core/token-store.ts +249 -0
  240. package/src/core/types.ts +302 -0
  241. package/src/core/uat-client.ts +298 -0
  242. package/src/core/version.ts +53 -0
  243. package/src/index.ts +138 -0
  244. package/src/tools/bitable/app.ts +390 -0
  245. package/src/tools/bitable/field.ts +406 -0
  246. package/src/tools/bitable/index.ts +43 -0
  247. package/src/tools/bitable/record.ts +559 -0
  248. package/src/tools/bitable/table.ts +472 -0
  249. package/src/tools/calendar/calendar.ts +254 -0
  250. package/src/tools/calendar/event.ts +606 -0
  251. package/src/tools/calendar/index.ts +41 -0
  252. package/src/tools/chat/chat.ts +127 -0
  253. package/src/tools/chat/index.ts +24 -0
  254. package/src/tools/chat/members.ts +93 -0
  255. package/src/tools/common/get-user.ts +127 -0
  256. package/src/tools/common/index.ts +24 -0
  257. package/src/tools/common/search-user.ts +99 -0
  258. package/src/tools/doc/create.ts +184 -0
  259. package/src/tools/doc/fetch.ts +149 -0
  260. package/src/tools/doc/index.ts +38 -0
  261. package/src/tools/doc/shared.ts +228 -0
  262. package/src/tools/doc/update.ts +240 -0
  263. package/src/tools/drive/file.ts +265 -0
  264. package/src/tools/drive/index.ts +29 -0
  265. package/src/tools/im/format-messages.ts +391 -0
  266. package/src/tools/im/helpers.ts +109 -0
  267. package/src/tools/im/index.ts +49 -0
  268. package/src/tools/im/message-read.ts +676 -0
  269. package/src/tools/im/message.ts +303 -0
  270. package/src/tools/im/resource.ts +225 -0
  271. package/src/tools/im/time-utils.ts +347 -0
  272. package/src/tools/index.ts +205 -0
  273. package/src/tools/oauth.ts +460 -0
  274. package/src/tools/search/doc-search.ts +250 -0
  275. package/src/tools/search/index.ts +22 -0
  276. package/src/tools/sheets/index.ts +22 -0
  277. package/src/tools/sheets/sheet.ts +382 -0
  278. package/src/tools/task/index.ts +34 -0
  279. package/src/tools/task/task.ts +265 -0
  280. package/src/tools/task/tasklist.ts +262 -0
  281. package/src/tools/wiki/index.ts +24 -0
  282. package/src/tools/wiki/node.ts +131 -0
  283. package/src/tools/wiki/space.ts +152 -0
  284. package/src/utils/index.ts +8 -0
  285. package/src/utils/logger.ts +132 -0
  286. package/tests/core/config.test.ts +238 -0
  287. package/tests/core/device-flow.test.ts +490 -0
  288. package/tests/core/lark-client.test.ts +378 -0
  289. package/tests/core/token-store.test.ts +438 -0
  290. package/tests/index.test.ts +360 -0
  291. package/tests/tools/doc/create.test.ts +224 -0
  292. package/tests/tools/doc/fetch.test.ts +182 -0
  293. package/tests/tools/doc/shared.test.ts +183 -0
  294. package/tests/tools/doc/update.test.ts +330 -0
  295. package/tests/tools/im/format-messages.test.ts +184 -0
  296. package/tests/tools/im/time-utils.test.ts +178 -0
  297. package/tests/utils/logger.test.ts +140 -0
  298. 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
+ }