#!/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 <&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