cloverleaf-larry/install-larry.sh
Bryan Johnson 61f1500492 v0.3.1: OAuth subscription auth + offline manual cheat sheet
Two additions:

1. OAuth subscription auth (lib/oauth.sh + larry-auth.sh)
   - PKCE-based out-of-band flow against Claude.ai (no localhost server
     needed; works behind any firewall).
   - Uses the same client_id Claude Code uses, so calls bill against your
     Max/Pro subscription quota instead of pay-as-you-go API metering.
   - Tokens stored at $LARRY_HOME/.oauth.json (mode 0600), auto-refresh.
   - larry.sh now detects oauth file at startup and uses Bearer auth.
   - First-run flow now offers OAuth or API key; /login, /logout, /auth
     slash commands in the REPL.
   - Transparent fallback to API key if OAuth flow fails.

2. MANUAL.md — offline tool cheat sheet
   - Documents every lib/*.sh script with copy-paste examples.
   - Bryan's backup plan: when Anthropic is unreachable (no internet, on
     a plane, etc.), all the underlying tools work standalone from the
     shell. Larry just sequences them; they do not need Larry to run.
   - Quick-recipe table at the bottom for the common day-to-day asks.

Files added:
  - lib/oauth.sh
  - larry-auth.sh
  - MANUAL.md

Files modified:
  - larry.sh — auth-mode detection, /auth /login /logout commands
  - install-larry.sh — fetch new files

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:57:44 -07:00

177 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# install-larry.sh — bootstrap Larry-Anywhere on a fresh remote shell.
# No root, no package install, no sudo. Writes only into $LARRY_HOME.
#
# Usage:
# curl -fsSL <BASE_URL>/install-larry.sh | bash
#
# Or with explicit base URL:
# LARRY_BASE_URL=https://example.com/larry-anywhere bash install-larry.sh
#
# Env vars:
# LARRY_HOME install location (default: $HOME/.larry)
# LARRY_BASE_URL where to fetch files from (no trailing slash)
# LARRY_BIN_DIR where to symlink the `larry` command (default: $HOME/bin)
set -eu
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# Canonical hosting: bojj27/cloverleaf-larry on GitHub, branch main, raw view.
# Override via env if you fork or mirror elsewhere.
LARRY_BASE_URL="${LARRY_BASE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main}"
LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}"
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_GREEN=$'\033[32m'
C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m'
say() { printf '%s%sinstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; }
ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; }
# ─────────────────────────────────────────────────────────────────────────────
# Detect platform
# ─────────────────────────────────────────────────────────────────────────────
UNAME_S="$(uname -s 2>/dev/null || echo unknown)"
PLATFORM=""
case "$UNAME_S" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="darwin" ;;
CYGWIN*|MINGW*|MSYS*) PLATFORM="windows-cygwin" ;; # MobaXterm lives here
*) PLATFORM="unknown" ;;
esac
ARCH="$(uname -m 2>/dev/null || echo unknown)"
case "$ARCH" in
x86_64|amd64) ARCH_NORM="amd64" ;;
aarch64|arm64) ARCH_NORM="arm64" ;;
i?86) ARCH_NORM="i386" ;;
*) ARCH_NORM="$ARCH" ;;
esac
say "platform: $PLATFORM/$ARCH_NORM • LARRY_HOME=$LARRY_HOME"
# ─────────────────────────────────────────────────────────────────────────────
# Check required commands
# ─────────────────────────────────────────────────────────────────────────────
command -v bash >/dev/null 2>&1 || die "bash not found"
command -v curl >/dev/null 2>&1 || die "curl not found"
# ─────────────────────────────────────────────────────────────────────────────
# Make dirs
# ─────────────────────────────────────────────────────────────────────────────
mkdir -p "$LARRY_HOME"/{agents,sessions,bin,lib} || die "cannot create $LARRY_HOME"
chmod 700 "$LARRY_HOME" 2>/dev/null || true
ok "created $LARRY_HOME"
# ─────────────────────────────────────────────────────────────────────────────
# Fetch the scripts. If LARRY_BASE_URL is not set, try to detect being run
# from a local checkout (sibling files present) — copy locally instead.
# ─────────────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)" || SCRIPT_DIR=""
fetch() {
# $1 = remote relative path, $2 = local destination
if [ -n "$LARRY_BASE_URL" ]; then
say "fetching $1"
curl -fsSL --max-time 30 "$LARRY_BASE_URL/$1" -o "$2" \
&& ok "$2" || die "failed to fetch $1"
elif [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/$1" ]; then
cp "$SCRIPT_DIR/$1" "$2" && ok "copied $1 (local)"
else
die "no LARRY_BASE_URL set and $1 not found in script dir"
fi
}
fetch larry.sh "$LARRY_HOME/larry.sh"
fetch larry-tunnel.sh "$LARRY_HOME/larry-tunnel.sh"
fetch agents/larry.md "$LARRY_HOME/agents/larry.md"
fetch agents/clover.md "$LARRY_HOME/agents/clover.md"
fetch agents/cloverleaf-cheatsheet.md "$LARRY_HOME/agents/cloverleaf-cheatsheet.md"
fetch agents/regress.md "$LARRY_HOME/agents/regress.md"
fetch larry-rollback.sh "$LARRY_HOME/larry-rollback.sh"
fetch larry-auth.sh "$LARRY_HOME/larry-auth.sh"
fetch lib/oauth.sh "$LARRY_HOME/lib/oauth.sh"
fetch lib/nc-parse.sh "$LARRY_HOME/lib/nc-parse.sh"
fetch lib/nc-inbound.sh "$LARRY_HOME/lib/nc-inbound.sh"
fetch lib/nc-make-jump.sh "$LARRY_HOME/lib/nc-make-jump.sh"
fetch lib/hl7-field.sh "$LARRY_HOME/lib/hl7-field.sh"
fetch lib/nc-msgs.sh "$LARRY_HOME/lib/nc-msgs.sh"
fetch lib/nc-document.sh "$LARRY_HOME/lib/nc-document.sh"
fetch lib/nc-diff-interface.sh "$LARRY_HOME/lib/nc-diff-interface.sh"
fetch lib/nc-find.sh "$LARRY_HOME/lib/nc-find.sh"
fetch lib/nc-insert-protocol.sh "$LARRY_HOME/lib/nc-insert-protocol.sh"
fetch lib/hl7-diff.sh "$LARRY_HOME/lib/hl7-diff.sh"
fetch lib/nc-regression.sh "$LARRY_HOME/lib/nc-regression.sh"
fetch lib/journal.sh "$LARRY_HOME/lib/journal.sh"
fetch VERSION "$LARRY_HOME/VERSION"
fetch MANUAL.md "$LARRY_HOME/MANUAL.md"
chmod +x "$LARRY_HOME/larry.sh" "$LARRY_HOME/larry-tunnel.sh" "$LARRY_HOME/larry-rollback.sh" "$LARRY_HOME/larry-auth.sh" "$LARRY_HOME/lib/"*.sh
# ─────────────────────────────────────────────────────────────────────────────
# jq fallback — download static binary into $LARRY_HOME/bin/ if missing
# ─────────────────────────────────────────────────────────────────────────────
if ! command -v jq >/dev/null 2>&1 && [ ! -x "$LARRY_HOME/bin/jq" ]; then
say "jq not found — fetching static binary into $LARRY_HOME/bin/jq"
JQ_BASE="https://github.com/jqlang/jq/releases/download/jq-1.7.1"
JQ_URL=""
case "$PLATFORM/$ARCH_NORM" in
linux/amd64) JQ_URL="$JQ_BASE/jq-linux-amd64" ;;
linux/arm64) JQ_URL="$JQ_BASE/jq-linux-arm64" ;;
linux/i386) JQ_URL="$JQ_BASE/jq-linux-i386" ;;
darwin/amd64) JQ_URL="$JQ_BASE/jq-macos-amd64" ;;
darwin/arm64) JQ_URL="$JQ_BASE/jq-macos-arm64" ;;
windows-cygwin/amd64) JQ_URL="$JQ_BASE/jq-windows-amd64.exe" ;;
windows-cygwin/i386) JQ_URL="$JQ_BASE/jq-windows-i386.exe" ;;
*) warn "no jq binary known for $PLATFORM/$ARCH_NORM — install jq manually" ;;
esac
if [ -n "$JQ_URL" ]; then
local_jq="$LARRY_HOME/bin/jq"
case "$PLATFORM" in windows-cygwin) local_jq="$LARRY_HOME/bin/jq.exe" ;; esac
if curl -fsSL --max-time 60 "$JQ_URL" -o "$local_jq"; then
chmod +x "$local_jq"
ok "jq -> $local_jq"
else
warn "could not download jq from $JQ_URL"
fi
fi
else
ok "jq available"
fi
# ─────────────────────────────────────────────────────────────────────────────
# Drop a `larry` shim onto PATH (best-effort)
# ─────────────────────────────────────────────────────────────────────────────
mkdir -p "$LARRY_BIN_DIR" 2>/dev/null || true
if [ -d "$LARRY_BIN_DIR" ] && [ -w "$LARRY_BIN_DIR" ]; then
cat > "$LARRY_BIN_DIR/larry" <<EOF
#!/usr/bin/env bash
# Auto-generated by install-larry.sh
export LARRY_HOME="${LARRY_HOME}"
exec "${LARRY_HOME}/larry.sh" "\$@"
EOF
chmod +x "$LARRY_BIN_DIR/larry"
ok "shim: $LARRY_BIN_DIR/larry"
case ":$PATH:" in
*":$LARRY_BIN_DIR:"*) : ;;
*) warn "$LARRY_BIN_DIR is not on PATH — add 'export PATH=\"$LARRY_BIN_DIR:\$PATH\"' to your shell rc" ;;
esac
else
warn "cannot write to $LARRY_BIN_DIR — invoke larry directly as: $LARRY_HOME/larry.sh"
fi
# ─────────────────────────────────────────────────────────────────────────────
# Done
# ─────────────────────────────────────────────────────────────────────────────
echo ""
say "install complete (no system changes were made; everything lives under $LARRY_HOME)"
echo ""
echo "Next steps:"
echo " 1) export ANTHROPIC_API_KEY=sk-ant-... (or larry will prompt on first run)"
echo " 2) larry (or $LARRY_HOME/larry.sh)"
echo " 3) larry /path/to/cloverleaf/site_root (to start with a working dir)"
echo ""
echo "Reverse SSH tunnel (optional, run in another shell or backgrounded):"
echo " $LARRY_HOME/larry-tunnel.sh --serveo # zero-config"
echo " $LARRY_HOME/larry-tunnel.sh --hop=user@bjnoela.com:22 # your hop"
echo ""