im_wower
·
2026-03-29
common.sh
1#!/usr/bin/env bash
2
3if [[ -n "${BAA_RUNTIME_COMMON_SH_LOADED:-}" ]]; then
4 return 0
5fi
6
7readonly BAA_RUNTIME_COMMON_SH_LOADED=1
8readonly BAA_RUNTIME_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
9readonly BAA_RUNTIME_REPO_DIR_DEFAULT="$(cd -- "${BAA_RUNTIME_SCRIPT_DIR}/../.." && pwd)"
10readonly BAA_RUNTIME_DEFAULT_PUBLIC_API_BASE="https://conductor.makefile.so"
11readonly BAA_RUNTIME_DEFAULT_CONTROL_API_BASE="${BAA_RUNTIME_DEFAULT_PUBLIC_API_BASE}"
12readonly BAA_RUNTIME_DEFAULT_LOCAL_API="http://127.0.0.1:4317"
13readonly BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API="http://127.0.0.1:4319"
14readonly BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH="/v1/codexd/events"
15readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE="app-server"
16readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY="spawn"
17readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND="codex"
18readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS="app-server"
19readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT="stdio://codex-app-server"
20readonly BAA_RUNTIME_DEFAULT_CLAUDE_CODED_LOCAL_API="http://127.0.0.1:4320"
21readonly BAA_RUNTIME_DEFAULT_STATUS_API="http://127.0.0.1:4318"
22readonly BAA_RUNTIME_DEFAULT_LOCALE="en_US.UTF-8"
23
24runtime_log() {
25 printf '[runtime] %s\n' "$*"
26}
27
28runtime_error() {
29 printf '[runtime] error: %s\n' "$*" >&2
30}
31
32die() {
33 runtime_error "$*"
34 exit 1
35}
36
37require_command() {
38 if ! command -v "$1" >/dev/null 2>&1; then
39 die "Missing required command: $1"
40 fi
41}
42
43contains_value() {
44 local needle="$1"
45 shift
46
47 local value
48 for value in "$@"; do
49 if [[ "$value" == "$needle" ]]; then
50 return 0
51 fi
52 done
53
54 return 1
55}
56
57validate_service() {
58 case "$1" in
59 conductor | codexd | claude-coded | worker-runner | status-api) ;;
60 *)
61 die "Unsupported service: $1"
62 ;;
63 esac
64}
65
66validate_scope() {
67 case "$1" in
68 agent | daemon) ;;
69 *)
70 die "Unsupported launchd scope: $1"
71 ;;
72 esac
73}
74
75validate_node() {
76 case "$1" in
77 mini) ;;
78 *)
79 die "Unsupported node: $1"
80 ;;
81 esac
82}
83
84default_services() {
85 printf '%s\n' conductor codexd
86}
87
88default_node_verification_services() {
89 printf '%s\n' conductor codexd
90}
91
92all_services() {
93 printf '%s\n' conductor codexd claude-coded worker-runner status-api
94}
95
96service_requires_shared_token() {
97 case "$1" in
98 codexd | claude-coded)
99 return 1
100 ;;
101 *)
102 return 0
103 ;;
104 esac
105}
106
107service_uses_public_api_base() {
108 case "$1" in
109 conductor)
110 return 0
111 ;;
112 *)
113 return 1
114 ;;
115 esac
116}
117
118service_uses_control_api_base() {
119 service_uses_public_api_base "$1"
120}
121
122service_label() {
123 case "$1" in
124 conductor)
125 printf '%s\n' "so.makefile.baa-conductor"
126 ;;
127 codexd)
128 printf '%s\n' "so.makefile.baa-codexd"
129 ;;
130 claude-coded)
131 printf '%s\n' "so.makefile.baa-claude-coded"
132 ;;
133 worker-runner)
134 printf '%s\n' "so.makefile.baa-worker-runner"
135 ;;
136 status-api)
137 printf '%s\n' "so.makefile.baa-status-api"
138 ;;
139 esac
140}
141
142service_dist_entry_relative() {
143 case "$1" in
144 conductor)
145 printf '%s\n' "apps/conductor-daemon/dist/index.js"
146 ;;
147 codexd)
148 printf '%s\n' "apps/codexd/dist/index.js"
149 ;;
150 claude-coded)
151 printf '%s\n' "apps/claude-coded/dist/index.js"
152 ;;
153 worker-runner)
154 printf '%s\n' "apps/worker-runner/dist/index.js"
155 ;;
156 status-api)
157 printf '%s\n' "apps/status-api/dist/index.js"
158 ;;
159 esac
160}
161
162service_default_port() {
163 case "$1" in
164 conductor)
165 printf '%s\n' "4317"
166 ;;
167 codexd)
168 printf '%s\n' "4319"
169 ;;
170 claude-coded)
171 printf '%s\n' "4320"
172 ;;
173 status-api)
174 printf '%s\n' "4318"
175 ;;
176 worker-runner)
177 printf '%s\n' ""
178 ;;
179 esac
180}
181
182service_process_match() {
183 local repo_dir="$1"
184 local service="$2"
185 local conductor_host="${3:-}"
186 local conductor_role="${4:-}"
187 local dist_entry
188
189 dist_entry="${repo_dir}/$(service_dist_entry_relative "$service")"
190
191 case "$service" in
192 conductor)
193 printf '%s --host %s --role %s\n' "$dist_entry" "$conductor_host" "$conductor_role"
194 ;;
195 codexd)
196 printf '%s start\n' "$dist_entry"
197 ;;
198 claude-coded)
199 printf '%s start\n' "$dist_entry"
200 ;;
201 *)
202 printf '%s\n' "$dist_entry"
203 ;;
204 esac
205}
206
207service_template_path() {
208 local repo_dir="$1"
209 local service="$2"
210
211 printf '%s/ops/launchd/%s.plist\n' "$repo_dir" "$(service_label "$service")"
212}
213
214service_install_path() {
215 local install_dir="$1"
216 local service="$2"
217
218 printf '%s/%s.plist\n' "$install_dir" "$(service_label "$service")"
219}
220
221service_stdout_path() {
222 local logs_launchd_dir="$1"
223 local service="$2"
224
225 printf '%s/%s.out.log\n' "$logs_launchd_dir" "$(service_label "$service")"
226}
227
228service_stderr_path() {
229 local logs_launchd_dir="$1"
230 local service="$2"
231
232 printf '%s/%s.err.log\n' "$logs_launchd_dir" "$(service_label "$service")"
233}
234
235resolve_node_defaults() {
236 case "$1" in
237 mini)
238 printf '%s %s %s\n' "mini" "primary" "mini-main"
239 ;;
240 esac
241}
242
243default_home_dir() {
244 if [[ -n "${HOME:-}" ]]; then
245 printf '%s\n' "$HOME"
246 return 0
247 fi
248
249 printf '/Users/%s\n' "$(id -un)"
250}
251
252default_username() {
253 printf '%s\n' "$(id -un)"
254}
255
256default_launchd_path() {
257 local home_dir="$1"
258
259 printf '%s\n' "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${home_dir}/.local/bin:${home_dir}/bin"
260}
261
262default_install_dir() {
263 local scope="$1"
264 local home_dir="$2"
265
266 case "$scope" in
267 agent)
268 printf '%s/Library/LaunchAgents\n' "$home_dir"
269 ;;
270 daemon)
271 printf '%s\n' "/Library/LaunchDaemons"
272 ;;
273 esac
274}
275
276default_domain_target() {
277 local scope="$1"
278
279 case "$scope" in
280 agent)
281 printf 'gui/%s\n' "$(id -u)"
282 ;;
283 daemon)
284 printf '%s\n' "system"
285 ;;
286 esac
287}
288
289ensure_directory() {
290 local path="$1"
291 local mode="$2"
292
293 install -d -m "$mode" "$path"
294}
295
296assert_directory() {
297 if [[ ! -d "$1" ]]; then
298 die "Missing directory: $1"
299 fi
300}
301
302assert_file() {
303 if [[ ! -f "$1" ]]; then
304 die "Missing file: $1"
305 fi
306}
307
308escape_plist_value() {
309 local value="$1"
310
311 value=${value//\\/\\\\}
312 value=${value//\"/\\\"}
313
314 printf '%s' "$value"
315}
316
317plist_set_string() {
318 local plist_path="$1"
319 local key="$2"
320 local value
321
322 value="$(escape_plist_value "$3")"
323
324 if ! /usr/libexec/PlistBuddy -c "Set ${key} \"${value}\"" "$plist_path" >/dev/null 2>&1; then
325 /usr/libexec/PlistBuddy -c "Add ${key} string \"${value}\"" "$plist_path" >/dev/null
326 fi
327}
328
329plist_delete_key() {
330 local plist_path="$1"
331 local key="$2"
332
333 /usr/libexec/PlistBuddy -c "Delete ${key}" "$plist_path" >/dev/null 2>&1 || true
334}
335
336plist_print_value() {
337 local plist_path="$1"
338 local key="$2"
339
340 /usr/libexec/PlistBuddy -c "Print ${key}" "$plist_path"
341}
342
343plist_has_key() {
344 local plist_path="$1"
345 local key="$2"
346
347 /usr/libexec/PlistBuddy -c "Print ${key}" "$plist_path" >/dev/null 2>&1
348}
349
350print_shell_command() {
351 printf '+'
352
353 local arg
354 for arg in "$@"; do
355 printf ' %q' "$arg"
356 done
357
358 printf '\n'
359}
360
361run_or_print() {
362 local dry_run="$1"
363 shift
364
365 if [[ "$dry_run" == "1" ]]; then
366 print_shell_command "$@"
367 return 0
368 fi
369
370 "$@"
371}
372
373resolve_runtime_paths() {
374 local repo_dir="$1"
375
376 printf '%s\n' \
377 "${repo_dir}/state" \
378 "${repo_dir}/state/codexd" \
379 "${repo_dir}/runs" \
380 "${repo_dir}/worktrees" \
381 "${repo_dir}/logs" \
382 "${repo_dir}/logs/codexd" \
383 "${repo_dir}/logs/launchd" \
384 "${repo_dir}/tmp"
385}
386
387load_shared_token() {
388 local shared_token="$1"
389 local shared_token_file="$2"
390
391 if [[ -n "$shared_token_file" ]]; then
392 assert_file "$shared_token_file"
393 shared_token="$(tr -d '\r\n' <"$shared_token_file")"
394 fi
395
396 printf '%s' "$shared_token"
397}
398
399read_env_file_value() {
400 local env_file="$1"
401 local key="$2"
402
403 if [[ -z "$env_file" || ! -f "$env_file" ]]; then
404 return 0
405 fi
406
407 awk -F= -v key="$key" '
408 $1 == key {
409 sub(/^[^=]*=/, "");
410 print;
411 exit;
412 }
413 ' "$env_file"
414}
415
416resolve_env_or_file_value() {
417 local current_value="$1"
418 local key="$2"
419 local env_file="$3"
420
421 if [[ -n "$current_value" ]]; then
422 printf '%s' "$current_value"
423 return 0
424 fi
425
426 read_env_file_value "$env_file" "$key"
427}