@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 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 JSON when piped, interactive tables when run in a terminal.
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
- ## Quick start
5
+ [![npm](https://img.shields.io/npm/v/@krodak/clickup-cli)](https://www.npmjs.com/package/@krodak/clickup-cli)
6
+ [![node](https://img.shields.io/node/v/@krodak/clickup-cli)](https://nodejs.org)
7
+ [![license](https://img.shields.io/npm/l/@krodak/clickup-cli)](./LICENSE)
8
+ [![CI](https://github.com/krodak/clickup-cli/actions/workflows/ci.yml/badge.svg)](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 Node 22+ and a ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps).
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 get raw JSON output instead of the interactive UI.
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 automatically output JSON. All commands support `--json` to force JSON output even in a terminal.
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 || !isTTY()) {
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 formatDate(d) {
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 ? `${formatDate(dates.start)} - ${formatDate(dates.end)}` : "";
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 formatDate2(timestamp) {
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 || !isTTY()) {
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(formatDate2(c.date))}`);
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 || !isTTY()) {
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 || !isTTY()) {
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 (!opts.json && isTTY()) {
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 || !isTTY()) {
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
- if (isTTY()) {
1137
- console.log(task.name);
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
- console.log(first.name);
1164
- console.log(first.url);
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 || !isTTY()) {
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 formatDate3(timestamp) {
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(formatDate3(c.date))}`);
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 || !isTTY()) {
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 (opts.json || !isTTY()) {
2049
+ if (shouldOutputJson(false)) {
1879
2050
  console.log(JSON.stringify(result, null, 2));
1880
2051
  } else {
1881
- console.log(`Updated task ${result.id}: "${result.name}"`);
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 (opts.json || !isTTY()) {
2060
+ if (shouldOutputJson(false)) {
1890
2061
  console.log(JSON.stringify(result, null, 2));
1891
2062
  } else {
1892
- console.log(`Created task ${result.id}: "${result.name}" - ${result.url}`);
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 (isTTY()) {
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 || !isTTY()) {
2177
+ if (shouldOutputJson(opts.json ?? false)) {
2007
2178
  console.log(JSON.stringify(result, null, 2));
2008
2179
  } else {
2009
- const parts = [];
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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. Covers task queries, status updates, sprint tracking, and project management workflows.'
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` command-line tool to interact with ClickUp. Covers task management, sprint tracking, initiatives, and comments.
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` (or `$XDG_CONFIG_HOME/cu/config.json`) with `apiToken` and `teamId`. Run `cu init` to set up.
14
+ Config at `~/.config/cu/config.json` with `apiToken` and `teamId`. Run `cu init` to set up interactively.
15
15
 
16
- Alternatively, set `CU_API_TOKEN` and `CU_TEAM_ID` environment variables (overrides config file, no file needed when both are set).
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 inbox [--days n] [--json]` | Tasks updated in last n days (default 30) |
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]` | List comments on a task |
34
- | `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
35
- | `cu lists <spaceId> [--name partial] [--json]` | List all lists in a space (including folder lists) |
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 search <query> [--status s] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
40
- | `cu activity <id> [--json]` | Task details + comment history combined |
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 from a task |
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>` | Output shell completions (bash/zsh/fish) |
54
-
55
- ## Output modes
56
-
57
- - **TTY (terminal)**: Interactive picker UI. Use `--json` to bypass.
58
- - **Piped / non-TTY**: Always JSON. This is what agents get by default.
59
- - **Agents should always pass `--json`** to guarantee machine-readable output.
60
- - Set `NO_COLOR` to disable color output (tables still render, just without color).
61
-
62
- ## Key facts
63
-
64
- - All task IDs are stable alphanumeric strings (e.g. `abc123def`)
65
- - Initiatives are detected via `custom_item_id !== 0` (not `task_type`)
66
- - `--list` is optional in `cu create` when `--parent` is given (list auto-detected from parent)
67
- - `cu sprint` auto-detects active sprint from spaces where user has tasks, using view API and date range parsing from sprint names like "Acme Sprint 4 (3/1 - 3/14)"
68
- - `--name` on tasks/initiatives filters by partial name match (case-insensitive)
69
- - `--space` on sprint/tasks accepts partial name match (e.g. `--space Acm`)
70
- - `--priority` accepts names (`urgent`, `high`, `normal`, `low`) or numbers (1-4)
71
- - `--due-date` accepts `YYYY-MM-DD` format
72
- - `--assignee` takes a numeric user ID (use `cu task <id> --json` to find assignee IDs)
73
- - `cu assign` supports `me` as a shorthand for your own user ID
74
- - `cu open` tries task ID lookup first, then falls back to name search
75
- - `cu summary` categories: completed (done/complete/closed within N hours), in progress, overdue
76
- - `cu overdue` excludes done/complete/closed tasks, sorted most overdue first
77
- - `--tags` accepts comma-separated tag names (e.g. `--tags "bug,frontend"`)
78
- - `cu lists <spaceId>` discovers list IDs needed for `--list` and `cu create -l`
79
- - Strict argument parsing - excess/unknown arguments are rejected
80
- - `cu update -s` supports fuzzy status matching (exact > starts-with > contains). Prints matched status to stderr when fuzzy-resolved.
81
- - `cu task` shows custom fields in detail view (read-only)
82
- - `cu search` matches all query words against task name (case-insensitive). `--status` supports fuzzy matching.
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
- # List my in-progress tasks
89
- cu tasks --status "in progress" --json
90
-
91
- # Find tasks by partial name
92
- cu tasks --name "login" --json
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
- # Get full details on a task
98
- cu task abc123def --json
104
+ ### Find tasks
99
105
 
100
- # List subtasks of an initiative
101
- cu subtasks abc123def --json
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
- # Read comments on a task
104
- cu comments abc123def --json
117
+ ### Make changes
105
118
 
106
- # Update task status
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
- # Check/set config
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
- cu auth --json
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
- cu sprints --json
137
+ ### Standup
147
138
 
148
- cu update abc123def -s "prog"
139
+ ```bash
140
+ cu summary # completed / in progress / overdue
141
+ cu summary --hours 48 # wider window
149
142
  ```