KeiSeiKit-1.0/install/lib-pathway.sh
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

189 lines
6.1 KiB
Bash

# shellcheck shell=bash
# lib-pathway.sh — PATH wiring for KeiSeiKit substrate.
#
# Adds two dirs to the user's shell PATH (idempotent block, marker-fenced):
# 1. ~/.cargo/bin/ — canonical Rust binary install location (v0.18+)
# 2. ~/.claude/bin/ — entry-point scripts (keisei) + symlinks
#
# Architecture (v0.18+): substrate Rust binaries install to ~/.cargo/bin/
# (universal Rust idiom, present in PATH for any rustup user). The pre-v0.18
# dual-location pattern (~/.claude/agents/.../target/release in PATH) is
# DEPRECATED — see commit 1d92f7e in KeiSeiKit-public for the migration.
# Existing users get the old block stripped and the new one written on
# next install.sh / pathway_install run.
#
# Public API:
# pathway_detect_shell -> bash | zsh | fish | unknown
# pathway_install -> dispatch + write block (default flow)
# pathway_uninstall -> remove block from every supported rc
#
# Requires: say / warn from lib-log.sh.
# Reads globals: $HOME_DIR, $AGENTS_DIR.
# Marker fences. Short, unmistakable, never collide with user content.
_PATHWAY_BEGIN='# >>> kei-substrate <<<'
_PATHWAY_END='# <<< kei-substrate >>>'
# Detect the user's interactive shell. Falls through to $SHELL basename so
# non-login installs (cron, CI) still get a sensible default.
pathway_detect_shell() {
local sh
sh="$(basename "${SHELL:-/bin/bash}" 2>/dev/null)"
case "$sh" in
bash|zsh|fish) printf '%s\n' "$sh" ;;
*) printf 'unknown\n' ;;
esac
}
# Render the bash/zsh PATH-export block. POSIX-style $PATH prepend, guarded
# to skip a duplicate prepend if already present in the current shell.
# Two paths wired:
# 1. ~/.cargo/bin/ — Rust primitives (kei-fork / kei-ledger / ...)
# 2. ~/.claude/bin/ — entry-point scripts (keisei) + symlinks
_render_posix_block() {
local cargo_dir="$HOME_DIR/.cargo/bin"
local bin_dir="$HOME_DIR/.claude/bin"
cat <<EOF
$_PATHWAY_BEGIN
# Added by KeiSeiKit install.sh — Rust primitives + claude/bin on PATH.
# Remove this block (with markers) to opt out.
for _kei_path in "$cargo_dir" "$bin_dir"; do
if [ -d "\$_kei_path" ]; then
case ":\$PATH:" in
*":\$_kei_path:"*) ;;
*) export PATH="\$_kei_path:\$PATH" ;;
esac
fi
done
unset _kei_path
$_PATHWAY_END
EOF
}
# Render the fish-shell variant (fish_add_path is idempotent natively).
# Same two paths as posix variant.
_render_fish_block() {
local cargo_dir="$HOME_DIR/.cargo/bin"
local bin_dir="$HOME_DIR/.claude/bin"
cat <<EOF
$_PATHWAY_BEGIN
# Added by KeiSeiKit install.sh — Rust primitives + claude/bin on PATH.
# Remove this block (with markers) to opt out.
for _kei_path in "$cargo_dir" "$bin_dir"
if test -d "\$_kei_path"
fish_add_path -p "\$_kei_path"
end
end
$_PATHWAY_END
EOF
}
# Refuse to operate on a symlink. Replacing a symlink with `mv tmp link`
# destroys the link and writes a regular file in its place — catastrophic
# for users whose rc files are managed by a dotfile repo. MISS-10 fix:
# bail with a clear message asking the user to add the block manually
# inside the symlink target (i.e. their dotfile repo).
_refuse_if_symlink() {
local file="$1"
if [ -L "$file" ]; then
local target
target="$(readlink "$file" 2>/dev/null || printf '?\n')"
warn "$file is a symlink (-> $target); refusing to replace."
warn " Add the kei-substrate block manually inside your dotfile target,"
warn " or unlink \"$file\" and re-run install."
return 1
fi
return 0
}
# Strip an existing kei-substrate block from a file. Idempotent: zero blocks
# removed is fine. Uses awk for portability (no GNU-sed inplace).
_strip_block_from_file() {
local file="$1"
[ -f "$file" ] || return 0
_refuse_if_symlink "$file" || return 1
local tmp
tmp="$(mktemp "$file.XXXXXX")"
if awk -v b="$_PATHWAY_BEGIN" -v e="$_PATHWAY_END" '
$0 == b { skip=1; next }
$0 == e { skip=0; next }
!skip { print }
' "$file" > "$tmp"; then
mv "$tmp" "$file"
else
rm -f "$tmp"
return 1
fi
}
# Append a block to an rc file. Strips any prior copy first → idempotent.
_install_block_into_file() {
local file="$1" block="$2"
mkdir -p "$(dirname "$file")"
[ -L "$file" ] && { _refuse_if_symlink "$file"; return 1; }
[ -f "$file" ] || : > "$file"
_strip_block_from_file "$file" || return 1
# ensure trailing newline before append
if [ -s "$file" ] && [ "$(tail -c 1 "$file" | xxd -p 2>/dev/null)" != "0a" ]; then
printf '\n' >> "$file"
fi
printf '%s\n' "$block" >> "$file"
}
pathway_install_bashrc() {
local rc="$HOME_DIR/.bashrc"
local block
block="$(_render_posix_block)"
_install_block_into_file "$rc" "$block" || return 1
say " wired PATH in $rc"
}
pathway_install_zshrc() {
local rc="$HOME_DIR/.zshrc"
local block
block="$(_render_posix_block)"
_install_block_into_file "$rc" "$block" || return 1
say " wired PATH in $rc"
}
pathway_install_fish_config() {
local rc="$HOME_DIR/.config/fish/config.fish"
local block
block="$(_render_fish_block)"
_install_block_into_file "$rc" "$block" || return 1
say " wired PATH in $rc"
}
# Public dispatcher. Honors $WITH_PATHWAY=1 (forced on) / $NO_PATHWAY=1
# (forced off, no-op). Default: install when interactive TTY OR
# $WITH_PATHWAY=1 was passed.
pathway_install() {
if [ "${NO_PATHWAY:-0}" = "1" ]; then
say "PATH wiring skipped (--no-pathway)"
return 0
fi
local sh
sh="$(pathway_detect_shell)"
say "wiring PATH (~/.cargo/bin + ~/.claude/bin) for shell=$sh"
case "$sh" in
bash) pathway_install_bashrc ;;
zsh) pathway_install_zshrc ;;
fish) pathway_install_fish_config ;;
*)
warn "unknown shell ($sh); add to your rc manually:"
warn " export PATH=\"\$HOME/.cargo/bin:\$HOME/.claude/bin:\$PATH\""
return 0
;;
esac
say " open a new terminal or run: source ~/.${sh}rc"
}
# Remove the block from every supported rc, regardless of detected shell.
pathway_uninstall() {
local rc
for rc in "$HOME_DIR/.bashrc" "$HOME_DIR/.zshrc" "$HOME_DIR/.config/fish/config.fish"; do
[ -f "$rc" ] || continue
_strip_block_from_file "$rc"
say " removed PATH block from $rc"
done
}