Initial commit: pi harness bring-up split out of mempalace-toolkit
Pi-generic config artifacts (no mempalace dependency): - pi-env.zsh: shell loader sourcing ~/.config/pi/.env for AWS_PROFILE / AWS_REGION. POSIX-compatible (works in bash and zsh). - keybindings.json: mosh/tmux newline bindings (shift+enter, ctrl+j, alt+j). - settings.example.json: ~/.pi/agent/settings.json template so pi starts without --provider/--model. Region-specific (Bedrock inference-profile prefix). install.sh mirrors mempalace-toolkit + opencode-toolkit patterns: - require_pi_installed: hard exit 4 if ~/.pi/agent/ missing (cannot do anything useful; user must install pi first). - symlink keybindings.json (safe: pi doesn't rewrite it). - cp pi-env.zsh into ~/.oh-my-zsh/custom/ (portability over symlink, that dir is part of dotfiles backups). Print source snippet for bash / plain-zsh users. - settings.example.json NOT installed \u2014 pi rewrites settings.json at runtime. check_pi_settings probe prints the cp command instead. - check_aws_env: gated on settings.json selecting amazon-bedrock; silent for non-Bedrock providers or missing settings. - All probes warn + return 0, never halt. - Non-destructive: backup on symlink collision, cmp-based drift detection on the cp path, uninstall only removes copies whose content still matches repo. Split rationale: opencode-devbox's mempalace opt-out (~300 MB saved) wants pi available without mempalace. That dependency asymmetry is cleanest when pi's own config lives in its own repo, same shape as opencode-toolkit split out earlier today. The pi\u2194mempalace MCP bridge (mempalace.ts) stays in mempalace-toolkit where it belongs \u2014 it imports pi's ExtensionAPI but only exists to bridge to the palace. Verified on tor-ms22: fresh install \u2192 drift detect \u2192 adopt canonical \u2192 uninstall \u2192 reinstall \u2192 zsh -ic loads AWS vars. Bash fallback path also tested via HOME=/tmp/fake SHELL=/bin/bash.
This commit is contained in:
Executable
+276
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh — install pi-toolkit
|
||||
#
|
||||
# Harness-side bring-up for the pi coding-agent (https://github.com/mariozechner/pi-coding-agent):
|
||||
# - pi-env.zsh : shell loader sourcing ~/.config/pi/.env so AWS_PROFILE
|
||||
# and AWS_REGION are in every shell that launches pi.
|
||||
# - keybindings.json : mosh/tmux-friendly newline bindings
|
||||
# (shift+enter, ctrl+j, alt+j).
|
||||
# - settings.example.json : template for ~/.pi/agent/settings.json so pi
|
||||
# starts without --provider/--model. Copy + edit.
|
||||
#
|
||||
# No dependency on MemPalace. For the palace bridge see
|
||||
# https://gitea.jordbo.se/joakimp/mempalace-toolkit (optional).
|
||||
#
|
||||
# Idempotent. Safe to re-run. Non-destructive on drift: if installed content
|
||||
# differs from repo, leaves user edits alone and points at diff.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── locate self ──────────────────────────────────────
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# ── targets ──────────────────────────────────────────
|
||||
PI_EXT_DEST_DIR="${HOME}/.pi/agent/extensions" # existence = pi is installed
|
||||
PI_AGENT_DIR="${HOME}/.pi/agent"
|
||||
|
||||
# keybindings — symlinked (pi doesn't rewrite it at runtime)
|
||||
PI_KEYS_SRC="${SCRIPT_DIR}/keybindings.json"
|
||||
PI_KEYS_DEST="${HOME}/.pi/agent/keybindings.json"
|
||||
|
||||
# settings template — NOT installed, only referenced (pi rewrites settings.json)
|
||||
PI_SETTINGS_EXAMPLE="${SCRIPT_DIR}/settings.example.json"
|
||||
PI_SETTINGS_DEST="${HOME}/.pi/agent/settings.json"
|
||||
|
||||
# shell env loader — cp'd into oh-my-zsh custom dir (portability over symlink)
|
||||
PI_ENV_SRC="${SCRIPT_DIR}/pi-env.zsh"
|
||||
PI_ENV_OMZ_DEST="${HOME}/.oh-my-zsh/custom/pi-env.zsh"
|
||||
|
||||
# ── 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 pi-toolkit
|
||||
|
||||
Usage:
|
||||
./install.sh # install (interactive confirm)
|
||||
./install.sh --yes # install without prompt
|
||||
./install.sh --uninstall # remove symlinks and copied loaders
|
||||
|
||||
What install does:
|
||||
- Requires pi installed (~/.pi/agent/ must exist). Aborts cleanly otherwise.
|
||||
- Symlinks keybindings.json into ~/.pi/agent/keybindings.json.
|
||||
- If oh-my-zsh is present (~/.oh-my-zsh/custom/), copies pi-env.zsh there
|
||||
so every new zsh shell sources ~/.config/pi/.env. Uses cp (not symlink)
|
||||
for dotfile-backup portability. Prints source snippet otherwise.
|
||||
- Probes (non-halting warnings): settings.json present, and AWS_PROFILE /
|
||||
AWS_REGION set if settings.json selects amazon-bedrock.
|
||||
|
||||
What uninstall does:
|
||||
- Removes keybindings.json symlink if it points into this repo.
|
||||
- Removes pi-env.zsh copy ONLY if its content still matches the repo.
|
||||
- Never touches settings.json (it's your file, not managed here).
|
||||
|
||||
No dependency on MemPalace. For memory integration see:
|
||||
https://gitea.jordbo.se/joakimp/mempalace-toolkit
|
||||
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"/* ]]
|
||||
}
|
||||
|
||||
require_pi_installed() {
|
||||
if [[ ! -d "$PI_AGENT_DIR" ]]; then
|
||||
err "pi not detected at $PI_AGENT_DIR"
|
||||
printf ' Install pi first: https://github.com/mariozechner/pi-coding-agent\n'
|
||||
printf ' Re-run this installer after `pi` has been started at least once\n'
|
||||
printf ' (first run creates ~/.pi/agent/).\n'
|
||||
exit 4
|
||||
fi
|
||||
# Ensure the extensions dir exists so pi loads any extensions we may add
|
||||
# later (e.g. mempalace-toolkit's mempalace.ts). pi creates this on first
|
||||
# extension install but we may race ahead of that.
|
||||
mkdir -p "$PI_EXT_DEST_DIR"
|
||||
ok "pi detected at $PI_AGENT_DIR"
|
||||
}
|
||||
|
||||
# ── install ──────────────────────────────────────────
|
||||
install_keybindings() {
|
||||
# Generic mosh/tmux newline fix. Symlinked so edits flow through git.
|
||||
note "Linking keybindings.json → $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
|
||||
# Back up any existing real file / foreign symlink.
|
||||
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"
|
||||
}
|
||||
|
||||
install_env_loader() {
|
||||
# Copy pi-env.zsh into ~/.oh-my-zsh/custom/ (auto-loaded by omz).
|
||||
# cp not symlink because that dir is typically part of a dotfiles backup
|
||||
# and a symlink into this repo breaks on restore to another host.
|
||||
# No oh-my-zsh → print a source snippet for manual rc-file addition.
|
||||
|
||||
if [[ -d "$HOME/.oh-my-zsh/custom" ]]; then
|
||||
note "Installing pi-env.zsh into ~/.oh-my-zsh/custom/"
|
||||
if [[ -f "$PI_ENV_OMZ_DEST" ]]; then
|
||||
if cmp -s "$PI_ENV_SRC" "$PI_ENV_OMZ_DEST"; then
|
||||
ok "pi-env.zsh already installed (content matches repo)"
|
||||
return 0
|
||||
fi
|
||||
warn "$PI_ENV_OMZ_DEST exists and differs from repo copy"
|
||||
printf ' Leaving your edits alone. Compare with:\n'
|
||||
printf ' diff %q %q\n' "$PI_ENV_OMZ_DEST" "$PI_ENV_SRC"
|
||||
printf ' To adopt the repo version: rm that file and re-run install.sh\n'
|
||||
return 0
|
||||
fi
|
||||
cp "$PI_ENV_SRC" "$PI_ENV_OMZ_DEST"
|
||||
ok "Copied pi-env.zsh → $PI_ENV_OMZ_DEST"
|
||||
printf ' Open a new shell (or `exec zsh`) to load AWS_PROFILE / AWS_REGION.\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# No oh-my-zsh — print shell-specific snippet.
|
||||
note "oh-my-zsh not detected — manual shell setup needed"
|
||||
local shell_name
|
||||
shell_name="$(basename "${SHELL:-bash}")"
|
||||
local rc_file
|
||||
case "$shell_name" in
|
||||
zsh) rc_file="~/.zshrc" ;;
|
||||
bash) rc_file="~/.bashrc" ;;
|
||||
*) rc_file="~/.${shell_name}rc # adjust for your shell" ;;
|
||||
esac
|
||||
printf ' Add this line to %s so every shell sources ~/.config/pi/.env:\n' "$rc_file"
|
||||
printf ' source %q\n' "$PI_ENV_SRC"
|
||||
printf ' (the loader itself is POSIX-compatible — works in bash and zsh.)\n'
|
||||
}
|
||||
|
||||
# ── Probes ───────────────────────────────────────────
|
||||
# Never halt. warn + return 0 — mirrors opencode-toolkit and mempalace-toolkit.
|
||||
check_pi_settings() {
|
||||
# If settings.json missing, pi refuses to start without --provider/--model
|
||||
# on every run. Template exists in this repo; point at the cp command.
|
||||
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 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 README.md for the eu./us./anthropic: prefix rules.\n'
|
||||
}
|
||||
|
||||
check_aws_env() {
|
||||
# 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 tor-ms22 dotfiles pattern):\n'
|
||||
printf ' ~/.config/pi/.env # AWS_PROFILE=..., AWS_REGION=...\n'
|
||||
printf ' The pi-env.zsh loader (installed above) sources it automatically.\n'
|
||||
printf ' Open a fresh shell to pick up the vars after creating the file.\n'
|
||||
}
|
||||
|
||||
do_install() {
|
||||
echo
|
||||
echo "pi-toolkit installer"
|
||||
echo "Repository: $SCRIPT_DIR"
|
||||
echo
|
||||
require_pi_installed
|
||||
echo
|
||||
echo "==> Installation plan:"
|
||||
echo " Symlink keybindings.json → $PI_KEYS_DEST"
|
||||
if [[ -d "$HOME/.oh-my-zsh/custom" ]]; then
|
||||
echo " Copy pi-env.zsh → $PI_ENV_OMZ_DEST"
|
||||
else
|
||||
echo " Print manual source-snippet instructions for pi-env.zsh"
|
||||
fi
|
||||
echo
|
||||
confirm || { echo "Aborted."; exit 0; }
|
||||
echo
|
||||
install_keybindings
|
||||
echo
|
||||
install_env_loader
|
||||
echo
|
||||
check_pi_settings
|
||||
echo
|
||||
check_aws_env
|
||||
echo
|
||||
ok "Done."
|
||||
}
|
||||
|
||||
# ── uninstall ────────────────────────────────────────
|
||||
do_uninstall() {
|
||||
echo
|
||||
echo "pi-toolkit uninstaller"
|
||||
echo "Repository: $SCRIPT_DIR"
|
||||
echo
|
||||
confirm || { echo "Aborted."; exit 0; }
|
||||
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
|
||||
|
||||
echo
|
||||
note "Removing pi-env.zsh loader (oh-my-zsh path)"
|
||||
# Only remove if content still matches the repo copy — user may have
|
||||
# local edits we shouldn't silently discard.
|
||||
if [[ -f "$PI_ENV_OMZ_DEST" ]] && cmp -s "$PI_ENV_SRC" "$PI_ENV_OMZ_DEST"; then
|
||||
rm "$PI_ENV_OMZ_DEST"
|
||||
ok "Removed $PI_ENV_OMZ_DEST"
|
||||
elif [[ -f "$PI_ENV_OMZ_DEST" ]]; then
|
||||
warn "$PI_ENV_OMZ_DEST differs from repo copy — left alone"
|
||||
else
|
||||
ok "No pi-env.zsh loader to remove"
|
||||
fi
|
||||
|
||||
echo
|
||||
ok "Done."
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
install) do_install ;;
|
||||
uninstall) do_uninstall ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user