@m1heng-clawd/feishu 0.1.9 → 0.1.11

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 CHANGED
@@ -3,6 +3,8 @@
3
3
  Feishu/Lark (飞书) channel plugin for [OpenClaw](https://github.com/openclaw/openclaw).
4
4
 
5
5
  > **中文社区资料** - 配置教程、常见问题、使用技巧:[Wiki](https://github.com/m1heng/clawdbot-feishu/wiki)
6
+ >
7
+ > **Contributing / 贡献指南**: [CONTRIBUTING.md](./CONTRIBUTING.md)
6
8
 
7
9
  [English](#english) | [中文](#中文)
8
10
 
@@ -16,6 +18,19 @@ Feishu/Lark (飞书) channel plugin for [OpenClaw](https://github.com/openclaw/o
16
18
  openclaw plugins install @m1heng-clawd/feishu
17
19
  ```
18
20
 
21
+ > [!IMPORTANT]
22
+ > **Windows Troubleshooting (`spawn npm ENOENT`)**
23
+ >
24
+ > If `openclaw plugins install` fails, install manually:
25
+ >
26
+ > ```bash
27
+ > # 1. Download the package
28
+ > curl -O https://registry.npmjs.org/@m1heng-clawd/feishu/-/feishu-0.1.3.tgz
29
+ >
30
+ > # 2. Install from local file
31
+ > openclaw plugins install ./feishu-0.1.3.tgz
32
+ > ```
33
+
19
34
  ### Upgrade
20
35
 
21
36
  ```bash
@@ -61,16 +76,20 @@ openclaw plugins update feishu
61
76
  | `drive:drive:readonly` | `feishu_drive` | List folders, get file info |
62
77
  | `wiki:wiki:readonly` | `feishu_wiki` | List spaces, list nodes, get node info, search |
63
78
  | `bitable:app:readonly` | `feishu_bitable` | Read bitable records and fields |
79
+ | `task:task:read` | `feishu_task_get` | Get task details |
64
80
 
65
81
  **Read-write** (optional, for create/edit/delete operations):
66
82
 
67
83
  | Permission | Tool | Description |
68
84
  |------------|------|-------------|
69
85
  | `docx:document` | `feishu_doc` | Create/edit documents |
70
- | `docx:document.block:convert` | `feishu_doc` | Markdown to blocks conversion (required for write/append) |
86
+ | `docx:document.block:convert` | `feishu_doc` | Markdown to blocks conversion (required for write/append/create_and_write; also used by `feishu_drive.import_document`) |
71
87
  | `drive:drive` | `feishu_doc`, `feishu_drive` | Upload images to documents, create folders, move/delete files |
72
88
  | `wiki:wiki` | `feishu_wiki` | Create/move/rename wiki nodes |
73
- | `bitable:app` | `feishu_bitable` | Create/update bitable records |
89
+ | `bitable:app` | `feishu_bitable` | Create/update/delete bitable records and manage fields |
90
+ | `task:task:write` | `feishu_task_create`, `feishu_task_update`, `feishu_task_delete` | Create/update/delete tasks |
91
+
92
+ > Task scope names may vary slightly in Feishu console UI. If needed, search for Task-related permissions and grant read/write accordingly.
74
93
 
75
94
  #### Drive Access ⚠️
76
95
 
@@ -149,6 +168,8 @@ channels:
149
168
  connectionMode: "websocket"
150
169
  # DM policy: "pairing" | "open" | "allowlist"
151
170
  dmPolicy: "pairing"
171
+ # DM allowlist (open_id/user_id). Include "*" when dmPolicy="open"
172
+ allowFrom: []
152
173
  # Group policy: "open" | "allowlist" | "disabled"
153
174
  groupPolicy: "allowlist"
154
175
  # Require @mention in groups
@@ -159,6 +180,81 @@ channels:
159
180
  renderMode: "auto"
160
181
  ```
161
182
 
183
+ #### DM Policy & Access Control
184
+
185
+ `dmPolicy` controls who can interact with the bot in direct messages (DM).
186
+ In multi-account mode, this is resolved per account (`channels.feishu.accounts.<accountId>`).
187
+
188
+ | `dmPolicy` | Who can send DM | How to grant access to a user |
189
+ |------------|------------------|--------------------------------|
190
+ | `pairing` | Users in `allowFrom`, or users approved through pairing | User sends a DM and gets a pairing code; bot owner runs `openclaw pairing approve feishu <code>`. |
191
+ | `open` | Everyone | Set `allowFrom: ["*"]` so all users are treated as allowed. |
192
+ | `allowlist` | Only users in `allowFrom` | Add the user's `open_id`/`user_id` to `allowFrom`, then reload config. |
193
+
194
+ Notes:
195
+ - `allowFrom` accepts Feishu user IDs (`open_id` recommended, `user_id` also supported).
196
+ - If `dmPolicy: "open"`, use `allowFrom: ["*"]`. This is required by top-level schema validation and keeps access behavior explicit.
197
+ - `pairing` and `allowlist` can both pre-authorize users with `allowFrom`.
198
+
199
+ Pairing flow (owner approval):
200
+ 1. User sends any DM to the bot.
201
+ 2. Bot replies with a pairing code (for example `H9ZEHY8R`).
202
+ 3. Bot owner approves:
203
+
204
+ ```bash
205
+ openclaw pairing approve feishu H9ZEHY8R
206
+ ```
207
+
208
+ 4. The user is added to the allow store and can chat immediately.
209
+
210
+ Example: open to everyone
211
+
212
+ ```yaml
213
+ channels:
214
+ feishu:
215
+ dmPolicy: "open"
216
+ allowFrom: ["*"]
217
+ ```
218
+
219
+ Example: controlled rollout (pairing + pre-approved users)
220
+
221
+ ```yaml
222
+ channels:
223
+ feishu:
224
+ dmPolicy: "pairing"
225
+ allowFrom:
226
+ - "ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
227
+ ```
228
+
229
+ Example: strict allowlist
230
+
231
+ ```yaml
232
+ channels:
233
+ feishu:
234
+ dmPolicy: "allowlist"
235
+ allowFrom:
236
+ - "ou_alice"
237
+ - "ou_bob"
238
+ ```
239
+
240
+ Example: account-level isolation
241
+
242
+ ```yaml
243
+ channels:
244
+ feishu:
245
+ accounts:
246
+ lobster-1:
247
+ dmPolicy: "open"
248
+ allowFrom: ["*"]
249
+ lobster-5:
250
+ dmPolicy: "pairing"
251
+ ```
252
+
253
+ Top-level `channels.feishu.dmPolicy` / `channels.feishu.allowFrom` are fallback defaults for accounts that do not override them.
254
+
255
+ > `dmPolicy` only controls who can trigger the bot.
256
+ > To actually read/write docs or files, you still need: (1) correct Feishu app scopes, and (2) sharing the target resources (Drive/Wiki/Bitable) with the bot.
257
+
162
258
  #### Connection Mode
163
259
 
164
260
  Two connection modes are available for receiving events from Feishu:
@@ -257,10 +353,11 @@ session:
257
353
  - Pairing flow for DM approval
258
354
  - User and group directory lookup
259
355
  - **Card render mode**: Optional markdown rendering with syntax highlighting
260
- - **Document tools**: Read, create, and write Feishu documents with markdown (tables not supported due to API limitations)
356
+ - **Document tools**: Read, create, and write Feishu documents with markdown, including atomic `create_and_write` / `import_document` flows for reliable create+content write
261
357
  - **Wiki tools**: Navigate knowledge bases, list spaces, get node details, search, create/move/rename nodes
262
358
  - **Drive tools**: List folders, get file info, create folders, move/delete files
263
- - **Bitable tools**: Read/write bitable (多维表格) records, supports both `/base/` and `/wiki/` URLs
359
+ - **Bitable tools**: Manage bitable (多维表格) fields and records (read/create/update/delete), supports both `/base/` and `/wiki/` URLs
360
+ - **Task tools**: Create, get details, update, and delete tasks via Feishu Task v2 API
264
361
  - **@mention forwarding**: When you @mention someone in your message, the bot's reply will automatically @mention them too
265
362
  - **Permission error notification**: When the bot encounters a Feishu API permission error, it automatically notifies the user with the permission grant URL
266
363
  - **Dynamic agent creation**: Each DM user can have their own isolated agent instance with dedicated workspace (optional)
@@ -299,18 +396,6 @@ Send `/new` command in the chat.
299
396
 
300
397
  Feishu API has rate limits. Streaming updates can easily trigger throttling. We use complete-then-send approach for stability.
301
398
 
302
- #### Windows install error `spawn npm ENOENT`
303
-
304
- If `openclaw plugins install` fails, install manually:
305
-
306
- ```bash
307
- # 1. Download the package
308
- curl -O https://registry.npmjs.org/@m1heng-clawd/feishu/-/feishu-0.1.3.tgz
309
-
310
- # 2. Install from local file
311
- openclaw plugins install ./feishu-0.1.3.tgz
312
- ```
313
-
314
399
  #### Cannot find the bot in Feishu
315
400
 
316
401
  1. Ensure the app is published (at least to test version)
@@ -327,6 +412,19 @@ openclaw plugins install ./feishu-0.1.3.tgz
327
412
  openclaw plugins install @m1heng-clawd/feishu
328
413
  ```
329
414
 
415
+ > [!IMPORTANT]
416
+ > **Windows 排错(`spawn npm ENOENT`)**
417
+ >
418
+ > 如果 `openclaw plugins install` 失败,可以手动安装:
419
+ >
420
+ > ```bash
421
+ > # 1. 下载插件包
422
+ > curl -O https://registry.npmjs.org/@m1heng-clawd/feishu/-/feishu-0.1.3.tgz
423
+ >
424
+ > # 2. 从本地安装
425
+ > openclaw plugins install ./feishu-0.1.3.tgz
426
+ > ```
427
+
330
428
  ### 升级
331
429
 
332
430
  ```bash
@@ -372,16 +470,20 @@ openclaw plugins update feishu
372
470
  | `drive:drive:readonly` | `feishu_drive` | 列出文件夹、获取文件信息 |
373
471
  | `wiki:wiki:readonly` | `feishu_wiki` | 列出空间、列出节点、获取节点详情、搜索 |
374
472
  | `bitable:app:readonly` | `feishu_bitable` | 读取多维表格记录和字段 |
473
+ | `task:task:read` | `feishu_task_get` | 获取任务详情 |
375
474
 
376
475
  **读写权限**(可选,用于创建/编辑/删除操作):
377
476
 
378
477
  | 权限 | 工具 | 说明 |
379
478
  |------|------|------|
380
479
  | `docx:document` | `feishu_doc` | 创建/编辑文档 |
381
- | `docx:document.block:convert` | `feishu_doc` | Markdown 转 blocks(write/append 必需) |
480
+ | `docx:document.block:convert` | `feishu_doc` | Markdown 转 blocks(write/append/create_and_write 必需,`feishu_drive.import_document` 也会用到) |
382
481
  | `drive:drive` | `feishu_doc`, `feishu_drive` | 上传图片到文档、创建文件夹、移动/删除文件 |
383
482
  | `wiki:wiki` | `feishu_wiki` | 创建/移动/重命名知识库节点 |
384
- | `bitable:app` | `feishu_bitable` | 创建/更新多维表格记录 |
483
+ | `bitable:app` | `feishu_bitable` | 创建/更新/删除多维表格记录并管理字段 |
484
+ | `task:task:write` | `feishu_task_create`, `feishu_task_update`, `feishu_task_delete` | 创建/更新/删除任务 |
485
+
486
+ > 飞书控制台中任务权限的显示名称可能略有差异,必要时可按关键字 `task` 搜索并授予对应读写权限。
385
487
 
386
488
  #### 云空间访问权限 ⚠️
387
489
 
@@ -460,6 +562,8 @@ channels:
460
562
  connectionMode: "websocket"
461
563
  # 私聊策略: "pairing" | "open" | "allowlist"
462
564
  dmPolicy: "pairing"
565
+ # 私聊白名单(open_id/user_id);当 dmPolicy="open" 时请包含 "*"
566
+ allowFrom: []
463
567
  # 群聊策略: "open" | "allowlist" | "disabled"
464
568
  groupPolicy: "allowlist"
465
569
  # 群聊是否需要 @机器人
@@ -470,6 +574,81 @@ channels:
470
574
  renderMode: "auto"
471
575
  ```
472
576
 
577
+ #### 私聊策略(dmPolicy)与访问授权
578
+
579
+ `dmPolicy` 控制的是“谁可以在私聊里触发机器人”。
580
+ 在多账号模式下,它按账号生效(`channels.feishu.accounts.<accountId>`)。
581
+
582
+ | `dmPolicy` | 谁能私聊触发机器人 | 如何给用户开通 |
583
+ |------------|------------------|----------------|
584
+ | `pairing` | `allowFrom` 中的用户,或已通过配对审批的用户 | 用户先私聊机器人拿到配对码;管理员执行 `openclaw pairing approve feishu <code>`。 |
585
+ | `open` | 所有人 | 配置 `allowFrom: ["*"]`,表示全部放开。 |
586
+ | `allowlist` | 仅 `allowFrom` 中的用户 | 将用户 `open_id`/`user_id` 加入 `allowFrom`,然后重载配置。 |
587
+
588
+ 说明:
589
+ - `allowFrom` 支持飞书用户 ID(推荐 `open_id`,也支持 `user_id`)。
590
+ - 当 `dmPolicy: "open"` 时,建议固定写 `allowFrom: ["*"]`,语义最清晰,也满足顶层配置校验要求。
591
+ - `pairing` 和 `allowlist` 都可以先通过 `allowFrom` 预授权部分用户。
592
+
593
+ 配对审批流程(pairing):
594
+ 1. 用户先给机器人发一条私聊消息。
595
+ 2. 机器人返回配对码(例如 `H9ZEHY8R`)。
596
+ 3. 管理员执行审批命令:
597
+
598
+ ```bash
599
+ openclaw pairing approve feishu H9ZEHY8R
600
+ ```
601
+
602
+ 4. 审批后该用户立即可用。
603
+
604
+ 示例:全部放开
605
+
606
+ ```yaml
607
+ channels:
608
+ feishu:
609
+ dmPolicy: "open"
610
+ allowFrom: ["*"]
611
+ ```
612
+
613
+ 示例:灰度放开(pairing + 预授权)
614
+
615
+ ```yaml
616
+ channels:
617
+ feishu:
618
+ dmPolicy: "pairing"
619
+ allowFrom:
620
+ - "ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
621
+ ```
622
+
623
+ 示例:严格白名单
624
+
625
+ ```yaml
626
+ channels:
627
+ feishu:
628
+ dmPolicy: "allowlist"
629
+ allowFrom:
630
+ - "ou_alice"
631
+ - "ou_bob"
632
+ ```
633
+
634
+ 示例:按账号隔离配置
635
+
636
+ ```yaml
637
+ channels:
638
+ feishu:
639
+ accounts:
640
+ lobster-1:
641
+ dmPolicy: "open"
642
+ allowFrom: ["*"]
643
+ lobster-5:
644
+ dmPolicy: "pairing"
645
+ ```
646
+
647
+ `channels.feishu.dmPolicy` / `channels.feishu.allowFrom` 是“默认值”;账号下未覆盖时才会继承。
648
+
649
+ > `dmPolicy` 只控制“是否允许触发机器人”。
650
+ > 真正执行文档/云盘/知识库/多维表格操作,还需要两层权限:1)应用 API 权限(scopes);2)把目标资源分享给机器人。
651
+
473
652
  #### 连接模式
474
653
 
475
654
  支持两种从飞书接收事件的连接模式:
@@ -571,7 +750,8 @@ session:
571
750
  - **文档工具**:读取、创建、用 Markdown 写入飞书文档(表格因 API 限制不支持)
572
751
  - **知识库工具**:浏览知识库、列出空间、获取节点详情、搜索、创建/移动/重命名节点
573
752
  - **云空间工具**:列出文件夹、获取文件信息、创建文件夹、移动/删除文件
574
- - **多维表格工具**:读写多维表格记录,支持 `/base/` 和 `/wiki/` 两种链接格式
753
+ - **多维表格工具**:支持多维表格字段与记录的读取/创建/更新/删除,支持 `/base/` 和 `/wiki/` 两种链接格式
754
+ - **任务工具**:基于 Task v2 API 支持任务创建、获取详情、更新和删除
575
755
  - **@ 转发功能**:在消息中 @ 某人,机器人的回复会自动 @ 该用户
576
756
  - **权限错误提示**:当机器人遇到飞书 API 权限错误时,会自动通知用户并提供权限授权链接
577
757
  - **动态 Agent 创建**:每个私聊用户可拥有独立的 agent 实例和专属 workspace(可选)
@@ -610,18 +790,6 @@ session:
610
790
 
611
791
  飞书 API 有请求频率限制,流式更新消息很容易触发限流。当前采用完整回复后一次性发送的方式,以保证稳定性。
612
792
 
613
- #### Windows 安装报错 `spawn npm ENOENT`
614
-
615
- 如果 `openclaw plugins install` 失败,可以手动安装:
616
-
617
- ```bash
618
- # 1. 下载插件包
619
- curl -O https://registry.npmjs.org/@m1heng-clawd/feishu/-/feishu-0.1.3.tgz
620
-
621
- # 2. 从本地安装
622
- openclaw plugins install ./feishu-0.1.3.tgz
623
- ```
624
-
625
793
  #### 在飞书里找不到机器人
626
794
 
627
795
  1. 确保应用已发布(至少发布到测试版本)
package/index.ts CHANGED
@@ -7,6 +7,7 @@ import { registerFeishuWikiTools } from "./src/wiki.js";
7
7
  import { registerFeishuDriveTools } from "./src/drive.js";
8
8
  import { registerFeishuPermTools } from "./src/perm.js";
9
9
  import { registerFeishuBitableTools } from "./src/bitable.js";
10
+ import { registerFeishuTaskTools } from "./src/task.js";
10
11
 
11
12
  export { monitorFeishuProvider } from "./src/monitor.js";
12
13
  export {
@@ -23,7 +24,7 @@ export {
23
24
  sendFileFeishu,
24
25
  sendMediaFeishu,
25
26
  } from "./src/media.js";
26
- export { probeFeishu } from "./src/probe.js";
27
+ export { probeFeishu, clearProbeCache } from "./src/probe.js";
27
28
  export {
28
29
  addReactionFeishu,
29
30
  removeReactionFeishu,
@@ -57,6 +58,7 @@ const plugin = {
57
58
  registerFeishuDriveTools(api);
58
59
  registerFeishuPermTools(api);
59
60
  registerFeishuBitableTools(api);
61
+ registerFeishuTaskTools(api);
60
62
  },
61
63
  };
62
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m1heng-clawd/feishu",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
@@ -46,17 +46,17 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@larksuiteoapi/node-sdk": "^1.58.0",
49
+ "@larksuiteoapi/node-sdk": "^1.59.0",
50
50
  "@sinclair/typebox": "0.34.48",
51
51
  "zod": "^4.3.6"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^25.0.10",
55
- "openclaw": "2026.1.29",
55
+ "openclaw": "2026.2.13",
56
56
  "tsx": "^4.21.0",
57
57
  "typescript": "^5.7.0"
58
58
  },
59
59
  "peerDependencies": {
60
- "openclaw": ">=2026.1.29"
60
+ "openclaw": ">=2026.2.13"
61
61
  }
62
62
  }
@@ -32,6 +32,28 @@ Replaces entire document with markdown content. Supports: headings, lists, code
32
32
 
33
33
  **Limitation:** Markdown tables are NOT supported.
34
34
 
35
+ ### Create + Write (Atomic, Recommended)
36
+
37
+ ```json
38
+ {
39
+ "action": "create_and_write",
40
+ "title": "New Document",
41
+ "content": "# Title\n\nMarkdown content..."
42
+ }
43
+ ```
44
+
45
+ With folder:
46
+ ```json
47
+ {
48
+ "action": "create_and_write",
49
+ "title": "New Document",
50
+ "content": "# Title\n\nMarkdown content...",
51
+ "folder_token": "fldcnXXX"
52
+ }
53
+ ```
54
+
55
+ Creates the document and writes content in one call. Prefer this over separate `create` + `write`.
56
+
35
57
  ### Append Content
36
58
 
37
59
  ```json
@@ -51,6 +73,8 @@ With folder:
51
73
  { "action": "create", "title": "New Document", "folder_token": "fldcnXXX" }
52
74
  ```
53
75
 
76
+ Creates an empty document (title only).
77
+
54
78
  ### List Blocks
55
79
 
56
80
  ```json
@@ -0,0 +1,199 @@
1
+ import type {
2
+ BitableClient,
3
+ BitableFieldCreateData,
4
+ BitableFieldUpdateData,
5
+ } from "./common.js";
6
+ import { formatField, runBitableApiCall } from "./common.js";
7
+
8
+ // -------- Field operations --------
9
+
10
+ export async function listFields(client: BitableClient, appToken: string, tableId: string) {
11
+ const res = await runBitableApiCall("bitable.appTableField.list", () =>
12
+ client.bitable.appTableField.list({
13
+ path: { app_token: appToken, table_id: tableId },
14
+ }),
15
+ );
16
+
17
+ const fields = res.data?.items ?? [];
18
+ return {
19
+ fields: fields.map((f) => formatField(f)),
20
+ total: fields.length,
21
+ };
22
+ }
23
+
24
+ export async function createField(
25
+ client: BitableClient,
26
+ appToken: string,
27
+ tableId: string,
28
+ field: BitableFieldCreateData,
29
+ ) {
30
+ const res = await runBitableApiCall("bitable.appTableField.create", () =>
31
+ client.bitable.appTableField.create({
32
+ path: { app_token: appToken, table_id: tableId },
33
+ data: field,
34
+ }),
35
+ );
36
+
37
+ return {
38
+ field: res.data?.field ? formatField(res.data.field) : undefined,
39
+ };
40
+ }
41
+
42
+ export async function updateField(
43
+ client: BitableClient,
44
+ appToken: string,
45
+ tableId: string,
46
+ fieldId: string,
47
+ field: BitableFieldUpdateData,
48
+ ) {
49
+ const res = await runBitableApiCall("bitable.appTableField.update", () =>
50
+ client.bitable.appTableField.update({
51
+ path: { app_token: appToken, table_id: tableId, field_id: fieldId },
52
+ data: field,
53
+ }),
54
+ );
55
+
56
+ return {
57
+ field: res.data?.field ? formatField(res.data.field) : undefined,
58
+ };
59
+ }
60
+
61
+ export async function deleteField(
62
+ client: BitableClient,
63
+ appToken: string,
64
+ tableId: string,
65
+ fieldId: string,
66
+ ) {
67
+ const res = await runBitableApiCall("bitable.appTableField.delete", () =>
68
+ client.bitable.appTableField.delete({
69
+ path: { app_token: appToken, table_id: tableId, field_id: fieldId },
70
+ }),
71
+ );
72
+
73
+ return {
74
+ success: res.data?.deleted ?? true,
75
+ field_id: res.data?.field_id ?? fieldId,
76
+ deleted: res.data?.deleted ?? true,
77
+ };
78
+ }
79
+
80
+ // -------- Record operations --------
81
+
82
+ export async function listRecords(
83
+ client: BitableClient,
84
+ appToken: string,
85
+ tableId: string,
86
+ pageSize?: number,
87
+ pageToken?: string,
88
+ ) {
89
+ const res = await runBitableApiCall("bitable.appTableRecord.list", () =>
90
+ client.bitable.appTableRecord.list({
91
+ path: { app_token: appToken, table_id: tableId },
92
+ params: {
93
+ page_size: pageSize ?? 100,
94
+ ...(pageToken && { page_token: pageToken }),
95
+ },
96
+ }),
97
+ );
98
+
99
+ return {
100
+ records: res.data?.items ?? [],
101
+ has_more: res.data?.has_more ?? false,
102
+ page_token: res.data?.page_token,
103
+ total: res.data?.total,
104
+ };
105
+ }
106
+
107
+ export async function getRecord(
108
+ client: BitableClient,
109
+ appToken: string,
110
+ tableId: string,
111
+ recordId: string,
112
+ ) {
113
+ const res = await runBitableApiCall("bitable.appTableRecord.get", () =>
114
+ client.bitable.appTableRecord.get({
115
+ path: { app_token: appToken, table_id: tableId, record_id: recordId },
116
+ }),
117
+ );
118
+
119
+ return {
120
+ record: res.data?.record,
121
+ };
122
+ }
123
+
124
+ export async function createRecord(
125
+ client: BitableClient,
126
+ appToken: string,
127
+ tableId: string,
128
+ fields: Record<string, unknown>,
129
+ ) {
130
+ const res = await runBitableApiCall("bitable.appTableRecord.create", () =>
131
+ client.bitable.appTableRecord.create({
132
+ path: { app_token: appToken, table_id: tableId },
133
+ data: { fields },
134
+ }),
135
+ );
136
+
137
+ return {
138
+ record: res.data?.record,
139
+ };
140
+ }
141
+
142
+ export async function updateRecord(
143
+ client: BitableClient,
144
+ appToken: string,
145
+ tableId: string,
146
+ recordId: string,
147
+ fields: Record<string, unknown>,
148
+ ) {
149
+ const res = await runBitableApiCall("bitable.appTableRecord.update", () =>
150
+ client.bitable.appTableRecord.update({
151
+ path: { app_token: appToken, table_id: tableId, record_id: recordId },
152
+ data: { fields },
153
+ }),
154
+ );
155
+
156
+ return {
157
+ record: res.data?.record,
158
+ };
159
+ }
160
+
161
+ export async function deleteRecord(
162
+ client: BitableClient,
163
+ appToken: string,
164
+ tableId: string,
165
+ recordId: string,
166
+ ) {
167
+ const res = await runBitableApiCall("bitable.appTableRecord.delete", () =>
168
+ client.bitable.appTableRecord.delete({
169
+ path: { app_token: appToken, table_id: tableId, record_id: recordId },
170
+ }),
171
+ );
172
+
173
+ return {
174
+ success: res.data?.deleted ?? true,
175
+ record_id: res.data?.record_id ?? recordId,
176
+ deleted: res.data?.deleted ?? true,
177
+ };
178
+ }
179
+
180
+ export async function batchDeleteRecords(
181
+ client: BitableClient,
182
+ appToken: string,
183
+ tableId: string,
184
+ recordIds: string[],
185
+ ) {
186
+ const res = await runBitableApiCall("bitable.appTableRecord.batchDelete", () =>
187
+ client.bitable.appTableRecord.batchDelete({
188
+ path: { app_token: appToken, table_id: tableId },
189
+ data: { records: recordIds },
190
+ }),
191
+ );
192
+
193
+ const results = res.data?.records ?? [];
194
+ return {
195
+ results,
196
+ requested: recordIds.length,
197
+ deleted: results.filter((r) => r.deleted).length,
198
+ };
199
+ }