@krodak/clickup-cli 0.13.2 → 0.15.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/README.md +160 -441
- package/dist/index.js +553 -4
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +27 -2
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 ?? [];
|
|
@@ -193,6 +196,105 @@ var ClickUpClient = class {
|
|
|
193
196
|
method: "DELETE"
|
|
194
197
|
});
|
|
195
198
|
}
|
|
199
|
+
async updateComment(commentId, text, resolved) {
|
|
200
|
+
const body = { comment_text: text };
|
|
201
|
+
if (resolved !== void 0) body.resolved = resolved;
|
|
202
|
+
await this.request(`/comment/${commentId}`, {
|
|
203
|
+
method: "PUT",
|
|
204
|
+
body: JSON.stringify(body)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async getListCustomFields(listId) {
|
|
208
|
+
const data = await this.request(`/list/${listId}/field`);
|
|
209
|
+
return data.fields ?? [];
|
|
210
|
+
}
|
|
211
|
+
async createChecklist(taskId, name) {
|
|
212
|
+
const data = await this.request(`/task/${taskId}/checklist`, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: JSON.stringify({ name })
|
|
215
|
+
});
|
|
216
|
+
return data.checklist;
|
|
217
|
+
}
|
|
218
|
+
async deleteChecklist(checklistId) {
|
|
219
|
+
await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
|
|
220
|
+
}
|
|
221
|
+
async createChecklistItem(checklistId, name) {
|
|
222
|
+
const data = await this.request(
|
|
223
|
+
`/checklist/${checklistId}/checklist_item`,
|
|
224
|
+
{ method: "POST", body: JSON.stringify({ name }) }
|
|
225
|
+
);
|
|
226
|
+
return data.checklist;
|
|
227
|
+
}
|
|
228
|
+
async editChecklistItem(checklistId, checklistItemId, updates) {
|
|
229
|
+
const data = await this.request(
|
|
230
|
+
`/checklist/${checklistId}/checklist_item/${checklistItemId}`,
|
|
231
|
+
{ method: "PUT", body: JSON.stringify(updates) }
|
|
232
|
+
);
|
|
233
|
+
return data.checklist;
|
|
234
|
+
}
|
|
235
|
+
async deleteChecklistItem(checklistId, checklistItemId) {
|
|
236
|
+
await this.request(
|
|
237
|
+
`/checklist/${checklistId}/checklist_item/${checklistItemId}`,
|
|
238
|
+
{ method: "DELETE" }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
async startTimeEntry(teamId, taskId, description) {
|
|
242
|
+
const body = {
|
|
243
|
+
tid: taskId,
|
|
244
|
+
start: Date.now(),
|
|
245
|
+
duration: -1
|
|
246
|
+
};
|
|
247
|
+
if (description) body.description = description;
|
|
248
|
+
const data = await this.request(`/team/${teamId}/time_entries/start`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: JSON.stringify(body)
|
|
251
|
+
});
|
|
252
|
+
return data.data;
|
|
253
|
+
}
|
|
254
|
+
async stopTimeEntry(teamId) {
|
|
255
|
+
const data = await this.request(`/team/${teamId}/time_entries/stop`, {
|
|
256
|
+
method: "POST"
|
|
257
|
+
});
|
|
258
|
+
return data.data;
|
|
259
|
+
}
|
|
260
|
+
async getRunningTimeEntry(teamId) {
|
|
261
|
+
const data = await this.request(
|
|
262
|
+
`/team/${teamId}/time_entries/current`
|
|
263
|
+
);
|
|
264
|
+
return data.data ?? null;
|
|
265
|
+
}
|
|
266
|
+
async createTimeEntry(teamId, taskId, duration, opts) {
|
|
267
|
+
const start = opts?.start ?? Date.now() - duration;
|
|
268
|
+
const body = {
|
|
269
|
+
tid: taskId,
|
|
270
|
+
start,
|
|
271
|
+
duration
|
|
272
|
+
};
|
|
273
|
+
if (opts?.description) body.description = opts.description;
|
|
274
|
+
const data = await this.request(`/team/${teamId}/time_entries`, {
|
|
275
|
+
method: "POST",
|
|
276
|
+
body: JSON.stringify(body)
|
|
277
|
+
});
|
|
278
|
+
return data.data;
|
|
279
|
+
}
|
|
280
|
+
async getTimeEntries(teamId, opts) {
|
|
281
|
+
const params = new URLSearchParams();
|
|
282
|
+
if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
|
|
283
|
+
if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
|
|
284
|
+
const query = params.toString();
|
|
285
|
+
const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
|
|
286
|
+
const data = await this.request(url);
|
|
287
|
+
const entries = data.data ?? [];
|
|
288
|
+
if (opts?.taskId) {
|
|
289
|
+
return entries.filter((e) => e.task?.id === opts.taskId);
|
|
290
|
+
}
|
|
291
|
+
return entries;
|
|
292
|
+
}
|
|
293
|
+
async deleteTimeEntry(teamId, timeEntryId) {
|
|
294
|
+
await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
|
|
295
|
+
method: "DELETE"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
196
298
|
};
|
|
197
299
|
|
|
198
300
|
// src/config.ts
|
|
@@ -414,6 +516,17 @@ function formatTaskDetailMarkdown(task) {
|
|
|
414
516
|
if (descriptionContent) {
|
|
415
517
|
lines.push("", "## Description", "", descriptionContent);
|
|
416
518
|
}
|
|
519
|
+
if (task.checklists?.length) {
|
|
520
|
+
lines.push("", "## Checklists", "");
|
|
521
|
+
for (const cl of task.checklists) {
|
|
522
|
+
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
523
|
+
lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
|
|
524
|
+
for (const item of cl.items) {
|
|
525
|
+
lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
|
|
526
|
+
}
|
|
527
|
+
lines.push("");
|
|
528
|
+
}
|
|
529
|
+
}
|
|
417
530
|
return lines.join("\n");
|
|
418
531
|
}
|
|
419
532
|
function formatUpdateConfirmation(id, name) {
|
|
@@ -548,6 +661,18 @@ function formatTaskDetail(task) {
|
|
|
548
661
|
}
|
|
549
662
|
}
|
|
550
663
|
}
|
|
664
|
+
if (task.checklists?.length) {
|
|
665
|
+
lines.push("");
|
|
666
|
+
lines.push(chalk2.bold("Checklists"));
|
|
667
|
+
for (const cl of task.checklists) {
|
|
668
|
+
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
669
|
+
lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
|
|
670
|
+
for (const item of cl.items) {
|
|
671
|
+
const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
|
|
672
|
+
lines.push(` ${check} ${item.name}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
551
676
|
if (task.text_content?.trim()) {
|
|
552
677
|
lines.push("");
|
|
553
678
|
lines.push(descriptionPreview(task.text_content));
|
|
@@ -800,9 +925,8 @@ function hasUpdateFields(options) {
|
|
|
800
925
|
}
|
|
801
926
|
async function resolveStatus(client, taskId, statusInput) {
|
|
802
927
|
const task = await client.getTask(taskId);
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
const available = space.statuses.map((s) => s.status);
|
|
928
|
+
const list = await client.getListWithStatuses(task.list.id);
|
|
929
|
+
const available = list.statuses.map((s) => s.status);
|
|
806
930
|
const matched = matchStatus(statusInput, available);
|
|
807
931
|
if (!matched) {
|
|
808
932
|
throw new Error(`No matching status for "${statusInput}". Available: ${available.join(", ")}`);
|
|
@@ -1684,7 +1808,7 @@ function bashCompletion() {
|
|
|
1684
1808
|
cword=$COMP_CWORD
|
|
1685
1809
|
fi
|
|
1686
1810
|
|
|
1687
|
-
local commands="init auth tasks task update create sprint sprints subtasks comment comments activity lists spaces inbox assigned open search summary overdue assign depend move field delete tag config completion"
|
|
1811
|
+
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 time config completion"
|
|
1688
1812
|
|
|
1689
1813
|
if [[ $cword -eq 1 ]]; then
|
|
1690
1814
|
COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
|
|
@@ -1780,6 +1904,19 @@ function bashCompletion() {
|
|
|
1780
1904
|
tag)
|
|
1781
1905
|
COMPREPLY=($(compgen -W "--add --remove --json" -- "$cur"))
|
|
1782
1906
|
;;
|
|
1907
|
+
checklist)
|
|
1908
|
+
if [[ $cword -eq 2 ]]; then
|
|
1909
|
+
COMPREPLY=($(compgen -W "view create delete add-item edit-item delete-item" -- "$cur"))
|
|
1910
|
+
fi
|
|
1911
|
+
;;
|
|
1912
|
+
time)
|
|
1913
|
+
if [[ $cword -eq 2 ]]; then
|
|
1914
|
+
COMPREPLY=($(compgen -W "start stop status log list" -- "$cur"))
|
|
1915
|
+
fi
|
|
1916
|
+
;;
|
|
1917
|
+
comment-edit)
|
|
1918
|
+
COMPREPLY=($(compgen -W "-m --message --resolved --unresolved --json" -- "$cur"))
|
|
1919
|
+
;;
|
|
1783
1920
|
config)
|
|
1784
1921
|
if [[ $cword -eq 2 ]]; then
|
|
1785
1922
|
COMPREPLY=($(compgen -W "get set path" -- "$cur"))
|
|
@@ -1832,6 +1969,9 @@ _cu() {
|
|
|
1832
1969
|
'field:Set or remove a custom field value on a task'
|
|
1833
1970
|
'delete:Delete a task'
|
|
1834
1971
|
'tag:Add or remove tags from a task'
|
|
1972
|
+
'checklist:Manage checklists on a task'
|
|
1973
|
+
'time:Track time on tasks'
|
|
1974
|
+
'comment-edit:Edit an existing comment'
|
|
1835
1975
|
'config:Manage CLI configuration'
|
|
1836
1976
|
'completion:Output shell completion script'
|
|
1837
1977
|
)
|
|
@@ -2019,6 +2159,109 @@ _cu() {
|
|
|
2019
2159
|
'--remove[Comma-separated tag names to remove]:tags:' \\
|
|
2020
2160
|
'--json[Force JSON output]'
|
|
2021
2161
|
;;
|
|
2162
|
+
checklist)
|
|
2163
|
+
local -a checklist_cmds
|
|
2164
|
+
checklist_cmds=(
|
|
2165
|
+
'view:View checklists on a task'
|
|
2166
|
+
'create:Create a checklist on a task'
|
|
2167
|
+
'delete:Delete a checklist'
|
|
2168
|
+
'add-item:Add an item to a checklist'
|
|
2169
|
+
'edit-item:Edit a checklist item'
|
|
2170
|
+
'delete-item:Delete a checklist item'
|
|
2171
|
+
)
|
|
2172
|
+
_arguments -C \\
|
|
2173
|
+
'1:checklist command:->checklist_cmd' \\
|
|
2174
|
+
'*::checklist_arg:->checklist_args'
|
|
2175
|
+
case $state in
|
|
2176
|
+
checklist_cmd)
|
|
2177
|
+
_describe 'checklist command' checklist_cmds
|
|
2178
|
+
;;
|
|
2179
|
+
checklist_args)
|
|
2180
|
+
case $words[1] in
|
|
2181
|
+
view)
|
|
2182
|
+
_arguments '1:task_id:' '--json[Force JSON output]'
|
|
2183
|
+
;;
|
|
2184
|
+
create)
|
|
2185
|
+
_arguments '1:task_id:' '2:name:' '--json[Force JSON output]'
|
|
2186
|
+
;;
|
|
2187
|
+
delete)
|
|
2188
|
+
_arguments '1:checklist_id:' '--json[Force JSON output]'
|
|
2189
|
+
;;
|
|
2190
|
+
add-item)
|
|
2191
|
+
_arguments '1:checklist_id:' '2:name:' '--json[Force JSON output]'
|
|
2192
|
+
;;
|
|
2193
|
+
edit-item)
|
|
2194
|
+
_arguments \\
|
|
2195
|
+
'1:checklist_id:' \\
|
|
2196
|
+
'2:checklist_item_id:' \\
|
|
2197
|
+
'--name[New item name]:name:' \\
|
|
2198
|
+
'--resolved[Mark item as resolved]' \\
|
|
2199
|
+
'--unresolved[Mark item as unresolved]' \\
|
|
2200
|
+
'--assignee[Assign user by ID]:user_id:' \\
|
|
2201
|
+
'--json[Force JSON output]'
|
|
2202
|
+
;;
|
|
2203
|
+
delete-item)
|
|
2204
|
+
_arguments '1:checklist_id:' '2:checklist_item_id:' '--json[Force JSON output]'
|
|
2205
|
+
;;
|
|
2206
|
+
esac
|
|
2207
|
+
;;
|
|
2208
|
+
esac
|
|
2209
|
+
;;
|
|
2210
|
+
time)
|
|
2211
|
+
local -a time_cmds
|
|
2212
|
+
time_cmds=(
|
|
2213
|
+
'start:Start tracking time on a task'
|
|
2214
|
+
'stop:Stop the running timer'
|
|
2215
|
+
'status:Show the currently running timer'
|
|
2216
|
+
'log:Log a manual time entry'
|
|
2217
|
+
'list:List recent time entries'
|
|
2218
|
+
)
|
|
2219
|
+
_arguments -C \\
|
|
2220
|
+
'1:time command:->time_cmd' \\
|
|
2221
|
+
'*::time_arg:->time_args'
|
|
2222
|
+
case $state in
|
|
2223
|
+
time_cmd)
|
|
2224
|
+
_describe 'time command' time_cmds
|
|
2225
|
+
;;
|
|
2226
|
+
time_args)
|
|
2227
|
+
case $words[1] in
|
|
2228
|
+
start)
|
|
2229
|
+
_arguments \\
|
|
2230
|
+
'1:task_id:' \\
|
|
2231
|
+
'(-d --description)'{-d,--description}'[Description]:text:' \\
|
|
2232
|
+
'--json[Force JSON output]'
|
|
2233
|
+
;;
|
|
2234
|
+
stop)
|
|
2235
|
+
_arguments '--json[Force JSON output]'
|
|
2236
|
+
;;
|
|
2237
|
+
status)
|
|
2238
|
+
_arguments '--json[Force JSON output]'
|
|
2239
|
+
;;
|
|
2240
|
+
log)
|
|
2241
|
+
_arguments \\
|
|
2242
|
+
'1:task_id:' \\
|
|
2243
|
+
'2:duration:' \\
|
|
2244
|
+
'(-d --description)'{-d,--description}'[Description]:text:' \\
|
|
2245
|
+
'--json[Force JSON output]'
|
|
2246
|
+
;;
|
|
2247
|
+
list)
|
|
2248
|
+
_arguments \\
|
|
2249
|
+
'--days[Number of days to look back]:days:' \\
|
|
2250
|
+
'--task[Filter by task ID]:task_id:' \\
|
|
2251
|
+
'--json[Force JSON output]'
|
|
2252
|
+
;;
|
|
2253
|
+
esac
|
|
2254
|
+
;;
|
|
2255
|
+
esac
|
|
2256
|
+
;;
|
|
2257
|
+
comment-edit)
|
|
2258
|
+
_arguments \\
|
|
2259
|
+
'1:comment_id:' \\
|
|
2260
|
+
'(-m --message)'{-m,--message}'[New comment text]:text:' \\
|
|
2261
|
+
'--resolved[Mark comment as resolved]' \\
|
|
2262
|
+
'--unresolved[Mark comment as unresolved]' \\
|
|
2263
|
+
'--json[Force JSON output]'
|
|
2264
|
+
;;
|
|
2022
2265
|
config)
|
|
2023
2266
|
local -a config_cmds
|
|
2024
2267
|
config_cmds=(
|
|
@@ -2085,6 +2328,9 @@ complete -c cu -n __fish_use_subcommand -a move -d 'Add or remove a task from a
|
|
|
2085
2328
|
complete -c cu -n __fish_use_subcommand -a field -d 'Set or remove a custom field value on a task'
|
|
2086
2329
|
complete -c cu -n __fish_use_subcommand -a delete -d 'Delete a task'
|
|
2087
2330
|
complete -c cu -n __fish_use_subcommand -a tag -d 'Add or remove tags from a task'
|
|
2331
|
+
complete -c cu -n __fish_use_subcommand -a checklist -d 'Manage checklists on a task'
|
|
2332
|
+
complete -c cu -n __fish_use_subcommand -a time -d 'Track time on tasks'
|
|
2333
|
+
complete -c cu -n __fish_use_subcommand -a comment-edit -d 'Edit an existing comment'
|
|
2088
2334
|
complete -c cu -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
|
|
2089
2335
|
complete -c cu -n __fish_use_subcommand -a completion -d 'Output shell completion script'
|
|
2090
2336
|
|
|
@@ -2194,6 +2440,34 @@ complete -c cu -n '__fish_seen_subcommand_from tag' -l add -d 'Comma-separated t
|
|
|
2194
2440
|
complete -c cu -n '__fish_seen_subcommand_from tag' -l remove -d 'Comma-separated tag names to remove'
|
|
2195
2441
|
complete -c cu -n '__fish_seen_subcommand_from tag' -l json -d 'Force JSON output'
|
|
2196
2442
|
|
|
2443
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a view -d 'View checklists on a task'
|
|
2444
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a create -d 'Create a checklist on a task'
|
|
2445
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a delete -d 'Delete a checklist'
|
|
2446
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a add-item -d 'Add an item to a checklist'
|
|
2447
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a edit-item -d 'Edit a checklist item'
|
|
2448
|
+
complete -c cu -n '__fish_seen_subcommand_from checklist; and not __fish_seen_subcommand_from view create delete add-item edit-item delete-item' -a delete-item -d 'Delete a checklist item'
|
|
2449
|
+
complete -c cu -n '__fish_seen_subcommand_from view create delete add-item edit-item delete-item' -l json -d 'Force JSON output'
|
|
2450
|
+
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l name -d 'New item name'
|
|
2451
|
+
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l resolved -d 'Mark item as resolved'
|
|
2452
|
+
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l unresolved -d 'Mark item as unresolved'
|
|
2453
|
+
complete -c cu -n '__fish_seen_subcommand_from edit-item' -l assignee -d 'Assign user by ID'
|
|
2454
|
+
|
|
2455
|
+
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'
|
|
2456
|
+
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'
|
|
2457
|
+
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'
|
|
2458
|
+
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'
|
|
2459
|
+
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'
|
|
2460
|
+
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'
|
|
2461
|
+
complete -c cu -n '__fish_seen_subcommand_from start; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
|
|
2462
|
+
complete -c cu -n '__fish_seen_subcommand_from log; and __fish_seen_subcommand_from time' -s d -l description -d 'Description'
|
|
2463
|
+
complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l days -d 'Number of days to look back'
|
|
2464
|
+
complete -c cu -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from time' -l task -d 'Filter by task ID'
|
|
2465
|
+
|
|
2466
|
+
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -s m -l message -d 'New comment text'
|
|
2467
|
+
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l resolved -d 'Mark comment as resolved'
|
|
2468
|
+
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
|
|
2469
|
+
complete -c cu -n '__fish_seen_subcommand_from comment-edit' -l json -d 'Force JSON output'
|
|
2470
|
+
|
|
2197
2471
|
complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
|
|
2198
2472
|
complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
|
|
2199
2473
|
complete -c cu -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
|
|
@@ -2448,6 +2722,124 @@ async function manageTags(config, taskId, opts) {
|
|
|
2448
2722
|
return { taskId, added, removed };
|
|
2449
2723
|
}
|
|
2450
2724
|
|
|
2725
|
+
// src/commands/checklist.ts
|
|
2726
|
+
import chalk5 from "chalk";
|
|
2727
|
+
async function viewChecklists(config, taskId) {
|
|
2728
|
+
const client = new ClickUpClient(config);
|
|
2729
|
+
const task = await client.getTask(taskId);
|
|
2730
|
+
return task.checklists ?? [];
|
|
2731
|
+
}
|
|
2732
|
+
async function createChecklist(config, taskId, name) {
|
|
2733
|
+
const client = new ClickUpClient(config);
|
|
2734
|
+
return client.createChecklist(taskId, name);
|
|
2735
|
+
}
|
|
2736
|
+
async function deleteChecklist(config, checklistId) {
|
|
2737
|
+
const client = new ClickUpClient(config);
|
|
2738
|
+
await client.deleteChecklist(checklistId);
|
|
2739
|
+
return { checklistId };
|
|
2740
|
+
}
|
|
2741
|
+
async function addChecklistItem(config, checklistId, name) {
|
|
2742
|
+
const client = new ClickUpClient(config);
|
|
2743
|
+
return client.createChecklistItem(checklistId, name);
|
|
2744
|
+
}
|
|
2745
|
+
async function editChecklistItem(config, checklistId, checklistItemId, updates) {
|
|
2746
|
+
const client = new ClickUpClient(config);
|
|
2747
|
+
return client.editChecklistItem(checklistId, checklistItemId, updates);
|
|
2748
|
+
}
|
|
2749
|
+
async function deleteChecklistItem(config, checklistId, checklistItemId) {
|
|
2750
|
+
const client = new ClickUpClient(config);
|
|
2751
|
+
await client.deleteChecklistItem(checklistId, checklistItemId);
|
|
2752
|
+
return { checklistId, checklistItemId };
|
|
2753
|
+
}
|
|
2754
|
+
function formatChecklists(checklists) {
|
|
2755
|
+
if (checklists.length === 0) return "No checklists";
|
|
2756
|
+
const lines = [];
|
|
2757
|
+
for (const cl of checklists) {
|
|
2758
|
+
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
2759
|
+
lines.push(chalk5.bold(`${cl.name} (${resolved}/${cl.items.length})`));
|
|
2760
|
+
lines.push(chalk5.dim(` ID: ${cl.id}`));
|
|
2761
|
+
for (const item of cl.items) {
|
|
2762
|
+
const check = item.resolved ? chalk5.green("[x]") : chalk5.dim("[ ]");
|
|
2763
|
+
const assignee = item.assignee ? chalk5.dim(` @${item.assignee.username}`) : "";
|
|
2764
|
+
lines.push(` ${check} ${item.name}${assignee}`);
|
|
2765
|
+
lines.push(chalk5.dim(` item-id: ${item.id}`));
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
return lines.join("\n");
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
// src/commands/comment-edit.ts
|
|
2772
|
+
async function editComment(config, commentId, text, resolved) {
|
|
2773
|
+
if (!text.trim()) throw new Error("Comment text cannot be empty");
|
|
2774
|
+
const client = new ClickUpClient(config);
|
|
2775
|
+
await client.updateComment(commentId, text, resolved);
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/commands/time.ts
|
|
2779
|
+
import chalk6 from "chalk";
|
|
2780
|
+
function formatDuration2(ms) {
|
|
2781
|
+
const totalMinutes = Math.round(Math.abs(ms) / 6e4);
|
|
2782
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
2783
|
+
const minutes = totalMinutes % 60;
|
|
2784
|
+
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
|
|
2785
|
+
if (hours > 0) return `${hours}h`;
|
|
2786
|
+
return `${minutes}m`;
|
|
2787
|
+
}
|
|
2788
|
+
function formatTimestamp2(ms) {
|
|
2789
|
+
return new Date(Number(ms)).toLocaleString("en-US", {
|
|
2790
|
+
month: "short",
|
|
2791
|
+
day: "numeric",
|
|
2792
|
+
hour: "numeric",
|
|
2793
|
+
minute: "2-digit"
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
async function startTimer(config, taskId, description) {
|
|
2797
|
+
const client = new ClickUpClient(config);
|
|
2798
|
+
return client.startTimeEntry(config.teamId, taskId, description);
|
|
2799
|
+
}
|
|
2800
|
+
async function stopTimer(config) {
|
|
2801
|
+
const client = new ClickUpClient(config);
|
|
2802
|
+
return client.stopTimeEntry(config.teamId);
|
|
2803
|
+
}
|
|
2804
|
+
async function timerStatus(config) {
|
|
2805
|
+
const client = new ClickUpClient(config);
|
|
2806
|
+
return client.getRunningTimeEntry(config.teamId);
|
|
2807
|
+
}
|
|
2808
|
+
async function logTime(config, taskId, durationStr, description) {
|
|
2809
|
+
const client = new ClickUpClient(config);
|
|
2810
|
+
const duration = parseTimeEstimate(durationStr);
|
|
2811
|
+
return client.createTimeEntry(config.teamId, taskId, duration, { description });
|
|
2812
|
+
}
|
|
2813
|
+
async function listTimeEntries(config, opts) {
|
|
2814
|
+
const client = new ClickUpClient(config);
|
|
2815
|
+
const days = opts?.days ?? 7;
|
|
2816
|
+
const endDate = Date.now();
|
|
2817
|
+
const startDate = endDate - days * 24 * 60 * 60 * 1e3;
|
|
2818
|
+
return client.getTimeEntries(config.teamId, {
|
|
2819
|
+
startDate,
|
|
2820
|
+
endDate,
|
|
2821
|
+
taskId: opts?.taskId
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
function formatTimeEntry(entry) {
|
|
2825
|
+
const lines = [];
|
|
2826
|
+
const taskName = entry.task?.name ?? "No task";
|
|
2827
|
+
const taskId = entry.task?.id ?? "";
|
|
2828
|
+
const isRunning = entry.duration < 0;
|
|
2829
|
+
const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
|
|
2830
|
+
const durationStr = formatDuration2(elapsed);
|
|
2831
|
+
const status = isRunning ? chalk6.green("RUNNING") : "";
|
|
2832
|
+
lines.push(`${chalk6.bold(taskName)} ${chalk6.dim(taskId)} ${status}`);
|
|
2833
|
+
lines.push(
|
|
2834
|
+
` ${durationStr} - ${formatTimestamp2(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
|
|
2835
|
+
);
|
|
2836
|
+
return lines.join("\n");
|
|
2837
|
+
}
|
|
2838
|
+
function formatTimeEntries(entries) {
|
|
2839
|
+
if (entries.length === 0) return "No time entries";
|
|
2840
|
+
return entries.map(formatTimeEntry).join("\n");
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2451
2843
|
// src/index.ts
|
|
2452
2844
|
var require2 = createRequire(import.meta.url);
|
|
2453
2845
|
var { version } = require2("../package.json");
|
|
@@ -2589,6 +2981,22 @@ program.command("comments <taskId>").description("List comments on a task").opti
|
|
|
2589
2981
|
printComments(comments, opts.json ?? false);
|
|
2590
2982
|
})
|
|
2591
2983
|
);
|
|
2984
|
+
program.command("comment-edit <commentId>").description("Edit an existing comment").requiredOption("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option("--json", "Force JSON output even in terminal").action(
|
|
2985
|
+
wrapAction(
|
|
2986
|
+
async (commentId, opts) => {
|
|
2987
|
+
const config = loadConfig();
|
|
2988
|
+
let resolved;
|
|
2989
|
+
if (opts.resolved) resolved = true;
|
|
2990
|
+
if (opts.unresolved) resolved = false;
|
|
2991
|
+
await editComment(config, commentId, opts.message, resolved);
|
|
2992
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
2993
|
+
console.log(JSON.stringify({ success: true, commentId }, null, 2));
|
|
2994
|
+
} else {
|
|
2995
|
+
console.log(`Comment ${commentId} updated`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
)
|
|
2999
|
+
);
|
|
2592
3000
|
program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
|
|
2593
3001
|
wrapAction(async (taskId, opts) => {
|
|
2594
3002
|
const config = loadConfig();
|
|
@@ -2754,6 +3162,147 @@ program.command("tag <taskId>").description("Add or remove tags from a task").op
|
|
|
2754
3162
|
}
|
|
2755
3163
|
})
|
|
2756
3164
|
);
|
|
3165
|
+
var checklistCmd = program.command("checklist").description("Manage checklists on a task");
|
|
3166
|
+
checklistCmd.command("view <taskId>").description("View checklists on a task").option("--json", "Force JSON output even in terminal").action(
|
|
3167
|
+
wrapAction(async (taskId, opts) => {
|
|
3168
|
+
const config = loadConfig();
|
|
3169
|
+
const checklists = await viewChecklists(config, taskId);
|
|
3170
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3171
|
+
console.log(JSON.stringify(checklists, null, 2));
|
|
3172
|
+
} else {
|
|
3173
|
+
console.log(formatChecklists(checklists));
|
|
3174
|
+
}
|
|
3175
|
+
})
|
|
3176
|
+
);
|
|
3177
|
+
checklistCmd.command("create <taskId> <name>").description("Create a checklist on a task").option("--json", "Force JSON output even in terminal").action(
|
|
3178
|
+
wrapAction(async (taskId, name, opts) => {
|
|
3179
|
+
const config = loadConfig();
|
|
3180
|
+
const result = await createChecklist(config, taskId, name);
|
|
3181
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3182
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3183
|
+
} else {
|
|
3184
|
+
console.log(`Created checklist "${result.name}" (id: ${result.id})`);
|
|
3185
|
+
}
|
|
3186
|
+
})
|
|
3187
|
+
);
|
|
3188
|
+
checklistCmd.command("delete <checklistId>").description("Delete a checklist").option("--json", "Force JSON output even in terminal").action(
|
|
3189
|
+
wrapAction(async (checklistId, opts) => {
|
|
3190
|
+
const config = loadConfig();
|
|
3191
|
+
const result = await deleteChecklist(config, checklistId);
|
|
3192
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3193
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3194
|
+
} else {
|
|
3195
|
+
console.log(`Deleted checklist ${result.checklistId}`);
|
|
3196
|
+
}
|
|
3197
|
+
})
|
|
3198
|
+
);
|
|
3199
|
+
checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
|
|
3200
|
+
wrapAction(async (checklistId, name, opts) => {
|
|
3201
|
+
const config = loadConfig();
|
|
3202
|
+
const result = await addChecklistItem(config, checklistId, name);
|
|
3203
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3205
|
+
} else {
|
|
3206
|
+
console.log(`Added item "${name}" to checklist ${checklistId}`);
|
|
3207
|
+
}
|
|
3208
|
+
})
|
|
3209
|
+
);
|
|
3210
|
+
checklistCmd.command("edit-item <checklistId> <checklistItemId>").description("Edit a checklist item").option("--name <name>", "New item name").option("--resolved", "Mark item as resolved").option("--unresolved", "Mark item as unresolved").option("--assignee <userId>", 'Assign user by ID (use "null" to unassign)').option("--json", "Force JSON output even in terminal").action(
|
|
3211
|
+
wrapAction(
|
|
3212
|
+
async (checklistId, checklistItemId, opts) => {
|
|
3213
|
+
const config = loadConfig();
|
|
3214
|
+
const updates = {};
|
|
3215
|
+
if (opts.name) updates.name = opts.name;
|
|
3216
|
+
if (opts.resolved) updates.resolved = true;
|
|
3217
|
+
if (opts.unresolved) updates.resolved = false;
|
|
3218
|
+
if (opts.assignee !== void 0) {
|
|
3219
|
+
updates.assignee = opts.assignee === "null" ? null : Number(opts.assignee);
|
|
3220
|
+
}
|
|
3221
|
+
const result = await editChecklistItem(config, checklistId, checklistItemId, updates);
|
|
3222
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3223
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3224
|
+
} else {
|
|
3225
|
+
console.log(`Updated checklist item ${checklistItemId}`);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
)
|
|
3229
|
+
);
|
|
3230
|
+
checklistCmd.command("delete-item <checklistId> <checklistItemId>").description("Delete a checklist item").option("--json", "Force JSON output even in terminal").action(
|
|
3231
|
+
wrapAction(async (checklistId, checklistItemId, opts) => {
|
|
3232
|
+
const config = loadConfig();
|
|
3233
|
+
const result = await deleteChecklistItem(config, checklistId, checklistItemId);
|
|
3234
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3235
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3236
|
+
} else {
|
|
3237
|
+
console.log(`Deleted checklist item ${result.checklistItemId}`);
|
|
3238
|
+
}
|
|
3239
|
+
})
|
|
3240
|
+
);
|
|
3241
|
+
var timeCmd = program.command("time").description("Track time on tasks");
|
|
3242
|
+
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(
|
|
3243
|
+
wrapAction(async (taskId, opts) => {
|
|
3244
|
+
const config = loadConfig();
|
|
3245
|
+
const result = await startTimer(config, taskId, opts.description);
|
|
3246
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3247
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3248
|
+
} else {
|
|
3249
|
+
const taskName = result.task?.name ?? taskId;
|
|
3250
|
+
console.log(`Started timer on "${taskName}"`);
|
|
3251
|
+
}
|
|
3252
|
+
})
|
|
3253
|
+
);
|
|
3254
|
+
timeCmd.command("stop").description("Stop the running timer").option("--json", "Force JSON output even in terminal").action(
|
|
3255
|
+
wrapAction(async (opts) => {
|
|
3256
|
+
const config = loadConfig();
|
|
3257
|
+
const result = await stopTimer(config);
|
|
3258
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3259
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3260
|
+
} else {
|
|
3261
|
+
console.log(formatTimeEntry(result));
|
|
3262
|
+
}
|
|
3263
|
+
})
|
|
3264
|
+
);
|
|
3265
|
+
timeCmd.command("status").description("Show the currently running timer").option("--json", "Force JSON output even in terminal").action(
|
|
3266
|
+
wrapAction(async (opts) => {
|
|
3267
|
+
const config = loadConfig();
|
|
3268
|
+
const result = await timerStatus(config);
|
|
3269
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3270
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3271
|
+
} else if (result) {
|
|
3272
|
+
console.log(formatTimeEntry(result));
|
|
3273
|
+
} else {
|
|
3274
|
+
console.log("No timer running");
|
|
3275
|
+
}
|
|
3276
|
+
})
|
|
3277
|
+
);
|
|
3278
|
+
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(
|
|
3279
|
+
wrapAction(
|
|
3280
|
+
async (taskId, duration, opts) => {
|
|
3281
|
+
const config = loadConfig();
|
|
3282
|
+
const result = await logTime(config, taskId, duration, opts.description);
|
|
3283
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3284
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3285
|
+
} else {
|
|
3286
|
+
console.log(`Logged ${duration} on task ${taskId}`);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
)
|
|
3290
|
+
);
|
|
3291
|
+
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(
|
|
3292
|
+
wrapAction(async (opts) => {
|
|
3293
|
+
const config = loadConfig();
|
|
3294
|
+
const days = opts.days ? Number(opts.days) : 7;
|
|
3295
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
3296
|
+
throw new Error("--days must be a positive number");
|
|
3297
|
+
}
|
|
3298
|
+
const entries = await listTimeEntries(config, { days, taskId: opts.task });
|
|
3299
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3300
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
3301
|
+
} else {
|
|
3302
|
+
console.log(formatTimeEntries(entries));
|
|
3303
|
+
}
|
|
3304
|
+
})
|
|
3305
|
+
);
|
|
2757
3306
|
var configCmd = program.command("config").description("Manage CLI configuration");
|
|
2758
3307
|
configCmd.command("get <key>").description("Print a config value").action(
|
|
2759
3308
|
wrapAction(async (key) => {
|