Files
mempalace-toolkit/install.sh
T
Joakim Persson 60c2a4abec install.sh: probe for mempalace wake-up protocol, warn if missing
The mempalace skill is only useful if an agent loads its wake-up
protocol at session start. Without ~/.config/opencode/instructions/
mempalace.md, the skill is reachable but never auto-runs — agents
forget to search before answering and to write a diary at wind-down.
The failure mode is silent: no error, no warning, just gradual
memory degradation.

install.sh now probes for the file after its regular installation
steps and prints an actionable warning if missing:

  !  Wake-up protocol NOT installed at /path/to/.../mempalace.md
         Without it, the mempalace skill is loadable but never auto-
         runs at session start. Install via the skillset repo:
             git clone .../skillset.git ~/skillset
             cd ~/skillset && ./deploy-skills.sh --bootstrap
         (if skillset is already cloned, just run the --bootstrap step)

If the file is present: prints a matching success line. If the host
doesn't even have ~/.config/opencode/, the check is skipped entirely
(non-opencode machine → no warning to display).

README Prerequisites gains a 4th bullet pointing at skillset's
--bootstrap as the canonical source for the wake-up protocol, so
anyone reading the docs without running install.sh also learns
about the dependency.

The wake-up file is shipped by skillset, not this toolkit. Rationale:
the file bootstraps the 'mempalace' skill (which lives in skillset)
and applies to any harness, not just opencode-plus-toolkit machines.
Cross-referenced via the skillset gitea URL in both the installer
message and the README.

Smoke-tested present + missing scenarios on the reference box —
cleanly detects both, does not hard-fail on missing.
2026-04-30 11:14:15 +00:00

266 lines
9.3 KiB
Bash
Executable File

#!/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 <<EOF
install.sh — install mempalace-toolkit
Usage:
./install.sh # install (interactive confirm)
./install.sh --yes # install without prompt
./install.sh --uninstall # remove symlinks
What install does:
- Symlinks each executable in bin/ into ~/.local/bin/
- Symlinks SKILL.md into ~/.agents/skills/opencode-mempalace-bridge/SKILL.md
(auto-discovered by opencode; run agents-sync from cli_utils to also
reach Claude Code and Kiro)
- Drops a .skill-source marker in the skill dir so sibling tooling
(deploy-skills.sh, agents-sync.zsh) knows the dir is externally owned
What uninstall does:
- Removes symlinks in ~/.local/bin/ that point into this repo
- Removes the skill symlink if it points into this repo
- Removes the .skill-source marker and empty skill dir
EOF
exit 0 ;;
*) echo "Unknown flag: $1" >&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: <repo-name>" + "# url: <clone-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" <<EOF
# skill-source: mempalace-toolkit
# repo: $SCRIPT_DIR
# url: ssh://git@gitea.jordbo.se:2222/joakimp/mempalace-toolkit.git
# This skill directory is managed by an external repo's install.sh.
# deploy-skills.sh (skillset) and agents-sync.zsh (cli_utils) leave
# directories containing this marker alone. Do not move SKILL.md out
# of this directory without also updating the owning repo.
EOF
ok "Wrote $marker"
fi
if [[ -e "$SKILL_DEST" || -L "$SKILL_DEST" ]]; then
if link_if_into_repo "$SKILL_DEST"; then
ok "Skill already linked"
return 0
else
warn "Skipping skill: $SKILL_DEST exists and is not our symlink"
return 0
fi
fi
ln -s "$SKILL_SRC" "$SKILL_DEST"
ok "Linked SKILL.md → $SKILL_SRC"
}
check_path() {
case ":$PATH:" in
*":$BIN_DEST:"*) : ;;
*) warn "$BIN_DEST is not on \$PATH. Add to your shell rc:";
printf ' export PATH="%s:$PATH"\n' "\$HOME/.local/bin" ;;
esac
}
# ── Verify the mempalace wake-up protocol is reachable ──
# The mempalace skill is only useful if the agent actually loads its
# wake-up protocol at session start. Opencode loads that from
# ~/.config/opencode/instructions/mempalace.md. Without this file, the
# skill is available but never auto-runs, and most of mempalace's value
# (search-before-speak, wind-down diary) is forfeited silently.
#
# The file is owned by the skillset repo, not this one — pointing users
# at skillset if they haven't run it there. Opencode-only: we skip this
# check if ~/.config/opencode doesn't exist (non-opencode host).
check_wake_up_protocol() {
local opencode_config="$HOME/.config/opencode"
[[ -d "$opencode_config" ]] || return 0 # not an opencode box → nothing to warn about
local instr="$opencode_config/instructions/mempalace.md"
if [[ -e "$instr" ]]; then
ok "Wake-up protocol detected: $instr"
return 0
fi
warn "Wake-up protocol NOT installed at $instr"
printf ' Without it, the mempalace skill is loadable but never auto-runs\n'
printf ' at session start. Agents forget to search before answering and to\n'
printf ' write a diary entry at wind-down. Install via the skillset repo:\n'
printf ' git clone ssh://git@gitea.jordbo.se:2222/joakimp/skillset.git ~/skillset\n'
printf ' cd ~/skillset && ./deploy-skills.sh --bootstrap\n'
printf ' (if skillset is already cloned, just run the --bootstrap step)\n'
return 0
}
do_install() {
echo
echo "mempalace-toolkit installer"
echo "Repository: $SCRIPT_DIR"
echo
echo "==> 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
check_wake_up_protocol
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