@m1heng-clawd/feishu 0.1.4 → 0.1.5
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 +86 -18
- package/index.ts +6 -0
- package/package.json +1 -1
- package/src/bot.ts +132 -9
- package/src/config-schema.ts +20 -0
- package/src/doc-schema.ts +47 -0
- package/src/docx.ts +46 -287
- package/src/drive-schema.ts +46 -0
- package/src/drive.ts +201 -0
- package/src/perm-schema.ts +52 -0
- package/src/perm.ts +160 -0
- package/src/tools-config.ts +21 -0
- package/src/types.ts +8 -0
- package/src/wiki-schema.ts +55 -0
- package/src/wiki.ts +218 -0
package/README.md
CHANGED
|
@@ -49,17 +49,48 @@ npm install @m1heng-clawd/feishu
|
|
|
49
49
|
| `im:message:recall` | Recall | Recall sent messages |
|
|
50
50
|
| `im:message.reactions:read` | Reactions | View message reactions |
|
|
51
51
|
|
|
52
|
-
####
|
|
52
|
+
#### Tool Permissions
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
**Read-only** (minimum required):
|
|
55
55
|
|
|
56
|
-
| Permission | Description |
|
|
57
|
-
|
|
58
|
-
| `docx:document` |
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
| Permission | Tool | Description |
|
|
57
|
+
|------------|------|-------------|
|
|
58
|
+
| `docx:document:readonly` | `feishu_doc` | Read documents |
|
|
59
|
+
| `drive:drive:readonly` | `feishu_drive` | List folders, get file info |
|
|
60
|
+
| `wiki:wiki:readonly` | `feishu_wiki` | List spaces, list nodes, get node info, search |
|
|
61
|
+
|
|
62
|
+
**Read-write** (optional, for create/edit/delete operations):
|
|
63
|
+
|
|
64
|
+
| Permission | Tool | Description |
|
|
65
|
+
|------------|------|-------------|
|
|
66
|
+
| `docx:document` | `feishu_doc` | Create/edit documents |
|
|
67
|
+
| `docx:document.block:convert` | `feishu_doc` | Markdown to blocks conversion (required for write/append) |
|
|
68
|
+
| `drive:drive` | `feishu_doc`, `feishu_drive` | Upload images to documents, create folders, move/delete files |
|
|
69
|
+
| `wiki:wiki` | `feishu_wiki` | Create/move/rename wiki nodes |
|
|
70
|
+
|
|
71
|
+
#### Drive Access ⚠️
|
|
72
|
+
|
|
73
|
+
> **Important:** Bots don't have their own "My Space" (root folder). Bots can only access files/folders that have been **shared with them**.
|
|
74
|
+
|
|
75
|
+
To let the bot manage files:
|
|
76
|
+
1. Create a folder in your Feishu Drive
|
|
77
|
+
2. Right-click the folder → **Share** → search for your bot name
|
|
78
|
+
3. Grant appropriate permission (view/edit)
|
|
79
|
+
|
|
80
|
+
Without this step, `feishu_drive` operations like `create_folder` will fail because the bot has no root folder to create in.
|
|
81
|
+
|
|
82
|
+
#### Wiki Space Access ⚠️
|
|
83
|
+
|
|
84
|
+
> **Important:** API permissions alone are not enough for wiki access. You must also add the bot to each wiki space.
|
|
85
|
+
|
|
86
|
+
1. Open the wiki space you want the bot to access
|
|
87
|
+
2. Click **Settings** (gear icon) → **Members**
|
|
88
|
+
3. Click **Add Member** → search for your bot name
|
|
89
|
+
4. Select appropriate permission level (view/edit)
|
|
90
|
+
|
|
91
|
+
Without this step, `feishu_wiki` will return empty results even with correct API permissions.
|
|
92
|
+
|
|
93
|
+
Reference: [Wiki FAQ - How to add app to wiki](https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca)
|
|
63
94
|
|
|
64
95
|
#### Event Subscriptions ⚠️
|
|
65
96
|
|
|
@@ -129,7 +160,10 @@ channels:
|
|
|
129
160
|
- User and group directory lookup
|
|
130
161
|
- **Card render mode**: Optional markdown rendering with syntax highlighting
|
|
131
162
|
- **Document tools**: Read, create, and write Feishu documents with markdown (tables not supported due to API limitations)
|
|
163
|
+
- **Wiki tools**: Navigate knowledge bases, list spaces, get node details, search, create/move/rename nodes
|
|
164
|
+
- **Drive tools**: List folders, get file info, create folders, move/delete files
|
|
132
165
|
- **@mention forwarding**: When you @mention someone in your message, the bot's reply will automatically @mention them too
|
|
166
|
+
- **Permission error notification**: When the bot encounters a Feishu API permission error, it automatically notifies the user with the permission grant URL
|
|
133
167
|
|
|
134
168
|
#### @Mention Forwarding
|
|
135
169
|
|
|
@@ -225,17 +259,48 @@ npm install @m1heng-clawd/feishu
|
|
|
225
259
|
| `im:message:recall` | 撤回 | 撤回已发送消息 |
|
|
226
260
|
| `im:message.reactions:read` | 表情 | 查看消息表情回复 |
|
|
227
261
|
|
|
228
|
-
####
|
|
262
|
+
#### 工具权限
|
|
229
263
|
|
|
230
|
-
|
|
264
|
+
**只读权限**(最低要求):
|
|
231
265
|
|
|
232
|
-
| 权限 | 说明 |
|
|
233
|
-
|
|
234
|
-
| `docx:document` |
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
|
|
238
|
-
|
|
266
|
+
| 权限 | 工具 | 说明 |
|
|
267
|
+
|------|------|------|
|
|
268
|
+
| `docx:document:readonly` | `feishu_doc` | 读取文档 |
|
|
269
|
+
| `drive:drive:readonly` | `feishu_drive` | 列出文件夹、获取文件信息 |
|
|
270
|
+
| `wiki:wiki:readonly` | `feishu_wiki` | 列出空间、列出节点、获取节点详情、搜索 |
|
|
271
|
+
|
|
272
|
+
**读写权限**(可选,用于创建/编辑/删除操作):
|
|
273
|
+
|
|
274
|
+
| 权限 | 工具 | 说明 |
|
|
275
|
+
|------|------|------|
|
|
276
|
+
| `docx:document` | `feishu_doc` | 创建/编辑文档 |
|
|
277
|
+
| `docx:document.block:convert` | `feishu_doc` | Markdown 转 blocks(write/append 必需) |
|
|
278
|
+
| `drive:drive` | `feishu_doc`, `feishu_drive` | 上传图片到文档、创建文件夹、移动/删除文件 |
|
|
279
|
+
| `wiki:wiki` | `feishu_wiki` | 创建/移动/重命名知识库节点 |
|
|
280
|
+
|
|
281
|
+
#### 云空间访问权限 ⚠️
|
|
282
|
+
|
|
283
|
+
> **重要:** 机器人没有自己的"我的空间"(根目录)。机器人只能访问**被分享给它的文件/文件夹**。
|
|
284
|
+
|
|
285
|
+
要让机器人管理文件:
|
|
286
|
+
1. 在你的飞书云空间创建一个文件夹
|
|
287
|
+
2. 右键文件夹 → **分享** → 搜索机器人名称
|
|
288
|
+
3. 授予相应权限(查看/编辑)
|
|
289
|
+
|
|
290
|
+
如果不做这一步,`feishu_drive` 的 `create_folder` 等操作会失败,因为机器人没有根目录可以创建文件夹。
|
|
291
|
+
|
|
292
|
+
#### 知识库空间权限 ⚠️
|
|
293
|
+
|
|
294
|
+
> **重要:** 仅有 API 权限不够,还需要将机器人添加到知识库空间。
|
|
295
|
+
|
|
296
|
+
1. 打开需要机器人访问的知识库空间
|
|
297
|
+
2. 点击 **设置**(齿轮图标)→ **成员管理**
|
|
298
|
+
3. 点击 **添加成员** → 搜索机器人名称
|
|
299
|
+
4. 选择权限级别(查看/编辑)
|
|
300
|
+
|
|
301
|
+
如果不做这一步,即使 API 权限正确,`feishu_wiki` 也会返回空结果。
|
|
302
|
+
|
|
303
|
+
参考文档:[知识库常见问题 - 如何将应用添加为知识库成员](https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca)
|
|
239
304
|
|
|
240
305
|
#### 事件订阅 ⚠️
|
|
241
306
|
|
|
@@ -305,7 +370,10 @@ channels:
|
|
|
305
370
|
- 用户和群组目录查询
|
|
306
371
|
- **卡片渲染模式**:支持语法高亮的 Markdown 渲染
|
|
307
372
|
- **文档工具**:读取、创建、用 Markdown 写入飞书文档(表格因 API 限制不支持)
|
|
373
|
+
- **知识库工具**:浏览知识库、列出空间、获取节点详情、搜索、创建/移动/重命名节点
|
|
374
|
+
- **云空间工具**:列出文件夹、获取文件信息、创建文件夹、移动/删除文件
|
|
308
375
|
- **@ 转发功能**:在消息中 @ 某人,机器人的回复会自动 @ 该用户
|
|
376
|
+
- **权限错误提示**:当机器人遇到飞书 API 权限错误时,会自动通知用户并提供权限授权链接
|
|
309
377
|
|
|
310
378
|
#### @ 转发功能
|
|
311
379
|
|
package/index.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
|
3
3
|
import { feishuPlugin } from "./src/channel.js";
|
|
4
4
|
import { setFeishuRuntime } from "./src/runtime.js";
|
|
5
5
|
import { registerFeishuDocTools } from "./src/docx.js";
|
|
6
|
+
import { registerFeishuWikiTools } from "./src/wiki.js";
|
|
7
|
+
import { registerFeishuDriveTools } from "./src/drive.js";
|
|
8
|
+
import { registerFeishuPermTools } from "./src/perm.js";
|
|
6
9
|
|
|
7
10
|
export { monitorFeishuProvider } from "./src/monitor.js";
|
|
8
11
|
export {
|
|
@@ -49,6 +52,9 @@ const plugin = {
|
|
|
49
52
|
setFeishuRuntime(api.runtime);
|
|
50
53
|
api.registerChannel({ plugin: feishuPlugin });
|
|
51
54
|
registerFeishuDocTools(api);
|
|
55
|
+
registerFeishuWikiTools(api);
|
|
56
|
+
registerFeishuDriveTools(api);
|
|
57
|
+
registerFeishuPermTools(api);
|
|
52
58
|
},
|
|
53
59
|
};
|
|
54
60
|
|
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -24,23 +24,70 @@ import {
|
|
|
24
24
|
isMentionForwardRequest,
|
|
25
25
|
} from "./mention.js";
|
|
26
26
|
|
|
27
|
+
// --- Permission error extraction ---
|
|
28
|
+
// Extract permission grant URL from Feishu API error response.
|
|
29
|
+
type PermissionError = {
|
|
30
|
+
code: number;
|
|
31
|
+
message: string;
|
|
32
|
+
grantUrl?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function extractPermissionError(err: unknown): PermissionError | null {
|
|
36
|
+
if (!err || typeof err !== "object") return null;
|
|
37
|
+
|
|
38
|
+
// Axios error structure: err.response.data contains the Feishu error
|
|
39
|
+
const axiosErr = err as { response?: { data?: unknown } };
|
|
40
|
+
const data = axiosErr.response?.data;
|
|
41
|
+
if (!data || typeof data !== "object") return null;
|
|
42
|
+
|
|
43
|
+
const feishuErr = data as {
|
|
44
|
+
code?: number;
|
|
45
|
+
msg?: string;
|
|
46
|
+
error?: { permission_violations?: Array<{ uri?: string }> };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Feishu permission error code: 99991672
|
|
50
|
+
if (feishuErr.code !== 99991672) return null;
|
|
51
|
+
|
|
52
|
+
// Extract the grant URL from the error message (contains the direct link)
|
|
53
|
+
const msg = feishuErr.msg ?? "";
|
|
54
|
+
const urlMatch = msg.match(/https:\/\/open\.feishu\.cn\/app\/[^\s,]+/);
|
|
55
|
+
const grantUrl = urlMatch?.[0];
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
code: feishuErr.code,
|
|
59
|
+
message: msg,
|
|
60
|
+
grantUrl,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
27
64
|
// --- Sender name resolution (so the agent can distinguish who is speaking in group chats) ---
|
|
28
65
|
// Cache display names by open_id to avoid an API call on every message.
|
|
29
66
|
const SENDER_NAME_TTL_MS = 10 * 60 * 1000;
|
|
30
67
|
const senderNameCache = new Map<string, { name: string; expireAt: number }>();
|
|
31
68
|
|
|
69
|
+
// Cache permission errors to avoid spamming the user with repeated notifications.
|
|
70
|
+
// Key: appId or "default", Value: timestamp of last notification
|
|
71
|
+
const permissionErrorNotifiedAt = new Map<string, number>();
|
|
72
|
+
const PERMISSION_ERROR_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
|
73
|
+
|
|
74
|
+
type SenderNameResult = {
|
|
75
|
+
name?: string;
|
|
76
|
+
permissionError?: PermissionError;
|
|
77
|
+
};
|
|
78
|
+
|
|
32
79
|
async function resolveFeishuSenderName(params: {
|
|
33
80
|
feishuCfg?: FeishuConfig;
|
|
34
81
|
senderOpenId: string;
|
|
35
82
|
log: (...args: any[]) => void;
|
|
36
|
-
}): Promise<
|
|
83
|
+
}): Promise<SenderNameResult> {
|
|
37
84
|
const { feishuCfg, senderOpenId, log } = params;
|
|
38
|
-
if (!feishuCfg) return
|
|
39
|
-
if (!senderOpenId) return
|
|
85
|
+
if (!feishuCfg) return {};
|
|
86
|
+
if (!senderOpenId) return {};
|
|
40
87
|
|
|
41
88
|
const cached = senderNameCache.get(senderOpenId);
|
|
42
89
|
const now = Date.now();
|
|
43
|
-
if (cached && cached.expireAt > now) return cached.name;
|
|
90
|
+
if (cached && cached.expireAt > now) return { name: cached.name };
|
|
44
91
|
|
|
45
92
|
try {
|
|
46
93
|
const client = createFeishuClient(feishuCfg);
|
|
@@ -59,14 +106,21 @@ async function resolveFeishuSenderName(params: {
|
|
|
59
106
|
|
|
60
107
|
if (name && typeof name === "string") {
|
|
61
108
|
senderNameCache.set(senderOpenId, { name, expireAt: now + SENDER_NAME_TTL_MS });
|
|
62
|
-
return name;
|
|
109
|
+
return { name };
|
|
63
110
|
}
|
|
64
111
|
|
|
65
|
-
return
|
|
112
|
+
return {};
|
|
66
113
|
} catch (err) {
|
|
114
|
+
// Check if this is a permission error
|
|
115
|
+
const permErr = extractPermissionError(err);
|
|
116
|
+
if (permErr) {
|
|
117
|
+
log(`feishu: permission error resolving sender name: code=${permErr.code}`);
|
|
118
|
+
return { permissionError: permErr };
|
|
119
|
+
}
|
|
120
|
+
|
|
67
121
|
// Best-effort. Don't fail message handling if name lookup fails.
|
|
68
122
|
log(`feishu: failed to resolve sender name for ${senderOpenId}: ${String(err)}`);
|
|
69
|
-
return
|
|
123
|
+
return {};
|
|
70
124
|
}
|
|
71
125
|
}
|
|
72
126
|
|
|
@@ -447,12 +501,25 @@ export async function handleFeishuMessage(params: {
|
|
|
447
501
|
const isGroup = ctx.chatType === "group";
|
|
448
502
|
|
|
449
503
|
// Resolve sender display name (best-effort) so the agent can attribute messages correctly.
|
|
450
|
-
const
|
|
504
|
+
const senderResult = await resolveFeishuSenderName({
|
|
451
505
|
feishuCfg,
|
|
452
506
|
senderOpenId: ctx.senderOpenId,
|
|
453
507
|
log,
|
|
454
508
|
});
|
|
455
|
-
if (
|
|
509
|
+
if (senderResult.name) ctx = { ...ctx, senderName: senderResult.name };
|
|
510
|
+
|
|
511
|
+
// Track permission error to inform agent later (with cooldown to avoid repetition)
|
|
512
|
+
let permissionErrorForAgent: PermissionError | undefined;
|
|
513
|
+
if (senderResult.permissionError) {
|
|
514
|
+
const appKey = feishuCfg?.appId ?? "default";
|
|
515
|
+
const now = Date.now();
|
|
516
|
+
const lastNotified = permissionErrorNotifiedAt.get(appKey) ?? 0;
|
|
517
|
+
|
|
518
|
+
if (now - lastNotified > PERMISSION_ERROR_COOLDOWN_MS) {
|
|
519
|
+
permissionErrorNotifiedAt.set(appKey, now);
|
|
520
|
+
permissionErrorForAgent = senderResult.permissionError;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
456
523
|
|
|
457
524
|
log(`feishu: received message from ${ctx.senderOpenId} in ${ctx.chatId} (${ctx.chatType})`);
|
|
458
525
|
|
|
@@ -598,6 +665,62 @@ export async function handleFeishuMessage(params: {
|
|
|
598
665
|
|
|
599
666
|
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderOpenId}` : ctx.senderOpenId;
|
|
600
667
|
|
|
668
|
+
// If there's a permission error, dispatch a separate notification first
|
|
669
|
+
if (permissionErrorForAgent) {
|
|
670
|
+
const grantUrl = permissionErrorForAgent.grantUrl ?? "";
|
|
671
|
+
const permissionNotifyBody = `[System: The bot encountered a Feishu API permission error. Please inform the user about this issue and provide the permission grant URL for the admin to authorize. Permission grant URL: ${grantUrl}]`;
|
|
672
|
+
|
|
673
|
+
const permissionBody = core.channel.reply.formatAgentEnvelope({
|
|
674
|
+
channel: "Feishu",
|
|
675
|
+
from: envelopeFrom,
|
|
676
|
+
timestamp: new Date(),
|
|
677
|
+
envelope: envelopeOptions,
|
|
678
|
+
body: permissionNotifyBody,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const permissionCtx = core.channel.reply.finalizeInboundContext({
|
|
682
|
+
Body: permissionBody,
|
|
683
|
+
RawBody: permissionNotifyBody,
|
|
684
|
+
CommandBody: permissionNotifyBody,
|
|
685
|
+
From: feishuFrom,
|
|
686
|
+
To: feishuTo,
|
|
687
|
+
SessionKey: route.sessionKey,
|
|
688
|
+
AccountId: route.accountId,
|
|
689
|
+
ChatType: isGroup ? "group" : "direct",
|
|
690
|
+
GroupSubject: isGroup ? ctx.chatId : undefined,
|
|
691
|
+
SenderName: "system",
|
|
692
|
+
SenderId: "system",
|
|
693
|
+
Provider: "feishu" as const,
|
|
694
|
+
Surface: "feishu" as const,
|
|
695
|
+
MessageSid: `${ctx.messageId}:permission-error`,
|
|
696
|
+
Timestamp: Date.now(),
|
|
697
|
+
WasMentioned: false,
|
|
698
|
+
CommandAuthorized: true,
|
|
699
|
+
OriginatingChannel: "feishu" as const,
|
|
700
|
+
OriginatingTo: feishuTo,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const { dispatcher: permDispatcher, replyOptions: permReplyOptions, markDispatchIdle: markPermIdle } =
|
|
704
|
+
createFeishuReplyDispatcher({
|
|
705
|
+
cfg,
|
|
706
|
+
agentId: route.agentId,
|
|
707
|
+
runtime: runtime as RuntimeEnv,
|
|
708
|
+
chatId: ctx.chatId,
|
|
709
|
+
replyToMessageId: ctx.messageId,
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
log(`feishu: dispatching permission error notification to agent`);
|
|
713
|
+
|
|
714
|
+
await core.channel.reply.dispatchReplyFromConfig({
|
|
715
|
+
ctx: permissionCtx,
|
|
716
|
+
cfg,
|
|
717
|
+
dispatcher: permDispatcher,
|
|
718
|
+
replyOptions: permReplyOptions,
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
markPermIdle();
|
|
722
|
+
}
|
|
723
|
+
|
|
601
724
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
602
725
|
channel: "Feishu",
|
|
603
726
|
from: envelopeFrom,
|
package/src/config-schema.ts
CHANGED
|
@@ -50,6 +50,25 @@ const ChannelHeartbeatVisibilitySchema = z
|
|
|
50
50
|
.strict()
|
|
51
51
|
.optional();
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Feishu tools configuration.
|
|
55
|
+
* Controls which tool categories are enabled.
|
|
56
|
+
*
|
|
57
|
+
* Dependencies:
|
|
58
|
+
* - wiki requires doc (wiki content is edited via doc tools)
|
|
59
|
+
* - perm can work independently but is typically used with drive
|
|
60
|
+
*/
|
|
61
|
+
const FeishuToolsConfigSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
doc: z.boolean().optional(), // Document operations (default: true)
|
|
64
|
+
wiki: z.boolean().optional(), // Knowledge base operations (default: true, requires doc)
|
|
65
|
+
drive: z.boolean().optional(), // Cloud storage operations (default: true)
|
|
66
|
+
perm: z.boolean().optional(), // Permission management (default: false, sensitive)
|
|
67
|
+
scopes: z.boolean().optional(), // App scopes diagnostic (default: true)
|
|
68
|
+
})
|
|
69
|
+
.strict()
|
|
70
|
+
.optional();
|
|
71
|
+
|
|
53
72
|
export const FeishuGroupSchema = z
|
|
54
73
|
.object({
|
|
55
74
|
requireMention: z.boolean().optional(),
|
|
@@ -90,6 +109,7 @@ export const FeishuConfigSchema = z
|
|
|
90
109
|
mediaMaxMb: z.number().positive().optional(),
|
|
91
110
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
92
111
|
renderMode: RenderModeSchema, // raw = plain text (default), card = interactive card with markdown
|
|
112
|
+
tools: FeishuToolsConfigSchema,
|
|
93
113
|
})
|
|
94
114
|
.strict()
|
|
95
115
|
.superRefine((value, ctx) => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
export const FeishuDocSchema = Type.Union([
|
|
4
|
+
Type.Object({
|
|
5
|
+
action: Type.Literal("read"),
|
|
6
|
+
doc_token: Type.String({ description: "Document token (extract from URL /docx/XXX)" }),
|
|
7
|
+
}),
|
|
8
|
+
Type.Object({
|
|
9
|
+
action: Type.Literal("write"),
|
|
10
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
11
|
+
content: Type.String({
|
|
12
|
+
description: "Markdown content to write (replaces entire document content)",
|
|
13
|
+
}),
|
|
14
|
+
}),
|
|
15
|
+
Type.Object({
|
|
16
|
+
action: Type.Literal("append"),
|
|
17
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
18
|
+
content: Type.String({ description: "Markdown content to append to end of document" }),
|
|
19
|
+
}),
|
|
20
|
+
Type.Object({
|
|
21
|
+
action: Type.Literal("create"),
|
|
22
|
+
title: Type.String({ description: "Document title" }),
|
|
23
|
+
folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
|
|
24
|
+
}),
|
|
25
|
+
Type.Object({
|
|
26
|
+
action: Type.Literal("list_blocks"),
|
|
27
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
28
|
+
}),
|
|
29
|
+
Type.Object({
|
|
30
|
+
action: Type.Literal("get_block"),
|
|
31
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
32
|
+
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
|
33
|
+
}),
|
|
34
|
+
Type.Object({
|
|
35
|
+
action: Type.Literal("update_block"),
|
|
36
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
37
|
+
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
|
38
|
+
content: Type.String({ description: "New text content" }),
|
|
39
|
+
}),
|
|
40
|
+
Type.Object({
|
|
41
|
+
action: Type.Literal("delete_block"),
|
|
42
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
43
|
+
block_id: Type.String({ description: "Block ID" }),
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export type FeishuDocParams = Static<typeof FeishuDocSchema>;
|