clickup-agent-cli 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +61 -0
  4. package/LICENSE +21 -0
  5. package/README.md +7 -1
  6. package/dist/clickup.js +1 -1
  7. package/package.json +4 -3
  8. package/skills/clickup/SKILL.md +2 -0
  9. package/skills/clickup-blocker-report/SKILL.md +3 -1
  10. package/skills/clickup-blocker-report/assets/report-template.md +16 -0
  11. package/skills/clickup-capacity-check/SKILL.md +3 -1
  12. package/skills/clickup-capacity-check/assets/report-template.md +14 -0
  13. package/skills/clickup-goal-progress/SKILL.md +3 -1
  14. package/skills/clickup-goal-progress/assets/report-template.md +13 -0
  15. package/skills/clickup-my-day/SKILL.md +2 -14
  16. package/skills/clickup-my-day/assets/report-template.md +12 -0
  17. package/skills/clickup-release-notes/SKILL.md +2 -13
  18. package/skills/clickup-release-notes/assets/report-template.md +12 -0
  19. package/skills/clickup-standup/SKILL.md +2 -14
  20. package/skills/clickup-standup/assets/report-template.md +9 -0
  21. package/skills/clickup-team-report/SKILL.md +3 -1
  22. package/skills/clickup-team-report/assets/report-template.md +24 -0
  23. package/skills/clickup-time-audit/SKILL.md +5 -1
  24. package/skills/clickup-time-audit/assets/report-template.md +19 -0
  25. package/skills/clickup-timesheet-export/SKILL.md +18 -19
  26. package/skills/clickup-timesheet-export/assets/report-template.md +9 -0
  27. package/skills/clickup-timesheet-export/scripts/aggregate.mjs +73 -0
  28. package/skills/clickup-weekly-review/SKILL.md +3 -1
  29. package/skills/clickup-weekly-review/assets/report-template.md +26 -0
  30. package/skills/clickup-workspace-audit/SKILL.md +12 -20
  31. package/skills/clickup-workspace-audit/assets/report-template.md +21 -0
  32. package/skills/clickup-workspace-audit/scripts/classify.mjs +72 -0
@@ -11,7 +11,7 @@
11
11
  "name": "clickup",
12
12
  "source": "./",
13
13
  "description": "ClickUp CLI with 31 agent skills covering the full API -- token-efficient alternative to MCP with chat, time tracking, docs, and project management workflows",
14
- "version": "0.5.0",
14
+ "version": "0.5.2",
15
15
  "homepage": "https://github.com/henryreith/clickup-cli",
16
16
  "keywords": ["clickup", "project-management", "tasks", "time-tracking"],
17
17
  "category": "productivity"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clickup",
3
3
  "description": "ClickUp CLI with 31 agent skills covering the full API -- token-efficient alternative to MCP with chat, time tracking, docs, and project management workflows",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "author": {
6
6
  "name": "Henry Reith"
7
7
  },
package/AGENTS.md ADDED
@@ -0,0 +1,61 @@
1
+ # ClickUp CLI: Agent Operating Manual
2
+
3
+ You have (or can install) `clickup`, a command-line tool covering the full ClickUp API v2. This file is the fastest path from zero to productive for any agent on any platform. Everything here works headless.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npm install -g clickup-agent-cli
9
+
10
+ # Authenticate: env var (preferred for agents), --token-file, or --token per call
11
+ export CLICKUP_API_TOKEN=pk_your_token
12
+
13
+ # Verify auth and workspace resolution
14
+ clickup config validate
15
+
16
+ # One-time workspace binding (auto-selects when the account has one workspace)
17
+ clickup workspace setup
18
+ ```
19
+
20
+ ## Discover, never guess
21
+
22
+ Do not guess command syntax. The CLI is self-describing:
23
+
24
+ ```bash
25
+ clickup skill show clickup # Root skill: index of all capabilities (~150 tokens)
26
+ clickup skill list # All 31 skills (12 resource references, 18 workflows)
27
+ clickup skill show <name> # Full skill content (JSON with a "content" field when piped)
28
+ clickup skill path <name> # Skill directory: bundled references/, assets/, scripts/
29
+ clickup schema <resource> # Actions for a resource (task, list, doc, time, ...)
30
+ clickup schema <resource>.<action> # Required and optional flags for one action
31
+ ```
32
+
33
+ Skills bundle supporting files per the Agent Skills standard: `assets/report-template.md` (copy the structure for report output), `scripts/*.mjs` (deterministic helpers; pipe JSON in via node), and `references/` (deep detail such as `clickup skill path clickup`/references/gotchas.md).
34
+
35
+ ## Conventions that matter
36
+
37
+ - **Output**: JSON by default when piped; force with `--format json|table|csv|tsv|quiet|id|md`. `--format id` prints just the first result's ID for capture: `ID=$(clickup task create ... --format id)`.
38
+ - **Exit codes**: 0 ok, 1 general, 2 bad arguments, 3 auth, 4 not found, 5 permission, 6 rate limited, 7 network. Branch on these.
39
+ - **Destructive commands** (`* delete`, `field remove`, `time delete`) require `--confirm` when non-interactive. Pass it deliberately; never bypass a confirmation you were not asked to give.
40
+ - **Dates**: Unix ms/seconds, ISO 8601 (`2026-07-10`), or relative (`today`, `tomorrow`, `3d`, `-1w`, `friday`). Filter flags ending `-gt`/`-lt` take Unix ms only.
41
+ - **Rate limits**: ~100 requests/min; 429s retry automatically. Prefer `task bulk-update`/`task bulk-delete` for batches.
42
+ - **Dry runs**: add `--dry-run` to print the exact request without sending it.
43
+ - Errors and spinners go to stderr; stdout is data only. Safe to pipe.
44
+
45
+ ## Quick example
46
+
47
+ ```bash
48
+ # Find the list, create a task, comment on it
49
+ LIST_ID=$(clickup list list --space-id 123 --filter name="Sprint 12" --format id)
50
+ TASK_ID=$(clickup task create --list-id "$LIST_ID" --name "Fix login redirect" \
51
+ --priority high --due-date friday --format id)
52
+ clickup comment create --task-id "$TASK_ID" --text "Repro steps in thread"
53
+ ```
54
+
55
+ ## For Claude Code specifically
56
+
57
+ Install as a plugin instead: `/plugin marketplace add henryreith/clickup-cli` then `/plugin install clickup@clickup-agent-cli`. Skills load natively (`/clickup:weekly-review`, etc.).
58
+
59
+ ## Developing this repo
60
+
61
+ Build/test conventions live in [CLAUDE.md](./CLAUDE.md): `npm run typecheck && npm test && npm run build && npm run lint:skills` must pass. The full command reference is [COMMANDS.md](./COMMANDS.md); architecture is [SPEC.md](./SPEC.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henry Reith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # ClickUp CLI
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/clickup-agent-cli)](https://www.npmjs.com/package/clickup-agent-cli)
4
+ [![node](https://img.shields.io/node/v/clickup-agent-cli)](https://nodejs.org)
5
+ [![CI](https://github.com/henryreith/clickup-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/henryreith/clickup-cli/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/npm/l/clickup-agent-cli)](./LICENSE)
7
+ [![Agent Skills](https://img.shields.io/badge/agent%20skills-31-blueviolet)](./skills)
8
+
3
9
  Zero-overhead CLI for the ClickUp API v2 -- for AI agents, scripts, and automation. Covers the entire API surface with all MCP capabilities and more.
4
10
 
5
11
  ## Why This Exists
@@ -187,7 +193,7 @@ This creates `/marketing-weekly` alongside the built-in `/clickup:*` skills.
187
193
 
188
194
  ### Bootstrap From Any Agent
189
195
 
190
- Any agent that can run shell commands can set up and use the CLI in five steps, no plugin system required:
196
+ The repo and npm package ship an [AGENTS.md](./AGENTS.md) operating manual that agent platforms (Codex, Cursor, Gemini CLI, custom agents) pick up automatically. Any agent that can run shell commands can set up and use the CLI in five steps, no plugin system required:
191
197
 
192
198
  ```bash
193
199
  # 1. Install
package/dist/clickup.js CHANGED
@@ -4629,7 +4629,7 @@ function registerChatCommands(program, getClient) {
4629
4629
  }
4630
4630
 
4631
4631
  // src/cli.ts
4632
- var VERSION = "0.5.0";
4632
+ var VERSION = "0.5.2";
4633
4633
  function createProgram() {
4634
4634
  const program = new Command();
4635
4635
  program.name("clickup").description("ClickUp CLI - Manage ClickUp workspaces from the terminal").version(VERSION).option("--token <token>", "API token").option("--token-file <path>", "Read API token from this file path").option("--profile <name>", "Profile to use (key, workspace name, or nickname)").option("--workspace-id <id>", "Workspace ID").addOption(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clickup-agent-cli",
3
- "version": "0.5.0",
4
- "description": "CLI covering the entire ClickUp API v2 surface",
3
+ "version": "0.5.2",
4
+ "description": "Zero-overhead ClickUp CLI for AI agents, shell scripts, and automation. Full ClickUp API v2 coverage with 31 bundled agent skills.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "clickup": "dist/clickup.js"
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "skills",
12
- ".claude-plugin"
12
+ ".claude-plugin",
13
+ "AGENTS.md"
13
14
  ],
14
15
  "engines": {
15
16
  "node": ">=22.0.0"
@@ -37,6 +37,8 @@ clickup skill show <name> # Print a skill's full contents (e.g. cli
37
37
 
38
38
  Known pitfalls (rate limits, error codes, exit codes, destructive-command rules) live in `references/gotchas.md` next to this file; read it before debugging a failing command (`clickup skill path clickup` prints this skill's directory).
39
39
 
40
+ Skills bundle supporting files per the Agent Skills standard: `references/` (deep detail), `assets/` (output templates to copy), and `scripts/` (deterministic helpers to run with node). `clickup skill path <name>` prints any skill's directory so these files can be read or executed directly.
41
+
40
42
  ## Sub-Skills (load when needed)
41
43
 
42
44
  | Skill | What it covers |
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[workspace-id]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Blocker Report
@@ -71,6 +71,8 @@ clickup task bulk-time-in-status --task-id <id1> --task-id <id2> --format json
71
71
 
72
72
  ### Step 6: Compile the report
73
73
 
74
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-blocker-report` prints it). Fill every placeholder; drop sections with no content.
75
+
74
76
  - **Blocked tasks**: What, who, and what is blocking them
75
77
  - **Stale tasks**: In progress but not updated recently
76
78
  - **Overdue + unassigned**: Nobody owns these
@@ -0,0 +1,16 @@
1
+ ## Blocker Report: <scope> (<date>)
2
+
3
+ ### Blocked tasks
4
+ - <task> - <assignee>, blocked <n> days, waiting on: <blocker>
5
+
6
+ ### Dependency chains
7
+ - <task A> blocks <task B> blocks <task C> (chain head is <status>)
8
+
9
+ ### Stale (in progress, no updates)
10
+ - <task> - <assignee>, last touched <date>
11
+
12
+ ### Overdue and unassigned (nobody owns these)
13
+ - <task> - due <date> (<list>)
14
+
15
+ ### Escalations needed
16
+ 1. <task>: blocked <n> days - <suggested action>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[workspace-id]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Capacity Check
@@ -57,6 +57,8 @@ clickup task search --workspace-id <id> --assignee <user-id> \
57
57
 
58
58
  ### Step 5: Compile capacity report
59
59
 
60
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-capacity-check` prints it). Fill every placeholder; drop sections with no content.
61
+
60
62
  For each team member:
61
63
  - **Active tasks**: Count and total estimated hours
62
64
  - **Time logged this week**: Hours
@@ -0,0 +1,14 @@
1
+ ## Capacity Check: <team/scope> (<date>)
2
+
3
+ | Person | Active tasks | Est. hours | Logged this week | Overdue | Status |
4
+ |--------|--------------|-----------|------------------|---------|--------|
5
+ | <name> | <n> | <n>h | <n>h | <n> | Under / At / Over |
6
+
7
+ ### Overloaded
8
+ - <name>: <what to move and to whom>
9
+
10
+ ### Available bandwidth
11
+ - <name>: <n> open tasks, can take on more
12
+
13
+ ### Suggested rebalance
14
+ 1. Move "<task>" from <overloaded> to <available>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[workspace-id]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Goal Progress Report
@@ -56,6 +56,8 @@ clickup task list --list-id <linked-list-id> --include-closed --format json
56
56
 
57
57
  ### Step 5: Compile the report
58
58
 
59
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-goal-progress` prints it). Fill every placeholder; drop sections with no content.
60
+
59
61
  For each goal:
60
62
  - **Goal name** and due date
61
63
  - **Key results**: Current vs. target, % complete
@@ -0,0 +1,13 @@
1
+ ## Goal Progress: <scope> (<date>)
2
+
3
+ | Goal | Due | Progress | Expected | Status |
4
+ |------|-----|----------|----------|--------|
5
+ | <goal> | <date> | <current>/<target> (<x>%) | <y>% | On track / At risk / Behind |
6
+
7
+ ### <goal name>
8
+ - <key result>: <current> of <target> (<x>%)
9
+ - Latest: <most recent note or update>
10
+ - <status rationale in one line>
11
+
12
+ ### Stale goals (no recent key result updates)
13
+ - <goal> - last update <date>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[optional: a different person, or 'this week' for a wider window]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # My Day
@@ -45,19 +45,7 @@ Priority (urgent=1 to low=4) breaks ties inside each bucket.
45
45
 
46
46
  ### Step 4: Present the agenda
47
47
 
48
- ```
49
- ## Your day - <date>
50
-
51
- Now: <running timer task, if any>
52
-
53
- 1. [OVERDUE] <task> - was due <date> (<list>)
54
- 2. [TODAY] <task> - <priority>
55
- 3. ...
56
-
57
- Blocked (not actionable): <task> - waiting on <dependency>
58
-
59
- Suggestion: <one sentence - e.g. "Clear the two overdue items before starting new work.">
60
- ```
48
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-my-day` prints it). Fill every placeholder; drop sections with no content.
61
49
 
62
50
  Keep it under ~10 items; summarize the rest as "and N more in the backlog".
63
51
 
@@ -0,0 +1,12 @@
1
+ ## Your day - <date>
2
+
3
+ Now: <running timer task, if any>
4
+
5
+ 1. [OVERDUE] <task> - was due <date> (<list>)
6
+ 2. [TODAY] <task> - <priority>
7
+ 3. [IN PROGRESS] <task> - untouched <n> days
8
+ 4. [UP NEXT] <task> - <priority>
9
+
10
+ Blocked (not actionable): <task> - waiting on <dependency>
11
+
12
+ Suggestion: <one sentence on what to tackle first>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[period and scope - e.g. 'last sprint for the mobile space', 'June, customer-facing']"
9
- allowed-tools: Bash(clickup *), Write
9
+ allowed-tools: Bash(clickup *), Write, Read
10
10
  ---
11
11
 
12
12
  # Release Notes
@@ -39,18 +39,7 @@ Group by tags and task names into: **Features**, **Improvements**, **Fixes**, an
39
39
 
40
40
  Rewrite task names for the audience - "Fix login redirect on Safari" becomes "Fixed an issue where Safari users could be redirected to the wrong page after logging in." Never paste raw task IDs into customer-facing notes.
41
41
 
42
- ```markdown
43
- # Release Notes - <period>
44
-
45
- ## New
46
- - ...
47
-
48
- ## Improved
49
- - ...
50
-
51
- ## Fixed
52
- - ...
53
- ```
42
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-release-notes` prints it). Fill every placeholder; drop sections with no content.
54
43
 
55
44
  For internal notes append task IDs and assignees for traceability.
56
45
 
@@ -0,0 +1,12 @@
1
+ # Release Notes - <period>
2
+
3
+ ## New
4
+ - <benefit-focused line for each shipped feature>
5
+
6
+ ## Improved
7
+ - <what got better and for whom>
8
+
9
+ ## Fixed
10
+ - <user-visible bug fixes, written as outcomes>
11
+
12
+ <!-- Internal variant: append task IDs and assignees per line -->
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[scope - e.g. 'my tasks', 'engineering team', user-id]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Daily Standup
@@ -74,19 +74,7 @@ clickup time running --workspace-id <id> --assignee <user-id> --format json
74
74
 
75
75
  ### Step 6: Compile standup
76
76
 
77
- Format as:
78
-
79
- ```
80
- **Yesterday:**
81
- - Completed: [list of finished tasks with names]
82
-
83
- **Today:**
84
- - Working on: [list of in-progress tasks]
85
- - Due today: [list of tasks due today]
86
-
87
- **Blockers:**
88
- - [list of blocked tasks, if any]
89
- ```
77
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-standup` prints it). Fill every placeholder; drop sections with no content.
90
78
 
91
79
  ## Tips
92
80
 
@@ -0,0 +1,9 @@
1
+ **Yesterday:**
2
+ - Completed: <finished task names>
3
+
4
+ **Today:**
5
+ - Working on: <in-progress tasks>
6
+ - Due today: <tasks due today>
7
+
8
+ **Blockers:**
9
+ - <blocked tasks, or "None">
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[team or department name, e.g. 'marketing', 'engineering', 'operations']"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Team / Department Report
@@ -102,6 +102,8 @@ Filter time entries to those related to the team's tasks.
102
102
 
103
103
  ### Step 7: Compile the report
104
104
 
105
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-team-report` prints it). Fill every placeholder; drop sections with no content.
106
+
105
107
  Structure the report as:
106
108
 
107
109
  - **Team/Department**: Name and scope of what is covered
@@ -0,0 +1,24 @@
1
+ # <Team> Status Report (<date>)
2
+
3
+ **Scope:** <spaces/folders/lists covered>
4
+ **Summary:** <2-3 sentences on where things stand>
5
+
6
+ ## By the numbers
7
+ | To do | In progress | Done this week | Overdue |
8
+ |-------|-------------|----------------|---------|
9
+ | <n> | <n> | <n> | <n> |
10
+
11
+ ## Key accomplishments
12
+ - <completed task worth naming> (<assignee>)
13
+
14
+ ## Currently working on
15
+ - <task> - <assignee>, due <date>
16
+
17
+ ## Upcoming deadlines (next 7 days)
18
+ - <task> - due <date>, <assignee>
19
+
20
+ ## Risks and blockers
21
+ - <overdue or blocked item> - <what unblocks it>
22
+
23
+ ## Time invested
24
+ <n> hours logged this period (<top contributors>)
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[workspace-id] [start-date] [end-date]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Time Audit
@@ -81,6 +81,10 @@ Look for:
81
81
  clickup time running --workspace-id <id> --format json
82
82
  ```
83
83
 
84
+ ### Compile the report
85
+
86
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-time-audit` prints it). Fill every placeholder; drop sections with no content.
87
+
84
88
  ## Tips
85
89
 
86
90
  - Duration is in milliseconds. Divide by 3600000 for hours.
@@ -0,0 +1,19 @@
1
+ ## Time Audit: <scope> (<period>)
2
+
3
+ Total logged: <n>h (<m>h billable / <k>h non-billable)
4
+
5
+ ### By person
6
+ | Person | Hours | Billable | Entries |
7
+ |--------|-------|----------|---------|
8
+ | <name> | <n>h | <n>h | <n> |
9
+
10
+ ### Estimate accuracy (actual / estimated)
11
+ - <task>: <actual>h vs <estimated>h (<ratio>)
12
+
13
+ ### Anomalies
14
+ - <entry over 8h - forgot to stop timer?>
15
+ - <entries with no description>
16
+ - <running timers active for days>
17
+
18
+ ### Missing time
19
+ - <tasks that changed status with zero time logged>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[period and scope - e.g. 'last week', 'June for Sarah', 'this month billable only']"
9
- allowed-tools: Bash(clickup *), Write
9
+ allowed-tools: Bash(clickup *), Bash(node *), Write, Read
10
10
  ---
11
11
 
12
12
  # Timesheet Export
@@ -29,38 +29,37 @@ clickup time list --workspace-id <id> \
29
29
 
30
30
  `--start`/`--end` accept ISO dates (`2026-06-01`), relative forms (`-30d`), or Unix timestamps. Entry durations are milliseconds.
31
31
 
32
- ### Step 2: Aggregate
32
+ ### Step 2: Aggregate deterministically
33
33
 
34
- Compute from the JSON:
34
+ Never sum hours yourself. Pipe the entries through the bundled script; the math comes out exact:
35
35
 
36
- - Hours per person per day (duration ms / 3,600,000, round to 2 decimals)
37
- - Hours per task (join `task.id` to task names from the entries)
38
- - Billable vs non-billable split (the `billable` field)
39
- - Grand totals
36
+ ```bash
37
+ SKILL_DIR=$(clickup skill path clickup-timesheet-export)
38
+
39
+ # JSON summary: totals, byPerson, byTask, flagged entries
40
+ clickup time list --workspace-id <id> --start <start> --end <end> --format json \
41
+ | node "$SKILL_DIR/scripts/aggregate.mjs"
42
+
43
+ # CSV rows for the export file
44
+ clickup time list --workspace-id <id> --start <start> --end <end> --format json \
45
+ | node "$SKILL_DIR/scripts/aggregate.mjs" --csv
46
+ ```
47
+
48
+ The summary's `flags` field lists running timers (excluded from totals), entries with no task, and entries over 12h.
40
49
 
41
50
  ### Step 3: Write the export
42
51
 
43
52
  CSV for spreadsheets (default when the user says export/invoice):
44
53
 
45
- ```csv
46
- date,person,task,description,hours,billable
47
- 2026-06-30,Sarah,Login bug fix,Safari redirect,2.50,true
48
- ```
54
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-timesheet-export` prints it). Fill every placeholder; drop sections with no content.
49
55
 
50
56
  Save with the Write tool as `timesheet-<start>-<end>.csv` and tell the user the path. For a chat answer, use a markdown table plus totals instead.
51
57
 
52
58
  ### Step 4: Summarize
53
59
 
54
- ```
55
- Timesheet <start> to <end>
56
- - Total: N hours (M billable / K non-billable)
57
- - By person: Sarah 32.5h, Tom 28.0h, ...
58
- - Top tasks: <task> 12.5h, ...
59
- Flagged: entries with no task attached, running timers still open
60
- ```
60
+ Use the summary block from the same template.
61
61
 
62
62
  ## Tips
63
63
 
64
- - Entries with a null `end` are running timers; exclude them from totals and flag them.
65
64
  - Cross-check suspicious days (over 12h per person) rather than silently exporting them.
66
65
  - For a recurring client invoice, filter to the client's space via the task IDs' locations.
@@ -0,0 +1,9 @@
1
+ date,person,task,description,hours,billable
2
+ 2026-06-30,Sarah,Login bug fix,Safari redirect,2.50,true
3
+
4
+ <!-- Summary block to accompany the CSV: -->
5
+ Timesheet <start> to <end>
6
+ - Total: <n> hours (<m> billable / <k> non-billable)
7
+ - By person: <name> <n>h, ...
8
+ - Top tasks: <task> <n>h, ...
9
+ Flagged: <entries with no task, running timers still open>
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic timesheet aggregation. Pipe `clickup time list ... --format json`
3
+ // into this script; totals come out exact so no arithmetic is done by the agent.
4
+ //
5
+ // clickup time list --workspace-id X --start -7d --end today --format json \
6
+ // | node scripts/aggregate.mjs [--csv]
7
+ //
8
+ // Default output: JSON summary (totals, byPerson, byTask, flags).
9
+ // --csv: timesheet rows (date,person,task,description,hours,billable) on stdout.
10
+
11
+ import { readFileSync } from 'node:fs'
12
+
13
+ const wantCsv = process.argv.includes('--csv')
14
+ const raw = readFileSync(0, 'utf-8')
15
+ let entries = JSON.parse(raw)
16
+ if (!Array.isArray(entries)) entries = entries.data ?? [entries]
17
+
18
+ const MS_PER_HOUR = 3_600_000
19
+ const hours = (ms) => Math.round((ms / MS_PER_HOUR) * 100) / 100
20
+ const rows = []
21
+ const flags = { runningTimers: [], noTask: [], over12h: [] }
22
+ const byPerson = {}
23
+ const byTask = {}
24
+ let totalMs = 0
25
+ let billableMs = 0
26
+
27
+ for (const e of entries) {
28
+ const duration = Number(e.duration)
29
+ const person = e.user_name || e.user?.username || 'unknown'
30
+ const task = e.task?.name || e.task_id || e.task?.id || ''
31
+ const desc = e.description || ''
32
+ const billable = e.billable === true || e.billable === 'true'
33
+
34
+ if (!e.end || duration < 0) {
35
+ flags.runningTimers.push({ id: e.id, person, task })
36
+ continue
37
+ }
38
+ if (!task) flags.noTask.push({ id: e.id, person, hours: hours(duration) })
39
+ if (duration > 12 * MS_PER_HOUR) flags.over12h.push({ id: e.id, person, task, hours: hours(duration) })
40
+
41
+ totalMs += duration
42
+ if (billable) billableMs += duration
43
+ byPerson[person] = (byPerson[person] ?? 0) + duration
44
+ byTask[task || '(no task)'] = (byTask[task || '(no task)'] ?? 0) + duration
45
+
46
+ const date = new Date(Number(e.start)).toISOString().slice(0, 10)
47
+ rows.push({ date, person, task, desc, hours: hours(duration), billable })
48
+ }
49
+
50
+ if (wantCsv) {
51
+ const esc = (v) => (/[",\n]/.test(String(v)) ? `"${String(v).replace(/"/g, '""')}"` : String(v))
52
+ process.stdout.write('date,person,task,description,hours,billable\n')
53
+ for (const r of rows.sort((a, b) => a.date.localeCompare(b.date) || a.person.localeCompare(b.person))) {
54
+ process.stdout.write([r.date, r.person, r.task, r.desc, r.hours.toFixed(2), r.billable].map(esc).join(',') + '\n')
55
+ }
56
+ } else {
57
+ const toHours = (o) => Object.fromEntries(Object.entries(o).sort((a, b) => b[1] - a[1]).map(([k, v]) => [k, hours(v)]))
58
+ process.stdout.write(
59
+ JSON.stringify(
60
+ {
61
+ entries: rows.length,
62
+ totalHours: hours(totalMs),
63
+ billableHours: hours(billableMs),
64
+ nonBillableHours: hours(totalMs - billableMs),
65
+ byPerson: toHours(byPerson),
66
+ byTask: toHours(byTask),
67
+ flags,
68
+ },
69
+ null,
70
+ 2,
71
+ ) + '\n',
72
+ )
73
+ }
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[scope - e.g. 'marketing team', space-id, or workspace-id]"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Read
10
10
  ---
11
11
 
12
12
  # Weekly Review
@@ -82,6 +82,8 @@ clickup goal list --workspace-id <id> --format json
82
82
 
83
83
  ### Step 7: Compile the report
84
84
 
85
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-weekly-review` prints it). Fill every placeholder; drop sections with no content.
86
+
85
87
  Summarize the data into a clear report with sections:
86
88
  - **Completed this week**: Count and highlights of finished tasks
87
89
  - **In progress**: Tasks actively being worked on
@@ -0,0 +1,26 @@
1
+ # Weekly Review: <scope> (<week-start> to <week-end>)
2
+
3
+ **Summary:** <2-3 sentences: overall trajectory, the one thing to know>
4
+
5
+ ## By the numbers
6
+ | Metric | This week |
7
+ |--------|-----------|
8
+ | Completed | <n> |
9
+ | In progress | <n> |
10
+ | Overdue | <n> |
11
+ | Hours logged | <n> |
12
+
13
+ ## Completed this week
14
+ - <task name> (<assignee>)
15
+
16
+ ## In progress
17
+ - <task name> - <assignee>, due <date>
18
+
19
+ ## Overdue / Blocked
20
+ - <task name> - due <date>, <why it matters>
21
+
22
+ ## Goal progress
23
+ - <goal>: <current>/<target> (<status>)
24
+
25
+ ## Next week
26
+ - <what is due or planned>
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  context: fork
7
7
  agent: general-purpose
8
8
  argument-hint: "[scope - workspace, space name, or list name; optionally 'and fix']"
9
- allowed-tools: Bash(clickup *)
9
+ allowed-tools: Bash(clickup *), Bash(node *), Read
10
10
  ---
11
11
 
12
12
  # Workspace Audit
@@ -27,9 +27,17 @@ clickup task search --workspace-id <id> --format json
27
27
 
28
28
  Server-side filters cannot express "no assignee" or "no due date", so fetch active tasks and inspect the JSON client-side. For large workspaces sweep one space at a time (`--space-id`).
29
29
 
30
- ### Step 2: Detect problems
30
+ ### Step 2: Classify deterministically
31
31
 
32
- Classify each task into any of:
32
+ Pipe the tasks through the bundled script so every task is bucketed by rule, not judgment:
33
+
34
+ ```bash
35
+ SKILL_DIR=$(clickup skill path clickup-workspace-audit)
36
+ clickup task search --workspace-id <id> --format json \
37
+ | node "$SKILL_DIR/scripts/classify.mjs" [--stale-days 14]
38
+ ```
39
+
40
+ The output contains `activeTasks`, `flaggedTasks`, per-bucket lists, and per-person `load`. The buckets:
33
41
 
34
42
  | Check | Condition |
35
43
  |-------|-----------|
@@ -45,23 +53,7 @@ Tasks with `blocked` or `waiting` status whose blockers are themselves overdue d
45
53
 
46
54
  ### Step 4: Report
47
55
 
48
- ```
49
- ## Workspace Audit: <scope> (<date>)
50
-
51
- Overall: N active tasks, M flagged (X%)
52
-
53
- ### Overdue (worst first)
54
- - <task> - due <date>, assignee <name> (<id>)
55
-
56
- ### Unassigned / No due date / Stale
57
- - ...
58
-
59
- ### Load
60
- - <name>: N open tasks (team median: M)
61
-
62
- ### Recommended fixes
63
- 1. ...
64
- ```
56
+ Copy the exact structure from `assets/report-template.md` in this skill's directory (`clickup skill path clickup-workspace-audit` prints it). Fill every placeholder; drop sections with no content.
65
57
 
66
58
  ### Step 5: Apply fixes (only when asked)
67
59
 
@@ -0,0 +1,21 @@
1
+ ## Workspace Audit: <scope> (<date>)
2
+
3
+ Overall: <n> active tasks, <m> flagged (<x>%)
4
+
5
+ ### Overdue (worst first)
6
+ - <task> - due <date>, assignee <name> (<id>)
7
+
8
+ ### Unassigned
9
+ - <task> (<list>)
10
+
11
+ ### No due date
12
+ - <task> (<list>)
13
+
14
+ ### Stale (in progress, untouched 14+ days)
15
+ - <task> - last touched <date>
16
+
17
+ ### Load
18
+ - <name>: <n> open tasks (team median: <m>)
19
+
20
+ ### Recommended fixes
21
+ 1. <specific action with the command to run>
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic hygiene classification. Pipe `clickup task search ... --format json`
3
+ // into this script; every task is bucketed by rule, not judgment.
4
+ //
5
+ // clickup task search --workspace-id X --format json | node scripts/classify.mjs
6
+ //
7
+ // Output: JSON buckets (overdue, unassigned, noDueDate, stale, load) plus counts.
8
+ // --stale-days <n> overrides the 14-day staleness window.
9
+
10
+ import { readFileSync } from 'node:fs'
11
+
12
+ const staleIdx = process.argv.indexOf('--stale-days')
13
+ const STALE_DAYS = staleIdx !== -1 ? Number(process.argv[staleIdx + 1]) : 14
14
+ const now = Date.now()
15
+ const staleCutoff = now - STALE_DAYS * 24 * 3_600_000
16
+
17
+ const raw = readFileSync(0, 'utf-8')
18
+ let tasks = JSON.parse(raw)
19
+ if (!Array.isArray(tasks)) tasks = tasks.tasks ?? [tasks]
20
+
21
+ const summarize = (t) => ({
22
+ id: t.id,
23
+ name: t.name,
24
+ status: typeof t.status === 'object' ? t.status?.status : t.status,
25
+ assignees: (t.assignees ?? []).map((a) => a.username ?? a.id),
26
+ due_date: t.due_date ? new Date(Number(t.due_date)).toISOString().slice(0, 10) : null,
27
+ list: t.list?.name ?? null,
28
+ })
29
+
30
+ const buckets = { overdue: [], unassigned: [], noDueDate: [], stale: [] }
31
+ const load = {}
32
+ const isClosed = (t) => {
33
+ const type = typeof t.status === 'object' ? t.status?.type : undefined
34
+ const s = (typeof t.status === 'object' ? t.status?.status : t.status ?? '').toLowerCase()
35
+ return type === 'closed' || type === 'done' || ['closed', 'complete', 'done'].includes(s)
36
+ }
37
+
38
+ let active = 0
39
+ for (const t of tasks) {
40
+ if (isClosed(t)) continue
41
+ active++
42
+ const s = summarize(t)
43
+ for (const a of s.assignees) load[a] = (load[a] ?? 0) + 1
44
+
45
+ if (s.assignees.length === 0) buckets.unassigned.push(s)
46
+ if (!t.due_date) buckets.noDueDate.push(s)
47
+ else if (Number(t.due_date) < now) buckets.overdue.push(s)
48
+
49
+ const statusType = typeof t.status === 'object' ? t.status?.type : undefined
50
+ const updated = Number(t.date_updated)
51
+ if (statusType === 'custom' || /progress|review|doing/.test((s.status ?? '').toLowerCase())) {
52
+ if (updated && updated < staleCutoff) buckets.stale.push({ ...s, last_updated: new Date(updated).toISOString().slice(0, 10) })
53
+ }
54
+ }
55
+
56
+ buckets.overdue.sort((a, b) => (a.due_date ?? '').localeCompare(b.due_date ?? ''))
57
+ const flagged = new Set([...buckets.overdue, ...buckets.unassigned, ...buckets.noDueDate, ...buckets.stale].map((t) => t.id))
58
+
59
+ process.stdout.write(
60
+ JSON.stringify(
61
+ {
62
+ activeTasks: active,
63
+ flaggedTasks: flagged.size,
64
+ staleDays: STALE_DAYS,
65
+ counts: Object.fromEntries(Object.entries(buckets).map(([k, v]) => [k, v.length])),
66
+ load: Object.fromEntries(Object.entries(load).sort((a, b) => b[1] - a[1])),
67
+ ...buckets,
68
+ },
69
+ null,
70
+ 2,
71
+ ) + '\n',
72
+ )