#!/usr/bin/env bash # PiCode public installer — no monorepo required. # curl -fsSL https://pigpt.ir/install-picode.sh | bash set -euo pipefail CLI_NAME="picode" DISPLAY_NAME="PiCode" API_BASE="${PICODE_API_URL:-https://pigpt.ir}" DOWNLOAD_BASE="${PICODE_DOWNLOAD_BASE:-https://pigpt.ir/downloads/picode}" # Prefer exact wheel; fall back to sdist / directory listing scrape. WHEEL_NAME="${PICODE_WHEEL:-picode-0.2.1-py3-none-any.whl}" SDIST_NAME="${PICODE_SDIST:-picode-0.2.1.tar.gz}" BIN_DIR="${HOME}/.local/bin" VENV_DIR="${PICODE_VENV:-${HOME}/.picode-venv}" CFG_DIR="${PICODE_HOME:-${HOME}/.picode}" die() { echo "error: $*" >&2; exit 1; } info() { echo "$*"; } try_install_python_hint() { # Soft helpers only — full auto-install is the Windows script's job. if command -v apt-get >/dev/null 2>&1 && [[ "${EUID:-$(id -u)}" -eq 0 ]]; then info "Attempting apt install python3 python3-venv python3-pip…" apt-get update -qq && apt-get install -y -qq python3 python3-venv python3-pip && return 0 fi if command -v brew >/dev/null 2>&1; then info "Attempting brew install python@3.12…" brew install python@3.12 && return 0 fi return 1 } require_python() { local py="" if command -v python3 >/dev/null 2>&1; then py="python3" elif command -v python >/dev/null 2>&1; then py="python" else try_install_python_hint || true if command -v python3 >/dev/null 2>&1; then py="python3" elif command -v python >/dev/null 2>&1; then py="python" else die "Python 3.10+ required. Install: https://www.python.org/downloads/ (Windows: irm https://pigpt.ir/install-picode.ps1 | iex — auto-installs Python)." fi fi local ver ver="$("$py" -c 'import sys; print("%d.%d"%sys.version_info[:2])' 2>/dev/null || true)" local major minor major="${ver%%.*}" minor="${ver#*.}" if [[ -z "$ver" || "$major" -lt 3 || ( "$major" -eq 3 && "$minor" -lt 10 ) ]]; then die "Python >= 3.10 required (found ${ver:-unknown})." fi echo "$py" } download() { local url="$1" out="$2" if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o "$out" elif command -v wget >/dev/null 2>&1; then wget -qO "$out" "$url" else die "curl or wget required" fi } http_ok() { local url="$1" if command -v curl >/dev/null 2>&1; then curl -fsSIL "$url" >/dev/null 2>&1 else wget -q --spider "$url" 2>/dev/null fi } create_venv() { local dir="$1" py="$2" if "$py" -m venv "${dir}" 2>/dev/null; then return 0 fi rm -rf "${dir}" "$py" -m venv --without-pip "${dir}" || die "python3-venv missing (sudo apt install -y python3-venv)" local getpip getpip="$(mktemp)" download https://bootstrap.pypa.io/get-pip.py "${getpip}" "${dir}/bin/python" "${getpip}" rm -f "${getpip}" } PY="$(require_python)" info "PiCode installer · Python $($PY -c 'import sys; print(".".join(map(str,sys.version_info[:3])))')" info "API default: ${API_BASE}" TMP="$(mktemp -d)" trap 'rm -rf "${TMP}"' EXIT PKG_URL="" if http_ok "${DOWNLOAD_BASE}/${WHEEL_NAME}"; then PKG_URL="${DOWNLOAD_BASE}/${WHEEL_NAME}" elif http_ok "${DOWNLOAD_BASE}/${SDIST_NAME}"; then PKG_URL="${DOWNLOAD_BASE}/${SDIST_NAME}" else die "Could not find PiCode package at ${DOWNLOAD_BASE}/ (tried ${WHEEL_NAME}, ${SDIST_NAME})" fi PKG_FILE="${TMP}/$(basename "${PKG_URL}")" info "Downloading ${PKG_URL}" download "${PKG_URL}" "${PKG_FILE}" # Sanity: must not be SPA HTML if head -c 15 "${PKG_FILE}" | grep -qi '/dev/null 2>&1; then rm -rf "${VENV_DIR}" create_venv "${VENV_DIR}" "${PY}" fi "${VENV_DIR}/bin/python" -m pip -q install -U pip setuptools wheel "${VENV_DIR}/bin/pip" -q install --force-reinstall "${PKG_FILE}" mkdir -p "${BIN_DIR}" ln -sfn "${VENV_DIR}/bin/${CLI_NAME}" "${BIN_DIR}/${CLI_NAME}" PATH_LINE='export PATH="$HOME/.local/bin:$PATH"' path_note="already configured" for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do if [[ -f "$rc" ]] || [[ "$rc" == "${HOME}/.bashrc" ]]; then touch "$rc" if ! grep -Fqs '.local/bin' "$rc"; then { echo "" echo "# ${DISPLAY_NAME} CLI" echo "${PATH_LINE}" } >> "$rc" path_note="appended to $(basename "$rc")" fi fi done export PATH="${BIN_DIR}:${PATH}" # Seed production API default if no config yet mkdir -p "${CFG_DIR}" CFG_FILE="${CFG_DIR}/config.json" if [[ ! -f "${CFG_FILE}" ]]; then printf '{\n "api_url": "%s"\n}\n' "${API_BASE}" > "${CFG_FILE}" elif ! grep -Fqs '"api_url"' "${CFG_FILE}"; then CFG_FILE="${CFG_FILE}" API_BASE="${API_BASE}" "${VENV_DIR}/bin/python" - <<'PY' import json, os from pathlib import Path p = Path(os.environ["CFG_FILE"]) data = {} try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: data = {} if not data.get("api_url"): data["api_url"] = os.environ["API_BASE"] p.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") PY fi echo "" echo "success: ${DISPLAY_NAME} (${CLI_NAME}) installed" echo "PATH: ${BIN_DIR} (${path_note})" echo "this shell: export PATH=\"\$HOME/.local/bin:\$PATH\"" "${BIN_DIR}/${CLI_NAME}" --version echo "" echo "Next steps:" echo " 1) ${CLI_NAME} login" echo " 2) ${CLI_NAME} # interactive REPL" echo " 3) ${CLI_NAME} chat \"hello\" # one-shot (needs token)" echo "" echo "Docs: ${API_BASE}/app/cli" echo "Override API: export PICODE_API_URL=https://your-host"