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
|