@krodak/clickup-cli 1.35.0 → 1.37.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.35.0",
4
+ "version": "1.37.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/dist/index.js CHANGED
@@ -74,6 +74,10 @@ function normalizeTaskId(input) {
74
74
  const match = /^https?:\/\/app\.clickup\.com\/t\/(?:[^/?#]+\/)?([^/?#]+)/.exec(input.trim());
75
75
  return match ? match[1] : input;
76
76
  }
77
+ function normalizeViewId(input) {
78
+ const match = /^https?:\/\/app\.clickup\.com\/[^/]+\/v\/[^/]+\/([^/?#]+)/.exec(input.trim());
79
+ return match ? match[1] : input;
80
+ }
77
81
  var ClickUpClient = class {
78
82
  apiToken;
79
83
  teamId;
@@ -394,10 +398,12 @@ var ClickUpClient = class {
394
398
  return readCollectionField(data, "views", "views");
395
399
  }
396
400
  async getViewTasks(viewId) {
397
- return this.paginate((page) => `/view/${viewId}/task?page=${page}`);
401
+ const id = normalizeViewId(viewId);
402
+ return this.paginate((page) => `/view/${id}/task?page=${page}`);
398
403
  }
399
404
  async getView(viewId) {
400
- const data = await this.request(`/view/${viewId}`);
405
+ const id = normalizeViewId(viewId);
406
+ const data = await this.request(`/view/${id}`);
401
407
  return expectRecordField(data, "view", "view");
402
408
  }
403
409
  async createListView(listId, payload) {
@@ -408,14 +414,16 @@ var ClickUpClient = class {
408
414
  return expectRecordField(data, "view", "view");
409
415
  }
410
416
  async updateView(viewId, payload) {
411
- const data = await this.request(`/view/${viewId}`, {
417
+ const id = normalizeViewId(viewId);
418
+ const data = await this.request(`/view/${id}`, {
412
419
  method: "PUT",
413
420
  body: JSON.stringify(payload)
414
421
  });
415
422
  return expectRecordField(data, "view", "view");
416
423
  }
417
424
  async deleteView(viewId) {
418
- await this.request(`/view/${viewId}`, { method: "DELETE" });
425
+ const id = normalizeViewId(viewId);
426
+ await this.request(`/view/${id}`, { method: "DELETE" });
419
427
  }
420
428
  async getListTemplates(teamId) {
421
429
  const data = await this.request(`/team/${teamId}/list_template`);
@@ -873,7 +881,7 @@ var ClickUpClient = class {
873
881
  body: JSON.stringify({ name })
874
882
  });
875
883
  }
876
- async createCustomField(teamId, name, type, opts) {
884
+ buildCustomFieldBody(name, type, opts) {
877
885
  const typeConfig = {};
878
886
  if (opts?.options?.length) {
879
887
  typeConfig.options = opts.options.map((optName, i) => ({
@@ -881,7 +889,7 @@ var ClickUpClient = class {
881
889
  orderindex: i
882
890
  }));
883
891
  }
884
- const body = {
892
+ return {
885
893
  name,
886
894
  type,
887
895
  type_config: typeConfig,
@@ -895,11 +903,22 @@ var ClickUpClient = class {
895
903
  members: [],
896
904
  groups: []
897
905
  };
906
+ }
907
+ async createCustomField(teamId, name, type, opts) {
908
+ const body = this.buildCustomFieldBody(name, type, opts);
898
909
  const data = await this.request(
899
- `/field?workspace_id=${teamId}`,
910
+ `/team/${teamId}/field`,
900
911
  { method: "POST", body: JSON.stringify(body) }
901
912
  );
902
- return data.data;
913
+ return data.field;
914
+ }
915
+ async createListCustomField(listId, name, type, opts) {
916
+ const body = this.buildCustomFieldBody(name, type, opts);
917
+ const data = await this.request(
918
+ `/list/${listId}/field`,
919
+ { method: "POST", body: JSON.stringify(body) }
920
+ );
921
+ return data.field;
903
922
  }
904
923
  chatChannelsPath(suffix = "") {
905
924
  return `/workspaces/${this.teamId}/chat/channels${suffix}`;
@@ -1102,13 +1121,15 @@ var ClickUpClient = class {
1102
1121
  });
1103
1122
  }
1104
1123
  async getViewComments(viewId) {
1105
- const data = await this.request(`/view/${viewId}/comment`);
1124
+ const id = normalizeViewId(viewId);
1125
+ const data = await this.request(`/view/${id}/comment`);
1106
1126
  return readCollectionField(data, "comments", "view comments");
1107
1127
  }
1108
1128
  async postViewComment(viewId, commentText, notifyAll, richBlocks) {
1129
+ const id = normalizeViewId(viewId);
1109
1130
  const body = richBlocks ? { comment: richBlocks } : { comment_text: commentText };
1110
1131
  if (notifyAll) body.notify_all = true;
1111
- return this.request(`/view/${viewId}/comment`, {
1132
+ return this.request(`/view/${id}/comment`, {
1112
1133
  method: "POST",
1113
1134
  body: JSON.stringify(body)
1114
1135
  });
@@ -4499,13 +4520,23 @@ var commandMetadata = [
4499
4520
  },
4500
4521
  {
4501
4522
  name: "field-create",
4502
- description: "Create a custom field in your workspace",
4503
- flags: ["-t", "--type", "-d", "--description", "--options", "--required", "--json"],
4523
+ description: "Create a custom field in your workspace or on one or more lists",
4524
+ flags: [
4525
+ "-t",
4526
+ "--type",
4527
+ "-d",
4528
+ "--description",
4529
+ "--options",
4530
+ "--required",
4531
+ "--list",
4532
+ "--lists",
4533
+ "--json"
4534
+ ],
4504
4535
  quickReference: [
4505
4536
  {
4506
4537
  section: "write",
4507
4538
  usage: "field-create <name>",
4508
- description: "Create a custom field in your workspace"
4539
+ description: "Create a custom field in your workspace or on one or more lists"
4509
4540
  }
4510
4541
  ]
4511
4542
  },
@@ -4698,6 +4729,14 @@ var commandMetadata = [
4698
4729
  flags: ["--json"],
4699
4730
  quickReference: [{ section: "read", usage: "view <viewId>", description: "Get view details" }]
4700
4731
  },
4732
+ {
4733
+ name: "view-tasks",
4734
+ description: "List tasks in a view",
4735
+ flags: ["--me", "--json"],
4736
+ quickReference: [
4737
+ { section: "read", usage: "view-tasks <viewId>", description: "List tasks in a view" }
4738
+ ]
4739
+ },
4701
4740
  {
4702
4741
  name: "view-create",
4703
4742
  description: "Create a view on a list",
@@ -7443,21 +7482,6 @@ function formatFieldsMarkdown(fields) {
7443
7482
  }).join("\n");
7444
7483
  }
7445
7484
 
7446
- // src/commands/duplicate.ts
7447
- async function duplicateTask(config, taskId) {
7448
- const client = new ClickUpClient(config);
7449
- const task = await client.getTask(taskId);
7450
- const created = await client.createTask(task.list.id, {
7451
- name: `${task.name} (copy)`,
7452
- description: task.description,
7453
- markdown_content: task.markdown_content,
7454
- priority: task.priority ? parsePriority(task.priority.priority.toLowerCase()) : void 0,
7455
- tags: task.tags?.map((t) => t.name),
7456
- time_estimate: task.time_estimate ?? void 0
7457
- });
7458
- return { id: created.id, name: created.name, url: created.url };
7459
- }
7460
-
7461
7485
  // src/util/batch.ts
7462
7486
  async function runInBatches(items, concurrency, fn) {
7463
7487
  if (!Number.isInteger(concurrency) || concurrency < 1) {
@@ -7480,6 +7504,66 @@ async function runInBatches(items, concurrency, fn) {
7480
7504
  return results;
7481
7505
  }
7482
7506
 
7507
+ // src/commands/field-create.ts
7508
+ var FIELD_CREATE_CONCURRENCY = 5;
7509
+ var VALID_FIELD_TYPES = [
7510
+ "text",
7511
+ "short_text",
7512
+ "number",
7513
+ "date",
7514
+ "checkbox",
7515
+ "drop_down",
7516
+ "labels",
7517
+ "email",
7518
+ "phone",
7519
+ "url",
7520
+ "currency"
7521
+ ];
7522
+ function resolveFieldScope(list, lists) {
7523
+ if (list && lists) throw new Error("Cannot use --list and --lists together");
7524
+ if (list) return { mode: "single", listId: list };
7525
+ if (lists) {
7526
+ const listIds = lists.split(",").map((id) => id.trim()).filter(Boolean);
7527
+ if (listIds.length === 0) throw new Error("--lists requires at least one list ID");
7528
+ return { mode: "bulk", listIds };
7529
+ }
7530
+ return { mode: "workspace" };
7531
+ }
7532
+ function validateFieldType(type, options) {
7533
+ if (!VALID_FIELD_TYPES.includes(type)) {
7534
+ throw new Error(`Invalid field type "${type}". Valid types: ${VALID_FIELD_TYPES.join(", ")}`);
7535
+ }
7536
+ if ((type === "drop_down" || type === "labels") && !options?.length) {
7537
+ throw new Error(`--options is required for ${type} fields (comma-separated values)`);
7538
+ }
7539
+ }
7540
+ async function createFieldAcrossLists(config, name, type, listIds, opts) {
7541
+ const client = new ClickUpClient(config);
7542
+ const outcomes = await runInBatches(
7543
+ listIds,
7544
+ FIELD_CREATE_CONCURRENCY,
7545
+ (listId) => client.createListCustomField(listId, name, type, opts)
7546
+ );
7547
+ return outcomes.map(
7548
+ (outcome) => outcome.ok ? { listId: outcome.item, ok: true, fieldId: outcome.result.id } : { listId: outcome.item, ok: false, error: outcome.error.message }
7549
+ );
7550
+ }
7551
+
7552
+ // src/commands/duplicate.ts
7553
+ async function duplicateTask(config, taskId) {
7554
+ const client = new ClickUpClient(config);
7555
+ const task = await client.getTask(taskId);
7556
+ const created = await client.createTask(task.list.id, {
7557
+ name: `${task.name} (copy)`,
7558
+ description: task.description,
7559
+ markdown_content: task.markdown_content,
7560
+ priority: task.priority ? parsePriority(task.priority.priority.toLowerCase()) : void 0,
7561
+ tags: task.tags?.map((t) => t.name),
7562
+ time_estimate: task.time_estimate ?? void 0
7563
+ });
7564
+ return { id: created.id, name: created.name, url: created.url };
7565
+ }
7566
+
7483
7567
  // src/commands/bulk.ts
7484
7568
  var BULK_CONCURRENCY = 5;
7485
7569
  function toBulkResult(outcomes) {
@@ -7967,6 +8051,22 @@ function formatViewMarkdown(view) {
7967
8051
  return lines.join("\n");
7968
8052
  }
7969
8053
 
8054
+ // src/commands/view-tasks.ts
8055
+ async function listViewTasks(config, viewId, opts) {
8056
+ const client = new ClickUpClient(config);
8057
+ const [tasks, customTypes] = await Promise.all([
8058
+ client.getViewTasks(viewId),
8059
+ client.getCustomTaskTypes(config.teamId)
8060
+ ]);
8061
+ const typeMap = buildTypeMap(customTypes);
8062
+ let filtered = tasks;
8063
+ if (opts.me) {
8064
+ const me = await client.getMe();
8065
+ filtered = tasks.filter((t) => t.assignees.some((a) => Number(a.id) === me.id));
8066
+ }
8067
+ return filtered.map((t) => summarize(t, typeMap));
8068
+ }
8069
+
7970
8070
  // src/commands/view-create.ts
7971
8071
  var VALID_VIEW_TYPES = ["list", "board", "calendar", "gantt", "table", "timeline"];
7972
8072
  var VALID_GROUP_BY_FIELDS = [
@@ -9558,44 +9658,35 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
9558
9658
  }
9559
9659
  })
9560
9660
  );
9561
- program.command("field-create <name>").description("Create a custom field in your workspace").requiredOption(
9661
+ program.command("field-create <name>").description("Create a custom field in your workspace or on one or more lists").requiredOption(
9562
9662
  "-t, --type <type>",
9563
9663
  "Field type (text, number, date, checkbox, drop_down, labels, email, phone, url, currency, short_text)"
9564
- ).option("-d, --description <text>", "Field description").option("--options <items>", "Comma-separated options for drop_down or labels types").option("--required", "Make the field required").option("--json", "Force JSON output even in terminal").action(
9664
+ ).option("-d, --description <text>", "Field description").option("--options <items>", "Comma-separated options for drop_down or labels types").option("--required", "Make the field required").option("--list <listId>", "Create the field scoped to a single list").option("--lists <ids>", "Comma-separated list IDs to create the same field on each").option("--json", "Force JSON output even in terminal").action(
9565
9665
  wrapAction(
9566
9666
  async (name, opts) => {
9567
9667
  if (!name.trim()) throw new Error("Field name cannot be empty");
9568
- const validTypes = [
9569
- "text",
9570
- "short_text",
9571
- "number",
9572
- "date",
9573
- "checkbox",
9574
- "drop_down",
9575
- "labels",
9576
- "email",
9577
- "phone",
9578
- "url",
9579
- "currency"
9580
- ];
9581
- if (!validTypes.includes(opts.type)) {
9582
- throw new Error(
9583
- `Invalid field type "${opts.type}". Valid types: ${validTypes.join(", ")}`
9584
- );
9585
- }
9586
- const config = loadConfig(getProfileName());
9587
- const client = new ClickUpClient(config);
9668
+ const scope = resolveFieldScope(opts.list, opts.lists);
9588
9669
  const options = opts.options ? opts.options.split(",").map((o) => o.trim()).filter(Boolean) : void 0;
9589
- if ((opts.type === "drop_down" || opts.type === "labels") && !options?.length) {
9590
- throw new Error(
9591
- `--options is required for ${opts.type} fields (comma-separated values)`
9592
- );
9593
- }
9594
- const field = await client.createCustomField(config.teamId, name, opts.type, {
9670
+ validateFieldType(opts.type, options);
9671
+ const config = loadConfig(getProfileName());
9672
+ const fieldOpts = {
9595
9673
  description: opts.description,
9596
9674
  required: opts.required,
9597
9675
  options
9598
- });
9676
+ };
9677
+ if (scope.mode === "bulk") {
9678
+ const results = await createFieldAcrossLists(
9679
+ config,
9680
+ name,
9681
+ opts.type,
9682
+ scope.listIds,
9683
+ fieldOpts
9684
+ );
9685
+ outputListFieldResults(results, opts.json ?? false);
9686
+ return;
9687
+ }
9688
+ const client = new ClickUpClient(config);
9689
+ const field = scope.mode === "single" ? await client.createListCustomField(scope.listId, name, opts.type, fieldOpts) : await client.createCustomField(config.teamId, name, opts.type, fieldOpts);
9599
9690
  if (shouldOutputJson(opts.json ?? false)) {
9600
9691
  console.log(JSON.stringify(field, null, 2));
9601
9692
  } else {
@@ -9615,6 +9706,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
9615
9706
  }
9616
9707
  })
9617
9708
  );
9709
+ function outputListFieldResults(results, forceJson) {
9710
+ if (shouldOutputJson(forceJson)) {
9711
+ console.log(JSON.stringify(results, null, 2));
9712
+ return;
9713
+ }
9714
+ for (const result of results) {
9715
+ if (result.ok) {
9716
+ console.log(`\u2713 ${result.listId}: created field ${result.fieldId}`);
9717
+ } else {
9718
+ console.error(`\u2717 ${result.listId}: ${result.error}`);
9719
+ }
9720
+ }
9721
+ const created = results.filter((r) => r.ok).length;
9722
+ console.log(`Created on ${created}/${results.length} lists`);
9723
+ }
9618
9724
  function outputBulkResult(result, forceJson, operation) {
9619
9725
  if (shouldOutputJson(forceJson)) {
9620
9726
  console.log(JSON.stringify(result, null, 2));
@@ -10136,6 +10242,13 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
10136
10242
  }
10137
10243
  })
10138
10244
  );
10245
+ program.command("view-tasks <viewId>").description("List tasks in a view").option("--me", "Only tasks assigned to the current user").option("--json", "Force JSON output even in terminal").action(
10246
+ wrapAction(async (viewId, opts) => {
10247
+ const config = loadConfig(getProfileName());
10248
+ const tasks = await listViewTasks(config, viewId, { me: opts.me });
10249
+ await printTasks(tasks, opts.json ?? false, config);
10250
+ })
10251
+ );
10139
10252
  program.command("view-create <listId> <name>").description("Create a view on a list").requiredOption(
10140
10253
  "-t, --type <type>",
10141
10254
  "View type (list, board, calendar, gantt, table, timeline)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.35.0",
3
+ "version": "1.37.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,11 +3,11 @@ name: clickup
3
3
  description: 'Use when managing ClickUp tasks, sprints, or comments via the `cup` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, threaded replies, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments, task links, time tracking, attachments, file uploads, listing members, listing fields, duplicating tasks, bulk operations, goals, key results, saved filters, favorites.'
4
4
  ---
5
5
 
6
- # ClickUp CLI (`cup`) - skill version 1.35.0
6
+ # ClickUp CLI (`cup`) - skill version 1.37.0
7
7
 
8
8
  Reference for AI agents using the `cup` CLI tool. Covers task management, sprint tracking, comments, time tracking, custom fields, goals, docs, and project workflows.
9
9
 
10
- > **Version check:** Run `cup --version`. If your installed version is older than 1.35.0, 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.37.0, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
11
11
 
12
12
  ## Install & Configure
13
13
 
@@ -144,6 +144,7 @@ All commands support `--help` for full flag details. All commands support `--jso
144
144
  | `cup folder-templates` | Folder templates |
145
145
  | `cup views <listId>` | List views on a list |
146
146
  | `cup view <viewId>` | Get view details |
147
+ | `cup view-tasks <viewId> [--me]` | List tasks in a view (`--me` filters to you) |
147
148
  | `cup open <query>` | Open task in browser by ID or name |
148
149
  | `cup auth` | Check authentication status |
149
150
  | `cup list-comments <listId>` | Comments on a list |
@@ -173,7 +174,7 @@ All commands support `--help` for full flag details. All commands support `--jso
173
174
  | `cup depend <id> [--on taskId] [--blocks taskId] [--remove]` | Add/remove dependencies |
174
175
  | `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`. |
175
176
  | `cup field <id> [--set "Name" value] [--remove "Name"]` | Set/remove custom field values |
176
- | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required]` | Create a custom field |
177
+ | `cup field-create <name> -t <type> [-d desc] [--options "a,b,c"] [--required] [--list id] [--lists id1,id2]` | Create a custom field — workspace-wide (default), on one list (`--list`), or bulk across lists (`--lists`, parallel, per-list reporting) |
177
178
  | `cup tag <id> [--add tags] [--remove tags]` | Add/remove tags on a task |
178
179
  | `cup link <taskId> <linksTo> [--remove]` | Link/unlink tasks |
179
180
  | `cup attach <taskId> <filePath>` | Upload file attachment |
@@ -266,6 +267,7 @@ All commands support `--help` for full flag details. All commands support `--jso
266
267
  | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
267
268
  | Task IDs | Native (`abc123def`) or custom (`PROJ-123`). Custom IDs auto-detected by `PREFIX-DIGITS` format |
268
269
  | Task URLs | All commands that accept a task ID also accept full ClickUp URLs (`https://app.clickup.com/t/<id>` or `https://app.clickup.com/t/<workspace>/<id>`). The ID is auto-extracted |
270
+ | View URLs | All commands that accept a view ID also accept full ClickUp view URLs (`https://app.clickup.com/<workspace>/v/<type>/<viewId>`). The ID is auto-extracted |
269
271
  | `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr |
270
272
  | `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
271
273
  | `--due-date` | `YYYY-MM-DD` (date only), `YYYY-MM-DDTHH:MM` (with time), or full ISO 8601 with offset. Time-of-day formats set `due_date_time: true` in ClickUp |