@krodak/clickup-cli 0.7.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "clickup-cli",
3
+ "description": "Claude Code skills for the cu (ClickUp CLI) tool - manage tasks, sprints, initiatives, and comments",
4
+ "version": "0.6.1",
5
+ "author": {
6
+ "name": "Krzysztof Rodak"
7
+ },
8
+ "homepage": "https://github.com/krodak/clickup-cli",
9
+ "repository": "https://github.com/krodak/clickup-cli",
10
+ "license": "MIT"
11
+ }
package/README.md CHANGED
@@ -20,13 +20,21 @@ This is the primary use case. Install the tool, install the skill file, and your
20
20
 
21
21
  ### 1. Install the skill
22
22
 
23
- The repo includes a skill file at `skill/SKILL.md` that teaches agents all available commands and when to use them.
23
+ The repo includes a skill file at `skills/clickup-cli/SKILL.md` that teaches agents all available commands and when to use them. It's also packaged as a Claude Code plugin.
24
24
 
25
- **Claude Code:**
25
+ **Claude Code (plugin - recommended):**
26
+
27
+ The repo ships as a Claude Code plugin. Point Claude Code at the repo or installed npm package:
28
+
29
+ ```bash
30
+ claude --plugin-dir ./node_modules/@krodak/clickup-cli
31
+ ```
32
+
33
+ Or copy the skill manually:
26
34
 
27
35
  ```bash
28
36
  mkdir -p ~/.claude/skills/clickup
29
- cp skill/SKILL.md ~/.claude/skills/clickup/SKILL.md
37
+ cp skills/clickup-cli/SKILL.md ~/.claude/skills/clickup/SKILL.md
30
38
  ```
31
39
 
32
40
  Then reference it in your `CLAUDE.md` or project instructions.
@@ -35,12 +43,12 @@ Then reference it in your `CLAUDE.md` or project instructions.
35
43
 
36
44
  ```bash
37
45
  mkdir -p ~/.config/opencode/skills/clickup
38
- cp skill/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
46
+ cp skills/clickup-cli/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
39
47
  ```
40
48
 
41
49
  **Codex / other agents:**
42
50
 
43
- Copy the contents of `skill/SKILL.md` into your system prompt or project instructions. It's a standalone markdown document.
51
+ Copy the contents of `skills/clickup-cli/SKILL.md` into your system prompt or project instructions. It's a standalone markdown document.
44
52
 
45
53
  ### 2. Talk to your agent
46
54
 
@@ -90,7 +98,7 @@ When output is piped (no TTY), all commands output **Markdown** by default - opt
90
98
 
91
99
  ## Commands
92
100
 
93
- 20 commands total. All support `--help` for full flag details.
101
+ 24 commands total. All support `--help` for full flag details.
94
102
 
95
103
  ### `cu init`
96
104
 
@@ -136,6 +144,15 @@ cu sprint --status "in progress"
136
144
  cu sprint --json
137
145
  ```
138
146
 
147
+ ### `cu sprints`
148
+
149
+ List all sprints across sprint folders. Marks the currently active sprint.
150
+
151
+ ```bash
152
+ cu sprints
153
+ cu sprints --json
154
+ ```
155
+
139
156
  ### `cu assigned`
140
157
 
141
158
  All tasks assigned to me, grouped by pipeline stage (code review, in progress, to do, etc.).
@@ -158,7 +175,7 @@ cu inbox --json
158
175
 
159
176
  ### `cu task <id>`
160
177
 
161
- Get task details. Pretty summary in terminal, JSON when piped.
178
+ Get task details including custom fields. Pretty summary in terminal, JSON when piped.
162
179
 
163
180
  ```bash
164
181
  cu task abc123
@@ -188,16 +205,18 @@ cu update abc123 --priority high
188
205
  cu update abc123 --due-date 2025-03-15
189
206
  cu update abc123 --assignee 12345
190
207
  cu update abc123 -n "New name" -s "done" --priority urgent
208
+ cu update abc123 -s "in progress" --json
191
209
  ```
192
210
 
193
- | Flag | Description |
194
- | -------------------------- | ---------------------------------------------------- |
195
- | `-n, --name <text>` | New task name |
196
- | `-d, --description <text>` | New description (markdown supported) |
197
- | `-s, --status <status>` | New status (e.g. `"in progress"`, `"done"`) |
198
- | `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
199
- | `--due-date <date>` | Due date (`YYYY-MM-DD`) |
200
- | `--assignee <userId>` | Add assignee by numeric user ID |
211
+ | Flag | Description |
212
+ | -------------------------- | --------------------------------------------------------------------------- |
213
+ | `-n, --name <text>` | New task name |
214
+ | `-d, --description <text>` | New description (markdown supported) |
215
+ | `-s, --status <status>` | New status, supports fuzzy matching (e.g. `"prog"` matches `"in progress"`) |
216
+ | `--priority <level>` | Priority: `urgent`, `high`, `normal`, `low` (or 1-4) |
217
+ | `--due-date <date>` | Due date (`YYYY-MM-DD`) |
218
+ | `--assignee <userId>` | Add assignee by numeric user ID |
219
+ | `--json` | Force JSON output even in terminal |
201
220
 
202
221
  ### `cu create`
203
222
 
@@ -209,6 +228,7 @@ cu create -n "Subtask name" -p <parentTaskId> # --list auto-detected
209
228
  cu create -n "Task" -l <listId> -d "desc" -s "open"
210
229
  cu create -n "Task" -l <listId> --priority high --due-date 2025-06-01
211
230
  cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
231
+ cu create -n "Fix bug" -l <listId> --json
212
232
  ```
213
233
 
214
234
  | Flag | Required | Description |
@@ -222,6 +242,7 @@ cu create -n "Task" -l <listId> --assignee 12345 --tags "bug,frontend"
222
242
  | `--due-date <date>` | no | Due date (`YYYY-MM-DD`) |
223
243
  | `--assignee <userId>` | no | Assignee by numeric user ID |
224
244
  | `--tags <tags>` | no | Comma-separated tag names |
245
+ | `--json` | no | Force JSON output even in terminal |
225
246
 
226
247
  ### `cu comment <id>`
227
248
 
@@ -240,6 +261,15 @@ cu comments abc123
240
261
  cu comments abc123 --json
241
262
  ```
242
263
 
264
+ ### `cu activity <id>`
265
+
266
+ View task details and comment history together. Combines `cu task` and `cu comments` into a single view.
267
+
268
+ ```bash
269
+ cu activity abc123
270
+ cu activity abc123 --json
271
+ ```
272
+
243
273
  ### `cu lists <spaceId>`
244
274
 
245
275
  List all lists in a space, including lists inside folders. Useful for discovering list IDs needed by `--list` filter and `cu create -l`.
@@ -284,6 +314,17 @@ cu open abc123 --json
284
314
 
285
315
  If the query matches multiple tasks by name, all matches are listed and the first is opened.
286
316
 
317
+ ### `cu search <query>`
318
+
319
+ Search my tasks by name. Supports multi-word queries with case-insensitive matching. Status filter supports fuzzy matching.
320
+
321
+ ```bash
322
+ cu search "login bug"
323
+ cu search auth
324
+ cu search "payment flow" --json
325
+ cu search auth --status "prog" # fuzzy matches "in progress"
326
+ ```
327
+
287
328
  ### `cu summary`
288
329
 
289
330
  Daily standup helper. Shows tasks grouped into: recently completed, in progress, and overdue.
@@ -326,6 +367,15 @@ cu assign abc123 --to me --json
326
367
  | `--remove <userId>` | Remove assignee (user ID or `me`) |
327
368
  | `--json` | Force JSON output |
328
369
 
370
+ ### `cu auth`
371
+
372
+ Check authentication status. Validates your API token and shows your user info.
373
+
374
+ ```bash
375
+ cu auth
376
+ cu auth --json
377
+ ```
378
+
329
379
  ### `cu config`
330
380
 
331
381
  Manage CLI configuration.
package/dist/index.js CHANGED
@@ -61,9 +61,13 @@ function getConfigPath() {
61
61
  function writeConfig(config) {
62
62
  const dir = configDir();
63
63
  if (!fs.existsSync(dir)) {
64
- fs.mkdirSync(dir, { recursive: true });
64
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
65
65
  }
66
- fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
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
+ });
67
71
  }
68
72
 
69
73
  // src/api.ts
@@ -187,6 +191,9 @@ var ClickUpClient = class {
187
191
  const data = await this.request("/team");
188
192
  return data.teams ?? [];
189
193
  }
194
+ async getSpaceWithStatuses(spaceId) {
195
+ return this.request(`/space/${spaceId}`);
196
+ }
190
197
  async getSpaces(teamId) {
191
198
  const data = await this.request(`/team/${teamId}/space?archived=false`);
192
199
  return data.spaces ?? [];
@@ -422,6 +429,40 @@ function descriptionPreview(text, maxLines = 3) {
422
429
  ${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
423
430
  return result;
424
431
  }
432
+ function stringifyFieldValue(value) {
433
+ if (typeof value === "string") return value;
434
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
435
+ return JSON.stringify(value);
436
+ }
437
+ function formatCustomFieldValue(field) {
438
+ if (field.value === null || field.value === void 0) return null;
439
+ const options = field.type_config?.options;
440
+ switch (field.type) {
441
+ case "drop_down": {
442
+ if (!options) return stringifyFieldValue(field.value);
443
+ const match = options.find((o) => o.id === Number(field.value));
444
+ return match?.name ?? stringifyFieldValue(field.value);
445
+ }
446
+ case "labels": {
447
+ if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
448
+ const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
449
+ return names.length > 0 ? names.join(", ") : null;
450
+ }
451
+ case "date": {
452
+ const ts = Number(field.value);
453
+ if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
454
+ return new Date(ts).toLocaleDateString("en-US", {
455
+ month: "short",
456
+ day: "numeric",
457
+ year: "numeric"
458
+ });
459
+ }
460
+ case "checkbox":
461
+ return field.value === true || field.value === "true" ? "Yes" : "No";
462
+ default:
463
+ return stringifyFieldValue(field.value);
464
+ }
465
+ }
425
466
  function formatTaskDetail(task) {
426
467
  const lines = [];
427
468
  const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
@@ -451,6 +492,16 @@ function formatTaskDetail(task) {
451
492
  if (!value) continue;
452
493
  lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
453
494
  }
495
+ if (task.custom_fields?.length) {
496
+ const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
497
+ if (formatted.length > 0) {
498
+ lines.push("");
499
+ lines.push(chalk2.bold("Custom Fields"));
500
+ for (const [name, value] of formatted) {
501
+ lines.push(` ${chalk2.bold(name)} ${value}`);
502
+ }
503
+ }
504
+ }
454
505
  if (task.text_content?.trim()) {
455
506
  lines.push("");
456
507
  lines.push(descriptionPreview(task.text_content));
@@ -586,6 +637,19 @@ async function printTasks(tasks, forceJson, config) {
586
637
  await showDetailsAndOpen(selected, fetchTask);
587
638
  }
588
639
 
640
+ // src/status.ts
641
+ function matchStatus(input, statuses) {
642
+ if (!input) return null;
643
+ const lower = input.toLowerCase();
644
+ const exact = statuses.find((s) => s.toLowerCase() === lower);
645
+ if (exact) return exact;
646
+ const startsWith = statuses.find((s) => s.toLowerCase().startsWith(lower));
647
+ if (startsWith) return startsWith;
648
+ const contains = statuses.find((s) => s.toLowerCase().includes(lower));
649
+ if (contains) return contains;
650
+ return null;
651
+ }
652
+
589
653
  // src/commands/update.ts
590
654
  var PRIORITY_MAP = {
591
655
  urgent: 1,
@@ -631,12 +695,30 @@ function buildUpdatePayload(opts) {
631
695
  function hasUpdateFields(options) {
632
696
  return options.name !== void 0 || options.description !== void 0 || options.status !== void 0 || options.priority !== void 0 || options.due_date !== void 0 || options.assignees !== void 0;
633
697
  }
698
+ async function resolveStatus(client, taskId, statusInput) {
699
+ const task = await client.getTask(taskId);
700
+ if (!task.space) return statusInput;
701
+ const space = await client.getSpaceWithStatuses(task.space.id);
702
+ const available = space.statuses.map((s) => s.status);
703
+ const matched = matchStatus(statusInput, available);
704
+ if (!matched) {
705
+ throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
706
+ }
707
+ if (matched.toLowerCase() !== statusInput.toLowerCase()) {
708
+ process.stderr.write(`Status matched: "${statusInput}" -> "${matched}"
709
+ `);
710
+ }
711
+ return matched;
712
+ }
634
713
  async function updateTask(config, taskId, options) {
635
714
  if (!hasUpdateFields(options))
636
715
  throw new Error(
637
716
  "Provide at least one of: --name, --description, --status, --priority, --due-date, --assignee"
638
717
  );
639
718
  const client = new ClickUpClient(config);
719
+ if (options.status !== void 0) {
720
+ options.status = await resolveStatus(client, taskId, options.status);
721
+ }
640
722
  const task = await client.updateTask(taskId, options);
641
723
  return { id: task.id, name: task.name };
642
724
  }
@@ -814,6 +896,83 @@ async function runSprintCommand(config, opts) {
814
896
  await printTasks(summaries, opts.json ?? false, config);
815
897
  }
816
898
 
899
+ // src/commands/sprints.ts
900
+ var SPRINT_COLUMNS = [
901
+ { key: "id", label: "ID" },
902
+ { key: "sprint", label: "SPRINT", maxWidth: 60 },
903
+ { key: "dates", label: "DATES" }
904
+ ];
905
+ function formatDate2(d) {
906
+ return `${d.getMonth() + 1}/${d.getDate()}`;
907
+ }
908
+ function buildSprintInfos(lists, folderName, today) {
909
+ return lists.map((list) => {
910
+ const dates = parseSprintDates(list.name);
911
+ const active = dates !== null && today >= dates.start && today <= dates.end;
912
+ return {
913
+ id: list.id,
914
+ name: list.name,
915
+ folder: folderName,
916
+ start: dates ? dates.start.toISOString() : null,
917
+ end: dates ? dates.end.toISOString() : null,
918
+ active
919
+ };
920
+ });
921
+ }
922
+ async function listSprints(config, opts = {}) {
923
+ const client = new ClickUpClient(config);
924
+ const [myTasks, allSpaces] = await Promise.all([
925
+ client.getMyTasks(config.teamId),
926
+ client.getSpaces(config.teamId)
927
+ ]);
928
+ let spaces;
929
+ if (opts.space) {
930
+ spaces = allSpaces.filter(
931
+ (s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
932
+ );
933
+ if (spaces.length === 0) {
934
+ throw new Error(
935
+ `No space matching "${opts.space}" found. Use \`cu spaces\` to list available spaces.`
936
+ );
937
+ }
938
+ } else {
939
+ const mySpaceIds = new Set(
940
+ myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
941
+ );
942
+ spaces = findRelatedSpaces(mySpaceIds, allSpaces);
943
+ }
944
+ const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
945
+ const sprintFolders = foldersBySpace.flat().filter((f) => f.name.toLowerCase().includes("sprint"));
946
+ const today = /* @__PURE__ */ new Date();
947
+ const allSprints = [];
948
+ const listsByFolder = await Promise.all(
949
+ sprintFolders.map((folder) => client.getFolderLists(folder.id))
950
+ );
951
+ for (let i = 0; i < sprintFolders.length; i++) {
952
+ const folder = sprintFolders[i];
953
+ const lists = listsByFolder[i];
954
+ allSprints.push(...buildSprintInfos(lists, folder.name, today));
955
+ }
956
+ if (opts.json || !isTTY()) {
957
+ console.log(JSON.stringify(allSprints, null, 2));
958
+ return;
959
+ }
960
+ if (allSprints.length === 0) {
961
+ console.log("No sprints found.");
962
+ return;
963
+ }
964
+ const rows = allSprints.map((s) => {
965
+ const dates = parseSprintDates(s.name);
966
+ const dateStr = dates ? `${formatDate2(dates.start)} - ${formatDate2(dates.end)}` : "";
967
+ return {
968
+ id: s.id,
969
+ sprint: s.active ? `* ${s.name}` : s.name,
970
+ dates: dateStr
971
+ };
972
+ });
973
+ console.log(formatTable(rows, SPRINT_COLUMNS));
974
+ }
975
+
817
976
  // src/commands/subtasks.ts
818
977
  async function fetchSubtasks(config, taskId) {
819
978
  const client = new ClickUpClient(config);
@@ -831,7 +990,7 @@ async function postComment(config, taskId, text) {
831
990
 
832
991
  // src/commands/comments.ts
833
992
  import chalk3 from "chalk";
834
- function formatDate2(timestamp) {
993
+ function formatDate3(timestamp) {
835
994
  return new Date(Number(timestamp)).toLocaleString("en-US", {
836
995
  month: "short",
837
996
  day: "numeric",
@@ -867,7 +1026,7 @@ function printComments(comments, forceJson) {
867
1026
  for (let i = 0; i < comments.length; i++) {
868
1027
  const c = comments[i];
869
1028
  if (i > 0) console.log(separator);
870
- console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate2(c.date))}`);
1029
+ console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate3(c.date))}`);
871
1030
  console.log(c.text);
872
1031
  if (i < comments.length - 1) console.log("");
873
1032
  }
@@ -1317,6 +1476,55 @@ async function assignTask(config, taskId, opts) {
1317
1476
  });
1318
1477
  }
1319
1478
 
1479
+ // src/commands/activity.ts
1480
+ import chalk4 from "chalk";
1481
+ function formatDate4(timestamp) {
1482
+ return new Date(Number(timestamp)).toLocaleString("en-US", {
1483
+ month: "short",
1484
+ day: "numeric",
1485
+ year: "numeric",
1486
+ hour: "numeric",
1487
+ minute: "2-digit"
1488
+ });
1489
+ }
1490
+ async function fetchActivity(config, taskId) {
1491
+ const client = new ClickUpClient(config);
1492
+ const [task, rawComments] = await Promise.all([
1493
+ client.getTask(taskId),
1494
+ client.getTaskComments(taskId)
1495
+ ]);
1496
+ const comments = rawComments.map((c) => ({
1497
+ id: c.id,
1498
+ user: c.user.username,
1499
+ date: c.date,
1500
+ text: c.comment_text
1501
+ }));
1502
+ return { task, comments };
1503
+ }
1504
+ function printActivity(result, forceJson) {
1505
+ if (forceJson || !isTTY()) {
1506
+ console.log(JSON.stringify(result, null, 2));
1507
+ return;
1508
+ }
1509
+ console.log(formatTaskDetail(result.task));
1510
+ console.log("");
1511
+ console.log(chalk4.bold("Comments"));
1512
+ console.log(chalk4.dim("-".repeat(60)));
1513
+ if (result.comments.length === 0) {
1514
+ console.log("No comments.");
1515
+ return;
1516
+ }
1517
+ for (let i = 0; i < result.comments.length; i++) {
1518
+ const c = result.comments[i];
1519
+ if (i > 0) {
1520
+ console.log("");
1521
+ console.log(chalk4.dim("-".repeat(60)));
1522
+ }
1523
+ console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatDate4(c.date))}`);
1524
+ console.log(c.text);
1525
+ }
1526
+ }
1527
+
1320
1528
  // src/commands/completion.ts
1321
1529
  function bashCompletion() {
1322
1530
  return `_cu_completions() {
@@ -1721,6 +1929,47 @@ function generateCompletion(shell) {
1721
1929
  }
1722
1930
  }
1723
1931
 
1932
+ // src/commands/auth.ts
1933
+ async function checkAuth(config) {
1934
+ const client = new ClickUpClient(config);
1935
+ try {
1936
+ const user = await client.getMe();
1937
+ return { authenticated: true, user };
1938
+ } catch (err) {
1939
+ const message = err instanceof Error ? err.message : String(err);
1940
+ return { authenticated: false, error: message };
1941
+ }
1942
+ }
1943
+
1944
+ // src/commands/search.ts
1945
+ async function searchTasks(config, query, opts = {}) {
1946
+ const trimmed = query.trim();
1947
+ if (!trimmed) {
1948
+ throw new Error("Search query cannot be empty");
1949
+ }
1950
+ const client = new ClickUpClient(config);
1951
+ const allTasks = await client.getMyTasks(config.teamId);
1952
+ const words = trimmed.toLowerCase().split(/\s+/);
1953
+ let matched = allTasks.filter((task) => {
1954
+ const name = task.name.toLowerCase();
1955
+ return words.every((word) => name.includes(word));
1956
+ });
1957
+ if (opts.status) {
1958
+ const availableStatuses = [...new Set(allTasks.map((t) => t.status.status))];
1959
+ const resolved = matchStatus(opts.status, availableStatuses);
1960
+ if (resolved) {
1961
+ if (resolved.toLowerCase() !== opts.status.toLowerCase()) {
1962
+ process.stderr.write(`Status matched: "${opts.status}" -> "${resolved}"
1963
+ `);
1964
+ }
1965
+ matched = matched.filter((t) => t.status.status.toLowerCase() === resolved.toLowerCase());
1966
+ } else {
1967
+ matched = matched.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase());
1968
+ }
1969
+ }
1970
+ return matched.map(summarize);
1971
+ }
1972
+
1724
1973
  // src/index.ts
1725
1974
  var require2 = createRequire(import.meta.url);
1726
1975
  var { version } = require2("../package.json");
@@ -1739,6 +1988,20 @@ program.command("init").description("Set up cu for the first time").action(
1739
1988
  await runInitCommand();
1740
1989
  })
1741
1990
  );
1991
+ program.command("auth").description("Validate API token and show current user").option("--json", "Force JSON output even in terminal").action(
1992
+ wrapAction(async (opts) => {
1993
+ const config = loadConfig();
1994
+ const result = await checkAuth(config);
1995
+ if (opts.json || !isTTY()) {
1996
+ console.log(JSON.stringify(result, null, 2));
1997
+ } else if (result.authenticated && result.user) {
1998
+ console.log(`Authenticated as @${result.user.username} (id: ${result.user.id})`);
1999
+ } else {
2000
+ console.error(`Authentication failed: ${result.error ?? "unknown error"}`);
2001
+ process.exit(1);
2002
+ }
2003
+ })
2004
+ );
1742
2005
  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("--json", "Force JSON output even in terminal").action(
1743
2006
  wrapAction(async (opts) => {
1744
2007
  const config = loadConfig();
@@ -1774,21 +2037,11 @@ program.command("task <taskId>").description("Get task details").option("--json"
1774
2037
  } else if (!isTTY()) {
1775
2038
  console.log(formatTaskDetailMarkdown(result));
1776
2039
  } else {
1777
- const lines = [
1778
- `ID: ${result.id}`,
1779
- `Name: ${result.name}`,
1780
- `Status: ${result.status?.status ?? "unknown"}`,
1781
- `Type: ${(result.custom_item_id ?? 0) !== 0 ? "initiative" : "task"}`,
1782
- `List: ${result.list?.name ?? "unknown"}`,
1783
- `URL: ${result.url}`,
1784
- ...result.parent ? [`Parent: ${result.parent}`] : [],
1785
- ...result.description ? ["", result.description] : []
1786
- ];
1787
- console.log(lines.join("\n"));
2040
+ console.log(formatTaskDetail(result));
1788
2041
  }
1789
2042
  })
1790
2043
  );
1791
- 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("--assignee <userId>", "Add assignee by user ID").action(
2044
+ 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("--assignee <userId>", "Add assignee by user ID").option("--json", "Force JSON output even in terminal").action(
1792
2045
  wrapAction(async (taskId, opts) => {
1793
2046
  const config = loadConfig();
1794
2047
  const payload = buildUpdatePayload(opts);
@@ -1800,7 +2053,7 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
1800
2053
  }
1801
2054
  })
1802
2055
  );
1803
- 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").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").option("--tags <tags>", "Comma-separated tag names").action(
2056
+ 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").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").option("--tags <tags>", "Comma-separated tag names").option("--json", "Force JSON output even in terminal").action(
1804
2057
  wrapAction(async (opts) => {
1805
2058
  const config = loadConfig();
1806
2059
  const result = await createTask(config, opts);
@@ -1817,6 +2070,12 @@ program.command("sprint").description("List my tasks in the current active sprin
1817
2070
  await runSprintCommand(config, opts);
1818
2071
  })
1819
2072
  );
2073
+ program.command("sprints").description("List all sprints in sprint folders").option("--space <nameOrId>", "Filter by space (partial name or ID)").option("--json", "Force JSON output even in terminal").action(
2074
+ wrapAction(async (opts) => {
2075
+ const config = loadConfig();
2076
+ await listSprints(config, opts);
2077
+ })
2078
+ );
1820
2079
  program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--json", "Force JSON output even in terminal").action(
1821
2080
  wrapAction(async (taskId, opts) => {
1822
2081
  const config = loadConfig();
@@ -1842,6 +2101,13 @@ program.command("comments <taskId>").description("List comments on a task").opti
1842
2101
  printComments(comments, opts.json ?? false);
1843
2102
  })
1844
2103
  );
2104
+ program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
2105
+ wrapAction(async (taskId, opts) => {
2106
+ const config = loadConfig();
2107
+ const result = await fetchActivity(config, taskId);
2108
+ printActivity(result, opts.json ?? false);
2109
+ })
2110
+ );
1845
2111
  program.command("lists <spaceId>").description("List all lists in a space (including lists inside folders)").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--json", "Force JSON output even in terminal").action(
1846
2112
  wrapAction(async (spaceId, opts) => {
1847
2113
  const config = loadConfig();
@@ -1879,6 +2145,13 @@ program.command("open <query>").description("Open a task in the browser by ID or
1879
2145
  await openTask(config, query, opts);
1880
2146
  })
1881
2147
  );
2148
+ program.command("search <query>").description("Search my tasks by name").option("--status <status>", "Filter by status").option("--json", "Force JSON output even in terminal").action(
2149
+ wrapAction(async (query, opts) => {
2150
+ const config = loadConfig();
2151
+ const tasks = await searchTasks(config, query, { status: opts.status });
2152
+ await printTasks(tasks, opts.json ?? false, config);
2153
+ })
2154
+ );
1882
2155
  program.command("summary").description("Daily standup summary: completed, in-progress, overdue").option("--hours <n>", "Completed-tasks lookback in hours", "24").option("--json", "Force JSON output even in terminal").action(
1883
2156
  wrapAction(async (opts) => {
1884
2157
  const config = loadConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
+ ".claude-plugin",
28
+ "skills",
27
29
  "README.md",
28
30
  "LICENSE"
29
31
  ],
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: clickup
3
+ description: 'Use when managing ClickUp tasks, initiatives, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, standup summaries, searching tasks, checking overdue items.'
4
+ ---
5
+
6
+ # ClickUp CLI (`cu`)
7
+
8
+ Reference for AI agents using the `cu` CLI tool. Covers task management, sprint tracking, initiatives, comments, and project workflows.
9
+
10
+ Keywords: ClickUp, task management, sprint, initiative, project management, agile, backlog, subtasks, standup, overdue, search
11
+
12
+ ## Setup
13
+
14
+ Config at `~/.config/cu/config.json` with `apiToken` and `teamId`. Run `cu init` to set up interactively.
15
+
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`
30
+
31
+ ## Commands
32
+
33
+ All commands support `--help` for full flag details.
34
+
35
+ ### Read
36
+
37
+ | Command | What it returns |
38
+ | -------------------------------------------------------------------------- | -------------------------------------------------- |
39
+ | `cu tasks [--status s] [--name q] [--list id] [--space id] [--json]` | My tasks (workspace-wide) |
40
+ | `cu initiatives [--status s] [--name q] [--list id] [--space id] [--json]` | My initiatives |
41
+ | `cu assigned [--include-closed] [--json]` | All my tasks grouped by status |
42
+ | `cu sprint [--status s] [--space nameOrId] [--json]` | Tasks in active sprint (auto-detected) |
43
+ | `cu sprints [--space nameOrId] [--json]` | List all sprints (marks active with \*) |
44
+ | `cu search <query> [--status s] [--json]` | Search my tasks by name (multi-word, fuzzy status) |
45
+ | `cu task <id> [--json]` | Single task details |
46
+ | `cu subtasks <id> [--json]` | Subtasks of a task or initiative |
47
+ | `cu comments <id> [--json]` | Comments on a task |
48
+ | `cu activity <id> [--json]` | Task details + comment history combined |
49
+ | `cu inbox [--days n] [--json]` | Tasks updated in last n days (default 30) |
50
+ | `cu summary [--hours n] [--json]` | Standup helper: completed, in-progress, overdue |
51
+ | `cu overdue [--json]` | Tasks past their due date |
52
+ | `cu spaces [--name partial] [--my] [--json]` | List/filter workspace spaces |
53
+ | `cu lists <spaceId> [--name partial] [--json]` | Lists in a space (including folder lists) |
54
+ | `cu open <query> [--json]` | Open task in browser by ID or name |
55
+ | `cu auth [--json]` | Check authentication status |
56
+
57
+ ### Write
58
+
59
+ | Command | What it does |
60
+ | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
61
+ | `cu update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--assignee id] [--json]` | Update task fields |
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) |
63
+ | `cu comment <id> -m text` | Post comment on task |
64
+ | `cu assign <id> [--to userId\|me] [--remove userId\|me] [--json]` | Assign/unassign users |
65
+ | `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
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
96
+
97
+ ```bash
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
+ ```
103
+
104
+ ### Find tasks
105
+
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
+ ```
116
+
117
+ ### Make changes
118
+
119
+ ```bash
120
+ cu update abc123def -s "done"
121
+ cu update abc123def --priority high --due-date 2025-03-15
122
+ cu create -n "Fix the thing" -p abc123def
123
+ cu create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
124
+ cu comment abc123def -m "Completed in PR #42"
125
+ cu assign abc123def --to me
126
+ ```
127
+
128
+ ### Discover workspace structure
129
+
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
+ ```
136
+
137
+ ### Standup
138
+
139
+ ```bash
140
+ cu summary # completed / in progress / overdue
141
+ cu summary --hours 48 # wider window
142
+ ```