Files
my-pi/install.sh
T
云服务部-叶林立 73f3117a7e feat(pi-hermes-memory): 接入持久记忆扩展并移除 Hippo
用仓库内 pi-hermes-memory 替换 hippo-memory-pi,清理 Hippo 在组合包中的
全部注入点。

移除 Hippo:
- 删除 hippo-memory-pi/ 与 config/hippo-memory-version。
- extensions/plugin-init.ts 只保留 CodeGraph 初始化目标与预检。
- install.sh / update.sh / uninstall.sh 移除 Hippo CLI 的安装、升级、卸载
  与版本校验逻辑;uninstall.sh 同时删除仅服务于该提示的死代码 ask_yes_no。
- 删除 tests/hippo-extension.test.ts 与 tests/hippo-scripts.test.ts。
- tests/extension-load.test.ts 反转“不得加载 Hermes”的断言为必须加载,
  并断言其排在 pi-compaction 之前。

接入 pi-hermes-memory:
- package.json 增加 file:./pi-hermes-memory 依赖、files 条目与加载入口;
  加载顺序位于 pi-compaction 之前,否则 flushOnCompact 会被 pi-compaction
  的 { cancel: true } 静默跳过。
- 六个记忆工具在 Tool Search 中常驻:memory_search 与 session_search 连同
  context_tree_query 归入 memory-recall,memory_add / memory_replace /
  memory_remove / skill_manage 归入 memory-management。常驻是必需的——注入
  的 memory policy 会在普通工作中指示模型调用它们,schema 不可见会让模型
  静默放弃而不是去 tool_search,同时常驻也保持请求前缀缓存稳定。
- bundleDefaultsVersion 由 2 升到 3;migrate-tool-search-settings.mjs 新增
  上一版快照到当前版本的迁移路径,仍只迁移完整匹配的默认快照并保留自定义
  配置。
- config/pi-permission-policy.jsonc 为六个工具设置 allow:它们只写扩展自己的
  agent 目录数据、不触碰工作区,且 memory_search 每轮都可能被调用,落入
  默认 ask 会产生每轮 AutoReview 往返。

依赖:
- pi-hermes-memory 的 devDependencies 由上游 ^0.80.2 等范围改为与根包一致的
  精确版本。上游范围与根包 0.84.2 不兼容,会让 npm 在本目录嵌套安装第二套
  Pi runtime(实测 229 MB),违反“不得在组合包内再安装一套 Pi runtime”的
  纪律。
- 根 allowScripts 保持只有 better-sqlite3@12.11.1。hermes 的
  better-sqlite3@13 运行时 dlopen 的是包内 N-API 预编译二进制
  prebuilds/<platform>.node,npm 因 binding.gyp 推断出的 node-gyp rebuild
  无需执行;实测删除 build/ 目录后仍正常工作。

文档:AGENTS.md 与 README.md 改写记忆章节,新增 pi-hermes-memory 开发约定,
替换文末的 Hippo 项目记忆块。

验证:根 12 个测试文件、pi-tool-search 的 typecheck/test/build、
pi-permission-system 的 typecheck/2569 测试、pi-hermes-memory 自带的 59 个
测试文件全部通过;隔离 Pi 环境联合加载无错误并生成 bundleDefaultsVersion 3;
npm install 幂等;迁移 helper 对 v2 快照迁移、对自定义配置保留。
2026-10-14 00:56:41 -07:00

446 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
set -u
SCRIPT_DIR=$(CDPATH=; cd "$(dirname "$0")" && pwd) || exit 1
MANAGED_CONFIG_HELPERS=$SCRIPT_DIR/scripts/managed-config.sh
if [ ! -f "$MANAGED_CONFIG_HELPERS" ]; then
printf '错误: 找不到受管配置辅助脚本:%s\n' "$MANAGED_CONFIG_HELPERS" >&2
exit 1
fi
# shellcheck source=scripts/managed-config.sh
. "$MANAGED_CONFIG_HELPERS"
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh
CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh
KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh
OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
POWERLEVEL10K_REPOSITORY=https://github.com/romkatv/powerlevel10k.git
ZSH_AUTOSUGGESTIONS_REPOSITORY=https://github.com/zsh-users/zsh-autosuggestions.git
ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY=https://github.com/zsh-users/zsh-syntax-highlighting.git
install_failed=0
say() {
printf '%s\n' "$*"
}
warn() {
printf '警告: %s\n' "$*" >&2
}
ask_yes_no() {
prompt=$1
printf '%s [y/N] ' "$prompt"
if ! IFS= read -r answer; then
answer=n
fi
case $answer in
y|Y|yes|YES|Yes) return 0 ;;
*) return 1 ;;
esac
}
ask_to_install() {
ask_yes_no "未检测到 $1。是否现在安装?"
}
ask_to_enable() {
ask_yes_no "尚未启用 $1。是否现在安装所需文件并启用?"
}
require_brew() {
if command -v brew >/dev/null 2>&1; then
return 0
fi
warn "自动安装该依赖需要 Homebrew,但当前 PATH 中没有 brew。"
warn "请先安装 Homebrew(https://brew.sh/),然后重新运行本脚本。"
return 1
}
record_install_failure() {
warn "$1 安装失败;其余依赖仍会继续检查。"
install_failed=1
}
java_major_version() {
java_version=$(java -version 2>&1 | awk -F '"' 'NR == 1 { print $2 }')
case $java_version in
1.*) java_version=${java_version#1.} ;;
esac
java_major=${java_version%%.*}
case $java_major in
''|*[!0-9]*) return 1 ;;
*) printf '%s\n' "$java_major" ;;
esac
}
install_codegraph() {
if ! command -v curl >/dev/null 2>&1; then
warn "安装 CodeGraph 需要 curl。"
return 1
fi
curl -fsSL "$CODEGRAPH_INSTALL_URL" | sh || return 1
# 官方安装器默认使用 ~/.local/bin;让本次检查立即看到新命令。
if [ -d "$HOME/.local/bin" ]; then
PATH="$HOME/.local/bin:$PATH"
export PATH
fi
command -v codegraph >/dev/null 2>&1
}
install_kotlin_lsp() {
require_brew || return 1
brew install JetBrains/utils/kotlin-lsp || return 1
command -v kotlin-lsp >/dev/null 2>&1
}
install_java_21() {
require_brew || return 1
brew install openjdk@21 || return 1
java_home=$(brew --prefix openjdk@21 2>/dev/null) || return 1
PATH="$java_home/bin:$PATH"
export PATH
installed_java_major=$(java_major_version || true)
if [ -z "$installed_java_major" ] || [ "$installed_java_major" -lt 21 ]; then
return 1
fi
say "已在本次安装流程中使用 $java_home/bin。若新终端仍找不到 Java,请把该目录加入 PATH。"
}
install_jdtls() {
require_brew || return 1
brew install jdtls || return 1
command -v jdtls >/dev/null 2>&1
}
detect_kitty() {
kitty_bin=
if command -v kitty >/dev/null 2>&1; then
kitty_bin=$(command -v kitty)
return 0
fi
for candidate in /Applications/kitty.app/Contents/MacOS/kitty "$HOME/.local/kitty.app/bin/kitty"; do
if [ -x "$candidate" ]; then
kitty_bin=$candidate
return 0
fi
done
return 1
}
install_kitty() {
case $(uname -s) in
Darwin)
require_brew || return 1
brew install --cask kitty || return 1
;;
*)
if ! command -v curl >/dev/null 2>&1; then
warn "安装 Kitty 需要 curl。"
return 1
fi
curl -L "$KITTY_INSTALL_URL" | sh /dev/stdin || return 1
;;
esac
detect_kitty
}
resolve_kitty_config_dir() {
kitty_config_dir=${KITTY_CONFIG_DIRECTORY:-${XDG_CONFIG_HOME:-$HOME/.config}/kitty}
}
kitty_solarized_dark_configured() {
resolve_kitty_config_dir
[ -f "$kitty_config_dir/current-theme.conf" ] || return 1
[ -f "$kitty_config_dir/kitty.conf" ] || return 1
grep -F "Solarized Dark" "$kitty_config_dir/current-theme.conf" >/dev/null 2>&1 || return 1
grep -E '^[[:space:]]*include[[:space:]]+([^#[:space:]]*/)?current-theme\.conf([[:space:]]*(#.*)?)?$' \
"$kitty_config_dir/kitty.conf" >/dev/null 2>&1
}
configure_kitty_solarized_dark() {
"$kitty_bin" +kitten themes --reload-in=none "Solarized Dark"
}
resolve_zsh_paths() {
oh_my_zsh_dir=${ZSH:-$HOME/.oh-my-zsh}
zsh_custom_dir=${ZSH_CUSTOM:-$oh_my_zsh_dir/custom}
zshrc_file=${ZDOTDIR:-$HOME}/.zshrc
my_pi_config_dir=${XDG_CONFIG_HOME:-$HOME/.config}/my-pi
p10k_managed_config_file=$my_pi_config_dir/p10k.zsh
}
oh_my_zsh_installed() {
resolve_zsh_paths
[ -f "$oh_my_zsh_dir/oh-my-zsh.sh" ]
}
install_oh_my_zsh() {
if ! command -v curl >/dev/null 2>&1; then
warn "安装 Oh My Zsh 需要 curl。"
return 1
fi
if ! command -v git >/dev/null 2>&1; then
warn "安装 Oh My Zsh 需要 git。"
return 1
fi
resolve_zsh_paths
oh_my_zsh_installer=$(curl -fsSL "$OH_MY_ZSH_INSTALL_URL") || return 1
RUNZSH=no CHSH=no KEEP_ZSHRC=yes ZSH="$oh_my_zsh_dir" \
sh -c "$oh_my_zsh_installer" "" --unattended --keep-zshrc || return 1
oh_my_zsh_installed
}
prepare_zshrc() {
resolve_zsh_paths
zshrc_dir=${zshrc_file%/*}
[ "$zshrc_dir" = "$zshrc_file" ] && zshrc_dir=.
mkdir -p "$zshrc_dir" || return 1
[ -f "$zshrc_file" ] || : > "$zshrc_file" || return 1
}
replace_zsh_managed_block() {
managed_id=$1
managed_placement=$2
managed_anchor=$3
shift 3
managed_content=$(mktemp "${TMPDIR:-/tmp}/my-pi-zsh-block.XXXXXX") || return 1
printf '%s\n' "$@" > "$managed_content"
my_pi_replace_managed_block "$zshrc_file" "$managed_id" "$managed_content" \
"$managed_placement" "$managed_anchor"
managed_status=$?
rm -f "$managed_content"
return "$managed_status"
}
zshrc_has_oh_my_zsh_loader() {
[ -f "$zshrc_file" ] || return 1
grep -E '^[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh' "$zshrc_file" >/dev/null 2>&1
}
ensure_oh_my_zsh_loader() {
prepare_zshrc || return 1
if zshrc_has_oh_my_zsh_loader; then
return 0
fi
replace_zsh_managed_block "oh-my-zsh-loader" "append" "" \
"export ZSH=\"$oh_my_zsh_dir\"" \
"source \"\$ZSH/oh-my-zsh.sh\""
}
set_powerlevel10k_theme() {
prepare_zshrc || return 1
replace_zsh_managed_block "powerlevel10k-theme" "before" \
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
'ZSH_THEME="powerlevel10k/powerlevel10k"'
}
p10k_config_sourced() {
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-config"
}
ensure_p10k_config_source() {
prepare_zshrc || return 1
replace_zsh_managed_block "powerlevel10k-config" "append" "" \
"[[ ! -f \"$p10k_managed_config_file\" ]] || source \"$p10k_managed_config_file\""
}
configure_powerlevel10k() {
resolve_zsh_paths
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
if [ ! -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ]; then
if ! command -v git >/dev/null 2>&1; then
warn "安装 Powerlevel10k 需要 git。"
return 1
fi
mkdir -p "$zsh_custom_dir/themes" || return 1
git clone --depth=1 "$POWERLEVEL10K_REPOSITORY" "$powerlevel10k_dir" || return 1
fi
my_pi_sync_managed_file "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" || return 1
set_powerlevel10k_theme || return 1
ensure_oh_my_zsh_loader || return 1
ensure_p10k_config_source || return 1
say "已应用仓库内置的 Powerlevel10k Rainbow/ASCII 受管配置。"
}
zsh_plugin_enabled() {
plugin=$1
[ -f "$zshrc_file" ] || return 1
awk -v target="$plugin" '
{
line = $0
sub(/[[:space:]]*#.*/, "", line)
if (line ~ /^[[:space:]]*plugins[[:space:]]*[+]?=/) active = 1
if (active) {
gsub(/[()=+]/, " ", line)
count = split(line, fields, /[[:space:]]+/)
for (i = 1; i <= count; i++) if (fields[i] == target) found = 1
if ($0 ~ /\)/) active = 0
}
}
END { exit(found ? 0 : 1) }
' "$zshrc_file"
}
add_zsh_plugin() {
plugin=$1
prepare_zshrc || return 1
ensure_oh_my_zsh_loader || return 1
replace_zsh_managed_block "zsh-plugin-$plugin" "before" \
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
"plugins+=($plugin)"
}
install_and_enable_zsh_plugin() {
plugin=$1
repository=${2:-}
if [ -n "$repository" ] && [ ! -d "$zsh_custom_dir/plugins/$plugin" ]; then
if ! command -v git >/dev/null 2>&1; then
warn "安装 $plugin 需要 git。"
return 1
fi
mkdir -p "$zsh_custom_dir/plugins" || return 1
git clone --depth=1 "$repository" "$zsh_custom_dir/plugins/$plugin" || return 1
fi
add_zsh_plugin "$plugin"
}
check_zsh_plugin() {
plugin=$1
description=$2
repository=${3:-}
if zsh_plugin_enabled "$plugin"; then
say "✓ $plugin 已启用"
elif ask_to_enable "$description"; then
install_and_enable_zsh_plugin "$plugin" "$repository" || record_install_failure "$plugin"
else
say "- 已跳过 ${plugin}。"
fi
}
if ! command -v pi >/dev/null 2>&1; then
warn "找不到 pi,无法安装组合包。请先安装 Pi 并确保 pi 位于 PATH。"
exit 127
fi
say "正在安装 Pi 组合包:$PACKAGE_SOURCE"
if ! pi install "$PACKAGE_SOURCE"; then
warn "pi install 执行失败,已停止后续依赖检查。"
exit 1
fi
say "Pi 组合包安装完成,开始检查终端环境和机器级 CLI 依赖。"
kitty_ok=0
if detect_kitty; then
kitty_ok=1
say "✓ Kitty 已安装(${kitty_bin})"
elif ask_to_install "Kitty 终端"; then
if install_kitty; then
kitty_ok=1
say "✓ Kitty 安装完成"
else
record_install_failure "Kitty"
fi
else
say "- 已跳过 Kitty;不会检查 Solarized Dark 主题。"
fi
if [ "$kitty_ok" -eq 1 ]; then
if kitty_solarized_dark_configured; then
say "✓ Kitty 已配置 Solarized Dark"
elif ask_yes_no "Kitty 尚未配置 Solarized Dark。是否现在配置?"; then
configure_kitty_solarized_dark || record_install_failure "Kitty Solarized Dark 主题"
else
say "- 已跳过 Kitty Solarized Dark 主题。"
fi
fi
oh_my_zsh_ok=0
if oh_my_zsh_installed; then
oh_my_zsh_ok=1
say "✓ Oh My Zsh 已安装(${oh_my_zsh_dir})"
elif ask_to_install "Oh My Zsh"; then
if install_oh_my_zsh; then
oh_my_zsh_ok=1
say "✓ Oh My Zsh 安装完成"
else
record_install_failure "Oh My Zsh"
fi
else
say "- 已跳过 Oh My Zsh;不会检查 Powerlevel10k 和 Zsh 插件。"
fi
if [ "$oh_my_zsh_ok" -eq 1 ]; then
resolve_zsh_paths
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
if [ -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ] && \
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-theme" && \
[ -f "$p10k_managed_config_file" ] && \
cmp -s "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" && p10k_config_sourced; then
say "✓ Powerlevel10k 已安装并配置"
elif ask_yes_no "Powerlevel10k 尚未完整配置。是否安装并应用仓库内置的 Rainbow/ASCII 配置?"; then
configure_powerlevel10k || record_install_failure "Powerlevel10k"
else
say "- 已跳过 Powerlevel10k。"
fi
check_zsh_plugin "git" "git 插件" ""
check_zsh_plugin "zsh-autosuggestions" "zsh-autosuggestions(命令自动建议)" \
"$ZSH_AUTOSUGGESTIONS_REPOSITORY"
check_zsh_plugin "zsh-syntax-highlighting" "zsh-syntax-highlighting(语法高亮)" \
"$ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY"
fi
if command -v codegraph >/dev/null 2>&1; then
say "✓ codegraph 已安装"
elif ask_to_install "CodeGraph(提供 codegraph_explore)"; then
install_codegraph || record_install_failure "CodeGraph"
else
say "- 已跳过 CodeGraph;codegraph_explore 将不可用。"
fi
if command -v kotlin-lsp >/dev/null 2>&1; then
say "✓ kotlin-lsp 已安装"
elif ask_to_install "kotlin-lsp(Kotlin LSP)"; then
install_kotlin_lsp || record_install_failure "kotlin-lsp"
else
say "- 已跳过 kotlin-lsp;Kotlin LSP 功能将不可用。"
fi
java_ok=0
if command -v java >/dev/null 2>&1; then
java_major=$(java_major_version || true)
if [ -n "$java_major" ] && [ "$java_major" -ge 21 ]; then
java_ok=1
say "✓ Java $java_major 已安装(满足 Java 21+)"
fi
fi
if [ "$java_ok" -eq 0 ]; then
if ask_to_install "Java 21+(JDT LS 运行时)"; then
if install_java_21; then
java_ok=1
else
record_install_failure "Java 21+"
fi
else
say "- 已跳过 Java 21+;JDT LS 可能无法启动。"
fi
fi
if command -v jdtls >/dev/null 2>&1; then
say "✓ jdtls 已安装"
elif ask_to_install "jdtls(Java LSP)"; then
install_jdtls || record_install_failure "jdtls"
else
say "- 已跳过 jdtls;Java LSP 功能将不可用。"
fi
say "终端环境与依赖检查完成。RTK 命令改写默认关闭,因此未检查可选的 rtk CLI。"
exit "$install_failed"