@krodak/clickup-cli 1.16.0 → 1.16.2
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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +116 -49
- package/package.json +1 -1
|
@@ -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.16.
|
|
4
|
+
"version": "1.16.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
package/dist/index.js
CHANGED
|
@@ -128,14 +128,22 @@ var ClickUpClient = class {
|
|
|
128
128
|
}
|
|
129
129
|
async getMe() {
|
|
130
130
|
if (this.meCache) return this.meCache;
|
|
131
|
-
const data = await this.request(
|
|
131
|
+
const data = await this.request(
|
|
132
|
+
"/user"
|
|
133
|
+
);
|
|
132
134
|
const user = expectRecordField(data, "user", "user");
|
|
135
|
+
const timezone = typeof user.timezone === "string" && user.timezone ? user.timezone : void 0;
|
|
133
136
|
this.meCache = {
|
|
134
137
|
id: expectNumericField(user, "id", "user"),
|
|
135
|
-
username: expectStringField(user, "username", "user")
|
|
138
|
+
username: expectStringField(user, "username", "user"),
|
|
139
|
+
...timezone ? { timezone } : {}
|
|
136
140
|
};
|
|
137
141
|
return this.meCache;
|
|
138
142
|
}
|
|
143
|
+
async getUserTimezone() {
|
|
144
|
+
const me = await this.getMe();
|
|
145
|
+
return me.timezone;
|
|
146
|
+
}
|
|
139
147
|
async paginate(buildPath) {
|
|
140
148
|
const allTasks = [];
|
|
141
149
|
let page = 0;
|
|
@@ -304,21 +312,21 @@ var ClickUpClient = class {
|
|
|
304
312
|
}
|
|
305
313
|
async getView(viewId) {
|
|
306
314
|
const data = await this.request(`/view/${viewId}`);
|
|
307
|
-
return data
|
|
315
|
+
return expectRecordField(data, "view", "view");
|
|
308
316
|
}
|
|
309
317
|
async createListView(listId, payload) {
|
|
310
318
|
const data = await this.request(`/list/${listId}/view`, {
|
|
311
319
|
method: "POST",
|
|
312
320
|
body: JSON.stringify(payload)
|
|
313
321
|
});
|
|
314
|
-
return data
|
|
322
|
+
return expectRecordField(data, "view", "view");
|
|
315
323
|
}
|
|
316
324
|
async updateView(viewId, payload) {
|
|
317
325
|
const data = await this.request(`/view/${viewId}`, {
|
|
318
326
|
method: "PUT",
|
|
319
327
|
body: JSON.stringify(payload)
|
|
320
328
|
});
|
|
321
|
-
return data
|
|
329
|
+
return expectRecordField(data, "view", "view");
|
|
322
330
|
}
|
|
323
331
|
async deleteView(viewId) {
|
|
324
332
|
await this.request(`/view/${viewId}`, { method: "DELETE" });
|
|
@@ -676,7 +684,7 @@ var ClickUpClient = class {
|
|
|
676
684
|
async createGoal(teamId, name, opts) {
|
|
677
685
|
const body = { name, multiple_owners: true };
|
|
678
686
|
if (opts?.description) body.description = opts.description;
|
|
679
|
-
if (opts?.dueDate) body.due_date =
|
|
687
|
+
if (opts?.dueDate != null) body.due_date = opts.dueDate;
|
|
680
688
|
if (opts?.color) body.color = opts.color;
|
|
681
689
|
const data = await this.request(`/team/${teamId}/goal`, {
|
|
682
690
|
method: "POST",
|
|
@@ -965,7 +973,7 @@ function loadConfig(profileName) {
|
|
|
965
973
|
...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
|
|
966
974
|
};
|
|
967
975
|
}
|
|
968
|
-
const multi =
|
|
976
|
+
const multi = loadMultiProfileConfig();
|
|
969
977
|
const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
|
|
970
978
|
if (!resolvedProfile) {
|
|
971
979
|
throw new Error("No default profile set. Run: cup profile use <name>");
|
|
@@ -1158,9 +1166,9 @@ function formatDuration(ms) {
|
|
|
1158
1166
|
}
|
|
1159
1167
|
function formatDateISO(ms) {
|
|
1160
1168
|
const d = new Date(Number(ms));
|
|
1161
|
-
const year = d.
|
|
1162
|
-
const month = String(d.
|
|
1163
|
-
const day = String(d.
|
|
1169
|
+
const year = d.getUTCFullYear();
|
|
1170
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1171
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
1164
1172
|
return `${year}-${month}-${day}`;
|
|
1165
1173
|
}
|
|
1166
1174
|
|
|
@@ -1725,14 +1733,40 @@ function parsePriority(value) {
|
|
|
1725
1733
|
if (Number.isInteger(num) && num >= 1 && num <= 4) return num;
|
|
1726
1734
|
throw new Error("Priority must be urgent, high, normal, low, or 1-4");
|
|
1727
1735
|
}
|
|
1728
|
-
function parseDueDate(value) {
|
|
1736
|
+
function parseDueDate(value, timezone) {
|
|
1729
1737
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
1730
1738
|
throw new Error("Date must be in YYYY-MM-DD format");
|
|
1731
1739
|
}
|
|
1732
|
-
const parts = value.split("-");
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
|
|
1740
|
+
const parts = value.split("-").map(Number);
|
|
1741
|
+
const y = parts[0];
|
|
1742
|
+
const m = parts[1];
|
|
1743
|
+
const d = parts[2];
|
|
1744
|
+
if (timezone) {
|
|
1745
|
+
try {
|
|
1746
|
+
const ms2 = dateToTimezoneMs(y, m, d, timezone);
|
|
1747
|
+
if (!isNaN(ms2)) return ms2;
|
|
1748
|
+
} catch {
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const ms = Date.UTC(y, m - 1, d);
|
|
1752
|
+
if (isNaN(ms)) throw new Error(`Invalid date: ${value}`);
|
|
1753
|
+
return ms;
|
|
1754
|
+
}
|
|
1755
|
+
function dateToTimezoneMs(year, month, day, timezone) {
|
|
1756
|
+
const approxUtc = new Date(Date.UTC(year, month - 1, day));
|
|
1757
|
+
const tzStr = approxUtc.toLocaleString("en-US", {
|
|
1758
|
+
timeZone: timezone,
|
|
1759
|
+
year: "numeric",
|
|
1760
|
+
month: "2-digit",
|
|
1761
|
+
day: "2-digit",
|
|
1762
|
+
hour: "2-digit",
|
|
1763
|
+
minute: "2-digit",
|
|
1764
|
+
second: "2-digit",
|
|
1765
|
+
hour12: false
|
|
1766
|
+
});
|
|
1767
|
+
const tzDate = /* @__PURE__ */ new Date(tzStr + " UTC");
|
|
1768
|
+
const offset = approxUtc.getTime() - tzDate.getTime();
|
|
1769
|
+
return approxUtc.getTime() + offset;
|
|
1736
1770
|
}
|
|
1737
1771
|
function parseAssigneeId(value) {
|
|
1738
1772
|
const id = Number(value);
|
|
@@ -1761,7 +1795,7 @@ function parseTimeEstimate(value) {
|
|
|
1761
1795
|
'Time estimate must be a duration (e.g. "2h", "30m", "1h30m"), milliseconds, or "0" to clear'
|
|
1762
1796
|
);
|
|
1763
1797
|
}
|
|
1764
|
-
function buildUpdatePayload(opts) {
|
|
1798
|
+
function buildUpdatePayload(opts, timezone) {
|
|
1765
1799
|
if (opts.archive && opts.unarchive) {
|
|
1766
1800
|
throw new Error("Cannot use --archive and --unarchive together");
|
|
1767
1801
|
}
|
|
@@ -1780,12 +1814,12 @@ function buildUpdatePayload(opts) {
|
|
|
1780
1814
|
if (opts.dueDate === "none" || opts.dueDate === "clear") {
|
|
1781
1815
|
payload.due_date = null;
|
|
1782
1816
|
} else {
|
|
1783
|
-
payload.due_date = parseDueDate(opts.dueDate);
|
|
1817
|
+
payload.due_date = parseDueDate(opts.dueDate, timezone);
|
|
1784
1818
|
payload.due_date_time = false;
|
|
1785
1819
|
}
|
|
1786
1820
|
}
|
|
1787
1821
|
if (opts.startDate !== void 0) {
|
|
1788
|
-
payload.start_date = parseDueDate(opts.startDate);
|
|
1822
|
+
payload.start_date = parseDueDate(opts.startDate, timezone);
|
|
1789
1823
|
payload.start_date_time = false;
|
|
1790
1824
|
}
|
|
1791
1825
|
if (opts.assignee !== void 0 || opts.removeAssignee !== void 0) {
|
|
@@ -1832,10 +1866,11 @@ async function updateTask(config, taskId, options) {
|
|
|
1832
1866
|
"Provide at least one of: --name, --description, --status, --priority, --due-date, --start-date, --time-estimate, --assignee, --remove-assignee, --parent, --detach, --archive, --unarchive"
|
|
1833
1867
|
);
|
|
1834
1868
|
const client = new ClickUpClient(config);
|
|
1835
|
-
|
|
1836
|
-
|
|
1869
|
+
const resolved = { ...options };
|
|
1870
|
+
if (resolved.status !== void 0) {
|
|
1871
|
+
resolved.status = await resolveStatus(client, taskId, resolved.status);
|
|
1837
1872
|
}
|
|
1838
|
-
const task = await client.updateTask(taskId,
|
|
1873
|
+
const task = await client.updateTask(taskId, resolved);
|
|
1839
1874
|
return { id: task.id, name: task.name };
|
|
1840
1875
|
}
|
|
1841
1876
|
|
|
@@ -1855,6 +1890,7 @@ async function createTask(config, options) {
|
|
|
1855
1890
|
const task2 = await client.createTaskFromTemplate(listId, options.template, options.name);
|
|
1856
1891
|
return { id: task2.id, name: task2.name, url: task2.url };
|
|
1857
1892
|
}
|
|
1893
|
+
const timezone = await client.getUserTimezone();
|
|
1858
1894
|
const payload = {
|
|
1859
1895
|
name: options.name,
|
|
1860
1896
|
...options.description !== void 0 ? { markdown_content: options.description } : {},
|
|
@@ -1865,11 +1901,11 @@ async function createTask(config, options) {
|
|
|
1865
1901
|
payload.priority = parsePriority(options.priority);
|
|
1866
1902
|
}
|
|
1867
1903
|
if (options.dueDate !== void 0) {
|
|
1868
|
-
payload.due_date = parseDueDate(options.dueDate);
|
|
1904
|
+
payload.due_date = parseDueDate(options.dueDate, timezone);
|
|
1869
1905
|
payload.due_date_time = false;
|
|
1870
1906
|
}
|
|
1871
1907
|
if (options.startDate !== void 0) {
|
|
1872
|
-
payload.start_date = parseDueDate(options.startDate);
|
|
1908
|
+
payload.start_date = parseDueDate(options.startDate, timezone);
|
|
1873
1909
|
payload.start_date_time = false;
|
|
1874
1910
|
}
|
|
1875
1911
|
if (options.assignee !== void 0) {
|
|
@@ -4049,6 +4085,10 @@ ${renderZshTopLevelCommands(name)}
|
|
|
4049
4085
|
_arguments \\
|
|
4050
4086
|
'--days[Number of days to look back]:days:' \\
|
|
4051
4087
|
'--task[Filter by task ID]:task_id:' \\
|
|
4088
|
+
'--space[Filter by space ID]:space_id:' \\
|
|
4089
|
+
'--list[Filter by list ID]:list_id:' \\
|
|
4090
|
+
'--assignee[Filter by assignee user ID]:user_id:' \\
|
|
4091
|
+
'--all[Show all team entries]' \\
|
|
4052
4092
|
'--json[Force JSON output]'
|
|
4053
4093
|
;;
|
|
4054
4094
|
update)
|
|
@@ -4509,6 +4549,10 @@ complete -c ${name} -n '__fish_seen_subcommand_from start; and __fish_seen_subco
|
|
|
4509
4549
|
complete -c ${name} -n '__fish_seen_subcommand_from log; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
|
|
4510
4550
|
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l days -d 'Number of days to look back'
|
|
4511
4551
|
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l task -d 'Filter by task ID'
|
|
4552
|
+
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l space -d 'Filter by space ID'
|
|
4553
|
+
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l list -d 'Filter by list ID'
|
|
4554
|
+
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l assignee -d 'Filter by assignee user ID'
|
|
4555
|
+
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l all -d 'Show all team entries'
|
|
4512
4556
|
complete -c ${name} -n '__fish_seen_subcommand_from update; and __fish_seen_subcommand_from time' -s d -l description -d 'New description'
|
|
4513
4557
|
complete -c ${name} -n '__fish_seen_subcommand_from update; and __fish_seen_subcommand_from time' -l duration -d 'New duration'
|
|
4514
4558
|
|
|
@@ -5325,7 +5369,6 @@ function formatFieldsMarkdown(fields) {
|
|
|
5325
5369
|
}
|
|
5326
5370
|
|
|
5327
5371
|
// src/commands/duplicate.ts
|
|
5328
|
-
var PRIORITY_MAP2 = { urgent: 1, high: 2, normal: 3, low: 4 };
|
|
5329
5372
|
async function duplicateTask(config, taskId) {
|
|
5330
5373
|
const client = new ClickUpClient(config);
|
|
5331
5374
|
const task = await client.getTask(taskId);
|
|
@@ -5333,7 +5376,7 @@ async function duplicateTask(config, taskId) {
|
|
|
5333
5376
|
name: `${task.name} (copy)`,
|
|
5334
5377
|
description: task.description,
|
|
5335
5378
|
markdown_content: task.markdown_content,
|
|
5336
|
-
priority: task.priority ?
|
|
5379
|
+
priority: task.priority ? parsePriority(task.priority.priority.toLowerCase()) : void 0,
|
|
5337
5380
|
tags: task.tags?.map((t) => t.name),
|
|
5338
5381
|
time_estimate: task.time_estimate ?? void 0
|
|
5339
5382
|
});
|
|
@@ -5372,7 +5415,8 @@ async function bulkAssign(config, userIdOrMe, taskIds, action) {
|
|
|
5372
5415
|
}
|
|
5373
5416
|
async function bulkDueDate(config, date, taskIds) {
|
|
5374
5417
|
const client = new ClickUpClient(config);
|
|
5375
|
-
const
|
|
5418
|
+
const timezone = await client.getUserTimezone();
|
|
5419
|
+
const payload = date === "none" || date === "clear" ? { due_date: null } : { due_date: parseDueDate(date, timezone), due_date_time: false };
|
|
5376
5420
|
const failed = [];
|
|
5377
5421
|
for (const id of taskIds) {
|
|
5378
5422
|
try {
|
|
@@ -5430,7 +5474,12 @@ async function listGoals(config) {
|
|
|
5430
5474
|
}
|
|
5431
5475
|
async function createGoal(config, name, opts) {
|
|
5432
5476
|
const client = new ClickUpClient(config);
|
|
5433
|
-
|
|
5477
|
+
const timezone = await client.getUserTimezone();
|
|
5478
|
+
return client.createGoal(config.teamId, name, {
|
|
5479
|
+
description: opts?.description,
|
|
5480
|
+
color: opts?.color,
|
|
5481
|
+
...opts?.dueDate ? { dueDate: parseDueDate(opts.dueDate, timezone) } : {}
|
|
5482
|
+
});
|
|
5434
5483
|
}
|
|
5435
5484
|
async function updateGoal(config, goalId, updates) {
|
|
5436
5485
|
const client = new ClickUpClient(config);
|
|
@@ -5731,7 +5780,7 @@ function isAllowedFilterCommand(tokens) {
|
|
|
5731
5780
|
if (tokens[0] === "time" && tokens[1] === "list") return true;
|
|
5732
5781
|
return ALLOWED_FILTER_COMMANDS.has(tokens[0]);
|
|
5733
5782
|
}
|
|
5734
|
-
function runFilter(
|
|
5783
|
+
function runFilter(_name, entry) {
|
|
5735
5784
|
const result = spawnSync(process.execPath, [process.argv[1], ...entry.command], {
|
|
5736
5785
|
stdio: "inherit"
|
|
5737
5786
|
});
|
|
@@ -5755,14 +5804,17 @@ function formatFiltersTable(filters) {
|
|
|
5755
5804
|
}));
|
|
5756
5805
|
return formatTable(rows, FILTER_COLUMNS);
|
|
5757
5806
|
}
|
|
5807
|
+
function escapeMarkdownCell(value) {
|
|
5808
|
+
return value.replace(/\|/g, "\\|");
|
|
5809
|
+
}
|
|
5758
5810
|
function formatFiltersMarkdown(filters) {
|
|
5759
5811
|
const entries = Object.entries(filters);
|
|
5760
5812
|
if (entries.length === 0) return "No filters saved";
|
|
5761
5813
|
const lines = ["| Name | Command | Description |", "| --- | --- | --- |"];
|
|
5762
5814
|
for (const [name, entry] of entries) {
|
|
5763
|
-
const command = entry.command.join(" ");
|
|
5764
|
-
const description = entry.description ?? "";
|
|
5765
|
-
lines.push(`| ${name} | ${command} | ${description} |`);
|
|
5815
|
+
const command = escapeMarkdownCell(entry.command.join(" "));
|
|
5816
|
+
const description = escapeMarkdownCell(entry.description ?? "");
|
|
5817
|
+
lines.push(`| ${escapeMarkdownCell(name)} | ${command} | ${description} |`);
|
|
5766
5818
|
}
|
|
5767
5819
|
return lines.join("\n");
|
|
5768
5820
|
}
|
|
@@ -5786,18 +5838,29 @@ async function createListWithOptions(config, spaceId, name, opts) {
|
|
|
5786
5838
|
}
|
|
5787
5839
|
const list = opts.folder ? await client.createFolderList(opts.folder, name) : await client.createList(spaceId, name);
|
|
5788
5840
|
if (statuses) {
|
|
5789
|
-
|
|
5841
|
+
try {
|
|
5842
|
+
await client.updateList(list.id, { statuses });
|
|
5843
|
+
} catch (err) {
|
|
5844
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
5845
|
+
throw new Error(`List "${name}" (${list.id}) was created but status copy failed: ${reason}`, {
|
|
5846
|
+
cause: err
|
|
5847
|
+
});
|
|
5848
|
+
}
|
|
5790
5849
|
}
|
|
5791
5850
|
return {
|
|
5792
5851
|
...list,
|
|
5793
5852
|
...statuses ? { statusesCopied: statuses.length } : {}
|
|
5794
5853
|
};
|
|
5795
5854
|
}
|
|
5855
|
+
function isNotFound(err) {
|
|
5856
|
+
return err instanceof Error && /ClickUp API error 4(04|03)/.test(err.message);
|
|
5857
|
+
}
|
|
5796
5858
|
async function copyStatusesFrom(client, sourceId) {
|
|
5797
5859
|
try {
|
|
5798
5860
|
const list = await client.getListWithStatuses(sourceId);
|
|
5799
5861
|
return list.statuses.map((s) => ({ status: s.status, color: s.color, type: s.type ?? "custom" }));
|
|
5800
|
-
} catch {
|
|
5862
|
+
} catch (err) {
|
|
5863
|
+
if (!isNotFound(err)) throw err;
|
|
5801
5864
|
try {
|
|
5802
5865
|
const space = await client.getSpaceWithStatuses(sourceId);
|
|
5803
5866
|
return space.statuses.map((s) => ({
|
|
@@ -5805,9 +5868,11 @@ async function copyStatusesFrom(client, sourceId) {
|
|
|
5805
5868
|
color: s.color,
|
|
5806
5869
|
type: s.type ?? "custom"
|
|
5807
5870
|
}));
|
|
5808
|
-
} catch {
|
|
5871
|
+
} catch (spaceErr) {
|
|
5872
|
+
if (!isNotFound(spaceErr)) throw spaceErr;
|
|
5809
5873
|
throw new Error(
|
|
5810
|
-
`Could not find a list or space with ID "${sourceId}". Check the ID and try again
|
|
5874
|
+
`Could not find a list or space with ID "${sourceId}". Check the ID and try again.`,
|
|
5875
|
+
{ cause: spaceErr }
|
|
5811
5876
|
);
|
|
5812
5877
|
}
|
|
5813
5878
|
}
|
|
@@ -5890,15 +5955,17 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
5890
5955
|
wrapAction(
|
|
5891
5956
|
async (taskId, opts) => {
|
|
5892
5957
|
const config = loadConfig(getProfileName());
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
opts.removeAssignee
|
|
5900
|
-
|
|
5901
|
-
|
|
5958
|
+
const client = new ClickUpClient(config);
|
|
5959
|
+
const [timezone] = await Promise.all([
|
|
5960
|
+
client.getUserTimezone(),
|
|
5961
|
+
opts.assignee === "me" ? resolveAssigneeId(client, "me").then((id) => {
|
|
5962
|
+
opts.assignee = String(id);
|
|
5963
|
+
}) : Promise.resolve(),
|
|
5964
|
+
opts.removeAssignee === "me" ? resolveAssigneeId(client, "me").then((id) => {
|
|
5965
|
+
opts.removeAssignee = String(id);
|
|
5966
|
+
}) : Promise.resolve()
|
|
5967
|
+
]);
|
|
5968
|
+
const payload = buildUpdatePayload(opts, timezone);
|
|
5902
5969
|
const hasFields = (opts.field?.length ?? 0) > 0;
|
|
5903
5970
|
if (!hasFields && Object.keys(payload).length === 0) {
|
|
5904
5971
|
throw new Error(
|
|
@@ -5917,8 +5984,8 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
5917
5984
|
await setCustomField(config, taskId, { set: [opts.field[i], opts.field[i + 1]] });
|
|
5918
5985
|
}
|
|
5919
5986
|
if (!result) {
|
|
5920
|
-
const
|
|
5921
|
-
const task = await
|
|
5987
|
+
const client2 = new ClickUpClient(config);
|
|
5988
|
+
const task = await client2.getTask(taskId);
|
|
5922
5989
|
result = { id: task.id, name: task.name };
|
|
5923
5990
|
}
|
|
5924
5991
|
}
|
|
@@ -6623,10 +6690,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
6623
6690
|
outputBulkResult(result, opts.json ?? false, "due-date");
|
|
6624
6691
|
})
|
|
6625
6692
|
);
|
|
6626
|
-
bulkCmd.command("tag <tagName> <taskIds...>").description("Bulk add or remove a tag on tasks
|
|
6693
|
+
bulkCmd.command("tag <tagName> <taskIds...>").description("Bulk add or remove a tag on tasks (default: add)").option("--remove", "Remove tag instead of adding").option("--json", "Force JSON output even in terminal").action(
|
|
6627
6694
|
wrapAction(
|
|
6628
6695
|
async (tagName, taskIds, opts) => {
|
|
6629
|
-
if (opts.add && opts.remove) throw new Error("Cannot use --add and --remove together");
|
|
6630
6696
|
const action = opts.remove ? "remove" : "add";
|
|
6631
6697
|
const config = loadConfig(getProfileName());
|
|
6632
6698
|
const result = await bulkTag(config, tagName, taskIds, action);
|
|
@@ -7104,8 +7170,9 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
7104
7170
|
);
|
|
7105
7171
|
}
|
|
7106
7172
|
if (!isAllowedFilterCommand(args)) {
|
|
7173
|
+
const allowed = [...ALLOWED_FILTER_COMMANDS, "time list"].join(", ");
|
|
7107
7174
|
throw new Error(
|
|
7108
|
-
`Command "${args[0]}" is not allowed in saved filters. Allowed:
|
|
7175
|
+
`Command "${args[0]}" is not allowed in saved filters. Allowed: ${allowed}`
|
|
7109
7176
|
);
|
|
7110
7177
|
}
|
|
7111
7178
|
const entry = { command: args };
|