@krodak/clickup-cli 1.2.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.2.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>` | :construction: |
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
 
@@ -222,8 +223,8 @@ Status: :white_check_mark: implemented | :construction: planned | :no_entry_sign
222
223
  | Timer status | `cup time status` | :white_check_mark: |
223
224
  | Log time entry | `cup time log <id> <duration>` | :white_check_mark: |
224
225
  | List entries | `cup time list` | :white_check_mark: |
225
- | Update entry | `cup time update <id>` | :construction: |
226
- | Delete entry | `cup time delete <id>` | :construction: |
226
+ | Update entry | `cup time update <id>` | :white_check_mark: |
227
+ | Delete entry | `cup time delete <id>` | :white_check_mark: |
227
228
 
228
229
  ### Workspace
229
230
 
@@ -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
@@ -371,6 +371,36 @@ var ClickUpClient = class {
371
371
  }
372
372
  return entries;
373
373
  }
374
+ async updateTimeEntry(teamId, timeEntryId, updates) {
375
+ const data = await this.request(
376
+ `/team/${teamId}/time_entries/${timeEntryId}`,
377
+ { method: "PUT", body: JSON.stringify(updates) }
378
+ );
379
+ return data.data;
380
+ }
381
+ async getSpaceTags(spaceId) {
382
+ const data = await this.request(`/space/${spaceId}/tag`);
383
+ return data.tags ?? [];
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
+ }
374
404
  async deleteTimeEntry(teamId, timeEntryId) {
375
405
  await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
376
406
  method: "DELETE"
@@ -458,6 +488,52 @@ var ClickUpClient = class {
458
488
  );
459
489
  return data.pages ?? [];
460
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
+ }
461
537
  };
462
538
 
463
539
  // src/config.ts
@@ -2106,7 +2182,7 @@ function bashCompletion(name) {
2106
2182
  cword=$COMP_CWORD
2107
2183
  fi
2108
2184
 
2109
- 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 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"
2110
2186
 
2111
2187
  if [[ $cword -eq 1 ]]; then
2112
2188
  COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
@@ -2239,6 +2315,47 @@ function bashCompletion(name) {
2239
2315
  doc-pages)
2240
2316
  COMPREPLY=($(compgen -W "--json" -- "$cur"))
2241
2317
  ;;
2318
+ tags)
2319
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
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
+ ;;
2242
2359
  folders)
2243
2360
  COMPREPLY=($(compgen -W "--name --json" -- "$cur"))
2244
2361
  ;;
@@ -2317,6 +2434,19 @@ _${name}() {
2317
2434
  'doc-pages:List all pages in a doc with content'
2318
2435
  'doc-page-create:Create a page in a doc'
2319
2436
  'doc-page-edit:Edit a doc page'
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'
2320
2450
  'folders:List folders in a space'
2321
2451
  'config:Manage CLI configuration'
2322
2452
  'completion:Output shell completion script'
@@ -2656,6 +2786,99 @@ _${name}() {
2656
2786
  '1:doc_id:' \\
2657
2787
  '--json[Force JSON output]'
2658
2788
  ;;
2789
+ tags)
2790
+ _arguments \\
2791
+ '1:space_id:' \\
2792
+ '--json[Force JSON output]'
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
+ ;;
2659
2882
  folders)
2660
2883
  _arguments \\
2661
2884
  '1:space_id:' \\
@@ -2764,6 +2987,19 @@ complete -c ${name} -n __fish_use_subcommand -a doc-create -d 'Create a new doc'
2764
2987
  complete -c ${name} -n __fish_use_subcommand -a doc-pages -d 'List all pages in a doc with content'
2765
2988
  complete -c ${name} -n __fish_use_subcommand -a doc-page-create -d 'Create a page in a doc'
2766
2989
  complete -c ${name} -n __fish_use_subcommand -a doc-page-edit -d 'Edit a doc page'
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'
2767
3003
  complete -c ${name} -n __fish_use_subcommand -a folders -d 'List folders in a space'
2768
3004
  complete -c ${name} -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
2769
3005
  complete -c ${name} -n __fish_use_subcommand -a completion -d 'Output shell completion script'
@@ -2924,6 +3160,44 @@ complete -c ${name} -n '__fish_seen_subcommand_from doc' -l json -d 'Force JSON
2924
3160
 
2925
3161
  complete -c ${name} -n '__fish_seen_subcommand_from doc-pages' -l json -d 'Force JSON output'
2926
3162
 
3163
+ complete -c ${name} -n '__fish_seen_subcommand_from tags' -l json -d 'Force JSON output'
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
+
2927
3201
  complete -c ${name} -n '__fish_seen_subcommand_from folders' -l name -d 'Filter by folder name'
2928
3202
  complete -c ${name} -n '__fish_seen_subcommand_from folders' -l json -d 'Force JSON output'
2929
3203
 
@@ -3492,6 +3766,20 @@ async function listTimeEntries(config, opts) {
3492
3766
  taskId: opts?.taskId
3493
3767
  });
3494
3768
  }
3769
+ async function updateTimeEntry(config, timeEntryId, opts) {
3770
+ if (!opts.description && !opts.duration) {
3771
+ throw new Error("Provide --description or --duration to update");
3772
+ }
3773
+ const client = new ClickUpClient(config);
3774
+ const updates = {};
3775
+ if (opts.description) updates.description = opts.description;
3776
+ if (opts.duration) updates.duration = parseTimeEstimate(opts.duration);
3777
+ return client.updateTimeEntry(config.teamId, timeEntryId, updates);
3778
+ }
3779
+ async function deleteTimeEntry(config, timeEntryId) {
3780
+ const client = new ClickUpClient(config);
3781
+ await client.deleteTimeEntry(config.teamId, timeEntryId);
3782
+ }
3495
3783
  function formatTimeEntry(entry) {
3496
3784
  const lines = [];
3497
3785
  const taskName = entry.task?.name ?? "No task";
@@ -3524,6 +3812,156 @@ function formatTimeEntriesMarkdown(entries) {
3524
3812
  return entries.map(formatTimeEntryMarkdown).join("\n");
3525
3813
  }
3526
3814
 
3815
+ // src/commands/tags.ts
3816
+ import chalk11 from "chalk";
3817
+ async function listSpaceTags(config, spaceId) {
3818
+ const client = new ClickUpClient(config);
3819
+ return client.getSpaceTags(spaceId);
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
+ }
3829
+ function formatTags(tags) {
3830
+ if (tags.length === 0) return "No tags found";
3831
+ return tags.map((t) => chalk11.bold(t.name)).join(", ");
3832
+ }
3833
+ function formatTagsMarkdown(tags) {
3834
+ if (tags.length === 0) return "No tags found";
3835
+ return tags.map((t) => `- ${t.name}`).join("\n");
3836
+ }
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
+
3527
3965
  // src/index.ts
3528
3966
  var require2 = createRequire(import.meta.url);
3529
3967
  var { version } = require2("../package.json");
@@ -4066,6 +4504,220 @@ timeCmd.command("list").description("List recent time entries (default: last 7 d
4066
4504
  }
4067
4505
  })
4068
4506
  );
4507
+ timeCmd.command("update <timeEntryId>").description("Update a time entry").option("-d, --description <text>", "New description").option("--duration <duration>", 'New duration (e.g. "2h", "30m")').option("--json", "Force JSON output even in terminal").action(
4508
+ wrapAction(
4509
+ async (timeEntryId, opts) => {
4510
+ const config = loadConfig();
4511
+ const entry = await updateTimeEntry(config, timeEntryId, {
4512
+ description: opts.description,
4513
+ duration: opts.duration
4514
+ });
4515
+ if (shouldOutputJson(opts.json ?? false)) {
4516
+ console.log(JSON.stringify(entry, null, 2));
4517
+ } else if (isTTY()) {
4518
+ console.log(formatTimeEntry(entry));
4519
+ } else {
4520
+ console.log(formatTimeEntryMarkdown(entry));
4521
+ }
4522
+ }
4523
+ )
4524
+ );
4525
+ timeCmd.command("delete <timeEntryId>").description("Delete a time entry").option("--json", "Force JSON output even in terminal").action(
4526
+ wrapAction(async (timeEntryId, opts) => {
4527
+ const config = loadConfig();
4528
+ await deleteTimeEntry(config, timeEntryId);
4529
+ if (shouldOutputJson(opts.json ?? false)) {
4530
+ console.log(JSON.stringify({ deleted: timeEntryId }));
4531
+ } else {
4532
+ console.log(`Deleted time entry ${timeEntryId}`);
4533
+ }
4534
+ })
4535
+ );
4536
+ program.command("tags <spaceId>").description("List tags in a space").option("--json", "Force JSON output even in terminal").action(
4537
+ wrapAction(async (spaceId, opts) => {
4538
+ const config = loadConfig();
4539
+ const tags = await listSpaceTags(config, spaceId);
4540
+ if (shouldOutputJson(opts.json ?? false)) {
4541
+ console.log(JSON.stringify(tags, null, 2));
4542
+ } else if (isTTY()) {
4543
+ console.log(formatTags(tags));
4544
+ } else {
4545
+ console.log(formatTagsMarkdown(tags));
4546
+ }
4547
+ })
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
+ );
4069
4721
  program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
4070
4722
  wrapAction(async (query, opts) => {
4071
4723
  const config = loadConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.2.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`)
@@ -53,6 +53,11 @@ All commands support `--help` for full flag details.
53
53
  | `cup open <query> [--json]` | Open task in browser by ID or name |
54
54
  | `cup auth [--json]` | Check authentication status |
55
55
  | `cup folders <spaceId> [--name partial] [--json]` | Folders in a space (with their lists) |
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 |
56
61
  | `cup docs [query] [--json]` | List workspace docs (optionally filter by name) |
57
62
  | `cup doc <docId> [pageId] [--json]` | View doc metadata + page tree, or a specific page |
58
63
  | `cup doc-pages <docId> [--json]` | All pages in a doc with content |
@@ -87,9 +92,19 @@ All commands support `--help` for full flag details.
87
92
  | `cup time status [--json]` | Show currently running timer |
88
93
  | `cup time log <taskId> <duration> [-d desc] [--json]` | Log manual time entry (e.g. "2h", "30m") |
89
94
  | `cup time list [--days n] [--task id] [--json]` | List recent time entries |
95
+ | `cup time update <timeEntryId> [-d desc] [--duration dur] [--json]` | Update a time entry |
96
+ | `cup time delete <timeEntryId> [--json]` | Delete a time entry |
90
97
  | `cup doc-create <title> [-c content] [--json]` | Create a new doc |
91
98
  | `cup doc-page-create <docId> <name> [-c content] [--parent-page pageId] [--json]` | Create a page in a doc |
92
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 |
93
108
  | `cup config get <key>` / `cup config set <key> <value>` / `cup config path` | Manage CLI config (keys: apiToken, teamId, sprintFolderId) |
94
109
  | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
95
110
 
@@ -123,7 +138,22 @@ All commands support `--help` for full flag details.
123
138
  | `cup overdue` | Excludes closed tasks, sorted most overdue first |
124
139
  | `cup open` | Tries task ID first, falls back to name search |
125
140
  | `cup checklist` | Full CRUD for task checklists: view, create, delete, add-item, edit-item, delete-item |
126
- | `cup time` | Track time: start/stop timer, log entries, list history. Duration format: "2h", "30m", "1h30m" |
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 |
154
+ | `cup time` | Track time: start/stop timer, log entries, list history, update/delete entries. Duration format: "2h", "30m", "1h30m" |
155
+ | `cup time update` | Update a time entry description or duration |
156
+ | `cup time delete` | Delete a time entry |
127
157
  | `cup comment-edit` | Edit comment text and resolution status |
128
158
  | `cup comment-delete` | Delete a comment |
129
159
  | `cup replies` / `cup reply` | View and post threaded comment replies |
@@ -234,10 +264,32 @@ cup folders <spaceId> # folders with their lists
234
264
  cup folders <spaceId> --name "sprint" # filter folders by name
235
265
  cup lists <spaceId> # lists in a space (needs ID from cup spaces)
236
266
  cup sprints # all sprints across folders
267
+ cup members # workspace members
268
+ cup fields <listId> # custom fields on a list
237
269
  cup auth # verify token works
238
270
  cup config set sprintFolderId <id> # pin sprint detection to a folder
239
271
  ```
240
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
+
241
293
  ### Standup
242
294
 
243
295
  ```bash