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,302 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Core type definitions for the cc-lark MCP Server.
6
+ *
7
+ * Contains Feishu/Lark API types, configuration interfaces, and MCP-specific types.
8
+ * Adapted from openclaw-lark for MCP Server architecture.
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Domain & connection enums
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /**
16
+ * The Lark platform brand.
17
+ * - `"feishu"` targets the China-mainland Feishu service.
18
+ * - `"lark"` targets the international Lark service.
19
+ * - Any other string is treated as a custom base URL.
20
+ */
21
+ export type LarkBrand = 'feishu' | 'lark' | (string & {});
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Feishu identifiers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /** The four ID types recognised by the Feishu API. */
28
+ export type FeishuIdType = 'open_id' | 'user_id' | 'union_id' | 'chat_id';
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // MCP-specific types
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /** Standard MCP tool result structure. */
35
+ export interface ToolResult {
36
+ content: Array<{
37
+ type: 'text' | 'image' | 'resource';
38
+ text?: string;
39
+ data?: string;
40
+ mimeType?: string;
41
+ resource?: {
42
+ uri: string;
43
+ name: string;
44
+ mimeType?: string;
45
+ text?: string;
46
+ blob?: string;
47
+ };
48
+ }>;
49
+ isError?: boolean;
50
+ }
51
+
52
+ /** Tool definition for MCP server. */
53
+ export interface ToolDefinition {
54
+ name: string;
55
+ description: string;
56
+ inputSchema: {
57
+ type: 'object';
58
+ properties?: Record<string, unknown>;
59
+ required?: string[];
60
+ };
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Configuration types
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /** Feishu configuration loaded from environment variables. */
68
+ export interface FeishuConfig {
69
+ /** Feishu App ID */
70
+ appId: string;
71
+ /** Feishu App Secret */
72
+ appSecret: string;
73
+ /** User Access Token (optional, for user-authorized operations) */
74
+ userAccessToken?: string;
75
+ /** The Lark platform brand (feishu, lark, or custom URL) */
76
+ brand?: LarkBrand;
77
+ /** Encrypt key for webhook event decryption */
78
+ encryptKey?: string;
79
+ /** Verification token for webhook validation */
80
+ verificationToken?: string;
81
+ }
82
+
83
+ /** Validation result for configuration. */
84
+ export interface ConfigValidationResult {
85
+ valid: boolean;
86
+ errors: string[];
87
+ config?: FeishuConfig;
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Credentials
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /** The minimum credential set needed to interact with the Lark API. */
95
+ export interface LarkCredentials {
96
+ appId: string;
97
+ appSecret: string;
98
+ encryptKey?: string;
99
+ verificationToken?: string;
100
+ brand: LarkBrand;
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Lark API response types
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /** Base response from Lark API. */
108
+ export interface LarkApiResponse<T = unknown> {
109
+ code: number;
110
+ msg: string;
111
+ data?: T;
112
+ }
113
+
114
+ /** Token response from Lark API. */
115
+ export interface LarkTokenResponse {
116
+ tenant_access_token: string;
117
+ expire: number;
118
+ }
119
+
120
+ /** User info from Lark API. */
121
+ export interface LarkUserInfo {
122
+ open_id: string;
123
+ user_id?: string;
124
+ union_id?: string;
125
+ name?: string;
126
+ en_name?: string;
127
+ nickname?: string;
128
+ email?: string;
129
+ mobile?: string;
130
+ gender?: number;
131
+ avatar_url?: string;
132
+ status?: {
133
+ is_frozen?: boolean;
134
+ is_resigned?: boolean;
135
+ is_activated?: boolean;
136
+ };
137
+ department_ids?: string[];
138
+ leader_user_id?: string;
139
+ city?: string;
140
+ country?: string;
141
+ work_station?: string;
142
+ join_time?: number;
143
+ employee_no?: string;
144
+ employee_type?: number;
145
+ positions?: string[];
146
+ orders?: number;
147
+ }
148
+
149
+ /** Message content types. */
150
+ export type LarkMessageContent =
151
+ | { text: string }
152
+ | { image_key: string }
153
+ | { file_key: string }
154
+ | { audio_key: string }
155
+ | { media_key: string }
156
+ | { sticker_key: string }
157
+ | { rich_text: LarkRichTextContent };
158
+
159
+ /** Rich text content structure. */
160
+ export interface LarkRichTextContent {
161
+ zh_cn?: LarkRichTextSection[];
162
+ en_us?: LarkRichTextSection[];
163
+ }
164
+
165
+ /** Rich text section. */
166
+ export interface LarkRichTextSection {
167
+ title?: string;
168
+ lines?: LarkRichTextLine[];
169
+ }
170
+
171
+ /** Rich text line. */
172
+ export interface LarkRichTextLine {
173
+ text_run?: {
174
+ text: string;
175
+ bold?: boolean;
176
+ italic?: boolean;
177
+ underline?: boolean;
178
+ strikethrough?: boolean;
179
+ inline_code?: boolean;
180
+ color?: string;
181
+ link?: string;
182
+ };
183
+ mention_run?: {
184
+ text: string;
185
+ user_id: string;
186
+ };
187
+ equation_run?: {
188
+ text: string;
189
+ };
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Document types
194
+ // ---------------------------------------------------------------------------
195
+
196
+ /** Docx document metadata. */
197
+ export interface LarkDocxDocument {
198
+ document_id: string;
199
+ revision_id: number;
200
+ title: string;
201
+ create_time: number;
202
+ update_time: number;
203
+ }
204
+
205
+ /** Docx block content. */
206
+ export interface LarkDocxBlock {
207
+ block_type: number;
208
+ block_id: string;
209
+ parent_id?: string;
210
+ children?: string[];
211
+ text?: {
212
+ initial_style: Record<string, unknown>;
213
+ elements: Array<{
214
+ text_run?: { content: string };
215
+ mention_run?: { user_id: string; text: string };
216
+ equation_run?: { content: string };
217
+ }>;
218
+ };
219
+ heading1?: { elements: unknown[] };
220
+ heading2?: { elements: unknown[] };
221
+ heading3?: { elements: unknown[] };
222
+ heading4?: { elements: unknown[] };
223
+ heading5?: { elements: unknown[] };
224
+ heading6?: { elements: unknown[] };
225
+ heading7?: { elements: unknown[] };
226
+ heading8?: { elements: unknown[] };
227
+ heading9?: { elements: unknown[] };
228
+ bullet?: { elements: unknown[] };
229
+ ordered?: { elements: unknown[] };
230
+ code?: { elements: unknown[] };
231
+ quote?: { elements: unknown[] };
232
+ table?: {
233
+ rows: number;
234
+ columns: number;
235
+ property: {
236
+ row_size: number[];
237
+ column_size: number[];
238
+ };
239
+ cells: string[];
240
+ };
241
+ table_cell?: {
242
+ property: Record<string, unknown>;
243
+ };
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // Sheet types
248
+ // ---------------------------------------------------------------------------
249
+
250
+ /** Spreadsheet metadata. */
251
+ export interface LarkSpreadsheet {
252
+ spreadsheet_token: string;
253
+ name: string;
254
+ revision: number;
255
+ owner_id: string;
256
+ create_time: number;
257
+ update_time: number;
258
+ }
259
+
260
+ /** Sheet (worksheet) metadata. */
261
+ export interface LarkSheet {
262
+ sheet_id: string;
263
+ title: string;
264
+ index: number;
265
+ row_count: number;
266
+ column_count: number;
267
+ frozen_row_count?: number;
268
+ frozen_column_count?: number;
269
+ }
270
+
271
+ /** Sheet cell value. */
272
+ export interface LarkCellValue {
273
+ value: string;
274
+ type: number;
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Drive types
279
+ // ---------------------------------------------------------------------------
280
+
281
+ /** Drive file metadata. */
282
+ export interface LarkDriveFile {
283
+ token: string;
284
+ name: string;
285
+ type: string;
286
+ parent_token: string;
287
+ create_time: number;
288
+ update_time: number;
289
+ size: number;
290
+ creator_id?: string;
291
+ modifier_id?: string;
292
+ }
293
+
294
+ /** Drive folder metadata. */
295
+ export interface LarkDriveFolder {
296
+ token: string;
297
+ name: string;
298
+ parent_token: string;
299
+ create_time: number;
300
+ update_time: number;
301
+ creator_id?: string;
302
+ }
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * UAT (User Access Token) API call wrapper for cc-lark MCP Server.
6
+ *
7
+ * Provides a safe, auto-refreshing interface for making Lark API calls on
8
+ * behalf of a user. Tokens are read from file-based storage, refreshed
9
+ * transparently, and never exposed to the AI layer.
10
+ *
11
+ * Adapted from openclaw-lark for MCP Server architecture.
12
+ */
13
+
14
+ import type { LarkBrand } from './types.js';
15
+ import {
16
+ getStoredToken,
17
+ setStoredToken,
18
+ removeStoredToken,
19
+ tokenStatus,
20
+ maskToken,
21
+ type StoredUAToken,
22
+ } from './token-store.js';
23
+ import { resolveOAuthEndpoints } from './device-flow.js';
24
+ import { logger } from '../utils/logger.js';
25
+ import { getUserAgent } from './version.js';
26
+ import { REFRESH_TOKEN_IRRECOVERABLE, TOKEN_RETRY_CODES, NeedAuthorizationError } from './api-error.js';
27
+
28
+ const log = logger('uat-client');
29
+
30
+ // Re-export for backward compatibility
31
+ export { NeedAuthorizationError };
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Types
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface UATCallOptions {
38
+ userOpenId: string;
39
+ appId: string;
40
+ appSecret: string;
41
+ domain: LarkBrand;
42
+ }
43
+
44
+ export interface UATStatus {
45
+ authorized: boolean;
46
+ userOpenId: string;
47
+ scope?: string;
48
+ expiresAt?: number;
49
+ refreshExpiresAt?: number;
50
+ grantedAt?: number;
51
+ tokenStatus?: 'valid' | 'needs_refresh' | 'expired';
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // HTTP helper
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Header-aware fetch for Lark API calls.
60
+ */
61
+ function larkFetch(url: string | URL | Request, init?: RequestInit): Promise<Response> {
62
+ const headers = {
63
+ ...init?.headers,
64
+ 'User-Agent': getUserAgent(),
65
+ };
66
+
67
+ return fetch(url, { ...init, headers });
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Per-user refresh lock
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /**
75
+ * Guards against concurrent refresh operations for the same user.
76
+ *
77
+ * refresh_token is single-use: if two requests trigger a refresh
78
+ * simultaneously, the second one would use an already-consumed token and
79
+ * fail. The lock ensures only one refresh runs at a time per user.
80
+ */
81
+ const refreshLocks = new Map<string, Promise<StoredUAToken | null>>();
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Refresh implementation
85
+ // ---------------------------------------------------------------------------
86
+
87
+ async function doRefreshToken(opts: UATCallOptions, stored: StoredUAToken): Promise<StoredUAToken | null> {
88
+ // refresh_token already expired → can't refresh, need re-auth.
89
+ if (Date.now() >= stored.refreshExpiresAt) {
90
+ log.info(`refresh_token expired for ${opts.userOpenId}, clearing`);
91
+ await removeStoredToken(opts.appId, opts.userOpenId);
92
+ return null;
93
+ }
94
+
95
+ const endpoints = resolveOAuthEndpoints(opts.domain);
96
+
97
+ const resp = await larkFetch(endpoints.token, {
98
+ method: 'POST',
99
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
100
+ body: new URLSearchParams({
101
+ grant_type: 'refresh_token',
102
+ refresh_token: stored.refreshToken,
103
+ client_id: opts.appId,
104
+ client_secret: opts.appSecret,
105
+ }).toString(),
106
+ });
107
+
108
+ const data = (await resp.json()) as Record<string, unknown>;
109
+
110
+ // Lark v2 token endpoint returns `code: 0` on success.
111
+ // Some responses use `error` field instead (standard OAuth).
112
+ const code = data.code as number | undefined;
113
+ const error = data.error as string | undefined;
114
+
115
+ if ((code !== undefined && code !== 0) || error) {
116
+ const errCode = code ?? error;
117
+ const errMsg = (data.error_description as string) ?? (data.msg as string) ?? 'unknown';
118
+
119
+ // Known irrecoverable codes: invalid/expired/missing refresh_token
120
+ if (REFRESH_TOKEN_IRRECOVERABLE.has(code as number)) {
121
+ log.warn(`refresh failed (code=${errCode}), clearing token for ${opts.userOpenId}`);
122
+ await removeStoredToken(opts.appId, opts.userOpenId);
123
+ return null;
124
+ }
125
+
126
+ throw new Error(`Token refresh failed (code=${errCode}): ${errMsg}`);
127
+ }
128
+
129
+ if (!data.access_token) {
130
+ throw new Error('Token refresh returned no access_token');
131
+ }
132
+
133
+ const now = Date.now();
134
+ const updated: StoredUAToken = {
135
+ userOpenId: stored.userOpenId,
136
+ appId: opts.appId,
137
+ accessToken: data.access_token as string,
138
+ // refresh_token is rotated – always use the new one.
139
+ refreshToken: (data.refresh_token as string) ?? stored.refreshToken,
140
+ expiresAt: now + ((data.expires_in as number) ?? 7200) * 1000,
141
+ refreshExpiresAt: data.refresh_token_expires_in
142
+ ? now + (data.refresh_token_expires_in as number) * 1000
143
+ : stored.refreshExpiresAt,
144
+ scope: (data.scope as string) ?? stored.scope,
145
+ grantedAt: stored.grantedAt,
146
+ };
147
+
148
+ await setStoredToken(updated);
149
+ log.info(`refreshed UAT for ${opts.userOpenId} (at:${maskToken(updated.accessToken)})`);
150
+ return updated;
151
+ }
152
+
153
+ /**
154
+ * Refresh with per-user locking.
155
+ */
156
+ async function refreshWithLock(opts: UATCallOptions, stored: StoredUAToken): Promise<StoredUAToken | null> {
157
+ const key = `${opts.appId}:${opts.userOpenId}`;
158
+
159
+ // Another refresh is already in-flight – wait for it and re-read.
160
+ const existing = refreshLocks.get(key);
161
+ if (existing) {
162
+ await existing;
163
+ return getStoredToken(opts.appId, opts.userOpenId);
164
+ }
165
+
166
+ const promise = doRefreshToken(opts, stored);
167
+ refreshLocks.set(key, promise);
168
+ try {
169
+ return await promise;
170
+ } finally {
171
+ refreshLocks.delete(key);
172
+ }
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Public API
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /**
180
+ * Obtain a valid access_token for the given user.
181
+ *
182
+ * - Reads from storage.
183
+ * - Refreshes proactively if the token is about to expire.
184
+ * - Throws when no token exists or refresh fails irrecoverably.
185
+ *
186
+ * The returned token must never be exposed to the AI layer.
187
+ */
188
+ export async function getValidAccessToken(opts: UATCallOptions): Promise<string> {
189
+ const stored = await getStoredToken(opts.appId, opts.userOpenId);
190
+ if (!stored) {
191
+ throw new NeedAuthorizationError(opts.userOpenId);
192
+ }
193
+
194
+ const status = tokenStatus(stored);
195
+
196
+ if (status === 'valid') {
197
+ return stored.accessToken;
198
+ }
199
+
200
+ if (status === 'needs_refresh') {
201
+ const refreshed = await refreshWithLock(opts, stored);
202
+ if (!refreshed) {
203
+ throw new NeedAuthorizationError(opts.userOpenId);
204
+ }
205
+ return refreshed.accessToken;
206
+ }
207
+
208
+ // expired
209
+ await removeStoredToken(opts.appId, opts.userOpenId);
210
+ throw new NeedAuthorizationError(opts.userOpenId);
211
+ }
212
+
213
+ /**
214
+ * Execute an API call with a valid UAT, retrying once on token-expiry errors.
215
+ */
216
+ export async function callWithUAT<T>(
217
+ opts: UATCallOptions,
218
+ apiCall: (accessToken: string) => Promise<T>
219
+ ): Promise<T> {
220
+ const accessToken = await getValidAccessToken(opts);
221
+ try {
222
+ return await apiCall(accessToken);
223
+ } catch (err: unknown) {
224
+ // Retry once if the server reports token invalid/expired.
225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ const code = (err as any)?.code ?? (err as any)?.response?.data?.code;
227
+ if (TOKEN_RETRY_CODES.has(code as number)) {
228
+ log.warn(`API call failed (code=${code}), refreshing and retrying`);
229
+ const stored = await getStoredToken(opts.appId, opts.userOpenId);
230
+ if (!stored) throw new NeedAuthorizationError(opts.userOpenId);
231
+ const refreshed = await refreshWithLock(opts, stored);
232
+ if (!refreshed) throw new NeedAuthorizationError(opts.userOpenId);
233
+ return await apiCall(refreshed.accessToken);
234
+ }
235
+ throw err;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Get the current UAT status for a user.
241
+ */
242
+ export async function getUATStatus(opts: UATCallOptions): Promise<UATStatus> {
243
+ const stored = await getStoredToken(opts.appId, opts.userOpenId);
244
+ if (!stored) {
245
+ return {
246
+ authorized: false,
247
+ userOpenId: opts.userOpenId,
248
+ };
249
+ }
250
+
251
+ const status = tokenStatus(stored);
252
+ return {
253
+ authorized: status !== 'expired',
254
+ userOpenId: opts.userOpenId,
255
+ scope: stored.scope,
256
+ expiresAt: stored.expiresAt,
257
+ refreshExpiresAt: stored.refreshExpiresAt,
258
+ grantedAt: stored.grantedAt,
259
+ tokenStatus: status,
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Save a new token from OAuth device flow.
265
+ */
266
+ export async function saveTokenFromDeviceFlow(
267
+ opts: UATCallOptions,
268
+ tokenData: {
269
+ accessToken: string;
270
+ refreshToken: string;
271
+ expiresIn: number;
272
+ refreshExpiresIn: number;
273
+ scope: string;
274
+ }
275
+ ): Promise<StoredUAToken> {
276
+ const now = Date.now();
277
+ const stored: StoredUAToken = {
278
+ userOpenId: opts.userOpenId,
279
+ appId: opts.appId,
280
+ accessToken: tokenData.accessToken,
281
+ refreshToken: tokenData.refreshToken,
282
+ expiresAt: now + tokenData.expiresIn * 1000,
283
+ refreshExpiresAt: now + tokenData.refreshExpiresIn * 1000,
284
+ scope: tokenData.scope,
285
+ grantedAt: now,
286
+ };
287
+
288
+ await setStoredToken(stored);
289
+ return stored;
290
+ }
291
+
292
+ /**
293
+ * Revoke a user's UAT by removing it from storage.
294
+ */
295
+ export async function revokeUAT(appId: string, userOpenId: string): Promise<void> {
296
+ await removeStoredToken(appId, userOpenId);
297
+ log.info(`revoked UAT for ${userOpenId}`);
298
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Version management for cc-lark MCP Server.
6
+ *
7
+ * Reads version from package.json and generates User-Agent string.
8
+ */
9
+
10
+ import { fileURLToPath } from 'node:url';
11
+ import { dirname, join } from 'node:path';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ /** Cached version string */
15
+ let cachedVersion: string | undefined;
16
+
17
+ /**
18
+ * Get the package version from package.json.
19
+ *
20
+ * @returns Version string (e.g., "0.1.0") or "unknown" if read fails
21
+ */
22
+ export function getPackageVersion(): string {
23
+ if (cachedVersion) return cachedVersion;
24
+
25
+ try {
26
+ // Current file: src/core/version.ts -> up two levels to project root
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+ const packageJsonPath = join(__dirname, '..', '..', 'package.json');
30
+
31
+ const raw = readFileSync(packageJsonPath, 'utf8');
32
+ const pkg = JSON.parse(raw) as { version?: string };
33
+ cachedVersion = pkg.version ?? 'unknown';
34
+ return cachedVersion;
35
+ } catch {
36
+ cachedVersion = 'unknown';
37
+ return cachedVersion;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Generate User-Agent string for HTTP requests.
43
+ *
44
+ * @returns User-Agent string (e.g., "cc-lark/0.1.0")
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * getUserAgent() // => "cc-lark/0.1.0"
49
+ * ```
50
+ */
51
+ export function getUserAgent(): string {
52
+ return `cc-lark/${getPackageVersion()}`;
53
+ }