summaryrefslogtreecommitdiff
path: root/images/agent-base/tools/ct
blob: 46d9613c5023c3b689ec40c4851505a1624ad34c (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/bin/bash
# ct - Claudomator CLI for agents running inside containers
#
# Usage:
#   ct task create --name "..." --instructions "..."  # create subtask (parent auto-set)
#   ct task run <task-id>                             # queue a task for execution
#   ct task wait <task-id> [--timeout 300]            # poll until done, print status
#   ct task status <task-id>                          # 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 <resource> <command> [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 <id>"
  echo "  ct task wait <id> [--timeout 300]"
  echo "  ct task status <id>"
  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