#!/usr/bin/env bash set -euo pipefail LABEL="com.bvisible.mcp-ssh-manager" PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist" LOG_DIR="$HOME/Library/Logs/mcp-ssh-manager" OUT_LOG="$LOG_DIR/out.log" ERR_LOG="$LOG_DIR/err.log" fail_cleanup() { if launchctl print "gui/$(id -u)/${LABEL}" >/dev/null 2>&1; then launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true fi if [ -f "$PLIST_PATH" ]; then rm -f "$PLIST_PATH" || true fi } usage() { echo "usage: $0 {install|uninstall|status|logs}" } pick_node() { if [ -x /opt/homebrew/bin/node ]; then echo /opt/homebrew/bin/node elif [ -x /usr/local/bin/node ]; then echo /usr/local/bin/node elif command -v node >/dev/null 2>&1; then command -v node else echo "" fi } find_repo_up() { local dir="$PWD" for _ in 0 1 2 3 4 5; do local pj="$dir/package.json" if [ -f "$pj" ] && /usr/bin/grep -E '"name"[[:space:]]*:[[:space:]]*"[^\"]*mcp-ssh-manager' "$pj" >/dev/null 2>&1; then echo "$dir" return 0 fi dir="$(dirname "$dir")" done return 1 } find_repo_search() { local base for base in "$HOME/code" "$HOME/projects" "$HOME/src"; do if [ -d "$base" ]; then local hit hit="$(find "$base" -maxdepth 3 -type d -name '*mcp-ssh-manager*' 2>/dev/null | head -n 1)" if [ -n "$hit" ]; then echo "$hit" return 0 fi fi done return 1 } find_binary_under() { local base for base in /usr/local /opt/homebrew; do if [ -d "$base" ]; then local hit hit="$(find "$base" -maxdepth 4 -type f -name 'mcp-ssh-manager' 2>/dev/null | head -n 1)" if [ -n "$hit" ]; then echo "$hit" return 0 fi fi done return 1 } write_plist() { local workdir="$1" shift local -a args=("$@") mkdir -p "$(dirname "$PLIST_PATH")" "$LOG_DIR" { echo '' echo '' echo '' echo '' echo ' Label' echo " ${LABEL}" echo '' echo ' ProgramArguments' echo ' ' for arg in "${args[@]}"; do echo " ${arg}" done echo ' ' if [ -n "$workdir" ]; then echo '' echo ' WorkingDirectory' echo " ${workdir}" fi echo '' echo ' EnvironmentVariables' echo ' ' echo ' PATH' echo ' /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin' echo ' ' echo '' echo ' RunAtLoad' echo ' ' echo '' echo ' KeepAlive' echo ' ' echo '' echo ' StandardOutPath' echo " ${OUT_LOG}" echo '' echo ' StandardErrorPath' echo " ${ERR_LOG}" echo '' echo '' } > "$PLIST_PATH" } install() { trap 'fail_cleanup' ERR local binary_path="" if command -v mcp-ssh-manager >/dev/null 2>&1; then binary_path="$(command -v mcp-ssh-manager)" else binary_path="$(find_binary_under || true)" fi if [ -n "$binary_path" ]; then echo "using binary: $binary_path" write_plist "" "$binary_path" else local repo="" repo="$(find_repo_up || true)" if [ -z "$repo" ]; then repo="$(find_repo_search || true)" fi if [ -z "$repo" ]; then echo "could not find mcp-ssh-manager binary or repo" >&2 exit 1 fi local pj="$repo/package.json" local has_start="" if [ -f "$pj" ] && /usr/bin/grep -E '"start"[[:space:]]*:' "$pj" >/dev/null 2>&1; then has_start=1 fi if [ -n "$has_start" ]; then local pm="" if [ -f "$repo/pnpm-lock.yaml" ] && command -v pnpm >/dev/null 2>&1; then pm="$(command -v pnpm)" echo "using pnpm start in repo: $repo" write_plist "$repo" "$pm" "start" elif command -v npm >/dev/null 2>&1; then pm="$(command -v npm)" echo "using npm run start in repo: $repo" write_plist "$repo" "$pm" "run" "start" else echo "start script exists but npm or pnpm not found" >&2 exit 1 fi else local node node="$(pick_node)" if [ -z "$node" ]; then echo "node not found" >&2 exit 1 fi local entry="" for p in "dist/index.js" "build/index.js" "index.js" "src/index.ts"; do if [ -f "$repo/$p" ]; then entry="$repo/$p" break fi done if [ -z "$entry" ]; then echo "no entry file found in repo" >&2 exit 1 fi echo "using node entry: $entry" write_plist "$repo" "$node" "$entry" fi fi launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" launchctl kickstart -k "gui/$(id -u)/${LABEL}" echo "installed: $PLIST_PATH" echo "logs: $LOG_DIR" } uninstall() { launchctl bootout "gui/$(id -u)" "$PLIST_PATH" >/dev/null 2>&1 || true rm -f "$PLIST_PATH" echo "removed: $PLIST_PATH" } status() { launchctl print "gui/$(id -u)/${LABEL}" } logs() { tail -f "$OUT_LOG" "$ERR_LOG" } case "${1:-}" in install) install ;; uninstall) uninstall ;; status) status ;; logs) logs ;; *) usage; exit 1 ;; esac