@bobotu/feishu-fork 0.1.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +922 -0
  3. package/index.ts +65 -0
  4. package/openclaw.plugin.json +10 -0
  5. package/package.json +72 -0
  6. package/skills/feishu-doc/SKILL.md +161 -0
  7. package/skills/feishu-doc/references/block-types.md +102 -0
  8. package/skills/feishu-drive/SKILL.md +96 -0
  9. package/skills/feishu-perm/SKILL.md +90 -0
  10. package/skills/feishu-task/SKILL.md +210 -0
  11. package/skills/feishu-wiki/SKILL.md +96 -0
  12. package/src/accounts.ts +140 -0
  13. package/src/bitable-tools/actions.ts +199 -0
  14. package/src/bitable-tools/common.ts +90 -0
  15. package/src/bitable-tools/index.ts +1 -0
  16. package/src/bitable-tools/meta.ts +80 -0
  17. package/src/bitable-tools/register.ts +195 -0
  18. package/src/bitable-tools/schemas.ts +221 -0
  19. package/src/bot.ts +1125 -0
  20. package/src/channel.ts +334 -0
  21. package/src/client.ts +114 -0
  22. package/src/config-schema.ts +237 -0
  23. package/src/dedup.ts +54 -0
  24. package/src/directory.ts +165 -0
  25. package/src/doc-tools/actions.ts +341 -0
  26. package/src/doc-tools/common.ts +33 -0
  27. package/src/doc-tools/index.ts +2 -0
  28. package/src/doc-tools/register.ts +90 -0
  29. package/src/doc-tools/schemas.ts +85 -0
  30. package/src/doc-write-service.ts +711 -0
  31. package/src/drive-tools/actions.ts +182 -0
  32. package/src/drive-tools/common.ts +18 -0
  33. package/src/drive-tools/index.ts +2 -0
  34. package/src/drive-tools/register.ts +71 -0
  35. package/src/drive-tools/schemas.ts +67 -0
  36. package/src/dynamic-agent.ts +135 -0
  37. package/src/external-keys.ts +19 -0
  38. package/src/media.ts +510 -0
  39. package/src/mention.ts +121 -0
  40. package/src/monitor.ts +323 -0
  41. package/src/onboarding.ts +449 -0
  42. package/src/outbound.ts +40 -0
  43. package/src/perm-tools/actions.ts +111 -0
  44. package/src/perm-tools/common.ts +18 -0
  45. package/src/perm-tools/index.ts +2 -0
  46. package/src/perm-tools/register.ts +65 -0
  47. package/src/perm-tools/schemas.ts +52 -0
  48. package/src/policy.ts +117 -0
  49. package/src/probe.ts +147 -0
  50. package/src/reactions.ts +160 -0
  51. package/src/reply-dispatcher.ts +240 -0
  52. package/src/runtime.ts +14 -0
  53. package/src/send.ts +391 -0
  54. package/src/streaming-card.ts +211 -0
  55. package/src/targets.ts +58 -0
  56. package/src/task-tools/actions.ts +590 -0
  57. package/src/task-tools/common.ts +18 -0
  58. package/src/task-tools/constants.ts +13 -0
  59. package/src/task-tools/index.ts +1 -0
  60. package/src/task-tools/register.ts +263 -0
  61. package/src/task-tools/schemas.ts +567 -0
  62. package/src/text/markdown-links.ts +104 -0
  63. package/src/tools-common/feishu-api.ts +184 -0
  64. package/src/tools-common/tool-context.ts +23 -0
  65. package/src/tools-common/tool-exec.ts +73 -0
  66. package/src/tools-config.ts +22 -0
  67. package/src/types.ts +79 -0
  68. package/src/typing.ts +75 -0
  69. package/src/wiki-tools/actions.ts +166 -0
  70. package/src/wiki-tools/common.ts +18 -0
  71. package/src/wiki-tools/index.ts +2 -0
  72. package/src/wiki-tools/register.ts +66 -0
  73. package/src/wiki-tools/schemas.ts +55 -0
@@ -0,0 +1,210 @@
1
+ ---
2
+ name: feishu-task
3
+ description: |
4
+ Feishu Task, tasklist, subtask, comment, and attachment management. Activate when user mentions tasks, tasklists, subtasks, task comments, task attachments, or task links.
5
+ ---
6
+
7
+ # Feishu Task Tools
8
+
9
+ Tools:
10
+ - `feishu_task_create`
11
+ - `feishu_task_subtask_create`
12
+ - `feishu_task_get`
13
+ - `feishu_task_update`
14
+ - `feishu_task_delete`
15
+ - `feishu_task_comment_create`
16
+ - `feishu_task_comment_list`
17
+ - `feishu_task_comment_get`
18
+ - `feishu_task_comment_update`
19
+ - `feishu_task_comment_delete`
20
+ - `feishu_task_attachment_upload`
21
+ - `feishu_task_attachment_list`
22
+ - `feishu_task_attachment_get`
23
+ - `feishu_task_attachment_delete`
24
+ - `feishu_task_add_tasklist`
25
+ - `feishu_task_remove_tasklist`
26
+ - `feishu_tasklist_create`
27
+ - `feishu_tasklist_get`
28
+ - `feishu_tasklist_list`
29
+ - `feishu_tasklist_update`
30
+ - `feishu_tasklist_delete`
31
+ - `feishu_tasklist_add_members`
32
+ - `feishu_tasklist_remove_members`
33
+
34
+ ## Notes
35
+
36
+ - `task_guid` can be taken from a task URL (guid query param) or from `feishu_task_get` output.
37
+ - `comment_id` can be obtained from `feishu_task_comment_list` output.
38
+ - `attachment_guid` can be obtained from `feishu_task_attachment_list` output.
39
+ - `user_id_type` controls returned/accepted user identity type (`open_id`, `user_id`, `union_id`).
40
+ - If no assignee is specified, set the assignee to the requesting user. Avoid creating unassigned tasks because the user may not be able to view them.
41
+ - Task visibility: users can only view tasks when they are included as assignee.
42
+ - Current limitation: the bot can only create subtasks for tasks created by itself.
43
+ - Attachment upload supports local `file_path` and remote `file_url`. Remote URLs are fetched with runtime media safety checks and size limit (`mediaMaxMb`).
44
+ - Keep tasklist owner as the bot. Add users as members to avoid losing bot access.
45
+ - Use tasklist tools for tasklist membership changes; do not use `feishu_task_update` to move tasks between tasklists.
46
+
47
+ ## Create Task
48
+
49
+ ```json
50
+ {
51
+ "summary": "Quarterly review",
52
+ "description": "Prepare review notes",
53
+ "due": { "timestamp": "1735689600000", "is_all_day": true },
54
+ "members": [
55
+ { "id": "ou_xxx", "role": "assignee", "type": "user" }
56
+ ],
57
+ "user_id_type": "open_id"
58
+ }
59
+ ```
60
+
61
+ ## Create Subtask
62
+
63
+ ```json
64
+ {
65
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
66
+ "summary": "Draft report outline",
67
+ "description": "Collect key metrics",
68
+ "due": { "timestamp": "1735689600000", "is_all_day": true },
69
+ "members": [
70
+ { "id": "ou_xxx", "role": "assignee", "type": "user" }
71
+ ],
72
+ "user_id_type": "open_id"
73
+ }
74
+ ```
75
+
76
+ ## Create Comment
77
+
78
+ ```json
79
+ {
80
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
81
+ "content": "Looks good to me",
82
+ "user_id_type": "open_id"
83
+ }
84
+ ```
85
+
86
+ ## Upload Attachment (file_path)
87
+
88
+ ```json
89
+ {
90
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
91
+ "file_path": "/path/to/report.pdf",
92
+ "user_id_type": "open_id"
93
+ }
94
+ ```
95
+
96
+ ## Upload Attachment (file_url)
97
+
98
+ ```json
99
+ {
100
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
101
+ "file_url": "https://oss-example.com/bucket/report.pdf",
102
+ "filename": "report.pdf",
103
+ "user_id_type": "open_id"
104
+ }
105
+ ```
106
+
107
+ ## Tasklist Membership For Tasks
108
+
109
+ ### Add Task to Tasklist
110
+
111
+ ```json
112
+ {
113
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
114
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
115
+ "section_guid": "6d0f9f48-2e06-4e3d-8a0f-acde196e8c61",
116
+ "user_id_type": "open_id"
117
+ }
118
+ ```
119
+
120
+ ### Remove Task from Tasklist
121
+
122
+ ```json
123
+ {
124
+ "task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
125
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
126
+ "user_id_type": "open_id"
127
+ }
128
+ ```
129
+
130
+ ## Tasklists
131
+
132
+ Tasklists support three roles: owner (read/edit/manage), editor (read/edit), viewer (read).
133
+
134
+ ### Create Tasklist
135
+
136
+ ```json
137
+ {
138
+ "name": "Project Alpha Tasklist",
139
+ "members": [
140
+ { "id": "ou_xxx", "type": "user", "role": "editor" }
141
+ ],
142
+ "user_id_type": "open_id"
143
+ }
144
+ ```
145
+
146
+ ### Get Tasklist
147
+
148
+ ```json
149
+ {
150
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
151
+ "user_id_type": "open_id"
152
+ }
153
+ ```
154
+
155
+ ### List Tasklists
156
+
157
+ ```json
158
+ {
159
+ "page_size": 50,
160
+ "page_token": "aWQ9NzEwMjMzMjMxMDE=",
161
+ "user_id_type": "open_id"
162
+ }
163
+ ```
164
+
165
+ ### Update Tasklist
166
+
167
+ ```json
168
+ {
169
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
170
+ "tasklist": {
171
+ "name": "Renamed Tasklist",
172
+ "owner": { "id": "ou_xxx", "type": "user", "role": "owner" }
173
+ },
174
+ "update_fields": ["name", "owner"],
175
+ "origin_owner_to_role": "editor",
176
+ "user_id_type": "open_id"
177
+ }
178
+ ```
179
+
180
+ ### Delete Tasklist
181
+
182
+ ```json
183
+ {
184
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004"
185
+ }
186
+ ```
187
+
188
+ ### Add Tasklist Members
189
+
190
+ ```json
191
+ {
192
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
193
+ "members": [
194
+ { "id": "ou_xxx", "type": "user", "role": "editor" }
195
+ ],
196
+ "user_id_type": "open_id"
197
+ }
198
+ ```
199
+
200
+ ### Remove Tasklist Members
201
+
202
+ ```json
203
+ {
204
+ "tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
205
+ "members": [
206
+ { "id": "ou_xxx", "type": "user", "role": "viewer" }
207
+ ],
208
+ "user_id_type": "open_id"
209
+ }
210
+ ```
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: feishu-wiki
3
+ description: |
4
+ Feishu knowledge base navigation. Activate when user mentions knowledge base, wiki, or wiki links.
5
+ ---
6
+
7
+ # Feishu Wiki Tool
8
+
9
+ Single tool `feishu_wiki` for knowledge base operations.
10
+
11
+ ## Token Extraction
12
+
13
+ From URL `https://xxx.feishu.cn/wiki/ABC123def` → `token` = `ABC123def`
14
+
15
+ ## Actions
16
+
17
+ ### List Knowledge Spaces
18
+
19
+ ```json
20
+ { "action": "spaces" }
21
+ ```
22
+
23
+ Returns all accessible wiki spaces.
24
+
25
+ ### List Nodes
26
+
27
+ ```json
28
+ { "action": "nodes", "space_id": "7xxx" }
29
+ ```
30
+
31
+ With parent:
32
+ ```json
33
+ { "action": "nodes", "space_id": "7xxx", "parent_node_token": "wikcnXXX" }
34
+ ```
35
+
36
+ ### Get Node Details
37
+
38
+ ```json
39
+ { "action": "get", "token": "ABC123def" }
40
+ ```
41
+
42
+ Returns: `node_token`, `obj_token`, `obj_type`, etc. Use `obj_token` with `feishu_doc` to read/write the document.
43
+
44
+ ### Create Node
45
+
46
+ ```json
47
+ { "action": "create", "space_id": "7xxx", "title": "New Page" }
48
+ ```
49
+
50
+ With type and parent:
51
+ ```json
52
+ { "action": "create", "space_id": "7xxx", "title": "Sheet", "obj_type": "sheet", "parent_node_token": "wikcnXXX" }
53
+ ```
54
+
55
+ `obj_type`: `docx` (default), `sheet`, `bitable`, `mindnote`, `file`, `doc`, `slides`
56
+
57
+ ### Move Node
58
+
59
+ ```json
60
+ { "action": "move", "space_id": "7xxx", "node_token": "wikcnXXX" }
61
+ ```
62
+
63
+ To different location:
64
+ ```json
65
+ { "action": "move", "space_id": "7xxx", "node_token": "wikcnXXX", "target_space_id": "7yyy", "target_parent_token": "wikcnYYY" }
66
+ ```
67
+
68
+ ### Rename Node
69
+
70
+ ```json
71
+ { "action": "rename", "space_id": "7xxx", "node_token": "wikcnXXX", "title": "New Title" }
72
+ ```
73
+
74
+ ## Wiki-Doc Workflow
75
+
76
+ To edit a wiki page:
77
+
78
+ 1. Get node: `{ "action": "get", "token": "wiki_token" }` → returns `obj_token`
79
+ 2. Read doc: `feishu_doc { "action": "read", "doc_token": "obj_token" }`
80
+ 3. Write doc: `feishu_doc { "action": "write", "doc_token": "obj_token", "content": "..." }`
81
+
82
+ ## Configuration
83
+
84
+ ```yaml
85
+ channels:
86
+ feishu:
87
+ tools:
88
+ wiki: true # default: true
89
+ doc: true # required - wiki content uses feishu_doc
90
+ ```
91
+
92
+ **Dependency:** This tool requires `feishu_doc` to be enabled. Wiki pages are documents - use `feishu_wiki` to navigate, then `feishu_doc` to read/edit content.
93
+
94
+ ## Permissions
95
+
96
+ Required: `wiki:wiki` or `wiki:wiki:readonly`
@@ -0,0 +1,140 @@
1
+ import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
3
+ import type { FeishuConfig, FeishuAccountConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js";
4
+
5
+ /**
6
+ * List all configured account IDs from the accounts field.
7
+ */
8
+ function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
9
+ const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
10
+ if (!accounts || typeof accounts !== "object") {
11
+ return [];
12
+ }
13
+ return Object.keys(accounts).filter(Boolean);
14
+ }
15
+
16
+ /**
17
+ * List all Feishu account IDs.
18
+ * If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
19
+ */
20
+ export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
21
+ const ids = listConfiguredAccountIds(cfg);
22
+ if (ids.length === 0) {
23
+ // Backward compatibility: no accounts configured, use default
24
+ return [DEFAULT_ACCOUNT_ID];
25
+ }
26
+ return [...ids].sort((a, b) => a.localeCompare(b));
27
+ }
28
+
29
+ /**
30
+ * Resolve the default account ID.
31
+ */
32
+ export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
33
+ const ids = listFeishuAccountIds(cfg);
34
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
35
+ return DEFAULT_ACCOUNT_ID;
36
+ }
37
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
38
+ }
39
+
40
+ /**
41
+ * Get the raw account-specific config.
42
+ */
43
+ function resolveAccountConfig(
44
+ cfg: ClawdbotConfig,
45
+ accountId: string,
46
+ ): FeishuAccountConfig | undefined {
47
+ const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
48
+ if (!accounts || typeof accounts !== "object") {
49
+ return undefined;
50
+ }
51
+ return accounts[accountId];
52
+ }
53
+
54
+ /**
55
+ * Merge top-level config with account-specific config.
56
+ * Account-specific fields override top-level fields.
57
+ */
58
+ function mergeFeishuAccountConfig(
59
+ cfg: ClawdbotConfig,
60
+ accountId: string,
61
+ ): FeishuConfig {
62
+ const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
63
+
64
+ // Extract base config (exclude accounts field to avoid recursion)
65
+ const { accounts: _ignored, ...base } = feishuCfg ?? {};
66
+
67
+ // Get account-specific overrides
68
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
69
+
70
+ // Merge: account config overrides base config
71
+ return { ...base, ...account } as FeishuConfig;
72
+ }
73
+
74
+ /**
75
+ * Resolve Feishu credentials from a config.
76
+ */
77
+ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
78
+ appId: string;
79
+ appSecret: string;
80
+ encryptKey?: string;
81
+ verificationToken?: string;
82
+ domain: FeishuDomain;
83
+ } | null {
84
+ const appId = cfg?.appId?.trim();
85
+ const appSecret = cfg?.appSecret?.trim();
86
+ if (!appId || !appSecret) return null;
87
+ return {
88
+ appId,
89
+ appSecret,
90
+ encryptKey: cfg?.encryptKey?.trim() || undefined,
91
+ verificationToken: cfg?.verificationToken?.trim() || undefined,
92
+ domain: cfg?.domain ?? "feishu",
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Resolve a complete Feishu account with merged config.
98
+ */
99
+ export function resolveFeishuAccount(params: {
100
+ cfg: ClawdbotConfig;
101
+ accountId?: string | null;
102
+ }): ResolvedFeishuAccount {
103
+ const accountId = normalizeAccountId(params.accountId);
104
+ const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
105
+
106
+ // Base enabled state (top-level)
107
+ const baseEnabled = feishuCfg?.enabled !== false;
108
+
109
+ // Merge configs
110
+ const merged = mergeFeishuAccountConfig(params.cfg, accountId);
111
+
112
+ // Account-level enabled state
113
+ const accountEnabled = merged.enabled !== false;
114
+ const enabled = baseEnabled && accountEnabled;
115
+
116
+ // Resolve credentials from merged config
117
+ const creds = resolveFeishuCredentials(merged);
118
+
119
+ return {
120
+ accountId,
121
+ enabled,
122
+ configured: Boolean(creds),
123
+ name: (merged as FeishuAccountConfig).name?.trim() || undefined,
124
+ appId: creds?.appId,
125
+ appSecret: creds?.appSecret,
126
+ encryptKey: creds?.encryptKey,
127
+ verificationToken: creds?.verificationToken,
128
+ domain: creds?.domain ?? "feishu",
129
+ config: merged,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * List all enabled and configured accounts.
135
+ */
136
+ export function listEnabledFeishuAccounts(cfg: ClawdbotConfig): ResolvedFeishuAccount[] {
137
+ return listFeishuAccountIds(cfg)
138
+ .map((accountId) => resolveFeishuAccount({ cfg, accountId }))
139
+ .filter((account) => account.enabled && account.configured);
140
+ }
@@ -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
+ }