#!/bin/sh
# MyClawn install — installs under $HOME/.myclawn as the invoking user.
# The daemon runs as you; isolation comes from per-agent Unix users (the
# bot fleet) and a daemon-mediated proxy, not from hiding the daemon
# itself. See clawconnect/FLEET_DESIGN.md.
#
#   curl -fsSL https://www.myclawn.com/install.sh | bash
#
# The install itself needs NO sudo — everything lands under $HOME.
# The only sudo anywhere in the product comes later and is optional:
# the first `myclawn fleet add` prompts once to drop /etc/sudoers.d/myclawn
# so the daemon can `useradd bot-*` (isolated bot users). Skip the fleet
# and you never sudo. Exception: migrating a LEGACY system install
# (/var/myclawn, pre-userspace) needs sudo once to move your old state home.
#
# Legacy: previous installs created a `_myclawn` system user with data
# at /var/myclawn. If that layout is detected we migrate it back into
# $HOME/.myclawn and tear down the system LaunchDaemon / systemd unit.
# Set MYCLAWN_SYSTEM=1 to force the old system-install path (servers,
# CI). It's no longer the default.

set -e

BASE_URL="${MYCLAWN_BASE_URL:-https://www.myclawn.com}"
# Default to userspace install. Legacy MYCLAWN_USERSPACE=0 (or new
# MYCLAWN_SYSTEM=1) opts back into the _myclawn service-user shape.
if [ "${MYCLAWN_SYSTEM:-0}" = "1" ] || [ "${MYCLAWN_USERSPACE:-1}" = "0" ]; then
  USERSPACE=0
else
  USERSPACE=1
fi

# ── Platform ──────────────────────────────────────────────────────
case "$(uname -s)" in
  Darwin)  PLATFORM="macos" ;;
  Linux)   PLATFORM="linux" ;;
  *)       echo "Unsupported platform: $(uname -s)." >&2
           echo "MyClawn currently supports macOS and Linux." >&2
           exit 1 ;;
esac

# ── Node ──────────────────────────────────────────────────────────
# When called from a Tauri webview / desktop launcher the inherited PATH
# can miss the user's actual node. Probe well-known spots.
if ! command -v node >/dev/null 2>&1; then
  for c in /usr/local/bin/node /opt/homebrew/bin/node /usr/bin/node \
           "$HOME"/.nvm/versions/node/*/bin/node "$HOME"/.volta/bin/node \
           "$HOME"/.local/bin/node "$HOME"/n/bin/node; do
    [ -x "$c" ] && { PATH="$(dirname "$c"):$PATH"; export PATH; break; }
  done
fi
if ! command -v node >/dev/null 2>&1; then
  echo "  Node.js not found. Install from https://nodejs.org and retry." >&2
  exit 1
fi
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0)
# The daemon's native deps (better-sqlite3, sqlite-vec, node-llama-cpp)
# only ship prebuilt binaries for Node 20/22/24. On older Node there is no
# prebuild, so npm tries to COMPILE from source — which needs a full build
# toolchain the typical desktop lacks, and dies with "make: not found".
# Ubuntu 24.04 still ships Node 18 from apt, so a stock box lands users
# exactly in that broken spot. Require 20+. First reuse a newer Node that's
# already installed but not the default (nvm/volta/manual); failing that, on
# Linux, offer to provision Node 20 LTS via NodeSource (set
# MYCLAWN_SKIP_NODE_PROVISION=1 to opt out).
NODE_FLOOR=20
if [ "$NODE_MAJOR" -lt "$NODE_FLOOR" ]; then
  for c in /usr/local/bin/node /opt/homebrew/bin/node \
           "$HOME"/.nvm/versions/node/*/bin/node "$HOME"/.volta/bin/node \
           "$HOME"/.local/bin/node "$HOME"/n/bin/node; do
    [ -x "$c" ] || continue
    cm=$("$c" -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0)
    if [ "$cm" -ge "$NODE_FLOOR" ]; then
      PATH="$(dirname "$c"):$PATH"; export PATH
      NODE_MAJOR="$cm"; break
    fi
  done
fi
if [ "$NODE_MAJOR" -lt "$NODE_FLOOR" ] && [ "$PLATFORM" = "linux" ] \
   && [ "${MYCLAWN_SKIP_NODE_PROVISION:-0}" != "1" ] && command -v apt-get >/dev/null 2>&1; then
  APT_SUDO=""
  if [ "$(id -u)" -ne 0 ]; then
    if command -v sudo >/dev/null 2>&1; then APT_SUDO="sudo"; fi
  fi
  if [ "$(id -u)" -eq 0 ] || [ -n "$APT_SUDO" ]; then
    echo "▶ Node $(node -v 2>/dev/null || echo 'not found') is too old — installing Node ${NODE_FLOOR} LTS via NodeSource (one-time, may prompt for sudo)…"
    # Root runs the setup script directly; non-root pipes it through sudo.
    if [ -n "$APT_SUDO" ]; then NODE_SETUP_RUN="$APT_SUDO -E bash -"; else NODE_SETUP_RUN="bash -"; fi
    if curl -fsSL "https://deb.nodesource.com/setup_${NODE_FLOOR}.x" | $NODE_SETUP_RUN >/dev/null 2>&1 \
       && $APT_SUDO apt-get install -y nodejs >/dev/null 2>&1; then
      hash -r 2>/dev/null || true
      NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0)
      echo "  Node is now $(node -v 2>/dev/null)."
    else
      echo "  ⚠ Automatic Node upgrade failed — install Node ${NODE_FLOOR}+ manually." >&2
    fi
  fi
fi
if [ "$NODE_MAJOR" -lt "$NODE_FLOOR" ]; then
  echo "  Node.js $(node -v 2>/dev/null || echo 'not found') is too old. MyClawn needs Node ${NODE_FLOOR}+." >&2
  echo "  Install from https://nodejs.org — or on Debian/Ubuntu:" >&2
  echo "    curl -fsSL https://deb.nodesource.com/setup_${NODE_FLOOR}.x | sudo -E bash - && sudo apt-get install -y nodejs" >&2
  exit 1
fi
NODE_BIN="$(command -v node)"
# Resolve npm once, up front — npm sits next to node on every sane install.
# Step 3a AND the background heavy-deps step both need this; previously it
# was only resolved inside the NEED_NPM branch, so on a rerun with cached
# deps NPM_BIN was EMPTY and the heavy-deps step invoked a bare `install`
# (coreutils) instead of npm.
NPM_BIN="$(dirname "$NODE_BIN")/npm"
if [ ! -x "$NPM_BIN" ]; then
  NPM_BIN="$(command -v npm 2>/dev/null || true)"
fi

# ── Re-exec from a file when piped from `curl | bash` ──────────────
# `$0` is "bash" (or similar) when stdin-piped, which sudo can't re-exec.
# Stash the script content to a temp file the first time through so the
# self-elevation below works either way.
if [ ! -f "$0" ] || [ "${0##*/}" = "bash" ] || [ "${0##*/}" = "sh" ]; then
  TMP_SCRIPT=$(mktemp "${TMPDIR:-/tmp}/myclawn-install.XXXXXX")
  curl -fsSL "$BASE_URL/install.sh" -o "$TMP_SCRIPT" || {
    echo "  Could not re-fetch install.sh from $BASE_URL (needed to continue a piped install)." >&2
    echo "  Check your connection and retry — or bypass the pipe:" >&2
    echo "    curl -fsSL $BASE_URL/install.sh -o install.sh && sh install.sh" >&2
    exit 1
  }
  chmod +x "$TMP_SCRIPT"
  exec sh "$TMP_SCRIPT" "$@"
fi

# ── Mode setup: system vs user-space ──────────────────────────────
# All subsequent steps consume these variables; they are the only
# difference between the two install modes.
if [ "$USERSPACE" = "1" ]; then
  # No self-elevate in userspace mode — but the desktop app's admin
  # wrapper (osascript / pkexec) may have already escalated us to root.
  # When that happens, fall back to SUDO_USER so the install lands in
  # the real user's home rather than /var/root or /root.
  if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
    ORIG_USER="$SUDO_USER"
    case "$(uname -s)" in
      Darwin) ORIG_HOME="$(dscl . -read "/Users/$ORIG_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')" ;;
      Linux)  ORIG_HOME="$(getent passwd "$ORIG_USER" 2>/dev/null | cut -d: -f6)" ;;
    esac
    ORIG_HOME="${ORIG_HOME:-/Users/$ORIG_USER}"
  else
    ORIG_USER="${USER:-$(whoami)}"
    ORIG_HOME="${HOME:-$(eval echo "~$ORIG_USER")}"
  fi
  SERVICE_USER="$ORIG_USER"
  DATA_DIR="$ORIG_HOME/.myclawn"
  USER_CFG_DIR="$ORIG_HOME/.config/myclawn"
  PLIST_PATH="$ORIG_HOME/Library/LaunchAgents/com.myclawn.daemon.plist"
  LAUNCHCTL_DOMAIN="gui/$(id -u "$ORIG_USER")"
  USER_BIN_DIR="$ORIG_HOME/.local/bin"
  RUN_AS_SERVICE=""           # already running as service user (= ORIG_USER)
  IS_ROOT=0
  echo "▶ Installing in user-space mode under $DATA_DIR (no sudo)."
else
  # Self-elevate to root.
  if [ "$(id -u)" -ne 0 ]; then
    echo "Setting up MyClawn — needs admin password once."
    exec sudo -E sh "$0" "$@"
  fi
  ORIG_USER="${SUDO_USER:-${USER:-}}"
  if [ -z "$ORIG_USER" ] || [ "$ORIG_USER" = "root" ]; then
    echo "  Cannot determine the invoking user. Set SUDO_USER and retry." >&2
    exit 1
  fi
  case "$PLATFORM" in
    macos) ORIG_HOME="$(dscl . -read "/Users/$ORIG_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')" ;;
    linux) ORIG_HOME="$(getent passwd "$ORIG_USER" | cut -d: -f6)" ;;
  esac
  ORIG_HOME="${ORIG_HOME:-/Users/$ORIG_USER}"
  SERVICE_USER="_myclawn"
  DATA_DIR="/var/myclawn"
  USER_CFG_DIR="$ORIG_HOME/.config/myclawn"
  PLIST_PATH="/Library/LaunchDaemons/com.myclawn.daemon.plist"
  LAUNCHCTL_DOMAIN="system"
  if [ -w /usr/local/bin ]; then
    USER_BIN_DIR="/usr/local/bin"
  else
    USER_BIN_DIR="$ORIG_HOME/.local/bin"
  fi
  RUN_AS_SERVICE="sudo -u $SERVICE_USER"
  IS_ROOT=1
fi

# Tiny chown helper. System mode runs `chown` as root (we are root).
# Userspace mode running as the user is a no-op. Userspace mode running
# as root (desktop-app admin wrapper) still needs to chown so files end
# up owned by ORIG_USER, not root.
do_chown() {
  if [ "$USERSPACE" = "1" ] && [ "$(id -u)" -ne 0 ]; then return 0; fi
  chown "$@"
}

# ── 1. Service user (system mode only) ────────────────────────────
if [ "$USERSPACE" != "1" ]; then
  echo "▶ 1/5 Service account ($SERVICE_USER)..."
  case "$PLATFORM" in
    macos)
      if ! dscl . -read "/Users/$SERVICE_USER" >/dev/null 2>&1; then
        UID_NEXT=$(dscl . -list /Users UniqueID | awk '$2 >= 200 && $2 < 500 {print $2}' | sort -n | tail -1)
        UID_NEXT=$((${UID_NEXT:-200} + 1))
        dscl . -create "/Users/$SERVICE_USER"
        dscl . -create "/Users/$SERVICE_USER" UniqueID "$UID_NEXT"
        dscl . -create "/Users/$SERVICE_USER" PrimaryGroupID 20
        dscl . -create "/Users/$SERVICE_USER" UserShell /usr/bin/false
        dscl . -create "/Users/$SERVICE_USER" RealName "MyClawn Service"
        dscl . -create "/Users/$SERVICE_USER" NFSHomeDirectory "$DATA_DIR"
        dscl . -create "/Users/$SERVICE_USER" IsHidden 1
      fi
      ;;
    linux)
      id "$SERVICE_USER" >/dev/null 2>&1 || \
        useradd --system --no-create-home --home-dir "$DATA_DIR" --shell /usr/sbin/nologin "$SERVICE_USER"
      ;;
  esac
fi

# ── 2. Data directory ─────────────────────────────────────────────
# System mode: 0711 on the top dir lets ORIG_USER traverse to the
# control socket but not list contents. User-space mode: 0700 since
# the user is the only audience.
echo "▶ 2/5 Data dir ($DATA_DIR)..."
mkdir -p "$DATA_DIR"
do_chown "$SERVICE_USER" "$DATA_DIR"
# Userspace data dir is 0755, not 0700: fleet bot users (bot-1, bot-2, …)
# need traversal so they can read ca.pem (mode 0644) for proxy TLS trust.
# Every sensitive file inside (daemon.key, keys.json, wallet.enc,
# credentials.json, signer.cookie, audit.jsonl) is independently 0600,
# so loosening the dir mode doesn't expose any secret content — bots
# can `ls` the directory but every secret stays unreadable.
if [ "$USERSPACE" = "1" ]; then
  chmod 0755 "$DATA_DIR"
else
  chmod 0711 "$DATA_DIR"
fi

# Pre-create $DATA_DIR/.myclawn at 0755 (system) or 0700 (userspace).
# In system mode, the helper (running as ORIG_USER) needs to read
# mcp-config.json from here when spawning claude. In user-space mode,
# the daemon IS ORIG_USER so any private mode is fine.
mkdir -p "$DATA_DIR/.myclawn"
do_chown "$SERVICE_USER" "$DATA_DIR/.myclawn"
if [ "$USERSPACE" = "1" ]; then
  chmod 0700 "$DATA_DIR/.myclawn"
else
  chmod 0755 "$DATA_DIR/.myclawn"
fi

# ── 3. Runtime files ──────────────────────────────────────────────
echo "▶ 3/5 Downloading runtime..."
FILES="
myclawn-daemon.js daemon.js
myclawn-signer.mjs signer.mjs
myclawn-providers.js providers.js
myclawn-transport.js transport.js
myclawn-core.js core.js
myclawn-trust.js trust.js
myclawn-verify.mjs verify.mjs
myclawn-dreams.mjs dreams.mjs
myclawn-dream-now.mjs dream-now.mjs
myclawn-log.mjs log.mjs
myclawn-spending.mjs spending.mjs
myclawn-security.mjs security.mjs
myclawn-bundle.js myclawn.js
myclawn-ask.js ask.js
myclawn-run.js run.js
myclawn-fleet.js fleet.js
myclawn-keys.js keys.js
"
DL_FAILED=0
# Heredoc-fed loop (not a pipe) so DL_FAILED survives the loop in POSIX sh.
while read -r REMOTE LOCAL; do
  [ -z "$REMOTE" ] && continue
  # Download to a temp name and move into place only on success — an
  # interrupted curl otherwise leaves a truncated daemon.js that boots
  # into a confusing SyntaxError instead of a clear install failure.
  if curl -fsSL "$BASE_URL/$REMOTE" -o "$DATA_DIR/.dl.$$.tmp"; then
    mv "$DATA_DIR/.dl.$$.tmp" "$DATA_DIR/$LOCAL"
  else
    rm -f "$DATA_DIR/.dl.$$.tmp"
    echo "  ✗ download failed: $BASE_URL/$REMOTE" >&2
    DL_FAILED=1
  fi
done <<RUNTIME_FILES
$FILES
RUNTIME_FILES
if [ "$DL_FAILED" != "0" ]; then
  echo "  ✗ Some runtime files failed to download — the daemon would be broken." >&2
  echo "    Check your internet connection (proxy? VPN? firewall?) and re-run install.sh." >&2
  echo "    It resumes where it left off — already-downloaded files are simply refreshed." >&2
  exit 1
fi

# ── 3a. package.json + npm install (when needed) ──────────────────
NEED_NPM=0
for need in @supabase/supabase-js @modelcontextprotocol/sdk ws ethers chokidar node-cron node-pty node-forge better-sqlite3 sqlite-vec; do
  [ -d "$DATA_DIR/node_modules/$need" ] || NEED_NPM=1
done
# node-pty's prebuilt spawn-helper sometimes lands without an execute
# bit (npm extraction mode preservation quirk on macOS). Run after every
# install — cheap and idempotent.
fix_node_pty_perms() {
  for p in "$DATA_DIR"/node_modules/node-pty/prebuilds/*/spawn-helper \
           "$DATA_DIR"/node_modules/node-pty/build/Release/spawn-helper; do
    [ -e "$p" ] && chmod +x "$p" 2>/dev/null || true
  done
}

if [ "$NEED_NPM" = "1" ]; then
  echo "▶ 3a/5 Installing daemon dependencies..."
  cat > "$DATA_DIR/package.json" <<'EOF'
{
  "type": "module",
  "dependencies": {
    "ws": "^8.0.0",
    "ethers": "^6.16.0",
    "@supabase/supabase-js": "^2.99.2",
    "@modelcontextprotocol/sdk": "^1.12.0",
    "chokidar": "^4.0.3",
    "node-cron": "^3.0.3",
    "node-pty": "^1.1.0",
    "node-forge": "^1.4.0",
    "better-sqlite3": "^12.9.0",
    "sqlite-vec": "^0.1.9"
  }
}
EOF
  do_chown "$SERVICE_USER" "$DATA_DIR/package.json"
  if [ -z "$NPM_BIN" ] || [ ! -x "$NPM_BIN" ]; then
    echo "  ✗ npm not found next to node ($NODE_BIN)." >&2
    echo "    Reinstall Node.js from https://nodejs.org (npm ships with it), then re-run install.sh." >&2
    exit 1
  fi
  NPM_FAILED=0
  if [ "$USERSPACE" = "1" ]; then
    "$NPM_BIN" --prefix "$DATA_DIR" install --omit=dev --no-audit --no-fund --silent 2>&1 || NPM_FAILED=1
  else
    sudo -u "$SERVICE_USER" env \
      "HOME=$DATA_DIR" \
      "PATH=$(dirname "$NODE_BIN"):/usr/local/bin:/usr/bin:/bin" \
      "$NPM_BIN" --prefix "$DATA_DIR" install --omit=dev --no-audit --no-fund --silent 2>&1 || NPM_FAILED=1
  fi
  # A failed npm install must not end in a cheerful "installed" — without
  # these deps the daemon crashes on boot. Verify the load-critical native
  # module actually landed and bail with a remedy if it didn't.
  if [ "$NPM_FAILED" = "1" ] || [ ! -d "$DATA_DIR/node_modules/better-sqlite3" ]; then
    echo "  ✗ Daemon dependencies failed to install (npm install)." >&2
    echo "    Usually a network/proxy issue. Check your connection, then retry with:" >&2
    echo "      cd $DATA_DIR && npm install --omit=dev" >&2
    echo "    …or just re-run install.sh — it resumes where it left off." >&2
    exit 1
  fi
else
  echo "▶ 3a/5 Daemon dependencies already present — skipping npm install."
fi

# Always fix node-pty perms — even on the cached path, in case a prior
# install left spawn-helper without the execute bit.
fix_node_pty_perms

# Syntax-validate the runtime before declaring success. node --check only
# parses (no imports execute) — catches truncated/error-page downloads
# that would otherwise boot into a raw SyntaxError with no remedy. Runs
# after 3a so $DATA_DIR/package.json (type: module) exists and the .js
# files parse as ESM.
CHECK_FAILED=0
for f in daemon.js signer.mjs core.js trust.js providers.js transport.js \
         spending.mjs security.mjs verify.mjs dreams.mjs dream-now.mjs log.mjs \
         myclawn.js ask.js run.js fleet.js keys.js; do
  if [ ! -f "$DATA_DIR/$f" ]; then
    echo "  ✗ missing runtime file: $f" >&2
    CHECK_FAILED=1
    continue
  fi
  if ! "$NODE_BIN" --check "$DATA_DIR/$f" 2>/dev/null; then
    echo "  ✗ $f failed syntax check (corrupt or partial download)." >&2
    CHECK_FAILED=1
  fi
done
if [ "$CHECK_FAILED" != "0" ]; then
  echo "  ✗ Runtime validation failed — re-run install.sh to re-download." >&2
  echo "    If it keeps failing, check https://www.myclawn.com status from a browser." >&2
  exit 1
fi

if [ "$USERSPACE" != "1" ]; then
  chown -R "$SERVICE_USER" "$DATA_DIR"
  chmod -R u=rwX,go= "$DATA_DIR"
  chmod 0711 "$DATA_DIR"
  chmod 0755 "$DATA_DIR/.myclawn"
elif [ "$(id -u)" -eq 0 ]; then
  # Userspace mode invoked via admin wrapper (desktop app) — we are root
  # right now. Make sure everything ends up owned by the real user, not
  # by root. Without this the daemon LaunchAgent (which runs as the
  # user) can't read its own files.
  chown -R "$ORIG_USER" "$DATA_DIR"
  chmod -R u=rwX,go= "$DATA_DIR"
  chmod 0700 "$DATA_DIR"
fi

# ── 3b. Reverse-migrate from legacy /var/myclawn system install ──
# The default install shape is now userspace ($HOME/.myclawn). If we
# detect a previous system install (data at /var/myclawn under the
# _myclawn user + a LaunchDaemon/systemd unit), tear down the system
# daemon and pull the state into $HOME/.myclawn so the user keeps
# their wallet, credentials, conversations, memory across the move.
if [ "$USERSPACE" = "1" ] && [ -d /var/myclawn ]; then
  echo "▶ Found legacy system install at /var/myclawn — migrating to $DATA_DIR..."

  # Tear down system service first so it isn't holding sockets / files.
  case "$PLATFORM" in
    macos)
      sudo launchctl bootout system/com.myclawn.daemon 2>/dev/null || true
      for _ in 1 2 3 4 5; do
        sudo launchctl print system/com.myclawn.daemon >/dev/null 2>&1 || break
        sleep 1
      done
      sudo rm -f /Library/LaunchDaemons/com.myclawn.daemon.plist 2>/dev/null || true
      ;;
    linux)
      sudo systemctl disable --now myclawn-daemon.service 2>/dev/null || true
      sudo rm -f /etc/systemd/system/myclawn-daemon.service 2>/dev/null || true
      sudo systemctl daemon-reload 2>/dev/null || true
      ;;
  esac

  # Copy state over (sudo because /var/myclawn is _myclawn-owned).
  LEGACY_FILES="wallet.json wallet.enc config.json credentials.json spending.json approvals.json keypair.json daemon.key daemon.pub daemon.fp signer.cookie connect_info.json personality.json chat.json queue.json core.md focus.md agent_notes.json usage.json cooldown.json backoff.json blocked.json shim.key shim.pub audit.jsonl"
  for f in $LEGACY_FILES; do
    if sudo test -f "/var/myclawn/$f" && [ ! -f "$DATA_DIR/$f" ]; then
      sudo cp -p "/var/myclawn/$f" "$DATA_DIR/$f" 2>/dev/null && \
        sudo chown "$ORIG_USER" "$DATA_DIR/$f" && \
        echo "  ✓ $f"
    fi
  done
  LEGACY_DIRS="conversations memory skills .dreams .myclawn"
  for d in $LEGACY_DIRS; do
    if sudo test -d "/var/myclawn/$d" && [ ! -d "$DATA_DIR/$d" ]; then
      sudo cp -Rp "/var/myclawn/$d" "$DATA_DIR/$d" 2>/dev/null && \
        sudo chown -R "$ORIG_USER" "$DATA_DIR/$d" && \
        echo "  ✓ $d/"
    fi
  done

  # Move /var/myclawn out of the way (don't delete — keep as backup).
  sudo mv /var/myclawn /var/myclawn.backup-$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
  echo "  Legacy data preserved at /var/myclawn.backup-* (delete after verifying)."

  # Mark _myclawn system user for later cleanup. Don't delete yet —
  # gives the user a way to recover if migration was incomplete.
  echo "  Legacy _myclawn user still present; delete with: sudo dscl . -delete /Users/_myclawn (macOS) or sudo userdel _myclawn (linux)."
fi

# ── 3c. Migrate existing user-space install (legacy system-mode path) ─
# Kept for the rare case someone explicitly invokes MYCLAWN_SYSTEM=1.
# Default install (USERSPACE=1) never enters this branch.
if [ "$USERSPACE" != "1" ]; then
  USER_DATA_HOME="$ORIG_HOME/.myclawn"
  USER_DATA_CFG="$ORIG_HOME/.config/myclawn"
  if [ -d "$USER_DATA_HOME" ] || [ -d "$USER_DATA_CFG" ]; then
    echo "▶ Migrating data from user-space install to $DATA_DIR..."

    # Tear down the user-space LaunchAgent / systemd unit BEFORE copying
    # state. Without this, two daemons share the same Label across
    # different launchd domains: the old LaunchAgent (running as
    # ORIG_USER) keeps owning the control socket / pid file under
    # ~/.myclawn, the new LaunchDaemon (under _myclawn at /var/myclawn)
    # never wins, and `launchctl print system/com.myclawn.daemon`
    # silently shows "not found" — the symptom that left the user with
    # a half-migrated install where pause/resume/quit all silently
    # failed because the Tauri tray hit the userspace socket.
    case "$PLATFORM" in
      macos)
        USER_AGENT_PLIST="$ORIG_HOME/Library/LaunchAgents/com.myclawn.daemon.plist"
        USER_UID="$(id -u "$ORIG_USER" 2>/dev/null || echo "")"
        if [ -n "$USER_UID" ]; then
          # bootout in the user's GUI domain. Errors are ignored — a
          # missing job is fine; we just want it gone.
          launchctl bootout "gui/$USER_UID/com.myclawn.daemon" 2>/dev/null || true
          # Same poll-for-teardown dance as the bootstrap below.
          for _ in 1 2 3 4 5; do
            launchctl print "gui/$USER_UID/com.myclawn.daemon" >/dev/null 2>&1 || break
            sleep 1
          done
        fi
        if [ -f "$USER_AGENT_PLIST" ]; then
          rm -f "$USER_AGENT_PLIST"
          echo "  ✓ removed user LaunchAgent ($USER_AGENT_PLIST)"
        fi
        ;;
      linux)
        # Per-user systemd unit name, if the user-space install used one.
        if command -v systemctl >/dev/null 2>&1; then
          sudo -u "$ORIG_USER" XDG_RUNTIME_DIR="/run/user/$(id -u "$ORIG_USER")" \
            systemctl --user disable --now myclawn-daemon.service 2>/dev/null || true
        fi
        rm -f "$ORIG_HOME/.config/systemd/user/myclawn-daemon.service" 2>/dev/null || true
        ;;
    esac

    for PIDF in "$USER_DATA_CFG/daemon.pid" "$USER_DATA_CFG/signer.pid" "$USER_DATA_HOME/daemon.pid" "$USER_DATA_HOME/signer.pid"; do
      [ -f "$PIDF" ] && kill "$(cat "$PIDF" 2>/dev/null)" 2>/dev/null && rm -f "$PIDF"
    done
    rm -f "$USER_DATA_CFG/signer.sock" "$USER_DATA_CFG/mcp.sock" "$USER_DATA_CFG/helper.sock" \
          "$USER_DATA_HOME/signer.sock" "$USER_DATA_HOME/mcp.sock" "$USER_DATA_HOME/helper.sock" \
          "$USER_DATA_HOME/control.sock" 2>/dev/null || true
    STATE_FILES="wallet.json config.json credentials.json spending.json approvals.json keypair.json daemon.key daemon.pub daemon.fp signer.cookie connect_info.json personality.json chat.json queue.json core.md focus.md agent_notes.json usage.json cooldown.json backoff.json blocked.json shim.key shim.pub audit.jsonl"
    for SRC in "$USER_DATA_HOME" "$USER_DATA_CFG"; do
      [ -d "$SRC" ] || continue
      for f in $STATE_FILES; do
        if [ -f "$SRC/$f" ] && [ ! -f "$DATA_DIR/$f" ]; then
          cp -p "$SRC/$f" "$DATA_DIR/$f" 2>/dev/null && echo "  ✓ $f"
        fi
      done
    done
    STATE_DIRS="conversations memory skills .dreams"
    for SRC in "$USER_DATA_HOME" "$USER_DATA_CFG"; do
      [ -d "$SRC" ] || continue
      for d in $STATE_DIRS; do
        if [ -d "$SRC/$d" ] && [ ! -d "$DATA_DIR/$d" ]; then
          cp -Rp "$SRC/$d" "$DATA_DIR/$d" 2>/dev/null && echo "  ✓ $d/"
        fi
      done
    done
    chown -R "$SERVICE_USER" "$DATA_DIR"
    chmod -R u=rwX,go= "$DATA_DIR"
    chmod 0711 "$DATA_DIR"
    echo "  Originals at $USER_DATA_HOME and $USER_DATA_CFG are preserved."
  fi
fi

# ── 3c. legacy claude-code keychain bridge — removed ──────────────
# Old installs (< this version) used /usr/local/bin/myclawn-claude-bridge
# plus a sudoers rule to let the daemon spawn `claude` as the user.
# The helper.sock path (helper.js running as the user via LaunchAgent /
# systemd user service) supersedes it entirely. Clean up the artifacts
# left by previous installers so they don't accumulate.
if [ "$PLATFORM" = "macos" ]; then
  rm -f /usr/local/bin/myclawn-claude-bridge
  rm -f /etc/sudoers.d/myclawn-claude
fi

# ── 4. Service unit ───────────────────────────────────────────────
echo "▶ 4/5 Registering background service..."
case "$PLATFORM" in
  macos)
    if [ "$USERSPACE" = "1" ]; then
      USER_NAME_KV=""  # LaunchAgent inherits the user automatically
    else
      USER_NAME_KV="  <key>UserName</key>          <string>$SERVICE_USER</string>"
    fi
    mkdir -p "$(dirname "$PLIST_PATH")"
    cat > "$PLIST_PATH" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>             <string>com.myclawn.daemon</string>
$USER_NAME_KV
  <key>WorkingDirectory</key>  <string>$DATA_DIR</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>                     <string>$ORIG_HOME/.local/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    <key>HOME</key>                     <string>$ORIG_HOME</string>
    <key>MYCLAWN_HOME</key>             <string>$DATA_DIR</string>
    <key>MYCLAWN_CONFIG_DIR</key>       <string>$DATA_DIR</string>
    <key>MYCLAWN_SIGNER_SOCKET</key>    <string>$DATA_DIR/signer.sock</string>
    <key>MYCLAWN_SIGNER_COOKIE</key>    <string>$DATA_DIR/signer.cookie</string>
    <key>MYCLAWN_CONTROL_SOCKET</key>   <string>$DATA_DIR/control.sock</string>
    <key>MYCLAWN_HELPER_SOCKET</key>    <string>$DATA_DIR/helper.sock</string>
    <key>MYCLAWN_MCP_SOCKET</key>       <string>$DATA_DIR/mcp.sock</string>
  </dict>
  <key>ProgramArguments</key>
  <array>
    <string>$NODE_BIN</string>
    <string>$DATA_DIR/daemon.js</string>
  </array>
  <key>RunAtLoad</key>         <true/>
  <key>KeepAlive</key>
  <dict>
    <key>SuccessfulExit</key>  <false/>
    <key>Crashed</key>         <true/>
  </dict>
  <key>StandardErrorPath</key> <string>$DATA_DIR/daemon.err.log</string>
  <key>StandardOutPath</key>   <string>$DATA_DIR/daemon.log</string>
</dict>
</plist>
EOF
    chmod 0644 "$PLIST_PATH"
    if [ "$USERSPACE" = "1" ]; then
      chown "$ORIG_USER" "$PLIST_PATH" 2>/dev/null || true
    fi
    # Reliable load sequence on macOS — derived from a real install where
    # the first bootstrap hit "5: Input/output error" because the same
    # Label was previously held by a user LaunchAgent (different domain,
    # but launchd's database still flagged the service as ghost-loaded
    # in the system domain). The recovery on that machine was:
    #   bootout system/com.myclawn.daemon  (returns 3 if not present, fine)
    #   enable  system/com.myclawn.daemon  (clears any disabled state)
    #   bootstrap system $PLIST_PATH       (works after the cleanup)
    # We now run that exact sequence here so install.sh succeeds first try.
    #
    # Stderr is captured (NOT swallowed) so any remaining failure surfaces
    # in the picker output — the previous 2>/dev/null masked every problem
    # as "daemon will retry on next launch" with no useful signal.
    launchctl bootout "$LAUNCHCTL_DOMAIN/com.myclawn.daemon" 2>/dev/null || true
    for _ in 1 2 3 4 5; do
      launchctl print "$LAUNCHCTL_DOMAIN/com.myclawn.daemon" >/dev/null 2>&1 || break
      sleep 1
    done
    # Belt-and-suspenders: clear any "disabled" state for this Label.
    # On macOS this is harmless if already enabled; on Linux it's a no-op
    # because we're in the macos branch.
    launchctl enable "$LAUNCHCTL_DOMAIN/com.myclawn.daemon" 2>/dev/null || true

    BOOTSTRAP_OK=0
    BOOTSTRAP_ERR=""
    for _ in 1 2 3 4 5; do
      BOOTSTRAP_ERR="$(launchctl bootstrap "$LAUNCHCTL_DOMAIN" "$PLIST_PATH" 2>&1)"
      if [ -z "$BOOTSTRAP_ERR" ]; then
        BOOTSTRAP_OK=1
        break
      fi
      sleep 1
    done
    if [ "$BOOTSTRAP_OK" != "1" ]; then
      echo "  ⚠ launchctl bootstrap failed:" >&2
      echo "      $BOOTSTRAP_ERR" >&2
      LOAD_ERR="$(launchctl load "$PLIST_PATH" 2>&1)"
      if [ -n "$LOAD_ERR" ]; then
        echo "  ⚠ launchctl load also failed: $LOAD_ERR" >&2
        echo "  ⚠ daemon will not start. Recover manually with:" >&2
        echo "      sudo launchctl bootout    $LAUNCHCTL_DOMAIN/com.myclawn.daemon" >&2
        echo "      sudo launchctl enable     $LAUNCHCTL_DOMAIN/com.myclawn.daemon" >&2
        echo "      sudo launchctl bootstrap  $LAUNCHCTL_DOMAIN $PLIST_PATH" >&2
        echo "      sudo tail -50 $DATA_DIR/daemon.err.log" >&2
      fi
    fi
    ;;
  linux)
    if [ "$USERSPACE" = "1" ]; then
      # User-space mode (the default): a unit in /etc/systemd/system needs
      # root, which we deliberately don't take here. Writing it anyway was
      # the Ubuntu install-breaker — `cat > /etc/systemd/system/...` failed
      # with EACCES and `set -e` aborted the whole install before step 5
      # ever put the `myclawn` CLI on PATH, so the documented command simply
      # didn't exist. The launcher is the supervisor in this mode: `myclawn`
      # (foreground) and `myclawn --background` start the signer + daemon
      # together and keep them alive, and `myclawn --stop` stops them. The
      # daemon comes up on the user's first `myclawn` run (which also handles
      # registration), so there's nothing to register at install time.
      echo "  User-space: the daemon is launcher-managed (no system service)."
      echo "             Start it any time with:  myclawn        (foreground)"
      echo "                                  or:  myclawn --background"
    else
      UNIT="/etc/systemd/system/myclawn-daemon.service"
      cat > "$UNIT" <<EOF
[Unit]
Description=MyClawn daemon
After=network.target

[Service]
Type=simple
User=$SERVICE_USER
WorkingDirectory=$DATA_DIR
Environment=MYCLAWN_HOME=$DATA_DIR
Environment=MYCLAWN_CONFIG_DIR=$DATA_DIR
Environment=MYCLAWN_SIGNER_SOCKET=$DATA_DIR/signer.sock
Environment=MYCLAWN_SIGNER_COOKIE=$DATA_DIR/signer.cookie
Environment=MYCLAWN_CONTROL_SOCKET=$DATA_DIR/control.sock
Environment=MYCLAWN_HELPER_SOCKET=$DATA_DIR/helper.sock
Environment=MYCLAWN_MCP_SOCKET=$DATA_DIR/mcp.sock
ExecStart=$NODE_BIN $DATA_DIR/daemon.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=$DATA_DIR
PrivateTmp=true
StandardOutput=append:$DATA_DIR/daemon.log
StandardError=append:$DATA_DIR/daemon.err.log

[Install]
WantedBy=multi-user.target
EOF
      systemctl daemon-reload
      systemctl enable myclawn-daemon.service
      systemctl restart myclawn-daemon.service
    fi
    ;;
esac

# ── 5. User CLI ───────────────────────────────────────────────────
echo "▶ 5/5 Installing user CLI..."
mkdir -p "$USER_BIN_DIR" 2>/dev/null || true
USER_BIN="$USER_BIN_DIR/myclawn"
if ! curl -fsSL "$BASE_URL/myclawn-launcher.sh" -o "$USER_BIN"; then
  echo "  ✗ Could not download the myclawn CLI — without it there is no 'myclawn' command." >&2
  echo "    Check your connection and re-run install.sh, or fetch it manually:" >&2
  echo "      curl -fsSL $BASE_URL/myclawn-launcher.sh -o $USER_BIN && chmod +x $USER_BIN" >&2
  exit 1
fi
chmod 0755 "$USER_BIN" 2>/dev/null || true
do_chown "$ORIG_USER" "$USER_BIN" 2>/dev/null || true

# Idempotently make sure $USER_BIN_DIR is on the user's interactive PATH.
# Without this the first thing a user sees after `curl | bash` is the
# launcher complaining "Native installation exists but ~/.local/bin is not
# in your PATH" — i.e. the documented `myclawn` command fails until they
# manually edit a dotfile.
ensure_path_in_rc() {
  rc="$1"
  [ -f "$rc" ] || return 0
  if grep -Fq "$USER_BIN_DIR" "$rc" 2>/dev/null; then return 0; fi
  printf '\n# Added by MyClawn install\nexport PATH="%s:$PATH"\n' "$USER_BIN_DIR" >> "$rc"
  do_chown "$ORIG_USER" "$rc" 2>/dev/null || true
}
ensure_path_in_rc "$ORIG_HOME/.zshrc"
ensure_path_in_rc "$ORIG_HOME/.bashrc"
ensure_path_in_rc "$ORIG_HOME/.profile"
# zsh on macOS only reads .zshrc for interactive shells; create it if
# missing so the launcher doesn't trip in fresh-user setups.
if [ "$PLATFORM" = "macos" ] && [ ! -f "$ORIG_HOME/.zshrc" ]; then
  printf '# Added by MyClawn install\nexport PATH="%s:$PATH"\n' "$USER_BIN_DIR" > "$ORIG_HOME/.zshrc"
  do_chown "$ORIG_USER" "$ORIG_HOME/.zshrc" 2>/dev/null || true
fi

# Directed-invite attribution (landing page: /i/<code> bakes
# MYCLAWN_INVITE into the curl). Sanitize to the code charset and persist
# so whichever path registers — step 6 here, or the launcher on first
# interactive run — attaches it to the registration.
NI_INVITE=$(printf '%s' "${MYCLAWN_INVITE:-}" | tr -cd 'A-Za-z0-9._-')
if [ -n "$NI_INVITE" ]; then
  printf '%s' "$NI_INVITE" > "$DATA_DIR/invite_code" 2>/dev/null || true
  do_chown "$SERVICE_USER" "$DATA_DIR/invite_code" 2>/dev/null || true
fi
INVITE_JSON=""
[ -n "$NI_INVITE" ] && INVITE_JSON=",\"invite_code\":\"${NI_INVITE}\""

# ── 6. Non-interactive registration (called from Tauri picker / web setup) ─
if [ "${MYCLAWN_NONINTERACTIVE:-0}" = "1" ]; then
  if [ "$USERSPACE" != "1" ]; then
    sudo -u "$ORIG_USER" mkdir -p "$USER_CFG_DIR" 2>/dev/null || mkdir -p "$USER_CFG_DIR"
  else
    mkdir -p "$USER_CFG_DIR"
  fi

  # A corrupt credentials.json (interrupted earlier write, manual edit
  # gone wrong) would both skip registration AND crash-loop the daemon's
  # boot. Validate it; quarantine and register fresh on failure.
  if [ -f "$DATA_DIR/credentials.json" ] && \
     ! "$NODE_BIN" -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$DATA_DIR/credentials.json" 2>/dev/null; then
    mv "$DATA_DIR/credentials.json" "$DATA_DIR/credentials.json.bad-$(date +%Y%m%d-%H%M%S)"
    echo "  ⚠ Existing credentials.json was corrupt — moved to credentials.json.bad-*; registering fresh."
  fi

  if [ ! -f "$DATA_DIR/credentials.json" ]; then
    echo "▶ 6/6 Registering agent..."
    NI_NAME="${MYCLAWN_AGENT_NAME:-${ORIG_USER}_AI}"
    NI_PROVIDER="${MYCLAWN_PROVIDER:-claude-code}"
    REG_OK=0
    for attempt in 1 2 3 4 5; do
      NI_TRY="$NI_NAME"
      # $RANDOM is a bash-ism (empty under dash, which install.sh may run as
      # via `exec sh`); PID+attempt is a portable, collision-free suffix.
      [ "$attempt" -gt 1 ] && NI_TRY="${NI_NAME}-$$-${attempt}"
      REG_RESULT=$(curl -s -X POST "${BASE_URL}/api/clones/register" \
        -H "Content-Type: application/json" \
        -d "{\"name\":\"${NI_TRY}\",\"manifest\":{\"knowledge\":[],\"offers\":[],\"seeks\":[]},\"agent_type\":\"claude-code\"${INVITE_JSON}}")
      if MYCLAWN_REG_NAME="$NI_TRY" \
         MYCLAWN_REG_PROVIDER="$NI_PROVIDER" \
         MYCLAWN_REG_API_KEY="${MYCLAWN_API_KEY:-}" \
         MYCLAWN_REG_MODEL="${MYCLAWN_MODEL:-}" \
         MYCLAWN_REG_DATA_DIR="$DATA_DIR" \
         MYCLAWN_REG_USER_CFG="$USER_CFG_DIR" \
         MYCLAWN_REG_BASE="$BASE_URL" \
         "$NODE_BIN" -e "
const fs = require('fs');
let data = {};
try { data = JSON.parse(process.argv[1] || '{}'); } catch (e) { process.exit(2); }
if (data.error || !data.id) process.exit(2);
const settings = { provider: process.env.MYCLAWN_REG_PROVIDER };
if (process.env.MYCLAWN_REG_API_KEY) settings.api_key = process.env.MYCLAWN_REG_API_KEY;
if (process.env.MYCLAWN_REG_MODEL)   settings.model   = process.env.MYCLAWN_REG_MODEL;
const creds = {
  clone_id: data.id, api_key: data.api_key,
  clone_name: process.env.MYCLAWN_REG_NAME,
  connect_code: data.connect_code,
  settings,
};
fs.writeFileSync(process.env.MYCLAWN_REG_DATA_DIR + '/credentials.json', JSON.stringify(creds, null, 2));
fs.writeFileSync(process.env.MYCLAWN_REG_DATA_DIR + '/personality.json', JSON.stringify({ knowledge: [], offers: [], seeks: [] }, null, 2));
const url = data.connect_url || process.env.MYCLAWN_REG_BASE + '/api/connect/' + data.connect_code;
fs.writeFileSync(process.env.MYCLAWN_REG_USER_CFG + '/connect_info.json', JSON.stringify({ code: data.connect_code, url, name: process.env.MYCLAWN_REG_NAME }));
console.log('  Registered as ' + process.env.MYCLAWN_REG_NAME);
" "$REG_RESULT"; then
        REG_OK=1
        break
      fi
      # Brief pause between attempts — instant retries just hammer an API
      # that's already struggling (and burn into the per-IP rate limit).
      [ "$attempt" -lt 5 ] && sleep 3
    done
    if [ "$REG_OK" = "1" ]; then
      do_chown "$SERVICE_USER" "$DATA_DIR/credentials.json" "$DATA_DIR/personality.json" 2>/dev/null || true
      chmod 0600 "$DATA_DIR/credentials.json" "$DATA_DIR/personality.json" 2>/dev/null || true
      do_chown "$ORIG_USER" "$USER_CFG_DIR/connect_info.json" 2>/dev/null || true
      chmod 0644 "$USER_CFG_DIR/connect_info.json" 2>/dev/null || true
    else
      # Surface the server response so the picker (and curl users) get an
      # actionable error instead of falling through to a daemon that crashes
      # in a loop because credentials.json was never written. The Tauri shell
      # captures stderr + non-zero exit and shows the tail to the user.
      echo "  ✗ Registration failed after 5 attempts." >&2
      echo "    Last server response:" >&2
      printf '    %s\n' "$REG_RESULT" >&2
      case "$REG_RESULT" in
        *rate_limit*)
          echo "    You were rate-limited — wait 60s and re-run install.sh (it resumes)." >&2 ;;
        *)
          echo "    This usually means the MyClawn API at $BASE_URL is temporarily down." >&2
          echo "    Check https://www.myclawn.com — try again in a few minutes." >&2 ;;
      esac
      exit 1
    fi
  else
    echo "▶ 6/6 Refreshing connect URL..."
    # Switch-provider support: when MYCLAWN_PROVIDER (or _API_KEY /
    # _MODEL) is supplied on a re-run, merge the new values into
    # credentials.json's settings block before refreshing the connect
    # code. Daemon picks them up on the kickstart at the end of step 6.
    set +e
    MYCLAWN_REG_PROVIDER="${MYCLAWN_PROVIDER:-}" \
    MYCLAWN_REG_API_KEY="${MYCLAWN_API_KEY:-}" \
    MYCLAWN_REG_MODEL="${MYCLAWN_MODEL:-}" \
    "$NODE_BIN" -e "
const fs = require('fs');
const path = '$DATA_DIR/credentials.json';
const c = JSON.parse(fs.readFileSync(path, 'utf8'));
c.settings = c.settings || {};
const oldProvider = c.settings.provider;
const newProvider = process.env.MYCLAWN_REG_PROVIDER || oldProvider;
const providerChanged = newProvider && oldProvider !== newProvider;
let dirty = false;
// When the provider changes, reset api_key and model to whatever the
// caller passed (or clear them if they didn't pass anything). Mixing
// stale anthropic settings into a claude-code spawn is exactly the
// kind of subtle mis-config that causes 'agent silently broken'.
if (providerChanged) {
  c.settings.provider = newProvider;
  if (process.env.MYCLAWN_REG_API_KEY) {
    c.settings.api_key = process.env.MYCLAWN_REG_API_KEY;
  } else {
    delete c.settings.api_key;
  }
  if (process.env.MYCLAWN_REG_MODEL) {
    c.settings.model = process.env.MYCLAWN_REG_MODEL;
  } else {
    delete c.settings.model;
  }
  dirty = true;
} else {
  // Same provider — only update fields the caller explicitly supplied.
  if (process.env.MYCLAWN_REG_API_KEY && c.settings.api_key !== process.env.MYCLAWN_REG_API_KEY) {
    c.settings.api_key = process.env.MYCLAWN_REG_API_KEY;
    dirty = true;
  }
  if (process.env.MYCLAWN_REG_MODEL && c.settings.model !== process.env.MYCLAWN_REG_MODEL) {
    c.settings.model = process.env.MYCLAWN_REG_MODEL;
    dirty = true;
  }
}
if (dirty) {
  fs.writeFileSync(path, JSON.stringify(c, null, 2));
  const tail = c.settings.model ? ', model=' + c.settings.model : '';
  console.log('  Updated credentials.json settings: provider=' + c.settings.provider + tail);
}
" 2>/dev/null || true
    set -e

    set +e
    CC_CREDS=$("$NODE_BIN" -e "
const fs = require('fs');
const c = JSON.parse(fs.readFileSync('$DATA_DIR/credentials.json', 'utf8'));
process.stdout.write((c.clone_id || '') + '\n' + (c.api_key || ''));
" 2>/dev/null)
    set -e
    CLONE_ID=$(printf '%s' "$CC_CREDS" | sed -n '1p')
    CLONE_KEY=$(printf '%s' "$CC_CREDS" | sed -n '2p')
    if [ -n "$CLONE_ID" ] && [ -n "$CLONE_KEY" ]; then
      CC_JSON=$(curl -s -X POST \
        "${BASE_URL}/api/clones/${CLONE_ID}/connect-code" \
        -H "Authorization: Bearer ${CLONE_KEY}")
      set +e
      MYCLAWN_REG_USER_CFG="$USER_CFG_DIR" \
      MYCLAWN_REG_DATA_DIR="$DATA_DIR" \
      MYCLAWN_REG_BASE="$BASE_URL" \
      "$NODE_BIN" -e "
const fs = require('fs');
let data = {};
try { data = JSON.parse(process.argv[1] || '{}'); } catch (e) { process.exit(2); }
if (!data.connect_code) process.exit(2);
const c = JSON.parse(fs.readFileSync(process.env.MYCLAWN_REG_DATA_DIR + '/credentials.json', 'utf8'));
const url = data.connect_url || process.env.MYCLAWN_REG_BASE + '/api/connect/' + data.connect_code;
fs.writeFileSync(process.env.MYCLAWN_REG_USER_CFG + '/connect_info.json', JSON.stringify({ code: data.connect_code, url, name: c.clone_name }));
console.log('  Refreshed connect URL for ' + c.clone_name);
" "$CC_JSON"
      RC=$?
      set -e
      if [ "$RC" -ne 0 ]; then
        echo "  ⚠ Connect-code refresh failed (rc=$RC, response: $(printf '%s' "$CC_JSON" | head -c 200))." >&2
      else
        do_chown "$ORIG_USER" "$USER_CFG_DIR/connect_info.json" 2>/dev/null || true
        chmod 0644 "$USER_CFG_DIR/connect_info.json" 2>/dev/null || true
      fi
    else
      echo "  ⚠ Could not read clone_id / api_key from $DATA_DIR/credentials.json — skipping refresh." >&2
    fi
  fi

  # Kick the daemon so it picks up any credential changes.
  case "$PLATFORM" in
    macos) launchctl kickstart -k "$LAUNCHCTL_DOMAIN/com.myclawn.daemon" 2>/dev/null || true ;;
    linux)
      if [ "$USERSPACE" = "1" ]; then
        # No system service in user-space mode — start the signer + daemon
        # through the launcher's own background supervisor instead. Runs as
        # the invoking user (the install may be root via the desktop admin
        # wrapper). Best-effort: the user can always run `myclawn` by hand.
        if [ -x "$USER_BIN" ]; then
          if [ "$(id -u)" -eq 0 ] && [ "$ORIG_USER" != "root" ]; then
            sudo -u "$ORIG_USER" env HOME="$ORIG_HOME" MYCLAWN_BASE_URL="$BASE_URL" \
              "$USER_BIN" --background >/dev/null 2>&1 || true
          else
            MYCLAWN_BASE_URL="$BASE_URL" "$USER_BIN" --background >/dev/null 2>&1 || true
          fi
        fi
      else
        systemctl restart myclawn-daemon.service 2>/dev/null || true
      fi
      ;;
  esac

  # Heavy deps: node-llama-cpp ships its own native compilation step
  # (GGML kernels) and takes 1-3 min. The launcher's no-args path
  # normally kicks this off after registration, but the desktop installer
  # talks to install.sh directly and never runs the launcher. Fire the
  # same background install here when we just registered non-interactively,
  # so the brain's local embeddings come online without the user having
  # to drop into a shell afterward.
  DEPS_READY="$DATA_DIR/.deps-heavy-ready"
  HEAVY_PID_FILE="$DATA_DIR/.deps-heavy.pid"
  if [ ! -f "$DEPS_READY" ] || [ ! -d "$DATA_DIR/node_modules/node-llama-cpp" ]; then
    if [ ! -f "$HEAVY_PID_FILE" ] || ! kill -0 "$(cat "$HEAVY_PID_FILE" 2>/dev/null)" 2>/dev/null; then
      rm -f "$DEPS_READY" "$HEAVY_PID_FILE" "$DATA_DIR/.deps-heavy.log"
      (
        cd "$DATA_DIR"
        echo $$ > "$HEAVY_PID_FILE"
        "$NPM_BIN" install --no-audit --no-fund --save \
          node-llama-cpp@^3.13.0 \
          > "$DATA_DIR/.deps-heavy.log" 2>&1
        rc=$?
        rm -f "$HEAVY_PID_FILE"
        if [ "$rc" = "0" ]; then
          touch "$DEPS_READY"
          echo "[deps-heavy] ready" >> "$DATA_DIR/.deps-heavy.log"
        else
          echo "[deps-heavy] failed (exit $rc)" >> "$DATA_DIR/.deps-heavy.log"
        fi
      ) </dev/null >/dev/null 2>&1 &
      echo "  Local embeddings compiling in background (~1-3 min). Tail: $DATA_DIR/.deps-heavy.log"
    fi
  fi
fi

echo ""
echo "✅ MyClawn installed."
echo ""
if [ "$USERSPACE" = "1" ]; then
  echo "   Daemon runs as $ORIG_USER, files in $DATA_DIR"
  echo "   Fleet:  myclawn fleet add <name>    (creates an isolated bot user)"
  echo "           myclawn ls                  (list bots)"
else
  echo "   Legacy system install — daemon runs as $SERVICE_USER, files in $DATA_DIR"
fi
echo "   CLI:  myclawn (run \`myclawn\` to set up your provider)"
echo ""
case "$PLATFORM" in
  macos)
    if [ "$USERSPACE" = "1" ]; then
      echo "   Logs: tail -f $DATA_DIR/daemon.log"
      echo "   Stop: launchctl bootout $LAUNCHCTL_DOMAIN/com.myclawn.daemon"
    else
      echo "   Logs: sudo tail -f $DATA_DIR/daemon.log"
      echo "   Stop: sudo launchctl bootout system/com.myclawn.daemon"
    fi
    ;;
  linux)
    if [ "$USERSPACE" = "1" ]; then
      echo "   Start: myclawn            (foreground)   ·   myclawn --background"
      echo "   Logs:  tail -f $DATA_DIR/daemon.log"
      echo "   Stop:  myclawn --stop"
    else
      echo "   Logs: sudo journalctl -u myclawn-daemon -f"
      echo "   Stop: sudo systemctl stop myclawn-daemon"
    fi
    ;;
esac
echo ""
