@krodak/clickup-cli 0.14.0 → 0.16.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.
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +213 -477
- package/dist/index.js +420 -4
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +57 -34
package/dist/index.js
CHANGED
|
@@ -120,6 +120,9 @@ var ClickUpClient = class {
|
|
|
120
120
|
async getSpaceWithStatuses(spaceId) {
|
|
121
121
|
return this.request(`/space/${spaceId}`);
|
|
122
122
|
}
|
|
123
|
+
async getListWithStatuses(listId) {
|
|
124
|
+
return this.request(`/list/${listId}`);
|
|
125
|
+
}
|
|
123
126
|
async getSpaces(teamId) {
|
|
124
127
|
const data = await this.request(`/team/${teamId}/space?archived=false`);
|
|
125
128
|
return data.spaces ?? [];
|
|
@@ -201,6 +204,25 @@ var ClickUpClient = class {
|
|
|
201
204
|
body: JSON.stringify(body)
|
|
202
205
|
});
|
|
203
206
|
}
|
|
207
|
+
async deleteComment(commentId) {
|
|
208
|
+
await this.request(`/comment/${commentId}`, { method: "DELETE" });
|
|
209
|
+
}
|
|
210
|
+
async getThreadedComments(commentId) {
|
|
211
|
+
const data = await this.request(`/comment/${commentId}/reply`);
|
|
212
|
+
return data.comments ?? [];
|
|
213
|
+
}
|
|
214
|
+
async createThreadedComment(commentId, text) {
|
|
215
|
+
await this.request(`/comment/${commentId}/reply`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
body: JSON.stringify({ comment_text: text })
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async addTaskLink(taskId, linksTo) {
|
|
221
|
+
await this.request(`/task/${taskId}/link/${linksTo}`, { method: "POST" });
|
|
222
|
+
}
|
|
223
|
+
async deleteTaskLink(taskId, linksTo) {
|
|
224
|
+
await this.request(`/task/${taskId}/link/${linksTo}`, { method: "DELETE" });
|
|
225
|
+
}
|
|
204
226
|
async getListCustomFields(listId) {
|
|
205
227
|
const data = await this.request(`/list/${listId}/field`);
|
|
206
228
|
return data.fields ?? [];
|
|
@@ -235,6 +257,63 @@ var ClickUpClient = class {
|
|
|
235
257
|
{ method: "DELETE" }
|
|
236
258
|
);
|
|
237
259
|
}
|
|
260
|
+
async startTimeEntry(teamId, taskId, description) {
|
|
261
|
+
const body = {
|
|
262
|
+
tid: taskId,
|
|
263
|
+
start: Date.now(),
|
|
264
|
+
duration: -1
|
|
265
|
+
};
|
|
266
|
+
if (description) body.description = description;
|
|
267
|
+
const data = await this.request(`/team/${teamId}/time_entries/start`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
body: JSON.stringify(body)
|
|
270
|
+
});
|
|
271
|
+
return data.data;
|
|
272
|
+
}
|
|
273
|
+
async stopTimeEntry(teamId) {
|
|
274
|
+
const data = await this.request(`/team/${teamId}/time_entries/stop`, {
|
|
275
|
+
method: "POST"
|
|
276
|
+
});
|
|
277
|
+
return data.data;
|
|
278
|
+
}
|
|
279
|
+
async getRunningTimeEntry(teamId) {
|
|
280
|
+
const data = await this.request(
|
|
281
|
+
`/team/${teamId}/time_entries/current`
|
|
282
|
+
);
|
|
283
|
+
return data.data ?? null;
|
|
284
|
+
}
|
|
285
|
+
async createTimeEntry(teamId, taskId, duration, opts) {
|
|
286
|
+
const start = opts?.start ?? Date.now() - duration;
|
|
287
|
+
const body = {
|
|
288
|
+
tid: taskId,
|
|
289
|
+
start,
|
|
290
|
+
duration
|
|
291
|
+
};
|
|
292
|
+
if (opts?.description) body.description = opts.description;
|
|
293
|
+
const data = await this.request(`/team/${teamId}/time_entries`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
body: JSON.stringify(body)
|
|
296
|
+
});
|
|
297
|
+
return data.data;
|
|
298
|
+
}
|
|
299
|
+
async getTimeEntries(teamId, opts) {
|
|
300
|
+
const params = new URLSearchParams();
|
|
301
|
+
if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
|
|
302
|
+
if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
|
|
303
|
+
const query = params.toString();
|
|
304
|
+
const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
|
|
305
|
+
const data = await this.request(url);
|
|
306
|
+
const entries = data.data ?? [];
|
|
307
|
+
if (opts?.taskId) {
|
|
308
|
+
return entries.filter((e) => e.task?.id === opts.taskId);
|
|
309
|
+
}
|
|
310
|
+
return entries;
|
|
311
|
+
}
|
|
312
|
+
async deleteTimeEntry(teamId, timeEntryId) {
|
|
313
|
+
await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
|
|
314
|
+
method: "DELETE"
|
|
315
|
+
});
|
|
316
|
+
}
|
|
238
317
|
};
|
|
239
318
|
|
|
240
319
|
// src/config.ts
|
|
@@ -865,9 +944,8 @@ function hasUpdateFields(options) {
|
|
|
865
944
|
}
|
|
866
945
|
async function resolveStatus(client, taskId, statusInput) {
|
|
867
946
|
const task = await client.getTask(taskId);
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
const available = space.statuses.map((s) => s.status);
|
|
947
|
+
const list = await client.getListWithStatuses(task.list.id);
|
|
948
|
+
const available = list.statuses.map((s) => s.status);
|
|
871
949
|
const matched = matchStatus(statusInput, available);
|
|
872
950
|
if (!matched) {
|
|
873
951
|
throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
|
|
@@ -1749,7 +1827,7 @@ function bashCompletion() {
|
|
|
1749
1827
|
cword=$COMP_CWORD
|
|
1750
1828
|
fi
|
|
1751
1829
|
|
|
1752
|
-
local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comments activity lists spaces inbox assigned open search summary overdue assign depend move field delete tag checklist config completion"
|
|
1830
|
+
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 move field delete tag checklist time config completion"
|
|
1753
1831
|
|
|
1754
1832
|
if [[ $cword -eq 1 ]]; then
|
|
1755
1833
|
COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
|
|
@@ -1850,9 +1928,26 @@ function bashCompletion() {
|
|
|
1850
1928
|
COMPREPLY=($(compgen -W "view create delete add-item edit-item delete-item" -- "$cur"))
|
|
1851
1929
|
fi
|
|
1852
1930
|
;;
|
|
1931
|
+
time)
|
|
1932
|
+
if [[ $cword -eq 2 ]]; then
|
|
1933
|
+
COMPREPLY=($(compgen -W "start stop status log list" -- "$cur"))
|
|
1934
|
+
fi
|
|
1935
|
+
;;
|
|
1853
1936
|
comment-edit)
|
|
1854
1937
|
COMPREPLY=($(compgen -W "-m --message --resolved --unresolved --json" -- "$cur"))
|
|
1855
1938
|
;;
|
|
1939
|
+
comment-delete)
|
|
1940
|
+
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
1941
|
+
;;
|
|
1942
|
+
replies)
|
|
1943
|
+
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
1944
|
+
;;
|
|
1945
|
+
reply)
|
|
1946
|
+
COMPREPLY=($(compgen -W "-m --message --json" -- "$cur"))
|
|
1947
|
+
;;
|
|
1948
|
+
link)
|
|
1949
|
+
COMPREPLY=($(compgen -W "--remove --json" -- "$cur"))
|
|
1950
|
+
;;
|
|
1856
1951
|
config)
|
|
1857
1952
|
if [[ $cword -eq 2 ]]; then
|
|
1858
1953
|
COMPREPLY=($(compgen -W "get set path" -- "$cur"))
|
|
@@ -1906,7 +2001,12 @@ _cu() {
|
|
|
1906
2001
|
'delete:Delete a task'
|
|
1907
2002
|
'tag:Add or remove tags from a task'
|
|
1908
2003
|
'checklist:Manage checklists on a task'
|
|
2004
|
+
'time:Track time on tasks'
|
|
1909
2005
|
'comment-edit:Edit an existing comment'
|
|
2006
|
+
'comment-delete:Delete a comment'
|
|
2007
|
+
'replies:List threaded replies on a comment'
|
|
2008
|
+
'reply:Reply to a comment'
|
|
2009
|
+
'link:Add or remove a link between two tasks'
|
|
1910
2010
|
'config:Manage CLI configuration'
|
|
1911
2011
|
'completion:Output shell completion script'
|
|
1912
2012
|
)
|
|
@@ -2142,6 +2242,53 @@ _cu() {
|
|
|
2142
2242
|
;;
|
|
2143
2243
|
esac
|
|
2144
2244
|
;;
|
|
2245
|
+
time)
|
|
2246
|
+
local -a time_cmds
|
|
2247
|
+
time_cmds=(
|
|
2248
|
+
'start:Start tracking time on a task'
|
|
2249
|
+
'stop:Stop the running timer'
|
|
2250
|
+
'status:Show the currently running timer'
|
|
2251
|
+
'log:Log a manual time entry'
|
|
2252
|
+
'list:List recent time entries'
|
|
2253
|
+
)
|
|
2254
|
+
_arguments -C \\
|
|
2255
|
+
'1:time command:->time_cmd' \\
|
|
2256
|
+
'*::time_arg:->time_args'
|
|
2257
|
+
case $state in
|
|
2258
|
+
time_cmd)
|
|
2259
|
+
_describe 'time command' time_cmds
|
|
2260
|
+
;;
|
|
2261
|
+
time_args)
|
|
2262
|
+
case $words[1] in
|
|
2263
|
+
start)
|
|
2264
|
+
_arguments \\
|
|
2265
|
+
'1:task_id:' \\
|
|
2266
|
+
'(-d --description)'{-d,--description}'[Description]:text:' \\
|
|
2267
|
+
'--json[Force JSON output]'
|
|
2268
|
+
;;
|
|
2269
|
+
stop)
|
|
2270
|
+
_arguments '--json[Force JSON output]'
|
|
2271
|
+
;;
|
|
2272
|
+
status)
|
|
2273
|
+
_arguments '--json[Force JSON output]'
|
|
2274
|
+
;;
|
|
2275
|
+
log)
|
|
2276
|
+
_arguments \\
|
|
2277
|
+
'1:task_id:' \\
|
|
2278
|
+
'2:duration:' \\
|
|
2279
|
+
'(-d --description)'{-d,--description}'[Description]:text:' \\
|
|
2280
|
+
'--json[Force JSON output]'
|
|
2281
|
+
;;
|
|
2282
|
+
list)
|
|
2283
|
+
_arguments \\
|
|
2284
|
+
'--days[Number of days to look back]:days:' \\
|
|
2285
|
+
'--task[Filter by task ID]:task_id:' \\
|
|
2286
|
+
'--json[Force JSON output]'
|
|
2287
|
+
;;
|
|
2288
|
+
esac
|
|
2289
|
+
;;
|
|
2290
|
+
esac
|
|
2291
|
+
;;
|
|
2145
2292
|
comment-edit)
|
|
2146
2293
|
_arguments \\
|
|
2147
2294
|
'1:comment_id:' \\
|
|
@@ -2150,6 +2297,29 @@ _cu() {
|
|
|
2150
2297
|
'--unresolved[Mark comment as unresolved]' \\
|
|
2151
2298
|
'--json[Force JSON output]'
|
|
2152
2299
|
;;
|
|
2300
|
+
comment-delete)
|
|
2301
|
+
_arguments \\
|
|
2302
|
+
'1:comment_id:' \\
|
|
2303
|
+
'--json[Force JSON output]'
|
|
2304
|
+
;;
|
|
2305
|
+
replies)
|
|
2306
|
+
_arguments \\
|
|
2307
|
+
'1:comment_id:' \\
|
|
2308
|
+
'--json[Force JSON output]'
|
|
2309
|
+
;;
|
|
2310
|
+
reply)
|
|
2311
|
+
_arguments \\
|
|
2312
|
+
'1:comment_id:' \\
|
|
2313
|
+
'(-m --message)'{-m,--message}'[Reply text]:text:' \\
|
|
2314
|
+
'--json[Force JSON output]'
|
|
2315
|
+
;;
|
|
2316
|
+
link)
|
|
2317
|
+
_arguments \\
|
|
2318
|
+
'1:task_id:' \\
|
|
2319
|
+
'2:links_to:' \\
|
|
2320
|
+
'--remove[Remove the link instead of adding it]' \\
|
|
2321
|
+
'--json[Force JSON output]'
|
|
2322
|
+
;;
|
|
2153
2323
|
config)
|
|
2154
2324
|
local -a config_cmds
|
|
2155
2325
|
config_cmds=(
|
|
@@ -2217,7 +2387,12 @@ complete -c cu -n __fish_use_subcommand -a field -d 'Set or remove a custom fiel
|
|
|
2217
2387
|
complete -c cu -n __fish_use_subcommand -a delete -d 'Delete a task'
|
|
2218
2388
|
complete -c cu -n __fish_use_subcommand -a tag -d 'Add or remove tags from a task'
|
|
2219
2389
|
complete -c cu -n __fish_use_subcommand -a checklist -d 'Manage checklists on a task'
|
|
2390
|
+
complete -c cu -n __fish_use_subcommand -a time -d 'Track time on tasks'
|
|
2220
2391
|
complete -c cu -n __fish_use_subcommand -a comment-edit -d 'Edit an existing comment'
|
|
2392
|
+
complete -c cu -n __fish_use_subcommand -a comment-delete -d 'Delete a comment'
|
|
2393
|
+
complete -c cu -n __fish_use_subcommand -a replies -d 'List threaded replies on a comment'
|
|
2394
|
+
complete -c cu -n __fish_use_subcommand -a reply -d 'Reply to a comment'
|
|
2395
|
+
complete -c cu -n __fish_use_subcommand -a link -d 'Add or remove a link between two tasks'
|
|
2221
2396
|
complete -c cu -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
|
|
2222
2397
|
complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completion script'
|
|
2223
2398
|
|
|
@@ -2339,6 +2514,27 @@ complete -c cu -n '__fish_seen_subcommand_from edit-item' -l resolved -d 'Mark i
|
|
|
2339
2514
|
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l unresolved -d 'Mark item as unresolved'
|
|
2340
2515
|
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l assignee -d 'Assign user by ID'
|
|
2341
2516
|
|
|
2517
|
+
complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a start -d 'Start tracking time on a task'
|
|
2518
|
+
complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a stop -d 'Stop the running timer'
|
|
2519
|
+
complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a status -d 'Show the currently running timer'
|
|
2520
|
+
complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a log -d 'Log a manual time entry'
|
|
2521
|
+
complete -c cu -n '__fish_seen_subcommand_from time; and not __fish_seen_subcommand_from start stop status log list' -a list -d 'List recent time entries'
|
|
2522
|
+
complete -c cu -n '__fish_seen_subcommand_from start stop status log list; and __fish_seen_subcommand_from time' -l json -d 'Force JSON output'
|
|
2523
|
+
complete -c cu -n '__fish_seen_subcommand_from start; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
|
|
2524
|
+
complete -c cu -n '__fish_seen_subcommand_from log; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
|
|
2525
|
+
complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l days -d 'Number of days to look back'
|
|
2526
|
+
complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l task -d 'Filter by task ID'
|
|
2527
|
+
|
|
2528
|
+
complete -c cu -n '__fish_seen_subcommand_from comment-delete' -l json -d 'Force JSON output'
|
|
2529
|
+
|
|
2530
|
+
complete -c cu -n '__fish_seen_subcommand_from replies' -l json -d 'Force JSON output'
|
|
2531
|
+
|
|
2532
|
+
complete -c cu -n '__fish_seen_subcommand_from reply' -s m -l message -d 'Reply text'
|
|
2533
|
+
complete -c cu -n '__fish_seen_subcommand_from reply' -l json -d 'Force JSON output'
|
|
2534
|
+
|
|
2535
|
+
complete -c cu -n '__fish_seen_subcommand_from link' -l remove -d 'Remove the link'
|
|
2536
|
+
complete -c cu -n '__fish_seen_subcommand_from link' -l json -d 'Force JSON output'
|
|
2537
|
+
|
|
2342
2538
|
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -s m -l message -d 'New comment text'
|
|
2343
2539
|
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l resolved -d 'Mark comment as resolved'
|
|
2344
2540
|
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
|
|
@@ -2651,6 +2847,109 @@ async function editComment(config, commentId, text, resolved) {
|
|
|
2651
2847
|
await client.updateComment(commentId, text, resolved);
|
|
2652
2848
|
}
|
|
2653
2849
|
|
|
2850
|
+
// src/commands/comment-delete.ts
|
|
2851
|
+
async function deleteComment(config, commentId) {
|
|
2852
|
+
const client = new ClickUpClient(config);
|
|
2853
|
+
await client.deleteComment(commentId);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/commands/replies.ts
|
|
2857
|
+
import chalk6 from "chalk";
|
|
2858
|
+
async function getReplies(config, commentId) {
|
|
2859
|
+
const client = new ClickUpClient(config);
|
|
2860
|
+
return client.getThreadedComments(commentId);
|
|
2861
|
+
}
|
|
2862
|
+
async function createReply(config, commentId, text) {
|
|
2863
|
+
if (!text.trim()) throw new Error("Reply text cannot be empty");
|
|
2864
|
+
const client = new ClickUpClient(config);
|
|
2865
|
+
await client.createThreadedComment(commentId, text);
|
|
2866
|
+
}
|
|
2867
|
+
function formatReplies(replies) {
|
|
2868
|
+
if (replies.length === 0) return "No replies";
|
|
2869
|
+
return replies.map((r) => {
|
|
2870
|
+
const user = r.user?.username ?? "Unknown";
|
|
2871
|
+
const date = new Date(Number(r.date)).toLocaleString();
|
|
2872
|
+
return `${chalk6.bold(user)} ${chalk6.dim(date)}
|
|
2873
|
+
${r.comment_text}`;
|
|
2874
|
+
}).join("\n\n");
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// src/commands/link.ts
|
|
2878
|
+
async function manageTaskLink(config, taskId, linksTo, remove) {
|
|
2879
|
+
const client = new ClickUpClient(config);
|
|
2880
|
+
if (remove) {
|
|
2881
|
+
await client.deleteTaskLink(taskId, linksTo);
|
|
2882
|
+
return `Removed link between ${taskId} and ${linksTo}`;
|
|
2883
|
+
}
|
|
2884
|
+
await client.addTaskLink(taskId, linksTo);
|
|
2885
|
+
return `Linked ${taskId} to ${linksTo}`;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// src/commands/time.ts
|
|
2889
|
+
import chalk7 from "chalk";
|
|
2890
|
+
function formatDuration2(ms) {
|
|
2891
|
+
const totalMinutes = Math.round(Math.abs(ms) / 6e4);
|
|
2892
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
2893
|
+
const minutes = totalMinutes % 60;
|
|
2894
|
+
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
|
|
2895
|
+
if (hours > 0) return `${hours}h`;
|
|
2896
|
+
return `${minutes}m`;
|
|
2897
|
+
}
|
|
2898
|
+
function formatTimestamp2(ms) {
|
|
2899
|
+
return new Date(Number(ms)).toLocaleString("en-US", {
|
|
2900
|
+
month: "short",
|
|
2901
|
+
day: "numeric",
|
|
2902
|
+
hour: "numeric",
|
|
2903
|
+
minute: "2-digit"
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
async function startTimer(config, taskId, description) {
|
|
2907
|
+
const client = new ClickUpClient(config);
|
|
2908
|
+
return client.startTimeEntry(config.teamId, taskId, description);
|
|
2909
|
+
}
|
|
2910
|
+
async function stopTimer(config) {
|
|
2911
|
+
const client = new ClickUpClient(config);
|
|
2912
|
+
return client.stopTimeEntry(config.teamId);
|
|
2913
|
+
}
|
|
2914
|
+
async function timerStatus(config) {
|
|
2915
|
+
const client = new ClickUpClient(config);
|
|
2916
|
+
return client.getRunningTimeEntry(config.teamId);
|
|
2917
|
+
}
|
|
2918
|
+
async function logTime(config, taskId, durationStr, description) {
|
|
2919
|
+
const client = new ClickUpClient(config);
|
|
2920
|
+
const duration = parseTimeEstimate(durationStr);
|
|
2921
|
+
return client.createTimeEntry(config.teamId, taskId, duration, { description });
|
|
2922
|
+
}
|
|
2923
|
+
async function listTimeEntries(config, opts) {
|
|
2924
|
+
const client = new ClickUpClient(config);
|
|
2925
|
+
const days = opts?.days ?? 7;
|
|
2926
|
+
const endDate = Date.now();
|
|
2927
|
+
const startDate = endDate - days * 24 * 60 * 60 * 1e3;
|
|
2928
|
+
return client.getTimeEntries(config.teamId, {
|
|
2929
|
+
startDate,
|
|
2930
|
+
endDate,
|
|
2931
|
+
taskId: opts?.taskId
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
function formatTimeEntry(entry) {
|
|
2935
|
+
const lines = [];
|
|
2936
|
+
const taskName = entry.task?.name ?? "No task";
|
|
2937
|
+
const taskId = entry.task?.id ?? "";
|
|
2938
|
+
const isRunning = entry.duration < 0;
|
|
2939
|
+
const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
|
|
2940
|
+
const durationStr = formatDuration2(elapsed);
|
|
2941
|
+
const status = isRunning ? chalk7.green("RUNNING") : "";
|
|
2942
|
+
lines.push(`${chalk7.bold(taskName)} ${chalk7.dim(taskId)} ${status}`);
|
|
2943
|
+
lines.push(
|
|
2944
|
+
` ${durationStr} - ${formatTimestamp2(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
|
|
2945
|
+
);
|
|
2946
|
+
return lines.join("\n");
|
|
2947
|
+
}
|
|
2948
|
+
function formatTimeEntries(entries) {
|
|
2949
|
+
if (entries.length === 0) return "No time entries";
|
|
2950
|
+
return entries.map(formatTimeEntry).join("\n");
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2654
2953
|
// src/index.ts
|
|
2655
2954
|
var require2 = createRequire(import.meta.url);
|
|
2656
2955
|
var { version } = require2("../package.json");
|
|
@@ -2808,6 +3107,39 @@ program.command("comment-edit <commentId>").description("Edit an existing commen
|
|
|
2808
3107
|
}
|
|
2809
3108
|
)
|
|
2810
3109
|
);
|
|
3110
|
+
program.command("comment-delete <commentId>").description("Delete a comment").option("--json", "Force JSON output even in terminal").action(
|
|
3111
|
+
wrapAction(async (commentId, opts) => {
|
|
3112
|
+
const config = loadConfig();
|
|
3113
|
+
await deleteComment(config, commentId);
|
|
3114
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3115
|
+
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
3116
|
+
} else {
|
|
3117
|
+
console.log(`Deleted comment ${commentId}`);
|
|
3118
|
+
}
|
|
3119
|
+
})
|
|
3120
|
+
);
|
|
3121
|
+
program.command("replies <commentId>").description("List threaded replies on a comment").option("--json", "Force JSON output even in terminal").action(
|
|
3122
|
+
wrapAction(async (commentId, opts) => {
|
|
3123
|
+
const config = loadConfig();
|
|
3124
|
+
const replies = await getReplies(config, commentId);
|
|
3125
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3126
|
+
console.log(JSON.stringify(replies, null, 2));
|
|
3127
|
+
} else {
|
|
3128
|
+
console.log(formatReplies(replies));
|
|
3129
|
+
}
|
|
3130
|
+
})
|
|
3131
|
+
);
|
|
3132
|
+
program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--json", "Force JSON output even in terminal").action(
|
|
3133
|
+
wrapAction(async (commentId, opts) => {
|
|
3134
|
+
const config = loadConfig();
|
|
3135
|
+
await createReply(config, commentId, opts.message);
|
|
3136
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3137
|
+
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
3138
|
+
} else {
|
|
3139
|
+
console.log(`Replied to comment ${commentId}`);
|
|
3140
|
+
}
|
|
3141
|
+
})
|
|
3142
|
+
);
|
|
2811
3143
|
program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
|
|
2812
3144
|
wrapAction(async (taskId, opts) => {
|
|
2813
3145
|
const config = loadConfig();
|
|
@@ -2908,6 +3240,25 @@ program.command("depend <taskId>").description("Add or remove task dependencies"
|
|
|
2908
3240
|
}
|
|
2909
3241
|
})
|
|
2910
3242
|
);
|
|
3243
|
+
program.command("link <taskId> <linksTo>").description("Add or remove a link between two tasks").option("--remove", "Remove the link instead of adding it").option("--json", "Force JSON output even in terminal").action(
|
|
3244
|
+
wrapAction(
|
|
3245
|
+
async (taskId, linksTo, opts) => {
|
|
3246
|
+
const config = loadConfig();
|
|
3247
|
+
const result = await manageTaskLink(config, taskId, linksTo, opts.remove ?? false);
|
|
3248
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3249
|
+
console.log(
|
|
3250
|
+
JSON.stringify(
|
|
3251
|
+
{ success: true, taskId, linksTo, action: opts.remove ? "removed" : "added" },
|
|
3252
|
+
null,
|
|
3253
|
+
2
|
|
3254
|
+
)
|
|
3255
|
+
);
|
|
3256
|
+
} else {
|
|
3257
|
+
console.log(result);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
)
|
|
3261
|
+
);
|
|
2911
3262
|
program.command("move <taskId>").description("Add or remove a task from a list").option("--to <listId>", "Add task to this list").option("--remove <listId>", "Remove task from this list").option("--json", "Force JSON output even in terminal").action(
|
|
2912
3263
|
wrapAction(async (taskId, opts) => {
|
|
2913
3264
|
const config = loadConfig();
|
|
@@ -3049,6 +3400,71 @@ checklistCmd.command("delete-item <checklistId> <checklistItemId>").description(
|
|
|
3049
3400
|
}
|
|
3050
3401
|
})
|
|
3051
3402
|
);
|
|
3403
|
+
var timeCmd = program.command("time").description("Track time on tasks");
|
|
3404
|
+
timeCmd.command("start <taskId>").description("Start tracking time on a task").option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
|
|
3405
|
+
wrapAction(async (taskId, opts) => {
|
|
3406
|
+
const config = loadConfig();
|
|
3407
|
+
const result = await startTimer(config, taskId, opts.description);
|
|
3408
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3409
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3410
|
+
} else {
|
|
3411
|
+
const taskName = result.task?.name ?? taskId;
|
|
3412
|
+
console.log(`Started timer on "${taskName}"`);
|
|
3413
|
+
}
|
|
3414
|
+
})
|
|
3415
|
+
);
|
|
3416
|
+
timeCmd.command("stop").description("Stop the running timer").option("--json", "Force JSON output even in terminal").action(
|
|
3417
|
+
wrapAction(async (opts) => {
|
|
3418
|
+
const config = loadConfig();
|
|
3419
|
+
const result = await stopTimer(config);
|
|
3420
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3421
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3422
|
+
} else {
|
|
3423
|
+
console.log(formatTimeEntry(result));
|
|
3424
|
+
}
|
|
3425
|
+
})
|
|
3426
|
+
);
|
|
3427
|
+
timeCmd.command("status").description("Show the currently running timer").option("--json", "Force JSON output even in terminal").action(
|
|
3428
|
+
wrapAction(async (opts) => {
|
|
3429
|
+
const config = loadConfig();
|
|
3430
|
+
const result = await timerStatus(config);
|
|
3431
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3432
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3433
|
+
} else if (result) {
|
|
3434
|
+
console.log(formatTimeEntry(result));
|
|
3435
|
+
} else {
|
|
3436
|
+
console.log("No timer running");
|
|
3437
|
+
}
|
|
3438
|
+
})
|
|
3439
|
+
);
|
|
3440
|
+
timeCmd.command("log <taskId> <duration>").description('Log a manual time entry (e.g. "2h", "30m", "1h30m")').option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
|
|
3441
|
+
wrapAction(
|
|
3442
|
+
async (taskId, duration, opts) => {
|
|
3443
|
+
const config = loadConfig();
|
|
3444
|
+
const result = await logTime(config, taskId, duration, opts.description);
|
|
3445
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3446
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3447
|
+
} else {
|
|
3448
|
+
console.log(`Logged ${duration} on task ${taskId}`);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
)
|
|
3452
|
+
);
|
|
3453
|
+
timeCmd.command("list").description("List recent time entries (default: last 7 days)").option("--days <n>", "Number of days to look back", "7").option("--task <taskId>", "Filter by task ID").option("--json", "Force JSON output even in terminal").action(
|
|
3454
|
+
wrapAction(async (opts) => {
|
|
3455
|
+
const config = loadConfig();
|
|
3456
|
+
const days = opts.days ? Number(opts.days) : 7;
|
|
3457
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
3458
|
+
throw new Error("--days must be a positive number");
|
|
3459
|
+
}
|
|
3460
|
+
const entries = await listTimeEntries(config, { days, taskId: opts.task });
|
|
3461
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3462
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
3463
|
+
} else {
|
|
3464
|
+
console.log(formatTimeEntries(entries));
|
|
3465
|
+
}
|
|
3466
|
+
})
|
|
3467
|
+
);
|
|
3052
3468
|
var configCmd = program.command("config").description("Manage CLI configuration");
|
|
3053
3469
|
configCmd.command("get <key>").description("Print a config value").action(
|
|
3054
3470
|
wrapAction(async (key) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clickup
|
|
3
|
-
description: 'Use when managing ClickUp tasks, sprints, or comments via the `cu` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, 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.'
|
|
3
|
+
description: 'Use when managing ClickUp tasks, sprints, or comments via the `cu` 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.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ClickUp CLI (`cu`)
|
|
@@ -73,44 +73,57 @@ All commands support `--help` for full flag details.
|
|
|
73
73
|
| `cu checklist add-item <checklistId> <name> [--json]` | Add item to a checklist |
|
|
74
74
|
| `cu checklist edit-item <checklistId> <itemId> [--name n] [--resolved] [--unresolved] [--assignee id] [--json]` | Edit a checklist item |
|
|
75
75
|
| `cu checklist delete-item <checklistId> <itemId> [--json]` | Delete a checklist item |
|
|
76
|
+
| `cu comment-delete <commentId> [--json]` | Delete a comment |
|
|
77
|
+
| `cu replies <commentId> [--json]` | List threaded replies on a comment |
|
|
78
|
+
| `cu reply <commentId> -m text [--json]` | Reply to a comment |
|
|
79
|
+
| `cu link <taskId> <linksTo> [--remove] [--json]` | Add or remove link between tasks |
|
|
80
|
+
| `cu time start <taskId> [-d desc] [--json]` | Start tracking time on a task |
|
|
81
|
+
| `cu time stop [--json]` | Stop the running timer |
|
|
82
|
+
| `cu time status [--json]` | Show currently running timer |
|
|
83
|
+
| `cu time log <taskId> <duration> [-d desc] [--json]` | Log manual time entry (e.g. "2h", "30m") |
|
|
84
|
+
| `cu time list [--days n] [--task id] [--json]` | List recent time entries |
|
|
76
85
|
| `cu config get <key>` / `cu config set <key> <value>` / `cu config path` | Manage CLI config |
|
|
77
86
|
| `cu completion <shell>` | Shell completions (bash/zsh/fish) |
|
|
78
87
|
|
|
79
88
|
## Quick Reference
|
|
80
89
|
|
|
81
|
-
| Topic
|
|
82
|
-
|
|
|
83
|
-
| Task IDs
|
|
84
|
-
| `--type`
|
|
85
|
-
| `--list` on create
|
|
86
|
-
| `--status`
|
|
87
|
-
| `--priority`
|
|
88
|
-
| `--due-date`
|
|
89
|
-
| `--assignee`
|
|
90
|
-
| `--tags`
|
|
91
|
-
| `--time-estimate`
|
|
92
|
-
| `--custom-item-id`
|
|
93
|
-
| `--on` / `--blocks`
|
|
94
|
-
| `--to` / `--remove`
|
|
95
|
-
| `cu field --set`
|
|
96
|
-
| `cu field`
|
|
97
|
-
| `cu delete`
|
|
98
|
-
| `cu tag --add/--remove`
|
|
99
|
-
| `--space`
|
|
100
|
-
| `--name`
|
|
101
|
-
| `--include-closed`
|
|
102
|
-
| `cu assign --to me`
|
|
103
|
-
| `cu search`
|
|
104
|
-
| `cu sprint`
|
|
105
|
-
| `cu summary`
|
|
106
|
-
| `cu overdue`
|
|
107
|
-
| `cu open`
|
|
108
|
-
| `cu checklist`
|
|
109
|
-
| `cu
|
|
110
|
-
| `cu
|
|
111
|
-
| `cu
|
|
112
|
-
|
|
|
113
|
-
|
|
|
90
|
+
| Topic | Detail |
|
|
91
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
92
|
+
| Task IDs | Stable alphanumeric strings (e.g. `abc123def`) |
|
|
93
|
+
| `--type` | Filter by task type: `task` (regular), or custom type name/ID (e.g. `initiative`, `Bug`) |
|
|
94
|
+
| `--list` on create | Optional when `--parent` is given (auto-detected) |
|
|
95
|
+
| `--status` | Fuzzy matching: exact > starts-with > contains. Prints match to stderr. |
|
|
96
|
+
| `--priority` | Names (`urgent`, `high`, `normal`, `low`) or numbers (1-4) |
|
|
97
|
+
| `--due-date` | `YYYY-MM-DD` format |
|
|
98
|
+
| `--assignee` | User ID or `me` (on `cu create`, `cu update`, `cu assign`) |
|
|
99
|
+
| `--tags` | Comma-separated (e.g. `--tags "bug,frontend"`) |
|
|
100
|
+
| `--time-estimate` | Duration format: `"2h"`, `"30m"`, `"1h30m"`, or raw milliseconds |
|
|
101
|
+
| `--custom-item-id` | Custom task type ID for `cu create` (e.g. `1` for initiative) |
|
|
102
|
+
| `--on` / `--blocks` | Task dependency direction (used with `cu depend`) |
|
|
103
|
+
| `--to` / `--remove` | List ID to add/remove task (used with `cu move`) |
|
|
104
|
+
| `cu field --set` | Supports: text, number, checkbox (true/false), dropdown (option name), date (YYYY-MM-DD), url, email |
|
|
105
|
+
| `cu field` | Field names resolved case-insensitively; errors list available fields/options |
|
|
106
|
+
| `cu delete` | DESTRUCTIVE. Requires `--confirm` in non-interactive mode. Cannot be undone |
|
|
107
|
+
| `cu tag --add/--remove` | Comma-separated tag names (e.g. `--add "bug,frontend"`) |
|
|
108
|
+
| `--space` | Partial name match or exact ID |
|
|
109
|
+
| `--name` | Partial match, case-insensitive |
|
|
110
|
+
| `--include-closed` | Include closed/done tasks (on `tasks`, `assigned`, `subtasks`, `sprint`, `search`, `inbox`, `overdue`) |
|
|
111
|
+
| `cu assign --to me` | Shorthand for your own user ID |
|
|
112
|
+
| `cu search` | Matches all query words against task name, case-insensitive |
|
|
113
|
+
| `cu sprint` | Auto-detects active sprint via view API and date range parsing |
|
|
114
|
+
| `cu summary` | Categories: completed (done/complete/closed within N hours), in progress, overdue |
|
|
115
|
+
| `cu overdue` | Excludes closed tasks, sorted most overdue first |
|
|
116
|
+
| `cu open` | Tries task ID first, falls back to name search |
|
|
117
|
+
| `cu checklist` | Full CRUD for task checklists: view, create, delete, add-item, edit-item, delete-item |
|
|
118
|
+
| `cu time` | Track time: start/stop timer, log entries, list history. Duration format: "2h", "30m", "1h30m" |
|
|
119
|
+
| `cu comment-edit` | Edit comment text and resolution status |
|
|
120
|
+
| `cu comment-delete` | Delete a comment |
|
|
121
|
+
| `cu replies` / `cu reply` | View and post threaded comment replies |
|
|
122
|
+
| `cu link` | Link/unlink tasks (different from dependencies) |
|
|
123
|
+
| `cu task` | Shows custom fields and checklists in detail view |
|
|
124
|
+
| `cu lists` | Discovers list IDs needed for `--list` and `cu create -l` |
|
|
125
|
+
| Errors | stderr with exit code 1 |
|
|
126
|
+
| Parsing | Strict - excess/unknown arguments rejected |
|
|
114
127
|
|
|
115
128
|
## Agent Workflow Examples
|
|
116
129
|
|
|
@@ -164,6 +177,16 @@ cu checklist create abc123def "QA Steps" # add checklist
|
|
|
164
177
|
cu checklist add-item <clId> "Run unit tests" # add item
|
|
165
178
|
cu checklist edit-item <clId> <itemId> --resolved # check off item
|
|
166
179
|
cu comment-edit <commentId> -m "Updated findings" # edit a comment
|
|
180
|
+
cu comment-delete <commentId> # delete a comment
|
|
181
|
+
cu replies <commentId> # view threaded replies
|
|
182
|
+
cu reply <commentId> -m "Agreed, fixing" # reply to a comment
|
|
183
|
+
cu link abc123 def456 # link two tasks
|
|
184
|
+
cu link abc123 def456 --remove # unlink two tasks
|
|
185
|
+
cu time start abc123def -d "Working on feature" # start timer
|
|
186
|
+
cu time status # check running timer
|
|
187
|
+
cu time stop # stop timer
|
|
188
|
+
cu time log abc123def 2h -d "Code review" # log manual entry
|
|
189
|
+
cu time list --days 7 # recent entries
|
|
167
190
|
cu delete abc123def --confirm # irreversible!
|
|
168
191
|
```
|
|
169
192
|
|