feishu-user-plugin 1.2.1 → 1.3.0
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 +2 -2
- package/skills/feishu-user-plugin/references/CLAUDE.md +295 -60
- package/src/index.js +487 -0
- package/src/official.js +318 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "All-in-one Feishu plugin for Claude Code —
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "All-in-one Feishu plugin for Claude Code — messaging, docs, bitable, calendar, tasks, drive. 76 tools + 9 skills, 3 auth layers.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"feishu-user-plugin": "src/cli.js"
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
## What This Is
|
|
4
4
|
All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
5
5
|
- **User Identity** (cookie auth): Send messages (text, image, file, post, sticker, audio) as yourself
|
|
6
|
-
- **Official API** (app credentials): Read group messages, docs, tables, wiki, drive, contacts
|
|
6
|
+
- **Official API** (app credentials): Read group messages, docs, tables, wiki, drive, contacts, upload files
|
|
7
7
|
- **User OAuth UAT** (user_access_token): Read P2P chat history, list all user's chats
|
|
8
8
|
|
|
9
|
-
## Tool Categories
|
|
9
|
+
## Tool Categories (76 tools)
|
|
10
10
|
|
|
11
11
|
### User Identity — Messaging (reverse-engineered, cookie-based)
|
|
12
|
-
- `send_to_user` — Search user + send text (one step, most common)
|
|
13
|
-
- `send_to_group` — Search group + send text (one step)
|
|
12
|
+
- `send_to_user` — Search user + send text (one step, most common). Returns candidates if multiple matches.
|
|
13
|
+
- `send_to_group` — Search group + send text (one step). Returns candidates if multiple matches.
|
|
14
14
|
- `send_as_user` — Send text to any chat by ID, supports reply threading (root_id/parent_id)
|
|
15
|
-
- `send_image_as_user` — Send image (requires image_key from
|
|
16
|
-
- `send_file_as_user` — Send file (requires file_key from
|
|
15
|
+
- `send_image_as_user` — Send image (requires image_key from `upload_image`)
|
|
16
|
+
- `send_file_as_user` — Send file (requires file_key from `upload_file`)
|
|
17
17
|
- `send_post_as_user` — Send rich text with title + formatted paragraphs
|
|
18
18
|
- `send_sticker_as_user` — Send sticker/emoji
|
|
19
19
|
- `send_audio_as_user` — Send audio message
|
|
@@ -21,64 +21,176 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
21
21
|
### User Identity — Contacts & Info
|
|
22
22
|
- `search_contacts` — Search users/groups by name
|
|
23
23
|
- `create_p2p_chat` — Create/get P2P chat
|
|
24
|
-
- `get_chat_info` — Group details (name, members, owner)
|
|
25
|
-
- `get_user_info` — User display name lookup (
|
|
24
|
+
- `get_chat_info` — Group details (name, members, owner). Supports both oc_xxx and numeric chat_id (Official API + protobuf fallback)
|
|
25
|
+
- `get_user_info` — User display name lookup (official API first, cookie cache fallback)
|
|
26
26
|
- `get_login_status` — Check cookie, app, and UAT status
|
|
27
27
|
|
|
28
28
|
### User OAuth UAT Tools (P2P chat reading)
|
|
29
|
-
- `read_p2p_messages` — Read P2P (direct message) chat history. chat_id accepts both numeric IDs (from create_p2p_chat) and oc_xxx format.
|
|
29
|
+
- `read_p2p_messages` — Read P2P (direct message) chat history. chat_id accepts both numeric IDs (from create_p2p_chat) and oc_xxx format. Returns newest messages first by default.
|
|
30
30
|
- `list_user_chats` — List group chats the user is in. Note: API only returns groups, not P2P. For P2P, use: `search_contacts` → `create_p2p_chat` → `read_p2p_messages`.
|
|
31
31
|
|
|
32
32
|
### Official API Tools (app credentials)
|
|
33
|
-
- `list_chats` / `read_messages` — Chat history (accepts chat name, oc_ ID, or numeric ID; auto-falls back to UAT for external groups
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
33
|
+
- `list_chats` / `read_messages` — Chat history (read_messages accepts chat name, oc_ ID, or numeric ID; auto-resolves via bot's group list → im.chat.search → search_contacts). **Auto-falls back to UAT for external groups the bot cannot access.** Returns newest messages first by default. Messages include sender names.
|
|
34
|
+
- `send_message_as_bot` — Bot sends message to any chat (text, post, interactive, etc.)
|
|
35
|
+
- `reply_message` / `forward_message` — Message operations (as bot)
|
|
36
|
+
- `delete_message` / `update_message` — Recall or edit bot's own messages
|
|
37
|
+
- `add_reaction` / `delete_reaction` — Emoji reactions on messages
|
|
38
|
+
- `pin_message` / `unpin_message` — Pin/unpin messages in chat
|
|
39
|
+
- `create_group` / `update_group` — Create and manage group chats
|
|
40
|
+
- `list_members` / `add_members` / `remove_members` — Group membership management
|
|
41
|
+
- `search_docs` / `read_doc` / `get_doc_blocks` / `create_doc` — Document operations
|
|
42
|
+
- `create_doc_block` / `update_doc_block` / `delete_doc_blocks` — Document content editing (insert/update/delete blocks)
|
|
43
|
+
- `create_bitable` — Create a new Bitable (multi-dimensional table) app
|
|
44
|
+
- `list_bitable_tables` / `create_bitable_table` — Table management
|
|
45
|
+
- `list_bitable_fields` / `create_bitable_field` / `update_bitable_field` / `delete_bitable_field` — Field (column) management
|
|
46
|
+
- `list_bitable_views` — List views in a table
|
|
47
|
+
- `search_bitable_records` — Query records with filter/sort
|
|
48
|
+
- `create_bitable_record` / `update_bitable_record` / `delete_bitable_record` — Single record CRUD
|
|
49
|
+
- `batch_create_bitable_records` / `batch_update_bitable_records` / `batch_delete_bitable_records` — Batch operations (max 500/call)
|
|
38
50
|
- `list_wiki_spaces` / `search_wiki` / `list_wiki_nodes` — Wiki
|
|
39
51
|
- `list_files` / `create_folder` — Drive
|
|
52
|
+
- `copy_file` / `move_file` / `delete_file` — Drive file operations (copy, move, delete)
|
|
53
|
+
- `upload_image` / `upload_file` — Upload image/file, returns key for send_image/send_file
|
|
40
54
|
- `find_user` — Contact lookup by email/mobile
|
|
55
|
+
- `list_calendars` / `create_calendar_event` / `list_calendar_events` / `delete_calendar_event` — Calendar management
|
|
56
|
+
- `get_freebusy` — Check user availability
|
|
57
|
+
- `create_task` / `get_task` / `list_tasks` / `update_task` / `complete_task` — Task management
|
|
41
58
|
|
|
42
59
|
## Usage Patterns
|
|
60
|
+
|
|
61
|
+
### Messaging
|
|
43
62
|
- Send text as yourself → `send_to_user` or `send_to_group`
|
|
44
|
-
- Send
|
|
63
|
+
- Send image → `upload_image` → `send_image_as_user`
|
|
64
|
+
- Send file → `upload_file` → `send_file_as_user`
|
|
65
|
+
- Send rich content → `send_post_as_user` (formatted text with links, @mentions)
|
|
66
|
+
- Reply as user in thread → `send_as_user` with root_id
|
|
67
|
+
- Reply as bot → `reply_message` (official API)
|
|
68
|
+
|
|
69
|
+
### Reading
|
|
45
70
|
- Read any group chat history → `read_messages` with chat name or ID (auto-handles external groups via UAT fallback)
|
|
46
71
|
- Read P2P chat history → `search_contacts` → `create_p2p_chat` → `read_p2p_messages`
|
|
47
|
-
-
|
|
48
|
-
|
|
72
|
+
- Get chat details → `get_chat_info` (supports both oc_xxx and numeric ID)
|
|
73
|
+
|
|
74
|
+
### Bitable (Multi-dimensional Tables)
|
|
75
|
+
- Create a bitable from scratch → `create_bitable` → `create_bitable_table` → `create_bitable_field`
|
|
76
|
+
- Query data → `list_bitable_tables` → `list_bitable_fields` → `search_bitable_records`
|
|
77
|
+
- Single record CRUD → `create_bitable_record` / `update_bitable_record` / `delete_bitable_record`
|
|
78
|
+
- Bulk operations → `batch_create_bitable_records` / `batch_update_bitable_records` / `batch_delete_bitable_records` (max 500/call)
|
|
79
|
+
- Manage fields → `create_bitable_field` / `update_bitable_field` (requires type param) / `delete_bitable_field`
|
|
80
|
+
|
|
81
|
+
### Group Management
|
|
82
|
+
- Create a group → `create_group` with name and optional member open_ids
|
|
83
|
+
- Add/remove members → `add_members` / `remove_members` with chat_id + user open_ids
|
|
84
|
+
- List members → `list_members`
|
|
85
|
+
|
|
86
|
+
### Document Editing
|
|
87
|
+
- Create doc with content → `create_doc` → `create_doc_block` (use document_id as parent_block_id for root)
|
|
88
|
+
- Edit existing block → `get_doc_blocks` to find block_id → `update_doc_block`
|
|
89
|
+
- Delete blocks → `delete_doc_blocks` with start/end index range
|
|
90
|
+
|
|
91
|
+
### Calendar
|
|
92
|
+
- View schedule → `list_calendars` → `list_calendar_events`
|
|
93
|
+
- Create event → `create_calendar_event` with calendar_id, summary, start/end time
|
|
94
|
+
- Check availability → `get_freebusy` with user open_ids and time range
|
|
95
|
+
|
|
96
|
+
### Tasks
|
|
97
|
+
- Create task → `create_task` with summary, optional description/due
|
|
98
|
+
- Track tasks → `list_tasks` → `update_task` / `complete_task`
|
|
99
|
+
|
|
100
|
+
### Diagnostics
|
|
49
101
|
- Diagnose issues → `get_login_status` first
|
|
50
102
|
|
|
51
103
|
## Auth & Session
|
|
52
|
-
- **LARK_COOKIE**: Required for user identity tools. Session auto-refreshed every 4h via heartbeat.
|
|
104
|
+
- **LARK_COOKIE**: Required for user identity tools. Session auto-refreshed every 4h via heartbeat and persisted to config.
|
|
53
105
|
- **LARK_APP_ID + LARK_APP_SECRET**: Required for official API tools.
|
|
54
|
-
- **LARK_USER_ACCESS_TOKEN + LARK_USER_REFRESH_TOKEN**: Required for P2P reading. Auto-refreshed
|
|
106
|
+
- **LARK_USER_ACCESS_TOKEN + LARK_USER_REFRESH_TOKEN**: Required for P2P reading. Auto-refreshed on expiry (error codes 99991668/99991663/99991677). Token auto-persisted to MCP config on refresh.
|
|
107
|
+
- Cookie expiry: sl_session has 12h max-age, auto-refreshed by heartbeat every 4h.
|
|
108
|
+
- UAT expiry: 2h, auto-refreshed via refresh_token.
|
|
109
|
+
- Refresh token expiry: 7 days. Use `keepalive` cron to prevent expiration.
|
|
55
110
|
|
|
56
|
-
##
|
|
111
|
+
## Required Environment Variables (ALL are required for full functionality)
|
|
112
|
+
|
|
113
|
+
| Variable | Purpose |
|
|
114
|
+
|----------|---------|
|
|
115
|
+
| LARK_COOKIE | User identity messaging |
|
|
116
|
+
| LARK_APP_ID | Official API access |
|
|
117
|
+
| LARK_APP_SECRET | Official API access |
|
|
118
|
+
| LARK_USER_ACCESS_TOKEN | P2P chat reading |
|
|
119
|
+
| LARK_USER_REFRESH_TOKEN | UAT auto-refresh |
|
|
120
|
+
|
|
121
|
+
All 5 must be configured. Without UAT, `read_p2p_messages` and `list_user_chats` will not work.
|
|
122
|
+
|
|
123
|
+
## Installation
|
|
124
|
+
|
|
125
|
+
### Config location
|
|
126
|
+
|
|
127
|
+
Credentials are stored in `~/.claude.json` top-level `mcpServers` (global — works in all directories).
|
|
128
|
+
**Do NOT put credentials in project-level config** (`projects[*].mcpServers` or `.mcp.json`) — this causes scope issues.
|
|
129
|
+
|
|
130
|
+
### Non-interactive setup (for Claude Code agents)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx feishu-user-plugin setup --app-id <APP_ID> --app-secret <APP_SECRET>
|
|
134
|
+
```
|
|
57
135
|
|
|
58
|
-
|
|
136
|
+
Writes config to `~/.claude.json` top-level `mcpServers` without any interactive prompts. Supports `--cookie` flag too.
|
|
137
|
+
|
|
138
|
+
### Interactive setup
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx feishu-user-plugin setup # Interactive setup wizard
|
|
142
|
+
npx feishu-user-plugin oauth # Get OAuth UAT tokens
|
|
143
|
+
npx feishu-user-plugin status # Check auth status
|
|
144
|
+
npx feishu-user-plugin keepalive # Refresh cookie + UAT (for cron jobs)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Token auto-renewal via cron (optional)
|
|
148
|
+
|
|
149
|
+
To keep tokens alive even when Claude Code is closed:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
crontab -e
|
|
153
|
+
# Add: 0 */4 * * * npx feishu-user-plugin keepalive >> /tmp/feishu-keepalive.log 2>&1
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Automated Cookie Setup via Playwright
|
|
59
157
|
|
|
60
158
|
### Prerequisites
|
|
61
|
-
Playwright MCP must be available. If not:
|
|
62
|
-
> Run: `npx @anthropic-ai/claude-code mcp add playwright -- npx @anthropic-ai/mcp-server-playwright`
|
|
63
|
-
> Then restart Claude Code.
|
|
159
|
+
Playwright MCP must be available. If not installed:
|
|
160
|
+
> Run: `npx @anthropic-ai/claude-code mcp add playwright -- npx @anthropic-ai/mcp-server-playwright` then restart Claude Code.
|
|
64
161
|
|
|
65
|
-
### Automated Flow — FOLLOW EXACTLY
|
|
162
|
+
### Automated Flow — FOLLOW EXACTLY, DO NOT IMPROVISE
|
|
66
163
|
|
|
67
|
-
**Step 1: Clear
|
|
164
|
+
**Step 1: Clear existing browser session (MANDATORY)**
|
|
165
|
+
|
|
166
|
+
Playwright MCP uses Edge's persistent profile. It may have a cached login from a DIFFERENT Feishu account. You MUST clear cookies first:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
browser_run_code:
|
|
170
|
+
await context.clearCookies();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then navigate:
|
|
68
174
|
```
|
|
69
|
-
browser_run_code: await context.clearCookies();
|
|
70
175
|
browser_navigate: https://www.feishu.cn/messenger/
|
|
71
176
|
```
|
|
72
177
|
|
|
73
|
-
**Step 2:
|
|
178
|
+
**Step 2: Wait for user to scan QR code**
|
|
179
|
+
|
|
180
|
+
Take a screenshot to show the QR code:
|
|
74
181
|
```
|
|
75
182
|
browser_take_screenshot
|
|
76
183
|
```
|
|
77
|
-
Poll `browser_snapshot` every 5s until URL changes from `/accounts/`.
|
|
78
184
|
|
|
79
|
-
|
|
185
|
+
Tell the user: "Please scan the QR code with Feishu mobile app to log in. Make sure you use the correct account."
|
|
186
|
+
|
|
187
|
+
Poll with `browser_snapshot` every 5 seconds until the URL changes away from `/accounts/` (indicating login complete).
|
|
188
|
+
|
|
189
|
+
**Step 3: Extract cookie — TWO-STEP approach (MANDATORY)**
|
|
190
|
+
|
|
191
|
+
NEVER use `browser_run_code` output directly as the cookie string. Its output includes `### Result\n` markdown prefix, page snapshots, and console logs that contaminate the cookie.
|
|
80
192
|
|
|
81
|
-
Step 3a via `browser_run_code`:
|
|
193
|
+
Step 3a — Store cookie in page context via `browser_run_code`:
|
|
82
194
|
```js
|
|
83
195
|
const cookies = await page.context().cookies('https://www.feishu.cn');
|
|
84
196
|
const str = cookies.map(c => c.name + '=' + c.value).join('; ');
|
|
@@ -86,47 +198,170 @@ await page.evaluate(s => { window.__COOKIE__ = s; }, str);
|
|
|
86
198
|
return 'Stored ' + cookies.length + ' cookies, length=' + str.length;
|
|
87
199
|
```
|
|
88
200
|
|
|
89
|
-
Step 3b via `browser_evaluate`:
|
|
201
|
+
Step 3b — Read the clean cookie string via `browser_evaluate`:
|
|
90
202
|
```js
|
|
91
203
|
window.__COOKIE__
|
|
92
204
|
```
|
|
93
205
|
|
|
94
|
-
|
|
206
|
+
This two-step approach ensures the cookie string is clean, with no markdown prefix or page content mixed in.
|
|
95
207
|
|
|
96
|
-
**Step
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
208
|
+
**Step 4: Validate BEFORE writing (MANDATORY)**
|
|
209
|
+
|
|
210
|
+
Check the cookie string:
|
|
211
|
+
1. Must be pure ASCII — no Chinese characters, no markdown (`###`), no HTML
|
|
212
|
+
2. Must contain `session=` and `sl_session=`
|
|
213
|
+
3. Length should be 500-5000 characters. If >10000, it is contaminated — DO NOT write it.
|
|
214
|
+
4. Must NOT start with `###` or contain `\n` followed by non-cookie content
|
|
215
|
+
|
|
216
|
+
If validation fails: STOP. Debug the extraction. Do NOT write a bad cookie to config.
|
|
217
|
+
|
|
218
|
+
**Step 5: Write cookie to config**
|
|
219
|
+
|
|
220
|
+
Use `persistToConfig` or directly update the `LARK_COOKIE` field in `~/.claude.json` → `mcpServers` → `feishu-user-plugin` → `env`.
|
|
221
|
+
|
|
222
|
+
**Step 6: Run OAuth for UAT (if not already configured)**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npx feishu-user-plugin oauth
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This opens a browser for OAuth consent. After completion, tokens are auto-saved to `~/.claude.json`.
|
|
229
|
+
|
|
230
|
+
**Step 7: Close browser and prompt restart**
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
browser_close
|
|
111
234
|
```
|
|
112
235
|
|
|
113
|
-
|
|
236
|
+
Tell user to restart Claude Code. Only ONE restart should be needed.
|
|
237
|
+
|
|
238
|
+
## Troubleshooting Guide
|
|
239
|
+
|
|
240
|
+
### If MCP tools are not available
|
|
241
|
+
1. Check `~/.claude.json` — config must be in **top-level** `mcpServers`, not inside `projects[*]`
|
|
242
|
+
2. Restart Claude Code after config changes
|
|
243
|
+
3. After restart, tools may take a few seconds to register — if first call fails with "No such tool", wait and retry once
|
|
114
244
|
|
|
115
245
|
### If cookie authentication fails
|
|
246
|
+
- `document.cookie` in browser console CANNOT access HttpOnly cookies (`session`, `sl_session`)
|
|
247
|
+
- **Correct method**: Network tab → first request → Request Headers → Cookie → Copy value
|
|
116
248
|
- **Best method**: Playwright two-step extraction (see above)
|
|
117
|
-
- **Manual fallback**: Network tab → first request → Request Headers → Cookie → Copy value
|
|
118
|
-
- Do NOT use `document.cookie` or Application → Cookies (misses HttpOnly cookies)
|
|
119
249
|
|
|
120
|
-
### If Playwright logs into the wrong account
|
|
121
|
-
-
|
|
122
|
-
-
|
|
250
|
+
### If Playwright logs into the wrong Feishu account
|
|
251
|
+
- Playwright uses Edge's persistent profile with cached sessions
|
|
252
|
+
- **ALWAYS clear cookies first** with `context.clearCookies()` before navigating to feishu.cn
|
|
253
|
+
|
|
254
|
+
### If read_messages returns an error
|
|
255
|
+
- Error messages include the actual Feishu error code and description
|
|
256
|
+
- `read_messages` auto-falls back to UAT when bot API fails (e.g. external groups)
|
|
257
|
+
- Chat name resolution: bot's group list → `im.chat.search` → `search_contacts` (cookie)
|
|
258
|
+
- If all three strategies fail, provide the oc_xxx or numeric chat ID directly
|
|
123
259
|
|
|
124
|
-
### If UAT refresh fails with "invalid_grant"
|
|
125
|
-
-
|
|
126
|
-
-
|
|
260
|
+
### If UAT refresh fails with "invalid_grant"
|
|
261
|
+
- The refresh token has expired or been revoked — auto-refresh cannot recover this
|
|
262
|
+
- **Fix**: Re-run OAuth: `npx feishu-user-plugin oauth`
|
|
263
|
+
- Then restart Claude Code
|
|
264
|
+
|
|
265
|
+
### If OAuth fails with "Missing LARK_APP_ID"
|
|
266
|
+
- `oauth.js` reads credentials from `~/.claude.json` MCP config (not .env)
|
|
267
|
+
- Run `npx feishu-user-plugin setup` first, then re-run OAuth
|
|
268
|
+
|
|
269
|
+
### If two MCP servers are running (duplicate tools)
|
|
270
|
+
- This happens when both `~/.claude.json` mcpServers AND a team-skills plugin have feishu-user-plugin
|
|
271
|
+
- team-skills plugin should NOT have `.mcp.json` — it only provides skills and CLAUDE.md
|
|
272
|
+
- Delete `.mcp.json` from the team-skills plugin directory if it exists
|
|
127
273
|
|
|
128
274
|
### If list_user_chats doesn't return P2P chats
|
|
129
|
-
-
|
|
275
|
+
- This is expected — the API only returns group chats
|
|
276
|
+
- **Correct P2P flow**: `search_contacts` → `create_p2p_chat` → `read_p2p_messages`
|
|
277
|
+
|
|
278
|
+
## Architecture
|
|
279
|
+
|
|
280
|
+
### Two distribution channels
|
|
281
|
+
- **npm package** (`npx feishu-user-plugin`): MCP server code + skills + CLAUDE.md. For external users.
|
|
282
|
+
- **team-skills plugin**: Skills + CLAUDE.md only (no .mcp.json). For internal team members.
|
|
283
|
+
|
|
284
|
+
### Config management
|
|
285
|
+
- `src/config.js`: Unified config module. Discovers config in `~/.claude.json` (top-level + project-level) and `.mcp.json`.
|
|
286
|
+
- `setup` always writes to `~/.claude.json` top-level `mcpServers` (global).
|
|
287
|
+
- `persistToConfig()` finds the correct config entry and writes back (used by heartbeat + UAT refresh).
|
|
288
|
+
|
|
289
|
+
## Development & Publishing
|
|
290
|
+
|
|
291
|
+
### Publishing to npm
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# 1. Update version in package.json
|
|
295
|
+
# 2. Commit and tag
|
|
296
|
+
git add -A && git commit -m "v1.2.1: description"
|
|
297
|
+
git tag v1.2.1
|
|
298
|
+
git push && git push --tags
|
|
299
|
+
# 3. GitHub Actions auto-publishes to npm on tag push
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
GitHub Actions workflow (`.github/workflows/publish.yml`) auto-publishes on `v*` tags.
|
|
303
|
+
NPM_TOKEN is stored as a GitHub repo secret.
|
|
304
|
+
|
|
305
|
+
### Syncing to team-skills
|
|
306
|
+
|
|
307
|
+
After publishing, sync plugin assets to team-skills:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# From the feishu-user-plugin repo:
|
|
311
|
+
cp -r skills/ /path/to/team-skills/plugins/feishu-user-plugin/skills/
|
|
312
|
+
cp .claude-plugin/plugin.json /path/to/team-skills/plugins/feishu-user-plugin/.claude-plugin/
|
|
313
|
+
# Do NOT copy .mcp.json — team-skills plugin should not have one
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Development Workflow
|
|
317
|
+
|
|
318
|
+
### Keeping ROADMAP.md up to date
|
|
319
|
+
- When completing a feature or fixing a bug, check the corresponding item in ROADMAP.md as `[x]` done
|
|
320
|
+
- When discovering new bugs, limitations, or feature ideas during development, add them to the appropriate section in ROADMAP.md
|
|
321
|
+
- When a version is released (tag pushed), move completed items under the "已完成" section with the version number
|
|
322
|
+
- When researching a direction and deciding not to implement, add it to "已调研但暂不实施" with the reasoning
|
|
323
|
+
|
|
324
|
+
### When adding new tools
|
|
325
|
+
1. Add method to `src/official.js`(Official API)or `src/client.js`(Cookie 身份)
|
|
326
|
+
2. Add tool definition to `TOOLS` array in `src/index.js`
|
|
327
|
+
3. Add handler case in `handleTool()` switch in `src/index.js`
|
|
328
|
+
4. Run `node -c src/official.js && node -c src/index.js` to verify syntax
|
|
329
|
+
5. Update this file (CLAUDE.md) — tool count, tool list, usage patterns
|
|
330
|
+
6. Update ROADMAP.md if relevant
|
|
331
|
+
|
|
332
|
+
### When fixing bugs
|
|
333
|
+
1. Write a standalone test script (`node -e "..."`) to reproduce the bug before fixing
|
|
334
|
+
2. After fixing, verify with the same script
|
|
335
|
+
3. If the bug affects MCP tool behavior, test via MCP tool call after server restart
|
|
336
|
+
|
|
337
|
+
### Commit conventions
|
|
338
|
+
- `feat:` new tools or capabilities
|
|
339
|
+
- `fix:` bug fixes
|
|
340
|
+
- `docs:` CLAUDE.md, ROADMAP.md, README updates
|
|
341
|
+
- `chore:` dependencies, CI, config changes
|
|
342
|
+
|
|
343
|
+
### Publishing
|
|
344
|
+
1. Update `version` in `package.json`
|
|
345
|
+
2. `git add <files> && git commit -m "v1.x.x: description"`
|
|
346
|
+
3. `git tag v1.x.x && git push && git push --tags`
|
|
347
|
+
4. GitHub Actions auto-publishes to npm. Users get the new version on next Claude Code restart.
|
|
348
|
+
|
|
349
|
+
### Syncing to team-skills (after any CLAUDE.md or skills change)
|
|
350
|
+
1. Copy CLAUDE.md to skill reference: `cp CLAUDE.md skills/feishu-user-plugin/references/CLAUDE.md`
|
|
351
|
+
2. Sync to team-skills repo: `cp -r skills/ /Users/abble/team-skills/plugins/feishu-user-plugin/skills/`
|
|
352
|
+
3. Also sync plugin.json: `cp .claude-plugin/plugin.json /Users/abble/team-skills/plugins/feishu-user-plugin/.claude-plugin/`
|
|
353
|
+
4. Commit and push both repos
|
|
354
|
+
|
|
355
|
+
### Testing a tool
|
|
356
|
+
- For Official API tools: can test directly via MCP tool call or standalone script using `readCredentials()` from `src/config.js`
|
|
357
|
+
- For Cookie tools: need active session, test via MCP tool call
|
|
358
|
+
- Always verify `_safeSDKCall` handles the response format (multipart uploads return data at top level, not nested under `.data`)
|
|
130
359
|
|
|
131
|
-
|
|
132
|
-
-
|
|
360
|
+
## Known Limitations
|
|
361
|
+
- CARD message type (type=14) not yet implemented — complex JSON schema
|
|
362
|
+
- External tenant users may not be resolvable via `get_user_info` (contact API scope limitation)
|
|
363
|
+
- Cookie auth requires human interaction (QR scan) — cannot be fully automated
|
|
364
|
+
- Refresh token expires after 7 days without use — set up `keepalive` cron to prevent this
|
|
365
|
+
- `update_bitable_field` requires `type` parameter even when only changing field name (Feishu API requirement)
|
|
366
|
+
- `list_wiki_spaces` may return empty if bot lacks `wiki:wiki:readonly` permission
|
|
367
|
+
- `search_wiki` uses same API as `search_docs` — `docs_types` filter may not work as expected
|
package/src/index.js
CHANGED
|
@@ -710,6 +710,384 @@ const TOOLS = [
|
|
|
710
710
|
},
|
|
711
711
|
},
|
|
712
712
|
},
|
|
713
|
+
|
|
714
|
+
// ========== IM — Bot Send / Edit / Delete ==========
|
|
715
|
+
{
|
|
716
|
+
name: 'send_message_as_bot',
|
|
717
|
+
description: '[Official API] Send a message as the bot to any chat. Supports text, post, interactive, etc.',
|
|
718
|
+
inputSchema: {
|
|
719
|
+
type: 'object',
|
|
720
|
+
properties: {
|
|
721
|
+
chat_id: { type: 'string', description: 'Target chat_id (oc_xxx) or open_id' },
|
|
722
|
+
msg_type: { type: 'string', description: 'Message type: text, post, image, interactive, etc.', enum: ['text', 'post', 'image', 'interactive', 'share_chat', 'share_user', 'audio', 'media', 'file', 'sticker'] },
|
|
723
|
+
content: { description: 'Message content (string or object, auto-serialized). For text: {"text":"hello"}' },
|
|
724
|
+
},
|
|
725
|
+
required: ['chat_id', 'msg_type', 'content'],
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
name: 'delete_message',
|
|
730
|
+
description: '[Official API] Recall/delete a message (bot can only delete its own messages).',
|
|
731
|
+
inputSchema: {
|
|
732
|
+
type: 'object',
|
|
733
|
+
properties: { message_id: { type: 'string', description: 'Message ID (om_xxx)' } },
|
|
734
|
+
required: ['message_id'],
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: 'update_message',
|
|
739
|
+
description: '[Official API] Edit a sent message (bot can only edit its own messages). Supports text and post.',
|
|
740
|
+
inputSchema: {
|
|
741
|
+
type: 'object',
|
|
742
|
+
properties: {
|
|
743
|
+
message_id: { type: 'string', description: 'Message ID (om_xxx)' },
|
|
744
|
+
msg_type: { type: 'string', description: 'Message type: text or post' },
|
|
745
|
+
content: { description: 'New content. For text: {"text":"updated text"}' },
|
|
746
|
+
},
|
|
747
|
+
required: ['message_id', 'msg_type', 'content'],
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
|
|
751
|
+
// ========== IM — Reactions ==========
|
|
752
|
+
{
|
|
753
|
+
name: 'add_reaction',
|
|
754
|
+
description: '[Official API] Add an emoji reaction to a message.',
|
|
755
|
+
inputSchema: {
|
|
756
|
+
type: 'object',
|
|
757
|
+
properties: {
|
|
758
|
+
message_id: { type: 'string', description: 'Message ID (om_xxx)' },
|
|
759
|
+
emoji_type: { type: 'string', description: 'Emoji type string, e.g. "THUMBSUP", "SMILE", "HEART"' },
|
|
760
|
+
},
|
|
761
|
+
required: ['message_id', 'emoji_type'],
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
name: 'delete_reaction',
|
|
766
|
+
description: '[Official API] Remove an emoji reaction from a message.',
|
|
767
|
+
inputSchema: {
|
|
768
|
+
type: 'object',
|
|
769
|
+
properties: {
|
|
770
|
+
message_id: { type: 'string', description: 'Message ID' },
|
|
771
|
+
reaction_id: { type: 'string', description: 'Reaction ID (from add_reaction response)' },
|
|
772
|
+
},
|
|
773
|
+
required: ['message_id', 'reaction_id'],
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
// ========== IM — Pin Messages ==========
|
|
778
|
+
{
|
|
779
|
+
name: 'pin_message',
|
|
780
|
+
description: '[Official API] Pin a message in a chat.',
|
|
781
|
+
inputSchema: {
|
|
782
|
+
type: 'object',
|
|
783
|
+
properties: { message_id: { type: 'string', description: 'Message ID to pin' } },
|
|
784
|
+
required: ['message_id'],
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: 'unpin_message',
|
|
789
|
+
description: '[Official API] Unpin a message from a chat.',
|
|
790
|
+
inputSchema: {
|
|
791
|
+
type: 'object',
|
|
792
|
+
properties: { message_id: { type: 'string', description: 'Message ID to unpin' } },
|
|
793
|
+
required: ['message_id'],
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
|
|
797
|
+
// ========== IM — Chat Management ==========
|
|
798
|
+
{
|
|
799
|
+
name: 'create_group',
|
|
800
|
+
description: '[Official API] Create a new group chat (as bot). Can add initial members.',
|
|
801
|
+
inputSchema: {
|
|
802
|
+
type: 'object',
|
|
803
|
+
properties: {
|
|
804
|
+
name: { type: 'string', description: 'Group name' },
|
|
805
|
+
description: { type: 'string', description: 'Group description (optional)' },
|
|
806
|
+
user_ids: { type: 'array', items: { type: 'string' }, description: 'Initial member open_ids (optional)' },
|
|
807
|
+
},
|
|
808
|
+
required: ['name'],
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: 'update_group',
|
|
813
|
+
description: '[Official API] Update group chat name or description.',
|
|
814
|
+
inputSchema: {
|
|
815
|
+
type: 'object',
|
|
816
|
+
properties: {
|
|
817
|
+
chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
|
|
818
|
+
name: { type: 'string', description: 'New group name (optional)' },
|
|
819
|
+
description: { type: 'string', description: 'New description (optional)' },
|
|
820
|
+
},
|
|
821
|
+
required: ['chat_id'],
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
name: 'list_members',
|
|
826
|
+
description: '[Official API] List all members in a group chat.',
|
|
827
|
+
inputSchema: {
|
|
828
|
+
type: 'object',
|
|
829
|
+
properties: {
|
|
830
|
+
chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
|
|
831
|
+
page_size: { type: 'number', description: 'Items per page (default 50)' },
|
|
832
|
+
page_token: { type: 'string', description: 'Pagination token' },
|
|
833
|
+
},
|
|
834
|
+
required: ['chat_id'],
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
name: 'add_members',
|
|
839
|
+
description: '[Official API] Add users to a group chat.',
|
|
840
|
+
inputSchema: {
|
|
841
|
+
type: 'object',
|
|
842
|
+
properties: {
|
|
843
|
+
chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
|
|
844
|
+
user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to add' },
|
|
845
|
+
},
|
|
846
|
+
required: ['chat_id', 'user_ids'],
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
name: 'remove_members',
|
|
851
|
+
description: '[Official API] Remove users from a group chat.',
|
|
852
|
+
inputSchema: {
|
|
853
|
+
type: 'object',
|
|
854
|
+
properties: {
|
|
855
|
+
chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
|
|
856
|
+
user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to remove' },
|
|
857
|
+
},
|
|
858
|
+
required: ['chat_id', 'user_ids'],
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
// ========== Docs — Block Editing ==========
|
|
863
|
+
{
|
|
864
|
+
name: 'create_doc_block',
|
|
865
|
+
description: '[Official API] Insert content blocks into a document. Add text, headings, lists, etc. after create_doc.',
|
|
866
|
+
inputSchema: {
|
|
867
|
+
type: 'object',
|
|
868
|
+
properties: {
|
|
869
|
+
document_id: { type: 'string', description: 'Document ID' },
|
|
870
|
+
parent_block_id: { type: 'string', description: 'Parent block ID (use document_id for root)' },
|
|
871
|
+
children: { type: 'array', description: 'Array of block objects to insert. E.g. [{block_type:2, text:{elements:[{text_run:{content:"Hello"}}]}}]', items: { type: 'object' } },
|
|
872
|
+
index: { type: 'number', description: 'Insert position (optional, appends to end if omitted)' },
|
|
873
|
+
},
|
|
874
|
+
required: ['document_id', 'parent_block_id', 'children'],
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: 'update_doc_block',
|
|
879
|
+
description: '[Official API] Update a specific block in a document (change text content, style, etc.).',
|
|
880
|
+
inputSchema: {
|
|
881
|
+
type: 'object',
|
|
882
|
+
properties: {
|
|
883
|
+
document_id: { type: 'string', description: 'Document ID' },
|
|
884
|
+
block_id: { type: 'string', description: 'Block ID to update' },
|
|
885
|
+
update_body: { type: 'object', description: 'Update payload. E.g. {update_text_elements:{elements:[{text_run:{content:"new text"}}]}}' },
|
|
886
|
+
},
|
|
887
|
+
required: ['document_id', 'block_id', 'update_body'],
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: 'delete_doc_blocks',
|
|
892
|
+
description: '[Official API] Delete a range of blocks from a document.',
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: 'object',
|
|
895
|
+
properties: {
|
|
896
|
+
document_id: { type: 'string', description: 'Document ID' },
|
|
897
|
+
parent_block_id: { type: 'string', description: 'Parent block ID containing the blocks to delete' },
|
|
898
|
+
start_index: { type: 'number', description: 'Start index (inclusive)' },
|
|
899
|
+
end_index: { type: 'number', description: 'End index (exclusive)' },
|
|
900
|
+
},
|
|
901
|
+
required: ['document_id', 'parent_block_id', 'start_index', 'end_index'],
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
|
|
905
|
+
// ========== Bitable — Additional ==========
|
|
906
|
+
{
|
|
907
|
+
name: 'get_bitable_record',
|
|
908
|
+
description: '[Official API] Get a single record by ID from a Bitable table.',
|
|
909
|
+
inputSchema: {
|
|
910
|
+
type: 'object',
|
|
911
|
+
properties: {
|
|
912
|
+
app_token: { type: 'string', description: 'Bitable app token' },
|
|
913
|
+
table_id: { type: 'string', description: 'Table ID' },
|
|
914
|
+
record_id: { type: 'string', description: 'Record ID' },
|
|
915
|
+
},
|
|
916
|
+
required: ['app_token', 'table_id', 'record_id'],
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: 'delete_bitable_table',
|
|
921
|
+
description: '[Official API] Delete a data table from a Bitable app.',
|
|
922
|
+
inputSchema: {
|
|
923
|
+
type: 'object',
|
|
924
|
+
properties: {
|
|
925
|
+
app_token: { type: 'string', description: 'Bitable app token' },
|
|
926
|
+
table_id: { type: 'string', description: 'Table ID to delete' },
|
|
927
|
+
},
|
|
928
|
+
required: ['app_token', 'table_id'],
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
// ========== Drive — File Operations ==========
|
|
933
|
+
{
|
|
934
|
+
name: 'copy_file',
|
|
935
|
+
description: '[Official API] Copy a file/doc in Drive.',
|
|
936
|
+
inputSchema: {
|
|
937
|
+
type: 'object',
|
|
938
|
+
properties: {
|
|
939
|
+
file_token: { type: 'string', description: 'File token to copy' },
|
|
940
|
+
name: { type: 'string', description: 'New file name' },
|
|
941
|
+
folder_token: { type: 'string', description: 'Destination folder token (optional)' },
|
|
942
|
+
type: { type: 'string', description: 'File type: file, doc, sheet, bitable, docx, mindnote, slides (optional)' },
|
|
943
|
+
},
|
|
944
|
+
required: ['file_token', 'name'],
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: 'move_file',
|
|
949
|
+
description: '[Official API] Move a file to another folder in Drive.',
|
|
950
|
+
inputSchema: {
|
|
951
|
+
type: 'object',
|
|
952
|
+
properties: {
|
|
953
|
+
file_token: { type: 'string', description: 'File token to move' },
|
|
954
|
+
folder_token: { type: 'string', description: 'Destination folder token' },
|
|
955
|
+
},
|
|
956
|
+
required: ['file_token', 'folder_token'],
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
name: 'delete_file',
|
|
961
|
+
description: '[Official API] Delete a file/folder from Drive.',
|
|
962
|
+
inputSchema: {
|
|
963
|
+
type: 'object',
|
|
964
|
+
properties: {
|
|
965
|
+
file_token: { type: 'string', description: 'File token to delete' },
|
|
966
|
+
type: { type: 'string', description: 'Type: file, folder, doc, sheet, bitable, docx, mindnote, slides' },
|
|
967
|
+
},
|
|
968
|
+
required: ['file_token'],
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
|
|
972
|
+
// ========== Calendar ==========
|
|
973
|
+
{
|
|
974
|
+
name: 'list_calendars',
|
|
975
|
+
description: '[Official API] List all calendars accessible by the bot.',
|
|
976
|
+
inputSchema: { type: 'object', properties: {} },
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
name: 'create_calendar_event',
|
|
980
|
+
description: '[Official API] Create a calendar event.',
|
|
981
|
+
inputSchema: {
|
|
982
|
+
type: 'object',
|
|
983
|
+
properties: {
|
|
984
|
+
calendar_id: { type: 'string', description: 'Calendar ID (from list_calendars)' },
|
|
985
|
+
summary: { type: 'string', description: 'Event title' },
|
|
986
|
+
start_time: { type: 'string', description: 'Start time (RFC3339 or Unix timestamp string)' },
|
|
987
|
+
end_time: { type: 'string', description: 'End time (RFC3339 or Unix timestamp string)' },
|
|
988
|
+
description: { type: 'string', description: 'Event description (optional)' },
|
|
989
|
+
location: { type: 'string', description: 'Event location (optional)' },
|
|
990
|
+
},
|
|
991
|
+
required: ['calendar_id', 'summary', 'start_time', 'end_time'],
|
|
992
|
+
},
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
name: 'list_calendar_events',
|
|
996
|
+
description: '[Official API] List events in a calendar.',
|
|
997
|
+
inputSchema: {
|
|
998
|
+
type: 'object',
|
|
999
|
+
properties: {
|
|
1000
|
+
calendar_id: { type: 'string', description: 'Calendar ID' },
|
|
1001
|
+
start_time: { type: 'string', description: 'Start time filter (Unix timestamp string, optional)' },
|
|
1002
|
+
end_time: { type: 'string', description: 'End time filter (Unix timestamp string, optional)' },
|
|
1003
|
+
page_size: { type: 'number', description: 'Items per page (default 50)' },
|
|
1004
|
+
},
|
|
1005
|
+
required: ['calendar_id'],
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: 'delete_calendar_event',
|
|
1010
|
+
description: '[Official API] Delete a calendar event.',
|
|
1011
|
+
inputSchema: {
|
|
1012
|
+
type: 'object',
|
|
1013
|
+
properties: {
|
|
1014
|
+
calendar_id: { type: 'string', description: 'Calendar ID' },
|
|
1015
|
+
event_id: { type: 'string', description: 'Event ID to delete' },
|
|
1016
|
+
},
|
|
1017
|
+
required: ['calendar_id', 'event_id'],
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: 'get_freebusy',
|
|
1022
|
+
description: '[Official API] Check free/busy status of users for a time range.',
|
|
1023
|
+
inputSchema: {
|
|
1024
|
+
type: 'object',
|
|
1025
|
+
properties: {
|
|
1026
|
+
user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to check' },
|
|
1027
|
+
start_time: { type: 'string', description: 'Range start (RFC3339)' },
|
|
1028
|
+
end_time: { type: 'string', description: 'Range end (RFC3339)' },
|
|
1029
|
+
},
|
|
1030
|
+
required: ['user_ids', 'start_time', 'end_time'],
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
|
|
1034
|
+
// ========== Tasks ==========
|
|
1035
|
+
{
|
|
1036
|
+
name: 'create_task',
|
|
1037
|
+
description: '[Official API] Create a new task in Feishu Tasks.',
|
|
1038
|
+
inputSchema: {
|
|
1039
|
+
type: 'object',
|
|
1040
|
+
properties: {
|
|
1041
|
+
summary: { type: 'string', description: 'Task title/summary' },
|
|
1042
|
+
description: { type: 'string', description: 'Task description (optional)' },
|
|
1043
|
+
due: { type: 'string', description: 'Due date/time (RFC3339 or Unix timestamp string, optional)' },
|
|
1044
|
+
},
|
|
1045
|
+
required: ['summary'],
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
name: 'get_task',
|
|
1050
|
+
description: '[Official API] Get task details by ID.',
|
|
1051
|
+
inputSchema: {
|
|
1052
|
+
type: 'object',
|
|
1053
|
+
properties: { task_id: { type: 'string', description: 'Task ID' } },
|
|
1054
|
+
required: ['task_id'],
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
name: 'list_tasks',
|
|
1059
|
+
description: '[Official API] List tasks.',
|
|
1060
|
+
inputSchema: {
|
|
1061
|
+
type: 'object',
|
|
1062
|
+
properties: {
|
|
1063
|
+
page_size: { type: 'number', description: 'Items per page (default 50)' },
|
|
1064
|
+
page_token: { type: 'string', description: 'Pagination token' },
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
name: 'update_task',
|
|
1070
|
+
description: '[Official API] Update a task (title, description, due date, etc.).',
|
|
1071
|
+
inputSchema: {
|
|
1072
|
+
type: 'object',
|
|
1073
|
+
properties: {
|
|
1074
|
+
task_id: { type: 'string', description: 'Task ID' },
|
|
1075
|
+
summary: { type: 'string', description: 'New title (optional)' },
|
|
1076
|
+
description: { type: 'string', description: 'New description (optional)' },
|
|
1077
|
+
due: { type: 'string', description: 'New due date (optional)' },
|
|
1078
|
+
},
|
|
1079
|
+
required: ['task_id'],
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
name: 'complete_task',
|
|
1084
|
+
description: '[Official API] Mark a task as complete.',
|
|
1085
|
+
inputSchema: {
|
|
1086
|
+
type: 'object',
|
|
1087
|
+
properties: { task_id: { type: 'string', description: 'Task ID to complete' } },
|
|
1088
|
+
required: ['task_id'],
|
|
1089
|
+
},
|
|
1090
|
+
},
|
|
713
1091
|
];
|
|
714
1092
|
|
|
715
1093
|
// --- Server ---
|
|
@@ -1035,6 +1413,115 @@ async function handleTool(name, args) {
|
|
|
1035
1413
|
return text(`File uploaded: ${r.fileKey}\nUse this file_key with send_file_as_user to send it.`);
|
|
1036
1414
|
}
|
|
1037
1415
|
|
|
1416
|
+
// --- Official API: Bot Send / Edit / Delete ---
|
|
1417
|
+
|
|
1418
|
+
case 'send_message_as_bot': {
|
|
1419
|
+
const r = await getOfficialClient().sendMessageAsBot(args.chat_id, args.msg_type, args.content);
|
|
1420
|
+
return text(`Message sent (bot): ${r.messageId}`);
|
|
1421
|
+
}
|
|
1422
|
+
case 'delete_message':
|
|
1423
|
+
return text(`Message deleted: ${(await getOfficialClient().deleteMessage(args.message_id)).deleted}`);
|
|
1424
|
+
case 'update_message':
|
|
1425
|
+
return text(`Message updated: ${(await getOfficialClient().updateMessage(args.message_id, args.msg_type, args.content)).messageId}`);
|
|
1426
|
+
|
|
1427
|
+
// --- Official API: Reactions ---
|
|
1428
|
+
|
|
1429
|
+
case 'add_reaction':
|
|
1430
|
+
return text(`Reaction added: ${(await getOfficialClient().addReaction(args.message_id, args.emoji_type)).reactionId}`);
|
|
1431
|
+
case 'delete_reaction':
|
|
1432
|
+
return text(`Reaction removed: ${(await getOfficialClient().deleteReaction(args.message_id, args.reaction_id)).deleted}`);
|
|
1433
|
+
|
|
1434
|
+
// --- Official API: Pins ---
|
|
1435
|
+
|
|
1436
|
+
case 'pin_message':
|
|
1437
|
+
return json(await getOfficialClient().pinMessage(args.message_id));
|
|
1438
|
+
case 'unpin_message':
|
|
1439
|
+
return text(`Unpinned: ${(await getOfficialClient().unpinMessage(args.message_id)).deleted}`);
|
|
1440
|
+
|
|
1441
|
+
// --- Official API: Chat Management ---
|
|
1442
|
+
|
|
1443
|
+
case 'create_group':
|
|
1444
|
+
return text(`Group created: ${(await getOfficialClient().createChat({ name: args.name, description: args.description, userIds: args.user_ids })).chatId}`);
|
|
1445
|
+
case 'update_group':
|
|
1446
|
+
return text(`Group updated: ${(await getOfficialClient().updateChat(args.chat_id, { name: args.name, description: args.description })).updated}`);
|
|
1447
|
+
case 'list_members':
|
|
1448
|
+
return json(await getOfficialClient().listChatMembers(args.chat_id, { pageSize: args.page_size, pageToken: args.page_token }));
|
|
1449
|
+
case 'add_members': {
|
|
1450
|
+
const r = await getOfficialClient().addChatMembers(args.chat_id, args.user_ids);
|
|
1451
|
+
return text(r.invalidIds.length ? `Added (invalid IDs: ${r.invalidIds.join(', ')})` : 'Members added');
|
|
1452
|
+
}
|
|
1453
|
+
case 'remove_members': {
|
|
1454
|
+
const r = await getOfficialClient().removeChatMembers(args.chat_id, args.user_ids);
|
|
1455
|
+
return text(r.invalidIds.length ? `Removed (invalid IDs: ${r.invalidIds.join(', ')})` : 'Members removed');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// --- Official API: Doc Block Editing ---
|
|
1459
|
+
|
|
1460
|
+
case 'create_doc_block':
|
|
1461
|
+
return json(await getOfficialClient().createDocBlock(args.document_id, args.parent_block_id, args.children, args.index));
|
|
1462
|
+
case 'update_doc_block':
|
|
1463
|
+
return json(await getOfficialClient().updateDocBlock(args.document_id, args.block_id, args.update_body));
|
|
1464
|
+
case 'delete_doc_blocks':
|
|
1465
|
+
return text(`Blocks deleted: ${(await getOfficialClient().deleteDocBlocks(args.document_id, args.parent_block_id, args.start_index, args.end_index)).deleted}`);
|
|
1466
|
+
|
|
1467
|
+
// --- Official API: Bitable Additional ---
|
|
1468
|
+
|
|
1469
|
+
case 'get_bitable_record':
|
|
1470
|
+
return json(await getOfficialClient().getBitableRecord(args.app_token, args.table_id, args.record_id));
|
|
1471
|
+
case 'delete_bitable_table':
|
|
1472
|
+
return text(`Table deleted: ${(await getOfficialClient().deleteBitableTable(args.app_token, args.table_id)).deleted}`);
|
|
1473
|
+
|
|
1474
|
+
// --- Official API: Drive File Operations ---
|
|
1475
|
+
|
|
1476
|
+
case 'copy_file':
|
|
1477
|
+
return json(await getOfficialClient().copyFile(args.file_token, args.name, args.folder_token, args.type));
|
|
1478
|
+
case 'move_file':
|
|
1479
|
+
return text(`File moved: task=${(await getOfficialClient().moveFile(args.file_token, args.folder_token)).taskId}`);
|
|
1480
|
+
case 'delete_file':
|
|
1481
|
+
return text(`File deleted: task=${(await getOfficialClient().deleteFile(args.file_token, args.type)).taskId}`);
|
|
1482
|
+
|
|
1483
|
+
// --- Official API: Calendar ---
|
|
1484
|
+
|
|
1485
|
+
case 'list_calendars':
|
|
1486
|
+
return json(await getOfficialClient().listCalendars());
|
|
1487
|
+
case 'create_calendar_event': {
|
|
1488
|
+
const event = { summary: args.summary, description: args.description || '' };
|
|
1489
|
+
event.start_time = { timestamp: args.start_time };
|
|
1490
|
+
event.end_time = { timestamp: args.end_time };
|
|
1491
|
+
if (args.location) event.location = { name: args.location };
|
|
1492
|
+
return json(await getOfficialClient().createCalendarEvent(args.calendar_id, event));
|
|
1493
|
+
}
|
|
1494
|
+
case 'list_calendar_events':
|
|
1495
|
+
return json(await getOfficialClient().listCalendarEvents(args.calendar_id, {
|
|
1496
|
+
startTime: args.start_time, endTime: args.end_time, pageSize: args.page_size,
|
|
1497
|
+
}));
|
|
1498
|
+
case 'delete_calendar_event':
|
|
1499
|
+
return text(`Event deleted: ${(await getOfficialClient().deleteCalendarEvent(args.calendar_id, args.event_id)).deleted}`);
|
|
1500
|
+
case 'get_freebusy':
|
|
1501
|
+
return json(await getOfficialClient().getFreeBusy(args.user_ids, args.start_time, args.end_time));
|
|
1502
|
+
|
|
1503
|
+
// --- Official API: Tasks ---
|
|
1504
|
+
|
|
1505
|
+
case 'create_task': {
|
|
1506
|
+
const task = { summary: args.summary };
|
|
1507
|
+
if (args.description) task.description = args.description;
|
|
1508
|
+
if (args.due) task.due = { timestamp: args.due };
|
|
1509
|
+
return json(await getOfficialClient().createTask(task));
|
|
1510
|
+
}
|
|
1511
|
+
case 'get_task':
|
|
1512
|
+
return json(await getOfficialClient().getTask(args.task_id));
|
|
1513
|
+
case 'list_tasks':
|
|
1514
|
+
return json(await getOfficialClient().listTasks({ pageSize: args.page_size, pageToken: args.page_token }));
|
|
1515
|
+
case 'update_task': {
|
|
1516
|
+
const task = {};
|
|
1517
|
+
if (args.summary) task.summary = args.summary;
|
|
1518
|
+
if (args.description) task.description = args.description;
|
|
1519
|
+
if (args.due) task.due = { timestamp: args.due };
|
|
1520
|
+
return json(await getOfficialClient().updateTask(args.task_id, task));
|
|
1521
|
+
}
|
|
1522
|
+
case 'complete_task':
|
|
1523
|
+
return text(`Task completed: ${(await getOfficialClient().completeTask(args.task_id)).completed}`);
|
|
1524
|
+
|
|
1038
1525
|
default:
|
|
1039
1526
|
return text(`Unknown tool: ${name}`);
|
|
1040
1527
|
}
|
package/src/official.js
CHANGED
|
@@ -177,6 +177,140 @@ class LarkOfficialClient {
|
|
|
177
177
|
return { messageId: res.data.message_id };
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// --- IM: Send (Bot Identity) ---
|
|
181
|
+
|
|
182
|
+
async sendMessageAsBot(chatId, msgType, content, receiveIdType = 'chat_id') {
|
|
183
|
+
const res = await this._safeSDKCall(
|
|
184
|
+
() => this.client.im.message.create({
|
|
185
|
+
params: { receive_id_type: receiveIdType },
|
|
186
|
+
data: { receive_id: chatId, msg_type: msgType, content: typeof content === 'string' ? content : JSON.stringify(content) },
|
|
187
|
+
}),
|
|
188
|
+
'sendMessage'
|
|
189
|
+
);
|
|
190
|
+
return { messageId: res.data.message_id };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async deleteMessage(messageId) {
|
|
194
|
+
await this._safeSDKCall(
|
|
195
|
+
() => this.client.im.message.delete({ path: { message_id: messageId } }),
|
|
196
|
+
'deleteMessage'
|
|
197
|
+
);
|
|
198
|
+
return { deleted: true };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async updateMessage(messageId, msgType, content) {
|
|
202
|
+
const res = await this._safeSDKCall(
|
|
203
|
+
() => this.client.im.message.patch({
|
|
204
|
+
path: { message_id: messageId },
|
|
205
|
+
data: { msg_type: msgType, content: typeof content === 'string' ? content : JSON.stringify(content) },
|
|
206
|
+
}),
|
|
207
|
+
'updateMessage'
|
|
208
|
+
);
|
|
209
|
+
return { messageId: res.data?.message_id || messageId };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- IM: Reactions ---
|
|
213
|
+
|
|
214
|
+
async addReaction(messageId, emojiType) {
|
|
215
|
+
const res = await this._safeSDKCall(
|
|
216
|
+
() => this.client.im.messageReaction.create({
|
|
217
|
+
path: { message_id: messageId },
|
|
218
|
+
data: { reaction_type: { emoji_type: emojiType } },
|
|
219
|
+
}),
|
|
220
|
+
'addReaction'
|
|
221
|
+
);
|
|
222
|
+
return { reactionId: res.data.reaction_id };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async deleteReaction(messageId, reactionId) {
|
|
226
|
+
await this._safeSDKCall(
|
|
227
|
+
() => this.client.im.messageReaction.delete({
|
|
228
|
+
path: { message_id: messageId, reaction_id: reactionId },
|
|
229
|
+
}),
|
|
230
|
+
'deleteReaction'
|
|
231
|
+
);
|
|
232
|
+
return { deleted: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --- IM: Pins ---
|
|
236
|
+
|
|
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) {
|
|
246
|
+
await this._safeSDKCall(
|
|
247
|
+
() => this.client.im.pin.delete({ data: { message_id: messageId } }),
|
|
248
|
+
'unpinMessage'
|
|
249
|
+
);
|
|
250
|
+
return { deleted: true };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// --- IM: Chat Management ---
|
|
254
|
+
|
|
255
|
+
async createChat({ name, description, userIds, botIds } = {}) {
|
|
256
|
+
const data = {};
|
|
257
|
+
if (name) data.name = name;
|
|
258
|
+
if (description) data.description = description;
|
|
259
|
+
if (userIds) data.user_id_list = userIds;
|
|
260
|
+
if (botIds) data.bot_id_list = botIds;
|
|
261
|
+
const res = await this._safeSDKCall(
|
|
262
|
+
() => this.client.im.chat.create({ params: { user_id_type: 'open_id' }, data }),
|
|
263
|
+
'createChat'
|
|
264
|
+
);
|
|
265
|
+
return { chatId: res.data.chat_id };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async updateChat(chatId, { name, description } = {}) {
|
|
269
|
+
const data = {};
|
|
270
|
+
if (name) data.name = name;
|
|
271
|
+
if (description) data.description = description;
|
|
272
|
+
const res = await this._safeSDKCall(
|
|
273
|
+
() => this.client.im.chat.update({ path: { chat_id: chatId }, data }),
|
|
274
|
+
'updateChat'
|
|
275
|
+
);
|
|
276
|
+
return { updated: true };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async listChatMembers(chatId, { pageSize = 50, pageToken } = {}) {
|
|
280
|
+
const res = await this._safeSDKCall(
|
|
281
|
+
() => this.client.im.chatMembers.get({
|
|
282
|
+
path: { chat_id: chatId },
|
|
283
|
+
params: { member_id_type: 'open_id', page_size: pageSize, page_token: pageToken },
|
|
284
|
+
}),
|
|
285
|
+
'listChatMembers'
|
|
286
|
+
);
|
|
287
|
+
return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async addChatMembers(chatId, userIds) {
|
|
291
|
+
const res = await this._safeSDKCall(
|
|
292
|
+
() => this.client.im.chatMembers.create({
|
|
293
|
+
path: { chat_id: chatId },
|
|
294
|
+
params: { member_id_type: 'open_id' },
|
|
295
|
+
data: { id_list: userIds },
|
|
296
|
+
}),
|
|
297
|
+
'addChatMembers'
|
|
298
|
+
);
|
|
299
|
+
return { invalidIds: res.data.invalid_id_list || [] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async removeChatMembers(chatId, userIds) {
|
|
303
|
+
const res = await this._safeSDKCall(
|
|
304
|
+
() => this.client.im.chatMembers.delete({
|
|
305
|
+
path: { chat_id: chatId },
|
|
306
|
+
params: { member_id_type: 'open_id' },
|
|
307
|
+
data: { id_list: userIds },
|
|
308
|
+
}),
|
|
309
|
+
'removeChatMembers'
|
|
310
|
+
);
|
|
311
|
+
return { invalidIds: res.data.invalid_id_list || [] };
|
|
312
|
+
}
|
|
313
|
+
|
|
180
314
|
// --- Upload ---
|
|
181
315
|
|
|
182
316
|
async uploadImage(imagePath, imageType = 'message') {
|
|
@@ -250,6 +384,41 @@ class LarkOfficialClient {
|
|
|
250
384
|
return { items: res.data.items || [] };
|
|
251
385
|
}
|
|
252
386
|
|
|
387
|
+
async createDocBlock(documentId, parentBlockId, children, index) {
|
|
388
|
+
const data = { children };
|
|
389
|
+
if (index !== undefined) data.index = index;
|
|
390
|
+
const res = await this._safeSDKCall(
|
|
391
|
+
() => this.client.docx.documentBlockChildren.create({
|
|
392
|
+
path: { document_id: documentId, block_id: parentBlockId },
|
|
393
|
+
data,
|
|
394
|
+
}),
|
|
395
|
+
'createDocBlock'
|
|
396
|
+
);
|
|
397
|
+
return { blocks: res.data.children || [] };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async updateDocBlock(documentId, blockId, updateBody) {
|
|
401
|
+
const res = await this._safeSDKCall(
|
|
402
|
+
() => this.client.docx.documentBlock.patch({
|
|
403
|
+
path: { document_id: documentId, block_id: blockId },
|
|
404
|
+
data: updateBody,
|
|
405
|
+
}),
|
|
406
|
+
'updateDocBlock'
|
|
407
|
+
);
|
|
408
|
+
return { block: res.data.block };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async deleteDocBlocks(documentId, parentBlockId, startIndex, endIndex) {
|
|
412
|
+
const res = await this._safeSDKCall(
|
|
413
|
+
() => this.client.docx.documentBlockChildren.batchDelete({
|
|
414
|
+
path: { document_id: documentId, block_id: parentBlockId },
|
|
415
|
+
data: { start_index: startIndex, end_index: endIndex },
|
|
416
|
+
}),
|
|
417
|
+
'deleteDocBlocks'
|
|
418
|
+
);
|
|
419
|
+
return { deleted: true };
|
|
420
|
+
}
|
|
421
|
+
|
|
253
422
|
// --- Chat Info (Official API) ---
|
|
254
423
|
|
|
255
424
|
async getChatInfo(chatId) {
|
|
@@ -387,6 +556,22 @@ class LarkOfficialClient {
|
|
|
387
556
|
return { items: res.data.items || [] };
|
|
388
557
|
}
|
|
389
558
|
|
|
559
|
+
async getBitableRecord(appToken, tableId, recordId) {
|
|
560
|
+
const res = await this._safeSDKCall(
|
|
561
|
+
() => this.client.bitable.appTableRecord.get({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
|
|
562
|
+
'getRecord'
|
|
563
|
+
);
|
|
564
|
+
return { record: res.data.record };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async deleteBitableTable(appToken, tableId) {
|
|
568
|
+
await this._safeSDKCall(
|
|
569
|
+
() => this.client.bitable.appTable.delete({ path: { app_token: appToken, table_id: tableId } }),
|
|
570
|
+
'deleteTable'
|
|
571
|
+
);
|
|
572
|
+
return { deleted: true };
|
|
573
|
+
}
|
|
574
|
+
|
|
390
575
|
// --- Wiki ---
|
|
391
576
|
|
|
392
577
|
async listWikiSpaces() {
|
|
@@ -435,6 +620,34 @@ class LarkOfficialClient {
|
|
|
435
620
|
return { token: res.data.token };
|
|
436
621
|
}
|
|
437
622
|
|
|
623
|
+
// --- Drive: File Operations ---
|
|
624
|
+
|
|
625
|
+
async copyFile(fileToken, name, folderToken, type) {
|
|
626
|
+
const data = { name, folder_token: folderToken || '' };
|
|
627
|
+
if (type) data.type = type;
|
|
628
|
+
const res = await this._safeSDKCall(
|
|
629
|
+
() => this.client.drive.file.copy({ path: { file_token: fileToken }, data }),
|
|
630
|
+
'copyFile'
|
|
631
|
+
);
|
|
632
|
+
return { file: res.data.file };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async moveFile(fileToken, folderToken) {
|
|
636
|
+
const res = await this._safeSDKCall(
|
|
637
|
+
() => this.client.drive.file.move({ path: { file_token: fileToken }, data: { folder_token: folderToken || '' } }),
|
|
638
|
+
'moveFile'
|
|
639
|
+
);
|
|
640
|
+
return { taskId: res.data.task_id };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async deleteFile(fileToken, type) {
|
|
644
|
+
const res = await this._safeSDKCall(
|
|
645
|
+
() => this.client.drive.file.delete({ path: { file_token: fileToken }, params: { type: type || 'file' } }),
|
|
646
|
+
'deleteFile'
|
|
647
|
+
);
|
|
648
|
+
return { taskId: res.data.task_id };
|
|
649
|
+
}
|
|
650
|
+
|
|
438
651
|
// --- Contact ---
|
|
439
652
|
|
|
440
653
|
async findUserByIdentity({ emails, mobiles } = {}) {
|
|
@@ -466,6 +679,111 @@ class LarkOfficialClient {
|
|
|
466
679
|
return allChats;
|
|
467
680
|
}
|
|
468
681
|
|
|
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
|
+
}
|
|
759
|
+
|
|
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 };
|
|
766
|
+
}
|
|
767
|
+
|
|
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 };
|
|
777
|
+
}
|
|
778
|
+
|
|
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 };
|
|
785
|
+
}
|
|
786
|
+
|
|
469
787
|
// --- Safe SDK Call (extracts real Feishu error from AxiosError) ---
|
|
470
788
|
|
|
471
789
|
async _safeSDKCall(fn, label = 'API') {
|