CheddahBot/scripts/create_clickup_task.py

185 lines
5.6 KiB
Python

"""CLI script to create a ClickUp task in a client's Overall list.
Usage:
uv run python scripts/create_clickup_task.py --name "Task" --client "Client"
uv run python scripts/create_clickup_task.py --name "LB" --client "Acme" \\
--category "Link Building" --due-date 2026-03-11 --tag feb26 \\
--field "Keyword=some keyword" --field "CLIFlags=--tier1-count 5"
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from datetime import UTC, datetime
from pathlib import Path
# Add project root to path so we can import cheddahbot
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from dotenv import load_dotenv
from cheddahbot.clickup import ClickUpClient
DEFAULT_ASSIGNEE = 10765627 # Bryan Bigari
def _date_to_unix_ms(date_str: str) -> int:
"""Convert YYYY-MM-DD to Unix milliseconds (noon UTC).
Noon UTC ensures the date displays correctly in US timezones.
"""
dt = datetime.strptime(date_str, "%Y-%m-%d").replace(
hour=12, tzinfo=UTC
)
return int(dt.timestamp() * 1000)
def _parse_time_estimate(s: str) -> int:
"""Parse a human time string like '2h', '30m', '1h30m' to ms."""
import re
total_min = 0
match = re.match(r"(?:(\d+)h)?(?:(\d+)m)?$", s.strip())
if not match or not any(match.groups()):
raise ValueError(f"Invalid time estimate: '{s}' (use e.g. '2h', '30m', '1h30m')")
if match.group(1):
total_min += int(match.group(1)) * 60
if match.group(2):
total_min += int(match.group(2))
return total_min * 60 * 1000
def main():
load_dotenv()
parser = argparse.ArgumentParser(description="Create a ClickUp task")
parser.add_argument("--name", required=True, help="Task name")
parser.add_argument(
"--client", required=True, help="Client folder name"
)
parser.add_argument(
"--category", default="", help="Work Category dropdown value"
)
parser.add_argument("--description", default="", help="Task description")
parser.add_argument(
"--status", default="to do", help="Initial status (default: 'to do')"
)
parser.add_argument(
"--due-date", default="", help="Due date as YYYY-MM-DD"
)
parser.add_argument(
"--tag", action="append", default=[], help="Tag (mmmYY, repeatable)"
)
parser.add_argument(
"--field",
action="append",
default=[],
help="Custom field as Name=Value (repeatable)",
)
parser.add_argument(
"--priority",
type=int,
default=2,
help="Priority: 1=Urgent, 2=High, 3=Normal, 4=Low (default: 2)",
)
parser.add_argument(
"--assignee",
type=int,
action="append",
default=[],
help="ClickUp user ID (default: Bryan 10765627)",
)
parser.add_argument(
"--time-estimate",
default="",
help="Time estimate (e.g. '2h', '30m', '1h30m')",
)
args = parser.parse_args()
api_token = os.environ.get("CLICKUP_API_TOKEN", "")
space_id = os.environ.get("CLICKUP_SPACE_ID", "")
if not api_token:
print("Error: CLICKUP_API_TOKEN not set", file=sys.stderr)
sys.exit(1)
if not space_id:
print("Error: CLICKUP_SPACE_ID not set", file=sys.stderr)
sys.exit(1)
# Parse custom fields
custom_fields: dict[str, str] = {}
for f in args.field:
if "=" not in f:
print(f"Error: --field must be Name=Value, got: {f}", file=sys.stderr)
sys.exit(1)
name, value = f.split("=", 1)
custom_fields[name] = value
client = ClickUpClient(api_token=api_token)
try:
# Find the client's Overall list
list_id = client.find_list_in_folder(space_id, args.client)
if not list_id:
msg = f"Error: No folder '{args.client}' with 'Overall' list"
print(msg, file=sys.stderr)
sys.exit(1)
# Build create_task kwargs
create_kwargs: dict = {
"list_id": list_id,
"name": args.name,
"description": args.description,
"status": args.status,
}
if args.due_date:
create_kwargs["due_date"] = _date_to_unix_ms(args.due_date)
if args.tag:
create_kwargs["tags"] = args.tag
create_kwargs["priority"] = args.priority
create_kwargs["assignees"] = args.assignee or [DEFAULT_ASSIGNEE]
if args.time_estimate:
create_kwargs["time_estimate"] = _parse_time_estimate(
args.time_estimate
)
# Create the task
result = client.create_task(**create_kwargs)
task_id = result.get("id", "")
# Set Client dropdown field
client.set_custom_field_smart(task_id, list_id, "Client", args.client)
# Set Work Category if provided
if args.category:
client.set_custom_field_smart(
task_id, list_id, "Work Category", args.category
)
# Set any additional custom fields
for field_name, field_value in custom_fields.items():
ok = client.set_custom_field_smart(
task_id, list_id, field_name, field_value
)
if not ok:
print(
f"Warning: Failed to set '{field_name}'",
file=sys.stderr,
)
print(json.dumps({
"id": task_id,
"name": args.name,
"client": args.client,
"url": result.get("url", ""),
"status": args.status,
}, indent=2))
finally:
client.close()
if __name__ == "__main__":
main()