@krodak/clickup-cli 0.13.0 → 0.13.2
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 +11 -21
- package/dist/index.js +198 -148
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +67 -66
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
|
|
@@ -212,6 +201,7 @@ cu update abc123 -n "New task name"
|
|
|
212
201
|
cu update abc123 -d "Updated description with **markdown**"
|
|
213
202
|
cu update abc123 --priority high
|
|
214
203
|
cu update abc123 --due-date 2025-03-15
|
|
204
|
+
cu update abc123 --assignee me
|
|
215
205
|
cu update abc123 --assignee 12345
|
|
216
206
|
cu update abc123 -n "New name" -s "done" --priority urgent
|
|
217
207
|
cu update abc123 --time-estimate 2h
|
|
@@ -227,7 +217,7 @@ cu update abc123 -s "in progress" --json
|
|
|
227
217
|
| `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
|
|
228
218
|
| `--due-date <date>` | Due date (`YYYY-MM-DD`) |
|
|
229
219
|
| `--time-estimate <duration>` | Time estimate (e.g. `"2h"`, `"30m"`, `"1h30m"`) |
|
|
230
|
-
| `--assignee <userId>` | Add assignee by
|
|
220
|
+
| `--assignee <userId>` | Add assignee by user ID or `"me"` |
|
|
231
221
|
| `--parent <taskId>` | Set parent task (makes this a subtask) |
|
|
232
222
|
| `--json` | Force JSON output even in terminal |
|
|
233
223
|
|
|
@@ -240,7 +230,7 @@ cu create -n "Fix login bug" -l <listId>
|
|
|
240
230
|
cu create -n "Subtask name" -p <parentTaskId> # --list auto-detected
|
|
241
231
|
cu create -n "Task" -l <listId> -d "desc" -s "open"
|
|
242
232
|
cu create -n "Task" -l <listId> --priority high --due-date 2025-06-01
|
|
243
|
-
cu create -n "Task" -l <listId> --assignee
|
|
233
|
+
cu create -n "Task" -l <listId> --assignee me --tags "bug,frontend"
|
|
244
234
|
cu create -n "Initiative" -l <listId> --custom-item-id 1
|
|
245
235
|
cu create -n "Task" -l <listId> --time-estimate 2h
|
|
246
236
|
cu create -n "Fix bug" -l <listId> --json
|
|
@@ -256,9 +246,9 @@ cu create -n "Fix bug" -l <listId> --json
|
|
|
256
246
|
| `--priority <level>` | no | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
|
|
257
247
|
| `--due-date <date>` | no | Due date (`YYYY-MM-DD`) |
|
|
258
248
|
| `--time-estimate <duration>` | no | Time estimate (e.g. `"2h"`, `"30m"`, `"1h30m"`) |
|
|
259
|
-
| `--assignee <userId>` | no | Assignee by
|
|
249
|
+
| `--assignee <userId>` | no | Assignee by user ID or `"me"` |
|
|
260
250
|
| `--tags <tags>` | no | Comma-separated tag names |
|
|
261
|
-
| `--custom-item-id <id>` | no | Custom task type ID (for creating initiatives)
|
|
251
|
+
| `--custom-item-id <id>` | no | Custom task type ID (e.g. for creating initiatives) |
|
|
262
252
|
| `--json` | no | Force JSON output even in terminal |
|
|
263
253
|
|
|
264
254
|
### `cu delete <id>`
|
package/dist/index.js
CHANGED
|
@@ -4,72 +4,6 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
|
-
// src/config.ts
|
|
8
|
-
import fs from "fs";
|
|
9
|
-
import { homedir } from "os";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
function configDir() {
|
|
12
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
13
|
-
if (xdg) return join(xdg, "cu");
|
|
14
|
-
return join(homedir(), ".config", "cu");
|
|
15
|
-
}
|
|
16
|
-
function configPath() {
|
|
17
|
-
return join(configDir(), "config.json");
|
|
18
|
-
}
|
|
19
|
-
function loadConfig() {
|
|
20
|
-
const envToken = process.env.CU_API_TOKEN?.trim();
|
|
21
|
-
const envTeamId = process.env.CU_TEAM_ID?.trim();
|
|
22
|
-
let fileToken;
|
|
23
|
-
let fileTeamId;
|
|
24
|
-
const path = configPath();
|
|
25
|
-
if (fs.existsSync(path)) {
|
|
26
|
-
const raw = fs.readFileSync(path, "utf-8");
|
|
27
|
-
let parsed;
|
|
28
|
-
try {
|
|
29
|
-
parsed = JSON.parse(raw);
|
|
30
|
-
} catch {
|
|
31
|
-
throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
|
|
32
|
-
}
|
|
33
|
-
fileToken = parsed.apiToken?.trim();
|
|
34
|
-
fileTeamId = parsed.teamId?.trim();
|
|
35
|
-
}
|
|
36
|
-
const apiToken = envToken || fileToken;
|
|
37
|
-
if (!apiToken) {
|
|
38
|
-
throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cu init");
|
|
39
|
-
}
|
|
40
|
-
if (!apiToken.startsWith("pk_")) {
|
|
41
|
-
throw new Error("Config apiToken must start with pk_. The configured token does not.");
|
|
42
|
-
}
|
|
43
|
-
const teamId = envTeamId || fileTeamId;
|
|
44
|
-
if (!teamId) {
|
|
45
|
-
throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cu init");
|
|
46
|
-
}
|
|
47
|
-
return { apiToken, teamId };
|
|
48
|
-
}
|
|
49
|
-
function loadRawConfig() {
|
|
50
|
-
const path = configPath();
|
|
51
|
-
if (!fs.existsSync(path)) return {};
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
54
|
-
} catch {
|
|
55
|
-
return {};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function getConfigPath() {
|
|
59
|
-
return configPath();
|
|
60
|
-
}
|
|
61
|
-
function writeConfig(config) {
|
|
62
|
-
const dir = configDir();
|
|
63
|
-
if (!fs.existsSync(dir)) {
|
|
64
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
65
|
-
}
|
|
66
|
-
const filePath = join(dir, "config.json");
|
|
67
|
-
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
|
|
68
|
-
encoding: "utf-8",
|
|
69
|
-
mode: 384
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
7
|
// src/api.ts
|
|
74
8
|
var BASE_URL = "https://api.clickup.com/api/v2";
|
|
75
9
|
var MAX_PAGES = 100;
|
|
@@ -190,6 +124,12 @@ var ClickUpClient = class {
|
|
|
190
124
|
const data = await this.request(`/team/${teamId}/space?archived=false`);
|
|
191
125
|
return data.spaces ?? [];
|
|
192
126
|
}
|
|
127
|
+
async getCustomTaskTypes(teamId) {
|
|
128
|
+
const data = await this.request(
|
|
129
|
+
`/team/${teamId}/custom_item`
|
|
130
|
+
);
|
|
131
|
+
return data.custom_items ?? [];
|
|
132
|
+
}
|
|
193
133
|
async getLists(spaceId) {
|
|
194
134
|
const data = await this.request(`/space/${spaceId}/list?archived=false`);
|
|
195
135
|
return data.lists ?? [];
|
|
@@ -255,6 +195,72 @@ var ClickUpClient = class {
|
|
|
255
195
|
}
|
|
256
196
|
};
|
|
257
197
|
|
|
198
|
+
// src/config.ts
|
|
199
|
+
import fs from "fs";
|
|
200
|
+
import { homedir } from "os";
|
|
201
|
+
import { join } from "path";
|
|
202
|
+
function configDir() {
|
|
203
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
204
|
+
if (xdg) return join(xdg, "cu");
|
|
205
|
+
return join(homedir(), ".config", "cu");
|
|
206
|
+
}
|
|
207
|
+
function configPath() {
|
|
208
|
+
return join(configDir(), "config.json");
|
|
209
|
+
}
|
|
210
|
+
function loadConfig() {
|
|
211
|
+
const envToken = process.env.CU_API_TOKEN?.trim();
|
|
212
|
+
const envTeamId = process.env.CU_TEAM_ID?.trim();
|
|
213
|
+
let fileToken;
|
|
214
|
+
let fileTeamId;
|
|
215
|
+
const path = configPath();
|
|
216
|
+
if (fs.existsSync(path)) {
|
|
217
|
+
const raw = fs.readFileSync(path, "utf-8");
|
|
218
|
+
let parsed;
|
|
219
|
+
try {
|
|
220
|
+
parsed = JSON.parse(raw);
|
|
221
|
+
} catch {
|
|
222
|
+
throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
|
|
223
|
+
}
|
|
224
|
+
fileToken = parsed.apiToken?.trim();
|
|
225
|
+
fileTeamId = parsed.teamId?.trim();
|
|
226
|
+
}
|
|
227
|
+
const apiToken = envToken || fileToken;
|
|
228
|
+
if (!apiToken) {
|
|
229
|
+
throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cu init");
|
|
230
|
+
}
|
|
231
|
+
if (!apiToken.startsWith("pk_")) {
|
|
232
|
+
throw new Error("Config apiToken must start with pk_. The configured token does not.");
|
|
233
|
+
}
|
|
234
|
+
const teamId = envTeamId || fileTeamId;
|
|
235
|
+
if (!teamId) {
|
|
236
|
+
throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cu init");
|
|
237
|
+
}
|
|
238
|
+
return { apiToken, teamId };
|
|
239
|
+
}
|
|
240
|
+
function loadRawConfig() {
|
|
241
|
+
const path = configPath();
|
|
242
|
+
if (!fs.existsSync(path)) return {};
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
245
|
+
} catch {
|
|
246
|
+
return {};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function getConfigPath() {
|
|
250
|
+
return configPath();
|
|
251
|
+
}
|
|
252
|
+
function writeConfig(config) {
|
|
253
|
+
const dir = configDir();
|
|
254
|
+
if (!fs.existsSync(dir)) {
|
|
255
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
256
|
+
}
|
|
257
|
+
const filePath = join(dir, "config.json");
|
|
258
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
|
|
259
|
+
encoding: "utf-8",
|
|
260
|
+
mode: 384
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
258
264
|
// src/date.ts
|
|
259
265
|
function formatDate(ms) {
|
|
260
266
|
const d = new Date(Number(ms));
|
|
@@ -372,11 +378,11 @@ function formatDuration(ms) {
|
|
|
372
378
|
}
|
|
373
379
|
function formatTaskDetailMarkdown(task) {
|
|
374
380
|
const lines = [`# ${task.name}`, ""];
|
|
375
|
-
const
|
|
381
|
+
const isInitiative = (task.custom_item_id ?? 0) !== 0;
|
|
376
382
|
const fields = [
|
|
377
383
|
["ID", task.id],
|
|
378
384
|
["Status", task.status.status],
|
|
379
|
-
["Type",
|
|
385
|
+
["Type", isInitiative ? "initiative" : "task"],
|
|
380
386
|
["List", task.list.name],
|
|
381
387
|
["URL", task.url],
|
|
382
388
|
[
|
|
@@ -505,8 +511,8 @@ function formatCustomFieldValue(field) {
|
|
|
505
511
|
}
|
|
506
512
|
function formatTaskDetail(task) {
|
|
507
513
|
const lines = [];
|
|
508
|
-
const
|
|
509
|
-
const typeLabel =
|
|
514
|
+
const isInitiative = (task.custom_item_id ?? 0) !== 0;
|
|
515
|
+
const typeLabel = isInitiative ? "initiative" : "task";
|
|
510
516
|
lines.push(chalk2.bold.underline(task.name));
|
|
511
517
|
lines.push("");
|
|
512
518
|
const fields = [
|
|
@@ -631,19 +637,21 @@ function isDoneStatus(status) {
|
|
|
631
637
|
const lower = status.toLowerCase();
|
|
632
638
|
return DONE_PATTERNS.some((p) => lower.includes(p));
|
|
633
639
|
}
|
|
634
|
-
function isInitiative(task) {
|
|
635
|
-
return (task.custom_item_id ?? 0) !== 0;
|
|
636
|
-
}
|
|
637
640
|
function formatDueDate(ms) {
|
|
638
641
|
if (!ms) return "";
|
|
639
642
|
return formatDate(ms);
|
|
640
643
|
}
|
|
641
|
-
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) {
|
|
642
650
|
return {
|
|
643
651
|
id: task.id,
|
|
644
652
|
name: task.name,
|
|
645
653
|
status: task.status.status,
|
|
646
|
-
task_type:
|
|
654
|
+
task_type: resolveTaskType(task, typeMap ?? /* @__PURE__ */ new Map()),
|
|
647
655
|
priority: task.priority?.priority ?? "none",
|
|
648
656
|
due_date: formatDueDate(task.due_date),
|
|
649
657
|
list: task.list.name,
|
|
@@ -651,16 +659,42 @@ function summarize(task) {
|
|
|
651
659
|
...task.parent ? { parent: task.parent } : {}
|
|
652
660
|
};
|
|
653
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
|
+
}
|
|
654
680
|
async function fetchMyTasks(config, opts = {}) {
|
|
655
681
|
const client = new ClickUpClient(config);
|
|
656
682
|
const { typeFilter, name, ...apiFilters } = opts;
|
|
657
|
-
const allTasks = await
|
|
658
|
-
|
|
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
|
+
}
|
|
659
693
|
if (name) {
|
|
660
694
|
const query = name.toLowerCase();
|
|
661
695
|
filtered = filtered.filter((t) => t.name.toLowerCase().includes(query));
|
|
662
696
|
}
|
|
663
|
-
return filtered.map(summarize);
|
|
697
|
+
return filtered.map((t) => summarize(t, typeMap));
|
|
664
698
|
}
|
|
665
699
|
async function printTasks(tasks, forceJson, config) {
|
|
666
700
|
if (shouldOutputJson(forceJson)) {
|
|
@@ -720,9 +754,16 @@ function parseDueDate(value) {
|
|
|
720
754
|
}
|
|
721
755
|
function parseAssigneeId(value) {
|
|
722
756
|
const id = Number(value);
|
|
723
|
-
if (!Number.isInteger(id)) throw new Error(
|
|
757
|
+
if (!Number.isInteger(id)) throw new Error('Assignee must be a numeric user ID or "me"');
|
|
724
758
|
return id;
|
|
725
759
|
}
|
|
760
|
+
async function resolveAssigneeId(client, value) {
|
|
761
|
+
if (value === "me") {
|
|
762
|
+
const user = await client.getMe();
|
|
763
|
+
return user.id;
|
|
764
|
+
}
|
|
765
|
+
return parseAssigneeId(value);
|
|
766
|
+
}
|
|
726
767
|
function parseTimeEstimate(value) {
|
|
727
768
|
const pattern = /^(?:(\d+)h)?(?:(\d+)m)?$/i;
|
|
728
769
|
const match = value.match(pattern);
|
|
@@ -920,10 +961,12 @@ function findRelatedSpaces(mySpaceIds, allSpaces) {
|
|
|
920
961
|
async function runSprintCommand(config, opts) {
|
|
921
962
|
const client = new ClickUpClient(config);
|
|
922
963
|
process.stderr.write("Detecting active sprint...\n");
|
|
923
|
-
const [myTasks, allSpaces] = await Promise.all([
|
|
964
|
+
const [myTasks, allSpaces, customTypes] = await Promise.all([
|
|
924
965
|
client.getMyTasks(config.teamId),
|
|
925
|
-
client.getSpaces(config.teamId)
|
|
966
|
+
client.getSpaces(config.teamId),
|
|
967
|
+
client.getCustomTaskTypes(config.teamId)
|
|
926
968
|
]);
|
|
969
|
+
const typeMap = buildTypeMap(customTypes);
|
|
927
970
|
let spaces;
|
|
928
971
|
if (opts.space) {
|
|
929
972
|
spaces = allSpaces.filter(
|
|
@@ -966,7 +1009,7 @@ async function runSprintCommand(config, opts) {
|
|
|
966
1009
|
sprintTasks = sprintTasks.filter((t) => !isDoneStatus(t.status.status));
|
|
967
1010
|
}
|
|
968
1011
|
const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
|
|
969
|
-
const summaries = filtered.map(summarize);
|
|
1012
|
+
const summaries = filtered.map((t) => summarize(t, typeMap));
|
|
970
1013
|
await printTasks(summaries, opts.json ?? false, config);
|
|
971
1014
|
}
|
|
972
1015
|
|
|
@@ -1059,13 +1102,17 @@ async function listSprints(config, opts = {}) {
|
|
|
1059
1102
|
// src/commands/subtasks.ts
|
|
1060
1103
|
async function fetchSubtasks(config, taskId, options = {}) {
|
|
1061
1104
|
const client = new ClickUpClient(config);
|
|
1062
|
-
const parent = await
|
|
1105
|
+
const [parent, customTypes] = await Promise.all([
|
|
1106
|
+
client.getTask(taskId),
|
|
1107
|
+
client.getCustomTaskTypes(config.teamId)
|
|
1108
|
+
]);
|
|
1109
|
+
const typeMap = buildTypeMap(customTypes);
|
|
1063
1110
|
const tasks = await client.getTasksFromList(
|
|
1064
1111
|
parent.list.id,
|
|
1065
1112
|
{ parent: taskId, subtasks: "false" },
|
|
1066
1113
|
{ includeClosed: options.includeClosed }
|
|
1067
1114
|
);
|
|
1068
|
-
return tasks.map(summarize);
|
|
1115
|
+
return tasks.map((t) => summarize(t, typeMap));
|
|
1069
1116
|
}
|
|
1070
1117
|
|
|
1071
1118
|
// src/commands/comment.ts
|
|
@@ -1171,9 +1218,9 @@ var TIME_PERIODS = [
|
|
|
1171
1218
|
{ key: "last_month", label: "Last month" },
|
|
1172
1219
|
{ key: "older", label: "Older" }
|
|
1173
1220
|
];
|
|
1174
|
-
function summarizeWithDate(task) {
|
|
1221
|
+
function summarizeWithDate(task, typeMap) {
|
|
1175
1222
|
return {
|
|
1176
|
-
...summarize(task),
|
|
1223
|
+
...summarize(task, typeMap),
|
|
1177
1224
|
date_updated: task.date_updated ?? "0"
|
|
1178
1225
|
};
|
|
1179
1226
|
}
|
|
@@ -1210,12 +1257,13 @@ function groupTasks(tasks, now) {
|
|
|
1210
1257
|
}
|
|
1211
1258
|
async function fetchInbox(config, days = 30, opts = {}) {
|
|
1212
1259
|
const client = new ClickUpClient(config);
|
|
1213
|
-
const tasks = await
|
|
1214
|
-
subtasks: true,
|
|
1215
|
-
|
|
1216
|
-
|
|
1260
|
+
const [tasks, customTypes] = await Promise.all([
|
|
1261
|
+
client.getMyTasks(config.teamId, { subtasks: true, includeClosed: opts.includeClosed }),
|
|
1262
|
+
client.getCustomTaskTypes(config.teamId)
|
|
1263
|
+
]);
|
|
1264
|
+
const typeMap = buildTypeMap(customTypes);
|
|
1217
1265
|
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
1218
|
-
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);
|
|
1266
|
+
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));
|
|
1219
1267
|
}
|
|
1220
1268
|
async function printInbox(tasks, forceJson, config) {
|
|
1221
1269
|
const now = Date.now();
|
|
@@ -1322,9 +1370,11 @@ function groupByStatus(tasks, includeClosed) {
|
|
|
1322
1370
|
}
|
|
1323
1371
|
async function runAssignedCommand(config, opts) {
|
|
1324
1372
|
const client = new ClickUpClient(config);
|
|
1325
|
-
const allTasks = await
|
|
1326
|
-
includeClosed: opts.includeClosed
|
|
1327
|
-
|
|
1373
|
+
const [allTasks, customTypes] = await Promise.all([
|
|
1374
|
+
client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed }),
|
|
1375
|
+
client.getCustomTaskTypes(config.teamId)
|
|
1376
|
+
]);
|
|
1377
|
+
const typeMap = buildTypeMap(customTypes);
|
|
1328
1378
|
let groups = groupByStatus(allTasks, opts.includeClosed ?? false);
|
|
1329
1379
|
if (opts.status) {
|
|
1330
1380
|
const lower = opts.status.toLowerCase();
|
|
@@ -1333,7 +1383,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1333
1383
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
1334
1384
|
const result = {};
|
|
1335
1385
|
for (const group of groups) {
|
|
1336
|
-
result[group.status.toLowerCase()] = group.tasks.map(summarize);
|
|
1386
|
+
result[group.status.toLowerCase()] = group.tasks.map((t) => summarize(t, typeMap));
|
|
1337
1387
|
}
|
|
1338
1388
|
console.log(JSON.stringify(result, null, 2));
|
|
1339
1389
|
return;
|
|
@@ -1341,7 +1391,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1341
1391
|
if (!isTTY()) {
|
|
1342
1392
|
const mdGroups = groups.map((g) => ({
|
|
1343
1393
|
label: g.status,
|
|
1344
|
-
tasks: g.tasks.map((t) => summarize(t))
|
|
1394
|
+
tasks: g.tasks.map((t) => summarize(t, typeMap))
|
|
1345
1395
|
}));
|
|
1346
1396
|
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1347
1397
|
return;
|
|
@@ -1352,7 +1402,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1352
1402
|
}
|
|
1353
1403
|
const pickerGroups = groups.map((g) => ({
|
|
1354
1404
|
label: g.status.toUpperCase(),
|
|
1355
|
-
tasks: g.tasks.map(summarize)
|
|
1405
|
+
tasks: g.tasks.map((t) => summarize(t, typeMap))
|
|
1356
1406
|
}));
|
|
1357
1407
|
const selected = await groupedTaskPicker(pickerGroups);
|
|
1358
1408
|
await showDetailsAndOpen(selected, (id) => client.getTask(id));
|
|
@@ -1435,7 +1485,7 @@ function isOverdue(task, now) {
|
|
|
1435
1485
|
const due = Number(task.due_date);
|
|
1436
1486
|
return !isNaN(due) && due < now;
|
|
1437
1487
|
}
|
|
1438
|
-
function categorizeTasks(tasks, hoursBack) {
|
|
1488
|
+
function categorizeTasks(tasks, hoursBack, typeMap) {
|
|
1439
1489
|
const now = Date.now();
|
|
1440
1490
|
const cutoff = now - hoursBack * 60 * 60 * 1e3;
|
|
1441
1491
|
const completed = [];
|
|
@@ -1444,13 +1494,13 @@ function categorizeTasks(tasks, hoursBack) {
|
|
|
1444
1494
|
for (const task of tasks) {
|
|
1445
1495
|
const done = isDoneStatus(task.status.status);
|
|
1446
1496
|
if (done && isCompletedRecently(task, cutoff)) {
|
|
1447
|
-
completed.push(summarize(task));
|
|
1497
|
+
completed.push(summarize(task, typeMap));
|
|
1448
1498
|
}
|
|
1449
1499
|
if (!done && isInProgress(task)) {
|
|
1450
|
-
inProgress.push(summarize(task));
|
|
1500
|
+
inProgress.push(summarize(task, typeMap));
|
|
1451
1501
|
}
|
|
1452
1502
|
if (!done && isOverdue(task, now)) {
|
|
1453
|
-
overdue.push(summarize(task));
|
|
1503
|
+
overdue.push(summarize(task, typeMap));
|
|
1454
1504
|
}
|
|
1455
1505
|
}
|
|
1456
1506
|
return { completed, inProgress, overdue };
|
|
@@ -1466,8 +1516,12 @@ ${label} (${tasks.length})`);
|
|
|
1466
1516
|
}
|
|
1467
1517
|
async function runSummaryCommand(config, opts) {
|
|
1468
1518
|
const client = new ClickUpClient(config);
|
|
1469
|
-
const allTasks = await
|
|
1470
|
-
|
|
1519
|
+
const [allTasks, customTypes] = await Promise.all([
|
|
1520
|
+
client.getMyTasks(config.teamId, { includeClosed: true }),
|
|
1521
|
+
client.getCustomTaskTypes(config.teamId)
|
|
1522
|
+
]);
|
|
1523
|
+
const typeMap = buildTypeMap(customTypes);
|
|
1524
|
+
const result = categorizeTasks(allTasks, opts.hours, typeMap);
|
|
1471
1525
|
if (shouldOutputJson(opts.json)) {
|
|
1472
1526
|
console.log(JSON.stringify(result, null, 2));
|
|
1473
1527
|
return;
|
|
@@ -1493,9 +1547,13 @@ function isOverdue2(task, now) {
|
|
|
1493
1547
|
}
|
|
1494
1548
|
async function fetchOverdueTasks(config, opts = {}) {
|
|
1495
1549
|
const client = new ClickUpClient(config);
|
|
1496
|
-
const allTasks = await
|
|
1550
|
+
const [allTasks, customTypes] = await Promise.all([
|
|
1551
|
+
client.getMyTasks(config.teamId, { includeClosed: opts.includeClosed }),
|
|
1552
|
+
client.getCustomTaskTypes(config.teamId)
|
|
1553
|
+
]);
|
|
1554
|
+
const typeMap = buildTypeMap(customTypes);
|
|
1497
1555
|
const now = Date.now();
|
|
1498
|
-
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);
|
|
1556
|
+
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));
|
|
1499
1557
|
}
|
|
1500
1558
|
|
|
1501
1559
|
// src/commands/config.ts
|
|
@@ -1532,13 +1590,6 @@ function configPath2() {
|
|
|
1532
1590
|
}
|
|
1533
1591
|
|
|
1534
1592
|
// src/commands/assign.ts
|
|
1535
|
-
async function resolveUserId(client, value) {
|
|
1536
|
-
if (value === "me") {
|
|
1537
|
-
const user = await client.getMe();
|
|
1538
|
-
return user.id;
|
|
1539
|
-
}
|
|
1540
|
-
return parseAssigneeId(value);
|
|
1541
|
-
}
|
|
1542
1593
|
async function assignTask(config, taskId, opts) {
|
|
1543
1594
|
if (!opts.to && !opts.remove) {
|
|
1544
1595
|
throw new Error("Provide at least one of: --to, --remove");
|
|
@@ -1547,10 +1598,10 @@ async function assignTask(config, taskId, opts) {
|
|
|
1547
1598
|
const add = [];
|
|
1548
1599
|
const rem = [];
|
|
1549
1600
|
if (opts.to) {
|
|
1550
|
-
add.push(await
|
|
1601
|
+
add.push(await resolveAssigneeId(client, opts.to));
|
|
1551
1602
|
}
|
|
1552
1603
|
if (opts.remove) {
|
|
1553
|
-
rem.push(await
|
|
1604
|
+
rem.push(await resolveAssigneeId(client, opts.remove));
|
|
1554
1605
|
}
|
|
1555
1606
|
return client.updateTask(taskId, {
|
|
1556
1607
|
assignees: {
|
|
@@ -1633,7 +1684,7 @@ function bashCompletion() {
|
|
|
1633
1684
|
cword=$COMP_CWORD
|
|
1634
1685
|
fi
|
|
1635
1686
|
|
|
1636
|
-
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"
|
|
1637
1688
|
|
|
1638
1689
|
if [[ $cword -eq 1 ]]; then
|
|
1639
1690
|
COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
|
|
@@ -1654,8 +1705,8 @@ function bashCompletion() {
|
|
|
1654
1705
|
esac
|
|
1655
1706
|
|
|
1656
1707
|
case "$cmd" in
|
|
1657
|
-
tasks
|
|
1658
|
-
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"))
|
|
1659
1710
|
;;
|
|
1660
1711
|
task)
|
|
1661
1712
|
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
@@ -1758,7 +1809,6 @@ _cu() {
|
|
|
1758
1809
|
'init:Set up cu for the first time'
|
|
1759
1810
|
'auth:Validate API token and show current user'
|
|
1760
1811
|
'tasks:List tasks assigned to me'
|
|
1761
|
-
'initiatives:List initiatives assigned to me'
|
|
1762
1812
|
'task:Get task details'
|
|
1763
1813
|
'update:Update a task'
|
|
1764
1814
|
'create:Create a new task'
|
|
@@ -1798,12 +1848,13 @@ _cu() {
|
|
|
1798
1848
|
;;
|
|
1799
1849
|
args)
|
|
1800
1850
|
case $words[1] in
|
|
1801
|
-
tasks
|
|
1851
|
+
tasks)
|
|
1802
1852
|
_arguments \\
|
|
1803
1853
|
'--status[Filter by status]:status:(open "in progress" "in review" done closed)' \\
|
|
1804
1854
|
'--list[Filter by list ID]:list_id:' \\
|
|
1805
1855
|
'--space[Filter by space ID]:space_id:' \\
|
|
1806
1856
|
'--name[Filter by name]:query:' \\
|
|
1857
|
+
'--type[Filter by task type]:type:' \\
|
|
1807
1858
|
'--include-closed[Include done/closed tasks]' \\
|
|
1808
1859
|
'--json[Force JSON output]'
|
|
1809
1860
|
;;
|
|
@@ -2011,7 +2062,6 @@ complete -c cu -n __fish_use_subcommand -s V -l version -d 'Show version'
|
|
|
2011
2062
|
complete -c cu -n __fish_use_subcommand -a init -d 'Set up cu for the first time'
|
|
2012
2063
|
complete -c cu -n __fish_use_subcommand -a auth -d 'Validate API token and show current user'
|
|
2013
2064
|
complete -c cu -n __fish_use_subcommand -a tasks -d 'List tasks assigned to me'
|
|
2014
|
-
complete -c cu -n __fish_use_subcommand -a initiatives -d 'List initiatives assigned to me'
|
|
2015
2065
|
complete -c cu -n __fish_use_subcommand -a task -d 'Get task details'
|
|
2016
2066
|
complete -c cu -n __fish_use_subcommand -a update -d 'Update a task'
|
|
2017
2067
|
complete -c cu -n __fish_use_subcommand -a create -d 'Create a new task'
|
|
@@ -2040,12 +2090,13 @@ complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completio
|
|
|
2040
2090
|
|
|
2041
2091
|
complete -c cu -n '__fish_seen_subcommand_from auth' -l json -d 'Force JSON output'
|
|
2042
2092
|
|
|
2043
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
2044
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
2045
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
2046
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
2047
|
-
complete -c cu -n '__fish_seen_subcommand_from tasks
|
|
2048
|
-
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'
|
|
2049
2100
|
|
|
2050
2101
|
complete -c cu -n '__fish_seen_subcommand_from task' -l json -d 'Force JSON output'
|
|
2051
2102
|
|
|
@@ -2183,9 +2234,11 @@ async function searchTasks(config, query, opts = {}) {
|
|
|
2183
2234
|
throw new Error("Search query cannot be empty");
|
|
2184
2235
|
}
|
|
2185
2236
|
const client = new ClickUpClient(config);
|
|
2186
|
-
const allTasks = await
|
|
2187
|
-
includeClosed: opts.includeClosed
|
|
2188
|
-
|
|
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);
|
|
2189
2242
|
const words = trimmed.toLowerCase().split(/\s+/);
|
|
2190
2243
|
let matched = allTasks.filter((task) => {
|
|
2191
2244
|
const name = task.name.toLowerCase();
|
|
@@ -2204,7 +2257,7 @@ async function searchTasks(config, query, opts = {}) {
|
|
|
2204
2257
|
matched = matched.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase());
|
|
2205
2258
|
}
|
|
2206
2259
|
}
|
|
2207
|
-
return matched.map(summarize);
|
|
2260
|
+
return matched.map((t) => summarize(t, typeMap));
|
|
2208
2261
|
}
|
|
2209
2262
|
|
|
2210
2263
|
// src/commands/depend.ts
|
|
@@ -2426,25 +2479,14 @@ program.command("auth").description("Validate API token and show current user").
|
|
|
2426
2479
|
}
|
|
2427
2480
|
})
|
|
2428
2481
|
);
|
|
2429
|
-
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(
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
typeFilter: "task",
|
|
2434
|
-
statuses: opts.status ? [opts.status] : void 0,
|
|
2435
|
-
listIds: opts.list ? [opts.list] : void 0,
|
|
2436
|
-
spaceIds: opts.space ? [opts.space] : void 0,
|
|
2437
|
-
name: opts.name,
|
|
2438
|
-
includeClosed: opts.includeClosed
|
|
2439
|
-
});
|
|
2440
|
-
await printTasks(tasks, opts.json ?? false, config);
|
|
2441
|
-
})
|
|
2442
|
-
);
|
|
2443
|
-
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(
|
|
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(
|
|
2444
2486
|
wrapAction(async (opts) => {
|
|
2445
2487
|
const config = loadConfig();
|
|
2446
2488
|
const tasks = await fetchMyTasks(config, {
|
|
2447
|
-
typeFilter:
|
|
2489
|
+
typeFilter: opts.type,
|
|
2448
2490
|
statuses: opts.status ? [opts.status] : void 0,
|
|
2449
2491
|
listIds: opts.list ? [opts.list] : void 0,
|
|
2450
2492
|
spaceIds: opts.space ? [opts.space] : void 0,
|
|
@@ -2467,9 +2509,13 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
2467
2509
|
}
|
|
2468
2510
|
})
|
|
2469
2511
|
);
|
|
2470
|
-
program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>",
|
|
2512
|
+
program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--json", "Force JSON output even in terminal").action(
|
|
2471
2513
|
wrapAction(async (taskId, opts) => {
|
|
2472
2514
|
const config = loadConfig();
|
|
2515
|
+
if (opts.assignee === "me") {
|
|
2516
|
+
const client = new ClickUpClient(config);
|
|
2517
|
+
opts.assignee = String(await resolveAssigneeId(client, "me"));
|
|
2518
|
+
}
|
|
2473
2519
|
const payload = buildUpdatePayload(opts);
|
|
2474
2520
|
const result = await updateTask(config, taskId, payload);
|
|
2475
2521
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
@@ -2479,9 +2525,13 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
|
|
|
2479
2525
|
}
|
|
2480
2526
|
})
|
|
2481
2527
|
);
|
|
2482
|
-
program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>",
|
|
2528
|
+
program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--json", "Force JSON output even in terminal").action(
|
|
2483
2529
|
wrapAction(async (opts) => {
|
|
2484
2530
|
const config = loadConfig();
|
|
2531
|
+
if (opts.assignee === "me") {
|
|
2532
|
+
const client = new ClickUpClient(config);
|
|
2533
|
+
opts.assignee = String(await resolveAssigneeId(client, "me"));
|
|
2534
|
+
}
|
|
2485
2535
|
const result = await createTask(config, opts);
|
|
2486
2536
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
2487
2537
|
console.log(JSON.stringify(result, null, 2));
|
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,75 +34,74 @@ 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
|
|
|
59
|
-
| Command
|
|
60
|
-
|
|
|
61
|
-
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id] [--parent id] [--json]` | Update task fields (desc supports markdown) |
|
|
62
|
-
| `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id] [--tags t] [--custom-item-id n] [--json]` | Create task (desc supports markdown) |
|
|
63
|
-
| `cu comment <id> -m text [--json]`
|
|
64
|
-
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]`
|
|
65
|
-
| `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]`
|
|
66
|
-
| `cu move <id> [--to listId] [--remove listId] [--json]`
|
|
67
|
-
| `cu field <id> [--set "Name" value] [--remove "Name"] [--json]`
|
|
68
|
-
| `cu delete <id> [--confirm] [--json]`
|
|
69
|
-
| `cu tag <id> [--add tags] [--remove tags] [--json]`
|
|
70
|
-
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path`
|
|
71
|
-
| `cu completion <shell>`
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
60
|
+
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--parent id] [--json]` | Update task fields (desc supports markdown) |
|
|
61
|
+
| `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--json]` | Create task (desc supports markdown) |
|
|
62
|
+
| `cu comment <id> -m text [--json]` | Post comment on task |
|
|
63
|
+
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
|
|
64
|
+
| `cu depend <id> [--on taskId] [--blocks taskId] [--remove] [--json]` | Add/remove task dependencies |
|
|
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 |
|
|
69
|
+
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
70
|
+
| `cu completion <shell>` | Shell completions (bash/zsh/fish) |
|
|
72
71
|
|
|
73
72
|
## Quick Reference
|
|
74
73
|
|
|
75
|
-
| Topic | Detail
|
|
76
|
-
| ----------------------- |
|
|
77
|
-
| Task IDs | Stable alphanumeric strings (e.g. `abc123def`)
|
|
78
|
-
|
|
|
79
|
-
| `--list` on create | Optional when `--parent` is given (auto-detected)
|
|
80
|
-
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr.
|
|
81
|
-
| `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4)
|
|
82
|
-
| `--due-date` | `YYYY-MM-DD` format
|
|
83
|
-
| `--assignee` |
|
|
84
|
-
| `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`)
|
|
85
|
-
| `--time-estimate` | Duration format: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds
|
|
86
|
-
| `--custom-item-id` | Custom task type ID (e.g. `1` for initiative)
|
|
87
|
-
| `--on` / `--blocks` | Task dependency direction (used with `cu depend`)
|
|
88
|
-
| `--to` / `--remove` | List ID to add/remove task (used with `cu move`)
|
|
89
|
-
| `cu field --set` | Supports: text, number, checkbox (true/false), dropdown (option name), date (YYYY-MM-DD), url, email
|
|
90
|
-
| `cu field` | Field names resolved case-insensitively; errors list available fields/options
|
|
91
|
-
| `cu delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone
|
|
92
|
-
| `cu tag --add/--remove` | Comma-separated tag names (e.g. `--add "bug,frontend"`)
|
|
93
|
-
| `--space` | Partial name match or exact ID
|
|
94
|
-
| `--name` | Partial match, case-insensitive
|
|
95
|
-
| `--include-closed` | Include closed/done tasks (on `tasks`, `
|
|
96
|
-
| `cu assign --to me` | Shorthand for your own user ID
|
|
97
|
-
| `cu search` | Matches all query words against task name, case-insensitive
|
|
98
|
-
| `cu sprint` | Auto-detects active sprint via view API and date range parsing
|
|
99
|
-
| `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue
|
|
100
|
-
| `cu overdue` | Excludes closed tasks, sorted most overdue first
|
|
101
|
-
| `cu open` | Tries task ID first, falls back to name search
|
|
102
|
-
| `cu task` | Shows custom fields in detail view
|
|
103
|
-
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l`
|
|
104
|
-
| Errors | stderr with exit code 1
|
|
105
|
-
| 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` | User ID or `me` (on `cu create`, `cu update`, `cu assign`) |
|
|
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 |
|
|
106
105
|
|
|
107
106
|
## Agent Workflow Examples
|
|
108
107
|
|
|
@@ -121,6 +120,8 @@ cu activity abc123def # task + comments combined
|
|
|
121
120
|
```bash
|
|
122
121
|
cu tasks --status "in progress" # by status
|
|
123
122
|
cu tasks --name "login" # by partial name
|
|
123
|
+
cu tasks --type initiative # initiatives only
|
|
124
|
+
cu tasks --type task # regular tasks only
|
|
124
125
|
cu search "payment flow" # multi-word search
|
|
125
126
|
cu search auth --status "prog" # fuzzy status match
|
|
126
127
|
cu sprint # current sprint
|