1381a37115
Pi moved to its new home at earendil-works on 2026-05-07 (https://pi.dev/news/2026/5/7/pi-has-a-new-home). Affected packages: @mariozechner/pi-coding-agent -> @earendil-works/pi-coding-agent @mariozechner/pi-tui -> @earendil-works/pi-tui @mariozechner/pi-ai -> @earendil-works/pi-ai @mariozechner/pi-agent-core -> @earendil-works/pi-agent-core The old @mariozechner/* packages are deprecated on npm with the explicit message 'please use @earendil-works/pi-coding-agent instead going forward', and the version stream has moved on (old top-out 0.73.1; new currently 0.74.0). Anyone npm-installing the old names gets a deprecation warning + a stale binary. Sweep: - All 7 extension TypeScript files: import statements updated. - README, AGENTS, install.sh: textual references and the github.com/ mariozechner/pi-coding-agent URL pointed at github.com/earendil-works/ pi (the new monorepo root; coding-agent now lives at packages/coding-agent inside it). - Bun build of mcp-loader, ext-toggle, ssh-controlmaster verified clean. Brew install references (`brew install pi-coding-agent`) left as-is: the homebrew formula still works at 0.73.1 and a tap update is tracked upstream at earendil-works/pi#2755. Historical CHANGELOG entries are untouched.
219 lines
7.3 KiB
Bash
Executable File
219 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh — install pi-extensions
|
|
#
|
|
# Symlinks each extension in extensions/ into ~/.pi/agent/extensions/ so pi
|
|
# loads them automatically on every session. Idempotent and non-destructive.
|
|
#
|
|
# Usage:
|
|
# ./install.sh install all extensions
|
|
# ./install.sh --only ssh-controlmaster install one extension
|
|
# ./install.sh --only "ext1,ext2" install a subset
|
|
# ./install.sh --skip "ext1,ext2" install all except these
|
|
# ./install.sh --yes skip confirmation prompt
|
|
# ./install.sh --uninstall remove symlinks that point into this repo
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
EXTENSIONS_SRC="${SCRIPT_DIR}/extensions"
|
|
EXTENSIONS_DEST="${HOME}/.pi/agent/extensions"
|
|
PI_AGENT_DIR="${HOME}/.pi/agent"
|
|
|
|
# ── 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_into_repo() {
|
|
local target
|
|
[[ -L "$1" ]] || return 1
|
|
target=$(readlink -f "$1" 2>/dev/null || true)
|
|
[[ "$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/earendil-works/pi\n'
|
|
printf ' Re-run after `pi --help` (first run creates ~/.pi/agent/).\n'
|
|
exit 4
|
|
fi
|
|
mkdir -p "$EXTENSIONS_DEST"
|
|
ok "pi detected at $PI_AGENT_DIR"
|
|
}
|
|
|
|
# ── args ─────────────────────────────────────────────
|
|
ACTION="install"
|
|
ASSUME_YES="no"
|
|
ONLY="" # comma-separated names to include (empty = all)
|
|
SKIP="" # comma-separated names to exclude (empty = none)
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--uninstall) ACTION="uninstall"; shift ;;
|
|
-y|--yes) ASSUME_YES="yes"; shift ;;
|
|
--only) ONLY="$2"; shift 2 ;;
|
|
--only=*) ONLY="${1#--only=}"; shift ;;
|
|
--skip) SKIP="$2"; shift 2 ;;
|
|
--skip=*) SKIP="${1#--skip=}"; shift ;;
|
|
-h|--help)
|
|
cat <<EOF
|
|
install.sh — install pi-extensions
|
|
|
|
Usage:
|
|
./install.sh install all extensions
|
|
./install.sh --only ssh-controlmaster install one extension
|
|
./install.sh --only "ext1,ext2" install a subset
|
|
./install.sh --skip "ext1,ext2" install all except these
|
|
./install.sh --yes skip confirmation prompt
|
|
./install.sh --uninstall remove symlinks that point into this repo
|
|
|
|
--only and --skip accept comma-separated extension names (without .ts suffix).
|
|
--only takes precedence over --skip if both are given.
|
|
|
|
Each .ts file in extensions/ is symlinked into ~/.pi/agent/extensions/.
|
|
Existing files already symlinked into this repo are left alone.
|
|
Existing real files or foreign symlinks are backed up with a timestamp.
|
|
EOF
|
|
exit 0 ;;
|
|
*) echo "Unknown flag: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
# ── build install set ─────────────────────────────────
|
|
# INSTALL_SET: space-delimited bare names (no .ts suffix). Bash 3 compatible.
|
|
INSTALL_SET=""
|
|
|
|
in_install_set() { [[ " $INSTALL_SET " == *" $1 "* ]]; }
|
|
|
|
build_install_set() {
|
|
local n f bare entry new
|
|
if [[ -n "$ONLY" ]]; then
|
|
# Explicit allowlist — only install what's named
|
|
IFS=',' read -ra names <<< "$ONLY"
|
|
for n in "${names[@]}"; do
|
|
INSTALL_SET="${INSTALL_SET:+$INSTALL_SET }${n%.ts}"
|
|
done
|
|
else
|
|
# Start with everything present on disk, then remove --skip entries
|
|
for f in "${EXTENSIONS_SRC}"/*.ts; do
|
|
[[ -e "$f" ]] && INSTALL_SET="${INSTALL_SET:+$INSTALL_SET }$(basename "$f" .ts)"
|
|
done
|
|
if [[ -n "$SKIP" ]]; then
|
|
IFS=',' read -ra names <<< "$SKIP"
|
|
for n in "${names[@]}"; do
|
|
bare="${n%.ts}"
|
|
new=""
|
|
for entry in $INSTALL_SET; do
|
|
[[ "$entry" == "$bare" ]] || new="${new:+$new }$entry"
|
|
done
|
|
INSTALL_SET="$new"
|
|
done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── install ──────────────────────────────────────────
|
|
do_install() {
|
|
echo
|
|
echo "pi-extensions installer"
|
|
echo "Repository: $SCRIPT_DIR"
|
|
echo
|
|
|
|
require_pi_installed
|
|
build_install_set
|
|
|
|
if [[ -z "$INSTALL_SET" ]]; then
|
|
warn "No extensions selected — nothing to install."
|
|
exit 0
|
|
fi
|
|
|
|
echo "==> Extensions to symlink into ${EXTENSIONS_DEST}/:"
|
|
for n in $INSTALL_SET; do
|
|
printf ' %s.ts\n' "$n"
|
|
done
|
|
echo
|
|
confirm || { echo "Aborted."; exit 0; }
|
|
echo
|
|
|
|
for src in "${EXTENSIONS_SRC}"/*.ts; do
|
|
[[ -e "$src" ]] || continue
|
|
local name
|
|
name="$(basename "$src")"
|
|
local bare="${name%.ts}"
|
|
in_install_set "$bare" || continue
|
|
|
|
local dest="${EXTENSIONS_DEST}/${name}"
|
|
local disabled="${dest}.off"
|
|
|
|
note "Linking ${name}"
|
|
|
|
# Respect a prior /ext disable: if <name>.ts.off exists and points
|
|
# into this repo, leave it alone. ext-toggle will flip it back.
|
|
if [[ -L "$disabled" ]] && link_into_repo "$disabled"; then
|
|
ok "${name} kept disabled (${name}.off present)"
|
|
continue
|
|
fi
|
|
|
|
if [[ -e "$dest" || -L "$dest" ]]; then
|
|
if link_into_repo "$dest"; then
|
|
ok "${name} already linked"
|
|
continue
|
|
fi
|
|
local backup="${dest}.bak.$(date +%Y%m%d-%H%M%S)"
|
|
mv "$dest" "$backup"
|
|
warn "Existing ${dest} backed up to ${backup}"
|
|
fi
|
|
|
|
ln -s "$src" "$dest"
|
|
ok "Linked ${name} → ${src}"
|
|
done
|
|
|
|
echo
|
|
ok "Done. Reload pi with /reload or restart to pick up new extensions."
|
|
}
|
|
|
|
# ── uninstall ────────────────────────────────────────
|
|
do_uninstall() {
|
|
echo
|
|
echo "pi-extensions uninstaller"
|
|
echo "Repository: $SCRIPT_DIR"
|
|
echo
|
|
confirm || { echo "Aborted."; exit 0; }
|
|
echo
|
|
|
|
local removed=0
|
|
# Match both active (.ts) and disabled (.ts.off) symlinks — the ext-toggle
|
|
# extension can rename a link to .ts.off to disable it, and uninstall
|
|
# should still clean those up.
|
|
for pattern in "*.ts" "*.ts.off"; do
|
|
for dest in "${EXTENSIONS_DEST}"/$pattern; do
|
|
[[ -e "$dest" || -L "$dest" ]] || continue
|
|
if link_into_repo "$dest"; then
|
|
rm "$dest"
|
|
ok "Removed $(basename "$dest")"
|
|
(( removed++ )) || true
|
|
fi
|
|
done
|
|
done
|
|
|
|
if [[ $removed -eq 0 ]]; then
|
|
ok "No symlinks pointing into this repo found — nothing removed."
|
|
else
|
|
echo
|
|
ok "Done. Removed ${removed} symlink(s)."
|
|
fi
|
|
}
|
|
|
|
case "$ACTION" in
|
|
install) do_install ;;
|
|
uninstall) do_uninstall ;;
|
|
esac
|