@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,90 @@
1
+ import { createFeishuClient } from "../client.js";
2
+ import {
3
+ errorResult,
4
+ json,
5
+ runFeishuApiCall,
6
+ type FeishuApiResponse,
7
+ } from "../tools-common/feishu-api.js";
8
+
9
+ export type BitableClient = ReturnType<typeof createFeishuClient>;
10
+
11
+ // SDK-derived payload types keep tool input strongly typed and aligned with upstream API changes.
12
+ type AppTableFieldCreatePayload = NonNullable<
13
+ Parameters<BitableClient["bitable"]["appTableField"]["create"]>[0]
14
+ >;
15
+ type AppTableFieldUpdatePayload = NonNullable<
16
+ Parameters<BitableClient["bitable"]["appTableField"]["update"]>[0]
17
+ >;
18
+
19
+ export type BitableFieldCreateData = AppTableFieldCreatePayload["data"];
20
+ export type BitableFieldUpdateData = AppTableFieldUpdatePayload["data"];
21
+ export type BitableFieldDescription = NonNullable<BitableFieldCreateData["description"]>;
22
+
23
+ export { json, errorResult };
24
+
25
+ const RETRYABLE_BITABLE_ERROR_CODES = new Set<number>([
26
+ 1254607, // Data not ready
27
+ 1255040, // Request timeout
28
+ 1254290, // Too many requests
29
+ 1254291, // Write conflict
30
+ ]);
31
+
32
+ const RETRY_BACKOFF_MS = [350, 900, 1800];
33
+
34
+ export async function runBitableApiCall<T extends FeishuApiResponse>(
35
+ context: string,
36
+ fn: () => Promise<T>,
37
+ ): Promise<T> {
38
+ // Bitable APIs can briefly return "Data not ready" after writes.
39
+ // Retry only known transient codes with short backoff.
40
+ return runFeishuApiCall(context, fn, {
41
+ retryableCodes: RETRYABLE_BITABLE_ERROR_CODES,
42
+ backoffMs: RETRY_BACKOFF_MS,
43
+ });
44
+ }
45
+
46
+ const FIELD_TYPE_NAMES: Record<number, string> = {
47
+ 1: "Text",
48
+ 2: "Number",
49
+ 3: "SingleSelect",
50
+ 4: "MultiSelect",
51
+ 5: "DateTime",
52
+ 7: "Checkbox",
53
+ 11: "User",
54
+ 13: "Phone",
55
+ 15: "URL",
56
+ 17: "Attachment",
57
+ 18: "SingleLink",
58
+ 19: "Lookup",
59
+ 20: "Formula",
60
+ 21: "DuplexLink",
61
+ 22: "Location",
62
+ 23: "GroupChat",
63
+ 1001: "CreatedTime",
64
+ 1002: "ModifiedTime",
65
+ 1003: "CreatedUser",
66
+ 1004: "ModifiedUser",
67
+ 1005: "AutoNumber",
68
+ };
69
+
70
+ export function formatField(field: {
71
+ field_id?: string;
72
+ field_name?: string;
73
+ type?: number;
74
+ is_primary?: boolean;
75
+ property?: unknown;
76
+ ui_type?: string;
77
+ is_hidden?: boolean;
78
+ }) {
79
+ const typeName = field.type != null ? FIELD_TYPE_NAMES[field.type] || `type_${field.type}` : undefined;
80
+ return {
81
+ field_id: field.field_id,
82
+ field_name: field.field_name,
83
+ type: field.type,
84
+ ...(typeName && { type_name: typeName }),
85
+ is_primary: field.is_primary,
86
+ ui_type: field.ui_type,
87
+ is_hidden: field.is_hidden,
88
+ ...(field.property && { property: field.property }),
89
+ };
90
+ }
@@ -0,0 +1 @@
1
+ export { registerFeishuBitableTools } from "./register.js";
@@ -0,0 +1,80 @@
1
+ import { runBitableApiCall, type BitableClient } from "./common.js";
2
+
3
+ // Accept both native bitable URLs and wiki-embedded bitable URLs.
4
+ function parseBitableUrl(url: string): { token: string; tableId?: string; isWiki: boolean } | null {
5
+ try {
6
+ const u = new URL(url);
7
+ const tableId = u.searchParams.get("table") ?? undefined;
8
+
9
+ const wikiMatch = u.pathname.match(/\/wiki\/([A-Za-z0-9]+)/);
10
+ if (wikiMatch) {
11
+ return { token: wikiMatch[1], tableId, isWiki: true };
12
+ }
13
+
14
+ const baseMatch = u.pathname.match(/\/base\/([A-Za-z0-9]+)/);
15
+ if (baseMatch) {
16
+ return { token: baseMatch[1], tableId, isWiki: false };
17
+ }
18
+
19
+ return null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ async function getAppTokenFromWiki(client: BitableClient, nodeToken: string): Promise<string> {
26
+ // Wiki links expose a wiki node token; bitable APIs require app_token.
27
+ const res = await runBitableApiCall("wiki.space.getNode", () =>
28
+ client.wiki.space.getNode({
29
+ params: { token: nodeToken },
30
+ }),
31
+ );
32
+
33
+ const node = res.data?.node;
34
+ if (!node) throw new Error("Node not found");
35
+ if (node.obj_type !== "bitable") {
36
+ throw new Error(`Node is not a bitable (type: ${node.obj_type})`);
37
+ }
38
+
39
+ return node.obj_token!;
40
+ }
41
+
42
+ export async function getBitableMeta(client: BitableClient, url: string) {
43
+ const parsed = parseBitableUrl(url);
44
+ if (!parsed) {
45
+ throw new Error("Invalid URL format. Expected /base/XXX or /wiki/XXX URL");
46
+ }
47
+
48
+ const appToken = parsed.isWiki ? await getAppTokenFromWiki(client, parsed.token) : parsed.token;
49
+
50
+ const res = await runBitableApiCall("bitable.app.get", () =>
51
+ client.bitable.app.get({
52
+ path: { app_token: appToken },
53
+ }),
54
+ );
55
+
56
+ let tables: { table_id: string; name: string }[] = [];
57
+ if (!parsed.tableId) {
58
+ // If table is not specified in URL, return available tables to guide the next call.
59
+ const tablesRes = await runBitableApiCall("bitable.appTable.list", () =>
60
+ client.bitable.appTable.list({
61
+ path: { app_token: appToken },
62
+ }),
63
+ );
64
+ tables = (tablesRes.data?.items ?? []).map((t) => ({
65
+ table_id: t.table_id!,
66
+ name: t.name!,
67
+ }));
68
+ }
69
+
70
+ return {
71
+ app_token: appToken,
72
+ table_id: parsed.tableId,
73
+ name: res.data?.app?.name,
74
+ url_type: parsed.isWiki ? "wiki" : "base",
75
+ ...(tables.length > 0 && { tables }),
76
+ hint: parsed.tableId
77
+ ? `Use app_token="${appToken}" and table_id="${parsed.tableId}" for other bitable tools`
78
+ : `Use app_token="${appToken}" for other bitable tools. Select a table_id from the tables list.`,
79
+ };
80
+ }
@@ -0,0 +1,195 @@
1
+ import type { TSchema } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "../tools-common/tool-exec.js";
4
+ import {
5
+ batchDeleteRecords,
6
+ createField,
7
+ createRecord,
8
+ deleteField,
9
+ deleteRecord,
10
+ getRecord,
11
+ listFields,
12
+ listRecords,
13
+ updateField,
14
+ updateRecord,
15
+ } from "./actions.js";
16
+ import { errorResult, json, type BitableClient } from "./common.js";
17
+ import { getBitableMeta } from "./meta.js";
18
+ import {
19
+ BatchDeleteRecordsSchema,
20
+ type BatchDeleteRecordsParams,
21
+ CreateFieldSchema,
22
+ type CreateFieldParams,
23
+ CreateRecordSchema,
24
+ type CreateRecordParams,
25
+ DeleteFieldSchema,
26
+ type DeleteFieldParams,
27
+ DeleteRecordSchema,
28
+ type DeleteRecordParams,
29
+ GetMetaSchema,
30
+ type GetMetaParams,
31
+ GetRecordSchema,
32
+ type GetRecordParams,
33
+ ListFieldsSchema,
34
+ type ListFieldsParams,
35
+ ListRecordsSchema,
36
+ type ListRecordsParams,
37
+ UpdateFieldSchema,
38
+ type UpdateFieldParams,
39
+ UpdateRecordSchema,
40
+ type UpdateRecordParams,
41
+ } from "./schemas.js";
42
+
43
+ type ToolSpec<P> = {
44
+ name: string;
45
+ label: string;
46
+ description: string;
47
+ parameters: TSchema;
48
+ run: (client: BitableClient, params: P) => Promise<unknown>;
49
+ };
50
+
51
+ // Shared registration wrapper keeps all bitable tools consistent:
52
+ // same response envelope and same error conversion path.
53
+ function registerBitableTool<P>(
54
+ api: OpenClawPluginApi,
55
+ spec: ToolSpec<P>,
56
+ ) {
57
+ api.registerTool(
58
+ {
59
+ name: spec.name,
60
+ label: spec.label,
61
+ description: spec.description,
62
+ parameters: spec.parameters,
63
+ async execute(_toolCallId, params) {
64
+ try {
65
+ return await withFeishuToolClient({
66
+ api,
67
+ toolName: spec.name,
68
+ run: async ({ client }) => json(await spec.run(client as BitableClient, params as P)),
69
+ });
70
+ } catch (err) {
71
+ return errorResult(err);
72
+ }
73
+ },
74
+ },
75
+ { name: spec.name },
76
+ );
77
+ }
78
+
79
+ export function registerFeishuBitableTools(api: OpenClawPluginApi) {
80
+ if (!api.config || !hasFeishuToolEnabledForAnyAccount(api.config)) {
81
+ api.logger.debug?.("feishu_bitable: Feishu credentials not configured, skipping bitable tools");
82
+ return;
83
+ }
84
+
85
+ // Bitable tools are globally registered once, but each execution resolves
86
+ // the effective account through withFeishuToolClient().
87
+ // Keep registration explicit and flat so each tool is easy to locate and modify.
88
+ registerBitableTool<GetMetaParams>(api, {
89
+ name: "feishu_bitable_get_meta",
90
+ label: "Feishu Bitable Get Meta",
91
+ description:
92
+ "Parse a Bitable URL and get app_token, table_id, and table list. Use this first when given a /wiki/ or /base/ URL.",
93
+ parameters: GetMetaSchema,
94
+ run: (client, { url }) => getBitableMeta(client, url),
95
+ });
96
+
97
+ registerBitableTool<ListFieldsParams>(api, {
98
+ name: "feishu_bitable_list_fields",
99
+ label: "Feishu Bitable List Fields",
100
+ description: "List all fields (columns) in a Bitable table with their types and properties",
101
+ parameters: ListFieldsSchema,
102
+ run: (client, { app_token, table_id }) => listFields(client, app_token, table_id),
103
+ });
104
+
105
+ registerBitableTool<ListRecordsParams>(api, {
106
+ name: "feishu_bitable_list_records",
107
+ label: "Feishu Bitable List Records",
108
+ description: "List records (rows) from a Bitable table with pagination support",
109
+ parameters: ListRecordsSchema,
110
+ run: (client, { app_token, table_id, page_size, page_token }) =>
111
+ listRecords(client, app_token, table_id, page_size, page_token),
112
+ });
113
+
114
+ registerBitableTool<CreateFieldParams>(api, {
115
+ name: "feishu_bitable_create_field",
116
+ label: "Feishu Bitable Create Field",
117
+ description: "Create a new field (column) in a Bitable table",
118
+ parameters: CreateFieldSchema,
119
+ run: (client, { app_token, table_id, field_name, type, property, description, ui_type }) =>
120
+ createField(client, app_token, table_id, {
121
+ field_name,
122
+ type,
123
+ property,
124
+ description,
125
+ ui_type,
126
+ }),
127
+ });
128
+
129
+ registerBitableTool<UpdateFieldParams>(api, {
130
+ name: "feishu_bitable_update_field",
131
+ label: "Feishu Bitable Update Field",
132
+ description: "Update an existing field (column) in a Bitable table",
133
+ parameters: UpdateFieldSchema,
134
+ run: (client, { app_token, table_id, field_id, field_name, type, property, description, ui_type }) =>
135
+ updateField(client, app_token, table_id, field_id, {
136
+ field_name,
137
+ type,
138
+ property,
139
+ description,
140
+ ui_type,
141
+ }),
142
+ });
143
+
144
+ registerBitableTool<DeleteFieldParams>(api, {
145
+ name: "feishu_bitable_delete_field",
146
+ label: "Feishu Bitable Delete Field",
147
+ description: "Delete a field (column) from a Bitable table",
148
+ parameters: DeleteFieldSchema,
149
+ run: (client, { app_token, table_id, field_id }) => deleteField(client, app_token, table_id, field_id),
150
+ });
151
+
152
+ registerBitableTool<GetRecordParams>(api, {
153
+ name: "feishu_bitable_get_record",
154
+ label: "Feishu Bitable Get Record",
155
+ description: "Get a single record by ID from a Bitable table",
156
+ parameters: GetRecordSchema,
157
+ run: (client, { app_token, table_id, record_id }) => getRecord(client, app_token, table_id, record_id),
158
+ });
159
+
160
+ registerBitableTool<CreateRecordParams>(api, {
161
+ name: "feishu_bitable_create_record",
162
+ label: "Feishu Bitable Create Record",
163
+ description: "Create a new record (row) in a Bitable table",
164
+ parameters: CreateRecordSchema,
165
+ run: (client, { app_token, table_id, fields }) => createRecord(client, app_token, table_id, fields),
166
+ });
167
+
168
+ registerBitableTool<UpdateRecordParams>(api, {
169
+ name: "feishu_bitable_update_record",
170
+ label: "Feishu Bitable Update Record",
171
+ description: "Update an existing record (row) in a Bitable table",
172
+ parameters: UpdateRecordSchema,
173
+ run: (client, { app_token, table_id, record_id, fields }) =>
174
+ updateRecord(client, app_token, table_id, record_id, fields),
175
+ });
176
+
177
+ registerBitableTool<DeleteRecordParams>(api, {
178
+ name: "feishu_bitable_delete_record",
179
+ label: "Feishu Bitable Delete Record",
180
+ description: "Delete a single record (row) from a Bitable table",
181
+ parameters: DeleteRecordSchema,
182
+ run: (client, { app_token, table_id, record_id }) => deleteRecord(client, app_token, table_id, record_id),
183
+ });
184
+
185
+ registerBitableTool<BatchDeleteRecordsParams>(api, {
186
+ name: "feishu_bitable_batch_delete_records",
187
+ label: "Feishu Bitable Batch Delete Records",
188
+ description: "Delete multiple records (rows) from a Bitable table in one request",
189
+ parameters: BatchDeleteRecordsSchema,
190
+ run: (client, { app_token, table_id, record_ids }) =>
191
+ batchDeleteRecords(client, app_token, table_id, record_ids),
192
+ });
193
+
194
+ api.logger.debug?.("feishu_bitable: Registered 11 bitable tools");
195
+ }
@@ -0,0 +1,221 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type {
3
+ BitableFieldCreateData,
4
+ BitableFieldDescription,
5
+ BitableFieldUpdateData,
6
+ } from "./common.js";
7
+
8
+ export type GetMetaParams = {
9
+ url: string;
10
+ };
11
+
12
+ export type ListFieldsParams = {
13
+ app_token: string;
14
+ table_id: string;
15
+ };
16
+
17
+ export type CreateFieldParams = {
18
+ app_token: string;
19
+ table_id: string;
20
+ field_name: BitableFieldCreateData["field_name"];
21
+ type: BitableFieldCreateData["type"];
22
+ property?: BitableFieldCreateData["property"];
23
+ description?: BitableFieldDescription;
24
+ ui_type?: BitableFieldCreateData["ui_type"];
25
+ };
26
+
27
+ export type UpdateFieldParams = {
28
+ app_token: string;
29
+ table_id: string;
30
+ field_id: string;
31
+ field_name: BitableFieldUpdateData["field_name"];
32
+ type: BitableFieldUpdateData["type"];
33
+ property?: BitableFieldUpdateData["property"];
34
+ description?: BitableFieldDescription;
35
+ ui_type?: BitableFieldUpdateData["ui_type"];
36
+ };
37
+
38
+ export type DeleteFieldParams = {
39
+ app_token: string;
40
+ table_id: string;
41
+ field_id: string;
42
+ };
43
+
44
+ export type ListRecordsParams = {
45
+ app_token: string;
46
+ table_id: string;
47
+ page_size?: number;
48
+ page_token?: string;
49
+ };
50
+
51
+ export type GetRecordParams = {
52
+ app_token: string;
53
+ table_id: string;
54
+ record_id: string;
55
+ };
56
+
57
+ export type CreateRecordParams = {
58
+ app_token: string;
59
+ table_id: string;
60
+ fields: Record<string, unknown>;
61
+ };
62
+
63
+ export type UpdateRecordParams = {
64
+ app_token: string;
65
+ table_id: string;
66
+ record_id: string;
67
+ fields: Record<string, unknown>;
68
+ };
69
+
70
+ export type DeleteRecordParams = {
71
+ app_token: string;
72
+ table_id: string;
73
+ record_id: string;
74
+ };
75
+
76
+ export type BatchDeleteRecordsParams = {
77
+ app_token: string;
78
+ table_id: string;
79
+ record_ids: string[];
80
+ };
81
+
82
+ export const GetMetaSchema = Type.Object({
83
+ url: Type.String({
84
+ description:
85
+ "Bitable URL. Supports both formats: /base/XXX?table=YYY or /wiki/XXX?table=YYY",
86
+ }),
87
+ });
88
+
89
+ export const ListFieldsSchema = Type.Object({
90
+ app_token: Type.String({
91
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
92
+ }),
93
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
94
+ });
95
+
96
+ export const CreateFieldSchema = Type.Object({
97
+ app_token: Type.String({
98
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
99
+ }),
100
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
101
+ field_name: Type.String({ description: "Field name" }),
102
+ type: Type.Number({
103
+ description: "Field type ID (e.g., 1=Text, 2=Number, 3=SingleSelect, 4=MultiSelect)",
104
+ }),
105
+ property: Type.Optional(
106
+ Type.Record(Type.String(), Type.Any(), {
107
+ description:
108
+ "Optional field property object, pass-through to Feishu API (e.g., select options/date format)",
109
+ }),
110
+ ),
111
+ description: Type.Optional(
112
+ Type.Object(
113
+ {
114
+ disable_sync: Type.Optional(Type.Boolean()),
115
+ text: Type.Optional(Type.String()),
116
+ },
117
+ { description: "Optional field description metadata" },
118
+ ),
119
+ ),
120
+ ui_type: Type.Optional(
121
+ Type.String({ description: "Optional UI type override (e.g., Text, Number, SingleSelect)" }),
122
+ ),
123
+ });
124
+
125
+ export const UpdateFieldSchema = Type.Object({
126
+ app_token: Type.String({
127
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
128
+ }),
129
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
130
+ field_id: Type.String({ description: "Field ID to update" }),
131
+ field_name: Type.String({ description: "Updated field name" }),
132
+ type: Type.Number({ description: "Updated field type ID" }),
133
+ property: Type.Optional(
134
+ Type.Record(Type.String(), Type.Any(), {
135
+ description: "Optional field property object, pass-through to Feishu API",
136
+ }),
137
+ ),
138
+ description: Type.Optional(
139
+ Type.Object(
140
+ {
141
+ disable_sync: Type.Optional(Type.Boolean()),
142
+ text: Type.Optional(Type.String()),
143
+ },
144
+ { description: "Optional field description metadata" },
145
+ ),
146
+ ),
147
+ ui_type: Type.Optional(Type.String({ description: "Optional UI type override" })),
148
+ });
149
+
150
+ export const DeleteFieldSchema = Type.Object({
151
+ app_token: Type.String({
152
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
153
+ }),
154
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
155
+ field_id: Type.String({ description: "Field ID to delete" }),
156
+ });
157
+
158
+ export const ListRecordsSchema = Type.Object({
159
+ app_token: Type.String({
160
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
161
+ }),
162
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
163
+ page_size: Type.Optional(
164
+ Type.Number({
165
+ description: "Number of records per page (1-500, default 100)",
166
+ minimum: 1,
167
+ maximum: 500,
168
+ }),
169
+ ),
170
+ page_token: Type.Optional(Type.String({ description: "Pagination token from previous response" })),
171
+ });
172
+
173
+ export const GetRecordSchema = Type.Object({
174
+ app_token: Type.String({
175
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
176
+ }),
177
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
178
+ record_id: Type.String({ description: "Record ID to retrieve" }),
179
+ });
180
+
181
+ export const CreateRecordSchema = Type.Object({
182
+ app_token: Type.String({
183
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
184
+ }),
185
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
186
+ fields: Type.Record(Type.String(), Type.Any(), {
187
+ description:
188
+ "Field values keyed by field name. Format by type: Text='string', Number=123, SingleSelect='Option', MultiSelect=['A','B'], DateTime=timestamp_ms, User=[{id:'ou_xxx'}], URL={text:'Display',link:'https://...'}",
189
+ }),
190
+ });
191
+
192
+ export const UpdateRecordSchema = Type.Object({
193
+ app_token: Type.String({
194
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
195
+ }),
196
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
197
+ record_id: Type.String({ description: "Record ID to update" }),
198
+ fields: Type.Record(Type.String(), Type.Any(), {
199
+ description: "Field values to update (same format as create_record)",
200
+ }),
201
+ });
202
+
203
+ export const DeleteRecordSchema = Type.Object({
204
+ app_token: Type.String({
205
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
206
+ }),
207
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
208
+ record_id: Type.String({ description: "Record ID to delete" }),
209
+ });
210
+
211
+ export const BatchDeleteRecordsSchema = Type.Object({
212
+ app_token: Type.String({
213
+ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
214
+ }),
215
+ table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
216
+ record_ids: Type.Array(Type.String(), {
217
+ description: "Record ID list to delete (max 500 per request)",
218
+ minItems: 1,
219
+ maxItems: 500,
220
+ }),
221
+ });