feishu-user-plugin 1.2.0 → 1.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "feishu-user-plugin",
3
- "version": "1.2.0",
4
- "description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 33 tools + 9 skills, 3 auth layers.",
3
+ "version": "1.2.1",
4
+ "description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 46 tools + 9 skills, 3 auth layers.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "feishu-user-plugin": "src/cli.js"
package/src/index.js CHANGED
@@ -272,10 +272,10 @@ const TOOLS = [
272
272
  },
273
273
  {
274
274
  name: 'get_chat_info',
275
- description: '[User Identity] Get chat details: name, description, member count, owner.',
275
+ description: '[Official API + User Identity fallback] Get chat details: name, description, member count, owner. Supports both oc_xxx and numeric chat_id.',
276
276
  inputSchema: {
277
277
  type: 'object',
278
- properties: { chat_id: { type: 'string', description: 'Chat ID' } },
278
+ properties: { chat_id: { type: 'string', description: 'Chat ID (oc_xxx or numeric)' } },
279
279
  required: ['chat_id'],
280
280
  },
281
281
  },
@@ -421,6 +421,17 @@ const TOOLS = [
421
421
  },
422
422
 
423
423
  // ========== Bitable — Official API ==========
424
+ {
425
+ name: 'create_bitable',
426
+ description: '[Official API] Create a new Bitable (multi-dimensional table) app.',
427
+ inputSchema: {
428
+ type: 'object',
429
+ properties: {
430
+ name: { type: 'string', description: 'Bitable app name' },
431
+ folder_id: { type: 'string', description: 'Parent folder token (optional, defaults to root)' },
432
+ },
433
+ },
434
+ },
424
435
  {
425
436
  name: 'list_bitable_tables',
426
437
  description: '[Official API] List all tables in a Bitable app.',
@@ -430,6 +441,23 @@ const TOOLS = [
430
441
  required: ['app_token'],
431
442
  },
432
443
  },
444
+ {
445
+ name: 'create_bitable_table',
446
+ description: '[Official API] Create a new data table in a Bitable app. Optionally define initial fields.',
447
+ inputSchema: {
448
+ type: 'object',
449
+ properties: {
450
+ app_token: { type: 'string', description: 'Bitable app token' },
451
+ name: { type: 'string', description: 'Table name' },
452
+ fields: {
453
+ type: 'array',
454
+ description: 'Initial field definitions (optional). Each item: {field_name, type} where type is 1=Text, 2=Number, 3=SingleSelect, 4=MultiSelect, 5=DateTime, 7=Checkbox, 11=User, 13=Phone, 15=URL, 17=Attachment, 18=Link, 20=Formula, 21=DuplexLink, 22=Location, 23=GroupChat, 1001=CreateTime, 1002=ModifiedTime, 1003=Creator, 1004=Modifier',
455
+ items: { type: 'object' },
456
+ },
457
+ },
458
+ required: ['app_token', 'name'],
459
+ },
460
+ },
433
461
  {
434
462
  name: 'list_bitable_fields',
435
463
  description: '[Official API] List all fields (columns) in a Bitable table.',
@@ -442,6 +470,62 @@ const TOOLS = [
442
470
  required: ['app_token', 'table_id'],
443
471
  },
444
472
  },
473
+ {
474
+ name: 'create_bitable_field',
475
+ description: '[Official API] Create a new field (column) in a Bitable table.',
476
+ inputSchema: {
477
+ type: 'object',
478
+ properties: {
479
+ app_token: { type: 'string', description: 'Bitable app token' },
480
+ table_id: { type: 'string', description: 'Table ID' },
481
+ field_name: { type: 'string', description: 'Field display name' },
482
+ type: { type: 'number', description: 'Field type: 1=Text, 2=Number, 3=SingleSelect, 4=MultiSelect, 5=DateTime, 7=Checkbox, 11=User, 13=Phone, 15=URL, 17=Attachment, 18=Link, 20=Formula, 21=DuplexLink, 22=Location, 23=GroupChat, 1001=CreateTime, 1002=ModifiedTime, 1003=Creator, 1004=Modifier' },
483
+ property: { type: 'object', description: 'Field-type-specific properties (optional). E.g. for SingleSelect: {options: [{name:"A"},{name:"B"}]}' },
484
+ },
485
+ required: ['app_token', 'table_id', 'field_name', 'type'],
486
+ },
487
+ },
488
+ {
489
+ name: 'update_bitable_field',
490
+ description: '[Official API] Update an existing field (column) in a Bitable table.',
491
+ inputSchema: {
492
+ type: 'object',
493
+ properties: {
494
+ app_token: { type: 'string', description: 'Bitable app token' },
495
+ table_id: { type: 'string', description: 'Table ID' },
496
+ field_id: { type: 'string', description: 'Field ID to update' },
497
+ field_name: { type: 'string', description: 'New field name (optional)' },
498
+ type: { type: 'number', description: 'Field type (REQUIRED by Feishu API, see create_bitable_field for values)' },
499
+ property: { type: 'object', description: 'Field-type-specific properties (optional)' },
500
+ },
501
+ required: ['app_token', 'table_id', 'field_id', 'type'],
502
+ },
503
+ },
504
+ {
505
+ name: 'delete_bitable_field',
506
+ description: '[Official API] Delete a field (column) from a Bitable table.',
507
+ inputSchema: {
508
+ type: 'object',
509
+ properties: {
510
+ app_token: { type: 'string', description: 'Bitable app token' },
511
+ table_id: { type: 'string', description: 'Table ID' },
512
+ field_id: { type: 'string', description: 'Field ID to delete' },
513
+ },
514
+ required: ['app_token', 'table_id', 'field_id'],
515
+ },
516
+ },
517
+ {
518
+ name: 'list_bitable_views',
519
+ description: '[Official API] List all views in a Bitable table.',
520
+ inputSchema: {
521
+ type: 'object',
522
+ properties: {
523
+ app_token: { type: 'string', description: 'Bitable app token' },
524
+ table_id: { type: 'string', description: 'Table ID' },
525
+ },
526
+ required: ['app_token', 'table_id'],
527
+ },
528
+ },
445
529
  {
446
530
  name: 'search_bitable_records',
447
531
  description: '[Official API] Search/query records in a Bitable table.',
@@ -484,6 +568,58 @@ const TOOLS = [
484
568
  required: ['app_token', 'table_id', 'record_id', 'fields'],
485
569
  },
486
570
  },
571
+ {
572
+ name: 'delete_bitable_record',
573
+ description: '[Official API] Delete a record (row) from a Bitable table.',
574
+ inputSchema: {
575
+ type: 'object',
576
+ properties: {
577
+ app_token: { type: 'string', description: 'Bitable app token' },
578
+ table_id: { type: 'string', description: 'Table ID' },
579
+ record_id: { type: 'string', description: 'Record ID to delete' },
580
+ },
581
+ required: ['app_token', 'table_id', 'record_id'],
582
+ },
583
+ },
584
+ {
585
+ name: 'batch_create_bitable_records',
586
+ description: '[Official API] Batch create multiple records (rows) in a Bitable table. Max 500 per call.',
587
+ inputSchema: {
588
+ type: 'object',
589
+ properties: {
590
+ app_token: { type: 'string', description: 'Bitable app token' },
591
+ table_id: { type: 'string', description: 'Table ID' },
592
+ records: { type: 'array', description: 'Array of {fields: {field_name: value}} objects', items: { type: 'object' } },
593
+ },
594
+ required: ['app_token', 'table_id', 'records'],
595
+ },
596
+ },
597
+ {
598
+ name: 'batch_update_bitable_records',
599
+ description: '[Official API] Batch update multiple records in a Bitable table. Max 500 per call.',
600
+ inputSchema: {
601
+ type: 'object',
602
+ properties: {
603
+ app_token: { type: 'string', description: 'Bitable app token' },
604
+ table_id: { type: 'string', description: 'Table ID' },
605
+ records: { type: 'array', description: 'Array of {record_id, fields: {field_name: value}} objects', items: { type: 'object' } },
606
+ },
607
+ required: ['app_token', 'table_id', 'records'],
608
+ },
609
+ },
610
+ {
611
+ name: 'batch_delete_bitable_records',
612
+ description: '[Official API] Batch delete multiple records from a Bitable table. Max 500 per call.',
613
+ inputSchema: {
614
+ type: 'object',
615
+ properties: {
616
+ app_token: { type: 'string', description: 'Bitable app token' },
617
+ table_id: { type: 'string', description: 'Table ID' },
618
+ record_ids: { type: 'array', description: 'Array of record IDs to delete', items: { type: 'string' } },
619
+ },
620
+ required: ['app_token', 'table_id', 'record_ids'],
621
+ },
622
+ },
487
623
 
488
624
  // ========== Wiki — Official API ==========
489
625
  {
@@ -677,9 +813,24 @@ async function handleTool(name, args) {
677
813
  return text(chatId ? `P2P chat: ${chatId}` : 'Failed to create P2P chat');
678
814
  }
679
815
  case 'get_chat_info': {
680
- const c = await getUserClient();
681
- const info = await c.getGroupInfo(args.chat_id);
682
- return info ? json(info) : text(`No info for chat ${args.chat_id}`);
816
+ // Strategy 1: Official API im.chat.get (supports oc_xxx format)
817
+ if (args.chat_id.startsWith('oc_')) {
818
+ try {
819
+ const info = await getOfficialClient().getChatInfo(args.chat_id);
820
+ return info ? json(info) : text(`No info for chat ${args.chat_id}`);
821
+ } catch (e) {
822
+ console.error(`[feishu-user-plugin] Official getChatInfo failed: ${e.message}`);
823
+ }
824
+ }
825
+ // Strategy 2: Protobuf gateway (supports numeric chat_id)
826
+ try {
827
+ const c = await getUserClient();
828
+ const info = await c.getGroupInfo(args.chat_id);
829
+ if (info) return json(info);
830
+ } catch (e) {
831
+ console.error(`[feishu-user-plugin] Protobuf getChatInfo failed: ${e.message}`);
832
+ }
833
+ return text(`No info for chat ${args.chat_id}`);
683
834
  }
684
835
  case 'get_user_info': {
685
836
  let n = null;
@@ -807,10 +958,34 @@ async function handleTool(name, args) {
807
958
 
808
959
  // --- Official API: Bitable ---
809
960
 
961
+ case 'create_bitable': {
962
+ const r = await getOfficialClient().createBitable(args.name, args.folder_id);
963
+ return text(`Bitable created: ${r.appToken}${r.url ? `\nURL: ${r.url}` : ''}`);
964
+ }
810
965
  case 'list_bitable_tables':
811
966
  return json(await getOfficialClient().listBitableTables(args.app_token));
967
+ case 'create_bitable_table':
968
+ return text(`Table created: ${(await getOfficialClient().createBitableTable(args.app_token, args.name, args.fields)).tableId}`);
812
969
  case 'list_bitable_fields':
813
970
  return json(await getOfficialClient().listBitableFields(args.app_token, args.table_id));
971
+ case 'create_bitable_field': {
972
+ const config = { field_name: args.field_name, type: args.type };
973
+ if (args.property) config.property = args.property;
974
+ return json(await getOfficialClient().createBitableField(args.app_token, args.table_id, config));
975
+ }
976
+ case 'update_bitable_field': {
977
+ const config = {};
978
+ if (args.field_name) config.field_name = args.field_name;
979
+ if (args.type) config.type = args.type;
980
+ if (args.property) config.property = args.property;
981
+ return json(await getOfficialClient().updateBitableField(args.app_token, args.table_id, args.field_id, config));
982
+ }
983
+ case 'delete_bitable_field': {
984
+ const r = await getOfficialClient().deleteBitableField(args.app_token, args.table_id, args.field_id);
985
+ return text(r.deleted ? `Field ${r.fieldId} deleted` : `Field deletion returned deleted=${r.deleted}`);
986
+ }
987
+ case 'list_bitable_views':
988
+ return json(await getOfficialClient().listBitableViews(args.app_token, args.table_id));
814
989
  case 'search_bitable_records':
815
990
  return json(await getOfficialClient().searchBitableRecords(args.app_token, args.table_id, {
816
991
  filter: args.filter, sort: args.sort, pageSize: args.page_size,
@@ -819,6 +994,14 @@ async function handleTool(name, args) {
819
994
  return text(`Record created: ${(await getOfficialClient().createBitableRecord(args.app_token, args.table_id, args.fields)).recordId}`);
820
995
  case 'update_bitable_record':
821
996
  return text(`Record updated: ${(await getOfficialClient().updateBitableRecord(args.app_token, args.table_id, args.record_id, args.fields)).recordId}`);
997
+ case 'delete_bitable_record':
998
+ return text(`Record deleted: ${(await getOfficialClient().deleteBitableRecord(args.app_token, args.table_id, args.record_id)).deleted}`);
999
+ case 'batch_create_bitable_records':
1000
+ return json(await getOfficialClient().batchCreateBitableRecords(args.app_token, args.table_id, args.records));
1001
+ case 'batch_update_bitable_records':
1002
+ return json(await getOfficialClient().batchUpdateBitableRecords(args.app_token, args.table_id, args.records));
1003
+ case 'batch_delete_bitable_records':
1004
+ return json(await getOfficialClient().batchDeleteBitableRecords(args.app_token, args.table_id, args.record_ids));
822
1005
 
823
1006
  // --- Official API: Wiki ---
824
1007
 
package/src/official.js CHANGED
@@ -187,7 +187,10 @@ class LarkOfficialClient {
187
187
  }),
188
188
  'uploadImage'
189
189
  );
190
- return { imageKey: res.data.image_key };
190
+ // SDK multipart responses may have data at top level or nested under .data
191
+ const imageKey = res.data?.image_key || res.image_key;
192
+ if (!imageKey) throw new Error(`uploadImage: unexpected response structure: ${JSON.stringify(res).slice(0, 500)}`);
193
+ return { imageKey };
191
194
  }
192
195
 
193
196
  async uploadFile(filePath, fileType = 'stream', fileName) {
@@ -204,7 +207,10 @@ class LarkOfficialClient {
204
207
  }),
205
208
  'uploadFile'
206
209
  );
207
- return { fileKey: res.data.file_key };
210
+ // SDK multipart responses may have data at top level or nested under .data
211
+ const fileKey = res.data?.file_key || res.file_key;
212
+ if (!fileKey) throw new Error(`uploadFile: unexpected response structure: ${JSON.stringify(res).slice(0, 500)}`);
213
+ return { fileKey };
208
214
  }
209
215
 
210
216
  // --- Docs ---
@@ -244,18 +250,74 @@ class LarkOfficialClient {
244
250
  return { items: res.data.items || [] };
245
251
  }
246
252
 
253
+ // --- Chat Info (Official API) ---
254
+
255
+ async getChatInfo(chatId) {
256
+ const res = await this._safeSDKCall(
257
+ () => this.client.im.chat.get({ path: { chat_id: chatId } }),
258
+ 'getChatInfo'
259
+ );
260
+ return res.data;
261
+ }
262
+
247
263
  // --- Bitable ---
248
264
 
265
+ async createBitable(name, folderId) {
266
+ const data = {};
267
+ if (name) data.name = name;
268
+ if (folderId) data.folder_token = folderId;
269
+ const res = await this._safeSDKCall(
270
+ () => this.client.bitable.app.create({ data }),
271
+ 'createBitable'
272
+ );
273
+ return { appToken: res.data.app?.app_token, name: res.data.app?.name, url: res.data.app?.url };
274
+ }
275
+
249
276
  async listBitableTables(appToken) {
250
277
  const res = await this._safeSDKCall(() => this.client.bitable.appTable.list({ path: { app_token: appToken } }), 'listTables');
251
278
  return { items: res.data.items || [] };
252
279
  }
253
280
 
281
+ async createBitableTable(appToken, name, fields) {
282
+ const data = { table: { name } };
283
+ if (fields && fields.length > 0) data.table.default_view_name = name;
284
+ if (fields && fields.length > 0) data.table.fields = fields;
285
+ const res = await this._safeSDKCall(
286
+ () => this.client.bitable.appTable.create({ path: { app_token: appToken }, data }),
287
+ 'createTable'
288
+ );
289
+ return { tableId: res.data.table_id };
290
+ }
291
+
254
292
  async listBitableFields(appToken, tableId) {
255
293
  const res = await this._safeSDKCall(() => this.client.bitable.appTableField.list({ path: { app_token: appToken, table_id: tableId } }), 'listFields');
256
294
  return { items: res.data.items || [] };
257
295
  }
258
296
 
297
+ async createBitableField(appToken, tableId, fieldConfig) {
298
+ const res = await this._safeSDKCall(
299
+ () => this.client.bitable.appTableField.create({ path: { app_token: appToken, table_id: tableId }, data: fieldConfig }),
300
+ 'createField'
301
+ );
302
+ return { field: res.data.field };
303
+ }
304
+
305
+ async updateBitableField(appToken, tableId, fieldId, fieldConfig) {
306
+ const res = await this._safeSDKCall(
307
+ () => this.client.bitable.appTableField.update({ path: { app_token: appToken, table_id: tableId, field_id: fieldId }, data: fieldConfig }),
308
+ 'updateField'
309
+ );
310
+ return { field: res.data.field };
311
+ }
312
+
313
+ async deleteBitableField(appToken, tableId, fieldId) {
314
+ const res = await this._safeSDKCall(
315
+ () => this.client.bitable.appTableField.delete({ path: { app_token: appToken, table_id: tableId, field_id: fieldId } }),
316
+ 'deleteField'
317
+ );
318
+ return { fieldId: res.data.field_id, deleted: res.data.deleted };
319
+ }
320
+
259
321
  async searchBitableRecords(appToken, tableId, { filter, sort, pageSize = 20, pageToken } = {}) {
260
322
  const data = {};
261
323
  if (filter) data.filter = filter;
@@ -285,6 +347,46 @@ class LarkOfficialClient {
285
347
  return { recordId: res.data.record?.record_id };
286
348
  }
287
349
 
350
+ async deleteBitableRecord(appToken, tableId, recordId) {
351
+ const res = await this._safeSDKCall(
352
+ () => this.client.bitable.appTableRecord.delete({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
353
+ 'deleteRecord'
354
+ );
355
+ return { deleted: res.data.deleted };
356
+ }
357
+
358
+ async batchCreateBitableRecords(appToken, tableId, records) {
359
+ const res = await this._safeSDKCall(
360
+ () => this.client.bitable.appTableRecord.batchCreate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
361
+ 'batchCreateRecords'
362
+ );
363
+ return { records: res.data.records || [] };
364
+ }
365
+
366
+ async batchUpdateBitableRecords(appToken, tableId, records) {
367
+ const res = await this._safeSDKCall(
368
+ () => this.client.bitable.appTableRecord.batchUpdate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
369
+ 'batchUpdateRecords'
370
+ );
371
+ return { records: res.data.records || [] };
372
+ }
373
+
374
+ async batchDeleteBitableRecords(appToken, tableId, recordIds) {
375
+ const res = await this._safeSDKCall(
376
+ () => this.client.bitable.appTableRecord.batchDelete({ path: { app_token: appToken, table_id: tableId }, data: { records: recordIds } }),
377
+ 'batchDeleteRecords'
378
+ );
379
+ return { records: res.data.records || [] };
380
+ }
381
+
382
+ async listBitableViews(appToken, tableId) {
383
+ const res = await this._safeSDKCall(
384
+ () => this.client.bitable.appTableView.list({ path: { app_token: appToken, table_id: tableId }, params: { page_size: 50 } }),
385
+ 'listViews'
386
+ );
387
+ return { items: res.data.items || [] };
388
+ }
389
+
288
390
  // --- Wiki ---
289
391
 
290
392
  async listWikiSpaces() {
@@ -369,7 +471,9 @@ class LarkOfficialClient {
369
471
  async _safeSDKCall(fn, label = 'API') {
370
472
  try {
371
473
  const res = await fn();
372
- if (res.code !== 0) throw new Error(`${label} failed (${res.code}): ${res.msg}`);
474
+ // SDK returns abbreviated responses for multipart uploads (code/msg undefined)
475
+ // Only treat as error if code is explicitly non-zero
476
+ if (res.code !== undefined && res.code !== 0) throw new Error(`${label} failed (${res.code}): ${res.msg}`);
373
477
  return res;
374
478
  } catch (err) {
375
479
  // Lark SDK uses axios; extract actual Feishu error from response body