@krodak/clickup-cli 1.21.0 → 1.22.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.21.0",
4
+ "version": "1.22.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -286,8 +286,10 @@ var ClickUpClient = class {
286
286
  body: JSON.stringify({ name, multiple_assignees: true })
287
287
  });
288
288
  }
289
- async getSpaces(teamId) {
290
- const data = await this.request(`/team/${teamId}/space?archived=false`);
289
+ async getSpaces(teamId, archived = false) {
290
+ const data = await this.request(
291
+ `/team/${teamId}/space?archived=${archived ? "true" : "false"}`
292
+ );
291
293
  return readCollectionField(data, "spaces", "spaces");
292
294
  }
293
295
  async getCustomTaskTypes(teamId) {
@@ -324,18 +326,22 @@ var ClickUpClient = class {
324
326
  body: JSON.stringify({ name })
325
327
  });
326
328
  }
327
- async getLists(spaceId) {
328
- const data = await this.request(`/space/${spaceId}/list?archived=false`);
329
+ async getLists(spaceId, archived = false) {
330
+ const data = await this.request(
331
+ `/space/${spaceId}/list?archived=${archived ? "true" : "false"}`
332
+ );
329
333
  return readCollectionField(data, "lists", "space lists");
330
334
  }
331
- async getFolders(spaceId) {
335
+ async getFolders(spaceId, archived = false) {
332
336
  const data = await this.request(
333
- `/space/${spaceId}/folder?archived=false`
337
+ `/space/${spaceId}/folder?archived=${archived ? "true" : "false"}`
334
338
  );
335
339
  return readCollectionField(data, "folders", "space folders");
336
340
  }
337
- async getFolderLists(folderId) {
338
- const data = await this.request(`/folder/${folderId}/list?archived=false`);
341
+ async getFolderLists(folderId, archived = false) {
342
+ const data = await this.request(
343
+ `/folder/${folderId}/list?archived=${archived ? "true" : "false"}`
344
+ );
339
345
  return readCollectionField(data, "lists", "folder lists");
340
346
  }
341
347
  async getListViews(listId) {
@@ -1945,10 +1951,16 @@ function buildUpdatePayload(opts, timezone) {
1945
1951
  }
1946
1952
  if (opts.archive) payload.archived = true;
1947
1953
  if (opts.unarchive) payload.archived = false;
1954
+ if (opts.type !== void 0) {
1955
+ const num = Number(opts.type);
1956
+ if (Number.isInteger(num) && num >= 0) {
1957
+ payload.custom_item_id = num;
1958
+ }
1959
+ }
1948
1960
  return payload;
1949
1961
  }
1950
1962
  function hasUpdateFields(options) {
1951
- 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;
1963
+ 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 || options.custom_item_id !== void 0;
1952
1964
  }
1953
1965
  async function resolveStatus(client, taskId, statusInput) {
1954
1966
  const task = await client.getTask(taskId);
@@ -1964,16 +1976,29 @@ async function resolveStatus(client, taskId, statusInput) {
1964
1976
  }
1965
1977
  return matched;
1966
1978
  }
1967
- async function updateTask(config, taskId, options) {
1968
- if (!hasUpdateFields(options))
1979
+ async function resolveTaskType2(client, teamId, typeInput) {
1980
+ const types = await client.getCustomTaskTypes(teamId);
1981
+ const lower = typeInput.toLowerCase();
1982
+ const match = types.find((t) => t.name.toLowerCase() === lower);
1983
+ if (!match) {
1984
+ const available = types.map((t) => `${t.name} (${String(t.id)})`).join(", ");
1985
+ throw new Error(`No matching task type for "${typeInput}". Available: ${available}`);
1986
+ }
1987
+ return match.id;
1988
+ }
1989
+ async function updateTask(config, taskId, options, typeInput) {
1990
+ if (!hasUpdateFields(options) && typeInput === void 0)
1969
1991
  throw new Error(
1970
- "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --parent, --detach, --archive, --unarchive"
1992
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --parent, --detach, --archive, --unarchive, --type"
1971
1993
  );
1972
1994
  const client = new ClickUpClient(config);
1973
1995
  const resolved = { ...options };
1974
1996
  if (resolved.status !== void 0) {
1975
1997
  resolved.status = await resolveStatus(client, taskId, resolved.status);
1976
1998
  }
1999
+ if (resolved.custom_item_id === void 0 && typeInput !== void 0) {
2000
+ resolved.custom_item_id = await resolveTaskType2(client, config.teamId, typeInput);
2001
+ }
1977
2002
  const task = await client.updateTask(taskId, resolved);
1978
2003
  return { id: task.id, name: task.name };
1979
2004
  }
@@ -2502,12 +2527,14 @@ function printComments(comments, forceJson) {
2502
2527
  async function fetchLists(config, spaceId, opts = {}) {
2503
2528
  const client = new ClickUpClient(config);
2504
2529
  const results = [];
2505
- const folderlessLists = await client.getLists(spaceId);
2530
+ const folderlessLists = await client.getLists(spaceId, opts.archived);
2506
2531
  for (const list of folderlessLists) {
2507
2532
  results.push({ id: list.id, name: list.name, folder: "(none)" });
2508
2533
  }
2509
- const folders = await client.getFolders(spaceId);
2510
- const folderListArrays = await Promise.all(folders.map((f) => client.getFolderLists(f.id)));
2534
+ const folders = await client.getFolders(spaceId, opts.archived);
2535
+ const folderListArrays = await Promise.all(
2536
+ folders.map((f) => client.getFolderLists(f.id, opts.archived))
2537
+ );
2511
2538
  for (let i = 0; i < folders.length; i++) {
2512
2539
  const folder = folders[i];
2513
2540
  for (const list of folderListArrays[i]) {
@@ -2637,7 +2664,7 @@ async function printInbox(tasks, forceJson, config) {
2637
2664
  // src/commands/spaces.ts
2638
2665
  async function listSpaces(config, opts) {
2639
2666
  const client = new ClickUpClient(config);
2640
- let spaces = await client.getSpaces(config.teamId);
2667
+ let spaces = await client.getSpaces(config.teamId, opts.archived);
2641
2668
  if (opts.name) {
2642
2669
  const lower = opts.name.toLowerCase();
2643
2670
  spaces = spaces.filter((s) => s.name.toLowerCase().includes(lower));
@@ -2949,6 +2976,9 @@ function configPath2() {
2949
2976
  }
2950
2977
 
2951
2978
  // src/commands/assign.ts
2979
+ function parseIdList(value) {
2980
+ return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
2981
+ }
2952
2982
  async function assignTask(config, taskId, opts) {
2953
2983
  if (!opts.to && !opts.remove) {
2954
2984
  throw new Error("Provide at least one of: --to, --remove");
@@ -2957,10 +2987,14 @@ async function assignTask(config, taskId, opts) {
2957
2987
  const add = [];
2958
2988
  const rem = [];
2959
2989
  if (opts.to) {
2960
- add.push(await resolveAssigneeId(client, opts.to));
2990
+ for (const value of parseIdList(opts.to)) {
2991
+ add.push(await resolveAssigneeId(client, value));
2992
+ }
2961
2993
  }
2962
2994
  if (opts.remove) {
2963
- rem.push(await resolveAssigneeId(client, opts.remove));
2995
+ for (const value of parseIdList(opts.remove)) {
2996
+ rem.push(await resolveAssigneeId(client, value));
2997
+ }
2964
2998
  }
2965
2999
  return client.updateTask(taskId, {
2966
3000
  assignees: {
@@ -3164,6 +3198,7 @@ var commandMetadata = [
3164
3198
  "--detach",
3165
3199
  "--archive",
3166
3200
  "--unarchive",
3201
+ "--type",
3167
3202
  "--field",
3168
3203
  "--json"
3169
3204
  ],
@@ -3294,7 +3329,7 @@ var commandMetadata = [
3294
3329
  {
3295
3330
  name: "lists",
3296
3331
  description: "List all lists in a space (including lists inside folders)",
3297
- flags: ["--name", "--json"],
3332
+ flags: ["--name", "--archived", "--json"],
3298
3333
  quickReference: [
3299
3334
  { section: "read", usage: "lists <spaceId>", description: "List all lists in a space" }
3300
3335
  ]
@@ -3302,7 +3337,7 @@ var commandMetadata = [
3302
3337
  {
3303
3338
  name: "spaces",
3304
3339
  description: "List spaces in your workspace",
3305
- flags: ["--name", "--my", "--json"],
3340
+ flags: ["--name", "--my", "--archived", "--json"],
3306
3341
  quickReference: [{ section: "read", usage: "spaces", description: "List spaces in workspace" }]
3307
3342
  },
3308
3343
  {
@@ -3627,7 +3662,7 @@ var commandMetadata = [
3627
3662
  {
3628
3663
  name: "folders",
3629
3664
  description: "List folders in a space (with their lists)",
3630
- flags: ["--name", "--json"],
3665
+ flags: ["--name", "--archived", "--json"],
3631
3666
  quickReference: [
3632
3667
  { section: "read", usage: "folders <spaceId>", description: "List folders in a space" }
3633
3668
  ]
@@ -3733,6 +3768,16 @@ var commandMetadata = [
3733
3768
  section: "write",
3734
3769
  usage: "bulk tag <tagName> <taskIds...>",
3735
3770
  description: "Bulk add/remove tag"
3771
+ },
3772
+ {
3773
+ section: "write",
3774
+ usage: "bulk priority <taskIds...>",
3775
+ description: "Bulk set priority on tasks"
3776
+ },
3777
+ {
3778
+ section: "write",
3779
+ usage: "bulk field <taskIds...>",
3780
+ description: "Bulk set a custom field value on tasks"
3736
3781
  }
3737
3782
  ]
3738
3783
  },
@@ -5219,7 +5264,11 @@ var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
5219
5264
  "checkbox",
5220
5265
  "date",
5221
5266
  "url",
5222
- "email"
5267
+ "email",
5268
+ "emoji",
5269
+ "manual_progress",
5270
+ "tasks",
5271
+ "users"
5223
5272
  ]);
5224
5273
  function findFieldByName(fields, name) {
5225
5274
  const lower = name.toLowerCase();
@@ -5284,6 +5333,30 @@ function parseFieldValue(field, rawValue) {
5284
5333
  throw new Error(`Value "${rawValue}" is not a valid date (use YYYY-MM-DD)`);
5285
5334
  return ms;
5286
5335
  }
5336
+ case "emoji": {
5337
+ const n = Number(rawValue);
5338
+ if (!Number.isFinite(n) || n < 0 || n > 5) {
5339
+ throw new Error("Rating value must be a number between 0 and 5");
5340
+ }
5341
+ return n;
5342
+ }
5343
+ case "manual_progress": {
5344
+ const n = Number(rawValue);
5345
+ if (!Number.isFinite(n) || n < 0 || n > 100) {
5346
+ throw new Error("Progress value must be a number between 0 and 100");
5347
+ }
5348
+ return { current: n };
5349
+ }
5350
+ case "tasks": {
5351
+ const ids = rawValue.split(",").map((s) => s.trim()).filter(Boolean);
5352
+ if (ids.length === 0) throw new Error("Provide at least one task ID (comma-separated)");
5353
+ return { add: ids };
5354
+ }
5355
+ case "users": {
5356
+ const ids = rawValue.split(",").map((s) => s.trim()).filter(Boolean);
5357
+ if (ids.length === 0) throw new Error("Provide at least one user ID (comma-separated)");
5358
+ return { add: ids.map(Number) };
5359
+ }
5287
5360
  default:
5288
5361
  return rawValue;
5289
5362
  }
@@ -5668,9 +5741,9 @@ async function deleteDocPage(config, docId, pageId) {
5668
5741
 
5669
5742
  // src/commands/folders.ts
5670
5743
  import chalk12 from "chalk";
5671
- async function listFolders(config, spaceId, nameFilter) {
5744
+ async function listFolders(config, spaceId, nameFilter, archived) {
5672
5745
  const client = new ClickUpClient(config);
5673
- const folders = await client.getFolders(spaceId);
5746
+ const folders = await client.getFolders(spaceId, archived);
5674
5747
  let filtered = folders;
5675
5748
  if (nameFilter) {
5676
5749
  const lower = nameFilter.toLowerCase();
@@ -5932,66 +6005,98 @@ async function duplicateTask(config, taskId) {
5932
6005
  }
5933
6006
 
5934
6007
  // src/commands/bulk.ts
5935
- async function bulkUpdateStatus(config, taskIds, status) {
5936
- const client = new ClickUpClient(config);
6008
+ var BULK_CONCURRENCY = 5;
6009
+ async function runInBatches(items, concurrency, fn) {
6010
+ const results = [];
6011
+ for (let i = 0; i < items.length; i += concurrency) {
6012
+ const batch = items.slice(i, i + concurrency);
6013
+ const batchResults = await Promise.allSettled(batch.map(fn));
6014
+ batchResults.forEach((res, idx) => {
6015
+ const item = batch[idx];
6016
+ if (res.status === "fulfilled") {
6017
+ results.push({ item, result: res.value });
6018
+ } else {
6019
+ results.push({
6020
+ item,
6021
+ error: res.reason instanceof Error ? res.reason : new Error(String(res.reason))
6022
+ });
6023
+ }
6024
+ });
6025
+ }
6026
+ return results;
6027
+ }
6028
+ function toBulkResult(outcomes) {
5937
6029
  const failed = [];
5938
- for (const id of taskIds) {
5939
- try {
5940
- await client.updateTask(id, { status });
5941
- } catch (err) {
5942
- const reason = err instanceof Error ? err.message : String(err);
5943
- failed.push({ id, reason });
6030
+ for (const outcome of outcomes) {
6031
+ if (outcome.error) {
6032
+ failed.push({ id: outcome.item, reason: outcome.error.message });
5944
6033
  }
5945
6034
  }
5946
- return { updated: taskIds.length - failed.length, failed };
6035
+ return { updated: outcomes.length - failed.length, failed };
6036
+ }
6037
+ async function bulkUpdateStatus(config, taskIds, status) {
6038
+ const client = new ClickUpClient(config);
6039
+ const outcomes = await runInBatches(
6040
+ taskIds,
6041
+ BULK_CONCURRENCY,
6042
+ (id) => client.updateTask(id, { status })
6043
+ );
6044
+ return toBulkResult(outcomes);
5947
6045
  }
5948
6046
  async function bulkAssign(config, userIdOrMe, taskIds, action) {
5949
6047
  const client = new ClickUpClient(config);
5950
6048
  const numericId = await resolveAssigneeId(client, userIdOrMe);
5951
- const failed = [];
5952
- for (const id of taskIds) {
5953
- try {
5954
- await client.updateTask(id, {
5955
- assignees: action === "add" ? { add: [numericId] } : { rem: [numericId] }
5956
- });
5957
- } catch (err) {
5958
- const reason = err instanceof Error ? err.message : String(err);
5959
- failed.push({ id, reason });
5960
- }
5961
- }
5962
- return { updated: taskIds.length - failed.length, failed };
6049
+ const payload = action === "add" ? { assignees: { add: [numericId] } } : { assignees: { rem: [numericId] } };
6050
+ const outcomes = await runInBatches(
6051
+ taskIds,
6052
+ BULK_CONCURRENCY,
6053
+ (id) => client.updateTask(id, payload)
6054
+ );
6055
+ return toBulkResult(outcomes);
5963
6056
  }
5964
6057
  async function bulkDueDate(config, date, taskIds) {
5965
6058
  const client = new ClickUpClient(config);
5966
6059
  const timezone = await client.getUserTimezone();
5967
6060
  const payload = date === "none" || date === "clear" ? { due_date: null } : { due_date: parseDueDate(date, timezone), due_date_time: false };
5968
- const failed = [];
5969
- for (const id of taskIds) {
5970
- try {
5971
- await client.updateTask(id, payload);
5972
- } catch (err) {
5973
- const reason = err instanceof Error ? err.message : String(err);
5974
- failed.push({ id, reason });
5975
- }
5976
- }
5977
- return { updated: taskIds.length - failed.length, failed };
6061
+ const outcomes = await runInBatches(
6062
+ taskIds,
6063
+ BULK_CONCURRENCY,
6064
+ (id) => client.updateTask(id, payload)
6065
+ );
6066
+ return toBulkResult(outcomes);
5978
6067
  }
5979
6068
  async function bulkTag(config, tagName, taskIds, action) {
5980
6069
  const client = new ClickUpClient(config);
5981
- const failed = [];
5982
- for (const id of taskIds) {
5983
- try {
5984
- if (action === "add") {
5985
- await client.addTagToTask(id, tagName);
5986
- } else {
5987
- await client.removeTagFromTask(id, tagName);
5988
- }
5989
- } catch (err) {
5990
- const reason = err instanceof Error ? err.message : String(err);
5991
- failed.push({ id, reason });
5992
- }
5993
- }
5994
- return { updated: taskIds.length - failed.length, failed };
6070
+ const outcomes = await runInBatches(
6071
+ taskIds,
6072
+ BULK_CONCURRENCY,
6073
+ (id) => action === "add" ? client.addTagToTask(id, tagName) : client.removeTagFromTask(id, tagName)
6074
+ );
6075
+ return toBulkResult(outcomes);
6076
+ }
6077
+ async function bulkPriority(config, priorityValue, taskIds) {
6078
+ const priority = parsePriority(priorityValue);
6079
+ const client = new ClickUpClient(config);
6080
+ const outcomes = await runInBatches(
6081
+ taskIds,
6082
+ BULK_CONCURRENCY,
6083
+ (id) => client.updateTask(id, { priority })
6084
+ );
6085
+ return toBulkResult(outcomes);
6086
+ }
6087
+ async function bulkField(config, fieldName, rawValue, taskIds) {
6088
+ if (taskIds.length === 0) return { updated: 0, failed: [] };
6089
+ const client = new ClickUpClient(config);
6090
+ const firstTask = await client.getTask(taskIds[0]);
6091
+ const fields = firstTask.custom_fields ?? [];
6092
+ const field = findFieldByName(fields, fieldName);
6093
+ const parsed = parseFieldValue(field, rawValue);
6094
+ const outcomes = await runInBatches(
6095
+ taskIds,
6096
+ BULK_CONCURRENCY,
6097
+ (id) => client.setCustomFieldValue(id, field.id, parsed)
6098
+ );
6099
+ return toBulkResult(outcomes);
5995
6100
  }
5996
6101
 
5997
6102
  // src/commands/goals.ts
@@ -6596,7 +6701,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6596
6701
  ).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(
6597
6702
  "--time-estimate <duration>",
6598
6703
  'Time estimate (e.g. "2h", "30m", "1h30m", "0" or "none" to clear)'
6599
- ).option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--remove-assignee <userId>", 'Remove 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(
6704
+ ).option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--remove-assignee <userId>", 'Remove 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("--type <type>", "Change task type (name or custom_item_id)").option("--field <nameAndValue...>", 'Set custom field: --field "Name" value').option("--json", "Force JSON output even in terminal").action(
6600
6705
  wrapAction(
6601
6706
  async (taskId, opts) => {
6602
6707
  const config = loadConfig(getProfileName());
@@ -6612,14 +6717,15 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6612
6717
  ]);
6613
6718
  const payload = buildUpdatePayload(opts, timezone);
6614
6719
  const hasFields = (opts.field?.length ?? 0) > 0;
6615
- if (!hasFields && Object.keys(payload).length === 0) {
6720
+ const hasTypeName = opts.type !== void 0;
6721
+ if (!hasFields && !hasTypeName && Object.keys(payload).length === 0) {
6616
6722
  throw new Error(
6617
- "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --remove-assignee, --parent, --archive, --unarchive, --field"
6723
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --remove-assignee, --parent, --archive, --unarchive, --type, --field"
6618
6724
  );
6619
6725
  }
6620
6726
  let result;
6621
- if (Object.keys(payload).length > 0) {
6622
- result = await updateTask(config, taskId, payload);
6727
+ if (Object.keys(payload).length > 0 || hasTypeName) {
6728
+ result = await updateTask(config, taskId, payload, opts.type);
6623
6729
  }
6624
6730
  if (hasFields) {
6625
6731
  if ((opts.field?.length ?? 0) % 2 !== 0) {
@@ -6804,18 +6910,25 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6804
6910
  printTimeInStatus(result, opts.json ?? false);
6805
6911
  })
6806
6912
  );
6807
- 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(
6808
- wrapAction(async (spaceId, opts) => {
6809
- const config = loadConfig(getProfileName());
6810
- const lists = await fetchLists(config, spaceId, { name: opts.name });
6811
- printLists(lists, opts.json ?? false);
6812
- })
6913
+ 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("--archived", "Include only archived items (default: active items)").option("--json", "Force JSON output even in terminal").action(
6914
+ wrapAction(
6915
+ async (spaceId, opts) => {
6916
+ const config = loadConfig(getProfileName());
6917
+ const lists = await fetchLists(config, spaceId, {
6918
+ name: opts.name,
6919
+ archived: opts.archived
6920
+ });
6921
+ printLists(lists, opts.json ?? false);
6922
+ }
6923
+ )
6813
6924
  );
6814
- program.command("spaces").description("List spaces in your workspace").option("--name <partial>", "Filter spaces by name (case-insensitive contains)").option("--my", "Show only spaces where I have assigned tasks").option("--json", "Force JSON output even in terminal").action(
6815
- wrapAction(async (opts) => {
6816
- const config = loadConfig(getProfileName());
6817
- await listSpaces(config, opts);
6818
- })
6925
+ program.command("spaces").description("List spaces in your workspace").option("--name <partial>", "Filter spaces by name (case-insensitive contains)").option("--my", "Show only spaces where I have assigned tasks").option("--archived", "Include only archived items (default: active items)").option("--json", "Force JSON output even in terminal").action(
6926
+ wrapAction(
6927
+ async (opts) => {
6928
+ const config = loadConfig(getProfileName());
6929
+ await listSpaces(config, opts);
6930
+ }
6931
+ )
6819
6932
  );
6820
6933
  program.command("inbox").description("Recently updated tasks grouped by time period").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").option("--days <n>", "Lookback period in days", "30").action(
6821
6934
  wrapAction(async (opts) => {
@@ -7433,6 +7546,23 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7433
7546
  }
7434
7547
  )
7435
7548
  );
7549
+ bulkCmd.command("priority <taskIds...>").description("Bulk set priority on tasks (urgent/high/normal/low or 1-4)").requiredOption("--to <priority>", "Priority to set (urgent, high, normal, low, or 1-4)").option("--json", "Force JSON output even in terminal").action(
7550
+ wrapAction(async (taskIds, opts) => {
7551
+ const config = loadConfig(getProfileName());
7552
+ const result = await bulkPriority(config, opts.to, taskIds);
7553
+ outputBulkResult(result, opts.json ?? false, `priority ${opts.to}`);
7554
+ })
7555
+ );
7556
+ bulkCmd.command("field <taskIds...>").description("Bulk set the same custom field value on tasks").requiredOption("--set <nameAndValue...>", 'Set field: --set "Field Name" value').option("--json", "Force JSON output even in terminal").action(
7557
+ wrapAction(async (taskIds, opts) => {
7558
+ if (opts.set.length !== 2) {
7559
+ throw new Error("--set requires exactly two arguments: field name and value");
7560
+ }
7561
+ const config = loadConfig(getProfileName());
7562
+ const result = await bulkField(config, opts.set[0], opts.set[1], taskIds);
7563
+ outputBulkResult(result, opts.json ?? false, `field "${opts.set[0]}"`);
7564
+ })
7565
+ );
7436
7566
  program.command("goals").description("List goals in your workspace").option("--json", "Force JSON output even in terminal").action(
7437
7567
  wrapAction(async (opts) => {
7438
7568
  const config = loadConfig(getProfileName());
@@ -7602,18 +7732,20 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7602
7732
  }
7603
7733
  })
7604
7734
  );
7605
- program.command("folders <spaceId>").description("List folders in a space (with their lists)").option("--name <partial>", "Filter folders by partial name match").option("--json", "Force JSON output even in terminal").action(
7606
- wrapAction(async (spaceId, opts) => {
7607
- const config = loadConfig(getProfileName());
7608
- const folders = await listFolders(config, spaceId, opts.name);
7609
- if (shouldOutputJson(opts.json ?? false)) {
7610
- console.log(JSON.stringify(folders, null, 2));
7611
- } else if (isTTY()) {
7612
- console.log(formatFolders(folders));
7613
- } else {
7614
- console.log(formatFoldersMarkdown(folders));
7735
+ program.command("folders <spaceId>").description("List folders in a space (with their lists)").option("--name <partial>", "Filter folders by partial name match").option("--archived", "Include only archived items (default: active items)").option("--json", "Force JSON output even in terminal").action(
7736
+ wrapAction(
7737
+ async (spaceId, opts) => {
7738
+ const config = loadConfig(getProfileName());
7739
+ const folders = await listFolders(config, spaceId, opts.name, opts.archived);
7740
+ if (shouldOutputJson(opts.json ?? false)) {
7741
+ console.log(JSON.stringify(folders, null, 2));
7742
+ } else if (isTTY()) {
7743
+ console.log(formatFolders(folders));
7744
+ } else {
7745
+ console.log(formatFoldersMarkdown(folders));
7746
+ }
7615
7747
  }
7616
- })
7748
+ )
7617
7749
  );
7618
7750
  program.command("space-create <name>").description("Create a new space in your workspace").option("--json", "Force JSON output even in terminal").action(
7619
7751
  wrapAction(async (name, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -152,7 +152,7 @@ All commands support `--help` for full flag details. All commands support `--jso
152
152
  | `cup comment-delete <commentId>` or `cup comment-delete --task <taskId> --mine [--match text]` | Delete a comment by ID or delete one of your task comments |
153
153
  | `cup replies <commentId>` | List threaded replies |
154
154
  | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment |
155
- | `cup assign <id> [--to userId\|me] [--remove userId\|me]` | Assign/unassign users |
155
+ | `cup assign <id> [--to ids\|me] [--remove ids\|me]` | Assign/unassign users (`--to`/`--remove` accept comma-separated IDs) |
156
156
  | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
157
157
  | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists (`--to` accepts `sprint:current`) |
158
158
  | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
@@ -219,32 +219,32 @@ All commands support `--help` for full flag details. All commands support `--jso
219
219
 
220
220
  ## Flags & Conventions
221
221
 
222
- | Topic | Detail |
223
- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
- | Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
225
- | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr |
226
- | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
227
- | `--due-date` | `YYYY-MM-DD` format |
228
- | `--assignee` | User ID or `me` |
229
- | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
230
- | `--time-estimate` | Duration: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
231
- | `--type` | `task` (regular) or custom type name/ID (e.g. `initiative`, `Bug`) |
232
- | `--custom-item-id` | Custom task type ID for `cup create` (find with `cup task-types`) |
233
- | `--space` | Partial name match or exact ID |
234
- | `--name` | Partial match, case-insensitive |
235
- | `--all` | Show all tasks in workspace, not just assigned to me. Available on `tasks`, `search`, `overdue`. Default: my tasks only (smaller output for agent context windows) |
236
- | `--include-closed` | Include closed/done tasks |
237
- | `--list` on create | Optional when `--parent` is given (auto-detected). Accepts `sprint:current` pseudo-ID |
238
- | `cup field --set` | Supports: text, number, checkbox (true/false), dropdown (option name), labels (comma-separated names), date (YYYY-MM-DD), url, email. Names resolved case-insensitively; errors list available fields/options |
239
- | `cup field-create` | Use `--options "a,b,c"` for `drop_down` and `labels` types (required). Other types don't need `--options` |
240
- | `--field` filter | `--field "Name" value` on `tasks` and `search` requires `--list` to resolve field names to IDs |
241
- | `--due-before/after` | `YYYY-MM-DD` date filters for due date range |
242
- | `--created-before/after` | `YYYY-MM-DD` date filters for creation date range |
243
- | `cup sprint` | Auto-detects active sprint by folder name (sprint/iteration/cycle/scrum), parses multiple date formats. Override with `--folder <id>`, `cup config set sprintFolderId <id>`, or favorite a sprint folder |
244
- | `cup favorite` | Local-only favorites (not synced to ClickUp). Types: sprint-folder, space, list, folder, view, task. Favorited sprint-folders auto-used by sprint commands |
245
- | `cup link` | Both IDs must be the same type (both custom or both native) |
246
- | `cup delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone |
247
- | Errors | stderr with exit code 1. Strict parsing - excess/unknown arguments rejected |
222
+ | Topic | Detail |
223
+ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
225
+ | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr |
226
+ | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
227
+ | `--due-date` | `YYYY-MM-DD` format |
228
+ | `--assignee` | User ID or `me` |
229
+ | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
230
+ | `--time-estimate` | Duration: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
231
+ | `--type` | `task` (regular) or custom type name/ID (e.g. `initiative`, `Bug`) |
232
+ | `--custom-item-id` | Custom task type ID for `cup create` (find with `cup task-types`) |
233
+ | `--space` | Partial name match or exact ID |
234
+ | `--name` | Partial match, case-insensitive |
235
+ | `--all` | Show all tasks in workspace, not just assigned to me. Available on `tasks`, `search`, `overdue`. Default: my tasks only (smaller output for agent context windows) |
236
+ | `--include-closed` | Include closed/done tasks |
237
+ | `--list` on create | Optional when `--parent` is given (auto-detected). Accepts `sprint:current` pseudo-ID |
238
+ | `cup field --set` | Supports: text, number, checkbox (true/false), dropdown (option name), labels (comma-separated names), date (YYYY-MM-DD), url, email, emoji/rating (0-5), manual_progress (0-100), tasks/relationship (comma-separated task IDs), users/people (comma-separated user IDs). Names resolved case-insensitively; errors list available fields/options |
239
+ | `cup field-create` | Use `--options "a,b,c"` for `drop_down` and `labels` types (required). Other types don't need `--options` |
240
+ | `--field` filter | `--field "Name" value` on `tasks` and `search` requires `--list` to resolve field names to IDs |
241
+ | `--due-before/after` | `YYYY-MM-DD` date filters for due date range |
242
+ | `--created-before/after` | `YYYY-MM-DD` date filters for creation date range |
243
+ | `cup sprint` | Auto-detects active sprint by folder name (sprint/iteration/cycle/scrum), parses multiple date formats. Override with `--folder <id>`, `cup config set sprintFolderId <id>`, or favorite a sprint folder |
244
+ | `cup favorite` | Local-only favorites (not synced to ClickUp). Types: sprint-folder, space, list, folder, view, task. Favorited sprint-folders auto-used by sprint commands |
245
+ | `cup link` | Both IDs must be the same type (both custom or both native) |
246
+ | `cup delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone |
247
+ | Errors | stderr with exit code 1. Strict parsing - excess/unknown arguments rejected |
248
248
 
249
249
  ## Agent Workflow Examples
250
250
 
@@ -289,6 +289,7 @@ cup create -n "Bug fix" -l sprint:current # create in active sprint
289
289
  cup create -n "Q3 Roadmap" -l <listId> --custom-item-id 1
290
290
  cup comment abc123def -m "Completed in PR #42"
291
291
  cup assign abc123def --to me
292
+ cup assign abc123def --to 12345,67890 # assign multiple users at once
292
293
  cup depend task3 --on task2 # task3 waits for task2
293
294
  cup move task1 --to list2 --remove list1
294
295
  cup move task1 --to sprint:current # move to active sprint