@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 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 is reported in the response, and the comment or task is
315
- still written.
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
@@ -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;AA4BD;;;;;;GAMG;AACH,wBAAsB,uCAAuC,CAC3D,SAAS,EAAE,eAAe,EAAE,GAC3B,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAsNrE;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CA0I5D;AA8BD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE;YACb,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,EAAE,CAAC;QAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,IAAI,CAAC,EAAE;YACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;SACtD,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;KACtD,CAAC;IACF;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,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;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,oBAAoB,EAChC,OAAO,EAAE,MAAM,GACd,mBAAmB,CAsBrB;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqBnE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CAwB5F;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,GAClD,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,GACnD,mBAAmB,EAAE,CAoBvB"}
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,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,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;AAyCD;;;;;;GAMG;AACH,wBAAsB,uCAAuC,CAC3D,SAAS,EAAE,eAAe,EAAE,GAC3B,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAiQrE;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CA0I5D;AA8BD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE;YACb,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,EAAE,CAAC;QAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,IAAI,CAAC,EAAE;YACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;SACtD,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;KACtD,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,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;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,oBAAoB,EAChC,OAAO,EAAE,MAAM,GACd,mBAAmB,CAsBrB;AAYD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqBnE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CAwB5F;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,GAClD,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,GACnD,mBAAmB,EAAE,CAoBvB"}
@@ -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 `![${safeAlt}](${safeUrl})`;
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
- // Add image URL reference inline to current text block
88
- currentTextBlock += `\nImage: ${imageFileName} - ${imageUrl}`;
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
- // Add image URL reference inline to current text block
352
+ // Keep the markdown syntax, so the reference stays usable in a write call
296
353
  const imageFileName = altText || "image";
297
- currentTextBlock += `\nImage: ${imageFileName} - ${imageUrl}`;
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.push({ text: codeNode.value, attributes: {} });
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
- // Link - recurse with link attribute
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
- /** One markdown image reference and what became of it */
65
- export interface ImageUploadResult {
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
- /** Attachment to embed, or null when the upload failed */
69
- attachment: ClickUpUploadedAttachment | null;
70
- /** Reason the upload failed, for reporting back to the caller */
71
- error?: string;
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
- * Upload every image referenced in the markdown to the given task.
77
+ * Phase 1 of attaching images: resolve every source without writing anything.
75
78
  *
76
- * Uploads run sequentially: a typical comment has a handful of screenshots, and
77
- * N uploads plus one write call stays well inside ClickUp's 100 calls/minute.
78
- * A failing image never fails the whole batch - the caller writes the comment
79
- * anyway and reports which images did not make it.
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 uploadMarkdownImages(taskId: string, images: {
84
+ export declare function resolveMarkdownImages(images: {
82
85
  src: string;
83
86
  alt: string;
84
- }[], baseDir?: string): Promise<ImageUploadResult[]>;
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
- * Map from markdown `src` to the attachment that should be embedded for it.
87
- * Sources whose upload failed are absent, so the converters fall back to text.
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 toAttachmentMap(results: ImageUploadResult[]): Map<string, ClickUpUploadedAttachment>;
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,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EACtC,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAqD9B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,iBAAiB,EAAE,GAC3B,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAQxC"}
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"}