4dcd2959ec
Two missing pieces bundled here:
1. The README described the MCP server wrapper (mempalace-mcp-server,
a 3-line shell script that exec's the venv's python) as the
canonical answer on a uv-tool install. That is over-engineered and
out of step with the live reference box: opencode.json there uses
['mempalace-mcp'] directly, which is the shim uv tool install
already creates. Rewrote the section to put the simple canonical
answer first and demote the wrapper to a legacy-fallback sidebar.
New 'Registering mempalace with opencode' section covers:
- The one-entry JSON stanza to paste into the mcp object.
- A full minimal opencode.json for someone starting fresh (with
the 'instructions' array pointing at the wake-up protocol).
- Custom palace path variant.
- Claude Code one-liner (claude mcp add mempalace -- mempalace-mcp).
- Pointer at 'mempalace mcp' subcommand which prints the currently-
recommended snippets — useful when upstream updates conventions.
- Troubleshooting table (tools absent / server unavailable /
ModuleNotFoundError) with per-symptom fixes.
The legacy-fallback subsection explains what the wrapper script
was for (the pip-install → uv-tool-install transition era), shows
its 3-line implementation for completeness, and is explicit about
not using it for new installs.
Verification checklist updated: now runs 'which mempalace-mcp' and
'mempalace-mcp --help' against the shim, not the wrapper.
2. install.sh gains a matching probe: after the existing wake-up
protocol check, it grep-checks ~/.config/opencode/opencode.json
for 'mempalace' + 'mempalace-mcp' substrings. Present → clean
success line. Missing → actionable warning that prints the exact
JSON stanza to add plus a pointer to the README anchor and the
'mempalace mcp' CLI helper. Skipped when opencode.json doesn't
exist (non-opencode hosts).
Textual grep rather than strict JSON parse: we don't want to
hard-depend on python/jq at install time, and the two substrings
together are specific enough to avoid false positives.
Prerequisites list gets a 5th bullet flagging MCP registration as
required and pointing at the new section + the installer probe.
Smoke-tested both scenarios on the reference box — config with
mempalace entry present (all ticks green), config with entry removed
(actionable warning, correct snippet shown), config restored to
original state.
301 lines
11 KiB
Bash
Executable File
301 lines
11 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
|
|
}
|
|
|
|
# ── 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
|
|
}
|
|
|
|
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
|
|
check_opencode_mcp
|
|
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
|