- 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).
337 lines
14 KiB
Bash
Executable File
337 lines
14 KiB
Bash
Executable File
#!/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
|