#!/bin/sh # KinetixAI command line (kx) installer for macOS and Linux. # # curl -fsSL https://kinetixai.ai/install.sh | sh # # What it does, and nothing else: # 1. finds Python 3.10+ (it never installs Python and never uses sudo) # 2. downloads the kx wheel published with the site, checks its SHA-256, # and keeps it in ~/.kinetixai/wheels (pipx needs it there later for # `pipx reinstall` / `pipx upgrade`) # 3. installs it with pipx if you have it, otherwise into its own virtual # environment at ~/.kinetixai/venv, linked as ~/.local/bin/kx # # Options (environment variables): # KX_EXTRAS optional features, default "rag,mcp" ("" for the bare CLI; # "all" adds keyring too) # KX_SOURCE site (default) | pypi | git. PyPI is used only when you ask # for it: kx is not published there yet. # KX_WHEEL path to a local kx wheel to install instead of downloading # KX_HOME where kx keeps its wheels and fallback venv (default ~/.kinetixai) # KX_NO_PIPX set to 1 to use the venv even when pipx is installed # # Everything runs inside main(), called on the last line, so a download cut # off part-way runs nothing. Read it first if you like: # https://kinetixai.ai/install.sh set -eu say() { printf '%s\n' "kx-install: $*"; } fail() { printf '%s\n' "kx-install: error: $*" >&2; exit 1; } # The host part of a URL, exactly: no userinfo tricks (http://localhost@evil), # no prefix tricks (http://localhost.evil.example). url_host() { rest="${1#*://}" rest="${rest%%/*}"; rest="${rest%%\?*}"; rest="${rest%%#*}" case "$rest" in *@*) printf '%s' "!userinfo"; return ;; esac case "$rest" in \[*\]*) host="${rest#\[}"; host="${host%%\]*}" ;; *) host="${rest%%:*}" ;; esac printf '%s' "$host" | tr 'A-Z' 'a-z' } check_base_url() { case "$1" in https://*) host="$(url_host "$1")" ;; http://*) host="$(url_host "$1")" case "$host" in localhost|127.0.0.1|::1) ;; *) fail "KX_BASE_URL must be https (plain http only for localhost testing)." ;; esac ;; *) fail "KX_BASE_URL must be an https URL." ;; esac case "$host" in ""|"!userinfo") fail "KX_BASE_URL has no usable host." ;; esac } main() { BASE_URL="${KX_BASE_URL:-https://kinetixai.ai}" BASE_URL="${BASE_URL%/}" EXTRAS="${KX_EXTRAS-rag,mcp}" SOURCE="${KX_SOURCE:-site}" KX_HOME="${KX_HOME:-$HOME/.kinetixai}" WHEELS="$KX_HOME/wheels" BIN_DIR="$HOME/.local/bin" GIT_URL="git+https://github.com/kinetixai-ai/kx.git" if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then say "warning: running as root. kx is a per-user tool; consider running this as your normal user." fi check_base_url "$BASE_URL" # ── 1. Python 3.10+ ──────────────────────────────────────────────────── PY="" for cand in python3.13 python3.12 python3.11 python3.10 python3 python; do if command -v "$cand" >/dev/null 2>&1 && "$cand" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' >/dev/null 2>&1; then PY="$(command -v "$cand")" break fi done if [ -z "$PY" ]; then fail "Python 3.10 or newer is required and was not found. macOS: brew install python@3.12 (or https://www.python.org/downloads/) Ubuntu/Debian: sudo apt install python3 python3-venv Fedora: sudo dnf install python3 Then run this installer again." fi say "using $("$PY" -c 'import sys; print("Python %d.%d.%d" % sys.version_info[:3])') at $PY" TMP="$(mktemp -d 2>/dev/null || mktemp -d -t kxinstall)" trap 'rm -rf "$TMP"' EXIT INT TERM case "$EXTRAS" in "") SUFFIX="" ;; *) SUFFIX="[$EXTRAS]" ;; esac # ── 2. what to install ───────────────────────────────────────────────── SPEC="" WHL="" case "$SOURCE" in site|pypi|git) ;; *) fail "KX_SOURCE must be site, pypi or git." ;; esac if [ -n "${KX_WHEEL:-}" ]; then [ -f "$KX_WHEEL" ] || fail "KX_WHEEL=$KX_WHEEL does not exist." NAME="$(basename "$KX_WHEEL")" case "$NAME" in kinetixai-*.whl) ;; *) fail "KX_WHEEL must be a kinetixai-*.whl file." ;; esac mkdir -p "$WHEELS" cp "$KX_WHEEL" "$TMP/$NAME" WHL="$TMP/$NAME" say "installing local wheel $KX_WHEEL" elif [ "$SOURCE" = "site" ]; then fetch "$BASE_URL/cli/dl/latest.txt" "$TMP/latest.txt" || fail "could not read $BASE_URL/cli/dl/latest.txt. Check your connection and try again. (This installer does not fall back to it on its own; run again with KX_SOURCE=pypi to install from PyPI.)" REL="$(awk 'NR==1{print $1}' "$TMP/latest.txt")" WANT="$(awk 'NR==1{print $2}' "$TMP/latest.txt")" # "/kinetixai--py3-none-any.whl": the hash directory # makes every published wheel URL immutable. printf '%s' "$REL" | grep -Eq '^[0-9a-f]{12}/kinetixai-[A-Za-z0-9_.-]+\.whl$' || fail "unexpected entry in $BASE_URL/cli/dl/latest.txt" printf '%s' "$WANT" | grep -Eq '^[0-9a-f]{64}$' || fail "bad checksum in $BASE_URL/cli/dl/latest.txt" NAME="${REL#*/}" say "downloading $NAME" fetch "$BASE_URL/cli/dl/$REL" "$TMP/$NAME" || fail "could not download $BASE_URL/cli/dl/$REL" GOT="$(sha256 "$TMP/$NAME")" [ "$GOT" = "$WANT" ] || fail "checksum mismatch for $NAME (expected $WANT, got $GOT). Not installing." say "checksum ok ($GOT)" WHL="$TMP/$NAME" fi if [ -n "$WHL" ]; then # Keep the wheel where pipx can find it again: pipx records this path # and re-reads it on `pipx reinstall` and `pipx upgrade`. mkdir -p "$WHEELS" mv -f "$WHL" "$WHEELS/$NAME" SPEC="kinetixai$SUFFIX @ file://$WHEELS/$NAME" elif [ "$SOURCE" = "pypi" ]; then say "installing from PyPI because KX_SOURCE=pypi" SPEC="kinetixai$SUFFIX" else SPEC="kinetixai$SUFFIX @ $GIT_URL" fi # ── 3. install ───────────────────────────────────────────────────────── if [ "${KX_NO_PIPX:-}" != "1" ] && command -v pipx >/dev/null 2>&1; then # Replace an existing kx cleanly. `pipx install --force` can't reuse a # venv made by another pipx backend (e.g. uv), so uninstall first. Your # kx settings, library and token live outside the venv and are kept. if pipx list --short 2>/dev/null | grep -q '^kinetixai '; then say "replacing the kx already installed with pipx" pipx uninstall kinetixai >/dev/null 2>&1 || fail "pipx could not remove the old kx. Run: pipx uninstall kinetixai" fi say "installing with pipx" pipx install --python "$PY" "$SPEC" || fail "pipx could not install kx. See the messages above." pipx ensurepath >/dev/null 2>&1 || true PIPX_BIN="$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)" BIN_DIR="${PIPX_BIN:-$BIN_DIR}" KX="$BIN_DIR/kx" else VENV="$KX_HOME/venv" say "installing into $VENV" if ! "$PY" -m venv "$VENV" >/dev/null 2>&1; then fail "could not create a virtual environment. On Debian/Ubuntu: sudo apt install python3-venv Or install pipx (https://pipx.pypa.io) and run this again." fi "$VENV/bin/python" -m pip install --quiet --disable-pip-version-check --upgrade "$SPEC" || fail "pip could not install kx. See the messages above." mkdir -p "$BIN_DIR" ln -sf "$VENV/bin/kx" "$BIN_DIR/kx" KX="$BIN_DIR/kx" fi "$KX" --version >/dev/null 2>&1 || fail "kx was installed but does not run. Try: $KX doctor" say "installed $("$KX" --version)" # Older wheels are no longer referenced by any install. if [ -n "$WHL" ]; then for old in "$WHEELS"/kinetixai-*.whl; do if [ -e "$old" ] && [ "$old" != "$WHEELS/$NAME" ]; then rm -f "$old"; fi done fi case ":$PATH:" in *":$BIN_DIR:"*) ;; *) say "$BIN_DIR is not on your PATH yet. Add this line to ~/.zshrc or ~/.bashrc, then open a new terminal:" printf '\n export PATH="%s:$PATH"\n\n' "$BIN_DIR" ;; esac say "next steps: kx doctor check everything is ready kx code 2205.14135 find code for a paper kx library add 1706.03762 start your local library kx login optional: token from $BASE_URL/account/tokens/" } fetch() { # fetch URL DEST if command -v curl >/dev/null 2>&1; then case "$1" in https://*) curl -fsSL --proto '=https' --tlsv1.2 --retry 2 -o "$2" "$1" ;; *) curl -fsSL --retry 2 -o "$2" "$1" ;; esac elif command -v wget >/dev/null 2>&1; then wget -q -O "$2" "$1" else fail "curl or wget is needed to download kx." fi } sha256() { "$PY" -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$1"; } main "$@"