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,22 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Search Tools Index
6
+ *
7
+ * Search tools for Feishu/Lark.
8
+ */
9
+
10
+ import type { ToolRegistry } from '../index.js';
11
+ import { registerSearchDocWikiTool } from './doc-search.js';
12
+ import { logger } from '../../utils/logger.js';
13
+
14
+ const log = logger('tools:search');
15
+
16
+ export function registerSearchTools(registry: ToolRegistry): void {
17
+ registerSearchDocWikiTool(registry);
18
+
19
+ log.info('Search tools registered', {
20
+ tools: ['feishu_search_doc_wiki'].join(', '),
21
+ });
22
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Sheets Tools Index
6
+ *
7
+ * Spreadsheet tools for Feishu/Lark.
8
+ */
9
+
10
+ import type { ToolRegistry } from '../index.js';
11
+ import { registerSheetTool } from './sheet.js';
12
+ import { logger } from '../../utils/logger.js';
13
+
14
+ const log = logger('tools:sheets');
15
+
16
+ export function registerSheetsTools(registry: ToolRegistry): void {
17
+ registerSheetTool(registry);
18
+
19
+ log.info('Sheets tools registered', {
20
+ tools: ['feishu_sheet_info', 'feishu_sheet_read', 'feishu_sheet_write', 'feishu_sheet_append', 'feishu_sheet_create'].join(', '),
21
+ });
22
+ }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_sheet tool - Manage Feishu Spreadsheets.
6
+ *
7
+ * Actions: info, read, write, append, create
8
+ */
9
+
10
+ import { z } from 'zod';
11
+ import type { ToolRegistry } from '../index.js';
12
+ import { LarkClient } from '../../core/lark-client.js';
13
+ import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
14
+ import { assertLarkOk } from '../../core/api-error.js';
15
+ import { json, jsonError, type ToolResult } from '../im/helpers.js';
16
+ import { logger } from '../../utils/logger.js';
17
+
18
+ const log = logger('tools:sheets:sheet');
19
+
20
+ const MAX_READ_ROWS = 200;
21
+
22
+ /**
23
+ * Parse spreadsheet URL to extract token and optional sheet ID.
24
+ */
25
+ function parseSheetUrl(url: string): { token: string; sheetId?: string } | null {
26
+ try {
27
+ const u = new URL(url);
28
+ const match = u.pathname.match(/\/(?:sheets|wiki)\/([^/?#]+)/);
29
+ if (!match) return null;
30
+ return {
31
+ token: match[1],
32
+ sheetId: u.searchParams.get('sheet') || undefined,
33
+ };
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Convert column number to letter (A, B, ..., Z, AA, AB, ...).
41
+ */
42
+ function colLetter(n: number): string {
43
+ let result = '';
44
+ while (n > 0) {
45
+ n--;
46
+ result = String.fromCharCode(65 + (n % 26)) + result;
47
+ n = Math.floor(n / 26);
48
+ }
49
+ return result;
50
+ }
51
+
52
+ // Schemas
53
+ const infoActionSchema = {
54
+ action: z.literal('info').describe('Get spreadsheet info'),
55
+ spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
56
+ url: z.string().optional().describe('Spreadsheet URL'),
57
+ };
58
+
59
+ const readActionSchema = {
60
+ action: z.literal('read').describe('Read data from a spreadsheet'),
61
+ spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
62
+ url: z.string().optional().describe('Spreadsheet URL'),
63
+ range: z.string().optional().describe('Range to read (e.g., Sheet1!A1:D10)'),
64
+ sheet_id: z.string().optional().describe('Sheet ID'),
65
+ };
66
+
67
+ const writeActionSchema = {
68
+ action: z.literal('write').describe('Write data to a spreadsheet (overwrites existing data)'),
69
+ spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
70
+ url: z.string().optional().describe('Spreadsheet URL'),
71
+ range: z.string().optional().describe('Range to write (e.g., Sheet1!A1)'),
72
+ sheet_id: z.string().optional().describe('Sheet ID'),
73
+ values: z.array(z.array(z.any())).describe('Data to write (2D array)'),
74
+ };
75
+
76
+ const appendActionSchema = {
77
+ action: z.literal('append').describe('Append data to a spreadsheet'),
78
+ spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
79
+ url: z.string().optional().describe('Spreadsheet URL'),
80
+ range: z.string().optional().describe('Range to append to (e.g., Sheet1)'),
81
+ sheet_id: z.string().optional().describe('Sheet ID'),
82
+ values: z.array(z.array(z.any())).describe('Data to append (2D array)'),
83
+ };
84
+
85
+ const createActionSchema = {
86
+ action: z.literal('create').describe('Create a new spreadsheet'),
87
+ title: z.string().describe('Spreadsheet title'),
88
+ folder_token: z.string().optional().describe('Folder token (optional)'),
89
+ };
90
+
91
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
92
+ const { larkClient, config } = context;
93
+ if (!larkClient) return jsonError('LarkClient not initialized.');
94
+ const { appId, appSecret, brand } = config;
95
+ if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
96
+
97
+ const { listStoredTokens } = await import('../../core/token-store.js');
98
+ const tokens = await listStoredTokens(appId);
99
+ if (tokens.length === 0) return jsonError('No user authorization found.');
100
+ const userOpenId = tokens[0].userOpenId;
101
+
102
+ try {
103
+ return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
104
+ } catch (err) {
105
+ if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
106
+ throw err;
107
+ }
108
+ }
109
+
110
+ async function resolveToken(
111
+ p: { url?: string; spreadsheet_token?: string },
112
+ larkClient: LarkClient,
113
+ accessToken: string
114
+ ): Promise<{ token: string; urlSheetId?: string }> {
115
+ let token: string;
116
+ let urlSheetId: string | undefined;
117
+
118
+ if (p.spreadsheet_token) {
119
+ token = p.spreadsheet_token;
120
+ } else if (p.url) {
121
+ const parsed = parseSheetUrl(p.url);
122
+ if (!parsed) {
123
+ throw new Error(`Failed to parse spreadsheet_token from URL: ${p.url}`);
124
+ }
125
+ token = parsed.token;
126
+ urlSheetId = parsed.sheetId;
127
+ } else {
128
+ throw new Error('url or spreadsheet_token is required');
129
+ }
130
+
131
+ return { token, urlSheetId };
132
+ }
133
+
134
+ export function registerSheetTool(registry: ToolRegistry): void {
135
+ // Info
136
+ registry.register({
137
+ name: 'feishu_sheet_info',
138
+ description: 'Get Feishu spreadsheet info and sheet list.\n\nRequires OAuth authorization.',
139
+ inputSchema: infoActionSchema,
140
+ handler: async (args, context) => {
141
+ const p = args as z.infer<ReturnType<typeof z.object<typeof infoActionSchema>>>;
142
+ const { larkClient } = context;
143
+
144
+ const tokenResult = await getAccessToken(context);
145
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
146
+ const accessToken = tokenResult;
147
+
148
+ const { token } = await resolveToken(p, larkClient!, accessToken);
149
+ log.info(`info: token=${token}`);
150
+
151
+ const Lark = await import('@larksuiteoapi/node-sdk');
152
+ const opts = Lark.withUserAccessToken(accessToken);
153
+
154
+ const [spreadsheetRes, sheetsRes] = await Promise.all([
155
+ larkClient!.sdk.sheets.spreadsheet.get({ path: { spreadsheet_token: token } }, opts),
156
+ larkClient!.sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts),
157
+ ]);
158
+ assertLarkOk(spreadsheetRes);
159
+ assertLarkOk(sheetsRes);
160
+
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ const spreadsheet = spreadsheetRes.data?.spreadsheet as any;
163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
+ const sheets = (sheetsRes.data?.sheets ?? []).map((s: any) => ({
165
+ sheet_id: s.sheet_id,
166
+ title: s.title,
167
+ index: s.index,
168
+ row_count: s.grid_properties?.row_count,
169
+ column_count: s.grid_properties?.column_count,
170
+ }));
171
+
172
+ return json({
173
+ title: spreadsheet?.title,
174
+ spreadsheet_token: token,
175
+ url: `https://www.feishu.cn/sheets/${token}`,
176
+ sheets,
177
+ });
178
+ },
179
+ });
180
+
181
+ // Read
182
+ registry.register({
183
+ name: 'feishu_sheet_read',
184
+ description: 'Read data from a Feishu spreadsheet.\n\nRequires OAuth authorization.',
185
+ inputSchema: readActionSchema,
186
+ handler: async (args, context) => {
187
+ const p = args as z.infer<ReturnType<typeof z.object<typeof readActionSchema>>>;
188
+ const { larkClient } = context;
189
+
190
+ const tokenResult = await getAccessToken(context);
191
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
192
+ const accessToken = tokenResult;
193
+
194
+ const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
195
+ let range = p.range;
196
+ if (!range && (p.sheet_id || urlSheetId)) {
197
+ range = p.sheet_id || urlSheetId;
198
+ }
199
+
200
+ if (!range) {
201
+ // Get first sheet
202
+ const Lark = await import('@larksuiteoapi/node-sdk');
203
+ const opts = Lark.withUserAccessToken(accessToken);
204
+ const sheetsRes = await larkClient!.sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts);
205
+ assertLarkOk(sheetsRes);
206
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
207
+ const firstSheet = (sheetsRes.data?.sheets ?? [])[0] as any;
208
+ if (!firstSheet?.sheet_id) {
209
+ return jsonError('Spreadsheet has no worksheets');
210
+ }
211
+ range = firstSheet.sheet_id;
212
+ }
213
+
214
+ log.info(`read: token=${token}, range=${range}`);
215
+
216
+ // Use direct API call for reading values
217
+ const Lark = await import('@larksuiteoapi/node-sdk');
218
+ const opts = Lark.withUserAccessToken(accessToken);
219
+
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
+ const res = await (larkClient!.sdk as any).request({
222
+ method: 'GET',
223
+ url: `/open-apis/sheets/v2/spreadsheets/${token}/values/${encodeURIComponent(range!)}`,
224
+ headers: { Authorization: `Bearer ${accessToken}` },
225
+ }, opts);
226
+
227
+ if (res.code && res.code !== 0) {
228
+ return jsonError(res.msg || `API error: ${res.code}`);
229
+ }
230
+
231
+ const valueRange = res.data?.valueRange;
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ let values = valueRange?.values as any[][] | undefined;
234
+
235
+ // Truncate if needed
236
+ let truncated = false;
237
+ let totalRows = values?.length ?? 0;
238
+ if (values && values.length > MAX_READ_ROWS) {
239
+ values = values.slice(0, MAX_READ_ROWS);
240
+ truncated = true;
241
+ }
242
+
243
+ return json({
244
+ range: valueRange?.range,
245
+ values,
246
+ ...(truncated ? { truncated: true, total_rows: totalRows, hint: `Data exceeds ${MAX_READ_ROWS} rows, truncated.` } : {}),
247
+ });
248
+ },
249
+ });
250
+
251
+ // Write
252
+ registry.register({
253
+ name: 'feishu_sheet_write',
254
+ description: 'Write data to a Feishu spreadsheet (overwrites existing data).\n\nRequires OAuth authorization.',
255
+ inputSchema: writeActionSchema,
256
+ handler: async (args, context) => {
257
+ const p = args as z.infer<ReturnType<typeof z.object<typeof writeActionSchema>>>;
258
+ const { larkClient } = context;
259
+
260
+ const tokenResult = await getAccessToken(context);
261
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
262
+ const accessToken = tokenResult;
263
+
264
+ const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
265
+ let range = p.range;
266
+ if (!range && (p.sheet_id || urlSheetId)) {
267
+ range = p.sheet_id || urlSheetId;
268
+ }
269
+
270
+ log.info(`write: token=${token}, range=${range}, rows=${p.values?.length}`);
271
+
272
+ const Lark = await import('@larksuiteoapi/node-sdk');
273
+ const opts = Lark.withUserAccessToken(accessToken);
274
+
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ const res = await (larkClient!.sdk as any).request({
277
+ method: 'PUT',
278
+ url: `/open-apis/sheets/v2/spreadsheets/${token}/values`,
279
+ data: { valueRange: { range, values: p.values } },
280
+ headers: { Authorization: `Bearer ${accessToken}` },
281
+ }, opts);
282
+
283
+ if (res.code && res.code !== 0) {
284
+ return jsonError(res.msg || `API error: ${res.code}`);
285
+ }
286
+
287
+ return json({
288
+ updated_range: res.data?.updatedRange,
289
+ updated_rows: res.data?.updatedRows,
290
+ updated_columns: res.data?.updatedColumns,
291
+ updated_cells: res.data?.updatedCells,
292
+ });
293
+ },
294
+ });
295
+
296
+ // Append
297
+ registry.register({
298
+ name: 'feishu_sheet_append',
299
+ description: 'Append data to a Feishu spreadsheet.\n\nRequires OAuth authorization.',
300
+ inputSchema: appendActionSchema,
301
+ handler: async (args, context) => {
302
+ const p = args as z.infer<ReturnType<typeof z.object<typeof appendActionSchema>>>;
303
+ const { larkClient } = context;
304
+
305
+ const tokenResult = await getAccessToken(context);
306
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
307
+ const accessToken = tokenResult;
308
+
309
+ const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
310
+ let range = p.range;
311
+ if (!range && (p.sheet_id || urlSheetId)) {
312
+ range = p.sheet_id || urlSheetId;
313
+ }
314
+
315
+ log.info(`append: token=${token}, range=${range}, rows=${p.values?.length}`);
316
+
317
+ const Lark = await import('@larksuiteoapi/node-sdk');
318
+ const opts = Lark.withUserAccessToken(accessToken);
319
+
320
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
321
+ const res = await (larkClient!.sdk as any).request({
322
+ method: 'POST',
323
+ url: `/open-apis/sheets/v2/spreadsheets/${token}/values_append`,
324
+ data: { valueRange: { range, values: p.values } },
325
+ headers: { Authorization: `Bearer ${accessToken}` },
326
+ }, opts);
327
+
328
+ if (res.code && res.code !== 0) {
329
+ return jsonError(res.msg || `API error: ${res.code}`);
330
+ }
331
+
332
+ const updates = res.data?.updates;
333
+
334
+ return json({
335
+ table_range: res.data?.tableRange,
336
+ updated_range: updates?.updatedRange,
337
+ updated_rows: updates?.updatedRows,
338
+ updated_columns: updates?.updatedColumns,
339
+ updated_cells: updates?.updatedCells,
340
+ });
341
+ },
342
+ });
343
+
344
+ // Create
345
+ registry.register({
346
+ name: 'feishu_sheet_create',
347
+ description: 'Create a new Feishu spreadsheet.\n\nRequires OAuth authorization.',
348
+ inputSchema: createActionSchema,
349
+ handler: async (args, context) => {
350
+ const p = args as z.infer<ReturnType<typeof z.object<typeof createActionSchema>>>;
351
+ const { larkClient } = context;
352
+
353
+ const tokenResult = await getAccessToken(context);
354
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
355
+ const accessToken = tokenResult;
356
+
357
+ log.info(`create: title=${p.title}, folder=${p.folder_token ?? '(root)'}`);
358
+
359
+ const Lark = await import('@larksuiteoapi/node-sdk');
360
+ const opts = Lark.withUserAccessToken(accessToken);
361
+
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
+ const data: any = { title: p.title };
364
+ if (p.folder_token) data.folder_token = p.folder_token;
365
+
366
+ const res = await larkClient!.sdk.sheets.spreadsheet.create({ data }, opts);
367
+ assertLarkOk(res);
368
+
369
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
370
+ const spreadsheet = res.data?.spreadsheet as any;
371
+ const token = spreadsheet?.spreadsheet_token;
372
+
373
+ return json({
374
+ spreadsheet_token: token,
375
+ title: p.title,
376
+ url: `https://www.feishu.cn/sheets/${token}`,
377
+ });
378
+ },
379
+ });
380
+
381
+ log.debug('feishu_sheet tools registered');
382
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Task Tools Index
6
+ *
7
+ * Task tools for Feishu/Lark.
8
+ * Adapted from openclaw-lark for MCP Server architecture.
9
+ */
10
+
11
+ import type { ToolRegistry } from '../index.js';
12
+ import { registerTaskTool } from './task.js';
13
+ import { registerTasklistTool } from './tasklist.js';
14
+ import { logger } from '../../utils/logger.js';
15
+
16
+ const log = logger('tools:task');
17
+
18
+ export function registerTaskTools(registry: ToolRegistry): void {
19
+ registerTaskTool(registry);
20
+ registerTasklistTool(registry);
21
+
22
+ log.info('Task tools registered', {
23
+ tools: [
24
+ 'feishu_task_create',
25
+ 'feishu_task_get',
26
+ 'feishu_task_list',
27
+ 'feishu_task_patch',
28
+ 'feishu_tasklist_create',
29
+ 'feishu_tasklist_get',
30
+ 'feishu_tasklist_list',
31
+ 'feishu_tasklist_tasks',
32
+ ].join(', '),
33
+ });
34
+ }