#!/bin/sh
# Synchronise AI -> Claude Code, in one command.
#
#   curl -fsSL https://synchronise.ai/mcp | sh
#
# What it does, in order: opens a five-minute handshake with the server, prints a code, waits for
# you to approve it in a browser you are already signed into, then registers the MCP server with
# your Claude Code at user scope so it works in every directory.
#
# What it does NOT do: ask for a password, write a secret to disk itself, or install anything but
# one MCP entry. The credential goes straight to `claude mcp add`. If you would rather read this
# before running it, that is the correct instinct for any `curl | sh`, and it is why this file is
# short enough to read in one screen.
#
# Deliberately POSIX sh, no bashisms, no jq: the whole point is that it runs on a stock mac or
# linux box with nothing installed.
set -eu

VERSION="1"
API="${SYNCHRONISE_API:-https://synchronise.ai}"
NAME="${SYNCHRONISE_TOKEN_NAME:-$(hostname 2>/dev/null || echo "a laptop")}"

if [ -t 1 ]; then
  B=$(printf '\033[1m'); D=$(printf '\033[2m'); G=$(printf '\033[32m'); R=$(printf '\033[31m'); X=$(printf '\033[0m')
  EOL=$(printf '\033[K')
else
  # Not a terminal: no colours, and no erase-to-end-of-line either. That escape was being written
  # unconditionally, so a piped or logged install ended up with a literal control code sitting in
  # the middle of the success line.
  B=""; D=""; G=""; R=""; X=""; EOL=""
fi

say() { printf '%s\n' "$*"; }
die() { printf '%s\n' "  ${R}$*${X}" >&2; exit 1; }

# Pull one string field out of a flat JSON object. Fine for exactly the four keys this script
# reads, and cheaper than making jq a prerequisite for installing anything at all.
field() { sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" <<EOF
$1
EOF
}

say ""
say "  ${B}Synchronise AI${X} ${D}-> Claude Code   (installer v$VERSION)${X}"
say ""

command -v curl >/dev/null 2>&1 || die "curl is required."
if ! command -v claude >/dev/null 2>&1; then
  say "  ${R}Claude Code is not installed.${X}"
  say "  ${D}Install it first, then run this again:${X}"
  say "      npm install -g @anthropic-ai/claude-code"
  say ""
  exit 1
fi

START=$(curl -fsS -X POST "$API/api/mcp/device" -H 'content-type: application/json' \
  -d "{\"action\":\"start\",\"name\":\"$NAME\"}" 2>/dev/null) || die "Could not reach $API."
USER_CODE=$(field "$START" user_code)
DEVICE_CODE=$(field "$START" device_code)
VERIFY_URL=$(field "$START" verification_url)
[ -n "$USER_CODE" ] && [ -n "$DEVICE_CODE" ] || die "The server did not open a session."

say "  Open this and approve:  ${B}$VERIFY_URL${X}"
say "  Your code:              ${B}$USER_CODE${X}"
say ""

# Best effort. A machine with no browser (a server, a container) still shows the URL above, which
# is the whole reason this flow prints one rather than assuming it can open a window.
( command -v open >/dev/null 2>&1 && open "$VERIFY_URL?code=$USER_CODE" >/dev/null 2>&1 ) ||
  ( command -v xdg-open >/dev/null 2>&1 && xdg-open "$VERIFY_URL?code=$USER_CODE" >/dev/null 2>&1 ) || true

printf '  %sWaiting...%s' "$D" "$X"
TOKEN=""; WORKSPACE=""
# 150 polls at 2s is the server's five-minute window. Expiring here is the same as expiring there.
i=0
while [ "$i" -lt 150 ]; do
  i=$((i + 1))
  sleep 2
  POLL=$(curl -fsS -X POST "$API/api/mcp/device" -H 'content-type: application/json' \
    -d "{\"action\":\"poll\",\"device_code\":\"$DEVICE_CODE\"}" 2>/dev/null) || continue
  case "$POLL" in
    *'"status":"ready"'*)
      TOKEN=$(field "$POLL" token)
      WORKSPACE=$(field "$POLL" workspace_name)
      break ;;
    *'"status":"expired"'*)
      printf '\n'; die "That code expired. Run the command again." ;;
  esac
  printf '.'
done
[ -n "$TOKEN" ] || { printf '\n'; die "Timed out waiting for approval."; }

printf '\r  %sApproved%s  %s%s%s%s\n' "$G" "$X" "$D" "$WORKSPACE" "$X" "$EOL"

# `claude mcp add` owns the config file. Writing ~/.claude.json ourselves would be a second home for
# the same fact, and it is the CLI's fact (§5).
claude mcp remove synchronise --scope user >/dev/null 2>&1 || true
claude mcp add synchronise "$API/api/mcp" \
  --scope user \
  --transport http \
  --header "Authorization: Bearer $TOKEN" >/dev/null 2>&1 ||
  die "Could not register the MCP server with Claude Code."

say "  ${G}Installed${X}  ${D}user scope, works in any directory${X}"
say ""
say "  Try this:"
say ""
say "      ${B}claude \"what is in my send queue for tomorrow?\"${X}"
say ""
say "  ${D}Revoke it any time from Settings. Sending, posting and spending are${X}"
say "  ${D}blocked at the server, so nothing leaves without your approval.${X}"
say ""
exit 0
