feishu-user-plugin 1.3.5 → 1.3.7

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 (56) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +66 -40
  4. package/package.json +10 -3
  5. package/scripts/check-tool-count.js +15 -0
  6. package/scripts/check-version.js +40 -0
  7. package/scripts/smoke.js +224 -0
  8. package/scripts/sync-claude-md.sh +12 -0
  9. package/scripts/sync-team-skills.sh +22 -0
  10. package/scripts/test-all-tools.js +158 -0
  11. package/skills/feishu-user-plugin/SKILL.md +5 -5
  12. package/skills/feishu-user-plugin/references/CLAUDE.md +152 -96
  13. package/skills/feishu-user-plugin/references/table.md +18 -9
  14. package/src/auth/credentials.js +350 -0
  15. package/src/cli.js +42 -13
  16. package/src/clients/official/base.js +424 -0
  17. package/src/clients/official/bitable.js +269 -0
  18. package/src/clients/official/calendar.js +176 -0
  19. package/src/clients/official/contacts.js +54 -0
  20. package/src/clients/official/docs.js +301 -0
  21. package/src/clients/official/drive.js +77 -0
  22. package/src/clients/official/groups.js +68 -0
  23. package/src/clients/official/im.js +414 -0
  24. package/src/clients/official/index.js +30 -0
  25. package/src/clients/official/okr.js +127 -0
  26. package/src/clients/official/tasks.js +142 -0
  27. package/src/clients/official/uploads.js +260 -0
  28. package/src/clients/official/wiki.js +207 -0
  29. package/src/{client.js → clients/user.js} +23 -17
  30. package/src/doc-blocks.js +20 -5
  31. package/src/index.js +4 -1744
  32. package/src/logger.js +20 -0
  33. package/src/oauth.js +8 -1
  34. package/src/official.js +5 -1734
  35. package/src/prompts/_registry.js +69 -0
  36. package/src/prompts/index.js +54 -0
  37. package/src/server.js +242 -0
  38. package/src/test-all.js +2 -2
  39. package/src/test-comprehensive.js +3 -3
  40. package/src/test-send.js +1 -1
  41. package/src/tools/_registry.js +30 -0
  42. package/src/tools/bitable.js +246 -0
  43. package/src/tools/calendar.js +207 -0
  44. package/src/tools/contacts.js +66 -0
  45. package/src/tools/diagnostics.js +172 -0
  46. package/src/tools/docs.js +158 -0
  47. package/src/tools/drive.js +111 -0
  48. package/src/tools/groups.js +81 -0
  49. package/src/tools/im-read.js +259 -0
  50. package/src/tools/messaging-bot.js +151 -0
  51. package/src/tools/messaging-user.js +292 -0
  52. package/src/tools/okr.js +159 -0
  53. package/src/tools/profile.js +43 -0
  54. package/src/tools/tasks.js +168 -0
  55. package/src/tools/uploads.js +63 -0
  56. package/src/tools/wiki.js +191 -0
@@ -1,6 +1,6 @@
1
1
  const path = require('path');
2
2
  const protobuf = require('protobufjs');
3
- const { generateRequestId, generateCid, parseCookie, formatCookie, fetchWithTimeout } = require('./utils');
3
+ const { generateRequestId, generateCid, parseCookie, formatCookie, fetchWithTimeout } = require('../utils');
4
4
 
5
5
  const GATEWAY_URL = 'https://internal-api-lark-api.feishu.cn/im/gateway/';
6
6
  const CSRF_URL = 'https://internal-api-lark-api.feishu.cn/accounts/csrf';
@@ -34,7 +34,10 @@ class LarkUserClient {
34
34
  }
35
35
 
36
36
  async init() {
37
- this.proto = await protobuf.load(path.join(__dirname, '..', 'proto', 'lark.proto'));
37
+ // Path: clients/user.js ../../proto/lark.proto. Phase A refactor moved
38
+ // the file from src/client.js to src/clients/user.js but didn't deepen the
39
+ // relative path, so cookie init would ENOENT. Fixed here as part of B2.
40
+ this.proto = await protobuf.load(path.join(__dirname, '..', '..', 'proto', 'lark.proto'));
38
41
  await this._getCsrfToken();
39
42
  await this._getUserInfo();
40
43
  if (!this.userId) {
@@ -90,8 +93,10 @@ class LarkUserClient {
90
93
  this._heartbeatTimer = setInterval(async () => {
91
94
  try {
92
95
  await this._getCsrfToken();
93
- // Lazy require to avoid circular dependency at module load time
94
- const { persistToConfig } = require('./config');
96
+ // Lazy require to avoid circular dependency at module load time.
97
+ // auth/credentials writes to credentials.json (single source of truth)
98
+ // when it exists; falls back to legacy mcpServers persistence otherwise.
99
+ const { persistToConfig } = require('../auth/credentials');
95
100
  persistToConfig({ LARK_COOKIE: this.cookieStr });
96
101
  console.error('[feishu-user-plugin] Cookie heartbeat: session refreshed and persisted');
97
102
  } catch (e) {
@@ -195,7 +200,20 @@ class LarkUserClient {
195
200
  if (rootId) req.rootId = rootId;
196
201
  if (parentId) req.parentId = parentId;
197
202
  const { packet, ok } = await this._gateway(5, 'PutMessageRequest', req, '5.7.0');
198
- return { success: ok && (packet.status === 0 || packet.status == null), status: packet.status };
203
+ if (!ok) {
204
+ // The cookie protobuf gateway returns HTTP 400 when our wire format is
205
+ // missing required fields. Verified for IMAGE (v1.3.7 testing): the
206
+ // simple {imageKey} content payload is rejected — Feishu Web encodes
207
+ // images with extra metadata (image dimensions, mime type, etc.) that
208
+ // we don't have in proto/lark.proto. Reverse-engineering requires Chrome
209
+ // DevTools capture and is deferred to v1.3.8. Surface a clear error
210
+ // routing the user to send_message_as_bot, which works.
211
+ if (type === MsgType.IMAGE) {
212
+ throw new Error('send_image_as_user: Feishu cookie protobuf gateway rejected the IMAGE wire format (HTTP 400). User-identity image sends are not yet supported — wire format reverse-engineering is deferred to v1.3.8. Workaround: use send_message_as_bot(chat_id, msg_type="image", payload={image_key:"..."}).');
213
+ }
214
+ throw new Error(`_sendMsg: cookie protobuf gateway returned non-2xx for type=${type}. The wire format likely doesn't match what Feishu expects.`);
215
+ }
216
+ return { success: true, status: packet.status };
199
217
  }
200
218
 
201
219
  // --- Send Text Message ---
@@ -275,18 +293,6 @@ class LarkUserClient {
275
293
  return this._sendMsg(MsgType.FILE, chatId, { fileKey, fileName }, opts);
276
294
  }
277
295
 
278
- // --- Send Audio ---
279
-
280
- async sendAudio(chatId, audioKey, opts = {}) {
281
- return this._sendMsg(MsgType.AUDIO, chatId, { audioKey }, opts);
282
- }
283
-
284
- // --- Send Sticker ---
285
-
286
- async sendSticker(chatId, stickerId, stickerSetId, opts = {}) {
287
- return this._sendMsg(MsgType.STICKER, chatId, { stickerId, stickerSetId }, opts);
288
- }
289
-
290
296
  // --- Send Rich Text / POST ---
291
297
 
292
298
  async sendPost(chatId, title, paragraphs, opts = {}) {
package/src/doc-blocks.js CHANGED
@@ -1,10 +1,8 @@
1
1
  // Helpers for constructing docx block payloads.
2
2
  //
3
- // Right now (v1.3.4) only the image path is implemented enough to cover the
4
- // create_doc_block / update_doc_block image shortcuts. More block builders
5
- // (heading / list / code / table) will land in v1.3.5 when the local-markdown
6
- // → wiki-docx sync feature is built; parking the skeleton here now so the
7
- // later additions don't introduce a new file.
3
+ // v1.3.4 added image block builders. v1.3.6 adds file block builders so the
4
+ // create_doc_block / update_doc_block tools can attach arbitrary file
5
+ // attachments (PDF / zip / etc.) the same way they handle images.
8
6
 
9
7
  // docx v1 block_type enum (relevant subset).
10
8
  // Docs: https://open.feishu.cn/document/server-docs/docs/docs/docx-v1/document-block/create
@@ -63,8 +61,25 @@ function buildReplaceImagePayload(imageToken) {
63
61
  return { replace_image: { token: imageToken } };
64
62
  }
65
63
 
64
+ // File-block flow mirrors the image-block flow but uses block_type=23 (FILE)
65
+ // and parent_type=docx_file when uploading the binary, and replace_file in
66
+ // the PATCH body. See official.js::createDocBlockWithFile.
67
+
68
+ /** Empty file block placeholder for step 1 of file attachment flow. */
69
+ function buildEmptyFileBlock() {
70
+ return { block_type: BLOCK_TYPE.FILE, file: {} };
71
+ }
72
+
73
+ /** Patch body that swaps an empty file block's content with an uploaded file token. */
74
+ function buildReplaceFilePayload(fileToken) {
75
+ if (!fileToken) throw new Error('buildReplaceFilePayload: fileToken is required');
76
+ return { replace_file: { token: fileToken } };
77
+ }
78
+
66
79
  module.exports = {
67
80
  BLOCK_TYPE,
68
81
  buildEmptyImageBlock,
69
82
  buildReplaceImagePayload,
83
+ buildEmptyFileBlock,
84
+ buildReplaceFilePayload,
70
85
  };