#!/bin/bash # ct - Claudomator CLI for agents running inside containers # # Usage: # ct task create --name "..." --instructions "..." # create subtask (parent auto-set) # ct task run # queue a task for execution # ct task wait [--timeout 300] # poll until done, print status # ct task status # print current state # ct task list # list recent tasks # # Environment (injected by ContainerRunner): # CLAUDOMATOR_API_URL base URL of the Claudomator API # CLAUDOMATOR_TASK_ID ID of the currently running task (used as default parent) set -euo pipefail API="${CLAUDOMATOR_API_URL:-http://host.docker.internal:8484}" PARENT="${CLAUDOMATOR_TASK_ID:-}" _api() { local method="$1"; shift local path="$1"; shift curl -sf -X "$method" "${API}${path}" \ -H "Content-Type: application/json" \ "$@" } _require() { if ! command -v "$1" &>/dev/null; then echo "ct: required tool '$1' not found" >&2 exit 1 fi } _require curl _require jq cmd_task_create() { local name="" instructions="" instructions_file="" model="" budget="" parent="$PARENT" while [[ $# -gt 0 ]]; do case "$1" in --name) name="$2"; shift 2 ;; --instructions) instructions="$2"; shift 2 ;; --file) instructions_file="$2"; shift 2 ;; --model) model="$2"; shift 2 ;; --budget) budget="$2"; shift 2 ;; --parent) parent="$2"; shift 2 ;; *) echo "ct task create: unknown flag $1" >&2; exit 1 ;; esac done if [[ -z "$name" ]]; then echo "ct task create: --name is required" >&2; exit 1 fi if [[ -n "$instructions_file" ]]; then instructions=$(cat "$instructions_file") fi if [[ -z "$instructions" ]]; then echo "ct task create: --instructions or --file is required" >&2; exit 1 fi local payload payload=$(jq -n \ --arg name "$name" \ --arg instructions "$instructions" \ --arg parent "$parent" \ --arg model "${model:-sonnet}" \ --argjson budget "${budget:-3.0}" \ '{ name: $name, parent_task_id: $parent, agent: { type: "claude", model: $model, instructions: $instructions, max_budget_usd: $budget } }') local response response=$(_api POST /api/tasks -d "$payload") local task_id task_id=$(echo "$response" | jq -r '.id // empty') if [[ -z "$task_id" ]]; then echo "ct task create: API error: $(echo "$response" | jq -r '.error // .')" >&2 exit 1 fi echo "$task_id" } cmd_task_run() { local task_id="${1:-}" if [[ -z "$task_id" ]]; then echo "ct task run: task-id required" >&2; exit 1 fi local response response=$(_api POST "/api/tasks/${task_id}/run") echo "$response" | jq -r '.message // .error // .' } cmd_task_wait() { local task_id="${1:-}" local timeout=300 shift || true while [[ $# -gt 0 ]]; do case "$1" in --timeout) timeout="$2"; shift 2 ;; *) echo "ct task wait: unknown flag $1" >&2; exit 1 ;; esac done if [[ -z "$task_id" ]]; then echo "ct task wait: task-id required" >&2; exit 1 fi local deadline=$(( $(date +%s) + timeout )) local interval=5 while true; do local response response=$(_api GET "/api/tasks/${task_id}" 2>/dev/null) || true local state state=$(echo "$response" | jq -r '.state // "UNKNOWN"') case "$state" in COMPLETED|FAILED|TIMED_OUT|CANCELLED|BUDGET_EXCEEDED) echo "$state" [[ "$state" == "COMPLETED" ]] && exit 0 || exit 1 ;; BLOCKED) echo "BLOCKED" exit 2 ;; esac if [[ $(date +%s) -ge $deadline ]]; then echo "ct task wait: timed out after ${timeout}s (state: $state)" >&2 exit 1 fi sleep "$interval" done } cmd_task_status() { local task_id="${1:-}" if [[ -z "$task_id" ]]; then echo "ct task status: task-id required" >&2; exit 1 fi _api GET "/api/tasks/${task_id}" | jq -r '.state' } cmd_task_list() { _api GET "/api/tasks" | jq -r '.[] | "\(.state)\t\(.id)\t\(.name)"' | sort } # create-and-run shorthand: create a subtask and immediately queue it, then optionally wait cmd_task_submit() { local wait=false local args=() while [[ $# -gt 0 ]]; do case "$1" in --wait) wait=true; shift ;; *) args+=("$1"); shift ;; esac done local task_id task_id=$(cmd_task_create "${args[@]}") cmd_task_run "$task_id" >/dev/null echo "$task_id" if $wait; then cmd_task_wait "$task_id" fi } # Dispatch if [[ $# -lt 2 ]]; then echo "Usage: ct [args...]" echo " ct task create --name NAME --instructions TEXT [--file FILE] [--model MODEL] [--budget N]" echo " ct task submit --name NAME --instructions TEXT [--wait]" echo " ct task run " echo " ct task wait [--timeout 300]" echo " ct task status " echo " ct task list" exit 1 fi resource="$1"; shift command="$1"; shift case "${resource}/${command}" in task/create) cmd_task_create "$@" ;; task/run) cmd_task_run "$@" ;; task/wait) cmd_task_wait "$@" ;; task/status) cmd_task_status "$@" ;; task/list) cmd_task_list ;; task/submit) cmd_task_submit "$@" ;; *) echo "ct: unknown command: ${resource} ${command}" >&2; exit 1 ;; esac