75876e5c41
check_aws_env was warning about missing AWS_PROFILE/AWS_REGION even on fresh machines with no settings.json yet \u2014 but at that point we don't know which provider the user will pick, so the warning is noise. check_pi_settings already tells the user to bootstrap settings.json; the AWS probe now stays quiet until it has evidence (amazon-bedrock in settings.json) that AWS creds are actually needed.
459 lines
18 KiB
Bash
Executable File
459 lines
18 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"
|
|
|
|
# pi coding-agent extension (optional — only linked if pi is installed)
|
|
PI_EXT_SRC="${SCRIPT_DIR}/extensions/pi/mempalace.ts"
|
|
PI_EXT_DEST_DIR="${HOME}/.pi/agent/extensions"
|
|
PI_EXT_DEST="${PI_EXT_DEST_DIR}/mempalace.ts"
|
|
|
|
# pi keybindings (generic mosh/tmux newline fix — safe on any machine)
|
|
PI_KEYS_SRC="${SCRIPT_DIR}/extensions/pi/keybindings.json"
|
|
PI_KEYS_DEST="${HOME}/.pi/agent/keybindings.json"
|
|
|
|
# pi settings template (NOT symlinked — pi rewrites this file at runtime)
|
|
PI_SETTINGS_EXAMPLE="${SCRIPT_DIR}/extensions/pi/settings.example.json"
|
|
PI_SETTINGS_DEST="${HOME}/.pi/agent/settings.json"
|
|
|
|
# ── 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)
|
|
- If pi (~/.pi/agent/extensions/) exists, symlinks extensions/pi/mempalace.ts
|
|
into ~/.pi/agent/extensions/mempalace.ts (pi bridge). Skipped otherwise.
|
|
- If pi exists, symlinks extensions/pi/keybindings.json into
|
|
~/.pi/agent/keybindings.json (generic mosh/tmux newline fix).
|
|
- If pi exists, warns if ~/.pi/agent/settings.json is missing and points
|
|
at extensions/pi/settings.example.json as a template (NOT symlinked —
|
|
pi rewrites this file at runtime).
|
|
- Warns if AWS_PROFILE / AWS_REGION are unset (only relevant to users
|
|
whose pi settings.json selects amazon-bedrock as defaultProvider).
|
|
- 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 pi extension symlink if it points into this repo
|
|
- Removes the pi keybindings 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
|
|
}
|
|
|
|
# ── Verify mempalace is registered as an MCP server in opencode.json ──
|
|
# Even with mempalace installed and the wake-up protocol in place, opencode
|
|
# won't actually launch the MCP server (so mempalace_* tools won't appear in
|
|
# the agent's toolset) unless ~/.config/opencode/opencode.json declares a
|
|
# server under .mcp.mempalace with command ["mempalace-mcp"] (or the legacy
|
|
# wrapper equivalent).
|
|
#
|
|
# This is a lightweight textual check — we don't parse JSON strictly, just
|
|
# look for the expected substring. False negatives are acceptable (a weirdly
|
|
# formatted but valid config), false positives less so but very unlikely
|
|
# given the specific shim name. Skipped on non-opencode hosts.
|
|
check_opencode_mcp() {
|
|
local opencode_config="$HOME/.config/opencode/opencode.json"
|
|
[[ -f "$opencode_config" ]] || return 0 # no config → nothing to check
|
|
|
|
if grep -q '"mempalace"[[:space:]]*:' "$opencode_config" 2>/dev/null \
|
|
&& grep -q 'mempalace-mcp' "$opencode_config" 2>/dev/null; then
|
|
ok "mempalace MCP server registered in opencode.json"
|
|
return 0
|
|
fi
|
|
|
|
warn "mempalace MCP server NOT registered in $opencode_config"
|
|
printf ' Without this, opencode loads the skill but no mempalace_* tools\n'
|
|
printf ' are reachable at runtime, so search/diary/KG calls silently fail.\n'
|
|
printf ' Add this entry under the top-level "mcp" object:\n'
|
|
printf ' "mempalace": { "type": "local", "command": ["mempalace-mcp"] }\n'
|
|
printf ' Or open a fresh config with:\n'
|
|
printf ' mempalace mcp # prints current recommended snippets\n'
|
|
printf ' Full details (including Claude Code one-liner) in:\n'
|
|
printf ' README.md#registering-mempalace-with-opencode-or-other-mcp-clients\n'
|
|
return 0
|
|
}
|
|
|
|
install_pi_extension() {
|
|
# The pi coding-agent extension is optional: link it only if pi is
|
|
# already installed on this machine (its ~/.pi/agent/extensions/
|
|
# directory exists). Otherwise silently skip — mempalace-toolkit is
|
|
# useful on opencode-only boxes too.
|
|
if [[ ! -d "$PI_EXT_DEST_DIR" ]]; then
|
|
note "pi not detected at $PI_EXT_DEST_DIR — skipping pi extension"
|
|
printf ' (install pi first if you want the pi↔mempalace bridge:\n'
|
|
printf ' https://github.com/mariozechner/pi-coding-agent)\n'
|
|
return 0
|
|
fi
|
|
|
|
note "Linking pi extension into $PI_EXT_DEST_DIR"
|
|
if [[ -e "$PI_EXT_DEST" || -L "$PI_EXT_DEST" ]]; then
|
|
if link_if_into_repo "$PI_EXT_DEST"; then
|
|
ok "pi extension already linked"
|
|
return 0
|
|
fi
|
|
# Non-symlink (or foreign symlink) in the way — back it up rather
|
|
# than clobber. User may have local edits they want to preserve.
|
|
local backup="${PI_EXT_DEST}.bak.$(date +%Y%m%d-%H%M%S)"
|
|
mv "$PI_EXT_DEST" "$backup"
|
|
warn "Existing $PI_EXT_DEST backed up to $backup"
|
|
fi
|
|
ln -s "$PI_EXT_SRC" "$PI_EXT_DEST"
|
|
ok "Linked mempalace.ts → $PI_EXT_SRC"
|
|
printf ' Restart pi to load the extension (it reads ~/.pi/agent/extensions/\n'
|
|
printf ' at startup only).\n'
|
|
}
|
|
|
|
install_pi_keybindings() {
|
|
# Generic mosh/tmux newline fix. Non-destructive: if a real
|
|
# keybindings.json exists we back it up rather than clobber.
|
|
[[ -d "$PI_EXT_DEST_DIR" ]] || return 0 # no pi → no keybindings
|
|
|
|
note "Linking pi keybindings → $PI_KEYS_DEST"
|
|
if [[ -e "$PI_KEYS_DEST" || -L "$PI_KEYS_DEST" ]]; then
|
|
if link_if_into_repo "$PI_KEYS_DEST"; then
|
|
ok "pi keybindings already linked"
|
|
return 0
|
|
fi
|
|
local backup="${PI_KEYS_DEST}.bak.$(date +%Y%m%d-%H%M%S)"
|
|
mv "$PI_KEYS_DEST" "$backup"
|
|
warn "Existing $PI_KEYS_DEST backed up to $backup"
|
|
fi
|
|
ln -s "$PI_KEYS_SRC" "$PI_KEYS_DEST"
|
|
ok "Linked keybindings.json → $PI_KEYS_SRC"
|
|
}
|
|
|
|
# ── Verify ~/.pi/agent/settings.json exists ──────────────────────────
|
|
# If pi is installed but settings.json is missing, `pi` refuses to start
|
|
# without `--provider ... --model ...` on every invocation. The toolkit
|
|
# ships extensions/pi/settings.example.json as a template with a working
|
|
# Bedrock (eu-west-1) stanza — copy + edit for your region/account.
|
|
#
|
|
# NOT symlinked: pi rewrites settings.json at runtime (lastChangelogVersion
|
|
# bumps on upgrade), which would dirty the repo and cause merge noise.
|
|
# Template-only install is the right trade-off.
|
|
check_pi_settings() {
|
|
[[ -d "$PI_EXT_DEST_DIR" ]] || return 0 # no pi → nothing to check
|
|
|
|
if [[ -f "$PI_SETTINGS_DEST" ]]; then
|
|
ok "pi settings.json present at $PI_SETTINGS_DEST"
|
|
return 0
|
|
fi
|
|
|
|
warn "pi settings.json NOT found at $PI_SETTINGS_DEST"
|
|
printf ' Without it, pi must be invoked with --provider/--model on every run.\n'
|
|
printf ' Bootstrap from the shipped template:\n'
|
|
printf ' cp %q %q\n' "$PI_SETTINGS_EXAMPLE" "$PI_SETTINGS_DEST"
|
|
printf ' $EDITOR %q # adjust region prefix + model IDs\n' "$PI_SETTINGS_DEST"
|
|
printf ' See extensions/pi/README.md for the eu./us./anthropic: prefix rules.\n'
|
|
return 0
|
|
}
|
|
|
|
# ── Verify AWS env vars are present for Bedrock-backed pi ────────────
|
|
# Only meaningful if pi's settings.json selects amazon-bedrock. We do a
|
|
# best-effort grep rather than parsing JSON — false positives are cheap
|
|
# (one extra probe) and the check is gated on pi being installed at all.
|
|
check_aws_env() {
|
|
[[ -d "$PI_EXT_DEST_DIR" ]] || return 0 # no pi → nothing to check
|
|
|
|
# Only warn if settings.json selects amazon-bedrock. If pi uses a
|
|
# non-Bedrock provider (bare anthropic, openai, ...) AWS creds are
|
|
# irrelevant and this probe would be noise. If settings.json doesn't
|
|
# exist yet, check_pi_settings already told the user to bootstrap it
|
|
# — we can't know which provider they'll pick, so stay quiet here.
|
|
[[ -f "$PI_SETTINGS_DEST" ]] || return 0
|
|
grep -q '"amazon-bedrock"' "$PI_SETTINGS_DEST" 2>/dev/null || return 0
|
|
|
|
if [[ -n "${AWS_PROFILE:-}" && -n "${AWS_REGION:-}" ]]; then
|
|
ok "AWS env present (AWS_PROFILE=$AWS_PROFILE, AWS_REGION=$AWS_REGION)"
|
|
return 0
|
|
fi
|
|
|
|
warn "AWS_PROFILE and/or AWS_REGION not set in this shell"
|
|
printf ' pi with defaultProvider=amazon-bedrock needs both to invoke Bedrock.\n'
|
|
printf ' Recommended layout (matches the tor-ms22 dotfiles pattern):\n'
|
|
printf ' ~/.config/pi/.env # AWS_PROFILE=..., AWS_REGION=...\n'
|
|
printf ' ~/.oh-my-zsh/custom/pi-env.zsh # set -a; source ~/.config/pi/.env; set +a\n'
|
|
printf ' See extensions/pi/README.md#environment-setup for the template.\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"
|
|
if [[ -d "$PI_EXT_DEST_DIR" ]]; then
|
|
echo " Symlink extensions/pi/mempalace.ts into $PI_EXT_DEST"
|
|
echo " Symlink extensions/pi/keybindings.json into $PI_KEYS_DEST"
|
|
fi
|
|
echo
|
|
confirm || { echo "Aborted."; exit 0; }
|
|
echo
|
|
install_bin
|
|
echo
|
|
install_skill
|
|
echo
|
|
install_pi_extension
|
|
echo
|
|
install_pi_keybindings
|
|
echo
|
|
check_path
|
|
echo
|
|
check_wake_up_protocol
|
|
echo
|
|
check_opencode_mcp
|
|
echo
|
|
check_pi_settings
|
|
echo
|
|
check_aws_env
|
|
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
|
|
|
|
echo
|
|
note "Removing pi extension symlink"
|
|
if link_if_into_repo "$PI_EXT_DEST"; then
|
|
rm "$PI_EXT_DEST"
|
|
ok "Removed pi extension symlink"
|
|
else
|
|
ok "No pi extension symlink to remove"
|
|
fi
|
|
|
|
echo
|
|
note "Removing pi keybindings symlink"
|
|
if link_if_into_repo "$PI_KEYS_DEST"; then
|
|
rm "$PI_KEYS_DEST"
|
|
ok "Removed pi keybindings symlink"
|
|
else
|
|
ok "No pi keybindings 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
|