Fix --tier1-count CLI flag + set Stage on task creation + update docs

- Fix --tier-1 -> --tier1-count in create_task_set.py and create-task-set.md skill
- Set initial Stage field when creating tasks (run_cora for most, draft for PR)
- Add Quick Reference section to clickup_runner README with stage tables
- Add Stage field docs to clickup-task-creation.md
- Remove stale xlsx_urls tests from test_claude_runner.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clickup-runner
PeninsulaInd 2026-04-09 10:17:52 -05:00
parent e4c7bc8e02
commit e0388b1f6f
5 changed files with 80 additions and 19 deletions

View File

@ -16,6 +16,44 @@ export CLICKUP_SPACE_ID="..."
uv run python -m clickup_runner uv run python -m clickup_runner
``` ```
## Quick Reference: Running a Task
To delegate a task to the runner, it needs **all three fields** set or it will be skipped:
1. **Work Category** -- which task type (dropdown)
2. **Stage** -- which pipeline step to run (dropdown)
3. **Delegate to Claude** -- checked (checkbox)
Plus the task must have a **due date <= today** and be in an **"Overall" list**.
### Initial Stage by Task Type
| Task Type | First Stage | What it does | Full pipeline |
|-----------|-------------|--------------|---------------|
| Content Creation | `run_cora` | Submits Cora analysis | `run_cora` -> `outline` -> `draft` |
| On Page Optimization | `run_cora` | Submits Cora analysis | `run_cora` -> `outline` -> `draft` -> `hidden div` |
| Press Release | `draft` | Writes full PR + schemas | `draft` (single stage) |
| Link Building | `run_cora` | Submits Cora analysis | `run_cora` -> `build` |
### After Each Stage
The runner **unchecks** "Delegate to Claude" and sets status to **Review** so you can check the output. To continue to the next stage, review the output, then re-check "Delegate to Claude". The Stage field is advanced automatically.
### Required Custom Fields by Task Type
| Task Type | Required Fields |
|-----------|----------------|
| Content Creation | Keyword, IMSURL (for run_cora) |
| On Page Optimization | Keyword, IMSURL (for run_cora) |
| Press Release | Keyword, IMSURL |
| Link Building | Keyword, IMSURL, CLIFlags (`--tier1-count N`) |
### If Something Goes Wrong
The runner sets the **Error** checkbox, posts a comment explaining what failed and how to fix it, unchecks "Delegate to Claude", and sets status to **Review**. Fix the issue, then re-check "Delegate to Claude" to retry.
---
## How It Works ## How It Works
1. Every 720 seconds, polls all "Overall" lists in the ClickUp space 1. Every 720 seconds, polls all "Overall" lists in the ClickUp space

View File

@ -5,7 +5,8 @@
```bash ```bash
uv run python scripts/create_clickup_task.py --name "LINKS - keyword" --client "Client Name" \ uv run python scripts/create_clickup_task.py --name "LINKS - keyword" --client "Client Name" \
--category "Link Building" --due-date 2026-03-18 --tag mar26 --time-estimate 2h \ --category "Link Building" --due-date 2026-03-18 --tag mar26 --time-estimate 2h \
--field "Keyword=keyword" --field "IMSURL=https://example.com" --field "LB Method=Cora Backlinks" --field "Keyword=keyword" --field "IMSURL=https://example.com" --field "LB Method=Cora Backlinks" \
--dependency TASK_ID
``` ```
## Defaults ## Defaults
@ -20,11 +21,23 @@ uv run python scripts/create_clickup_task.py --name "LINKS - keyword" --client "
Any field can be set via `--field "Name=Value"`. Dropdowns are auto-resolved by name (case-insensitive). Any field can be set via `--field "Name=Value"`. Dropdowns are auto-resolved by name (case-insensitive).
## Stage Field (Required for ClickUp Runner)
Every task must have a **Stage** set or the runner will skip it. Set Stage to the first stage for the task type:
| Task Type | Initial Stage |
|-----------|---------------|
| Content Creation | `run_cora` |
| On Page Optimization | `run_cora` |
| Press Release | `draft` |
| Link Building | `run_cora` |
## Task Types ## Task Types
### Link Building ### Link Building
- **Prefix**: `LINKS - {keyword}` - **Prefix**: `LINKS - {keyword}`
- **Work Category**: "Link Building" - **Work Category**: "Link Building"
- **Stage**: `run_cora`
- **Required fields**: Keyword, IMSURL - **Required fields**: Keyword, IMSURL
- **LB Method**: default "Cora Backlinks" - **LB Method**: default "Cora Backlinks"
- **CLIFlags**: only add `--tier1-count N` when count is specified - **CLIFlags**: only add `--tier1-count N` when count is specified
@ -35,6 +48,7 @@ Any field can be set via `--field "Name=Value"`. Dropdowns are auto-resolved by
### On Page Optimization ### On Page Optimization
- **Prefix**: `OPT - {keyword}` - **Prefix**: `OPT - {keyword}`
- **Work Category**: "On Page Optimization" - **Work Category**: "On Page Optimization"
- **Stage**: `run_cora`
- **Required fields**: Keyword, IMSURL - **Required fields**: Keyword, IMSURL
- **time estimate**: 3h - **time estimate**: 3h
- -
@ -42,13 +56,15 @@ Any field can be set via `--field "Name=Value"`. Dropdowns are auto-resolved by
### Content Creation ### Content Creation
- **Prefix**: `CREATE - {keyword}` - **Prefix**: `CREATE - {keyword}`
- **Work Category**: "Content Creation" - **Work Category**: "Content Creation"
- **Stage**: `run_cora`
- **Required fields**: Keyword - **Required fields**: Keyword
- **time estimate**: 4h - **time estimate**: 4h
### Press Release ### Press Release
- **Prefix**: `PR - {keyword}` - **Prefix**: `PR - {keyword}`
- **Required fields**: Keyword, IMSURL
- **Work Category**: "Press Release" - **Work Category**: "Press Release"
- **Stage**: `draft`
- **Required fields**: Keyword, IMSURL
- **PR Topic**: if not provided, ask if there is a topic. it can be blank if they respond with none. - **PR Topic**: if not provided, ask if there is a topic. it can be blank if they respond with none.
- **time estimate**: 1.5h - **time estimate**: 1.5h
- **Headline tone trigger words**: By default, the PR writer assumes the company already offers the capability (awareness tone). To get announcement-style headlines (Announces, Launches, Introduces), include one of these words in the task name or PR Topic: - **Headline tone trigger words**: By default, the PR writer assumes the company already offers the capability (awareness tone). To get announcement-style headlines (Announces, Launches, Introduces), include one of these words in the task name or PR Topic:
@ -61,6 +77,12 @@ Any field can be set via `--field "Name=Value"`. Dropdowns are auto-resolved by
The `clickup_create_task` tool provides the same capabilities via CheddahBot UI. Arbitrary custom fields are passed as JSON via `custom_fields_json`. The `clickup_create_task` tool provides the same capabilities via CheddahBot UI. Arbitrary custom fields are passed as JSON via `custom_fields_json`.
## Dependencies and Due Dates
When a CREATE (NEW) task exists, LINKS and PR tasks that need the new page URL should be **blocked by** the CREATE task using `--dependency`. Their due date should be **one week after** the CREATE task's due date. For example, if CREATE is due 4/9, the LINKS and PR tasks should be due 4/16.
OPT tasks already have a URL, so LINKS paired with an OPT does **not** need a dependency -- just the +1 week due date offset.
## Client Folder Lookup ## Client Folder Lookup
Tasks are created in the "Overall" list inside the client's folder. Folder name is matched case-insensitively. Tasks are created in the "Overall" list inside the client's folder. Folder name is matched case-insensitively.

View File

@ -50,6 +50,13 @@ WORK_CATEGORIES = {
"PR": "Press Release", "PR": "Press Release",
} }
INITIAL_STAGES = {
"NEW": "run_cora",
"OPT": "run_cora",
"LINKS": "run_cora",
"PR": "draft",
}
def _date_to_unix_ms(date_str: str) -> int: def _date_to_unix_ms(date_str: str) -> int:
"""Convert YYYY-MM-DD to Unix milliseconds (noon UTC).""" """Convert YYYY-MM-DD to Unix milliseconds (noon UTC)."""
@ -199,6 +206,9 @@ def main():
client.set_custom_field_smart( client.set_custom_field_smart(
task_id, list_id, "Work Category", WORK_CATEGORIES[task_type] task_id, list_id, "Work Category", WORK_CATEGORIES[task_type]
) )
client.set_custom_field_smart(
task_id, list_id, "Stage", INITIAL_STAGES[task_type]
)
client.set_custom_field_smart(task_id, list_id, "Delegate to Claude", "true") client.set_custom_field_smart(task_id, list_id, "Delegate to Claude", "true")
# IMSURL # IMSURL
@ -211,7 +221,7 @@ def main():
client.set_custom_field_smart(task_id, list_id, "BrandedPlusRatio", "0.80") client.set_custom_field_smart(task_id, list_id, "BrandedPlusRatio", "0.80")
if args.articles: if args.articles:
client.set_custom_field_smart( client.set_custom_field_smart(
task_id, list_id, "CLIFlags", f"--tier-1 {args.articles}" task_id, list_id, "CLIFlags", f"--tier1-count {args.articles}"
) )
if args.anchors: if args.anchors:
client.set_custom_field_smart( client.set_custom_field_smart(

View File

@ -33,7 +33,7 @@ Creates a coordinated set of ClickUp tasks for a client keyword. The user specif
## Optional ## Optional
- **articles**: Number of tier-1 articles for LINKS task. Sets CLIFlags to `--tier-1 {N}`. - **articles**: Number of tier-1 articles for LINKS task. Sets CLIFlags to `--tier1-count {N}`.
- **anchors**: Custom anchor texts for LINKS task. Comma-delimited string goes into CustomAnchors field. - **anchors**: Custom anchor texts for LINKS task. Comma-delimited string goes into CustomAnchors field.
- **priority**: Default is 2 (High). - **priority**: Default is 2 (High).
- **assignee**: Default is 10765627 (Bryan Bigari). - **assignee**: Default is 10765627 (Bryan Bigari).
@ -68,26 +68,30 @@ When a URL is provided (or OPT is used instead of NEW):
### LINKS (Link Building) ### LINKS (Link Building)
- Work Category: "Link Building" - Work Category: "Link Building"
- Stage: "run_cora"
- LB Method: "Cora Backlinks" - LB Method: "Cora Backlinks"
- BrandedPlusRatio: "0.80" - BrandedPlusRatio: "0.80"
- IMSURL: the url (if available) - IMSURL: the url (if available)
- CLIFlags: `--tier-1 {N}` (only if articles specified) - CLIFlags: `--tier1-count {N}` (only if articles specified)
- CustomAnchors: comma-delimited anchors (only if provided) - CustomAnchors: comma-delimited anchors (only if provided)
- Time estimate: 9000000 ms - Time estimate: 9000000 ms
### PR (Press Release) ### PR (Press Release)
- Work Category: "Press Release" - Work Category: "Press Release"
- Stage: "draft"
- PR Topic: the pr_topic value - PR Topic: the pr_topic value
- IMSURL: the url (if available) - IMSURL: the url (if available)
- Time estimate: 5400000 ms - Time estimate: 5400000 ms
### NEW (Content Creation) ### NEW (Content Creation)
- Work Category: "Content Creation" - Work Category: "Content Creation"
- Stage: "run_cora"
- IMSURL: not set (URL does not exist yet) - IMSURL: not set (URL does not exist yet)
- Time estimate: 14400000 ms - Time estimate: 14400000 ms
### OPT (On Page Optimization) ### OPT (On Page Optimization)
- Work Category: "On Page Optimization" - Work Category: "On Page Optimization"
- Stage: "run_cora"
- IMSURL: the url (required) - IMSURL: the url (required)
- Time estimate: 7200000 ms - Time estimate: 7200000 ms
@ -128,4 +132,4 @@ User: "Create OPT, LINKS for RPM Rubber, keyword rubber seals, url https://rpmru
User: "Create LINKS, PR, NEW for RPM Rubber, keyword rubber gaskets, due April 15, tag apr26, pr topic RPM launches gasket line, 4 articles, anchors rubber gaskets,custom rubber gaskets,industrial rubber gaskets" User: "Create LINKS, PR, NEW for RPM Rubber, keyword rubber gaskets, due April 15, tag apr26, pr topic RPM launches gasket line, 4 articles, anchors rubber gaskets,custom rubber gaskets,industrial rubber gaskets"
-> Same as first example but LINKS also gets CLIFlags=`--tier-1 4` and CustomAnchors=`rubber gaskets,custom rubber gaskets,industrial rubber gaskets` -> Same as first example but LINKS also gets CLIFlags=`--tier1-count 4` and CustomAnchors=`rubber gaskets,custom rubber gaskets,industrial rubber gaskets`

View File

@ -104,19 +104,6 @@ class TestBuildPrompt:
prompt = build_prompt(task, route, "skill content") prompt = build_prompt(task, route, "skill content")
assert "Write about blue widgets" in prompt assert "Write about blue widgets" in prompt
def test_includes_xlsx_urls(self):
task = _make_task()
route = _make_route()
urls = ["https://cdn.clickup.com/report.xlsx"]
prompt = build_prompt(task, route, "skill", xlsx_urls=urls)
assert "https://cdn.clickup.com/report.xlsx" in prompt
assert "Cora Reports" in prompt
def test_no_xlsx_section_when_none(self):
task = _make_task()
route = _make_route()
prompt = build_prompt(task, route, "skill")
assert "Cora Reports" not in prompt
def test_no_customer_when_missing(self): def test_no_customer_when_missing(self):
task = _make_task(custom_fields={}) task = _make_task(custom_fields={})