@krodak/clickup-cli 1.24.0 → 1.25.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clickup-cli",
3
3
  "description": "ClickUp CLI skills for managing tasks, sprints, comments, checklists, custom fields, tags, and time tracking via the cup command",
4
- "version": "1.24.0",
4
+ "version": "1.25.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -236,8 +236,8 @@ var ClickUpClient = class {
236
236
  body: JSON.stringify(options)
237
237
  });
238
238
  }
239
- async postComment(taskId, commentText, notifyAll) {
240
- const body = { comment_text: commentText };
239
+ async postComment(taskId, commentText, notifyAll, richBlocks) {
240
+ const body = richBlocks ? { comment: richBlocks } : { comment_text: commentText };
241
241
  if (notifyAll) body.notify_all = true;
242
242
  return this.request(this.taskPath(taskId, "/comment"), {
243
243
  method: "POST",
@@ -492,8 +492,8 @@ var ClickUpClient = class {
492
492
  method: "DELETE"
493
493
  });
494
494
  }
495
- async updateComment(commentId, text, resolved) {
496
- const body = { comment_text: text };
495
+ async updateComment(commentId, text, resolved, richBlocks) {
496
+ const body = richBlocks ? { comment: richBlocks } : { comment_text: text };
497
497
  if (resolved !== void 0) body.resolved = resolved;
498
498
  await this.request(`/comment/${commentId}`, {
499
499
  method: "PUT",
@@ -511,8 +511,8 @@ var ClickUpClient = class {
511
511
  "threaded comments"
512
512
  );
513
513
  }
514
- async createThreadedComment(commentId, text, notifyAll) {
515
- const body = { comment_text: text };
514
+ async createThreadedComment(commentId, text, notifyAll, richBlocks) {
515
+ const body = richBlocks ? { comment: richBlocks } : { comment_text: text };
516
516
  if (notifyAll) body.notify_all = true;
517
517
  await this.request(`/comment/${commentId}/reply`, {
518
518
  method: "POST",
@@ -2575,11 +2575,182 @@ async function fetchSubtasks(config, taskId, options = {}) {
2575
2575
  return tasks.map((t) => summarize(t, typeMap));
2576
2576
  }
2577
2577
 
2578
+ // src/commands/comment-format.ts
2579
+ var HEADER_RE = /^(#{1,6})\s+(.+)$/;
2580
+ var BULLET_RE = /^[-*]\s+(.+)$/;
2581
+ var ORDERED_RE = /^\d+\.\s+(.+)$/;
2582
+ var BLOCKQUOTE_RE = /^>\s+(.+)$/;
2583
+ var HR_RE = /^(---+|\*\*\*+|___+)\s*$/;
2584
+ var FENCED_OPEN_RE = /^```(\w*)$/;
2585
+ var FENCED_CLOSE_RE = /^```\s*$/;
2586
+ function processInlineFormatting(text, lineAttrs) {
2587
+ const blocks = [];
2588
+ let remaining = text;
2589
+ while (remaining.length > 0) {
2590
+ let earliestIndex = remaining.length;
2591
+ let matchType = "";
2592
+ let matchResult = null;
2593
+ const patterns = [
2594
+ { type: "code", re: /`([^`]+)`/ },
2595
+ { type: "bolditalic", re: /\*{3}([^*]+)\*{3}/ },
2596
+ { type: "bold", re: /\*{2}([^*]+)\*{2}/ },
2597
+ { type: "italic", re: /(?<!\*)\*(?!\*)([^*]+)(?<!\*)\*(?!\*)/ },
2598
+ { type: "strike", re: /~~([^~]+)~~/ },
2599
+ { type: "link", re: /\[([^\]]+)\]\(([^)]+)\)/ }
2600
+ ];
2601
+ for (const { type, re } of patterns) {
2602
+ const m = re.exec(remaining);
2603
+ if (m && m.index < earliestIndex) {
2604
+ earliestIndex = m.index;
2605
+ matchType = type;
2606
+ matchResult = m;
2607
+ }
2608
+ }
2609
+ if (!matchResult) {
2610
+ if (remaining.length > 0) {
2611
+ blocks.push({
2612
+ text: remaining,
2613
+ ...Object.keys(lineAttrs).length > 0 ? { attributes: { ...lineAttrs } } : {}
2614
+ });
2615
+ }
2616
+ break;
2617
+ }
2618
+ if (earliestIndex > 0) {
2619
+ blocks.push({
2620
+ text: remaining.slice(0, earliestIndex),
2621
+ ...Object.keys(lineAttrs).length > 0 ? { attributes: { ...lineAttrs } } : {}
2622
+ });
2623
+ }
2624
+ const innerText = matchResult[1];
2625
+ switch (matchType) {
2626
+ case "code":
2627
+ blocks.push({ text: innerText, attributes: { ...lineAttrs, code: true } });
2628
+ break;
2629
+ case "bolditalic":
2630
+ blocks.push({ text: innerText, attributes: { ...lineAttrs, bold: true, italic: true } });
2631
+ break;
2632
+ case "bold":
2633
+ blocks.push({ text: innerText, attributes: { ...lineAttrs, bold: true } });
2634
+ break;
2635
+ case "italic":
2636
+ blocks.push({ text: innerText, attributes: { ...lineAttrs, italic: true } });
2637
+ break;
2638
+ case "strike":
2639
+ blocks.push({ text: innerText, attributes: { ...lineAttrs, strike: true } });
2640
+ break;
2641
+ case "link":
2642
+ blocks.push({
2643
+ text: innerText,
2644
+ attributes: { ...lineAttrs, link: matchResult[2] }
2645
+ });
2646
+ break;
2647
+ }
2648
+ remaining = remaining.slice(earliestIndex + matchResult[0].length);
2649
+ }
2650
+ return blocks;
2651
+ }
2652
+ function markdownToCommentBlocks(markdown) {
2653
+ if (!markdown) return [{ text: "" }];
2654
+ const lines = markdown.split("\n");
2655
+ const blocks = [];
2656
+ let inCodeBlock = false;
2657
+ let codeBlockLang = "";
2658
+ let codeBlockContent = "";
2659
+ for (let i = 0; i < lines.length; i++) {
2660
+ const line = lines[i];
2661
+ if (inCodeBlock) {
2662
+ if (FENCED_CLOSE_RE.test(line)) {
2663
+ const lang = codeBlockLang || true;
2664
+ blocks.push({
2665
+ text: codeBlockContent,
2666
+ attributes: { "code-block": lang }
2667
+ });
2668
+ inCodeBlock = false;
2669
+ codeBlockLang = "";
2670
+ codeBlockContent = "";
2671
+ } else {
2672
+ codeBlockContent += (codeBlockContent.length > 0 ? "\n" : "") + line;
2673
+ }
2674
+ continue;
2675
+ }
2676
+ const fencedOpen = FENCED_OPEN_RE.exec(line);
2677
+ if (fencedOpen) {
2678
+ inCodeBlock = true;
2679
+ codeBlockLang = fencedOpen[1] ?? "";
2680
+ codeBlockContent = "";
2681
+ continue;
2682
+ }
2683
+ if (HR_RE.test(line)) {
2684
+ blocks.push({ text: "\n", attributes: { divider: true } });
2685
+ continue;
2686
+ }
2687
+ const headerMatch = HEADER_RE.exec(line);
2688
+ if (headerMatch) {
2689
+ const level = headerMatch[1].length;
2690
+ const content = headerMatch[2];
2691
+ const inlineBlocks2 = processInlineFormatting(content, {});
2692
+ for (const b of inlineBlocks2) {
2693
+ blocks.push(b);
2694
+ }
2695
+ blocks.push({ text: "\n", attributes: { header: level } });
2696
+ continue;
2697
+ }
2698
+ const bulletMatch = BULLET_RE.exec(line);
2699
+ if (bulletMatch) {
2700
+ const content = bulletMatch[1];
2701
+ const inlineBlocks2 = processInlineFormatting(content, {});
2702
+ for (const b of inlineBlocks2) {
2703
+ blocks.push(b);
2704
+ }
2705
+ blocks.push({ text: "\n", attributes: { list: "bullet" } });
2706
+ continue;
2707
+ }
2708
+ const orderedMatch = ORDERED_RE.exec(line);
2709
+ if (orderedMatch) {
2710
+ const content = orderedMatch[1];
2711
+ const inlineBlocks2 = processInlineFormatting(content, {});
2712
+ for (const b of inlineBlocks2) {
2713
+ blocks.push(b);
2714
+ }
2715
+ blocks.push({ text: "\n", attributes: { list: "ordered" } });
2716
+ continue;
2717
+ }
2718
+ const blockquoteMatch = BLOCKQUOTE_RE.exec(line);
2719
+ if (blockquoteMatch) {
2720
+ const content = blockquoteMatch[1];
2721
+ const inlineBlocks2 = processInlineFormatting(content, {});
2722
+ for (const b of inlineBlocks2) {
2723
+ blocks.push(b);
2724
+ }
2725
+ blocks.push({ text: "\n", attributes: { blockquote: true } });
2726
+ continue;
2727
+ }
2728
+ if (line === "") {
2729
+ blocks.push({ text: "\n" });
2730
+ continue;
2731
+ }
2732
+ const inlineBlocks = processInlineFormatting(line, {});
2733
+ for (const b of inlineBlocks) {
2734
+ blocks.push(b);
2735
+ }
2736
+ blocks.push({ text: "\n" });
2737
+ }
2738
+ if (inCodeBlock && codeBlockContent.length > 0) {
2739
+ const lang = codeBlockLang || true;
2740
+ blocks.push({
2741
+ text: codeBlockContent,
2742
+ attributes: { "code-block": lang }
2743
+ });
2744
+ }
2745
+ return blocks;
2746
+ }
2747
+
2578
2748
  // src/commands/comment.ts
2579
2749
  async function postComment(config, taskId, text, notifyAll) {
2580
2750
  if (!text.trim()) throw new Error("Comment text cannot be empty");
2581
2751
  const client = new ClickUpClient(config);
2582
- return client.postComment(taskId, text, notifyAll);
2752
+ const blocks = markdownToCommentBlocks(text);
2753
+ return client.postComment(taskId, text, notifyAll, blocks);
2583
2754
  }
2584
2755
 
2585
2756
  // src/commands/comments.ts
@@ -5717,7 +5888,8 @@ async function editComment(config, commentId, text, resolved) {
5717
5888
  }
5718
5889
  if (text !== void 0 && !text.trim()) throw new Error("Comment text cannot be empty");
5719
5890
  const client = new ClickUpClient(config);
5720
- await client.updateComment(commentId, text ?? "", resolved);
5891
+ const blocks = text !== void 0 ? markdownToCommentBlocks(text) : void 0;
5892
+ await client.updateComment(commentId, text ?? "", resolved, blocks);
5721
5893
  }
5722
5894
 
5723
5895
  // src/commands/comment-delete.ts
@@ -5760,7 +5932,8 @@ async function getReplies(config, commentId) {
5760
5932
  async function createReply(config, commentId, text, notifyAll) {
5761
5933
  if (!text.trim()) throw new Error("Reply text cannot be empty");
5762
5934
  const client = new ClickUpClient(config);
5763
- await client.createThreadedComment(commentId, text, notifyAll);
5935
+ const blocks = markdownToCommentBlocks(text);
5936
+ await client.createThreadedComment(commentId, text, notifyAll, blocks);
5764
5937
  }
5765
5938
  function formatReplies(replies) {
5766
5939
  if (replies.length === 0) return "No replies";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,11 +3,11 @@ name: clickup
3
3
  description: 'Use when managing ClickUp tasks, sprints, or comments via the `cup` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, threaded replies, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments, task links, time tracking, attachments, file uploads, listing members, listing fields, duplicating tasks, bulk operations, goals, key results, saved filters, favorites.'
4
4
  ---
5
5
 
6
- # ClickUp CLI (`cup`) - skill version 1.24.0
6
+ # ClickUp CLI (`cup`) - skill version 1.25.0
7
7
 
8
8
  Reference for AI agents using the `cup` CLI tool. Covers task management, sprint tracking, comments, time tracking, custom fields, goals, docs, and project workflows.
9
9
 
10
- > **Version check:** Run `cup --version`. If your installed version is older than 1.24.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
10
+ > **Version check:** Run `cup --version`. If your installed version is older than 1.25.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -148,11 +148,11 @@ All commands support `--help` for full flag details. All commands support `--jso
148
148
  | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
149
149
  | `cup create -n name [-l listId\|sprint:current] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--start-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--template id] [--field "Name" val]` | Create task (`--list` accepts `sprint:current`, `--field` sets custom fields inline) |
150
150
  | `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d\|none] [--start-date d] [--time-estimate t] [--assignee id\|me] [--remove-assignee id\|me] [--parent id] [--detach] [--archive] [--unarchive] [--type type] [--field "Name" val]` | Update task fields (including custom fields) |
151
- | `cup comment <id> -m text [--notify-all]` | Post comment on task |
152
- | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment |
151
+ | `cup comment <id> -m text [--notify-all]` | Post comment (markdown auto-converted to rich text) |
152
+ | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment (markdown auto-converted to rich text) |
153
153
  | `cup comment-delete <commentId>` or `cup comment-delete --task <taskId> --mine [--match text]` | Delete a comment by ID or delete one of your task comments |
154
154
  | `cup replies <commentId>` | List threaded replies |
155
- | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
155
+ | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment (markdown auto-converted to rich text) |
156
156
  | `cup assign <id> [--to ids\|me] [--remove ids\|me]` | Assign/unassign users (`--to`/`--remove` accept comma-separated IDs) |
157
157
  | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
158
158
  | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists (`--to` accepts `sprint:current`) |
@@ -296,6 +296,7 @@ cup create -n "Bug fix" -l sprint:current # create in active sprint
296
296
  cup create -n "Q3 Roadmap" -l <listId> --custom-item-id 1
297
297
  cup create -n "New story" -l <listId> --field "Story Points" 5 --field "Stage" "In Review"
298
298
  cup comment abc123def -m "Completed in PR #42"
299
+ cup comment abc123def -m "## Results\n\n**Passed**: 15/15\n- Unit tests\n- Integration"
299
300
  cup assign abc123def --to me
300
301
  cup assign abc123def --to 12345,67890 # assign multiple users at once
301
302
  cup depend task3 --on task2 # task3 waits for task2