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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Search Tools Index
|
|
6
|
+
*
|
|
7
|
+
* Search tools for Feishu/Lark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolRegistry } from '../index.js';
|
|
11
|
+
import { registerSearchDocWikiTool } from './doc-search.js';
|
|
12
|
+
import { logger } from '../../utils/logger.js';
|
|
13
|
+
|
|
14
|
+
const log = logger('tools:search');
|
|
15
|
+
|
|
16
|
+
export function registerSearchTools(registry: ToolRegistry): void {
|
|
17
|
+
registerSearchDocWikiTool(registry);
|
|
18
|
+
|
|
19
|
+
log.info('Search tools registered', {
|
|
20
|
+
tools: ['feishu_search_doc_wiki'].join(', '),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Sheets Tools Index
|
|
6
|
+
*
|
|
7
|
+
* Spreadsheet tools for Feishu/Lark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolRegistry } from '../index.js';
|
|
11
|
+
import { registerSheetTool } from './sheet.js';
|
|
12
|
+
import { logger } from '../../utils/logger.js';
|
|
13
|
+
|
|
14
|
+
const log = logger('tools:sheets');
|
|
15
|
+
|
|
16
|
+
export function registerSheetsTools(registry: ToolRegistry): void {
|
|
17
|
+
registerSheetTool(registry);
|
|
18
|
+
|
|
19
|
+
log.info('Sheets tools registered', {
|
|
20
|
+
tools: ['feishu_sheet_info', 'feishu_sheet_read', 'feishu_sheet_write', 'feishu_sheet_append', 'feishu_sheet_create'].join(', '),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_sheet tool - Manage Feishu Spreadsheets.
|
|
6
|
+
*
|
|
7
|
+
* Actions: info, read, write, append, 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:sheets:sheet');
|
|
19
|
+
|
|
20
|
+
const MAX_READ_ROWS = 200;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse spreadsheet URL to extract token and optional sheet ID.
|
|
24
|
+
*/
|
|
25
|
+
function parseSheetUrl(url: string): { token: string; sheetId?: string } | null {
|
|
26
|
+
try {
|
|
27
|
+
const u = new URL(url);
|
|
28
|
+
const match = u.pathname.match(/\/(?:sheets|wiki)\/([^/?#]+)/);
|
|
29
|
+
if (!match) return null;
|
|
30
|
+
return {
|
|
31
|
+
token: match[1],
|
|
32
|
+
sheetId: u.searchParams.get('sheet') || undefined,
|
|
33
|
+
};
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert column number to letter (A, B, ..., Z, AA, AB, ...).
|
|
41
|
+
*/
|
|
42
|
+
function colLetter(n: number): string {
|
|
43
|
+
let result = '';
|
|
44
|
+
while (n > 0) {
|
|
45
|
+
n--;
|
|
46
|
+
result = String.fromCharCode(65 + (n % 26)) + result;
|
|
47
|
+
n = Math.floor(n / 26);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Schemas
|
|
53
|
+
const infoActionSchema = {
|
|
54
|
+
action: z.literal('info').describe('Get spreadsheet info'),
|
|
55
|
+
spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
|
|
56
|
+
url: z.string().optional().describe('Spreadsheet URL'),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const readActionSchema = {
|
|
60
|
+
action: z.literal('read').describe('Read data from a spreadsheet'),
|
|
61
|
+
spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
|
|
62
|
+
url: z.string().optional().describe('Spreadsheet URL'),
|
|
63
|
+
range: z.string().optional().describe('Range to read (e.g., Sheet1!A1:D10)'),
|
|
64
|
+
sheet_id: z.string().optional().describe('Sheet ID'),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const writeActionSchema = {
|
|
68
|
+
action: z.literal('write').describe('Write data to a spreadsheet (overwrites existing data)'),
|
|
69
|
+
spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
|
|
70
|
+
url: z.string().optional().describe('Spreadsheet URL'),
|
|
71
|
+
range: z.string().optional().describe('Range to write (e.g., Sheet1!A1)'),
|
|
72
|
+
sheet_id: z.string().optional().describe('Sheet ID'),
|
|
73
|
+
values: z.array(z.array(z.any())).describe('Data to write (2D array)'),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const appendActionSchema = {
|
|
77
|
+
action: z.literal('append').describe('Append data to a spreadsheet'),
|
|
78
|
+
spreadsheet_token: z.string().optional().describe('Spreadsheet token'),
|
|
79
|
+
url: z.string().optional().describe('Spreadsheet URL'),
|
|
80
|
+
range: z.string().optional().describe('Range to append to (e.g., Sheet1)'),
|
|
81
|
+
sheet_id: z.string().optional().describe('Sheet ID'),
|
|
82
|
+
values: z.array(z.array(z.any())).describe('Data to append (2D array)'),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const createActionSchema = {
|
|
86
|
+
action: z.literal('create').describe('Create a new spreadsheet'),
|
|
87
|
+
title: z.string().describe('Spreadsheet title'),
|
|
88
|
+
folder_token: z.string().optional().describe('Folder token (optional)'),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
|
|
92
|
+
const { larkClient, config } = context;
|
|
93
|
+
if (!larkClient) return jsonError('LarkClient not initialized.');
|
|
94
|
+
const { appId, appSecret, brand } = config;
|
|
95
|
+
if (!appId || !appSecret) return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
|
|
96
|
+
|
|
97
|
+
const { listStoredTokens } = await import('../../core/token-store.js');
|
|
98
|
+
const tokens = await listStoredTokens(appId);
|
|
99
|
+
if (tokens.length === 0) return jsonError('No user authorization found.');
|
|
100
|
+
const userOpenId = tokens[0].userOpenId;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return await getValidAccessToken({ userOpenId, appId, appSecret, domain: brand ?? 'feishu' });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err instanceof NeedAuthorizationError) return jsonError('User authorization expired.');
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function resolveToken(
|
|
111
|
+
p: { url?: string; spreadsheet_token?: string },
|
|
112
|
+
larkClient: LarkClient,
|
|
113
|
+
accessToken: string
|
|
114
|
+
): Promise<{ token: string; urlSheetId?: string }> {
|
|
115
|
+
let token: string;
|
|
116
|
+
let urlSheetId: string | undefined;
|
|
117
|
+
|
|
118
|
+
if (p.spreadsheet_token) {
|
|
119
|
+
token = p.spreadsheet_token;
|
|
120
|
+
} else if (p.url) {
|
|
121
|
+
const parsed = parseSheetUrl(p.url);
|
|
122
|
+
if (!parsed) {
|
|
123
|
+
throw new Error(`Failed to parse spreadsheet_token from URL: ${p.url}`);
|
|
124
|
+
}
|
|
125
|
+
token = parsed.token;
|
|
126
|
+
urlSheetId = parsed.sheetId;
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('url or spreadsheet_token is required');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { token, urlSheetId };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function registerSheetTool(registry: ToolRegistry): void {
|
|
135
|
+
// Info
|
|
136
|
+
registry.register({
|
|
137
|
+
name: 'feishu_sheet_info',
|
|
138
|
+
description: 'Get Feishu spreadsheet info and sheet list.\n\nRequires OAuth authorization.',
|
|
139
|
+
inputSchema: infoActionSchema,
|
|
140
|
+
handler: async (args, context) => {
|
|
141
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof infoActionSchema>>>;
|
|
142
|
+
const { larkClient } = context;
|
|
143
|
+
|
|
144
|
+
const tokenResult = await getAccessToken(context);
|
|
145
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
146
|
+
const accessToken = tokenResult;
|
|
147
|
+
|
|
148
|
+
const { token } = await resolveToken(p, larkClient!, accessToken);
|
|
149
|
+
log.info(`info: token=${token}`);
|
|
150
|
+
|
|
151
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
152
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
153
|
+
|
|
154
|
+
const [spreadsheetRes, sheetsRes] = await Promise.all([
|
|
155
|
+
larkClient!.sdk.sheets.spreadsheet.get({ path: { spreadsheet_token: token } }, opts),
|
|
156
|
+
larkClient!.sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts),
|
|
157
|
+
]);
|
|
158
|
+
assertLarkOk(spreadsheetRes);
|
|
159
|
+
assertLarkOk(sheetsRes);
|
|
160
|
+
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
const spreadsheet = spreadsheetRes.data?.spreadsheet as any;
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
const sheets = (sheetsRes.data?.sheets ?? []).map((s: any) => ({
|
|
165
|
+
sheet_id: s.sheet_id,
|
|
166
|
+
title: s.title,
|
|
167
|
+
index: s.index,
|
|
168
|
+
row_count: s.grid_properties?.row_count,
|
|
169
|
+
column_count: s.grid_properties?.column_count,
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
return json({
|
|
173
|
+
title: spreadsheet?.title,
|
|
174
|
+
spreadsheet_token: token,
|
|
175
|
+
url: `https://www.feishu.cn/sheets/${token}`,
|
|
176
|
+
sheets,
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Read
|
|
182
|
+
registry.register({
|
|
183
|
+
name: 'feishu_sheet_read',
|
|
184
|
+
description: 'Read data from a Feishu spreadsheet.\n\nRequires OAuth authorization.',
|
|
185
|
+
inputSchema: readActionSchema,
|
|
186
|
+
handler: async (args, context) => {
|
|
187
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof readActionSchema>>>;
|
|
188
|
+
const { larkClient } = context;
|
|
189
|
+
|
|
190
|
+
const tokenResult = await getAccessToken(context);
|
|
191
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
192
|
+
const accessToken = tokenResult;
|
|
193
|
+
|
|
194
|
+
const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
|
|
195
|
+
let range = p.range;
|
|
196
|
+
if (!range && (p.sheet_id || urlSheetId)) {
|
|
197
|
+
range = p.sheet_id || urlSheetId;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!range) {
|
|
201
|
+
// Get first sheet
|
|
202
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
203
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
204
|
+
const sheetsRes = await larkClient!.sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts);
|
|
205
|
+
assertLarkOk(sheetsRes);
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
207
|
+
const firstSheet = (sheetsRes.data?.sheets ?? [])[0] as any;
|
|
208
|
+
if (!firstSheet?.sheet_id) {
|
|
209
|
+
return jsonError('Spreadsheet has no worksheets');
|
|
210
|
+
}
|
|
211
|
+
range = firstSheet.sheet_id;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
log.info(`read: token=${token}, range=${range}`);
|
|
215
|
+
|
|
216
|
+
// Use direct API call for reading values
|
|
217
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
218
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
219
|
+
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
const res = await (larkClient!.sdk as any).request({
|
|
222
|
+
method: 'GET',
|
|
223
|
+
url: `/open-apis/sheets/v2/spreadsheets/${token}/values/${encodeURIComponent(range!)}`,
|
|
224
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
225
|
+
}, opts);
|
|
226
|
+
|
|
227
|
+
if (res.code && res.code !== 0) {
|
|
228
|
+
return jsonError(res.msg || `API error: ${res.code}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const valueRange = res.data?.valueRange;
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
+
let values = valueRange?.values as any[][] | undefined;
|
|
234
|
+
|
|
235
|
+
// Truncate if needed
|
|
236
|
+
let truncated = false;
|
|
237
|
+
let totalRows = values?.length ?? 0;
|
|
238
|
+
if (values && values.length > MAX_READ_ROWS) {
|
|
239
|
+
values = values.slice(0, MAX_READ_ROWS);
|
|
240
|
+
truncated = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return json({
|
|
244
|
+
range: valueRange?.range,
|
|
245
|
+
values,
|
|
246
|
+
...(truncated ? { truncated: true, total_rows: totalRows, hint: `Data exceeds ${MAX_READ_ROWS} rows, truncated.` } : {}),
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Write
|
|
252
|
+
registry.register({
|
|
253
|
+
name: 'feishu_sheet_write',
|
|
254
|
+
description: 'Write data to a Feishu spreadsheet (overwrites existing data).\n\nRequires OAuth authorization.',
|
|
255
|
+
inputSchema: writeActionSchema,
|
|
256
|
+
handler: async (args, context) => {
|
|
257
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof writeActionSchema>>>;
|
|
258
|
+
const { larkClient } = context;
|
|
259
|
+
|
|
260
|
+
const tokenResult = await getAccessToken(context);
|
|
261
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
262
|
+
const accessToken = tokenResult;
|
|
263
|
+
|
|
264
|
+
const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
|
|
265
|
+
let range = p.range;
|
|
266
|
+
if (!range && (p.sheet_id || urlSheetId)) {
|
|
267
|
+
range = p.sheet_id || urlSheetId;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
log.info(`write: token=${token}, range=${range}, rows=${p.values?.length}`);
|
|
271
|
+
|
|
272
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
273
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
274
|
+
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
const res = await (larkClient!.sdk as any).request({
|
|
277
|
+
method: 'PUT',
|
|
278
|
+
url: `/open-apis/sheets/v2/spreadsheets/${token}/values`,
|
|
279
|
+
data: { valueRange: { range, values: p.values } },
|
|
280
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
281
|
+
}, opts);
|
|
282
|
+
|
|
283
|
+
if (res.code && res.code !== 0) {
|
|
284
|
+
return jsonError(res.msg || `API error: ${res.code}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return json({
|
|
288
|
+
updated_range: res.data?.updatedRange,
|
|
289
|
+
updated_rows: res.data?.updatedRows,
|
|
290
|
+
updated_columns: res.data?.updatedColumns,
|
|
291
|
+
updated_cells: res.data?.updatedCells,
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Append
|
|
297
|
+
registry.register({
|
|
298
|
+
name: 'feishu_sheet_append',
|
|
299
|
+
description: 'Append data to a Feishu spreadsheet.\n\nRequires OAuth authorization.',
|
|
300
|
+
inputSchema: appendActionSchema,
|
|
301
|
+
handler: async (args, context) => {
|
|
302
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof appendActionSchema>>>;
|
|
303
|
+
const { larkClient } = context;
|
|
304
|
+
|
|
305
|
+
const tokenResult = await getAccessToken(context);
|
|
306
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
307
|
+
const accessToken = tokenResult;
|
|
308
|
+
|
|
309
|
+
const { token, urlSheetId } = await resolveToken(p, larkClient!, accessToken);
|
|
310
|
+
let range = p.range;
|
|
311
|
+
if (!range && (p.sheet_id || urlSheetId)) {
|
|
312
|
+
range = p.sheet_id || urlSheetId;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
log.info(`append: token=${token}, range=${range}, rows=${p.values?.length}`);
|
|
316
|
+
|
|
317
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
318
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
319
|
+
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
321
|
+
const res = await (larkClient!.sdk as any).request({
|
|
322
|
+
method: 'POST',
|
|
323
|
+
url: `/open-apis/sheets/v2/spreadsheets/${token}/values_append`,
|
|
324
|
+
data: { valueRange: { range, values: p.values } },
|
|
325
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
326
|
+
}, opts);
|
|
327
|
+
|
|
328
|
+
if (res.code && res.code !== 0) {
|
|
329
|
+
return jsonError(res.msg || `API error: ${res.code}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const updates = res.data?.updates;
|
|
333
|
+
|
|
334
|
+
return json({
|
|
335
|
+
table_range: res.data?.tableRange,
|
|
336
|
+
updated_range: updates?.updatedRange,
|
|
337
|
+
updated_rows: updates?.updatedRows,
|
|
338
|
+
updated_columns: updates?.updatedColumns,
|
|
339
|
+
updated_cells: updates?.updatedCells,
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Create
|
|
345
|
+
registry.register({
|
|
346
|
+
name: 'feishu_sheet_create',
|
|
347
|
+
description: 'Create a new Feishu spreadsheet.\n\nRequires OAuth authorization.',
|
|
348
|
+
inputSchema: createActionSchema,
|
|
349
|
+
handler: async (args, context) => {
|
|
350
|
+
const p = args as z.infer<ReturnType<typeof z.object<typeof createActionSchema>>>;
|
|
351
|
+
const { larkClient } = context;
|
|
352
|
+
|
|
353
|
+
const tokenResult = await getAccessToken(context);
|
|
354
|
+
if (typeof tokenResult === 'object' && 'content' in tokenResult) return tokenResult;
|
|
355
|
+
const accessToken = tokenResult;
|
|
356
|
+
|
|
357
|
+
log.info(`create: title=${p.title}, folder=${p.folder_token ?? '(root)'}`);
|
|
358
|
+
|
|
359
|
+
const Lark = await import('@larksuiteoapi/node-sdk');
|
|
360
|
+
const opts = Lark.withUserAccessToken(accessToken);
|
|
361
|
+
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
|
+
const data: any = { title: p.title };
|
|
364
|
+
if (p.folder_token) data.folder_token = p.folder_token;
|
|
365
|
+
|
|
366
|
+
const res = await larkClient!.sdk.sheets.spreadsheet.create({ data }, opts);
|
|
367
|
+
assertLarkOk(res);
|
|
368
|
+
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
370
|
+
const spreadsheet = res.data?.spreadsheet as any;
|
|
371
|
+
const token = spreadsheet?.spreadsheet_token;
|
|
372
|
+
|
|
373
|
+
return json({
|
|
374
|
+
spreadsheet_token: token,
|
|
375
|
+
title: p.title,
|
|
376
|
+
url: `https://www.feishu.cn/sheets/${token}`,
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
log.debug('feishu_sheet tools registered');
|
|
382
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Task Tools Index
|
|
6
|
+
*
|
|
7
|
+
* Task tools for Feishu/Lark.
|
|
8
|
+
* Adapted from openclaw-lark for MCP Server architecture.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ToolRegistry } from '../index.js';
|
|
12
|
+
import { registerTaskTool } from './task.js';
|
|
13
|
+
import { registerTasklistTool } from './tasklist.js';
|
|
14
|
+
import { logger } from '../../utils/logger.js';
|
|
15
|
+
|
|
16
|
+
const log = logger('tools:task');
|
|
17
|
+
|
|
18
|
+
export function registerTaskTools(registry: ToolRegistry): void {
|
|
19
|
+
registerTaskTool(registry);
|
|
20
|
+
registerTasklistTool(registry);
|
|
21
|
+
|
|
22
|
+
log.info('Task tools registered', {
|
|
23
|
+
tools: [
|
|
24
|
+
'feishu_task_create',
|
|
25
|
+
'feishu_task_get',
|
|
26
|
+
'feishu_task_list',
|
|
27
|
+
'feishu_task_patch',
|
|
28
|
+
'feishu_tasklist_create',
|
|
29
|
+
'feishu_tasklist_get',
|
|
30
|
+
'feishu_tasklist_list',
|
|
31
|
+
'feishu_tasklist_tasks',
|
|
32
|
+
].join(', '),
|
|
33
|
+
});
|
|
34
|
+
}
|