@krodak/clickup-cli 1.23.1 → 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.23.1",
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",
@@ -551,10 +551,12 @@ var ClickUpClient = class {
551
551
  async deleteChecklist(checklistId) {
552
552
  await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
553
553
  }
554
- async createChecklistItem(checklistId, name) {
554
+ async createChecklistItem(checklistId, name, parent) {
555
+ const body = { name };
556
+ if (parent !== void 0) body.parent = parent;
555
557
  const data = await this.request(
556
558
  `/checklist/${checklistId}/checklist_item`,
557
- { method: "POST", body: JSON.stringify({ name }) }
559
+ { method: "POST", body: JSON.stringify(body) }
558
560
  );
559
561
  return expectRecordField(
560
562
  data,
@@ -2573,11 +2575,182 @@ async function fetchSubtasks(config, taskId, options = {}) {
2573
2575
  return tasks.map((t) => summarize(t, typeMap));
2574
2576
  }
2575
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
+
2576
2748
  // src/commands/comment.ts
2577
2749
  async function postComment(config, taskId, text, notifyAll) {
2578
2750
  if (!text.trim()) throw new Error("Comment text cannot be empty");
2579
2751
  const client = new ClickUpClient(config);
2580
- return client.postComment(taskId, text, notifyAll);
2752
+ const blocks = markdownToCommentBlocks(text);
2753
+ return client.postComment(taskId, text, notifyAll, blocks);
2581
2754
  }
2582
2755
 
2583
2756
  // src/commands/comments.ts
@@ -5638,9 +5811,9 @@ async function deleteChecklist(config, checklistId) {
5638
5811
  await client.deleteChecklist(checklistId);
5639
5812
  return { checklistId };
5640
5813
  }
5641
- async function addChecklistItem(config, checklistId, name) {
5814
+ async function addChecklistItem(config, checklistId, name, parent) {
5642
5815
  const client = new ClickUpClient(config);
5643
- return client.createChecklistItem(checklistId, name);
5816
+ return client.createChecklistItem(checklistId, name, parent);
5644
5817
  }
5645
5818
  async function editChecklistItem(config, checklistId, checklistItemId, updates) {
5646
5819
  const client = new ClickUpClient(config);
@@ -5651,31 +5824,59 @@ async function deleteChecklistItem(config, checklistId, checklistItemId) {
5651
5824
  await client.deleteChecklistItem(checklistId, checklistItemId);
5652
5825
  return { checklistId, checklistItemId };
5653
5826
  }
5827
+ function sortByOrder(items) {
5828
+ return [...items].sort((a, b) => (a.orderindex ?? 0) - (b.orderindex ?? 0));
5829
+ }
5830
+ function countItems(items) {
5831
+ let total = 0;
5832
+ let resolved = 0;
5833
+ for (const item of items) {
5834
+ total++;
5835
+ if (item.resolved) resolved++;
5836
+ if (item.children?.length) {
5837
+ const nested = countItems(item.children);
5838
+ total += nested.total;
5839
+ resolved += nested.resolved;
5840
+ }
5841
+ }
5842
+ return { total, resolved };
5843
+ }
5654
5844
  function formatChecklists(checklists) {
5655
5845
  if (checklists.length === 0) return "No checklists";
5656
5846
  const lines = [];
5847
+ const renderItem = (item, depth) => {
5848
+ const indent = " ".repeat(depth + 1);
5849
+ const check = item.resolved ? chalk9.green("[x]") : chalk9.dim("[ ]");
5850
+ const name = item.resolved ? chalk9.dim(item.name) : item.name;
5851
+ const assignee = item.assignee ? chalk9.dim(` @${item.assignee.username}`) : "";
5852
+ lines.push(`${indent}${check} ${name}${assignee}`);
5853
+ lines.push(chalk9.dim(`${indent} item-id: ${item.id}`));
5854
+ for (const child of sortByOrder(item.children ?? [])) {
5855
+ renderItem(child, depth + 1);
5856
+ }
5857
+ };
5657
5858
  for (const cl of checklists) {
5658
- const resolved = cl.items.filter((i) => i.resolved).length;
5659
- lines.push(chalk9.bold(`${cl.name} (${resolved}/${cl.items.length})`));
5859
+ const { total, resolved } = countItems(cl.items);
5860
+ lines.push(chalk9.bold(`${cl.name} (${resolved}/${total})`));
5660
5861
  lines.push(chalk9.dim(` ID: ${cl.id}`));
5661
- for (const item of cl.items) {
5662
- const check = item.resolved ? chalk9.green("[x]") : chalk9.dim("[ ]");
5663
- const name = item.resolved ? chalk9.dim(item.name) : item.name;
5664
- const assignee = item.assignee ? chalk9.dim(` @${item.assignee.username}`) : "";
5665
- lines.push(` ${check} ${name}${assignee}`);
5666
- lines.push(chalk9.dim(` item-id: ${item.id}`));
5667
- }
5862
+ for (const item of sortByOrder(cl.items)) renderItem(item, 0);
5668
5863
  }
5669
5864
  return lines.join("\n");
5670
5865
  }
5671
5866
  function formatChecklistsMarkdown(checklists) {
5672
5867
  if (checklists.length === 0) return "No checklists";
5868
+ const renderItem = (item, depth) => {
5869
+ const indent = " ".repeat(depth);
5870
+ const lines = [`${indent}- [${item.resolved ? "x" : " "}] ${item.name}`];
5871
+ for (const child of sortByOrder(item.children ?? [])) {
5872
+ lines.push(...renderItem(child, depth + 1));
5873
+ }
5874
+ return lines;
5875
+ };
5673
5876
  return checklists.map((cl) => {
5674
- const resolved = cl.items.filter((i) => i.resolved).length;
5675
- const header = `### ${cl.name} (${resolved}/${cl.items.length})`;
5676
- const items = cl.items.map(
5677
- (item) => `- [${item.resolved ? "x" : " "}] ${item.name}`
5678
- );
5877
+ const { total, resolved } = countItems(cl.items);
5878
+ const header = `### ${cl.name} (${resolved}/${total})`;
5879
+ const items = sortByOrder(cl.items).flatMap((item) => renderItem(item, 0));
5679
5880
  return [header, "", ...items].join("\n");
5680
5881
  }).join("\n\n");
5681
5882
  }
@@ -5687,7 +5888,8 @@ async function editComment(config, commentId, text, resolved) {
5687
5888
  }
5688
5889
  if (text !== void 0 && !text.trim()) throw new Error("Comment text cannot be empty");
5689
5890
  const client = new ClickUpClient(config);
5690
- await client.updateComment(commentId, text ?? "", resolved);
5891
+ const blocks = text !== void 0 ? markdownToCommentBlocks(text) : void 0;
5892
+ await client.updateComment(commentId, text ?? "", resolved, blocks);
5691
5893
  }
5692
5894
 
5693
5895
  // src/commands/comment-delete.ts
@@ -5730,7 +5932,8 @@ async function getReplies(config, commentId) {
5730
5932
  async function createReply(config, commentId, text, notifyAll) {
5731
5933
  if (!text.trim()) throw new Error("Reply text cannot be empty");
5732
5934
  const client = new ClickUpClient(config);
5733
- await client.createThreadedComment(commentId, text, notifyAll);
5935
+ const blocks = markdownToCommentBlocks(text);
5936
+ await client.createThreadedComment(commentId, text, notifyAll, blocks);
5734
5937
  }
5735
5938
  function formatReplies(replies) {
5736
5939
  if (replies.length === 0) return "No replies";
@@ -7435,18 +7638,23 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7435
7638
  }
7436
7639
  })
7437
7640
  );
7438
- checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
7439
- wrapAction(async (checklistId, name, opts) => {
7440
- const config = loadConfig(getProfileName());
7441
- const result = await addChecklistItem(config, checklistId, name);
7442
- if (shouldOutputJson(opts.json ?? false)) {
7443
- console.log(JSON.stringify(result, null, 2));
7444
- } else {
7445
- console.log(`Added item "${name}" to checklist ${checklistId}`);
7641
+ checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--parent <itemId>", "Nest under a parent checklist item ID").option("--json", "Force JSON output even in terminal").action(
7642
+ wrapAction(
7643
+ async (checklistId, name, opts) => {
7644
+ const config = loadConfig(getProfileName());
7645
+ const result = await addChecklistItem(config, checklistId, name, opts.parent);
7646
+ if (shouldOutputJson(opts.json ?? false)) {
7647
+ console.log(JSON.stringify(result, null, 2));
7648
+ } else {
7649
+ console.log(`Added item "${name}" to checklist ${checklistId}`);
7650
+ }
7446
7651
  }
7447
- })
7652
+ )
7448
7653
  );
7449
- checklistCmd.command("edit-item <checklistId> <checklistItemId>").description("Edit a checklist item").option("--name <name>", "New item name").option("--resolved", "Mark item as resolved").option("--unresolved", "Mark item as unresolved").option("--assignee <userId>", 'Assign user by ID (use "null" to unassign)').option("--json", "Force JSON output even in terminal").action(
7654
+ checklistCmd.command("edit-item <checklistId> <checklistItemId>").description("Edit a checklist item").option("--name <name>", "New item name").option("--resolved", "Mark item as resolved").option("--unresolved", "Mark item as unresolved").option("--assignee <userId>", 'Assign user by ID (use "null" to unassign)').option(
7655
+ "--parent <itemId>",
7656
+ 'Reparent item under another checklist item ID (use "null" to unnest)'
7657
+ ).option("--json", "Force JSON output even in terminal").action(
7450
7658
  wrapAction(
7451
7659
  async (checklistId, checklistItemId, opts) => {
7452
7660
  const config = loadConfig(getProfileName());
@@ -7457,6 +7665,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7457
7665
  if (opts.assignee !== void 0) {
7458
7666
  updates.assignee = opts.assignee === "null" ? null : parseOptionalNumberOption(opts.assignee, "--assignee");
7459
7667
  }
7668
+ if (opts.parent !== void 0) {
7669
+ updates.parent = opts.parent === "null" ? null : opts.parent;
7670
+ }
7460
7671
  const result = await editChecklistItem(config, checklistId, checklistItemId, updates);
7461
7672
  if (shouldOutputJson(opts.json ?? false)) {
7462
7673
  console.log(JSON.stringify(result, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.23.1",
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.23.1
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.23.1, 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`) |
@@ -173,8 +173,8 @@ All commands support `--help` for full flag details. All commands support `--jso
173
173
  | `cup checklist view <id>` | View checklists on a task |
174
174
  | `cup checklist create <id> <name>` | Create a checklist |
175
175
  | `cup checklist delete <checklistId>` | Delete a checklist |
176
- | `cup checklist add-item <checklistId> <name>` | Add item to checklist |
177
- | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id]` | Edit checklist item |
176
+ | `cup checklist add-item <checklistId> <name> [--parent itemId]` | Add item to checklist (nest under parent via `--parent`) |
177
+ | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id] [--parent itemId\|null]` | Edit checklist item (reparent with `--parent`, use `"null"` to unnest) |
178
178
  | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
179
179
  | `cup time start <taskId> [-d desc]` | Start timer |
180
180
  | `cup time stop` | Stop running timer |
@@ -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
@@ -305,7 +306,9 @@ cup field abc123def --set "Story Points" 5
305
306
  cup tag abc123def --add "bug,frontend"
306
307
  cup checklist create abc123def "QA Steps"
307
308
  cup checklist add-item <clId> "Run unit tests"
309
+ cup checklist add-item <clId> "Sub step" --parent <itemId> # nest under parent
308
310
  cup checklist edit-item <clId> <itemId> --resolved
311
+ cup checklist edit-item <clId> <itemId> --parent <newParent> # reparent (use "null" to unnest)
309
312
  cup link abc123 def456
310
313
  cup attach abc123def ./screenshot.png
311
314
  cup time start abc123def -d "Working on feature"