@krodak/clickup-cli 1.14.1 → 1.15.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clickup-cli",
3
3
  "description": "ClickUp CLI skills for managing tasks, sprints, comments, checklists, custom fields, tags, and time tracking via the cup command",
4
- "version": "1.14.1",
4
+ "version": "1.15.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -281,6 +281,18 @@ var ClickUpClient = class {
281
281
  async getListViews(listId) {
282
282
  return this.request(`/list/${listId}/view`);
283
283
  }
284
+ async getSpaceViews(spaceId) {
285
+ const data = await this.request(`/space/${spaceId}/view`);
286
+ return readCollectionField(data, "views", "views");
287
+ }
288
+ async getFolderViews(folderId) {
289
+ const data = await this.request(`/folder/${folderId}/view`);
290
+ return readCollectionField(data, "views", "views");
291
+ }
292
+ async getWorkspaceViews(teamId) {
293
+ const data = await this.request(`/team/${teamId}/view`);
294
+ return readCollectionField(data, "views", "views");
295
+ }
284
296
  async getViewTasks(viewId) {
285
297
  return this.paginate((page) => `/view/${viewId}/task?page=${page}`);
286
298
  }
@@ -510,6 +522,9 @@ var ClickUpClient = class {
510
522
  const params = new URLSearchParams();
511
523
  if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
512
524
  if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
525
+ if (opts?.spaceId) params.set("space_id", opts.spaceId);
526
+ if (opts?.listId) params.set("list_id", opts.listId);
527
+ if (opts?.assigneeId) params.set("assignee", opts.assigneeId);
513
528
  const query = params.toString();
514
529
  const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
515
530
  const data = await this.request(url);
@@ -1225,7 +1240,7 @@ function formatTasksMarkdown(tasks) {
1225
1240
  }
1226
1241
  function formatCommentsMarkdown(comments) {
1227
1242
  if (comments.length === 0) return "No comments found.";
1228
- return comments.map((c) => `**${c.user}** (${c.date})
1243
+ return comments.map((c) => `**${c.user}** (${formatDateISO(c.date)})
1229
1244
 
1230
1245
  ${c.text}`).join("\n\n---\n\n");
1231
1246
  }
@@ -1279,6 +1294,10 @@ function formatTaskDetailMarkdown(task) {
1279
1294
  task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
1280
1295
  ],
1281
1296
  ["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
1297
+ [
1298
+ "Lists",
1299
+ task.locations && task.locations.length > 0 ? task.locations.map((l) => l.name).join(", ") : void 0
1300
+ ],
1282
1301
  ["Created", task.date_created ? formatDateISO(task.date_created) : void 0],
1283
1302
  ["Updated", task.date_updated ? formatDateISO(task.date_updated) : void 0]
1284
1303
  ];
@@ -1420,6 +1439,7 @@ function formatTaskDetail(task) {
1420
1439
  ["Estimate", task.time_estimate ? formatDuration(task.time_estimate) : void 0],
1421
1440
  ["Tracked", task.time_spent ? formatDuration(task.time_spent) : void 0],
1422
1441
  ["Tags", task.tags?.length ? task.tags.map((t) => t.name).join(", ") : void 0],
1442
+ ["Lists", task.locations?.length ? task.locations.map((l) => l.name).join(", ") : void 0],
1423
1443
  ["Parent", task.parent || void 0],
1424
1444
  ["URL", task.url]
1425
1445
  ];
@@ -1711,6 +1731,9 @@ function buildUpdatePayload(opts) {
1711
1731
  if (opts.archive && opts.unarchive) {
1712
1732
  throw new Error("Cannot use --archive and --unarchive together");
1713
1733
  }
1734
+ if (opts.parent !== void 0 && opts.detach) {
1735
+ throw new Error("Cannot use --parent and --detach together");
1736
+ }
1714
1737
  const payload = {};
1715
1738
  if (opts.name !== void 0) {
1716
1739
  if (!opts.name.trim()) throw new Error("Task name cannot be empty");
@@ -1720,8 +1743,16 @@ function buildUpdatePayload(opts) {
1720
1743
  if (opts.status !== void 0) payload.status = opts.status;
1721
1744
  if (opts.priority !== void 0) payload.priority = parsePriority(opts.priority);
1722
1745
  if (opts.dueDate !== void 0) {
1723
- payload.due_date = parseDueDate(opts.dueDate);
1724
- payload.due_date_time = false;
1746
+ if (opts.dueDate === "none" || opts.dueDate === "clear") {
1747
+ payload.due_date = null;
1748
+ } else {
1749
+ payload.due_date = parseDueDate(opts.dueDate);
1750
+ payload.due_date_time = false;
1751
+ }
1752
+ }
1753
+ if (opts.startDate !== void 0) {
1754
+ payload.start_date = parseDueDate(opts.startDate);
1755
+ payload.start_date_time = false;
1725
1756
  }
1726
1757
  if (opts.assignee !== void 0) {
1727
1758
  payload.assignees = { add: [parseAssigneeId(opts.assignee)] };
@@ -1729,13 +1760,17 @@ function buildUpdatePayload(opts) {
1729
1760
  if (opts.timeEstimate !== void 0) {
1730
1761
  payload.time_estimate = parseTimeEstimate(opts.timeEstimate);
1731
1762
  }
1732
- if (opts.parent !== void 0) payload.parent = opts.parent;
1763
+ if (opts.detach) {
1764
+ payload.parent = null;
1765
+ } else if (opts.parent !== void 0) {
1766
+ payload.parent = opts.parent;
1767
+ }
1733
1768
  if (opts.archive) payload.archived = true;
1734
1769
  if (opts.unarchive) payload.archived = false;
1735
1770
  return payload;
1736
1771
  }
1737
1772
  function hasUpdateFields(options) {
1738
- return options.name !== void 0 || options.description !== void 0 || options.markdown_content !== void 0 || options.status !== void 0 || options.priority !== void 0 || options.due_date !== void 0 || options.time_estimate !== void 0 || options.assignees !== void 0 || options.parent !== void 0 || options.archived !== void 0;
1773
+ return options.name !== void 0 || options.description !== void 0 || options.markdown_content !== void 0 || options.status !== void 0 || options.priority !== void 0 || options.due_date !== void 0 || options.start_date !== void 0 || options.time_estimate !== void 0 || options.assignees !== void 0 || options.parent !== void 0 || options.archived !== void 0;
1739
1774
  }
1740
1775
  async function resolveStatus(client, taskId, statusInput) {
1741
1776
  const task = await client.getTask(taskId);
@@ -1754,7 +1789,7 @@ async function resolveStatus(client, taskId, statusInput) {
1754
1789
  async function updateTask(config, taskId, options) {
1755
1790
  if (!hasUpdateFields(options))
1756
1791
  throw new Error(
1757
- "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --parent, --archive, --unarchive"
1792
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --parent, --detach, --archive, --unarchive"
1758
1793
  );
1759
1794
  const client = new ClickUpClient(config);
1760
1795
  if (options.status !== void 0) {
@@ -1793,6 +1828,10 @@ async function createTask(config, options) {
1793
1828
  payload.due_date = parseDueDate(options.dueDate);
1794
1829
  payload.due_date_time = false;
1795
1830
  }
1831
+ if (options.startDate !== void 0) {
1832
+ payload.start_date = parseDueDate(options.startDate);
1833
+ payload.start_date_time = false;
1834
+ }
1796
1835
  if (options.assignee !== void 0) {
1797
1836
  payload.assignees = [parseAssigneeId(options.assignee)];
1798
1837
  }
@@ -2760,9 +2799,11 @@ var commandMetadata = [
2760
2799
  "--status",
2761
2800
  "--priority",
2762
2801
  "--due-date",
2802
+ "--start-date",
2763
2803
  "--time-estimate",
2764
2804
  "--assignee",
2765
2805
  "--parent",
2806
+ "--detach",
2766
2807
  "--archive",
2767
2808
  "--unarchive",
2768
2809
  "--field",
@@ -2786,6 +2827,7 @@ var commandMetadata = [
2786
2827
  "--status",
2787
2828
  "--priority",
2788
2829
  "--due-date",
2830
+ "--start-date",
2789
2831
  "--assignee",
2790
2832
  "--tags",
2791
2833
  "--custom-item-id",
@@ -3290,7 +3332,7 @@ var commandMetadata = [
3290
3332
  {
3291
3333
  name: "goal-create",
3292
3334
  description: "Create a goal",
3293
- flags: ["-d", "--description", "--color", "--json"],
3335
+ flags: ["-d", "--description", "--color", "--due-date", "--json"],
3294
3336
  quickReference: [
3295
3337
  { section: "write", usage: "goal-create <name>", description: "Create a goal" }
3296
3338
  ]
@@ -3403,10 +3445,14 @@ var commandMetadata = [
3403
3445
  },
3404
3446
  {
3405
3447
  name: "views",
3406
- description: "List views on a list",
3407
- flags: ["--json"],
3448
+ description: "List views on a list, space, folder, or workspace",
3449
+ flags: ["--space", "--folder", "--workspace", "--json"],
3408
3450
  quickReference: [
3409
- { section: "read", usage: "views <listId>", description: "List views on a list" }
3451
+ {
3452
+ section: "read",
3453
+ usage: "views <id>",
3454
+ description: "List views on a list, space, folder, or workspace"
3455
+ }
3410
3456
  ]
3411
3457
  },
3412
3458
  {
@@ -3674,10 +3720,12 @@ ${renderZshTopLevelCommands(name)}
3674
3720
  '(-d --description)'{-d,--description}'[New description]:text:' \\
3675
3721
  '(-s --status)'{-s,--status}'[New status]:status:(open "in progress" "in review" done closed)' \\
3676
3722
  '--priority[Priority level]:priority:(urgent high normal low)' \\
3677
- '--due-date[Due date]:date:' \\
3723
+ '--due-date[Due date (YYYY-MM-DD or "none" to clear)]:date:' \\
3724
+ '--start-date[Start date]:date:' \\
3678
3725
  '--time-estimate[Time estimate]:duration:' \\
3679
3726
  '--assignee[Add assignee]:user_id:' \\
3680
3727
  '--parent[Set parent task]:task_id:' \\
3728
+ '--detach[Remove parent task]' \\
3681
3729
  '--archive[Archive the task]' \\
3682
3730
  '--unarchive[Unarchive the task]' \\
3683
3731
  '--field[Set custom field]:field_name_and_value:' \\
@@ -3771,6 +3819,7 @@ ${renderZshTopLevelCommands(name)}
3771
3819
  '1:query:' \\
3772
3820
  '--status[Filter by status]:status:(open "in progress" "in review" done closed)' \\
3773
3821
  '--include-closed[Include done/closed tasks in search]' \\
3822
+ '--all[Search all workspace tasks, not just mine]' \\
3774
3823
  '--json[Force JSON output]'
3775
3824
  ;;
3776
3825
  summary)
@@ -3781,6 +3830,7 @@ ${renderZshTopLevelCommands(name)}
3781
3830
  overdue)
3782
3831
  _arguments \\
3783
3832
  '--include-closed[Include done/closed overdue tasks]' \\
3833
+ '--all[Check all workspace tasks, not just mine]' \\
3784
3834
  '--json[Force JSON output]'
3785
3835
  ;;
3786
3836
  assign)
@@ -4458,7 +4508,18 @@ async function moveTask(config, taskId, opts) {
4458
4508
  }
4459
4509
 
4460
4510
  // src/commands/field.ts
4461
- var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["text", "number", "drop_down", "checkbox", "date", "url", "email"]);
4511
+ var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
4512
+ "text",
4513
+ "short_text",
4514
+ "number",
4515
+ "currency",
4516
+ "phone",
4517
+ "drop_down",
4518
+ "checkbox",
4519
+ "date",
4520
+ "url",
4521
+ "email"
4522
+ ]);
4462
4523
  function findFieldByName(fields, name) {
4463
4524
  const lower = name.toLowerCase();
4464
4525
  const match = fields.find((f) => f.name.toLowerCase() === lower);
@@ -4672,9 +4733,12 @@ function formatChecklistsMarkdown(checklists) {
4672
4733
 
4673
4734
  // src/commands/comment-edit.ts
4674
4735
  async function editComment(config, commentId, text, resolved) {
4675
- if (!text.trim()) throw new Error("Comment text cannot be empty");
4736
+ if (text === void 0 && resolved === void 0) {
4737
+ throw new Error("Provide at least one of: --message, --resolved, --unresolved");
4738
+ }
4739
+ if (text !== void 0 && !text.trim()) throw new Error("Comment text cannot be empty");
4676
4740
  const client = new ClickUpClient(config);
4677
- await client.updateComment(commentId, text, resolved);
4741
+ await client.updateComment(commentId, text ?? "", resolved);
4678
4742
  }
4679
4743
 
4680
4744
  // src/commands/comment-delete.ts
@@ -4929,7 +4993,10 @@ async function listTimeEntries(config, opts) {
4929
4993
  return client.getTimeEntries(config.teamId, {
4930
4994
  startDate,
4931
4995
  endDate,
4932
- taskId: opts?.taskId
4996
+ taskId: opts?.taskId,
4997
+ spaceId: opts?.spaceId,
4998
+ listId: opts?.listId,
4999
+ assigneeId: opts?.assigneeId
4933
5000
  });
4934
5001
  }
4935
5002
  async function updateTimeEntry(config, timeEntryId, opts) {
@@ -4991,6 +5058,11 @@ function formatTimeEntriesMarkdown(entries) {
4991
5058
 
4992
5059
  // src/commands/tags.ts
4993
5060
  import chalk12 from "chalk";
5061
+ var TAG_COLUMNS = [
5062
+ { key: "name", label: "Name", maxWidth: 40 },
5063
+ { key: "fg", label: "FG", maxWidth: 10 },
5064
+ { key: "bg", label: "BG", maxWidth: 10 }
5065
+ ];
4994
5066
  async function listSpaceTags(config, spaceId) {
4995
5067
  const client = new ClickUpClient(config);
4996
5068
  return client.getSpaceTags(spaceId);
@@ -5004,20 +5076,34 @@ async function deleteSpaceTag(config, spaceId, tagName) {
5004
5076
  await client.deleteSpaceTag(spaceId, tagName);
5005
5077
  }
5006
5078
  async function updateSpaceTag(config, spaceId, tagName, updates) {
5079
+ if (!updates.name && !updates.fg && !updates.bg) {
5080
+ throw new Error("Provide at least one of: --name, --fg, --bg");
5081
+ }
5007
5082
  const client = new ClickUpClient(config);
5008
5083
  await client.updateSpaceTag(spaceId, tagName, {
5009
- name: updates.name,
5084
+ name: updates.name ?? tagName,
5010
5085
  tag_fg: updates.fg,
5011
5086
  tag_bg: updates.bg
5012
5087
  });
5013
5088
  }
5014
5089
  function formatTags(tags) {
5015
5090
  if (tags.length === 0) return "No tags found";
5091
+ if (isTTY()) {
5092
+ const rows = tags.map((t) => ({
5093
+ name: t.tag_bg ? chalk12.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk12.bold(t.name),
5094
+ fg: t.tag_fg || "",
5095
+ bg: t.tag_bg || ""
5096
+ }));
5097
+ return formatTable(rows, TAG_COLUMNS);
5098
+ }
5016
5099
  return tags.map((t) => chalk12.bold(t.name)).join(", ");
5017
5100
  }
5018
5101
  function formatTagsMarkdown(tags) {
5019
5102
  if (tags.length === 0) return "No tags found";
5020
- return tags.map((t) => `- ${t.name}`).join("\n");
5103
+ return tags.map((t) => {
5104
+ const colors = t.tag_bg ? ` (bg: ${t.tag_bg}${t.tag_fg ? `, fg: ${t.tag_fg}` : ""})` : "";
5105
+ return `- ${t.name}${colors}`;
5106
+ }).join("\n");
5021
5107
  }
5022
5108
 
5023
5109
  // src/commands/members.ts
@@ -5047,6 +5133,7 @@ function formatMembersMarkdown(members) {
5047
5133
  // src/commands/fields.ts
5048
5134
  import chalk13 from "chalk";
5049
5135
  var FIELD_COLUMNS = [
5136
+ { key: "id", label: "ID", maxWidth: 20 },
5050
5137
  { key: "name", label: "Name", maxWidth: 30 },
5051
5138
  { key: "type", label: "Type", maxWidth: 15 },
5052
5139
  {
@@ -5064,6 +5151,7 @@ async function listFields(config, listId) {
5064
5151
  function formatFields(fields) {
5065
5152
  if (fields.length === 0) return "No custom fields";
5066
5153
  const rows = fields.map((f) => ({
5154
+ id: f.id,
5067
5155
  name: f.name,
5068
5156
  type: f.type,
5069
5157
  required: f.required ? "yes" : "no",
@@ -5076,7 +5164,7 @@ function formatFieldsMarkdown(fields) {
5076
5164
  return fields.map((f) => {
5077
5165
  const options = f.type_config?.options?.map((o) => o.name).join(", ");
5078
5166
  const optStr = options ? ` [${options}]` : "";
5079
- return `- **${f.name}** (${f.type})${f.required ? " - required" : ""}${optStr}`;
5167
+ return `- **${f.name}** \`${f.id}\` (${f.type})${f.required ? " - required" : ""}${optStr}`;
5080
5168
  }).join("\n");
5081
5169
  }
5082
5170
 
@@ -5124,7 +5212,8 @@ var GOAL_COLUMNS = [
5124
5212
  { key: "id", label: "ID", maxWidth: 15 },
5125
5213
  { key: "name", label: "Name", maxWidth: 40 },
5126
5214
  { key: "progress", label: "Progress", maxWidth: 10, format: (v) => colorProgress(v) },
5127
- { key: "owner", label: "Owner", maxWidth: 20 }
5215
+ { key: "owner", label: "Owner", maxWidth: 20 },
5216
+ { key: "due_date", label: "Due", maxWidth: 12 }
5128
5217
  ];
5129
5218
  var KEY_RESULT_COLUMNS = [
5130
5219
  { key: "id", label: "ID", maxWidth: 15 },
@@ -5173,7 +5262,8 @@ function formatGoals(goals) {
5173
5262
  name: g.name,
5174
5263
  id: g.id,
5175
5264
  progress: `${Math.round(g.percent_completed * 100)}%`,
5176
- owner: g.owner ? `@${g.owner.username}` : ""
5265
+ owner: g.owner ? `@${g.owner.username}` : "",
5266
+ due_date: g.due_date ? formatDateISO(g.due_date) : ""
5177
5267
  }));
5178
5268
  return formatTable(rows, GOAL_COLUMNS);
5179
5269
  }
@@ -5182,7 +5272,8 @@ function formatGoalsMarkdown(goals) {
5182
5272
  return goals.map((g) => {
5183
5273
  const pct = Math.round(g.percent_completed * 100);
5184
5274
  const owner = g.owner ? ` - @${g.owner.username}` : "";
5185
- return `- **${g.name}** (${g.id}) - ${pct}%${owner}`;
5275
+ const due = g.due_date ? ` - due ${formatDateISO(g.due_date)}` : "";
5276
+ return `- **${g.name}** (${g.id}) - ${pct}%${owner}${due}`;
5186
5277
  }).join("\n");
5187
5278
  }
5188
5279
  function formatKeyResults(keyResults) {
@@ -5280,9 +5371,12 @@ async function createListFromTemplate(config, name, opts) {
5280
5371
 
5281
5372
  // src/commands/views.ts
5282
5373
  import chalk19 from "chalk";
5283
- async function listViews(config, listId) {
5374
+ async function listViews(config, id, container = "list") {
5284
5375
  const client = new ClickUpClient(config);
5285
- const data = await client.getListViews(listId);
5376
+ if (container === "space") return client.getSpaceViews(id);
5377
+ if (container === "folder") return client.getFolderViews(id);
5378
+ if (container === "workspace") return client.getWorkspaceViews(config.teamId);
5379
+ const data = await client.getListViews(id);
5286
5380
  return data.views;
5287
5381
  }
5288
5382
  function formatViews(views) {
@@ -5364,6 +5458,9 @@ var VALID_GROUP_BY_FIELDS2 = [
5364
5458
  "sprint"
5365
5459
  ];
5366
5460
  async function updateView(config, viewId, opts) {
5461
+ if (opts.name === void 0 && opts.groupBy === void 0) {
5462
+ throw new Error("Provide at least one of: --name, --group-by");
5463
+ }
5367
5464
  if (opts.name !== void 0 && !opts.name.trim()) {
5368
5465
  throw new Error("View name cannot be empty");
5369
5466
  }
@@ -5475,7 +5572,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5475
5572
  }
5476
5573
  })
5477
5574
  );
5478
- program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--archive", "Archive the task").option("--unarchive", "Unarchive the task").option("--field <nameAndValue...>", 'Set custom field: --field "Name" value').option("--json", "Force JSON output even in terminal").action(
5575
+ 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, or "none"/"clear" to remove)').option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--detach", "Remove parent task (promote subtask to top-level)").option("--archive", "Archive the task").option("--unarchive", "Unarchive the task").option("--field <nameAndValue...>", 'Set custom field: --field "Name" value').option("--json", "Force JSON output even in terminal").action(
5479
5576
  wrapAction(
5480
5577
  async (taskId, opts) => {
5481
5578
  const config = loadConfig(getProfileName());
@@ -5484,14 +5581,28 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5484
5581
  opts.assignee = String(await resolveAssigneeId(client, "me"));
5485
5582
  }
5486
5583
  const payload = buildUpdatePayload(opts);
5487
- const result = await updateTask(config, taskId, payload);
5488
- if (opts.field?.length) {
5489
- if (opts.field.length % 2 !== 0) {
5584
+ const hasFields = (opts.field?.length ?? 0) > 0;
5585
+ if (!hasFields && Object.keys(payload).length === 0) {
5586
+ throw new Error(
5587
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --parent, --archive, --unarchive, --field"
5588
+ );
5589
+ }
5590
+ let result;
5591
+ if (Object.keys(payload).length > 0) {
5592
+ result = await updateTask(config, taskId, payload);
5593
+ }
5594
+ if (hasFields) {
5595
+ if ((opts.field?.length ?? 0) % 2 !== 0) {
5490
5596
  throw new Error('--field requires pairs: --field "Name" value');
5491
5597
  }
5492
- for (let i = 0; i < opts.field.length; i += 2) {
5598
+ for (let i = 0; i < (opts.field?.length ?? 0); i += 2) {
5493
5599
  await setCustomField(config, taskId, { set: [opts.field[i], opts.field[i + 1]] });
5494
5600
  }
5601
+ if (!result) {
5602
+ const client = new ClickUpClient(config);
5603
+ const task = await client.getTask(taskId);
5604
+ result = { id: task.id, name: task.name };
5605
+ }
5495
5606
  }
5496
5607
  if (shouldOutputJson(opts.json ?? false)) {
5497
5608
  console.log(JSON.stringify(result, null, 2));
@@ -5501,7 +5612,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5501
5612
  }
5502
5613
  )
5503
5614
  );
5504
- program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--template <id>", "Create from a task template").option("--json", "Force JSON output even in terminal").action(
5615
+ program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--template <id>", "Create from a task template").option("--json", "Force JSON output even in terminal").action(
5505
5616
  wrapAction(async (opts) => {
5506
5617
  const config = loadConfig(getProfileName());
5507
5618
  if (opts.assignee === "me") {
@@ -5567,7 +5678,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5567
5678
  printComments(comments, opts.json ?? false);
5568
5679
  })
5569
5680
  );
5570
- program.command("comment-edit <commentId>").description("Edit an existing comment").requiredOption("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option("--json", "Force JSON output even in terminal").action(
5681
+ program.command("comment-edit <commentId>").description("Edit an existing comment").option("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option("--json", "Force JSON output even in terminal").action(
5571
5682
  wrapAction(
5572
5683
  async (commentId, opts) => {
5573
5684
  const config = loadConfig(getProfileName());
@@ -5969,22 +6080,30 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5969
6080
  }
5970
6081
  )
5971
6082
  );
5972
- timeCmd.command("list").description("List recent time entries (default: last 7 days)").option("--days <n>", "Number of days to look back", "7").option("--task <taskId>", "Filter by task ID").option("--json", "Force JSON output even in terminal").action(
5973
- wrapAction(async (opts) => {
5974
- const config = loadConfig(getProfileName());
5975
- const days = opts.days ? Number(opts.days) : 7;
5976
- if (!Number.isFinite(days) || days <= 0) {
5977
- throw new Error("--days must be a positive number");
5978
- }
5979
- const entries = await listTimeEntries(config, { days, taskId: opts.task });
5980
- if (shouldOutputJson(opts.json ?? false)) {
5981
- console.log(JSON.stringify(entries, null, 2));
5982
- } else if (isTTY()) {
5983
- console.log(formatTimeEntries(entries));
5984
- } else {
5985
- console.log(formatTimeEntriesMarkdown(entries));
6083
+ timeCmd.command("list").description("List recent time entries (default: last 7 days)").option("--days <n>", "Number of days to look back", "7").option("--task <taskId>", "Filter by task ID").option("--space <spaceId>", "Filter by space ID").option("--list <listId>", "Filter by list ID").option("--assignee <userId>", "Filter by assignee user ID").option("--json", "Force JSON output even in terminal").action(
6084
+ wrapAction(
6085
+ async (opts) => {
6086
+ const config = loadConfig(getProfileName());
6087
+ const days = opts.days ? Number(opts.days) : 7;
6088
+ if (!Number.isFinite(days) || days <= 0) {
6089
+ throw new Error("--days must be a positive number");
6090
+ }
6091
+ const entries = await listTimeEntries(config, {
6092
+ days,
6093
+ taskId: opts.task,
6094
+ spaceId: opts.space,
6095
+ listId: opts.list,
6096
+ assigneeId: opts.assignee
6097
+ });
6098
+ if (shouldOutputJson(opts.json ?? false)) {
6099
+ console.log(JSON.stringify(entries, null, 2));
6100
+ } else if (isTTY()) {
6101
+ console.log(formatTimeEntries(entries));
6102
+ } else {
6103
+ console.log(formatTimeEntriesMarkdown(entries));
6104
+ }
5986
6105
  }
5987
- })
6106
+ )
5988
6107
  );
5989
6108
  timeCmd.command("update <timeEntryId>").description("Update a time entry").option("-d, --description <text>", "New description").option("--duration <duration>", 'New duration (e.g. "2h", "30m")').option("--json", "Force JSON output even in terminal").action(
5990
6109
  wrapAction(
@@ -6165,13 +6284,14 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6165
6284
  }
6166
6285
  })
6167
6286
  );
6168
- program.command("goal-create <name>").description("Create a goal").option("-d, --description <text>", "Goal description").option("--color <hex>", "Goal color (hex)").option("--json", "Force JSON output even in terminal").action(
6287
+ program.command("goal-create <name>").description("Create a goal").option("-d, --description <text>", "Goal description").option("--color <hex>", "Goal color (hex)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--json", "Force JSON output even in terminal").action(
6169
6288
  wrapAction(
6170
6289
  async (name, opts) => {
6171
6290
  const config = loadConfig(getProfileName());
6172
6291
  const goal = await createGoal(config, name, {
6173
6292
  description: opts.description,
6174
- color: opts.color
6293
+ color: opts.color,
6294
+ dueDate: opts.dueDate
6175
6295
  });
6176
6296
  if (shouldOutputJson(opts.json ?? false)) {
6177
6297
  console.log(JSON.stringify(goal, null, 2));
@@ -6436,7 +6556,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6436
6556
  }
6437
6557
  })
6438
6558
  );
6439
- program.command("tag-update <spaceId> <tagName>").description("Update a tag in a space").requiredOption("--name <newName>", "New tag name").option("--fg <color>", "New foreground color (hex)").option("--bg <color>", "New background color (hex)").option("--json", "Force JSON output even in terminal").action(
6559
+ program.command("tag-update <spaceId> <tagName>").description("Update a tag in a space").option("--name <newName>", "New tag name").option("--fg <color>", "New foreground color (hex)").option("--bg <color>", "New background color (hex)").option("--json", "Force JSON output even in terminal").action(
6440
6560
  wrapAction(
6441
6561
  async (spaceId, tagName, opts) => {
6442
6562
  const config = loadConfig(getProfileName());
@@ -6445,16 +6565,15 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6445
6565
  fg: opts.fg,
6446
6566
  bg: opts.bg
6447
6567
  });
6568
+ const newName = opts.name ?? tagName;
6448
6569
  if (shouldOutputJson(opts.json ?? false)) {
6449
6570
  console.log(
6450
- JSON.stringify(
6451
- { success: true, spaceId, oldName: tagName, newName: opts.name },
6452
- null,
6453
- 2
6454
- )
6571
+ JSON.stringify({ success: true, spaceId, oldName: tagName, newName }, null, 2)
6455
6572
  );
6456
- } else {
6573
+ } else if (opts.name && opts.name !== tagName) {
6457
6574
  console.log(`Renamed tag "${tagName}" to "${opts.name}" in space ${spaceId}`);
6575
+ } else {
6576
+ console.log(`Updated tag "${tagName}" in space ${spaceId}`);
6458
6577
  }
6459
6578
  }
6460
6579
  )
@@ -6524,18 +6643,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6524
6643
  }
6525
6644
  )
6526
6645
  );
6527
- program.command("views <listId>").description("List views on a list").option("--json", "Force JSON output even in terminal").action(
6528
- wrapAction(async (listId, opts) => {
6529
- const config = loadConfig(getProfileName());
6530
- const views = await listViews(config, listId);
6531
- if (shouldOutputJson(opts.json ?? false)) {
6532
- console.log(JSON.stringify(views, null, 2));
6533
- } else if (isTTY()) {
6534
- console.log(formatViews(views));
6535
- } else {
6536
- console.log(formatViewsMarkdown(views));
6646
+ program.command("views <id>").description("List views on a list, space, folder, or workspace").option("--space", "Treat <id> as a space ID").option("--folder", "Treat <id> as a folder ID").option("--workspace", "List workspace-level views (ignores <id>)").option("--json", "Force JSON output even in terminal").action(
6647
+ wrapAction(
6648
+ async (id, opts) => {
6649
+ const config = loadConfig(getProfileName());
6650
+ const container = opts.workspace ? "workspace" : opts.space ? "space" : opts.folder ? "folder" : "list";
6651
+ const views = await listViews(config, id, container);
6652
+ if (shouldOutputJson(opts.json ?? false)) {
6653
+ console.log(JSON.stringify(views, null, 2));
6654
+ } else if (isTTY()) {
6655
+ console.log(formatViews(views));
6656
+ } else {
6657
+ console.log(formatViewsMarkdown(views));
6658
+ }
6537
6659
  }
6538
- })
6660
+ )
6539
6661
  );
6540
6662
  program.command("view <viewId>").description("Get view details").option("--json", "Force JSON output even in terminal").action(
6541
6663
  wrapAction(async (viewId, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -69,66 +69,66 @@ All commands support `--help` for full flag details. All commands support `--jso
69
69
 
70
70
  ### Write
71
71
 
72
- | Command | What it does |
73
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
74
- | `cup create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--template id]` | Create task |
75
- | `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d] [--time-estimate t] [--assignee id\|me] [--parent id] [--archive] [--unarchive] [--field "Name" val]` | Update task fields (including custom fields) |
76
- | `cup comment <id> -m text [--notify-all]` | Post comment on task |
77
- | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment |
78
- | `cup comment-delete <commentId>` | Delete a comment |
79
- | `cup replies <commentId>` | List threaded replies |
80
- | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
81
- | `cup assign <id> [--to userId\|me] [--remove userId\|me]` | Assign/unassign users |
82
- | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
83
- | `cup move <id> [--to listId] [--remove listId]` | Add/remove task from lists |
84
- | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
85
- | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
86
- | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
87
- | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
88
- | `cup attach <taskId> <filePath>` | Upload file attachment |
89
- | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
90
- | `cup duplicate <taskId>` | Duplicate a task |
91
- | `cup bulk status <status> <taskIds...>` | Bulk update status |
92
- | `cup checklist view <id>` | View checklists on a task |
93
- | `cup checklist create <id> <name>` | Create a checklist |
94
- | `cup checklist delete <checklistId>` | Delete a checklist |
95
- | `cup checklist add-item <checklistId> <name>` | Add item to checklist |
96
- | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id]` | Edit checklist item |
97
- | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
98
- | `cup time start <taskId> [-d desc]` | Start timer |
99
- | `cup time stop` | Stop running timer |
100
- | `cup time status` | Show running timer |
101
- | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
102
- | `cup time list [--days n] [--task id]` | List recent time entries |
103
- | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
104
- | `cup time delete <timeEntryId>` | Delete time entry |
105
- | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
106
- | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
107
- | `cup goal-delete <goalId>` | Delete a goal |
108
- | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
109
- | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
110
- | `cup key-result-delete <keyResultId>` | Delete key result |
111
- | `cup doc-create <title> [-c content]` | Create a doc |
112
- | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
113
- | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
114
- | `cup doc-delete <docId>` | Delete a doc |
115
- | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
116
- | `cup space-create <name>` | Create a space |
117
- | `cup list-create <spaceId> <name> [--folder folderId]` | Create a list in a space or folder |
118
- | `cup folder-create <spaceId> <name>` | Create a folder in a space |
119
- | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
120
- | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
121
- | `cup tag-delete <spaceId> <name>` | Delete space tag |
122
- | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
123
- | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
124
- | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
125
- | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
126
- | `cup profile list [--json]` | List all profiles |
127
- | `cup profile add <name>` | Add a new profile (interactive) |
128
- | `cup profile remove <name>` | Remove a profile |
129
- | `cup profile use <name>` | Set the default profile |
130
- | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
131
- | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
72
+ | Command | What it does |
73
+ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
74
+ | `cup create -n name [-l listId] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--start-date d] [--time-estimate t] [--assignee id\|me] [--tags t] [--custom-item-id n] [--template id]` | Create task |
75
+ | `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d\|none] [--start-date d] [--time-estimate t] [--assignee id\|me] [--parent id] [--detach] [--archive] [--unarchive] [--field "Name" val]` | Update task fields (including custom fields) |
76
+ | `cup comment <id> -m text [--notify-all]` | Post comment on task |
77
+ | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment |
78
+ | `cup comment-delete <commentId>` | Delete a comment |
79
+ | `cup replies <commentId>` | List threaded replies |
80
+ | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
81
+ | `cup assign <id> [--to userId\|me] [--remove userId\|me]` | Assign/unassign users |
82
+ | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
83
+ | `cup move <id> [--to listId] [--remove listId]` | Add/remove task from lists |
84
+ | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
85
+ | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
86
+ | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
87
+ | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
88
+ | `cup attach <taskId> <filePath>` | Upload file attachment |
89
+ | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
90
+ | `cup duplicate <taskId>` | Duplicate a task |
91
+ | `cup bulk status <status> <taskIds...>` | Bulk update status |
92
+ | `cup checklist view <id>` | View checklists on a task |
93
+ | `cup checklist create <id> <name>` | Create a checklist |
94
+ | `cup checklist delete <checklistId>` | Delete a checklist |
95
+ | `cup checklist add-item <checklistId> <name>` | Add item to checklist |
96
+ | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id]` | Edit checklist item |
97
+ | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
98
+ | `cup time start <taskId> [-d desc]` | Start timer |
99
+ | `cup time stop` | Stop running timer |
100
+ | `cup time status` | Show running timer |
101
+ | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
102
+ | `cup time list [--days n] [--task id]` | List recent time entries |
103
+ | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
104
+ | `cup time delete <timeEntryId>` | Delete time entry |
105
+ | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
106
+ | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
107
+ | `cup goal-delete <goalId>` | Delete a goal |
108
+ | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
109
+ | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
110
+ | `cup key-result-delete <keyResultId>` | Delete key result |
111
+ | `cup doc-create <title> [-c content]` | Create a doc |
112
+ | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
113
+ | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
114
+ | `cup doc-delete <docId>` | Delete a doc |
115
+ | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
116
+ | `cup space-create <name>` | Create a space |
117
+ | `cup list-create <spaceId> <name> [--folder folderId]` | Create a list in a space or folder |
118
+ | `cup folder-create <spaceId> <name>` | Create a folder in a space |
119
+ | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
120
+ | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
121
+ | `cup tag-delete <spaceId> <name>` | Delete space tag |
122
+ | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
123
+ | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
124
+ | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
125
+ | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
126
+ | `cup profile list [--json]` | List all profiles |
127
+ | `cup profile add <name>` | Add a new profile (interactive) |
128
+ | `cup profile remove <name>` | Remove a profile |
129
+ | `cup profile use <name>` | Set the default profile |
130
+ | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
131
+ | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
132
132
 
133
133
  ## Global Flags
134
134