@krodak/clickup-cli 1.30.1 → 1.31.1

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.30.1",
4
+ "version": "1.31.1",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -171,23 +171,23 @@ npx skills add https://github.com/krodak/clickup-cli
171
171
 
172
172
  Full CRUD for the core ClickUp workflow:
173
173
 
174
- | Area | Capabilities |
175
- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
- | ✅ **Tasks** | Create, read, update, delete, duplicate, merge, search, subtasks, assign, dependencies, links, multi-list, bulk operations (status, assign, due-date, tag, priority, field, move) |
177
- | 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all, list and view comments |
178
- | 🗨️ **Chat** | List channels, send messages, replies, reactions, channel management |
179
- | 📄 **Docs** | List, read, create, edit, delete (v3 API) |
180
- | ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history, per-user time estimates |
181
- | ☑️ **Checklists** | View, create, delete, add/edit/delete items |
182
- | 🔧 **Custom Fields** | List, create, set, remove values (text, number, dropdown, labels, date, checkbox, url, email, rating, progress, relationship, people) |
183
- | 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
184
- | 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
185
- | 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders |
186
- | ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks |
187
- | 👁️ **Views** | List, get, create, update, delete views on lists |
188
- | 🔗 **Webhooks** | List, create, update, delete webhooks; scope to space, folder, list, or task |
189
- | 🏢 **Workspace** | Spaces, folders, lists (full CRUD + rename + from template), members, task types, templates, plan, shared hierarchy |
190
- | 📎 **Attachments** | Upload files to tasks, list task attachments, shown in detail views |
174
+ | Area | Capabilities |
175
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | ✅ **Tasks** | Create, read, update, delete, duplicate, merge, search, subtasks, assign (users and groups), dependencies, links, multi-list, bulk operations (status, assign, due-date, tag, priority, field, move) |
177
+ | 💬 **Comments** | Post, edit, delete by ID or by task scope for your own comments, threaded replies, notify all, list and view comments |
178
+ | 🗨️ **Chat** | List channels, send messages, replies, reactions, channel management |
179
+ | 📄 **Docs** | List, read, create, edit, delete (v3 API) |
180
+ | ⏱️ **Time Tracking** | Start/stop timer, log entries, list/update/delete history, per-user time estimates |
181
+ | ☑️ **Checklists** | View, create, delete, add/edit/delete items |
182
+ | 🔧 **Custom Fields** | List, create, set, remove values (text, number, dropdown, labels, date, checkbox, url, email, rating, progress, relationship, people) |
183
+ | 🏷️ **Tags** | Add/remove on tasks, space-level create/update/delete |
184
+ | 🎯 **Goals & OKRs** | Goals CRUD, key results CRUD |
185
+ | 🏃 **Sprints** | Auto-detect active sprint, `sprint:current` pseudo-ID for move/create, flexible date parsing, config override, favorite sprint folders |
186
+ | ⭐ **Favorites** | Local favorites for quick access to sprint folders, spaces, lists, folders, views, tasks |
187
+ | 👁️ **Views** | List, get, create, update, delete views on lists |
188
+ | 🔗 **Webhooks** | List, create, update, delete webhooks; scope to space, folder, list, or task |
189
+ | 🏢 **Workspace** | Spaces, folders, lists (full CRUD + rename + from template), members, user groups, task types, templates, plan, shared hierarchy |
190
+ | 📎 **Attachments** | Upload files to tasks, list task attachments, shown in detail views |
191
191
 
192
192
  [Full API coverage details](docs/api-coverage.md) | [Command reference](docs/commands.md)
193
193
 
package/dist/index.js CHANGED
@@ -658,6 +658,10 @@ var ClickUpClient = class {
658
658
  ).find((t) => t.id === teamId);
659
659
  return team?.members?.map((m) => m.user) ?? [];
660
660
  }
661
+ async getGroups() {
662
+ const data = await this.request(`/group?team_id=${this.teamId}`);
663
+ return readCollectionField(data, "groups", "groups");
664
+ }
661
665
  async deleteTimeEntry(teamId, timeEntryId) {
662
666
  await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
663
667
  method: "DELETE"
@@ -1748,6 +1752,8 @@ function formatAssignConfirmation(taskId, opts) {
1748
1752
  const parts = [];
1749
1753
  if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
1750
1754
  if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
1755
+ if (opts.group) parts.push(`Assigned group ${opts.group} to ${taskId}`);
1756
+ if (opts.removeGroup) parts.push(`Removed group ${opts.removeGroup} from ${taskId}`);
1751
1757
  return parts.join("; ");
1752
1758
  }
1753
1759
 
@@ -2160,6 +2166,16 @@ async function resolveAssigneeId(client, value) {
2160
2166
  }
2161
2167
  return parseAssigneeId(value);
2162
2168
  }
2169
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2170
+ async function resolveGroupId(client, value) {
2171
+ if (UUID_RE.test(value)) return value;
2172
+ const handle = value.startsWith("@") ? value.slice(1) : value;
2173
+ const groups = await client.getGroups();
2174
+ const match = groups.find((g) => g.handle.toLowerCase() === handle.toLowerCase());
2175
+ if (match) return match.id;
2176
+ const available = groups.map((g) => `@${g.handle}`).join(", ") || "(none)";
2177
+ throw new Error(`Group "${value}" not found. Available: ${available}`);
2178
+ }
2163
2179
  function parseTimeEstimate(value) {
2164
2180
  if (value === "0" || value.toLowerCase() === "none" || value === "") return 0;
2165
2181
  const pattern = /^(?:(\d+)h)?(?:(\d+)m)?$/i;
@@ -2213,6 +2229,14 @@ function buildUpdatePayload(opts, timezone) {
2213
2229
  payload.assignees.rem = [parseAssigneeId(opts.removeAssignee)];
2214
2230
  }
2215
2231
  }
2232
+ const addGroups = opts.groupAssigneeIds ?? [];
2233
+ const remGroups = opts.removeGroupAssigneeIds ?? [];
2234
+ if (addGroups.length > 0 || remGroups.length > 0) {
2235
+ payload.group_assignees = {
2236
+ ...addGroups.length > 0 ? { add: addGroups } : {},
2237
+ ...remGroups.length > 0 ? { rem: remGroups } : {}
2238
+ };
2239
+ }
2216
2240
  if (opts.timeEstimate !== void 0) {
2217
2241
  payload.time_estimate = parseTimeEstimate(opts.timeEstimate);
2218
2242
  }
@@ -2232,7 +2256,7 @@ function buildUpdatePayload(opts, timezone) {
2232
2256
  return payload;
2233
2257
  }
2234
2258
  function hasUpdateFields(options) {
2235
- 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;
2259
+ 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.group_assignees !== void 0 || options.parent !== void 0 || options.archived !== void 0 || options.custom_item_id !== void 0;
2236
2260
  }
2237
2261
  async function resolveStatus(client, taskId, statusInput) {
2238
2262
  const task = await client.getTask(taskId);
@@ -2261,7 +2285,7 @@ async function resolveTaskType2(client, teamId, typeInput) {
2261
2285
  async function updateTask(config, taskId, options, typeInput) {
2262
2286
  if (!hasUpdateFields(options) && typeInput === void 0)
2263
2287
  throw new Error(
2264
- "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --parent, --detach, --archive, --unarchive, --type"
2288
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --group-assignee, --remove-group-assignee, --parent, --detach, --archive, --unarchive, --type"
2265
2289
  );
2266
2290
  const client = new ClickUpClient(config);
2267
2291
  const resolved = { ...options };
@@ -2314,6 +2338,9 @@ async function createTask(config, options) {
2314
2338
  if (options.assignee !== void 0) {
2315
2339
  payload.assignees = [parseAssigneeId(options.assignee)];
2316
2340
  }
2341
+ if (options.groupAssigneeIds !== void 0 && options.groupAssigneeIds.length > 0) {
2342
+ payload.group_assignees = options.groupAssigneeIds;
2343
+ }
2317
2344
  if (options.tags !== void 0) {
2318
2345
  payload.tags = options.tags.split(",").map((t) => t.trim());
2319
2346
  }
@@ -3466,13 +3493,26 @@ function configPath2() {
3466
3493
  function parseIdList(value) {
3467
3494
  return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
3468
3495
  }
3496
+ function createGroupCache(client) {
3497
+ let cache = null;
3498
+ const cachingClient = {
3499
+ getGroups: async () => {
3500
+ if (cache === null) cache = await client.getGroups();
3501
+ return cache;
3502
+ }
3503
+ };
3504
+ return (value) => resolveGroupId(cachingClient, value);
3505
+ }
3469
3506
  async function assignTask(config, taskId, opts) {
3470
- if (!opts.to && !opts.remove) {
3471
- throw new Error("Provide at least one of: --to, --remove");
3507
+ if (!opts.to && !opts.remove && !opts.group && !opts.removeGroup) {
3508
+ throw new Error("Provide at least one of: --to, --remove, --group, --remove-group");
3472
3509
  }
3473
3510
  const client = new ClickUpClient(config);
3511
+ const resolveGroup = createGroupCache(client);
3474
3512
  const add = [];
3475
3513
  const rem = [];
3514
+ const groupAdd = [];
3515
+ const groupRem = [];
3476
3516
  if (opts.to) {
3477
3517
  for (const value of parseIdList(opts.to)) {
3478
3518
  add.push(await resolveAssigneeId(client, value));
@@ -3483,12 +3523,30 @@ async function assignTask(config, taskId, opts) {
3483
3523
  rem.push(await resolveAssigneeId(client, value));
3484
3524
  }
3485
3525
  }
3486
- return client.updateTask(taskId, {
3487
- assignees: {
3526
+ if (opts.group) {
3527
+ for (const value of parseIdList(opts.group)) {
3528
+ groupAdd.push(await resolveGroup(value));
3529
+ }
3530
+ }
3531
+ if (opts.removeGroup) {
3532
+ for (const value of parseIdList(opts.removeGroup)) {
3533
+ groupRem.push(await resolveGroup(value));
3534
+ }
3535
+ }
3536
+ const payload = {};
3537
+ if (add.length > 0 || rem.length > 0) {
3538
+ payload.assignees = {
3488
3539
  ...add.length > 0 ? { add } : {},
3489
3540
  ...rem.length > 0 ? { rem } : {}
3490
- }
3491
- });
3541
+ };
3542
+ }
3543
+ if (groupAdd.length > 0 || groupRem.length > 0) {
3544
+ payload.group_assignees = {
3545
+ ...groupAdd.length > 0 ? { add: groupAdd } : {},
3546
+ ...groupRem.length > 0 ? { rem: groupRem } : {}
3547
+ };
3548
+ }
3549
+ return client.updateTask(taskId, payload);
3492
3550
  }
3493
3551
 
3494
3552
  // src/commands/activity.ts
@@ -3687,6 +3745,8 @@ var commandMetadata = [
3687
3745
  "--time-estimate",
3688
3746
  "--assignee",
3689
3747
  "--remove-assignee",
3748
+ "--group-assignee",
3749
+ "--remove-group-assignee",
3690
3750
  "--parent",
3691
3751
  "--detach",
3692
3752
  "--archive",
@@ -3715,6 +3775,7 @@ var commandMetadata = [
3715
3775
  "--due-date",
3716
3776
  "--start-date",
3717
3777
  "--assignee",
3778
+ "--group-assignee",
3718
3779
  "--tags",
3719
3780
  "--custom-item-id",
3720
3781
  "--time-estimate",
@@ -3912,10 +3973,14 @@ var commandMetadata = [
3912
3973
  },
3913
3974
  {
3914
3975
  name: "assign",
3915
- description: "Assign or unassign users from a task",
3916
- flags: ["--to", "--remove", "--json"],
3976
+ description: "Assign or unassign users and groups from a task",
3977
+ flags: ["--to", "--remove", "--group", "--remove-group", "--json"],
3917
3978
  quickReference: [
3918
- { section: "write", usage: "assign <taskId>", description: "Assign or unassign users" }
3979
+ {
3980
+ section: "write",
3981
+ usage: "assign <taskId>",
3982
+ description: "Assign or unassign users and groups"
3983
+ }
3919
3984
  ]
3920
3985
  },
3921
3986
  {
@@ -4310,6 +4375,18 @@ var commandMetadata = [
4310
4375
  flags: ["--json"],
4311
4376
  quickReference: [{ section: "read", usage: "members", description: "List workspace members" }]
4312
4377
  },
4378
+ {
4379
+ name: "groups",
4380
+ description: "List user groups (teams) in your workspace",
4381
+ flags: ["--json"],
4382
+ quickReference: [
4383
+ {
4384
+ section: "read",
4385
+ usage: "groups",
4386
+ description: "List user groups (teams) in your workspace"
4387
+ }
4388
+ ]
4389
+ },
4313
4390
  {
4314
4391
  name: "fields",
4315
4392
  description: "List custom fields for a list",
@@ -7102,6 +7179,36 @@ function formatMembersMarkdown(members) {
7102
7179
  return members.map((m) => `- **${m.username}** (${m.id}) - ${m.email}`).join("\n");
7103
7180
  }
7104
7181
 
7182
+ // src/commands/groups.ts
7183
+ var GROUP_COLUMNS = [
7184
+ { key: "handle", label: "Handle", maxWidth: 25 },
7185
+ { key: "name", label: "Name", maxWidth: 30 },
7186
+ { key: "id", label: "ID", maxWidth: 38 },
7187
+ { key: "members", label: "Members", maxWidth: 8 }
7188
+ ];
7189
+ async function listGroups(config) {
7190
+ const client = new ClickUpClient(config);
7191
+ return client.getGroups();
7192
+ }
7193
+ function formatGroupsTable(groups) {
7194
+ if (groups.length === 0) return "No groups found";
7195
+ const rows = groups.map((g) => ({
7196
+ handle: g.handle,
7197
+ name: g.name,
7198
+ id: g.id,
7199
+ members: String(g.members?.length ?? 0)
7200
+ }));
7201
+ return formatTable(rows, GROUP_COLUMNS);
7202
+ }
7203
+ function formatGroupsMarkdown(groups) {
7204
+ if (groups.length === 0) return "No groups found";
7205
+ return groups.map((g) => {
7206
+ const count = g.members?.length ?? 0;
7207
+ const label = count === 1 ? "1 member" : `${count} members`;
7208
+ return `- **@${g.handle}** ${g.name} (${g.id}) - ${label}`;
7209
+ }).join("\n");
7210
+ }
7211
+
7105
7212
  // src/commands/fields.ts
7106
7213
  import chalk16 from "chalk";
7107
7214
  var FIELD_COLUMNS = [
@@ -8172,6 +8279,19 @@ function parseOptionalNumberOption(value, optionName) {
8172
8279
  }
8173
8280
  return parsed;
8174
8281
  }
8282
+ function splitCommaList(value) {
8283
+ return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
8284
+ }
8285
+ function createCachedGroupResolver(client) {
8286
+ let cache = null;
8287
+ const cachingClient = {
8288
+ getGroups: async () => {
8289
+ if (cache === null) cache = await client.getGroups();
8290
+ return cache;
8291
+ }
8292
+ };
8293
+ return (value) => resolveGroupId(cachingClient, value);
8294
+ }
8175
8295
  function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
8176
8296
  const program = new Command();
8177
8297
  program.name(programName).description("ClickUp CLI for AI agents").version(version).allowExcessArguments(false).option("-p, --profile <name>", "Use a specific profile");
@@ -8292,7 +8412,13 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
8292
8412
  ).option(
8293
8413
  "--time-estimate <duration>",
8294
8414
  'Time estimate (e.g. "2h", "30m", "1h30m", "0" or "none" to clear)'
8295
- ).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(
8415
+ ).option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--remove-assignee <userId>", 'Remove assignee by user ID or "me"').option(
8416
+ "--group-assignee <groupIds...>",
8417
+ "Add group assignee (UUID or @handle, can repeat or comma-separated)"
8418
+ ).option(
8419
+ "--remove-group-assignee <groupIds...>",
8420
+ "Remove group assignee (UUID or @handle, can repeat or comma-separated)"
8421
+ ).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(
8296
8422
  wrapAction(
8297
8423
  async (taskId, opts) => {
8298
8424
  const config = loadConfig(getProfileName());
@@ -8306,12 +8432,25 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
8306
8432
  opts.removeAssignee = String(id);
8307
8433
  }) : Promise.resolve()
8308
8434
  ]);
8435
+ const groupAddRaw = (opts.groupAssignee ?? []).flatMap(splitCommaList);
8436
+ const groupRemRaw = (opts.removeGroupAssignee ?? []).flatMap(splitCommaList);
8437
+ if (groupAddRaw.length > 0 || groupRemRaw.length > 0) {
8438
+ const resolveGroup = createCachedGroupResolver(client);
8439
+ opts.groupAssigneeIds = [];
8440
+ for (const value of groupAddRaw) {
8441
+ opts.groupAssigneeIds.push(await resolveGroup(value));
8442
+ }
8443
+ opts.removeGroupAssigneeIds = [];
8444
+ for (const value of groupRemRaw) {
8445
+ opts.removeGroupAssigneeIds.push(await resolveGroup(value));
8446
+ }
8447
+ }
8309
8448
  const payload = buildUpdatePayload(opts, timezone);
8310
8449
  const hasFields = (opts.field?.length ?? 0) > 0;
8311
8450
  const hasTypeName = opts.type !== void 0;
8312
8451
  if (!hasFields && !hasTypeName && Object.keys(payload).length === 0) {
8313
8452
  throw new Error(
8314
- "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --remove-assignee, --parent, --archive, --unarchive, --type, --field"
8453
+ "Provide at least one of: --name, --description, --status, --priority, --due-date, --time-estimate, --assignee, --remove-assignee, --group-assignee, --remove-group-assignee, --parent, --archive, --unarchive, --type, --field"
8315
8454
  );
8316
8455
  }
8317
8456
  let result;
@@ -8345,49 +8484,62 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
8345
8484
  ).option(
8346
8485
  "--start-date <date>",
8347
8486
  "Start date (YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or full ISO 8601 with offset)"
8348
- ).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(
8349
- wrapAction(async (opts) => {
8350
- const config = loadConfig(getProfileName());
8351
- if (opts.list === "sprint:current") {
8352
- opts.list = await resolveActiveSprintListId(config);
8353
- }
8354
- if (opts.assignee === "me") {
8355
- const client = new ClickUpClient(config);
8356
- opts.assignee = String(await resolveAssigneeId(client, "me"));
8357
- }
8358
- if (opts.field?.length) {
8359
- if (opts.field.length % 2 !== 0) {
8360
- throw new Error('--field requires pairs: --field "Name" value');
8487
+ ).option("--assignee <userId>", 'Assignee user ID or "me"').option("--group-assignee <groupIds>", "Group assignees (UUID or @handle, comma-separated)").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(
8488
+ wrapAction(
8489
+ async (opts) => {
8490
+ const config = loadConfig(getProfileName());
8491
+ if (opts.list === "sprint:current") {
8492
+ opts.list = await resolveActiveSprintListId(config);
8361
8493
  }
8362
- let listId = opts.list;
8363
- if (!listId && opts.parent) {
8364
- const client2 = new ClickUpClient(config);
8365
- const parentTask = await client2.getTask(opts.parent);
8366
- listId = parentTask.list.id;
8367
- opts.list = listId;
8494
+ if (opts.assignee === "me") {
8495
+ const client = new ClickUpClient(config);
8496
+ opts.assignee = String(await resolveAssigneeId(client, "me"));
8368
8497
  }
8369
- if (!listId) {
8370
- throw new Error("--field requires --list or --parent to resolve custom field names");
8498
+ if (opts.groupAssignee !== void 0) {
8499
+ const rawList = splitCommaList(opts.groupAssignee);
8500
+ if (rawList.length > 0) {
8501
+ const client = new ClickUpClient(config);
8502
+ const resolveGroup = createCachedGroupResolver(client);
8503
+ opts.groupAssigneeIds = [];
8504
+ for (const value of rawList) {
8505
+ opts.groupAssigneeIds.push(await resolveGroup(value));
8506
+ }
8507
+ }
8371
8508
  }
8372
- const client = new ClickUpClient(config);
8373
- const fields = await client.getListCustomFields(listId);
8374
- const customFields = [];
8375
- for (let i = 0; i < opts.field.length; i += 2) {
8376
- const fieldName = opts.field[i];
8377
- const rawValue = opts.field[i + 1];
8378
- const field = findFieldByName(fields, fieldName);
8379
- const value = parseFieldValue(field, rawValue);
8380
- customFields.push({ id: field.id, value });
8509
+ if (opts.field?.length) {
8510
+ if (opts.field.length % 2 !== 0) {
8511
+ throw new Error('--field requires pairs: --field "Name" value');
8512
+ }
8513
+ let listId = opts.list;
8514
+ if (!listId && opts.parent) {
8515
+ const client2 = new ClickUpClient(config);
8516
+ const parentTask = await client2.getTask(opts.parent);
8517
+ listId = parentTask.list.id;
8518
+ opts.list = listId;
8519
+ }
8520
+ if (!listId) {
8521
+ throw new Error("--field requires --list or --parent to resolve custom field names");
8522
+ }
8523
+ const client = new ClickUpClient(config);
8524
+ const fields = await client.getListCustomFields(listId);
8525
+ const customFields = [];
8526
+ for (let i = 0; i < opts.field.length; i += 2) {
8527
+ const fieldName = opts.field[i];
8528
+ const rawValue = opts.field[i + 1];
8529
+ const field = findFieldByName(fields, fieldName);
8530
+ const value = parseFieldValue(field, rawValue);
8531
+ customFields.push({ id: field.id, value });
8532
+ }
8533
+ opts.customFields = customFields;
8534
+ }
8535
+ const result = await createTask(config, opts);
8536
+ if (shouldOutputJson(opts.json ?? false)) {
8537
+ console.log(JSON.stringify(result, null, 2));
8538
+ } else {
8539
+ console.log(formatCreateConfirmation(result.id, result.name, result.url));
8381
8540
  }
8382
- opts.customFields = customFields;
8383
- }
8384
- const result = await createTask(config, opts);
8385
- if (shouldOutputJson(opts.json ?? false)) {
8386
- console.log(JSON.stringify(result, null, 2));
8387
- } else {
8388
- console.log(formatCreateConfirmation(result.id, result.name, result.url));
8389
8541
  }
8390
- })
8542
+ )
8391
8543
  );
8392
8544
  program.command("sprint").description("List my tasks in the current active sprint (auto-detected)").option("--status <status>", "Filter by status").option("--space <nameOrId>", "Narrow sprint search to a specific space (partial name or ID)").option("--folder <folderId>", "Sprint folder ID (overrides config and auto-detection)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
8393
8545
  wrapAction(
@@ -8664,16 +8816,25 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
8664
8816
  await printTasks(tasks, opts.json ?? false, config);
8665
8817
  })
8666
8818
  );
8667
- program.command("assign <taskId>").description("Assign or unassign users from a task").option("--to <userId>", 'Add assignee (user ID or "me")').option("--remove <userId>", 'Remove assignee (user ID or "me")').option("--json", "Force JSON output even in terminal").action(
8668
- wrapAction(async (taskId, opts) => {
8669
- const config = loadConfig(getProfileName());
8670
- const result = await assignTask(config, taskId, opts);
8671
- if (shouldOutputJson(opts.json ?? false)) {
8672
- console.log(JSON.stringify(result, null, 2));
8673
- } else {
8674
- console.log(formatAssignConfirmation(taskId, { to: opts.to, remove: opts.remove }));
8819
+ program.command("assign <taskId>").description("Assign or unassign users and groups from a task").option("--to <userId>", 'Add assignee (user ID or "me", comma-separated)').option("--remove <userId>", 'Remove assignee (user ID or "me", comma-separated)').option("--group <groupIds>", "Add group assignee (UUID or @handle, comma-separated)").option("--remove-group <groupIds>", "Remove group assignee (UUID or @handle, comma-separated)").option("--json", "Force JSON output even in terminal").action(
8820
+ wrapAction(
8821
+ async (taskId, opts) => {
8822
+ const config = loadConfig(getProfileName());
8823
+ const result = await assignTask(config, taskId, opts);
8824
+ if (shouldOutputJson(opts.json ?? false)) {
8825
+ console.log(JSON.stringify(result, null, 2));
8826
+ } else {
8827
+ console.log(
8828
+ formatAssignConfirmation(taskId, {
8829
+ to: opts.to,
8830
+ remove: opts.remove,
8831
+ group: opts.group,
8832
+ removeGroup: opts.removeGroup
8833
+ })
8834
+ );
8835
+ }
8675
8836
  }
8676
- })
8837
+ )
8677
8838
  );
8678
8839
  program.command("depend <taskId>").description("Add or remove task dependencies").option("--on <taskId>", "Task that this task depends on (waiting on)").option("--blocks <taskId>", "Task that this task blocks").option("--remove", "Remove the dependency instead of adding it").option("--json", "Force JSON output even in terminal").action(
8679
8840
  wrapAction(async (taskId, opts) => {
@@ -9125,6 +9286,19 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
9125
9286
  }
9126
9287
  })
9127
9288
  );
9289
+ program.command("groups").description("List user groups (teams) in your workspace").option("--json", "Force JSON output even in terminal").action(
9290
+ wrapAction(async (opts) => {
9291
+ const config = loadConfig(getProfileName());
9292
+ const groups = await listGroups(config);
9293
+ if (shouldOutputJson(opts.json ?? false)) {
9294
+ console.log(JSON.stringify(groups, null, 2));
9295
+ } else if (isTTY()) {
9296
+ console.log(formatGroupsTable(groups));
9297
+ } else {
9298
+ console.log(formatGroupsMarkdown(groups));
9299
+ }
9300
+ })
9301
+ );
9128
9302
  program.command("fields <listId>").description("List custom fields for a list").option("--json", "Force JSON output even in terminal").action(
9129
9303
  wrapAction(async (listId, opts) => {
9130
9304
  const config = loadConfig(getProfileName());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.30.1",
3
+ "version": "1.31.1",
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.30.1
6
+ # ClickUp CLI (`cup`) - skill version 1.31.1
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.30.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.31.1, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -126,6 +126,7 @@ All commands support `--help` for full flag details. All commands support `--jso
126
126
  | `cup folders <spaceId> [--name partial] [--archived]` | Folders in a space (with their lists) |
127
127
  | `cup time-in-status <id>` | Show how long a task has been in each status |
128
128
  | `cup members` | Workspace members (username, ID, email) |
129
+ | `cup groups` | User groups/teams (handle, name, UUID, member count) for `--group-assignee` flags |
129
130
  | `cup fields <listId>` | Custom fields on a list (type, required, options) |
130
131
  | `cup attachments <taskId>` | List attachments on a task (name, size, URL) |
131
132
  | `cup task-members <taskId>` | List members with access to a task |
@@ -158,98 +159,98 @@ All commands support `--help` for full flag details. All commands support `--jso
158
159
 
159
160
  ### Write
160
161
 
161
- | Command | What it does |
162
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
163
- | `cup create -n name [-l listId\|sprint:current] [-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] [--field "Name" val]` | Create task (`--list` accepts `sprint:current`, `--field` sets custom fields inline) |
164
- | `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d\|none] [--start-date d] [--time-estimate t] [--assignee id\|me] [--remove-assignee id\|me] [--parent id] [--detach] [--archive] [--unarchive] [--type type] [--field "Name" val]` | Update task fields (including custom fields) |
165
- | `cup comment <id> -m text [--notify-all]` | Post comment (markdown auto-converted to rich text) |
166
- | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment (markdown auto-converted to rich text) |
167
- | `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 |
168
- | `cup replies <commentId>` | List threaded replies |
169
- | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment (markdown auto-converted to rich text) |
170
- | `cup assign <id> [--to ids\|me] [--remove ids\|me]` | Assign/unassign users (`--to`/`--remove` accept comma-separated IDs) |
171
- | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
172
- | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists (`--to` accepts `sprint:current`) |
173
- | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
174
- | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
175
- | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
176
- | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
177
- | `cup attach <taskId> <filePath>` | Upload file attachment |
178
- | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
179
- | `cup list-delete <listId> [--confirm]` | Delete list (DESTRUCTIVE, requires --confirm in non-interactive) |
180
- | `cup folder-delete <folderId> [--confirm]` | Delete folder (DESTRUCTIVE, requires --confirm in non-interactive) |
181
- | `cup space-delete <spaceId> [--confirm]` | Delete space (DESTRUCTIVE, requires --confirm in non-interactive) |
182
- | `cup duplicate <taskId>` | Duplicate a task |
183
- | `cup bulk status <status> <taskIds...>` | Bulk update status |
184
- | `cup bulk assign <taskIds...> [--to userId\|me] [--remove userId\|me]` | Bulk assign/unassign user from tasks |
185
- | `cup bulk due-date <date\|none\|clear> <taskIds...>` | Bulk set or clear due dates |
186
- | `cup bulk tag <tagName> <taskIds...> [--remove]` | Bulk add/remove tag from tasks |
187
- | `cup bulk priority <taskIds...> --to <priority>` | Bulk set priority on many tasks |
188
- | `cup bulk field <taskIds...> --set "Name" value` | Bulk set a custom field value |
189
- | `cup bulk move <taskIds...> --to <listId>` | Bulk move tasks to a destination list |
190
- | `cup checklist view <id>` | View checklists on a task |
191
- | `cup checklist create <id> <name>` | Create a checklist |
192
- | `cup checklist delete <checklistId>` | Delete a checklist |
193
- | `cup checklist add-item <checklistId> <name> [--parent itemId]` | Add item to checklist (nest under parent via `--parent`) |
194
- | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id] [--parent itemId\|null]` | Edit checklist item (reparent with `--parent`, use `"null"` to unnest) |
195
- | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
196
- | `cup time start <taskId> [-d desc]` | Start timer |
197
- | `cup time stop` | Stop running timer |
198
- | `cup time status` | Show running timer |
199
- | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
200
- | `cup time list [--days n] [--task id] [--all]` | List my recent time entries (--all for team) |
201
- | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
202
- | `cup time delete <timeEntryId>` | Delete time entry |
203
- | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
204
- | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
205
- | `cup goal-delete <goalId>` | Delete a goal |
206
- | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
207
- | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
208
- | `cup key-result-delete <keyResultId>` | Delete key result |
209
- | `cup doc-create <title> [-c content]` | Create a doc |
210
- | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
211
- | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
212
- | `cup doc-delete <docId>` | Delete a doc |
213
- | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
214
- | `cup space-create <name>` | Create a space |
215
- | `cup list-create <spaceId> <name> [--folder folderId] [--copy-statuses-from id]` | Create a list in a space or folder |
216
- | `cup folder-create <spaceId> <name>` | Create a folder in a space |
217
- | `cup list-rename <listId> <newName>` | Rename a list |
218
- | `cup folder-rename <folderId> <newName>` | Rename a folder |
219
- | `cup space-rename <spaceId> <newName>` | Rename a space |
220
- | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
221
- | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
222
- | `cup tag-delete <spaceId> <name>` | Delete space tag |
223
- | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
224
- | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
225
- | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
226
- | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
227
- | `cup list-comment <listId> -m text [--notify-all]` | Post comment on a list |
228
- | `cup view-comment <viewId> -m text [--notify-all]` | Post comment on a view |
229
- | `cup webhook create --url <url> --events <events> [--space\|--folder\|--list\|--task <id>]` | Create a webhook |
230
- | `cup webhook update <webhookId> [--url u] [--events e] [--status s]` | Update a webhook |
231
- | `cup webhook delete <webhookId> [--confirm]` | Delete a webhook (DESTRUCTIVE) |
232
- | `cup merge <sourceTaskId> <intoTaskId> [--confirm]` | Merge task into another (DESTRUCTIVE, source deleted) |
233
- | `cup time estimate-by-user <taskId> <userId> <duration> [--replace]` | Set per-user time estimate |
234
- | `cup chat send <channelId> -m <text> [--post --title t]` | Send message to channel |
235
- | `cup chat reply <messageId> -m <text>` | Reply to a message |
236
- | `cup chat react <messageId> --emoji <name>` | Add reaction |
237
- | `cup chat unreact <messageId> --emoji <name>` | Remove reaction |
238
- | `cup chat channel-create <name> [--private] [--topic t]` | Create channel |
239
- | `cup chat dm <userIds...>` | Create/open DM |
240
- | `cup chat channel-update <id> [--name n] [--topic t]` | Update channel |
241
- | `cup chat channel-delete <id> [--confirm]` | Delete channel |
242
- | `cup chat message-update <messageId> -m <text>` | Edit message |
243
- | `cup chat message-delete <messageId> [--confirm]` | Delete message |
244
- | `cup favorite add <type> <id> [alias] [-n name]` | Add a local favorite |
245
- | `cup favorite remove <alias>` | Remove a favorite |
246
- | `cup favorite list [--type t]` | List favorites (optionally filter by type) |
247
- | `cup profile list [--json]` | List all profiles |
248
- | `cup profile add <name>` | Add a new profile (interactive) |
249
- | `cup profile remove <name>` | Remove a profile |
250
- | `cup profile use <name>` | Set the default profile |
251
- | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
252
- | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
162
+ | Command | What it does |
163
+ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
164
+ | `cup create -n name [-l listId\|sprint:current] [-p parentId] [-d desc] [-s status] [--priority p] [--due-date d] [--start-date d] [--time-estimate t] [--assignee id\|me] [--group-assignee uuid\|@handle,...] [--tags t] [--custom-item-id n] [--template id] [--field "Name" val]` | Create task (`--list` accepts `sprint:current`, `--field` sets custom fields inline) |
165
+ | `cup update <id> [-n name] [-d desc] [-s status] [--priority p] [--due-date d\|none] [--start-date d] [--time-estimate t] [--assignee id\|me] [--remove-assignee id\|me] [--group-assignee uuid\|@handle] [--remove-group-assignee uuid\|@handle] [--parent id] [--detach] [--archive] [--unarchive] [--type type] [--field "Name" val]` | Update task fields (including custom fields and group assignees) |
166
+ | `cup comment <id> -m text [--notify-all]` | Post comment (markdown auto-converted to rich text) |
167
+ | `cup comment-edit <commentId> -m text [--resolved] [--unresolved]` | Edit a comment (markdown auto-converted to rich text) |
168
+ | `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 |
169
+ | `cup replies <commentId>` | List threaded replies |
170
+ | `cup reply <commentId> -m text [--notify-all]` | Reply to a comment (markdown auto-converted to rich text) |
171
+ | `cup assign <id> [--to ids\|me] [--remove ids\|me] [--group uuid\|@handle,...] [--remove-group uuid\|@handle,...]` | Assign/unassign users and groups (all flags accept comma-separated values) |
172
+ | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
173
+ | `cup move <id> [--to listId\|sprint:current] [--remove listId]` | Add/remove task from lists. **`--to` + `--remove` together changes the task's _home_ list** (uses v3 `home_list` endpoint with auto status mapping). `--to` alone adds multi-list membership. `--to` accepts `sprint:current`. |
174
+ | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
175
+ | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
176
+ | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
177
+ | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
178
+ | `cup attach <taskId> <filePath>` | Upload file attachment |
179
+ | `cup delete <id> [--confirm]` | Delete task (DESTRUCTIVE) |
180
+ | `cup list-delete <listId> [--confirm]` | Delete list (DESTRUCTIVE, requires --confirm in non-interactive) |
181
+ | `cup folder-delete <folderId> [--confirm]` | Delete folder (DESTRUCTIVE, requires --confirm in non-interactive) |
182
+ | `cup space-delete <spaceId> [--confirm]` | Delete space (DESTRUCTIVE, requires --confirm in non-interactive) |
183
+ | `cup duplicate <taskId>` | Duplicate a task |
184
+ | `cup bulk status <status> <taskIds...>` | Bulk update status |
185
+ | `cup bulk assign <taskIds...> [--to userId\|me] [--remove userId\|me]` | Bulk assign/unassign user from tasks |
186
+ | `cup bulk due-date <date\|none\|clear> <taskIds...>` | Bulk set or clear due dates |
187
+ | `cup bulk tag <tagName> <taskIds...> [--remove]` | Bulk add/remove tag from tasks |
188
+ | `cup bulk priority <taskIds...> --to <priority>` | Bulk set priority on many tasks |
189
+ | `cup bulk field <taskIds...> --set "Name" value` | Bulk set a custom field value |
190
+ | `cup bulk move <taskIds...> --to <listId>` | Bulk move tasks to a destination list |
191
+ | `cup checklist view <id>` | View checklists on a task |
192
+ | `cup checklist create <id> <name>` | Create a checklist |
193
+ | `cup checklist delete <checklistId>` | Delete a checklist |
194
+ | `cup checklist add-item <checklistId> <name> [--parent itemId]` | Add item to checklist (nest under parent via `--parent`) |
195
+ | `cup checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id] [--parent itemId\|null]` | Edit checklist item (reparent with `--parent`, use `"null"` to unnest) |
196
+ | `cup checklist delete-item <checklistId> <itemId>` | Delete checklist item |
197
+ | `cup time start <taskId> [-d desc]` | Start timer |
198
+ | `cup time stop` | Stop running timer |
199
+ | `cup time status` | Show running timer |
200
+ | `cup time log <taskId> <duration> [-d desc]` | Log manual entry (e.g. "2h", "30m") |
201
+ | `cup time list [--days n] [--task id] [--all]` | List my recent time entries (--all for team) |
202
+ | `cup time update <timeEntryId> [-d desc] [--duration dur]` | Update time entry |
203
+ | `cup time delete <timeEntryId>` | Delete time entry |
204
+ | `cup goal-create <name> [-d desc] [--color hex]` | Create a goal |
205
+ | `cup goal-update <goalId> [-n name] [-d desc] [--color hex]` | Update a goal |
206
+ | `cup goal-delete <goalId>` | Delete a goal |
207
+ | `cup key-result-create <goalId> <name> [--type t] [--target n]` | Create key result |
208
+ | `cup key-result-update <keyResultId> [--progress n] [--note text]` | Update key result |
209
+ | `cup key-result-delete <keyResultId>` | Delete key result |
210
+ | `cup doc-create <title> [-c content]` | Create a doc |
211
+ | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId]` | Create doc page |
212
+ | `cup doc-page-edit <docId> <pageId> [--name text] [-c content]` | Edit doc page |
213
+ | `cup doc-delete <docId>` | Delete a doc |
214
+ | `cup doc-page-delete <docId> <pageId>` | Delete doc page |
215
+ | `cup space-create <name>` | Create a space |
216
+ | `cup list-create <spaceId> <name> [--folder folderId] [--copy-statuses-from id]` | Create a list in a space or folder |
217
+ | `cup folder-create <spaceId> <name>` | Create a folder in a space |
218
+ | `cup list-rename <listId> <newName>` | Rename a list |
219
+ | `cup folder-rename <folderId> <newName>` | Rename a folder |
220
+ | `cup space-rename <spaceId> <newName>` | Rename a space |
221
+ | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
222
+ | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
223
+ | `cup tag-delete <spaceId> <name>` | Delete space tag |
224
+ | `cup list-from-template <name> --template <id> [--space id] [--folder id]` | Create list from template |
225
+ | `cup view-create <listId> <name> -t <type> [--group-by field]` | Create a view on a list |
226
+ | `cup view-update <viewId> [-n name] [--group-by field]` | Update a view |
227
+ | `cup view-delete <viewId> [--confirm]` | Delete a view (DESTRUCTIVE) |
228
+ | `cup list-comment <listId> -m text [--notify-all]` | Post comment on a list |
229
+ | `cup view-comment <viewId> -m text [--notify-all]` | Post comment on a view |
230
+ | `cup webhook create --url <url> --events <events> [--space\|--folder\|--list\|--task <id>]` | Create a webhook |
231
+ | `cup webhook update <webhookId> [--url u] [--events e] [--status s]` | Update a webhook |
232
+ | `cup webhook delete <webhookId> [--confirm]` | Delete a webhook (DESTRUCTIVE) |
233
+ | `cup merge <sourceTaskId> <intoTaskId> [--confirm]` | Merge task into another (DESTRUCTIVE, source deleted) |
234
+ | `cup time estimate-by-user <taskId> <userId> <duration> [--replace]` | Set per-user time estimate |
235
+ | `cup chat send <channelId> -m <text> [--post --title t]` | Send message to channel |
236
+ | `cup chat reply <messageId> -m <text>` | Reply to a message |
237
+ | `cup chat react <messageId> --emoji <name>` | Add reaction |
238
+ | `cup chat unreact <messageId> --emoji <name>` | Remove reaction |
239
+ | `cup chat channel-create <name> [--private] [--topic t]` | Create channel |
240
+ | `cup chat dm <userIds...>` | Create/open DM |
241
+ | `cup chat channel-update <id> [--name n] [--topic t]` | Update channel |
242
+ | `cup chat channel-delete <id> [--confirm]` | Delete channel |
243
+ | `cup chat message-update <messageId> -m <text>` | Edit message |
244
+ | `cup chat message-delete <messageId> [--confirm]` | Delete message |
245
+ | `cup favorite add <type> <id> [alias] [-n name]` | Add a local favorite |
246
+ | `cup favorite remove <alias>` | Remove a favorite |
247
+ | `cup favorite list [--type t]` | List favorites (optionally filter by type) |
248
+ | `cup profile list [--json]` | List all profiles |
249
+ | `cup profile add <name>` | Add a new profile (interactive) |
250
+ | `cup profile remove <name>` | Remove a profile |
251
+ | `cup profile use <name>` | Set the default profile |
252
+ | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
253
+ | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
253
254
 
254
255
  ## Global Flags
255
256
 
@@ -351,9 +352,14 @@ cup comment abc123def -m "Completed in PR #42"
351
352
  cup comment abc123def -m "## Results\n\n**Passed**: 15/15\n- Unit tests\n- Integration"
352
353
  cup assign abc123def --to me
353
354
  cup assign abc123def --to 12345,67890 # assign multiple users at once
355
+ cup assign abc123def --group @mobile-team # assign a user group (find handles with `cup groups`)
356
+ cup assign abc123def --to me --group @backend # assign self and a group together
357
+ cup update abc123def --group-assignee @mobile-team # group assignment via update
358
+ cup update abc123def --remove-group-assignee @backend
354
359
  cup depend task3 --on task2 # task3 waits for task2
355
- cup move task1 --to list2 --remove list1
356
- cup move task1 --to sprint:current # move to active sprint
360
+ cup move task1 --to list2 # add task1 to list2 (multi-list membership)
361
+ cup move task1 --to list2 --remove list1 # change home list from list1 to list2 (v3 home_list)
362
+ cup move task1 --to sprint:current --remove list1 # move home list to active sprint
357
363
  cup field abc123def --set "Story Points" 5
358
364
  cup tag abc123def --add "bug,frontend"
359
365
  cup checklist create abc123def "QA Steps"
@@ -393,6 +399,7 @@ cup space-rename <spaceId> "Platform" # rename a space
393
399
  cup folder-rename <folderId> "Q3" # rename a folder
394
400
  cup list-rename <listId> "Sprint 12" # rename a list
395
401
  cup members # workspace members
402
+ cup groups # user groups/teams (for --group-assignee)
396
403
  cup fields <listId> # custom fields on a list
397
404
  cup task-types # custom task types
398
405
  cup templates # task templates