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,127 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_chat tool - Manage Feishu chats/groups.
6
+ *
7
+ * Actions: search, get
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:chat:chat');
19
+
20
+ // Schemas
21
+ const searchActionSchema = {
22
+ action: z.literal('search').describe('Search for chats'),
23
+ query: z.string().describe('Search query (matches group name or member name)'),
24
+ page_size: z.number().min(1).optional().describe('Page size (default 20)'),
25
+ page_token: z.string().optional().describe('Pagination token'),
26
+ };
27
+
28
+ const getActionSchema = {
29
+ action: z.literal('get').describe('Get chat info'),
30
+ chat_id: z.string().describe('Chat ID (format: oc_xxx)'),
31
+ };
32
+
33
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
34
+ const { larkClient, config } = context;
35
+ if (!larkClient) return jsonError('LarkClient not initialized.');
36
+ const { appId, appSecret, brand } = config;
37
+ if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
38
+
39
+ const { listStoredTokens } = await import('../../core/token-store.js');
40
+ const tokens = await listStoredTokens(appId);
41
+ if (tokens.length === 0) return jsonError('No user authorization found.');
42
+ const userOpenId = tokens[0].userOpenId;
43
+
44
+ try {
45
+ return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
46
+ } catch (err) {
47
+ if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
48
+ throw err;
49
+ }
50
+ }
51
+
52
+ export function registerChatTool(registry: ToolRegistry): void {
53
+ // Search chats
54
+ registry.register({
55
+ name: 'feishu_chat_search',
56
+ description: 'Search for Feishu chats by name or member.\n\nRequires OAuth authorization.',
57
+ inputSchema: searchActionSchema,
58
+ handler: async (args, context) => {
59
+ const p = args as z.infer<ReturnType<typeof z.object<typeof searchActionSchema>>>;
60
+ const { larkClient } = context;
61
+
62
+ const tokenResult = await getAccessToken(context);
63
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
64
+ const accessToken = tokenResult;
65
+
66
+ log.info(`search: query="${p.query}", page_size=${p.page_size ?? 20}`);
67
+
68
+ const Lark = await import('@larksuiteoapi/node-sdk');
69
+ const opts = Lark.withUserAccessToken(accessToken);
70
+
71
+ const res = await larkClient!.sdk.im.v1.chat.search(
72
+ {
73
+ params: {
74
+ user_id_type: 'open_id',
75
+ query: p.query,
76
+ page_size: p.page_size,
77
+ page_token: p.page_token,
78
+ },
79
+ },
80
+ opts
81
+ );
82
+ assertLarkOk(res);
83
+
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ const data = res.data as any;
86
+
87
+ return json({
88
+ items: data?.items,
89
+ has_more: data?.has_more ?? false,
90
+ page_token: data?.page_token,
91
+ });
92
+ },
93
+ });
94
+
95
+ // Get chat info
96
+ registry.register({
97
+ name: 'feishu_chat_get',
98
+ description: 'Get Feishu chat info by ID.\n\nRequires OAuth authorization.',
99
+ inputSchema: getActionSchema,
100
+ handler: async (args, context) => {
101
+ const p = args as z.infer<ReturnType<typeof z.object<typeof getActionSchema>>>;
102
+ const { larkClient } = context;
103
+
104
+ const tokenResult = await getAccessToken(context);
105
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
106
+ const accessToken = tokenResult;
107
+
108
+ log.info(`get: chat_id=${p.chat_id}`);
109
+
110
+ const Lark = await import('@larksuiteoapi/node-sdk');
111
+ const opts = Lark.withUserAccessToken(accessToken);
112
+
113
+ const res = await larkClient!.sdk.im.v1.chat.get(
114
+ {
115
+ path: { chat_id: p.chat_id },
116
+ params: { user_id_type: 'open_id' },
117
+ },
118
+ opts
119
+ );
120
+ assertLarkOk(res);
121
+
122
+ return json({ chat: res.data });
123
+ },
124
+ });
125
+
126
+ log.debug('feishu_chat tools registered');
127
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Chat Tools Index
6
+ *
7
+ * Chat/group management tools for Feishu/Lark.
8
+ */
9
+
10
+ import type { ToolRegistry } from '../index.js';
11
+ import { registerChatTool } from './chat.js';
12
+ import { registerChatMembersTool } from './members.js';
13
+ import { logger } from '../../utils/logger.js';
14
+
15
+ const log = logger('tools:chat');
16
+
17
+ export function registerChatTools(registry: ToolRegistry): void {
18
+ registerChatTool(registry);
19
+ registerChatMembersTool(registry);
20
+
21
+ log.info('Chat tools registered', {
22
+ tools: ['feishu_chat_search', 'feishu_chat_get', 'feishu_chat_members'].join(', '),
23
+ });
24
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_chat_members tool - Get chat members.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+ import type { ToolRegistry } from '../index.js';
10
+ import { LarkClient } from '../../core/lark-client.js';
11
+ import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
12
+ import { assertLarkOk } from '../../core/api-error.js';
13
+ import { json, jsonError, type ToolResult } from '../im/helpers.js';
14
+ import { logger } from '../../utils/logger.js';
15
+
16
+ const log = logger('tools:chat:members');
17
+
18
+ // Schemas
19
+ const membersActionSchema = {
20
+ chat_id: z.string().describe('Chat ID (format: oc_xxx)'),
21
+ member_id_type: z.enum(['open_id', 'union_id', 'user_id']).optional().describe('Member ID type (default: open_id)'),
22
+ page_size: z.number().min(1).optional().describe('Page size (default 20)'),
23
+ page_token: z.string().optional().describe('Pagination token'),
24
+ };
25
+
26
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
27
+ const { larkClient, config } = context;
28
+ if (!larkClient) return jsonError('LarkClient not initialized.');
29
+ const { appId, appSecret, brand } = config;
30
+ if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
31
+
32
+ const { listStoredTokens } = await import('../../core/token-store.js');
33
+ const tokens = await listStoredTokens(appId);
34
+ if (tokens.length === 0) return jsonError('No user authorization found.');
35
+ const userOpenId = tokens[0].userOpenId;
36
+
37
+ try {
38
+ return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
39
+ } catch (err) {
40
+ if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ export function registerChatMembersTool(registry: ToolRegistry): void {
46
+ registry.register({
47
+ name: 'feishu_chat_members',
48
+ description: 'Get members of a Feishu chat.\n\nRequires OAuth authorization.',
49
+ inputSchema: membersActionSchema,
50
+ handler: async (args, context) => {
51
+ const p = args as z.infer<ReturnType<typeof z.object<typeof membersActionSchema>>>;
52
+ const { larkClient } = context;
53
+
54
+ const tokenResult = await getAccessToken(context);
55
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
56
+ const accessToken = tokenResult;
57
+
58
+ log.info(`members: chat_id="${p.chat_id}", page_size=${p.page_size ?? 20}`);
59
+
60
+ const Lark = await import('@larksuiteoapi/node-sdk');
61
+ const opts = Lark.withUserAccessToken(accessToken);
62
+
63
+ const res = await larkClient!.sdk.im.v1.chatMembers.get(
64
+ {
65
+ path: { chat_id: p.chat_id },
66
+ params: {
67
+ member_id_type: p.member_id_type || 'open_id',
68
+ page_size: p.page_size,
69
+ page_token: p.page_token,
70
+ },
71
+ },
72
+ opts
73
+ );
74
+ assertLarkOk(res);
75
+
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const data = res.data as any;
78
+ const memberCount = data?.items?.length ?? 0;
79
+ const memberTotal = data?.member_total ?? 0;
80
+
81
+ log.info(`members: found ${memberCount} members (total: ${memberTotal})`);
82
+
83
+ return json({
84
+ items: data?.items,
85
+ has_more: data?.has_more ?? false,
86
+ page_token: data?.page_token,
87
+ member_total: memberTotal,
88
+ });
89
+ },
90
+ });
91
+
92
+ log.debug('feishu_chat_members tool registered');
93
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_get_user tool - Get user information.
6
+ *
7
+ * Actions: get current user or get user by ID
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 { json, jsonError, type ToolResult } from '../im/helpers.js';
15
+ import { logger } from '../../utils/logger.js';
16
+
17
+ const log = logger('tools:common:get-user');
18
+
19
+ // Schemas
20
+ const getUserSchema = {
21
+ user_id: z.string().optional().describe('User ID (format: ou_xxx). If not provided, returns current user info'),
22
+ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).optional().describe('User ID type (default: open_id)'),
23
+ };
24
+
25
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
26
+ const { larkClient, config } = context;
27
+ if (!larkClient) return jsonError('LarkClient not initialized.');
28
+ const { appId, appSecret, brand } = config;
29
+ if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
30
+
31
+ const { listStoredTokens } = await import('../../core/token-store.js');
32
+ const tokens = await listStoredTokens(appId);
33
+ if (tokens.length === 0) return jsonError('No user authorization found.');
34
+ const userOpenId = tokens[0].userOpenId;
35
+
36
+ try {
37
+ return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
38
+ } catch (err) {
39
+ if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
40
+ throw err;
41
+ }
42
+ }
43
+
44
+ export function registerGetUserTool(registry: ToolRegistry): void {
45
+ registry.register({
46
+ name: 'feishu_get_user',
47
+ description: 'Get Feishu user information. Returns current user if user_id not provided.\n\nRequires OAuth authorization.',
48
+ inputSchema: getUserSchema,
49
+ handler: async (args, context) => {
50
+ const p = args as z.infer<ReturnType<typeof z.object<typeof getUserSchema>>>;
51
+ const { larkClient } = context;
52
+
53
+ const tokenResult = await getAccessToken(context);
54
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
55
+ const accessToken = tokenResult;
56
+
57
+ const Lark = await import('@larksuiteoapi/node-sdk');
58
+ const opts = Lark.withUserAccessToken(accessToken);
59
+
60
+ // If no user_id provided, get current user info
61
+ if (!p.user_id) {
62
+ log.info('get_user: fetching current user info');
63
+
64
+ try {
65
+ const res = await larkClient!.sdk.authen.userInfo.get({}, opts);
66
+
67
+ // Check for API error
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ if ((res as any).code !== undefined && (res as any).code !== 0) {
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ const e = res as any;
72
+ if (e.code === 41050) {
73
+ return jsonError('Permission denied. User visibility scope limits access to this user.');
74
+ }
75
+ return jsonError(`API Error: code=${e.code}, msg=${e.msg}`);
76
+ }
77
+
78
+ log.info('get_user: current user fetched successfully');
79
+ return json({ user: res.data });
80
+ } catch (invokeErr) {
81
+ // Handle 41050 error
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ if (invokeErr && typeof invokeErr === 'object' && (invokeErr as any).response?.data?.code === 41050) {
84
+ return jsonError('Permission denied. User visibility scope limits access to this user.');
85
+ }
86
+ throw invokeErr;
87
+ }
88
+ }
89
+
90
+ // Get specific user info
91
+ log.info(`get_user: fetching user ${p.user_id}`);
92
+
93
+ const userIdType = p.user_id_type || 'open_id';
94
+
95
+ try {
96
+ const res = await larkClient!.sdk.contact.v3.user.get(
97
+ {
98
+ path: { user_id: p.user_id },
99
+ params: { user_id_type: userIdType as 'open_id' | 'union_id' | 'user_id' },
100
+ },
101
+ opts
102
+ );
103
+
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ if ((res as any).code !== undefined && (res as any).code !== 0) {
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ const e = res as any;
108
+ if (e.code === 41050) {
109
+ return jsonError('Permission denied. User visibility scope limits access to this user.');
110
+ }
111
+ return jsonError(`API Error: code=${e.code}, msg=${e.msg}`);
112
+ }
113
+
114
+ log.info(`get_user: user ${p.user_id} fetched successfully`);
115
+ return json({ user: res.data?.user });
116
+ } catch (invokeErr) {
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ if (invokeErr && typeof invokeErr === 'object' && (invokeErr as any).response?.data?.code === 41050) {
119
+ return jsonError('Permission denied. User visibility scope limits access to this user.');
120
+ }
121
+ throw invokeErr;
122
+ }
123
+ },
124
+ });
125
+
126
+ log.debug('feishu_get_user tool registered');
127
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Common Tools Index
6
+ *
7
+ * User-related tools for Feishu/Lark.
8
+ */
9
+
10
+ import type { ToolRegistry } from '../index.js';
11
+ import { registerGetUserTool } from './get-user.js';
12
+ import { registerSearchUserTool } from './search-user.js';
13
+ import { logger } from '../../utils/logger.js';
14
+
15
+ const log = logger('tools:common');
16
+
17
+ export function registerCommonTools(registry: ToolRegistry): void {
18
+ registerGetUserTool(registry);
19
+ registerSearchUserTool(registry);
20
+
21
+ log.info('Common tools registered', {
22
+ tools: ['feishu_get_user', 'feishu_search_user'].join(', '),
23
+ });
24
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_search_user tool - Search for users.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+ import type { ToolRegistry } from '../index.js';
10
+ import { LarkClient } from '../../core/lark-client.js';
11
+ import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
12
+ import { json, jsonError, type ToolResult } from '../im/helpers.js';
13
+ import { logger } from '../../utils/logger.js';
14
+
15
+ const log = logger('tools:common:search-user');
16
+
17
+ // Schemas
18
+ const searchUserSchema = {
19
+ query: z.string().describe('Search query (matches user name, phone, email)'),
20
+ page_size: z.number().min(1).max(200).optional().describe('Page size (default 20)'),
21
+ page_token: z.string().optional().describe('Pagination token'),
22
+ };
23
+
24
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
25
+ const { larkClient, config } = context;
26
+ if (!larkClient) return jsonError('LarkClient not initialized.');
27
+ const { appId, appSecret, brand } = config;
28
+ if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
29
+
30
+ const { listStoredTokens } = await import('../../core/token-store.js');
31
+ const tokens = await listStoredTokens(appId);
32
+ if (tokens.length === 0) return jsonError('No user authorization found.');
33
+ const userOpenId = tokens[0].userOpenId;
34
+
35
+ try {
36
+ return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
37
+ } catch (err) {
38
+ if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
39
+ throw err;
40
+ }
41
+ }
42
+
43
+ export function registerSearchUserTool(registry: ToolRegistry): void {
44
+ registry.register({
45
+ name: 'feishu_search_user',
46
+ description: 'Search for Feishu users by name, phone, or email.\n\nRequires OAuth authorization.',
47
+ inputSchema: searchUserSchema,
48
+ handler: async (args, context) => {
49
+ const p = args as z.infer<ReturnType<typeof z.object<typeof searchUserSchema>>>;
50
+ const { larkClient } = context;
51
+
52
+ const tokenResult = await getAccessToken(context);
53
+ if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
54
+ const accessToken = tokenResult;
55
+
56
+ log.info(`search_user: query="${p.query}", page_size=${p.page_size ?? 20}`);
57
+
58
+ const Lark = await import('@larksuiteoapi/node-sdk');
59
+ const opts = Lark.withUserAccessToken(accessToken);
60
+
61
+ // Build query parameters
62
+ const queryParams: Record<string, string> = {
63
+ query: p.query,
64
+ page_size: String(p.page_size ?? 20),
65
+ };
66
+ if (p.page_token) queryParams.page_token = p.page_token;
67
+
68
+ // Use direct request since SDK search API may not be fully typed
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ const res = await (larkClient!.sdk as any).request({
71
+ method: 'GET',
72
+ url: '/open-apis/search/v1/user',
73
+ params: queryParams,
74
+ headers: { Authorization: `Bearer ${accessToken}` },
75
+ }, opts);
76
+
77
+ // Check for API error
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ if ((res as any).code !== undefined && (res as any).code !== 0) {
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ return jsonError(`API Error: code=${(res as any).code}, msg=${(res as any).msg}`);
82
+ }
83
+
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ const data = res.data as any;
86
+ const users = data?.users ?? [];
87
+
88
+ log.info(`search_user: found ${users.length} users`);
89
+
90
+ return json({
91
+ users,
92
+ has_more: data?.has_more ?? false,
93
+ page_token: data?.page_token,
94
+ });
95
+ },
96
+ });
97
+
98
+ log.debug('feishu_search_user tool registered');
99
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_create_doc tool - Create a new docx document from Markdown.
6
+ *
7
+ * Creates a new Feishu document from Markdown content.
8
+ * Supports async task status checking via task_id.
9
+ *
10
+ * Adapted from openclaw-lark for MCP Server architecture.
11
+ */
12
+
13
+ import { z } from 'zod';
14
+ import type { ToolRegistry } from '../index.js';
15
+ import { LarkClient } from '../../core/lark-client.js';
16
+ import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
17
+ import { callMcpTool, json, jsonError, processMcpResult, type ToolResult } from './shared.js';
18
+ import { logger } from '../../utils/logger.js';
19
+
20
+ const log = logger('tools:doc:create');
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Input schema
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const createDocSchema = {
27
+ markdown: z.string().optional().describe('Markdown content for the document'),
28
+ title: z.string().optional().describe('Document title'),
29
+ folder_token: z.string().optional().describe('Parent folder token (optional)'),
30
+ wiki_node: z
31
+ .string()
32
+ .optional()
33
+ .describe('Wiki node token or URL (optional, creates document under this node)'),
34
+ wiki_space: z.string().optional().describe('Wiki space ID (optional, special value: my_library)'),
35
+ task_id: z
36
+ .string()
37
+ .optional()
38
+ .describe('Async task ID. If provided, queries task status instead of creating a new document'),
39
+ };
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Validation
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function validateCreateDocParams(p: Record<string, unknown>): void {
46
+ // If task_id is provided, we're just querying status
47
+ if (p.task_id) return;
48
+
49
+ // For creating new doc, markdown and title are required
50
+ if (!p.markdown || !p.title) {
51
+ throw new Error('create-doc: When not providing task_id, markdown and title are required');
52
+ }
53
+
54
+ // Only one of folder_token, wiki_node, wiki_space can be provided
55
+ const flags = [p.folder_token, p.wiki_node, p.wiki_space].filter(Boolean);
56
+ if (flags.length > 1) {
57
+ throw new Error('create-doc: folder_token / wiki_node / wiki_space are mutually exclusive, provide only one');
58
+ }
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Tool registration
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /**
66
+ * Register the feishu_create_doc tool.
67
+ */
68
+ export function registerCreateDocTool(registry: ToolRegistry): void {
69
+ registry.register({
70
+ name: 'feishu_create_doc',
71
+ description: [
72
+ 'Create a new Feishu docx document from Markdown content.',
73
+ '',
74
+ 'Usage:',
75
+ '- Provide markdown and title to create a new document',
76
+ '- Provide task_id to check async task status',
77
+ '- Optionally specify folder_token, wiki_node, or wiki_space for document location',
78
+ '',
79
+ 'Parameters:',
80
+ '- markdown: Markdown content for the document (required for new doc)',
81
+ '- title: Document title (required for new doc)',
82
+ '- folder_token: Parent folder token (optional)',
83
+ '- wiki_node: Wiki node token or URL (optional)',
84
+ '- wiki_space: Wiki space ID (optional)',
85
+ '- task_id: Async task ID for status check (optional)',
86
+ '',
87
+ 'Returns:',
88
+ '- For new doc: { task_id, doc_id } or completed document info',
89
+ '- For task_id query: { status, result? }',
90
+ '',
91
+ 'Requires OAuth authorization (use feishu_oauth tool first).',
92
+ ].join('\n'),
93
+ inputSchema: createDocSchema,
94
+ handler: async (args, context) => {
95
+ return handleCreateDoc(args, context);
96
+ },
97
+ });
98
+
99
+ log.debug('feishu_create_doc tool registered');
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Handler
104
+ // ---------------------------------------------------------------------------
105
+
106
+ async function handleCreateDoc(
107
+ args: unknown,
108
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
109
+ ): Promise<ToolResult> {
110
+ const p = args as Record<string, unknown>;
111
+ const { larkClient, config } = context;
112
+
113
+ if (!larkClient) {
114
+ return jsonError('LarkClient not initialized. Check FEISHU_APP_ID and FEISHU_APP_SECRET.');
115
+ }
116
+
117
+ const { appId, appSecret, brand } = config;
118
+ if (!appId || !appSecret) {
119
+ return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
120
+ }
121
+
122
+ // Validate parameters
123
+ try {
124
+ validateCreateDocParams(p);
125
+ } catch (err) {
126
+ return jsonError(err instanceof Error ? err.message : String(err));
127
+ }
128
+
129
+ // Get the first stored user token
130
+ const { listStoredTokens } = await import('../../core/token-store.js');
131
+ const tokens = await listStoredTokens(appId);
132
+ if (tokens.length === 0) {
133
+ return jsonError(
134
+ 'No user authorization found. Please use the feishu_oauth tool with action="authorize" to authorize a user first.'
135
+ );
136
+ }
137
+
138
+ const userOpenId = tokens[0].userOpenId;
139
+
140
+ try {
141
+ const accessToken = await getValidAccessToken({
142
+ userOpenId,
143
+ appId,
144
+ appSecret,
145
+ domain: brand ?? 'feishu',
146
+ });
147
+
148
+ log.info('Creating document', {
149
+ title: p.title,
150
+ has_markdown: !!p.markdown,
151
+ folder_token: p.folder_token,
152
+ wiki_node: p.wiki_node,
153
+ task_id: p.task_id,
154
+ });
155
+
156
+ // Build MCP tool arguments
157
+ const mcpArgs: Record<string, unknown> = {};
158
+ if (p.markdown) mcpArgs.markdown = p.markdown;
159
+ if (p.title) mcpArgs.title = p.title;
160
+ if (p.folder_token) mcpArgs.folder_token = p.folder_token;
161
+ if (p.wiki_node) mcpArgs.wiki_node = p.wiki_node;
162
+ if (p.wiki_space) mcpArgs.wiki_space = p.wiki_space;
163
+ if (p.task_id) mcpArgs.task_id = p.task_id;
164
+
165
+ // Generate a unique tool call ID
166
+ const toolCallId = `create-doc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
167
+
168
+ // Call the MCP endpoint
169
+ const result = await callMcpTool('create-doc', mcpArgs, toolCallId, accessToken);
170
+
171
+ log.info('Document created/task queried', { result });
172
+
173
+ return processMcpResult(result);
174
+ } catch (err) {
175
+ if (err instanceof NeedAuthorizationError) {
176
+ return jsonError(
177
+ `User authorization required or expired. Please use feishu_oauth tool with action="authorize" to re-authorize.`,
178
+ { userOpenId }
179
+ );
180
+ }
181
+ log.error('Create document failed', { error: err instanceof Error ? err.message : String(err) });
182
+ return jsonError(err instanceof Error ? err.message : String(err));
183
+ }
184
+ }