#!/usr/bin/env bash
# amux-remote — drive another machine's amux server over its REST API.
#
# The `amux` CLI is local-only: `amux ls` reads ~/.amux on the machine it runs
# on. This wrapper talks to another box's amux dashboard (e.g. over Tailscale) so
# you can list, peek, and message sessions running on that box.
#
# A BOX IS ALWAYS REQUIRED — the first argument names a configured box, whose
# ~/.amux/remotes/<box>.env supplies the connection. There is no default remote.
#   AMUX_URL       base URL of the box's server, e.g. https://192.0.2.10:8822
#   AMUX_TOKEN     auth token from the box's ~/.amux/auth_token
#   AMUX_SSH_HOST  SSH hostname for `attach` (defaults to host extracted from AMUX_URL)
#   AMUX_SSH_USER  SSH user for `attach` (defaults to current $USER)
#   AMUX_CC        iTerm2 native attach (control mode): 1/0 to force, "auto" (default) = on in iTerm2
#
# Usage:
#   amux-remote                         list configured boxes (~/.amux/remotes/*.env)
#   amux-remote <box> ls [-c] [--bare]  list sessions (-c = compact grid; --bare = no header)
#   amux-remote <box> attach <name> [--cc|--plain]   SSH in and attach (native iTerm2 tabs in -CC mode)
#   amux-remote <box> peek <name> [lines]    print a session's recent output (default 80)
#   amux-remote <box> send <name> <text...>  send text/prompt to a session
#   amux-remote <box> exec <name> [options]   create and start a session
#   amux-remote <box> start <name> [flags...]   start a session (flags forwarded
#                                       one-shot to the launch; does NOT attach —
#                                       attach with: amux-up <box> <name>)
#   amux-remote <box> stop <name>       stop a session (takes no flags)
#   amux-remote <box> provider <name> [provider]  show or change the coding agent
#   amux-remote <box> info <name>       session status/meta as JSON
#   amux-remote <box> url               print the box's server URL
#   amux-remote <box> curl <path> [args...]  raw authenticated curl against the API

set -euo pipefail

# amux-remote REQUIRES a box. The first argument names a configured box whose
# ~/.amux/remotes/<box>.env supplies AMUX_URL + AMUX_TOKEN (+ AMUX_SSH_HOST).
# There is no default remote — every call targets a box explicitly. The box is
# selected + sourced in the dispatch block at the bottom (once die()/usage() exist).
# The shared library owns colors, config paths, and the secret-safe HTTP helper;
# the renderer keeps local and remote session views identical. Resolve both from
# a checkout or through an install.sh symlink in ~/.local/bin.
_self="${BASH_SOURCE[0]}"
_dir=$(cd "$(dirname "$_self")" && pwd)
if [ -f "$_dir/../lib/amux-common.sh" ]; then
  _libdir=$(cd "$_dir/../lib" && pwd)
else
  _real=$(readlink "$_self" 2>/dev/null || printf '%s' "$_self")
  case "$_real" in /*) ;; *) _real="$_dir/$_real" ;; esac
  _libdir=$(cd "$(dirname "$_real")/../lib" 2>/dev/null && pwd)
fi
[ -n "${_libdir:-}" ] && [ -f "$_libdir/amux-common.sh" ] \
  || { echo "amux-remote: cannot find lib/amux-common.sh" >&2; exit 1; }
# shellcheck disable=SC1090
. "$_libdir/amux-common.sh"
AMUX_RENDER="$_libdir/amux-render.py"
[ -f "$AMUX_RENDER" ] || { echo "amux-remote: cannot find lib/amux-render.py" >&2; exit 1; }

# ASCII status marks on Windows consoles outside WSL, where the round Unicode
# marks often render as boxes. An inherited value always wins; the :- guard is
# required because this script runs under `set -u`, where a bare reference to an
# unset variable aborts before the test can run.
if [ -z "${AMUX_LS_ASCII:-}" ]; then
  case "$(uname -s 2>/dev/null)" in
    MINGW*|MSYS*|CYGWIN*) AMUX_LS_ASCII=1 ;;
    *)                    AMUX_LS_ASCII=0 ;;
  esac
fi
export AMUX_LS_ASCII

# Honor CC_HOME (amux's own variable) so a relocated amux home works, and so the
# test suite can point at a throwaway config dir instead of your real one.
AMUX_REMOTES_DIR="$(amux_remotes_dir)"
BOX="" BOX_ENV=""   # set by the dispatch block once a box is chosen

# space-padded list of configured box names — for --help and error messages.
# MUST NOT fail: with `set -euo pipefail`, a bare `ls` on a missing remotes dir
# would abort the script mid-usage() and print nothing at all on a fresh install.
list_boxes() {
  amux_list_boxes
}

die() { amux_die "$@"; }

require_config() {
  [[ -n "${AMUX_URL:-}" ]]   || die "AMUX_URL is not set (missing from $BOX_ENV)"
  [[ -n "${AMUX_TOKEN:-}" ]] || die "AMUX_TOKEN is not set (get it from box '$BOX's ~/.amux/auth_token)"
  AMUX_URL="${AMUX_URL%/}"  # strip trailing slash
}

# Session names enter REST paths and, for attach, a remotely parsed shell
# command. Keep them within the server's portable name grammar, then encode the
# REST component as defense in depth. Leading '-' and '.' are not valid names.
validate_remote_name() {
  local name="$1"
  [[ "$name" =~ ^[A-Za-z0-9_.-]+$ ]] \
    || die "invalid session name '$name' (allowed: letters, digits, '_', '-', '.')"
  case "$name" in
    -*|.*) die "invalid session name '$name' (must not start with '-' or '.')" ;;
  esac
}

encode_path_component() {
  python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
}

cmd_ls() {
  require_config
  # Flags: -c/--compact = grid (mirrors `amux ls -c`); --bare/--no-header =
  # drop the identity banner (used when embedding, e.g. amux-all / ccjump).
  local compact=0 bare=0
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -c|--compact)        compact=1 ;;
      --bare|--no-header)  bare=1 ;;
      -*)                  die "unknown ls option: $1 (try: -c/--compact, --bare)" ;;
      *)                   die "unexpected argument: $1 (usage: amux-remote $BOX ls [-c] [--bare])" ;;
    esac
    shift
  done

  # Which box are we listing? Prefer the explicit SSH host; else the host in AMUX_URL.
  local label="${AMUX_SSH_HOST:-}"
  if [[ -z "$label" ]]; then
    label=$(printf '%s' "$AMUX_URL" | sed -E 's#^[a-zA-Z]+://([^:/?#]+).*#\1#')
  fi

  amux_api GET /api/sessions | \
    AMUX_LS_LABEL="$label" AMUX_LS_URL="$AMUX_URL" AMUX_LS_COMPACT="$compact" AMUX_LS_BARE="$bare" \
    AMUX_LS_KIND=remote \
    python3 "$AMUX_RENDER"
}

cmd_peek() {
  require_config
  local name="${1:-}"; local lines="${2:-80}"
  [[ -n "$name" ]] || die "usage: amux-remote $BOX peek <name> [lines]"
  validate_remote_name "$name"
  [[ "$lines" =~ ^[0-9]+$ ]] || die "lines must be a number, got: $lines"
  local encoded; encoded=$(encode_path_component "$name")
  amux_api GET "/api/sessions/${encoded}/peek?lines=${lines}" \
    | python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit("server: "+str(d["error"])) if isinstance(d,dict) and d.get("error") else print(d.get("output",""))'
}

cmd_send() {
  require_config
  local name="${1:-}"; shift || true
  local text="$*"
  [[ -n "$name" && -n "$text" ]] || die "usage: amux-remote $BOX send <name> <text...>"
  validate_remote_name "$name"
  local payload encoded
  encoded=$(encode_path_component "$name")
  payload=$(python3 -c 'import json,sys; print(json.dumps({"text": sys.argv[1]}))' "$text")
  amux_api POST "/api/sessions/${encoded}/send" -H 'Content-Type: application/json' -d "$payload" \
    | python3 -c 'import json,sys; d=json.load(sys.stdin); print(("\033[32m✓\033[0m sent" if d.get("ok") else "\033[31m✗\033[0m "+str(d.get("message","failed"))))'
}

# Is <name> running on this box, per the server's authoritative flag?
# exit 0 = running · 1 = not running · 2 = could not tell
_session_running() {
  amux_api GET /api/sessions 2>/dev/null | python3 -c '
import json, sys
try: data = json.load(sys.stdin)
except Exception: sys.exit(2)
for s in (data or []):
    if s.get("name") == sys.argv[1]:
        sys.exit(0 if s.get("running") else 1)
sys.exit(1)' "$1"
}

# Parse a server JSON response into "<0|1>\n<message>" (1 = ok).
_resp_ok_msg() {
  printf '%s' "$1" | python3 -c 'import json,sys
try: d=json.load(sys.stdin)
except Exception: d={}
bad = (isinstance(d,dict) and d.get("error")) or (isinstance(d,dict) and not d.get("ok",True))
print("0" if bad else "1")
print(str((d.get("error") or d.get("message","failed")) if isinstance(d,dict) else "failed"))' 2>/dev/null
}

# start <name> [flags...] — flags are forwarded to the server as ONE-SHOT launch
# flags (JSON body {"flags": "<shlex-joined>"}), e.g. --provider opencode. They
# survive spaces/quotes: shlex.join here, then the server validates and re-quotes
# each token at spawn time. NOTE: for claude sessions resume is AUTOMATIC (the
# server auto-resumes the newest conversation on start), so --continue is rarely
# needed — but it is forwarded and honored. `start` does NOT attach; attach
# with: amux-up <machine> <name>.
cmd_start() {
  require_config
  local name="${1:-}"; shift || true
  [[ -n "$name" ]] || die "usage: amux-remote $BOX start <name> [flags...]"
  validate_remote_name "$name"
  local resp encoded
  encoded=$(encode_path_component "$name")
  if [[ $# -gt 0 ]]; then
    local payload
    payload=$(python3 -c 'import json, shlex, sys; print(json.dumps({"flags": shlex.join(sys.argv[1:])}))' "$@") \
      || die "could not encode start flags"
    resp=$(amux_api POST "/api/sessions/${encoded}/start" -H 'Content-Type: application/json' -d "$payload")
  else
    resp=$(amux_api POST "/api/sessions/${encoded}/start")
  fi
  local ok; ok=$(_resp_ok_msg "$resp")
  local good="${ok%%$'\n'*}" msg="${ok#*$'\n'}"
  if [[ "$good" != "1" ]]; then
    echo "${RED}✗${RESET} ${msg:-failed}"; return 1
  fi
  echo "${GREEN}✓${RESET} ${msg:-started}"
  # start != attach over the REST API — say what the next step is, unless a
  # wrapper (amux-up) is about to attach for us and set AMUX_NO_HINT=1.
  [[ -n "${AMUX_NO_HINT:-}" ]] || echo "${DIM}  attach: amux-up $BOX $name${RESET}"
}

cmd_simple_post() {  # stop/clear — take NO flags; error loudly on extras
  require_config
  local action="$1" name="${2:-}"
  [[ -n "$name" ]] || die "usage: amux-remote $BOX ${action} <name>"
  validate_remote_name "$name"
  shift 2
  [[ $# -eq 0 ]] || die "unexpected extra arguments after '$name': $* — 'amux-remote $BOX $action' takes no flags"
  local resp ok encoded
  encoded=$(encode_path_component "$name")
  resp=$(amux_api POST "/api/sessions/${encoded}/${action}")
  ok=$(_resp_ok_msg "$resp")
  local good="${ok%%$'\n'*}" msg="${ok#*$'\n'}"
  if [[ "$good" != "1" ]]; then
    echo "${RED}✗${RESET} ${msg:-failed}"; return 1
  fi

  # `stop` is a GRACEFUL stop server-side: it exits the agent but deliberately leaves
  # the tmux shell alive, and the agent takes a few seconds to wind down. Acking the
  # request as "stopped" was misleading, so confirm against the server before saying so.
  # AMUX_NO_WAIT=1 skips the wait for scripts that only want to fire the request.
  if [[ "$action" == "stop" && -z "${AMUX_NO_WAIT:-}" ]]; then
    local i rc
    for i in 1 2 3 4 5 6 7 8; do
      sleep 2
      # `|| rc=$?` is REQUIRED: under `set -e` a bare call returning non-zero
      # (which is exactly the "not running" answer we want) would abort the script.
      rc=0; _session_running "$name" || rc=$?
      case $rc in
        1) echo "${GREEN}✓${RESET} stopped ${DIM}(agent exited; tmux shell kept for resume)${RESET}"; return 0 ;;
        2) echo "${GREEN}✓${RESET} stop requested ${DIM}(could not confirm — server unreachable)${RESET}"; return 0 ;;
      esac
    done
    echo "${DIM}stop requested — still winding down after 16s; check with 'amux-remote $BOX ls'${RESET}"
    return 0
  fi
  echo "${GREEN}✓${RESET} ${action}"
}

# ── exec: create + configure + start, in one call ───────────────────────────
# The REST API exposes these as separate operations, so compose them and check
# each response. A model update failure is reported, but the new session still
# starts on the remote machine's default rather than being left half-created.
cmd_exec() {
  require_config
  local name="${1:-}"; shift || true
  [[ -n "$name" ]] \
    || die "usage: amux-remote $BOX exec <name> [--dir <path>] [--model <m>] [--provider <p>] [--desc <text>] [--worktree]"
  validate_remote_name "$name"

  local dir="" model="" desc="" provider="" worktree=0
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --dir)
        [[ $# -ge 2 && -n "${2:-}" ]] || die "--dir needs a value"
        dir="$2"; shift 2 ;;
      --model)
        [[ $# -ge 2 && -n "${2:-}" ]] || die "--model needs a value"
        model="$2"; shift 2 ;;
      --provider)
        [[ $# -ge 2 && -n "${2:-}" ]] || die "--provider needs a value"
        provider="$2"
        case "$provider" in
          claude|codex|gemini|opencode) ;;
          *) die "unknown provider '$provider' (valid: claude codex gemini opencode)" ;;
        esac
        shift 2 ;;
      --desc)
        [[ $# -ge 2 ]] || die "--desc needs a value"
        desc="$2"; shift 2 ;;
      --worktree) worktree=1; shift ;;
      -*) die "unknown exec option: $1 (try --dir, --model, --provider, --desc, --worktree)" ;;
      *)  die "unexpected argument: $1 (name comes first)" ;;
    esac
  done

  # An unquoted `--dir ~/project` expands on the caller before this script sees
  # it, which can produce the wrong absolute path when the two machines use
  # different home-directory layouts. Quoting leaves expansion to the server.
  if [[ -n "$dir" && "$dir" == "$HOME"/* ]]; then
    printf '%snote: --dir was expanded by your local shell to %s%s\n' "$DIM" "$dir" "$RESET" >&2
    printf "%s      quote it as '~/…' to let %s expand it instead%s\n" "$DIM" "$BOX" "$RESET" >&2
  fi

  local payload resp created ok msg
  payload=$(python3 -c '
import json, sys
name, directory, description, worktree, provider = sys.argv[1:6]
body = {"name": name}
if directory:
    body["dir"] = directory
if description:
    body["desc"] = description
if worktree == "1":
    body["worktree"] = True
if provider:
    body["provider"] = provider
print(json.dumps(body))' "$name" "$dir" "$desc" "$worktree" "$provider")

  resp=$(amux_api POST /api/sessions -H 'Content-Type: application/json' -d "$payload")
  created=$(printf '%s' "$resp" | python3 -c '
import json, sys
try:
    data = json.load(sys.stdin)
except Exception:
    print("0")
    print("could not parse server response")
    raise SystemExit
if not isinstance(data, dict):
    print("0")
    print("unexpected server response")
    raise SystemExit
print("1" if data.get("ok") else "0")
print(str(data.get("error") or data.get("message") or "create failed"))' 2>/dev/null)
  ok="${created%%$'\n'*}"; msg="${created#*$'\n'}"
  if [[ "$ok" != "1" ]]; then
    echo "${RED}✗${RESET} ${msg:-create failed}" >&2
    case "$msg" in
      *"already exists"*)
        echo "${DIM}  already registered — start it with: amux-remote $BOX start $name${RESET}" >&2 ;;
    esac
    return 1
  fi
  echo "${GREEN}✓${RESET} created ${BOLD}${name}${RESET}${dir:+ ${DIM}in ${dir}${RESET}}${provider:+ ${DIM}(provider: ${provider})${RESET}}"

  if [[ -n "$model" ]]; then
    local encoded model_payload model_resp
    encoded=$(encode_path_component "$name")
    model_payload=$(python3 -c 'import json,sys; print(json.dumps({"model": sys.argv[1]}))' "$model")
    model_resp=$(amux_api PATCH "/api/sessions/${encoded}/config" \
      -H 'Content-Type: application/json' -d "$model_payload")
    if printf '%s' "$model_resp" | python3 -c '
import json, sys
try:
    data = json.load(sys.stdin)
except Exception:
    raise SystemExit(1)
raise SystemExit(0 if isinstance(data, dict) and not data.get("error") and data.get("ok", True) else 1)' 2>/dev/null; then
      echo "${GREEN}✓${RESET} model ${CYAN}${model}${RESET}"
    else
      echo "${RED}✗${RESET} could not set model '$model' — starting on the remote default instead" >&2
    fi
  fi

  cmd_start "$name"
}

cmd_info() {
  require_config
  local name="${1:-}"
  [[ -n "$name" ]] || die "usage: amux-remote $BOX info <name>"
  validate_remote_name "$name"
  local encoded; encoded=$(encode_path_component "$name")
  amux_api GET "/api/sessions/${encoded}/info" \
    | python3 -m json.tool 2>/dev/null \
    || amux_api GET "/api/sessions/${encoded}/info"
}

# Show or change which supported coding-agent provider a session uses.
cmd_provider() {
  require_config
  local name="${1:-}" new="${2:-}"
  [[ -n "$name" ]] \
    || die "usage: amux-remote $BOX provider <name> [claude|codex|gemini|opencode]"
  [[ $# -le 2 ]] || die "usage: amux-remote $BOX provider <name> [claude|codex|gemini|opencode]"
  validate_remote_name "$name"
  local encoded; encoded=$(encode_path_component "$name")

  if [[ -z "$new" ]]; then
    amux_api GET "/api/sessions/${encoded}/info" | python3 -c '
import json, sys
try:
    data = json.load(sys.stdin)
except Exception:
    sys.exit("could not parse server response")
if not isinstance(data, dict):
    sys.exit("unexpected server response")
if isinstance(data, dict) and data.get("error"):
    sys.exit("server: " + str(data["error"]))
config = data.get("config") if isinstance(data.get("config"), dict) else {}
print(data.get("provider") or config.get("CC_PROVIDER") or "claude")'
    return
  fi
  case "$new" in
    claude|codex|gemini|opencode) ;;
    *) die "unknown provider '$new' (valid: claude codex gemini opencode)" ;;
  esac

  local payload resp result ok msg
  payload=$(python3 -c 'import json,sys; print(json.dumps({"provider": sys.argv[1]}))' "$new")
  resp=$(amux_api PATCH "/api/sessions/${encoded}/config" \
    -H 'Content-Type: application/json' -d "$payload")
  result=$(printf '%s' "$resp" | python3 -c '
import json, sys
try:
    data = json.load(sys.stdin)
except Exception:
    print("0")
    print("could not parse server response")
    raise SystemExit
if not isinstance(data, dict):
    print("0")
    print("unexpected server response")
    raise SystemExit
ok = isinstance(data, dict) and not data.get("error") and data.get("ok", True)
print("1" if ok else "0")
print(str(data.get("error") or data.get("message") or "provider update failed"))' 2>/dev/null)
  ok="${result%%$'\n'*}"; msg="${result#*$'\n'}"
  [[ "$ok" = 1 ]] || die "$msg"
  echo "${GREEN}✓${RESET} ${msg:-provider updated}"
}

cmd_curl() {
  require_config
  local path="${1:-}"; shift || true
  [[ -n "$path" ]] || die "usage: amux-remote $BOX curl <path> [curl args...]"
  [[ "$path" == /* ]] || path="/$path"
  amux_api GET "$path" "$@"
}

# Derive SSH host from AMUX_URL (strip scheme + port)
_ssh_host() {
  local host="${AMUX_SSH_HOST:-}"
  if [[ -z "$host" ]]; then
    host=$(python3 -c '
import sys, re
u = sys.argv[1].strip()
m = re.match(r'https?://([^:/?#]+)', u)
print(m.group(1) if m else '')
' "$AMUX_URL")
  fi
  [[ -n "$host" ]] || die "could not derive SSH host from AMUX_URL='${AMUX_URL:-}' — set AMUX_SSH_HOST in $BOX_ENV"
  echo "$host"
}

# True when we should attach with iTerm2 native tmux integration (control mode).
# iTerm2 renders the remote tmux session's windows/panes as native local tabs —
# trackpad scroll, local clipboard, Cmd+T/Cmd+D all work as if it were local.
# Auto-on when the local terminal is iTerm2; AMUX_CC=1 forces, AMUX_CC=0 disables.
_use_cc() {
  case "${AMUX_CC:-auto}" in
    1|yes|true|on)   return 0 ;;
    0|no|false|off)  return 1 ;;
    *) [[ "${TERM_PROGRAM:-}" == "iTerm.app" || "${LC_TERMINAL:-}" == "iTerm2" ]] ;;
  esac
}

cmd_attach() {
  require_config
  # Flags may appear before or after the name; they override AMUX_CC / auto-detect.
  local name=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --cc)    AMUX_CC=1 ;;
      --plain) AMUX_CC=0 ;;
      --*)     die "unknown attach option: $1 (try --cc or --plain)" ;;
      *)       [[ -n "$name" ]] && die "unexpected argument: $1"; name="$1" ;;
    esac
    shift
  done
  [[ -n "$name" ]] || die "usage: amux-remote $BOX attach <name> [--cc|--plain]"
  # The name is interpolated into a remote shell command below. This whitelist
  # prevents a quote or shell metacharacter from escaping the tmux argument.
  validate_remote_name "$name"
  local host; host=$(_ssh_host)
  local user="${AMUX_SSH_USER:-$USER}"
  local tname="amux-${name}"
  local tmux_cmd="tmux attach-session" note=""
  if _use_cc; then
    tmux_cmd="tmux -CC attach-session"; note=" ${DIM}(iTerm2 native)${RESET}"
  fi
  echo "${DIM}→ ssh ${user}@${host} — ${tmux_cmd} -t =${tname}${RESET}${note}"
  exec ssh "${user}@${host}" -t "${tmux_cmd} -t '=${tname}' || (echo 'Session ${tname} not found. Running sessions:' && tmux ls 2>/dev/null; exit 1)"
}

usage() {
  local boxes; boxes="$(list_boxes)"
  cat <<EOF
${BOLD}amux-remote${RESET} — drive another machine's amux server over its REST API

${BOLD}A box is always required${RESET} ${DIM}(there is no default remote)${RESET}
  ${CYAN}amux-remote <box> <cmd> …${RESET}   ${DIM}drive that box; e.g. 'amux-remote desktop ls -c'${RESET}
  ${CYAN}amux-remote${RESET}                 ${DIM}list configured boxes${RESET}

${BOLD}Configured boxes${RESET} ${DIM}(~/.amux/remotes/<box>.env)${RESET}
  ${GREEN}${boxes:-<none — add ~/.amux/remotes/<box>.env>}${RESET}

${BOLD}Per-box config${RESET} ${DIM}(~/.amux/remotes/<box>.env, chmod 600)${RESET}
  AMUX_URL       ${DIM}e.g. https://192.0.2.10:8822${RESET}
  AMUX_TOKEN     ${DIM}from that box's ~/.amux/auth_token${RESET}
  AMUX_SSH_HOST  ${DIM}SSH host for attach (default: host from AMUX_URL)${RESET}
  AMUX_SSH_USER  ${DIM}SSH user for attach (default: \$USER)${RESET}
  AMUX_CC        ${DIM}native iTerm2 attach: 1/0 to force, auto (default) = on in iTerm2${RESET}

${BOLD}Commands${RESET} ${DIM}(after the box name)${RESET}
  ${CYAN}ls${RESET} [-c] [--bare]         list sessions ${DIM}(-c = compact grid, like 'amux ls -c'; --bare = no header)${RESET}
  ${CYAN}attach${RESET} <name> [--cc|--plain]  SSH in + attach (native iTerm2 tabs in -CC mode)
  ${CYAN}peek${RESET} <name> [lines]     print recent output (default 80)
  ${CYAN}send${RESET} <name> <text...>   send text/prompt to a session
  ${CYAN}exec${RESET} <name> [--dir <p>] [--model <m>] [--provider <p>] [--desc <t>] [--worktree]
                          ${DIM}create + start in one step (quote '~/…' for remote expansion)${RESET}
  ${CYAN}start${RESET} <name> [flags...] start an already-registered session; flags forwarded one-shot
                          ${DIM}(e.g. --provider opencode). Does NOT attach — attach with${RESET}
                          ${DIM}'amux-up <box> <name>'. claude auto-resumes; --continue rarely needed${RESET}
  ${CYAN}stop${RESET} <name>             stop a session ${DIM}(takes no flags)${RESET}
  ${CYAN}provider${RESET} <name> [<p>]   show or change the coding agent ${DIM}(claude|codex|gemini|opencode)${RESET}
  ${CYAN}info${RESET} <name>             session status/meta as JSON
  ${CYAN}url${RESET}                     print the box's server URL
  ${CYAN}curl${RESET} <path> [args...]   raw authenticated GET against the API
EOF
}

# ── Select the box (always required), then dispatch its command ──────────────
BOX="${1:-}"; shift || true
case "$BOX" in
  ""|help|-h|--help) usage; exit 0 ;;
esac
BOX_ENV="$AMUX_REMOTES_DIR/$BOX.env"
if [[ ! -f "$BOX_ENV" ]]; then
  # A leading subcommand (the common slip now that a box is mandatory) lands here.
  case " ls list attach a peek send exec start stop info meta curl url provider " in
    *" $BOX "*) die "a box is required first: try 'amux-remote <box> $BOX …' — boxes: $(list_boxes)" ;;
  esac
  die "unknown box '$BOX'. configured: $(list_boxes)— add ~/.amux/remotes/$BOX.env"
fi
# shellcheck disable=SC1090
set -a; source "$BOX_ENV"; set +a

cmd="${1:-}"; shift || true
case "$cmd" in
  ls|list)       cmd_ls "$@" ;;
  attach|a)      cmd_attach "$@" ;;
  peek)          cmd_peek "$@" ;;
  send)          cmd_send "$@" ;;
  exec)          cmd_exec "$@" ;;
  start)         cmd_start "$@" ;;
  stop)          cmd_simple_post stop "$@" ;;
  info|meta)     cmd_info "$@" ;;
  provider)      cmd_provider "$@" ;;
  curl)          cmd_curl "$@" ;;
  url)           require_config; echo "$AMUX_URL" ;;
  ""|help|-h|--help) usage ;;
  *)             die "unknown command: $cmd (try: amux-remote help)" ;;
esac
