cc-lark 0.1.1
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/.github/workflows/ci.yml +47 -0
- package/.github/workflows/release.yml +47 -0
- package/.github/workflows/sync-upstream.yml +127 -0
- package/.prettierrc.json +7 -0
- package/README.md +214 -0
- package/dist/core/api-error.d.ts +193 -0
- package/dist/core/api-error.d.ts.map +1 -0
- package/dist/core/api-error.js +263 -0
- package/dist/core/api-error.js.map +1 -0
- package/dist/core/auth-errors.d.ts +13 -0
- package/dist/core/auth-errors.d.ts.map +1 -0
- package/dist/core/auth-errors.js +14 -0
- package/dist/core/auth-errors.js.map +1 -0
- package/dist/core/config.d.ts +60 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +115 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/device-flow.d.ts +80 -0
- package/dist/core/device-flow.d.ts.map +1 -0
- package/dist/core/device-flow.js +231 -0
- package/dist/core/device-flow.js.map +1 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +16 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lark-client.d.ts +136 -0
- package/dist/core/lark-client.d.ts.map +1 -0
- package/dist/core/lark-client.js +315 -0
- package/dist/core/lark-client.js.map +1 -0
- package/dist/core/token-store.d.ts +67 -0
- package/dist/core/token-store.d.ts.map +1 -0
- package/dist/core/token-store.js +215 -0
- package/dist/core/token-store.js.map +1 -0
- package/dist/core/types.d.ts +286 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +11 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/uat-client.d.ts +64 -0
- package/dist/core/uat-client.d.ts.map +1 -0
- package/dist/core/uat-client.js +227 -0
- package/dist/core/uat-client.js.map +1 -0
- package/dist/core/version.d.ts +26 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +50 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/bitable/app.d.ts +20 -0
- package/dist/tools/bitable/app.d.ts.map +1 -0
- package/dist/tools/bitable/app.js +301 -0
- package/dist/tools/bitable/app.js.map +1 -0
- package/dist/tools/bitable/field.d.ts +19 -0
- package/dist/tools/bitable/field.d.ts.map +1 -0
- package/dist/tools/bitable/field.js +315 -0
- package/dist/tools/bitable/field.js.map +1 -0
- package/dist/tools/bitable/index.d.ts +21 -0
- package/dist/tools/bitable/index.d.ts.map +1 -0
- package/dist/tools/bitable/index.js +39 -0
- package/dist/tools/bitable/index.js.map +1 -0
- package/dist/tools/bitable/record.d.ts +22 -0
- package/dist/tools/bitable/record.d.ts.map +1 -0
- package/dist/tools/bitable/record.js +434 -0
- package/dist/tools/bitable/record.js.map +1 -0
- package/dist/tools/bitable/table.d.ts +21 -0
- package/dist/tools/bitable/table.d.ts.map +1 -0
- package/dist/tools/bitable/table.js +361 -0
- package/dist/tools/bitable/table.js.map +1 -0
- package/dist/tools/calendar/calendar.d.ts +18 -0
- package/dist/tools/calendar/calendar.d.ts.map +1 -0
- package/dist/tools/calendar/calendar.js +192 -0
- package/dist/tools/calendar/calendar.js.map +1 -0
- package/dist/tools/calendar/event.d.ts +20 -0
- package/dist/tools/calendar/event.d.ts.map +1 -0
- package/dist/tools/calendar/event.js +465 -0
- package/dist/tools/calendar/event.js.map +1 -0
- package/dist/tools/calendar/index.d.ts +19 -0
- package/dist/tools/calendar/index.d.ts.map +1 -0
- package/dist/tools/calendar/index.js +37 -0
- package/dist/tools/calendar/index.js.map +1 -0
- package/dist/tools/chat/chat.d.ts +11 -0
- package/dist/tools/chat/chat.d.ts.map +1 -0
- package/dist/tools/chat/chat.js +106 -0
- package/dist/tools/chat/chat.js.map +1 -0
- package/dist/tools/chat/index.d.ts +11 -0
- package/dist/tools/chat/index.d.ts.map +1 -0
- package/dist/tools/chat/index.js +20 -0
- package/dist/tools/chat/index.js.map +1 -0
- package/dist/tools/chat/members.d.ts +9 -0
- package/dist/tools/chat/members.d.ts.map +1 -0
- package/dist/tools/chat/members.js +80 -0
- package/dist/tools/chat/members.js.map +1 -0
- package/dist/tools/common/get-user.d.ts +11 -0
- package/dist/tools/common/get-user.d.ts.map +1 -0
- package/dist/tools/common/get-user.js +112 -0
- package/dist/tools/common/get-user.js.map +1 -0
- package/dist/tools/common/index.d.ts +11 -0
- package/dist/tools/common/index.d.ts.map +1 -0
- package/dist/tools/common/index.js +20 -0
- package/dist/tools/common/index.js.map +1 -0
- package/dist/tools/common/search-user.d.ts +9 -0
- package/dist/tools/common/search-user.d.ts.map +1 -0
- package/dist/tools/common/search-user.js +88 -0
- package/dist/tools/common/search-user.js.map +1 -0
- package/dist/tools/doc/create.d.ts +17 -0
- package/dist/tools/doc/create.d.ts.map +1 -0
- package/dist/tools/doc/create.js +159 -0
- package/dist/tools/doc/create.js.map +1 -0
- package/dist/tools/doc/fetch.d.ts +17 -0
- package/dist/tools/doc/fetch.d.ts.map +1 -0
- package/dist/tools/doc/fetch.js +123 -0
- package/dist/tools/doc/fetch.js.map +1 -0
- package/dist/tools/doc/index.d.ts +21 -0
- package/dist/tools/doc/index.d.ts.map +1 -0
- package/dist/tools/doc/index.js +33 -0
- package/dist/tools/doc/index.js.map +1 -0
- package/dist/tools/doc/shared.d.ts +69 -0
- package/dist/tools/doc/shared.d.ts.map +1 -0
- package/dist/tools/doc/shared.js +172 -0
- package/dist/tools/doc/shared.js.map +1 -0
- package/dist/tools/doc/update.d.ts +25 -0
- package/dist/tools/doc/update.d.ts.map +1 -0
- package/dist/tools/doc/update.js +208 -0
- package/dist/tools/doc/update.js.map +1 -0
- package/dist/tools/drive/file.d.ts +13 -0
- package/dist/tools/drive/file.d.ts.map +1 -0
- package/dist/tools/drive/file.js +212 -0
- package/dist/tools/drive/file.js.map +1 -0
- package/dist/tools/drive/index.d.ts +12 -0
- package/dist/tools/drive/index.d.ts.map +1 -0
- package/dist/tools/drive/index.js +25 -0
- package/dist/tools/drive/index.js.map +1 -0
- package/dist/tools/im/format-messages.d.ts +99 -0
- package/dist/tools/im/format-messages.d.ts.map +1 -0
- package/dist/tools/im/format-messages.js +277 -0
- package/dist/tools/im/format-messages.js.map +1 -0
- package/dist/tools/im/helpers.d.ts +53 -0
- package/dist/tools/im/helpers.d.ts.map +1 -0
- package/dist/tools/im/helpers.js +85 -0
- package/dist/tools/im/helpers.js.map +1 -0
- package/dist/tools/im/index.d.ts +25 -0
- package/dist/tools/im/index.d.ts.map +1 -0
- package/dist/tools/im/index.js +44 -0
- package/dist/tools/im/index.js.map +1 -0
- package/dist/tools/im/message-read.d.ts +19 -0
- package/dist/tools/im/message-read.d.ts.map +1 -0
- package/dist/tools/im/message-read.js +526 -0
- package/dist/tools/im/message-read.js.map +1 -0
- package/dist/tools/im/message.d.ts +22 -0
- package/dist/tools/im/message.d.ts.map +1 -0
- package/dist/tools/im/message.js +233 -0
- package/dist/tools/im/message.js.map +1 -0
- package/dist/tools/im/resource.d.ts +19 -0
- package/dist/tools/im/resource.d.ts.map +1 -0
- package/dist/tools/im/resource.js +185 -0
- package/dist/tools/im/resource.js.map +1 -0
- package/dist/tools/im/time-utils.d.ts +70 -0
- package/dist/tools/im/time-utils.d.ts.map +1 -0
- package/dist/tools/im/time-utils.js +277 -0
- package/dist/tools/im/time-utils.js.map +1 -0
- package/dist/tools/index.d.ts +85 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +135 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/oauth.d.ts +15 -0
- package/dist/tools/oauth.d.ts.map +1 -0
- package/dist/tools/oauth.js +379 -0
- package/dist/tools/oauth.js.map +1 -0
- package/dist/tools/search/doc-search.d.ts +9 -0
- package/dist/tools/search/doc-search.d.ts.map +1 -0
- package/dist/tools/search/doc-search.js +219 -0
- package/dist/tools/search/doc-search.js.map +1 -0
- package/dist/tools/search/index.d.ts +11 -0
- package/dist/tools/search/index.d.ts.map +1 -0
- package/dist/tools/search/index.js +18 -0
- package/dist/tools/search/index.js.map +1 -0
- package/dist/tools/sheets/index.d.ts +11 -0
- package/dist/tools/sheets/index.d.ts.map +1 -0
- package/dist/tools/sheets/index.js +18 -0
- package/dist/tools/sheets/index.js.map +1 -0
- package/dist/tools/sheets/sheet.d.ts +11 -0
- package/dist/tools/sheets/sheet.d.ts.map +1 -0
- package/dist/tools/sheets/sheet.js +332 -0
- package/dist/tools/sheets/sheet.js.map +1 -0
- package/dist/tools/task/index.d.ts +12 -0
- package/dist/tools/task/index.d.ts.map +1 -0
- package/dist/tools/task/index.js +30 -0
- package/dist/tools/task/index.js.map +1 -0
- package/dist/tools/task/task.d.ts +13 -0
- package/dist/tools/task/task.d.ts.map +1 -0
- package/dist/tools/task/task.js +225 -0
- package/dist/tools/task/task.js.map +1 -0
- package/dist/tools/task/tasklist.d.ts +13 -0
- package/dist/tools/task/tasklist.d.ts.map +1 -0
- package/dist/tools/task/tasklist.js +206 -0
- package/dist/tools/task/tasklist.js.map +1 -0
- package/dist/tools/wiki/index.d.ts +11 -0
- package/dist/tools/wiki/index.d.ts.map +1 -0
- package/dist/tools/wiki/index.js +20 -0
- package/dist/tools/wiki/index.js.map +1 -0
- package/dist/tools/wiki/node.d.ts +11 -0
- package/dist/tools/wiki/node.d.ts.map +1 -0
- package/dist/tools/wiki/node.js +112 -0
- package/dist/tools/wiki/node.js.map +1 -0
- package/dist/tools/wiki/space.d.ts +11 -0
- package/dist/tools/wiki/space.d.ts.map +1 -0
- package/dist/tools/wiki/space.js +125 -0
- package/dist/tools/wiki/space.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +101 -0
- package/dist/utils/logger.js.map +1 -0
- package/eslint.config.js +13 -0
- package/package.json +54 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +18 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/core/api-error.ts +342 -0
- package/src/core/auth-errors.ts +27 -0
- package/src/core/config.ts +134 -0
- package/src/core/device-flow.ts +314 -0
- package/src/core/index.ts +16 -0
- package/src/core/lark-client.ts +391 -0
- package/src/core/token-store.ts +249 -0
- package/src/core/types.ts +302 -0
- package/src/core/uat-client.ts +298 -0
- package/src/core/version.ts +53 -0
- package/src/index.ts +138 -0
- package/src/tools/bitable/app.ts +390 -0
- package/src/tools/bitable/field.ts +406 -0
- package/src/tools/bitable/index.ts +43 -0
- package/src/tools/bitable/record.ts +559 -0
- package/src/tools/bitable/table.ts +472 -0
- package/src/tools/calendar/calendar.ts +254 -0
- package/src/tools/calendar/event.ts +606 -0
- package/src/tools/calendar/index.ts +41 -0
- package/src/tools/chat/chat.ts +127 -0
- package/src/tools/chat/index.ts +24 -0
- package/src/tools/chat/members.ts +93 -0
- package/src/tools/common/get-user.ts +127 -0
- package/src/tools/common/index.ts +24 -0
- package/src/tools/common/search-user.ts +99 -0
- package/src/tools/doc/create.ts +184 -0
- package/src/tools/doc/fetch.ts +149 -0
- package/src/tools/doc/index.ts +38 -0
- package/src/tools/doc/shared.ts +228 -0
- package/src/tools/doc/update.ts +240 -0
- package/src/tools/drive/file.ts +265 -0
- package/src/tools/drive/index.ts +29 -0
- package/src/tools/im/format-messages.ts +391 -0
- package/src/tools/im/helpers.ts +109 -0
- package/src/tools/im/index.ts +49 -0
- package/src/tools/im/message-read.ts +676 -0
- package/src/tools/im/message.ts +303 -0
- package/src/tools/im/resource.ts +225 -0
- package/src/tools/im/time-utils.ts +347 -0
- package/src/tools/index.ts +205 -0
- package/src/tools/oauth.ts +460 -0
- package/src/tools/search/doc-search.ts +250 -0
- package/src/tools/search/index.ts +22 -0
- package/src/tools/sheets/index.ts +22 -0
- package/src/tools/sheets/sheet.ts +382 -0
- package/src/tools/task/index.ts +34 -0
- package/src/tools/task/task.ts +265 -0
- package/src/tools/task/tasklist.ts +262 -0
- package/src/tools/wiki/index.ts +24 -0
- package/src/tools/wiki/node.ts +131 -0
- package/src/tools/wiki/space.ts +152 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +132 -0
- package/tests/core/config.test.ts +238 -0
- package/tests/core/device-flow.test.ts +490 -0
- package/tests/core/lark-client.test.ts +378 -0
- package/tests/core/token-store.test.ts +438 -0
- package/tests/index.test.ts +360 -0
- package/tests/tools/doc/create.test.ts +224 -0
- package/tests/tools/doc/fetch.test.ts +182 -0
- package/tests/tools/doc/shared.test.ts +183 -0
- package/tests/tools/doc/update.test.ts +330 -0
- package/tests/tools/im/format-messages.test.ts +184 -0
- package/tests/tools/im/time-utils.test.ts +178 -0
- package/tests/utils/logger.test.ts +140 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_chat tool - Manage Feishu chats/groups.
|
|
6
|
+
*
|
|
7
|
+
* Actions: search, get
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import type { ToolRegistry } from '../index.js';
|
|
12
|
+
import { LarkClient } from '../../core/lark-client.js';
|
|
13
|
+
import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
|
|
14
|
+
import { assertLarkOk } from '../../core/api-error.js';
|
|
15
|
+
import { json, jsonError, type ToolResult } from '../im/helpers.js';
|
|
16
|
+
import { logger } from '../../utils/logger.js';
|
|
17
|
+
|
|
18
|
+
const log = logger('tools:chat:chat');
|
|
19
|
+
|
|
20
|
+
// Schemas
|
|
21
|
+
const searchActionSchema = {
|
|
22
|
+
action: z.literal('search').describe('Search for chats'),
|
|
23
|
+
query: z.string().describe('Search query (matches group name or member name)'),
|
|
24
|
+
page_size: z.number().min(1).optional().describe('Page size (default 20)'),
|
|
25
|
+
page_token: z.string().optional().describe('Pagination token'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getActionSchema = {
|
|
29
|
+
action: z.literal('get').describe('Get chat info'),
|
|
30
|
+
chat_id: z.string().describe('Chat ID (format: oc_xxx)'),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
34
|
+
const { larkClient, config } = context;
|
|
35
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
36
|
+
const { appId, appSecret, brand } = config;
|
|
37
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
38
|
+
|
|
39
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
40
|
+
const tokens = await listStoredTokens(appId);
|
|
41
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
42
|
+
const userOpenId = tokens[0].userOpenId;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerChatTool(registry: ToolRegistry): void {
|
|
53
|
+
// Search chats
|
|
54
|
+
registry.register({
|
|
55
|
+
name: 'feishu_chat_search',
|
|
56
|
+
description: 'Search for Feishu chats by name or member.\n\nRequires OAuth authorization.',
|
|
57
|
+
inputSchema: searchActionSchema,
|
|
58
|
+
handler: async (args, context) => {
|
|
59
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof searchActionSchema>>>;
|
|
60
|
+
const { larkClient } = context;
|
|
61
|
+
|
|
62
|
+
const tokenResult = await getAccessToken(context);
|
|
63
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
64
|
+
const accessToken = tokenResult;
|
|
65
|
+
|
|
66
|
+
log.info(`search: query="${p.query}", page_size=${p.page_size ?? 20}`);
|
|
67
|
+
|
|
68
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
69
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
70
|
+
|
|
71
|
+
const res = await larkClient!.sdk.im.v1.chat.search(
|
|
72
|
+
{
|
|
73
|
+
params: {
|
|
74
|
+
user_id_type: 'open_id',
|
|
75
|
+
query: p.query,
|
|
76
|
+
page_size: p.page_size,
|
|
77
|
+
page_token: p.page_token,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
opts
|
|
81
|
+
);
|
|
82
|
+
assertLarkOk(res);
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const data = res.data as any;
|
|
86
|
+
|
|
87
|
+
return json({
|
|
88
|
+
items: data?.items,
|
|
89
|
+
has_more: data?.has_more ?? false,
|
|
90
|
+
page_token: data?.page_token,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Get chat info
|
|
96
|
+
registry.register({
|
|
97
|
+
name: 'feishu_chat_get',
|
|
98
|
+
description: 'Get Feishu chat info by ID.\n\nRequires OAuth authorization.',
|
|
99
|
+
inputSchema: getActionSchema,
|
|
100
|
+
handler: async (args, context) => {
|
|
101
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof getActionSchema>>>;
|
|
102
|
+
const { larkClient } = context;
|
|
103
|
+
|
|
104
|
+
const tokenResult = await getAccessToken(context);
|
|
105
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
106
|
+
const accessToken = tokenResult;
|
|
107
|
+
|
|
108
|
+
log.info(`get: chat_id=${p.chat_id}`);
|
|
109
|
+
|
|
110
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
111
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
112
|
+
|
|
113
|
+
const res = await larkClient!.sdk.im.v1.chat.get(
|
|
114
|
+
{
|
|
115
|
+
path: { chat_id: p.chat_id },
|
|
116
|
+
params: { user_id_type: 'open_id' },
|
|
117
|
+
},
|
|
118
|
+
opts
|
|
119
|
+
);
|
|
120
|
+
assertLarkOk(res);
|
|
121
|
+
|
|
122
|
+
return json({ chat: res.data });
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
log.debug('feishu_chat tools registered');
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Chat Tools Index
|
|
6
|
+
*
|
|
7
|
+
* Chat/group management tools for Feishu/Lark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolRegistry } from '../index.js';
|
|
11
|
+
import { registerChatTool } from './chat.js';
|
|
12
|
+
import { registerChatMembersTool } from './members.js';
|
|
13
|
+
import { logger } from '../../utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const log = logger('tools:chat');
|
|
16
|
+
|
|
17
|
+
export function registerChatTools(registry: ToolRegistry): void {
|
|
18
|
+
registerChatTool(registry);
|
|
19
|
+
registerChatMembersTool(registry);
|
|
20
|
+
|
|
21
|
+
log.info('Chat tools registered', {
|
|
22
|
+
tools: ['feishu_chat_search', 'feishu_chat_get', 'feishu_chat_members'].join(', '),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_chat_members tool - Get chat members.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { ToolRegistry } from '../index.js';
|
|
10
|
+
import { LarkClient } from '../../core/lark-client.js';
|
|
11
|
+
import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
|
|
12
|
+
import { assertLarkOk } from '../../core/api-error.js';
|
|
13
|
+
import { json, jsonError, type ToolResult } from '../im/helpers.js';
|
|
14
|
+
import { logger } from '../../utils/logger.js';
|
|
15
|
+
|
|
16
|
+
const log = logger('tools:chat:members');
|
|
17
|
+
|
|
18
|
+
// Schemas
|
|
19
|
+
const membersActionSchema = {
|
|
20
|
+
chat_id: z.string().describe('Chat ID (format: oc_xxx)'),
|
|
21
|
+
member_id_type: z.enum(['open_id', 'union_id', 'user_id']).optional().describe('Member ID type (default: open_id)'),
|
|
22
|
+
page_size: z.number().min(1).optional().describe('Page size (default 20)'),
|
|
23
|
+
page_token: z.string().optional().describe('Pagination token'),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
27
|
+
const { larkClient, config } = context;
|
|
28
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
29
|
+
const { appId, appSecret, brand } = config;
|
|
30
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
31
|
+
|
|
32
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
33
|
+
const tokens = await listStoredTokens(appId);
|
|
34
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
35
|
+
const userOpenId = tokens[0].userOpenId;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function registerChatMembersTool(registry: ToolRegistry): void {
|
|
46
|
+
registry.register({
|
|
47
|
+
name: 'feishu_chat_members',
|
|
48
|
+
description: 'Get members of a Feishu chat.\n\nRequires OAuth authorization.',
|
|
49
|
+
inputSchema: membersActionSchema,
|
|
50
|
+
handler: async (args, context) => {
|
|
51
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof membersActionSchema>>>;
|
|
52
|
+
const { larkClient } = context;
|
|
53
|
+
|
|
54
|
+
const tokenResult = await getAccessToken(context);
|
|
55
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
56
|
+
const accessToken = tokenResult;
|
|
57
|
+
|
|
58
|
+
log.info(`members: chat_id="${p.chat_id}", page_size=${p.page_size ?? 20}`);
|
|
59
|
+
|
|
60
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
61
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
62
|
+
|
|
63
|
+
const res = await larkClient!.sdk.im.v1.chatMembers.get(
|
|
64
|
+
{
|
|
65
|
+
path: { chat_id: p.chat_id },
|
|
66
|
+
params: {
|
|
67
|
+
member_id_type: p.member_id_type || 'open_id',
|
|
68
|
+
page_size: p.page_size,
|
|
69
|
+
page_token: p.page_token,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
opts
|
|
73
|
+
);
|
|
74
|
+
assertLarkOk(res);
|
|
75
|
+
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const data = res.data as any;
|
|
78
|
+
const memberCount = data?.items?.length ?? 0;
|
|
79
|
+
const memberTotal = data?.member_total ?? 0;
|
|
80
|
+
|
|
81
|
+
log.info(`members: found ${memberCount} members (total: ${memberTotal})`);
|
|
82
|
+
|
|
83
|
+
return json({
|
|
84
|
+
items: data?.items,
|
|
85
|
+
has_more: data?.has_more ?? false,
|
|
86
|
+
page_token: data?.page_token,
|
|
87
|
+
member_total: memberTotal,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
log.debug('feishu_chat_members tool registered');
|
|
93
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_get_user tool - Get user information.
|
|
6
|
+
*
|
|
7
|
+
* Actions: get current user or get user by ID
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import type { ToolRegistry } from '../index.js';
|
|
12
|
+
import { LarkClient } from '../../core/lark-client.js';
|
|
13
|
+
import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
|
|
14
|
+
import { json, jsonError, type ToolResult } from '../im/helpers.js';
|
|
15
|
+
import { logger } from '../../utils/logger.js';
|
|
16
|
+
|
|
17
|
+
const log = logger('tools:common:get-user');
|
|
18
|
+
|
|
19
|
+
// Schemas
|
|
20
|
+
const getUserSchema = {
|
|
21
|
+
user_id: z.string().optional().describe('User ID (format: ou_xxx). If not provided, returns current user info'),
|
|
22
|
+
user_id_type: z.enum(['open_id', 'union_id', 'user_id']).optional().describe('User ID type (default: open_id)'),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
26
|
+
const { larkClient, config } = context;
|
|
27
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
28
|
+
const { appId, appSecret, brand } = config;
|
|
29
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
30
|
+
|
|
31
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
32
|
+
const tokens = await listStoredTokens(appId);
|
|
33
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
34
|
+
const userOpenId = tokens[0].userOpenId;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function registerGetUserTool(registry: ToolRegistry): void {
|
|
45
|
+
registry.register({
|
|
46
|
+
name: 'feishu_get_user',
|
|
47
|
+
description: 'Get Feishu user information. Returns current user if user_id not provided.\n\nRequires OAuth authorization.',
|
|
48
|
+
inputSchema: getUserSchema,
|
|
49
|
+
handler: async (args, context) => {
|
|
50
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof getUserSchema>>>;
|
|
51
|
+
const { larkClient } = context;
|
|
52
|
+
|
|
53
|
+
const tokenResult = await getAccessToken(context);
|
|
54
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
55
|
+
const accessToken = tokenResult;
|
|
56
|
+
|
|
57
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
58
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
59
|
+
|
|
60
|
+
// If no user_id provided, get current user info
|
|
61
|
+
if (!p.user_id) {
|
|
62
|
+
log.info('get_user: fetching current user info');
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const res = await larkClient!.sdk.authen.userInfo.get({}, opts);
|
|
66
|
+
|
|
67
|
+
// Check for API error
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
if ((res as any).code !== undefined && (res as any).code !== 0) {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
const e = res as any;
|
|
72
|
+
if (e.code === 41050) {
|
|
73
|
+
return jsonError('Permission denied. User visibility scope limits access to this user.');
|
|
74
|
+
}
|
|
75
|
+
return jsonError(`API Error: code=${e.code}, msg=${e.msg}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log.info('get_user: current user fetched successfully');
|
|
79
|
+
return json({ user: res.data });
|
|
80
|
+
} catch (invokeErr) {
|
|
81
|
+
// Handle 41050 error
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
if (invokeErr && typeof invokeErr === 'object' && (invokeErr as any).response?.data?.code === 41050) {
|
|
84
|
+
return jsonError('Permission denied. User visibility scope limits access to this user.');
|
|
85
|
+
}
|
|
86
|
+
throw invokeErr;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get specific user info
|
|
91
|
+
log.info(`get_user: fetching user ${p.user_id}`);
|
|
92
|
+
|
|
93
|
+
const userIdType = p.user_id_type || 'open_id';
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const res = await larkClient!.sdk.contact.v3.user.get(
|
|
97
|
+
{
|
|
98
|
+
path: { user_id: p.user_id },
|
|
99
|
+
params: { user_id_type: userIdType as 'open_id' | 'union_id' | 'user_id' },
|
|
100
|
+
},
|
|
101
|
+
opts
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
if ((res as any).code !== undefined && (res as any).code !== 0) {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
const e = res as any;
|
|
108
|
+
if (e.code === 41050) {
|
|
109
|
+
return jsonError('Permission denied. User visibility scope limits access to this user.');
|
|
110
|
+
}
|
|
111
|
+
return jsonError(`API Error: code=${e.code}, msg=${e.msg}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
log.info(`get_user: user ${p.user_id} fetched successfully`);
|
|
115
|
+
return json({ user: res.data?.user });
|
|
116
|
+
} catch (invokeErr) {
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
if (invokeErr && typeof invokeErr === 'object' && (invokeErr as any).response?.data?.code === 41050) {
|
|
119
|
+
return jsonError('Permission denied. User visibility scope limits access to this user.');
|
|
120
|
+
}
|
|
121
|
+
throw invokeErr;
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
log.debug('feishu_get_user tool registered');
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Common Tools Index
|
|
6
|
+
*
|
|
7
|
+
* User-related tools for Feishu/Lark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolRegistry } from '../index.js';
|
|
11
|
+
import { registerGetUserTool } from './get-user.js';
|
|
12
|
+
import { registerSearchUserTool } from './search-user.js';
|
|
13
|
+
import { logger } from '../../utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const log = logger('tools:common');
|
|
16
|
+
|
|
17
|
+
export function registerCommonTools(registry: ToolRegistry): void {
|
|
18
|
+
registerGetUserTool(registry);
|
|
19
|
+
registerSearchUserTool(registry);
|
|
20
|
+
|
|
21
|
+
log.info('Common tools registered', {
|
|
22
|
+
tools: ['feishu_get_user', 'feishu_search_user'].join(', '),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_search_user tool - Search for users.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { ToolRegistry } from '../index.js';
|
|
10
|
+
import { LarkClient } from '../../core/lark-client.js';
|
|
11
|
+
import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
|
|
12
|
+
import { json, jsonError, type ToolResult } from '../im/helpers.js';
|
|
13
|
+
import { logger } from '../../utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const log = logger('tools:common:search-user');
|
|
16
|
+
|
|
17
|
+
// Schemas
|
|
18
|
+
const searchUserSchema = {
|
|
19
|
+
query: z.string().describe('Search query (matches user name, phone, email)'),
|
|
20
|
+
page_size: z.number().min(1).max(200).optional().describe('Page size (default 20)'),
|
|
21
|
+
page_token: z.string().optional().describe('Pagination token'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
25
|
+
const { larkClient, config } = context;
|
|
26
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
27
|
+
const { appId, appSecret, brand } = config;
|
|
28
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
29
|
+
|
|
30
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
31
|
+
const tokens = await listStoredTokens(appId);
|
|
32
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
33
|
+
const userOpenId = tokens[0].userOpenId;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerSearchUserTool(registry: ToolRegistry): void {
|
|
44
|
+
registry.register({
|
|
45
|
+
name: 'feishu_search_user',
|
|
46
|
+
description: 'Search for Feishu users by name, phone, or email.\n\nRequires OAuth authorization.',
|
|
47
|
+
inputSchema: searchUserSchema,
|
|
48
|
+
handler: async (args, context) => {
|
|
49
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof searchUserSchema>>>;
|
|
50
|
+
const { larkClient } = context;
|
|
51
|
+
|
|
52
|
+
const tokenResult = await getAccessToken(context);
|
|
53
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
54
|
+
const accessToken = tokenResult;
|
|
55
|
+
|
|
56
|
+
log.info(`search_user: query="${p.query}", page_size=${p.page_size ?? 20}`);
|
|
57
|
+
|
|
58
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
59
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
60
|
+
|
|
61
|
+
// Build query parameters
|
|
62
|
+
const queryParams: Record<string, string> = {
|
|
63
|
+
query: p.query,
|
|
64
|
+
page_size: String(p.page_size ?? 20),
|
|
65
|
+
};
|
|
66
|
+
if (p.page_token) queryParams.page_token = p.page_token;
|
|
67
|
+
|
|
68
|
+
// Use direct request since SDK search API may not be fully typed
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
const res = await (larkClient!.sdk as any).request({
|
|
71
|
+
method: 'GET',
|
|
72
|
+
url: '/open-apis/search/v1/user',
|
|
73
|
+
params: queryParams,
|
|
74
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
75
|
+
}, opts);
|
|
76
|
+
|
|
77
|
+
// Check for API error
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
if ((res as any).code !== undefined && (res as any).code !== 0) {
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
return jsonError(`API Error: code=${(res as any).code}, msg=${(res as any).msg}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const data = res.data as any;
|
|
86
|
+
const users = data?.users ?? [];
|
|
87
|
+
|
|
88
|
+
log.info(`search_user: found ${users.length} users`);
|
|
89
|
+
|
|
90
|
+
return json({
|
|
91
|
+
users,
|
|
92
|
+
has_more: data?.has_more ?? false,
|
|
93
|
+
page_token: data?.page_token,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
log.debug('feishu_search_user tool registered');
|
|
99
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_create_doc tool - Create a new docx document from Markdown.
|
|
6
|
+
*
|
|
7
|
+
* Creates a new Feishu document from Markdown content.
|
|
8
|
+
* Supports async task status checking via task_id.
|
|
9
|
+
*
|
|
10
|
+
* Adapted from openclaw-lark for MCP Server architecture.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import type { ToolRegistry } from '../index.js';
|
|
15
|
+
import { LarkClient } from '../../core/lark-client.js';
|
|
16
|
+
import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
|
|
17
|
+
import { callMcpTool, json, jsonError, processMcpResult, type ToolResult } from './shared.js';
|
|
18
|
+
import { logger } from '../../utils/logger.js';
|
|
19
|
+
|
|
20
|
+
const log = logger('tools:doc:create');
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Input schema
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const createDocSchema = {
|
|
27
|
+
markdown: z.string().optional().describe('Markdown content for the document'),
|
|
28
|
+
title: z.string().optional().describe('Document title'),
|
|
29
|
+
folder_token: z.string().optional().describe('Parent folder token (optional)'),
|
|
30
|
+
wiki_node: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Wiki node token or URL (optional, creates document under this node)'),
|
|
34
|
+
wiki_space: z.string().optional().describe('Wiki space ID (optional, special value: my_library)'),
|
|
35
|
+
task_id: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Async task ID. If provided, queries task status instead of creating a new document'),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Validation
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function validateCreateDocParams(p: Record<string, unknown>): void {
|
|
46
|
+
// If task_id is provided, we're just querying status
|
|
47
|
+
if (p.task_id) return;
|
|
48
|
+
|
|
49
|
+
// For creating new doc, markdown and title are required
|
|
50
|
+
if (!p.markdown || !p.title) {
|
|
51
|
+
throw new Error('create-doc: When not providing task_id, markdown and title are required');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Only one of folder_token, wiki_node, wiki_space can be provided
|
|
55
|
+
const flags = [p.folder_token, p.wiki_node, p.wiki_space].filter(Boolean);
|
|
56
|
+
if (flags.length > 1) {
|
|
57
|
+
throw new Error('create-doc: folder_token / wiki_node / wiki_space are mutually exclusive, provide only one');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Tool registration
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register the feishu_create_doc tool.
|
|
67
|
+
*/
|
|
68
|
+
export function registerCreateDocTool(registry: ToolRegistry): void {
|
|
69
|
+
registry.register({
|
|
70
|
+
name: 'feishu_create_doc',
|
|
71
|
+
description: [
|
|
72
|
+
'Create a new Feishu docx document from Markdown content.',
|
|
73
|
+
'',
|
|
74
|
+
'Usage:',
|
|
75
|
+
'- Provide markdown and title to create a new document',
|
|
76
|
+
'- Provide task_id to check async task status',
|
|
77
|
+
'- Optionally specify folder_token, wiki_node, or wiki_space for document location',
|
|
78
|
+
'',
|
|
79
|
+
'Parameters:',
|
|
80
|
+
'- markdown: Markdown content for the document (required for new doc)',
|
|
81
|
+
'- title: Document title (required for new doc)',
|
|
82
|
+
'- folder_token: Parent folder token (optional)',
|
|
83
|
+
'- wiki_node: Wiki node token or URL (optional)',
|
|
84
|
+
'- wiki_space: Wiki space ID (optional)',
|
|
85
|
+
'- task_id: Async task ID for status check (optional)',
|
|
86
|
+
'',
|
|
87
|
+
'Returns:',
|
|
88
|
+
'- For new doc: { task_id, doc_id } or completed document info',
|
|
89
|
+
'- For task_id query: { status, result? }',
|
|
90
|
+
'',
|
|
91
|
+
'Requires OAuth authorization (use feishu_oauth tool first).',
|
|
92
|
+
].join('\n'),
|
|
93
|
+
inputSchema: createDocSchema,
|
|
94
|
+
handler: async (args, context) => {
|
|
95
|
+
return handleCreateDoc(args, context);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
log.debug('feishu_create_doc tool registered');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Handler
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
async function handleCreateDoc(
|
|
107
|
+
args: unknown,
|
|
108
|
+
context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
|
|
109
|
+
): Promise<ToolResult> {
|
|
110
|
+
const p = args as Record<string, unknown>;
|
|
111
|
+
const { larkClient, config } = context;
|
|
112
|
+
|
|
113
|
+
if (!larkClient) {
|
|
114
|
+
return jsonError('LarkClient not initialized. Check FEISHU_APP_ID and FEISHU_APP_SECRET.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { appId, appSecret, brand } = config;
|
|
118
|
+
if (!appId || !appSecret) {
|
|
119
|
+
return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate parameters
|
|
123
|
+
try {
|
|
124
|
+
validateCreateDocParams(p);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return jsonError(err instanceof Error ? err.message : String(err));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get the first stored user token
|
|
130
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
131
|
+
const tokens = await listStoredTokens(appId);
|
|
132
|
+
if (tokens.length === 0) {
|
|
133
|
+
return jsonError(
|
|
134
|
+
'No user authorization found. Please use the feishu_oauth tool with action="authorize" to authorize a user first.'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const userOpenId = tokens[0].userOpenId;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const accessToken = await getValidAccessToken({
|
|
142
|
+
userOpenId,
|
|
143
|
+
appId,
|
|
144
|
+
appSecret,
|
|
145
|
+
domain: brand ?? 'feishu',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
log.info('Creating document', {
|
|
149
|
+
title: p.title,
|
|
150
|
+
has_markdown: !!p.markdown,
|
|
151
|
+
folder_token: p.folder_token,
|
|
152
|
+
wiki_node: p.wiki_node,
|
|
153
|
+
task_id: p.task_id,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Build MCP tool arguments
|
|
157
|
+
const mcpArgs: Record<string, unknown> = {};
|
|
158
|
+
if (p.markdown) mcpArgs.markdown = p.markdown;
|
|
159
|
+
if (p.title) mcpArgs.title = p.title;
|
|
160
|
+
if (p.folder_token) mcpArgs.folder_token = p.folder_token;
|
|
161
|
+
if (p.wiki_node) mcpArgs.wiki_node = p.wiki_node;
|
|
162
|
+
if (p.wiki_space) mcpArgs.wiki_space = p.wiki_space;
|
|
163
|
+
if (p.task_id) mcpArgs.task_id = p.task_id;
|
|
164
|
+
|
|
165
|
+
// Generate a unique tool call ID
|
|
166
|
+
const toolCallId = `create-doc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
167
|
+
|
|
168
|
+
// Call the MCP endpoint
|
|
169
|
+
const result = await callMcpTool('create-doc', mcpArgs, toolCallId, accessToken);
|
|
170
|
+
|
|
171
|
+
log.info('Document created/task queried', { result });
|
|
172
|
+
|
|
173
|
+
return processMcpResult(result);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (err instanceof NeedAuthorizationError) {
|
|
176
|
+
return jsonError(
|
|
177
|
+
`User authorization required or expired. Please use feishu_oauth tool with action="authorize" to re-authorize.`,
|
|
178
|
+
{ userOpenId }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
log.error('Create document failed', { error: err instanceof Error ? err.message : String(err) });
|
|
182
|
+
return jsonError(err instanceof Error ? err.message : String(err));
|
|
183
|
+
}
|
|
184
|
+
}
|