feishu-user-plugin 1.3.0 → 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;
@@ -234,20 +245,19 @@ class LarkOfficialClient {
234
245
 
235
246
  // --- IM: Pins ---
236
247
 
237
- async pinMessage(messageId) {
238
- const res = await this._safeSDKCall(
239
- () => this.client.im.pin.create({ data: { message_id: messageId } }),
240
- 'pinMessage'
241
- );
242
- return { pin: res.data.pin };
243
- }
244
-
245
- async unpinMessage(messageId) {
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
+ }
246
256
  await this._safeSDKCall(
247
257
  () => this.client.im.pin.delete({ data: { message_id: messageId } }),
248
258
  'unpinMessage'
249
259
  );
250
- return { deleted: true };
260
+ return { unpinned: true };
251
261
  }
252
262
 
253
263
  // --- IM: Chat Management ---
@@ -572,6 +582,48 @@ class LarkOfficialClient {
572
582
  return { deleted: true };
573
583
  }
574
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
+
575
627
  // --- Wiki ---
576
628
 
577
629
  async listWikiSpaces() {
@@ -679,109 +731,51 @@ class LarkOfficialClient {
679
731
  return allChats;
680
732
  }
681
733
 
682
- // --- Calendar ---
683
-
684
- async listCalendars() {
685
- const res = await this._safeSDKCall(
686
- () => this.client.calendar.calendar.list({ params: { page_size: 50 } }),
687
- 'listCalendars'
688
- );
689
- return { items: res.data.calendar_list || [] };
690
- }
691
-
692
- async createCalendarEvent(calendarId, event) {
693
- const res = await this._safeSDKCall(
694
- () => this.client.calendar.calendarEvent.create({
695
- path: { calendar_id: calendarId },
696
- data: event,
697
- }),
698
- 'createCalendarEvent'
699
- );
700
- return { event: res.data.event };
701
- }
702
-
703
- async listCalendarEvents(calendarId, { startTime, endTime, pageSize = 50, pageToken } = {}) {
704
- const params = { page_size: pageSize };
705
- if (startTime) params.start_time = startTime;
706
- if (endTime) params.end_time = endTime;
707
- if (pageToken) params.page_token = pageToken;
708
- const res = await this._safeSDKCall(
709
- () => this.client.calendar.calendarEvent.list({
710
- path: { calendar_id: calendarId },
711
- params,
712
- }),
713
- 'listCalendarEvents'
714
- );
715
- return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
716
- }
717
-
718
- async deleteCalendarEvent(calendarId, eventId) {
719
- await this._safeSDKCall(
720
- () => this.client.calendar.calendarEvent.delete({
721
- path: { calendar_id: calendarId, event_id: eventId },
722
- }),
723
- 'deleteCalendarEvent'
724
- );
725
- return { deleted: true };
726
- }
727
-
728
- async getFreeBusy(userIds, startTime, endTime) {
729
- const res = await this._safeSDKCall(
730
- () => this.client.calendar.freebusy.list({
731
- data: {
732
- time_min: startTime,
733
- time_max: endTime,
734
- user_id: { user_ids: userIds, id_type: 'open_id' },
735
- },
736
- }),
737
- 'getFreeBusy'
738
- );
739
- return { freebusyList: res.data.freebusy_list || [] };
740
- }
741
-
742
- // --- Tasks ---
743
-
744
- async createTask(task) {
745
- const res = await this._safeSDKCall(
746
- () => this.client.task.task.create({ data: task }),
747
- 'createTask'
748
- );
749
- return { task: res.data.task };
750
- }
751
-
752
- async getTask(taskId) {
753
- const res = await this._safeSDKCall(
754
- () => this.client.task.task.get({ path: { task_id: taskId } }),
755
- 'getTask'
756
- );
757
- return { task: res.data.task };
758
- }
734
+ // --- UAT-based creation (resources owned by user, not app) ---
759
735
 
760
- async listTasks({ pageSize = 50, pageToken } = {}) {
761
- const res = await this._safeSDKCall(
762
- () => this.client.task.task.list({ params: { page_size: pageSize, page_token: pageToken } }),
763
- 'listTasks'
764
- );
765
- return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
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 };
766
749
  }
767
750
 
768
- async updateTask(taskId, task) {
769
- const res = await this._safeSDKCall(
770
- () => this.client.task.task.patch({
771
- path: { task_id: taskId },
772
- data: task,
773
- }),
774
- 'updateTask'
775
- );
776
- return { task: res.data.task };
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 };
777
765
  }
778
766
 
779
- async completeTask(taskId) {
780
- const res = await this._safeSDKCall(
781
- () => this.client.task.task.complete({ path: { task_id: taskId } }),
782
- 'completeTask'
783
- );
784
- return { completed: true };
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 };
785
779
  }
786
780
 
787
781
  // --- Safe SDK Call (extracts real Feishu error from AxiosError) ---
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));