summaryrefslogtreecommitdiff
path: root/scripts/ct-task
blob: cd3388ac2863375954ed0d8ac347691d64d7a6b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env bash
# ct-task: Query, retry, cancel, or reset a Claudomator task by ID prefix.
# Usage:
#   ct-task <id-prefix>                 — show task state + latest execution error
#   ct-task <id-prefix> retry           — reset to PENDING and queue
#   ct-task <id-prefix> run             — queue (must already be PENDING)
#   ct-task <id-prefix> reset           — reset state to PENDING only
#   ct-task <id-prefix> 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 <id-prefix> [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