feishu-user-plugin 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/official.js CHANGED
@@ -1,10 +1,21 @@
1
1
  const lark = require('@larksuiteoapi/node-sdk');
2
2
 
3
+ // Redirect all Lark SDK logs to stderr.
4
+ // The SDK's defaultLogger.error uses console.log (stdout), which corrupts
5
+ // MCP's JSON-RPC stdio transport and causes session disconnects.
6
+ const stderrLogger = {
7
+ error: (...msg) => console.error('[lark-sdk][error]:', ...msg),
8
+ warn: (...msg) => console.error('[lark-sdk][warn]:', ...msg),
9
+ info: () => {},
10
+ debug: () => {},
11
+ trace: () => {},
12
+ };
13
+
3
14
  class LarkOfficialClient {
4
15
  constructor(appId, appSecret) {
5
16
  this.appId = appId;
6
17
  this.appSecret = appSecret;
7
- this.client = new lark.Client({ appId, appSecret, disableTokenCache: false });
18
+ this.client = new lark.Client({ appId, appSecret, disableTokenCache: false, logger: stderrLogger, loggerLevel: lark.LoggerLevel.warn });
8
19
  this._uat = null;
9
20
  this._uatRefresh = null;
10
21
  this._uatExpires = 0;
@@ -177,6 +188,139 @@ class LarkOfficialClient {
177
188
  return { messageId: res.data.message_id };
178
189
  }
179
190
 
191
+ // --- IM: Send (Bot Identity) ---
192
+
193
+ async sendMessageAsBot(chatId, msgType, content, receiveIdType = 'chat_id') {
194
+ const res = await this._safeSDKCall(
195
+ () => this.client.im.message.create({
196
+ params: { receive_id_type: receiveIdType },
197
+ data: { receive_id: chatId, msg_type: msgType, content: typeof content === 'string' ? content : JSON.stringify(content) },
198
+ }),
199
+ 'sendMessage'
200
+ );
201
+ return { messageId: res.data.message_id };
202
+ }
203
+
204
+ async deleteMessage(messageId) {
205
+ await this._safeSDKCall(
206
+ () => this.client.im.message.delete({ path: { message_id: messageId } }),
207
+ 'deleteMessage'
208
+ );
209
+ return { deleted: true };
210
+ }
211
+
212
+ async updateMessage(messageId, msgType, content) {
213
+ const res = await this._safeSDKCall(
214
+ () => this.client.im.message.patch({
215
+ path: { message_id: messageId },
216
+ data: { msg_type: msgType, content: typeof content === 'string' ? content : JSON.stringify(content) },
217
+ }),
218
+ 'updateMessage'
219
+ );
220
+ return { messageId: res.data?.message_id || messageId };
221
+ }
222
+
223
+ // --- IM: Reactions ---
224
+
225
+ async addReaction(messageId, emojiType) {
226
+ const res = await this._safeSDKCall(
227
+ () => this.client.im.messageReaction.create({
228
+ path: { message_id: messageId },
229
+ data: { reaction_type: { emoji_type: emojiType } },
230
+ }),
231
+ 'addReaction'
232
+ );
233
+ return { reactionId: res.data.reaction_id };
234
+ }
235
+
236
+ async deleteReaction(messageId, reactionId) {
237
+ await this._safeSDKCall(
238
+ () => this.client.im.messageReaction.delete({
239
+ path: { message_id: messageId, reaction_id: reactionId },
240
+ }),
241
+ 'deleteReaction'
242
+ );
243
+ return { deleted: true };
244
+ }
245
+
246
+ // --- IM: Pins ---
247
+
248
+ async pinMessage(messageId, pinned = true) {
249
+ if (pinned) {
250
+ const res = await this._safeSDKCall(
251
+ () => this.client.im.pin.create({ data: { message_id: messageId } }),
252
+ 'pinMessage'
253
+ );
254
+ return { pin: res.data.pin };
255
+ }
256
+ await this._safeSDKCall(
257
+ () => this.client.im.pin.delete({ data: { message_id: messageId } }),
258
+ 'unpinMessage'
259
+ );
260
+ return { unpinned: true };
261
+ }
262
+
263
+ // --- IM: Chat Management ---
264
+
265
+ async createChat({ name, description, userIds, botIds } = {}) {
266
+ const data = {};
267
+ if (name) data.name = name;
268
+ if (description) data.description = description;
269
+ if (userIds) data.user_id_list = userIds;
270
+ if (botIds) data.bot_id_list = botIds;
271
+ const res = await this._safeSDKCall(
272
+ () => this.client.im.chat.create({ params: { user_id_type: 'open_id' }, data }),
273
+ 'createChat'
274
+ );
275
+ return { chatId: res.data.chat_id };
276
+ }
277
+
278
+ async updateChat(chatId, { name, description } = {}) {
279
+ const data = {};
280
+ if (name) data.name = name;
281
+ if (description) data.description = description;
282
+ const res = await this._safeSDKCall(
283
+ () => this.client.im.chat.update({ path: { chat_id: chatId }, data }),
284
+ 'updateChat'
285
+ );
286
+ return { updated: true };
287
+ }
288
+
289
+ async listChatMembers(chatId, { pageSize = 50, pageToken } = {}) {
290
+ const res = await this._safeSDKCall(
291
+ () => this.client.im.chatMembers.get({
292
+ path: { chat_id: chatId },
293
+ params: { member_id_type: 'open_id', page_size: pageSize, page_token: pageToken },
294
+ }),
295
+ 'listChatMembers'
296
+ );
297
+ return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
298
+ }
299
+
300
+ async addChatMembers(chatId, userIds) {
301
+ const res = await this._safeSDKCall(
302
+ () => this.client.im.chatMembers.create({
303
+ path: { chat_id: chatId },
304
+ params: { member_id_type: 'open_id' },
305
+ data: { id_list: userIds },
306
+ }),
307
+ 'addChatMembers'
308
+ );
309
+ return { invalidIds: res.data.invalid_id_list || [] };
310
+ }
311
+
312
+ async removeChatMembers(chatId, userIds) {
313
+ const res = await this._safeSDKCall(
314
+ () => this.client.im.chatMembers.delete({
315
+ path: { chat_id: chatId },
316
+ params: { member_id_type: 'open_id' },
317
+ data: { id_list: userIds },
318
+ }),
319
+ 'removeChatMembers'
320
+ );
321
+ return { invalidIds: res.data.invalid_id_list || [] };
322
+ }
323
+
180
324
  // --- Upload ---
181
325
 
182
326
  async uploadImage(imagePath, imageType = 'message') {
@@ -250,6 +394,41 @@ class LarkOfficialClient {
250
394
  return { items: res.data.items || [] };
251
395
  }
252
396
 
397
+ async createDocBlock(documentId, parentBlockId, children, index) {
398
+ const data = { children };
399
+ if (index !== undefined) data.index = index;
400
+ const res = await this._safeSDKCall(
401
+ () => this.client.docx.documentBlockChildren.create({
402
+ path: { document_id: documentId, block_id: parentBlockId },
403
+ data,
404
+ }),
405
+ 'createDocBlock'
406
+ );
407
+ return { blocks: res.data.children || [] };
408
+ }
409
+
410
+ async updateDocBlock(documentId, blockId, updateBody) {
411
+ const res = await this._safeSDKCall(
412
+ () => this.client.docx.documentBlock.patch({
413
+ path: { document_id: documentId, block_id: blockId },
414
+ data: updateBody,
415
+ }),
416
+ 'updateDocBlock'
417
+ );
418
+ return { block: res.data.block };
419
+ }
420
+
421
+ async deleteDocBlocks(documentId, parentBlockId, startIndex, endIndex) {
422
+ const res = await this._safeSDKCall(
423
+ () => this.client.docx.documentBlockChildren.batchDelete({
424
+ path: { document_id: documentId, block_id: parentBlockId },
425
+ data: { start_index: startIndex, end_index: endIndex },
426
+ }),
427
+ 'deleteDocBlocks'
428
+ );
429
+ return { deleted: true };
430
+ }
431
+
253
432
  // --- Chat Info (Official API) ---
254
433
 
255
434
  async getChatInfo(chatId) {
@@ -387,6 +566,64 @@ class LarkOfficialClient {
387
566
  return { items: res.data.items || [] };
388
567
  }
389
568
 
569
+ async getBitableRecord(appToken, tableId, recordId) {
570
+ const res = await this._safeSDKCall(
571
+ () => this.client.bitable.appTableRecord.get({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
572
+ 'getRecord'
573
+ );
574
+ return { record: res.data.record };
575
+ }
576
+
577
+ async deleteBitableTable(appToken, tableId) {
578
+ await this._safeSDKCall(
579
+ () => this.client.bitable.appTable.delete({ path: { app_token: appToken, table_id: tableId } }),
580
+ 'deleteTable'
581
+ );
582
+ return { deleted: true };
583
+ }
584
+
585
+ async getBitableMeta(appToken) {
586
+ const res = await this._safeSDKCall(
587
+ () => this.client.bitable.app.get({ path: { app_token: appToken } }),
588
+ 'getBitableMeta'
589
+ );
590
+ return { app: res.data.app };
591
+ }
592
+
593
+ async updateBitableTable(appToken, tableId, name) {
594
+ const res = await this._safeSDKCall(
595
+ () => this.client.bitable.appTable.patch({ path: { app_token: appToken, table_id: tableId }, data: { name } }),
596
+ 'updateTable'
597
+ );
598
+ return { name: res.data.name };
599
+ }
600
+
601
+ async createBitableView(appToken, tableId, viewName, viewType = 'grid') {
602
+ const res = await this._safeSDKCall(
603
+ () => this.client.bitable.appTableView.create({ path: { app_token: appToken, table_id: tableId }, data: { view_name: viewName, view_type: viewType } }),
604
+ 'createView'
605
+ );
606
+ return { view: res.data.view };
607
+ }
608
+
609
+ async deleteBitableView(appToken, tableId, viewId) {
610
+ await this._safeSDKCall(
611
+ () => this.client.bitable.appTableView.delete({ path: { app_token: appToken, table_id: tableId, view_id: viewId } }),
612
+ 'deleteView'
613
+ );
614
+ return { deleted: true };
615
+ }
616
+
617
+ async copyBitable(appToken, name, folderId) {
618
+ const data = { name };
619
+ if (folderId) data.folder_token = folderId;
620
+ const res = await this._safeSDKCall(
621
+ () => this.client.bitable.app.copy({ path: { app_token: appToken }, data }),
622
+ 'copyBitable'
623
+ );
624
+ return { app: res.data.app };
625
+ }
626
+
390
627
  // --- Wiki ---
391
628
 
392
629
  async listWikiSpaces() {
@@ -435,6 +672,34 @@ class LarkOfficialClient {
435
672
  return { token: res.data.token };
436
673
  }
437
674
 
675
+ // --- Drive: File Operations ---
676
+
677
+ async copyFile(fileToken, name, folderToken, type) {
678
+ const data = { name, folder_token: folderToken || '' };
679
+ if (type) data.type = type;
680
+ const res = await this._safeSDKCall(
681
+ () => this.client.drive.file.copy({ path: { file_token: fileToken }, data }),
682
+ 'copyFile'
683
+ );
684
+ return { file: res.data.file };
685
+ }
686
+
687
+ async moveFile(fileToken, folderToken) {
688
+ const res = await this._safeSDKCall(
689
+ () => this.client.drive.file.move({ path: { file_token: fileToken }, data: { folder_token: folderToken || '' } }),
690
+ 'moveFile'
691
+ );
692
+ return { taskId: res.data.task_id };
693
+ }
694
+
695
+ async deleteFile(fileToken, type) {
696
+ const res = await this._safeSDKCall(
697
+ () => this.client.drive.file.delete({ path: { file_token: fileToken }, params: { type: type || 'file' } }),
698
+ 'deleteFile'
699
+ );
700
+ return { taskId: res.data.task_id };
701
+ }
702
+
438
703
  // --- Contact ---
439
704
 
440
705
  async findUserByIdentity({ emails, mobiles } = {}) {
@@ -466,6 +731,53 @@ class LarkOfficialClient {
466
731
  return allChats;
467
732
  }
468
733
 
734
+ // --- UAT-based creation (resources owned by user, not app) ---
735
+
736
+ async createDocAsUser(title, folderId) {
737
+ const data = { title };
738
+ if (folderId) data.folder_token = folderId;
739
+ const result = await this._withUAT(async (uat) => {
740
+ const res = await fetch('https://open.feishu.cn/open-apis/docx/v1/documents', {
741
+ method: 'POST',
742
+ headers: { 'Authorization': `Bearer ${uat}`, 'content-type': 'application/json' },
743
+ body: JSON.stringify(data),
744
+ });
745
+ return res.json();
746
+ });
747
+ if (result.code !== 0) throw new Error(`createDocAsUser failed (${result.code}): ${result.msg}`);
748
+ return { documentId: result.data.document?.document_id };
749
+ }
750
+
751
+ async createBitableAsUser(name, folderId) {
752
+ const data = {};
753
+ if (name) data.name = name;
754
+ if (folderId) data.folder_token = folderId;
755
+ const result = await this._withUAT(async (uat) => {
756
+ const res = await fetch('https://open.feishu.cn/open-apis/bitable/v1/apps', {
757
+ method: 'POST',
758
+ headers: { 'Authorization': `Bearer ${uat}`, 'content-type': 'application/json' },
759
+ body: JSON.stringify(data),
760
+ });
761
+ return res.json();
762
+ });
763
+ if (result.code !== 0) throw new Error(`createBitableAsUser failed (${result.code}): ${result.msg}`);
764
+ return { appToken: result.data.app?.app_token, name: result.data.app?.name, url: result.data.app?.url };
765
+ }
766
+
767
+ async createFolderAsUser(name, parentToken) {
768
+ const data = { name, folder_token: parentToken || '' };
769
+ const result = await this._withUAT(async (uat) => {
770
+ const res = await fetch('https://open.feishu.cn/open-apis/drive/v1/files/create_folder', {
771
+ method: 'POST',
772
+ headers: { 'Authorization': `Bearer ${uat}`, 'content-type': 'application/json' },
773
+ body: JSON.stringify(data),
774
+ });
775
+ return res.json();
776
+ });
777
+ if (result.code !== 0) throw new Error(`createFolderAsUser failed (${result.code}): ${result.msg}`);
778
+ return { token: result.data.token };
779
+ }
780
+
469
781
  // --- Safe SDK Call (extracts real Feishu error from AxiosError) ---
470
782
 
471
783
  async _safeSDKCall(fn, label = 'API') {
package/src/setup.js CHANGED
@@ -12,7 +12,7 @@
12
12
  const readline = require('readline');
13
13
  const { findMcpConfig, writeNewConfig } = require('./config');
14
14
 
15
- // Parse CLI args: --app-id, --app-secret, --cookie
15
+ // Parse CLI args: --app-id, --app-secret, --cookie, --client
16
16
  function parseArgs() {
17
17
  const args = {};
18
18
  const argv = process.argv.slice(2);
@@ -20,6 +20,7 @@ function parseArgs() {
20
20
  if (argv[i] === '--app-id' && argv[i + 1]) args.appId = argv[++i];
21
21
  else if (argv[i] === '--app-secret' && argv[i + 1]) args.appSecret = argv[++i];
22
22
  else if (argv[i] === '--cookie' && argv[i + 1]) args.cookie = argv[++i];
23
+ else if (argv[i] === '--client' && argv[i + 1]) args.client = argv[++i];
23
24
  }
24
25
  return args;
25
26
  }
@@ -135,6 +136,20 @@ async function main() {
135
136
  const existingRT = existingEnv.LARK_USER_REFRESH_TOKEN;
136
137
  const hasUAT = existingUAT && existingUAT !== 'SETUP_NEEDED' && existingUAT.length > 20;
137
138
 
139
+ // Resolve target client
140
+ let client = cliArgs.client || null; // 'claude' | 'codex' | 'both' | null (interactive)
141
+ if (!client && !nonInteractive) {
142
+ console.log('\n--- Target Client ---');
143
+ console.log(' 1. Claude Code (default)');
144
+ console.log(' 2. Codex');
145
+ console.log(' 3. Both');
146
+ const choice = (await ask('Choose target [1]: ')).trim();
147
+ if (choice === '2') client = 'codex';
148
+ else if (choice === '3') client = 'both';
149
+ else client = 'claude';
150
+ }
151
+ if (!client) client = 'claude';
152
+
138
153
  // Write config
139
154
  console.log('\n--- Writing Config ---');
140
155
 
@@ -146,8 +161,9 @@ async function main() {
146
161
  LARK_USER_REFRESH_TOKEN: hasUAT ? (existingRT || '') : '',
147
162
  };
148
163
 
149
- const result = writeNewConfig(env);
150
- console.log(`Written to ${result.configPath} (global)`);
164
+ const result = writeNewConfig(env, undefined, undefined, client);
165
+ if (result.configPath) console.log(`Written to ${result.configPath} (Claude Code)`);
166
+ if (result.codexConfigPath) console.log(`Written to ${result.codexConfigPath} (Codex)`);
151
167
 
152
168
  // Summary
153
169
  console.log('\n' + '='.repeat(60));