Files
Panama/setup/scripts/link-vicinae-scripts
T
Gabriel Brown 033d5b21f8 Build the launcher's extension on a machine that has only nvm
The search extension is compiled, and the stage that compiles it checked for npm
and skipped quietly when it found none. On this machine it always found one --
but only because Claude Desktop depends on nodejs and dragged a system npm in.

Nowhere else would. Node moved to nvm when the shell config turned out to have
been assuming it for months, and nvm is a shell function in a file only an
interactive shell sources; a stage is not one. So a fresh install would have set
up the launcher, printed one line about extensions not being built, and left
somebody wondering why typing in it suggested nothing.

The stage sources nvm before looking, and the contract pins that it does --
checking for npm is not the same as being able to find it.

Claude-Session: https://claude.ai/code/session_01Q84axqUE5inJhf5Jz9CFy1
2026-08-21 12:31:34 -04:00

107 lines
4.4 KiB
Bash
Executable File

#!/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