@xwang152/claw-lark 0.1.28 → 0.1.30

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.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Lark/Feishu Document Tools
3
+ *
4
+ * Functions for creating, reading, updating, and managing Lark documents.
5
+ * Supports markdown-to-document conversion with image handling.
6
+ */
7
+ import type { ResolvedLarkAccount } from "./types.js";
8
+ export type LarkBlockType = number;
9
+ export type LarkDocumentInfo = {
10
+ title?: string;
11
+ content?: string;
12
+ revision_id?: string;
13
+ block_count: number;
14
+ block_types: Record<string, number>;
15
+ hint?: string;
16
+ };
17
+ export type LarkCreateDocResult = {
18
+ document_id?: string;
19
+ title?: string;
20
+ url: string;
21
+ };
22
+ export type LarkWriteDocResult = {
23
+ success: boolean;
24
+ blocks_deleted: number;
25
+ blocks_added: number;
26
+ images_processed: number;
27
+ images_failed: number;
28
+ image_errors: Array<{
29
+ url: string;
30
+ error: string;
31
+ }>;
32
+ warning?: string;
33
+ };
34
+ export type LarkAppendDocResult = {
35
+ success: boolean;
36
+ blocks_added: number;
37
+ images_processed: number;
38
+ images_failed: number;
39
+ image_errors: Array<{
40
+ url: string;
41
+ error: string;
42
+ }>;
43
+ block_ids: string[];
44
+ warning?: string;
45
+ };
46
+ export type LarkBlock = {
47
+ block_id?: string;
48
+ block_type: number;
49
+ parent_id?: string;
50
+ [key: string]: unknown;
51
+ };
52
+ export type LarkListBlocksResult = {
53
+ blocks: LarkBlock[];
54
+ };
55
+ export type LarkGetBlockResult = {
56
+ block: LarkBlock | undefined;
57
+ };
58
+ export type LarkFolderItem = {
59
+ token: string;
60
+ name: string;
61
+ type: string;
62
+ url: string;
63
+ };
64
+ export type LarkListFolderResult = {
65
+ files: LarkFolderItem[];
66
+ };
67
+ export type LarkAppScopeResult = {
68
+ granted: Array<{
69
+ name: string;
70
+ type: string;
71
+ }>;
72
+ pending: Array<{
73
+ name: string;
74
+ type: string;
75
+ }>;
76
+ summary: string;
77
+ };
78
+ /**
79
+ * Read a Lark document's content and metadata.
80
+ *
81
+ * @param account - The Lark account to use
82
+ * @param docToken - The document token (from URL /docx/XXX)
83
+ * @returns Document info with title, content, block counts
84
+ */
85
+ export declare function readDocumentLark(account: ResolvedLarkAccount, docToken: string): Promise<LarkDocumentInfo>;
86
+ /**
87
+ * Create a new Lark document.
88
+ *
89
+ * @param account - The Lark account to use
90
+ * @param title - Document title
91
+ * @param folderToken - Optional parent folder token
92
+ * @returns Created document info with ID and URL
93
+ */
94
+ export declare function createDocumentLark(account: ResolvedLarkAccount, title: string, folderToken?: string): Promise<LarkCreateDocResult>;
95
+ /**
96
+ * Write markdown content to a Lark document (replaces all content).
97
+ *
98
+ * @param account - The Lark account to use
99
+ * @param docToken - Document token
100
+ * @param markdown - Markdown content to write
101
+ * @returns Write result with block counts and warnings
102
+ */
103
+ export declare function writeDocumentLark(account: ResolvedLarkAccount, docToken: string, markdown: string): Promise<LarkWriteDocResult>;
104
+ /**
105
+ * Append markdown content to a Lark document.
106
+ *
107
+ * @param account - The Lark account to use
108
+ * @param docToken - Document token
109
+ * @param markdown - Markdown content to append
110
+ * @returns Append result with block counts
111
+ */
112
+ export declare function appendDocumentLark(account: ResolvedLarkAccount, docToken: string, markdown: string): Promise<LarkAppendDocResult>;
113
+ /**
114
+ * Update a single text block in a document.
115
+ *
116
+ * @param account - The Lark account to use
117
+ * @param docToken - Document token
118
+ * @param blockId - Block ID to update
119
+ * @param content - New text content
120
+ * @returns Update result
121
+ */
122
+ export declare function updateBlockLark(account: ResolvedLarkAccount, docToken: string, blockId: string, content: string): Promise<{
123
+ success: boolean;
124
+ block_id: string;
125
+ }>;
126
+ /**
127
+ * Delete a block from a document.
128
+ *
129
+ * @param account - The Lark account to use
130
+ * @param docToken - Document token
131
+ * @param blockId - Block ID to delete
132
+ * @returns Delete result
133
+ */
134
+ export declare function deleteBlockLark(account: ResolvedLarkAccount, docToken: string, blockId: string): Promise<{
135
+ success: boolean;
136
+ deleted_block_id: string;
137
+ }>;
138
+ /**
139
+ * List all blocks in a document.
140
+ *
141
+ * @param account - The Lark account to use
142
+ * @param docToken - Document token
143
+ * @returns List of all blocks with full data
144
+ */
145
+ export declare function listBlocksLark(account: ResolvedLarkAccount, docToken: string): Promise<LarkListBlocksResult>;
146
+ /**
147
+ * Get a single block's details.
148
+ *
149
+ * @param account - The Lark account to use
150
+ * @param docToken - Document token
151
+ * @param blockId - Block ID
152
+ * @returns Block details
153
+ */
154
+ export declare function getBlockLark(account: ResolvedLarkAccount, docToken: string, blockId: string): Promise<LarkGetBlockResult>;
155
+ /**
156
+ * List files in a folder.
157
+ *
158
+ * @param account - The Lark account to use
159
+ * @param folderToken - Folder token
160
+ * @returns List of files in folder
161
+ */
162
+ export declare function listFolderLark(account: ResolvedLarkAccount, folderToken: string): Promise<LarkListFolderResult>;
163
+ /**
164
+ * List application scopes (permissions).
165
+ *
166
+ * @param account - The Lark account to use
167
+ * @returns List of granted and pending scopes
168
+ */
169
+ export declare function listAppScopesLark(account: ResolvedLarkAccount): Promise<LarkAppScopeResult>;
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Lark/Feishu Document Tools
3
+ *
4
+ * Functions for creating, reading, updating, and managing Lark documents.
5
+ * Supports markdown-to-document conversion with image handling.
6
+ */
7
+ import { createLarkClient } from "./client.js";
8
+ import { getLarkRuntime } from "./runtime.js";
9
+ import { Readable } from "stream";
10
+ // ============ Constants ============
11
+ const BLOCK_TYPE_NAMES = {
12
+ 1: "Page",
13
+ 2: "Text",
14
+ 3: "Heading1",
15
+ 4: "Heading2",
16
+ 5: "Heading3",
17
+ 12: "Bullet",
18
+ 13: "Ordered",
19
+ 14: "Code",
20
+ 15: "Quote",
21
+ 17: "Todo",
22
+ 18: "Bitable",
23
+ 21: "Diagram",
24
+ 22: "Divider",
25
+ 23: "File",
26
+ 27: "Image",
27
+ 30: "Sheet",
28
+ 31: "Table",
29
+ 32: "TableCell",
30
+ };
31
+ /**
32
+ * Wrap API errors to avoid exposing sensitive details.
33
+ * Sanitizes error messages while preserving error codes.
34
+ */
35
+ function wrapLarkError(message, apiError) {
36
+ if (apiError?.code !== undefined && apiError.code !== 0) {
37
+ // Include error code but sanitize the message
38
+ return new Error(`${message} (API error code: ${apiError.code})`);
39
+ }
40
+ return new Error(message);
41
+ }
42
+ // Block types that cannot be created via documentBlockChildren.create API
43
+ const UNSUPPORTED_CREATE_TYPES = new Set([
44
+ 31, // Table - must use different API or workaround
45
+ 32, // TableCell - child of Table
46
+ ]);
47
+ // Block types that are NOT included in rawContent (plain text) output
48
+ const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
49
+ // ============ Helper Functions ============
50
+ function extractBlockPreview(block) {
51
+ const elements = block.text?.elements ??
52
+ block.heading1?.elements ??
53
+ block.heading2?.elements ??
54
+ block.heading3?.elements ??
55
+ [];
56
+ return elements
57
+ .filter((e) => e.text_run)
58
+ .map((e) => e.text_run.content)
59
+ .join("")
60
+ .slice(0, 50);
61
+ }
62
+ /** Extract image URLs from markdown content */
63
+ function extractImageUrls(markdown) {
64
+ const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
65
+ const urls = [];
66
+ let match;
67
+ while ((match = regex.exec(markdown)) !== null) {
68
+ const url = match[1].trim();
69
+ // Only collect http(s) URLs, not file paths
70
+ if (url.startsWith("http://") || url.startsWith("https://")) {
71
+ urls.push(url);
72
+ }
73
+ }
74
+ return urls;
75
+ }
76
+ /** Clean blocks for insertion (remove unsupported types and read-only fields) */
77
+ function cleanBlocksForInsert(blocks) {
78
+ const skipped = [];
79
+ const cleaned = blocks
80
+ .filter((block) => {
81
+ if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
82
+ const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
83
+ skipped.push(typeName);
84
+ return false;
85
+ }
86
+ return true;
87
+ })
88
+ .map((block) => {
89
+ // Remove any read-only fields that might slip through
90
+ if (block.block_type === 31 && block.table?.merge_info) {
91
+ const { merge_info, ...tableRest } = block.table;
92
+ return { ...block, table: tableRest };
93
+ }
94
+ return block;
95
+ });
96
+ return { cleaned, skipped };
97
+ }
98
+ /** Convert markdown to Lark blocks using the Convert API */
99
+ async function convertMarkdown(client, markdown) {
100
+ const res = await client.docx.document.convert({
101
+ data: { content_type: "markdown", content: markdown },
102
+ });
103
+ if (res.code !== 0)
104
+ throw wrapLarkError("Failed to convert markdown to document blocks", res);
105
+ return {
106
+ blocks: res.data?.blocks ?? [],
107
+ firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
108
+ };
109
+ }
110
+ /** Insert blocks as children of a parent block */
111
+ async function insertBlocks(client, docToken, blocks, parentBlockId) {
112
+ const { cleaned, skipped } = cleanBlocksForInsert(blocks);
113
+ const blockId = parentBlockId ?? docToken;
114
+ if (cleaned.length === 0) {
115
+ return { children: [], skipped };
116
+ }
117
+ const res = await client.docx.documentBlockChildren.create({
118
+ path: { document_id: docToken, block_id: blockId },
119
+ data: { children: cleaned },
120
+ });
121
+ if (res.code !== 0)
122
+ throw wrapLarkError("Failed to insert blocks into document", res);
123
+ return { children: res.data?.children ?? [], skipped };
124
+ }
125
+ /** Delete all child blocks from a parent */
126
+ async function clearDocumentContent(client, docToken) {
127
+ const existing = await client.docx.documentBlock.list({
128
+ path: { document_id: docToken },
129
+ });
130
+ if (existing.code !== 0)
131
+ throw wrapLarkError("Failed to list document blocks", existing);
132
+ const childIds = existing.data?.items
133
+ ?.filter((b) => b.parent_id === docToken && b.block_type !== 1)
134
+ .map((b) => b.block_id) ?? [];
135
+ if (childIds.length > 0) {
136
+ const res = await client.docx.documentBlockChildren.batchDelete({
137
+ path: { document_id: docToken, block_id: docToken },
138
+ data: { start_index: 0, end_index: childIds.length },
139
+ });
140
+ if (res.code !== 0)
141
+ throw wrapLarkError("Failed to clear document content", res);
142
+ }
143
+ return childIds.length;
144
+ }
145
+ /** Upload image to Lark drive for docx */
146
+ async function uploadImageToDocx(client, blockId, imageBuffer, fileName) {
147
+ const res = await client.drive.media.uploadAll({
148
+ data: {
149
+ file_name: fileName,
150
+ parent_type: "docx_image",
151
+ parent_node: blockId,
152
+ size: imageBuffer.length,
153
+ file: Readable.from(imageBuffer),
154
+ },
155
+ });
156
+ const fileToken = res?.file_token;
157
+ if (!fileToken) {
158
+ throw new Error("Image upload failed: no file_token returned");
159
+ }
160
+ return fileToken;
161
+ }
162
+ /**
163
+ * Validate URL and fetch with timeout.
164
+ * Only allows http/https protocols to prevent SSRF attacks.
165
+ */
166
+ async function validatedFetch(url, timeoutMs = 30000) {
167
+ let parsedUrl;
168
+ try {
169
+ parsedUrl = new URL(url);
170
+ }
171
+ catch (err) {
172
+ throw new Error(`Invalid URL: ${url}`);
173
+ }
174
+ // Only allow http/https protocols
175
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
176
+ throw new Error(`URL protocol not allowed: ${parsedUrl.protocol}. ` +
177
+ `Only http and https are supported.`);
178
+ }
179
+ // Create AbortController for timeout
180
+ const controller = new AbortController();
181
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
182
+ try {
183
+ const response = await fetch(url, {
184
+ signal: controller.signal,
185
+ redirect: "follow",
186
+ });
187
+ if (!response.ok) {
188
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
189
+ }
190
+ return Buffer.from(await response.arrayBuffer());
191
+ }
192
+ catch (err) {
193
+ if (err instanceof Error && err.name === "AbortError") {
194
+ throw new Error(`Request timeout after ${timeoutMs}ms`);
195
+ }
196
+ throw err;
197
+ }
198
+ finally {
199
+ clearTimeout(timeoutId);
200
+ }
201
+ }
202
+ /** Download image from URL with validation */
203
+ async function downloadImage(url) {
204
+ return validatedFetch(url, 30000);
205
+ }
206
+ /** Process images in markdown: download from URL, upload to Lark, update blocks */
207
+ async function processImages(client, docToken, markdown, insertedBlocks) {
208
+ const imageUrls = extractImageUrls(markdown);
209
+ if (imageUrls.length === 0)
210
+ return { processed: 0, failed: 0, errors: [] };
211
+ // Find Image blocks (block_type 27)
212
+ const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27);
213
+ let processed = 0;
214
+ const errors = [];
215
+ for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
216
+ const url = imageUrls[i];
217
+ const blockId = imageBlocks[i].block_id;
218
+ try {
219
+ // Download image from URL
220
+ const buffer = await downloadImage(url);
221
+ // Generate filename from URL
222
+ const urlPath = new URL(url).pathname;
223
+ const fileName = urlPath.split("/").pop() || `image_${i}.png`;
224
+ // Upload to Lark
225
+ const fileToken = await uploadImageToDocx(client, blockId, buffer, fileName);
226
+ // Update the image block
227
+ await client.docx.documentBlock.patch({
228
+ path: { document_id: docToken, block_id: blockId },
229
+ data: {
230
+ replace_image: { token: fileToken },
231
+ },
232
+ });
233
+ processed++;
234
+ }
235
+ catch (err) {
236
+ // Track error but continue processing other images
237
+ const errorMsg = err instanceof Error ? err.message : String(err);
238
+ errors.push({ url, error: errorMsg });
239
+ getLarkRuntime().logger.warn(`[lark] Failed to process image ${url}: ${errorMsg}`);
240
+ }
241
+ }
242
+ return { processed, failed: errors.length, errors };
243
+ }
244
+ // ============ Public API Functions ============
245
+ /**
246
+ * Read a Lark document's content and metadata.
247
+ *
248
+ * @param account - The Lark account to use
249
+ * @param docToken - The document token (from URL /docx/XXX)
250
+ * @returns Document info with title, content, block counts
251
+ */
252
+ export async function readDocumentLark(account, docToken) {
253
+ const client = createLarkClient(account);
254
+ const [contentRes, infoRes, blocksRes] = await Promise.all([
255
+ client.docx.document.rawContent({ path: { document_id: docToken } }),
256
+ client.docx.document.get({ path: { document_id: docToken } }),
257
+ client.docx.documentBlock.list({ path: { document_id: docToken } }),
258
+ ]);
259
+ if (contentRes.code !== 0)
260
+ throw wrapLarkError("Failed to read document content", contentRes);
261
+ const blocks = blocksRes.data?.items ?? [];
262
+ const blockCounts = {};
263
+ const structuredTypes = [];
264
+ for (const b of blocks) {
265
+ const type = b.block_type ?? 0;
266
+ const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
267
+ blockCounts[name] = (blockCounts[name] || 0) + 1;
268
+ // Track structured types that need list_blocks to read
269
+ if (STRUCTURED_BLOCK_TYPES.has(type) && !structuredTypes.includes(name)) {
270
+ structuredTypes.push(name);
271
+ }
272
+ }
273
+ // Build hint if there are structured blocks
274
+ let hint;
275
+ if (structuredTypes.length > 0) {
276
+ hint = `This document contains ${structuredTypes.join(", ")} which are NOT included in the plain text above. Use listBlocksLark to get full content.`;
277
+ }
278
+ return {
279
+ title: infoRes.data?.document?.title,
280
+ content: contentRes.data?.content,
281
+ revision_id: infoRes.data?.document?.revision_id,
282
+ block_count: blocks.length,
283
+ block_types: blockCounts,
284
+ ...(hint && { hint }),
285
+ };
286
+ }
287
+ /**
288
+ * Create a new Lark document.
289
+ *
290
+ * @param account - The Lark account to use
291
+ * @param title - Document title
292
+ * @param folderToken - Optional parent folder token
293
+ * @returns Created document info with ID and URL
294
+ */
295
+ export async function createDocumentLark(account, title, folderToken) {
296
+ const client = createLarkClient(account);
297
+ const res = await client.docx.document.create({
298
+ data: { title, folder_token: folderToken },
299
+ });
300
+ if (res.code !== 0)
301
+ throw wrapLarkError("Failed to create document", res);
302
+ const doc = res.data?.document;
303
+ // Build URL based on domain
304
+ const baseUrl = account.domain === "lark" ? "https://larksuite.com" : "https://feishu.cn";
305
+ return {
306
+ document_id: doc?.document_id,
307
+ title: doc?.title,
308
+ url: `${baseUrl}/docx/${doc?.document_id}`,
309
+ };
310
+ }
311
+ /**
312
+ * Write markdown content to a Lark document (replaces all content).
313
+ *
314
+ * @param account - The Lark account to use
315
+ * @param docToken - Document token
316
+ * @param markdown - Markdown content to write
317
+ * @returns Write result with block counts and warnings
318
+ */
319
+ export async function writeDocumentLark(account, docToken, markdown) {
320
+ const client = createLarkClient(account);
321
+ // 1. Clear existing content
322
+ const deleted = await clearDocumentContent(client, docToken);
323
+ // 2. Convert markdown to blocks
324
+ const { blocks } = await convertMarkdown(client, markdown);
325
+ if (blocks.length === 0) {
326
+ return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0, images_failed: 0, image_errors: [] };
327
+ }
328
+ // 3. Insert new blocks (unsupported types like Table are filtered)
329
+ const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
330
+ // 4. Process images
331
+ const { processed: imagesProcessed, failed: imagesFailed, errors: imageErrors } = await processImages(client, docToken, markdown, inserted);
332
+ return {
333
+ success: true,
334
+ blocks_deleted: deleted,
335
+ blocks_added: inserted.length,
336
+ images_processed: imagesProcessed,
337
+ images_failed: imagesFailed,
338
+ image_errors: imageErrors,
339
+ ...(skipped.length > 0 && {
340
+ warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
341
+ }),
342
+ };
343
+ }
344
+ /**
345
+ * Append markdown content to a Lark document.
346
+ *
347
+ * @param account - The Lark account to use
348
+ * @param docToken - Document token
349
+ * @param markdown - Markdown content to append
350
+ * @returns Append result with block counts
351
+ */
352
+ export async function appendDocumentLark(account, docToken, markdown) {
353
+ const client = createLarkClient(account);
354
+ // 1. Convert markdown to blocks
355
+ const { blocks } = await convertMarkdown(client, markdown);
356
+ if (blocks.length === 0) {
357
+ throw new Error("Content is empty");
358
+ }
359
+ // 2. Insert blocks (unsupported types like Table are filtered)
360
+ const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
361
+ // 3. Process images
362
+ const { processed: imagesProcessed, failed: imagesFailed, errors: imageErrors } = await processImages(client, docToken, markdown, inserted);
363
+ return {
364
+ success: true,
365
+ blocks_added: inserted.length,
366
+ images_processed: imagesProcessed,
367
+ images_failed: imagesFailed,
368
+ image_errors: imageErrors,
369
+ block_ids: inserted.map((b) => b.block_id),
370
+ ...(skipped.length > 0 && {
371
+ warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
372
+ }),
373
+ };
374
+ }
375
+ /**
376
+ * Update a single text block in a document.
377
+ *
378
+ * @param account - The Lark account to use
379
+ * @param docToken - Document token
380
+ * @param blockId - Block ID to update
381
+ * @param content - New text content
382
+ * @returns Update result
383
+ */
384
+ export async function updateBlockLark(account, docToken, blockId, content) {
385
+ const client = createLarkClient(account);
386
+ const blockInfo = await client.docx.documentBlock.get({
387
+ path: { document_id: docToken, block_id: blockId },
388
+ });
389
+ if (blockInfo.code !== 0)
390
+ throw wrapLarkError("Failed to get block info", blockInfo);
391
+ const res = await client.docx.documentBlock.patch({
392
+ path: { document_id: docToken, block_id: blockId },
393
+ data: {
394
+ update_text_elements: {
395
+ elements: [{ text_run: { content } }],
396
+ },
397
+ },
398
+ });
399
+ if (res.code !== 0)
400
+ throw wrapLarkError("Failed to update block", res);
401
+ return { success: true, block_id: blockId };
402
+ }
403
+ /**
404
+ * Delete a block from a document.
405
+ *
406
+ * @param account - The Lark account to use
407
+ * @param docToken - Document token
408
+ * @param blockId - Block ID to delete
409
+ * @returns Delete result
410
+ */
411
+ export async function deleteBlockLark(account, docToken, blockId) {
412
+ const client = createLarkClient(account);
413
+ const blockInfo = await client.docx.documentBlock.get({
414
+ path: { document_id: docToken, block_id: blockId },
415
+ });
416
+ if (blockInfo.code !== 0)
417
+ throw wrapLarkError("Failed to get block info", blockInfo);
418
+ const parentId = blockInfo.data?.block?.parent_id ?? docToken;
419
+ const children = await client.docx.documentBlockChildren.get({
420
+ path: { document_id: docToken, block_id: parentId },
421
+ });
422
+ if (children.code !== 0)
423
+ throw wrapLarkError("Failed to get block children", children);
424
+ const items = children.data?.items ?? [];
425
+ const index = items.findIndex((item) => item.block_id === blockId);
426
+ if (index === -1)
427
+ throw new Error("Block not found");
428
+ const res = await client.docx.documentBlockChildren.batchDelete({
429
+ path: { document_id: docToken, block_id: parentId },
430
+ data: { start_index: index, end_index: index + 1 },
431
+ });
432
+ if (res.code !== 0)
433
+ throw wrapLarkError("Failed to delete block", res);
434
+ return { success: true, deleted_block_id: blockId };
435
+ }
436
+ /**
437
+ * List all blocks in a document.
438
+ *
439
+ * @param account - The Lark account to use
440
+ * @param docToken - Document token
441
+ * @returns List of all blocks with full data
442
+ */
443
+ export async function listBlocksLark(account, docToken) {
444
+ const client = createLarkClient(account);
445
+ const res = await client.docx.documentBlock.list({
446
+ path: { document_id: docToken },
447
+ });
448
+ if (res.code !== 0)
449
+ throw wrapLarkError("Failed to list blocks", res);
450
+ // Return full block data for agent to parse
451
+ return {
452
+ blocks: res.data?.items ?? [],
453
+ };
454
+ }
455
+ /**
456
+ * Get a single block's details.
457
+ *
458
+ * @param account - The Lark account to use
459
+ * @param docToken - Document token
460
+ * @param blockId - Block ID
461
+ * @returns Block details
462
+ */
463
+ export async function getBlockLark(account, docToken, blockId) {
464
+ const client = createLarkClient(account);
465
+ const res = await client.docx.documentBlock.get({
466
+ path: { document_id: docToken, block_id: blockId },
467
+ });
468
+ if (res.code !== 0)
469
+ throw wrapLarkError("Failed to get block", res);
470
+ return {
471
+ block: res.data?.block,
472
+ };
473
+ }
474
+ /**
475
+ * List files in a folder.
476
+ *
477
+ * @param account - The Lark account to use
478
+ * @param folderToken - Folder token
479
+ * @returns List of files in folder
480
+ */
481
+ export async function listFolderLark(account, folderToken) {
482
+ const client = createLarkClient(account);
483
+ const res = await client.drive.file.list({
484
+ params: { folder_token: folderToken },
485
+ });
486
+ if (res.code !== 0)
487
+ throw wrapLarkError("Failed to list folder", res);
488
+ return {
489
+ files: res.data?.files?.map((f) => ({
490
+ token: f.token,
491
+ name: f.name,
492
+ type: f.type,
493
+ url: f.url,
494
+ })) ?? [],
495
+ };
496
+ }
497
+ /**
498
+ * List application scopes (permissions).
499
+ *
500
+ * @param account - The Lark account to use
501
+ * @returns List of granted and pending scopes
502
+ */
503
+ export async function listAppScopesLark(account) {
504
+ const client = createLarkClient(account);
505
+ const res = await client.application.scope.list({});
506
+ if (res.code !== 0)
507
+ throw wrapLarkError("Failed to list app scopes", res);
508
+ const scopes = res.data?.scopes ?? [];
509
+ const granted = scopes.filter((s) => s.grant_status === 1);
510
+ const pending = scopes.filter((s) => s.grant_status !== 1);
511
+ return {
512
+ granted: granted.map((s) => ({ name: s.scope_name, type: s.scope_type })),
513
+ pending: pending.map((s) => ({ name: s.scope_name, type: s.scope_type })),
514
+ summary: `${granted.length} granted, ${pending.length} pending`,
515
+ };
516
+ }
517
+ //# sourceMappingURL=docx.js.map