@krodak/clickup-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -9
- package/dist/index.js +205 -37
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +87 -94
package/README.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# cu - ClickUp CLI
|
|
2
2
|
|
|
3
|
-
A ClickUp CLI built for AI agents that also works well for humans. Outputs
|
|
3
|
+
> A ClickUp CLI built for AI agents that also works well for humans. Outputs Markdown when piped (optimized for AI context windows), interactive tables when run in a terminal.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@krodak/clickup-cli)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://github.com/krodak/clickup-cli/actions/workflows/ci.yml)
|
|
6
9
|
|
|
7
10
|
```bash
|
|
8
11
|
npm install -g @krodak/clickup-cli # or: brew tap krodak/tap && brew install clickup-cli
|
|
9
12
|
cu init # walks you through API token + workspace setup
|
|
10
13
|
```
|
|
11
14
|
|
|
12
|
-
You need
|
|
15
|
+
You need a ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps).
|
|
13
16
|
|
|
14
17
|
## Using with AI agents
|
|
15
18
|
|
|
@@ -89,9 +92,9 @@ cu update abc123 -s "done" # update status
|
|
|
89
92
|
cu assign abc123 --to me # assign yourself
|
|
90
93
|
```
|
|
91
94
|
|
|
92
|
-
Pass `--json` to any command to
|
|
95
|
+
Pass `--json` to any read command to force JSON output instead of the default format.
|
|
93
96
|
|
|
94
|
-
When output is piped (no TTY), all commands
|
|
97
|
+
When output is piped (no TTY), all commands output **Markdown** by default - optimized for AI agent context windows. Pass `--json` to any command for JSON output. Set `CU_OUTPUT=json` environment variable to always get JSON when piped.
|
|
95
98
|
|
|
96
99
|
## Commands
|
|
97
100
|
|
|
@@ -179,6 +182,8 @@ cu task abc123
|
|
|
179
182
|
cu task abc123 --json
|
|
180
183
|
```
|
|
181
184
|
|
|
185
|
+
**Note:** When piped, `cu task` outputs a structured Markdown summary of the task. For the full raw API response with all fields (custom fields, checklists, etc.), use `--json`.
|
|
186
|
+
|
|
182
187
|
### `cu subtasks <id>`
|
|
183
188
|
|
|
184
189
|
List subtasks of a task or initiative.
|
|
@@ -410,10 +415,11 @@ cu completion fish > ~/.config/fish/completions/cu.fish # Fish
|
|
|
410
415
|
|
|
411
416
|
Environment variables override config file values:
|
|
412
417
|
|
|
413
|
-
| Variable | Description
|
|
414
|
-
| -------------- |
|
|
415
|
-
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`)
|
|
416
|
-
| `CU_TEAM_ID` | Workspace (team) ID
|
|
418
|
+
| Variable | Description |
|
|
419
|
+
| -------------- | ----------------------------------------------------------------- |
|
|
420
|
+
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
421
|
+
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
422
|
+
| `CU_OUTPUT` | Set to `json` to force JSON output when piped (default: markdown) |
|
|
417
423
|
|
|
418
424
|
When both are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
419
425
|
|
package/dist/index.js
CHANGED
|
@@ -227,6 +227,11 @@ import chalk from "chalk";
|
|
|
227
227
|
function isTTY() {
|
|
228
228
|
return Boolean(process.stdout.isTTY);
|
|
229
229
|
}
|
|
230
|
+
function shouldOutputJson(forceJson) {
|
|
231
|
+
if (forceJson) return true;
|
|
232
|
+
if (process.env["CU_OUTPUT"] === "json") return true;
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
230
235
|
function cell(value, width) {
|
|
231
236
|
if (value.length > width) return value.slice(0, width - 1) + "\u2026";
|
|
232
237
|
return value.padEnd(width);
|
|
@@ -259,6 +264,128 @@ var TASK_COLUMNS = [
|
|
|
259
264
|
{ key: "list", label: "LIST" }
|
|
260
265
|
];
|
|
261
266
|
|
|
267
|
+
// src/markdown.ts
|
|
268
|
+
function escapeCell(value) {
|
|
269
|
+
return value.replace(/\|/g, "\\|");
|
|
270
|
+
}
|
|
271
|
+
function formatMarkdownTable(rows, columns) {
|
|
272
|
+
const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
|
|
273
|
+
const divider = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
274
|
+
const lines = [header, divider];
|
|
275
|
+
for (const row of rows) {
|
|
276
|
+
const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
|
|
277
|
+
lines.push("| " + cells.join(" | ") + " |");
|
|
278
|
+
}
|
|
279
|
+
return lines.join("\n");
|
|
280
|
+
}
|
|
281
|
+
var TASK_MD_COLUMNS = [
|
|
282
|
+
{ key: "id", label: "ID" },
|
|
283
|
+
{ key: "name", label: "Name" },
|
|
284
|
+
{ key: "status", label: "Status" },
|
|
285
|
+
{ key: "list", label: "List" }
|
|
286
|
+
];
|
|
287
|
+
function formatTasksMarkdown(tasks) {
|
|
288
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
289
|
+
return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
|
|
290
|
+
}
|
|
291
|
+
function formatCommentsMarkdown(comments) {
|
|
292
|
+
if (comments.length === 0) return "No comments found.";
|
|
293
|
+
return comments.map((c) => `**${c.user}** (${c.date})
|
|
294
|
+
|
|
295
|
+
${c.text}`).join("\n\n---\n\n");
|
|
296
|
+
}
|
|
297
|
+
var LIST_MD_COLUMNS = [
|
|
298
|
+
{ key: "id", label: "ID" },
|
|
299
|
+
{ key: "name", label: "Name" },
|
|
300
|
+
{ key: "folder", label: "Folder" }
|
|
301
|
+
];
|
|
302
|
+
function formatListsMarkdown(lists) {
|
|
303
|
+
if (lists.length === 0) return "No lists found.";
|
|
304
|
+
return formatMarkdownTable(lists, LIST_MD_COLUMNS);
|
|
305
|
+
}
|
|
306
|
+
var SPACE_MD_COLUMNS = [
|
|
307
|
+
{ key: "id", label: "ID" },
|
|
308
|
+
{ key: "name", label: "Name" }
|
|
309
|
+
];
|
|
310
|
+
function formatSpacesMarkdown(spaces) {
|
|
311
|
+
if (spaces.length === 0) return "No spaces found.";
|
|
312
|
+
return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
|
|
313
|
+
}
|
|
314
|
+
function formatGroupedTasksMarkdown(groups) {
|
|
315
|
+
const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
|
|
316
|
+
|
|
317
|
+
${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
|
|
318
|
+
if (sections.length === 0) return "No tasks found.";
|
|
319
|
+
return sections.join("\n\n");
|
|
320
|
+
}
|
|
321
|
+
function formatDate(ms) {
|
|
322
|
+
const d = new Date(Number(ms));
|
|
323
|
+
const year = d.getUTCFullYear();
|
|
324
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
325
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
326
|
+
return `${year}-${month}-${day}`;
|
|
327
|
+
}
|
|
328
|
+
function formatDuration(ms) {
|
|
329
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
330
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
331
|
+
const minutes = totalMinutes % 60;
|
|
332
|
+
return `${hours}h ${minutes}m`;
|
|
333
|
+
}
|
|
334
|
+
function formatTaskDetailMarkdown(task) {
|
|
335
|
+
const lines = [`# ${task.name}`, ""];
|
|
336
|
+
const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
|
|
337
|
+
const fields = [
|
|
338
|
+
["ID", task.id],
|
|
339
|
+
["Status", task.status.status],
|
|
340
|
+
["Type", isInitiative2 ? "initiative" : "task"],
|
|
341
|
+
["List", task.list.name],
|
|
342
|
+
["URL", task.url],
|
|
343
|
+
[
|
|
344
|
+
"Assignees",
|
|
345
|
+
task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
346
|
+
],
|
|
347
|
+
["Priority", task.priority?.priority],
|
|
348
|
+
["Parent", task.parent ?? void 0],
|
|
349
|
+
["Start Date", task.start_date ? formatDate(task.start_date) : void 0],
|
|
350
|
+
["Due Date", task.due_date ? formatDate(task.due_date) : void 0],
|
|
351
|
+
[
|
|
352
|
+
"Time Estimate",
|
|
353
|
+
task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
|
|
354
|
+
],
|
|
355
|
+
[
|
|
356
|
+
"Time Spent",
|
|
357
|
+
task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
|
|
358
|
+
],
|
|
359
|
+
["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
360
|
+
["Created", task.date_created ? formatDate(task.date_created) : void 0],
|
|
361
|
+
["Updated", task.date_updated ? formatDate(task.date_updated) : void 0]
|
|
362
|
+
];
|
|
363
|
+
for (const [label, value] of fields) {
|
|
364
|
+
if (value != null && value !== "") {
|
|
365
|
+
lines.push(`**${label}:** ${value}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (task.description) {
|
|
369
|
+
lines.push("", "## Description", "", task.description);
|
|
370
|
+
}
|
|
371
|
+
return lines.join("\n");
|
|
372
|
+
}
|
|
373
|
+
function formatUpdateConfirmation(id, name) {
|
|
374
|
+
return `Updated task ${id}: "${name}"`;
|
|
375
|
+
}
|
|
376
|
+
function formatCreateConfirmation(id, name, url) {
|
|
377
|
+
return `Created task ${id}: "${name}" - ${url}`;
|
|
378
|
+
}
|
|
379
|
+
function formatCommentConfirmation(id) {
|
|
380
|
+
return `Comment posted (id: ${id})`;
|
|
381
|
+
}
|
|
382
|
+
function formatAssignConfirmation(taskId, opts) {
|
|
383
|
+
const parts = [];
|
|
384
|
+
if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
|
|
385
|
+
if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
|
|
386
|
+
return parts.join("; ");
|
|
387
|
+
}
|
|
388
|
+
|
|
262
389
|
// src/interactive.ts
|
|
263
390
|
import { execFileSync } from "child_process";
|
|
264
391
|
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
@@ -490,10 +617,14 @@ async function fetchMyTasks(config, opts = {}) {
|
|
|
490
617
|
return filtered.map(summarize);
|
|
491
618
|
}
|
|
492
619
|
async function printTasks(tasks, forceJson, config) {
|
|
493
|
-
if (forceJson
|
|
620
|
+
if (shouldOutputJson(forceJson)) {
|
|
494
621
|
console.log(JSON.stringify(tasks, null, 2));
|
|
495
622
|
return;
|
|
496
623
|
}
|
|
624
|
+
if (!isTTY()) {
|
|
625
|
+
console.log(formatTasksMarkdown(tasks));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
497
628
|
if (tasks.length === 0) {
|
|
498
629
|
console.log("No tasks found.");
|
|
499
630
|
return;
|
|
@@ -771,7 +902,7 @@ var SPRINT_COLUMNS = [
|
|
|
771
902
|
{ key: "sprint", label: "SPRINT", maxWidth: 60 },
|
|
772
903
|
{ key: "dates", label: "DATES" }
|
|
773
904
|
];
|
|
774
|
-
function
|
|
905
|
+
function formatDate2(d) {
|
|
775
906
|
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
776
907
|
}
|
|
777
908
|
function buildSprintInfos(lists, folderName, today) {
|
|
@@ -832,7 +963,7 @@ async function listSprints(config, opts = {}) {
|
|
|
832
963
|
}
|
|
833
964
|
const rows = allSprints.map((s) => {
|
|
834
965
|
const dates = parseSprintDates(s.name);
|
|
835
|
-
const dateStr = dates ? `${
|
|
966
|
+
const dateStr = dates ? `${formatDate2(dates.start)} - ${formatDate2(dates.end)}` : "";
|
|
836
967
|
return {
|
|
837
968
|
id: s.id,
|
|
838
969
|
sprint: s.active ? `* ${s.name}` : s.name,
|
|
@@ -859,7 +990,7 @@ async function postComment(config, taskId, text) {
|
|
|
859
990
|
|
|
860
991
|
// src/commands/comments.ts
|
|
861
992
|
import chalk3 from "chalk";
|
|
862
|
-
function
|
|
993
|
+
function formatDate3(timestamp) {
|
|
863
994
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
864
995
|
month: "short",
|
|
865
996
|
day: "numeric",
|
|
@@ -879,10 +1010,14 @@ async function fetchComments(config, taskId) {
|
|
|
879
1010
|
}));
|
|
880
1011
|
}
|
|
881
1012
|
function printComments(comments, forceJson) {
|
|
882
|
-
if (forceJson
|
|
1013
|
+
if (shouldOutputJson(forceJson)) {
|
|
883
1014
|
console.log(JSON.stringify(comments, null, 2));
|
|
884
1015
|
return;
|
|
885
1016
|
}
|
|
1017
|
+
if (!isTTY()) {
|
|
1018
|
+
console.log(formatCommentsMarkdown(comments));
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
886
1021
|
if (comments.length === 0) {
|
|
887
1022
|
console.log("No comments found.");
|
|
888
1023
|
return;
|
|
@@ -891,7 +1026,7 @@ function printComments(comments, forceJson) {
|
|
|
891
1026
|
for (let i = 0; i < comments.length; i++) {
|
|
892
1027
|
const c = comments[i];
|
|
893
1028
|
if (i > 0) console.log(separator);
|
|
894
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
1029
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate3(c.date))}`);
|
|
895
1030
|
console.log(c.text);
|
|
896
1031
|
if (i < comments.length - 1) console.log("");
|
|
897
1032
|
}
|
|
@@ -920,10 +1055,14 @@ async function fetchLists(config, spaceId, opts = {}) {
|
|
|
920
1055
|
return results;
|
|
921
1056
|
}
|
|
922
1057
|
function printLists(lists, forceJson) {
|
|
923
|
-
if (forceJson
|
|
1058
|
+
if (shouldOutputJson(forceJson)) {
|
|
924
1059
|
console.log(JSON.stringify(lists, null, 2));
|
|
925
1060
|
return;
|
|
926
1061
|
}
|
|
1062
|
+
if (!isTTY()) {
|
|
1063
|
+
console.log(formatListsMarkdown(lists));
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
927
1066
|
if (lists.length === 0) {
|
|
928
1067
|
console.log("No lists found.");
|
|
929
1068
|
return;
|
|
@@ -991,7 +1130,7 @@ async function fetchInbox(config, days = 30) {
|
|
|
991
1130
|
async function printInbox(tasks, forceJson, config) {
|
|
992
1131
|
const now = Date.now();
|
|
993
1132
|
const groups = groupTasks(tasks, now);
|
|
994
|
-
if (forceJson
|
|
1133
|
+
if (shouldOutputJson(forceJson)) {
|
|
995
1134
|
const jsonGroups = {};
|
|
996
1135
|
for (const { key } of TIME_PERIODS) {
|
|
997
1136
|
if (groups[key].length > 0) {
|
|
@@ -1001,6 +1140,14 @@ async function printInbox(tasks, forceJson, config) {
|
|
|
1001
1140
|
console.log(JSON.stringify(jsonGroups, null, 2));
|
|
1002
1141
|
return;
|
|
1003
1142
|
}
|
|
1143
|
+
if (!isTTY()) {
|
|
1144
|
+
const mdGroups = TIME_PERIODS.filter((p) => groups[p.key].length > 0).map((p) => ({
|
|
1145
|
+
label: p.label,
|
|
1146
|
+
tasks: groups[p.key]
|
|
1147
|
+
}));
|
|
1148
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1004
1151
|
if (tasks.length === 0) {
|
|
1005
1152
|
console.log("No recently updated tasks.");
|
|
1006
1153
|
return;
|
|
@@ -1032,7 +1179,11 @@ async function listSpaces(config, opts) {
|
|
|
1032
1179
|
);
|
|
1033
1180
|
spaces = spaces.filter((s) => mySpaceIds.has(s.id));
|
|
1034
1181
|
}
|
|
1035
|
-
if (
|
|
1182
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1183
|
+
console.log(JSON.stringify(spaces, null, 2));
|
|
1184
|
+
} else if (!isTTY()) {
|
|
1185
|
+
console.log(formatSpacesMarkdown(spaces.map((s) => ({ id: s.id, name: s.name }))));
|
|
1186
|
+
} else {
|
|
1036
1187
|
const table = formatTable(
|
|
1037
1188
|
spaces.map((s) => ({ id: s.id, name: s.name })),
|
|
1038
1189
|
[
|
|
@@ -1041,8 +1192,6 @@ async function listSpaces(config, opts) {
|
|
|
1041
1192
|
]
|
|
1042
1193
|
);
|
|
1043
1194
|
console.log(table);
|
|
1044
|
-
} else {
|
|
1045
|
-
console.log(JSON.stringify(spaces, null, 2));
|
|
1046
1195
|
}
|
|
1047
1196
|
}
|
|
1048
1197
|
|
|
@@ -1097,7 +1246,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
1097
1246
|
const client = new ClickUpClient(config);
|
|
1098
1247
|
const allTasks = await client.getMyTasks(config.teamId);
|
|
1099
1248
|
const groups = groupByStatus(allTasks, opts.includeClosed ?? false);
|
|
1100
|
-
if (opts.json
|
|
1249
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1101
1250
|
const result = {};
|
|
1102
1251
|
for (const group of groups) {
|
|
1103
1252
|
result[group.status.toLowerCase()] = group.tasks.map((t) => toJsonTask(t, summarize(t)));
|
|
@@ -1105,6 +1254,14 @@ async function runAssignedCommand(config, opts) {
|
|
|
1105
1254
|
console.log(JSON.stringify(result, null, 2));
|
|
1106
1255
|
return;
|
|
1107
1256
|
}
|
|
1257
|
+
if (!isTTY()) {
|
|
1258
|
+
const mdGroups = groups.map((g) => ({
|
|
1259
|
+
label: g.status,
|
|
1260
|
+
tasks: g.tasks.map((t) => summarize(t))
|
|
1261
|
+
}));
|
|
1262
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1108
1265
|
if (groups.length === 0) {
|
|
1109
1266
|
console.log("No tasks found.");
|
|
1110
1267
|
return;
|
|
@@ -1130,13 +1287,13 @@ async function openTask(config, query, opts = {}) {
|
|
|
1130
1287
|
} catch {
|
|
1131
1288
|
}
|
|
1132
1289
|
if (task) {
|
|
1133
|
-
if (opts.json) {
|
|
1290
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1134
1291
|
console.log(JSON.stringify(task, null, 2));
|
|
1292
|
+
} else if (!isTTY()) {
|
|
1293
|
+
console.log(formatTaskDetailMarkdown(task));
|
|
1135
1294
|
} else {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
console.log(task.url);
|
|
1139
|
-
}
|
|
1295
|
+
console.log(task.name);
|
|
1296
|
+
console.log(task.url);
|
|
1140
1297
|
openUrl(task.url);
|
|
1141
1298
|
}
|
|
1142
1299
|
return task;
|
|
@@ -1154,15 +1311,18 @@ async function openTask(config, query, opts = {}) {
|
|
|
1154
1311
|
}
|
|
1155
1312
|
console.log("Opening first match...");
|
|
1156
1313
|
}
|
|
1157
|
-
if (opts.json) {
|
|
1314
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1158
1315
|
const fullTask = await client.getTask(first.id);
|
|
1159
1316
|
console.log(JSON.stringify(fullTask, null, 2));
|
|
1160
1317
|
return fullTask;
|
|
1161
1318
|
}
|
|
1162
|
-
if (isTTY()) {
|
|
1163
|
-
|
|
1164
|
-
console.log(
|
|
1319
|
+
if (!isTTY()) {
|
|
1320
|
+
const fullTask = await client.getTask(first.id);
|
|
1321
|
+
console.log(formatTaskDetailMarkdown(fullTask));
|
|
1322
|
+
return fullTask;
|
|
1165
1323
|
}
|
|
1324
|
+
console.log(first.name);
|
|
1325
|
+
console.log(first.url);
|
|
1166
1326
|
openUrl(first.url);
|
|
1167
1327
|
return {
|
|
1168
1328
|
id: first.id,
|
|
@@ -1224,10 +1384,19 @@ async function runSummaryCommand(config, opts) {
|
|
|
1224
1384
|
const client = new ClickUpClient(config);
|
|
1225
1385
|
const allTasks = await client.getMyTasks(config.teamId);
|
|
1226
1386
|
const result = categorizeTasks(allTasks, opts.hours);
|
|
1227
|
-
if (opts.json
|
|
1387
|
+
if (shouldOutputJson(opts.json)) {
|
|
1228
1388
|
console.log(JSON.stringify(result, null, 2));
|
|
1229
1389
|
return;
|
|
1230
1390
|
}
|
|
1391
|
+
if (!isTTY()) {
|
|
1392
|
+
const mdGroups = [
|
|
1393
|
+
{ label: "Completed Recently", tasks: result.completed },
|
|
1394
|
+
{ label: "In Progress", tasks: result.inProgress },
|
|
1395
|
+
{ label: "Overdue", tasks: result.overdue }
|
|
1396
|
+
];
|
|
1397
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1231
1400
|
printSection("Completed Recently", result.completed);
|
|
1232
1401
|
printSection("In Progress", result.inProgress);
|
|
1233
1402
|
printSection("Overdue", result.overdue);
|
|
@@ -1309,7 +1478,7 @@ async function assignTask(config, taskId, opts) {
|
|
|
1309
1478
|
|
|
1310
1479
|
// src/commands/activity.ts
|
|
1311
1480
|
import chalk4 from "chalk";
|
|
1312
|
-
function
|
|
1481
|
+
function formatDate4(timestamp) {
|
|
1313
1482
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1314
1483
|
month: "short",
|
|
1315
1484
|
day: "numeric",
|
|
@@ -1351,7 +1520,7 @@ function printActivity(result, forceJson) {
|
|
|
1351
1520
|
console.log("");
|
|
1352
1521
|
console.log(chalk4.dim("-".repeat(60)));
|
|
1353
1522
|
}
|
|
1354
|
-
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(
|
|
1523
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatDate4(c.date))}`);
|
|
1355
1524
|
console.log(c.text);
|
|
1356
1525
|
}
|
|
1357
1526
|
}
|
|
@@ -1863,8 +2032,10 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
1863
2032
|
wrapAction(async (taskId, opts) => {
|
|
1864
2033
|
const config = loadConfig();
|
|
1865
2034
|
const result = await getTask(config, taskId);
|
|
1866
|
-
if (opts.json
|
|
2035
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1867
2036
|
console.log(JSON.stringify(result, null, 2));
|
|
2037
|
+
} else if (!isTTY()) {
|
|
2038
|
+
console.log(formatTaskDetailMarkdown(result));
|
|
1868
2039
|
} else {
|
|
1869
2040
|
console.log(formatTaskDetail(result));
|
|
1870
2041
|
}
|
|
@@ -1875,10 +2046,10 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
|
|
|
1875
2046
|
const config = loadConfig();
|
|
1876
2047
|
const payload = buildUpdatePayload(opts);
|
|
1877
2048
|
const result = await updateTask(config, taskId, payload);
|
|
1878
|
-
if (
|
|
2049
|
+
if (shouldOutputJson(false)) {
|
|
1879
2050
|
console.log(JSON.stringify(result, null, 2));
|
|
1880
2051
|
} else {
|
|
1881
|
-
console.log(
|
|
2052
|
+
console.log(formatUpdateConfirmation(result.id, result.name));
|
|
1882
2053
|
}
|
|
1883
2054
|
})
|
|
1884
2055
|
);
|
|
@@ -1886,10 +2057,10 @@ program.command("create").description("Create a new task").option("-l, --list <l
|
|
|
1886
2057
|
wrapAction(async (opts) => {
|
|
1887
2058
|
const config = loadConfig();
|
|
1888
2059
|
const result = await createTask(config, opts);
|
|
1889
|
-
if (
|
|
2060
|
+
if (shouldOutputJson(false)) {
|
|
1890
2061
|
console.log(JSON.stringify(result, null, 2));
|
|
1891
2062
|
} else {
|
|
1892
|
-
console.log(
|
|
2063
|
+
console.log(formatCreateConfirmation(result.id, result.name, result.url));
|
|
1893
2064
|
}
|
|
1894
2065
|
})
|
|
1895
2066
|
);
|
|
@@ -1916,10 +2087,10 @@ program.command("comment <taskId>").description("Post a comment on a task").requ
|
|
|
1916
2087
|
wrapAction(async (taskId, opts) => {
|
|
1917
2088
|
const config = loadConfig();
|
|
1918
2089
|
const result = await postComment(config, taskId, opts.message);
|
|
1919
|
-
if (
|
|
1920
|
-
console.log(`Comment posted (id: ${result.id})`);
|
|
1921
|
-
} else {
|
|
2090
|
+
if (shouldOutputJson(false)) {
|
|
1922
2091
|
console.log(JSON.stringify(result, null, 2));
|
|
2092
|
+
} else {
|
|
2093
|
+
console.log(formatCommentConfirmation(result.id));
|
|
1923
2094
|
}
|
|
1924
2095
|
})
|
|
1925
2096
|
);
|
|
@@ -2003,13 +2174,10 @@ program.command("assign <taskId>").description("Assign or unassign users from a
|
|
|
2003
2174
|
wrapAction(async (taskId, opts) => {
|
|
2004
2175
|
const config = loadConfig();
|
|
2005
2176
|
const result = await assignTask(config, taskId, opts);
|
|
2006
|
-
if (opts.json
|
|
2177
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
2007
2178
|
console.log(JSON.stringify(result, null, 2));
|
|
2008
2179
|
} else {
|
|
2009
|
-
|
|
2010
|
-
if (opts.to) parts.push(`Assigned ${opts.to} to task ${taskId}`);
|
|
2011
|
-
if (opts.remove) parts.push(`Removed ${opts.remove} from task ${taskId}`);
|
|
2012
|
-
console.log(parts.join("; "));
|
|
2180
|
+
console.log(formatAssignConfirmation(taskId, { to: opts.to, remove: opts.remove }));
|
|
2013
2181
|
}
|
|
2014
2182
|
})
|
|
2015
2183
|
);
|
package/package.json
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clickup
|
|
3
|
-
description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool.
|
|
3
|
+
description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ClickUp CLI (`cu`)
|
|
7
7
|
|
|
8
|
-
Reference for AI agents using the `cu`
|
|
8
|
+
Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, initiatives, comments, and project workflows.
|
|
9
9
|
|
|
10
|
-
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks
|
|
10
|
+
Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks, standup, overdue, search
|
|
11
11
|
|
|
12
12
|
## Setup
|
|
13
13
|
|
|
14
|
-
Config at `~/.config/cu/config.json`
|
|
14
|
+
Config at `~/.config/cu/config.json` with `apiToken` and `teamId`. Run `cu init` to set up interactively.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Environment variables `CU_API_TOKEN` and `CU_TEAM_ID` override config file when both are set.
|
|
17
|
+
|
|
18
|
+
## Output Modes
|
|
19
|
+
|
|
20
|
+
| Context | Default output | Override |
|
|
21
|
+
| --------------- | --------------------- | ----------------- |
|
|
22
|
+
| Terminal (TTY) | Interactive picker UI | `--json` for JSON |
|
|
23
|
+
| Piped / non-TTY | Markdown tables | `--json` for JSON |
|
|
24
|
+
|
|
25
|
+
- Default piped output is **Markdown** - optimized for agent context windows
|
|
26
|
+
- `cu task <id>` outputs a Markdown summary when piped; use `--json` for the full raw API object (custom fields, checklists, etc.)
|
|
27
|
+
- Set `CU_OUTPUT=json` to always get JSON when piped
|
|
28
|
+
- Set `NO_COLOR` to disable color (tables still render, just uncolored)
|
|
29
|
+
- Agents typically don't need `--json` unless parsing structured data with `jq`
|
|
17
30
|
|
|
18
31
|
## Commands
|
|
19
32
|
|
|
@@ -27,19 +40,19 @@ All commands support `--help` for full flag details.
|
|
|
27
40
|
| `cu initiatives [--status s] [--name q] [--list id] [--space id] [--json]` | My initiatives |
|
|
28
41
|
| `cu assigned [--include-closed] [--json]` | All my tasks grouped by status |
|
|
29
42
|
| `cu sprint [--status s] [--space nameOrId] [--json]` | Tasks in active sprint (auto-detected) |
|
|
30
|
-
| `cu
|
|
43
|
+
| `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
|
|
44
|
+
| `cu search <query> [--status s] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
|
|
31
45
|
| `cu task <id> [--json]` | Single task details |
|
|
32
46
|
| `cu subtasks <id> [--json]` | Subtasks of a task or initiative |
|
|
33
|
-
| `cu comments <id> [--json]` |
|
|
34
|
-
| `cu
|
|
35
|
-
| `cu
|
|
36
|
-
| `cu open <query> [--json]` | Open task in browser by ID or name |
|
|
47
|
+
| `cu comments <id> [--json]` | Comments on a task |
|
|
48
|
+
| `cu activity <id> [--json]` | Task details + comment history combined |
|
|
49
|
+
| `cu inbox [--days n] [--json]` | Tasks updated in last n days (default 30) |
|
|
37
50
|
| `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
|
|
38
51
|
| `cu overdue [--json]` | Tasks past their due date |
|
|
39
|
-
| `cu
|
|
40
|
-
| `cu
|
|
52
|
+
| `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
|
|
53
|
+
| `cu lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
|
|
54
|
+
| `cu open <query> [--json]` | Open task in browser by ID or name |
|
|
41
55
|
| `cu auth [--json]` | Check authentication status |
|
|
42
|
-
| `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
|
|
43
56
|
|
|
44
57
|
### Write
|
|
45
58
|
|
|
@@ -48,102 +61,82 @@ All commands support `--help` for full flag details.
|
|
|
48
61
|
| `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--json]` | Update task fields |
|
|
49
62
|
| `cu create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--tags t] [--json]` | Create task (list auto-detected from parent) |
|
|
50
63
|
| `cu comment <id> -m text` | Post comment on task |
|
|
51
|
-
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users
|
|
64
|
+
| `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
|
|
52
65
|
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
53
|
-
| `cu completion <shell>` |
|
|
54
|
-
|
|
55
|
-
##
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- Errors go to stderr with exit code 1
|
|
84
|
-
|
|
85
|
-
## Agent workflow example
|
|
66
|
+
| `cu completion <shell>` | Shell completions (bash/zsh/fish) |
|
|
67
|
+
|
|
68
|
+
## Quick Reference
|
|
69
|
+
|
|
70
|
+
| Topic | Detail |
|
|
71
|
+
| ------------------- | --------------------------------------------------------------------------------- |
|
|
72
|
+
| Task IDs | Stable alphanumeric strings (e.g. `abc123def`) |
|
|
73
|
+
| Initiatives | Detected via `custom_item_id !== 0` |
|
|
74
|
+
| `--list` on create | Optional when `--parent` is given (auto-detected) |
|
|
75
|
+
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
|
|
76
|
+
| `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
|
|
77
|
+
| `--due-date` | `YYYY-MM-DD` format |
|
|
78
|
+
| `--assignee` | Numeric user ID (find via `cu task <id> --json`) |
|
|
79
|
+
| `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
|
|
80
|
+
| `--space` | Partial name match or exact ID |
|
|
81
|
+
| `--name` | Partial match, case-insensitive |
|
|
82
|
+
| `cu assign --to me` | Shorthand for your own user ID |
|
|
83
|
+
| `cu search` | Matches all query words against task name, case-insensitive |
|
|
84
|
+
| `cu sprint` | Auto-detects active sprint via view API and date range parsing |
|
|
85
|
+
| `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
|
|
86
|
+
| `cu overdue` | Excludes closed tasks, sorted most overdue first |
|
|
87
|
+
| `cu open` | Tries task ID first, falls back to name search |
|
|
88
|
+
| `cu task` | Shows custom fields in detail view |
|
|
89
|
+
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
|
|
90
|
+
| Errors | stderr with exit code 1 |
|
|
91
|
+
| Parsing | Strict - excess/unknown arguments rejected |
|
|
92
|
+
|
|
93
|
+
## Agent Workflow Examples
|
|
94
|
+
|
|
95
|
+
### Investigate a task
|
|
86
96
|
|
|
87
97
|
```bash
|
|
88
|
-
|
|
89
|
-
cu
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Check current sprint
|
|
95
|
-
cu sprint --json | jq '.[].name'
|
|
98
|
+
cu task abc123def # markdown summary
|
|
99
|
+
cu subtasks abc123def # child tasks
|
|
100
|
+
cu comments abc123def # discussion
|
|
101
|
+
cu activity abc123def # task + comments combined
|
|
102
|
+
```
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
cu task abc123def --json
|
|
104
|
+
### Find tasks
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
cu
|
|
106
|
+
```bash
|
|
107
|
+
cu tasks --status "in progress" # by status
|
|
108
|
+
cu tasks --name "login" # by partial name
|
|
109
|
+
cu search "payment flow" # multi-word search
|
|
110
|
+
cu search auth --status "prog" # fuzzy status match
|
|
111
|
+
cu sprint # current sprint
|
|
112
|
+
cu assigned # all my tasks by status
|
|
113
|
+
cu overdue # past due date
|
|
114
|
+
cu inbox --days 7 # recently updated
|
|
115
|
+
```
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
cu comments abc123def --json
|
|
117
|
+
### Make changes
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
```bash
|
|
107
120
|
cu update abc123def -s "done"
|
|
108
|
-
|
|
109
|
-
# Update priority and due date
|
|
110
121
|
cu update abc123def --priority high --due-date 2025-03-15
|
|
111
|
-
|
|
112
|
-
# Create subtask under initiative (no --list needed)
|
|
113
122
|
cu create -n "Fix the thing" -p abc123def
|
|
114
|
-
|
|
115
|
-
# Create task with priority and tags
|
|
116
123
|
cu create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
|
|
117
|
-
|
|
118
|
-
# Post a comment
|
|
119
124
|
cu comment abc123def -m "Completed in PR #42"
|
|
120
|
-
|
|
121
|
-
# Discover lists in a space
|
|
122
|
-
cu lists <spaceId> --json
|
|
123
|
-
|
|
124
|
-
# Open a task in the browser
|
|
125
|
-
cu open abc123def
|
|
126
|
-
|
|
127
|
-
# Standup summary
|
|
128
|
-
cu summary --json
|
|
129
|
-
|
|
130
|
-
# Check overdue tasks
|
|
131
|
-
cu overdue --json
|
|
132
|
-
|
|
133
|
-
# Assign yourself to a task
|
|
134
125
|
cu assign abc123def --to me
|
|
126
|
+
```
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
cu config get teamId
|
|
138
|
-
cu config set apiToken pk_example_token
|
|
139
|
-
|
|
140
|
-
cu search "login bug" --json
|
|
141
|
-
|
|
142
|
-
cu activity abc123def --json
|
|
128
|
+
### Discover workspace structure
|
|
143
129
|
|
|
144
|
-
|
|
130
|
+
```bash
|
|
131
|
+
cu spaces # all spaces
|
|
132
|
+
cu lists <spaceId> # lists in a space (needed for --list flag)
|
|
133
|
+
cu sprints # all sprints across folders
|
|
134
|
+
cu auth # verify token works
|
|
135
|
+
```
|
|
145
136
|
|
|
146
|
-
|
|
137
|
+
### Standup
|
|
147
138
|
|
|
148
|
-
|
|
139
|
+
```bash
|
|
140
|
+
cu summary # completed / in progress / overdue
|
|
141
|
+
cu summary --hours 48 # wider window
|
|
149
142
|
```
|