#!/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/branch/stable/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) — ок." # --- 1b. git (required by `pi install git:...`) ------------------------------ if ! command -v git >/dev/null 2>&1; then err "git не найден. Установи git (macOS: xcode-select --install; Ubuntu: apt install git) и запусти скрипт снова." exit 1 fi info "git $(git --version | awk '{print $3}') — ок." # --- 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 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() { # 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 read -rs ans /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 [ -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 read -rs prov_val /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 < /feature <тикет> /review [фокус] /rfc <тема> /kit-config /kit-doctor /kit-help Проверка: /kit-doctor — health-check окружения Внутренний курс по Pi: ${INTERNAL_COURSE_URL} ──────────────────────────────────────────────────────── EOF