Skip to main content

Trigger runs from the CLI

@click.group()
def trigger():
    pass


@trigger.command()
@click.argument("task_id")
@click.argument("project_id")
@click.option("--api-key", envvar="TRIGGER_API_KEY", help="API key for authentication")
@click.option("--payload", help="Payload to include in the request")
def run(task_id: str, project_id: str, payload: str, api_key: str):
    """Run a Trigger task with the given task ID."""
    if not api_key:
        cli.print(
            "API key is required. Please provide it using --api-key option or set TRIGGER_API_KEY environment variable.",
            label="error",
        )
        return

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
    }
    data = {
        "payload": json.loads(payload) if payload else {},
    }

    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        run_id = response.json()["id"]
        run_url = f"https://cloud.trigger.dev/projects/v3/{project_id}/runs/{run_id}"
        cli.print("Task triggered successfully!", label="success")
        cli.print(f"View run at: {run_url}")
        if cli.confirm("Open run?"):
            subprocess.run(["open", run_url])
    except requests.exceptions.RequestException as e:
        cli.print(f"Error triggering task: {str(e)}", label="error")

I currently use the following script to trigger runs using the API from the CLI. Would be nice to be able to use the already implemented API functionality (i.e., trigger runs, cancel runs, list runs, list env vars, create schedules, etc.) directly from the Trigger.dev CLI.

2 comments

Comments2

  • An Anonymous User

    •

    Jan 24

    #!/usr/bin/env python3

    """Trigger.dev CLI - Run tasks from the command line."""

    import os

    import sys

    import json

    import subprocess

    import platform

    import requests

    # Load .env files from current directory

    try:

    from dotenv import load_dotenv

    load_dotenv(".env") # Load base first

    load_dotenv(".env.local", override=True) # Local overrides

    except ImportError:

    pass # dotenv not installed, rely on shell env

    # Configuration from environment

    API_KEY = os.environ.get("TRIGGER_SECRET_KEY")

    PROJECT_ID = os.environ.get("TRIGGER_PROJECT_ID")

    BASE_URL = "https://api.trigger.dev/api/v1"

    # Cache last listed tasks for quick selection

    CACHE_FILE = os.path.expanduser("~/.trigger_last_tasks.json")

    def print_help():

    print("""

    trigger - Trigger.dev task runner

    Usage:

    trigger List recent runs (numbered)

    trigger list Same as above

    trigger schedules List scheduled tasks (numbered)

    trigger run <task_id> Run a task (asks for confirmation)

    trigger run <task_id> -y Run without confirmation

    trigger run <task_id> -p <json> Run with JSON payload

    trigger run <task_id> --open Open run URL after trigger

    trigger <number> Run task by number from last list

    trigger -h, --help Show this help

    Environment:

    TRIGGER_SECRET_KEY API key (tr_dev_... / tr_prod_...)

    TRIGGER_PROJECT_ID Project ID (for dashboard URLs)

    """.strip())

    def open_url(url):

    """Open URL in browser (cross-platform)."""

    system = platform.system()

    try:

    cmd = "open" if system == "Darwin" else "xdg-open"

    subprocess.run([cmd, url], check=True,

    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    except Exception:

    print(f"Open manually: {url}")

    def confirm(message):

    """Ask for confirmation, return True if yes."""

    try:

    response = input(f"{message} [y/N] ").strip().lower()

    return response in ("y", "yes")

    except (EOFError, KeyboardInterrupt):

    return False

    def list_runs(limit=20):

    """List recent runs with unique task identifiers."""

    response = requests.get(

    f"{BASE_URL}/runs",

    headers={"Authorization": f"Bearer {API_KEY}"},

    params={"page[size]": limit}

    )

    response.raise_for_status()

    runs = response.json().get("data", [])

    seen = {}

    tasks = []

    for run in runs:

    task_id = run.get("taskIdentifier")

    if task_id and task_id not in seen:

    seen[task_id] = True

    tasks.append({

    "id": task_id,

    "status": run.get("status"),

    "updated": run.get("updatedAt", "")[:10]

    })

    # Save for quick selection

    with open(CACHE_FILE, "w") as f:

    json.dump(tasks, f)

    print("Recent tasks:")

    for i, t in enumerate(tasks, 1):

    status_icon = "✔️" if t["status"] == "COMPLETED" else "⏳"

    print(f" {i}. {t['id']} {status_icon} ({t['updated']})")

    return tasks

    def list_schedules():

    """List scheduled tasks."""

    response = requests.get(

    f"{BASE_URL}/schedules",

    headers={"Authorization": f"Bearer {API_KEY}"}

    )

    response.raise_for_status()

    schedules = response.json().get("data", [])

    tasks = []

    print("Scheduled tasks:")

    for i, s in enumerate(schedules, 1):

    task_id = s.get("task")

    cron = s.get("generator", {}).get("expression", "")

    active = "🟢" if s.get("active") else "🔴"

    next_run = s.get("nextRun", "")[:16].replace("T", " ")

    tasks.append({"id": task_id, "schedule_id": s.get("id")})

    print(f" {i}. {task_id} {active} [{cron}] next: {next_run}")

    # Save for quick selection

    with open(CACHE_FILE, "w") as f:

    json.dump(tasks, f)

    return schedules

    def run_task(task_id, payload=None, auto_open=False, skip_confirm=False):

    """Trigger a task."""

    if not skip_confirm:

    if not confirm(f"Trigger '{task_id}'?"):

    print("Cancelled")

    return

    response = requests.post(

    f"{BASE_URL}/tasks/{task_id}/trigger",

    headers={

    "Authorization": f"Bearer {API_KEY}",

    "Content-Type": "application/json",

    },

    json={"payload": payload or {}}

    )

    response.raise_for_status()

    run_id = response.json().get("id")

    print(f"✔️ Triggered {task_id}")

    if PROJECT_ID and run_id:

    run_url = f"https://cloud.trigger.dev/projects/v3/{PROJECT_ID}/runs/{run_id}"

    print(f" {run_url}")

    if auto_open:

    open_url(run_url)

    def get_cached_task(number):

    """Get task ID from cache by number."""

    try:

    with open(CACHE_FILE) as f:

    tasks = json.load(f)

    return tasks[number - 1]["id"]

    except (FileNotFoundError, IndexError, KeyError):

    return None

    def main():

    args = sys.argv[1:]

    # Handle help first (doesn't require API key)

    if "-h" in args or "--help" in args:

    print_help()

    return

    if not API_KEY:

    print("❌ TRIGGER_SECRET_KEY not set")

    sys.exit(1)

    # Handle flags

    skip_confirm = "-y" in args

    auto_open = "--open" in args

    args = [a for a in args if a not in ["-y", "--open"]]

    if not args:

    list_runs()

    return

    if args[0] == "list":

    list_runs()

    elif args[0] == "schedules":

    list_schedules()

    elif args[0] == "run":

    if len(args) < 2:

    print("Usage: trigger run <task_id>")

    sys.exit(1)

    task_id = args[1]

    payload = None

    if "-p" in args:

    p_idx = args.index("-p")

    payload = json.loads(args[p_idx + 1])

    run_task(task_id, payload, auto_open, skip_confirm)

    elif args[0].isdigit():

    task_id = get_cached_task(int(args[0]))

    if not task_id:

    print("❌ Run 'trigger list' first")

    sys.exit(1)

    run_task(task_id, auto_open=auto_open, skip_confirm=skip_confirm)

    else:

    # Assume it's a task ID

    run_task(args[0], auto_open=auto_open, skip_confirm=skip_confirm)

    if name == "__main__":

    try:

    main()

    except requests.exceptions.HTTPError as e:

    print(f"❌ API error: {e.response.status_code}")

    sys.exit(1)

    except KeyboardInterrupt:

    print("\nCancelled")

  • mattaitken

    Team•

    May 2, 2025

    It would be great to pass the payload in such a way that it can be piped in. Like this:

    ```
    -payload '{ "id": "12hdn98dj" }
    ```