@krodak/clickup-cli 1.16.0 → 1.16.2

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.16.0",
4
+ "version": "1.16.2",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -128,14 +128,22 @@ var ClickUpClient = class {
128
128
  }
129
129
  async getMe() {
130
130
  if (this.meCache) return this.meCache;
131
- const data = await this.request("/user");
131
+ const data = await this.request(
132
+ "/user"
133
+ );
132
134
  const user = expectRecordField(data, "user", "user");
135
+ const timezone = typeof user.timezone === "string" && user.timezone ? user.timezone : void 0;
133
136
  this.meCache = {
134
137
  id: expectNumericField(user, "id", "user"),
135
- username: expectStringField(user, "username", "user")
138
+ username: expectStringField(user, "username", "user"),
139
+ ...timezone ? { timezone } : {}
136
140
  };
137
141
  return this.meCache;
138
142
  }
143
+ async getUserTimezone() {
144
+ const me = await this.getMe();
145
+ return me.timezone;
146
+ }
139
147
  async paginate(buildPath) {
140
148
  const allTasks = [];
141
149
  let page = 0;
@@ -304,21 +312,21 @@ var ClickUpClient = class {
304
312
  }
305
313
  async getView(viewId) {
306
314
  const data = await this.request(`/view/${viewId}`);
307
- return data.view;
315
+ return expectRecordField(data, "view", "view");
308
316
  }
309
317
  async createListView(listId, payload) {
310
318
  const data = await this.request(`/list/${listId}/view`, {
311
319
  method: "POST",
312
320
  body: JSON.stringify(payload)
313
321
  });
314
- return data.view;
322
+ return expectRecordField(data, "view", "view");
315
323
  }
316
324
  async updateView(viewId, payload) {
317
325
  const data = await this.request(`/view/${viewId}`, {
318
326
  method: "PUT",
319
327
  body: JSON.stringify(payload)
320
328
  });
321
- return data.view;
329
+ return expectRecordField(data, "view", "view");
322
330
  }
323
331
  async deleteView(viewId) {
324
332
  await this.request(`/view/${viewId}`, { method: "DELETE" });
@@ -676,7 +684,7 @@ var ClickUpClient = class {
676
684
  async createGoal(teamId, name, opts) {
677
685
  const body = { name, multiple_owners: true };
678
686
  if (opts?.description) body.description = opts.description;
679
- if (opts?.dueDate) body.due_date = Number(opts.dueDate);
687
+ if (opts?.dueDate != null) body.due_date = opts.dueDate;
680
688
  if (opts?.color) body.color = opts.color;
681
689
  const data = await this.request(`/team/${teamId}/goal`, {
682
690
  method: "POST",
@@ -965,7 +973,7 @@ function loadConfig(profileName) {
965
973
  ...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
966
974
  };
967
975
  }
968
- const multi = migrateToMultiProfile(parsed, path);
976
+ const multi = loadMultiProfileConfig();
969
977
  const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
970
978
  if (!resolvedProfile) {
971
979
  throw new Error("No default profile set. Run: cup profile use <name>");
@@ -1158,9 +1166,9 @@ function formatDuration(ms) {
1158
1166
  }
1159
1167
  function formatDateISO(ms) {
1160
1168
  const d = new Date(Number(ms));
1161
- const year = d.getFullYear();
1162
- const month = String(d.getMonth() + 1).padStart(2, "0");
1163
- const day = String(d.getDate()).padStart(2, "0");
1169
+ const year = d.getUTCFullYear();
1170
+ const month = String(d.getUTCMonth() + 1).padStart(2, "0");
1171
+ const day = String(d.getUTCDate()).padStart(2, "0");
1164
1172
  return `${year}-${month}-${day}`;
1165
1173
  }
1166
1174
 
@@ -1725,14 +1733,40 @@ function parsePriority(value) {
1725
1733
  if (Number.isInteger(num) && num >= 1 && num <= 4) return num;
1726
1734
  throw new Error("Priority must be urgent, high, normal, low, or 1-4");
1727
1735
  }
1728
- function parseDueDate(value) {
1736
+ function parseDueDate(value, timezone) {
1729
1737
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1730
1738
  throw new Error("Date must be in YYYY-MM-DD format");
1731
1739
  }
1732
- const parts = value.split("-");
1733
- const date = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
1734
- if (isNaN(date.getTime())) throw new Error(`Invalid date: ${value}`);
1735
- return date.getTime();
1740
+ const parts = value.split("-").map(Number);
1741
+ const y = parts[0];
1742
+ const m = parts[1];
1743
+ const d = parts[2];
1744
+ if (timezone) {
1745
+ try {
1746
+ const ms2 = dateToTimezoneMs(y, m, d, timezone);
1747
+ if (!isNaN(ms2)) return ms2;
1748
+ } catch {
1749
+ }
1750
+ }
1751
+ const ms = Date.UTC(y, m - 1, d);
1752
+ if (isNaN(ms)) throw new Error(`Invalid date: ${value}`);
1753
+ return ms;
1754
+ }
1755
+ function dateToTimezoneMs(year, month, day, timezone) {
1756
+ const approxUtc = new Date(Date.UTC(year, month - 1, day));
1757
+ const tzStr = approxUtc.toLocaleString("en-US", {
1758
+ timeZone: timezone,
1759
+ year: "numeric",
1760
+ month: "2-digit",
1761
+ day: "2-digit",
1762
+ hour: "2-digit",
1763
+ minute: "2-digit",
1764
+ second: "2-digit",
1765
+ hour12: false
1766
+ });
1767
+ const tzDate = /* @__PURE__ */ new Date(tzStr + " UTC");
1768
+ const offset = approxUtc.getTime() - tzDate.getTime();
1769
+ return approxUtc.getTime() + offset;
1736
1770
  }
1737
1771
  function parseAssigneeId(value) {
1738
1772
  const id = Number(value);
@@ -1761,7 +1795,7 @@ function parseTimeEstimate(value) {
1761
1795
  'Time estimate must be a duration (e.g. "2h", "30m", "1h30m"), milliseconds, or "0" to clear'
1762
1796
  );
1763
1797
  }
1764
- function buildUpdatePayload(opts) {
1798
+ function buildUpdatePayload(opts, timezone) {
1765
1799
  if (opts.archive && opts.unarchive) {
1766
1800
  throw new Error("Cannot use --archive and --unarchive together");
1767
1801
  }
@@ -1780,12 +1814,12 @@ function buildUpdatePayload(opts) {
1780
1814
  if (opts.dueDate === "none" || opts.dueDate === "clear") {
1781
1815
  payload.due_date = null;
1782
1816
  } else {
1783
- payload.due_date = parseDueDate(opts.dueDate);
1817
+ payload.due_date = parseDueDate(opts.dueDate, timezone);
1784
1818
  payload.due_date_time = false;
1785
1819
  }
1786
1820
  }
1787
1821
  if (opts.startDate !== void 0) {
1788
- payload.start_date = parseDueDate(opts.startDate);
1822
+ payload.start_date = parseDueDate(opts.startDate, timezone);
1789
1823
  payload.start_date_time = false;
1790
1824
  }
1791
1825
  if (opts.assignee !== void 0 || opts.removeAssignee !== void 0) {
@@ -1832,10 +1866,11 @@ async function updateTask(config, taskId, options) {
1832
1866
  "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --parent, --detach, --archive, --unarchive"
1833
1867
  );
1834
1868
  const client = new ClickUpClient(config);
1835
- if (options.status !== void 0) {
1836
- options.status = await resolveStatus(client, taskId, options.status);
1869
+ const resolved = { ...options };
1870
+ if (resolved.status !== void 0) {
1871
+ resolved.status = await resolveStatus(client, taskId, resolved.status);
1837
1872
  }
1838
- const task = await client.updateTask(taskId, options);
1873
+ const task = await client.updateTask(taskId, resolved);
1839
1874
  return { id: task.id, name: task.name };
1840
1875
  }
1841
1876
 
@@ -1855,6 +1890,7 @@ async function createTask(config, options) {
1855
1890
  const task2 = await client.createTaskFromTemplate(listId, options.template, options.name);
1856
1891
  return { id: task2.id, name: task2.name, url: task2.url };
1857
1892
  }
1893
+ const timezone = await client.getUserTimezone();
1858
1894
  const payload = {
1859
1895
  name: options.name,
1860
1896
  ...options.description !== void 0 ? { markdown_content: options.description } : {},
@@ -1865,11 +1901,11 @@ async function createTask(config, options) {
1865
1901
  payload.priority = parsePriority(options.priority);
1866
1902
  }
1867
1903
  if (options.dueDate !== void 0) {
1868
- payload.due_date = parseDueDate(options.dueDate);
1904
+ payload.due_date = parseDueDate(options.dueDate, timezone);
1869
1905
  payload.due_date_time = false;
1870
1906
  }
1871
1907
  if (options.startDate !== void 0) {
1872
- payload.start_date = parseDueDate(options.startDate);
1908
+ payload.start_date = parseDueDate(options.startDate, timezone);
1873
1909
  payload.start_date_time = false;
1874
1910
  }
1875
1911
  if (options.assignee !== void 0) {
@@ -4049,6 +4085,10 @@ ${renderZshTopLevelCommands(name)}
4049
4085
  _arguments \\
4050
4086
  '--days[Number of days to look back]:days:' \\
4051
4087
  '--task[Filter by task ID]:task_id:' \\
4088
+ '--space[Filter by space ID]:space_id:' \\
4089
+ '--list[Filter by list ID]:list_id:' \\
4090
+ '--assignee[Filter by assignee user ID]:user_id:' \\
4091
+ '--all[Show all team entries]' \\
4052
4092
  '--json[Force JSON output]'
4053
4093
  ;;
4054
4094
  update)
@@ -4509,6 +4549,10 @@ complete -c ${name} -n '__fish_seen_subcommand_from start; and __fish_seen_subco
4509
4549
  complete -c ${name} -n '__fish_seen_subcommand_from log; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
4510
4550
  complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l days -d 'Number of days to look back'
4511
4551
  complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l task -d 'Filter by task ID'
4552
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l space -d 'Filter by space ID'
4553
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l list -d 'Filter by list ID'
4554
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l assignee -d 'Filter by assignee user ID'
4555
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l all -d 'Show all team entries'
4512
4556
  complete -c ${name} -n '__fish_seen_subcommand_from update; and __fish_seen_subcommand_from time' -s d -l description -d 'New description'
4513
4557
  complete -c ${name} -n '__fish_seen_subcommand_from update; and __fish_seen_subcommand_from time' -l duration -d 'New duration'
4514
4558
 
@@ -5325,7 +5369,6 @@ function formatFieldsMarkdown(fields) {
5325
5369
  }
5326
5370
 
5327
5371
  // src/commands/duplicate.ts
5328
- var PRIORITY_MAP2 = { urgent: 1, high: 2, normal: 3, low: 4 };
5329
5372
  async function duplicateTask(config, taskId) {
5330
5373
  const client = new ClickUpClient(config);
5331
5374
  const task = await client.getTask(taskId);
@@ -5333,7 +5376,7 @@ async function duplicateTask(config, taskId) {
5333
5376
  name: `${task.name} (copy)`,
5334
5377
  description: task.description,
5335
5378
  markdown_content: task.markdown_content,
5336
- priority: task.priority ? PRIORITY_MAP2[task.priority.priority.toLowerCase()] : void 0,
5379
+ priority: task.priority ? parsePriority(task.priority.priority.toLowerCase()) : void 0,
5337
5380
  tags: task.tags?.map((t) => t.name),
5338
5381
  time_estimate: task.time_estimate ?? void 0
5339
5382
  });
@@ -5372,7 +5415,8 @@ async function bulkAssign(config, userIdOrMe, taskIds, action) {
5372
5415
  }
5373
5416
  async function bulkDueDate(config, date, taskIds) {
5374
5417
  const client = new ClickUpClient(config);
5375
- const payload = date === "none" || date === "clear" ? { due_date: null } : { due_date: parseDueDate(date), due_date_time: false };
5418
+ const timezone = await client.getUserTimezone();
5419
+ const payload = date === "none" || date === "clear" ? { due_date: null } : { due_date: parseDueDate(date, timezone), due_date_time: false };
5376
5420
  const failed = [];
5377
5421
  for (const id of taskIds) {
5378
5422
  try {
@@ -5430,7 +5474,12 @@ async function listGoals(config) {
5430
5474
  }
5431
5475
  async function createGoal(config, name, opts) {
5432
5476
  const client = new ClickUpClient(config);
5433
- return client.createGoal(config.teamId, name, opts);
5477
+ const timezone = await client.getUserTimezone();
5478
+ return client.createGoal(config.teamId, name, {
5479
+ description: opts?.description,
5480
+ color: opts?.color,
5481
+ ...opts?.dueDate ? { dueDate: parseDueDate(opts.dueDate, timezone) } : {}
5482
+ });
5434
5483
  }
5435
5484
  async function updateGoal(config, goalId, updates) {
5436
5485
  const client = new ClickUpClient(config);
@@ -5731,7 +5780,7 @@ function isAllowedFilterCommand(tokens) {
5731
5780
  if (tokens[0] === "time" && tokens[1] === "list") return true;
5732
5781
  return ALLOWED_FILTER_COMMANDS.has(tokens[0]);
5733
5782
  }
5734
- function runFilter(name, entry) {
5783
+ function runFilter(_name, entry) {
5735
5784
  const result = spawnSync(process.execPath, [process.argv[1], ...entry.command], {
5736
5785
  stdio: "inherit"
5737
5786
  });
@@ -5755,14 +5804,17 @@ function formatFiltersTable(filters) {
5755
5804
  }));
5756
5805
  return formatTable(rows, FILTER_COLUMNS);
5757
5806
  }
5807
+ function escapeMarkdownCell(value) {
5808
+ return value.replace(/\|/g, "\\|");
5809
+ }
5758
5810
  function formatFiltersMarkdown(filters) {
5759
5811
  const entries = Object.entries(filters);
5760
5812
  if (entries.length === 0) return "No filters saved";
5761
5813
  const lines = ["| Name | Command | Description |", "| --- | --- | --- |"];
5762
5814
  for (const [name, entry] of entries) {
5763
- const command = entry.command.join(" ");
5764
- const description = entry.description ?? "";
5765
- lines.push(`| ${name} | ${command} | ${description} |`);
5815
+ const command = escapeMarkdownCell(entry.command.join(" "));
5816
+ const description = escapeMarkdownCell(entry.description ?? "");
5817
+ lines.push(`| ${escapeMarkdownCell(name)} | ${command} | ${description} |`);
5766
5818
  }
5767
5819
  return lines.join("\n");
5768
5820
  }
@@ -5786,18 +5838,29 @@ async function createListWithOptions(config, spaceId, name, opts) {
5786
5838
  }
5787
5839
  const list = opts.folder ? await client.createFolderList(opts.folder, name) : await client.createList(spaceId, name);
5788
5840
  if (statuses) {
5789
- await client.updateList(list.id, { statuses });
5841
+ try {
5842
+ await client.updateList(list.id, { statuses });
5843
+ } catch (err) {
5844
+ const reason = err instanceof Error ? err.message : String(err);
5845
+ throw new Error(`List "${name}" (${list.id}) was created but status copy failed: ${reason}`, {
5846
+ cause: err
5847
+ });
5848
+ }
5790
5849
  }
5791
5850
  return {
5792
5851
  ...list,
5793
5852
  ...statuses ? { statusesCopied: statuses.length } : {}
5794
5853
  };
5795
5854
  }
5855
+ function isNotFound(err) {
5856
+ return err instanceof Error && /ClickUp API error 4(04|03)/.test(err.message);
5857
+ }
5796
5858
  async function copyStatusesFrom(client, sourceId) {
5797
5859
  try {
5798
5860
  const list = await client.getListWithStatuses(sourceId);
5799
5861
  return list.statuses.map((s) => ({ status: s.status, color: s.color, type: s.type ?? "custom" }));
5800
- } catch {
5862
+ } catch (err) {
5863
+ if (!isNotFound(err)) throw err;
5801
5864
  try {
5802
5865
  const space = await client.getSpaceWithStatuses(sourceId);
5803
5866
  return space.statuses.map((s) => ({
@@ -5805,9 +5868,11 @@ async function copyStatusesFrom(client, sourceId) {
5805
5868
  color: s.color,
5806
5869
  type: s.type ?? "custom"
5807
5870
  }));
5808
- } catch {
5871
+ } catch (spaceErr) {
5872
+ if (!isNotFound(spaceErr)) throw spaceErr;
5809
5873
  throw new Error(
5810
- `Could not find a list or space with ID "${sourceId}". Check the ID and try again.`
5874
+ `Could not find a list or space with ID "${sourceId}". Check the ID and try again.`,
5875
+ { cause: spaceErr }
5811
5876
  );
5812
5877
  }
5813
5878
  }
@@ -5890,15 +5955,17 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5890
5955
  wrapAction(
5891
5956
  async (taskId, opts) => {
5892
5957
  const config = loadConfig(getProfileName());
5893
- if (opts.assignee === "me") {
5894
- const client = new ClickUpClient(config);
5895
- opts.assignee = String(await resolveAssigneeId(client, "me"));
5896
- }
5897
- if (opts.removeAssignee === "me") {
5898
- const client = new ClickUpClient(config);
5899
- opts.removeAssignee = String(await resolveAssigneeId(client, "me"));
5900
- }
5901
- const payload = buildUpdatePayload(opts);
5958
+ const client = new ClickUpClient(config);
5959
+ const [timezone] = await Promise.all([
5960
+ client.getUserTimezone(),
5961
+ opts.assignee === "me" ? resolveAssigneeId(client, "me").then((id) => {
5962
+ opts.assignee = String(id);
5963
+ }) : Promise.resolve(),
5964
+ opts.removeAssignee === "me" ? resolveAssigneeId(client, "me").then((id) => {
5965
+ opts.removeAssignee = String(id);
5966
+ }) : Promise.resolve()
5967
+ ]);
5968
+ const payload = buildUpdatePayload(opts, timezone);
5902
5969
  const hasFields = (opts.field?.length ?? 0) > 0;
5903
5970
  if (!hasFields && Object.keys(payload).length === 0) {
5904
5971
  throw new Error(
@@ -5917,8 +5984,8 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5917
5984
  await setCustomField(config, taskId, { set: [opts.field[i], opts.field[i + 1]] });
5918
5985
  }
5919
5986
  if (!result) {
5920
- const client = new ClickUpClient(config);
5921
- const task = await client.getTask(taskId);
5987
+ const client2 = new ClickUpClient(config);
5988
+ const task = await client2.getTask(taskId);
5922
5989
  result = { id: task.id, name: task.name };
5923
5990
  }
5924
5991
  }
@@ -6623,10 +6690,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
6623
6690
  outputBulkResult(result, opts.json ?? false, "due-date");
6624
6691
  })
6625
6692
  );
6626
- bulkCmd.command("tag <tagName> <taskIds...>").description("Bulk add or remove a tag on tasks").option("--add", "Add tag (default)").option("--remove", "Remove tag instead of adding").option("--json", "Force JSON output even in terminal").action(
6693
+ bulkCmd.command("tag <tagName> <taskIds...>").description("Bulk add or remove a tag on tasks (default: add)").option("--remove", "Remove tag instead of adding").option("--json", "Force JSON output even in terminal").action(
6627
6694
  wrapAction(
6628
6695
  async (tagName, taskIds, opts) => {
6629
- if (opts.add && opts.remove) throw new Error("Cannot use --add and --remove together");
6630
6696
  const action = opts.remove ? "remove" : "add";
6631
6697
  const config = loadConfig(getProfileName());
6632
6698
  const result = await bulkTag(config, tagName, taskIds, action);
@@ -7104,8 +7170,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7104
7170
  );
7105
7171
  }
7106
7172
  if (!isAllowedFilterCommand(args)) {
7173
+ const allowed = [...ALLOWED_FILTER_COMMANDS, "time list"].join(", ");
7107
7174
  throw new Error(
7108
- `Command "${args[0]}" is not allowed in saved filters. Allowed: tasks, search, sprint, assigned, overdue, inbox, summary, views, lists, spaces, folders, members, tags, goals, key-results, task-types, templates, list-templates, folder-templates, docs, time list`
7175
+ `Command "${args[0]}" is not allowed in saved filters. Allowed: ${allowed}`
7109
7176
  );
7110
7177
  }
7111
7178
  const entry = { command: args };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",