#!/bin/sh # RoninForge BudgetClaw integration for Claude Code. # # Installs: # - ~/.claude/statusline-command.sh (per-project spend in your status line) # - ~/.claude/commands/budget.md (the /budget slash command) # - ~/Library/LaunchAgents/...plist (macOS only - keeps the watcher running) # - patches ~/.claude/settings.json (wires the statusline) # # Usage: # curl -fsSL https://roninforge.org/claude/install.sh | sh # # Uninstall: # curl -fsSL https://roninforge.org/claude/uninstall.sh | sh # # Prerequisites: # - budgetclaw (curl -fsSL https://roninforge.org/budgetclaw/install.sh | sh) # - jq (brew install jq) # # Env vars: # RONIN_NO_COLOR=1 disable color output # RONIN_NO_LAUNCHD=1 skip launchd setup on macOS # RONIN_FORCE=1 overwrite an existing statusline without prompting set -eu BASE_URL="${RONIN_BASE_URL:-https://roninforge.org/claude}" if [ -t 1 ] && [ -z "${RONIN_NO_COLOR:-}" ]; then bold=$(printf '\033[1m'); dim=$(printf '\033[2m'); reset=$(printf '\033[0m') green=$(printf '\033[32m'); yellow=$(printf '\033[33m'); red=$(printf '\033[31m') else bold=""; dim=""; reset=""; green=""; yellow=""; red="" fi section() { printf '\n%s==> %s%s\n' "$bold" "$*" "$reset"; } ok() { printf ' %s%s%s %s\n' "$green" "ok" "$reset" "$*"; } warn() { printf ' %s%s%s %s\n' "$yellow" "warn" "$reset" "$*"; } fail() { printf ' %s%s%s %s\n' "$red" "fail" "$reset" "$*" >&2; exit 1; } info() { printf ' %s%s%s\n' "$dim" "$*" "$reset"; } # ---------- preflight ---------- section "Checking prerequisites" command -v curl >/dev/null 2>&1 || fail "curl is required" command -v git >/dev/null 2>&1 || fail "git is required" if ! command -v budgetclaw >/dev/null 2>&1; then BUDGETCLAW_FOUND="" for p in "$HOME/.local/bin/budgetclaw" /opt/homebrew/bin/budgetclaw /usr/local/bin/budgetclaw; do if [ -x "$p" ]; then BUDGETCLAW_FOUND="$p"; break; fi done if [ -z "$BUDGETCLAW_FOUND" ]; then warn "budgetclaw not found on PATH" info "install with: curl -fsSL https://roninforge.org/budgetclaw/install.sh | sh" fail "install budgetclaw first, then re-run this installer" fi BUDGETCLAW_BIN="$BUDGETCLAW_FOUND" else BUDGETCLAW_BIN=$(command -v budgetclaw) fi ok "budgetclaw at $BUDGETCLAW_BIN" if ! command -v jq >/dev/null 2>&1; then if [ "$(uname)" = "Darwin" ] && command -v brew >/dev/null 2>&1; then warn "jq not found - installing via Homebrew" brew install jq >/dev/null ok "jq installed" else fail "jq is required (install: brew install jq, apt install jq, etc.)" fi else ok "jq at $(command -v jq)" fi # ---------- ~/.claude paths ---------- CLAUDE_DIR="$HOME/.claude" STATUSLINE_PATH="$CLAUDE_DIR/statusline-command.sh" COMMANDS_DIR="$CLAUDE_DIR/commands" SETTINGS_PATH="$CLAUDE_DIR/settings.json" mkdir -p "$CLAUDE_DIR" "$COMMANDS_DIR" # ---------- statusline ---------- section "Installing statusline" if [ -f "$STATUSLINE_PATH" ] && [ -z "${RONIN_FORCE:-}" ]; then backup="$STATUSLINE_PATH.bak.$(date +%Y%m%d-%H%M%S)" cp "$STATUSLINE_PATH" "$backup" info "backed up existing statusline to $backup" fi curl -fsSL "$BASE_URL/statusline.sh" -o "$STATUSLINE_PATH" chmod +x "$STATUSLINE_PATH" ok "wrote $STATUSLINE_PATH" # ---------- /budget slash command ---------- section "Installing /budget slash command" curl -fsSL "$BASE_URL/budget.md" -o "$COMMANDS_DIR/budget.md" ok "wrote $COMMANDS_DIR/budget.md" # ---------- settings.json patch ---------- section "Patching $SETTINGS_PATH" if [ ! -f "$SETTINGS_PATH" ]; then printf '{}\n' > "$SETTINGS_PATH" info "created empty settings.json" fi # Use jq to set statusLine without clobbering other keys. tmp=$(mktemp) jq --arg cmd "$STATUSLINE_PATH" \ '.statusLine = {type: "command", command: $cmd}' \ "$SETTINGS_PATH" > "$tmp" mv "$tmp" "$SETTINGS_PATH" ok "set statusLine.command -> $STATUSLINE_PATH" # ---------- macOS launchd ---------- if [ "$(uname)" = "Darwin" ] && [ -z "${RONIN_NO_LAUNCHD:-}" ]; then section "Installing launchd agent (keeps budgetclaw watch running)" LAUNCHAGENTS_DIR="$HOME/Library/LaunchAgents" PLIST_PATH="$LAUNCHAGENTS_DIR/org.roninforge.budgetclaw.plist" LOGS_DIR="$HOME/Library/Logs" mkdir -p "$LAUNCHAGENTS_DIR" "$LOGS_DIR" plist_tmpl=$(curl -fsSL "$BASE_URL/budgetclaw.plist.tmpl") printf '%s\n' "$plist_tmpl" \ | sed "s|__BUDGETCLAW_BIN__|$BUDGETCLAW_BIN|g; s|__HOME__|$HOME|g" \ > "$PLIST_PATH" ok "wrote $PLIST_PATH" if launchctl list 2>/dev/null | grep -q org.roninforge.budgetclaw; then launchctl unload "$PLIST_PATH" 2>/dev/null || true info "unloaded previous agent" fi launchctl load "$PLIST_PATH" ok "loaded launchd agent (watcher will start now and restart on login)" info "logs: ~/Library/Logs/budgetclaw.log" else if [ "$(uname)" != "Darwin" ]; then section "Skipping launchd (not macOS)" info "to keep budgetclaw watch running on Linux, write a systemd unit" info " see: https://roninforge.org/claude/systemd-example" else section "Skipping launchd (RONIN_NO_LAUNCHD=1)" info "start the watcher manually with: budgetclaw watch" fi fi # ---------- summary ---------- section "Done" info "open a fresh Claude Code session - the status line will show today's spend per project" info "type /budget inside Claude Code for the full per-branch table" info "tutorial: https://roninforge.org/tutorials/how-to-see-ongoing-project-costs" printf '\n'