#!/usr/bin/env bash # install.sh — Build and install claude-statusline (Rust binary) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INSTALL_DIR="$HOME/.local/bin" BINARY_NAME="claude-statusline" CLAUDE_DIR="$HOME/.claude" SETTINGS="$CLAUDE_DIR/settings.json" echo "claude-statusline installer" echo "===========================" echo "" # ── Check toolchain ────────────────────────────────────────────────── if ! command -v cargo &>/dev/null; then echo "ERROR: cargo (Rust toolchain) is required but not installed." echo " Install via: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" exit 1 fi echo "[ok] cargo found ($(cargo --version))" if ! command -v jq &>/dev/null; then echo "ERROR: jq is required for settings.json updates." echo " macOS: brew install jq" echo " Ubuntu: sudo apt install jq" exit 1 fi echo "[ok] jq found" # ── Build release binary ───────────────────────────────────────────── echo "" echo "Building release binary..." if ! (cd "$SCRIPT_DIR" && cargo build --release); then echo "" echo "ERROR: cargo build failed. Check the output above for details." echo " Common causes:" echo " - Missing C compiler: sudo apt install build-essential (Debian/Ubuntu)" echo " - Missing libc headers: sudo apt install libc6-dev" exit 1 fi # Find the binary — respects CARGO_TARGET_DIR, custom target triples, etc. # Use cargo metadata as the source of truth for the target directory. TARGET_DIR=$(cd "$SCRIPT_DIR" && cargo metadata --format-version 1 --no-deps 2>/dev/null | jq -r '.target_directory' 2>/dev/null) || true TARGET_DIR="${TARGET_DIR:-$SCRIPT_DIR/target}" BINARY="$TARGET_DIR/release/$BINARY_NAME" if [[ ! -f "$BINARY" ]]; then # Fall back to searching (handles custom target triples like target//release/) BINARY=$(find "$TARGET_DIR" -name "$BINARY_NAME" -type f -path "*/release/*" ! -name "*.d" 2>/dev/null | head -1) fi if [[ -z "${BINARY:-}" || ! -f "$BINARY" ]]; then echo "ERROR: Build succeeded but binary not found." echo " Target dir: $TARGET_DIR" echo " Searched: $TARGET_DIR/*/release/$BINARY_NAME" echo " Contents: $(ls "$TARGET_DIR/release/" 2>/dev/null || echo '(empty or missing)')" exit 1 fi echo "[ok] Built: $(ls -lh "$BINARY" | awk '{print $5}')" # ── Install binary ─────────────────────────────────────────────────── mkdir -p "$INSTALL_DIR" cp "$BINARY" "$INSTALL_DIR/$BINARY_NAME" chmod +x "$INSTALL_DIR/$BINARY_NAME" BINARY_PATH="$INSTALL_DIR/$BINARY_NAME" echo "[ok] Installed to $BINARY_PATH" # Verify it's on PATH if ! command -v "$BINARY_NAME" &>/dev/null; then echo "[warn] $INSTALL_DIR is not on your PATH" echo " Add to your shell config: export PATH=\"$INSTALL_DIR:\$PATH\"" fi # Smoke test — verify the binary actually runs if "$BINARY_PATH" --test --color=never >/dev/null 2>&1; then echo "[ok] Binary smoke test passed" else echo "[warn] Binary smoke test failed (exit $?). It may still work inside Claude Code." echo " Debug: $BINARY_PATH --test --dump-state=json" fi # ── Configure Claude Code settings.json ────────────────────────────── echo "" mkdir -p "$CLAUDE_DIR" # The binary runs in a non-TTY context, so force color on. STATUSLINE_CMD="$BINARY_PATH --color=always" if [[ -f "$SETTINGS" ]]; then # Update existing settings.json, preserving all other keys CURRENT_CMD=$(jq -r '.statusLine.command // empty' "$SETTINGS" 2>/dev/null || true) if [[ -n "$CURRENT_CMD" ]]; then echo "[info] Previous statusLine command: $CURRENT_CMD" fi TMP="$SETTINGS.tmp.$$" if jq --arg cmd "$STATUSLINE_CMD" '.statusLine = {"type": "command", "command": $cmd, "padding": 0}' "$SETTINGS" > "$TMP" 2>/dev/null; then mv "$TMP" "$SETTINGS" echo "[ok] Updated statusLine in $SETTINGS" else rm -f "$TMP" echo "[warn] Failed to update $SETTINGS (invalid JSON?). Creating backup and writing fresh." cp "$SETTINGS" "$SETTINGS.bak" jq -n --arg cmd "$STATUSLINE_CMD" '{"statusLine": {"type": "command", "command": $cmd, "padding": 0}}' > "$SETTINGS" echo "[ok] Wrote fresh $SETTINGS (backup: $SETTINGS.bak)" fi else # Create minimal settings.json jq -n --arg cmd "$STATUSLINE_CMD" '{"statusLine": {"type": "command", "command": $cmd, "padding": 0}}' > "$SETTINGS" echo "[ok] Created $SETTINGS" fi # Show what was written so the user can verify echo "" echo " statusLine config:" jq '.statusLine' "$SETTINGS" 2>/dev/null || echo " (could not read settings)" # ── Symlink config ─────────────────────────────────────────────────── CONFIG_SRC="$SCRIPT_DIR/statusline.json" CONFIG_DST="$CLAUDE_DIR/statusline.json" if [[ -f "$CONFIG_SRC" ]]; then if [[ -L "$CONFIG_DST" ]]; then EXISTING="$(readlink "$CONFIG_DST")" if [[ "$EXISTING" == "$CONFIG_SRC" ]]; then echo "[ok] Config already linked" else ln -sf "$CONFIG_SRC" "$CONFIG_DST" echo "[ok] Config symlink updated (was: $EXISTING)" fi elif [[ -f "$CONFIG_DST" ]]; then echo "[skip] $CONFIG_DST exists as a regular file" echo " To use the symlink, remove it first: rm $CONFIG_DST" else ln -s "$CONFIG_SRC" "$CONFIG_DST" echo "[ok] Config linked: $CONFIG_DST -> $CONFIG_SRC" fi else echo "[info] No statusline.json in project. To customize, create:" echo " $CONFIG_SRC" echo "" echo " Print defaults: $BINARY_NAME --print-defaults" echo " Print schema: $BINARY_NAME --config-schema" fi # ── Done ───────────────────────────────────────────────────────────── echo "" echo "Done. RESTART Claude Code (exit and reopen) to see the status line." echo "" echo "Verify: $BINARY_PATH --test --color=always" echo "Debug: $BINARY_PATH --test --dump-state=json" echo "Settings: cat $SETTINGS | jq .statusLine"