#!/usr/bin/env bash # # Install the iMessage adapter, persist mode/creds to .env + data/env/env, # and restart the service. Non-interactive — the Full Disk Access walkthrough # (local mode) and Photon URL/key prompts (remote mode) live in # setup/channels/imessage.ts. Creds come in via env vars: # IMESSAGE_LOCAL 'true' | 'false' (required) # IMESSAGE_ENABLED 'true' (required when IMESSAGE_LOCAL=true) # IMESSAGE_SERVER_URL (required when IMESSAGE_LOCAL=false) # IMESSAGE_API_KEY (required when IMESSAGE_LOCAL=false) # # Emits exactly one status block on stdout (ADD_IMESSAGE) at the end. set -euo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$PROJECT_ROOT" # Keep in sync with .claude/skills/add-imessage/SKILL.md. ADAPTER_VERSION="chat-adapter-imessage@0.1.1" # Resolve which remote carries the channels branch — handles forks where # upstream lives on a different remote than `origin`. # shellcheck source=setup/lib/channels-remote.sh source "$PROJECT_ROOT/setup/lib/channels-remote.sh" CHANNELS_REMOTE=$(resolve_channels_remote) CHANNELS_BRANCH="${CHANNELS_REMOTE}/channels" emit_status() { local status=$1 error=${2:-} local already=${ADAPTER_ALREADY_INSTALLED:-false} local mode=${IMESSAGE_LOCAL:-} echo "=== NANOCLAW SETUP: ADD_IMESSAGE ===" echo "STATUS: ${status}" echo "ADAPTER_VERSION: ${ADAPTER_VERSION}" echo "ADAPTER_ALREADY_INSTALLED: ${already}" [ -n "$mode" ] && echo "MODE: $([ "$mode" = "true" ] && echo local || echo remote)" [ -n "$error" ] && echo "ERROR: ${error}" echo "=== END ===" } log() { echo "[add-imessage] $*" >&2; } # Validate creds based on mode. if [ -z "${IMESSAGE_LOCAL:-}" ]; then emit_status failed "IMESSAGE_LOCAL env var not set (expected true|false)" exit 1 fi if [ "${IMESSAGE_LOCAL}" = "true" ]; then if [ -z "${IMESSAGE_ENABLED:-}" ]; then emit_status failed "IMESSAGE_ENABLED env var not set for local mode" exit 1 fi if [ "$(uname -s)" != "Darwin" ]; then emit_status failed "local mode requires macOS" exit 1 fi else if [ -z "${IMESSAGE_SERVER_URL:-}" ]; then emit_status failed "IMESSAGE_SERVER_URL env var not set for remote mode" exit 1 fi if [ -z "${IMESSAGE_API_KEY:-}" ]; then emit_status failed "IMESSAGE_API_KEY env var not set for remote mode" exit 1 fi fi need_install() { [ ! -f src/channels/imessage.ts ] && return 0 ! grep -q "^import './imessage.js';" src/channels/index.ts 2>/dev/null && return 0 return 1 } ADAPTER_ALREADY_INSTALLED=true if need_install; then ADAPTER_ALREADY_INSTALLED=false log "Fetching channels branch…" git fetch "$CHANNELS_REMOTE" channels >&2 2>/dev/null || { emit_status failed "git fetch ${CHANNELS_REMOTE} channels failed" exit 1 } log "Copying adapter from ${CHANNELS_BRANCH}…" git show "${CHANNELS_BRANCH}:src/channels/imessage.ts" > src/channels/imessage.ts # Append self-registration import if missing. if ! grep -q "^import './imessage.js';" src/channels/index.ts; then echo "import './imessage.js';" >> src/channels/index.ts fi log "Installing ${ADAPTER_VERSION}…" pnpm install "${ADAPTER_VERSION}" >&2 2>/dev/null || { emit_status failed "pnpm install ${ADAPTER_VERSION} failed" exit 1 } log "Building…" pnpm run build >&2 2>/dev/null || { emit_status failed "pnpm run build failed" exit 1 } else log "Adapter files already installed — skipping install phase." fi touch .env upsert_env() { local key=$1 value=$2 if grep -q "^${key}=" .env; then awk -v k="$key" -v v="$value" \ 'BEGIN{FS=OFS="="} $1==k {print k "=" v; next} {print}' \ .env > .env.tmp && mv .env.tmp .env else echo "${key}=${value}" >> .env fi } remove_env() { local key=$1 if grep -q "^${key}=" .env 2>/dev/null; then grep -v "^${key}=" .env > .env.tmp && mv .env.tmp .env fi } # Write the canonical keys for the chosen mode, strip the opposite mode's # keys so stale values can't confuse the adapter's factory. upsert_env IMESSAGE_LOCAL "$IMESSAGE_LOCAL" if [ "$IMESSAGE_LOCAL" = "true" ]; then upsert_env IMESSAGE_ENABLED "$IMESSAGE_ENABLED" remove_env IMESSAGE_SERVER_URL remove_env IMESSAGE_API_KEY else upsert_env IMESSAGE_SERVER_URL "$IMESSAGE_SERVER_URL" upsert_env IMESSAGE_API_KEY "$IMESSAGE_API_KEY" remove_env IMESSAGE_ENABLED fi # Container reads from data/env/env (the host mounts it). mkdir -p data/env cp .env data/env/env log "Restarting service so the new adapter picks up the creds…" # shellcheck source=setup/lib/install-slug.sh source "$PROJECT_ROOT/setup/lib/install-slug.sh" case "$(uname -s)" in Darwin) launchctl kickstart -k "gui/$(id -u)/$(launchd_label)" >&2 2>/dev/null || true ;; Linux) systemctl --user restart "$(systemd_unit)" >&2 2>/dev/null \ || sudo systemctl restart "$(systemd_unit)" >&2 2>/dev/null \ || true ;; esac # Give the adapter a moment to open chat.db (local) or handshake with # Photon (remote) before emitting success. sleep 3 emit_status success