@krodak/clickup-cli 1.3.0 → 1.4.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.3.0",
4
+ "version": "1.4.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -134,20 +134,20 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
134
134
 
135
135
  ### Tasks
136
136
 
137
- | Feature | Command | Status |
138
- | -------------------- | ----------------------- | ------------------ |
139
- | List my tasks | `cup tasks` | :white_check_mark: |
140
- | Get task details | `cup task <id>` | :white_check_mark: |
141
- | Create task | `cup create` | :white_check_mark: |
142
- | Update task | `cup update <id>` | :white_check_mark: |
143
- | Delete task | `cup delete <id>` | :white_check_mark: |
144
- | Search tasks | `cup search <query>` | :white_check_mark: |
145
- | Open in browser | `cup open <query>` | :white_check_mark: |
146
- | List subtasks | `cup subtasks <id>` | :white_check_mark: |
147
- | Assign / unassign | `cup assign <id>` | :white_check_mark: |
148
- | Duplicate task | `cup duplicate <id>` | :construction: |
149
- | Create from template | `cup create --template` | :construction: |
150
- | Bulk operations | `cup bulk` | :construction: |
137
+ | Feature | Command | Status |
138
+ | -------------------- | -------------------- | ------------------ |
139
+ | List my tasks | `cup tasks` | :white_check_mark: |
140
+ | Get task details | `cup task <id>` | :white_check_mark: |
141
+ | Create task | `cup create` | :white_check_mark: |
142
+ | Update task | `cup update <id>` | :white_check_mark: |
143
+ | Delete task | `cup delete <id>` | :white_check_mark: |
144
+ | Search tasks | `cup search <query>` | :white_check_mark: |
145
+ | Open in browser | `cup open <query>` | :white_check_mark: |
146
+ | List subtasks | `cup subtasks <id>` | :white_check_mark: |
147
+ | Assign / unassign | `cup assign <id>` | :white_check_mark: |
148
+ | Duplicate task | `cup duplicate <id>` | :white_check_mark: |
149
+ | Create from template | | :no_entry_sign: |
150
+ | Bulk operations | `cup bulk status` | :white_check_mark: |
151
151
 
152
152
  ### Dependencies & Relations
153
153
 
@@ -203,15 +203,16 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
203
203
  | --------------------- | ------------------------- | ------------------ |
204
204
  | Set field value | `cup field <id> --set` | :white_check_mark: |
205
205
  | Remove field value | `cup field <id> --remove` | :white_check_mark: |
206
- | List available fields | `cup fields <listId>` | :construction: |
206
+ | List available fields | `cup fields <listId>` | :white_check_mark: |
207
207
 
208
208
  ### Tags
209
209
 
210
- | Feature | Command | Status |
211
- | ----------------------- | -------------------- | ------------------ |
212
- | Add/remove tag on task | `cup tag <id>` | :white_check_mark: |
213
- | List space tags | `cup tags <spaceId>` | :white_check_mark: |
214
- | Create/delete space tag | | :construction: |
210
+ | Feature | Command | Status |
211
+ | ---------------------- | --------------------------------- | ------------------ |
212
+ | Add/remove tag on task | `cup tag <id>` | :white_check_mark: |
213
+ | List space tags | `cup tags <spaceId>` | :white_check_mark: |
214
+ | Create space tag | `cup tag-create <spaceId> <name>` | :white_check_mark: |
215
+ | Delete space tag | `cup tag-delete <spaceId> <name>` | :white_check_mark: |
215
216
 
216
217
  ### Time Tracking
217
218
 
@@ -233,15 +234,18 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
233
234
  | List lists | `cup lists <spaceId>` | :white_check_mark: |
234
235
  | Check auth | `cup auth` | :white_check_mark: |
235
236
  | List folders | `cup folders <spaceId>` | :white_check_mark: |
236
- | List members | `cup members` | :construction: |
237
+ | List members | `cup members` | :white_check_mark: |
237
238
 
238
239
  ### Goals & Key Results
239
240
 
240
- | Feature | Command | Status |
241
- | ------------------ | ----------- | -------------- |
242
- | List goals | `cup goals` | :construction: |
243
- | Create/update goal | | :construction: |
244
- | Key results CRUD | | :construction: |
241
+ | Feature | Command | Status |
242
+ | ----------------- | ------------------------------------ | ------------------ |
243
+ | List goals | `cup goals` | :white_check_mark: |
244
+ | Create goal | `cup goal-create <name>` | :white_check_mark: |
245
+ | Update goal | `cup goal-update <goalId>` | :white_check_mark: |
246
+ | List key results | `cup key-results <goalId>` | :white_check_mark: |
247
+ | Create key result | `cup key-result-create <goalId> <n>` | :white_check_mark: |
248
+ | Update key result | `cup key-result-update <krId>` | :white_check_mark: |
245
249
 
246
250
  ### Docs
247
251
 
@@ -265,6 +269,7 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
265
269
 
266
270
  | Feature | Why |
267
271
  | --------------------- | --------------------------------------------------------------------------- |
272
+ | Create from template | No public API for task templates. |
268
273
  | Webhooks | Server-side. A CLI can't listen for events. |
269
274
  | OAuth flow | `cup init` already handles auth with API tokens. |
270
275
  | Guest/ACL | Enterprise admin. Not what you reach for in a terminal. |
package/dist/index.js CHANGED
@@ -382,6 +382,25 @@ var ClickUpClient = class {
382
382
  const data = await this.request(`/space/${spaceId}/tag`);
383
383
  return data.tags ?? [];
384
384
  }
385
+ async createSpaceTag(spaceId, name, fg, bg) {
386
+ await this.request(`/space/${spaceId}/tag`, {
387
+ method: "POST",
388
+ body: JSON.stringify({
389
+ tag: { name, tag_fg: fg ?? "#000000", tag_bg: bg ?? "#04A9F4" }
390
+ })
391
+ });
392
+ }
393
+ async deleteSpaceTag(spaceId, tagName) {
394
+ await this.request(
395
+ `/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
396
+ { method: "DELETE" }
397
+ );
398
+ }
399
+ async getWorkspaceMembers(teamId) {
400
+ const data = await this.request("/team");
401
+ const team = data.teams?.find((t) => t.id === teamId);
402
+ return team?.members?.map((m) => m.user) ?? [];
403
+ }
385
404
  async deleteTimeEntry(teamId, timeEntryId) {
386
405
  await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
387
406
  method: "DELETE"
@@ -469,6 +488,52 @@ var ClickUpClient = class {
469
488
  );
470
489
  return data.pages ?? [];
471
490
  }
491
+ async getGoals(teamId) {
492
+ const data = await this.request(`/team/${teamId}/goal`);
493
+ return data.goals ?? [];
494
+ }
495
+ async createGoal(teamId, name, opts) {
496
+ const body = { name, multiple_owners: true };
497
+ if (opts?.description) body.description = opts.description;
498
+ if (opts?.dueDate) body.due_date = Number(opts.dueDate);
499
+ if (opts?.color) body.color = opts.color;
500
+ const data = await this.request(`/team/${teamId}/goal`, {
501
+ method: "POST",
502
+ body: JSON.stringify(body)
503
+ });
504
+ return data.goal;
505
+ }
506
+ async updateGoal(goalId, updates) {
507
+ const data = await this.request(`/goal/${goalId}`, {
508
+ method: "PUT",
509
+ body: JSON.stringify(updates)
510
+ });
511
+ return data.goal;
512
+ }
513
+ async getKeyResults(goalId) {
514
+ const data = await this.request(`/goal/${goalId}`);
515
+ return data.goal?.key_results ?? [];
516
+ }
517
+ async createKeyResult(goalId, name, type, stepsEnd) {
518
+ const data = await this.request(`/goal/${goalId}/key_result`, {
519
+ method: "POST",
520
+ body: JSON.stringify({
521
+ name,
522
+ type,
523
+ steps_start: 0,
524
+ steps_end: stepsEnd,
525
+ unit: type === "number" ? "items" : "%"
526
+ })
527
+ });
528
+ return data.key_result;
529
+ }
530
+ async updateKeyResult(keyResultId, updates) {
531
+ const data = await this.request(`/key_result/${keyResultId}`, {
532
+ method: "PUT",
533
+ body: JSON.stringify(updates)
534
+ });
535
+ return data.key_result;
536
+ }
472
537
  };
473
538
 
474
539
  // src/config.ts
@@ -2117,7 +2182,7 @@ function bashCompletion(name) {
2117
2182
  cword=$COMP_CWORD
2118
2183
  fi
2119
2184
 
2120
- local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag tags checklist time docs doc doc-create doc-pages doc-page-create doc-page-edit folders config completion"
2185
+ local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag tags tag-create tag-delete checklist time docs doc doc-create doc-pages doc-page-create doc-page-edit folders members fields duplicate bulk goals goal-create goal-update key-results key-result-create key-result-update config completion"
2121
2186
 
2122
2187
  if [[ $cword -eq 1 ]]; then
2123
2188
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -2253,6 +2318,44 @@ function bashCompletion(name) {
2253
2318
  tags)
2254
2319
  COMPREPLY=($(compgen -W "--json" -- "$cur"))
2255
2320
  ;;
2321
+ tag-create)
2322
+ COMPREPLY=($(compgen -W "--fg --bg --json" -- "$cur"))
2323
+ ;;
2324
+ tag-delete)
2325
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2326
+ ;;
2327
+ members)
2328
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2329
+ ;;
2330
+ fields)
2331
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2332
+ ;;
2333
+ duplicate)
2334
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2335
+ ;;
2336
+ bulk)
2337
+ if [[ $cword -eq 2 ]]; then
2338
+ COMPREPLY=($(compgen -W "status" -- "$cur"))
2339
+ fi
2340
+ ;;
2341
+ goals)
2342
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2343
+ ;;
2344
+ goal-create)
2345
+ COMPREPLY=($(compgen -W "-d --description --color --json" -- "$cur"))
2346
+ ;;
2347
+ goal-update)
2348
+ COMPREPLY=($(compgen -W "-n --name -d --description --color --json" -- "$cur"))
2349
+ ;;
2350
+ key-results)
2351
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2352
+ ;;
2353
+ key-result-create)
2354
+ COMPREPLY=($(compgen -W "--type --target --json" -- "$cur"))
2355
+ ;;
2356
+ key-result-update)
2357
+ COMPREPLY=($(compgen -W "--progress --note --json" -- "$cur"))
2358
+ ;;
2256
2359
  folders)
2257
2360
  COMPREPLY=($(compgen -W "--name --json" -- "$cur"))
2258
2361
  ;;
@@ -2332,6 +2435,18 @@ _${name}() {
2332
2435
  'doc-page-create:Create a page in a doc'
2333
2436
  'doc-page-edit:Edit a doc page'
2334
2437
  'tags:List tags in a space'
2438
+ 'tag-create:Create a tag in a space'
2439
+ 'tag-delete:Delete a tag from a space'
2440
+ 'members:List workspace members'
2441
+ 'fields:List custom fields for a list'
2442
+ 'duplicate:Duplicate a task'
2443
+ 'bulk:Bulk task operations'
2444
+ 'goals:List goals in your workspace'
2445
+ 'goal-create:Create a goal'
2446
+ 'goal-update:Update a goal'
2447
+ 'key-results:List key results for a goal'
2448
+ 'key-result-create:Create a key result on a goal'
2449
+ 'key-result-update:Update a key result'
2335
2450
  'folders:List folders in a space'
2336
2451
  'config:Manage CLI configuration'
2337
2452
  'completion:Output shell completion script'
@@ -2676,6 +2791,94 @@ _${name}() {
2676
2791
  '1:space_id:' \\
2677
2792
  '--json[Force JSON output]'
2678
2793
  ;;
2794
+ tag-create)
2795
+ _arguments \\
2796
+ '1:space_id:' \\
2797
+ '2:name:' \\
2798
+ '--fg[Foreground color]:color:' \\
2799
+ '--bg[Background color]:color:' \\
2800
+ '--json[Force JSON output]'
2801
+ ;;
2802
+ tag-delete)
2803
+ _arguments \\
2804
+ '1:space_id:' \\
2805
+ '2:name:' \\
2806
+ '--json[Force JSON output]'
2807
+ ;;
2808
+ members)
2809
+ _arguments \\
2810
+ '--json[Force JSON output]'
2811
+ ;;
2812
+ fields)
2813
+ _arguments \\
2814
+ '1:list_id:' \\
2815
+ '--json[Force JSON output]'
2816
+ ;;
2817
+ duplicate)
2818
+ _arguments \\
2819
+ '1:task_id:' \\
2820
+ '--json[Force JSON output]'
2821
+ ;;
2822
+ bulk)
2823
+ local -a bulk_cmds
2824
+ bulk_cmds=(
2825
+ 'status:Update status of multiple tasks'
2826
+ )
2827
+ _arguments -C \\
2828
+ '1:bulk command:->bulk_cmd' \\
2829
+ '*::bulk_arg:->bulk_args'
2830
+ case $state in
2831
+ bulk_cmd)
2832
+ _describe 'bulk command' bulk_cmds
2833
+ ;;
2834
+ bulk_args)
2835
+ case $words[1] in
2836
+ status)
2837
+ _arguments '1:status:' '*:task_ids:' '--json[Force JSON output]'
2838
+ ;;
2839
+ esac
2840
+ ;;
2841
+ esac
2842
+ ;;
2843
+ goals)
2844
+ _arguments \\
2845
+ '--json[Force JSON output]'
2846
+ ;;
2847
+ goal-create)
2848
+ _arguments \\
2849
+ '1:name:' \\
2850
+ '(-d --description)'{-d,--description}'[Goal description]:text:' \\
2851
+ '--color[Goal color]:color:' \\
2852
+ '--json[Force JSON output]'
2853
+ ;;
2854
+ goal-update)
2855
+ _arguments \\
2856
+ '1:goal_id:' \\
2857
+ '(-n --name)'{-n,--name}'[New goal name]:text:' \\
2858
+ '(-d --description)'{-d,--description}'[New description]:text:' \\
2859
+ '--color[New color]:color:' \\
2860
+ '--json[Force JSON output]'
2861
+ ;;
2862
+ key-results)
2863
+ _arguments \\
2864
+ '1:goal_id:' \\
2865
+ '--json[Force JSON output]'
2866
+ ;;
2867
+ key-result-create)
2868
+ _arguments \\
2869
+ '1:goal_id:' \\
2870
+ '2:name:' \\
2871
+ '--type[Key result type]:type:(number percentage)' \\
2872
+ '--target[Target value]:number:' \\
2873
+ '--json[Force JSON output]'
2874
+ ;;
2875
+ key-result-update)
2876
+ _arguments \\
2877
+ '1:key_result_id:' \\
2878
+ '--progress[Current progress]:number:' \\
2879
+ '--note[Progress note]:text:' \\
2880
+ '--json[Force JSON output]'
2881
+ ;;
2679
2882
  folders)
2680
2883
  _arguments \\
2681
2884
  '1:space_id:' \\
@@ -2785,6 +2988,18 @@ complete -c ${name} -n __fish_use_subcommand -a doc-pages -d 'List all pages in
2785
2988
  complete -c ${name} -n __fish_use_subcommand -a doc-page-create -d 'Create a page in a doc'
2786
2989
  complete -c ${name} -n __fish_use_subcommand -a doc-page-edit -d 'Edit a doc page'
2787
2990
  complete -c ${name} -n __fish_use_subcommand -a tags -d 'List tags in a space'
2991
+ complete -c ${name} -n __fish_use_subcommand -a tag-create -d 'Create a tag in a space'
2992
+ complete -c ${name} -n __fish_use_subcommand -a tag-delete -d 'Delete a tag from a space'
2993
+ complete -c ${name} -n __fish_use_subcommand -a members -d 'List workspace members'
2994
+ complete -c ${name} -n __fish_use_subcommand -a fields -d 'List custom fields for a list'
2995
+ complete -c ${name} -n __fish_use_subcommand -a duplicate -d 'Duplicate a task'
2996
+ complete -c ${name} -n __fish_use_subcommand -a bulk -d 'Bulk task operations'
2997
+ complete -c ${name} -n __fish_use_subcommand -a goals -d 'List goals in your workspace'
2998
+ complete -c ${name} -n __fish_use_subcommand -a goal-create -d 'Create a goal'
2999
+ complete -c ${name} -n __fish_use_subcommand -a goal-update -d 'Update a goal'
3000
+ complete -c ${name} -n __fish_use_subcommand -a key-results -d 'List key results for a goal'
3001
+ complete -c ${name} -n __fish_use_subcommand -a key-result-create -d 'Create a key result on a goal'
3002
+ complete -c ${name} -n __fish_use_subcommand -a key-result-update -d 'Update a key result'
2788
3003
  complete -c ${name} -n __fish_use_subcommand -a folders -d 'List folders in a space'
2789
3004
  complete -c ${name} -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2790
3005
  complete -c ${name} -n __fish_use_subcommand -a completion -d 'Output shell completion script'
@@ -2947,6 +3162,42 @@ complete -c ${name} -n '__fish_seen_subcommand_from doc-pages' -l json -d 'Force
2947
3162
 
2948
3163
  complete -c ${name} -n '__fish_seen_subcommand_from tags' -l json -d 'Force JSON output'
2949
3164
 
3165
+ complete -c ${name} -n '__fish_seen_subcommand_from tag-create' -l fg -d 'Foreground color'
3166
+ complete -c ${name} -n '__fish_seen_subcommand_from tag-create' -l bg -d 'Background color'
3167
+ complete -c ${name} -n '__fish_seen_subcommand_from tag-create' -l json -d 'Force JSON output'
3168
+
3169
+ complete -c ${name} -n '__fish_seen_subcommand_from tag-delete' -l json -d 'Force JSON output'
3170
+
3171
+ complete -c ${name} -n '__fish_seen_subcommand_from members' -l json -d 'Force JSON output'
3172
+
3173
+ complete -c ${name} -n '__fish_seen_subcommand_from fields' -l json -d 'Force JSON output'
3174
+
3175
+ complete -c ${name} -n '__fish_seen_subcommand_from duplicate' -l json -d 'Force JSON output'
3176
+
3177
+ complete -c ${name} -n '__fish_seen_subcommand_from bulk; and not __fish_seen_subcommand_from status' -a status -d 'Update status of multiple tasks'
3178
+ complete -c ${name} -n '__fish_seen_subcommand_from status; and __fish_seen_subcommand_from bulk' -l json -d 'Force JSON output'
3179
+
3180
+ complete -c ${name} -n '__fish_seen_subcommand_from goals' -l json -d 'Force JSON output'
3181
+
3182
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-create' -s d -l description -d 'Goal description'
3183
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-create' -l color -d 'Goal color'
3184
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-create' -l json -d 'Force JSON output'
3185
+
3186
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-update' -s n -l name -d 'New goal name'
3187
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-update' -s d -l description -d 'New description'
3188
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-update' -l color -d 'New color'
3189
+ complete -c ${name} -n '__fish_seen_subcommand_from goal-update' -l json -d 'Force JSON output'
3190
+
3191
+ complete -c ${name} -n '__fish_seen_subcommand_from key-results' -l json -d 'Force JSON output'
3192
+
3193
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-create' -l type -d 'Key result type' -a 'number percentage'
3194
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-create' -l target -d 'Target value'
3195
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-create' -l json -d 'Force JSON output'
3196
+
3197
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-update' -l progress -d 'Current progress'
3198
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-update' -l note -d 'Progress note'
3199
+ complete -c ${name} -n '__fish_seen_subcommand_from key-result-update' -l json -d 'Force JSON output'
3200
+
2950
3201
  complete -c ${name} -n '__fish_seen_subcommand_from folders' -l name -d 'Filter by folder name'
2951
3202
  complete -c ${name} -n '__fish_seen_subcommand_from folders' -l json -d 'Force JSON output'
2952
3203
 
@@ -3567,6 +3818,14 @@ async function listSpaceTags(config, spaceId) {
3567
3818
  const client = new ClickUpClient(config);
3568
3819
  return client.getSpaceTags(spaceId);
3569
3820
  }
3821
+ async function createSpaceTag(config, spaceId, name, fg, bg) {
3822
+ const client = new ClickUpClient(config);
3823
+ await client.createSpaceTag(spaceId, name, fg, bg);
3824
+ }
3825
+ async function deleteSpaceTag(config, spaceId, tagName) {
3826
+ const client = new ClickUpClient(config);
3827
+ await client.deleteSpaceTag(spaceId, tagName);
3828
+ }
3570
3829
  function formatTags(tags) {
3571
3830
  if (tags.length === 0) return "No tags found";
3572
3831
  return tags.map((t) => chalk11.bold(t.name)).join(", ");
@@ -3576,6 +3835,133 @@ function formatTagsMarkdown(tags) {
3576
3835
  return tags.map((t) => `- ${t.name}`).join("\n");
3577
3836
  }
3578
3837
 
3838
+ // src/commands/members.ts
3839
+ import chalk12 from "chalk";
3840
+ async function listMembers(config) {
3841
+ const client = new ClickUpClient(config);
3842
+ return client.getWorkspaceMembers(config.teamId);
3843
+ }
3844
+ function formatMembers(members) {
3845
+ if (members.length === 0) return "No members found";
3846
+ return members.map((m) => `${chalk12.bold(m.username)} ${chalk12.dim(`(${m.id})`)} ${m.email}`).join("\n");
3847
+ }
3848
+ function formatMembersMarkdown(members) {
3849
+ if (members.length === 0) return "No members found";
3850
+ return members.map((m) => `- **${m.username}** (${m.id}) - ${m.email}`).join("\n");
3851
+ }
3852
+
3853
+ // src/commands/fields.ts
3854
+ import chalk13 from "chalk";
3855
+ async function listFields(config, listId) {
3856
+ const client = new ClickUpClient(config);
3857
+ return client.getListCustomFields(listId);
3858
+ }
3859
+ function formatFields(fields) {
3860
+ if (fields.length === 0) return "No custom fields";
3861
+ return fields.map((f) => {
3862
+ const options = f.type_config?.options?.map((o) => o.name).join(", ");
3863
+ const optStr = options ? ` ${chalk13.dim(`[${options}]`)}` : "";
3864
+ return `${chalk13.bold(f.name)} ${chalk13.dim(f.type)}${f.required ? chalk13.yellow(" (required)") : ""}${optStr}`;
3865
+ }).join("\n");
3866
+ }
3867
+ function formatFieldsMarkdown(fields) {
3868
+ if (fields.length === 0) return "No custom fields";
3869
+ return fields.map((f) => {
3870
+ const options = f.type_config?.options?.map((o) => o.name).join(", ");
3871
+ const optStr = options ? ` [${options}]` : "";
3872
+ return `- **${f.name}** (${f.type})${f.required ? " - required" : ""}${optStr}`;
3873
+ }).join("\n");
3874
+ }
3875
+
3876
+ // src/commands/duplicate.ts
3877
+ async function duplicateTask(config, taskId) {
3878
+ const client = new ClickUpClient(config);
3879
+ const task = await client.getTask(taskId);
3880
+ const created = await client.createTask(task.list.id, {
3881
+ name: `${task.name} (copy)`,
3882
+ description: task.description,
3883
+ markdown_content: task.markdown_content,
3884
+ priority: task.priority ? Number(task.priority.priority) : void 0,
3885
+ tags: task.tags?.map((t) => t.name),
3886
+ time_estimate: task.time_estimate ?? void 0
3887
+ });
3888
+ return { id: created.id, name: created.name, url: created.url };
3889
+ }
3890
+
3891
+ // src/commands/bulk.ts
3892
+ async function bulkUpdateStatus(config, taskIds, status) {
3893
+ const client = new ClickUpClient(config);
3894
+ const failed = [];
3895
+ for (const id of taskIds) {
3896
+ try {
3897
+ await client.updateTask(id, { status });
3898
+ } catch {
3899
+ failed.push(id);
3900
+ }
3901
+ }
3902
+ return { updated: taskIds.length - failed.length, failed };
3903
+ }
3904
+
3905
+ // src/commands/goals.ts
3906
+ import chalk14 from "chalk";
3907
+ async function listGoals(config) {
3908
+ const client = new ClickUpClient(config);
3909
+ return client.getGoals(config.teamId);
3910
+ }
3911
+ async function createGoal(config, name, opts) {
3912
+ const client = new ClickUpClient(config);
3913
+ return client.createGoal(config.teamId, name, opts);
3914
+ }
3915
+ async function updateGoal(config, goalId, updates) {
3916
+ const client = new ClickUpClient(config);
3917
+ return client.updateGoal(goalId, updates);
3918
+ }
3919
+ async function listKeyResults(config, goalId) {
3920
+ const client = new ClickUpClient(config);
3921
+ return client.getKeyResults(goalId);
3922
+ }
3923
+ async function createKeyResult(config, goalId, name, type, target) {
3924
+ const client = new ClickUpClient(config);
3925
+ return client.createKeyResult(goalId, name, type, target);
3926
+ }
3927
+ async function updateKeyResult(config, keyResultId, updates) {
3928
+ const client = new ClickUpClient(config);
3929
+ return client.updateKeyResult(keyResultId, {
3930
+ steps_current: updates.progress,
3931
+ note: updates.note
3932
+ });
3933
+ }
3934
+ function formatGoals(goals) {
3935
+ if (goals.length === 0) return "No goals found";
3936
+ return goals.map((g) => {
3937
+ const pct = Math.round(g.percent_completed * 100);
3938
+ const owner = g.owner ? ` ${chalk14.dim(`@${g.owner.username}`)}` : "";
3939
+ return `${chalk14.bold(g.name)} ${chalk14.dim(`(${g.id})`)} ${chalk14.cyan(`${pct}%`)}${owner}`;
3940
+ }).join("\n");
3941
+ }
3942
+ function formatGoalsMarkdown(goals) {
3943
+ if (goals.length === 0) return "No goals found";
3944
+ return goals.map((g) => {
3945
+ const pct = Math.round(g.percent_completed * 100);
3946
+ const owner = g.owner ? ` - @${g.owner.username}` : "";
3947
+ return `- **${g.name}** (${g.id}) - ${pct}%${owner}`;
3948
+ }).join("\n");
3949
+ }
3950
+ function formatKeyResults(keyResults) {
3951
+ if (keyResults.length === 0) return "No key results found";
3952
+ return keyResults.map((kr) => {
3953
+ const pct = Math.round(kr.percent_completed * 100);
3954
+ return `${chalk14.bold(kr.name)} ${chalk14.dim(`(${kr.id})`)} ${chalk14.cyan(`${kr.steps_current}/${kr.steps_end}`)} ${chalk14.dim(`${pct}%`)}`;
3955
+ }).join("\n");
3956
+ }
3957
+ function formatKeyResultsMarkdown(keyResults) {
3958
+ if (keyResults.length === 0) return "No key results found";
3959
+ return keyResults.map((kr) => {
3960
+ const pct = Math.round(kr.percent_completed * 100);
3961
+ return `- **${kr.name}** (${kr.id}) - ${kr.steps_current}/${kr.steps_end} (${pct}%)`;
3962
+ }).join("\n");
3963
+ }
3964
+
3579
3965
  // src/index.ts
3580
3966
  var require2 = createRequire(import.meta.url);
3581
3967
  var { version } = require2("../package.json");
@@ -4160,6 +4546,178 @@ program.command("tags <spaceId>").description("List tags in a space").option("--
4160
4546
  }
4161
4547
  })
4162
4548
  );
4549
+ program.command("tag-create <spaceId> <name>").description("Create a tag in a space").option("--fg <color>", "Foreground color (hex)").option("--bg <color>", "Background color (hex)").option("--json", "Force JSON output even in terminal").action(
4550
+ wrapAction(
4551
+ async (spaceId, name, opts) => {
4552
+ const config = loadConfig();
4553
+ await createSpaceTag(config, spaceId, name, opts.fg, opts.bg);
4554
+ if (shouldOutputJson(opts.json ?? false)) {
4555
+ console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
4556
+ } else {
4557
+ console.log(`Created tag "${name}" in space ${spaceId}`);
4558
+ }
4559
+ }
4560
+ )
4561
+ );
4562
+ program.command("tag-delete <spaceId> <name>").description("Delete a tag from a space").option("--json", "Force JSON output even in terminal").action(
4563
+ wrapAction(async (spaceId, name, opts) => {
4564
+ const config = loadConfig();
4565
+ await deleteSpaceTag(config, spaceId, name);
4566
+ if (shouldOutputJson(opts.json ?? false)) {
4567
+ console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
4568
+ } else {
4569
+ console.log(`Deleted tag "${name}" from space ${spaceId}`);
4570
+ }
4571
+ })
4572
+ );
4573
+ program.command("members").description("List workspace members").option("--json", "Force JSON output even in terminal").action(
4574
+ wrapAction(async (opts) => {
4575
+ const config = loadConfig();
4576
+ const members = await listMembers(config);
4577
+ if (shouldOutputJson(opts.json ?? false)) {
4578
+ console.log(JSON.stringify(members, null, 2));
4579
+ } else if (isTTY()) {
4580
+ console.log(formatMembers(members));
4581
+ } else {
4582
+ console.log(formatMembersMarkdown(members));
4583
+ }
4584
+ })
4585
+ );
4586
+ program.command("fields <listId>").description("List custom fields for a list").option("--json", "Force JSON output even in terminal").action(
4587
+ wrapAction(async (listId, opts) => {
4588
+ const config = loadConfig();
4589
+ const fields = await listFields(config, listId);
4590
+ if (shouldOutputJson(opts.json ?? false)) {
4591
+ console.log(JSON.stringify(fields, null, 2));
4592
+ } else if (isTTY()) {
4593
+ console.log(formatFields(fields));
4594
+ } else {
4595
+ console.log(formatFieldsMarkdown(fields));
4596
+ }
4597
+ })
4598
+ );
4599
+ program.command("duplicate <taskId>").description("Duplicate a task").option("--json", "Force JSON output even in terminal").action(
4600
+ wrapAction(async (taskId, opts) => {
4601
+ const config = loadConfig();
4602
+ const result = await duplicateTask(config, taskId);
4603
+ if (shouldOutputJson(opts.json ?? false)) {
4604
+ console.log(JSON.stringify(result, null, 2));
4605
+ } else {
4606
+ console.log(`Duplicated as "${result.name}" (${result.id})`);
4607
+ }
4608
+ })
4609
+ );
4610
+ var bulkCmd = program.command("bulk").description("Bulk task operations");
4611
+ bulkCmd.command("status <status> <taskIds...>").description("Update status of multiple tasks").option("--json", "Force JSON output even in terminal").action(
4612
+ wrapAction(async (status, taskIds, opts) => {
4613
+ const config = loadConfig();
4614
+ const result = await bulkUpdateStatus(config, taskIds, status);
4615
+ if (shouldOutputJson(opts.json ?? false)) {
4616
+ console.log(JSON.stringify(result, null, 2));
4617
+ } else {
4618
+ console.log(`Updated ${result.updated} tasks to "${status}"`);
4619
+ if (result.failed.length > 0) {
4620
+ console.log(`Failed: ${result.failed.join(", ")}`);
4621
+ }
4622
+ }
4623
+ })
4624
+ );
4625
+ program.command("goals").description("List goals in your workspace").option("--json", "Force JSON output even in terminal").action(
4626
+ wrapAction(async (opts) => {
4627
+ const config = loadConfig();
4628
+ const goals = await listGoals(config);
4629
+ if (shouldOutputJson(opts.json ?? false)) {
4630
+ console.log(JSON.stringify(goals, null, 2));
4631
+ } else if (isTTY()) {
4632
+ console.log(formatGoals(goals));
4633
+ } else {
4634
+ console.log(formatGoalsMarkdown(goals));
4635
+ }
4636
+ })
4637
+ );
4638
+ program.command("goal-create <name>").description("Create a goal").option("-d, --description <text>", "Goal description").option("--color <hex>", "Goal color (hex)").option("--json", "Force JSON output even in terminal").action(
4639
+ wrapAction(
4640
+ async (name, opts) => {
4641
+ const config = loadConfig();
4642
+ const goal = await createGoal(config, name, {
4643
+ description: opts.description,
4644
+ color: opts.color
4645
+ });
4646
+ if (shouldOutputJson(opts.json ?? false)) {
4647
+ console.log(JSON.stringify(goal, null, 2));
4648
+ } else {
4649
+ console.log(`Created goal "${goal.name}" (${goal.id})`);
4650
+ }
4651
+ }
4652
+ )
4653
+ );
4654
+ program.command("goal-update <goalId>").description("Update a goal").option("-n, --name <text>", "New goal name").option("-d, --description <text>", "New description").option("--color <hex>", "New color (hex)").option("--json", "Force JSON output even in terminal").action(
4655
+ wrapAction(
4656
+ async (goalId, opts) => {
4657
+ const config = loadConfig();
4658
+ const goal = await updateGoal(config, goalId, {
4659
+ name: opts.name,
4660
+ description: opts.description,
4661
+ color: opts.color
4662
+ });
4663
+ if (shouldOutputJson(opts.json ?? false)) {
4664
+ console.log(JSON.stringify(goal, null, 2));
4665
+ } else {
4666
+ console.log(`Updated goal "${goal.name}" (${goal.id})`);
4667
+ }
4668
+ }
4669
+ )
4670
+ );
4671
+ program.command("key-results <goalId>").description("List key results for a goal").option("--json", "Force JSON output even in terminal").action(
4672
+ wrapAction(async (goalId, opts) => {
4673
+ const config = loadConfig();
4674
+ const krs = await listKeyResults(config, goalId);
4675
+ if (shouldOutputJson(opts.json ?? false)) {
4676
+ console.log(JSON.stringify(krs, null, 2));
4677
+ } else if (isTTY()) {
4678
+ console.log(formatKeyResults(krs));
4679
+ } else {
4680
+ console.log(formatKeyResultsMarkdown(krs));
4681
+ }
4682
+ })
4683
+ );
4684
+ program.command("key-result-create <goalId> <name>").description("Create a key result on a goal").option("--type <type>", "Key result type (number or percentage)", "number").option("--target <n>", "Target value", "100").option("--json", "Force JSON output even in terminal").action(
4685
+ wrapAction(
4686
+ async (goalId, name, opts) => {
4687
+ const config = loadConfig();
4688
+ const target = Number(opts.target ?? 100);
4689
+ if (!Number.isFinite(target) || target <= 0) {
4690
+ throw new Error("--target must be a positive number");
4691
+ }
4692
+ const kr = await createKeyResult(config, goalId, name, opts.type ?? "number", target);
4693
+ if (shouldOutputJson(opts.json ?? false)) {
4694
+ console.log(JSON.stringify(kr, null, 2));
4695
+ } else {
4696
+ console.log(`Created key result "${kr.name}" (${kr.id})`);
4697
+ }
4698
+ }
4699
+ )
4700
+ );
4701
+ program.command("key-result-update <keyResultId>").description("Update a key result").option("--progress <n>", "Current progress value").option("--note <text>", "Progress note").option("--json", "Force JSON output even in terminal").action(
4702
+ wrapAction(
4703
+ async (keyResultId, opts) => {
4704
+ const config = loadConfig();
4705
+ const updates = {};
4706
+ if (opts.progress !== void 0) {
4707
+ const p = Number(opts.progress);
4708
+ if (!Number.isFinite(p)) throw new Error("--progress must be a number");
4709
+ updates.progress = p;
4710
+ }
4711
+ if (opts.note !== void 0) updates.note = opts.note;
4712
+ const kr = await updateKeyResult(config, keyResultId, updates);
4713
+ if (shouldOutputJson(opts.json ?? false)) {
4714
+ console.log(JSON.stringify(kr, null, 2));
4715
+ } else {
4716
+ console.log(`Updated key result "${kr.name}" (${kr.id})`);
4717
+ }
4718
+ }
4719
+ )
4720
+ );
4163
4721
  program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
4164
4722
  wrapAction(async (query, opts) => {
4165
4723
  const config = loadConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clickup
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.'
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.'
4
4
  ---
5
5
 
6
6
  # ClickUp CLI (`cup`)
@@ -54,6 +54,10 @@ All commands support `--help` for full flag details.
54
54
  | `cup auth [--json]` | Check authentication status |
55
55
  | `cup folders <spaceId> [--name partial] [--json]` | Folders in a space (with their lists) |
56
56
  | `cup tags <spaceId> [--json]` | List tags available in a space |
57
+ | `cup members [--json]` | List workspace members |
58
+ | `cup fields <listId> [--json]` | List custom fields for a list |
59
+ | `cup goals [--json]` | List goals in your workspace |
60
+ | `cup key-results <goalId> [--json]` | List key results for a goal |
57
61
  | `cup docs [query] [--json]` | List workspace docs (optionally filter by name) |
58
62
  | `cup doc <docId> [pageId] [--json]` | View doc metadata + page tree, or a specific page |
59
63
  | `cup doc-pages <docId> [--json]` | All pages in a doc with content |
@@ -93,6 +97,14 @@ All commands support `--help` for full flag details.
93
97
  | `cup doc-create <title> [-c content] [--json]` | Create a new doc |
94
98
  | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId] [--json]` | Create a page in a doc |
95
99
  | `cup doc-page-edit <docId> <pageId> [--name text] [-c content] [--json]` | Edit a doc page |
100
+ | `cup tag-create <spaceId> <name> [--fg color] [--bg color] [--json]` | Create a tag in a space |
101
+ | `cup tag-delete <spaceId> <name> [--json]` | Delete a tag from a space |
102
+ | `cup duplicate <taskId> [--json]` | Duplicate a task (read + create copy) |
103
+ | `cup bulk status <status> <taskIds...> [--json]` | Bulk update status of multiple tasks |
104
+ | `cup goal-create <name> [-d desc] [--color hex] [--json]` | Create a goal |
105
+ | `cup goal-update <goalId> [-n name] [-d desc] [--color hex] [--json]` | Update a goal |
106
+ | `cup key-result-create <goalId> <name> [--type t] [--target n] [--json]` | Create a key result on a goal |
107
+ | `cup key-result-update <keyResultId> [--progress n] [--note text] [--json]` | Update a key result |
96
108
  | `cup config get <key>` / `cup config set <key> <value>` / `cup config path` | Manage CLI config (keys: apiToken, teamId, sprintFolderId) |
97
109
  | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
98
110
 
@@ -127,6 +139,18 @@ All commands support `--help` for full flag details.
127
139
  | `cup open` | Tries task ID first, falls back to name search |
128
140
  | `cup checklist` | Full CRUD for task checklists: view, create, delete, add-item, edit-item, delete-item |
129
141
  | `cup tags` | List available tags in a space. Useful for discovering valid tag names before using `cup tag` |
142
+ | `cup tag-create` | Create a new tag in a space. Optional `--fg` and `--bg` for colors |
143
+ | `cup tag-delete` | Delete a tag from a space |
144
+ | `cup members` | List workspace members with username, ID, and email |
145
+ | `cup fields` | List custom fields on a list. Shows field type, required status, and dropdown options |
146
+ | `cup duplicate` | Duplicate a task. Creates a copy with name, description, priority, tags, and time estimate |
147
+ | `cup bulk status` | Update status of multiple tasks at once. Reports failures without stopping |
148
+ | `cup goals` | List goals with progress percentage and owner |
149
+ | `cup goal-create` | Create a goal with optional description and color |
150
+ | `cup goal-update` | Update goal name, description, or color |
151
+ | `cup key-results` | List key results for a goal with progress tracking |
152
+ | `cup key-result-create` | Create a key result (number or percentage type) with a target value |
153
+ | `cup key-result-update` | Update key result progress or add a note |
130
154
  | `cup time` | Track time: start/stop timer, log entries, list history, update/delete entries. Duration format: "2h", "30m", "1h30m" |
131
155
  | `cup time update` | Update a time entry description or duration |
132
156
  | `cup time delete` | Delete a time entry |
@@ -240,10 +264,32 @@ cup folders <spaceId> # folders with their lists
240
264
  cup folders <spaceId> --name "sprint" # filter folders by name
241
265
  cup lists <spaceId> # lists in a space (needs ID from cup spaces)
242
266
  cup sprints # all sprints across folders
267
+ cup members # workspace members
268
+ cup fields <listId> # custom fields on a list
243
269
  cup auth # verify token works
244
270
  cup config set sprintFolderId <id> # pin sprint detection to a folder
245
271
  ```
246
272
 
273
+ ### Bulk operations
274
+
275
+ ```bash
276
+ cup duplicate abc123 # duplicate a task
277
+ cup bulk status "done" t1 t2 t3 # mark multiple tasks done
278
+ cup tag-create <spaceId> "urgent" # create a space tag
279
+ cup tag-delete <spaceId> "old-tag" # delete a space tag
280
+ ```
281
+
282
+ ### Goals and key results
283
+
284
+ ```bash
285
+ cup goals # list all goals
286
+ cup goal-create "Ship v2" -d "Release version 2"
287
+ cup goal-update g123 -n "Updated name"
288
+ cup key-results g123 # key results for a goal
289
+ cup key-result-create g123 "API coverage" --type percentage --target 80
290
+ cup key-result-update kr456 --progress 60 --note "On track"
291
+ ```
292
+
247
293
  ### Standup
248
294
 
249
295
  ```bash