#!/usr/bin/env bash # Install Panama's searchable launcher actions as one narrow directory link. # Keeping the runtime path stable lets Vicinae watch it while every command # remains authored and reviewed in the Panama repository. set -euo pipefail panama_path="${PANAMA_PATH:-$HOME/.local/share/Panama}" vicinae_data_dir="${VICINAE_DATA_DIR:-$HOME/.local/share/vicinae}" source_dir="$panama_path/config/local/share/vicinae/scripts" scripts_dir="$vicinae_data_dir/scripts" target_dir="$scripts_dir/panama" backup_root="$vicinae_data_dir/backups" backup_dir="$backup_root/panama.pre-panama" legacy_backup="$scripts_dir/panama.pre-panama" [[ -d "$source_dir" ]] || { printf 'Panama command source is missing: %s\n' "$source_dir" >&2 exit 1 } mkdir -p "$scripts_dir" "$backup_root" # Older Panama builds preserved this directory alongside searchable commands. # Move it out before reloading so those scripts cannot appear twice. if [[ -e "$legacy_backup" ]]; then if [[ -e "$backup_dir" ]]; then printf 'Refusing to move %s; backup already exists at %s\n' \ "$legacy_backup" "$backup_dir" >&2 exit 1 fi mv "$legacy_backup" "$backup_dir" fi if [[ -L "$target_dir" ]]; then rm "$target_dir" elif [[ -e "$target_dir" ]]; then if [[ -e "$backup_dir" ]]; then printf 'Refusing to replace %s; backup already exists at %s\n' \ "$target_dir" "$backup_dir" >&2 exit 1 fi mv "$target_dir" "$backup_dir" fi ln -s "$source_dir" "$target_dir" # ── Extensions ─────────────────────────────────────────────────────────────── # # Script commands are a file and a shebang, so they are linked. Extensions are # not: they are TypeScript that has to be compiled, and `vici build` writes its # output straight into Vicinae's data directory rather than leaving a bundle to # link. So the source lives in this repository and the build is what installs # it. # # Never fatal, and never a reason to fail a stage. A build wants npm and the # network, and neither is guaranteed at this point in an install -- npm arrives # with nvm earlier in install-packages, which can itself be skipped. An # extension that did not build is a launcher missing one command, not a desktop # that failed to install. extensions_source="$panama_path/config/local/share/vicinae/extensions" # npm comes from nvm, and nvm is a shell function in a file that only an # interactive shell sources. A stage is not one, so without this npm is simply # absent and the build below is skipped -- silently, which is the whole problem. # # This machine hid the bug: /usr/sbin/npm existed, but only because Claude # Desktop depends on nodejs and dragged it in. A machine without that would have # installed the launcher and quietly not built its search extension. if [[ -s /etc/profile.d/nvm.sh ]] && ! command -v npm >/dev/null 2>&1; then # nvm's script reads unset variables, which `set -u` treats as fatal. set +u # shellcheck source=/dev/null source /etc/profile.d/nvm.sh set -u fi if [[ -d "$extensions_source" ]] && command -v npm >/dev/null 2>&1; then for extension in "$extensions_source"/*/; do [[ -f "$extension/package.json" ]] || continue name="$(basename "$extension")" # Skip a build that would produce what is already there. `npm install` # alone takes long enough to be worth not repeating on every re-run of # a stage that is otherwise nearly instant. built="$vicinae_data_dir/extensions/$name" if [[ -d "$built" && "$extension/src" -ot "$built" ]]; then printf 'Vicinae extension %s is already built\n' "$name" continue fi printf 'Building Vicinae extension %s\n' "$name" if ! (cd "$extension" && npm install --silent >/dev/null 2>&1 && npm run build >/dev/null 2>&1); then printf 'Vicinae extension %s did not build; skipping\n' "$name" >&2 fi done elif [[ -d "$extensions_source" ]]; then printf 'npm is not available, so Vicinae extensions were not built\n' >&2 fi # The server also rescans periodically, but an explicit reload makes a setup # run deterministic. If Vicinae is not active yet, its startup scan is enough. if command -v vicinae >/dev/null 2>&1 && vicinae ping >/dev/null 2>&1; then vicinae cmd launch core:reload-scripts >/dev/null 2>&1 || true fi