@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 +67 -18
- package/dist/index.js +364 -70
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +67 -49
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
|
-
|
|
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
|
|
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
|
|
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",
|
|
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
|
|
491
|
-
const typeLabel =
|
|
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
|
|
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:
|
|
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
|
|
640
|
-
|
|
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
|
|
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
|
|
1196
|
-
subtasks: true,
|
|
1197
|
-
|
|
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
|
|
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
|
|
1452
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1991
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
1992
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
1993
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
1994
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
1995
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
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
|
|
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(
|
|
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:
|
|
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,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clickup
|
|
3
|
-
description: 'Use when managing ClickUp tasks,
|
|
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,
|
|
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,
|
|
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
|
|
38
|
-
|
|
|
39
|
-
| `cu tasks [--status s] [--name q] [--list id] [--space id] [--include-closed] [--json]`
|
|
40
|
-
| `cu
|
|
41
|
-
| `cu
|
|
42
|
-
| `cu
|
|
43
|
-
| `cu
|
|
44
|
-
| `cu
|
|
45
|
-
| `cu
|
|
46
|
-
| `cu
|
|
47
|
-
| `cu
|
|
48
|
-
| `cu
|
|
49
|
-
| `cu
|
|
50
|
-
| `cu
|
|
51
|
-
| `cu
|
|
52
|
-
| `cu
|
|
53
|
-
| `cu
|
|
54
|
-
| `cu
|
|
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
|
|
73
|
-
|
|
|
74
|
-
| Task IDs
|
|
75
|
-
|
|
|
76
|
-
| `--list` on create
|
|
77
|
-
| `--status`
|
|
78
|
-
| `--priority`
|
|
79
|
-
| `--due-date`
|
|
80
|
-
| `--assignee`
|
|
81
|
-
| `--tags`
|
|
82
|
-
| `--time-estimate`
|
|
83
|
-
| `--custom-item-id`
|
|
84
|
-
| `--on` / `--blocks`
|
|
85
|
-
| `--to` / `--remove`
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
| `cu
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `cu
|
|
94
|
-
| `cu
|
|
95
|
-
| `cu
|
|
96
|
-
| `cu
|
|
97
|
-
|
|
|
98
|
-
|
|
|
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.
|