@hauptsache.net/clickup-mcp 1.7.0 → 1.7.3
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/README.md +9 -4
- package/dist/clickup-text.d.ts +15 -0
- package/dist/clickup-text.d.ts.map +1 -1
- package/dist/clickup-text.js +202 -21
- package/dist/shared/attachments.d.ts +37 -16
- package/dist/shared/attachments.d.ts.map +1 -1
- package/dist/shared/attachments.js +45 -31
- package/dist/shared/config.d.ts +1 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +41 -1
- package/dist/shared/utils.d.ts +1 -1
- package/dist/shared/utils.js +3 -3
- package/dist/tools/task-tools.js +3 -3
- package/dist/tools/task-write-tools.d.ts.map +1 -1
- package/dist/tools/task-write-tools.js +297 -51
- package/dist/tools/time-tools.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -188,6 +188,7 @@ The ClickUp MCP supports three operational modes to balance functionality, secur
|
|
|
188
188
|
|------------------------|:------------:|:----:|:-----:|-----------------------------------------------------------------------------------------|
|
|
189
189
|
| `getTaskById` | ✅ | ✅ | ✅ | Get complete task details including comments, images, and metadata |
|
|
190
190
|
| `addComment` | ❌ | ❌ | ✅ | Add comments to tasks for collaboration |
|
|
191
|
+
| `editComment` | ❌ | ❌ | ✅ | Correct your own comment within 24h instead of posting a follow-up |
|
|
191
192
|
| `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.) with **SAFE APPEND-ONLY** descriptions |
|
|
192
193
|
| `createTask` | ❌ | ❌ | ✅ | Create new tasks with full markdown support |
|
|
193
194
|
| `searchTasks` | ✅ | ✅ | ✅ | Find tasks by content, keywords, assignees, or project context |
|
|
@@ -223,7 +224,7 @@ Add the mode to your MCP configuration:
|
|
|
223
224
|
|
|
224
225
|
## Configuration
|
|
225
226
|
|
|
226
|
-
This MCP server can be configured using environment variables
|
|
227
|
+
This MCP server can be configured using environment variables. In the desktop extension (`.mcpb`) installer, the API key, team ID, primary language, upload size limit and comment edit window are offered as form fields; the remaining variables have to be set on the server environment directly.
|
|
227
228
|
|
|
228
229
|
- `CLICKUP_API_KEY`: (Required) Your ClickUp API key.
|
|
229
230
|
- `CLICKUP_TEAM_ID`: (Required) Your ClickUp Team ID (formerly Workspace ID).
|
|
@@ -231,6 +232,7 @@ This MCP server can be configured using environment variables:
|
|
|
231
232
|
- `MAX_IMAGES`: (Optional) The maximum number of images to return for a task in `getTaskById`. Defaults to 4.
|
|
232
233
|
- `MAX_RESPONSE_SIZE_MB`: (Optional) The maximum response size in megabytes for `getTaskById`. Uses intelligent size budgeting to fit the most important images within the limit. Defaults to 1.
|
|
233
234
|
- `MAX_UPLOAD_SIZE_MB`: (Optional) The maximum size of a single image uploaded when writing comments or descriptions. Defaults to 10.
|
|
235
|
+
- `CLICKUP_COMMENT_EDIT_WINDOW_HOURS`: (Optional) How long after creation `editComment` may still rewrite a comment. Defaults to 24. Set to `0` to disable comment editing entirely.
|
|
234
236
|
- `CLICKUP_PRIMARY_LANGUAGE`: (Optional) A hint for the primary language used in your ClickUp tasks (e.g., "de" for German, "en" for English). This helps the `searchTask` tool provide more tailored guidance in its description for multilingual searches.
|
|
235
237
|
- `LANG`: (Optional) If `CLICKUP_PRIMARY_LANGUAGE` is not set, the MCP will check this standard environment variable (e.g., "en_US.UTF-8", "de_DE") as a fallback to infer the primary language.
|
|
236
238
|
|
|
@@ -288,7 +290,7 @@ This ensures no existing content is ever lost while maintaining a clear audit tr
|
|
|
288
290
|
|
|
289
291
|
## Writing Images Into Tickets
|
|
290
292
|
|
|
291
|
-
`addComment`, `createTask` and `updateTask` accept images as ordinary markdown. Because
|
|
293
|
+
`addComment`, `editComment`, `createTask` and `updateTask` accept images as ordinary markdown. Because
|
|
292
294
|
this server runs locally, it reads the file itself - so a **local path is enough**:
|
|
293
295
|
|
|
294
296
|
```markdown
|
|
@@ -311,8 +313,11 @@ Notes:
|
|
|
311
313
|
- **An image inside a numbered list breaks ClickUp's numbering.** Write walkthrough
|
|
312
314
|
steps as bold lines with the image between them, as above.
|
|
313
315
|
- Only real PNG/JPEG/GIF/WebP files are uploaded - the content is checked, not the
|
|
314
|
-
extension. A file that fails
|
|
315
|
-
|
|
316
|
+
extension. A file that fails **aborts the write**: `addComment`, `editComment` and
|
|
317
|
+
`updateTask` report every broken reference and change nothing, so the markdown can be
|
|
318
|
+
fixed and the call retried without creating duplicates. `createTask` validates its
|
|
319
|
+
images before creating the task; only an upload failing afterwards is reported as a
|
|
320
|
+
warning, since the task already exists at that point.
|
|
316
321
|
- Attachments always belong to a task, so document pages cannot embed uploads this way.
|
|
317
322
|
|
|
318
323
|
## Performance & Limitations
|
package/dist/clickup-text.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { ImageMetadataBlock } from "./shared/types";
|
|
|
6
6
|
export interface ClickUpTextItem {
|
|
7
7
|
text?: string;
|
|
8
8
|
type?: string;
|
|
9
|
+
task_mention?: {
|
|
10
|
+
task_id?: string;
|
|
11
|
+
};
|
|
9
12
|
image?: {
|
|
10
13
|
id?: string;
|
|
11
14
|
name?: string;
|
|
@@ -54,6 +57,7 @@ export interface ClickUpCommentBlock {
|
|
|
54
57
|
attributes?: {
|
|
55
58
|
bold?: boolean;
|
|
56
59
|
italic?: boolean;
|
|
60
|
+
strike?: boolean;
|
|
57
61
|
code?: boolean;
|
|
58
62
|
link?: string;
|
|
59
63
|
'code-block'?: {
|
|
@@ -72,6 +76,13 @@ export interface ClickUpCommentBlock {
|
|
|
72
76
|
list?: {
|
|
73
77
|
list: 'bullet' | 'ordered' | 'unchecked' | 'checked';
|
|
74
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Present on task mention fragments. ClickUp renders these as live task references
|
|
81
|
+
* showing the current task name, status and assignee.
|
|
82
|
+
*/
|
|
83
|
+
task_mention?: {
|
|
84
|
+
task_id: string;
|
|
85
|
+
};
|
|
75
86
|
/**
|
|
76
87
|
* Present on image fragments. ClickUp only renders a preview when this holds the
|
|
77
88
|
* complete attachment object from the upload response - a bare URL string produces
|
|
@@ -111,6 +122,10 @@ export interface EmbeddableAttachment {
|
|
|
111
122
|
* `title`/`text` carry the caption; the rest is copied straight from the upload response.
|
|
112
123
|
*/
|
|
113
124
|
export declare function buildImageFragment(attachment: EmbeddableAttachment, caption: string): ClickUpCommentBlock;
|
|
125
|
+
/**
|
|
126
|
+
* Extract the task ID from a ClickUp task URL, or null if it is not one.
|
|
127
|
+
*/
|
|
128
|
+
export declare function parseClickUpTaskUrl(url: string): string | null;
|
|
114
129
|
/**
|
|
115
130
|
* Wrap image destinations that contain spaces in angle brackets.
|
|
116
131
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAOpD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"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"}
|
package/dist/clickup-text.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.convertClickUpTextItemsToToolCallResult = convertClickUpTextItemsToToolCallResult;
|
|
7
7
|
exports.convertMarkdownToToolCallResult = convertMarkdownToToolCallResult;
|
|
8
8
|
exports.buildImageFragment = buildImageFragment;
|
|
9
|
+
exports.parseClickUpTaskUrl = parseClickUpTaskUrl;
|
|
9
10
|
exports.normalizeImageDestinations = normalizeImageDestinations;
|
|
10
11
|
exports.collectMarkdownImageSources = collectMarkdownImageSources;
|
|
11
12
|
exports.rewriteMarkdownImageUrls = rewriteMarkdownImageUrls;
|
|
@@ -35,6 +36,18 @@ function extractThumbnailsFromDataAttachment(attributes) {
|
|
|
35
36
|
return {};
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Render an image reference as markdown, escaping whatever would break the syntax.
|
|
41
|
+
*
|
|
42
|
+
* Reading and writing use the same markdown here on purpose: it lets an agent feed a
|
|
43
|
+
* comment it just read straight back into editComment without losing the images.
|
|
44
|
+
*/
|
|
45
|
+
function toMarkdownImage(alt, url) {
|
|
46
|
+
const safeAlt = alt.replace(/[\[\]\r\n]/g, " ").trim();
|
|
47
|
+
// Angle brackets let a URL with spaces or parentheses survive the round trip
|
|
48
|
+
const safeUrl = /[\s()]/.test(url) ? `<${url}>` : url;
|
|
49
|
+
return ``;
|
|
50
|
+
}
|
|
38
51
|
/**
|
|
39
52
|
* Process an array of ClickUp text items into a structured content format
|
|
40
53
|
* that includes both text and images in their original sequence
|
|
@@ -49,11 +62,24 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
49
62
|
// Track current formatting state to avoid unnecessary close/reopen
|
|
50
63
|
let activeBold = false;
|
|
51
64
|
let activeItalic = false;
|
|
65
|
+
let activeStrike = false;
|
|
52
66
|
let activeCode = false;
|
|
67
|
+
// ClickUp emits one '\n' fragment with a code-block attribute per code LINE;
|
|
68
|
+
// consecutive ones belong to the same fenced block when read back as markdown.
|
|
69
|
+
let inCodeBlock = false;
|
|
70
|
+
let currentFenceLang = '';
|
|
71
|
+
const closeCodeFence = () => {
|
|
72
|
+
if (inCodeBlock) {
|
|
73
|
+
currentTextBlock += '```\n';
|
|
74
|
+
inCodeBlock = false;
|
|
75
|
+
currentFenceLang = '';
|
|
76
|
+
}
|
|
77
|
+
};
|
|
53
78
|
for (let i = 0; i < textItems.length; i++) {
|
|
54
79
|
const item = textItems[i];
|
|
55
80
|
// Handle image items
|
|
56
81
|
if (item.type === "image" && item.image && item.image.url) {
|
|
82
|
+
closeCodeFence();
|
|
57
83
|
const imageFileName = item.image.name || item.image.title || "image";
|
|
58
84
|
const imageUrl = item.image.url;
|
|
59
85
|
const altText = item.text || imageFileName;
|
|
@@ -84,8 +110,10 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
84
110
|
}
|
|
85
111
|
continue;
|
|
86
112
|
}
|
|
87
|
-
//
|
|
88
|
-
|
|
113
|
+
// Reference the image in the same markdown syntax the write tools accept, so a
|
|
114
|
+
// comment read here can be handed back to editComment unchanged and keep its
|
|
115
|
+
// images - an existing ClickUp attachment URL is re-embedded without re-uploading.
|
|
116
|
+
currentTextBlock += `\n${toMarkdownImage(altText, imageUrl)}`;
|
|
89
117
|
// Get working thumbnail URLs from data-attachment if available
|
|
90
118
|
const extractedThumbnails = extractThumbnailsFromDataAttachment(item.attributes);
|
|
91
119
|
// Determine best thumbnail URLs (prefer extracted over API thumbnails)
|
|
@@ -118,6 +146,27 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
118
146
|
else if (typeof item.text === "string") {
|
|
119
147
|
// Check if this is a newline with block formatting (header, blockquote, list)
|
|
120
148
|
if (item.text === '\n' && item.attributes) {
|
|
149
|
+
// Code block formatting: append this line to the open fence (or open one).
|
|
150
|
+
// The attribute value carries the language, either directly or nested as
|
|
151
|
+
// {'code-block': lang}; 'plain' means no language.
|
|
152
|
+
if (item.attributes['code-block']) {
|
|
153
|
+
const rawLang = item.attributes['code-block'];
|
|
154
|
+
const lang = typeof rawLang === 'string' ? rawLang : rawLang?.['code-block'] ?? '';
|
|
155
|
+
const fenceLang = lang === 'plain' ? '' : lang;
|
|
156
|
+
if (inCodeBlock && fenceLang !== currentFenceLang) {
|
|
157
|
+
closeCodeFence();
|
|
158
|
+
}
|
|
159
|
+
if (!inCodeBlock) {
|
|
160
|
+
currentTextBlock += '```' + fenceLang + '\n';
|
|
161
|
+
inCodeBlock = true;
|
|
162
|
+
currentFenceLang = fenceLang;
|
|
163
|
+
}
|
|
164
|
+
currentTextBlock += currentLine + '\n';
|
|
165
|
+
currentLine = "";
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Any other line terminator ends a code block
|
|
169
|
+
closeCodeFence();
|
|
121
170
|
// Header formatting
|
|
122
171
|
if (item.attributes.header) {
|
|
123
172
|
const level = item.attributes.header;
|
|
@@ -148,17 +197,8 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
148
197
|
break;
|
|
149
198
|
}
|
|
150
199
|
}
|
|
151
|
-
// Code block formatting
|
|
152
|
-
else if (item.attributes['code-block']) {
|
|
153
|
-
// Wrap the current line in code block markers
|
|
154
|
-
currentLine = '```\n' + currentLine + '\n```';
|
|
155
|
-
}
|
|
156
200
|
// Add formatted line to text block
|
|
157
|
-
currentTextBlock += currentLine;
|
|
158
|
-
// Add newline unless it's code block (already has newlines)
|
|
159
|
-
if (!item.attributes['code-block']) {
|
|
160
|
-
currentTextBlock += '\n';
|
|
161
|
-
}
|
|
201
|
+
currentTextBlock += currentLine + '\n';
|
|
162
202
|
currentLine = ""; // Reset for next line
|
|
163
203
|
continue;
|
|
164
204
|
}
|
|
@@ -167,15 +207,18 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
167
207
|
// Determine current and next formatting state
|
|
168
208
|
const hasBold = item.attributes?.bold === true;
|
|
169
209
|
const hasItalic = item.attributes?.italic === true;
|
|
210
|
+
const hasStrike = item.attributes?.strike === true;
|
|
170
211
|
const hasLink = item.attributes?.link;
|
|
171
212
|
// Look ahead to next non-newline block
|
|
172
213
|
let nextHasBold = false;
|
|
173
214
|
let nextHasItalic = false;
|
|
215
|
+
let nextHasStrike = false;
|
|
174
216
|
for (let j = i + 1; j < textItems.length; j++) {
|
|
175
217
|
const nextItem = textItems[j];
|
|
176
218
|
if (nextItem.text !== '\n' || !nextItem.attributes) {
|
|
177
219
|
nextHasBold = nextItem.attributes?.bold === true;
|
|
178
220
|
nextHasItalic = nextItem.attributes?.italic === true;
|
|
221
|
+
nextHasStrike = nextItem.attributes?.strike === true;
|
|
179
222
|
break;
|
|
180
223
|
}
|
|
181
224
|
}
|
|
@@ -185,14 +228,20 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
185
228
|
prefix += "**";
|
|
186
229
|
if (hasItalic && !activeItalic)
|
|
187
230
|
prefix += "*";
|
|
231
|
+
if (hasStrike && !activeStrike)
|
|
232
|
+
prefix += "~~";
|
|
188
233
|
// Build suffix (close formatting that won't continue)
|
|
189
234
|
let suffix = "";
|
|
235
|
+
if (hasStrike && !nextHasStrike)
|
|
236
|
+
suffix += "~~";
|
|
190
237
|
if (hasItalic && !nextHasItalic)
|
|
191
238
|
suffix += "*";
|
|
192
239
|
if (hasBold && !nextHasBold)
|
|
193
240
|
suffix += "**";
|
|
194
241
|
// Close formatting that's active but not in this block
|
|
195
242
|
let closingPrefix = "";
|
|
243
|
+
if (activeStrike && !hasStrike)
|
|
244
|
+
closingPrefix += "~~";
|
|
196
245
|
if (activeBold && !hasBold)
|
|
197
246
|
closingPrefix += "**";
|
|
198
247
|
if (activeItalic && !hasItalic)
|
|
@@ -201,6 +250,7 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
201
250
|
// Update state
|
|
202
251
|
activeBold = hasBold && nextHasBold;
|
|
203
252
|
activeItalic = hasItalic && nextHasItalic;
|
|
253
|
+
activeStrike = hasStrike && nextHasStrike;
|
|
204
254
|
// Link formatting (wraps everything)
|
|
205
255
|
if (hasLink) {
|
|
206
256
|
formattedText = `[${formattedText}](${hasLink})`;
|
|
@@ -212,6 +262,7 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
212
262
|
// Add to current line (not text block yet)
|
|
213
263
|
if (item.text === '\n') {
|
|
214
264
|
// Plain newline without formatting
|
|
265
|
+
closeCodeFence();
|
|
215
266
|
currentTextBlock += currentLine + '\n';
|
|
216
267
|
currentLine = "";
|
|
217
268
|
}
|
|
@@ -219,12 +270,18 @@ async function convertClickUpTextItemsToToolCallResult(textItems) {
|
|
|
219
270
|
currentLine += formattedText;
|
|
220
271
|
}
|
|
221
272
|
}
|
|
273
|
+
// Task mentions render as the task URL so the reference survives the round trip:
|
|
274
|
+
// writing that URL back through addComment/editComment regenerates the mention.
|
|
275
|
+
else if (item.type === "task_mention" && item.task_mention?.task_id) {
|
|
276
|
+
currentLine += `https://app.clickup.com/t/${item.task_mention.task_id}`;
|
|
277
|
+
}
|
|
222
278
|
// Handle other types of items like bookmarks or whatever clickup can think of
|
|
223
279
|
else {
|
|
224
280
|
currentTextBlock += JSON.stringify(item);
|
|
225
281
|
}
|
|
226
282
|
}
|
|
227
283
|
// Add any remaining text
|
|
284
|
+
closeCodeFence();
|
|
228
285
|
if (currentLine) {
|
|
229
286
|
currentTextBlock += currentLine;
|
|
230
287
|
}
|
|
@@ -292,9 +349,9 @@ function convertMarkdownToToolCallResult(markdownText, attachments) {
|
|
|
292
349
|
// Check if this image URL exists in our attachments
|
|
293
350
|
const attachment = attachmentMap.get(imageUrl);
|
|
294
351
|
if (attachment) {
|
|
295
|
-
//
|
|
352
|
+
// Keep the markdown syntax, so the reference stays usable in a write call
|
|
296
353
|
const imageFileName = altText || "image";
|
|
297
|
-
currentTextBlock += `\
|
|
354
|
+
currentTextBlock += `\n${toMarkdownImage(imageFileName, imageUrl)}`;
|
|
298
355
|
// Only create image_metadata if we have at least one thumbnail (never use original image)
|
|
299
356
|
if (attachment.thumbnail_large || attachment.thumbnail_medium || attachment.thumbnail_small) {
|
|
300
357
|
// Push accumulated text (including image URL) as a text block
|
|
@@ -411,6 +468,22 @@ function buildImageFragment(attachment, caption) {
|
|
|
411
468
|
}
|
|
412
469
|
return fragment;
|
|
413
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Matches a plain ClickUp task URL, with or without the team segment:
|
|
473
|
+
* https://app.clickup.com/t/86cb3t6t2 or https://app.clickup.com/t/4500611/86cb3t6t2
|
|
474
|
+
*
|
|
475
|
+
* Deliberately narrow: custom task IDs (PREFIX-123) and URLs carrying a query or
|
|
476
|
+
* fragment (e.g. ?comment=... deep links) do NOT match, because a mention would
|
|
477
|
+
* either not resolve or lose the anchor - those stay ordinary links.
|
|
478
|
+
*/
|
|
479
|
+
const CLICKUP_TASK_URL_PATTERN = /^https?:\/\/app\.clickup\.com\/t\/(?:\d+\/)?([a-z0-9]{6,16})\/?$/;
|
|
480
|
+
/**
|
|
481
|
+
* Extract the task ID from a ClickUp task URL, or null if it is not one.
|
|
482
|
+
*/
|
|
483
|
+
function parseClickUpTaskUrl(url) {
|
|
484
|
+
const match = url.match(CLICKUP_TASK_URL_PATTERN);
|
|
485
|
+
return match ? match[1] : null;
|
|
486
|
+
}
|
|
414
487
|
/**
|
|
415
488
|
* Wrap image destinations that contain spaces in angle brackets.
|
|
416
489
|
*
|
|
@@ -585,25 +658,119 @@ function walkMdastNodes(nodes, inheritedAttrs, blocks, depth = 0, attachmentsByS
|
|
|
585
658
|
// Code block
|
|
586
659
|
const codeNode = node;
|
|
587
660
|
if (codeNode.value) {
|
|
588
|
-
blocks
|
|
589
|
-
blocks.push({
|
|
590
|
-
text: '\n',
|
|
591
|
-
attributes: { 'code-block': { 'code-block': codeNode.lang || 'plain' } }
|
|
592
|
-
});
|
|
661
|
+
pushCodeBlockLines(blocks, codeNode.value, codeNode.lang || 'plain');
|
|
593
662
|
}
|
|
594
663
|
break;
|
|
595
664
|
case 'thematicBreak':
|
|
596
665
|
// Horizontal rule - just add a line break
|
|
597
666
|
blocks.push({ text: '\n', attributes: {} });
|
|
598
667
|
break;
|
|
668
|
+
case 'table': {
|
|
669
|
+
// ClickUp comments cannot render tables (there is no table fragment in the
|
|
670
|
+
// comment format - unknown attributes are stored but render as plain text).
|
|
671
|
+
// Re-render the table as an aligned pipe table inside a code block so the
|
|
672
|
+
// information survives and stays readable in monospace.
|
|
673
|
+
const tableText = serializeTableAsAlignedPipes(node);
|
|
674
|
+
if (tableText) {
|
|
675
|
+
pushCodeBlockLines(blocks, tableText, 'plain');
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
599
679
|
default:
|
|
600
680
|
// For any other block-level nodes, try to process children
|
|
601
681
|
if ('children' in node && Array.isArray(node.children)) {
|
|
602
682
|
walkMdastNodes(node.children, currentAttrs, blocks, depth, attachmentsBySrc);
|
|
603
683
|
}
|
|
684
|
+
else if ('value' in node && typeof node.value === 'string') {
|
|
685
|
+
// Last-resort safety net: never drop text content silently
|
|
686
|
+
blocks.push({ text: node.value, attributes: {} });
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Emit a (possibly multi-line) code block the way ClickUp's Quill-based format
|
|
694
|
+
* expects it: block attributes apply per line, so EVERY line needs its own '\n'
|
|
695
|
+
* fragment carrying the code-block attribute. A single text fragment with embedded
|
|
696
|
+
* newlines renders only its last line as code - the rest degrades to plain text.
|
|
697
|
+
*/
|
|
698
|
+
function pushCodeBlockLines(blocks, code, lang) {
|
|
699
|
+
for (const line of code.split('\n')) {
|
|
700
|
+
if (line) {
|
|
701
|
+
blocks.push({ text: line, attributes: {} });
|
|
702
|
+
}
|
|
703
|
+
blocks.push({
|
|
704
|
+
text: '\n',
|
|
705
|
+
attributes: { 'code-block': { 'code-block': lang } }
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Serialize phrasing content back to compact markdown for use inside a code block.
|
|
711
|
+
* Inline formatting markers are kept so nothing is lost, even though a code block
|
|
712
|
+
* renders them literally.
|
|
713
|
+
*/
|
|
714
|
+
function serializePhrasingToMarkdown(nodes) {
|
|
715
|
+
let out = '';
|
|
716
|
+
for (const node of nodes) {
|
|
717
|
+
switch (node.type) {
|
|
718
|
+
case 'text':
|
|
719
|
+
out += node.value;
|
|
720
|
+
break;
|
|
721
|
+
case 'strong':
|
|
722
|
+
out += `**${serializePhrasingToMarkdown(node.children)}**`;
|
|
723
|
+
break;
|
|
724
|
+
case 'emphasis':
|
|
725
|
+
out += `*${serializePhrasingToMarkdown(node.children)}*`;
|
|
726
|
+
break;
|
|
727
|
+
case 'delete':
|
|
728
|
+
out += `~~${serializePhrasingToMarkdown(node.children)}~~`;
|
|
729
|
+
break;
|
|
730
|
+
case 'inlineCode':
|
|
731
|
+
out += `\`${node.value}\``;
|
|
732
|
+
break;
|
|
733
|
+
case 'link':
|
|
734
|
+
out += `[${serializePhrasingToMarkdown(node.children)}](${node.url})`;
|
|
735
|
+
break;
|
|
736
|
+
case 'image':
|
|
737
|
+
out += node.alt || node.url;
|
|
738
|
+
break;
|
|
739
|
+
case 'break':
|
|
740
|
+
out += ' ';
|
|
741
|
+
break;
|
|
742
|
+
default:
|
|
743
|
+
if ('value' in node && typeof node.value === 'string') {
|
|
744
|
+
out += node.value;
|
|
745
|
+
}
|
|
746
|
+
else if ('children' in node && Array.isArray(node.children)) {
|
|
747
|
+
out += serializePhrasingToMarkdown(node.children);
|
|
748
|
+
}
|
|
604
749
|
break;
|
|
605
750
|
}
|
|
606
751
|
}
|
|
752
|
+
return out;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Render an mdast table as a column-aligned pipe table string.
|
|
756
|
+
*/
|
|
757
|
+
function serializeTableAsAlignedPipes(table) {
|
|
758
|
+
const rows = table.children.map((row) => row.children.map((cell) => serializePhrasingToMarkdown(cell.children).replace(/\|/g, '\\|').trim()));
|
|
759
|
+
if (rows.length === 0) {
|
|
760
|
+
return '';
|
|
761
|
+
}
|
|
762
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
763
|
+
const widths = [];
|
|
764
|
+
for (let col = 0; col < colCount; col++) {
|
|
765
|
+
widths[col] = Math.max(3, ...rows.map((r) => (r[col] ?? '').length));
|
|
766
|
+
}
|
|
767
|
+
const renderRow = (cells) => '| ' + widths.map((w, col) => (cells[col] ?? '').padEnd(w)).join(' | ') + ' |';
|
|
768
|
+
const lines = [renderRow(rows[0])];
|
|
769
|
+
lines.push('| ' + widths.map((w) => '-'.repeat(w)).join(' | ') + ' |');
|
|
770
|
+
for (const row of rows.slice(1)) {
|
|
771
|
+
lines.push(renderRow(row));
|
|
772
|
+
}
|
|
773
|
+
return lines.join('\n');
|
|
607
774
|
}
|
|
608
775
|
/**
|
|
609
776
|
* Recursively walk phrasing content (inline nodes) and build ClickUp blocks
|
|
@@ -652,6 +819,11 @@ function walkPhrasingContent(nodes, inheritedAttrs, blocks, attachmentsBySrc) {
|
|
|
652
819
|
currentAttrs.italic = true;
|
|
653
820
|
walkPhrasingContent(node.children, currentAttrs, blocks, attachmentsBySrc);
|
|
654
821
|
break;
|
|
822
|
+
case 'delete':
|
|
823
|
+
// GFM strikethrough (~~text~~) - ClickUp renders this via the strike attribute
|
|
824
|
+
currentAttrs.strike = true;
|
|
825
|
+
walkPhrasingContent(node.children, currentAttrs, blocks, attachmentsBySrc);
|
|
826
|
+
break;
|
|
655
827
|
case 'inlineCode':
|
|
656
828
|
// Inline code
|
|
657
829
|
if (node.value) {
|
|
@@ -662,11 +834,20 @@ function walkPhrasingContent(nodes, inheritedAttrs, blocks, attachmentsBySrc) {
|
|
|
662
834
|
});
|
|
663
835
|
}
|
|
664
836
|
break;
|
|
665
|
-
case 'link':
|
|
666
|
-
//
|
|
837
|
+
case 'link': {
|
|
838
|
+
// A link to a ClickUp task becomes a real task mention, matching what the
|
|
839
|
+
// ClickUp UI does when a task URL is pasted. The mention renders the live
|
|
840
|
+
// task name, so any custom link text is intentionally replaced by it.
|
|
841
|
+
const mentionedTaskId = parseClickUpTaskUrl(node.url);
|
|
842
|
+
if (mentionedTaskId) {
|
|
843
|
+
blocks.push({ type: 'task_mention', task_mention: { task_id: mentionedTaskId } });
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
// Ordinary link - recurse with link attribute
|
|
667
847
|
currentAttrs.link = node.url;
|
|
668
848
|
walkPhrasingContent(node.children, currentAttrs, blocks, attachmentsBySrc);
|
|
669
849
|
break;
|
|
850
|
+
}
|
|
670
851
|
case 'break':
|
|
671
852
|
// Line break - add as plain text
|
|
672
853
|
blocks.push({ text: '\n', attributes: {} });
|
|
@@ -61,31 +61,52 @@ export declare function captionToFilename(caption: string, fallbackName: string)
|
|
|
61
61
|
* Upload a single image to a task and return the full attachment object.
|
|
62
62
|
*/
|
|
63
63
|
export declare function uploadTaskAttachment(taskId: string, filename: string, bytes: Buffer, mimeType: string): Promise<ClickUpUploadedAttachment>;
|
|
64
|
-
/**
|
|
65
|
-
export interface
|
|
64
|
+
/** A markdown image whose source resolved to uploadable bytes or an existing attachment */
|
|
65
|
+
export interface ResolvedMarkdownImage {
|
|
66
66
|
/** The original `src` as written in the markdown */
|
|
67
67
|
src: string;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
alt: string;
|
|
69
|
+
resolved: ResolvedImageSource;
|
|
70
|
+
}
|
|
71
|
+
/** One markdown image reference that could not be used, and why */
|
|
72
|
+
export interface ImageFailure {
|
|
73
|
+
src: string;
|
|
74
|
+
error: string;
|
|
72
75
|
}
|
|
73
76
|
/**
|
|
74
|
-
*
|
|
77
|
+
* Phase 1 of attaching images: resolve every source without writing anything.
|
|
75
78
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
79
|
+
* Reads local files, downloads http(s) URLs, decodes data URIs and validates
|
|
80
|
+
* magic bytes and size. Failures are collected instead of thrown so the caller
|
|
81
|
+
* can report every broken reference at once - and abort before anything is
|
|
82
|
+
* posted to ClickUp. Identical sources are resolved once.
|
|
80
83
|
*/
|
|
81
|
-
export declare function
|
|
84
|
+
export declare function resolveMarkdownImages(images: {
|
|
82
85
|
src: string;
|
|
83
86
|
alt: string;
|
|
84
|
-
}[], baseDir?: string): Promise<
|
|
87
|
+
}[], baseDir?: string): Promise<{
|
|
88
|
+
resolved: ResolvedMarkdownImage[];
|
|
89
|
+
failures: ImageFailure[];
|
|
90
|
+
}>;
|
|
91
|
+
/** A successfully uploaded (or reused) attachment for one markdown source */
|
|
92
|
+
export interface UploadedMarkdownImage {
|
|
93
|
+
src: string;
|
|
94
|
+
attachment: ClickUpUploadedAttachment;
|
|
95
|
+
}
|
|
85
96
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
97
|
+
* Phase 2: upload the resolved images to the task.
|
|
98
|
+
*
|
|
99
|
+
* Uploads run sequentially: a typical comment has a handful of screenshots, and
|
|
100
|
+
* N uploads plus one write call stays well inside ClickUp's 100 calls/minute.
|
|
101
|
+
* Stops at the first upload error - an API failure is unlikely to heal mid-batch,
|
|
102
|
+
* and everything uploaded so far is returned so the caller can tell a retry to
|
|
103
|
+
* reference those CDN URLs directly instead of uploading again.
|
|
88
104
|
*/
|
|
89
|
-
export declare function
|
|
105
|
+
export declare function uploadResolvedImages(taskId: string, images: ResolvedMarkdownImage[]): Promise<{
|
|
106
|
+
uploaded: UploadedMarkdownImage[];
|
|
107
|
+
failure: ImageFailure | null;
|
|
108
|
+
}>;
|
|
109
|
+
/** Map from markdown `src` to the attachment that should be embedded for it. */
|
|
110
|
+
export declare function toAttachmentMap(uploaded: UploadedMarkdownImage[]): Map<string, ClickUpUploadedAttachment>;
|
|
90
111
|
export {};
|
|
91
112
|
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/shared/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAQhC;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,uCAAuC;AACvC,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,UAAU,gBAAgB;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAoCnE;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CA2C9B;AAcD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAS/E;AAmCD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,yBAAyB,CAAC,CAwBpC;AAED,
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/shared/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAQhC;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,uCAAuC;AACvC,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,UAAU,gBAAgB;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAoCnE;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CA2C9B;AAcD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAS/E;AAmCD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,yBAAyB,CAAC,CAwBpC;AAED,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EACtC,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC;IAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAqB1E;AAED,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,qBAAqB,EAAE,GAC9B,OAAO,CAAC;IAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CAAE,CAAC,CAwC9E;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,qBAAqB,EAAE,GAChC,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAMxC"}
|