Add task set creation skill + ClickUp dependency support

Add add_dependency() to ClickUpClient and clickup_add_dependency chat tool
for setting "blocked by" relationships between tasks. New create-task-set
skill enables bundled task creation (LINKS, PR, NEW, OPT) with correct
fields, time estimates, due date offsets, and automatic dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clickup-runner
PeninsulaInd 2026-04-01 15:23:53 -05:00
parent 99b000f25a
commit 935ab2d772
3 changed files with 190 additions and 0 deletions

View File

@ -500,6 +500,32 @@ class ClickUpClient:
) )
return False return False
def add_dependency(self, task_id: str, depends_on: str) -> bool:
"""Add a 'blocked by' dependency: *task_id* is blocked by *depends_on*.
Uses POST /task/{task_id}/dependency with {"depends_on": ...}.
"""
try:
def _call():
resp = self._client.post(
f"/task/{task_id}/dependency",
json={"depends_on": depends_on},
)
resp.raise_for_status()
return resp
self._retry(_call)
log.info(
"Added dependency: task %s blocked by %s", task_id, depends_on
)
return True
except (httpx.TransportError, httpx.HTTPStatusError) as e:
log.error(
"Failed to add dependency on task %s: %s", task_id, e
)
return False
def get_custom_field_by_name(self, task_id: str, field_name: str) -> Any: def get_custom_field_by_name(self, task_id: str, field_name: str) -> Any:
"""Read a custom field value from a task by field name. """Read a custom field value from a task by field name.

View File

@ -255,6 +255,39 @@ def clickup_create_task(
client_obj.close() client_obj.close()
@tool(
"clickup_add_dependency",
"Add a 'blocked by' dependency between two ClickUp tasks. "
"The blocked_task_id will be blocked by blocker_task_id "
"(i.e. blocker must complete before blocked can start).",
category="clickup",
)
def clickup_add_dependency(
blocked_task_id: str,
blocker_task_id: str,
ctx: dict | None = None,
) -> str:
"""Set blocked_task_id as blocked by blocker_task_id."""
client = _get_clickup_client(ctx)
if not client:
return "Error: ClickUp API token not configured."
try:
ok = client.add_dependency(blocked_task_id, depends_on=blocker_task_id)
if ok:
return (
f"Dependency added: task {blocked_task_id} "
f"is now blocked by {blocker_task_id}."
)
return (
f"Failed to add dependency. Check that both task IDs are valid."
)
except Exception as e:
return f"Error adding dependency: {e}"
finally:
client.close()
@tool( @tool(
"clickup_reset_task", "clickup_reset_task",
"Reset a ClickUp task to 'to do' status so it can be retried on the next poll. " "Reset a ClickUp task to 'to do' status so it can be retried on the next poll. "

View File

@ -0,0 +1,131 @@
---
name: create-task-set
description: Create a bundled set of ClickUp tasks (LINKS, PR, NEW, OPT) for a client keyword. Use when the user asks to create a task set, task bundle, or multiple related SEO tasks at once.
tools: [clickup_create_task, clickup_add_dependency]
agents: [default]
---
# Create Task Set
Creates a coordinated set of ClickUp tasks for a client keyword. The user specifies which task types to create and you build them with the correct fields, time estimates, due dates, and dependencies.
## Task Types
| Code | Task Name | Work Category | Time Estimate |
|------|-----------|--------------|---------------|
| LINKS | `LINKS - {keyword}` | Link Building | 2h 30m (9000000 ms) |
| PR | `PR - {keyword}` | Press Release | 1h 30m (5400000 ms) |
| NEW | `NEW - {keyword}` | Content Creation | 4h (14400000 ms) |
| OPT | `OPT - {keyword}` | On Page Optimization | 2h (7200000 ms) |
## Required Information (ask if not provided)
- **client**: Client/folder name (e.g. "RPM Rubber")
- **keyword**: The SEO keyword (e.g. "rubber gaskets")
- **types**: Which tasks to create -- any combination of LINKS, PR, NEW, OPT
- **due_date**: Due date for the content task (YYYY-MM-DD)
- **tag**: Month tag (e.g. "apr26")
## Conditionally Required
- **pr_topic**: Required ONLY when PR is in the set. The press release topic (different from keyword).
- **url**: The IMSURL. Required when OPT is in the set or when LINKS/PR are in the set WITHOUT a NEW task. If NEW is in the set and no URL is provided, LINKS and PR are blocked by the NEW task.
## Optional
- **articles**: Number of tier-1 articles for LINKS task. Sets CLIFlags to `--tier-1 {N}`.
- **anchors**: Custom anchor texts for LINKS task. Comma-delimited string goes into CustomAnchors field.
- **priority**: Default is 2 (High).
- **assignee**: Default is 10765627 (Bryan Bigari).
## Common Fields (set on ALL tasks)
Every task gets these fields set after creation:
- **Client**: Set to the client name (dropdown)
- **Delegate to Claude**: Set to `true` (checkbox)
- **Tag**: The month tag
- **Priority**: High (2) unless overridden
- **Assignee**: Bryan unless overridden
## Due Date Logic
- The content task (NEW or OPT) gets the provided due date.
- LINKS and PR get due date + 7 days (same day of week, one week later).
- If there is no content task in the set, all tasks get the provided due date.
## Dependency Logic
When NEW is in the set and no URL is provided:
- Create the NEW task FIRST.
- For each LINKS and PR task in the set, call `clickup_add_dependency` with `blocked_task_id` = the LINKS/PR task ID and `blocker_task_id` = the NEW task ID.
- Do NOT set IMSURL on LINKS or PR since the URL does not exist yet.
When a URL is provided (or OPT is used instead of NEW):
- Set IMSURL on all tasks that have the field.
- No dependencies needed.
## Field Reference Per Task Type
### LINKS (Link Building)
- Work Category: "Link Building"
- LB Method: "Cora Backlinks"
- BrandedPlusRatio: "0.80"
- IMSURL: the url (if available)
- CLIFlags: `--tier-1 {N}` (only if articles specified)
- CustomAnchors: comma-delimited anchors (only if provided)
- Time estimate: 9000000 ms
### PR (Press Release)
- Work Category: "Press Release"
- PR Topic: the pr_topic value
- IMSURL: the url (if available)
- Time estimate: 5400000 ms
### NEW (Content Creation)
- Work Category: "Content Creation"
- IMSURL: not set (URL does not exist yet)
- Time estimate: 14400000 ms
### OPT (On Page Optimization)
- Work Category: "On Page Optimization"
- IMSURL: the url (required)
- Time estimate: 7200000 ms
## Execution Steps
1. Confirm you have all required info for the requested task types. Ask for anything missing.
2. Convert the due date to Unix milliseconds (noon UTC): parse YYYY-MM-DD, set to 12:00 UTC, multiply epoch seconds by 1000.
3. Calculate the +7 day due date for LINKS/PR if a content task is in the set.
4. If NEW is in the set, create it first (you need its task ID for dependencies).
5. Create each task using `clickup_create_task` with:
- name: `{TYPE} - {keyword}`
- client: the client name
- work_category: per the table above
- status: "to do"
- due_date: the appropriate Unix ms value
- tags: the tag value
- priority: 2 (or override)
- assignee: 10765627 (or override)
- time_estimate_ms: per the table above
- custom_fields_json: JSON object with all type-specific fields plus `{"Delegate to Claude": true}`
6. If NEW is in the set with no URL, call `clickup_add_dependency` for each LINKS/PR task.
7. Report back with a summary: task names, IDs, URLs, and any dependencies set.
## Examples
User: "Create LINKS, PR, NEW for RPM Rubber, keyword rubber gaskets, due April 15, tag apr26, pr topic RPM Rubber launches new gasket product line"
-> Create 3 tasks:
1. `NEW - rubber gaskets` (Content Creation, due 2026-04-15, 4h)
2. `LINKS - rubber gaskets` (Link Building, due 2026-04-22, 2.5h, blocked by NEW)
3. `PR - rubber gaskets` (Press Release, due 2026-04-22, 1.5h, blocked by NEW)
User: "Create OPT, LINKS for RPM Rubber, keyword rubber seals, url https://rpmrubber.com/rubber-seals, due April 10, tag apr26"
-> Create 2 tasks:
1. `OPT - rubber seals` (On Page Optimization, due 2026-04-10, 2h, IMSURL set)
2. `LINKS - rubber seals` (Link Building, due 2026-04-17, 2.5h, IMSURL set)
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`