From 935ab2d772a2544bfcb4d2bf9becbf465ecc34ce Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Wed, 1 Apr 2026 15:23:53 -0500 Subject: [PATCH] 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) --- cheddahbot/clickup.py | 26 ++++++ cheddahbot/tools/clickup_tool.py | 33 ++++++++ skills/create-task-set.md | 131 +++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 skills/create-task-set.md diff --git a/cheddahbot/clickup.py b/cheddahbot/clickup.py index 7fe0972..70739c6 100644 --- a/cheddahbot/clickup.py +++ b/cheddahbot/clickup.py @@ -500,6 +500,32 @@ class ClickUpClient: ) 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: """Read a custom field value from a task by field name. diff --git a/cheddahbot/tools/clickup_tool.py b/cheddahbot/tools/clickup_tool.py index 137d891..a77e2e7 100644 --- a/cheddahbot/tools/clickup_tool.py +++ b/cheddahbot/tools/clickup_tool.py @@ -255,6 +255,39 @@ def clickup_create_task( 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( "clickup_reset_task", "Reset a ClickUp task to 'to do' status so it can be retried on the next poll. " diff --git a/skills/create-task-set.md b/skills/create-task-set.md new file mode 100644 index 0000000..17b1771 --- /dev/null +++ b/skills/create-task-set.md @@ -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`