@pripanggalih/clickup-mcp 1.6.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.
Files changed (57) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +295 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +184 -0
  6. package/dist/clickup-text.d.ts +83 -0
  7. package/dist/clickup-text.d.ts.map +1 -0
  8. package/dist/clickup-text.js +563 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +135 -0
  12. package/dist/resources/space-resources.d.ts +6 -0
  13. package/dist/resources/space-resources.d.ts.map +1 -0
  14. package/dist/resources/space-resources.js +95 -0
  15. package/dist/shared/config.d.ts +11 -0
  16. package/dist/shared/config.d.ts.map +1 -0
  17. package/dist/shared/config.js +61 -0
  18. package/dist/shared/data-uri.d.ts +14 -0
  19. package/dist/shared/data-uri.d.ts.map +1 -0
  20. package/dist/shared/data-uri.js +34 -0
  21. package/dist/shared/image-processing.d.ts +13 -0
  22. package/dist/shared/image-processing.d.ts.map +1 -0
  23. package/dist/shared/image-processing.js +199 -0
  24. package/dist/shared/types.d.ts +21 -0
  25. package/dist/shared/types.d.ts.map +1 -0
  26. package/dist/shared/types.js +2 -0
  27. package/dist/shared/utils.d.ts +71 -0
  28. package/dist/shared/utils.d.ts.map +1 -0
  29. package/dist/shared/utils.js +508 -0
  30. package/dist/test-utils.d.ts +23 -0
  31. package/dist/test-utils.d.ts.map +1 -0
  32. package/dist/test-utils.js +44 -0
  33. package/dist/tools/admin-tools.d.ts +3 -0
  34. package/dist/tools/admin-tools.d.ts.map +1 -0
  35. package/dist/tools/admin-tools.js +288 -0
  36. package/dist/tools/doc-tools.d.ts +4 -0
  37. package/dist/tools/doc-tools.d.ts.map +1 -0
  38. package/dist/tools/doc-tools.js +436 -0
  39. package/dist/tools/list-tools.d.ts +4 -0
  40. package/dist/tools/list-tools.d.ts.map +1 -0
  41. package/dist/tools/list-tools.js +175 -0
  42. package/dist/tools/search-tools.d.ts +3 -0
  43. package/dist/tools/search-tools.d.ts.map +1 -0
  44. package/dist/tools/search-tools.js +161 -0
  45. package/dist/tools/space-tools.d.ts +3 -0
  46. package/dist/tools/space-tools.d.ts.map +1 -0
  47. package/dist/tools/space-tools.js +128 -0
  48. package/dist/tools/task-tools.d.ts +8 -0
  49. package/dist/tools/task-tools.d.ts.map +1 -0
  50. package/dist/tools/task-tools.js +329 -0
  51. package/dist/tools/task-write-tools.d.ts +3 -0
  52. package/dist/tools/task-write-tools.d.ts.map +1 -0
  53. package/dist/tools/task-write-tools.js +567 -0
  54. package/dist/tools/time-tools.d.ts +4 -0
  55. package/dist/tools/time-tools.d.ts.map +1 -0
  56. package/dist/tools/time-tools.js +338 -0
  57. package/package.json +74 -0
@@ -0,0 +1,563 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.convertClickUpTextItemsToToolCallResult = convertClickUpTextItemsToToolCallResult;
7
+ exports.convertMarkdownToToolCallResult = convertMarkdownToToolCallResult;
8
+ exports.convertMarkdownToClickUpBlocks = convertMarkdownToClickUpBlocks;
9
+ const data_uri_1 = require("./shared/data-uri");
10
+ const unified_1 = require("unified");
11
+ const remark_parse_1 = __importDefault(require("remark-parse"));
12
+ const remark_gfm_1 = __importDefault(require("remark-gfm"));
13
+ /**
14
+ * Extract thumbnail URLs from data-attachment attribute JSON
15
+ * ClickUp API sometimes has broken thumbnail URLs, but data-attachment contains working ones
16
+ */
17
+ function extractThumbnailsFromDataAttachment(attributes) {
18
+ if (!attributes || !attributes['data-attachment']) {
19
+ return {};
20
+ }
21
+ try {
22
+ const attachmentData = JSON.parse(attributes['data-attachment']);
23
+ return {
24
+ thumbnail_large: attachmentData.thumbnail_large,
25
+ thumbnail_medium: attachmentData.thumbnail_medium,
26
+ thumbnail_small: attachmentData.thumbnail_small,
27
+ };
28
+ }
29
+ catch (error) {
30
+ console.error('Error parsing data-attachment:', error);
31
+ return {};
32
+ }
33
+ }
34
+ /**
35
+ * Process an array of ClickUp text items into a structured content format
36
+ * that includes both text and images in their original sequence
37
+ *
38
+ * @param textItems Array of text items from ClickUp API
39
+ * @returns Promise resolving to an array of content blocks (text and images)
40
+ */
41
+ async function convertClickUpTextItemsToToolCallResult(textItems) {
42
+ const contentBlocks = [];
43
+ let currentTextBlock = "";
44
+ let currentLine = ""; // Track current line separately for block formatting
45
+ // Track current formatting state to avoid unnecessary close/reopen
46
+ let activeBold = false;
47
+ let activeItalic = false;
48
+ let activeCode = false;
49
+ for (let i = 0; i < textItems.length; i++) {
50
+ const item = textItems[i];
51
+ // Handle image items
52
+ if (item.type === "image" && item.image && item.image.url) {
53
+ const imageFileName = item.image.name || item.image.title || "image";
54
+ const imageUrl = item.image.url;
55
+ const altText = item.text || imageFileName;
56
+ if (imageUrl.startsWith("data:")) {
57
+ const parsedData = (0, data_uri_1.parseDataUri)(imageUrl);
58
+ currentTextBlock += `\nImage: ${imageFileName} - [inline image data]`;
59
+ if (currentTextBlock.trim()) {
60
+ contentBlocks.push({
61
+ type: "text",
62
+ text: currentTextBlock.trim(),
63
+ });
64
+ }
65
+ currentTextBlock = "";
66
+ if (parsedData) {
67
+ contentBlocks.push({
68
+ type: "image_metadata",
69
+ urls: [],
70
+ alt: altText,
71
+ inlineData: parsedData,
72
+ });
73
+ }
74
+ else {
75
+ console.error(`Unable to parse inline image data for ${imageFileName}`);
76
+ contentBlocks.push({
77
+ type: "text",
78
+ text: `[Image "${altText}" omitted: unsupported inline data URI]`,
79
+ });
80
+ }
81
+ continue;
82
+ }
83
+ // Add image URL reference inline to current text block
84
+ currentTextBlock += `\nImage: ${imageFileName} - ${imageUrl}`;
85
+ // Get working thumbnail URLs from data-attachment if available
86
+ const extractedThumbnails = extractThumbnailsFromDataAttachment(item.attributes);
87
+ // Determine best thumbnail URLs (prefer extracted over API thumbnails)
88
+ const thumbnail_large = extractedThumbnails.thumbnail_large || item.image.thumbnail_large;
89
+ const thumbnail_medium = extractedThumbnails.thumbnail_medium || item.image.thumbnail_medium;
90
+ const thumbnail_small = extractedThumbnails.thumbnail_small || item.image.thumbnail_small;
91
+ // Only create image_metadata if we have at least one thumbnail (never use original image)
92
+ if (thumbnail_large || thumbnail_medium || thumbnail_small) {
93
+ // Push accumulated text (including image URL) as a text block
94
+ if (currentTextBlock.trim()) {
95
+ contentBlocks.push({
96
+ type: "text",
97
+ text: currentTextBlock.trim(),
98
+ });
99
+ }
100
+ // Reset current text block after pushing it
101
+ currentTextBlock = "";
102
+ // Create URLs array with largest to smallest preference, filter out undefined
103
+ const urls = [thumbnail_large, thumbnail_medium, thumbnail_small].filter(Boolean);
104
+ // Add image_metadata block for lazy loading
105
+ contentBlocks.push({
106
+ type: "image_metadata",
107
+ urls: urls,
108
+ alt: altText,
109
+ });
110
+ }
111
+ // If no thumbnails, just treat as a file reference (already added to currentTextBlock)
112
+ }
113
+ // Handle text items
114
+ else if (typeof item.text === "string") {
115
+ // Check if this is a newline with block formatting (header, blockquote, list)
116
+ if (item.text === '\n' && item.attributes) {
117
+ // Header formatting
118
+ if (item.attributes.header) {
119
+ const level = item.attributes.header;
120
+ currentLine = '#'.repeat(level) + ' ' + currentLine;
121
+ }
122
+ // Blockquote formatting
123
+ else if (item.attributes.blockquote) {
124
+ currentLine = '> ' + currentLine;
125
+ }
126
+ // List formatting
127
+ else if (item.attributes.list) {
128
+ const listType = item.attributes.list.list;
129
+ const indent = item.attributes.indent || 0;
130
+ // Add indentation (2 spaces per level) for nested lists
131
+ const indentStr = ' '.repeat(indent);
132
+ switch (listType) {
133
+ case 'bullet':
134
+ currentLine = indentStr + '- ' + currentLine;
135
+ break;
136
+ case 'ordered':
137
+ currentLine = indentStr + '1. ' + currentLine;
138
+ break;
139
+ case 'checked':
140
+ currentLine = indentStr + '- [x] ' + currentLine;
141
+ break;
142
+ case 'unchecked':
143
+ currentLine = indentStr + '- [ ] ' + currentLine;
144
+ break;
145
+ }
146
+ }
147
+ // Code block formatting
148
+ else if (item.attributes['code-block']) {
149
+ // Wrap the current line in code block markers
150
+ currentLine = '```\n' + currentLine + '\n```';
151
+ }
152
+ // Add formatted line to text block
153
+ currentTextBlock += currentLine;
154
+ // Add newline unless it's code block (already has newlines)
155
+ if (!item.attributes['code-block']) {
156
+ currentTextBlock += '\n';
157
+ }
158
+ currentLine = ""; // Reset for next line
159
+ continue;
160
+ }
161
+ // Regular text with inline formatting
162
+ let formattedText = item.text;
163
+ // Determine current and next formatting state
164
+ const hasBold = item.attributes?.bold === true;
165
+ const hasItalic = item.attributes?.italic === true;
166
+ const hasLink = item.attributes?.link;
167
+ // Look ahead to next non-newline block
168
+ let nextHasBold = false;
169
+ let nextHasItalic = false;
170
+ for (let j = i + 1; j < textItems.length; j++) {
171
+ const nextItem = textItems[j];
172
+ if (nextItem.text !== '\n' || !nextItem.attributes) {
173
+ nextHasBold = nextItem.attributes?.bold === true;
174
+ nextHasItalic = nextItem.attributes?.italic === true;
175
+ break;
176
+ }
177
+ }
178
+ // Build prefix (open new formatting)
179
+ let prefix = "";
180
+ if (hasBold && !activeBold)
181
+ prefix += "**";
182
+ if (hasItalic && !activeItalic)
183
+ prefix += "*";
184
+ // Build suffix (close formatting that won't continue)
185
+ let suffix = "";
186
+ if (hasItalic && !nextHasItalic)
187
+ suffix += "*";
188
+ if (hasBold && !nextHasBold)
189
+ suffix += "**";
190
+ // Close formatting that's active but not in this block
191
+ let closingPrefix = "";
192
+ if (activeBold && !hasBold)
193
+ closingPrefix += "**";
194
+ if (activeItalic && !hasItalic)
195
+ closingPrefix += "*";
196
+ formattedText = closingPrefix + prefix + formattedText + suffix;
197
+ // Update state
198
+ activeBold = hasBold && nextHasBold;
199
+ activeItalic = hasItalic && nextHasItalic;
200
+ // Link formatting (wraps everything)
201
+ if (hasLink) {
202
+ formattedText = `[${formattedText}](${hasLink})`;
203
+ }
204
+ // Code formatting
205
+ if (item.attributes?.code) {
206
+ formattedText = `\`${formattedText}\``;
207
+ }
208
+ // Add to current line (not text block yet)
209
+ if (item.text === '\n') {
210
+ // Plain newline without formatting
211
+ currentTextBlock += currentLine + '\n';
212
+ currentLine = "";
213
+ }
214
+ else {
215
+ currentLine += formattedText;
216
+ }
217
+ }
218
+ // Handle other types of items like bookmarks or whatever clickup can think of
219
+ else {
220
+ currentTextBlock += JSON.stringify(item);
221
+ }
222
+ }
223
+ // Add any remaining text
224
+ if (currentLine) {
225
+ currentTextBlock += currentLine;
226
+ }
227
+ if (currentTextBlock.trim()) {
228
+ contentBlocks.push({
229
+ type: "text",
230
+ text: currentTextBlock.trim(),
231
+ });
232
+ }
233
+ return contentBlocks;
234
+ }
235
+ /**
236
+ * Splits markdown text at image references and converts them to image blocks
237
+ * @param markdownText The markdown text to process
238
+ * @param attachments Array of attachments from the Clickup API
239
+ * @returns Array of content blocks (text and images)
240
+ */
241
+ function convertMarkdownToToolCallResult(markdownText, attachments) {
242
+ const contentBlocks = [];
243
+ let currentTextBlock = "";
244
+ // Create a map of attachment URLs to their full info for easy lookup
245
+ const attachmentMap = new Map();
246
+ if (attachments && Array.isArray(attachments)) {
247
+ for (const attachment of attachments) {
248
+ attachmentMap.set(attachment.url, attachment);
249
+ }
250
+ }
251
+ // Regular expression to match markdown image syntax: ![alt text](url)
252
+ const imageRegex = /!\[([^\]]*)\]\(([^\)]+)\)/g;
253
+ let lastIndex = 0;
254
+ let match;
255
+ while ((match = imageRegex.exec(markdownText)) !== null) {
256
+ const [fullMatch, altText, imageUrl] = match;
257
+ // Add text before the image reference to the current text block
258
+ currentTextBlock += markdownText.substring(lastIndex, match.index);
259
+ if (imageUrl.startsWith("data:")) {
260
+ const imageFileName = altText || "image";
261
+ const parsedData = (0, data_uri_1.parseDataUri)(imageUrl);
262
+ currentTextBlock += `\nImage: ${imageFileName} - [inline image data]`;
263
+ if (currentTextBlock.trim()) {
264
+ contentBlocks.push({
265
+ type: "text",
266
+ text: currentTextBlock.trim(),
267
+ });
268
+ }
269
+ currentTextBlock = "";
270
+ if (parsedData) {
271
+ contentBlocks.push({
272
+ type: "image_metadata",
273
+ urls: [],
274
+ alt: altText || imageFileName,
275
+ inlineData: parsedData,
276
+ });
277
+ }
278
+ else {
279
+ console.error(`Unable to parse inline image data for ${imageFileName}`);
280
+ contentBlocks.push({
281
+ type: "text",
282
+ text: `[Image "${altText || imageFileName}" omitted: unsupported inline data URI]`,
283
+ });
284
+ }
285
+ lastIndex = match.index + fullMatch.length;
286
+ continue;
287
+ }
288
+ // Check if this image URL exists in our attachments
289
+ const attachment = attachmentMap.get(imageUrl);
290
+ if (attachment) {
291
+ // Add image URL reference inline to current text block
292
+ const imageFileName = altText || "image";
293
+ currentTextBlock += `\nImage: ${imageFileName} - ${imageUrl}`;
294
+ // Only create image_metadata if we have at least one thumbnail (never use original image)
295
+ if (attachment.thumbnail_large || attachment.thumbnail_medium || attachment.thumbnail_small) {
296
+ // Push accumulated text (including image URL) as a text block
297
+ if (currentTextBlock.trim()) {
298
+ contentBlocks.push({
299
+ type: "text",
300
+ text: currentTextBlock.trim(),
301
+ });
302
+ }
303
+ // Reset current text block after pushing it
304
+ currentTextBlock = "";
305
+ // Create URLs array with largest to smallest preference, filter out undefined
306
+ const urls = [attachment.thumbnail_large, attachment.thumbnail_medium, attachment.thumbnail_small].filter(Boolean);
307
+ // Add image_metadata block for lazy loading
308
+ contentBlocks.push({
309
+ type: "image_metadata",
310
+ urls: urls,
311
+ alt: altText || imageFileName,
312
+ });
313
+ }
314
+ // If no thumbnails, just treat as a file reference (already added to currentTextBlock)
315
+ }
316
+ else {
317
+ // If the image URL doesn't match any attachment, keep the original markdown in the current text block
318
+ currentTextBlock += fullMatch;
319
+ console.error(`Image URL ${imageUrl} not found in attachments`, attachmentMap);
320
+ }
321
+ lastIndex = match.index + fullMatch.length;
322
+ }
323
+ // Add any remaining text after the last image
324
+ currentTextBlock += markdownText.substring(lastIndex);
325
+ // Process non-image attachments that weren't referenced in markdown
326
+ const referencedUrls = new Set();
327
+ const imageMatches = markdownText.matchAll(/!\[([^\]]*)\]\(([^\)]+)\)/g);
328
+ for (const match of imageMatches) {
329
+ referencedUrls.add(match[2]);
330
+ }
331
+ // Add non-image files inline to the current text block
332
+ if (attachments && Array.isArray(attachments)) {
333
+ for (const attachment of attachments) {
334
+ if (!referencedUrls.has(attachment.url)) {
335
+ // Determine if this is an image based on URL or type
336
+ const isImage = attachment.thumbnail_large ||
337
+ /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(attachment.url);
338
+ if (!isImage) {
339
+ // This is a non-image file - add inline to current text block
340
+ const fileName = extractFileNameFromUrl(attachment.url) || "file";
341
+ const fileType = extractFileTypeFromUrl(attachment.url);
342
+ const fileTypeText = fileType ? ` (${fileType.toUpperCase()})` : "";
343
+ currentTextBlock += `\nFile: ${fileName}${fileTypeText} - ${attachment.url}`;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ // Add any remaining text (including file references) as final text block
349
+ if (currentTextBlock.trim()) {
350
+ contentBlocks.push({
351
+ type: "text",
352
+ text: currentTextBlock.trim(),
353
+ });
354
+ }
355
+ return contentBlocks;
356
+ }
357
+ /**
358
+ * Extract filename from URL
359
+ */
360
+ function extractFileNameFromUrl(url) {
361
+ try {
362
+ const urlObj = new URL(url);
363
+ const pathname = urlObj.pathname;
364
+ const filename = pathname.split('/').pop();
365
+ return filename && filename !== '' ? filename : null;
366
+ }
367
+ catch {
368
+ return null;
369
+ }
370
+ }
371
+ /**
372
+ * Extract file extension from URL
373
+ */
374
+ function extractFileTypeFromUrl(url) {
375
+ const filename = extractFileNameFromUrl(url);
376
+ if (!filename)
377
+ return null;
378
+ const lastDot = filename.lastIndexOf('.');
379
+ if (lastDot === -1)
380
+ return null;
381
+ return filename.substring(lastDot + 1);
382
+ }
383
+ /**
384
+ * Convert markdown text to ClickUp comment blocks format using remark
385
+ * Supports: headers, bold, italic, code, links, lists, blockquotes, code blocks
386
+ *
387
+ * @param markdown The markdown text to convert
388
+ * @returns Array of ClickUp comment blocks
389
+ */
390
+ function convertMarkdownToClickUpBlocks(markdown) {
391
+ const blocks = [];
392
+ try {
393
+ // Parse the entire markdown document using remark with GFM support (for task lists)
394
+ const tree = (0, unified_1.unified)()
395
+ .use(remark_parse_1.default)
396
+ .use(remark_gfm_1.default)
397
+ .parse(markdown);
398
+ // Walk the tree recursively
399
+ walkMdastNodes(tree.children, {}, blocks);
400
+ }
401
+ catch (error) {
402
+ console.error('Failed to parse markdown:', error);
403
+ // Fallback to plain text
404
+ return [{ text: markdown, attributes: {} }];
405
+ }
406
+ return blocks;
407
+ }
408
+ /**
409
+ * Recursively walk mdast nodes and convert to ClickUp blocks
410
+ * @param nodes Array of mdast nodes to process
411
+ * @param inheritedAttrs Formatting attributes inherited from parent nodes
412
+ * @param blocks Output array to append ClickUp blocks to
413
+ * @param depth Nesting depth for lists (0 = top level, 1 = first nest, etc.)
414
+ */
415
+ function walkMdastNodes(nodes, inheritedAttrs, blocks, depth = 0) {
416
+ for (let i = 0; i < nodes.length; i++) {
417
+ const node = nodes[i];
418
+ const currentAttrs = { ...inheritedAttrs };
419
+ switch (node.type) {
420
+ case 'heading':
421
+ // Process heading content with inline formatting
422
+ walkPhrasingContent(node.children, currentAttrs, blocks);
423
+ // Add newline with header attribute
424
+ blocks.push({ text: '\n', attributes: { header: node.depth } });
425
+ break;
426
+ case 'paragraph':
427
+ // Process paragraph content with inline formatting
428
+ walkPhrasingContent(node.children, currentAttrs, blocks);
429
+ // Add newline unless it's the last node
430
+ if (i < nodes.length - 1) {
431
+ blocks.push({ text: '\n', attributes: {} });
432
+ }
433
+ break;
434
+ case 'blockquote':
435
+ // ClickUp limitation: Blockquotes only support paragraph content, not headers or lists
436
+ // For complex blockquote content (headers, lists), only paragraph text is preserved
437
+ const blockquoteChildren = node.children;
438
+ for (const child of blockquoteChildren) {
439
+ if (child.type === 'paragraph') {
440
+ walkPhrasingContent(child.children, currentAttrs, blocks);
441
+ blocks.push({ text: '\n', attributes: { blockquote: {} } });
442
+ }
443
+ // Note: Other child types (heading, list) are not supported by ClickUp blockquotes
444
+ // and will be silently skipped, preserving only inline paragraph content
445
+ }
446
+ break;
447
+ case 'list':
448
+ const listNode = node;
449
+ const listType = listNode.ordered ? 'ordered' : 'bullet';
450
+ for (const item of listNode.children) {
451
+ const listItem = item;
452
+ // Check if it's a checkbox item
453
+ const isChecked = listItem.checked === true;
454
+ const isUnchecked = listItem.checked === false;
455
+ const finalListType = isChecked ? 'checked' : isUnchecked ? 'unchecked' : listType;
456
+ // Process list item content
457
+ for (const itemChild of listItem.children) {
458
+ if (itemChild.type === 'paragraph') {
459
+ // Process paragraph content with inline formatting
460
+ walkPhrasingContent(itemChild.children, currentAttrs, blocks);
461
+ // Add newline with list formatting and optional indent
462
+ const listAttrs = {
463
+ list: { list: finalListType }
464
+ };
465
+ // Add indent for nested lists (depth 0 = no indent, depth 1+ = indented)
466
+ if (depth > 0) {
467
+ listAttrs.indent = depth;
468
+ }
469
+ blocks.push({ text: '\n', attributes: listAttrs });
470
+ }
471
+ else if (itemChild.type === 'list') {
472
+ // Nested list - recursively process with increased depth
473
+ walkMdastNodes([itemChild], currentAttrs, blocks, depth + 1);
474
+ }
475
+ }
476
+ }
477
+ break;
478
+ case 'code':
479
+ // Code block
480
+ const codeNode = node;
481
+ if (codeNode.value) {
482
+ blocks.push({ text: codeNode.value, attributes: {} });
483
+ blocks.push({
484
+ text: '\n',
485
+ attributes: { 'code-block': { 'code-block': codeNode.lang || 'plain' } }
486
+ });
487
+ }
488
+ break;
489
+ case 'thematicBreak':
490
+ // Horizontal rule - just add a line break
491
+ blocks.push({ text: '\n', attributes: {} });
492
+ break;
493
+ default:
494
+ // For any other block-level nodes, try to process children
495
+ if ('children' in node && Array.isArray(node.children)) {
496
+ walkMdastNodes(node.children, currentAttrs, blocks, depth);
497
+ }
498
+ break;
499
+ }
500
+ }
501
+ }
502
+ /**
503
+ * Recursively walk phrasing content (inline nodes) and build ClickUp blocks
504
+ * Accumulates formatting attributes from parent nodes
505
+ */
506
+ function walkPhrasingContent(nodes, inheritedAttrs, blocks) {
507
+ for (const node of nodes) {
508
+ const currentAttrs = { ...inheritedAttrs };
509
+ switch (node.type) {
510
+ case 'text':
511
+ // Plain text node
512
+ if (node.value) {
513
+ blocks.push({
514
+ text: node.value,
515
+ attributes: Object.keys(currentAttrs).length > 0 ? currentAttrs : {}
516
+ });
517
+ }
518
+ break;
519
+ case 'strong':
520
+ // Bold text - recurse with bold attribute
521
+ currentAttrs.bold = true;
522
+ walkPhrasingContent(node.children, currentAttrs, blocks);
523
+ break;
524
+ case 'emphasis':
525
+ // Italic text - recurse with italic attribute
526
+ currentAttrs.italic = true;
527
+ walkPhrasingContent(node.children, currentAttrs, blocks);
528
+ break;
529
+ case 'inlineCode':
530
+ // Inline code
531
+ if (node.value) {
532
+ currentAttrs.code = true;
533
+ blocks.push({
534
+ text: node.value,
535
+ attributes: currentAttrs
536
+ });
537
+ }
538
+ break;
539
+ case 'link':
540
+ // Link - recurse with link attribute
541
+ currentAttrs.link = node.url;
542
+ walkPhrasingContent(node.children, currentAttrs, blocks);
543
+ break;
544
+ case 'break':
545
+ // Line break - add as plain text
546
+ blocks.push({ text: '\n', attributes: {} });
547
+ break;
548
+ default:
549
+ // For any other node types, try to extract text if available
550
+ if ('value' in node && typeof node.value === 'string') {
551
+ blocks.push({
552
+ text: node.value,
553
+ attributes: Object.keys(currentAttrs).length > 0 ? currentAttrs : {}
554
+ });
555
+ }
556
+ else if ('children' in node && Array.isArray(node.children)) {
557
+ // Recurse into children for other container nodes
558
+ walkPhrasingContent(node.children, currentAttrs, blocks);
559
+ }
560
+ break;
561
+ }
562
+ }
563
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ declare const serverPromise: Promise<McpServer>;
4
+ export { serverPromise };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsIpE,QAAA,MAAM,aAAa,oBAAqB,CAAC;AAIzC,OAAO,EAAE,aAAa,EAAE,CAAC"}