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/README.md +103 -39
- package/package.json +5 -3
- package/scripts/confirm-version.js +28 -0
- package/scripts/mcp_stdio_bridge.js +97 -0
- package/skills/feishu-user-plugin/references/CLAUDE.md +76 -34
- package/src/cli.js +12 -7
- package/src/config.js +202 -27
- package/src/index.js +124 -237
- package/src/oauth.js +2 -1
- package/src/official.js +103 -109
- package/src/setup.js +19 -3
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 {
|
|
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
|
-
// ---
|
|
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
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
'
|
|
783
|
-
|
|
784
|
-
|
|
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} (
|
|
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));
|