@krodak/clickup-cli 0.6.0 → 0.7.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 +97 -109
- package/dist/index.js +208 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,127 +1,97 @@
|
|
|
1
1
|
# cu - ClickUp CLI
|
|
2
2
|
|
|
3
|
-
A ClickUp CLI for AI agents
|
|
3
|
+
> A ClickUp CLI built for AI agents that also works well for humans. Outputs Markdown when piped (optimized for AI context windows), interactive tables when run in a terminal.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
npm install -g @krodak/clickup-cli
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Getting started
|
|
5
|
+
[](https://www.npmjs.com/package/@krodak/clickup-cli)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://github.com/krodak/clickup-cli/actions/workflows/ci.yml)
|
|
17
9
|
|
|
18
10
|
```bash
|
|
19
|
-
|
|
11
|
+
npm install -g @krodak/clickup-cli # or: brew tap krodak/tap && brew install clickup-cli
|
|
12
|
+
cu init # walks you through API token + workspace setup
|
|
20
13
|
```
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Operating Modes
|
|
15
|
+
You need a ClickUp personal API token (`pk_...` from https://app.clickup.com/settings/apps).
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
## Using with AI agents
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
This is the primary use case. Install the tool, install the skill file, and your agent knows how to work with ClickUp.
|
|
29
20
|
|
|
30
|
-
1.
|
|
31
|
-
2. An interactive checkbox picker (powered by @inquirer/prompts) - navigate with arrow keys, toggle selection with space, confirm with enter.
|
|
32
|
-
3. For selected tasks, a rich detail view showing: name (bold/underlined), ID, status, type, list, assignees, priority, dates, estimate, tracked time, tags, parent, URL, and a description preview (first 3 lines).
|
|
33
|
-
4. A prompt to open the selected tasks in the browser.
|
|
21
|
+
### 1. Install the skill
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
The repo includes a skill file at `skill/SKILL.md` that teaches agents all available commands and when to use them.
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
When output is piped (no TTY), all list commands output JSON arrays to stdout. No interactive UI is shown.
|
|
40
|
-
|
|
41
|
-
- `cu task <id> --json` returns the full JSON response for a single task.
|
|
42
|
-
- All list commands output JSON arrays.
|
|
43
|
-
- Errors go to stderr with exit code 1.
|
|
44
|
-
- Write commands (`update`, `create`, `comment`, `assign`) always output JSON regardless of mode.
|
|
45
|
-
- Set `NO_COLOR` to disable color output.
|
|
46
|
-
|
|
47
|
-
## For AI agents
|
|
48
|
-
|
|
49
|
-
Always use the `--json` flag or pipe output to ensure you get JSON. Parse with `jq` or programmatically.
|
|
25
|
+
**Claude Code:**
|
|
50
26
|
|
|
51
27
|
```bash
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Get full task details as JSON
|
|
56
|
-
cu task abc123 --json | jq '{id, name, status}'
|
|
28
|
+
mkdir -p ~/.claude/skills/clickup
|
|
29
|
+
cp skill/SKILL.md ~/.claude/skills/clickup/SKILL.md
|
|
30
|
+
```
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
cu sprint --json | jq '.[] | select(.status != "done")'
|
|
32
|
+
Then reference it in your `CLAUDE.md` or project instructions.
|
|
60
33
|
|
|
61
|
-
|
|
62
|
-
INITIATIVE_ID=$(cu initiatives --json | jq -r '.[0].id')
|
|
63
|
-
cu create -n "New subtask" -p "$INITIATIVE_ID"
|
|
34
|
+
**OpenCode:**
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
|
|
36
|
+
```bash
|
|
37
|
+
mkdir -p ~/.config/opencode/skills/clickup
|
|
38
|
+
cp skill/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
|
|
67
39
|
```
|
|
68
40
|
|
|
69
|
-
|
|
41
|
+
**Codex / other agents:**
|
|
70
42
|
|
|
71
|
-
|
|
43
|
+
Copy the contents of `skill/SKILL.md` into your system prompt or project instructions. It's a standalone markdown document.
|
|
72
44
|
|
|
73
|
-
|
|
45
|
+
### 2. Talk to your agent
|
|
74
46
|
|
|
75
|
-
|
|
47
|
+
Once the skill is installed, you just tell the agent what you need in plain language. It figures out which `cu` commands to run.
|
|
76
48
|
|
|
77
|
-
```bash
|
|
78
|
-
mkdir -p ~/.config/opencode/skills/clickup
|
|
79
|
-
cp skill/SKILL.md ~/.config/opencode/skills/clickup/SKILL.md
|
|
80
49
|
```
|
|
50
|
+
"Read the description of task <id>, do the work, then mark it in review and leave a comment with the commit hash."
|
|
81
51
|
|
|
82
|
-
|
|
52
|
+
"Check all subtasks under initiative <id> and improve their descriptions based on what's in the codebase."
|
|
83
53
|
|
|
84
|
-
|
|
85
|
-
mkdir -p ~/.claude/skills/clickup
|
|
86
|
-
cp skill/SKILL.md ~/.claude/skills/clickup/SKILL.md
|
|
87
|
-
```
|
|
54
|
+
"What's my standup summary? What did I finish yesterday, what's in progress, what's overdue?"
|
|
88
55
|
|
|
89
|
-
|
|
56
|
+
"Do exploratory work for task <id>, update the description with your findings, and flag blockers in a comment."
|
|
90
57
|
|
|
91
|
-
|
|
58
|
+
"Create a subtask under <id> for the edge case we just found."
|
|
92
59
|
|
|
93
|
-
|
|
60
|
+
"Check my sprint and tell me what's overdue."
|
|
61
|
+
```
|
|
94
62
|
|
|
95
|
-
|
|
63
|
+
You don't need to learn the CLI commands yourself. The agent handles it.
|
|
96
64
|
|
|
97
|
-
|
|
65
|
+
### Why a CLI and not MCP?
|
|
98
66
|
|
|
99
|
-
|
|
67
|
+
A CLI + skill file has fewer moving parts. No extra server process, no protocol layer. The agent already knows how to run shell commands - the skill file just teaches it which ones exist. This matches what Peter Steinberger and the OpenClaw project have found: for tool-use with coding agents, CLI + instructions tends to work better than MCP in practice.
|
|
100
68
|
|
|
101
|
-
###
|
|
69
|
+
### Scoped output
|
|
102
70
|
|
|
103
|
-
|
|
71
|
+
Most commands return only your tasks by default. `cu tasks`, `cu sprint`, `cu overdue`, `cu summary` all scope to what's assigned to you. `cu spaces --my` filters to spaces where you have tasks. This keeps output small and relevant - important when it's going into an agent's context window.
|
|
104
72
|
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
"apiToken": "pk_...",
|
|
108
|
-
"teamId": "12345678"
|
|
109
|
-
}
|
|
110
|
-
```
|
|
73
|
+
## Using from the terminal
|
|
111
74
|
|
|
112
|
-
|
|
75
|
+
When you run `cu` in a terminal directly, you get an interactive mode with tables and a task picker.
|
|
113
76
|
|
|
114
|
-
|
|
77
|
+
```bash
|
|
78
|
+
cu tasks # interactive table with checkbox picker
|
|
79
|
+
cu sprint # your sprint tasks, auto-detected
|
|
80
|
+
cu summary # standup helper: completed / in progress / overdue
|
|
81
|
+
cu overdue # tasks past their due date
|
|
82
|
+
cu open "login bug" # fuzzy search, opens in browser
|
|
83
|
+
cu update abc123 -s "done" # update status
|
|
84
|
+
cu assign abc123 --to me # assign yourself
|
|
85
|
+
```
|
|
115
86
|
|
|
116
|
-
|
|
117
|
-
| -------------- | ---------------------------------- |
|
|
118
|
-
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
119
|
-
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
87
|
+
Pass `--json` to any read command to force JSON output instead of the default format.
|
|
120
88
|
|
|
121
|
-
When
|
|
89
|
+
When output is piped (no TTY), all commands output **Markdown** by default - optimized for AI agent context windows. Pass `--json` to any command for JSON output. Set `CU_OUTPUT=json` environment variable to always get JSON when piped.
|
|
122
90
|
|
|
123
91
|
## Commands
|
|
124
92
|
|
|
93
|
+
20 commands total. All support `--help` for full flag details.
|
|
94
|
+
|
|
125
95
|
### `cu init`
|
|
126
96
|
|
|
127
97
|
First-time setup. Prompts for your API token, verifies it, auto-detects your workspace, and writes `~/.config/cu/config.json`.
|
|
@@ -140,7 +110,7 @@ cu tasks --status "in progress"
|
|
|
140
110
|
cu tasks --name "login"
|
|
141
111
|
cu tasks --list <listId>
|
|
142
112
|
cu tasks --space <spaceId>
|
|
143
|
-
cu tasks --json
|
|
113
|
+
cu tasks --json
|
|
144
114
|
```
|
|
145
115
|
|
|
146
116
|
### `cu initiatives`
|
|
@@ -192,9 +162,11 @@ Get task details. Pretty summary in terminal, JSON when piped.
|
|
|
192
162
|
|
|
193
163
|
```bash
|
|
194
164
|
cu task abc123
|
|
195
|
-
cu task abc123 --json
|
|
165
|
+
cu task abc123 --json
|
|
196
166
|
```
|
|
197
167
|
|
|
168
|
+
**Note:** When piped, `cu task` outputs a structured Markdown summary of the task. For the full raw API response with all fields (custom fields, checklists, etc.), use `--json`.
|
|
169
|
+
|
|
198
170
|
### `cu subtasks <id>`
|
|
199
171
|
|
|
200
172
|
List subtasks of a task or initiative.
|
|
@@ -274,7 +246,7 @@ List all lists in a space, including lists inside folders. Useful for discoverin
|
|
|
274
246
|
|
|
275
247
|
```bash
|
|
276
248
|
cu lists <spaceId>
|
|
277
|
-
cu lists <spaceId> --name "sprint"
|
|
249
|
+
cu lists <spaceId> --name "sprint"
|
|
278
250
|
cu lists <spaceId> --json
|
|
279
251
|
```
|
|
280
252
|
|
|
@@ -289,8 +261,8 @@ List spaces in your workspace. Useful for getting space IDs for the `--space` fi
|
|
|
289
261
|
|
|
290
262
|
```bash
|
|
291
263
|
cu spaces
|
|
292
|
-
cu spaces --name "eng"
|
|
293
|
-
cu spaces --my
|
|
264
|
+
cu spaces --name "eng"
|
|
265
|
+
cu spaces --my
|
|
294
266
|
cu spaces --json
|
|
295
267
|
```
|
|
296
268
|
|
|
@@ -305,9 +277,9 @@ cu spaces --json
|
|
|
305
277
|
Open a task in the browser. Accepts a task ID or partial name.
|
|
306
278
|
|
|
307
279
|
```bash
|
|
308
|
-
cu open abc123
|
|
309
|
-
cu open "login bug"
|
|
310
|
-
cu open abc123 --json
|
|
280
|
+
cu open abc123
|
|
281
|
+
cu open "login bug"
|
|
282
|
+
cu open abc123 --json
|
|
311
283
|
```
|
|
312
284
|
|
|
313
285
|
If the query matches multiple tasks by name, all matches are listed and the first is opened.
|
|
@@ -318,7 +290,7 @@ Daily standup helper. Shows tasks grouped into: recently completed, in progress,
|
|
|
318
290
|
|
|
319
291
|
```bash
|
|
320
292
|
cu summary
|
|
321
|
-
cu summary --hours 48
|
|
293
|
+
cu summary --hours 48
|
|
322
294
|
cu summary --json
|
|
323
295
|
```
|
|
324
296
|
|
|
@@ -329,25 +301,23 @@ cu summary --json
|
|
|
329
301
|
|
|
330
302
|
### `cu overdue`
|
|
331
303
|
|
|
332
|
-
List tasks that are past their due date (excludes done/closed tasks).
|
|
304
|
+
List tasks that are past their due date (excludes done/closed tasks). Sorted most overdue first.
|
|
333
305
|
|
|
334
306
|
```bash
|
|
335
307
|
cu overdue
|
|
336
308
|
cu overdue --json
|
|
337
309
|
```
|
|
338
310
|
|
|
339
|
-
Tasks are sorted by due date ascending (most overdue first).
|
|
340
|
-
|
|
341
311
|
### `cu assign <id>`
|
|
342
312
|
|
|
343
313
|
Assign or unassign users from a task. Supports `me` as shorthand for your user ID.
|
|
344
314
|
|
|
345
315
|
```bash
|
|
346
|
-
cu assign abc123 --to 12345
|
|
347
|
-
cu assign abc123 --to me
|
|
348
|
-
cu assign abc123 --remove 12345
|
|
349
|
-
cu assign abc123 --to me --remove 67890
|
|
350
|
-
cu assign abc123 --to me --json
|
|
316
|
+
cu assign abc123 --to 12345
|
|
317
|
+
cu assign abc123 --to me
|
|
318
|
+
cu assign abc123 --remove 12345
|
|
319
|
+
cu assign abc123 --to me --remove 67890
|
|
320
|
+
cu assign abc123 --to me --json
|
|
351
321
|
```
|
|
352
322
|
|
|
353
323
|
| Flag | Description |
|
|
@@ -361,9 +331,9 @@ cu assign abc123 --to me --json # output updated task JSON
|
|
|
361
331
|
Manage CLI configuration.
|
|
362
332
|
|
|
363
333
|
```bash
|
|
364
|
-
cu config get apiToken
|
|
365
|
-
cu config set teamId 12345
|
|
366
|
-
cu config path
|
|
334
|
+
cu config get apiToken
|
|
335
|
+
cu config set teamId 12345
|
|
336
|
+
cu config path
|
|
367
337
|
```
|
|
368
338
|
|
|
369
339
|
Valid keys: `apiToken`, `teamId`. Setting `apiToken` validates the `pk_` prefix.
|
|
@@ -373,17 +343,35 @@ Valid keys: `apiToken`, `teamId`. Setting `apiToken` validates the `pk_` prefix.
|
|
|
373
343
|
Output shell completion script. Supports `bash`, `zsh`, and `fish`.
|
|
374
344
|
|
|
375
345
|
```bash
|
|
376
|
-
|
|
377
|
-
eval "$(cu completion
|
|
346
|
+
eval "$(cu completion bash)" # Bash
|
|
347
|
+
eval "$(cu completion zsh)" # Zsh
|
|
348
|
+
cu completion fish > ~/.config/fish/completions/cu.fish # Fish
|
|
349
|
+
```
|
|
378
350
|
|
|
379
|
-
|
|
380
|
-
eval "$(cu completion zsh)"
|
|
351
|
+
## Config
|
|
381
352
|
|
|
382
|
-
|
|
383
|
-
|
|
353
|
+
### Config file
|
|
354
|
+
|
|
355
|
+
`~/.config/cu/config.json` (or `$XDG_CONFIG_HOME/cu/config.json`):
|
|
356
|
+
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"apiToken": "pk_...",
|
|
360
|
+
"teamId": "12345678"
|
|
361
|
+
}
|
|
384
362
|
```
|
|
385
363
|
|
|
386
|
-
|
|
364
|
+
### Environment variables
|
|
365
|
+
|
|
366
|
+
Environment variables override config file values:
|
|
367
|
+
|
|
368
|
+
| Variable | Description |
|
|
369
|
+
| -------------- | ----------------------------------------------------------------- |
|
|
370
|
+
| `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
|
|
371
|
+
| `CU_TEAM_ID` | Workspace (team) ID |
|
|
372
|
+
| `CU_OUTPUT` | Set to `json` to force JSON output when piped (default: markdown) |
|
|
373
|
+
|
|
374
|
+
When both are set, the config file is not required. Useful for CI/CD and containerized agents.
|
|
387
375
|
|
|
388
376
|
## Development
|
|
389
377
|
|
package/dist/index.js
CHANGED
|
@@ -220,6 +220,11 @@ import chalk from "chalk";
|
|
|
220
220
|
function isTTY() {
|
|
221
221
|
return Boolean(process.stdout.isTTY);
|
|
222
222
|
}
|
|
223
|
+
function shouldOutputJson(forceJson) {
|
|
224
|
+
if (forceJson) return true;
|
|
225
|
+
if (process.env["CU_OUTPUT"] === "json") return true;
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
223
228
|
function cell(value, width) {
|
|
224
229
|
if (value.length > width) return value.slice(0, width - 1) + "\u2026";
|
|
225
230
|
return value.padEnd(width);
|
|
@@ -252,6 +257,128 @@ var TASK_COLUMNS = [
|
|
|
252
257
|
{ key: "list", label: "LIST" }
|
|
253
258
|
];
|
|
254
259
|
|
|
260
|
+
// src/markdown.ts
|
|
261
|
+
function escapeCell(value) {
|
|
262
|
+
return value.replace(/\|/g, "\\|");
|
|
263
|
+
}
|
|
264
|
+
function formatMarkdownTable(rows, columns) {
|
|
265
|
+
const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
|
|
266
|
+
const divider = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
267
|
+
const lines = [header, divider];
|
|
268
|
+
for (const row of rows) {
|
|
269
|
+
const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
|
|
270
|
+
lines.push("| " + cells.join(" | ") + " |");
|
|
271
|
+
}
|
|
272
|
+
return lines.join("\n");
|
|
273
|
+
}
|
|
274
|
+
var TASK_MD_COLUMNS = [
|
|
275
|
+
{ key: "id", label: "ID" },
|
|
276
|
+
{ key: "name", label: "Name" },
|
|
277
|
+
{ key: "status", label: "Status" },
|
|
278
|
+
{ key: "list", label: "List" }
|
|
279
|
+
];
|
|
280
|
+
function formatTasksMarkdown(tasks) {
|
|
281
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
282
|
+
return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
|
|
283
|
+
}
|
|
284
|
+
function formatCommentsMarkdown(comments) {
|
|
285
|
+
if (comments.length === 0) return "No comments found.";
|
|
286
|
+
return comments.map((c) => `**${c.user}** (${c.date})
|
|
287
|
+
|
|
288
|
+
${c.text}`).join("\n\n---\n\n");
|
|
289
|
+
}
|
|
290
|
+
var LIST_MD_COLUMNS = [
|
|
291
|
+
{ key: "id", label: "ID" },
|
|
292
|
+
{ key: "name", label: "Name" },
|
|
293
|
+
{ key: "folder", label: "Folder" }
|
|
294
|
+
];
|
|
295
|
+
function formatListsMarkdown(lists) {
|
|
296
|
+
if (lists.length === 0) return "No lists found.";
|
|
297
|
+
return formatMarkdownTable(lists, LIST_MD_COLUMNS);
|
|
298
|
+
}
|
|
299
|
+
var SPACE_MD_COLUMNS = [
|
|
300
|
+
{ key: "id", label: "ID" },
|
|
301
|
+
{ key: "name", label: "Name" }
|
|
302
|
+
];
|
|
303
|
+
function formatSpacesMarkdown(spaces) {
|
|
304
|
+
if (spaces.length === 0) return "No spaces found.";
|
|
305
|
+
return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
|
|
306
|
+
}
|
|
307
|
+
function formatGroupedTasksMarkdown(groups) {
|
|
308
|
+
const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
|
|
309
|
+
|
|
310
|
+
${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
|
|
311
|
+
if (sections.length === 0) return "No tasks found.";
|
|
312
|
+
return sections.join("\n\n");
|
|
313
|
+
}
|
|
314
|
+
function formatDate(ms) {
|
|
315
|
+
const d = new Date(Number(ms));
|
|
316
|
+
const year = d.getUTCFullYear();
|
|
317
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
318
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
319
|
+
return `${year}-${month}-${day}`;
|
|
320
|
+
}
|
|
321
|
+
function formatDuration(ms) {
|
|
322
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
323
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
324
|
+
const minutes = totalMinutes % 60;
|
|
325
|
+
return `${hours}h ${minutes}m`;
|
|
326
|
+
}
|
|
327
|
+
function formatTaskDetailMarkdown(task) {
|
|
328
|
+
const lines = [`# ${task.name}`, ""];
|
|
329
|
+
const isInitiative2 = (task.custom_item_id ?? 0) !== 0;
|
|
330
|
+
const fields = [
|
|
331
|
+
["ID", task.id],
|
|
332
|
+
["Status", task.status.status],
|
|
333
|
+
["Type", isInitiative2 ? "initiative" : "task"],
|
|
334
|
+
["List", task.list.name],
|
|
335
|
+
["URL", task.url],
|
|
336
|
+
[
|
|
337
|
+
"Assignees",
|
|
338
|
+
task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
339
|
+
],
|
|
340
|
+
["Priority", task.priority?.priority],
|
|
341
|
+
["Parent", task.parent ?? void 0],
|
|
342
|
+
["Start Date", task.start_date ? formatDate(task.start_date) : void 0],
|
|
343
|
+
["Due Date", task.due_date ? formatDate(task.due_date) : void 0],
|
|
344
|
+
[
|
|
345
|
+
"Time Estimate",
|
|
346
|
+
task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
|
|
347
|
+
],
|
|
348
|
+
[
|
|
349
|
+
"Time Spent",
|
|
350
|
+
task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
|
|
351
|
+
],
|
|
352
|
+
["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
353
|
+
["Created", task.date_created ? formatDate(task.date_created) : void 0],
|
|
354
|
+
["Updated", task.date_updated ? formatDate(task.date_updated) : void 0]
|
|
355
|
+
];
|
|
356
|
+
for (const [label, value] of fields) {
|
|
357
|
+
if (value != null && value !== "") {
|
|
358
|
+
lines.push(`**${label}:** ${value}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (task.description) {
|
|
362
|
+
lines.push("", "## Description", "", task.description);
|
|
363
|
+
}
|
|
364
|
+
return lines.join("\n");
|
|
365
|
+
}
|
|
366
|
+
function formatUpdateConfirmation(id, name) {
|
|
367
|
+
return `Updated task ${id}: "${name}"`;
|
|
368
|
+
}
|
|
369
|
+
function formatCreateConfirmation(id, name, url) {
|
|
370
|
+
return `Created task ${id}: "${name}" - ${url}`;
|
|
371
|
+
}
|
|
372
|
+
function formatCommentConfirmation(id) {
|
|
373
|
+
return `Comment posted (id: ${id})`;
|
|
374
|
+
}
|
|
375
|
+
function formatAssignConfirmation(taskId, opts) {
|
|
376
|
+
const parts = [];
|
|
377
|
+
if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
|
|
378
|
+
if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
|
|
379
|
+
return parts.join("; ");
|
|
380
|
+
}
|
|
381
|
+
|
|
255
382
|
// src/interactive.ts
|
|
256
383
|
import { execFileSync } from "child_process";
|
|
257
384
|
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
@@ -439,10 +566,14 @@ async function fetchMyTasks(config, opts = {}) {
|
|
|
439
566
|
return filtered.map(summarize);
|
|
440
567
|
}
|
|
441
568
|
async function printTasks(tasks, forceJson, config) {
|
|
442
|
-
if (forceJson
|
|
569
|
+
if (shouldOutputJson(forceJson)) {
|
|
443
570
|
console.log(JSON.stringify(tasks, null, 2));
|
|
444
571
|
return;
|
|
445
572
|
}
|
|
573
|
+
if (!isTTY()) {
|
|
574
|
+
console.log(formatTasksMarkdown(tasks));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
446
577
|
if (tasks.length === 0) {
|
|
447
578
|
console.log("No tasks found.");
|
|
448
579
|
return;
|
|
@@ -671,13 +802,13 @@ async function runSprintCommand(config, opts) {
|
|
|
671
802
|
const me = await client.getMe();
|
|
672
803
|
const viewData = await client.getListViews(activeList.id);
|
|
673
804
|
const listView = viewData.required_views?.list;
|
|
674
|
-
let
|
|
805
|
+
let allTasks;
|
|
675
806
|
if (listView) {
|
|
676
|
-
|
|
677
|
-
sprintTasks = allViewTasks.filter((t) => t.assignees.some((a) => a.id === me.id));
|
|
807
|
+
allTasks = await client.getViewTasks(listView.id);
|
|
678
808
|
} else {
|
|
679
|
-
|
|
809
|
+
allTasks = await client.getTasksFromList(activeList.id);
|
|
680
810
|
}
|
|
811
|
+
const sprintTasks = allTasks.filter((t) => t.assignees.some((a) => Number(a.id) === me.id));
|
|
681
812
|
const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
|
|
682
813
|
const summaries = filtered.map(summarize);
|
|
683
814
|
await printTasks(summaries, opts.json ?? false, config);
|
|
@@ -700,7 +831,7 @@ async function postComment(config, taskId, text) {
|
|
|
700
831
|
|
|
701
832
|
// src/commands/comments.ts
|
|
702
833
|
import chalk3 from "chalk";
|
|
703
|
-
function
|
|
834
|
+
function formatDate2(timestamp) {
|
|
704
835
|
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
705
836
|
month: "short",
|
|
706
837
|
day: "numeric",
|
|
@@ -720,10 +851,14 @@ async function fetchComments(config, taskId) {
|
|
|
720
851
|
}));
|
|
721
852
|
}
|
|
722
853
|
function printComments(comments, forceJson) {
|
|
723
|
-
if (forceJson
|
|
854
|
+
if (shouldOutputJson(forceJson)) {
|
|
724
855
|
console.log(JSON.stringify(comments, null, 2));
|
|
725
856
|
return;
|
|
726
857
|
}
|
|
858
|
+
if (!isTTY()) {
|
|
859
|
+
console.log(formatCommentsMarkdown(comments));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
727
862
|
if (comments.length === 0) {
|
|
728
863
|
console.log("No comments found.");
|
|
729
864
|
return;
|
|
@@ -732,7 +867,7 @@ function printComments(comments, forceJson) {
|
|
|
732
867
|
for (let i = 0; i < comments.length; i++) {
|
|
733
868
|
const c = comments[i];
|
|
734
869
|
if (i > 0) console.log(separator);
|
|
735
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
870
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatDate2(c.date))}`);
|
|
736
871
|
console.log(c.text);
|
|
737
872
|
if (i < comments.length - 1) console.log("");
|
|
738
873
|
}
|
|
@@ -761,10 +896,14 @@ async function fetchLists(config, spaceId, opts = {}) {
|
|
|
761
896
|
return results;
|
|
762
897
|
}
|
|
763
898
|
function printLists(lists, forceJson) {
|
|
764
|
-
if (forceJson
|
|
899
|
+
if (shouldOutputJson(forceJson)) {
|
|
765
900
|
console.log(JSON.stringify(lists, null, 2));
|
|
766
901
|
return;
|
|
767
902
|
}
|
|
903
|
+
if (!isTTY()) {
|
|
904
|
+
console.log(formatListsMarkdown(lists));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
768
907
|
if (lists.length === 0) {
|
|
769
908
|
console.log("No lists found.");
|
|
770
909
|
return;
|
|
@@ -832,7 +971,7 @@ async function fetchInbox(config, days = 30) {
|
|
|
832
971
|
async function printInbox(tasks, forceJson, config) {
|
|
833
972
|
const now = Date.now();
|
|
834
973
|
const groups = groupTasks(tasks, now);
|
|
835
|
-
if (forceJson
|
|
974
|
+
if (shouldOutputJson(forceJson)) {
|
|
836
975
|
const jsonGroups = {};
|
|
837
976
|
for (const { key } of TIME_PERIODS) {
|
|
838
977
|
if (groups[key].length > 0) {
|
|
@@ -842,6 +981,14 @@ async function printInbox(tasks, forceJson, config) {
|
|
|
842
981
|
console.log(JSON.stringify(jsonGroups, null, 2));
|
|
843
982
|
return;
|
|
844
983
|
}
|
|
984
|
+
if (!isTTY()) {
|
|
985
|
+
const mdGroups = TIME_PERIODS.filter((p) => groups[p.key].length > 0).map((p) => ({
|
|
986
|
+
label: p.label,
|
|
987
|
+
tasks: groups[p.key]
|
|
988
|
+
}));
|
|
989
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
845
992
|
if (tasks.length === 0) {
|
|
846
993
|
console.log("No recently updated tasks.");
|
|
847
994
|
return;
|
|
@@ -873,7 +1020,11 @@ async function listSpaces(config, opts) {
|
|
|
873
1020
|
);
|
|
874
1021
|
spaces = spaces.filter((s) => mySpaceIds.has(s.id));
|
|
875
1022
|
}
|
|
876
|
-
if (
|
|
1023
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1024
|
+
console.log(JSON.stringify(spaces, null, 2));
|
|
1025
|
+
} else if (!isTTY()) {
|
|
1026
|
+
console.log(formatSpacesMarkdown(spaces.map((s) => ({ id: s.id, name: s.name }))));
|
|
1027
|
+
} else {
|
|
877
1028
|
const table = formatTable(
|
|
878
1029
|
spaces.map((s) => ({ id: s.id, name: s.name })),
|
|
879
1030
|
[
|
|
@@ -882,8 +1033,6 @@ async function listSpaces(config, opts) {
|
|
|
882
1033
|
]
|
|
883
1034
|
);
|
|
884
1035
|
console.log(table);
|
|
885
|
-
} else {
|
|
886
|
-
console.log(JSON.stringify(spaces, null, 2));
|
|
887
1036
|
}
|
|
888
1037
|
}
|
|
889
1038
|
|
|
@@ -938,7 +1087,7 @@ async function runAssignedCommand(config, opts) {
|
|
|
938
1087
|
const client = new ClickUpClient(config);
|
|
939
1088
|
const allTasks = await client.getMyTasks(config.teamId);
|
|
940
1089
|
const groups = groupByStatus(allTasks, opts.includeClosed ?? false);
|
|
941
|
-
if (opts.json
|
|
1090
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
942
1091
|
const result = {};
|
|
943
1092
|
for (const group of groups) {
|
|
944
1093
|
result[group.status.toLowerCase()] = group.tasks.map((t) => toJsonTask(t, summarize(t)));
|
|
@@ -946,6 +1095,14 @@ async function runAssignedCommand(config, opts) {
|
|
|
946
1095
|
console.log(JSON.stringify(result, null, 2));
|
|
947
1096
|
return;
|
|
948
1097
|
}
|
|
1098
|
+
if (!isTTY()) {
|
|
1099
|
+
const mdGroups = groups.map((g) => ({
|
|
1100
|
+
label: g.status,
|
|
1101
|
+
tasks: g.tasks.map((t) => summarize(t))
|
|
1102
|
+
}));
|
|
1103
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
949
1106
|
if (groups.length === 0) {
|
|
950
1107
|
console.log("No tasks found.");
|
|
951
1108
|
return;
|
|
@@ -971,13 +1128,13 @@ async function openTask(config, query, opts = {}) {
|
|
|
971
1128
|
} catch {
|
|
972
1129
|
}
|
|
973
1130
|
if (task) {
|
|
974
|
-
if (opts.json) {
|
|
1131
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
975
1132
|
console.log(JSON.stringify(task, null, 2));
|
|
1133
|
+
} else if (!isTTY()) {
|
|
1134
|
+
console.log(formatTaskDetailMarkdown(task));
|
|
976
1135
|
} else {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
console.log(task.url);
|
|
980
|
-
}
|
|
1136
|
+
console.log(task.name);
|
|
1137
|
+
console.log(task.url);
|
|
981
1138
|
openUrl(task.url);
|
|
982
1139
|
}
|
|
983
1140
|
return task;
|
|
@@ -995,15 +1152,18 @@ async function openTask(config, query, opts = {}) {
|
|
|
995
1152
|
}
|
|
996
1153
|
console.log("Opening first match...");
|
|
997
1154
|
}
|
|
998
|
-
if (opts.json) {
|
|
1155
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
999
1156
|
const fullTask = await client.getTask(first.id);
|
|
1000
1157
|
console.log(JSON.stringify(fullTask, null, 2));
|
|
1001
1158
|
return fullTask;
|
|
1002
1159
|
}
|
|
1003
|
-
if (isTTY()) {
|
|
1004
|
-
|
|
1005
|
-
console.log(
|
|
1160
|
+
if (!isTTY()) {
|
|
1161
|
+
const fullTask = await client.getTask(first.id);
|
|
1162
|
+
console.log(formatTaskDetailMarkdown(fullTask));
|
|
1163
|
+
return fullTask;
|
|
1006
1164
|
}
|
|
1165
|
+
console.log(first.name);
|
|
1166
|
+
console.log(first.url);
|
|
1007
1167
|
openUrl(first.url);
|
|
1008
1168
|
return {
|
|
1009
1169
|
id: first.id,
|
|
@@ -1017,7 +1177,7 @@ async function openTask(config, query, opts = {}) {
|
|
|
1017
1177
|
}
|
|
1018
1178
|
|
|
1019
1179
|
// src/commands/summary.ts
|
|
1020
|
-
var IN_PROGRESS_PATTERNS = ["in progress", "in review", "doing"];
|
|
1180
|
+
var IN_PROGRESS_PATTERNS = ["in progress", "in review", "code review", "doing"];
|
|
1021
1181
|
function isCompletedRecently(task, cutoff) {
|
|
1022
1182
|
if (!isDoneStatus(task.status.status)) return false;
|
|
1023
1183
|
const updated = Number(task.date_updated);
|
|
@@ -1065,10 +1225,19 @@ async function runSummaryCommand(config, opts) {
|
|
|
1065
1225
|
const client = new ClickUpClient(config);
|
|
1066
1226
|
const allTasks = await client.getMyTasks(config.teamId);
|
|
1067
1227
|
const result = categorizeTasks(allTasks, opts.hours);
|
|
1068
|
-
if (opts.json
|
|
1228
|
+
if (shouldOutputJson(opts.json)) {
|
|
1069
1229
|
console.log(JSON.stringify(result, null, 2));
|
|
1070
1230
|
return;
|
|
1071
1231
|
}
|
|
1232
|
+
if (!isTTY()) {
|
|
1233
|
+
const mdGroups = [
|
|
1234
|
+
{ label: "Completed Recently", tasks: result.completed },
|
|
1235
|
+
{ label: "In Progress", tasks: result.inProgress },
|
|
1236
|
+
{ label: "Overdue", tasks: result.overdue }
|
|
1237
|
+
];
|
|
1238
|
+
console.log(formatGroupedTasksMarkdown(mdGroups));
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1072
1241
|
printSection("Completed Recently", result.completed);
|
|
1073
1242
|
printSection("In Progress", result.inProgress);
|
|
1074
1243
|
printSection("Overdue", result.overdue);
|
|
@@ -1600,8 +1769,10 @@ program.command("task <taskId>").description("Get task details").option("--json"
|
|
|
1600
1769
|
wrapAction(async (taskId, opts) => {
|
|
1601
1770
|
const config = loadConfig();
|
|
1602
1771
|
const result = await getTask(config, taskId);
|
|
1603
|
-
if (opts.json
|
|
1772
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1604
1773
|
console.log(JSON.stringify(result, null, 2));
|
|
1774
|
+
} else if (!isTTY()) {
|
|
1775
|
+
console.log(formatTaskDetailMarkdown(result));
|
|
1605
1776
|
} else {
|
|
1606
1777
|
const lines = [
|
|
1607
1778
|
`ID: ${result.id}`,
|
|
@@ -1622,10 +1793,10 @@ program.command("update <taskId>").description("Update a task").option("-n, --na
|
|
|
1622
1793
|
const config = loadConfig();
|
|
1623
1794
|
const payload = buildUpdatePayload(opts);
|
|
1624
1795
|
const result = await updateTask(config, taskId, payload);
|
|
1625
|
-
if (
|
|
1626
|
-
console.log(`Updated task ${result.id}: "${result.name}"`);
|
|
1627
|
-
} else {
|
|
1796
|
+
if (shouldOutputJson(false)) {
|
|
1628
1797
|
console.log(JSON.stringify(result, null, 2));
|
|
1798
|
+
} else {
|
|
1799
|
+
console.log(formatUpdateConfirmation(result.id, result.name));
|
|
1629
1800
|
}
|
|
1630
1801
|
})
|
|
1631
1802
|
);
|
|
@@ -1633,10 +1804,10 @@ program.command("create").description("Create a new task").option("-l, --list <l
|
|
|
1633
1804
|
wrapAction(async (opts) => {
|
|
1634
1805
|
const config = loadConfig();
|
|
1635
1806
|
const result = await createTask(config, opts);
|
|
1636
|
-
if (
|
|
1637
|
-
console.log(`Created task ${result.id}: "${result.name}" - ${result.url}`);
|
|
1638
|
-
} else {
|
|
1807
|
+
if (shouldOutputJson(false)) {
|
|
1639
1808
|
console.log(JSON.stringify(result, null, 2));
|
|
1809
|
+
} else {
|
|
1810
|
+
console.log(formatCreateConfirmation(result.id, result.name, result.url));
|
|
1640
1811
|
}
|
|
1641
1812
|
})
|
|
1642
1813
|
);
|
|
@@ -1657,10 +1828,10 @@ program.command("comment <taskId>").description("Post a comment on a task").requ
|
|
|
1657
1828
|
wrapAction(async (taskId, opts) => {
|
|
1658
1829
|
const config = loadConfig();
|
|
1659
1830
|
const result = await postComment(config, taskId, opts.message);
|
|
1660
|
-
if (
|
|
1661
|
-
console.log(`Comment posted (id: ${result.id})`);
|
|
1662
|
-
} else {
|
|
1831
|
+
if (shouldOutputJson(false)) {
|
|
1663
1832
|
console.log(JSON.stringify(result, null, 2));
|
|
1833
|
+
} else {
|
|
1834
|
+
console.log(formatCommentConfirmation(result.id));
|
|
1664
1835
|
}
|
|
1665
1836
|
})
|
|
1666
1837
|
);
|
|
@@ -1730,13 +1901,10 @@ program.command("assign <taskId>").description("Assign or unassign users from a
|
|
|
1730
1901
|
wrapAction(async (taskId, opts) => {
|
|
1731
1902
|
const config = loadConfig();
|
|
1732
1903
|
const result = await assignTask(config, taskId, opts);
|
|
1733
|
-
if (opts.json
|
|
1904
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
1734
1905
|
console.log(JSON.stringify(result, null, 2));
|
|
1735
1906
|
} else {
|
|
1736
|
-
|
|
1737
|
-
if (opts.to) parts.push(`Assigned ${opts.to} to task ${taskId}`);
|
|
1738
|
-
if (opts.remove) parts.push(`Removed ${opts.remove} from task ${taskId}`);
|
|
1739
|
-
console.log(parts.join("; "));
|
|
1907
|
+
console.log(formatAssignConfirmation(taskId, { to: opts.to, remove: opts.remove }));
|
|
1740
1908
|
}
|
|
1741
1909
|
})
|
|
1742
1910
|
);
|