@krodak/clickup-cli 1.27.1 → 1.28.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.27.1",
4
+ "version": "1.28.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -1993,27 +1993,54 @@ function parsePriority(value) {
1993
1993
  if (Number.isInteger(num) && num >= 1 && num <= 4) return num;
1994
1994
  throw new Error("Priority must be urgent, high, normal, low, or 1-4");
1995
1995
  }
1996
+ var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
1997
+ var LOCAL_DATETIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
1998
+ var ISO_WITH_OFFSET_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
1996
1999
  function parseDueDate(value, timezone) {
1997
- if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1998
- throw new Error("Date must be in YYYY-MM-DD format");
2000
+ if (DATE_ONLY_RE.test(value)) {
2001
+ const parts = value.split("-").map(Number);
2002
+ const y = parts[0];
2003
+ const m = parts[1];
2004
+ const d = parts[2];
2005
+ return { ms: wallClockToMs(y, m, d, 0, 0, 0, timezone, value), hasTime: false };
2006
+ }
2007
+ const localMatch = LOCAL_DATETIME_RE.exec(value);
2008
+ if (localMatch) {
2009
+ const y = Number(localMatch[1]);
2010
+ const m = Number(localMatch[2]);
2011
+ const d = Number(localMatch[3]);
2012
+ const hh = Number(localMatch[4]);
2013
+ const mm = Number(localMatch[5]);
2014
+ const ss = localMatch[6] !== void 0 ? Number(localMatch[6]) : 0;
2015
+ if (hh > 23 || mm > 59 || ss > 59) {
2016
+ throw new Error(
2017
+ "Date must be in YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 format (time component out of range)"
2018
+ );
2019
+ }
2020
+ return { ms: wallClockToMs(y, m, d, hh, mm, ss, timezone, value), hasTime: true };
1999
2021
  }
2000
- const parts = value.split("-").map(Number);
2001
- const y = parts[0];
2002
- const m = parts[1];
2003
- const d = parts[2];
2022
+ if (ISO_WITH_OFFSET_RE.test(value)) {
2023
+ const ms = Date.parse(value);
2024
+ if (!isNaN(ms)) return { ms, hasTime: true };
2025
+ }
2026
+ throw new Error(
2027
+ "Date must be in YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 format (e.g. 2025-03-15T14:30 or 2025-03-15T14:30:00+08:00)"
2028
+ );
2029
+ }
2030
+ function wallClockToMs(year, month, day, hour, minute, second, timezone, rawValue) {
2004
2031
  if (timezone) {
2005
2032
  try {
2006
- const ms2 = dateToTimezoneMs(y, m, d, timezone);
2033
+ const ms2 = wallClockToTimezoneMs(year, month, day, hour, minute, second, timezone);
2007
2034
  if (!isNaN(ms2)) return ms2;
2008
2035
  } catch {
2009
2036
  }
2010
2037
  }
2011
- const ms = Date.UTC(y, m - 1, d);
2012
- if (isNaN(ms)) throw new Error(`Invalid date: ${value}`);
2038
+ const ms = Date.UTC(year, month - 1, day, hour, minute, second);
2039
+ if (isNaN(ms)) throw new Error(`Invalid date: ${rawValue}`);
2013
2040
  return ms;
2014
2041
  }
2015
- function dateToTimezoneMs(year, month, day, timezone) {
2016
- const approxUtc = new Date(Date.UTC(year, month - 1, day));
2042
+ function wallClockToTimezoneMs(year, month, day, hour, minute, second, timezone) {
2043
+ const approxUtc = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
2017
2044
  const tzStr = approxUtc.toLocaleString("en-US", {
2018
2045
  timeZone: timezone,
2019
2046
  year: "numeric",
@@ -2074,13 +2101,15 @@ function buildUpdatePayload(opts, timezone) {
2074
2101
  if (opts.dueDate === "none" || opts.dueDate === "clear") {
2075
2102
  payload.due_date = null;
2076
2103
  } else {
2077
- payload.due_date = parseDueDate(opts.dueDate, timezone);
2078
- payload.due_date_time = false;
2104
+ const parsed = parseDueDate(opts.dueDate, timezone);
2105
+ payload.due_date = parsed.ms;
2106
+ payload.due_date_time = parsed.hasTime;
2079
2107
  }
2080
2108
  }
2081
2109
  if (opts.startDate !== void 0) {
2082
- payload.start_date = parseDueDate(opts.startDate, timezone);
2083
- payload.start_date_time = false;
2110
+ const parsed = parseDueDate(opts.startDate, timezone);
2111
+ payload.start_date = parsed.ms;
2112
+ payload.start_date_time = parsed.hasTime;
2084
2113
  }
2085
2114
  if (opts.assignee !== void 0 || opts.removeAssignee !== void 0) {
2086
2115
  payload.assignees = {};
@@ -2180,12 +2209,14 @@ async function createTask(config, options) {
2180
2209
  payload.priority = parsePriority(options.priority);
2181
2210
  }
2182
2211
  if (options.dueDate !== void 0) {
2183
- payload.due_date = parseDueDate(options.dueDate, timezone);
2184
- payload.due_date_time = false;
2212
+ const parsed = parseDueDate(options.dueDate, timezone);
2213
+ payload.due_date = parsed.ms;
2214
+ payload.due_date_time = parsed.hasTime;
2185
2215
  }
2186
2216
  if (options.startDate !== void 0) {
2187
- payload.start_date = parseDueDate(options.startDate, timezone);
2188
- payload.start_date_time = false;
2217
+ const parsed = parseDueDate(options.startDate, timezone);
2218
+ payload.start_date = parsed.ms;
2219
+ payload.start_date_time = parsed.hasTime;
2189
2220
  }
2190
2221
  if (options.assignee !== void 0) {
2191
2222
  payload.assignees = [parseAssigneeId(options.assignee)];
@@ -6804,7 +6835,13 @@ async function bulkAssign(config, userIdOrMe, taskIds, action) {
6804
6835
  async function bulkDueDate(config, date, taskIds) {
6805
6836
  const client = new ClickUpClient(config);
6806
6837
  const timezone = await client.getUserTimezone();
6807
- const payload = date === "none" || date === "clear" ? { due_date: null } : { due_date: parseDueDate(date, timezone), due_date_time: false };
6838
+ let payload;
6839
+ if (date === "none" || date === "clear") {
6840
+ payload = { due_date: null };
6841
+ } else {
6842
+ const parsed = parseDueDate(date, timezone);
6843
+ payload = { due_date: parsed.ms, due_date_time: parsed.hasTime };
6844
+ }
6808
6845
  const outcomes = await runInBatches(
6809
6846
  taskIds,
6810
6847
  BULK_CONCURRENCY,
@@ -6888,7 +6925,7 @@ async function createGoal(config, name, opts) {
6888
6925
  return client.createGoal(config.teamId, name, {
6889
6926
  description: opts?.description,
6890
6927
  color: opts?.color,
6891
- ...opts?.dueDate ? { dueDate: parseDueDate(opts.dueDate, timezone) } : {}
6928
+ ...opts?.dueDate ? { dueDate: parseDueDate(opts.dueDate, timezone).ms } : {}
6892
6929
  });
6893
6930
  }
6894
6931
  async function updateGoal(config, goalId, updates) {
@@ -7654,7 +7691,13 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7654
7691
  program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option(
7655
7692
  "-s, --status <status>",
7656
7693
  'New status (fuzzy matched, e.g. "prog" matches "in progress")'
7657
- ).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(
7694
+ ).option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option(
7695
+ "--due-date <date>",
7696
+ 'Due date (YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 with offset; or "none"/"clear" to remove)'
7697
+ ).option(
7698
+ "--start-date <date>",
7699
+ "Start date (YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 with offset)"
7700
+ ).option(
7658
7701
  "--time-estimate <duration>",
7659
7702
  'Time estimate (e.g. "2h", "30m", "1h30m", "0" or "none" to clear)'
7660
7703
  ).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(
@@ -7704,7 +7747,13 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
7704
7747
  }
7705
7748
  )
7706
7749
  );
7707
- program.command("create").description("Create a new task").option("-l, --list <listId>", 'Target list ID or "sprint:current" for active sprint').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 (find IDs with cup templates)").option("--field <nameAndValue...>", 'Set custom field: --field "Name" value (can repeat)').option("--json", "Force JSON output even in terminal").action(
7750
+ program.command("create").description("Create a new task").option("-l, --list <listId>", 'Target list ID or "sprint:current" for active sprint').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(
7751
+ "--due-date <date>",
7752
+ "Due date (YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 with offset)"
7753
+ ).option(
7754
+ "--start-date <date>",
7755
+ "Start date (YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 with offset)"
7756
+ ).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 (find IDs with cup templates)").option("--field <nameAndValue...>", 'Set custom field: --field "Name" value (can repeat)').option("--json", "Force JSON output even in terminal").action(
7708
7757
  wrapAction(async (opts) => {
7709
7758
  const config = loadConfig(getProfileName());
7710
7759
  if (opts.list === "sprint:current") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.27.1",
3
+ "version": "1.28.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,11 +3,11 @@ name: clickup
3
3
  description: 'Use when managing ClickUp tasks, sprints, or comments via the `cup` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, threaded replies, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments, task links, time tracking, attachments, file uploads, listing members, listing fields, duplicating tasks, bulk operations, goals, key results, saved filters, favorites.'
4
4
  ---
5
5
 
6
- # ClickUp CLI (`cup`) - skill version 1.27.1
6
+ # ClickUp CLI (`cup`) - skill version 1.28.0
7
7
 
8
8
  Reference for AI agents using the `cup` CLI tool. Covers task management, sprint tracking, comments, time tracking, custom fields, goals, docs, and project workflows.
9
9
 
10
- > **Version check:** Run `cup --version`. If your installed version is older than 1.27.1, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
10
+ > **Version check:** Run `cup --version`. If your installed version is older than 1.28.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -248,7 +248,7 @@ All commands support `--help` for full flag details. All commands support `--jso
248
248
  | Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
249
249
  | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr |
250
250
  | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
251
- | `--due-date` | `YYYY-MM-DD` format |
251
+ | `--due-date` | `YYYY-MM-DD` (date only), `YYYY-MM-DDTHH:MM` (with time), or full ISO 8601 with offset. Time-of-day formats set `due_date_time: true` in ClickUp |
252
252
  | `--assignee` | User ID or `me` |
253
253
  | `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
254
254
  | `--time-estimate` | Duration: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
@@ -323,6 +323,8 @@ cup inbox --days 7 # recently updated
323
323
  ```bash
324
324
  cup update abc123def -s "done"
325
325
  cup update abc123def --priority high --due-date 2025-03-15
326
+ cup update abc123def --due-date 2025-03-15T14:30 # date + time (user's timezone)
327
+ cup update abc123def --due-date 2025-03-15T14:30:00Z # UTC
326
328
  cup create -n "Fix the thing" -p abc123def
327
329
  cup create -n "Fix bug" -l <listId> --priority urgent --tags "bug,frontend"
328
330
  cup create -n "Bug fix" -l sprint:current # create in active sprint