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,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_wiki_space tool - Manage Feishu Wiki spaces.
|
|
6
|
+
*
|
|
7
|
+
* Actions: list, get, create
|
|
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:wiki:space');
|
|
19
|
+
|
|
20
|
+
// Schemas
|
|
21
|
+
const listActionSchema = {
|
|
22
|
+
action: z.literal('list').describe('List wiki spaces'),
|
|
23
|
+
page_size: z.number().min(1).max(50).optional().describe('Page size (default 10)'),
|
|
24
|
+
page_token: z.string().optional().describe('Pagination token'),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getActionSchema = {
|
|
28
|
+
action: z.literal('get').describe('Get a wiki space'),
|
|
29
|
+
space_id: z.string().describe('Space ID'),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createActionSchema = {
|
|
33
|
+
action: z.literal('create').describe('Create a wiki space'),
|
|
34
|
+
name: z.string().optional().describe('Space name'),
|
|
35
|
+
description: z.string().optional().describe('Space description'),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
39
|
+
const { larkClient, config } = context;
|
|
40
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
41
|
+
const { appId, appSecret, brand } = config;
|
|
42
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
43
|
+
|
|
44
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
45
|
+
const tokens = await listStoredTokens(appId);
|
|
46
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
47
|
+
const userOpenId = tokens[0].userOpenId;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function registerWikiSpaceTool(registry: ToolRegistry): void {
|
|
58
|
+
registry.register({
|
|
59
|
+
name: 'feishu_wiki_space_list',
|
|
60
|
+
description: 'List Feishu wiki spaces.\n\nRequires OAuth authorization.',
|
|
61
|
+
inputSchema: listActionSchema,
|
|
62
|
+
handler: async (args, context) => {
|
|
63
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof listActionSchema>>>;
|
|
64
|
+
const { larkClient } = context;
|
|
65
|
+
|
|
66
|
+
const tokenResult = await getAccessToken(context);
|
|
67
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
68
|
+
const accessToken = tokenResult;
|
|
69
|
+
|
|
70
|
+
log.info(`list: page_size=${p.page_size ?? 10}`);
|
|
71
|
+
|
|
72
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
73
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
74
|
+
|
|
75
|
+
const res = await larkClient!.sdk.wiki.space.list(
|
|
76
|
+
{ params: { page_size: p.page_size as any, page_token: p.page_token } },
|
|
77
|
+
opts
|
|
78
|
+
);
|
|
79
|
+
assertLarkOk(res);
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
const data = res.data as any;
|
|
83
|
+
|
|
84
|
+
return json({
|
|
85
|
+
spaces: data?.items,
|
|
86
|
+
has_more: data?.has_more,
|
|
87
|
+
page_token: data?.page_token,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
registry.register({
|
|
93
|
+
name: 'feishu_wiki_space_get',
|
|
94
|
+
description: 'Get a Feishu wiki space by ID.\n\nRequires OAuth authorization.',
|
|
95
|
+
inputSchema: getActionSchema,
|
|
96
|
+
handler: async (args, context) => {
|
|
97
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof getActionSchema>>>;
|
|
98
|
+
const { larkClient } = context;
|
|
99
|
+
|
|
100
|
+
const tokenResult = await getAccessToken(context);
|
|
101
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
102
|
+
const accessToken = tokenResult;
|
|
103
|
+
|
|
104
|
+
log.info(`get: space_id=${p.space_id}`);
|
|
105
|
+
|
|
106
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
107
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
108
|
+
|
|
109
|
+
const res = await larkClient!.sdk.wiki.space.get(
|
|
110
|
+
{ path: { space_id: p.space_id } },
|
|
111
|
+
opts
|
|
112
|
+
);
|
|
113
|
+
assertLarkOk(res);
|
|
114
|
+
|
|
115
|
+
return json({ space: res.data?.space });
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
registry.register({
|
|
120
|
+
name: 'feishu_wiki_space_create',
|
|
121
|
+
description: 'Create a Feishu wiki space.\n\nRequires OAuth authorization.',
|
|
122
|
+
inputSchema: createActionSchema,
|
|
123
|
+
handler: async (args, context) => {
|
|
124
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof createActionSchema>>>;
|
|
125
|
+
const { larkClient } = context;
|
|
126
|
+
|
|
127
|
+
const tokenResult = await getAccessToken(context);
|
|
128
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
129
|
+
const accessToken = tokenResult;
|
|
130
|
+
|
|
131
|
+
log.info(`create: name=${p.name ?? '(empty)'}`);
|
|
132
|
+
|
|
133
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
134
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
135
|
+
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
const data: any = {};
|
|
138
|
+
if (p.name) data.name = p.name;
|
|
139
|
+
if (p.description) data.description = p.description;
|
|
140
|
+
|
|
141
|
+
const res = await larkClient!.sdk.wiki.space.create({ data }, opts);
|
|
142
|
+
assertLarkOk(res);
|
|
143
|
+
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
const spaceData = res.data?.space as any;
|
|
146
|
+
|
|
147
|
+
return json({ space: res.data?.space, space_id: spaceData?.space_id });
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
log.debug('feishu_wiki_space tools registered');
|
|
152
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Simple logger utility for the cc-lark MCP Server.
|
|
6
|
+
*
|
|
7
|
+
* Provides prefixed logging with ANSI color support for console output.
|
|
8
|
+
* Adapted from openclaw-lark lark-logger for MCP Server architecture.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Public interface
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface Logger {
|
|
16
|
+
readonly subsystem: string;
|
|
17
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
18
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
19
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
20
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
21
|
+
child(name: string): Logger;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// ANSI colors
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const CYAN = '\x1b[36m';
|
|
29
|
+
const YELLOW = '\x1b[33m';
|
|
30
|
+
const RED = '\x1b[31m';
|
|
31
|
+
const GRAY = '\x1b[90m';
|
|
32
|
+
const RESET = '\x1b[0m';
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Message formatting
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Format a log message with optional metadata.
|
|
40
|
+
*
|
|
41
|
+
* @param prefix - Log prefix (subsystem tag)
|
|
42
|
+
* @param message - Log message
|
|
43
|
+
* @param meta - Optional metadata to include
|
|
44
|
+
* @returns Formatted message string
|
|
45
|
+
*/
|
|
46
|
+
function formatMessage(
|
|
47
|
+
prefix: string,
|
|
48
|
+
message: string,
|
|
49
|
+
meta: Record<string, unknown> | undefined
|
|
50
|
+
): string {
|
|
51
|
+
if (!meta || Object.keys(meta).length === 0) {
|
|
52
|
+
return `${prefix} ${message}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parts = Object.entries(meta)
|
|
56
|
+
.map(([k, v]) => {
|
|
57
|
+
if (v === undefined || v === null) return null;
|
|
58
|
+
if (typeof v === 'object') return `${k}=${JSON.stringify(v)}`;
|
|
59
|
+
return `${k}=${v}`;
|
|
60
|
+
})
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
|
|
63
|
+
return parts.length > 0 ? `${prefix} ${message} (${parts.join(', ')})` : `${prefix} ${message}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Logger implementation
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a new logger instance.
|
|
72
|
+
*
|
|
73
|
+
* @param subsystem - Subsystem name for log prefix
|
|
74
|
+
* @returns Logger instance
|
|
75
|
+
*/
|
|
76
|
+
function createLogger(subsystem: string): Logger {
|
|
77
|
+
const tag = `cc-lark/${subsystem}`;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
subsystem,
|
|
81
|
+
|
|
82
|
+
debug(message: string, meta?: Record<string, unknown>): void {
|
|
83
|
+
const formatted = formatMessage(tag, message, meta);
|
|
84
|
+
console.debug(`${GRAY}[DEBUG]${RESET}`, formatted);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
info(message: string, meta?: Record<string, unknown>): void {
|
|
88
|
+
const formatted = formatMessage(tag, message, meta);
|
|
89
|
+
console.log(`${CYAN}[INFO]${RESET}`, formatted);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
warn(message: string, meta?: Record<string, unknown>): void {
|
|
93
|
+
const formatted = formatMessage(tag, message, meta);
|
|
94
|
+
console.warn(`${YELLOW}[WARN]${RESET}`, formatted);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
error(message: string, meta?: Record<string, unknown>): void {
|
|
98
|
+
const formatted = formatMessage(tag, message, meta);
|
|
99
|
+
console.error(`${RED}[ERROR]${RESET}`, formatted);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
child(name: string): Logger {
|
|
103
|
+
return createLogger(`${subsystem}/${name}`);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Public factory
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a logger for a subsystem.
|
|
114
|
+
*
|
|
115
|
+
* @param subsystem - Name of the subsystem (e.g., "api", "tools", "config")
|
|
116
|
+
* @returns Logger instance
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const log = logger("api");
|
|
121
|
+
* log.info("Request started", { method: "GET", path: "/users" });
|
|
122
|
+
* log.error("Request failed", { error: "Connection timeout" });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function logger(subsystem: string): Logger {
|
|
126
|
+
return createLogger(subsystem);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Default logger instance for general use.
|
|
131
|
+
*/
|
|
132
|
+
export const defaultLogger = logger('core');
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Tests for the configuration module.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
loadConfig,
|
|
11
|
+
validateConfig,
|
|
12
|
+
loadAndValidateConfig,
|
|
13
|
+
hasUserAccessToken,
|
|
14
|
+
getApiBaseUrl,
|
|
15
|
+
ENV_VARS,
|
|
16
|
+
} from '../../src/core/config.js';
|
|
17
|
+
|
|
18
|
+
describe('config', () => {
|
|
19
|
+
const originalEnv = { ...process.env };
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Clear relevant environment variables before each test
|
|
23
|
+
Object.values(ENV_VARS).forEach(key => {
|
|
24
|
+
delete process.env[key];
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
// Restore original environment
|
|
30
|
+
Object.values(ENV_VARS).forEach(key => {
|
|
31
|
+
delete process.env[key];
|
|
32
|
+
});
|
|
33
|
+
Object.entries(originalEnv).forEach(([key, value]) => {
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
process.env[key] = value;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('loadConfig', () => {
|
|
41
|
+
it('should load configuration from environment variables', () => {
|
|
42
|
+
process.env[ENV_VARS.APP_ID] = 'test-app-id';
|
|
43
|
+
process.env[ENV_VARS.APP_SECRET] = 'test-app-secret';
|
|
44
|
+
process.env[ENV_VARS.USER_ACCESS_TOKEN] = 'test-user-token';
|
|
45
|
+
process.env[ENV_VARS.BRAND] = 'lark';
|
|
46
|
+
|
|
47
|
+
const config = loadConfig();
|
|
48
|
+
|
|
49
|
+
expect(config.appId).toBe('test-app-id');
|
|
50
|
+
expect(config.appSecret).toBe('test-app-secret');
|
|
51
|
+
expect(config.userAccessToken).toBe('test-user-token');
|
|
52
|
+
expect(config.brand).toBe('lark');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return empty strings for missing required values', () => {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
|
|
58
|
+
expect(config.appId).toBe('');
|
|
59
|
+
expect(config.appSecret).toBe('');
|
|
60
|
+
expect(config.userAccessToken).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should default brand to feishu if not specified', () => {
|
|
64
|
+
process.env[ENV_VARS.APP_ID] = 'test-app-id';
|
|
65
|
+
process.env[ENV_VARS.APP_SECRET] = 'test-app-secret';
|
|
66
|
+
|
|
67
|
+
const config = loadConfig();
|
|
68
|
+
|
|
69
|
+
expect(config.brand).toBe('feishu');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should load optional encrypt key and verification token', () => {
|
|
73
|
+
process.env[ENV_VARS.APP_ID] = 'test-app-id';
|
|
74
|
+
process.env[ENV_VARS.APP_SECRET] = 'test-app-secret';
|
|
75
|
+
process.env[ENV_VARS.ENCRYPT_KEY] = 'test-encrypt-key';
|
|
76
|
+
process.env[ENV_VARS.VERIFICATION_TOKEN] = 'test-verification-token';
|
|
77
|
+
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
|
|
80
|
+
expect(config.encryptKey).toBe('test-encrypt-key');
|
|
81
|
+
expect(config.verificationToken).toBe('test-verification-token');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('validateConfig', () => {
|
|
86
|
+
it('should pass validation with valid config', () => {
|
|
87
|
+
const config = {
|
|
88
|
+
appId: 'test-app-id',
|
|
89
|
+
appSecret: 'test-app-secret',
|
|
90
|
+
brand: 'feishu' as const,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = validateConfig(config);
|
|
94
|
+
|
|
95
|
+
expect(result.valid).toBe(true);
|
|
96
|
+
expect(result.errors).toHaveLength(0);
|
|
97
|
+
expect(result.config).toEqual(config);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should fail validation when appId is missing', () => {
|
|
101
|
+
const config = {
|
|
102
|
+
appId: '',
|
|
103
|
+
appSecret: 'test-app-secret',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = validateConfig(config);
|
|
107
|
+
|
|
108
|
+
expect(result.valid).toBe(false);
|
|
109
|
+
expect(result.errors).toContain(`Missing required environment variable: ${ENV_VARS.APP_ID}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should fail validation when appSecret is missing', () => {
|
|
113
|
+
const config = {
|
|
114
|
+
appId: 'test-app-id',
|
|
115
|
+
appSecret: '',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const result = validateConfig(config);
|
|
119
|
+
|
|
120
|
+
expect(result.valid).toBe(false);
|
|
121
|
+
expect(result.errors).toContain(`Missing required environment variable: ${ENV_VARS.APP_SECRET}`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should fail validation for invalid brand value', () => {
|
|
125
|
+
const config = {
|
|
126
|
+
appId: 'test-app-id',
|
|
127
|
+
appSecret: 'test-app-secret',
|
|
128
|
+
brand: 'invalid-brand' as const,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const result = validateConfig(config);
|
|
132
|
+
|
|
133
|
+
expect(result.valid).toBe(false);
|
|
134
|
+
expect(result.errors[0]).toContain('Invalid FEISHU_BRAND value');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should accept custom HTTPS URL as brand', () => {
|
|
138
|
+
const config = {
|
|
139
|
+
appId: 'test-app-id',
|
|
140
|
+
appSecret: 'test-app-secret',
|
|
141
|
+
brand: 'https://custom.lark.com' as const,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = validateConfig(config);
|
|
145
|
+
|
|
146
|
+
expect(result.valid).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should accept "lark" as brand', () => {
|
|
150
|
+
const config = {
|
|
151
|
+
appId: 'test-app-id',
|
|
152
|
+
appSecret: 'test-app-secret',
|
|
153
|
+
brand: 'lark' as const,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const result = validateConfig(config);
|
|
157
|
+
|
|
158
|
+
expect(result.valid).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should report multiple validation errors', () => {
|
|
162
|
+
const config = {
|
|
163
|
+
appId: '',
|
|
164
|
+
appSecret: '',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = validateConfig(config);
|
|
168
|
+
|
|
169
|
+
expect(result.valid).toBe(false);
|
|
170
|
+
expect(result.errors).toHaveLength(2);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('loadAndValidateConfig', () => {
|
|
175
|
+
it('should load and return valid config', () => {
|
|
176
|
+
process.env[ENV_VARS.APP_ID] = 'test-app-id';
|
|
177
|
+
process.env[ENV_VARS.APP_SECRET] = 'test-app-secret';
|
|
178
|
+
|
|
179
|
+
const config = loadAndValidateConfig();
|
|
180
|
+
|
|
181
|
+
expect(config.appId).toBe('test-app-id');
|
|
182
|
+
expect(config.appSecret).toBe('test-app-secret');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should throw error for invalid config', () => {
|
|
186
|
+
expect(() => loadAndValidateConfig()).toThrow('Invalid configuration');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('hasUserAccessToken', () => {
|
|
191
|
+
it('should return true when user access token is set', () => {
|
|
192
|
+
const config = {
|
|
193
|
+
appId: 'test-app-id',
|
|
194
|
+
appSecret: 'test-app-secret',
|
|
195
|
+
userAccessToken: 'test-token',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
expect(hasUserAccessToken(config)).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should return false when user access token is not set', () => {
|
|
202
|
+
const config = {
|
|
203
|
+
appId: 'test-app-id',
|
|
204
|
+
appSecret: 'test-app-secret',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
expect(hasUserAccessToken(config)).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return false when user access token is empty string', () => {
|
|
211
|
+
const config = {
|
|
212
|
+
appId: 'test-app-id',
|
|
213
|
+
appSecret: 'test-app-secret',
|
|
214
|
+
userAccessToken: '',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
expect(hasUserAccessToken(config)).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('getApiBaseUrl', () => {
|
|
222
|
+
it('should return Feishu URL for feishu brand', () => {
|
|
223
|
+
expect(getApiBaseUrl('feishu')).toBe('https://open.feishu.cn/open-apis');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should return Lark URL for lark brand', () => {
|
|
227
|
+
expect(getApiBaseUrl('lark')).toBe('https://open.larksuite.com/open-apis');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should return custom URL for custom brand', () => {
|
|
231
|
+
expect(getApiBaseUrl('https://custom.api.com')).toBe('https://custom.api.com');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should default to feishu when no brand specified', () => {
|
|
235
|
+
expect(getApiBaseUrl()).toBe('https://open.feishu.cn/open-apis');
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|