Files
pi-kit/install.sh
Aleksey Shakhmatov 3f987cecc5 feat(release): install channels (stable/beta) + RELEASING/CHANGELOG
- install.sh installs a moving channel ref: git:...@stable by default, PI_KIT_CHANNEL=beta|vX.Y.Z to override; pi update follows the channel
- cheat sheet shows channel + new commands (/kit-doctor, /kit-help)
- RELEASING.md: channel/tag release + rollback process; CHANGELOG.md (Keep a Changelog)
- supersedes earlier 'no ref = latest' for controlled rollout

Verified: install.sh syntax + channel resolution (stable default, beta/tag override).
2026-07-16 13:50:27 +03:00

337 lines
14 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.
#!/usr/bin/env bash
#
# pi-kit bootstrap — one-shot setup for new engineers. Idempotent: safe to re-run.
#
# Usage:
# curl -fsSL https://git.codelab.vc/ai/pi-kit/-/raw/main/install.sh | bash
# # or, from a checkout:
# ./install.sh
#
# Profile selection:
# Interactive by default (prompts via /dev/tty, so it works under curl | bash).
# Non-interactive: set PI_KIT_PROFILE=frontend|backend|qa|mobile before running.
#
set -euo pipefail
# ---------------------------------------------------------------------------
# The ONLY place the repository address appears. Employees install a CHANNEL
# (a moving git ref): `stable` (default, vetted releases) or `beta`. Maintainers
# fast-forward the channel branch to a tagged release. `pi update --extensions`
# then follows that channel. Override with PI_KIT_CHANNEL=beta (or a tag like v1.2.3).
# ---------------------------------------------------------------------------
PI_KIT_HOST="git.codelab.vc/ai/pi-kit"
PI_KIT_CHANNEL="${PI_KIT_CHANNEL:-stable}"
PI_KIT_REPO="git:${PI_KIT_HOST}@${PI_KIT_CHANNEL}"
MIN_NODE_MAJOR=20
INTERNAL_COURSE_URL="TODO: ссылка на внутренний курс по Pi"
SETTINGS="$HOME/.pi/agent/settings.json"
# Public skills installed for EVERYONE (repo:skill), via `npx skills add`.
COMMON_PUBLIC_SKILLS=(
"mattpocock/skills:grill-me"
"mattpocock/skills:grill-with-docs"
"mattpocock/skills:code-review"
"mattpocock/skills:diagnosing-bugs" # диагностика багов (в mattpocock/skills скилл называется так)
)
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; }
# --- 1. Node.js -------------------------------------------------------------
if ! command -v node >/dev/null 2>&1; then
err "Node.js не найден. Установи Node.js >= ${MIN_NODE_MAJOR} (https://nodejs.org) и запусти скрипт снова."
exit 1
fi
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
if [ "$NODE_MAJOR" -lt "$MIN_NODE_MAJOR" ]; then
err "Нужен Node.js >= ${MIN_NODE_MAJOR}, а установлен $(node -v). Обнови Node.js и запусти скрипт снова."
exit 1
fi
info "Node.js $(node -v) — ок."
# --- 2. Pi ------------------------------------------------------------------
if command -v pi >/dev/null 2>&1; then
info "Pi уже установлен ($(pi --version 2>/dev/null || echo 'версия неизвестна'))."
else
info "Ставлю Pi..."
curl -fsSL https://pi.dev/install.sh | sh
# Make pi available in the current shell if the installer added a new PATH dir.
if ! command -v pi >/dev/null 2>&1; then
export PATH="$HOME/.local/bin:$PATH"
fi
if ! command -v pi >/dev/null 2>&1; then
err "Pi установлен, но не найден в PATH. Открой новый терминал (или поправь PATH) и запусти скрипт снова."
exit 1
fi
fi
# --- 3. Install the corporate package --------------------------------------
info "Устанавливаю корпоративный пакет: ${PI_KIT_REPO}"
pi install "$PI_KIT_REPO"
info "Пакет установлен. Обновляться потом: pi update --extensions"
# --- 4. Resolve profile -----------------------------------------------------
# Precedence: PI_KIT_PROFILE env > interactive prompt > "all" (no gating).
PROFILE="${PI_KIT_PROFILE:-}"
case "$PROFILE" in
frontend | backend | qa | mobile) ;; # valid, keep
"") # ask if we have a terminal, even under `curl | bash` (read from /dev/tty)
if [ -r /dev/tty ]; then
{
printf 'Выбери профиль:\n'
printf ' 1) frontend — TS/UI\n'
printf ' 2) backend — Go/Rust/Python\n'
printf ' 3) qa — тестирование\n'
printf ' 4) mobile — Android/iOS\n'
printf 'Номер или имя [1-4]: '
} >/dev/tty
read -r ans </dev/tty || ans=""
case "$ans" in
1 | frontend) PROFILE=frontend ;;
2 | backend) PROFILE=backend ;;
3 | qa) PROFILE=qa ;;
4 | mobile) PROFILE=mobile ;;
*) PROFILE=all ;;
esac
else
PROFILE=all
fi
;;
*)
warn "Неизвестный PI_KIT_PROFILE='${PROFILE}', использую 'all' (без гейтинга скилов)."
PROFILE=all
;;
esac
info "Профиль: ${PROFILE}"
# Bundled language skills per profile. Common skills (jira-workflow/repo-map/
# docs-map) are always enabled. Prompts and guardrail extensions are never gated.
COMMON_BUNDLED="jira-workflow repo-map docs-map"
PROFILE_PUBLIC=()
case "$PROFILE" in
frontend)
LANG_BUNDLED="typescript-standards"
PROFILE_PUBLIC=("anthropics/skills:frontend-design")
;;
backend)
LANG_BUNDLED="go-standards rust-standards python-standards"
;;
qa) # TODO: уточнить стек тестировщиков; по умолчанию — Python и TS (автотесты)
LANG_BUNDLED="python-standards typescript-standards"
;;
mobile) # Android (Kotlin) + iOS (Swift)
LANG_BUNDLED="kotlin-standards swift-standards"
;;
*) # all: не гейтим, включаем все языковые скилы
LANG_BUNDLED="go-standards rust-standards python-standards typescript-standards kotlin-standards swift-standards"
;;
esac
# --- 5. Gate bundled skills via settings object-form filter -----------------
# Rewrites the pi-kit package entry in settings.json to {source, skills:[...]}
# so only the profile-relevant skills load. Extensions/prompts stay untouched
# (their keys are omitted => all load).
gate_bundled_skills() {
local allowed="$1"
if [ ! -f "$SETTINGS" ]; then
warn "settings.json не найден ($SETTINGS) — пропускаю гейтинг скилов."
return 0
fi
SETTINGS_FILE="$SETTINGS" ALLOWED="$allowed" SRC_MATCH="pi-kit" node <<'NODE'
const fs = require("fs");
const file = process.env.SETTINGS_FILE;
const allowed = process.env.ALLOWED.trim().split(/\s+/).filter(Boolean).map((s) => "skills/" + s);
const s = JSON.parse(fs.readFileSync(file, "utf8"));
if (!Array.isArray(s.packages)) process.exit(0);
let patched = false;
s.packages = s.packages.map((p) => {
const src = typeof p === "string" ? p : (p && p.source);
if (typeof src === "string" && src.includes(process.env.SRC_MATCH)) {
patched = true;
const base = (p && typeof p === "object") ? p : {};
return Object.assign({}, base, { source: src, skills: allowed });
}
return p;
});
fs.writeFileSync(file, JSON.stringify(s, null, 2) + "\n");
console.log(patched ? "gated bundled skills -> " + allowed.join(", ") : "pi-kit entry not found; skipped gating");
NODE
}
if [ "$PROFILE" = "all" ]; then
info "Профиль 'all' — оставляю все скилы пакета без гейтинга."
else
info "Гейчу скилы пакета под профиль '${PROFILE}'..."
gate_bundled_skills "${COMMON_BUNDLED} ${LANG_BUNDLED}" || warn "не удалось отфильтровать скилы (см. выше)."
fi
# --- 6. Install public skills ----------------------------------------------
# Uses the skills.sh CLI (`npx skills add`). Failures are non-fatal.
add_public_skill() { # <owner/repo> <skill-name>
local repo="$1" name="$2"
info "Публичный скилл: ${name} (${repo})"
# --global installs into ~/.agents/skills (the universal dir Pi reads),
# not the current project. --yes skips the interactive scope prompt.
if ! npx --yes skills add "https://github.com/${repo}" --skill "${name}" --global --yes >/dev/null 2>&1; then
warn "не удалось поставить '${name}'. Вручную: npx skills add https://github.com/${repo} --skill ${name} -g -y"
fi
}
info "Ставлю общие публичные скилы..."
for entry in "${COMMON_PUBLIC_SKILLS[@]}"; do
add_public_skill "${entry%%:*}" "${entry##*:}"
done
if [ "${#PROFILE_PUBLIC[@]}" -gt 0 ]; then
info "Ставлю публичные скилы профиля '${PROFILE}'..."
for entry in "${PROFILE_PUBLIC[@]}"; do
add_public_skill "${entry%%:*}" "${entry##*:}"
done
fi
# --- 7. Environment (URLs + secrets) ---------------------------------------
# Interactive setup: prompts (via /dev/tty so it works under curl | bash),
# stores chosen values in ~/.config/pi-kit/env.sh (chmod 600) and sources it
# from the shell rc. Secrets are read hidden and never echoed/logged.
# URLs have no package default yet (config/company.json is filled by maintainers);
# the current value is offered as the default. Skip with PI_KIT_SETUP_ENV=0.
CONFIG_DIR="$HOME/.config/pi-kit"
ENV_FILE="$CONFIG_DIR/env.sh"
RC_MARK=">>> pi-kit >>>"
# Which provider key (if any) is already present in the environment.
PROVIDER_KEYS=(ANTHROPIC_API_KEY OPENAI_API_KEY OPENROUTER_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GROQ_API_KEY)
found_key=""
for k in "${PROVIDER_KEYS[@]}"; do
if [ -n "${!k:-}" ]; then
found_key="$k"
break
fi
done
detect_rc() {
case "$(basename "${SHELL:-}")" in
zsh) echo "$HOME/.zshrc" ;;
bash) echo "$HOME/.bashrc" ;;
*) echo "$HOME/.profile" ;;
esac
}
# Escape a value for safe embedding inside single quotes.
esc_sq() { printf "%s" "$1" | sed "s/'/'\\\\''/g"; }
env_setup_interactive() {
info "Настройка окружения — значения сохранятся в ${ENV_FILE} (chmod 600)."
# Load previously stored values so re-runs keep them as defaults.
# shellcheck disable=SC1090
[ -f "$ENV_FILE" ] && . "$ENV_FILE" || true
local tracker="${TRACKER_URL:-}" jira="${JIRA_TOKEN:-}" gitlab="${GITLAB_TOKEN:-}" ans=""
# TRACKER_URL (not a secret): visible prompt, current value as default.
printf 'TRACKER_URL (адрес Jira)%s: ' "${tracker:+ [${tracker}]}" >/dev/tty
read -r ans </dev/tty || ans=""
[ -n "$ans" ] && tracker="$ans"
# JIRA_TOKEN (secret): hidden input; empty keeps the current value.
printf 'JIRA_TOKEN (скрытый ввод, Enter — оставить как есть): ' >/dev/tty
read -rs ans </dev/tty || ans=""
printf '\n' >/dev/tty
[ -n "$ans" ] && jira="$ans"
# GITLAB_TOKEN (secret, для create-mr.sh): hidden input.
printf 'GITLAB_TOKEN (скрытый ввод, Enter — пропустить): ' >/dev/tty
read -rs ans </dev/tty || ans=""
printf '\n' >/dev/tty
[ -n "$ans" ] && gitlab="$ans"
# LLM provider key — only ask if none is already set.
local prov_name="" prov_val=""
if [ -z "$found_key" ]; then
printf 'LLM-провайдер: 1) anthropic 2) openai 3) openrouter 4) пропустить [1-4]: ' >/dev/tty
read -r ans </dev/tty || ans=""
case "$ans" in
1 | anthropic) prov_name=ANTHROPIC_API_KEY ;;
2 | openai) prov_name=OPENAI_API_KEY ;;
3 | openrouter) prov_name=OPENROUTER_API_KEY ;;
*) prov_name="" ;;
esac
if [ -n "$prov_name" ]; then
printf '%s (скрытый ввод): ' "$prov_name" >/dev/tty
read -rs prov_val </dev/tty || prov_val=""
printf '\n' >/dev/tty
fi
fi
# Write env file with restrictive permissions (umask 077 => 600).
mkdir -p "$CONFIG_DIR"
(
umask 077
{
echo "# Managed by pi-kit install.sh — не коммить. chmod 600, plaintext."
[ -n "$tracker" ] && echo "export TRACKER_URL='$(esc_sq "$tracker")'"
[ -n "$jira" ] && echo "export JIRA_TOKEN='$(esc_sq "$jira")'"
[ -n "$gitlab" ] && echo "export GITLAB_TOKEN='$(esc_sq "$gitlab")'"
[ -n "$prov_name" ] && [ -n "$prov_val" ] && echo "export ${prov_name}='$(esc_sq "$prov_val")'"
} >"$ENV_FILE"
)
chmod 600 "$ENV_FILE"
info "Сохранено: ${ENV_FILE}"
# Ensure the shell rc sources the env file (managed block, idempotent).
local rc
rc="$(detect_rc)"
if [ -f "$rc" ] && grep -qF "$RC_MARK" "$rc" 2>/dev/null; then
info "Подключение уже есть в ${rc}."
else
{
echo ""
echo "# ${RC_MARK}"
echo '[ -f "$HOME/.config/pi-kit/env.sh" ] && . "$HOME/.config/pi-kit/env.sh"'
echo "# <<< pi-kit <<<"
} >>"$rc"
info "Добавил подключение в ${rc}. Применить сейчас: source ${rc}"
fi
warn "Токены лежат в ${ENV_FILE} (chmod 600, plaintext). Для строгих требований — секрет-менеджер."
}
if [ "${PI_KIT_SETUP_ENV:-1}" != 0 ] && [ -r /dev/tty ]; then
env_setup_interactive
else
# Non-interactive fallback: just check for a provider key and print guidance.
if [ -n "$found_key" ]; then
info "Найден ключ LLM-провайдера: ${found_key} — ок."
else
warn "Не найден ключ LLM-провайдера (установка неинтерактивная — пропускаю настройку)."
fi
cat <<'EOF'
Задай нужные переменные вручную (например, в ~/.config/pi-kit/env.sh):
export ANTHROPIC_API_KEY="..." # или OPENAI_API_KEY / OPENROUTER_API_KEY
export TRACKER_URL="..." # адрес Jira (Server/DC)
export JIRA_TOKEN="..." # Jira personal access token
export GITLAB_TOKEN="..." # для создания MR (scripts/create-mr.sh)
Провайдер/модель — флагами --provider/--model или в настройках Pi (пакет не навязывает).
EOF
fi
# --- 8. Cheat sheet ---------------------------------------------------------
cat <<EOF
────────────────────────────────────────────────────────
Готово! Профиль: ${PROFILE} Канал: ${PI_KIT_CHANNEL}
Запуск: pi
Обновление: pi update --extensions (следует за каналом ${PI_KIT_CHANNEL})
Окружение: ${ENV_FILE} (TRACKER_URL/JIRA_TOKEN/GITLAB_TOKEN/ключ провайдера)
Сменить профиль: PI_KIT_PROFILE=backend ./install.sh (или перезапусти и выбери)
Сменить канал: PI_KIT_CHANNEL=beta ./install.sh
Команды: /bugfix <тикет> /feature <тикет> /review [фокус] /rfc <тема>
/kit-config /kit-doctor /kit-help
Проверка: /kit-doctor — health-check окружения
Внутренний курс по Pi: ${INTERNAL_COURSE_URL}
────────────────────────────────────────────────────────
EOF