#!/usr/bin/env bash # install.sh — install mempalace-toolkit executables + companion agent skill # # Idempotent. Safe to re-run after container recreate. set -euo pipefail # ── locate self ────────────────────────────────────── SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # ── targets ────────────────────────────────────────── BIN_SRC="${SCRIPT_DIR}/bin" BIN_DEST="${HOME}/.local/bin" SKILL_SRC="${SCRIPT_DIR}/SKILL.md" SKILL_DEST_DIR="${HOME}/.agents/skills/opencode-mempalace-bridge" SKILL_DEST="${SKILL_DEST_DIR}/SKILL.md" # ── args ───────────────────────────────────────────── ACTION="install" ASSUME_YES="no" while [[ $# -gt 0 ]]; do case "$1" in --uninstall) ACTION="uninstall"; shift ;; -y|--yes) ASSUME_YES="yes"; shift ;; -h|--help) cat <&2; exit 2 ;; esac done # ── helpers ────────────────────────────────────────── ok() { printf ' \e[32m✓\e[0m %s\n' "$*"; } note() { printf '==> %s\n' "$*"; } warn() { printf ' \e[33m!\e[0m %s\n' "$*" >&2; } err() { printf ' \e[31m✗\e[0m %s\n' "$*" >&2; } confirm() { [[ "$ASSUME_YES" == "yes" ]] && return 0 read -r -p "Proceed? [y/N] " ans [[ "$ans" =~ ^[Yy]$ ]] } link_if_into_repo() { # Return 0 if $1 is a symlink pointing into $SCRIPT_DIR local target [[ -L "$1" ]] || return 1 target=$(readlink -f "$1") [[ "$target" == "$SCRIPT_DIR"/* ]] } # ── install ────────────────────────────────────────── install_bin() { mkdir -p "$BIN_DEST" note "Symlinking bin/ executables into $BIN_DEST" local count=0 local skipped=0 for src in "$BIN_SRC"/*; do [[ -x "$src" && -f "$src" ]] || continue local name; name=$(basename "$src") local dest="$BIN_DEST/$name" if [[ -e "$dest" || -L "$dest" ]]; then if link_if_into_repo "$dest"; then ok "Already linked: $name" count=$((count+1)) continue else # Tell the user exactly what's in the way and how to fix it. local what="real file" if [[ -L "$dest" ]]; then local current_target current_target=$(readlink "$dest") what="symlink → $current_target" fi warn "Skipping $name — $dest already exists ($what)" printf ' If stale (e.g. an old cli_utils install), remove and re-run:\n' printf ' rm %q && %q/install.sh\n' "$dest" "$SCRIPT_DIR" skipped=$((skipped+1)) continue fi fi ln -s "$src" "$dest" ok "Linked $name → $src" count=$((count+1)) done echo ok "Installed $count executable(s)" if (( skipped > 0 )); then warn "$skipped executable(s) skipped — see notes above" fi } install_skill() { note "Linking companion agent skill" mkdir -p "$SKILL_DEST_DIR" # Drop a marker file so sibling tooling (deploy-skills.sh, agents-sync.zsh, # and any future reconciler) can tell at a glance that this skill directory # is owned by an external repo and shouldn't be clobbered. The convention # is two lines: "# skill-source: " + "# url: ". # Any colocated skill from any repo can adopt the same convention. local marker="$SKILL_DEST_DIR/.skill-source" if [[ ! -e "$marker" ]]; then cat > "$marker" < Installation plan:" echo " Symlink executables in bin/ into $BIN_DEST" echo " Symlink SKILL.md into $SKILL_DEST" echo confirm || { echo "Aborted."; exit 0; } echo install_bin echo install_skill echo check_path echo ok "Done." echo echo "Next: ./bin/mempalace-session --dry-run" echo " or: ./bin/mempalace-docs /path/to/project --dry-run" } # ── uninstall ──────────────────────────────────────── do_uninstall() { echo echo "mempalace-toolkit uninstaller" echo "Repository: $SCRIPT_DIR" echo confirm || { echo "Aborted."; exit 0; } echo note "Removing executable symlinks from $BIN_DEST" local removed=0 for src in "$BIN_SRC"/*; do [[ -x "$src" && -f "$src" ]] || continue local name; name=$(basename "$src") local dest="$BIN_DEST/$name" if link_if_into_repo "$dest"; then rm "$dest" ok "Removed $name" removed=$((removed+1)) fi done ok "Removed $removed executable symlink(s)" echo note "Removing skill symlink" if link_if_into_repo "$SKILL_DEST"; then rm "$SKILL_DEST" ok "Removed skill symlink" else ok "No skill symlink to remove" fi # Remove the marker and the now-empty skill directory, but only if # the marker was written by us and the directory has nothing else in it. local marker="$SKILL_DEST_DIR/.skill-source" if [[ -f "$marker" ]] && grep -q '^# skill-source: mempalace-toolkit$' "$marker" 2>/dev/null; then rm "$marker" ok "Removed $marker" fi if [[ -d "$SKILL_DEST_DIR" ]] && [[ -z "$(ls -A "$SKILL_DEST_DIR" 2>/dev/null)" ]]; then rmdir "$SKILL_DEST_DIR" ok "Removed empty $SKILL_DEST_DIR" fi echo ok "Done." } case "$ACTION" in install) do_install ;; uninstall) do_uninstall ;; esac