Ctrl K
ComunidadGuías…

scripts/install_oss.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh

# Omnigent installer.
#
# Installs `omnigent[databricks]` from PyPI with uv and the Databricks CLI,
# wires up PATH, and points you at first-run. The wheel bundles the web UI, so the
# default install needs no Node/npm and runs no build.
#
# Options:
#   --version X   install a specific PyPI release (default: latest)
#   --repo URL    install from a git checkout instead (builds from source;
#                 requires Node 22+/npm) — for development
#   --extra NAME  install an optional-dependency extra (repeatable, or
#                 comma-separated), in addition to databricks
#   --no-databricks  omit the default Databricks extra and CLI
#   --non-interactive, --verbose
#
# uv and git (only with --repo) are required; the installer offers to install
# them if missing. Node/npm are needed by the Claude/Codex/Pi harnesses, tmux
# by their terminal launchers, and bubblewrap (Linux only) to OS-sandbox those
# terminals — missing ones are warnings, not errors, unless building from
# source.

set -eu

# Published PyPI package, the default install. --version pins a release.
PACKAGE_NAME="omnigent"
VERSION=
# Databricks workspace login is included; --extra adds other integrations.
EXTRAS=
INCLUDE_DATABRICKS=true
DATABRICKS_CLI_VERSION=1.21.0
# SHA-256 values from this release's official SHA256SUMS manifest.
DATABRICKS_CLI_SHA256_LINUX_AMD64=5599f6a4c01535142cdb1aa16527ec68e6aa849bd7ce72581ec4ed491e9d16b7
DATABRICKS_CLI_SHA256_LINUX_ARM64=27440b096de110978ea8621ed936692bf88e6912ddadea810f7cac78850ceb02
DATABRICKS_CLI_SHA256_DARWIN_AMD64=968946e53e0b197870931c34d103e2e4317002314db6e04a11495af2554415c3
DATABRICKS_CLI_SHA256_DARWIN_ARM64=5c899a18bb280a33cb1814418fd1352a6a6e5487a2d9eb850c573114885c2f83
# Set by --repo to install from a git checkout instead (development; builds
# the web UI from source). Empty => install the published wheel from PyPI.
REPO_URL=
PYTHON_VERSION="3.12"
INSTALL_URL=
NON_INTERACTIVE=false
VERBOSE=false
LEDGER_PROFILE=
ESC=$(printf '\033')
RESET=
BOLD=
DIM=
MAGENTA=
GREEN=
YELLOW=
RED=

use_terminal_ui() {
  [ -t 1 ] && [ "${TERM:-}" != "dumb" ]
}

init_style() {
  if use_terminal_ui && [ -z "${NO_COLOR:-}" ]; then
    RESET="${ESC}[0m"
    BOLD="${ESC}[1m"
    DIM="${ESC}[2m"
    # Brand accent — Otto's magenta-pink (#F43BA6), matching the Python CLI
    # palette in omnigent/inner/ui.py so the installer and the tool agree.
    MAGENTA="${ESC}[38;2;244;59;166m"
    GREEN="${ESC}[32m"
    YELLOW="${ESC}[33m"
    RED="${ESC}[31m"
  fi
}

# The Otto + "omnigent" wordmark lockup, printed once at the top of an
# interactive install. Mirrors omnigent.inner.wordmark.lockup_lines(); the
# whole lockup is painted in the brand magenta (flat — no gradient in sh).
# Skipped off a TTY (use_terminal_ui) so piped/CI installs stay clean.
print_banner() {
  use_terminal_ui || return 0
  printf '\n'
  printf '%s  ⠀⠀⠀⢠⣿⡄⠀⠀⠀   ██████╗ ███╗   ███╗███╗   ██╗██╗ ██████╗ ███████╗███╗   ██╗████████╗%s\n' "$MAGENTA" "$RESET"
  printf '%s  ⢴⣶⣶⠉⣿⠉⣶⣶⡦  ██╔═══██╗████╗ ████║████╗  ██║██║██╔════╝ ██╔════╝████╗  ██║╚══██╔══╝%s\n' "$MAGENTA" "$RESET"
  printf '%s  ⠀⠙⣿⣶⣿⣶⣿⠋⠀  ██║   ██║██╔████╔██║██╔██╗ ██║██║██║  ███╗█████╗  ██╔██╗ ██║   ██║%s\n' "$MAGENTA" "$RESET"
  printf '%s  ⠀⢠⣿⡿⠿⢿⣿⡄⠀  ╚██████╔╝██║ ╚═╝ ██║██║ ╚████║██║╚██████╔╝███████╗██║ ╚████║   ██║%s\n' "$MAGENTA" "$RESET"
  printf '%s  ⠀⠈⠁⠀⠀⠀⠈⠁⠀   ╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═══╝╚═╝ ╚═════╝ ╚══════╝╚═╝  ╚═══╝   ╚═╝%s\n' "$MAGENTA" "$RESET"
  printf '%s  all your agents, one cli%s\n\n' "$DIM" "$RESET"
}

usage() {
  printf 'Usage: install_oss.sh [--non-interactive] [--verbose] [--version X] [--repo URL] [--extra NAME] [--no-databricks]\n'
}

step() {
  printf '%s==>%s %s\n' "$MAGENTA" "$RESET" "$1"
}

verbose() {
  if [ "$VERBOSE" = true ]; then
    printf '%sDEBUG:%s %s\n' "$DIM" "$RESET" "$1" >&2
  fi
}

warn() {
  printf '%sWARNING:%s %s\n' "$YELLOW" "$RESET" "$1" >&2
}

fail() {
  printf '%sERROR:%s %s\n' "$RED" "$RESET" "$1" >&2
  exit 1
}

spinner_frame() {
  case $(($1 % 4)) in
    0) printf '-' ;;
    1) printf '%s' "\\" ;;
    2) printf '|' ;;
    *) printf '/' ;;
  esac
}

run_with_spinner() {
  label="$1"
  shift

  if [ "$VERBOSE" = true ]; then
    verbose "Running: $*"
    "$@"
    return
  fi

  if ! use_terminal_ui; then
    "$@"
    return
  fi

  log_file="${TMPDIR:-/tmp}/omnigent-oss-installer.$$.log"
  status_file="${TMPDIR:-/tmp}/omnigent-oss-installer.$$.status"
  rm -f "$log_file" "$status_file"

  (
    if "$@" >"$log_file" 2>&1; then
      printf '0\n' >"$status_file"
    else
      printf '%s\n' "$?" >"$status_file"
    fi
  ) &
  command_pid=$!

  frame=0
  while [ ! -f "$status_file" ]; do
    spinner="$(spinner_frame "$frame")"
    printf '\r\033[K%s%s%s %s%s%s' "$MAGENTA" "$spinner" "$RESET" "$BOLD" "$label" "$RESET"
    frame=$((frame + 1))
    sleep 0.1
  done

  wait "$command_pid" 2>/dev/null || true
  status="$(cat "$status_file")"
  rm -f "$status_file"

  if [ "$status" = 0 ]; then
    printf '\r\033[K%sok%s %s\n' "$GREEN" "$RESET" "$label"
    rm -f "$log_file"
    return
  fi

  printf '\r\033[K'
  warn "$label failed"
  cat "$log_file" >&2
  rm -f "$log_file"
  return "$status"
}

parse_args() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --non-interactive)
        NON_INTERACTIVE=true
        ;;
      --verbose)
        VERBOSE=true
        ;;
      --no-databricks)
        INCLUDE_DATABRICKS=false
        ;;
      --repo)
        if [ "$#" -lt 2 ]; then
          usage >&2
          exit 1
        fi
        REPO_URL="$2"
        shift
        ;;
      --version)
        if [ "$#" -lt 2 ]; then
          usage >&2
          exit 1
        fi
        VERSION="$2"
        shift
        ;;
      --extra)
        if [ "$#" -lt 2 ]; then
          usage >&2
          exit 1
        fi
        # Accumulate repeated flags into one comma-separated list; a value that
        # is itself comma-separated (--extra a,b) just concatenates cleanly.
        if [ -n "$EXTRAS" ]; then
          EXTRAS="$EXTRAS,$2"
        else
          EXTRAS="$2"
        fi
        shift
        ;;
      *)
        usage >&2
        exit 1
        ;;
    esac
    shift
  done
  if [ "$INCLUDE_DATABRICKS" = true ] && ! has_databricks_extra; then
    EXTRAS="databricks${EXTRAS:+,$EXTRAS}"
  fi
}

has_databricks_extra() {
  case ",$EXTRAS," in
    *,databricks,*) return 0 ;;
    *) return 1 ;;
  esac
}

normalize_repo_url() {
  case "$REPO_URL" in
    "")
      # No --repo: install the published wheel from PyPI (the default).
      INSTALL_URL=
      return
      ;;
    git+ssh://* | git+https://* | git+http://*)
      INSTALL_URL="$REPO_URL"
      ;;
    ssh://*)
      INSTALL_URL="git+$REPO_URL"
      ;;
    https://* | http://*)
      INSTALL_URL="git+$REPO_URL"
      ;;
    *@*:*)
      user_host="${REPO_URL%%:*}"
      repo_path="${REPO_URL#*:}"
      INSTALL_URL="git+ssh://$user_host/$repo_path"
      ;;
    *)
      fail "Unsupported --repo URL: $REPO_URL. Use https://..., ssh://..., or git@host:org/repo.git."
      ;;
  esac

  if [ -n "$VERSION" ]; then
    fail "--version pins a PyPI release and cannot be combined with --repo (a git source install)."
  fi
  verbose "Repository install URL: $INSTALL_URL"
}

# True only for a from-source git install (--repo): that path builds the web
# UI with npm, so Node/npm are hard requirements. A default PyPI install pulls
# the prebuilt wheel and needs neither.
building_from_source() {
  [ -n "$INSTALL_URL" ]
}

prompt_yes_no() {
  prompt="$1"

  if [ "$NON_INTERACTIVE" = true ]; then
    return 1
  fi

  if ! ( : </dev/tty ) 2>/dev/null; then
    return 1
  fi

  printf '%s [Y/n] ' "$prompt" >/dev/tty
  if ! IFS= read -r answer </dev/tty; then
    answer=
  fi

  case "$answer" in
    "" | y | Y | yes | YES | Yes)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

check_platform() {
  case "$(uname -s)" in
    Darwin | Linux)
      ;;
    *)
      fail "install_oss.sh supports macOS and Linux only."
      ;;
  esac
}

# Hard prerequisites. uv is always required (it performs the install). git is
# only needed for a from-source `--repo` install — a PyPI wheel install never
# touches git — so we check it only in that mode. The installer OFFERS to
# install each with a trusted one-line installer rather than just failing.
check_prerequisites() {
  if building_from_source; then
    ensure_git
  fi
  ensure_uv
}

ensure_git() {
  if command -v git >/dev/null 2>&1; then
    step "git is available"
    return
  fi
  case "$(uname -s)" in
    Linux)
      install_cmd="$(linux_pkg_install_cmd git)"
      if [ -n "$install_cmd" ] && prompt_yes_no "git is required and not installed. Install it now ($install_cmd)?"; then
        # Run directly (not via run_with_spinner) so sudo can prompt for a password.
        sh -c "$install_cmd" || true
        command -v git >/dev/null 2>&1 && { step "git installed"; return; }
      fi
      ;;
    Darwin)
      warn "git is required. Install the Xcode command-line tools with: xcode-select --install"
      ;;
  esac
  fail "git is required. Install it, then rerun this installer."
}

# uv performs the install, so it's required; offer the official one-liner,
# and fall back to a fail-with-hint on decline/non-interactive/failure.
ensure_uv() {
  if command -v uv >/dev/null 2>&1; then
    OMNIGENT_LEDGER_DEP_UV=preexisting
    export OMNIGENT_LEDGER_DEP_UV
    step "uv is available"
    return
  fi
  installer='curl -LsSf https://astral.sh/uv/install.sh | sh'
  if prompt_yes_no "uv is required and not installed. Install it now ($installer)?"; then
    run_with_spinner "install uv" sh -c "$installer" || true
    # uv's installer drops the binary in ~/.local/bin (or ~/.cargo/bin) and
    # wires PATH for *future* shells; pull it onto PATH for the rest of this run.
    if ! command -v uv >/dev/null 2>&1; then
      for d in "$HOME/.local/bin" "$HOME/.cargo/bin"; do
        if [ -x "$d/uv" ]; then
          PATH="$d:$PATH"
          export PATH
          break
        fi
      done
    fi
    if command -v uv >/dev/null 2>&1; then
      OMNIGENT_LEDGER_DEP_UV=omnigent
      export OMNIGENT_LEDGER_DEP_UV
      step "Installed uv"
      return
    fi
    fail "uv installed but is not on PATH — open a new shell and rerun this installer."
  fi
  fail "uv is required. Install from https://docs.astral.sh/uv/getting-started/installation/, then rerun."
}

# ── Harness toolchain ────────────────────────────────────────────────
#
# Node 22+/npm power the Claude/Codex/Pi harnesses; the default PyPI install
# and the bare web UI don't need them, so a missing one is a warning here.
# With --repo (source build) npm is required up front, so it becomes an error.

# Report a missing prereq: fatal when building from source, otherwise a warning.
require_or_warn() {
  if building_from_source; then
    fail "$1"
  fi
  warn "$1"
}

# Node >= 22.10: the Claude/Codex/Pi CLIs need worker_threads.markAsUncloneable
# (added in 22.10). Probe the symbol rather than parse a version string.
check_node() {
  if ! command -v node >/dev/null 2>&1; then
    require_or_warn "node not found — Node.js 22+ is needed for the Claude/Codex/Pi harnesses (https://nodejs.org)."
    return
  fi
  OMNIGENT_LEDGER_DEP_NODE=preexisting
  export OMNIGENT_LEDGER_DEP_NODE
  if node -e "process.exit(typeof require('node:worker_threads').markAsUncloneable === 'function' ? 0 : 1)" >/dev/null 2>&1; then
    step "Node.js is new enough for the harness CLIs"
  else
    require_or_warn "Node.js is older than 22.10 — the Claude/Codex/Pi harnesses need 22 LTS or newer (https://nodejs.org)."
  fi
}

check_npm() {
  if command -v npm >/dev/null 2>&1; then
    OMNIGENT_LEDGER_DEP_NPM=preexisting
    export OMNIGENT_LEDGER_DEP_NPM
    step "npm is available (installs the Claude/Codex/Pi harness CLIs on first run)"
  else
    require_or_warn "npm not found — needed to install the Claude/Codex/Pi harness CLIs (https://nodejs.org)."
  fi
}

# `omnigent claude` / `omnigent codex` launch through a local tmux terminal
# and won't start without it, so surface it up front and offer to install it.
# Emit the package-manager command that installs $1 on this Linux box, or
# nothing when no known package manager is present. Shared by the tmux and
# git install offers.
linux_pkg_install_cmd() {
  pkg="$1"
  if command -v apt-get >/dev/null 2>&1; then
    printf 'sudo apt-get install -y %s' "$pkg"
  elif command -v dnf >/dev/null 2>&1; then
    printf 'sudo dnf install -y %s' "$pkg"
  elif command -v yum >/dev/null 2>&1; then
    printf 'sudo yum install -y %s' "$pkg"
  elif command -v pacman >/dev/null 2>&1; then
    printf 'sudo pacman -S --noconfirm %s' "$pkg"
  elif command -v zypper >/dev/null 2>&1; then
    printf 'sudo zypper install -y %s' "$pkg"
  fi
}

check_tmux() {
  if command -v tmux >/dev/null 2>&1; then
    OMNIGENT_LEDGER_DEP_TMUX=preexisting
    export OMNIGENT_LEDGER_DEP_TMUX
    step "tmux is available"
    return
  fi

  case "$(uname -s)" in
    Darwin)
      if command -v brew >/dev/null 2>&1; then
        if prompt_yes_no "tmux is missing (needed for \`omnigent claude\` / \`omnigent codex\`). Install it with brew?"; then
          run_with_spinner "brew install tmux" brew install tmux || warn "brew install tmux failed — install tmux manually before \`omnigent claude\`."
          return
        fi
      fi
      warn "tmux not found — \`omnigent claude\` / \`omnigent codex\` need it. Install with: brew install tmux"
      ;;
    Linux)
      install_cmd="$(linux_pkg_install_cmd tmux)"
      if [ -n "$install_cmd" ] && prompt_yes_no "tmux is missing (needed for \`omnigent claude\` / \`omnigent codex\`). Install it now ($install_cmd)?"; then
        # Run directly (not via run_with_spinner) so sudo can prompt for a password.
        sh -c "$install_cmd" || warn "tmux install failed — run manually: $install_cmd"
        if command -v tmux >/dev/null 2>&1; then
          OMNIGENT_LEDGER_DEP_TMUX=omnigent
          export OMNIGENT_LEDGER_DEP_TMUX
          step "tmux installed"
        fi
        return
      fi
      if [ -n "$install_cmd" ]; then
        warn "tmux not found — \`omnigent claude\` / \`omnigent codex\` need it. Install with: $install_cmd"
      else
        warn "tmux not found — \`omnigent claude\` / \`omnigent codex\` need it. Install it with your package manager."
      fi
      ;;
  esac
}

# The native `omnigent claude` / `omnigent codex` / `pi` harnesses wrap each
# agent terminal in a bubblewrap (`bwrap`) OS-sandbox; on Linux that isolation
# is mandatory and fail-loud, so a missing `bwrap` binary makes those terminals
# fail to start. macOS sandboxes with the built-in seatbelt backend and needs
# nothing here, so this check is Linux-only.
check_bubblewrap() {
  [ "$(uname -s)" = Linux ] || return 0

  if command -v bwrap >/dev/null 2>&1; then
    OMNIGENT_LEDGER_DEP_BWRAP=preexisting
    export OMNIGENT_LEDGER_DEP_BWRAP
    step "bubblewrap (bwrap) is available"
    return
  fi

  install_cmd="$(linux_pkg_install_cmd bubblewrap)"
  if [ -n "$install_cmd" ] && prompt_yes_no "bubblewrap is missing (needed to sandbox native \`omnigent claude\` / \`omnigent codex\` terminals). Install it now ($install_cmd)?"; then
    run_with_spinner "install bubblewrap" sh -c "$install_cmd" || warn "bubblewrap install failed — run manually: $install_cmd"
    if command -v bwrap >/dev/null 2>&1; then
      OMNIGENT_LEDGER_DEP_BWRAP=omnigent
      export OMNIGENT_LEDGER_DEP_BWRAP
    fi
    return
  fi
  if [ -n "$install_cmd" ]; then
    warn "bubblewrap (bwrap) not found — native \`omnigent claude\` / \`omnigent codex\` terminals need it on Linux. Install with: $install_cmd"
  else
    warn "bubblewrap (bwrap) not found — native \`omnigent claude\` / \`omnigent codex\` terminals need it on Linux. Install it with your package manager."
  fi
}

install_omnigent() {
  # PyPI wheels bundle the web UI; --repo builds it from source with npm.
  # Extras apply to every install mode, including Databricks by default.
  extras_suffix=
  if [ -n "$EXTRAS" ]; then
    extras_suffix="[$EXTRAS]"
  fi
  if building_from_source; then
    # A PEP 508 direct reference attaches extras to a git source install:
    # "omnigent[databricks] @ git+https://...". Without extras, keep the bare
    # URL (the long-standing form uv accepts directly).
    if [ -n "$extras_suffix" ]; then
      target="${PACKAGE_NAME}${extras_suffix} @ ${INSTALL_URL}"
    else
      target="$INSTALL_URL"
    fi
    step "Installing Omnigent from source${extras_suffix:+ $extras_suffix} (Python $PYTHON_VERSION)"
  elif [ -n "$VERSION" ]; then
    target="${PACKAGE_NAME}${extras_suffix}==${VERSION}"
    step "Installing Omnigent $VERSION${extras_suffix:+ $extras_suffix} (Python $PYTHON_VERSION)"
  else
    target="${PACKAGE_NAME}${extras_suffix}"
    step "Installing Omnigent${extras_suffix:+ $extras_suffix} (Python $PYTHON_VERSION)"
  fi
  # --force so re-running upgrades instead of no-op'ing; -q hides uv's
  # "Installed N executables" summary (the package also ships an `omni` alias).
  run_with_spinner "uv tool install" uv tool install --force -q --python "$PYTHON_VERSION" "$target"
}

install_databricks_cli() (
  case "$(uname -s)" in
    Darwin) databricks_os=darwin ;;
    Linux) databricks_os=linux ;;
    *) fail "The Databricks CLI requires macOS or Linux." ;;
  esac
  case "$(uname -m)" in
    x86_64) databricks_arch=amd64 ;;
    arm64 | aarch64) databricks_arch=arm64 ;;
    *) fail "Unsupported Databricks CLI architecture: $(uname -m)." ;;
  esac
  case "$databricks_os:$databricks_arch" in
    linux:amd64) databricks_sha256="$DATABRICKS_CLI_SHA256_LINUX_AMD64" ;;
    linux:arm64) databricks_sha256="$DATABRICKS_CLI_SHA256_LINUX_ARM64" ;;
    darwin:amd64) databricks_sha256="$DATABRICKS_CLI_SHA256_DARWIN_AMD64" ;;
    darwin:arm64) databricks_sha256="$DATABRICKS_CLI_SHA256_DARWIN_ARM64" ;;
  esac

  databricks_tmp="$(mktemp -d "${TMPDIR:-/tmp}/omnigent-databricks.XXXXXX")" || exit 1
  trap 'rm -rf "$databricks_tmp"' 0
  trap 'exit 1' 1 2 15
  databricks_archive="databricks_cli_${DATABRICKS_CLI_VERSION}_${databricks_os}_${databricks_arch}.zip"
  curl -fsSL "https://github.com/databricks/cli/releases/download/v${DATABRICKS_CLI_VERSION}/${databricks_archive}" \
    -o "$databricks_tmp/databricks.zip" || exit 1

  # Reuse uv's Python so fresh machines need neither unzip nor sudo.
  databricks_python="$(uv python find "$PYTHON_VERSION")" || exit 1
  "$databricks_python" -c '
import hashlib
import sys
import zipfile

with open(sys.argv[1], "rb") as download:
    digest = hashlib.file_digest(download, "sha256").hexdigest()
if digest != sys.argv[3]:
    raise SystemExit("Databricks CLI archive checksum mismatch.")
with zipfile.ZipFile(sys.argv[1]) as archive:
    archive.extract("databricks", sys.argv[2])
' "$databricks_tmp/databricks.zip" "$databricks_tmp" "$databricks_sha256" || exit 1
  chmod +x "$databricks_tmp/databricks" || exit 1
  "$databricks_tmp/databricks" auth login --help >/dev/null || exit 1
  mkdir -p "$1" || exit 1
  mv "$databricks_tmp/databricks" "$1/databricks" || exit 1
)

ensure_databricks_cli() {
  databricks_path="$PATH"
  if ! path_contains "$1"; then
    databricks_path="$1:$PATH"
  fi
  databricks_cli="$(PATH="$databricks_path" command -v databricks 2>/dev/null || true)"
  if [ -z "$databricks_cli" ] && { [ -e "$1/databricks" ] || [ -L "$1/databricks" ]; }; then
    databricks_cli="$1/databricks"
  fi
  if [ -n "$databricks_cli" ]; then
    if ! "$databricks_cli" auth login --help >/dev/null 2>&1; then
      fail "Databricks CLI at $databricks_cli cannot run auth login. Update it using https://docs.databricks.com/aws/en/dev-tools/cli/install, then rerun this installer, or use --no-databricks to install without workspace login dependencies."
    fi
    OMNIGENT_LEDGER_DEP_DATABRICKS=preexisting
    export OMNIGENT_LEDGER_DEP_DATABRICKS
    step "Databricks CLI is available at $databricks_cli"
    return
  fi

  if ! run_with_spinner "Install Databricks CLI $DATABRICKS_CLI_VERSION" install_databricks_cli "$1"; then
    fail "Databricks CLI installation failed. Check the error above and rerun this installer."
  fi
  OMNIGENT_LEDGER_DEP_DATABRICKS=omnigent
  export OMNIGENT_LEDGER_DEP_DATABRICKS
  step "Installed Databricks CLI at $1/databricks"
}

uv_tool_bin_dir() {
  uv tool dir --bin
}

path_contains() {
  dir="$1"

  case ":$PATH:" in
    *":$dir:"*)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

pick_profile() {
  case "$(uname -s):${SHELL:-}" in
    Darwin:*/zsh)
      printf '%s/.zprofile\n' "$HOME"
      ;;
    Darwin:*/bash)
      printf '%s/.bash_profile\n' "$HOME"
      ;;
    Linux:*/zsh)
      printf '%s/.zshrc\n' "$HOME"
      ;;
    Linux:*/bash)
      printf '%s/.bashrc\n' "$HOME"
      ;;
    *)
      printf '%s/.profile\n' "$HOME"
      ;;
  esac
}

maybe_add_bin_to_path() {
  bin_dir="$1"

  if path_contains "$bin_dir"; then
    step "$bin_dir is already on PATH"
    return
  fi

  path_line="export PATH=\"$bin_dir:\$PATH\""
  profile="$(pick_profile)"
  begin_marker="# >>> Omnigent installer >>>"
  end_marker="# <<< Omnigent installer <<<"

  warn "$bin_dir is not on PATH."
  if [ "$NON_INTERACTIVE" = true ]; then
    warn "Add it with: $path_line"
    return
  fi

  if [ -f "$profile" ] && grep -F "$begin_marker" "$profile" >/dev/null 2>&1; then
    if grep -F "$path_line" "$profile" >/dev/null 2>&1; then
      LEDGER_PROFILE="$profile"
      step "PATH is already configured in $profile"
      return
    fi
    fail "$profile already has an Omnigent installer block. Update it manually to: $path_line"
  fi

  if ! prompt_yes_no "Add $bin_dir to PATH in $profile?"; then
    warn "Skipping PATH update. Current shell can run: $path_line"
    return
  fi

  mkdir -p "${profile%/*}"
  {
    printf '\n%s\n' "$begin_marker"
    printf '%s\n' "$path_line"
    printf '%s\n' "$end_marker"
  } >>"$profile"
  LEDGER_PROFILE="$profile"
  step "Added $bin_dir to PATH in $profile"
}

write_install_ledger() {
  bin_dir="$1"
  cli_path="$bin_dir/omnigent"
  if [ ! -x "$cli_path" ]; then
    cli_path="$(command -v omnigent 2>/dev/null || true)"
  fi
  if [ -z "$cli_path" ]; then
    warn "Could not write install ledger: omnigent command not found."
    return 0
  fi
  if [ -n "$LEDGER_PROFILE" ]; then
    OMNIGENT_LEDGER_PROFILE="$LEDGER_PROFILE"
    export OMNIGENT_LEDGER_PROFILE
  fi
  ledger_command_path="$PATH"
  if ! path_contains "$bin_dir"; then
    ledger_command_path="$bin_dir:$PATH"
  fi
  if PATH="$ledger_command_path" "$cli_path" _internal write-ledger --from-env >/dev/null 2>&1; then
    step "Recorded install ledger"
  else
    warn "Could not write install ledger; uninstall can backfill it later."
  fi
}

verify_omnigent() {
  bin_dir="$1"
  cli_path="$bin_dir/omnigent"

  if [ ! -x "$cli_path" ]; then
    cli_path="$(command -v omnigent 2>/dev/null || true)"
  fi

  if [ -z "$cli_path" ]; then
    fail "Omnigent installed, but the omnigent command was not found."
  fi

  "$cli_path" --help >/dev/null
  step "Verified $cli_path"

  # `omni` is a shorthand alias installed alongside `omnigent`; check it so a
  # packaging regression that drops it surfaces here rather than later.
  for alias_cmd in omni; do
    if [ ! -x "$bin_dir/$alias_cmd" ] && ! command -v "$alias_cmd" >/dev/null 2>&1; then
      warn "the $alias_cmd alias was not installed (expected a console-script entry point alongside omnigent)."
    fi
  done
}

# No setup step here by design: the first `omnigent` run configures a model
# credential and offers to install the harness CLI you pick.
print_next_steps() {
  bin_dir="$1"
  command_prefix=

  if ! path_contains "$bin_dir"; then
    command_prefix="PATH=\"$bin_dir:\$PATH\" "
  fi

  printf '\n%sOmnigent installed successfully.%s\n\n' "$BOLD" "$RESET"
  printf 'Start chatting — first run sets up a model and a local web UI:\n'
  printf '  %s%somnigent%s\n\n' "$command_prefix" "$MAGENTA" "$RESET"
  printf 'Or launch a specific coding harness:\n'
  printf '  %somnigent claude          # Claude Code\n' "$command_prefix"
  printf '  %somnigent codex           # Codex\n\n' "$command_prefix"
  printf 'Manage model credentials any time:\n'
  printf '  %somnigent setup\n\n' "$command_prefix"
  printf 'Uninstall the CLI but keep local history and credentials:\n'
  printf '  %somnigent uninstall --yes\n\n' "$command_prefix"
  if has_databricks_extra; then
    printf 'Connect to a Databricks-hosted Omnigent server:\n'
    printf '  %somnigent login <workspace-url>/omnigent\n\n' "$command_prefix"
    printf '%sDatabricks dependencies and CLI are installed. To use a workspace\n' "$DIM"
    printf 'as your model provider, select Databricks in omnigent setup.%s\n' "$RESET"
  fi
}

main() {
  init_style
  parse_args "$@"
  print_banner
  normalize_repo_url
  check_platform
  check_prerequisites
  check_node
  check_npm
  check_tmux
  check_bubblewrap
  install_omnigent
  bin_dir="$(uv_tool_bin_dir)"
  verify_omnigent "$bin_dir"
  if has_databricks_extra; then
    ensure_databricks_cli "$bin_dir"
  fi
  maybe_add_bin_to_path "$bin_dir"
  write_install_ledger "$bin_dir"
  print_next_steps "$bin_dir"
}

main "$@"