From 231ba4d73a5d59b3452281e1788db32ea88ab75a Mon Sep 17 00:00:00 2001 From: spomichter Date: Sun, 1 Mar 2026 23:53:14 +0000 Subject: [PATCH 01/46] feat(install): add interactive installer script single-command installer for DimOS that handles system detection, dependency installation, and uv setup interactively. supports library mode (uv pip install) and developer mode (git clone + uv sync). detects OS (ubuntu/macos/nixos/wsl), GPU (nvidia/apple silicon/cpu), and python version automatically. includes --dry-run, --non-interactive, and environment variable support for CI/automation. configures LCM sysctl buffers and runs post-install verification. Closes DIM-645 --- scripts/install.sh | 740 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 740 insertions(+) create mode 100755 scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000000..55e7c38a1b --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,740 @@ +#!/usr/bin/env bash +# Copyright 2025-2026 Dimensional Inc. +# Licensed under the Apache License, Version 2.0 +# +# Interactive installer for DimOS — the agentive operating system for generalist robotics. +# +# Usage: +# curl -fsSL https://dimensional.ai/install.sh | bash +# curl -fsSL https://dimensional.ai/install.sh | bash -s -- --help +# +# Non-interactive: +# curl -fsSL https://dimensional.ai/install.sh | bash -s -- --non-interactive --mode library --extras base,unitree +# +set -euo pipefail + +# ─── version ────────────────────────────────────────────────────────────────── +INSTALLER_VERSION="0.1.0" + +# ─── defaults ───────────────────────────────────────────────────────────────── +INSTALL_MODE="${DIMOS_INSTALL_MODE:-}" # library | dev +EXTRAS="${DIMOS_EXTRAS:-}" # comma-separated extras +NON_INTERACTIVE="${DIMOS_NO_PROMPT:-0}" +GIT_BRANCH="${DIMOS_BRANCH:-dev}" +NO_CUDA="${DIMOS_NO_CUDA:-0}" +NO_SYSCTL="${DIMOS_NO_SYSCTL:-0}" +DRY_RUN="${DIMOS_DRY_RUN:-0}" +PROJECT_DIR="${DIMOS_PROJECT_DIR:-}" +VERBOSE=0 + +# ─── colors (matching DimOS theme: cyan #00eeee, white #b5e4f4) ────────────── +if [[ -t 1 ]] && command -v tput &>/dev/null && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then + CYAN=$'\033[38;5;44m' + GREEN=$'\033[32m' + YELLOW=$'\033[33m' + RED=$'\033[31m' + BOLD=$'\033[1m' + DIM=$'\033[2m' + RESET=$'\033[0m' +else + CYAN="" GREEN="" YELLOW="" RED="" BOLD="" DIM="" RESET="" +fi + +# ─── helpers ────────────────────────────────────────────────────────────────── +info() { printf "%s▸%s %s\n" "$CYAN" "$RESET" "$*"; } +ok() { printf "%s✓%s %s\n" "$GREEN" "$RESET" "$*"; } +warn() { printf "%s⚠%s %s\n" "$YELLOW" "$RESET" "$*" >&2; } +err() { printf "%s✗%s %s\n" "$RED" "$RESET" "$*" >&2; } +die() { err "$@"; exit 1; } +dim() { printf "%s%s%s\n" "$DIM" "$*" "$RESET"; } + +run_cmd() { + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] $*" + return 0 + fi + if [[ "$VERBOSE" == "1" ]]; then + dim "$ $*" + fi + eval "$@" +} + +has_cmd() { + command -v "$1" &>/dev/null +} + +prompt_yn() { + local msg="$1" default="${2:-y}" + if [[ "$NON_INTERACTIVE" == "1" ]]; then + [[ "$default" == "y" ]] + return + fi + local yn + if [[ "$default" == "y" ]]; then + printf "%s [Y/n] " "$msg" + else + printf "%s [y/N] " "$msg" + fi + read -r yn Comma-separated pip extras (e.g. base,unitree,drone) + --branch Git branch for dev mode (default: dev) + --project-dir Project directory (default: ~/dimos-project or ~/dimos) + --non-interactive Accept defaults, no prompts + --no-cuda Force CPU-only (skip CUDA extras) + --no-sysctl Skip LCM sysctl configuration + --dry-run Print commands without executing + --verbose Show all commands being run + --help Show this help + +${BOLD}ENVIRONMENT VARIABLES${RESET} + DIMOS_INSTALL_MODE library | dev + DIMOS_EXTRAS Comma-separated extras + DIMOS_NO_PROMPT 1 = non-interactive + DIMOS_BRANCH Git branch (default: dev) + DIMOS_NO_CUDA 1 = skip CUDA + DIMOS_NO_SYSCTL 1 = skip sysctl + DIMOS_DRY_RUN 1 = dry run + DIMOS_PROJECT_DIR Project directory + +${BOLD}EXAMPLES${RESET} + # interactive install + curl -fsSL https://dimensional.ai/install.sh | bash + + # non-interactive library install with unitree + drone + curl -fsSL https://dimensional.ai/install.sh | bash -s -- \\ + --non-interactive --mode library --extras base,unitree,drone + + # developer install, cpu-only + curl -fsSL https://dimensional.ai/install.sh | bash -s -- --mode dev --no-cuda + + # dry run to see what would happen + curl -fsSL https://dimensional.ai/install.sh | bash -s -- --dry-run +EOF + exit 0 +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --mode) INSTALL_MODE="$2"; shift 2 ;; + --extras) EXTRAS="$2"; shift 2 ;; + --branch) GIT_BRANCH="$2"; shift 2 ;; + --project-dir) PROJECT_DIR="$2"; shift 2 ;; + --non-interactive) NON_INTERACTIVE=1; shift ;; + --no-cuda) NO_CUDA=1; shift ;; + --no-sysctl) NO_SYSCTL=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --verbose) VERBOSE=1; shift ;; + --help|-h) usage ;; + *) warn "unknown option: $1"; shift ;; + esac + done +} + +# ─── detection ──────────────────────────────────────────────────────────────── +DETECTED_OS="" +DETECTED_OS_VERSION="" +DETECTED_ARCH="" +DETECTED_GPU="" +DETECTED_CUDA="" +DETECTED_PYTHON="" +DETECTED_PYTHON_VER="" +DETECTED_RAM_GB=0 +DETECTED_DISK_GB=0 + +detect_os() { + DETECTED_ARCH="$(uname -m)" + local uname_s + uname_s="$(uname -s)" + + if [[ "$uname_s" == "Darwin" ]]; then + DETECTED_OS="macos" + DETECTED_OS_VERSION="$(sw_vers -productVersion 2>/dev/null || echo "unknown")" + elif [[ "$uname_s" == "Linux" ]]; then + if grep -qi microsoft /proc/version 2>/dev/null; then + DETECTED_OS="wsl" + elif [[ -f /etc/NIXOS ]] || has_cmd nixos-version; then + DETECTED_OS="nixos" + else + DETECTED_OS="ubuntu" + fi + DETECTED_OS_VERSION="$(. /etc/os-release 2>/dev/null && echo "${VERSION_ID:-unknown}" || echo "unknown")" + else + die "unsupported operating system: $uname_s" + fi + + # RAM + if [[ "$uname_s" == "Darwin" ]]; then + DETECTED_RAM_GB=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1073741824 )) + else + DETECTED_RAM_GB=$(( $(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo 0) / 1048576 )) + fi + + # Disk free + if [[ "$uname_s" == "Darwin" ]]; then + DETECTED_DISK_GB=$(df -g "${HOME}" 2>/dev/null | awk 'NR==2 {print $4}' || echo 0) + else + DETECTED_DISK_GB=$(df -BG "${HOME}" 2>/dev/null | awk 'NR==2 {gsub(/G/,"",$4); print $4}' || echo 0) + fi +} + +detect_gpu() { + if [[ "$DETECTED_OS" == "macos" ]]; then + if [[ "$DETECTED_ARCH" == "arm64" ]]; then + DETECTED_GPU="apple-silicon" + else + DETECTED_GPU="none" + fi + elif has_cmd nvidia-smi; then + DETECTED_GPU="nvidia" + DETECTED_CUDA="$(nvidia-smi 2>/dev/null | grep -oP 'CUDA Version: \K[0-9.]+' || echo "")" + else + DETECTED_GPU="none" + fi +} + +detect_python() { + local candidates=("python3.12" "python3.11" "python3.10" "python3") + for cmd in "${candidates[@]}"; do + if has_cmd "$cmd"; then + local ver + ver="$("$cmd" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")" + if [[ -n "$ver" ]]; then + local major minor + major="$(echo "$ver" | cut -d. -f1)" + minor="$(echo "$ver" | cut -d. -f2)" + if [[ "$major" -eq 3 ]] && [[ "$minor" -ge 10 ]]; then + DETECTED_PYTHON="$(command -v "$cmd")" + DETECTED_PYTHON_VER="$ver" + return + fi + fi + fi + done + DETECTED_PYTHON="" + DETECTED_PYTHON_VER="" +} + +print_sysinfo() { + printf "\n" + info "detecting system..." + printf "\n" + + local os_display gpu_display + case "$DETECTED_OS" in + ubuntu) os_display="Ubuntu ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; + macos) os_display="macOS ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; + nixos) os_display="NixOS ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; + wsl) os_display="WSL2 / Ubuntu ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; + *) os_display="Unknown" ;; + esac + + case "$DETECTED_GPU" in + nvidia) + local gpu_name + gpu_name="$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "NVIDIA GPU")" + gpu_display="${gpu_name} (CUDA ${DETECTED_CUDA})" + ;; + apple-silicon) gpu_display="Apple Silicon (Metal/MPS)" ;; + none) gpu_display="CPU only" ;; + esac + + local python_display + if [[ -n "$DETECTED_PYTHON_VER" ]]; then + python_display="$DETECTED_PYTHON_VER" + else + python_display="${YELLOW}not found (uv will install 3.12)${RESET}" + fi + + printf " %sOS:%s %s\n" "$DIM" "$RESET" "$os_display" + printf " %sPython:%s %s\n" "$DIM" "$RESET" "$python_display" + printf " %sGPU:%s %s\n" "$DIM" "$RESET" "$gpu_display" + printf " %sRAM:%s %s GB\n" "$DIM" "$RESET" "$DETECTED_RAM_GB" + printf " %sDisk:%s %s GB free\n" "$DIM" "$RESET" "$DETECTED_DISK_GB" + printf "\n" +} + +# ─── system dependencies ───────────────────────────────────────────────────── +install_system_deps() { + info "installing system dependencies..." + case "$DETECTED_OS" in + ubuntu|wsl) + run_cmd "sudo apt-get update -qq" + run_cmd "sudo apt-get install -y -qq curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + ;; + macos) + if ! has_cmd brew; then + info "installing homebrew..." + run_cmd '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + fi + run_cmd "brew install gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" + ;; + nixos) + info "NixOS detected — system deps managed via nix develop" + ;; + esac + ok "system dependencies ready" +} + +# ─── uv installation ───────────────────────────────────────────────────────── +install_uv() { + if has_cmd uv; then + ok "uv already installed ($(uv --version 2>/dev/null || echo 'unknown'))" + return + fi + info "installing uv..." + run_cmd 'curl -LsSf https://astral.sh/uv/install.sh | sh' + export PATH="$HOME/.local/bin:$PATH" + # try sourcing shell profiles if uv not found yet + if ! has_cmd uv; then + for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" "$HOME/.cargo/env"; do + [[ -f "$rc" ]] && source "$rc" 2>/dev/null || true + done + fi + has_cmd uv || die "uv installation failed — install manually: https://docs.astral.sh/uv/" + ok "uv installed ($(uv --version 2>/dev/null || echo 'unknown'))" +} + +# ─── install mode prompt ───────────────────────────────────────────────────── +prompt_install_mode() { + if [[ -n "$INSTALL_MODE" ]]; then + return + fi + local choice + choice=$(prompt_choice \ + "how do you want to use DimOS?" \ + "1" \ + "Library — pip install into your project (recommended for users)" \ + "Developer — git clone + editable install (recommended for contributors)") + + case "$choice" in + 1) INSTALL_MODE="library" ;; + 2) INSTALL_MODE="dev" ;; + *) INSTALL_MODE="library" ;; + esac +} + +# ─── extras selection ───────────────────────────────────────────────────────── +prompt_extras() { + if [[ -n "$EXTRAS" ]]; then + return + fi + + if [[ "$INSTALL_MODE" == "dev" ]]; then + EXTRAS="all" + info "developer mode: installing all extras (except dds)" + return + fi + + # Platform selection + local platforms + platforms=$(prompt_multi \ + "which robot platforms will you use?" \ + "Unitree (Go2, G1, B1)" \ + "Drone (Mavlink / DJI)" \ + "Manipulators (xArm, Piper, OpenARMs)") + + # Feature selection + local features + features=$(prompt_multi \ + "which features do you need?" \ + "AI Agents (LangChain, voice control)" \ + "Perception (object detection, VLMs)" \ + "Visualization (Rerun 3D viewer)" \ + "Simulation (MuJoCo)" \ + "Web Interface (FastAPI dashboard)" \ + "Misc (extra ML models, vector embedding)") + + # Build extras list + local -a extras_list=() + + # Platforms → extras + if [[ "$platforms" == *"1"* ]]; then extras_list+=("unitree"); fi + if [[ "$platforms" == *"2"* ]]; then extras_list+=("drone"); fi + if [[ "$platforms" == *"3"* ]]; then extras_list+=("manipulation"); fi + + # Features → extras + if [[ "$features" == *"1"* ]]; then extras_list+=("agents"); fi + if [[ "$features" == *"2"* ]]; then extras_list+=("perception"); fi + if [[ "$features" == *"3"* ]]; then extras_list+=("visualization"); fi + if [[ "$features" == *"4"* ]]; then extras_list+=("sim"); fi + if [[ "$features" == *"5"* ]]; then extras_list+=("web"); fi + if [[ "$features" == *"6"* ]]; then extras_list+=("misc"); fi + + # GPU extras + if [[ "$DETECTED_GPU" == "nvidia" ]] && [[ "$NO_CUDA" != "1" ]]; then + if prompt_yn " ${CYAN}▸${RESET} NVIDIA GPU detected — install CUDA support?" "y"; then + extras_list+=("cuda") + else + extras_list+=("cpu") + fi + else + extras_list+=("cpu") + fi + + # Dev tools + if prompt_yn " ${CYAN}▸${RESET} include development tools (ruff, pytest, mypy)?" "n"; then + extras_list+=("dev") + fi + + # Ensure at least base + if [[ ${#extras_list[@]} -eq 0 ]]; then + extras_list=("base") + fi + + EXTRAS="$(IFS=,; echo "${extras_list[*]}")" + printf "\n" + ok "selected extras: ${CYAN}${EXTRAS}${RESET}" +} + +# ─── installation ───────────────────────────────────────────────────────────── +do_install_library() { + local dir="${PROJECT_DIR:-$HOME/dimos-project}" + info "library install → ${dir}" + + run_cmd "mkdir -p '$dir'" + + info "creating virtual environment (python 3.12)..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && uv venv --python 3.12" + else + (cd "$dir" && uv venv --python 3.12) + fi + + local pip_extras="$EXTRAS" + info "installing dimos[${pip_extras}]..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && source .venv/bin/activate && uv pip install 'dimos[${pip_extras}]'" + else + ( + cd "$dir" + source .venv/bin/activate + uv pip install "dimos[${pip_extras}]" + ) + fi + + ok "dimos installed in ${dir}" +} + +do_install_dev() { + local dir="${PROJECT_DIR:-$HOME/dimos}" + info "developer install → ${dir}" + + if [[ -d "$dir/.git" ]]; then + info "existing clone found, pulling latest..." + run_cmd "cd '$dir' && git pull --rebase origin $GIT_BRANCH" + else + info "cloning dimos (branch: ${GIT_BRANCH})..." + run_cmd "GIT_LFS_SKIP_SMUDGE=1 git clone -b $GIT_BRANCH https://github.com/dimensionalOS/dimos.git '$dir'" + fi + + info "syncing dependencies (all extras, excluding dds)..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && uv sync --all-extras --no-extra dds" + else + (cd "$dir" && uv sync --all-extras --no-extra dds) + fi + + ok "dimos developer environment ready in ${dir}" +} + +do_install() { + case "$INSTALL_MODE" in + library) do_install_library ;; + dev) do_install_dev ;; + *) die "invalid install mode: $INSTALL_MODE" ;; + esac +} + +# ─── system configuration ──────────────────────────────────────────────────── +configure_system() { + if [[ "$NO_SYSCTL" == "1" ]]; then + dim " skipping sysctl configuration (--no-sysctl)" + return + fi + if [[ "$DETECTED_OS" == "macos" ]]; then + return + fi + if [[ "$DETECTED_OS" == "nixos" ]]; then + info "NixOS: add networking.kernel.sysctl to configuration.nix for LCM buffers" + dim " networking.kernel.sysctl.\"net.core.rmem_max\" = 67108864;" + dim " networking.kernel.sysctl.\"net.core.rmem_default\" = 67108864;" + return + fi + + local current_rmem + current_rmem="$(sysctl -n net.core.rmem_max 2>/dev/null || echo 0)" + local target=67108864 + + if [[ "$current_rmem" -ge "$target" ]]; then + ok "LCM buffers already configured (rmem_max=${current_rmem})" + return + fi + + printf "\n" + info "DimOS uses LCM transport which needs larger UDP buffers:" + dim " sudo sysctl -w net.core.rmem_max=67108864" + dim " sudo sysctl -w net.core.rmem_default=67108864" + printf "\n" + + if prompt_yn " apply sysctl changes?" "y"; then + run_cmd "sudo sysctl -w net.core.rmem_max=67108864" + run_cmd "sudo sysctl -w net.core.rmem_default=67108864" + + if prompt_yn " persist across reboots (/etc/sysctl.d/99-dimos.conf)?" "y"; then + if [[ "$DRY_RUN" != "1" ]]; then + printf "# DimOS LCM transport buffers\nnet.core.rmem_max=67108864\nnet.core.rmem_default=67108864\n" \ + | sudo tee /etc/sysctl.d/99-dimos.conf >/dev/null + else + dim "[dry-run] would write /etc/sysctl.d/99-dimos.conf" + fi + fi + ok "LCM buffers configured" + fi +} + +# ─── verification ───────────────────────────────────────────────────────────── +verify_install() { + info "verifying installation..." + + local install_dir + if [[ "$INSTALL_MODE" == "library" ]]; then + install_dir="${PROJECT_DIR:-$HOME/dimos-project}" + else + install_dir="${PROJECT_DIR:-$HOME/dimos}" + fi + + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] would verify: python imports, CLI, GPU" + ok "verification skipped (dry-run)" + return + fi + + local venv_python="${install_dir}/.venv/bin/python3" + + if [[ ! -f "$venv_python" ]]; then + warn "venv python not found at ${venv_python}, skipping verification" + return + fi + + # Check python import + if "$venv_python" -c "import dimos" 2>/dev/null; then + ok "python import: dimos ✓" + else + warn "python import check failed — this may be expected for some configurations" + fi + + # Check CLI + local cli_path="${install_dir}/.venv/bin/dimos" + if [[ -x "$cli_path" ]]; then + ok "dimos CLI available" + else + dim " dimos CLI not in PATH (activate venv first: source .venv/bin/activate)" + fi + + # Check CUDA if applicable + if [[ "$DETECTED_GPU" == "nvidia" ]] && [[ "$NO_CUDA" != "1" ]] && [[ "$EXTRAS" == *"cuda"* ]]; then + if "$venv_python" -c "import torch; assert torch.cuda.is_available(); print(f'CUDA: {torch.cuda.get_device_name(0)}')" 2>/dev/null; then + ok "CUDA available" + else + dim " CUDA check: torch not available or no GPU in this environment" + fi + fi + printf "\n" +} + +# ─── quickstart ─────────────────────────────────────────────────────────────── +print_quickstart() { + local install_dir + if [[ "$INSTALL_MODE" == "library" ]]; then + install_dir="${PROJECT_DIR:-$HOME/dimos-project}" + else + install_dir="${PROJECT_DIR:-$HOME/dimos}" + fi + + printf "\n" + printf " %s%s🎉 installation complete!%s\n\n" "$BOLD" "$GREEN" "$RESET" + + printf " %sget started:%s\n\n" "$BOLD" "$RESET" + printf " %s# activate the environment%s\n" "$DIM" "$RESET" + printf " cd %s && source .venv/bin/activate\n\n" "$install_dir" + + if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]] || [[ "$EXTRAS" == *"base"* ]]; then + printf " %s# run unitree go2 (simulation)%s\n" "$DIM" "$RESET" + printf " dimos --simulation run unitree-go2\n\n" + printf " %s# connect to real hardware%s\n" "$DIM" "$RESET" + printf " ROBOT_IP=192.168.1.100 dimos run unitree-go2\n\n" + fi + + if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]] || [[ "$EXTRAS" == *"base"* ]]; then + printf " %s# MuJoCo simulation with click-nav%s\n" "$DIM" "$RESET" + printf " dimos --simulation run unitree-go2-click-nav --viewer-backend rerun\n\n" + fi + + if [[ "$INSTALL_MODE" == "dev" ]]; then + printf " %s# run tests%s\n" "$DIM" "$RESET" + printf " uv run pytest dimos\n\n" + printf " %s# type checking%s\n" "$DIM" "$RESET" + printf " uv run mypy dimos\n\n" + fi + + printf " %sdocs:%s https://github.com/dimensionalOS/dimos\n" "$DIM" "$RESET" + printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" +} + +# ─── cleanup on error ──────────────────────────────────────────────────────── +cleanup() { + local exit_code=$? + if [[ $exit_code -ne 0 ]]; then + printf "\n" + err "installation failed (exit code: ${exit_code})" + err "for help: https://github.com/dimensionalOS/dimos/issues" + err "or join discord: https://discord.gg/dimos" + fi +} +trap cleanup EXIT + +# ─── main ───────────────────────────────────────────────────────────────────── +main() { + parse_args "$@" + + show_banner + + detect_os + detect_gpu + detect_python + print_sysinfo + + # Pre-flight checks + if [[ "$DETECTED_OS" == "ubuntu" ]] || [[ "$DETECTED_OS" == "wsl" ]]; then + local ver_major + ver_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" + if [[ "$ver_major" -lt 22 ]] 2>/dev/null; then + warn "Ubuntu ${DETECTED_OS_VERSION} — 22.04 or newer is recommended" + fi + fi + + if [[ "$DETECTED_OS" == "macos" ]]; then + local mac_major + mac_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" + if [[ "$mac_major" -lt 12 ]] 2>/dev/null; then + die "macOS ${DETECTED_OS_VERSION} is too old — 12.6+ required" + fi + fi + + install_system_deps + install_uv + + # re-detect python after uv install + if [[ -z "$DETECTED_PYTHON" ]]; then + detect_python + if [[ -z "$DETECTED_PYTHON" ]]; then + info "python 3.12 will be installed by uv automatically" + fi + fi + + prompt_install_mode + prompt_extras + do_install + configure_system + verify_install + print_quickstart +} + +main "$@" From cc977982c36428c1df71b5c51d800cdb66069970 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 07:25:38 +0000 Subject: [PATCH 02/46] feat(install): add full Nix support and post-install verification tests - Nix detection: detect nix on any OS (not just NixOS), source nix-daemon.sh - Nix install: offer to install via Determinate Systems installer with flakes - Nix setup flow: --use-nix / --no-nix flags, DIMOS_USE_NIX / DIMOS_NO_NIX env vars - Library mode + nix: download flake.nix/lock, init git repo, nix develop --command - Dev mode + nix: after git clone, uv sync inside nix develop --command - verify_nix_develop(): checks python3, gcc, pkg-config in nix shell - NixOS without nix: hard error (broken install) - Post-install tests: pytest (dev mode, fast subset) + replay verification - Replay test: 30s timeout, exit 124 = success, ImportError detection - --skip-tests / DIMOS_SKIP_TESTS to bypass post-install tests - Updated quickstart: nix develop instructions + warning note - Nix status shown in system info display - Bumped installer version to 0.2.0 --- scripts/install.sh | 396 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 371 insertions(+), 25 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 55e7c38a1b..68c809bdd6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -14,7 +14,7 @@ set -euo pipefail # ─── version ────────────────────────────────────────────────────────────────── -INSTALLER_VERSION="0.1.0" +INSTALLER_VERSION="0.2.0" # ─── defaults ───────────────────────────────────────────────────────────────── INSTALL_MODE="${DIMOS_INSTALL_MODE:-}" # library | dev @@ -26,6 +26,10 @@ NO_SYSCTL="${DIMOS_NO_SYSCTL:-0}" DRY_RUN="${DIMOS_DRY_RUN:-0}" PROJECT_DIR="${DIMOS_PROJECT_DIR:-}" VERBOSE=0 +USE_NIX="${DIMOS_USE_NIX:-0}" +NO_NIX="${DIMOS_NO_NIX:-0}" +SKIP_TESTS="${DIMOS_SKIP_TESTS:-0}" +HAS_NIX=0 # ─── colors (matching DimOS theme: cyan #00eeee, white #b5e4f4) ────────────── if [[ -t 1 ]] && command -v tput &>/dev/null && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then @@ -178,6 +182,9 @@ ${BOLD}OPTIONS${RESET} --non-interactive Accept defaults, no prompts --no-cuda Force CPU-only (skip CUDA extras) --no-sysctl Skip LCM sysctl configuration + --use-nix Force Nix-based setup (install Nix if needed) + --no-nix Skip Nix detection and setup entirely + --skip-tests Skip post-install verification tests --dry-run Print commands without executing --verbose Show all commands being run --help Show this help @@ -189,6 +196,9 @@ ${BOLD}ENVIRONMENT VARIABLES${RESET} DIMOS_BRANCH Git branch (default: dev) DIMOS_NO_CUDA 1 = skip CUDA DIMOS_NO_SYSCTL 1 = skip sysctl + DIMOS_USE_NIX 1 = force Nix-based setup + DIMOS_NO_NIX 1 = skip Nix entirely + DIMOS_SKIP_TESTS 1 = skip post-install tests DIMOS_DRY_RUN 1 = dry run DIMOS_PROJECT_DIR Project directory @@ -203,6 +213,9 @@ ${BOLD}EXAMPLES${RESET} # developer install, cpu-only curl -fsSL https://dimensional.ai/install.sh | bash -s -- --mode dev --no-cuda + # install using Nix for system dependencies + curl -fsSL https://dimensional.ai/install.sh | bash -s -- --use-nix + # dry run to see what would happen curl -fsSL https://dimensional.ai/install.sh | bash -s -- --dry-run EOF @@ -219,6 +232,9 @@ parse_args() { --non-interactive) NON_INTERACTIVE=1; shift ;; --no-cuda) NO_CUDA=1; shift ;; --no-sysctl) NO_SYSCTL=1; shift ;; + --use-nix) USE_NIX=1; shift ;; + --no-nix) NO_NIX=1; shift ;; + --skip-tests) SKIP_TESTS=1; shift ;; --dry-run) DRY_RUN=1; shift ;; --verbose) VERBOSE=1; shift ;; --help|-h) usage ;; @@ -311,6 +327,19 @@ detect_python() { DETECTED_PYTHON_VER="" } +detect_nix() { + if has_cmd nix; then + HAS_NIX=1 + elif [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then + # nix installed but not yet sourced in this shell + # shellcheck disable=SC1091 + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh 2>/dev/null || true + if has_cmd nix; then + HAS_NIX=1 + fi + fi +} + print_sysinfo() { printf "\n" info "detecting system..." @@ -342,16 +371,146 @@ print_sysinfo() { python_display="${YELLOW}not found (uv will install 3.12)${RESET}" fi + local nix_display + if [[ "$HAS_NIX" == "1" ]]; then + local nix_ver + nix_ver="$(nix --version 2>/dev/null | head -1 || echo "unknown")" + nix_display="${GREEN}${nix_ver}${RESET}" + else + nix_display="not installed" + fi + printf " %sOS:%s %s\n" "$DIM" "$RESET" "$os_display" printf " %sPython:%s %s\n" "$DIM" "$RESET" "$python_display" printf " %sGPU:%s %s\n" "$DIM" "$RESET" "$gpu_display" + printf " %sNix:%s %s\n" "$DIM" "$RESET" "$nix_display" printf " %sRAM:%s %s GB\n" "$DIM" "$RESET" "$DETECTED_RAM_GB" printf " %sDisk:%s %s GB free\n" "$DIM" "$RESET" "$DETECTED_DISK_GB" printf "\n" } +# ─── nix support ────────────────────────────────────────────────────────────── +install_nix() { + info "installing Nix via Determinate Systems installer..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" + dim "[dry-run] source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" + dim "[dry-run] enable flakes in ~/.config/nix/nix.conf" + HAS_NIX=1 + return + fi + + curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix \ + | sh -s -- install --no-confirm + + # Source nix into current shell + if [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then + # shellcheck disable=SC1091 + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + fi + + # Ensure flakes are enabled (Determinate installer enables by default, but be safe) + mkdir -p "$HOME/.config/nix" + if ! grep -q "experimental-features.*flakes" "$HOME/.config/nix/nix.conf" 2>/dev/null; then + echo "experimental-features = nix-command flakes" >> "$HOME/.config/nix/nix.conf" + fi + + # Verify + if ! has_cmd nix; then + die "Nix installation failed — 'nix' command not found after install" + fi + + HAS_NIX=1 + ok "Nix installed ($(nix --version 2>/dev/null || echo 'unknown'))" +} + +handle_nix_setup() { + # Skip nix handling entirely if --no-nix + if [[ "$NO_NIX" == "1" ]]; then + dim " skipping Nix setup (--no-nix)" + return + fi + + # --use-nix forces nix path + if [[ "$USE_NIX" == "1" ]]; then + if [[ "$HAS_NIX" == "1" ]]; then + ok "Nix detected — will use for system dependencies" + return + fi + info "--use-nix specified but Nix not found, installing..." + install_nix + USE_NIX=1 + return + fi + + if [[ "$HAS_NIX" == "1" ]]; then + printf "\n" + info "Nix detected on your system" + if prompt_yn " ${CYAN}▸${RESET} set up DimOS with Nix? (provides system deps via nix develop)" "y"; then + USE_NIX=1 + ok "will use Nix for system dependencies" + else + dim " proceeding without Nix" + fi + elif [[ "$DETECTED_OS" == "nixos" ]]; then + die "NixOS detected but 'nix' command not found. Your Nix installation may be broken." + else + # On non-NixOS, optionally offer Nix installation + if prompt_yn " ${CYAN}▸${RESET} would you like to install Nix? (recommended for reproducible system deps)" "n"; then + install_nix + USE_NIX=1 + ok "will use Nix for system dependencies" + fi + fi +} + +verify_nix_develop() { + # Verify that nix develop provides the expected dependencies + local dir="$1" + info "verifying nix develop environment (this may take a while on first run)..." + + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] nix develop --command bash -c 'which python3 && which gcc'" + ok "nix develop verification skipped (dry-run)" + return + fi + + local nix_check + nix_check=$(cd "$dir" && nix develop --command bash -c ' + echo "python3=$(which python3 2>/dev/null || echo MISSING)" + echo "gcc=$(which gcc 2>/dev/null || echo MISSING)" + echo "pkg-config=$(which pkg-config 2>/dev/null || echo MISSING)" + python3 --version 2>/dev/null || echo "python3 version: UNAVAILABLE" + ' 2>&1) || true + + if echo "$nix_check" | grep -q "python3=MISSING"; then + warn "nix develop: python3 not found in nix shell" + else + local py_path + py_path=$(echo "$nix_check" | grep "^python3=" | cut -d= -f2) + ok "nix develop: python3 available at ${py_path}" + fi + + if echo "$nix_check" | grep -q "gcc=MISSING"; then + warn "nix develop: gcc not found in nix shell" + else + ok "nix develop: gcc available" + fi + + if echo "$nix_check" | grep -q "pkg-config=MISSING"; then + warn "nix develop: pkg-config not found in nix shell" + else + ok "nix develop: pkg-config available" + fi +} + # ─── system dependencies ───────────────────────────────────────────────────── install_system_deps() { + if [[ "$USE_NIX" == "1" ]]; then + info "system dependencies will be provided by nix develop" + return + fi + info "installing system dependencies..." case "$DETECTED_OS" in ubuntu|wsl) @@ -366,7 +525,9 @@ install_system_deps() { run_cmd "brew install gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" ;; nixos) + # NixOS without USE_NIX — user declined nix path info "NixOS detected — system deps managed via nix develop" + warn "you declined Nix setup; you may need to manually run 'nix develop' for system deps" ;; esac ok "system dependencies ready" @@ -490,23 +651,60 @@ do_install_library() { run_cmd "mkdir -p '$dir'" - info "creating virtual environment (python 3.12)..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && uv venv --python 3.12" - else - (cd "$dir" && uv venv --python 3.12) - fi + if [[ "$USE_NIX" == "1" ]]; then + # Download flake files for nix develop + info "downloading flake.nix and flake.lock..." + local flake_base="https://raw.githubusercontent.com/dimensionalOS/dimos/refs/heads/${GIT_BRANCH}" + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] curl -fsSL ${flake_base}/flake.nix -o ${dir}/flake.nix" + dim "[dry-run] curl -fsSL ${flake_base}/flake.lock -o ${dir}/flake.lock" + else + curl -fsSL "${flake_base}/flake.nix" -o "${dir}/flake.nix" + curl -fsSL "${flake_base}/flake.lock" -o "${dir}/flake.lock" + fi + ok "flake files downloaded" - local pip_extras="$EXTRAS" - info "installing dimos[${pip_extras}]..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && source .venv/bin/activate && uv pip install 'dimos[${pip_extras}]'" + # Initialize a minimal git repo (nix flakes require git context) + if [[ "$DRY_RUN" != "1" ]] && [[ ! -d "${dir}/.git" ]]; then + (cd "$dir" && git init -q && git add flake.nix flake.lock && git commit -q -m "init: add flake files" --allow-empty) + fi + + verify_nix_develop "$dir" + + local pip_extras="$EXTRAS" + info "creating venv and installing dimos[${pip_extras}] via nix develop..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && nix develop --command bash -c 'uv venv --python 3.12 && source .venv/bin/activate && uv pip install \"dimos[${pip_extras}]\"'" + else + ( + cd "$dir" + nix develop --command bash -c " + set -euo pipefail + uv venv --python 3.12 + source .venv/bin/activate + uv pip install 'dimos[${pip_extras}]' + " + ) + fi else - ( - cd "$dir" - source .venv/bin/activate - uv pip install "dimos[${pip_extras}]" - ) + info "creating virtual environment (python 3.12)..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && uv venv --python 3.12" + else + (cd "$dir" && uv venv --python 3.12) + fi + + local pip_extras="$EXTRAS" + info "installing dimos[${pip_extras}]..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && source .venv/bin/activate && uv pip install 'dimos[${pip_extras}]'" + else + ( + cd "$dir" + source .venv/bin/activate + uv pip install "dimos[${pip_extras}]" + ) + fi fi ok "dimos installed in ${dir}" @@ -524,11 +722,22 @@ do_install_dev() { run_cmd "GIT_LFS_SKIP_SMUDGE=1 git clone -b $GIT_BRANCH https://github.com/dimensionalOS/dimos.git '$dir'" fi - info "syncing dependencies (all extras, excluding dds)..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && uv sync --all-extras --no-extra dds" + if [[ "$USE_NIX" == "1" ]]; then + verify_nix_develop "$dir" + + info "syncing dependencies via nix develop (all extras, excluding dds)..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && nix develop --command bash -c 'uv sync --all-extras --no-extra dds'" + else + (cd "$dir" && nix develop --command bash -c "set -euo pipefail && uv sync --all-extras --no-extra dds") + fi else - (cd "$dir" && uv sync --all-extras --no-extra dds) + info "syncing dependencies (all extras, excluding dds)..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] cd '$dir' && uv sync --all-extras --no-extra dds" + else + (cd "$dir" && uv sync --all-extras --no-extra dds) + fi fi ok "dimos developer environment ready in ${dir}" @@ -614,10 +823,18 @@ verify_install() { fi # Check python import - if "$venv_python" -c "import dimos" 2>/dev/null; then - ok "python import: dimos ✓" + if [[ "$USE_NIX" == "1" ]]; then + if (cd "$install_dir" && nix develop --command bash -c "source .venv/bin/activate && python3 -c 'import dimos'" 2>/dev/null); then + ok "python import: dimos ✓" + else + warn "python import check failed — this may be expected for some configurations" + fi else - warn "python import check failed — this may be expected for some configurations" + if "$venv_python" -c "import dimos" 2>/dev/null; then + ok "python import: dimos ✓" + else + warn "python import check failed — this may be expected for some configurations" + fi fi # Check CLI @@ -639,6 +856,119 @@ verify_install() { printf "\n" } +# ─── post-install tests ────────────────────────────────────────────────────── +run_post_install_tests() { + if [[ "$SKIP_TESTS" == "1" ]]; then + dim " skipping post-install tests (--skip-tests)" + return + fi + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] would run post-install verification tests" + return + fi + + local install_dir + if [[ "$INSTALL_MODE" == "library" ]]; then + install_dir="${PROJECT_DIR:-$HOME/dimos-project}" + else + install_dir="${PROJECT_DIR:-$HOME/dimos}" + fi + + local venv_activate="${install_dir}/.venv/bin/activate" + if [[ ! -f "$venv_activate" ]]; then + warn "venv not found, skipping post-install tests" + return + fi + + printf "\n" + info "${BOLD}running post-install verification tests...${RESET}" + printf "\n" + + local test_failures=0 + + # ── pytest (dev mode only) ──────────────────────────────────────── + if [[ "$INSTALL_MODE" == "dev" ]]; then + info "running quick pytest suite (fast tests only)..." + + local pytest_exit=0 + if [[ "$USE_NIX" == "1" ]]; then + (cd "$install_dir" && nix develop --command bash -c " + set -euo pipefail + source .venv/bin/activate + python -m pytest dimos -x -q --timeout=60 -k 'not slow and not mujoco' 2>&1 | tail -30 + ") || pytest_exit=$? + else + ( + cd "$install_dir" + source "$venv_activate" + uv run pytest dimos -x -q --timeout=60 -k "not slow and not mujoco" 2>&1 | tail -30 + ) || pytest_exit=$? + fi + + if [[ $pytest_exit -eq 0 ]]; then + ok "pytest: ${GREEN}passed${RESET} ✓" + else + warn "pytest: some tests failed (exit code: ${pytest_exit})" + ((test_failures++)) || true + fi + fi + + # ── replay verification ─────────────────────────────────────────── + info "verifying DimOS replay mode (unitree-go2, 30s timeout)..." + + local replay_log + replay_log=$(mktemp /tmp/dimos-replay-XXXXXX.log) + local replay_exit=0 + + if [[ "$USE_NIX" == "1" ]]; then + (cd "$install_dir" && nix develop --command bash -c " + set -euo pipefail + source .venv/bin/activate + timeout 30 dimos --replay run unitree-go2 + ") >"$replay_log" 2>&1 || replay_exit=$? + else + ( + cd "$install_dir" + source "$venv_activate" + timeout 30 dimos --replay run unitree-go2 + ) >"$replay_log" 2>&1 || replay_exit=$? + fi + + if [[ $replay_exit -eq 124 ]]; then + # timeout killed it — means it was still running after 30s = success + ok "replay: unitree-go2 ran for 30s without crashing ✓" + elif [[ $replay_exit -eq 0 ]]; then + ok "replay: unitree-go2 completed successfully ✓" + else + # Check if it hit import/startup errors + if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$replay_log" 2>/dev/null; then + warn "replay: unitree-go2 failed with errors (exit code: ${replay_exit})" + dim " last lines:" + tail -5 "$replay_log" | while IFS= read -r line; do + dim " $line" + done + ((test_failures++)) || true + else + warn "replay: unitree-go2 exited with code ${replay_exit}" + dim " this may be expected in headless/CI environments" + tail -3 "$replay_log" | while IFS= read -r line; do + dim " $line" + done + fi + fi + + rm -f "$replay_log" + + # ── summary ─────────────────────────────────────────────────────── + printf "\n" + if [[ $test_failures -eq 0 ]]; then + ok "${BOLD}all verification tests passed${RESET} 🎉" + else + warn "${test_failures} verification test(s) had issues (see above)" + dim " this may be expected depending on your environment" + fi +} + # ─── quickstart ─────────────────────────────────────────────────────────────── print_quickstart() { local install_dir @@ -652,8 +982,16 @@ print_quickstart() { printf " %s%s🎉 installation complete!%s\n\n" "$BOLD" "$GREEN" "$RESET" printf " %sget started:%s\n\n" "$BOLD" "$RESET" - printf " %s# activate the environment%s\n" "$DIM" "$RESET" - printf " cd %s && source .venv/bin/activate\n\n" "$install_dir" + + if [[ "$USE_NIX" == "1" ]]; then + printf " %s# enter the nix development shell (provides system deps)%s\n" "$DIM" "$RESET" + printf " cd %s && nix develop\n\n" "$install_dir" + printf " %s# then activate the python environment%s\n" "$DIM" "$RESET" + printf " source .venv/bin/activate\n\n" + else + printf " %s# activate the environment%s\n" "$DIM" "$RESET" + printf " cd %s && source .venv/bin/activate\n\n" "$install_dir" + fi if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]] || [[ "$EXTRAS" == *"base"* ]]; then printf " %s# run unitree go2 (simulation)%s\n" "$DIM" "$RESET" @@ -674,6 +1012,11 @@ print_quickstart() { printf " uv run mypy dimos\n\n" fi + if [[ "$USE_NIX" == "1" ]]; then + printf " %s⚠ note:%s always enter 'nix develop' before working with DimOS\n" "$YELLOW" "$RESET" + printf " %s nix develop provides the system libraries DimOS needs%s\n\n" "$DIM" "$RESET" + fi + printf " %sdocs:%s https://github.com/dimensionalOS/dimos\n" "$DIM" "$RESET" printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" } @@ -699,6 +1042,7 @@ main() { detect_os detect_gpu detect_python + detect_nix print_sysinfo # Pre-flight checks @@ -718,6 +1062,7 @@ main() { fi fi + handle_nix_setup install_system_deps install_uv @@ -734,6 +1079,7 @@ main() { do_install configure_system verify_install + run_post_install_tests print_quickstart } From 6bd283daba2794cab516a2396331c1d373a0bd53 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 11:34:31 +0000 Subject: [PATCH 03/46] fix(install): rework setup method selection, fix dry-run hang - replace y/n nix prompt with selectable choice (system packages vs nix) - dry-run now implies non-interactive (no hanging on prompts) - add disk space check with 10GB minimum warning - skip disk space die in dry-run mode --- scripts/install.sh | 76 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 68c809bdd6..812a4e8f2c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -235,7 +235,7 @@ parse_args() { --use-nix) USE_NIX=1; shift ;; --no-nix) NO_NIX=1; shift ;; --skip-tests) SKIP_TESTS=1; shift ;; - --dry-run) DRY_RUN=1; shift ;; + --dry-run) DRY_RUN=1; NON_INTERACTIVE=1; shift ;; --verbose) VERBOSE=1; shift ;; --help|-h) usage ;; *) warn "unknown option: $1"; shift ;; @@ -424,43 +424,71 @@ install_nix() { ok "Nix installed ($(nix --version 2>/dev/null || echo 'unknown'))" } -handle_nix_setup() { - # Skip nix handling entirely if --no-nix +prompt_setup_method() { + # If flags force a path, skip prompting if [[ "$NO_NIX" == "1" ]]; then - dim " skipping Nix setup (--no-nix)" + SETUP_METHOD="system" return fi - - # --use-nix forces nix path if [[ "$USE_NIX" == "1" ]]; then if [[ "$HAS_NIX" == "1" ]]; then ok "Nix detected — will use for system dependencies" + SETUP_METHOD="nix" return fi info "--use-nix specified but Nix not found, installing..." install_nix - USE_NIX=1 + SETUP_METHOD="nix" return fi - if [[ "$HAS_NIX" == "1" ]]; then - printf "\n" - info "Nix detected on your system" - if prompt_yn " ${CYAN}▸${RESET} set up DimOS with Nix? (provides system deps via nix develop)" "y"; then - USE_NIX=1 - ok "will use Nix for system dependencies" - else - dim " proceeding without Nix" + # Disk space warning + local disk_free_gb + disk_free_gb=$(df -BG --output=avail / 2>/dev/null | tail -1 | tr -d ' G' || echo "0") + if [[ "$disk_free_gb" -lt 10 ]] 2>/dev/null; then + warn "only ${disk_free_gb}GB disk space free — DimOS needs at least 10GB (50GB+ recommended)" + if ! prompt_yn " continue anyway?" "n"; then + if [[ "$DRY_RUN" != "1" ]]; then die "not enough disk space"; fi fi + fi + + # If Nix is available, offer choice between system packages and Nix + if [[ "$HAS_NIX" == "1" ]]; then + local choice + choice=$(prompt_choice \ + "how should we set up system dependencies?" \ + "2" \ + "System packages — apt/brew (simpler, uses your system package manager)" \ + "Nix — nix develop (reproducible, recommended if you already use Nix)") + case "$choice" in + 1) SETUP_METHOD="system" ;; + 2) SETUP_METHOD="nix" ;; + *) SETUP_METHOD="nix" ;; + esac elif [[ "$DETECTED_OS" == "nixos" ]]; then die "NixOS detected but 'nix' command not found. Your Nix installation may be broken." else - # On non-NixOS, optionally offer Nix installation - if prompt_yn " ${CYAN}▸${RESET} would you like to install Nix? (recommended for reproducible system deps)" "n"; then - install_nix - USE_NIX=1 - ok "will use Nix for system dependencies" - fi + # No Nix detected — use system packages, optionally offer Nix install + local choice + choice=$(prompt_choice \ + "how should we set up system dependencies?" \ + "1" \ + "System packages — apt/brew (simpler, recommended)" \ + "Install Nix — nix develop (reproducible, installs Nix first)") + case "$choice" in + 1) SETUP_METHOD="system" ;; + 2) + install_nix + SETUP_METHOD="nix" + ;; + *) SETUP_METHOD="system" ;; + esac + fi + + if [[ "$SETUP_METHOD" == "nix" ]]; then + ok "will use Nix for system dependencies" + else + ok "will use system package manager" fi } @@ -1062,8 +1090,10 @@ main() { fi fi - handle_nix_setup - install_system_deps + prompt_setup_method + if [[ "$SETUP_METHOD" != "nix" ]]; then + install_system_deps + fi install_uv # re-detect python after uv install From dd6ab3da1ef3e7587f9be98612a260f576792329 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 11:57:02 +0000 Subject: [PATCH 04/46] fix(install): interactive checkboxes, confirm prompt, fix daemon hang - replace numbered list multi-select with interactive checkbox UI (arrow keys to move, space to toggle, enter to confirm) - add 'ready to install?' confirmation after system info display - fix apt daemon restart hang: DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a - all items selected by default in multi-select --- scripts/install.sh | 106 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 20 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 812a4e8f2c..d4227dcb05 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -113,34 +113,92 @@ prompt_multi() { local msg="$1" shift local -a items=("$@") + local count=${#items[@]} + if [[ "$NON_INTERACTIVE" == "1" ]]; then local all="" - for ((i=1; i<=${#items[@]}; i++)); do + for ((i=1; i<=count; i++)); do [[ -n "$all" ]] && all+="," all+="$i" done echo "$all" return fi - printf "\n%s%s%s %s(comma-separated numbers, enter for all)%s\n\n" "$BOLD" "$msg" "$RESET" "$DIM" "$RESET" - local i=1 - for item in "${items[@]}"; do - printf " %s%d%s) %s\n" "$CYAN" "$i" "$RESET" "$item" - ((i++)) + + # Interactive checkbox UI + # All items selected by default + local -a selected=() + for ((i=0; i/dev/tty + + _draw_multi() { + # Move cursor up to redraw (except first draw) + if [[ "${1:-}" == "redraw" ]]; then + printf "\033[%dA" "$((count + 3))" >/dev/tty + fi + printf "\n%s%s%s %s(↑/↓ move, space toggle, enter confirm)%s\n\n" \ + "$BOLD" "$msg" "$RESET" "$DIM" "$RESET" >/dev/tty + for ((i=0; i/dev/tty + else + printf "%s[%s] %s\n" "$prefix" "$check" "${items[$i]}" >/dev/tty + fi done - echo "$all" - else - echo "$sel" - fi + } + + _draw_multi "first" + + while true; do + # Read single keypress + local key + IFS= read -rsn1 key 0)) && ((cursor--)) + ;; + '[B') # Down + ((cursor < count - 1)) && ((cursor++)) + ;; + esac + _draw_multi "redraw" + elif [[ "$key" == " " ]]; then + # Toggle + if [[ "${selected[$cursor]}" == "1" ]]; then + selected[$cursor]="0" + else + selected[$cursor]="1" + fi + _draw_multi "redraw" + elif [[ "$key" == "" ]]; then + # Enter — confirm + break + fi + done + + # Show cursor + printf "\033[?25h" >/dev/tty + + # Build result + local result="" + for ((i=0; i Date: Mon, 2 Mar 2026 11:58:16 +0000 Subject: [PATCH 05/46] =?UTF-8?q?fix(install):=20handle=20curl=20pipe=20?= =?UTF-8?q?=E2=80=94=20re-exec=20from=20temp=20file=20for=20TTY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit when piped from curl (stdin is not a TTY), saves script to temp file and re-executes with bash so interactive prompts get proper TTY input. fixes: curl -fsSL .../install.sh | bash --- scripts/install.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index d4227dcb05..a5b516e99d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,6 +13,15 @@ # set -euo pipefail +# If piped from curl (stdin is not a TTY), save to temp file and re-execute +# This ensures interactive prompts work correctly +if [ ! -t 0 ]; then + TMPSCRIPT="$(mktemp /tmp/dimos-install.XXXXXX.sh)" + cat > "$TMPSCRIPT" + chmod +x "$TMPSCRIPT" + exec bash "$TMPSCRIPT" "$@" +fi + # ─── version ────────────────────────────────────────────────────────────────── INSTALLER_VERSION="0.2.0" From f16298fd35015603ab2c01e4db0d3faca9757275 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:02:14 +0000 Subject: [PATCH 06/46] fix(install): handle Ctrl+C properly during subprocesses - add SIGINT trap that exits immediately with code 130 - restore terminal cursor visibility on any exit/interrupt - prevents script from eating Ctrl+C during uv install --- scripts/install.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index a5b516e99d..b7f615f117 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -142,7 +142,7 @@ prompt_multi() { done local cursor=0 - # Hide cursor + # Hide cursor (restored by cleanup trap on SIGINT) printf "\033[?25l" >/dev/tty _draw_multi() { @@ -1116,10 +1116,23 @@ print_quickstart() { printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" } -# ─── cleanup on error ──────────────────────────────────────────────────────── +# ─── signal handling ────────────────────────────────────────────────────────── +_interrupted=0 +handle_sigint() { + _interrupted=1 + printf "\n" + warn "interrupted — cleaning up..." + # Restore cursor visibility (checkbox UI hides it) + printf "\033[?25h" 2>/dev/null + exit 130 +} +trap handle_sigint INT + cleanup() { local exit_code=$? - if [[ $exit_code -ne 0 ]]; then + # Restore cursor visibility on any exit + printf "\033[?25h" 2>/dev/null + if [[ $exit_code -ne 0 ]] && [[ "$_interrupted" != "1" ]]; then printf "\n" err "installation failed (exit code: ${exit_code})" err "for help: https://github.com/dimensionalOS/dimos/issues" From 5b18ba1f575ee204aa9b50e37f89a3a2fefd0e1e Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:04:08 +0000 Subject: [PATCH 07/46] fix(install): interactive arrow-key select, fix curl pipe detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prompt_choice now uses arrow keys + ● / ○ radio buttons (like @clack/prompts) - fix curl pipe detection: only self-download when $0 is 'bash', not when running as 'bash script.sh | tail' (was eating stdin incorrectly) - remove redundant 'ready to install?' confirm — first prompt is now setup method choice which makes intent clear - flow: banner → sysinfo → setup method → system deps → uv → install mode → extras (checkboxes) → install → verify --- scripts/install.sh | 68 ++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index b7f615f117..fd7733f5b6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,9 +13,10 @@ # set -euo pipefail -# If piped from curl (stdin is not a TTY), save to temp file and re-execute -# This ensures interactive prompts work correctly -if [ ! -t 0 ]; then +# If piped from curl (stdin is not a TTY and we're not running from a file), +# save to temp file and re-execute so interactive prompts get proper TTY input. +# Detection: when `curl | bash`, $0 is "bash"; when `bash script.sh`, $0 is the path. +if [ ! -t 0 ] && { [ "$0" = "bash" ] || [ "$0" = "-bash" ] || [ "$0" = "/bin/bash" ] || [ "$0" = "/usr/bin/bash" ] || [ "$0" = "sh" ] || [ "$0" = "/bin/sh" ]; }; then TMPSCRIPT="$(mktemp /tmp/dimos-install.XXXXXX.sh)" cat > "$TMPSCRIPT" chmod +x "$TMPSCRIPT" @@ -97,25 +98,52 @@ prompt_choice() { local msg="$1" default="$2" shift 2 local -a options=("$@") + local count=${#options[@]} + if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "$default" return fi - printf "\n%s%s%s\n\n" "$BOLD" "$msg" "$RESET" - local i=1 - for opt in "${options[@]}"; do - if [[ "$i" == "$default" ]]; then - printf " %s❯%s %s\n" "$CYAN" "$RESET" "$opt" - else - printf " %s\n" "$opt" + + local cursor=$((default - 1)) + + # Hide cursor + printf "\033[?25l" >/dev/tty + + _draw_choice() { + if [[ "${1:-}" == "redraw" ]]; then + printf "\033[%dA" "$((count + 2))" >/dev/tty + fi + printf "\n%s%s%s %s(↑/↓ move, enter select)%s\n" \ + "$BOLD" "$msg" "$RESET" "$DIM" "$RESET" >/dev/tty + for ((i=0; i/dev/tty + else + printf " %s○%s %s\n" "$DIM" "$RESET" "${options[$i]}" >/dev/tty + fi + done + } + + _draw_choice "first" + + while true; do + local key + IFS= read -rsn1 key 0)) && ((cursor--)) ;; + '[B') ((cursor < count - 1)) && ((cursor++)) ;; + esac + _draw_choice "redraw" + elif [[ "$key" == "" ]]; then + break fi - ((i++)) done - printf "\n enter choice [%s]: " "$default" - local choice - read -r choice /dev/tty + echo "$((cursor + 1))" } prompt_multi() { @@ -1170,14 +1198,6 @@ main() { fi fi - # Confirm before proceeding - if [[ "$NON_INTERACTIVE" != "1" ]]; then - printf "\n" - if ! prompt_yn " ${CYAN}▸${RESET} ready to install? we\'ll walk you through the setup" "y"; then - die "installation cancelled" - fi - fi - prompt_setup_method if [[ "$SETUP_METHOD" != "nix" ]]; then install_system_deps From 1f4315fe4587ebf78ca5a6d51779008ab4b7932d Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:27:56 +0000 Subject: [PATCH 08/46] feat(install): rewrite with gum TUI library + add uninstall script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major rewrite of install.sh using charmbracelet/gum for interactive prompts: - Auto-downloads gum binary (~4.5MB) at script start - Falls back to basic prompts if download fails - gum choose for single-select (arrow keys, enter) - gum choose --no-limit for multi-select (checkboxes) - gum confirm for Y/n prompts - gum spin for spinners during apt/brew/uv operations - gum style for the banner - All gum commands handle Ctrl+C, cursor, cleanup natively - Cross-platform: Linux x64/arm64, macOS x64/arm64 Adds scripts/uninstall.sh for rapid test iteration: - Removes dimos-project/ and dimos/ dirs - Removes system packages (apt) - Removes uv - Removes sysctl config - Cleans tmp files - Supports --all (no prompts) and --dry-run v0.2.0 → v0.3.0, 1224 lines → 742 lines --- scripts/install.sh | 1142 ++++++++++++------------------------------ scripts/uninstall.sh | 132 +++++ 2 files changed, 462 insertions(+), 812 deletions(-) create mode 100755 scripts/uninstall.sh diff --git a/scripts/install.sh b/scripts/install.sh index fd7733f5b6..7a661c875c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,9 +13,8 @@ # set -euo pipefail -# If piped from curl (stdin is not a TTY and we're not running from a file), +# If piped from curl (stdin is not a TTY and $0 is the shell), # save to temp file and re-execute so interactive prompts get proper TTY input. -# Detection: when `curl | bash`, $0 is "bash"; when `bash script.sh`, $0 is the path. if [ ! -t 0 ] && { [ "$0" = "bash" ] || [ "$0" = "-bash" ] || [ "$0" = "/bin/bash" ] || [ "$0" = "/usr/bin/bash" ] || [ "$0" = "sh" ] || [ "$0" = "/bin/sh" ]; }; then TMPSCRIPT="$(mktemp /tmp/dimos-install.XXXXXX.sh)" cat > "$TMPSCRIPT" @@ -23,12 +22,10 @@ if [ ! -t 0 ] && { [ "$0" = "bash" ] || [ "$0" = "-bash" ] || [ "$0" = "/bin/bas exec bash "$TMPSCRIPT" "$@" fi -# ─── version ────────────────────────────────────────────────────────────────── -INSTALLER_VERSION="0.2.0" +INSTALLER_VERSION="0.3.0" -# ─── defaults ───────────────────────────────────────────────────────────────── -INSTALL_MODE="${DIMOS_INSTALL_MODE:-}" # library | dev -EXTRAS="${DIMOS_EXTRAS:-}" # comma-separated extras +INSTALL_MODE="${DIMOS_INSTALL_MODE:-}" +EXTRAS="${DIMOS_EXTRAS:-}" NON_INTERACTIVE="${DIMOS_NO_PROMPT:-0}" GIT_BRANCH="${DIMOS_BRANCH:-dev}" NO_CUDA="${DIMOS_NO_CUDA:-0}" @@ -40,21 +37,16 @@ USE_NIX="${DIMOS_USE_NIX:-0}" NO_NIX="${DIMOS_NO_NIX:-0}" SKIP_TESTS="${DIMOS_SKIP_TESTS:-0}" HAS_NIX=0 +SETUP_METHOD="" +GUM="" -# ─── colors (matching DimOS theme: cyan #00eeee, white #b5e4f4) ────────────── if [[ -t 1 ]] && command -v tput &>/dev/null && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then - CYAN=$'\033[38;5;44m' - GREEN=$'\033[32m' - YELLOW=$'\033[33m' - RED=$'\033[31m' - BOLD=$'\033[1m' - DIM=$'\033[2m' - RESET=$'\033[0m' + CYAN=$'\033[38;5;44m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m' + BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m' else CYAN="" GREEN="" YELLOW="" RED="" BOLD="" DIM="" RESET="" fi -# ─── helpers ────────────────────────────────────────────────────────────────── info() { printf "%s▸%s %s\n" "$CYAN" "$RESET" "$*"; } ok() { printf "%s✓%s %s\n" "$GREEN" "$RESET" "$*"; } warn() { printf "%s⚠%s %s\n" "$YELLOW" "$RESET" "$*" >&2; } @@ -63,201 +55,157 @@ die() { err "$@"; exit 1; } dim() { printf "%s%s%s\n" "$DIM" "$*" "$RESET"; } run_cmd() { - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] $*" - return 0 - fi - if [[ "$VERBOSE" == "1" ]]; then - dim "$ $*" - fi + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] $*"; return 0; fi + [[ "$VERBOSE" == "1" ]] && dim "$ $*" eval "$@" } -has_cmd() { - command -v "$1" &>/dev/null -} +has_cmd() { command -v "$1" &>/dev/null; } -prompt_yn() { - local msg="$1" default="${2:-y}" - if [[ "$NON_INTERACTIVE" == "1" ]]; then - [[ "$default" == "y" ]] - return - fi - local yn - if [[ "$default" == "y" ]]; then - printf "%s [Y/n] " "$msg" - else - printf "%s [y/N] " "$msg" - fi - read -r yn /dev/tty + local arch os gum_os gum_arch tmpdir url bin + arch="$(uname -m)"; os="$(uname -s)" + case "$os" in Linux) gum_os="Linux";; Darwin) gum_os="Darwin";; *) return 1;; esac + case "$arch" in + x86_64|amd64) gum_arch="x86_64";; + aarch64|arm64) gum_arch="arm64";; + armv7*|armhf) gum_arch="armv7";; + *) return 1;; + esac - _draw_choice() { - if [[ "${1:-}" == "redraw" ]]; then - printf "\033[%dA" "$((count + 2))" >/dev/tty - fi - printf "\n%s%s%s %s(↑/↓ move, enter select)%s\n" \ - "$BOLD" "$msg" "$RESET" "$DIM" "$RESET" >/dev/tty - for ((i=0; i/dev/tty - else - printf " %s○%s %s\n" "$DIM" "$RESET" "${options[$i]}" >/dev/tty - fi - done - } + tmpdir="$(mktemp -d /tmp/gum-install.XXXXXX)" + url="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_${gum_os}_${gum_arch}.tar.gz" - _draw_choice "first" - - while true; do - local key - IFS= read -rsn1 key 0)) && ((cursor--)) ;; - '[B') ((cursor < count - 1)) && ((cursor++)) ;; - esac - _draw_choice "redraw" - elif [[ "$key" == "" ]]; then - break + if curl -fsSL "$url" | tar xz -C "$tmpdir" 2>/dev/null; then + bin="$(find "$tmpdir" -name gum -type f 2>/dev/null | head -1)" + if [[ -n "$bin" ]] && chmod +x "$bin" && [[ -x "$bin" ]]; then + GUM="$bin"; return 0 fi - done - - printf "\033[?25h" >/dev/tty - echo "$((cursor + 1))" + fi + rm -rf "$tmpdir"; return 1 } -prompt_multi() { - local msg="$1" - shift - local -a items=("$@") - local count=${#items[@]} - - if [[ "$NON_INTERACTIVE" == "1" ]]; then - local all="" - for ((i=1; i<=count; i++)); do - [[ -n "$all" ]] && all+="," - all+="$i" +# ─── prompt wrappers (gum with fallback) ───────────────────────────────────── + +prompt_select() { + local msg="$1"; shift + local -a options=("$@") + if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "${options[0]}"; return; fi + printf "\n" >/dev/tty + if [[ -n "$GUM" ]]; then + "$GUM" choose --header "$msg" \ + --cursor "● " --cursor.foreground="44" \ + --header.foreground="255" --header.bold \ + --selected.foreground="44" \ + "${options[@]}" /dev/tty + local i=1 + for opt in "${options[@]}"; do + printf " %s%d)%s %s\n" "$CYAN" "$i" "$RESET" "$opt" >/dev/tty + ((i++)) done - echo "$all" - return + printf " choice [1]: " >/dev/tty + local choice; read -r choice /dev/tty - - _draw_multi() { - # Move cursor up to redraw (except first draw) - if [[ "${1:-}" == "redraw" ]]; then - printf "\033[%dA" "$((count + 3))" >/dev/tty - fi - printf "\n%s%s%s %s(↑/↓ move, space toggle, enter confirm)%s\n\n" \ - "$BOLD" "$msg" "$RESET" "$DIM" "$RESET" >/dev/tty - for ((i=0; i/dev/tty - else - printf "%s[%s] %s\n" "$prefix" "$check" "${items[$i]}" >/dev/tty - fi +prompt_multi() { + local msg="$1"; shift + local -a options=("$@") + if [[ "$NON_INTERACTIVE" == "1" ]]; then printf '%s\n' "${options[@]}"; return; fi + printf "\n" >/dev/tty + if [[ -n "$GUM" ]]; then + local selected_csv + selected_csv=$(IFS=,; echo "${options[*]}") + "$GUM" choose --no-limit --header "$msg" \ + --cursor "❯ " --cursor.foreground="44" \ + --header.foreground="255" --header.bold \ + --selected.foreground="44" \ + --selected="$selected_csv" \ + "${options[@]}" /dev/tty + local i=1 + for opt in "${options[@]}"; do + printf " %s%d)%s %s\n" "$CYAN" "$i" "$RESET" "$opt" >/dev/tty + ((i++)) done - } - - _draw_multi "first" - - while true; do - # Read single keypress - local key - IFS= read -rsn1 key 0)) && ((cursor--)) - ;; - '[B') # Down - ((cursor < count - 1)) && ((cursor++)) - ;; - esac - _draw_multi "redraw" - elif [[ "$key" == " " ]]; then - # Toggle - if [[ "${selected[$cursor]}" == "1" ]]; then - selected[$cursor]="0" - else - selected[$cursor]="1" - fi - _draw_multi "redraw" - elif [[ "$key" == "" ]]; then - # Enter — confirm - break + printf " selection: " >/dev/tty + local sel; read -r sel /dev/tty +prompt_confirm() { + local msg="$1" default="${2:-yes}" + if [[ "$NON_INTERACTIVE" == "1" ]]; then [[ "$default" == "yes" ]]; return; fi + if [[ -n "$GUM" ]]; then + local flag; [[ "$default" == "yes" ]] && flag="--default=yes" || flag="--default=no" + "$GUM" confirm "$msg" $flag --prompt.foreground="44" --selected.background="44" /dev/tty + else printf "%s [y/N] " "$msg" >/dev/tty; fi + read -r yn Comma-separated pip extras (e.g. base,unitree,drone) + --extras Comma-separated pip extras --branch Git branch for dev mode (default: dev) - --project-dir Project directory (default: ~/dimos-project or ~/dimos) + --project-dir Project directory --non-interactive Accept defaults, no prompts - --no-cuda Force CPU-only (skip CUDA extras) + --no-cuda Force CPU-only --no-sysctl Skip LCM sysctl configuration - --use-nix Force Nix-based setup (install Nix if needed) - --no-nix Skip Nix detection and setup entirely - --skip-tests Skip post-install verification tests + --use-nix Force Nix-based setup + --no-nix Skip Nix entirely + --skip-tests Skip post-install verification --dry-run Print commands without executing - --verbose Show all commands being run + --verbose Show all commands --help Show this help -${BOLD}ENVIRONMENT VARIABLES${RESET} - DIMOS_INSTALL_MODE library | dev - DIMOS_EXTRAS Comma-separated extras - DIMOS_NO_PROMPT 1 = non-interactive - DIMOS_BRANCH Git branch (default: dev) - DIMOS_NO_CUDA 1 = skip CUDA - DIMOS_NO_SYSCTL 1 = skip sysctl - DIMOS_USE_NIX 1 = force Nix-based setup - DIMOS_NO_NIX 1 = skip Nix entirely - DIMOS_SKIP_TESTS 1 = skip post-install tests - DIMOS_DRY_RUN 1 = dry run - DIMOS_PROJECT_DIR Project directory - ${BOLD}EXAMPLES${RESET} - # interactive install curl -fsSL https://dimensional.ai/install.sh | bash - - # non-interactive library install with unitree + drone - curl -fsSL https://dimensional.ai/install.sh | bash -s -- \\ - --non-interactive --mode library --extras base,unitree,drone - - # developer install, cpu-only curl -fsSL https://dimensional.ai/install.sh | bash -s -- --mode dev --no-cuda - - # install using Nix for system dependencies - curl -fsSL https://dimensional.ai/install.sh | bash -s -- --use-nix - - # dry run to see what would happen + curl -fsSL https://dimensional.ai/install.sh | bash -s -- --non-interactive --extras base,unitree curl -fsSL https://dimensional.ai/install.sh | bash -s -- --dry-run EOF exit 0 @@ -339,59 +263,37 @@ parse_args() { } # ─── detection ──────────────────────────────────────────────────────────────── -DETECTED_OS="" -DETECTED_OS_VERSION="" -DETECTED_ARCH="" -DETECTED_GPU="" -DETECTED_CUDA="" -DETECTED_PYTHON="" -DETECTED_PYTHON_VER="" -DETECTED_RAM_GB=0 -DETECTED_DISK_GB=0 +DETECTED_OS="" DETECTED_OS_VERSION="" DETECTED_ARCH="" +DETECTED_GPU="" DETECTED_CUDA="" +DETECTED_PYTHON="" DETECTED_PYTHON_VER="" +DETECTED_RAM_GB=0 DETECTED_DISK_GB=0 detect_os() { DETECTED_ARCH="$(uname -m)" - local uname_s - uname_s="$(uname -s)" - + local uname_s; uname_s="$(uname -s)" if [[ "$uname_s" == "Darwin" ]]; then DETECTED_OS="macos" DETECTED_OS_VERSION="$(sw_vers -productVersion 2>/dev/null || echo "unknown")" elif [[ "$uname_s" == "Linux" ]]; then - if grep -qi microsoft /proc/version 2>/dev/null; then - DETECTED_OS="wsl" - elif [[ -f /etc/NIXOS ]] || has_cmd nixos-version; then - DETECTED_OS="nixos" - else - DETECTED_OS="ubuntu" - fi + if grep -qi microsoft /proc/version 2>/dev/null; then DETECTED_OS="wsl" + elif [[ -f /etc/NIXOS ]] || has_cmd nixos-version; then DETECTED_OS="nixos" + else DETECTED_OS="ubuntu"; fi DETECTED_OS_VERSION="$(. /etc/os-release 2>/dev/null && echo "${VERSION_ID:-unknown}" || echo "unknown")" else die "unsupported operating system: $uname_s" fi - - # RAM if [[ "$uname_s" == "Darwin" ]]; then DETECTED_RAM_GB=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1073741824 )) - else - DETECTED_RAM_GB=$(( $(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo 0) / 1048576 )) - fi - - # Disk free - if [[ "$uname_s" == "Darwin" ]]; then DETECTED_DISK_GB=$(df -g "${HOME}" 2>/dev/null | awk 'NR==2 {print $4}' || echo 0) else + DETECTED_RAM_GB=$(( $(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo 0) / 1048576 )) DETECTED_DISK_GB=$(df -BG "${HOME}" 2>/dev/null | awk 'NR==2 {gsub(/G/,"",$4); print $4}' || echo 0) fi } detect_gpu() { if [[ "$DETECTED_OS" == "macos" ]]; then - if [[ "$DETECTED_ARCH" == "arm64" ]]; then - DETECTED_GPU="apple-silicon" - else - DETECTED_GPU="none" - fi + [[ "$DETECTED_ARCH" == "arm64" ]] && DETECTED_GPU="apple-silicon" || DETECTED_GPU="none" elif has_cmd nvidia-smi; then DETECTED_GPU="nvidia" DETECTED_CUDA="$(nvidia-smi 2>/dev/null | grep -oP 'CUDA Version: \K[0-9.]+' || echo "")" @@ -401,46 +303,31 @@ detect_gpu() { } detect_python() { - local candidates=("python3.12" "python3.11" "python3.10" "python3") - for cmd in "${candidates[@]}"; do + for cmd in python3.12 python3.11 python3.10 python3; do if has_cmd "$cmd"; then - local ver - ver="$("$cmd" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")" + local ver; ver="$("$cmd" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")" if [[ -n "$ver" ]]; then - local major minor - major="$(echo "$ver" | cut -d. -f1)" - minor="$(echo "$ver" | cut -d. -f2)" + local major minor; major="$(echo "$ver" | cut -d. -f1)"; minor="$(echo "$ver" | cut -d. -f2)" if [[ "$major" -eq 3 ]] && [[ "$minor" -ge 10 ]]; then - DETECTED_PYTHON="$(command -v "$cmd")" - DETECTED_PYTHON_VER="$ver" - return + DETECTED_PYTHON="$(command -v "$cmd")"; DETECTED_PYTHON_VER="$ver"; return fi fi fi done - DETECTED_PYTHON="" - DETECTED_PYTHON_VER="" + DETECTED_PYTHON=""; DETECTED_PYTHON_VER="" } detect_nix() { - if has_cmd nix; then - HAS_NIX=1 + if has_cmd nix; then HAS_NIX=1 elif [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then - # nix installed but not yet sourced in this shell - # shellcheck disable=SC1091 . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh 2>/dev/null || true - if has_cmd nix; then - HAS_NIX=1 - fi + has_cmd nix && HAS_NIX=1 fi } print_sysinfo() { - printf "\n" - info "detecting system..." - printf "\n" - - local os_display gpu_display + printf "\n"; info "detecting system..."; printf "\n" + local os_display gpu_display python_display nix_display case "$DETECTED_OS" in ubuntu) os_display="Ubuntu ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; macos) os_display="macOS ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; @@ -448,32 +335,15 @@ print_sysinfo() { wsl) os_display="WSL2 / Ubuntu ${DETECTED_OS_VERSION} (${DETECTED_ARCH})" ;; *) os_display="Unknown" ;; esac - case "$DETECTED_GPU" in nvidia) - local gpu_name - gpu_name="$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "NVIDIA GPU")" - gpu_display="${gpu_name} (CUDA ${DETECTED_CUDA})" - ;; + local gpu_name; gpu_name="$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "NVIDIA GPU")" + gpu_display="${gpu_name} (CUDA ${DETECTED_CUDA})" ;; apple-silicon) gpu_display="Apple Silicon (Metal/MPS)" ;; none) gpu_display="CPU only" ;; esac - - local python_display - if [[ -n "$DETECTED_PYTHON_VER" ]]; then - python_display="$DETECTED_PYTHON_VER" - else - python_display="${YELLOW}not found (uv will install 3.12)${RESET}" - fi - - local nix_display - if [[ "$HAS_NIX" == "1" ]]; then - local nix_ver - nix_ver="$(nix --version 2>/dev/null | head -1 || echo "unknown")" - nix_display="${GREEN}${nix_ver}${RESET}" - else - nix_display="not installed" - fi + [[ -n "$DETECTED_PYTHON_VER" ]] && python_display="$DETECTED_PYTHON_VER" || python_display="${YELLOW}not found (uv will install 3.12)${RESET}" + [[ "$HAS_NIX" == "1" ]] && nix_display="${GREEN}$(nix --version 2>/dev/null | head -1)${RESET}" || nix_display="not installed" printf " %sOS:%s %s\n" "$DIM" "$RESET" "$os_display" printf " %sPython:%s %s\n" "$DIM" "$RESET" "$python_display" @@ -482,361 +352,191 @@ print_sysinfo() { printf " %sRAM:%s %s GB\n" "$DIM" "$RESET" "$DETECTED_RAM_GB" printf " %sDisk:%s %s GB free\n" "$DIM" "$RESET" "$DETECTED_DISK_GB" printf "\n" + + if [[ "$DETECTED_DISK_GB" -lt 10 ]] 2>/dev/null; then + warn "only ${DETECTED_DISK_GB}GB disk space free — DimOS needs at least 10GB (50GB+ recommended)" + if [[ "$DRY_RUN" != "1" ]]; then + prompt_confirm "Continue with low disk space?" "no" || die "not enough disk space" + fi + fi } # ─── nix support ────────────────────────────────────────────────────────────── install_nix() { info "installing Nix via Determinate Systems installer..." if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" - dim "[dry-run] source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" - dim "[dry-run] enable flakes in ~/.config/nix/nix.conf" - HAS_NIX=1 - return - fi - - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix \ - | sh -s -- install --no-confirm - - # Source nix into current shell - if [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then - # shellcheck disable=SC1091 - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + dim "[dry-run] curl ... | sh -s -- install"; HAS_NIX=1; return fi - - # Ensure flakes are enabled (Determinate installer enables by default, but be safe) + curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm + [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]] && . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh mkdir -p "$HOME/.config/nix" - if ! grep -q "experimental-features.*flakes" "$HOME/.config/nix/nix.conf" 2>/dev/null; then - echo "experimental-features = nix-command flakes" >> "$HOME/.config/nix/nix.conf" - fi - - # Verify - if ! has_cmd nix; then - die "Nix installation failed — 'nix' command not found after install" - fi - - HAS_NIX=1 - ok "Nix installed ($(nix --version 2>/dev/null || echo 'unknown'))" + grep -q "experimental-features.*flakes" "$HOME/.config/nix/nix.conf" 2>/dev/null || echo "experimental-features = nix-command flakes" >> "$HOME/.config/nix/nix.conf" + has_cmd nix || die "Nix installation failed" + HAS_NIX=1; ok "Nix installed ($(nix --version 2>/dev/null))" } prompt_setup_method() { - # If flags force a path, skip prompting - if [[ "$NO_NIX" == "1" ]]; then - SETUP_METHOD="system" - return - fi + if [[ "$NO_NIX" == "1" ]]; then SETUP_METHOD="system"; return; fi if [[ "$USE_NIX" == "1" ]]; then - if [[ "$HAS_NIX" == "1" ]]; then - ok "Nix detected — will use for system dependencies" - SETUP_METHOD="nix" - return - fi - info "--use-nix specified but Nix not found, installing..." - install_nix - SETUP_METHOD="nix" - return + [[ "$HAS_NIX" == "1" ]] && { ok "Nix detected — using for system deps"; SETUP_METHOD="nix"; return; } + install_nix; SETUP_METHOD="nix"; return fi - # Disk space warning - local disk_free_gb - disk_free_gb=$(df -BG --output=avail / 2>/dev/null | tail -1 | tr -d ' G' || echo "0") - if [[ "$disk_free_gb" -lt 10 ]] 2>/dev/null; then - warn "only ${disk_free_gb}GB disk space free — DimOS needs at least 10GB (50GB+ recommended)" - if ! prompt_yn " continue anyway?" "n"; then - if [[ "$DRY_RUN" != "1" ]]; then die "not enough disk space"; fi - fi - fi - - # If Nix is available, offer choice between system packages and Nix + local choice if [[ "$HAS_NIX" == "1" ]]; then - local choice - choice=$(prompt_choice \ - "how should we set up system dependencies?" \ - "2" \ - "System packages — apt/brew (simpler, uses your system package manager)" \ - "Nix — nix develop (reproducible, recommended if you already use Nix)") - case "$choice" in - 1) SETUP_METHOD="system" ;; - 2) SETUP_METHOD="nix" ;; - *) SETUP_METHOD="nix" ;; - esac + choice=$(prompt_select "How should we set up system dependencies?" \ + "System packages — apt/brew (simpler)" \ + "Nix — nix develop (reproducible)") elif [[ "$DETECTED_OS" == "nixos" ]]; then - die "NixOS detected but 'nix' command not found. Your Nix installation may be broken." + die "NixOS detected but 'nix' command not found." else - # No Nix detected — use system packages, optionally offer Nix install - local choice - choice=$(prompt_choice \ - "how should we set up system dependencies?" \ - "1" \ - "System packages — apt/brew (simpler, recommended)" \ - "Install Nix — nix develop (reproducible, installs Nix first)") - case "$choice" in - 1) SETUP_METHOD="system" ;; - 2) - install_nix - SETUP_METHOD="nix" - ;; - *) SETUP_METHOD="system" ;; - esac + choice=$(prompt_select "How should we set up system dependencies?" \ + "System packages — apt/brew (recommended)" \ + "Install Nix — nix develop (reproducible, installs Nix first)") fi - if [[ "$SETUP_METHOD" == "nix" ]]; then - ok "will use Nix for system dependencies" - else - ok "will use system package manager" - fi + case "$choice" in + *Nix*|*nix*) + [[ "$HAS_NIX" != "1" ]] && install_nix + SETUP_METHOD="nix"; ok "will use Nix for system dependencies" ;; + *) + SETUP_METHOD="system"; ok "will use system package manager" ;; + esac } verify_nix_develop() { - # Verify that nix develop provides the expected dependencies local dir="$1" - info "verifying nix develop environment (this may take a while on first run)..." - - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] nix develop --command bash -c 'which python3 && which gcc'" - ok "nix develop verification skipped (dry-run)" - return - fi - + info "verifying nix develop environment..." + if [[ "$DRY_RUN" == "1" ]]; then ok "nix develop verification skipped (dry-run)"; return; fi local nix_check nix_check=$(cd "$dir" && nix develop --command bash -c ' echo "python3=$(which python3 2>/dev/null || echo MISSING)" echo "gcc=$(which gcc 2>/dev/null || echo MISSING)" - echo "pkg-config=$(which pkg-config 2>/dev/null || echo MISSING)" - python3 --version 2>/dev/null || echo "python3 version: UNAVAILABLE" ' 2>&1) || true - - if echo "$nix_check" | grep -q "python3=MISSING"; then - warn "nix develop: python3 not found in nix shell" - else - local py_path - py_path=$(echo "$nix_check" | grep "^python3=" | cut -d= -f2) - ok "nix develop: python3 available at ${py_path}" - fi - - if echo "$nix_check" | grep -q "gcc=MISSING"; then - warn "nix develop: gcc not found in nix shell" - else - ok "nix develop: gcc available" - fi - - if echo "$nix_check" | grep -q "pkg-config=MISSING"; then - warn "nix develop: pkg-config not found in nix shell" - else - ok "nix develop: pkg-config available" - fi + echo "$nix_check" | grep -q "python3=MISSING" && warn "nix develop: python3 not found" || ok "nix develop: python3 available" + echo "$nix_check" | grep -q "gcc=MISSING" && warn "nix develop: gcc not found" || ok "nix develop: gcc available" } # ─── system dependencies ───────────────────────────────────────────────────── install_system_deps() { - if [[ "$USE_NIX" == "1" ]]; then - info "system dependencies will be provided by nix develop" - return - fi - info "installing system dependencies..." case "$DETECTED_OS" in ubuntu|wsl) - run_cmd "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq" - run_cmd "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + prompt_spin "updating package lists..." \ + "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq" + prompt_spin "installing build tools and libraries..." \ + "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" ;; macos) if ! has_cmd brew; then info "installing homebrew..." run_cmd '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' fi - run_cmd "brew install gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" + prompt_spin "installing system libraries..." \ + "brew install gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" ;; nixos) - # NixOS without USE_NIX — user declined nix path info "NixOS detected — system deps managed via nix develop" - warn "you declined Nix setup; you may need to manually run 'nix develop' for system deps" + warn "you declined Nix setup; run 'nix develop' manually for system deps" ;; esac ok "system dependencies ready" } -# ─── uv installation ───────────────────────────────────────────────────────── install_uv() { - if has_cmd uv; then - ok "uv already installed ($(uv --version 2>/dev/null || echo 'unknown'))" - return - fi + if has_cmd uv; then ok "uv already installed ($(uv --version 2>/dev/null))"; return; fi info "installing uv..." run_cmd 'curl -LsSf https://astral.sh/uv/install.sh | sh' export PATH="$HOME/.local/bin:$PATH" - # try sourcing shell profiles if uv not found yet if ! has_cmd uv; then for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" "$HOME/.cargo/env"; do [[ -f "$rc" ]] && source "$rc" 2>/dev/null || true done fi has_cmd uv || die "uv installation failed — install manually: https://docs.astral.sh/uv/" - ok "uv installed ($(uv --version 2>/dev/null || echo 'unknown'))" + ok "uv installed ($(uv --version 2>/dev/null))" } -# ─── install mode prompt ───────────────────────────────────────────────────── +# ─── install mode + extras ─────────────────────────────────────────────────── prompt_install_mode() { - if [[ -n "$INSTALL_MODE" ]]; then - return - fi + [[ -n "$INSTALL_MODE" ]] && return local choice - choice=$(prompt_choice \ - "how do you want to use DimOS?" \ - "1" \ - "Library — pip install into your project (recommended for users)" \ - "Developer — git clone + editable install (recommended for contributors)") - - case "$choice" in - 1) INSTALL_MODE="library" ;; - 2) INSTALL_MODE="dev" ;; - *) INSTALL_MODE="library" ;; - esac + choice=$(prompt_select "How do you want to use DimOS?" \ + "Library — pip install into your project (recommended)" \ + "Developer — git clone + editable install (contributors)") + case "$choice" in *Library*) INSTALL_MODE="library";; *) INSTALL_MODE="dev";; esac } -# ─── extras selection ───────────────────────────────────────────────────────── prompt_extras() { - if [[ -n "$EXTRAS" ]]; then - return - fi + [[ -n "$EXTRAS" ]] && return + if [[ "$INSTALL_MODE" == "dev" ]]; then EXTRAS="all"; info "developer mode: all extras (except dds)"; return; fi - if [[ "$INSTALL_MODE" == "dev" ]]; then - EXTRAS="all" - info "developer mode: installing all extras (except dds)" - return - fi + local -a platform_sel=() feature_sel=() + while IFS= read -r line; do [[ -n "$line" ]] && platform_sel+=("$line"); done < <(prompt_multi \ + "Which robot platforms will you use?" \ + "Unitree (Go2, G1, B1)" "Drone (Mavlink / DJI)" "Manipulators (xArm, Piper, OpenARMs)") - # Platform selection - local platforms - platforms=$(prompt_multi \ - "which robot platforms will you use?" \ - "Unitree (Go2, G1, B1)" \ - "Drone (Mavlink / DJI)" \ - "Manipulators (xArm, Piper, OpenARMs)") - - # Feature selection - local features - features=$(prompt_multi \ - "which features do you need?" \ - "AI Agents (LangChain, voice control)" \ - "Perception (object detection, VLMs)" \ - "Visualization (Rerun 3D viewer)" \ - "Simulation (MuJoCo)" \ - "Web Interface (FastAPI dashboard)" \ - "Misc (extra ML models, vector embedding)") - - # Build extras list - local -a extras_list=() + while IFS= read -r line; do [[ -n "$line" ]] && feature_sel+=("$line"); done < <(prompt_multi \ + "Which features do you need?" \ + "AI Agents (LangChain, voice control)" "Perception (object detection, VLMs)" \ + "Visualization (Rerun 3D viewer)" "Simulation (MuJoCo)" \ + "Web Interface (FastAPI dashboard)" "Misc (extra ML models)") - # Platforms → extras - if [[ "$platforms" == *"1"* ]]; then extras_list+=("unitree"); fi - if [[ "$platforms" == *"2"* ]]; then extras_list+=("drone"); fi - if [[ "$platforms" == *"3"* ]]; then extras_list+=("manipulation"); fi - - # Features → extras - if [[ "$features" == *"1"* ]]; then extras_list+=("agents"); fi - if [[ "$features" == *"2"* ]]; then extras_list+=("perception"); fi - if [[ "$features" == *"3"* ]]; then extras_list+=("visualization"); fi - if [[ "$features" == *"4"* ]]; then extras_list+=("sim"); fi - if [[ "$features" == *"5"* ]]; then extras_list+=("web"); fi - if [[ "$features" == *"6"* ]]; then extras_list+=("misc"); fi + local -a extras_list=() + for p in "${platform_sel[@]}"; do + case "$p" in *Unitree*) extras_list+=("unitree");; *Drone*) extras_list+=("drone");; *Manipulator*) extras_list+=("manipulation");; esac + done + for f in "${feature_sel[@]}"; do + case "$f" in *Agent*) extras_list+=("agents");; *Perception*) extras_list+=("perception");; *Visualization*) extras_list+=("visualization");; + *Simulation*) extras_list+=("sim");; *Web*) extras_list+=("web");; *Misc*) extras_list+=("misc");; esac + done - # GPU extras if [[ "$DETECTED_GPU" == "nvidia" ]] && [[ "$NO_CUDA" != "1" ]]; then - if prompt_yn " ${CYAN}▸${RESET} NVIDIA GPU detected — install CUDA support?" "y"; then - extras_list+=("cuda") - else - extras_list+=("cpu") - fi + prompt_confirm "NVIDIA GPU detected — install CUDA support?" "yes" && extras_list+=("cuda") || extras_list+=("cpu") else extras_list+=("cpu") fi - # Dev tools - if prompt_yn " ${CYAN}▸${RESET} include development tools (ruff, pytest, mypy)?" "n"; then - extras_list+=("dev") - fi - - # Ensure at least base - if [[ ${#extras_list[@]} -eq 0 ]]; then - extras_list=("base") - fi + prompt_confirm "Include development tools (ruff, pytest, mypy)?" "no" && extras_list+=("dev") + [[ ${#extras_list[@]} -eq 0 ]] && extras_list=("base") EXTRAS="$(IFS=,; echo "${extras_list[*]}")" - printf "\n" - ok "selected extras: ${CYAN}${EXTRAS}${RESET}" + printf "\n"; ok "selected extras: ${CYAN}${EXTRAS}${RESET}" } # ─── installation ───────────────────────────────────────────────────────────── do_install_library() { local dir="${PROJECT_DIR:-$HOME/dimos-project}" info "library install → ${dir}" - run_cmd "mkdir -p '$dir'" if [[ "$USE_NIX" == "1" ]]; then - # Download flake files for nix develop - info "downloading flake.nix and flake.lock..." - local flake_base="https://raw.githubusercontent.com/dimensionalOS/dimos/refs/heads/${GIT_BRANCH}" - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] curl -fsSL ${flake_base}/flake.nix -o ${dir}/flake.nix" - dim "[dry-run] curl -fsSL ${flake_base}/flake.lock -o ${dir}/flake.lock" + info "downloading flake files..." + local base="https://raw.githubusercontent.com/dimensionalOS/dimos/refs/heads/${GIT_BRANCH}" + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] curl flake.nix + flake.lock" else - curl -fsSL "${flake_base}/flake.nix" -o "${dir}/flake.nix" - curl -fsSL "${flake_base}/flake.lock" -o "${dir}/flake.lock" - fi - ok "flake files downloaded" - - # Initialize a minimal git repo (nix flakes require git context) - if [[ "$DRY_RUN" != "1" ]] && [[ ! -d "${dir}/.git" ]]; then - (cd "$dir" && git init -q && git add flake.nix flake.lock && git commit -q -m "init: add flake files" --allow-empty) + curl -fsSL "${base}/flake.nix" -o "${dir}/flake.nix" + curl -fsSL "${base}/flake.lock" -o "${dir}/flake.lock" fi - + [[ "$DRY_RUN" != "1" ]] && [[ ! -d "${dir}/.git" ]] && (cd "$dir" && git init -q && git add flake.nix flake.lock && git commit -q -m "init" --allow-empty) verify_nix_develop "$dir" - - local pip_extras="$EXTRAS" - info "creating venv and installing dimos[${pip_extras}] via nix develop..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && nix develop --command bash -c 'uv venv --python 3.12 && source .venv/bin/activate && uv pip install \"dimos[${pip_extras}]\"'" - else - ( - cd "$dir" - nix develop --command bash -c " - set -euo pipefail - uv venv --python 3.12 - source .venv/bin/activate - uv pip install 'dimos[${pip_extras}]' - " - ) - fi + info "installing dimos[${EXTRAS}] via nix develop..." + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] nix develop + uv venv + uv pip install" + else (cd "$dir" && nix develop --command bash -c "set -euo pipefail; uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'"); fi else info "creating virtual environment (python 3.12)..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && uv venv --python 3.12" - else - (cd "$dir" && uv venv --python 3.12) - fi - - local pip_extras="$EXTRAS" - info "installing dimos[${pip_extras}]..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && source .venv/bin/activate && uv pip install 'dimos[${pip_extras}]'" - else - ( - cd "$dir" - source .venv/bin/activate - uv pip install "dimos[${pip_extras}]" - ) - fi + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv venv --python 3.12" + else (cd "$dir" && uv venv --python 3.12); fi + info "installing dimos[${EXTRAS}]..." + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv pip install 'dimos[${EXTRAS}]'" + else (cd "$dir" && source .venv/bin/activate && uv pip install "dimos[${EXTRAS}]"); fi fi - ok "dimos installed in ${dir}" } do_install_dev() { local dir="${PROJECT_DIR:-$HOME/dimos}" info "developer install → ${dir}" - if [[ -d "$dir/.git" ]]; then info "existing clone found, pulling latest..." run_cmd "cd '$dir' && git pull --rebase origin $GIT_BRANCH" @@ -844,60 +544,35 @@ do_install_dev() { info "cloning dimos (branch: ${GIT_BRANCH})..." run_cmd "GIT_LFS_SKIP_SMUDGE=1 git clone -b $GIT_BRANCH https://github.com/dimensionalOS/dimos.git '$dir'" fi - if [[ "$USE_NIX" == "1" ]]; then verify_nix_develop "$dir" - - info "syncing dependencies via nix develop (all extras, excluding dds)..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && nix develop --command bash -c 'uv sync --all-extras --no-extra dds'" - else - (cd "$dir" && nix develop --command bash -c "set -euo pipefail && uv sync --all-extras --no-extra dds") - fi + info "syncing via nix develop..." + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] nix develop + uv sync --all-extras --no-extra dds" + else (cd "$dir" && nix develop --command bash -c "set -euo pipefail && uv sync --all-extras --no-extra dds"); fi else - info "syncing dependencies (all extras, excluding dds)..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] cd '$dir' && uv sync --all-extras --no-extra dds" - else - (cd "$dir" && uv sync --all-extras --no-extra dds) - fi + info "syncing dependencies..." + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv sync --all-extras --no-extra dds" + else (cd "$dir" && uv sync --all-extras --no-extra dds); fi fi - - ok "dimos developer environment ready in ${dir}" + ok "developer environment ready in ${dir}" } do_install() { - case "$INSTALL_MODE" in - library) do_install_library ;; - dev) do_install_dev ;; - *) die "invalid install mode: $INSTALL_MODE" ;; - esac + case "$INSTALL_MODE" in library) do_install_library;; dev) do_install_dev;; *) die "invalid mode: $INSTALL_MODE";; esac } # ─── system configuration ──────────────────────────────────────────────────── configure_system() { - if [[ "$NO_SYSCTL" == "1" ]]; then - dim " skipping sysctl configuration (--no-sysctl)" - return - fi - if [[ "$DETECTED_OS" == "macos" ]]; then - return - fi + [[ "$NO_SYSCTL" == "1" ]] && { dim " skipping sysctl (--no-sysctl)"; return; } + [[ "$DETECTED_OS" == "macos" ]] && return if [[ "$DETECTED_OS" == "nixos" ]]; then - info "NixOS: add networking.kernel.sysctl to configuration.nix for LCM buffers" + info "NixOS: add to configuration.nix:" dim " networking.kernel.sysctl.\"net.core.rmem_max\" = 67108864;" dim " networking.kernel.sysctl.\"net.core.rmem_default\" = 67108864;" return fi - - local current_rmem - current_rmem="$(sysctl -n net.core.rmem_max 2>/dev/null || echo 0)" - local target=67108864 - - if [[ "$current_rmem" -ge "$target" ]]; then - ok "LCM buffers already configured (rmem_max=${current_rmem})" - return - fi + local current_rmem; current_rmem="$(sysctl -n net.core.rmem_max 2>/dev/null || echo 0)" + if [[ "$current_rmem" -ge 67108864 ]]; then ok "LCM buffers already configured"; return; fi printf "\n" info "DimOS uses LCM transport which needs larger UDP buffers:" @@ -905,17 +580,13 @@ configure_system() { dim " sudo sysctl -w net.core.rmem_default=67108864" printf "\n" - if prompt_yn " apply sysctl changes?" "y"; then + if prompt_confirm "Apply sysctl changes?" "yes"; then run_cmd "sudo sysctl -w net.core.rmem_max=67108864" run_cmd "sudo sysctl -w net.core.rmem_default=67108864" - - if prompt_yn " persist across reboots (/etc/sysctl.d/99-dimos.conf)?" "y"; then + if prompt_confirm "Persist across reboots?" "yes"; then if [[ "$DRY_RUN" != "1" ]]; then - printf "# DimOS LCM transport buffers\nnet.core.rmem_max=67108864\nnet.core.rmem_default=67108864\n" \ - | sudo tee /etc/sysctl.d/99-dimos.conf >/dev/null - else - dim "[dry-run] would write /etc/sysctl.d/99-dimos.conf" - fi + printf "# DimOS LCM transport buffers\nnet.core.rmem_max=67108864\nnet.core.rmem_default=67108864\n" | sudo tee /etc/sysctl.d/99-dimos.conf >/dev/null + else dim "[dry-run] would write /etc/sysctl.d/99-dimos.conf"; fi fi ok "LCM buffers configured" fi @@ -924,248 +595,104 @@ configure_system() { # ─── verification ───────────────────────────────────────────────────────────── verify_install() { info "verifying installation..." + local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + if [[ "$DRY_RUN" == "1" ]]; then ok "verification skipped (dry-run)"; return; fi + local venv_python="${dir}/.venv/bin/python3" + [[ ! -f "$venv_python" ]] && { warn "venv not found, skipping verification"; return; } - local install_dir - if [[ "$INSTALL_MODE" == "library" ]]; then - install_dir="${PROJECT_DIR:-$HOME/dimos-project}" - else - install_dir="${PROJECT_DIR:-$HOME/dimos}" - fi - - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] would verify: python imports, CLI, GPU" - ok "verification skipped (dry-run)" - return - fi - - local venv_python="${install_dir}/.venv/bin/python3" - - if [[ ! -f "$venv_python" ]]; then - warn "venv python not found at ${venv_python}, skipping verification" - return - fi - - # Check python import if [[ "$USE_NIX" == "1" ]]; then - if (cd "$install_dir" && nix develop --command bash -c "source .venv/bin/activate && python3 -c 'import dimos'" 2>/dev/null); then - ok "python import: dimos ✓" - else - warn "python import check failed — this may be expected for some configurations" - fi + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && python3 -c 'import dimos'" 2>/dev/null) && ok "python import: dimos ✓" || warn "import check failed" else - if "$venv_python" -c "import dimos" 2>/dev/null; then - ok "python import: dimos ✓" - else - warn "python import check failed — this may be expected for some configurations" - fi + "$venv_python" -c "import dimos" 2>/dev/null && ok "python import: dimos ✓" || warn "import check failed" fi - # Check CLI - local cli_path="${install_dir}/.venv/bin/dimos" - if [[ -x "$cli_path" ]]; then - ok "dimos CLI available" - else - dim " dimos CLI not in PATH (activate venv first: source .venv/bin/activate)" - fi + [[ -x "${dir}/.venv/bin/dimos" ]] && ok "dimos CLI available" || dim " activate venv for CLI: source .venv/bin/activate" - # Check CUDA if applicable if [[ "$DETECTED_GPU" == "nvidia" ]] && [[ "$NO_CUDA" != "1" ]] && [[ "$EXTRAS" == *"cuda"* ]]; then - if "$venv_python" -c "import torch; assert torch.cuda.is_available(); print(f'CUDA: {torch.cuda.get_device_name(0)}')" 2>/dev/null; then - ok "CUDA available" - else - dim " CUDA check: torch not available or no GPU in this environment" - fi + "$venv_python" -c "import torch; assert torch.cuda.is_available()" 2>/dev/null && ok "CUDA available" || dim " CUDA not available in this environment" fi printf "\n" } -# ─── post-install tests ────────────────────────────────────────────────────── run_post_install_tests() { - if [[ "$SKIP_TESTS" == "1" ]]; then - dim " skipping post-install tests (--skip-tests)" - return - fi - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] would run post-install verification tests" - return - fi - - local install_dir - if [[ "$INSTALL_MODE" == "library" ]]; then - install_dir="${PROJECT_DIR:-$HOME/dimos-project}" - else - install_dir="${PROJECT_DIR:-$HOME/dimos}" - fi + [[ "$SKIP_TESTS" == "1" ]] && { dim " skipping tests (--skip-tests)"; return; } + [[ "$DRY_RUN" == "1" ]] && { dim "[dry-run] would run post-install tests"; return; } - local venv_activate="${install_dir}/.venv/bin/activate" - if [[ ! -f "$venv_activate" ]]; then - warn "venv not found, skipping post-install tests" - return - fi - - printf "\n" - info "${BOLD}running post-install verification tests...${RESET}" - printf "\n" + local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + local venv="${dir}/.venv/bin/activate" + [[ ! -f "$venv" ]] && { warn "venv not found, skipping tests"; return; } - local test_failures=0 + printf "\n"; info "${BOLD}running post-install verification...${RESET}"; printf "\n" + local failures=0 - # ── pytest (dev mode only) ──────────────────────────────────────── if [[ "$INSTALL_MODE" == "dev" ]]; then - info "running quick pytest suite (fast tests only)..." - - local pytest_exit=0 + info "pytest (fast tests)..." + local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$install_dir" && nix develop --command bash -c " - set -euo pipefail - source .venv/bin/activate - python -m pytest dimos -x -q --timeout=60 -k 'not slow and not mujoco' 2>&1 | tail -30 - ") || pytest_exit=$? - else - ( - cd "$install_dir" - source "$venv_activate" - uv run pytest dimos -x -q --timeout=60 -k "not slow and not mujoco" 2>&1 | tail -30 - ) || pytest_exit=$? - fi - - if [[ $pytest_exit -eq 0 ]]; then - ok "pytest: ${GREEN}passed${RESET} ✓" + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && python -m pytest dimos -x -q --timeout=60 -k 'not slow and not mujoco' 2>&1 | tail -20") || exit_code=$? else - warn "pytest: some tests failed (exit code: ${pytest_exit})" - ((test_failures++)) || true + (cd "$dir" && source "$venv" && uv run pytest dimos -x -q --timeout=60 -k "not slow and not mujoco" 2>&1 | tail -20) || exit_code=$? fi + [[ $exit_code -eq 0 ]] && ok "pytest passed ✓" || { warn "pytest: some tests failed"; ((failures++)) || true; } fi - # ── replay verification ─────────────────────────────────────────── - info "verifying DimOS replay mode (unitree-go2, 30s timeout)..." - - local replay_log - replay_log=$(mktemp /tmp/dimos-replay-XXXXXX.log) - local replay_exit=0 - + info "replay verification (unitree-go2, 30s timeout)..." + local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) + local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$install_dir" && nix develop --command bash -c " - set -euo pipefail - source .venv/bin/activate - timeout 30 dimos --replay run unitree-go2 - ") >"$replay_log" 2>&1 || replay_exit=$? + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? else - ( - cd "$install_dir" - source "$venv_activate" - timeout 30 dimos --replay run unitree-go2 - ) >"$replay_log" 2>&1 || replay_exit=$? + (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? fi - if [[ $replay_exit -eq 124 ]]; then - # timeout killed it — means it was still running after 30s = success - ok "replay: unitree-go2 ran for 30s without crashing ✓" - elif [[ $replay_exit -eq 0 ]]; then - ok "replay: unitree-go2 completed successfully ✓" + if [[ $exit_code -eq 124 ]]; then ok "replay: ran 30s without crash ✓" + elif [[ $exit_code -eq 0 ]]; then ok "replay: completed ✓" else - # Check if it hit import/startup errors - if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$replay_log" 2>/dev/null; then - warn "replay: unitree-go2 failed with errors (exit code: ${replay_exit})" - dim " last lines:" - tail -5 "$replay_log" | while IFS= read -r line; do - dim " $line" - done - ((test_failures++)) || true + if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then + warn "replay failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true else - warn "replay: unitree-go2 exited with code ${replay_exit}" - dim " this may be expected in headless/CI environments" - tail -3 "$replay_log" | while IFS= read -r line; do - dim " $line" - done + warn "replay exited with code ${exit_code} (may be expected headless)" fi fi + rm -f "$log" - rm -f "$replay_log" - - # ── summary ─────────────────────────────────────────────────────── printf "\n" - if [[ $test_failures -eq 0 ]]; then - ok "${BOLD}all verification tests passed${RESET} 🎉" - else - warn "${test_failures} verification test(s) had issues (see above)" - dim " this may be expected depending on your environment" - fi + [[ $failures -eq 0 ]] && ok "${BOLD}all checks passed${RESET} 🎉" || warn "${failures} check(s) had issues" } # ─── quickstart ─────────────────────────────────────────────────────────────── print_quickstart() { - local install_dir - if [[ "$INSTALL_MODE" == "library" ]]; then - install_dir="${PROJECT_DIR:-$HOME/dimos-project}" - else - install_dir="${PROJECT_DIR:-$HOME/dimos}" - fi - - printf "\n" - printf " %s%s🎉 installation complete!%s\n\n" "$BOLD" "$GREEN" "$RESET" - - printf " %sget started:%s\n\n" "$BOLD" "$RESET" + local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + printf "\n %s%s🎉 installation complete!%s\n\n %sget started:%s\n\n" "$BOLD" "$GREEN" "$RESET" "$BOLD" "$RESET" if [[ "$USE_NIX" == "1" ]]; then - printf " %s# enter the nix development shell (provides system deps)%s\n" "$DIM" "$RESET" - printf " cd %s && nix develop\n\n" "$install_dir" - printf " %s# then activate the python environment%s\n" "$DIM" "$RESET" - printf " source .venv/bin/activate\n\n" + printf " %s# enter nix shell + activate python%s\n cd %s && nix develop\n source .venv/bin/activate\n\n" "$DIM" "$RESET" "$dir" else - printf " %s# activate the environment%s\n" "$DIM" "$RESET" - printf " cd %s && source .venv/bin/activate\n\n" "$install_dir" + printf " %s# activate the environment%s\n cd %s && source .venv/bin/activate\n\n" "$DIM" "$RESET" "$dir" fi if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]] || [[ "$EXTRAS" == *"base"* ]]; then - printf " %s# run unitree go2 (simulation)%s\n" "$DIM" "$RESET" - printf " dimos --simulation run unitree-go2\n\n" - printf " %s# connect to real hardware%s\n" "$DIM" "$RESET" - printf " ROBOT_IP=192.168.1.100 dimos run unitree-go2\n\n" + printf " %s# simulation%s\n dimos --simulation run unitree-go2\n\n" "$DIM" "$RESET" + printf " %s# real hardware%s\n ROBOT_IP=192.168.1.100 dimos run unitree-go2\n\n" "$DIM" "$RESET" fi - - if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]] || [[ "$EXTRAS" == *"base"* ]]; then - printf " %s# MuJoCo simulation with click-nav%s\n" "$DIM" "$RESET" - printf " dimos --simulation run unitree-go2-click-nav --viewer-backend rerun\n\n" + if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then + printf " %s# MuJoCo + click-nav%s\n dimos --simulation run unitree-go2-click-nav --viewer-backend rerun\n\n" "$DIM" "$RESET" fi - if [[ "$INSTALL_MODE" == "dev" ]]; then - printf " %s# run tests%s\n" "$DIM" "$RESET" - printf " uv run pytest dimos\n\n" - printf " %s# type checking%s\n" "$DIM" "$RESET" - printf " uv run mypy dimos\n\n" - fi - - if [[ "$USE_NIX" == "1" ]]; then - printf " %s⚠ note:%s always enter 'nix develop' before working with DimOS\n" "$YELLOW" "$RESET" - printf " %s nix develop provides the system libraries DimOS needs%s\n\n" "$DIM" "$RESET" + printf " %s# tests%s\n uv run pytest dimos\n\n %s# type check%s\n uv run mypy dimos\n\n" "$DIM" "$RESET" "$DIM" "$RESET" fi - + [[ "$USE_NIX" == "1" ]] && printf " %s⚠%s always run 'nix develop' before working with DimOS\n\n" "$YELLOW" "$RESET" printf " %sdocs:%s https://github.com/dimensionalOS/dimos\n" "$DIM" "$RESET" printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" } # ─── signal handling ────────────────────────────────────────────────────────── _interrupted=0 -handle_sigint() { - _interrupted=1 - printf "\n" - warn "interrupted — cleaning up..." - # Restore cursor visibility (checkbox UI hides it) - printf "\033[?25h" 2>/dev/null - exit 130 -} +handle_sigint() { _interrupted=1; printf "\n"; warn "interrupted"; exit 130; } trap handle_sigint INT - cleanup() { - local exit_code=$? - # Restore cursor visibility on any exit - printf "\033[?25h" 2>/dev/null - if [[ $exit_code -ne 0 ]] && [[ "$_interrupted" != "1" ]]; then - printf "\n" - err "installation failed (exit code: ${exit_code})" - err "for help: https://github.com/dimensionalOS/dimos/issues" - err "or join discord: https://discord.gg/dimos" - fi + local ec=$? + [[ $ec -ne 0 ]] && [[ "$_interrupted" != "1" ]] && { printf "\n"; err "installation failed (exit ${ec})"; err "help: https://github.com/dimensionalOS/dimos/issues"; } } trap cleanup EXIT @@ -1173,43 +700,34 @@ trap cleanup EXIT main() { parse_args "$@" - show_banner + if [[ "$NON_INTERACTIVE" != "1" ]]; then + if install_gum 2>/dev/null; then + dim " using gum for interactive prompts" + else + dim " using basic prompts (install gum for a better experience)" + fi + fi - detect_os - detect_gpu - detect_python - detect_nix + show_banner + detect_os; detect_gpu; detect_python; detect_nix print_sysinfo - # Pre-flight checks if [[ "$DETECTED_OS" == "ubuntu" ]] || [[ "$DETECTED_OS" == "wsl" ]]; then - local ver_major - ver_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" - if [[ "$ver_major" -lt 22 ]] 2>/dev/null; then - warn "Ubuntu ${DETECTED_OS_VERSION} — 22.04 or newer is recommended" - fi - fi - - if [[ "$DETECTED_OS" == "macos" ]]; then - local mac_major - mac_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" - if [[ "$mac_major" -lt 12 ]] 2>/dev/null; then - die "macOS ${DETECTED_OS_VERSION} is too old — 12.6+ required" - fi + local ver_major; ver_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" + [[ "$ver_major" -lt 22 ]] 2>/dev/null && warn "Ubuntu ${DETECTED_OS_VERSION} — 22.04+ recommended" fi + [[ "$DETECTED_OS" == "macos" ]] && { + local mac_major; mac_major="$(echo "$DETECTED_OS_VERSION" | cut -d. -f1)" + [[ "$mac_major" -lt 12 ]] 2>/dev/null && die "macOS ${DETECTED_OS_VERSION} too old — 12.6+ required" + } prompt_setup_method - if [[ "$SETUP_METHOD" != "nix" ]]; then - install_system_deps - fi + [[ "$SETUP_METHOD" != "nix" ]] && install_system_deps install_uv - # re-detect python after uv install if [[ -z "$DETECTED_PYTHON" ]]; then detect_python - if [[ -z "$DETECTED_PYTHON" ]]; then - info "python 3.12 will be installed by uv automatically" - fi + [[ -z "$DETECTED_PYTHON" ]] && info "python 3.12 will be installed by uv automatically" fi prompt_install_mode diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000000..518c25dbc1 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# DimOS Uninstaller — reverses what install.sh did, for testing iteration. +# +# Usage: +# bash scripts/uninstall.sh # interactive — asks what to remove +# bash scripts/uninstall.sh --all # remove everything +# bash scripts/uninstall.sh --dry-run # show what would be removed +# +set -euo pipefail + +CYAN=$'\033[38;5;44m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m' +BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m' + +info() { printf "%s▸%s %s\n" "$CYAN" "$RESET" "$*"; } +ok() { printf "%s✓%s %s\n" "$GREEN" "$RESET" "$*"; } +warn() { printf "%s⚠%s %s\n" "$YELLOW" "$RESET" "$*"; } +err() { printf "%s✗%s %s\n" "$RED" "$RESET" "$*" >&2; } +dim() { printf "%s%s%s\n" "$DIM" "$*" "$RESET"; } + +DRY_RUN=0 +ALL=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=1; shift ;; + --all) ALL=1; shift ;; + --help|-h) + echo "Usage: $0 [--all] [--dry-run] [--help]" + echo " --all Remove everything without prompting" + echo " --dry-run Show what would be removed" + exit 0 ;; + *) shift ;; + esac +done + +do_rm() { + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] rm -rf $1" + else + rm -rf "$1" + ok "removed $1" + fi +} + +should_remove() { + local msg="$1" + if [[ "$ALL" == "1" ]]; then return 0; fi + if [[ "$DRY_RUN" == "1" ]]; then return 0; fi + local yn + printf "%s [y/N] " "$msg" + read -r yn + [[ "$yn" =~ ^[Yy] ]] +} + +printf "\n%s%sDimOS Uninstaller%s\n\n" "$BOLD" "$CYAN" "$RESET" + +# ─── DimOS project dirs ────────────────────────────────────────────────────── +for dir in "$HOME/dimos-project" "$HOME/dimos"; do + if [[ -d "$dir" ]]; then + local_size="$(du -sh "$dir" 2>/dev/null | cut -f1 || echo "?")" + if should_remove "Remove ${dir}/ (${local_size})?"; then + do_rm "$dir" + fi + fi +done + +# ─── System packages (apt) ─────────────────────────────────────────────────── +if command -v apt-get &>/dev/null; then + PKGS="portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + installed="" + for pkg in $PKGS; do + dpkg -l "$pkg" &>/dev/null && installed+=" $pkg" + done + if [[ -n "$installed" ]]; then + if should_remove "Remove system packages:${installed}?"; then + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] sudo apt-get remove -y$installed" + else + sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get remove -y $installed + ok "system packages removed" + fi + fi + else + dim " no DimOS system packages found" + fi +fi + +# ─── uv ────────────────────────────────────────────────────────────────────── +if command -v uv &>/dev/null; then + if should_remove "Remove uv ($(uv --version 2>/dev/null))?"; then + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] rm ~/.local/bin/uv ~/.local/bin/uvx" + dim "[dry-run] rm -rf ~/.cache/uv" + else + rm -f "$HOME/.local/bin/uv" "$HOME/.local/bin/uvx" + rm -rf "$HOME/.cache/uv" + ok "uv removed" + fi + fi +fi + +# ─── sysctl ────────────────────────────────────────────────────────────────── +if [[ -f /etc/sysctl.d/99-dimos.conf ]]; then + if should_remove "Remove LCM sysctl config (/etc/sysctl.d/99-dimos.conf)?"; then + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] sudo rm /etc/sysctl.d/99-dimos.conf" + else + sudo rm -f /etc/sysctl.d/99-dimos.conf + sudo sysctl -w net.core.rmem_max=212992 2>/dev/null || true + sudo sysctl -w net.core.rmem_default=212992 2>/dev/null || true + ok "sysctl config removed, buffers reset to defaults" + fi + fi +fi + +# ─── gum (temp install from installer) ─────────────────────────────────────── +for tmpgum in /tmp/gum-install.*/gum*; do + if [[ -d "$(dirname "$tmpgum")" ]]; then + do_rm "$(dirname "$tmpgum")" + break + fi +done + +# ─── tmp installer files ───────────────────────────────────────────────────── +for tmp in /tmp/dimos-install.*.sh /tmp/dimos-replay-*.log /tmp/dimos-nix-* /tmp/dimos-test-*; do + if [[ -e "$tmp" ]]; then + do_rm "$tmp" + fi +done + +printf "\n%s✓ cleanup complete%s\n\n" "$GREEN" "$RESET" +[[ "$DRY_RUN" == "1" ]] && dim " (dry-run — nothing was actually removed)" From 23e1a358a3c178c76350631bb7938c8a04d00b87 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:39:48 +0000 Subject: [PATCH 09/46] fix(install): auto-replace existing .venv (UV_VENV_CLEAR=1) uv venv prompts interactively when .venv exists, which we can't wrap with gum. Set UV_VENV_CLEAR=1 to silently replace it. --- scripts/install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 7a661c875c..9ad54ef7f9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -522,11 +522,11 @@ do_install_library() { verify_nix_develop "$dir" info "installing dimos[${EXTRAS}] via nix develop..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] nix develop + uv venv + uv pip install" - else (cd "$dir" && nix develop --command bash -c "set -euo pipefail; uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'"); fi + else (cd "$dir" && nix develop --command bash -c "set -euo pipefail; UV_VENV_CLEAR=1 uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'"); fi else info "creating virtual environment (python 3.12)..." - if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv venv --python 3.12" - else (cd "$dir" && uv venv --python 3.12); fi + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] UV_VENV_CLEAR=1 uv venv --python 3.12" + else (cd "$dir" && UV_VENV_CLEAR=1 uv venv --python 3.12); fi info "installing dimos[${EXTRAS}]..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv pip install 'dimos[${EXTRAS}]'" else (cd "$dir" && source .venv/bin/activate && uv pip install "dimos[${EXTRAS}]"); fi From cd496593ffaa61c7329d8875ac6f762eca0b9ca8 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:40:49 +0000 Subject: [PATCH 10/46] fix(install): ask before replacing existing .venv, default no detect existing .venv before creating a new one. prompt the user with default=no so we never silently destroy someone's environment. if they decline, skip venv creation and install into the existing one. --- scripts/install.sh | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 9ad54ef7f9..0b426c4e6d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -522,11 +522,33 @@ do_install_library() { verify_nix_develop "$dir" info "installing dimos[${EXTRAS}] via nix develop..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] nix develop + uv venv + uv pip install" - else (cd "$dir" && nix develop --command bash -c "set -euo pipefail; UV_VENV_CLEAR=1 uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'"); fi + else + local venv_flag="" + if [[ -d "${dir}/.venv" ]]; then + warn "existing .venv found in ${dir}/" + if prompt_confirm "Replace existing virtual environment?" "no"; then + venv_flag="UV_VENV_CLEAR=1" + else + info "keeping existing .venv" + venv_flag="SKIP_VENV=1" + fi + fi + (cd "$dir" && nix develop --command bash -c "set -euo pipefail; [[ \"\${SKIP_VENV:-}\" != 1 ]] && ${venv_flag} uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'") + fi else - info "creating virtual environment (python 3.12)..." - if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] UV_VENV_CLEAR=1 uv venv --python 3.12" - else (cd "$dir" && UV_VENV_CLEAR=1 uv venv --python 3.12); fi + if [[ -d "${dir}/.venv" ]] && [[ "$DRY_RUN" != "1" ]]; then + warn "existing .venv found in ${dir}/" + if prompt_confirm "Replace existing virtual environment?" "no"; then + info "replacing virtual environment..." + (cd "$dir" && UV_VENV_CLEAR=1 uv venv --python 3.12) + else + info "keeping existing .venv — skipping venv creation" + fi + else + info "creating virtual environment (python 3.12)..." + if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv venv --python 3.12" + else (cd "$dir" && uv venv --python 3.12); fi + fi info "installing dimos[${EXTRAS}]..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv pip install 'dimos[${EXTRAS}]'" else (cd "$dir" && source .venv/bin/activate && uv pip install "dimos[${EXTRAS}]"); fi From d67930c6244b8cc0a7e075281e9999f215d45848 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 12:44:47 +0000 Subject: [PATCH 11/46] fix(install): skip apt if all system deps already installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check each package with dpkg -s before running apt-get update/install. if everything is present, skip entirely — saves 30-60s on repeat runs. --- scripts/install.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 0b426c4e6d..a71ff9eec2 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -419,13 +419,27 @@ verify_nix_develop() { # ─── system dependencies ───────────────────────────────────────────────────── install_system_deps() { - info "installing system dependencies..." + info "checking system dependencies..." + case "$DETECTED_OS" in ubuntu|wsl) + # Check if all packages are already installed + local needed="" + local all_pkgs="curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + for pkg in $all_pkgs; do + if ! dpkg -s "$pkg" &>/dev/null; then + needed+=" $pkg" + fi + done + if [[ -z "$needed" ]]; then + ok "all system dependencies already installed" + return + fi + info "need to install:${needed}" prompt_spin "updating package lists..." \ "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq" - prompt_spin "installing build tools and libraries..." \ - "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + prompt_spin "installing packages..." \ + "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq $needed" ;; macos) if ! has_cmd brew; then From 695bfbd8615cfb2e45732c759d4f455051441237 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 13:17:30 +0000 Subject: [PATCH 12/46] feat(install): ask install dir, offer nix installer choice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prompt for install directory with gum input, default to pwd (library) or pwd/dimos (dev) — respects --project-dir flag - nix installer: offer choice between Determinate Systems, official nixos.org, or skip. links to https://nixos.org/download/ - if user skips nix install, falls back to system packages gracefully --- scripts/install.sh | 74 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index a71ff9eec2..653e7bffe0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -363,15 +363,45 @@ print_sysinfo() { # ─── nix support ────────────────────────────────────────────────────────────── install_nix() { - info "installing Nix via Determinate Systems installer..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] curl ... | sh -s -- install"; HAS_NIX=1; return - fi - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm - [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]] && . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + info "Nix is not installed. See: https://nixos.org/download/" + printf "\n" + + local method + method=$(prompt_select "Which Nix installer would you like to use?" \ + "Determinate Systems (recommended, enables flakes by default)" \ + "Official nixos.org installer (multi-user daemon)" \ + "Skip — I'll install Nix myself later") + + case "$method" in + *Determinate*) + info "installing Nix via Determinate Systems..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] curl -sSf https://install.determinate.systems/nix | sh -s -- install" + HAS_NIX=1; return + fi + curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm + ;; + *nixos.org*) + info "installing Nix via official installer..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] sh <(curl -L https://nixos.org/nix/install) --daemon" + HAS_NIX=1; return + fi + sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon + ;; + *Skip*|*) + warn "skipping Nix installation — falling back to system packages" + SETUP_METHOD="system" + return + ;; + esac + + [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]] && \ + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh mkdir -p "$HOME/.config/nix" - grep -q "experimental-features.*flakes" "$HOME/.config/nix/nix.conf" 2>/dev/null || echo "experimental-features = nix-command flakes" >> "$HOME/.config/nix/nix.conf" - has_cmd nix || die "Nix installation failed" + grep -q "experimental-features.*flakes" "$HOME/.config/nix/nix.conf" 2>/dev/null || \ + echo "experimental-features = nix-command flakes" >> "$HOME/.config/nix/nix.conf" + has_cmd nix || die "Nix installation failed — 'nix' not found after install" HAS_NIX=1; ok "Nix installed ($(nix --version 2>/dev/null))" } @@ -518,9 +548,32 @@ prompt_extras() { printf "\n"; ok "selected extras: ${CYAN}${EXTRAS}${RESET}" } +prompt_install_dir() { + local default="$1" mode="$2" + if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "$default"; return; fi + + local hint + [[ "$mode" == "dev" ]] && hint="git clone destination" || hint="project directory" + + if [[ -n "$GUM" ]]; then + local result + result=$("$GUM" input --header "Where should we install DimOS? (${hint})" --placeholder "$default" --value "$default" --header.foreground="255" --header.bold --cursor.foreground="44" /dev/tty + printf " path [%s]: " "$default" >/dev/tty + local result + read -r result Date: Mon, 2 Mar 2026 13:25:20 +0000 Subject: [PATCH 13/46] fix(install): use official nixos.org installer only remove Determinate Systems option. use the official nix installer: sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon simple Y/n confirm before running, with link to nixos.org/download/ --- scripts/install.sh | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 653e7bffe0..443694e36a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -366,35 +366,19 @@ install_nix() { info "Nix is not installed. See: https://nixos.org/download/" printf "\n" - local method - method=$(prompt_select "Which Nix installer would you like to use?" \ - "Determinate Systems (recommended, enables flakes by default)" \ - "Official nixos.org installer (multi-user daemon)" \ - "Skip — I'll install Nix myself later") - - case "$method" in - *Determinate*) - info "installing Nix via Determinate Systems..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] curl -sSf https://install.determinate.systems/nix | sh -s -- install" - HAS_NIX=1; return - fi - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm - ;; - *nixos.org*) - info "installing Nix via official installer..." - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] sh <(curl -L https://nixos.org/nix/install) --daemon" - HAS_NIX=1; return - fi - sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon - ;; - *Skip*|*) - warn "skipping Nix installation — falling back to system packages" - SETUP_METHOD="system" - return - ;; - esac + if ! prompt_confirm "Install Nix now? (official nixos.org multi-user installer)" "yes"; then + warn "skipping Nix installation — falling back to system packages" + SETUP_METHOD="system" + return + fi + + info "installing Nix via official installer..." + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon" + HAS_NIX=1; return + fi + + sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]] && \ . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh From 83c19ea6a446d39b65b67ba1f278513ef5356208 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 13:40:04 +0000 Subject: [PATCH 14/46] fix(install): optional tests, correct pytest cmd, replay guard, ctrl+c fix - prompt 'Run post-install verification tests?' before running anything - pytest uses 'uv run pytest dimos' (matches README, pyproject.toml handles markers) - replay only runs if unitree extras were installed - replace subshells with pushd/popd for uv venv + uv pip install so SIGINT (Ctrl+C) propagates directly to uv process - use global INSTALL_DIR instead of re-computing paths --- scripts/install.sh | 58 ++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 443694e36a..c7470581bb 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -38,6 +38,7 @@ NO_NIX="${DIMOS_NO_NIX:-0}" SKIP_TESTS="${DIMOS_SKIP_TESTS:-0}" HAS_NIX=0 SETUP_METHOD="" +INSTALL_DIR="" GUM="" if [[ -t 1 ]] && command -v tput &>/dev/null && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then @@ -558,6 +559,7 @@ prompt_install_dir() { do_install_library() { local dir="${PROJECT_DIR:-}" if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD" "library"); fi + INSTALL_DIR="$dir" info "library install → ${dir}" run_cmd "mkdir -p '$dir'" @@ -598,11 +600,11 @@ do_install_library() { else info "creating virtual environment (python 3.12)..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv venv --python 3.12" - else (cd "$dir" && uv venv --python 3.12); fi + else pushd "$dir" >/dev/null && uv venv --python 3.12 && popd >/dev/null; fi fi info "installing dimos[${EXTRAS}]..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv pip install 'dimos[${EXTRAS}]'" - else (cd "$dir" && source .venv/bin/activate && uv pip install "dimos[${EXTRAS}]"); fi + else pushd "$dir" >/dev/null && source .venv/bin/activate && uv pip install "dimos[${EXTRAS}]" && popd >/dev/null; fi fi ok "dimos installed in ${dir}" } @@ -610,6 +612,7 @@ do_install_library() { do_install_dev() { local dir="${PROJECT_DIR:-}" if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimos" "dev"); fi + INSTALL_DIR="$dir" info "developer install → ${dir}" if [[ -d "$dir/.git" ]]; then info "existing clone found, pulling latest..." @@ -669,7 +672,7 @@ configure_system() { # ─── verification ───────────────────────────────────────────────────────────── verify_install() { info "verifying installation..." - local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + local dir="$INSTALL_DIR" if [[ "$DRY_RUN" == "1" ]]; then ok "verification skipped (dry-run)"; return; fi local venv_python="${dir}/.venv/bin/python3" [[ ! -f "$venv_python" ]] && { warn "venv not found, skipping verification"; return; } @@ -692,43 +695,52 @@ run_post_install_tests() { [[ "$SKIP_TESTS" == "1" ]] && { dim " skipping tests (--skip-tests)"; return; } [[ "$DRY_RUN" == "1" ]] && { dim "[dry-run] would run post-install tests"; return; } - local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + local dir="$INSTALL_DIR" local venv="${dir}/.venv/bin/activate" [[ ! -f "$venv" ]] && { warn "venv not found, skipping tests"; return; } + if ! prompt_confirm "Run post-install verification tests?" "yes"; then + dim " skipping tests" + return + fi + printf "\n"; info "${BOLD}running post-install verification...${RESET}"; printf "\n" local failures=0 + # pytest (dev mode only) — uses pyproject.toml markers to run fast suite if [[ "$INSTALL_MODE" == "dev" ]]; then - info "pytest (fast tests)..." + info "running fast test suite (uv run pytest dimos)..." local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && python -m pytest dimos -x -q --timeout=60 -k 'not slow and not mujoco' 2>&1 | tail -20") || exit_code=$? + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && uv run pytest dimos 2>&1 | tail -20") || exit_code=$? else - (cd "$dir" && source "$venv" && uv run pytest dimos -x -q --timeout=60 -k "not slow and not mujoco" 2>&1 | tail -20) || exit_code=$? + (cd "$dir" && source "$venv" && uv run pytest dimos 2>&1 | tail -20) || exit_code=$? fi [[ $exit_code -eq 0 ]] && ok "pytest passed ✓" || { warn "pytest: some tests failed"; ((failures++)) || true; } fi - info "replay verification (unitree-go2, 30s timeout)..." - local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? - else - (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? - fi + # replay verification — only if unitree extras were installed + if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; then + info "replay verification (unitree-go2, 30s timeout)..." + local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) + local exit_code=0 + if [[ "$USE_NIX" == "1" ]]; then + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? + else + (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? + fi - if [[ $exit_code -eq 124 ]]; then ok "replay: ran 30s without crash ✓" - elif [[ $exit_code -eq 0 ]]; then ok "replay: completed ✓" - else - if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then - warn "replay failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true + if [[ $exit_code -eq 124 ]]; then ok "replay: ran 30s without crash ✓" + elif [[ $exit_code -eq 0 ]]; then ok "replay: completed ✓" else - warn "replay exited with code ${exit_code} (may be expected headless)" + if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then + warn "replay failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true + else + warn "replay exited with code ${exit_code} (may be expected headless)" + fi fi + rm -f "$log" fi - rm -f "$log" printf "\n" [[ $failures -eq 0 ]] && ok "${BOLD}all checks passed${RESET} 🎉" || warn "${failures} check(s) had issues" @@ -736,7 +748,7 @@ run_post_install_tests() { # ─── quickstart ─────────────────────────────────────────────────────────────── print_quickstart() { - local dir; [[ "$INSTALL_MODE" == "library" ]] && dir="${PROJECT_DIR:-$HOME/dimos-project}" || dir="${PROJECT_DIR:-$HOME/dimos}" + local dir="$INSTALL_DIR" printf "\n %s%s🎉 installation complete!%s\n\n %sget started:%s\n\n" "$BOLD" "$GREEN" "$RESET" "$BOLD" "$RESET" if [[ "$USE_NIX" == "1" ]]; then From a0e2c0a14c0440140ce9ee51764123722b175c61 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 13:48:22 +0000 Subject: [PATCH 15/46] fix(install): show exact test commands, simulation mode, skip when nothing to test - show the exact command being run: 'running: uv run pytest dimos (fast test suite)' - use 'dimos --simulation run unitree-go2' if sim extras installed (MuJoCo, looks cooler) - fall back to 'dimos --replay run unitree-go2' if unitree but no sim - skip test prompt entirely if no testable extras installed (base-only) - say 'no verification tests available' instead of fake 'all checks passed' - report actual count: '2 check(s) passed' not just 'all' --- scripts/install.sh | 61 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index c7470581bb..c466284e43 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -699,51 +699,80 @@ run_post_install_tests() { local venv="${dir}/.venv/bin/activate" [[ ! -f "$venv" ]] && { warn "venv not found, skipping tests"; return; } + # Check if there's anything to test + local has_tests=0 + [[ "$INSTALL_MODE" == "dev" ]] && has_tests=1 + { [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; } && has_tests=1 + + if [[ "$has_tests" == "0" ]]; then + dim " no verification tests available for selected extras" + return + fi + if ! prompt_confirm "Run post-install verification tests?" "yes"; then dim " skipping tests" return fi - printf "\n"; info "${BOLD}running post-install verification...${RESET}"; printf "\n" - local failures=0 + printf "\n"; info "${BOLD}post-install verification${RESET}"; printf "\n" + local failures=0 ran=0 - # pytest (dev mode only) — uses pyproject.toml markers to run fast suite + # pytest (dev mode only) if [[ "$INSTALL_MODE" == "dev" ]]; then - info "running fast test suite (uv run pytest dimos)..." + info "running: ${DIM}uv run pytest dimos${RESET} (fast test suite from pyproject.toml)" local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && uv run pytest dimos 2>&1 | tail -20") || exit_code=$? else (cd "$dir" && source "$venv" && uv run pytest dimos 2>&1 | tail -20) || exit_code=$? fi + ((ran++)) [[ $exit_code -eq 0 ]] && ok "pytest passed ✓" || { warn "pytest: some tests failed"; ((failures++)) || true; } fi - # replay verification — only if unitree extras were installed + # simulation or replay — only if unitree extras installed if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; then - info "replay verification (unitree-go2, 30s timeout)..." - local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? + if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then + info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (MuJoCo simulation, 30s)" + local log; log=$(mktemp /tmp/dimos-sim-XXXXXX.log) + local exit_code=0 + if [[ "$USE_NIX" == "1" ]]; then + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? + else + (cd "$dir" && source "$venv" && timeout 30 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? + fi else - (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? + info "running: ${DIM}dimos --replay run unitree-go2${RESET} (replay mode, 30s)" + local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) + local exit_code=0 + if [[ "$USE_NIX" == "1" ]]; then + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? + else + (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? + fi fi - if [[ $exit_code -eq 124 ]]; then ok "replay: ran 30s without crash ✓" - elif [[ $exit_code -eq 0 ]]; then ok "replay: completed ✓" + ((ran++)) + if [[ $exit_code -eq 124 ]]; then ok "unitree-go2: ran 30s without crash ✓" + elif [[ $exit_code -eq 0 ]]; then ok "unitree-go2: completed ✓" else if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then - warn "replay failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true + warn "unitree-go2 failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true else - warn "replay exited with code ${exit_code} (may be expected headless)" + warn "unitree-go2 exited with code ${exit_code} (may be expected headless)" fi fi rm -f "$log" fi printf "\n" - [[ $failures -eq 0 ]] && ok "${BOLD}all checks passed${RESET} 🎉" || warn "${failures} check(s) had issues" + if [[ $ran -eq 0 ]]; then + dim " no tests were run" + elif [[ $failures -eq 0 ]]; then + ok "${BOLD}${ran} check(s) passed${RESET} 🎉" + else + warn "${failures}/${ran} check(s) had issues" + fi } # ─── quickstart ─────────────────────────────────────────────────────────────── From 87b96885558c80868d8a7c2df1af85255a217a91 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 14:02:36 +0000 Subject: [PATCH 16/46] fix(install): default library dir to dimensional-applications/ library mode now defaults to $PWD/dimensional-applications instead of bare $PWD (which dumped .venv into home dir) --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index c466284e43..8f6d101e09 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -558,7 +558,7 @@ prompt_install_dir() { # ─── installation ───────────────────────────────────────────────────────────── do_install_library() { local dir="${PROJECT_DIR:-}" - if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD" "library"); fi + if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimensional-applications" "library"); fi INSTALL_DIR="$dir" info "library install → ${dir}" run_cmd "mkdir -p '$dir'" From 5714776fd1ce43e7a629fc4fdd730939302c62d2 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 14:11:32 +0000 Subject: [PATCH 17/46] fix(install): Ctrl+C during gum prompts now exits cleanly gum exits 130 on SIGINT but $(command substitution) swallows the exit code, causing the script to continue with empty/garbage selection. fix: check exit code after every gum call. if non-zero (cancelled), die immediately with 'cancelled' message. applies to: - prompt_select (gum choose) - prompt_multi (gum choose --no-limit) - prompt_confirm (gum confirm, distinguish code 1=no vs 130=cancel) - prompt_install_dir (gum input) - fallback read prompts also handle EOF/cancel --- scripts/install.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 8f6d101e09..1f77b2dca0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -99,11 +99,13 @@ prompt_select() { if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "${options[0]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - "$GUM" choose --header "$msg" \ + local result + result=$("$GUM" choose --header "$msg" \ --cursor "● " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; die "cancelled"; } + echo "$result" else printf "%s%s%s\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -112,7 +114,7 @@ prompt_select() { ((i++)) done printf " choice [1]: " >/dev/tty - local choice; read -r choice /dev/tty; die "cancelled"; } choice="${choice:-1}" local idx=$((choice - 1)) if [[ $idx -ge 0 ]] && [[ $idx -lt ${#options[@]} ]]; then @@ -129,14 +131,15 @@ prompt_multi() { if [[ "$NON_INTERACTIVE" == "1" ]]; then printf '%s\n' "${options[@]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local selected_csv + local selected_csv result selected_csv=$(IFS=,; echo "${options[*]}") - "$GUM" choose --no-limit --header "$msg" \ + result=$("$GUM" choose --no-limit --header "$msg" \ --cursor "❯ " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ --selected="$selected_csv" \ - "${options[@]}" /dev/tty; die "cancelled"; } + echo "$result" else printf "%s%s%s (comma-separated, enter for all)\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -164,6 +167,10 @@ prompt_confirm() { if [[ -n "$GUM" ]]; then local flag; [[ "$default" == "yes" ]] && flag="--default=yes" || flag="--default=no" "$GUM" confirm "$msg" $flag --prompt.foreground="44" --selected.background="44" /dev/tty; die "cancelled"; } + return $ec else local yn if [[ "$default" == "yes" ]]; then printf "%s [Y/n] " "$msg" >/dev/tty From feeb812aed814c9a084ad69c65366d6abc6e413b Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 15:44:49 +0000 Subject: [PATCH 18/46] fix(install): add libgl1 libegl1 to system deps open3d and mujoco need libGL.so.1 which comes from libgl1. without it, dimos CLI crashes on import with: OSError: libGL.so.1: cannot open shared object file --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 1f77b2dca0..2258145489 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -447,7 +447,7 @@ install_system_deps() { ubuntu|wsl) # Check if all packages are already installed local needed="" - local all_pkgs="curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" + local all_pkgs="curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit libgl1 libegl1" for pkg in $all_pkgs; do if ! dpkg -s "$pkg" &>/dev/null; then needed+=" $pkg" From eb005c4bd60d21cda9952f8de4163718f97bdc50 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 15:49:28 +0000 Subject: [PATCH 19/46] docs: add headless Ubuntu note for libgl1/libegl1 headless machines (EC2, Docker, WSL2, CI) don't have OpenGL libs. open3d and mujoco crash without libgl1. nix users are fine (flake already provides libGL, libGLU, mesa). --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e07d623300..7cb720f372 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,15 @@ uv sync --all-extras --no-extra dds uv run pytest dimos ``` +> **Headless / Server Ubuntu (EC2, Docker, WSL2, CI)** +> +> If you're running on a headless machine without a display server, you'll need OpenGL libraries that aren't installed by default: +> ```sh +> sudo apt-get install -y libgl1 libegl1 +> ``` +> Without these, Open3D and MuJoCo will fail with `OSError: libGL.so.1: cannot open shared object file`. +> Nix users (`nix develop`) don't need this — the flake already provides `libGL`, `libGLU`, and `mesa`. + ## Multi Language Support Python is our glue and prototyping language, but we support many languages via LCM interop. From f980a62bb8629fd035525e391549bfbd5207c35e Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 16:44:13 +0000 Subject: [PATCH 20/46] fix(install): no pre-selection, proper Ctrl+C exit, 60s smoke tests - prompt_multi: remove --selected flag so nothing is pre-checked by default (users must explicitly space-toggle each option) - Ctrl+C at any prompt now kills the entire installer, not just the current step. prompt functions exit with code 130, callers check with || die - smoke test timeout bumped from 30s to 60s (simulation startup is slow) - labels say 'smoke test, 60s timeout' not implying a full test run - header says 'space to toggle, enter to confirm' for multi-select --- scripts/install.sh | 48 +++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 2258145489..6c49487e40 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -53,6 +53,9 @@ ok() { printf "%s✓%s %s\n" "$GREEN" "$RESET" "$*"; } warn() { printf "%s⚠%s %s\n" "$YELLOW" "$RESET" "$*" >&2; } err() { printf "%s✗%s %s\n" "$RED" "$RESET" "$*" >&2; } die() { err "$@"; exit 1; } +# Cancelled exit code — used by prompt functions to signal Ctrl+C +readonly CANCELLED_EXIT=130 +check_cancel() { [[ $? -eq $CANCELLED_EXIT ]] && { err "cancelled"; exit 1; }; return 0; } dim() { printf "%s%s%s\n" "$DIM" "$*" "$RESET"; } run_cmd() { @@ -104,7 +107,7 @@ prompt_select() { --cursor "● " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; die "cancelled"; } + "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } echo "$result" else printf "%s%s%s\n" "$BOLD" "$msg" "$RESET" >/dev/tty @@ -114,7 +117,7 @@ prompt_select() { ((i++)) done printf " choice [1]: " >/dev/tty - local choice; read -r choice /dev/tty; die "cancelled"; } + local choice; read -r choice /dev/tty; exit $CANCELLED_EXIT; } choice="${choice:-1}" local idx=$((choice - 1)) if [[ $idx -ge 0 ]] && [[ $idx -lt ${#options[@]} ]]; then @@ -131,14 +134,12 @@ prompt_multi() { if [[ "$NON_INTERACTIVE" == "1" ]]; then printf '%s\n' "${options[@]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local selected_csv result - selected_csv=$(IFS=,; echo "${options[*]}") - result=$("$GUM" choose --no-limit --header "$msg" \ + local result + result=$("$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ --cursor "❯ " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - --selected="$selected_csv" \ - "${options[@]}" /dev/tty; die "cancelled"; } + "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } echo "$result" else printf "%s%s%s (comma-separated, enter for all)\n" "$BOLD" "$msg" "$RESET" >/dev/tty @@ -414,7 +415,7 @@ prompt_setup_method() { else choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (recommended)" \ - "Install Nix — nix develop (reproducible, installs Nix first)") + "Install Nix — nix develop (reproducible, installs Nix first)") || die "cancelled" fi case "$choice" in @@ -499,7 +500,7 @@ prompt_install_mode() { local choice choice=$(prompt_select "How do you want to use DimOS?" \ "Library — pip install into your project (recommended)" \ - "Developer — git clone + editable install (contributors)") + "Developer — git clone + editable install (contributors)") || die "cancelled" case "$choice" in *Library*) INSTALL_MODE="library";; *) INSTALL_MODE="dev";; esac } @@ -508,15 +509,18 @@ prompt_extras() { if [[ "$INSTALL_MODE" == "dev" ]]; then EXTRAS="all"; info "developer mode: all extras (except dds)"; return; fi local -a platform_sel=() feature_sel=() - while IFS= read -r line; do [[ -n "$line" ]] && platform_sel+=("$line"); done < <(prompt_multi \ + local _platforms _features + _platforms=$(prompt_multi \ "Which robot platforms will you use?" \ - "Unitree (Go2, G1, B1)" "Drone (Mavlink / DJI)" "Manipulators (xArm, Piper, OpenARMs)") + "Unitree (Go2, G1, B1)" "Drone (Mavlink / DJI)" "Manipulators (xArm, Piper, OpenARMs)") || die "cancelled" + while IFS= read -r line; do [[ -n "$line" ]] && platform_sel+=("$line"); done <<< "$_platforms" - while IFS= read -r line; do [[ -n "$line" ]] && feature_sel+=("$line"); done < <(prompt_multi \ + _features=$(prompt_multi \ "Which features do you need?" \ "AI Agents (LangChain, voice control)" "Perception (object detection, VLMs)" \ "Visualization (Rerun 3D viewer)" "Simulation (MuJoCo)" \ - "Web Interface (FastAPI dashboard)" "Misc (extra ML models)") + "Web Interface (FastAPI dashboard)" "Misc (extra ML models)") || die "cancelled" + while IFS= read -r line; do [[ -n "$line" ]] && feature_sel+=("$line"); done <<< "$_features" local -a extras_list=() for p in "${platform_sel[@]}"; do @@ -565,7 +569,7 @@ prompt_install_dir() { # ─── installation ───────────────────────────────────────────────────────────── do_install_library() { local dir="${PROJECT_DIR:-}" - if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimensional-applications" "library"); fi + if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimensional-applications" "library") || die "cancelled"; fi INSTALL_DIR="$dir" info "library install → ${dir}" run_cmd "mkdir -p '$dir'" @@ -618,7 +622,7 @@ do_install_library() { do_install_dev() { local dir="${PROJECT_DIR:-}" - if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimos" "dev"); fi + if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimos" "dev") || die "cancelled"; fi INSTALL_DIR="$dir" info "developer install → ${dir}" if [[ -d "$dir/.git" ]]; then @@ -740,27 +744,27 @@ run_post_install_tests() { # simulation or replay — only if unitree extras installed if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; then if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then - info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (MuJoCo simulation, 30s)" + info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (smoke test, 60s timeout)" local log; log=$(mktemp /tmp/dimos-sim-XXXXXX.log) local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? else - (cd "$dir" && source "$venv" && timeout 30 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? + (cd "$dir" && source "$venv" && timeout 60 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? fi else - info "running: ${DIM}dimos --replay run unitree-go2${RESET} (replay mode, 30s)" + info "running: ${DIM}dimos --replay run unitree-go2${RESET} (smoke test, 60s timeout)" local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 30 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? else - (cd "$dir" && source "$venv" && timeout 30 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? + (cd "$dir" && source "$venv" && timeout 60 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? fi fi ((ran++)) - if [[ $exit_code -eq 124 ]]; then ok "unitree-go2: ran 30s without crash ✓" + if [[ $exit_code -eq 124 ]]; then ok "unitree-go2: ran 60s without crash ✓" elif [[ $exit_code -eq 0 ]]; then ok "unitree-go2: completed ✓" else if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then From cf620f941c9906887b068ee99ad7844d2966a5f6 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 18:29:54 +0000 Subject: [PATCH 21/46] fix(install): remove pytest from post-install, smoke test only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pytest hangs on fresh installs — tests need sysctl, hardware, LCM channels that don't exist. 878 tests is not a smoke check. post-install now only runs: - dimos --simulation run unitree-go2 (if sim+unitree extras) - dimos --replay run unitree-go2 (if unitree but no sim) - nothing if no unitree extras installed verify_install() already checks python import + CLI availability which is the correct post-install validation. --- scripts/install.sh | 82 +++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 56 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 6c49487e40..2270395f83 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -710,80 +710,50 @@ run_post_install_tests() { local venv="${dir}/.venv/bin/activate" [[ ! -f "$venv" ]] && { warn "venv not found, skipping tests"; return; } - # Check if there's anything to test - local has_tests=0 - [[ "$INSTALL_MODE" == "dev" ]] && has_tests=1 - { [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; } && has_tests=1 - - if [[ "$has_tests" == "0" ]]; then - dim " no verification tests available for selected extras" + # Only offer smoke test if unitree + sim extras installed + if ! { [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; }; then + dim " no smoke tests available for selected extras" return fi - if ! prompt_confirm "Run post-install verification tests?" "yes"; then - dim " skipping tests" + if ! prompt_confirm "Run a quick smoke test? (starts unitree-go2 for 60s)" "yes"; then + dim " skipping smoke test" return fi - printf "\n"; info "${BOLD}post-install verification${RESET}"; printf "\n" - local failures=0 ran=0 + printf "\n"; info "${BOLD}post-install smoke test${RESET}"; printf "\n" - # pytest (dev mode only) - if [[ "$INSTALL_MODE" == "dev" ]]; then - info "running: ${DIM}uv run pytest dimos${RESET} (fast test suite from pyproject.toml)" + if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then + info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (smoke test, 60s timeout)" + local log; log=$(mktemp /tmp/dimos-sim-XXXXXX.log) local exit_code=0 if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && uv run pytest dimos 2>&1 | tail -20") || exit_code=$? + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? else - (cd "$dir" && source "$venv" && uv run pytest dimos 2>&1 | tail -20) || exit_code=$? - fi - ((ran++)) - [[ $exit_code -eq 0 ]] && ok "pytest passed ✓" || { warn "pytest: some tests failed"; ((failures++)) || true; } - fi - - # simulation or replay — only if unitree extras installed - if [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; then - if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then - info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (smoke test, 60s timeout)" - local log; log=$(mktemp /tmp/dimos-sim-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? - else - (cd "$dir" && source "$venv" && timeout 60 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? - fi - else - info "running: ${DIM}dimos --replay run unitree-go2${RESET} (smoke test, 60s timeout)" - local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? - else - (cd "$dir" && source "$venv" && timeout 60 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? - fi + (cd "$dir" && source "$venv" && timeout 60 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? fi - - ((ran++)) - if [[ $exit_code -eq 124 ]]; then ok "unitree-go2: ran 60s without crash ✓" - elif [[ $exit_code -eq 0 ]]; then ok "unitree-go2: completed ✓" + else + info "running: ${DIM}dimos --replay run unitree-go2${RESET} (smoke test, 60s timeout)" + local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) + local exit_code=0 + if [[ "$USE_NIX" == "1" ]]; then + (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? else - if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then - warn "unitree-go2 failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done; ((failures++)) || true - else - warn "unitree-go2 exited with code ${exit_code} (may be expected headless)" - fi + (cd "$dir" && source "$venv" && timeout 60 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? fi - rm -f "$log" fi printf "\n" - if [[ $ran -eq 0 ]]; then - dim " no tests were run" - elif [[ $failures -eq 0 ]]; then - ok "${BOLD}${ran} check(s) passed${RESET} 🎉" + if [[ $exit_code -eq 124 ]]; then ok "smoke test: ran 60s without crash ✓" + elif [[ $exit_code -eq 0 ]]; then ok "smoke test: completed ✓" else - warn "${failures}/${ran} check(s) had issues" + if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then + warn "smoke test failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done + else + warn "smoke test exited with code ${exit_code} (may be expected headless)" + fi fi + rm -f "$log" } # ─── quickstart ─────────────────────────────────────────────────────────────── From 48250bd7b595ae788e5375e0eae2981cffface0c Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 18:57:08 +0000 Subject: [PATCH 22/46] fix(install): Ctrl+C works during smoke test + uv sync - smoke test: no subshells, no log redirection. runs dimos directly in foreground so SIGINT propagates immediately. output visible to user. - exit code 130 (Ctrl+C) shows 'stopped by user' instead of error - dev install: uv sync uses pushd/popd instead of subshell - says 'Ctrl+C to stop' so user knows they can abort --- scripts/install.sh | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 2270395f83..842912b18a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -640,7 +640,7 @@ do_install_dev() { else info "syncing dependencies..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv sync --all-extras --no-extra dds" - else (cd "$dir" && uv sync --all-extras --no-extra dds); fi + else pushd "$dir" >/dev/null && uv sync --all-extras --no-extra dds && popd >/dev/null; fi fi ok "developer environment ready in ${dir}" } @@ -710,7 +710,7 @@ run_post_install_tests() { local venv="${dir}/.venv/bin/activate" [[ ! -f "$venv" ]] && { warn "venv not found, skipping tests"; return; } - # Only offer smoke test if unitree + sim extras installed + # Only offer smoke test if unitree extras installed if ! { [[ "$EXTRAS" == *"unitree"* ]] || [[ "$EXTRAS" == "all" ]]; }; then dim " no smoke tests available for selected extras" return @@ -723,37 +723,28 @@ run_post_install_tests() { printf "\n"; info "${BOLD}post-install smoke test${RESET}"; printf "\n" + local cmd if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then - info "running: ${DIM}dimos --simulation run unitree-go2${RESET} (smoke test, 60s timeout)" - local log; log=$(mktemp /tmp/dimos-sim-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --simulation run unitree-go2") >"$log" 2>&1 || exit_code=$? - else - (cd "$dir" && source "$venv" && timeout 60 dimos --simulation run unitree-go2) >"$log" 2>&1 || exit_code=$? - fi + cmd="dimos --simulation run unitree-go2" else - info "running: ${DIM}dimos --replay run unitree-go2${RESET} (smoke test, 60s timeout)" - local log; log=$(mktemp /tmp/dimos-replay-XXXXXX.log) - local exit_code=0 - if [[ "$USE_NIX" == "1" ]]; then - (cd "$dir" && nix develop --command bash -c "source .venv/bin/activate && timeout 60 dimos --replay run unitree-go2") >"$log" 2>&1 || exit_code=$? - else - (cd "$dir" && source "$venv" && timeout 60 dimos --replay run unitree-go2) >"$log" 2>&1 || exit_code=$? - fi + cmd="dimos --replay run unitree-go2" fi + info "running: ${DIM}${cmd}${RESET} (Ctrl+C to stop)" + + local exit_code=0 + pushd "$dir" >/dev/null + source "$venv" + timeout 60 $cmd || exit_code=$? + popd >/dev/null + printf "\n" if [[ $exit_code -eq 124 ]]; then ok "smoke test: ran 60s without crash ✓" + elif [[ $exit_code -eq 130 ]] || [[ $exit_code -eq 137 ]]; then + ok "smoke test: stopped by user" elif [[ $exit_code -eq 0 ]]; then ok "smoke test: completed ✓" - else - if grep -qi "Traceback\|ModuleNotFoundError\|ImportError" "$log" 2>/dev/null; then - warn "smoke test failed (exit ${exit_code})"; tail -5 "$log" | while IFS= read -r l; do dim " $l"; done - else - warn "smoke test exited with code ${exit_code} (may be expected headless)" - fi + else warn "smoke test exited with code ${exit_code} (may be expected headless)" fi - rm -f "$log" } # ─── quickstart ─────────────────────────────────────────────────────────────── From 4e54bd97e88e1975f139266134972f39a0f0f06b Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 19:00:23 +0000 Subject: [PATCH 23/46] fix(install): actually fix Ctrl+C everywhere root cause: bash defers INT trap processing until foreground command finishes. three fixes: 1. move 'trap exit-130 INT' to top of script (line 17) so it's active during ALL phases, not just main() 2. smoke test: run dimos in background + wait, so bash processes INT trap immediately during wait 3. prompt_install_dir: add missing cancel handler on gum input (was silently dropping Ctrl+C and continuing with empty dir) --- scripts/install.sh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 842912b18a..79fe020b29 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,6 +13,9 @@ # set -euo pipefail +# ─── signal handling (must be early so Ctrl+C works everywhere) ──────────────── +trap 'printf "\n"; exit 130' INT + # If piped from curl (stdin is not a TTY and $0 is the shell), # save to temp file and re-execute so interactive prompts get proper TTY input. if [ ! -t 0 ] && { [ "$0" = "bash" ] || [ "$0" = "-bash" ] || [ "$0" = "/bin/bash" ] || [ "$0" = "/usr/bin/bash" ] || [ "$0" = "sh" ] || [ "$0" = "/bin/sh" ]; }; then @@ -553,7 +556,7 @@ prompt_install_dir() { if [[ -n "$GUM" ]]; then local result - result=$("$GUM" input --header "Where should we install DimOS? (${hint})" --placeholder "$default" --value "$default" --header.foreground="255" --header.bold --cursor.foreground="44" /dev/tty; exit $CANCELLED_EXIT; } [[ -z "$result" ]] && result="$default" echo "$result" else @@ -735,7 +738,10 @@ run_post_install_tests() { local exit_code=0 pushd "$dir" >/dev/null source "$venv" - timeout 60 $cmd || exit_code=$? + $cmd & + local pid=$! + # wait allows bash to process INT trap immediately + wait $pid || exit_code=$? popd >/dev/null printf "\n" @@ -773,13 +779,11 @@ print_quickstart() { printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" } -# ─── signal handling ────────────────────────────────────────────────────────── -_interrupted=0 -handle_sigint() { _interrupted=1; printf "\n"; warn "interrupted"; exit 130; } -trap handle_sigint INT +# ─── cleanup ───────────────────────────────────────────────────────────────── cleanup() { local ec=$? - [[ $ec -ne 0 ]] && [[ "$_interrupted" != "1" ]] && { printf "\n"; err "installation failed (exit ${ec})"; err "help: https://github.com/dimensionalOS/dimos/issues"; } + [[ $ec -eq 130 ]] && { warn "interrupted"; } + [[ $ec -ne 0 ]] && [[ $ec -ne 130 ]] && { printf "\n"; err "installation failed (exit ${ec})"; err "help: https://github.com/dimensionalOS/dimos/issues"; } } trap cleanup EXIT From 2719f4be78fb15ef0ed92dc228aa35a035a38ac9 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 19:52:41 +0000 Subject: [PATCH 24/46] fix(install): ask before installing deps, show exact commands both dev and library installs now: 1. show exactly what command will run (uv sync / uv pip install) 2. show estimated package count 3. ask 'Install dependencies now?' Y/n 4. if declined, print the manual commands to run later users who just want the clone can skip the 10-min pip install --- scripts/install.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 79fe020b29..610a17cc40 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -603,6 +603,24 @@ do_install_library() { (cd "$dir" && nix develop --command bash -c "set -euo pipefail; [[ \"\${SKIP_VENV:-}\" != 1 ]] && ${venv_flag} uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'") fi else + # Show what we're about to do and ask + printf "\n" + info "next step: create .venv and install Python packages" + dim " will run: ${CYAN}cd ${dir} && uv venv --python 3.12 && uv pip install \"dimos[${EXTRAS}]\"${RESET}" + printf "\n" + + if ! prompt_confirm "Install dependencies now?" "yes"; then + dim " skipping dependency install" + printf "\n" + dim " to install later, run:" + dim " cd ${dir}" + dim " uv venv --python 3.12" + dim " source .venv/bin/activate" + dim " uv pip install \"dimos[${EXTRAS}]\"" + ok "project directory created at ${dir}" + return + fi + if [[ -d "${dir}/.venv" ]] && [[ "$DRY_RUN" != "1" ]]; then warn "existing .venv found in ${dir}/" if prompt_confirm "Replace existing virtual environment?" "no"; then @@ -628,6 +646,8 @@ do_install_dev() { if [[ -z "$dir" ]]; then dir=$(prompt_install_dir "$PWD/dimos" "dev") || die "cancelled"; fi INSTALL_DIR="$dir" info "developer install → ${dir}" + + # Step 1: Clone or pull if [[ -d "$dir/.git" ]]; then info "existing clone found, pulling latest..." run_cmd "cd '$dir' && git pull --rebase origin $GIT_BRANCH" @@ -635,13 +655,37 @@ do_install_dev() { info "cloning dimos (branch: ${GIT_BRANCH})..." run_cmd "GIT_LFS_SKIP_SMUDGE=1 git clone -b $GIT_BRANCH https://github.com/dimensionalOS/dimos.git '$dir'" fi + + # Step 2: Install dependencies (ask first — this is the heavy part) + printf "\n" + info "next step: create .venv and install all Python dependencies" + if [[ "$USE_NIX" == "1" ]]; then + dim " will run: ${CYAN}nix develop --command bash -c \"uv sync --all-extras --no-extra dds\"${RESET}" + else + dim " will run: ${CYAN}cd ${dir} && uv sync --all-extras --no-extra dds${RESET}" + fi + dim " this creates a .venv and installs ~430 packages (may take several minutes)" + printf "\n" + + if ! prompt_confirm "Install dependencies now?" "yes"; then + dim " skipping dependency install" + printf "\n" + dim " to install later, run:" + dim " cd ${dir}" + if [[ "$USE_NIX" == "1" ]]; then + dim " nix develop --command bash -c \"uv sync --all-extras --no-extra dds\"" + else + dim " uv sync --all-extras --no-extra dds" + fi + ok "repository cloned to ${dir}" + return + fi + if [[ "$USE_NIX" == "1" ]]; then verify_nix_develop "$dir" - info "syncing via nix develop..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] nix develop + uv sync --all-extras --no-extra dds" else (cd "$dir" && nix develop --command bash -c "set -euo pipefail && uv sync --all-extras --no-extra dds"); fi else - info "syncing dependencies..." if [[ "$DRY_RUN" == "1" ]]; then dim "[dry-run] uv sync --all-extras --no-extra dds" else pushd "$dir" >/dev/null && uv sync --all-extras --no-extra dds && popd >/dev/null; fi fi From 7a8e9a1d299db7fb52bb0d740bf4e81ebbc231b1 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 20:23:17 +0000 Subject: [PATCH 25/46] fix(install): show correct nix commands, ask before install in both paths - library install now shows the correct command for both nix and non-nix paths before asking 'Install dependencies now?' - nix path shows: nix develop --command bash -c 'uv venv && uv pip install' - manual fallback for nix users includes 'nix develop' first - confirmation prompt was missing entirely from nix library path --- scripts/install.sh | 48 +++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 610a17cc40..e7b0b9f36d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -577,6 +577,36 @@ do_install_library() { info "library install → ${dir}" run_cmd "mkdir -p '$dir'" + # Show what we're about to do + printf "\n" + info "next step: create .venv and install Python packages" + if [[ "$USE_NIX" == "1" ]]; then + dim " will run: ${CYAN}cd ${dir} && nix develop --command bash -c \"uv venv && uv pip install 'dimos[${EXTRAS}]'\"${RESET}" + dim " nix develop provides system libraries (libGL, mesa, etc.)" + else + dim " will run: ${CYAN}cd ${dir} && uv venv --python 3.12 && uv pip install \"dimos[${EXTRAS}]\"${RESET}" + fi + printf "\n" + + if ! prompt_confirm "Install dependencies now?" "yes"; then + dim " skipping dependency install" + printf "\n" + dim " to install later, run:" + dim " cd ${dir}" + if [[ "$USE_NIX" == "1" ]]; then + dim " nix develop" + dim " uv venv --python 3.12" + dim " source .venv/bin/activate" + dim " uv pip install \"dimos[${EXTRAS}]\"" + else + dim " uv venv --python 3.12" + dim " source .venv/bin/activate" + dim " uv pip install \"dimos[${EXTRAS}]\"" + fi + ok "project directory created at ${dir}" + return + fi + if [[ "$USE_NIX" == "1" ]]; then info "downloading flake files..." local base="https://raw.githubusercontent.com/dimensionalOS/dimos/refs/heads/${GIT_BRANCH}" @@ -603,24 +633,6 @@ do_install_library() { (cd "$dir" && nix develop --command bash -c "set -euo pipefail; [[ \"\${SKIP_VENV:-}\" != 1 ]] && ${venv_flag} uv venv --python 3.12; source .venv/bin/activate; uv pip install 'dimos[${EXTRAS}]'") fi else - # Show what we're about to do and ask - printf "\n" - info "next step: create .venv and install Python packages" - dim " will run: ${CYAN}cd ${dir} && uv venv --python 3.12 && uv pip install \"dimos[${EXTRAS}]\"${RESET}" - printf "\n" - - if ! prompt_confirm "Install dependencies now?" "yes"; then - dim " skipping dependency install" - printf "\n" - dim " to install later, run:" - dim " cd ${dir}" - dim " uv venv --python 3.12" - dim " source .venv/bin/activate" - dim " uv pip install \"dimos[${EXTRAS}]\"" - ok "project directory created at ${dir}" - return - fi - if [[ -d "${dir}/.venv" ]] && [[ "$DRY_RUN" != "1" ]]; then warn "existing .venv found in ${dir}/" if prompt_confirm "Replace existing virtual environment?" "no"; then From dd909de6237ee5a2fba8db137f0b1474fa88682a Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 20:27:50 +0000 Subject: [PATCH 26/46] fix(install): actually use nix when user selects nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SETUP_METHOD was set to 'nix' but USE_NIX stayed 0 — every subsequent code path checks USE_NIX, not SETUP_METHOD. user selected nix, nix got installed, then the entire install ran the non-nix path anyway. now sets USE_NIX=1 in all three places SETUP_METHOD becomes 'nix' --- scripts/install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index e7b0b9f36d..1d6c90ef70 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -404,8 +404,8 @@ install_nix() { prompt_setup_method() { if [[ "$NO_NIX" == "1" ]]; then SETUP_METHOD="system"; return; fi if [[ "$USE_NIX" == "1" ]]; then - [[ "$HAS_NIX" == "1" ]] && { ok "Nix detected — using for system deps"; SETUP_METHOD="nix"; return; } - install_nix; SETUP_METHOD="nix"; return + [[ "$HAS_NIX" == "1" ]] && { ok "Nix detected — using for system deps"; SETUP_METHOD="nix"; USE_NIX=1; return; } + install_nix; SETUP_METHOD="nix"; USE_NIX=1; return fi local choice @@ -424,7 +424,7 @@ prompt_setup_method() { case "$choice" in *Nix*|*nix*) [[ "$HAS_NIX" != "1" ]] && install_nix - SETUP_METHOD="nix"; ok "will use Nix for system dependencies" ;; + SETUP_METHOD="nix"; USE_NIX=1; ok "will use Nix for system dependencies" ;; *) SETUP_METHOD="system"; ok "will use system package manager" ;; esac From 4e8f34c98d8254ab60b7ee1c738129b071ed9d58 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 20:33:57 +0000 Subject: [PATCH 27/46] feat(uninstall): add Nix removal + dimensional-applications dir - stops nix-daemon, removes systemd units, /nix, /etc/nix, profile.d scripts, nixbld users/group, ~/.nix-* and ~/.config/nix - also removes ~/dimensional-applications (library install default dir) - all behind should_remove prompts (or --all to skip) --- scripts/uninstall.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 518c25dbc1..a93d80a2b9 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -113,6 +113,38 @@ if [[ -f /etc/sysctl.d/99-dimos.conf ]]; then fi fi +# ─── Nix ───────────────────────────────────────────────────────────────────── +if [[ -d /nix ]]; then + if should_remove "Remove Nix completely (/nix, daemon, build users)?"; then + if [[ "$DRY_RUN" == "1" ]]; then + dim "[dry-run] stop nix-daemon" + dim "[dry-run] rm -rf /nix /etc/nix /etc/profile.d/nix*.sh" + dim "[dry-run] remove nixbld users + group" + dim "[dry-run] rm -rf ~/.nix-profile ~/.nix-defexpr ~/.nix-channels ~/.config/nix" + else + sudo systemctl stop nix-daemon.socket nix-daemon.service 2>/dev/null || true + sudo systemctl disable nix-daemon.socket nix-daemon.service 2>/dev/null || true + sudo rm -f /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket + sudo rm -f /etc/systemd/system/sockets.target.wants/nix-daemon.socket + sudo systemctl daemon-reload 2>/dev/null || true + sudo rm -rf /nix /etc/nix + sudo rm -f /etc/profile.d/nix.sh /etc/profile.d/nix-daemon.sh + rm -rf "$HOME/.nix-profile" "$HOME/.nix-defexpr" "$HOME/.nix-channels" "$HOME/.config/nix" + for i in $(seq 1 32); do sudo userdel "nixbld$i" 2>/dev/null || true; done + sudo groupdel nixbld 2>/dev/null || true + ok "Nix removed (open a new shell to clear environment)" + fi + fi +fi + +# ─── dimensional-applications (library install default dir) ─────────────────── +if [[ -d "$HOME/dimensional-applications" ]]; then + local_size="$(du -sh "$HOME/dimensional-applications" 2>/dev/null | cut -f1 || echo "?")" + if should_remove "Remove ~/dimensional-applications/ (${local_size})?"; then + do_rm "$HOME/dimensional-applications" + fi +fi + # ─── gum (temp install from installer) ─────────────────────────────────────── for tmpgum in /tmp/gum-install.*/gum*; do if [[ -d "$(dirname "$tmpgum")" ]]; then From 98b6bcaf1bfeb2b2af1b1e92466f87aa5d5bc0a0 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 20:55:51 +0000 Subject: [PATCH 28/46] fix(uninstall): restore shell rc backups from nix nix installer backs up /etc/bash.bashrc to .backup-before-nix before modifying it. if the backup file exists on reinstall, nix refuses to proceed. uninstall now restores these backups. --- scripts/uninstall.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index a93d80a2b9..c99888f0fc 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -130,6 +130,12 @@ if [[ -d /nix ]]; then sudo rm -rf /nix /etc/nix sudo rm -f /etc/profile.d/nix.sh /etc/profile.d/nix-daemon.sh rm -rf "$HOME/.nix-profile" "$HOME/.nix-defexpr" "$HOME/.nix-channels" "$HOME/.config/nix" + # Restore shell rc backups that Nix created (prevents reinstall failures) + for rc in /etc/bashrc /etc/bash.bashrc /etc/zshrc; do + if [[ -f "${rc}.backup-before-nix" ]]; then + sudo mv "${rc}.backup-before-nix" "$rc" + fi + done for i in $(seq 1 32); do sudo userdel "nixbld$i" 2>/dev/null || true; done sudo groupdel nixbld 2>/dev/null || true ok "Nix removed (open a new shell to clear environment)" From 5a0874ea8147d4292c6834cbc8b42ffb240c8100 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 20:57:47 +0000 Subject: [PATCH 29/46] fix(install): fix nix installer getcwd + TTY + stale backup errors - cd $HOME at script start (CWD may be deleted dir after uninstall) - run nix installer from /tmp with /dev/null || cd /tmp + # ─── signal handling (must be early so Ctrl+C works everywhere) ──────────────── trap 'printf "\n"; exit 130' INT @@ -390,7 +393,15 @@ install_nix() { HAS_NIX=1; return fi - sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon + # Clean stale Nix backup files that block reinstall + for rc in /etc/bashrc /etc/bash.bashrc /etc/zshrc; do + if [[ -f "${rc}.backup-before-nix" ]]; then + sudo mv "${rc}.backup-before-nix" "$rc" + fi + done + + # Run from /tmp (CWD may not exist) with TTY so nix installer can prompt + (cd /tmp && sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon) Date: Mon, 2 Mar 2026 21:10:49 +0000 Subject: [PATCH 30/46] fix(install): tell nix users to open new terminal or exec bash -l nix modifies /etc/bash.bashrc but current shell doesn't pick it up. quickstart now says 'open a new terminal first' and offers 'exec bash -l' as alternative to reload the current shell. --- scripts/install.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 8193ed1687..7ce6163135 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -841,7 +841,10 @@ print_quickstart() { if [[ "$INSTALL_MODE" == "dev" ]]; then printf " %s# tests%s\n uv run pytest dimos\n\n %s# type check%s\n uv run mypy dimos\n\n" "$DIM" "$RESET" "$DIM" "$RESET" fi - [[ "$USE_NIX" == "1" ]] && printf " %s⚠%s always run 'nix develop' before working with DimOS\n\n" "$YELLOW" "$RESET" + if [[ "$USE_NIX" == "1" ]]; then + printf " %s⚠%s open a %snew terminal%s first, then run 'nix develop' before working with DimOS\n" "$YELLOW" "$RESET" "$BOLD" "$RESET" + printf " %s⚠%s or run: %sexec bash -l%s to reload this shell\n\n" "$YELLOW" "$RESET" "$CYAN" "$RESET" + fi printf " %sdocs:%s https://github.com/dimensionalOS/dimos\n" "$DIM" "$RESET" printf " %sdiscord:%s https://discord.gg/dimos\n\n" "$DIM" "$RESET" } From 3f4aa85ad3561bd3b9c7b3b25f74935bf4af8668 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 21:13:18 +0000 Subject: [PATCH 31/46] fix(install): clarify nix develop + venv activation in quickstart users thought 'source .venv/bin/activate' runs outside nix shell. now shows one-liner and two-step version making it clear the venv activation happens INSIDE the nix develop shell. --- scripts/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 7ce6163135..4426b0514f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -826,7 +826,8 @@ print_quickstart() { printf "\n %s%s🎉 installation complete!%s\n\n %sget started:%s\n\n" "$BOLD" "$GREEN" "$RESET" "$BOLD" "$RESET" if [[ "$USE_NIX" == "1" ]]; then - printf " %s# enter nix shell + activate python%s\n cd %s && nix develop\n source .venv/bin/activate\n\n" "$DIM" "$RESET" "$dir" + printf " %s# enter nix shell + activate python%s\n cd %s && nix develop --command bash -c 'source .venv/bin/activate && exec bash'\n\n" "$DIM" "$RESET" "$dir" + printf " %s# or in two steps:%s\n cd %s && nix develop\n %s# then inside nix shell:%s\n source .venv/bin/activate\n\n" "$DIM" "$RESET" "$dir" "$DIM" "$RESET" else printf " %s# activate the environment%s\n cd %s && source .venv/bin/activate\n\n" "$DIM" "$RESET" "$dir" fi From 92ef46b7583f0454e6a59b1b73e06c2d1869505e Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 21:19:36 +0000 Subject: [PATCH 32/46] fix(install): nix only for dev mode, not library installs nix develop sets LD_LIBRARY_PATH to nix's libs (glibc 2.38+) which conflicts with pip wheels compiled against system glibc (2.35 on ubuntu 22.04). numpy, open3d, etc all crash with GLIBC_2.38 not found. nix only works when uv sync runs INSIDE nix develop (dev mode). library mode now falls back to system packages if nix was selected, with a clear warning explaining why. --- scripts/install.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 4426b0514f..822115af5b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -423,13 +423,13 @@ prompt_setup_method() { if [[ "$HAS_NIX" == "1" ]]; then choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (simpler)" \ - "Nix — nix develop (reproducible)") + "Nix — nix develop (dev mode only, reproducible)") elif [[ "$DETECTED_OS" == "nixos" ]]; then die "NixOS detected but 'nix' command not found." else choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (recommended)" \ - "Install Nix — nix develop (reproducible, installs Nix first)") || die "cancelled" + "Install Nix — nix develop (dev mode only, reproducible)") || die "cancelled" fi case "$choice" in @@ -893,6 +893,15 @@ main() { fi prompt_install_mode + + # Nix only works with dev mode (nix LD_LIBRARY_PATH conflicts with pip wheels) + if [[ "$USE_NIX" == "1" ]] && [[ "$INSTALL_MODE" == "library" ]]; then + warn "Nix is not compatible with library installs (LD_LIBRARY_PATH conflicts with pip wheels)" + info "switching to system packages for library mode" + USE_NIX=0; SETUP_METHOD="system" + install_system_deps + fi + prompt_extras do_install configure_system From 2be4686a8193b9d9d2358658eacfb8edfc537940 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 21:43:53 +0000 Subject: [PATCH 33/46] fix(install): revert nix=dev-only, add glibc warning for nix+library nix + library install works fine on glibc >= 2.38 (ubuntu 24.04+, macOS). only breaks on older glibc where nix's libstdc++ from GCC 14 conflicts with PyPI manylinux wheels. now shows a warning with specific guidance instead of blocking: - detects glibc version - warns if < 2.38 - suggests system packages or ubuntu upgrade as alternatives --- scripts/install.sh | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 822115af5b..d88a5996e7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -423,13 +423,13 @@ prompt_setup_method() { if [[ "$HAS_NIX" == "1" ]]; then choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (simpler)" \ - "Nix — nix develop (dev mode only, reproducible)") + "Nix — nix develop (reproducible)") elif [[ "$DETECTED_OS" == "nixos" ]]; then die "NixOS detected but 'nix' command not found." else choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (recommended)" \ - "Install Nix — nix develop (dev mode only, reproducible)") || die "cancelled" + "Install Nix — nix develop (reproducible, installs Nix first)") || die "cancelled" fi case "$choice" in @@ -894,12 +894,17 @@ main() { prompt_install_mode - # Nix only works with dev mode (nix LD_LIBRARY_PATH conflicts with pip wheels) + # Warn about known Nix + library + old glibc issue if [[ "$USE_NIX" == "1" ]] && [[ "$INSTALL_MODE" == "library" ]]; then - warn "Nix is not compatible with library installs (LD_LIBRARY_PATH conflicts with pip wheels)" - info "switching to system packages for library mode" - USE_NIX=0; SETUP_METHOD="system" - install_system_deps + if [[ "$DETECTED_OS" == "ubuntu" ]] || [[ "$DETECTED_OS" == "wsl" ]]; then + local glibc_ver; glibc_ver=$(ldd --version 2>&1 | head -1 | grep -oP '[0-9]+\.[0-9]+$' || echo "0") + if awk "BEGIN{exit !($glibc_ver < 2.38)}"; then + warn "Nix + library install on glibc ${glibc_ver} may have issues" + dim " Nix's LD_LIBRARY_PATH can conflict with PyPI wheels on glibc < 2.38" + dim " if you hit import errors, try: system packages instead of Nix" + dim " or upgrade to Ubuntu 24.04+ (glibc 2.39)" + fi + fi fi prompt_extras From 466e9418ccd270de7a57288ed293e3ab2282f795 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 21:54:28 +0000 Subject: [PATCH 34/46] chore: remove uninstall.sh dimos is just a pip install into a venv. uninstall is rm -rf .venv/ or rm -rf the project dir. no script needed. --- scripts/install.sh => install.sh | 0 scripts/uninstall.sh => uninstall.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename scripts/install.sh => install.sh (100%) rename scripts/uninstall.sh => uninstall.sh (100%) diff --git a/scripts/install.sh b/install.sh similarity index 100% rename from scripts/install.sh rename to install.sh diff --git a/scripts/uninstall.sh b/uninstall.sh similarity index 100% rename from scripts/uninstall.sh rename to uninstall.sh From 07fb483907d314f7d9e5e912f98e996562e3be16 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 21:54:36 +0000 Subject: [PATCH 35/46] chore: remove uninstall.sh, keep install.sh in scripts/ dimos is just a pip install into a venv. uninstall is rm -rf .venv/ or rm -rf the project dir. no script needed. --- install.sh => scripts/install.sh | 0 uninstall.sh | 170 ------------------------------- 2 files changed, 170 deletions(-) rename install.sh => scripts/install.sh (100%) delete mode 100755 uninstall.sh diff --git a/install.sh b/scripts/install.sh similarity index 100% rename from install.sh rename to scripts/install.sh diff --git a/uninstall.sh b/uninstall.sh deleted file mode 100755 index c99888f0fc..0000000000 --- a/uninstall.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env bash -# DimOS Uninstaller — reverses what install.sh did, for testing iteration. -# -# Usage: -# bash scripts/uninstall.sh # interactive — asks what to remove -# bash scripts/uninstall.sh --all # remove everything -# bash scripts/uninstall.sh --dry-run # show what would be removed -# -set -euo pipefail - -CYAN=$'\033[38;5;44m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m' -BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m' - -info() { printf "%s▸%s %s\n" "$CYAN" "$RESET" "$*"; } -ok() { printf "%s✓%s %s\n" "$GREEN" "$RESET" "$*"; } -warn() { printf "%s⚠%s %s\n" "$YELLOW" "$RESET" "$*"; } -err() { printf "%s✗%s %s\n" "$RED" "$RESET" "$*" >&2; } -dim() { printf "%s%s%s\n" "$DIM" "$*" "$RESET"; } - -DRY_RUN=0 -ALL=0 - -while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run) DRY_RUN=1; shift ;; - --all) ALL=1; shift ;; - --help|-h) - echo "Usage: $0 [--all] [--dry-run] [--help]" - echo " --all Remove everything without prompting" - echo " --dry-run Show what would be removed" - exit 0 ;; - *) shift ;; - esac -done - -do_rm() { - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] rm -rf $1" - else - rm -rf "$1" - ok "removed $1" - fi -} - -should_remove() { - local msg="$1" - if [[ "$ALL" == "1" ]]; then return 0; fi - if [[ "$DRY_RUN" == "1" ]]; then return 0; fi - local yn - printf "%s [y/N] " "$msg" - read -r yn - [[ "$yn" =~ ^[Yy] ]] -} - -printf "\n%s%sDimOS Uninstaller%s\n\n" "$BOLD" "$CYAN" "$RESET" - -# ─── DimOS project dirs ────────────────────────────────────────────────────── -for dir in "$HOME/dimos-project" "$HOME/dimos"; do - if [[ -d "$dir" ]]; then - local_size="$(du -sh "$dir" 2>/dev/null | cut -f1 || echo "?")" - if should_remove "Remove ${dir}/ (${local_size})?"; then - do_rm "$dir" - fi - fi -done - -# ─── System packages (apt) ─────────────────────────────────────────────────── -if command -v apt-get &>/dev/null; then - PKGS="portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit" - installed="" - for pkg in $PKGS; do - dpkg -l "$pkg" &>/dev/null && installed+=" $pkg" - done - if [[ -n "$installed" ]]; then - if should_remove "Remove system packages:${installed}?"; then - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] sudo apt-get remove -y$installed" - else - sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get remove -y $installed - ok "system packages removed" - fi - fi - else - dim " no DimOS system packages found" - fi -fi - -# ─── uv ────────────────────────────────────────────────────────────────────── -if command -v uv &>/dev/null; then - if should_remove "Remove uv ($(uv --version 2>/dev/null))?"; then - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] rm ~/.local/bin/uv ~/.local/bin/uvx" - dim "[dry-run] rm -rf ~/.cache/uv" - else - rm -f "$HOME/.local/bin/uv" "$HOME/.local/bin/uvx" - rm -rf "$HOME/.cache/uv" - ok "uv removed" - fi - fi -fi - -# ─── sysctl ────────────────────────────────────────────────────────────────── -if [[ -f /etc/sysctl.d/99-dimos.conf ]]; then - if should_remove "Remove LCM sysctl config (/etc/sysctl.d/99-dimos.conf)?"; then - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] sudo rm /etc/sysctl.d/99-dimos.conf" - else - sudo rm -f /etc/sysctl.d/99-dimos.conf - sudo sysctl -w net.core.rmem_max=212992 2>/dev/null || true - sudo sysctl -w net.core.rmem_default=212992 2>/dev/null || true - ok "sysctl config removed, buffers reset to defaults" - fi - fi -fi - -# ─── Nix ───────────────────────────────────────────────────────────────────── -if [[ -d /nix ]]; then - if should_remove "Remove Nix completely (/nix, daemon, build users)?"; then - if [[ "$DRY_RUN" == "1" ]]; then - dim "[dry-run] stop nix-daemon" - dim "[dry-run] rm -rf /nix /etc/nix /etc/profile.d/nix*.sh" - dim "[dry-run] remove nixbld users + group" - dim "[dry-run] rm -rf ~/.nix-profile ~/.nix-defexpr ~/.nix-channels ~/.config/nix" - else - sudo systemctl stop nix-daemon.socket nix-daemon.service 2>/dev/null || true - sudo systemctl disable nix-daemon.socket nix-daemon.service 2>/dev/null || true - sudo rm -f /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket - sudo rm -f /etc/systemd/system/sockets.target.wants/nix-daemon.socket - sudo systemctl daemon-reload 2>/dev/null || true - sudo rm -rf /nix /etc/nix - sudo rm -f /etc/profile.d/nix.sh /etc/profile.d/nix-daemon.sh - rm -rf "$HOME/.nix-profile" "$HOME/.nix-defexpr" "$HOME/.nix-channels" "$HOME/.config/nix" - # Restore shell rc backups that Nix created (prevents reinstall failures) - for rc in /etc/bashrc /etc/bash.bashrc /etc/zshrc; do - if [[ -f "${rc}.backup-before-nix" ]]; then - sudo mv "${rc}.backup-before-nix" "$rc" - fi - done - for i in $(seq 1 32); do sudo userdel "nixbld$i" 2>/dev/null || true; done - sudo groupdel nixbld 2>/dev/null || true - ok "Nix removed (open a new shell to clear environment)" - fi - fi -fi - -# ─── dimensional-applications (library install default dir) ─────────────────── -if [[ -d "$HOME/dimensional-applications" ]]; then - local_size="$(du -sh "$HOME/dimensional-applications" 2>/dev/null | cut -f1 || echo "?")" - if should_remove "Remove ~/dimensional-applications/ (${local_size})?"; then - do_rm "$HOME/dimensional-applications" - fi -fi - -# ─── gum (temp install from installer) ─────────────────────────────────────── -for tmpgum in /tmp/gum-install.*/gum*; do - if [[ -d "$(dirname "$tmpgum")" ]]; then - do_rm "$(dirname "$tmpgum")" - break - fi -done - -# ─── tmp installer files ───────────────────────────────────────────────────── -for tmp in /tmp/dimos-install.*.sh /tmp/dimos-replay-*.log /tmp/dimos-nix-* /tmp/dimos-test-*; do - if [[ -e "$tmp" ]]; then - do_rm "$tmp" - fi -done - -printf "\n%s✓ cleanup complete%s\n\n" "$GREEN" "$RESET" -[[ "$DRY_RUN" == "1" ]] && dim " (dry-run — nothing was actually removed)" From cc8e67f57ad188106a82a955250e015627477f1d Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 22:03:59 +0000 Subject: [PATCH 36/46] fix(install): update URLs from dimensional.ai to github raw replace placeholder dimensional.ai/install.sh with actual github raw URL pointing to dev branch. updates header comments, --help output, and all usage examples. --- scripts/install.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index d88a5996e7..502486dffb 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,11 +5,11 @@ # Interactive installer for DimOS — the agentive operating system for generalist robotics. # # Usage: -# curl -fsSL https://dimensional.ai/install.sh | bash -# curl -fsSL https://dimensional.ai/install.sh | bash -s -- --help +# curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash +# curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- --help # # Non-interactive: -# curl -fsSL https://dimensional.ai/install.sh | bash -s -- --non-interactive --mode library --extras base,unitree +# curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- --non-interactive --mode library --extras base,unitree # set -euo pipefail @@ -229,8 +229,8 @@ usage() { ${BOLD}DimOS Interactive Installer${RESET} v${INSTALLER_VERSION} ${BOLD}USAGE${RESET} - curl -fsSL https://dimensional.ai/install.sh | bash - curl -fsSL https://dimensional.ai/install.sh | bash -s -- [OPTIONS] + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- [OPTIONS] ${BOLD}OPTIONS${RESET} --mode library|dev Install mode (default: interactive prompt) @@ -248,10 +248,10 @@ ${BOLD}OPTIONS${RESET} --help Show this help ${BOLD}EXAMPLES${RESET} - curl -fsSL https://dimensional.ai/install.sh | bash - curl -fsSL https://dimensional.ai/install.sh | bash -s -- --mode dev --no-cuda - curl -fsSL https://dimensional.ai/install.sh | bash -s -- --non-interactive --extras base,unitree - curl -fsSL https://dimensional.ai/install.sh | bash -s -- --dry-run + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- --mode dev --no-cuda + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- --non-interactive --extras base,unitree + curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -s -- --dry-run EOF exit 0 } From 6f7dd9bf49f2dac0501b8137cf27c87b66a56dcd Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 22:10:55 +0000 Subject: [PATCH 37/46] docs: add Interactive Install section to README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7cb720f372..a1908c0725 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,14 @@ if __name__ == "__main__": # Development +## Interactive Install + +```sh +curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash +``` + +> See [`scripts/install.sh --help`](scripts/install.sh) for non-interactive and advanced options. + ## Develop on DimOS ```sh From 877a4fa6d51d479a986d6ed38537c72d5f1bda70 Mon Sep 17 00:00:00 2001 From: spomichter Date: Mon, 2 Mar 2026 22:14:23 +0000 Subject: [PATCH 38/46] docs: move Interactive Install to top of Installation section interactive install is first thing under '# Installation'. renamed 'System Install' to 'Manual System Install'. --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a1908c0725..45d95fd138 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,15 @@ DimOS has been tested and validated on the following hardware configurations: # Installation -## System Install +## Interactive Install + +```sh +curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash +``` + +> See [`scripts/install.sh --help`](scripts/install.sh) for non-interactive and advanced options. + +## Manual System Install To set up your system dependencies, follow one of these guides: @@ -284,14 +292,6 @@ if __name__ == "__main__": # Development -## Interactive Install - -```sh -curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/dev/scripts/install.sh | bash -``` - -> See [`scripts/install.sh --help`](scripts/install.sh) for non-interactive and advanced options. - ## Develop on DimOS ```sh From f4411060a14c41ec0c3d650d3e2dba9a918b3a19 Mon Sep 17 00:00:00 2001 From: spomichter Date: Wed, 4 Mar 2026 19:05:45 +0000 Subject: [PATCH 39/46] fix(install): address PR review comments - OS detection: check /etc/os-release for debian/ubuntu instead of catch-all assumption. Non-debian Linux distros now get a clear error. - Package lists: moved to top-level constants (UBUNTU_PACKAGES, MACOS_PACKAGES) so they're easy to find and edit. - System deps: always show what will be installed and ask for confirmation before running apt/brew. No more hidden installs. - System deps: show live output (no spinner) so users see progress during long package installs instead of a frozen terminal. - macOS parity: now checks which brew packages are already installed (matching Ubuntu's dpkg -s check) instead of blindly installing all. - Banner: falls back to compact text on terminals narrower than 90 cols. - Quickstart: removed click-nav example (may be renamed/merged). --- scripts/install.sh | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 502486dffb..938d275d7a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,6 +30,10 @@ fi INSTALLER_VERSION="0.3.0" +# ─── package lists (edit these when dependencies change) ────────────────────── +UBUNTU_PACKAGES="curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit libgl1 libegl1" +MACOS_PACKAGES="gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" + INSTALL_MODE="${DIMOS_INSTALL_MODE:-}" EXTRAS="${DIMOS_EXTRAS:-}" NON_INTERACTIVE="${DIMOS_NO_PROMPT:-0}" @@ -203,6 +207,11 @@ prompt_spin() { # ─── ascii banner ───────────────────────────────────────────────────────────── show_banner() { if [[ "$NON_INTERACTIVE" == "1" ]] && [[ -z "${DIMOS_SHOW_BANNER:-}" ]]; then return; fi + local cols; cols=$(tput cols 2>/dev/null || echo 80) + if [[ $cols -lt 90 ]]; then + printf "\n %s%sDimensionalOS Installer%s v%s\n\n" "$CYAN" "$BOLD" "$RESET" "$INSTALLER_VERSION" + return + fi local banner=' ▇▇▇▇▇▇╗ ▇▇╗▇▇▇╗ ▇▇▇╗▇▇▇▇▇▇▇╗▇▇▇╗ ▇▇╗▇▇▇▇▇▇▇╗▇▇╗ ▇▇▇▇▇▇╗ ▇▇▇╗ ▇▇╗ ▇▇▇▇▇╗ ▇▇╗ ▇▇╔══▇▇╗▇▇║▇▇▇▇╗ ▇▇▇▇║▇▇╔════╝▇▇▇▇╗ ▇▇║▇▇╔════╝▇▇║▇▇╔═══▇▇╗▇▇▇▇╗ ▇▇║▇▇╔══▇▇╗▇▇║ ▇▇║ ▇▇║▇▇║▇▇╔▇▇▇▇╔▇▇║▇▇▇▇▇╗ ▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇╗▇▇║▇▇║ ▇▇║▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇║▇▇║ @@ -292,7 +301,8 @@ detect_os() { elif [[ "$uname_s" == "Linux" ]]; then if grep -qi microsoft /proc/version 2>/dev/null; then DETECTED_OS="wsl" elif [[ -f /etc/NIXOS ]] || has_cmd nixos-version; then DETECTED_OS="nixos" - else DETECTED_OS="ubuntu"; fi + elif grep -qEi 'debian|ubuntu' /etc/os-release 2>/dev/null; then DETECTED_OS="ubuntu" + else die "unsupported Linux distribution (only Debian/Ubuntu-based distros supported)"; fi DETECTED_OS_VERSION="$(. /etc/os-release 2>/dev/null && echo "${VERSION_ID:-unknown}" || echo "unknown")" else die "unsupported operating system: $uname_s" @@ -460,10 +470,8 @@ install_system_deps() { case "$DETECTED_OS" in ubuntu|wsl) - # Check if all packages are already installed local needed="" - local all_pkgs="curl g++ portaudio19-dev git-lfs libturbojpeg python3-dev pre-commit libgl1 libegl1" - for pkg in $all_pkgs; do + for pkg in $UBUNTU_PACKAGES; do if ! dpkg -s "$pkg" &>/dev/null; then needed+=" $pkg" fi @@ -473,18 +481,34 @@ install_system_deps() { return fi info "need to install:${needed}" - prompt_spin "updating package lists..." \ - "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq" - prompt_spin "installing packages..." \ - "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq $needed" + if ! prompt_confirm "Install these packages via apt?" "yes"; then + warn "skipping system dependencies — some features may not work" + return + fi + run_cmd "sudo apt-get update" + run_cmd "sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y $needed" ;; macos) if ! has_cmd brew; then info "installing homebrew..." run_cmd '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' fi - prompt_spin "installing system libraries..." \ - "brew install gnu-sed gcc portaudio git-lfs libjpeg-turbo python pre-commit" + local needed="" + for pkg in $MACOS_PACKAGES; do + if ! brew list "$pkg" &>/dev/null 2>&1; then + needed+=" $pkg" + fi + done + if [[ -z "$needed" ]]; then + ok "all system dependencies already installed" + return + fi + info "need to install via brew:${needed}" + if ! prompt_confirm "Install these packages via brew?" "yes"; then + warn "skipping system dependencies — some features may not work" + return + fi + run_cmd "brew install $needed" ;; nixos) info "NixOS detected — system deps managed via nix develop" @@ -837,7 +861,7 @@ print_quickstart() { printf " %s# real hardware%s\n ROBOT_IP=192.168.1.100 dimos run unitree-go2\n\n" "$DIM" "$RESET" fi if [[ "$EXTRAS" == *"sim"* ]] || [[ "$EXTRAS" == "all" ]]; then - printf " %s# MuJoCo + click-nav%s\n dimos --simulation run unitree-go2-click-nav --viewer-backend rerun\n\n" "$DIM" "$RESET" + printf " %s# MuJoCo simulation%s\n dimos --simulation run unitree-go2\n\n" "$DIM" "$RESET" fi if [[ "$INSTALL_MODE" == "dev" ]]; then printf " %s# tests%s\n uv run pytest dimos\n\n %s# type check%s\n uv run mypy dimos\n\n" "$DIM" "$RESET" "$DIM" "$RESET" From 26fc068accb222b7e9e75b2933a7807865dd713b Mon Sep 17 00:00:00 2001 From: spomichter Date: Thu, 5 Mar 2026 11:39:30 +0000 Subject: [PATCH 40/46] fix(install): banner shows on default terminals and piped installs - Use stty =90 cols - Plain text fallback below 45 cols --- scripts/install.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 938d275d7a..abae6e24bf 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -207,17 +207,31 @@ prompt_spin() { # ─── ascii banner ───────────────────────────────────────────────────────────── show_banner() { if [[ "$NON_INTERACTIVE" == "1" ]] && [[ -z "${DIMOS_SHOW_BANNER:-}" ]]; then return; fi - local cols; cols=$(tput cols 2>/dev/null || echo 80) - if [[ $cols -lt 90 ]]; then - printf "\n %s%sDimensionalOS Installer%s v%s\n\n" "$CYAN" "$BOLD" "$RESET" "$INSTALLER_VERSION" - return - fi - local banner=' ▇▇▇▇▇▇╗ ▇▇╗▇▇▇╗ ▇▇▇╗▇▇▇▇▇▇▇╗▇▇▇╗ ▇▇╗▇▇▇▇▇▇▇╗▇▇╗ ▇▇▇▇▇▇╗ ▇▇▇╗ ▇▇╗ ▇▇▇▇▇╗ ▇▇╗ + # stty /dev/null | awk '{print $2}') \ + || cols=$(tput cols 2>/dev/null) \ + || cols=80 + + local banner + if [[ $cols -ge 90 ]]; then + banner=' ▇▇▇▇▇▇╗ ▇▇╗▇▇▇╗ ▇▇▇╗▇▇▇▇▇▇▇╗▇▇▇╗ ▇▇╗▇▇▇▇▇▇▇╗▇▇╗ ▇▇▇▇▇▇╗ ▇▇▇╗ ▇▇╗ ▇▇▇▇▇╗ ▇▇╗ ▇▇╔══▇▇╗▇▇║▇▇▇▇╗ ▇▇▇▇║▇▇╔════╝▇▇▇▇╗ ▇▇║▇▇╔════╝▇▇║▇▇╔═══▇▇╗▇▇▇▇╗ ▇▇║▇▇╔══▇▇╗▇▇║ ▇▇║ ▇▇║▇▇║▇▇╔▇▇▇▇╔▇▇║▇▇▇▇▇╗ ▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇╗▇▇║▇▇║ ▇▇║▇▇╔▇▇╗ ▇▇║▇▇▇▇▇▇▇║▇▇║ ▇▇║ ▇▇║▇▇║▇▇║╚▇▇╔╝▇▇║▇▇╔══╝ ▇▇║╚▇▇╗▇▇║╚════▇▇║▇▇║▇▇║ ▇▇║▇▇║╚▇▇╗▇▇║▇▇╔══▇▇║▇▇║ ▇▇▇▇▇▇╔╝▇▇║▇▇║ ╚═╝ ▇▇║▇▇▇▇▇▇▇╗▇▇║ ╚▇▇▇▇║▇▇▇▇▇▇▇║▇▇║╚▇▇▇▇▇▇╔╝▇▇║ ╚▇▇▇▇║▇▇║ ▇▇║▇▇▇▇▇▇▇╗ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝' + elif [[ $cols -ge 45 ]]; then + banner=' ▇▇▇▇▇▇╗ ▇▇╗▇▇▇╗ ▇▇▇╗ ▇▇▇▇▇▇╗ ▇▇▇▇▇▇▇╗ + ▇▇╔══▇▇╗▇▇║▇▇▇▇╗ ▇▇▇▇║▇▇╔═══▇▇╗▇▇╔════╝ + ▇▇║ ▇▇║▇▇║▇▇╔▇▇▇▇╔▇▇║▇▇║ ▇▇║▇▇▇▇▇▇▇╗ + ▇▇║ ▇▇║▇▇║▇▇║╚▇▇╔╝▇▇║▇▇║ ▇▇║╚════▇▇║ + ▇▇▇▇▇▇╔╝▇▇║▇▇║ ╚═╝ ▇▇║╚▇▇▇▇▇▇╔╝▇▇▇▇▇▇▇║ + ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝' + else + printf "\n %s%sDimOS Installer%s v%s\n\n" "$CYAN" "$BOLD" "$RESET" "$INSTALLER_VERSION" + return + fi if [[ -n "$GUM" ]]; then printf "\n" "$GUM" style --foreground 44 --bold "$banner" From b171b481e653037abd3c544705c124f44c6415bd Mon Sep 17 00:00:00 2001 From: spomichter Date: Thu, 5 Mar 2026 11:43:04 +0000 Subject: [PATCH 41/46] fix(install): Ctrl+C works on system deps prompt when Nix detected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nix-detected code path for prompt_setup_method was missing || die "cancelled" — Ctrl+C in gum exits the subshell but the parent continued silently. Non-Nix path already had it. --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index abae6e24bf..2714caa3cc 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -447,7 +447,7 @@ prompt_setup_method() { if [[ "$HAS_NIX" == "1" ]]; then choice=$(prompt_select "How should we set up system dependencies?" \ "System packages — apt/brew (simpler)" \ - "Nix — nix develop (reproducible)") + "Nix — nix develop (reproducible)") || die "cancelled" elif [[ "$DETECTED_OS" == "nixos" ]]; then die "NixOS detected but 'nix' command not found." else From 50792360e9d525671cdbed9eb42ad9c6a1f2c86c Mon Sep 17 00:00:00 2001 From: spomichter Date: Thu, 5 Mar 2026 11:48:30 +0000 Subject: [PATCH 42/46] fix(install): Ctrl+C properly exits in piped installs (curl | bash) Root cause: when running via curl|bash, bash reads from the pipe and is NOT in the terminal's foreground process group. Ctrl+C only reaches gum (reading /dev/tty), not bash. gum swallows SIGINT, returns the highlighted item with exit 0, and the script continues. Fix: prompt_select and prompt_multi no longer use $() subshell capture. Gum writes to a tmpfile, result read into PROMPT_RESULT global variable. This keeps gum in the current shell's process group so SIGINT propagates correctly. Also: stty =90, compact DimOS >=45, text). --- scripts/install.sh | 62 +++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 2714caa3cc..c375f411aa 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -109,16 +109,17 @@ install_gum() { prompt_select() { local msg="$1"; shift local -a options=("$@") - if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "${options[0]}"; return; fi + if [[ "$NON_INTERACTIVE" == "1" ]]; then PROMPT_RESULT="${options[0]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local result - result=$("$GUM" choose --header "$msg" \ + local tmpf; tmpf=$(mktemp) + "$GUM" choose --header "$msg" \ --cursor "● " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } - echo "$result" + "${options[@]}" "$tmpf" + local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" + [[ $ec -ne 0 ]] && die "cancelled" else printf "%s%s%s\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -127,13 +128,13 @@ prompt_select() { ((i++)) done printf " choice [1]: " >/dev/tty - local choice; read -r choice /dev/tty; exit $CANCELLED_EXIT; } + local choice; read -r choice /dev/tty if [[ -n "$GUM" ]]; then - local result - result=$("$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ + local tmpf; tmpf=$(mktemp) + "$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ --cursor "❯ " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } - echo "$result" + "${options[@]}" "$tmpf" + local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" + [[ $ec -ne 0 ]] && die "cancelled" else printf "%s%s%s (comma-separated, enter for all)\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -161,13 +163,18 @@ prompt_multi() { printf " selection: " >/dev/tty local sel; read -r sel Date: Thu, 5 Mar 2026 11:48:59 +0000 Subject: [PATCH 43/46] Revert "fix(install): Ctrl+C properly exits in piped installs (curl | bash)" This reverts commit 50792360e9d525671cdbed9eb42ad9c6a1f2c86c. --- scripts/install.sh | 62 +++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index c375f411aa..2714caa3cc 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -109,17 +109,16 @@ install_gum() { prompt_select() { local msg="$1"; shift local -a options=("$@") - if [[ "$NON_INTERACTIVE" == "1" ]]; then PROMPT_RESULT="${options[0]}"; return; fi + if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "${options[0]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local tmpf; tmpf=$(mktemp) - "$GUM" choose --header "$msg" \ + local result + result=$("$GUM" choose --header "$msg" \ --cursor "● " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" "$tmpf" - local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" - [[ $ec -ne 0 ]] && die "cancelled" + "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } + echo "$result" else printf "%s%s%s\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -128,13 +127,13 @@ prompt_select() { ((i++)) done printf " choice [1]: " >/dev/tty - local choice; read -r choice /dev/tty; exit $CANCELLED_EXIT; } choice="${choice:-1}" local idx=$((choice - 1)) if [[ $idx -ge 0 ]] && [[ $idx -lt ${#options[@]} ]]; then - PROMPT_RESULT="${options[$idx]}" + echo "${options[$idx]}" else - PROMPT_RESULT="${options[0]}" + echo "${options[0]}" fi fi } @@ -142,17 +141,16 @@ prompt_select() { prompt_multi() { local msg="$1"; shift local -a options=("$@") - if [[ "$NON_INTERACTIVE" == "1" ]]; then PROMPT_RESULT=$(printf '%s\n' "${options[@]}"); return; fi + if [[ "$NON_INTERACTIVE" == "1" ]]; then printf '%s\n' "${options[@]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local tmpf; tmpf=$(mktemp) - "$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ + local result + result=$("$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ --cursor "❯ " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" "$tmpf" - local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" - [[ $ec -ne 0 ]] && die "cancelled" + "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } + echo "$result" else printf "%s%s%s (comma-separated, enter for all)\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -163,18 +161,13 @@ prompt_multi() { printf " selection: " >/dev/tty local sel; read -r sel Date: Thu, 5 Mar 2026 11:49:32 +0000 Subject: [PATCH 44/46] Revert "Revert "fix(install): Ctrl+C properly exits in piped installs (curl | bash)"" This reverts commit 3ca6af6764d0c3b421e2de21c12a15215d1e40fb. --- scripts/install.sh | 62 +++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 2714caa3cc..c375f411aa 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -109,16 +109,17 @@ install_gum() { prompt_select() { local msg="$1"; shift local -a options=("$@") - if [[ "$NON_INTERACTIVE" == "1" ]]; then echo "${options[0]}"; return; fi + if [[ "$NON_INTERACTIVE" == "1" ]]; then PROMPT_RESULT="${options[0]}"; return; fi printf "\n" >/dev/tty if [[ -n "$GUM" ]]; then - local result - result=$("$GUM" choose --header "$msg" \ + local tmpf; tmpf=$(mktemp) + "$GUM" choose --header "$msg" \ --cursor "● " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } - echo "$result" + "${options[@]}" "$tmpf" + local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" + [[ $ec -ne 0 ]] && die "cancelled" else printf "%s%s%s\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -127,13 +128,13 @@ prompt_select() { ((i++)) done printf " choice [1]: " >/dev/tty - local choice; read -r choice /dev/tty; exit $CANCELLED_EXIT; } + local choice; read -r choice /dev/tty if [[ -n "$GUM" ]]; then - local result - result=$("$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ + local tmpf; tmpf=$(mktemp) + "$GUM" choose --no-limit --header "$msg (space to toggle, enter to confirm)" \ --cursor "❯ " --cursor.foreground="44" \ --header.foreground="255" --header.bold \ --selected.foreground="44" \ - "${options[@]}" /dev/tty; exit $CANCELLED_EXIT; } - echo "$result" + "${options[@]}" "$tmpf" + local ec=$?; PROMPT_RESULT=$(<"$tmpf"); rm -f "$tmpf" + [[ $ec -ne 0 ]] && die "cancelled" else printf "%s%s%s (comma-separated, enter for all)\n" "$BOLD" "$msg" "$RESET" >/dev/tty local i=1 @@ -161,13 +163,18 @@ prompt_multi() { printf " selection: " >/dev/tty local sel; read -r sel Date: Thu, 5 Mar 2026 14:18:49 +0000 Subject: [PATCH 45/46] fix(install): use PWD for install directory, not HOME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed cd $HOME at script top — it overrode $PWD so install dirs always went to ~/dimensional-applications or ~/dimos instead of the directory the user ran curl from. --- scripts/install.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index c375f411aa..a93b0e8ffe 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,8 +13,6 @@ # set -euo pipefail -# Ensure we're in a valid directory (CWD may have been deleted by uninstall) -cd "$HOME" 2>/dev/null || cd /tmp # ─── signal handling (must be early so Ctrl+C works everywhere) ──────────────── trap 'printf "\n"; exit 130' INT From 4efc0528f0f686ad4ea4b3b8132dd59dc2cee524 Mon Sep 17 00:00:00 2001 From: spomichter Date: Thu, 5 Mar 2026 14:43:51 +0000 Subject: [PATCH 46/46] fix(install): show nix develop progress instead of hanging silently verify_nix_develop was capturing all output in $(), hiding download and build progress. Now runs with live stderr output so users see what's happening. Added message about first-run download time. --- scripts/install.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index a93b0e8ffe..e226662a22 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -474,13 +474,15 @@ prompt_setup_method() { verify_nix_develop() { local dir="$1" - info "verifying nix develop environment..." + info "verifying nix develop environment (first run downloads dependencies, may take a few minutes)..." if [[ "$DRY_RUN" == "1" ]]; then ok "nix develop verification skipped (dry-run)"; return; fi - local nix_check - nix_check=$(cd "$dir" && nix develop --command bash -c ' + local tmpf; tmpf=$(mktemp) + # Run with live output so user sees download/build progress + (cd "$dir" && nix develop --command bash -c ' echo "python3=$(which python3 2>/dev/null || echo MISSING)" echo "gcc=$(which gcc 2>/dev/null || echo MISSING)" - ' 2>&1) || true + ' >"$tmpf") || true + local nix_check; nix_check=$(<"$tmpf"); rm -f "$tmpf" echo "$nix_check" | grep -q "python3=MISSING" && warn "nix develop: python3 not found" || ok "nix develop: python3 available" echo "$nix_check" | grep -q "gcc=MISSING" && warn "nix develop: gcc not found" || ok "nix develop: gcc available" }