@krodak/clickup-cli 0.12.3 → 0.13.1

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
@@ -98,7 +98,7 @@ When output is piped (no TTY), all commands output **Markdown** by default - opt
98
98
 
99
99
  ## Commands
100
100
 
101
- 27 commands total. All support `--help` for full flag details.
101
+ 29 commands total. All support `--help` for full flag details.
102
102
 
103
103
  ### `cu init`
104
104
 
@@ -110,32 +110,21 @@ cu init
110
110
 
111
111
  ### `cu tasks`
112
112
 
113
- List tasks assigned to me.
113
+ List tasks assigned to me. By default shows all task types. Use `--type` to filter by task type.
114
114
 
115
115
  ```bash
116
116
  cu tasks
117
117
  cu tasks --status "in progress"
118
118
  cu tasks --name "login"
119
+ cu tasks --type task # regular tasks only
120
+ cu tasks --type initiative # initiatives only
121
+ cu tasks --type "Bug" # custom task type by name
119
122
  cu tasks --list <listId>
120
123
  cu tasks --space <spaceId>
121
124
  cu tasks --include-closed
122
125
  cu tasks --json
123
126
  ```
124
127
 
125
- ### `cu initiatives`
126
-
127
- List initiatives assigned to me.
128
-
129
- ```bash
130
- cu initiatives
131
- cu initiatives --status "to do"
132
- cu initiatives --name "auth"
133
- cu initiatives --list <listId>
134
- cu initiatives --space <spaceId>
135
- cu initiatives --include-closed
136
- cu initiatives --json
137
- ```
138
-
139
128
  ### `cu sprint`
140
129
 
141
130
  List my tasks in the currently active sprint (auto-detected from sprint folder date ranges).
@@ -192,7 +181,7 @@ cu task abc123 --json
192
181
 
193
182
  ### `cu subtasks <id>`
194
183
 
195
- List subtasks of a task or initiative.
184
+ List subtasks of a task.
196
185
 
197
186
  ```bash
198
187
  cu subtasks abc123
@@ -258,9 +247,51 @@ cu create -n "Fix bug" -l <listId> --json
258
247
  | `--time-estimate <duration>` | no | Time estimate (e.g. `"2h"`, `"30m"`, `"1h30m"`) |
259
248
  | `--assignee <userId>` | no | Assignee by numeric user ID |
260
249
  | `--tags <tags>` | no | Comma-separated tag names |
261
- | `--custom-item-id <id>` | no | Custom task type ID (for creating initiatives) |
250
+ | `--custom-item-id <id>` | no | Custom task type ID (e.g. for creating initiatives) |
262
251
  | `--json` | no | Force JSON output even in terminal |
263
252
 
253
+ ### `cu delete <id>`
254
+
255
+ Delete a task. **DESTRUCTIVE - cannot be undone.**
256
+
257
+ ```bash
258
+ cu delete abc123
259
+ cu delete abc123 --confirm
260
+ cu delete abc123 --confirm --json
261
+ ```
262
+
263
+ In TTY mode without `--confirm`: shows the task name and prompts for confirmation (default: No). In non-interactive/piped mode, `--confirm` is required.
264
+
265
+ | Flag | Description |
266
+ | ----------- | ----------------------------------------------------------- |
267
+ | `--confirm` | Skip confirmation prompt (required in non-interactive mode) |
268
+ | `--json` | Force JSON output |
269
+
270
+ ### `cu field <id>`
271
+
272
+ Set or remove a custom field value. Field names are resolved case-insensitively; errors list available fields/options.
273
+
274
+ ```bash
275
+ cu field abc123 --set "Priority Level" high
276
+ cu field abc123 --set "Story Points" 5
277
+ cu field abc123 --set "Approved" true
278
+ cu field abc123 --set "Category" "Bug Fix"
279
+ cu field abc123 --set "Due" 2025-06-01
280
+ cu field abc123 --set "Website" "https://example.com"
281
+ cu field abc123 --set "Contact" "user@example.com"
282
+ cu field abc123 --remove "Priority Level"
283
+ cu field abc123 --set "Points" 3 --remove "Old Field"
284
+ cu field abc123 --set "Points" 3 --json
285
+ ```
286
+
287
+ | Flag | Description |
288
+ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
289
+ | `--set "Field Name" <val>` | Set a custom field by name. Supports: text, number, checkbox (true/false), dropdown (option name), date (YYYY-MM-DD), url, email |
290
+ | `--remove "Field Name"` | Remove a custom field value |
291
+ | `--json` | Force JSON output |
292
+
293
+ Both `--set` and `--remove` can be used together in one invocation.
294
+
264
295
  ### `cu comment <id>`
265
296
 
266
297
  Post a comment on a task.
@@ -423,6 +454,24 @@ cu move abc123 --to <listId> --json
423
454
  | `--remove <listId>` | Remove task from this list |
424
455
  | `--json` | Force JSON output |
425
456
 
457
+ ### `cu tag <id>`
458
+
459
+ Add or remove tags on a task. Both `--add` and `--remove` can be used together.
460
+
461
+ ```bash
462
+ cu tag abc123 --add "bug"
463
+ cu tag abc123 --add "bug,frontend,urgent"
464
+ cu tag abc123 --remove "wontfix"
465
+ cu tag abc123 --add "bug" --remove "triage"
466
+ cu tag abc123 --add "bug" --json
467
+ ```
468
+
469
+ | Flag | Description |
470
+ | ----------------- | ----------------------------------- |
471
+ | `--add <tags>` | Comma-separated tag names to add |
472
+ | `--remove <tags>` | Comma-separated tag names to remove |
473
+ | `--json` | Force JSON output |
474
+
426
475
  ### `cu auth`
427
476
 
428
477
  Check authentication status. Validates your API token and shows your user info.
package/dist/index.js CHANGED
@@ -190,6 +190,12 @@ var ClickUpClient = class {
190
190
  const data = await this.request(`/team/${teamId}/space?archived=false`);
191
191
  return data.spaces ?? [];
192
192
  }
193
+ async getCustomTaskTypes(teamId) {
194
+ const data = await this.request(
195
+ `/team/${teamId}/custom_item`
196
+ );
197
+ return data.custom_items ?? [];
198
+ }
193
199
  async getLists(spaceId) {
194
200
  const data = await this.request(`/space/${spaceId}/list?archived=false`);
195
201
  return data.lists ?? [];
@@ -218,6 +224,24 @@ var ClickUpClient = class {
218
224
  async removeTaskFromList(taskId, listId) {
219
225
  await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
220
226
  }
227
+ async setCustomFieldValue(taskId, fieldId, value) {
228
+ await this.request(`/task/${taskId}/field/${fieldId}`, {
229
+ method: "POST",
230
+ body: JSON.stringify({ value })
231
+ });
232
+ }
233
+ async removeCustomFieldValue(taskId, fieldId) {
234
+ await this.request(`/task/${taskId}/field/${fieldId}`, { method: "DELETE" });
235
+ }
236
+ async deleteTask(taskId) {
237
+ await this.request(`/task/${taskId}`, { method: "DELETE" });
238
+ }
239
+ async addTagToTask(taskId, tagName) {
240
+ await this.request(`/task/${taskId}/tag/${encodeURIComponent(tagName)}`, { method: "POST" });
241
+ }
242
+ async removeTagFromTask(taskId, tagName) {
243
+ await this.request(`/task/${taskId}/tag/${encodeURIComponent(tagName)}`, { method: "DELETE" });
244
+ }
221
245
  async addDependency(taskId, opts) {
222
246
  const body = {};
223
247
  if (opts.dependsOn) body.depends_on = opts.dependsOn;
@@ -354,11 +378,11 @@ function formatDuration(ms) {
354
378
  }
355
379
  function formatTaskDetailMarkdown(task) {
356
380
  const lines = [`# ${task.name}`, ""];
357
- const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
381
+ const isInitiative = (task.custom_item_id ?? 0) !== 0;
358
382
  const fields = [
359
383
  ["ID", task.id],
360
384
  ["Status", task.status.status],
361
- ["Type", isInitiative2 ? "initiative" : "task"],
385
+ ["Type", isInitiative ? "initiative" : "task"],
362
386
  ["List", task.list.name],
363
387
  ["URL", task.url],
364
388
  [
@@ -487,8 +511,8 @@ function formatCustomFieldValue(field) {
487
511
  }
488
512
  function formatTaskDetail(task) {
489
513
  const lines = [];
490
- const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
491
- const typeLabel = isInitiative2 ? "initiative" : "task";
514
+ const isInitiative = (task.custom_item_id ?? 0) !== 0;
515
+ const typeLabel = isInitiative ? "initiative" : "task";
492
516
  lines.push(chalk2.bold.underline(task.name));
493
517
  lines.push("");
494
518
  const fields = [
@@ -613,19 +637,21 @@ function isDoneStatus(status) {
613
637
  const lower = status.toLowerCase();
614
638
  return DONE_PATTERNS.some((p) => lower.includes(p));
615
639
  }
616
- function isInitiative(task) {
617
- return (task.custom_item_id ?? 0) !== 0;
618
- }
619
640
  function formatDueDate(ms) {
620
641
  if (!ms) return "";
621
642
  return formatDate(ms);
622
643
  }
623
- function summarize(task) {
644
+ function resolveTaskType(task, typeMap) {
645
+ const id = task.custom_item_id ?? 0;
646
+ if (id === 0) return "task";
647
+ return typeMap.get(id) ?? `type_${id}`;
648
+ }
649
+ function summarize(task, typeMap) {
624
650
  return {
625
651
  id: task.id,
626
652
  name: task.name,
627
653
  status: task.status.status,
628
- task_type: isInitiative(task) ? "initiative" : "task",
654
+ task_type: resolveTaskType(task, typeMap ?? /* @__PURE__ */ new Map()),
629
655
  priority: task.priority?.priority ?? "none",
630
656
  due_date: formatDueDate(task.due_date),
631
657
  list: task.list.name,
@@ -633,16 +659,42 @@ function summarize(task) {
633
659
  ...task.parent ? { parent: task.parent } : {}
634
660
  };
635
661
  }
662
+ function buildTypeMap(types) {
663
+ const map = /* @__PURE__ */ new Map();
664
+ for (const t of types) {
665
+ map.set(t.id, t.name);
666
+ }
667
+ return map;
668
+ }
669
+ function resolveTypeFilter(typeFilter, typeMap) {
670
+ if (typeFilter === "task") return 0;
671
+ const asNum = Number(typeFilter);
672
+ if (Number.isFinite(asNum)) return asNum;
673
+ const lower = typeFilter.toLowerCase();
674
+ for (const [id, name] of typeMap) {
675
+ if (name.toLowerCase() === lower) return id;
676
+ }
677
+ const available = ["task", ...Array.from(typeMap.values())].join(", ");
678
+ throw new Error(`Unknown task type "${typeFilter}". Available types: ${available}`);
679
+ }
636
680
  async function fetchMyTasks(config, opts = {}) {
637
681
  const client = new ClickUpClient(config);
638
682
  const { typeFilter, name, ...apiFilters } = opts;
639
- const allTasks = await client.getMyTasks(config.teamId, apiFilters);
640
- let filtered = typeFilter === "initiative" ? allTasks.filter(isInitiative) : typeFilter === "task" ? allTasks.filter((t) => !isInitiative(t)) : allTasks;
683
+ const [allTasks, customTypes] = await Promise.all([
684
+ client.getMyTasks(config.teamId, apiFilters),
685
+ client.getCustomTaskTypes(config.teamId)
686
+ ]);
687
+ const typeMap = buildTypeMap(customTypes);
688
+ let filtered = allTasks;
689
+ if (typeFilter) {
690
+ const targetId = resolveTypeFilter(typeFilter, typeMap);
691
+ filtered = allTasks.filter((t) => (t.custom_item_id ?? 0) === targetId);
692
+ }
641
693
  if (name) {
642
694
  const query = name.toLowerCase();
643
695
  filtered = filtered.filter((t) => t.name.toLowerCase().includes(query));
644
696
  }
645
- return filtered.map(summarize);
697
+ return filtered.map((t) => summarize(t, typeMap));
646
698
  }
647
699
  async function printTasks(tasks, forceJson, config) {
648
700
  if (shouldOutputJson(forceJson)) {
@@ -902,10 +954,12 @@ function findRelatedSpaces(mySpaceIds, allSpaces) {
902
954
  async function runSprintCommand(config, opts) {
903
955
  const client = new ClickUpClient(config);
904
956
  process.stderr.write("Detecting active sprint...\n");
905
- const [myTasks, allSpaces] = await Promise.all([
957
+ const [myTasks, allSpaces, customTypes] = await Promise.all([
906
958
  client.getMyTasks(config.teamId),
907
- client.getSpaces(config.teamId)
959
+ client.getSpaces(config.teamId),
960
+ client.getCustomTaskTypes(config.teamId)
908
961
  ]);
962
+ const typeMap = buildTypeMap(customTypes);
909
963
  let spaces;
910
964
  if (opts.space) {
911
965
  spaces = allSpaces.filter(
@@ -948,7 +1002,7 @@ async function runSprintCommand(config, opts) {
948
1002
  sprintTasks = sprintTasks.filter((t) => !isDoneStatus(t.status.status));
949
1003
  }
950
1004
  const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
951
- const summaries = filtered.map(summarize);
1005
+ const summaries = filtered.map((t) => summarize(t, typeMap));
952
1006
  await printTasks(summaries, opts.json ?? false, config);
953
1007
  }
954
1008
 
@@ -1041,13 +1095,17 @@ async function listSprints(config, opts = {}) {
1041
1095
  // src/commands/subtasks.ts
1042
1096
  async function fetchSubtasks(config, taskId, options = {}) {
1043
1097
  const client = new ClickUpClient(config);
1044
- const parent = await client.getTask(taskId);
1098
+ const [parent, customTypes] = await Promise.all([
1099
+ client.getTask(taskId),
1100
+ client.getCustomTaskTypes(config.teamId)
1101
+ ]);
1102
+ const typeMap = buildTypeMap(customTypes);
1045
1103
  const tasks = await client.getTasksFromList(
1046
1104
  parent.list.id,
1047
1105
  { parent: taskId, subtasks: "false" },
1048
1106
  { includeClosed: options.includeClosed }
1049
1107
  );
1050
- return tasks.map(summarize);
1108
+ return tasks.map((t) => summarize(t, typeMap));
1051
1109
  }
1052
1110
 
1053
1111
  // src/commands/comment.ts
@@ -1153,9 +1211,9 @@ var TIME_PERIODS = [
1153
1211
  { key: "last_month", label: "Last month" },
1154
1212
  { key: "older", label: "Older" }
1155
1213
  ];
1156
- function summarizeWithDate(task) {
1214
+ function summarizeWithDate(task, typeMap) {
1157
1215
  return {
1158
- ...summarize(task),
1216
+ ...summarize(task, typeMap),
1159
1217
  date_updated: task.date_updated ?? "0"
1160
1218
  };
1161
1219
  }
@@ -1192,12 +1250,13 @@ function groupTasks(tasks, now) {
1192
1250
  }
1193
1251
  async function fetchInbox(config, days = 30, opts = {}) {
1194
1252
  const client = new ClickUpClient(config);
1195
- const tasks = await client.getMyTasks(config.teamId, {
1196
- subtasks: true,
1197
- includeClosed: opts.includeClosed
1198
- });
1253
+ const [tasks, customTypes] = await Promise.all([
1254
+ client.getMyTasks(config.teamId, { subtasks: true, includeClosed: opts.includeClosed }),
1255
+ client.getCustomTaskTypes(config.teamId)
1256
+ ]);
1257
+ const typeMap = buildTypeMap(customTypes);
1199
1258
  const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
1200
- return tasks.filter((t) => Number(t.date_updated ?? 0) > cutoff).sort((a, b) => Number(b.date_updated ?? 0) - Number(a.date_updated ?? 0)).map(summarizeWithDate);
1259
+ return tasks.filter((t) => Number(t.date_updated ?? 0) > cutoff).sort((a, b) => Number(b.date_updated ?? 0) - Number(a.date_updated ?? 0)).map((t) => summarizeWithDate(t, typeMap));
1201
1260
  }
1202
1261
  async function printInbox(tasks, forceJson, config) {
1203
1262
  const now = Date.now();
@@ -1304,9 +1363,11 @@ function groupByStatus(tasks, includeClosed) {
1304
1363
  }
1305
1364
  async function runAssignedCommand(config, opts) {
1306
1365
  const client = new ClickUpClient(config);
1307
- const allTasks = await client.getMyTasks(config.teamId, {
1308
- includeClosed: opts.includeClosed
1309
- });
1366
+ const [allTasks, customTypes] = await Promise.all([
1367
+ client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed }),
1368
+ client.getCustomTaskTypes(config.teamId)
1369
+ ]);
1370
+ const typeMap = buildTypeMap(customTypes);
1310
1371
  let groups = groupByStatus(allTasks, opts.includeClosed ?? false);
1311
1372
  if (opts.status) {
1312
1373
  const lower = opts.status.toLowerCase();
@@ -1315,7 +1376,7 @@ async function runAssignedCommand(config, opts) {
1315
1376
  if (shouldOutputJson(opts.json ?? false)) {
1316
1377
  const result = {};
1317
1378
  for (const group of groups) {
1318
- result[group.status.toLowerCase()] = group.tasks.map(summarize);
1379
+ result[group.status.toLowerCase()] = group.tasks.map((t) => summarize(t, typeMap));
1319
1380
  }
1320
1381
  console.log(JSON.stringify(result, null, 2));
1321
1382
  return;
@@ -1323,7 +1384,7 @@ async function runAssignedCommand(config, opts) {
1323
1384
  if (!isTTY()) {
1324
1385
  const mdGroups = groups.map((g) => ({
1325
1386
  label: g.status,
1326
- tasks: g.tasks.map((t) => summarize(t))
1387
+ tasks: g.tasks.map((t) => summarize(t, typeMap))
1327
1388
  }));
1328
1389
  console.log(formatGroupedTasksMarkdown(mdGroups));
1329
1390
  return;
@@ -1334,7 +1395,7 @@ async function runAssignedCommand(config, opts) {
1334
1395
  }
1335
1396
  const pickerGroups = groups.map((g) => ({
1336
1397
  label: g.status.toUpperCase(),
1337
- tasks: g.tasks.map(summarize)
1398
+ tasks: g.tasks.map((t) => summarize(t, typeMap))
1338
1399
  }));
1339
1400
  const selected = await groupedTaskPicker(pickerGroups);
1340
1401
  await showDetailsAndOpen(selected, (id) => client.getTask(id));
@@ -1417,7 +1478,7 @@ function isOverdue(task, now) {
1417
1478
  const due = Number(task.due_date);
1418
1479
  return !isNaN(due) && due < now;
1419
1480
  }
1420
- function categorizeTasks(tasks, hoursBack) {
1481
+ function categorizeTasks(tasks, hoursBack, typeMap) {
1421
1482
  const now = Date.now();
1422
1483
  const cutoff = now - hoursBack * 60 * 60 * 1e3;
1423
1484
  const completed = [];
@@ -1426,13 +1487,13 @@ function categorizeTasks(tasks, hoursBack) {
1426
1487
  for (const task of tasks) {
1427
1488
  const done = isDoneStatus(task.status.status);
1428
1489
  if (done && isCompletedRecently(task, cutoff)) {
1429
- completed.push(summarize(task));
1490
+ completed.push(summarize(task, typeMap));
1430
1491
  }
1431
1492
  if (!done && isInProgress(task)) {
1432
- inProgress.push(summarize(task));
1493
+ inProgress.push(summarize(task, typeMap));
1433
1494
  }
1434
1495
  if (!done && isOverdue(task, now)) {
1435
- overdue.push(summarize(task));
1496
+ overdue.push(summarize(task, typeMap));
1436
1497
  }
1437
1498
  }
1438
1499
  return { completed, inProgress, overdue };
@@ -1448,8 +1509,12 @@ ${label} (${tasks.length})`);
1448
1509
  }
1449
1510
  async function runSummaryCommand(config, opts) {
1450
1511
  const client = new ClickUpClient(config);
1451
- const allTasks = await client.getMyTasks(config.teamId, { includeClosed: true });
1452
- const result = categorizeTasks(allTasks, opts.hours);
1512
+ const [allTasks, customTypes] = await Promise.all([
1513
+ client.getMyTasks(config.teamId, { includeClosed: true }),
1514
+ client.getCustomTaskTypes(config.teamId)
1515
+ ]);
1516
+ const typeMap = buildTypeMap(customTypes);
1517
+ const result = categorizeTasks(allTasks, opts.hours, typeMap);
1453
1518
  if (shouldOutputJson(opts.json)) {
1454
1519
  console.log(JSON.stringify(result, null, 2));
1455
1520
  return;
@@ -1475,9 +1540,13 @@ function isOverdue2(task, now) {
1475
1540
  }
1476
1541
  async function fetchOverdueTasks(config, opts = {}) {
1477
1542
  const client = new ClickUpClient(config);
1478
- const allTasks = await client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed });
1543
+ const [allTasks, customTypes] = await Promise.all([
1544
+ client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed }),
1545
+ client.getCustomTaskTypes(config.teamId)
1546
+ ]);
1547
+ const typeMap = buildTypeMap(customTypes);
1479
1548
  const now = Date.now();
1480
- return allTasks.filter((t) => isOverdue2(t, now) && (opts.includeClosed || !isDoneStatus(t.status.status))).sort((a, b) => Number(a.due_date) - Number(b.due_date)).map(summarize);
1549
+ return allTasks.filter((t) => isOverdue2(t, now) && (opts.includeClosed || !isDoneStatus(t.status.status))).sort((a, b) => Number(a.due_date) - Number(b.due_date)).map((t) => summarize(t, typeMap));
1481
1550
  }
1482
1551
 
1483
1552
  // src/commands/config.ts
@@ -1615,7 +1684,7 @@ function bashCompletion() {
1615
1684
  cword=$COMP_CWORD
1616
1685
  fi
1617
1686
 
1618
- local commands="init auth tasks initiatives task update create sprint sprints subtasks comment comments activity lists spaces inbox assigned open search summary overdue assign depend move config completion"
1687
+ local commands="init auth tasks task update create sprint sprints subtasks comment comments activity lists spaces inbox assigned open search summary overdue assign depend move field delete tag config completion"
1619
1688
 
1620
1689
  if [[ $cword -eq 1 ]]; then
1621
1690
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -1636,8 +1705,8 @@ function bashCompletion() {
1636
1705
  esac
1637
1706
 
1638
1707
  case "$cmd" in
1639
- tasks|initiatives)
1640
- COMPREPLY=($(compgen -W "--status --list --space --name --include-closed --json" -- "$cur"))
1708
+ tasks)
1709
+ COMPREPLY=($(compgen -W "--status --list --space --name --type --include-closed --json" -- "$cur"))
1641
1710
  ;;
1642
1711
  task)
1643
1712
  COMPREPLY=($(compgen -W "--json" -- "$cur"))
@@ -1702,6 +1771,15 @@ function bashCompletion() {
1702
1771
  move)
1703
1772
  COMPREPLY=($(compgen -W "--to --remove --json" -- "$cur"))
1704
1773
  ;;
1774
+ field)
1775
+ COMPREPLY=($(compgen -W "--set --remove --json" -- "$cur"))
1776
+ ;;
1777
+ delete)
1778
+ COMPREPLY=($(compgen -W "--confirm --json" -- "$cur"))
1779
+ ;;
1780
+ tag)
1781
+ COMPREPLY=($(compgen -W "--add --remove --json" -- "$cur"))
1782
+ ;;
1705
1783
  config)
1706
1784
  if [[ $cword -eq 2 ]]; then
1707
1785
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -1731,7 +1809,6 @@ _cu() {
1731
1809
  'init:Set up cu for the first time'
1732
1810
  'auth:Validate API token and show current user'
1733
1811
  'tasks:List tasks assigned to me'
1734
- 'initiatives:List initiatives assigned to me'
1735
1812
  'task:Get task details'
1736
1813
  'update:Update a task'
1737
1814
  'create:Create a new task'
@@ -1752,6 +1829,9 @@ _cu() {
1752
1829
  'assign:Assign or unassign users from a task'
1753
1830
  'depend:Add or remove task dependencies'
1754
1831
  'move:Add or remove a task from a list'
1832
+ 'field:Set or remove a custom field value on a task'
1833
+ 'delete:Delete a task'
1834
+ 'tag:Add or remove tags from a task'
1755
1835
  'config:Manage CLI configuration'
1756
1836
  'completion:Output shell completion script'
1757
1837
  )
@@ -1768,12 +1848,13 @@ _cu() {
1768
1848
  ;;
1769
1849
  args)
1770
1850
  case $words[1] in
1771
- tasks|initiatives)
1851
+ tasks)
1772
1852
  _arguments \\
1773
1853
  '--status[Filter by status]:status:(open "in progress" "in review" done closed)' \\
1774
1854
  '--list[Filter by list ID]:list_id:' \\
1775
1855
  '--space[Filter by space ID]:space_id:' \\
1776
1856
  '--name[Filter by name]:query:' \\
1857
+ '--type[Filter by task type]:type:' \\
1777
1858
  '--include-closed[Include done/closed tasks]' \\
1778
1859
  '--json[Force JSON output]'
1779
1860
  ;;
@@ -1918,6 +1999,26 @@ _cu() {
1918
1999
  '--remove[Remove task from this list]:list_id:' \\
1919
2000
  '--json[Force JSON output]'
1920
2001
  ;;
2002
+ field)
2003
+ _arguments \\
2004
+ '1:task_id:' \\
2005
+ '--set[Set field name and value]:name_and_value:' \\
2006
+ '--remove[Remove field value by name]:field_name:' \\
2007
+ '--json[Force JSON output]'
2008
+ ;;
2009
+ delete)
2010
+ _arguments \\
2011
+ '1:task_id:' \\
2012
+ '--confirm[Skip confirmation prompt]' \\
2013
+ '--json[Force JSON output]'
2014
+ ;;
2015
+ tag)
2016
+ _arguments \\
2017
+ '1:task_id:' \\
2018
+ '--add[Comma-separated tag names to add]:tags:' \\
2019
+ '--remove[Comma-separated tag names to remove]:tags:' \\
2020
+ '--json[Force JSON output]'
2021
+ ;;
1921
2022
  config)
1922
2023
  local -a config_cmds
1923
2024
  config_cmds=(
@@ -1961,7 +2062,6 @@ complete -c cu -n __fish_use_subcommand -s V -l version -d 'Show version'
1961
2062
  complete -c cu -n __fish_use_subcommand -a init -d 'Set up cu for the first time'
1962
2063
  complete -c cu -n __fish_use_subcommand -a auth -d 'Validate API token and show current user'
1963
2064
  complete -c cu -n __fish_use_subcommand -a tasks -d 'List tasks assigned to me'
1964
- complete -c cu -n __fish_use_subcommand -a initiatives -d 'List initiatives assigned to me'
1965
2065
  complete -c cu -n __fish_use_subcommand -a task -d 'Get task details'
1966
2066
  complete -c cu -n __fish_use_subcommand -a update -d 'Update a task'
1967
2067
  complete -c cu -n __fish_use_subcommand -a create -d 'Create a new task'
@@ -1982,17 +2082,21 @@ complete -c cu -n __fish_use_subcommand -a overdue -d 'List tasks that are past
1982
2082
  complete -c cu -n __fish_use_subcommand -a assign -d 'Assign or unassign users from a task'
1983
2083
  complete -c cu -n __fish_use_subcommand -a depend -d 'Add or remove task dependencies'
1984
2084
  complete -c cu -n __fish_use_subcommand -a move -d 'Add or remove a task from a list'
2085
+ complete -c cu -n __fish_use_subcommand -a field -d 'Set or remove a custom field value on a task'
2086
+ complete -c cu -n __fish_use_subcommand -a delete -d 'Delete a task'
2087
+ complete -c cu -n __fish_use_subcommand -a tag -d 'Add or remove tags from a task'
1985
2088
  complete -c cu -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
1986
2089
  complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completion script'
1987
2090
 
1988
2091
  complete -c cu -n '__fish_seen_subcommand_from auth' -l json -d 'Force JSON output'
1989
2092
 
1990
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l status -d 'Filter by status'
1991
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l list -d 'Filter by list ID'
1992
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l space -d 'Filter by space ID'
1993
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l name -d 'Filter by name'
1994
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l include-closed -d 'Include done/closed tasks'
1995
- complete -c cu -n '__fish_seen_subcommand_from tasks initiatives' -l json -d 'Force JSON output'
2093
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l status -d 'Filter by status'
2094
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l list -d 'Filter by list ID'
2095
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l space -d 'Filter by space ID'
2096
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l name -d 'Filter by name'
2097
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l type -d 'Filter by task type'
2098
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l include-closed -d 'Include done/closed tasks'
2099
+ complete -c cu -n '__fish_seen_subcommand_from tasks' -l json -d 'Force JSON output'
1996
2100
 
1997
2101
  complete -c cu -n '__fish_seen_subcommand_from task' -l json -d 'Force JSON output'
1998
2102
 
@@ -2079,6 +2183,17 @@ complete -c cu -n '__fish_seen_subcommand_from move' -l to -d 'Add task to this
2079
2183
  complete -c cu -n '__fish_seen_subcommand_from move' -l remove -d 'Remove task from this list'
2080
2184
  complete -c cu -n '__fish_seen_subcommand_from move' -l json -d 'Force JSON output'
2081
2185
 
2186
+ complete -c cu -n '__fish_seen_subcommand_from field' -l set -d 'Set field name and value'
2187
+ complete -c cu -n '__fish_seen_subcommand_from field' -l remove -d 'Remove field value by name'
2188
+ complete -c cu -n '__fish_seen_subcommand_from field' -l json -d 'Force JSON output'
2189
+
2190
+ complete -c cu -n '__fish_seen_subcommand_from delete' -l confirm -d 'Skip confirmation prompt'
2191
+ complete -c cu -n '__fish_seen_subcommand_from delete' -l json -d 'Force JSON output'
2192
+
2193
+ complete -c cu -n '__fish_seen_subcommand_from tag' -l add -d 'Comma-separated tag names to add'
2194
+ complete -c cu -n '__fish_seen_subcommand_from tag' -l remove -d 'Comma-separated tag names to remove'
2195
+ complete -c cu -n '__fish_seen_subcommand_from tag' -l json -d 'Force JSON output'
2196
+
2082
2197
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
2083
2198
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
2084
2199
  complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
@@ -2119,9 +2234,11 @@ async function searchTasks(config, query, opts = {}) {
2119
2234
  throw new Error("Search query cannot be empty");
2120
2235
  }
2121
2236
  const client = new ClickUpClient(config);
2122
- const allTasks = await client.getMyTasks(config.teamId, {
2123
- includeClosed: opts.includeClosed
2124
- });
2237
+ const [allTasks, customTypes] = await Promise.all([
2238
+ client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed }),
2239
+ client.getCustomTaskTypes(config.teamId)
2240
+ ]);
2241
+ const typeMap = buildTypeMap(customTypes);
2125
2242
  const words = trimmed.toLowerCase().split(/\s+/);
2126
2243
  let matched = allTasks.filter((task) => {
2127
2244
  const name = task.name.toLowerCase();
@@ -2140,7 +2257,7 @@ async function searchTasks(config, query, opts = {}) {
2140
2257
  matched = matched.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase());
2141
2258
  }
2142
2259
  }
2143
- return matched.map(summarize);
2260
+ return matched.map((t) => summarize(t, typeMap));
2144
2261
  }
2145
2262
 
2146
2263
  // src/commands/depend.ts
@@ -2197,6 +2314,140 @@ async function moveTask(config, taskId, opts) {
2197
2314
  return messages.join("; ");
2198
2315
  }
2199
2316
 
2317
+ // src/commands/field.ts
2318
+ var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["text", "number", "drop_down", "checkbox", "date", "url", "email"]);
2319
+ function findFieldByName(fields, name) {
2320
+ const lower = name.toLowerCase();
2321
+ const match = fields.find((f) => f.name.toLowerCase() === lower);
2322
+ if (!match) {
2323
+ const available = fields.map((f) => f.name).join(", ");
2324
+ throw new Error(`Field "${name}" not found. Available fields: ${available}`);
2325
+ }
2326
+ return match;
2327
+ }
2328
+ function parseFieldValue(field, rawValue) {
2329
+ if (!SUPPORTED_TYPES.has(field.type)) {
2330
+ throw new Error(
2331
+ `Field type "${field.type}" is not supported. Supported types: ${[...SUPPORTED_TYPES].join(", ")}`
2332
+ );
2333
+ }
2334
+ switch (field.type) {
2335
+ case "number": {
2336
+ const n = Number(rawValue);
2337
+ if (!Number.isFinite(n)) throw new Error(`Value "${rawValue}" is not a valid numeric value`);
2338
+ return n;
2339
+ }
2340
+ case "checkbox":
2341
+ if (rawValue !== "true" && rawValue !== "false") {
2342
+ throw new Error('Checkbox value must be "true" or "false"');
2343
+ }
2344
+ return rawValue === "true";
2345
+ case "drop_down": {
2346
+ const options = field.type_config?.options;
2347
+ if (!options?.length) throw new Error("Dropdown field has no configured options");
2348
+ const lower = rawValue.toLowerCase();
2349
+ const option = options.find((o) => o.name.toLowerCase() === lower);
2350
+ if (!option) {
2351
+ const available = options.map((o) => o.name).join(", ");
2352
+ throw new Error(`Option "${rawValue}" not found. Available options: ${available}`);
2353
+ }
2354
+ if (option.orderindex === void 0) {
2355
+ throw new Error(`Dropdown option "${option.name}" has no orderindex`);
2356
+ }
2357
+ return option.orderindex;
2358
+ }
2359
+ case "date": {
2360
+ const ms = new Date(rawValue).getTime();
2361
+ if (!Number.isFinite(ms))
2362
+ throw new Error(`Value "${rawValue}" is not a valid date (use YYYY-MM-DD)`);
2363
+ return ms;
2364
+ }
2365
+ default:
2366
+ return rawValue;
2367
+ }
2368
+ }
2369
+ async function setCustomField(config, taskId, opts) {
2370
+ if (!opts.set && !opts.remove) {
2371
+ throw new Error("Provide at least one of: --set, --remove");
2372
+ }
2373
+ const client = new ClickUpClient(config);
2374
+ const task = await client.getTask(taskId);
2375
+ const fields = task.custom_fields ?? [];
2376
+ const results = [];
2377
+ if (opts.set) {
2378
+ const [fieldName, rawValue] = opts.set;
2379
+ const field = findFieldByName(fields, fieldName);
2380
+ const parsed = parseFieldValue(field, rawValue);
2381
+ await client.setCustomFieldValue(taskId, field.id, parsed);
2382
+ results.push({ taskId, field: field.name, action: "set", value: parsed });
2383
+ }
2384
+ if (opts.remove) {
2385
+ const field = findFieldByName(fields, opts.remove);
2386
+ await client.removeCustomFieldValue(taskId, field.id);
2387
+ results.push({ taskId, field: field.name, action: "removed" });
2388
+ }
2389
+ return { results };
2390
+ }
2391
+
2392
+ // src/commands/delete.ts
2393
+ async function deleteTaskCommand(config, taskId, opts) {
2394
+ const client = new ClickUpClient(config);
2395
+ if (!opts.confirm) {
2396
+ if (!isTTY()) {
2397
+ throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
2398
+ }
2399
+ const task = await client.getTask(taskId);
2400
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
2401
+ const confirmed = await confirm3({
2402
+ message: `Delete task "${task.name}" (${task.id})? This cannot be undone.`,
2403
+ default: false
2404
+ });
2405
+ if (!confirmed) {
2406
+ throw new Error("Cancelled");
2407
+ }
2408
+ }
2409
+ await client.deleteTask(taskId);
2410
+ return { taskId, deleted: true };
2411
+ }
2412
+
2413
+ // src/commands/tag.ts
2414
+ function parseTags(input) {
2415
+ return input.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
2416
+ }
2417
+ async function manageTags(config, taskId, opts) {
2418
+ if (!opts.add && !opts.remove) {
2419
+ throw new Error("Provide at least one of: --add, --remove");
2420
+ }
2421
+ const client = new ClickUpClient(config);
2422
+ const added = [];
2423
+ const removed = [];
2424
+ if (opts.add) {
2425
+ const tags = parseTags(opts.add);
2426
+ for (const tag of tags) {
2427
+ await client.addTagToTask(taskId, tag);
2428
+ added.push(tag);
2429
+ }
2430
+ }
2431
+ if (opts.remove) {
2432
+ const tags = parseTags(opts.remove);
2433
+ try {
2434
+ for (const tag of tags) {
2435
+ await client.removeTagFromTask(taskId, tag);
2436
+ removed.push(tag);
2437
+ }
2438
+ } catch (err) {
2439
+ if (added.length > 0) {
2440
+ const reason = err instanceof Error ? err.message : String(err);
2441
+ throw new Error(`Added tags: ${added.join(", ")}; but failed to remove: ${reason}`, {
2442
+ cause: err
2443
+ });
2444
+ }
2445
+ throw err;
2446
+ }
2447
+ }
2448
+ return { taskId, added, removed };
2449
+ }
2450
+
2200
2451
  // src/index.ts
2201
2452
  var require2 = createRequire(import.meta.url);
2202
2453
  var { version } = require2("../package.json");
@@ -2228,25 +2479,14 @@ program.command("auth").description("Validate API token and show current user").
2228
2479
  }
2229
2480
  })
2230
2481
  );
2231
- program.command("tasks").description("List tasks assigned to me").option("--status <status>", 'Filter by status (e.g. "in progress")').option("--list <listId>", "Filter by list ID").option("--space <spaceId>", "Filter by space ID").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
2482
+ program.command("tasks").description("List tasks assigned to me").option("--status <status>", 'Filter by status (e.g. "in progress")').option("--list <listId>", "Filter by list ID").option("--space <spaceId>", "Filter by space ID").option("--name <partial>", "Filter by name (case-insensitive contains)").option(
2483
+ "--type <type>",
2484
+ 'Filter by task type (e.g. "task", "initiative", or custom type name/ID)'
2485
+ ).option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
2232
2486
  wrapAction(async (opts) => {
2233
2487
  const config = loadConfig();
2234
2488
  const tasks = await fetchMyTasks(config, {
2235
- typeFilter: "task",
2236
- statuses: opts.status ? [opts.status] : void 0,
2237
- listIds: opts.list ? [opts.list] : void 0,
2238
- spaceIds: opts.space ? [opts.space] : void 0,
2239
- name: opts.name,
2240
- includeClosed: opts.includeClosed
2241
- });
2242
- await printTasks(tasks, opts.json ?? false, config);
2243
- })
2244
- );
2245
- program.command("initiatives").description("List initiatives assigned to me").option("--status <status>", "Filter by status").option("--list <listId>", "Filter by list ID").option("--space <spaceId>", "Filter by space ID").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
2246
- wrapAction(async (opts) => {
2247
- const config = loadConfig();
2248
- const tasks = await fetchMyTasks(config, {
2249
- typeFilter: "initiative",
2489
+ typeFilter: opts.type,
2250
2490
  statuses: opts.status ? [opts.status] : void 0,
2251
2491
  listIds: opts.list ? [opts.list] : void 0,
2252
2492
  spaceIds: opts.space ? [opts.space] : void 0,
@@ -2452,6 +2692,60 @@ program.command("move <taskId>").description("Add or remove a task from a list")
2452
2692
  }
2453
2693
  })
2454
2694
  );
2695
+ program.command("field <taskId>").description("Set or remove a custom field value on a task").option("--set <nameAndValue...>", 'Set field: --set "Field Name" value').option("--remove <fieldName>", "Remove field value by name").option("--json", "Force JSON output even in terminal").action(
2696
+ wrapAction(
2697
+ async (taskId, opts) => {
2698
+ const config = loadConfig();
2699
+ const fieldOpts = {};
2700
+ if (opts.set) {
2701
+ if (opts.set.length !== 2) {
2702
+ throw new Error("--set requires exactly two arguments: field name and value");
2703
+ }
2704
+ fieldOpts.set = [opts.set[0], opts.set[1]];
2705
+ }
2706
+ if (opts.remove) {
2707
+ fieldOpts.remove = opts.remove;
2708
+ }
2709
+ const { results } = await setCustomField(config, taskId, fieldOpts);
2710
+ if (shouldOutputJson(opts.json ?? false)) {
2711
+ console.log(JSON.stringify(results, null, 2));
2712
+ } else {
2713
+ for (const r of results) {
2714
+ if (r.action === "set") {
2715
+ console.log(`Set "${r.field}" to ${JSON.stringify(r.value)} on ${r.taskId}`);
2716
+ } else {
2717
+ console.log(`Removed "${r.field}" from ${r.taskId}`);
2718
+ }
2719
+ }
2720
+ }
2721
+ }
2722
+ )
2723
+ );
2724
+ program.command("delete <taskId>").description("Delete a task (requires confirmation)").option("--confirm", "Skip confirmation prompt (required in non-interactive mode)").option("--json", "Force JSON output even in terminal").action(
2725
+ wrapAction(async (taskId, opts) => {
2726
+ const config = loadConfig();
2727
+ const result = await deleteTaskCommand(config, taskId, opts);
2728
+ if (shouldOutputJson(opts.json ?? false)) {
2729
+ console.log(JSON.stringify(result, null, 2));
2730
+ } else {
2731
+ console.log(`Deleted task ${result.taskId}`);
2732
+ }
2733
+ })
2734
+ );
2735
+ program.command("tag <taskId>").description("Add or remove tags from a task").option("--add <tags>", "Comma-separated tag names to add").option("--remove <tags>", "Comma-separated tag names to remove").option("--json", "Force JSON output even in terminal").action(
2736
+ wrapAction(async (taskId, opts) => {
2737
+ const config = loadConfig();
2738
+ const result = await manageTags(config, taskId, opts);
2739
+ if (shouldOutputJson(opts.json ?? false)) {
2740
+ console.log(JSON.stringify(result, null, 2));
2741
+ } else {
2742
+ const parts = [];
2743
+ if (result.added.length > 0) parts.push(`Added tags: ${result.added.join(", ")}`);
2744
+ if (result.removed.length > 0) parts.push(`Removed tags: ${result.removed.join(", ")}`);
2745
+ console.log(parts.join("; "));
2746
+ }
2747
+ })
2748
+ );
2455
2749
  var configCmd = program.command("config").description("Manage CLI configuration");
2456
2750
  configCmd.command("get <key>").description("Print a config value").action(
2457
2751
  wrapAction(async (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "0.12.3",
3
+ "version": "0.13.1",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: clickup
3
- description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config.'
3
+ description: 'Use when managing ClickUp tasks, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, 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.'
4
4
  ---
5
5
 
6
6
  # ClickUp CLI (`cu`)
7
7
 
8
- Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, initiatives, comments, and project workflows.
8
+ Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, comments, and project workflows.
9
9
 
10
- Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks, standup, overdue, search
10
+ Keywords: ClickUp, task management, sprint, project management, agile, backlog, subtasks, standup, overdue, search
11
11
 
12
12
  ## Setup
13
13
 
@@ -34,25 +34,24 @@ All commands support `--help` for full flag details.
34
34
 
35
35
  ### Read
36
36
 
37
- | Command | What it returns |
38
- | --------------------------------------------------------------------------------------------- | -------------------------------------------------- |
39
- | `cu tasks [--status s] [--name q] [--list id] [--space id] [--include-closed] [--json]` | My tasks (workspace-wide) |
40
- | `cu initiatives [--status s] [--name q] [--list id] [--space id] [--include-closed] [--json]` | My initiatives |
41
- | `cu assigned [--status s] [--include-closed] [--json]` | All my tasks grouped by status |
42
- | `cu sprint [--status s] [--space nameOrId] [--include-closed] [--json]` | Tasks in active sprint (auto-detected) |
43
- | `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
44
- | `cu search <query> [--status s] [--include-closed] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
45
- | `cu task <id> [--json]` | Single task details |
46
- | `cu subtasks <id> [--status s] [--name q] [--include-closed] [--json]` | Subtasks of a task or initiative |
47
- | `cu comments <id> [--json]` | Comments on a task |
48
- | `cu activity <id> [--json]` | Task details + comment history combined |
49
- | `cu inbox [--days n] [--include-closed] [--json]` | Tasks updated in last n days (default 30) |
50
- | `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
51
- | `cu overdue [--include-closed] [--json]` | Tasks past their due date |
52
- | `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
53
- | `cu lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
54
- | `cu open <query> [--json]` | Open task in browser by ID or name |
55
- | `cu auth [--json]` | Check authentication status |
37
+ | Command | What it returns |
38
+ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
39
+ | `cu tasks [--status s] [--name q] [--type t] [--list id] [--space id] [--include-closed] [--json]` | My tasks (all types, or filter with --type) |
40
+ | `cu assigned [--status s] [--include-closed] [--json]` | All my tasks grouped by status |
41
+ | `cu sprint [--status s] [--space nameOrId] [--include-closed] [--json]` | Tasks in active sprint (auto-detected) |
42
+ | `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
43
+ | `cu search <query> [--status s] [--include-closed] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
44
+ | `cu task <id> [--json]` | Single task details |
45
+ | `cu subtasks <id> [--status s] [--name q] [--include-closed] [--json]` | Subtasks of a task |
46
+ | `cu comments <id> [--json]` | Comments on a task |
47
+ | `cu activity <id> [--json]` | Task details + comment history combined |
48
+ | `cu inbox [--days n] [--include-closed] [--json]` | Tasks updated in last n days (default 30) |
49
+ | `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
50
+ | `cu overdue [--include-closed] [--json]` | Tasks past their due date |
51
+ | `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
52
+ | `cu lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
53
+ | `cu open <query> [--json]` | Open task in browser by ID or name |
54
+ | `cu auth [--json]` | Check authentication status |
56
55
 
57
56
  ### Write
58
57
 
@@ -64,38 +63,45 @@ All commands support `--help` for full flag details.
64
63
  | `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
65
64
  | `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]` | Add/remove task dependencies |
66
65
  | `cu move <id> [--to listId] [--remove listId] [--json]` | Add/remove task from lists |
66
+ | `cu field <id> [--set "Name" value] [--remove "Name"] [--json]` | Set/remove custom field values |
67
+ | `cu delete <id> [--confirm] [--json]` | Delete a task (DESTRUCTIVE, irreversible) |
68
+ | `cu tag <id> [--add tags] [--remove tags] [--json]` | Add/remove tags on a task |
67
69
  | `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
68
70
  | `cu completion <shell>` | Shell completions (bash/zsh/fish) |
69
71
 
70
72
  ## Quick Reference
71
73
 
72
- | Topic | Detail |
73
- | ------------------- | --------------------------------------------------------------------------------------------------------------------- |
74
- | Task IDs | Stable alphanumeric strings (e.g. `abc123def`) |
75
- | Initiatives | Detected via `custom_item_id !== 0` |
76
- | `--list` on create | Optional when `--parent` is given (auto-detected) |
77
- | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
78
- | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
79
- | `--due-date` | `YYYY-MM-DD` format |
80
- | `--assignee` | Numeric user ID (find via `cu task <id> --json`) |
81
- | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
82
- | `--time-estimate` | Duration format: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
83
- | `--custom-item-id` | Custom task type ID (e.g. `1` for initiative) |
84
- | `--on` / `--blocks` | Task dependency direction (used with `cu depend`) |
85
- | `--to` / `--remove` | List ID to add/remove task (used with `cu move`) |
86
- | `--space` | Partial name match or exact ID |
87
- | `--name` | Partial match, case-insensitive |
88
- | `--include-closed` | Include closed/done tasks (on `tasks`, `initiatives`, `assigned`, `subtasks`, `sprint`, `search`, `inbox`, `overdue`) |
89
- | `cu assign --to me` | Shorthand for your own user ID |
90
- | `cu search` | Matches all query words against task name, case-insensitive |
91
- | `cu sprint` | Auto-detects active sprint via view API and date range parsing |
92
- | `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
93
- | `cu overdue` | Excludes closed tasks, sorted most overdue first |
94
- | `cu open` | Tries task ID first, falls back to name search |
95
- | `cu task` | Shows custom fields in detail view |
96
- | `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
97
- | Errors | stderr with exit code 1 |
98
- | Parsing | Strict - excess/unknown arguments rejected |
74
+ | Topic | Detail |
75
+ | ----------------------- | ------------------------------------------------------------------------------------------------------ |
76
+ | Task IDs | Stable alphanumeric strings (e.g. `abc123def`) |
77
+ | `--type` | Filter by task type: `task` (regular), or custom type name/ID (e.g. `initiative`, `Bug`) |
78
+ | `--list` on create | Optional when `--parent` is given (auto-detected) |
79
+ | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
80
+ | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
81
+ | `--due-date` | `YYYY-MM-DD` format |
82
+ | `--assignee` | Numeric user ID (find via `cu task <id> --json`) |
83
+ | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
84
+ | `--time-estimate` | Duration format: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
85
+ | `--custom-item-id` | Custom task type ID for `cu create` (e.g. `1` for initiative) |
86
+ | `--on` / `--blocks` | Task dependency direction (used with `cu depend`) |
87
+ | `--to` / `--remove` | List ID to add/remove task (used with `cu move`) |
88
+ | `cu field --set` | Supports: text, number, checkbox (true/false), dropdown (option name), date (YYYY-MM-DD), url, email |
89
+ | `cu field` | Field names resolved case-insensitively; errors list available fields/options |
90
+ | `cu delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone |
91
+ | `cu tag --add/--remove` | Comma-separated tag names (e.g. `--add "bug,frontend"`) |
92
+ | `--space` | Partial name match or exact ID |
93
+ | `--name` | Partial match, case-insensitive |
94
+ | `--include-closed` | Include closed/done tasks (on `tasks`, `assigned`, `subtasks`, `sprint`, `search`, `inbox`, `overdue`) |
95
+ | `cu assign --to me` | Shorthand for your own user ID |
96
+ | `cu search` | Matches all query words against task name, case-insensitive |
97
+ | `cu sprint` | Auto-detects active sprint via view API and date range parsing |
98
+ | `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
99
+ | `cu overdue` | Excludes closed tasks, sorted most overdue first |
100
+ | `cu open` | Tries task ID first, falls back to name search |
101
+ | `cu task` | Shows custom fields in detail view |
102
+ | `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
103
+ | Errors | stderr with exit code 1 |
104
+ | Parsing | Strict - excess/unknown arguments rejected |
99
105
 
100
106
  ## Agent Workflow Examples
101
107
 
@@ -114,6 +120,8 @@ cu activity abc123def # task + comments combined
114
120
  ```bash
115
121
  cu tasks --status "in progress" # by status
116
122
  cu tasks --name "login" # by partial name
123
+ cu tasks --type initiative # initiatives only
124
+ cu tasks --type task # regular tasks only
117
125
  cu search "payment flow" # multi-word search
118
126
  cu search auth --status "prog" # fuzzy status match
119
127
  cu sprint # current sprint
@@ -137,6 +145,12 @@ cu assign abc123def --to me
137
145
  cu depend task3 --on task2 # task3 waits for task2
138
146
  cu depend task1 --blocks task2 # task1 blocks task2
139
147
  cu move task1 --to list2 --remove list1 # move between lists
148
+ cu field abc123def --set "Story Points" 5
149
+ cu field abc123def --set "Category" "Bug Fix"
150
+ cu field abc123def --remove "Old Field"
151
+ cu tag abc123def --add "bug,frontend"
152
+ cu tag abc123def --remove "triage"
153
+ cu delete abc123def --confirm # irreversible!
140
154
  ```
141
155
 
142
156
  ### Discover workspace structure
@@ -155,3 +169,7 @@ cu auth # verify token works
155
169
  cu summary # completed / in progress / overdue
156
170
  cu summary --hours 48 # wider window
157
171
  ```
172
+
173
+ ## DELETE SAFETY
174
+
175
+ IMPORTANT: Always confirm with the user before running `cu delete`. This is a destructive, irreversible operation. Even when using `--confirm` flag, verify the task ID is correct with the user first.