#!/usr/bin/env bash # ct-task: Query, retry, cancel, or reset a Claudomator task by ID prefix. # Usage: # ct-task — show task state + latest execution error # ct-task retry — reset to PENDING and queue # ct-task run — queue (must already be PENDING) # ct-task reset — reset state to PENDING only # ct-task cancel — cancel task (works from any state) # ct-task list [state] — list tasks (optional: filter by state) # ct-task cancel-all-failed — cancel every FAILED task # # Examples: # ct-task f8829d6f # ct-task f8829d6f retry # ct-task f8829d6f cancel # ct-task list FAILED # ct-task cancel-all-failed set -euo pipefail API="http://localhost:8484" DB="/site/doot.terst.org/data/claudomator.db" _jq() { python3 -c "import sys,json; d=json.load(sys.stdin); $1"; } task_id_from_prefix() { local prefix="$1" local id id=$(sqlite3 "$DB" "SELECT id FROM tasks WHERE id LIKE '${prefix}%' ORDER BY created_at DESC LIMIT 1;") if [[ -z "$id" ]]; then echo "ERROR: no task found matching '${prefix}'" >&2 exit 1 fi echo "$id" } cmd_show() { local id id=$(task_id_from_prefix "$1") local task task=$(curl -sf "$API/api/tasks/$id") echo "$task" | _jq " t=d print('ID: ', t['id']) print('Name: ', t['name']) print('State: ', t['state']) print('Project:', t.get('project','')) summary = t.get('summary','') if summary: print('Summary:', summary) rejection = t.get('rejection_comment','') if rejection: print('Rejection:', rejection) " # Latest execution error local execs execs=$(curl -sf "$API/api/tasks/$id/executions" 2>/dev/null || echo "[]") local count count=$(echo "$execs" | _jq "print(len(d))") if [[ "$count" -gt 0 ]]; then echo "" echo "$execs" | _jq " e=d[0] print('Execution:', e['ID']) print('Status: ', e['Status']) print('Exit code:', e['ExitCode']) err = e.get('ErrorMsg','') if err: print('Error: ', err) cost = e.get('CostUSD',0) if cost: print('Cost: ', f'\${cost:.4f}') " fi } cmd_reset() { local id id=$(task_id_from_prefix "$1") sqlite3 "$DB" "UPDATE tasks SET state='PENDING' WHERE id='$id';" echo "Reset $id to PENDING" } cmd_run() { local id id=$(task_id_from_prefix "$1") local resp resp=$(curl -sf -X POST "$API/api/tasks/$id/run" || true) local state state=$(curl -sf "$API/api/tasks/$id" | _jq "print(d['state'])") echo "$id → $state" } cmd_retry() { local id id=$(task_id_from_prefix "$1") sqlite3 "$DB" "UPDATE tasks SET state='PENDING' WHERE id='$id';" curl -sf -X POST "$API/api/tasks/$id/run" >/dev/null || true local state state=$(curl -sf "$API/api/tasks/$id" | _jq "print(d['state'])") echo "$id → $state" } cmd_cancel() { local id id=$(task_id_from_prefix "$1") # Try API cancel first (works for PENDING/QUEUED/RUNNING/BLOCKED) local http_status http_status=$(curl -sf -o /dev/null -w "%{http_code}" -X POST "$API/api/tasks/$id/cancel" 2>/dev/null || echo "000") if [[ "$http_status" == "200" ]]; then echo "$id → CANCELLED" return fi # Terminal states (FAILED, TIMED_OUT, etc.) can't transition via API — force via DB. sqlite3 "$DB" "UPDATE tasks SET state='CANCELLED' WHERE id='$id';" echo "$id → CANCELLED (forced)" } cmd_cancel_all_failed() { local ids ids=$(sqlite3 "$DB" "SELECT id FROM tasks WHERE state='FAILED';") if [[ -z "$ids" ]]; then echo "No FAILED tasks." return fi while IFS= read -r id; do sqlite3 "$DB" "UPDATE tasks SET state='CANCELLED' WHERE id='$id';" echo "${id:0:8}… → CANCELLED" done <<< "$ids" } cmd_list() { local filter="${1:-}" local tasks tasks=$(curl -sf "$API/api/tasks") if [[ -n "$filter" ]]; then echo "$tasks" | _jq " for t in d: if t['state'] == '${filter}'.upper() or '${filter}'.upper() in t['state']: print(t['id'][:8], t['state'].ljust(12), t['name'][:60]) " else echo "$tasks" | _jq " for t in d: print(t['id'][:8], t['state'].ljust(12), t['name'][:60]) " fi } # Dispatch PREFIX="${1:-}" SUBCMD="${2:-show}" if [[ -z "$PREFIX" ]]; then echo "Usage: ct-task [show|retry|run|reset|cancel] OR ct-task list [state] OR ct-task cancel-all-failed" >&2 exit 1 fi case "$PREFIX" in list) cmd_list "${2:-}" ;; cancel-all-failed) cmd_cancel_all_failed ;; *) case "$SUBCMD" in show|"") cmd_show "$PREFIX" ;; retry) cmd_retry "$PREFIX" ;; run) cmd_run "$PREFIX" ;; reset) cmd_reset "$PREFIX" ;; cancel) cmd_cancel "$PREFIX" ;; *) echo "Unknown subcommand: $SUBCMD" >&2; exit 1 ;; esac ;; esac