#!/bin/sh # Clawdie Shell — NPM Globals Module # Purpose: Install bundled npm-global CLI tools (currently pi only) # from local tarballs shipped on the install media. Fully offline. # POSIX-compliant (no bash-isms) # # Runs after shell-pkg.sh (so node24 + npm exist) and before shell-deploy.sh # (so the agent runtime can find `pi` on PATH at first run). set -eu # Configuration (can be overridden for testing) SHARE="${SHARE:-/usr/local/share/clawdie-iso}" NPM_GLOBALS_DIR="${NPM_GLOBALS_DIR:-${SHARE}/npm-globals}" CLAWDIE_HOME="${CLAWDIE_HOME:-/home/clawdie}" # MUST match shell-system.sh's /etc/profile.d/clawdie.sh which sets: # export npm_config_prefix="/opt/clawdie/npm-global" # export PATH="/opt/clawdie/npm-global/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}" # Otherwise globals land in /usr/local/bin and the clawdie user's PATH # (and the agent runtime's commandExists() probes) won't find them. NPM_PREFIX="${NPM_PREFIX:-/opt/clawdie/npm-global}" LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}" PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}" # ============================================================================ # MAIN ENTRY POINT # ============================================================================ clawdie_shell_npm_globals_install() { log_msg "[npm-globals] Starting offline install of bundled npm CLIs" if ! command -v npm >/dev/null 2>&1; then log_msg "[npm-globals] ERROR: npm not found in PATH" return 1 fi if [ ! -d "$NPM_GLOBALS_DIR" ]; then log_msg "[npm-globals] WARNING: $NPM_GLOBALS_DIR not present — skipping" echo "[NPM-GLOBALS] SKIPPED" >> "$PROGRESS_FILE" return 0 fi # Ensure target prefix exists and is owned by clawdie. Aligns with # shell-system.sh's /etc/profile.d/clawdie.sh PATH setup. mkdir -p "$NPM_PREFIX/bin" "$NPM_PREFIX/lib" if id clawdie >/dev/null 2>&1; then chown -R clawdie:clawdie "$NPM_PREFIX" 2>/dev/null || true fi # Keep the legacy path as a symlink for compatibility. if [ ! -L "${CLAWDIE_HOME}/.npm-global" ]; then rm -rf "${CLAWDIE_HOME}/.npm-global" 2>/dev/null || true ln -s "$NPM_PREFIX" "${CLAWDIE_HOME}/.npm-global" 2>/dev/null || true chown -h clawdie:clawdie "${CLAWDIE_HOME}/.npm-global" 2>/dev/null || true fi _found=0 for tgz in "$NPM_GLOBALS_DIR"/*.tgz; do [ -f "$tgz" ] || continue _found=1 log_msg "[npm-globals] Installing $(basename "$tgz") → $NPM_PREFIX" # Run as clawdie user with the same env the login shell will see, # so binaries land in /opt/clawdie/npm-global/bin/. if id clawdie >/dev/null 2>&1 && [ "$(id -u)" -eq 0 ]; then if su - clawdie -c "npm_config_prefix='$NPM_PREFIX' npm install -g '$tgz'" >>"$LOG_FILE" 2>&1; then log_msg "[npm-globals] ✓ $(basename "$tgz")" else log_msg "[npm-globals] ✗ FAILED: $(basename "$tgz")" return 1 fi else if npm_config_prefix="$NPM_PREFIX" npm install -g "$tgz" >>"$LOG_FILE" 2>&1; then log_msg "[npm-globals] ✓ $(basename "$tgz")" else log_msg "[npm-globals] ✗ FAILED: $(basename "$tgz")" return 1 fi fi done if [ "$_found" -eq 0 ]; then log_msg "[npm-globals] WARNING: No .tgz files in $NPM_GLOBALS_DIR" echo "[NPM-GLOBALS] SKIPPED" >> "$PROGRESS_FILE" return 0 fi clawdie_shell_npm_globals_patch_pi_footer echo "[NPM-GLOBALS] SUCCESS" >> "$PROGRESS_FILE" log_msg "[npm-globals] Install complete" return 0 } clawdie_shell_npm_globals_patch_pi_footer() { _patch_script="${SHARE}/firstboot/patch-pi-footer-hostname.js" _patched=0 if [ ! -f "$_patch_script" ]; then log_msg "[npm-globals] ERROR: Pi footer patch script missing: $_patch_script" return 1 fi for footer in \ "$NPM_PREFIX/lib/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js" \ "$NPM_PREFIX/lib/node_modules/@mariozechner/pi-coding-agent/dist/modes/interactive/components/footer.js" do [ -f "$footer" ] || continue log_msg "[npm-globals] Patching Pi footer hostname: $footer" if node "$_patch_script" "$footer" >>"$LOG_FILE" 2>&1 && node --check "$footer" >>"$LOG_FILE" 2>&1; then _patched=1 else log_msg "[npm-globals] ERROR: failed to patch Pi footer hostname" return 1 fi done if [ "$_patched" -ne 1 ]; then log_msg "[npm-globals] ERROR: Pi footer.js not found under $NPM_PREFIX" return 1 fi return 0 } # ============================================================================ # LOGGING HELPER # ============================================================================ log_msg() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG_FILE" 2>/dev/null || true } # Only run if sourced directly (not during test, not from firstboot.sh) if [ "${SHELL_NPM_GLOBALS_TEST:-0}" -eq 0 ]; then clawdie_shell_npm_globals_install fi