@krodak/clickup-cli 1.23.0 → 1.24.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.0",
4
+ "version": "1.24.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -171,21 +171,21 @@ npx skills add https://github.com/krodak/clickup-cli
171
171
 
172
172
  Full CRUD for the core ClickUp workflow:
173
173
 
174
- | Area | Capabilities |
175
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
176
- | ✅ **Tasks** | Create, read, update, delete, duplicate, search, subtasks, assign, dependencies, links, multi-list, bulk status updates |
177
- | 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all |
178
- | 📄 **Docs** | List, read, create, edit, delete (v3 API) |
179
- | ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history |
180
- | ☑️ **Checklists** | View, create, delete, add/edit/delete items |
181
- | 🔧 **Custom Fields** | List, create, set, remove values (dropdown, labels, date, checkbox, text, etc.) |
182
- | 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
183
- | 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
184
- | 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders |
185
- | ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks |
186
- | 👁️ **Views** | List, get, create, update, delete views on lists |
187
- | 🏢 **Workspace** | Spaces, folders, lists (read + create + rename + from template), members, task types, templates |
188
- | 📎 **Attachments** | Upload files to tasks, shown in detail views |
174
+ | Area | Capabilities |
175
+ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | ✅ **Tasks** | Create, read, update, delete, duplicate, search, subtasks, assign, dependencies, links, multi-list, bulk operations (status, assign, due-date, tag, priority, field, move) |
177
+ | 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all |
178
+ | 📄 **Docs** | List, read, create, edit, delete (v3 API) |
179
+ | ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history |
180
+ | ☑️ **Checklists** | View, create, delete, add/edit/delete items |
181
+ | 🔧 **Custom Fields** | List, create, set, remove values (text, number, dropdown, labels, date, checkbox, url, email, rating, progress, relationship, people) |
182
+ | 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
183
+ | 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
184
+ | 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders |
185
+ | ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks |
186
+ | 👁️ **Views** | List, get, create, update, delete views on lists |
187
+ | 🏢 **Workspace** | Spaces, folders, lists (read + create + rename + from template), members, task types, templates |
188
+ | 📎 **Attachments** | Upload files to tasks, shown in detail views |
189
189
 
190
190
  [Full API coverage details](docs/api-coverage.md) | [Command reference](docs/commands.md)
191
191
 
package/dist/index.js CHANGED
@@ -7,13 +7,15 @@ import { Command } from "commander";
7
7
  import { createRequire } from "module";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
+ // src/util/guards.ts
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+
10
15
  // src/api.ts
11
16
  var BASE_URL = "https://api.clickup.com/api/v2";
12
17
  var BASE_URL_V3 = "https://api.clickup.com/api/v3";
13
18
  var MAX_PAGES = 100;
14
- function isRecord(value) {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
16
- }
17
19
  function expectRecord(value, context) {
18
20
  if (!isRecord(value)) {
19
21
  throw new Error(`Unexpected API response: expected ${context} object`);
@@ -549,10 +551,12 @@ var ClickUpClient = class {
549
551
  async deleteChecklist(checklistId) {
550
552
  await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
551
553
  }
552
- async createChecklistItem(checklistId, name) {
554
+ async createChecklistItem(checklistId, name, parent) {
555
+ const body = { name };
556
+ if (parent !== void 0) body.parent = parent;
553
557
  const data = await this.request(
554
558
  `/checklist/${checklistId}/checklist_item`,
555
- { method: "POST", body: JSON.stringify({ name }) }
559
+ { method: "POST", body: JSON.stringify(body) }
556
560
  );
557
561
  return expectRecordField(
558
562
  data,
@@ -887,8 +891,44 @@ var ClickUpClient = class {
887
891
  import fs from "fs";
888
892
  import { homedir } from "os";
889
893
  import { join } from "path";
890
- function isRecord2(value) {
891
- return typeof value === "object" && value !== null && !Array.isArray(value);
894
+ var VALID_FAVORITE_TYPES = /* @__PURE__ */ new Set([
895
+ "sprint-folder",
896
+ "space",
897
+ "list",
898
+ "folder",
899
+ "view",
900
+ "task"
901
+ ]);
902
+ function isFilterEntry(v) {
903
+ if (!isRecord(v)) return false;
904
+ if (!Array.isArray(v.command)) return false;
905
+ if (!v.command.every((c) => typeof c === "string")) return false;
906
+ if (v.description !== void 0 && typeof v.description !== "string") return false;
907
+ return true;
908
+ }
909
+ function parseFiltersMap(raw) {
910
+ if (!isRecord(raw)) return {};
911
+ const out = {};
912
+ for (const [key, value] of Object.entries(raw)) {
913
+ if (isFilterEntry(value)) out[key] = value;
914
+ }
915
+ return out;
916
+ }
917
+ function isFavoriteEntry(v) {
918
+ if (!isRecord(v)) return false;
919
+ if (typeof v.type !== "string") return false;
920
+ if (!VALID_FAVORITE_TYPES.has(v.type)) return false;
921
+ if (typeof v.id !== "string") return false;
922
+ if (v.name !== void 0 && typeof v.name !== "string") return false;
923
+ return true;
924
+ }
925
+ function parseFavoritesMap(raw) {
926
+ if (!isRecord(raw)) return {};
927
+ const out = {};
928
+ for (const [key, value] of Object.entries(raw)) {
929
+ if (isFavoriteEntry(value)) out[key] = value;
930
+ }
931
+ return out;
892
932
  }
893
933
  function readConfigString(parsed, key, path, strict) {
894
934
  const value = parsed[key];
@@ -912,7 +952,7 @@ function parseConfigFile(raw, path, strictFields, strictRoot = strictFields) {
912
952
  }
913
953
  return {};
914
954
  }
915
- if (!isRecord2(parsed)) {
955
+ if (!isRecord(parsed)) {
916
956
  if (strictRoot) {
917
957
  throw new Error(`Config file at ${path} must contain a JSON object.`);
918
958
  }
@@ -978,18 +1018,18 @@ function migrateToMultiProfile(parsed, filePath) {
978
1018
  });
979
1019
  return migrated;
980
1020
  }
981
- if (isRecord2(parsed.profiles)) {
1021
+ if (isRecord(parsed.profiles)) {
982
1022
  const profiles = {};
983
1023
  for (const [name, value] of Object.entries(parsed.profiles)) {
984
- if (isRecord2(value)) {
1024
+ if (isRecord(value)) {
985
1025
  const p = {};
986
1026
  if (typeof value.apiToken === "string" && value.apiToken.trim())
987
1027
  p.apiToken = value.apiToken.trim();
988
1028
  if (typeof value.teamId === "string" && value.teamId.trim()) p.teamId = value.teamId.trim();
989
1029
  if (typeof value.sprintFolderId === "string" && value.sprintFolderId.trim())
990
1030
  p.sprintFolderId = value.sprintFolderId.trim();
991
- if (isRecord2(value.filters)) p.filters = value.filters;
992
- if (isRecord2(value.favorites)) p.favorites = value.favorites;
1031
+ if (value.filters !== void 0) p.filters = parseFiltersMap(value.filters);
1032
+ if (value.favorites !== void 0) p.favorites = parseFavoritesMap(value.favorites);
993
1033
  profiles[name] = p;
994
1034
  }
995
1035
  }
@@ -1010,7 +1050,7 @@ function parseRawConfig(filePath) {
1010
1050
  `Config file at ${filePath} contains invalid JSON. Please check the file syntax.`
1011
1051
  );
1012
1052
  }
1013
- if (!isRecord2(parsed)) {
1053
+ if (!isRecord(parsed)) {
1014
1054
  throw new Error(`Config file at ${filePath} must contain a JSON object.`);
1015
1055
  }
1016
1056
  return { parsed, raw };
@@ -1100,7 +1140,7 @@ function loadMultiProfileConfig() {
1100
1140
  } catch {
1101
1141
  return { defaultProfile: "", profiles: {} };
1102
1142
  }
1103
- if (!isRecord2(parsed)) return { defaultProfile: "", profiles: {} };
1143
+ if (!isRecord(parsed)) return { defaultProfile: "", profiles: {} };
1104
1144
  if (isOldFormat(parsed)) {
1105
1145
  return migrateToMultiProfile(parsed, path);
1106
1146
  }
@@ -1165,7 +1205,7 @@ function loadRawConfig(profileName) {
1165
1205
  } catch {
1166
1206
  return {};
1167
1207
  }
1168
- if (!isRecord2(parsed)) {
1208
+ if (!isRecord(parsed)) {
1169
1209
  throw new Error(`Config file at ${path} must contain a JSON object.`);
1170
1210
  }
1171
1211
  if (isOldFormat(parsed)) {
@@ -1565,7 +1605,7 @@ function formatCustomFieldValue(field) {
1565
1605
  switch (field.type) {
1566
1606
  case "drop_down": {
1567
1607
  if (!options) return stringifyFieldValue(field.value);
1568
- const match = options.find((o) => o.id === Number(field.value));
1608
+ const match = options.find((o) => o.orderindex === Number(field.value));
1569
1609
  return match?.name ?? stringifyFieldValue(field.value);
1570
1610
  }
1571
1611
  case "labels": {
@@ -5443,7 +5483,7 @@ function parseFieldValue(field, rawValue) {
5443
5483
  const available = options.map((o) => o.name).join(", ");
5444
5484
  throw new Error(`Label "${name}" not found. Available: ${available}`);
5445
5485
  }
5446
- ids.push(String(option.id));
5486
+ ids.push(option.id);
5447
5487
  }
5448
5488
  return ids;
5449
5489
  }
@@ -5600,9 +5640,9 @@ async function deleteChecklist(config, checklistId) {
5600
5640
  await client.deleteChecklist(checklistId);
5601
5641
  return { checklistId };
5602
5642
  }
5603
- async function addChecklistItem(config, checklistId, name) {
5643
+ async function addChecklistItem(config, checklistId, name, parent) {
5604
5644
  const client = new ClickUpClient(config);
5605
- return client.createChecklistItem(checklistId, name);
5645
+ return client.createChecklistItem(checklistId, name, parent);
5606
5646
  }
5607
5647
  async function editChecklistItem(config, checklistId, checklistItemId, updates) {
5608
5648
  const client = new ClickUpClient(config);
@@ -5613,31 +5653,59 @@ async function deleteChecklistItem(config, checklistId, checklistItemId) {
5613
5653
  await client.deleteChecklistItem(checklistId, checklistItemId);
5614
5654
  return { checklistId, checklistItemId };
5615
5655
  }
5656
+ function sortByOrder(items) {
5657
+ return [...items].sort((a, b) => (a.orderindex ?? 0) - (b.orderindex ?? 0));
5658
+ }
5659
+ function countItems(items) {
5660
+ let total = 0;
5661
+ let resolved = 0;
5662
+ for (const item of items) {
5663
+ total++;
5664
+ if (item.resolved) resolved++;
5665
+ if (item.children?.length) {
5666
+ const nested = countItems(item.children);
5667
+ total += nested.total;
5668
+ resolved += nested.resolved;
5669
+ }
5670
+ }
5671
+ return { total, resolved };
5672
+ }
5616
5673
  function formatChecklists(checklists) {
5617
5674
  if (checklists.length === 0) return "No checklists";
5618
5675
  const lines = [];
5676
+ const renderItem = (item, depth) => {
5677
+ const indent = " ".repeat(depth + 1);
5678
+ const check = item.resolved ? chalk9.green("[x]") : chalk9.dim("[ ]");
5679
+ const name = item.resolved ? chalk9.dim(item.name) : item.name;
5680
+ const assignee = item.assignee ? chalk9.dim(` @${item.assignee.username}`) : "";
5681
+ lines.push(`${indent}${check} ${name}${assignee}`);
5682
+ lines.push(chalk9.dim(`${indent} item-id: ${item.id}`));
5683
+ for (const child of sortByOrder(item.children ?? [])) {
5684
+ renderItem(child, depth + 1);
5685
+ }
5686
+ };
5619
5687
  for (const cl of checklists) {
5620
- const resolved = cl.items.filter((i) => i.resolved).length;
5621
- lines.push(chalk9.bold(`${cl.name} (${resolved}/${cl.items.length})`));
5688
+ const { total, resolved } = countItems(cl.items);
5689
+ lines.push(chalk9.bold(`${cl.name} (${resolved}/${total})`));
5622
5690
  lines.push(chalk9.dim(` ID: ${cl.id}`));
5623
- for (const item of cl.items) {
5624
- const check = item.resolved ? chalk9.green("[x]") : chalk9.dim("[ ]");
5625
- const name = item.resolved ? chalk9.dim(item.name) : item.name;
5626
- const assignee = item.assignee ? chalk9.dim(` @${item.assignee.username}`) : "";
5627
- lines.push(` ${check} ${name}${assignee}`);
5628
- lines.push(chalk9.dim(` item-id: ${item.id}`));
5629
- }
5691
+ for (const item of sortByOrder(cl.items)) renderItem(item, 0);
5630
5692
  }
5631
5693
  return lines.join("\n");
5632
5694
  }
5633
5695
  function formatChecklistsMarkdown(checklists) {
5634
5696
  if (checklists.length === 0) return "No checklists";
5697
+ const renderItem = (item, depth) => {
5698
+ const indent = " ".repeat(depth);
5699
+ const lines = [`${indent}- [${item.resolved ? "x" : " "}] ${item.name}`];
5700
+ for (const child of sortByOrder(item.children ?? [])) {
5701
+ lines.push(...renderItem(child, depth + 1));
5702
+ }
5703
+ return lines;
5704
+ };
5635
5705
  return checklists.map((cl) => {
5636
- const resolved = cl.items.filter((i) => i.resolved).length;
5637
- const header = `### ${cl.name} (${resolved}/${cl.items.length})`;
5638
- const items = cl.items.map(
5639
- (item) => `- [${item.resolved ? "x" : " "}] ${item.name}`
5640
- );
5706
+ const { total, resolved } = countItems(cl.items);
5707
+ const header = `### ${cl.name} (${resolved}/${total})`;
5708
+ const items = sortByOrder(cl.items).flatMap((item) => renderItem(item, 0));
5641
5709
  return [header, "", ...items].join("\n");
5642
5710
  }).join("\n\n");
5643
5711
  }
@@ -6124,31 +6192,34 @@ async function duplicateTask(config, taskId) {
6124
6192
  return { id: created.id, name: created.name, url: created.url };
6125
6193
  }
6126
6194
 
6127
- // src/commands/bulk.ts
6128
- var BULK_CONCURRENCY = 5;
6195
+ // src/util/batch.ts
6129
6196
  async function runInBatches(items, concurrency, fn) {
6197
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
6198
+ throw new Error(`concurrency must be a positive integer, got ${concurrency}`);
6199
+ }
6130
6200
  const results = [];
6131
6201
  for (let i = 0; i < items.length; i += concurrency) {
6132
6202
  const batch = items.slice(i, i + concurrency);
6133
- const batchResults = await Promise.allSettled(batch.map(fn));
6134
- batchResults.forEach((res, idx) => {
6203
+ const settled = await Promise.allSettled(batch.map(fn));
6204
+ settled.forEach((res, idx) => {
6135
6205
  const item = batch[idx];
6136
6206
  if (res.status === "fulfilled") {
6137
- results.push({ item, result: res.value });
6207
+ results.push({ item, ok: true, result: res.value });
6138
6208
  } else {
6139
- results.push({
6140
- item,
6141
- error: res.reason instanceof Error ? res.reason : new Error(String(res.reason))
6142
- });
6209
+ const error = res.reason instanceof Error ? res.reason : new Error(String(res.reason), { cause: res.reason });
6210
+ results.push({ item, ok: false, error });
6143
6211
  }
6144
6212
  });
6145
6213
  }
6146
6214
  return results;
6147
6215
  }
6216
+
6217
+ // src/commands/bulk.ts
6218
+ var BULK_CONCURRENCY = 5;
6148
6219
  function toBulkResult(outcomes) {
6149
6220
  const failed = [];
6150
6221
  for (const outcome of outcomes) {
6151
- if (outcome.error) {
6222
+ if (!outcome.ok) {
6152
6223
  failed.push({ id: outcome.item, reason: outcome.error.message });
6153
6224
  }
6154
6225
  }
@@ -6484,14 +6555,14 @@ async function createView(config, listId, name, opts) {
6484
6555
  }
6485
6556
 
6486
6557
  // src/commands/view-update.ts
6487
- var VALID_GROUP_BY_FIELDS2 = [
6558
+ var VALID_GROUP_BY_FIELDS2 = /* @__PURE__ */ new Set([
6488
6559
  "status",
6489
6560
  "assignee",
6490
6561
  "priority",
6491
6562
  "due_date",
6492
6563
  "tag",
6493
6564
  "sprint"
6494
- ];
6565
+ ]);
6495
6566
  async function updateView(config, viewId, opts) {
6496
6567
  if (opts.name === void 0 && opts.groupBy === void 0) {
6497
6568
  throw new Error("Provide at least one of: --name, --group-by");
@@ -6502,9 +6573,9 @@ async function updateView(config, viewId, opts) {
6502
6573
  const payload = {};
6503
6574
  if (opts.name) payload.name = opts.name;
6504
6575
  if (opts.groupBy) {
6505
- if (!VALID_GROUP_BY_FIELDS2.includes(opts.groupBy)) {
6576
+ if (!VALID_GROUP_BY_FIELDS2.has(opts.groupBy)) {
6506
6577
  throw new Error(
6507
- `Invalid group-by field "${opts.groupBy}". Valid fields: ${VALID_GROUP_BY_FIELDS2.join(", ")}`
6578
+ `Invalid group-by field "${opts.groupBy}". Valid fields: ${[...VALID_GROUP_BY_FIELDS2].join(", ")}`
6508
6579
  );
6509
6580
  }
6510
6581
  payload.grouping = { field: opts.groupBy };
@@ -7394,18 +7465,23 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7394
7465
  }
7395
7466
  })
7396
7467
  );
7397
- checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
7398
- wrapAction(async (checklistId, name, opts) => {
7399
- const config = loadConfig(getProfileName());
7400
- const result = await addChecklistItem(config, checklistId, name);
7401
- if (shouldOutputJson(opts.json ?? false)) {
7402
- console.log(JSON.stringify(result, null, 2));
7403
- } else {
7404
- console.log(`Added item "${name}" to checklist ${checklistId}`);
7468
+ 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(
7469
+ wrapAction(
7470
+ async (checklistId, name, opts) => {
7471
+ const config = loadConfig(getProfileName());
7472
+ const result = await addChecklistItem(config, checklistId, name, opts.parent);
7473
+ if (shouldOutputJson(opts.json ?? false)) {
7474
+ console.log(JSON.stringify(result, null, 2));
7475
+ } else {
7476
+ console.log(`Added item "${name}" to checklist ${checklistId}`);
7477
+ }
7405
7478
  }
7406
- })
7479
+ )
7407
7480
  );
7408
- 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(
7481
+ 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(
7482
+ "--parent <itemId>",
7483
+ 'Reparent item under another checklist item ID (use "null" to unnest)'
7484
+ ).option("--json", "Force JSON output even in terminal").action(
7409
7485
  wrapAction(
7410
7486
  async (checklistId, checklistItemId, opts) => {
7411
7487
  const config = loadConfig(getProfileName());
@@ -7416,6 +7492,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7416
7492
  if (opts.assignee !== void 0) {
7417
7493
  updates.assignee = opts.assignee === "null" ? null : parseOptionalNumberOption(opts.assignee, "--assignee");
7418
7494
  }
7495
+ if (opts.parent !== void 0) {
7496
+ updates.parent = opts.parent === "null" ? null : opts.parent;
7497
+ }
7419
7498
  const result = await editChecklistItem(config, checklistId, checklistItemId, updates);
7420
7499
  if (shouldOutputJson(opts.json ?? false)) {
7421
7500
  console.log(JSON.stringify(result, null, 2));
@@ -7682,16 +7761,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7682
7761
  wrapAction(async (status, taskIds, opts) => {
7683
7762
  const config = loadConfig(getProfileName());
7684
7763
  const result = await bulkUpdateStatus(config, taskIds, status);
7685
- if (shouldOutputJson(opts.json ?? false)) {
7686
- console.log(JSON.stringify(result, null, 2));
7687
- } else {
7688
- console.log(`Updated ${result.updated} tasks to "${status}"`);
7689
- if (result.failed.length > 0) {
7690
- for (const f of result.failed) {
7691
- console.log(` Failed ${f.id}: ${f.reason}`);
7692
- }
7693
- }
7694
- }
7764
+ outputBulkResult(result, opts.json ?? false, `status ${status}`);
7695
7765
  })
7696
7766
  );
7697
7767
  bulkCmd.command("assign <taskIds...>").description("Bulk assign or unassign a user from tasks").option("--to <userId>", 'Add this user (user ID or "me")').option("--remove <userId>", 'Remove this user (user ID or "me")').option("--json", "Force JSON output even in terminal").action(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.23.0",
3
+ "version": "1.24.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.0
6
+ # ClickUp CLI (`cup`) - skill version 1.24.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.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.24.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -120,10 +120,11 @@ All commands support `--help` for full flag details. All commands support `--jso
120
120
  | `cup activity <id>` | Task details + comment history combined |
121
121
  | `cup inbox [--days n] [--include-closed]` | Tasks updated in last n days (default 30) |
122
122
  | `cup summary [--hours n]` | Standup: completed, in-progress, overdue |
123
- | `cup overdue [--include-closed]` | Tasks past due date (most overdue first) |
124
- | `cup spaces [--name partial] [--my]` | List/filter workspace spaces |
125
- | `cup lists <spaceId> [--name partial]` | Lists in a space (including folder lists) |
126
- | `cup folders <spaceId> [--name partial]` | Folders in a space (with their lists) |
123
+ | `cup overdue [--all] [--include-closed]` | Tasks past due date (most overdue first) |
124
+ | `cup spaces [--name partial] [--my] [--archived]` | List/filter workspace spaces |
125
+ | `cup lists <spaceId> [--name partial] [--archived]` | Lists in a space (including folder lists) |
126
+ | `cup folders <spaceId> [--name partial] [--archived]` | Folders in a space (with their lists) |
127
+ | `cup time-in-status <id>` | Show how long a task has been in each status |
127
128
  | `cup members` | Workspace members (username, ID, email) |
128
129
  | `cup fields <listId>` | Custom fields on a list (type, required, options) |
129
130
  | `cup tags <spaceId>` | Tags available in a space |
@@ -143,76 +144,78 @@ All commands support `--help` for full flag details. All commands support `--jso
143
144
 
144
145
  ### Write
145
146
 
146
- | Command | What it does |
147
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
148
- | `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) |
149
- | `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] [--field "Name" val]` | Update task fields (including custom fields) |
150
- | `cup comment <id> -m text [--notify-all]` | Post comment on task |
151
- | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment |
152
- | `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 |
153
- | `cup replies <commentId>` | List threaded replies |
154
- | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
155
- | `cup assign <id> [--to ids\|me] [--remove ids\|me]` | Assign/unassign users (`--to`/`--remove` accept comma-separated IDs) |
156
- | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
157
- | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists (`--to` accepts `sprint:current`) |
158
- | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
159
- | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
160
- | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
161
- | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
162
- | `cup attach <taskId> <filePath>` | Upload file attachment |
163
- | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
164
- | `cup duplicate <taskId>` | Duplicate a task |
165
- | `cup bulk status <status> <taskIds...>` | Bulk update status |
166
- | `cup bulk assign <taskIds...> [--to userId\|me] [--remove userId\|me]` | Bulk assign/unassign user from tasks |
167
- | `cup bulk due-date <date\|none\|clear> <taskIds...>` | Bulk set or clear due dates |
168
- | `cup bulk tag <tagName> <taskIds...> [--add] [--remove]` | Bulk add/remove tag from tasks |
169
- | `cup bulk move <taskIds...> --to <listId>` | Bulk move tasks to a destination list |
170
- | `cup checklist view <id>` | View checklists on a task |
171
- | `cup checklist create <id> <name>` | Create a checklist |
172
- | `cup checklist delete <checklistId>` | Delete a checklist |
173
- | `cup checklist add-item <checklistId> <name>` | Add item to checklist |
174
- | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id]` | Edit checklist item |
175
- | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
176
- | `cup time start <taskId> [-d desc]` | Start timer |
177
- | `cup time stop` | Stop running timer |
178
- | `cup time status` | Show running timer |
179
- | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
180
- | `cup time list [--days n] [--task id] [--all]` | List my recent time entries (--all for team) |
181
- | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
182
- | `cup time delete <timeEntryId>` | Delete time entry |
183
- | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
184
- | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
185
- | `cup goal-delete <goalId>` | Delete a goal |
186
- | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
187
- | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
188
- | `cup key-result-delete <keyResultId>` | Delete key result |
189
- | `cup doc-create <title> [-c content]` | Create a doc |
190
- | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
191
- | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
192
- | `cup doc-delete <docId>` | Delete a doc |
193
- | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
194
- | `cup space-create <name>` | Create a space |
195
- | `cup list-create <spaceId> <name> [--folder folderId] [--copy-statuses-from id]` | Create a list in a space or folder |
196
- | `cup folder-create <spaceId> <name>` | Create a folder in a space |
197
- | `cup list-rename <listId> <newName>` | Rename a list |
198
- | `cup folder-rename <folderId> <newName>` | Rename a folder |
199
- | `cup space-rename <spaceId> <newName>` | Rename a space |
200
- | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
201
- | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
202
- | `cup tag-delete <spaceId> <name>` | Delete space tag |
203
- | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
204
- | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
205
- | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
206
- | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
207
- | `cup favorite add <type> <id> [alias] [-n name]` | Add a local favorite |
208
- | `cup favorite remove <alias>` | Remove a favorite |
209
- | `cup favorite list [--type t]` | List favorites (optionally filter by type) |
210
- | `cup profile list [--json]` | List all profiles |
211
- | `cup profile add <name>` | Add a new profile (interactive) |
212
- | `cup profile remove <name>` | Remove a profile |
213
- | `cup profile use <name>` | Set the default profile |
214
- | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
215
- | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
147
+ | Command | What it does |
148
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
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
+ | `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 |
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
+ | `cup replies <commentId>` | List threaded replies |
155
+ | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
156
+ | `cup assign <id> [--to ids\|me] [--remove ids\|me]` | Assign/unassign users (`--to`/`--remove` accept comma-separated IDs) |
157
+ | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
158
+ | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists (`--to` accepts `sprint:current`) |
159
+ | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
160
+ | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
161
+ | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
162
+ | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
163
+ | `cup attach <taskId> <filePath>` | Upload file attachment |
164
+ | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
165
+ | `cup duplicate <taskId>` | Duplicate a task |
166
+ | `cup bulk status <status> <taskIds...>` | Bulk update status |
167
+ | `cup bulk assign <taskIds...> [--to userId\|me] [--remove userId\|me]` | Bulk assign/unassign user from tasks |
168
+ | `cup bulk due-date <date\|none\|clear> <taskIds...>` | Bulk set or clear due dates |
169
+ | `cup bulk tag <tagName> <taskIds...> [--remove]` | Bulk add/remove tag from tasks |
170
+ | `cup bulk priority <taskIds...> --to <priority>` | Bulk set priority on many tasks |
171
+ | `cup bulk field <taskIds...> --set "Name" value` | Bulk set a custom field value |
172
+ | `cup bulk move <taskIds...> --to <listId>` | Bulk move tasks to a destination list |
173
+ | `cup checklist view <id>` | View checklists on a task |
174
+ | `cup checklist create <id> <name>` | Create a checklist |
175
+ | `cup checklist delete <checklistId>` | Delete a checklist |
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
+ | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
179
+ | `cup time start <taskId> [-d desc]` | Start timer |
180
+ | `cup time stop` | Stop running timer |
181
+ | `cup time status` | Show running timer |
182
+ | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
183
+ | `cup time list [--days n] [--task id] [--all]` | List my recent time entries (--all for team) |
184
+ | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
185
+ | `cup time delete <timeEntryId>` | Delete time entry |
186
+ | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
187
+ | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
188
+ | `cup goal-delete <goalId>` | Delete a goal |
189
+ | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
190
+ | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
191
+ | `cup key-result-delete <keyResultId>` | Delete key result |
192
+ | `cup doc-create <title> [-c content]` | Create a doc |
193
+ | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
194
+ | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
195
+ | `cup doc-delete <docId>` | Delete a doc |
196
+ | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
197
+ | `cup space-create <name>` | Create a space |
198
+ | `cup list-create <spaceId> <name> [--folder folderId] [--copy-statuses-from id]` | Create a list in a space or folder |
199
+ | `cup folder-create <spaceId> <name>` | Create a folder in a space |
200
+ | `cup list-rename <listId> <newName>` | Rename a list |
201
+ | `cup folder-rename <folderId> <newName>` | Rename a folder |
202
+ | `cup space-rename <spaceId> <newName>` | Rename a space |
203
+ | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
204
+ | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
205
+ | `cup tag-delete <spaceId> <name>` | Delete space tag |
206
+ | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
207
+ | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
208
+ | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
209
+ | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
210
+ | `cup favorite add <type> <id> [alias] [-n name]` | Add a local favorite |
211
+ | `cup favorite remove <alias>` | Remove a favorite |
212
+ | `cup favorite list [--type t]` | List favorites (optionally filter by type) |
213
+ | `cup profile list [--json]` | List all profiles |
214
+ | `cup profile add <name>` | Add a new profile (interactive) |
215
+ | `cup profile remove <name>` | Remove a profile |
216
+ | `cup profile use <name>` | Set the default profile |
217
+ | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
218
+ | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
216
219
 
217
220
  ## Global Flags
218
221
 
@@ -302,7 +305,9 @@ cup field abc123def --set "Story Points" 5
302
305
  cup tag abc123def --add "bug,frontend"
303
306
  cup checklist create abc123def "QA Steps"
304
307
  cup checklist add-item <clId> "Run unit tests"
308
+ cup checklist add-item <clId> "Sub step" --parent <itemId> # nest under parent
305
309
  cup checklist edit-item <clId> <itemId> --resolved
310
+ cup checklist edit-item <clId> <itemId> --parent <newParent> # reparent (use "null" to unnest)
306
311
  cup link abc123 def456
307
312
  cup attach abc123def ./screenshot.png
308
313
  cup time start abc123def -d "Working on feature"