#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=./codex-acp-service-common.sh source "$SCRIPT_DIR/codex-acp-service-common.sh" macos_launchctl_domain() { if launchctl print "gui/$UID" >/dev/null 2>&1; then printf 'gui/%s\n' "$UID" return 0 fi printf 'user/%s\n' "$UID" } SERVICE_LABEL="${XWORKMATE_CODEX_ACP_SERVICE_LABEL:-plus.svc.xworkmate.codex-acp}" CONFIG_DIR="${XWORKMATE_CODEX_ACP_CONFIG_DIR:-$HOME/Library/Application Support/XWorkmate/codex-acp}" RUNTIME_DIR="${XWORKMATE_CODEX_ACP_RUNTIME_DIR:-$CONFIG_DIR/runtime}" LOG_DIR="${XWORKMATE_CODEX_ACP_LOG_DIR:-$HOME/Library/Logs/XWorkmate/codex-acp}" CONFIG_FILE="$CONFIG_DIR/config.env" LAUNCHER_FILE="$RUNTIME_DIR/run-codex-acp.sh" PLIST_DIR="${XWORKMATE_CODEX_ACP_LAUNCHAGENTS_DIR:-$HOME/Library/LaunchAgents}" SERVICE_FILE="$PLIST_DIR/$SERVICE_LABEL.plist" LAUNCHCTL_DOMAIN="${XWORKMATE_CODEX_ACP_LAUNCHCTL_DOMAIN:-$(macos_launchctl_domain)}" macos_usage() { cat < [--port PORT] [--lines N] Commands: install Write LaunchAgent + start Codex ACP service start Start service using configured or auto-selected port stop Stop the LaunchAgent restart Restart service, re-picking a free port if needed status Show service state and endpoint endpoint Print configured websocket endpoint logs Tail saved stdout/stderr logs set-port PORT Reconfigure the service port, then restart if loaded uninstall Stop service and remove LaunchAgent + generated files help Show this message Defaults: host $XWORKMATE_CODEX_ACP_DEFAULT_HOST default port $XWORKMATE_CODEX_ACP_DEFAULT_PORT launchctl $LAUNCHCTL_DOMAIN Environment: CODEX_BIN=/absolute/path/to/codex XWORKMATE_CODEX_ACP_DRY_RUN=1 Generate files without calling launchctl EOF } macos_is_loaded() { launchctl print "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL" >/dev/null 2>&1 } macos_state() { if ! macos_is_loaded; then printf 'unloaded\n' return 0 fi local state state="$(launchctl print "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL" 2>/dev/null | awk -F'= ' '/state =/ { print $2; exit }')" if [[ -n "$state" ]]; then printf '%s\n' "$state" return 0 fi printf 'loaded\n' } macos_write_service_file() { common_ensure_dirs mkdir -p "$PLIST_DIR" cat > "$SERVICE_FILE" < Label $SERVICE_LABEL ProgramArguments $LAUNCHER_FILE RunAtLoad KeepAlive WorkingDirectory $HOME StandardOutPath $LOG_DIR/stdout.log StandardErrorPath $LOG_DIR/stderr.log EnvironmentVariables PATH $PATH EOF } macos_prepare_service() { common_load_config local configured_port="${CODEX_ACP_PORT:-$XWORKMATE_CODEX_ACP_DEFAULT_PORT}" local requested_port="${PORT_OVERRIDE:-$configured_port}" local preserve_busy="0" if macos_is_loaded && [[ "$requested_port" == "$configured_port" ]]; then preserve_busy="1" fi local codex_bin="${CODEX_BIN:-}" if [[ -z "$codex_bin" || ! -x "$codex_bin" ]]; then codex_bin="$(common_resolve_codex_bin)" fi local selected_port selected_port="$(common_select_port "$requested_port" "$configured_port" "$preserve_busy")" if [[ "$selected_port" != "$requested_port" ]]; then common_warn "Port $requested_port is busy; using $selected_port instead." fi local listen_url listen_url="$(common_listen_url "$selected_port")" common_write_config "$codex_bin" "$selected_port" common_write_launcher "$codex_bin" "$listen_url" macos_write_service_file } macos_activate_service() { common_run_service_cmd_allow_fail launchctl bootout "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE" common_run_service_cmd launchctl bootstrap "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE" common_run_service_cmd launchctl kickstart -k "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL" } macos_stop_service() { if ! macos_is_loaded; then common_info "Service is not loaded." return 0 fi common_run_service_cmd launchctl bootout "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE" } macos_status() { common_info "platform: macOS" common_info "label: $SERVICE_LABEL" common_info "state: $(macos_state)" common_info "endpoint: $(common_endpoint_from_config)" common_info "launch agent: $SERVICE_FILE" common_info "config: $CONFIG_FILE" common_info "logs:" common_info " $LOG_DIR/stdout.log" common_info " $LOG_DIR/stderr.log" } macos_logs() { mkdir -p "$LOG_DIR" touch "$LOG_DIR/stdout.log" "$LOG_DIR/stderr.log" common_info "==> $LOG_DIR/stdout.log <==" tail -n "$LOG_LINES" "$LOG_DIR/stdout.log" common_info common_info "==> $LOG_DIR/stderr.log <==" tail -n "$LOG_LINES" "$LOG_DIR/stderr.log" } macos_uninstall() { macos_stop_service rm -f "$SERVICE_FILE" "$LAUNCHER_FILE" "$CONFIG_FILE" common_cleanup_dir_if_empty "$RUNTIME_DIR" common_cleanup_dir_if_empty "$CONFIG_DIR" common_info "Removed LaunchAgent: $SERVICE_LABEL" } common_parse_args "$@" case "$ACTION" in install) macos_prepare_service macos_activate_service common_info "Codex ACP service ready at $(common_endpoint_from_config)" ;; start) if macos_is_loaded && [[ -z "$PORT_OVERRIDE" ]]; then common_info "Service already loaded at $(common_endpoint_from_config)" exit 0 fi macos_prepare_service macos_activate_service common_info "Codex ACP service started at $(common_endpoint_from_config)" ;; stop) macos_stop_service ;; restart) macos_prepare_service macos_activate_service common_info "Codex ACP service restarted at $(common_endpoint_from_config)" ;; status) macos_status ;; endpoint) common_endpoint_from_config ;; logs) macos_logs ;; set-port) [[ -n "$PORT_OVERRIDE" ]] || common_die "set-port requires a port value" macos_prepare_service if macos_is_loaded; then macos_activate_service common_info "Codex ACP service moved to $(common_endpoint_from_config)" else common_info "Configured Codex ACP service for $(common_endpoint_from_config)" fi ;; uninstall) macos_uninstall ;; help) macos_usage ;; *) common_die "Unknown command: $ACTION" ;; esac