#!/bin/sh # Installs the Lumbridge status-line bridge into Claude Code's settings. # # This is the one thing Lumbridge cannot do without touching the user's # configuration, so it is a separate, explicit, reversible step rather than # something a probe does on your behalf. # # It sets `statusLine` in $CLAUDE_CONFIG_DIR/settings.json (default # ~/.claude/settings.json) to run scripts/claude-statusline-bridge.sh. Claude # Code then pipes its rate-limit payload there on every turn, which is how # Lumbridge learns the five-hour and seven-day subscription windows without # ever reading a credential. # # install: scripts/install-claude-statusline.sh # uninstall: scripts/install-claude-statusline.sh --uninstall # preview: scripts/install-claude-statusline.sh --dry-run # # An existing statusLine is backed up and reported, never silently replaced. set -eu MODE=install case "${1:-}" in --uninstall) MODE=uninstall ;; --dry-run) MODE=dry-run ;; "") ;; *) echo "usage: $0 [--uninstall|--dry-run]" >&2; exit 2 ;; esac CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" SETTINGS="$CONFIG_DIR/settings.json" HERE=$(cd "$(dirname "$0")" && pwd) BRIDGE="$HERE/claude-statusline-bridge.sh" if [ ! -x "$BRIDGE" ]; then echo "bridge not executable: $BRIDGE" >&2 exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "python3 is required to edit settings.json safely" >&2 exit 1 fi python3 - "$SETTINGS" "$BRIDGE" "$MODE" <<'PYTHON' import json import os import shutil import sys import time settings_path, bridge, mode = sys.argv[1], sys.argv[2], sys.argv[3] desired = {"type": "command", "command": bridge} settings = {} if os.path.exists(settings_path): try: with open(settings_path) as handle: settings = json.load(handle) except Exception as error: print(f"refusing to edit unreadable settings: {error}", file=sys.stderr) raise SystemExit(1) if not isinstance(settings, dict): print("refusing to edit: settings.json is not an object", file=sys.stderr) raise SystemExit(1) current = settings.get("statusLine") if mode == "uninstall": if isinstance(current, dict) and current.get("command") == bridge: settings.pop("statusLine", None) action = "removed the Lumbridge status line" else: print("no Lumbridge status line installed; nothing to do") raise SystemExit(0) elif isinstance(current, dict) and current.get("command") == bridge: print("Lumbridge status line already installed; nothing to do") raise SystemExit(0) else: if current is not None: # Someone else's status line is here. Report it and stop rather than # replacing a thing the user set up. print("a different statusLine is already configured:", file=sys.stderr) print(f" {json.dumps(current)}", file=sys.stderr) print( "remove it first, or merge the bridge into it by hand:\n" f" {bridge}", file=sys.stderr, ) raise SystemExit(1) settings["statusLine"] = desired action = "installed the Lumbridge status line" if mode == "dry-run": print("would write:") print(json.dumps({"statusLine": settings.get("statusLine")}, indent=2)) raise SystemExit(0) os.makedirs(os.path.dirname(settings_path), exist_ok=True) if os.path.exists(settings_path): backup = f"{settings_path}.lumbridge-backup-{int(time.time())}" shutil.copy2(settings_path, backup) print(f"backed up existing settings to {backup}") # Write via a temporary file so an interrupted run cannot leave settings.json # truncated — this file configures the user's editor session. temporary = f"{settings_path}.lumbridge-tmp" with open(temporary, "w") as handle: json.dump(settings, handle, indent=2) handle.write("\n") os.replace(temporary, settings_path) print(f"{action} in {settings_path}") print("restart or start a Claude Code session for it to take effect") PYTHON