PlanOpticon
feat: cross-platform install scripts for one-command setup install.sh for macOS/Linux and install.ps1 for Windows. Handles Python, FFmpeg, pip install, optional Ollama setup, and API key configuration. Closes #107
Commit
d9ebd871b995017574dcb19034974515ac650b8fa36029a754211a61453aa98a
Parent
2a1b11a993e0b14…
2 files changed
+180
+375
+180
| --- a/install.ps1 | ||
| +++ b/install.ps1 | ||
| @@ -0,0 +1,180 @@ | ||
| 1 | +# PlanOpticon installer for Windows (PowerShell) | |
| 2 | +# Usage: irm https://planopticon.dev/install.ps1 | iex | |
| 3 | + | |
| 4 | +$ErrorActionPreference = "Stop" | |
| 5 | + | |
| 6 | +function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor White } | |
| 7 | +function Write-Ok($msg) { Write-Host "[ok] $msg" -ForegroundColor Green } | |
| 8 | +function Write-Warn($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow } | |
| 9 | +function Write-Err($msg) { Write-Host "[error] $msg" -ForegroundColor Red } | |
| 10 | +function Write-Info($msg) { Write-Host "[info] $msg" -ForegroundColor Blue } | |
| 11 | + | |
| 12 | +function Test-Command($cmd) { | |
| 13 | + try { Get-Command $cmd -ErrorAction Stop | Out-Null; return $true } | |
| 14 | + catch { return $false } | |
| 15 | +} | |
| 16 | + | |
| 17 | +# --- Header ------------------------------------------------------------------ | |
| 18 | + | |
| 19 | +Write-Host "`nPlanOpticon Installer" -ForegroundColor White | |
| 20 | +Write-Host "=====================================" -ForegroundColor White | |
| 21 | + | |
| 22 | +# --- Python ------------------------------------------------------------------ | |
| 23 | + | |
| 24 | +Write-Step "Checking Python" | |
| 25 | + | |
| 26 | +$python = $null | |
| 27 | +foreach ($cmd in @("python", "python3", "py")) { | |
| 28 | + if (Test-Command $cmd) { | |
| 29 | + $ver = & $cmd -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null | |
| 30 | + if ($ver) { | |
| 31 | + $parts = $ver.Split(".") | |
| 32 | + if ([int]$parts[0] -ge 3 -and [int]$parts[1] -ge 10) { | |
| 33 | + $python = $cmd | |
| 34 | + Write-Ok "Python $ver found ($cmd)" | |
| 35 | + break | |
| 36 | + } | |
| 37 | + } | |
| 38 | + } | |
| 39 | +} | |
| 40 | + | |
| 41 | +if (-not $python) { | |
| 42 | + Write-Warn "Python 3.10+ not found" | |
| 43 | + if (Test-Command "winget") { | |
| 44 | + Write-Info "Installing Python via winget..." | |
| 45 | + winget install Python.Python.3.12 --accept-source-agreements --accept-package-agreements | |
| 46 | + $python = "python" | |
| 47 | + } elseif (Test-Command "choco") { | |
| 48 | + Write-Info "Installing Python via Chocolatey..." | |
| 49 | + choco install python312 -y | |
| 50 | + $python = "python" | |
| 51 | + } else { | |
| 52 | + Write-Err "Please install Python 3.10+ from https://www.python.org/downloads/" | |
| 53 | + exit 1 | |
| 54 | + } | |
| 55 | + # Refresh PATH | |
| 56 | + $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") | |
| 57 | + Write-Ok "Python installed" | |
| 58 | +} | |
| 59 | + | |
| 60 | +# --- FFmpeg ------------------------------------------------------------------ | |
| 61 | + | |
| 62 | +Write-Step "Checking FFmpeg" | |
| 63 | + | |
| 64 | +if (Test-Command "ffmpeg") { | |
| 65 | + Write-Ok "FFmpeg found" | |
| 66 | +} else { | |
| 67 | + Write-Warn "FFmpeg not found" | |
| 68 | + if (Test-Command "winget") { | |
| 69 | + Write-Info "Installing FFmpeg via winget..." | |
| 70 | + winget install Gyan.FFmpeg --accept-source-agreements --accept-package-agreements | |
| 71 | + } elseif (Test-Command "choco") { | |
| 72 | + Write-Info "Installing FFmpeg via Chocolatey..." | |
| 73 | + choco install ffmpeg -y | |
| 74 | + } else { | |
| 75 | + Write-Err "Please install FFmpeg from https://ffmpeg.org/download.html" | |
| 76 | + exit 1 | |
| 77 | + } | |
| 78 | + $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") | |
| 79 | + Write-Ok "FFmpeg installed" | |
| 80 | +} | |
| 81 | + | |
| 82 | +# --- Extras ------------------------------------------------------------------ | |
| 83 | + | |
| 84 | +Write-Step "Choose extras" | |
| 85 | +Write-Host " 1) core - just the basics (default)" | |
| 86 | +Write-Host " 2) cloud - Google Drive, Dropbox, S3" | |
| 87 | +Write-Host " 3) pdf - PDF document ingestion" | |
| 88 | +Write-Host " 4) sources - YouTube, RSS, web scraping" | |
| 89 | +Write-Host " 5) all - everything" | |
| 90 | +Write-Host "" | |
| 91 | + | |
| 92 | +$choice = Read-Host "Choose extras [1-5, default=1]" | |
| 93 | +$extras = switch ($choice) { | |
| 94 | + "2" { "[cloud]" } | |
| 95 | + "3" { "[pdf]" } | |
| 96 | + "4" { "[sources]" } | |
| 97 | + "5" { "[all]" } | |
| 98 | + default { "" } | |
| 99 | +} | |
| 100 | + | |
| 101 | +# --- Install PlanOpticon ----------------------------------------------------- | |
| 102 | + | |
| 103 | +Write-Step "Installing PlanOpticon" | |
| 104 | + | |
| 105 | +$target = "planopticon$extras" | |
| 106 | +Write-Info "Installing: $target" | |
| 107 | + | |
| 108 | +try { | |
| 109 | + & $python -m pip install --upgrade $target | |
| 110 | + Write-Ok "PlanOpticon installed" | |
| 111 | +} catch { | |
| 112 | + Write-Warn "pip install failed, trying with --user" | |
| 113 | + & $python -m pip install --user --upgrade $target | |
| 114 | + Write-Ok "PlanOpticon installed (user)" | |
| 115 | +} | |
| 116 | + | |
| 117 | +# --- API key setup ----------------------------------------------------------- | |
| 118 | + | |
| 119 | +Write-Step "API key setup" | |
| 120 | + | |
| 121 | +$envFile = ".env" | |
| 122 | +if (Test-Path $envFile) { | |
| 123 | + Write-Ok "Found existing .env file" | |
| 124 | +} else { | |
| 125 | + Write-Host "`nPlanOpticon needs at least one AI provider API key." | |
| 126 | + Write-Host " 1) OpenAI (OPENAI_API_KEY)" | |
| 127 | + Write-Host " 2) Anthropic (ANTHROPIC_API_KEY)" | |
| 128 | + Write-Host " 3) Google (GEMINI_API_KEY)" | |
| 129 | + Write-Host " 4) Ollama (local, no key needed)" | |
| 130 | + Write-Host " 5) Skip for now" | |
| 131 | + Write-Host "" | |
| 132 | + | |
| 133 | + $providerChoice = Read-Host "Choose provider [1-5]" | |
| 134 | + switch ($providerChoice) { | |
| 135 | + "1" { | |
| 136 | + $key = Read-Host "Enter your OpenAI API key" | |
| 137 | + if ($key) { "OPENAI_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } | |
| 138 | + } | |
| 139 | + "2" { | |
| 140 | + $key = Read-Host "Enter your Anthropic API key" | |
| 141 | + if ($key) { "ANTHROPIC_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } | |
| 142 | + } | |
| 143 | + "3" { | |
| 144 | + $key = Read-Host "Enter your Google/Gemini API key" | |
| 145 | + if ($key) { "GEMINI_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } | |
| 146 | + } | |
| 147 | + "4" { | |
| 148 | + "OLLAMA_HOST=http://localhost:11434" | Out-File $envFile -Encoding utf8 | |
| 149 | + Write-Info "Using Ollama - no API key needed" | |
| 150 | + } | |
| 151 | + default { Write-Warn "Skipping API key setup. Add keys to .env later." } | |
| 152 | + } | |
| 153 | +} | |
| 154 | + | |
| 155 | +# --- Verify ------------------------------------------------------------------ | |
| 156 | + | |
| 157 | +Write-Step "Verifying installation" | |
| 158 | + | |
| 159 | +if (Test-Command "planopticon") { | |
| 160 | + try { | |
| 161 | + $version = & planopticon --version 2>$null | |
| 162 | + Write-Ok "planopticon CLI ready ($version)" | |
| 163 | + } catch { | |
| 164 | + Write-Ok "planopticon CLI ready" | |
| 165 | + } | |
| 166 | +} else { | |
| 167 | + Write-Warn "planopticon not in PATH - restart your terminal" | |
| 168 | +} | |
| 169 | + | |
| 170 | +# --- Done -------------------------------------------------------------------- | |
| 171 | + | |
| 172 | +Write-Host "`nInstallation complete!" -ForegroundColor Green | |
| 173 | +Write-Host "" | |
| 174 | +Write-Host "Quick start:" | |
| 175 | +Write-Host " planopticon process video.mp4 # Analyze a video" | |
| 176 | +Write-Host " planopticon companion # Start AI companion" | |
| 177 | +Write-Host " planopticon list-models # Check available models" | |
| 178 | +Write-Host "" | |
| 179 | +Write-Host "Docs: https://planopticon.dev" | |
| 180 | +Write-Host "" |
| --- a/install.ps1 | |
| +++ b/install.ps1 | |
| @@ -0,0 +1,180 @@ | |
| --- a/install.ps1 | |
| +++ b/install.ps1 | |
| @@ -0,0 +1,180 @@ | |
| 1 | # PlanOpticon installer for Windows (PowerShell) |
| 2 | # Usage: irm https://planopticon.dev/install.ps1 | iex |
| 3 | |
| 4 | $ErrorActionPreference = "Stop" |
| 5 | |
| 6 | function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor White } |
| 7 | function Write-Ok($msg) { Write-Host "[ok] $msg" -ForegroundColor Green } |
| 8 | function Write-Warn($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow } |
| 9 | function Write-Err($msg) { Write-Host "[error] $msg" -ForegroundColor Red } |
| 10 | function Write-Info($msg) { Write-Host "[info] $msg" -ForegroundColor Blue } |
| 11 | |
| 12 | function Test-Command($cmd) { |
| 13 | try { Get-Command $cmd -ErrorAction Stop | Out-Null; return $true } |
| 14 | catch { return $false } |
| 15 | } |
| 16 | |
| 17 | # --- Header ------------------------------------------------------------------ |
| 18 | |
| 19 | Write-Host "`nPlanOpticon Installer" -ForegroundColor White |
| 20 | Write-Host "=====================================" -ForegroundColor White |
| 21 | |
| 22 | # --- Python ------------------------------------------------------------------ |
| 23 | |
| 24 | Write-Step "Checking Python" |
| 25 | |
| 26 | $python = $null |
| 27 | foreach ($cmd in @("python", "python3", "py")) { |
| 28 | if (Test-Command $cmd) { |
| 29 | $ver = & $cmd -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null |
| 30 | if ($ver) { |
| 31 | $parts = $ver.Split(".") |
| 32 | if ([int]$parts[0] -ge 3 -and [int]$parts[1] -ge 10) { |
| 33 | $python = $cmd |
| 34 | Write-Ok "Python $ver found ($cmd)" |
| 35 | break |
| 36 | } |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | if (-not $python) { |
| 42 | Write-Warn "Python 3.10+ not found" |
| 43 | if (Test-Command "winget") { |
| 44 | Write-Info "Installing Python via winget..." |
| 45 | winget install Python.Python.3.12 --accept-source-agreements --accept-package-agreements |
| 46 | $python = "python" |
| 47 | } elseif (Test-Command "choco") { |
| 48 | Write-Info "Installing Python via Chocolatey..." |
| 49 | choco install python312 -y |
| 50 | $python = "python" |
| 51 | } else { |
| 52 | Write-Err "Please install Python 3.10+ from https://www.python.org/downloads/" |
| 53 | exit 1 |
| 54 | } |
| 55 | # Refresh PATH |
| 56 | $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") |
| 57 | Write-Ok "Python installed" |
| 58 | } |
| 59 | |
| 60 | # --- FFmpeg ------------------------------------------------------------------ |
| 61 | |
| 62 | Write-Step "Checking FFmpeg" |
| 63 | |
| 64 | if (Test-Command "ffmpeg") { |
| 65 | Write-Ok "FFmpeg found" |
| 66 | } else { |
| 67 | Write-Warn "FFmpeg not found" |
| 68 | if (Test-Command "winget") { |
| 69 | Write-Info "Installing FFmpeg via winget..." |
| 70 | winget install Gyan.FFmpeg --accept-source-agreements --accept-package-agreements |
| 71 | } elseif (Test-Command "choco") { |
| 72 | Write-Info "Installing FFmpeg via Chocolatey..." |
| 73 | choco install ffmpeg -y |
| 74 | } else { |
| 75 | Write-Err "Please install FFmpeg from https://ffmpeg.org/download.html" |
| 76 | exit 1 |
| 77 | } |
| 78 | $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") |
| 79 | Write-Ok "FFmpeg installed" |
| 80 | } |
| 81 | |
| 82 | # --- Extras ------------------------------------------------------------------ |
| 83 | |
| 84 | Write-Step "Choose extras" |
| 85 | Write-Host " 1) core - just the basics (default)" |
| 86 | Write-Host " 2) cloud - Google Drive, Dropbox, S3" |
| 87 | Write-Host " 3) pdf - PDF document ingestion" |
| 88 | Write-Host " 4) sources - YouTube, RSS, web scraping" |
| 89 | Write-Host " 5) all - everything" |
| 90 | Write-Host "" |
| 91 | |
| 92 | $choice = Read-Host "Choose extras [1-5, default=1]" |
| 93 | $extras = switch ($choice) { |
| 94 | "2" { "[cloud]" } |
| 95 | "3" { "[pdf]" } |
| 96 | "4" { "[sources]" } |
| 97 | "5" { "[all]" } |
| 98 | default { "" } |
| 99 | } |
| 100 | |
| 101 | # --- Install PlanOpticon ----------------------------------------------------- |
| 102 | |
| 103 | Write-Step "Installing PlanOpticon" |
| 104 | |
| 105 | $target = "planopticon$extras" |
| 106 | Write-Info "Installing: $target" |
| 107 | |
| 108 | try { |
| 109 | & $python -m pip install --upgrade $target |
| 110 | Write-Ok "PlanOpticon installed" |
| 111 | } catch { |
| 112 | Write-Warn "pip install failed, trying with --user" |
| 113 | & $python -m pip install --user --upgrade $target |
| 114 | Write-Ok "PlanOpticon installed (user)" |
| 115 | } |
| 116 | |
| 117 | # --- API key setup ----------------------------------------------------------- |
| 118 | |
| 119 | Write-Step "API key setup" |
| 120 | |
| 121 | $envFile = ".env" |
| 122 | if (Test-Path $envFile) { |
| 123 | Write-Ok "Found existing .env file" |
| 124 | } else { |
| 125 | Write-Host "`nPlanOpticon needs at least one AI provider API key." |
| 126 | Write-Host " 1) OpenAI (OPENAI_API_KEY)" |
| 127 | Write-Host " 2) Anthropic (ANTHROPIC_API_KEY)" |
| 128 | Write-Host " 3) Google (GEMINI_API_KEY)" |
| 129 | Write-Host " 4) Ollama (local, no key needed)" |
| 130 | Write-Host " 5) Skip for now" |
| 131 | Write-Host "" |
| 132 | |
| 133 | $providerChoice = Read-Host "Choose provider [1-5]" |
| 134 | switch ($providerChoice) { |
| 135 | "1" { |
| 136 | $key = Read-Host "Enter your OpenAI API key" |
| 137 | if ($key) { "OPENAI_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } |
| 138 | } |
| 139 | "2" { |
| 140 | $key = Read-Host "Enter your Anthropic API key" |
| 141 | if ($key) { "ANTHROPIC_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } |
| 142 | } |
| 143 | "3" { |
| 144 | $key = Read-Host "Enter your Google/Gemini API key" |
| 145 | if ($key) { "GEMINI_API_KEY=$key" | Out-File $envFile -Encoding utf8; Write-Ok "Saved to .env" } |
| 146 | } |
| 147 | "4" { |
| 148 | "OLLAMA_HOST=http://localhost:11434" | Out-File $envFile -Encoding utf8 |
| 149 | Write-Info "Using Ollama - no API key needed" |
| 150 | } |
| 151 | default { Write-Warn "Skipping API key setup. Add keys to .env later." } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | # --- Verify ------------------------------------------------------------------ |
| 156 | |
| 157 | Write-Step "Verifying installation" |
| 158 | |
| 159 | if (Test-Command "planopticon") { |
| 160 | try { |
| 161 | $version = & planopticon --version 2>$null |
| 162 | Write-Ok "planopticon CLI ready ($version)" |
| 163 | } catch { |
| 164 | Write-Ok "planopticon CLI ready" |
| 165 | } |
| 166 | } else { |
| 167 | Write-Warn "planopticon not in PATH - restart your terminal" |
| 168 | } |
| 169 | |
| 170 | # --- Done -------------------------------------------------------------------- |
| 171 | |
| 172 | Write-Host "`nInstallation complete!" -ForegroundColor Green |
| 173 | Write-Host "" |
| 174 | Write-Host "Quick start:" |
| 175 | Write-Host " planopticon process video.mp4 # Analyze a video" |
| 176 | Write-Host " planopticon companion # Start AI companion" |
| 177 | Write-Host " planopticon list-models # Check available models" |
| 178 | Write-Host "" |
| 179 | Write-Host "Docs: https://planopticon.dev" |
| 180 | Write-Host "" |
+375
| --- a/install.sh | ||
| +++ b/install.sh | ||
| @@ -0,0 +1,375 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# PlanOpticon installer — cross-platform one-command setup | |
| 3 | +# Usage: curl -fsSL https://planopticon.dev/install.sh | bash | |
| 4 | +# | |
| 5 | +# Supports: macOS (Homebrew), Ubuntu/Debian (apt), Fedora/RHEL (dnf), Arch (pacman) | |
| 6 | +# Idempotent — safe to re-run. | |
| 7 | + | |
| 8 | +set -euo pipefail | |
| 9 | + | |
| 10 | +# --- Colors & helpers -------------------------------------------------------- | |
| 11 | + | |
| 12 | +RED='\033[0;31m' | |
| 13 | +GREEN='\033[0;32m' | |
| 14 | +YELLOW='\033[1;33m' | |
| 15 | +BLUE='\033[0;34m' | |
| 16 | +BOLD='\033[1m' | |
| 17 | +NC='\033[0m' | |
| 18 | + | |
| 19 | +info() { printf "${BLUE}[info]${NC} %s\n" "$*"; } | |
| 20 | +ok() { printf "${GREEN}[ok]${NC} %s\n" "$*"; } | |
| 21 | +warn() { printf "${YELLOW}[warn]${NC} %s\n" "$*"; } | |
| 22 | +err() { printf "${RED}[error]${NC} %s\n" "$*" >&2; } | |
| 23 | +step() { printf "\n${BOLD}==> %s${NC}\n" "$*"; } | |
| 24 | + | |
| 25 | +command_exists() { command -v "$1" >/dev/null 2>&1; } | |
| 26 | + | |
| 27 | +# --- Detect OS & package manager --------------------------------------------- | |
| 28 | + | |
| 29 | +detect_os() { | |
| 30 | + case "$(uname -s)" in | |
| 31 | + Darwin) OS="macos" ;; | |
| 32 | + Linux) | |
| 33 | + if [ -f /etc/os-release ]; then | |
| 34 | + . /etc/os-release | |
| 35 | + case "$ID" in | |
| 36 | + ubuntu|debian|pop|linuxmint|elementary) OS="debian" ;; | |
| 37 | + fedora|rhel|centos|rocky|alma) OS="fedora" ;; | |
| 38 | + arch|manjaro|endeavouros) OS="arch" ;; | |
| 39 | + *) OS="linux-unknown" ;; | |
| 40 | + esac | |
| 41 | + else | |
| 42 | + OS="linux-unknown" | |
| 43 | + fi | |
| 44 | + ;; | |
| 45 | + MINGW*|MSYS*|CYGWIN*) | |
| 46 | + err "Windows detected. Please use install.ps1 instead:" | |
| 47 | + err " irm https://planopticon.dev/install.ps1 | iex" | |
| 48 | + exit 1 | |
| 49 | + ;; | |
| 50 | + *) | |
| 51 | + err "Unsupported OS: $(uname -s)" | |
| 52 | + exit 1 | |
| 53 | + ;; | |
| 54 | + esac | |
| 55 | +} | |
| 56 | + | |
| 57 | +# --- Python ------------------------------------------------------------------ | |
| 58 | + | |
| 59 | +MIN_PYTHON_MAJOR=3 | |
| 60 | +MIN_PYTHON_MINOR=10 | |
| 61 | + | |
| 62 | +find_python() { | |
| 63 | + for cmd in python3 python; do | |
| 64 | + if command_exists "$cmd"; then | |
| 65 | + local ver | |
| 66 | + ver=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null) || continue | |
| 67 | + local major minor | |
| 68 | + major=$(echo "$ver" | cut -d. -f1) | |
| 69 | + minor=$(echo "$ver" | cut -d. -f2) | |
| 70 | + if [ "$major" -ge "$MIN_PYTHON_MAJOR" ] && [ "$minor" -ge "$MIN_PYTHON_MINOR" ]; then | |
| 71 | + PYTHON="$cmd" | |
| 72 | + PYTHON_VERSION="$ver" | |
| 73 | + return 0 | |
| 74 | + fi | |
| 75 | + fi | |
| 76 | + done | |
| 77 | + return 1 | |
| 78 | +} | |
| 79 | + | |
| 80 | +install_python() { | |
| 81 | + step "Installing Python 3.10+" | |
| 82 | + case "$OS" in | |
| 83 | + macos) | |
| 84 | + if command_exists brew; then | |
| 85 | + brew install [email protected] | |
| 86 | + else | |
| 87 | + err "Homebrew not found. Install it first: https://brew.sh" | |
| 88 | + exit 1 | |
| 89 | + fi | |
| 90 | + ;; | |
| 91 | + debian) | |
| 92 | + sudo apt-get update -qq | |
| 93 | + sudo apt-get install -y -qq python3 python3-pip python3-venv | |
| 94 | + ;; | |
| 95 | + fedora) | |
| 96 | + sudo dnf install -y -q python3 python3-pip | |
| 97 | + ;; | |
| 98 | + arch) | |
| 99 | + sudo pacman -S --noconfirm python python-pip | |
| 100 | + ;; | |
| 101 | + *) | |
| 102 | + err "Cannot auto-install Python on this OS." | |
| 103 | + err "Please install Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ manually." | |
| 104 | + exit 1 | |
| 105 | + ;; | |
| 106 | + esac | |
| 107 | +} | |
| 108 | + | |
| 109 | +# --- FFmpeg ------------------------------------------------------------------ | |
| 110 | + | |
| 111 | +install_ffmpeg() { | |
| 112 | + step "Installing FFmpeg" | |
| 113 | + case "$OS" in | |
| 114 | + macos) | |
| 115 | + if command_exists brew; then | |
| 116 | + brew install ffmpeg | |
| 117 | + else | |
| 118 | + err "Homebrew not found. Install it first: https://brew.sh" | |
| 119 | + exit 1 | |
| 120 | + fi | |
| 121 | + ;; | |
| 122 | + debian) | |
| 123 | + sudo apt-get update -qq | |
| 124 | + sudo apt-get install -y -qq ffmpeg | |
| 125 | + ;; | |
| 126 | + fedora) | |
| 127 | + sudo dnf install -y -q ffmpeg-free || { | |
| 128 | + warn "ffmpeg-free not available, trying RPM Fusion..." | |
| 129 | + sudo dnf install -y -q \ | |
| 130 | + "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm" 2>/dev/null || true | |
| 131 | + sudo dnf install -y -q ffmpeg | |
| 132 | + } | |
| 133 | + ;; | |
| 134 | + arch) | |
| 135 | + sudo pacman -S --noconfirm ffmpeg | |
| 136 | + ;; | |
| 137 | + *) | |
| 138 | + err "Cannot auto-install FFmpeg on this OS." | |
| 139 | + err "Please install FFmpeg manually: https://ffmpeg.org/download.html" | |
| 140 | + exit 1 | |
| 141 | + ;; | |
| 142 | + esac | |
| 143 | +} | |
| 144 | + | |
| 145 | +# --- Ollama (optional) ------------------------------------------------------- | |
| 146 | + | |
| 147 | +install_ollama() { | |
| 148 | + step "Installing Ollama" | |
| 149 | + if command_exists ollama; then | |
| 150 | + ok "Ollama already installed" | |
| 151 | + return 0 | |
| 152 | + fi | |
| 153 | + case "$OS" in | |
| 154 | + macos) | |
| 155 | + if command_exists brew; then | |
| 156 | + brew install ollama | |
| 157 | + else | |
| 158 | + curl -fsSL https://ollama.com/install.sh | sh | |
| 159 | + fi | |
| 160 | + ;; | |
| 161 | + debian|fedora|arch|linux-unknown) | |
| 162 | + curl -fsSL https://ollama.com/install.sh | sh | |
| 163 | + ;; | |
| 164 | + esac | |
| 165 | +} | |
| 166 | + | |
| 167 | +pull_ollama_models() { | |
| 168 | + info "Pulling recommended local models..." | |
| 169 | + ollama pull llama3.2 2>/dev/null || warn "Failed to pull llama3.2" | |
| 170 | + ollama pull llava 2>/dev/null || warn "Failed to pull llava (vision model)" | |
| 171 | + ok "Ollama models ready" | |
| 172 | +} | |
| 173 | + | |
| 174 | +# --- Extras selection -------------------------------------------------------- | |
| 175 | + | |
| 176 | +select_extras() { | |
| 177 | + EXTRAS="" | |
| 178 | + printf "\n${BOLD}Optional extras:${NC}\n" | |
| 179 | + echo " 1) core — just the basics (default)" | |
| 180 | + echo " 2) cloud — Google Drive, Dropbox, S3 integrations" | |
| 181 | + echo " 3) pdf — PDF document ingestion" | |
| 182 | + echo " 4) sources — YouTube, RSS, web scraping" | |
| 183 | + echo " 5) all — everything" | |
| 184 | + echo "" | |
| 185 | + printf "Choose extras [1-5, default=1]: " | |
| 186 | + | |
| 187 | + if [ -t 0 ]; then | |
| 188 | + read -r choice | |
| 189 | + else | |
| 190 | + choice="1" | |
| 191 | + echo "1 (non-interactive, using default)" | |
| 192 | + fi | |
| 193 | + | |
| 194 | + case "${choice:-1}" in | |
| 195 | + 2) EXTRAS="[cloud]" ;; | |
| 196 | + 3) EXTRAS="[pdf]" ;; | |
| 197 | + 4) EXTRAS="[sources]" ;; | |
| 198 | + 5) EXTRAS="[all]" ;; | |
| 199 | + *) EXTRAS="" ;; | |
| 200 | + esac | |
| 201 | +} | |
| 202 | + | |
| 203 | +# --- API key setup ----------------------------------------------------------- | |
| 204 | + | |
| 205 | +setup_api_keys() { | |
| 206 | + step "API key setup" | |
| 207 | + local env_file=".env" | |
| 208 | + | |
| 209 | + if [ -f "$env_file" ]; then | |
| 210 | + ok "Found existing .env file, skipping API key setup" | |
| 211 | + return 0 | |
| 212 | + fi | |
| 213 | + | |
| 214 | + if [ ! -t 0 ]; then | |
| 215 | + warn "Non-interactive mode — skipping API key setup" | |
| 216 | + info "Create a .env file with at least one API key:" | |
| 217 | + info " OPENAI_API_KEY=sk-..." | |
| 218 | + info " ANTHROPIC_API_KEY=sk-ant-..." | |
| 219 | + info " GEMINI_API_KEY=..." | |
| 220 | + return 0 | |
| 221 | + fi | |
| 222 | + | |
| 223 | + printf "\n${BOLD}PlanOpticon needs at least one AI provider API key.${NC}\n" | |
| 224 | + echo "You can add more later in .env" | |
| 225 | + echo "" | |
| 226 | + echo " 1) OpenAI (OPENAI_API_KEY)" | |
| 227 | + echo " 2) Anthropic (ANTHROPIC_API_KEY)" | |
| 228 | + echo " 3) Google (GEMINI_API_KEY)" | |
| 229 | + echo " 4) Ollama (local, no key needed)" | |
| 230 | + echo " 5) Skip for now" | |
| 231 | + echo "" | |
| 232 | + printf "Choose provider [1-5]: " | |
| 233 | + read -r provider_choice | |
| 234 | + | |
| 235 | + case "${provider_choice:-5}" in | |
| 236 | + 1) | |
| 237 | + printf "Enter your OpenAI API key: " | |
| 238 | + read -r api_key | |
| 239 | + if [ -n "$api_key" ]; then | |
| 240 | + echo "OPENAI_API_KEY=$api_key" > "$env_file" | |
| 241 | + ok "Saved to .env" | |
| 242 | + fi | |
| 243 | + ;; | |
| 244 | + 2) | |
| 245 | + printf "Enter your Anthropic API key: " | |
| 246 | + read -r api_key | |
| 247 | + if [ -n "$api_key" ]; then | |
| 248 | + echo "ANTHROPIC_API_KEY=$api_key" > "$env_file" | |
| 249 | + ok "Saved to .env" | |
| 250 | + fi | |
| 251 | + ;; | |
| 252 | + 3) | |
| 253 | + printf "Enter your Google/Gemini API key: " | |
| 254 | + read -r api_key | |
| 255 | + if [ -n "$api_key" ]; then | |
| 256 | + echo "GEMINI_API_KEY=$api_key" > "$env_file" | |
| 257 | + ok "Saved to .env" | |
| 258 | + fi | |
| 259 | + ;; | |
| 260 | + 4) | |
| 261 | + info "Using Ollama — no API key needed" | |
| 262 | + echo "OLLAMA_HOST=http://localhost:11434" > "$env_file" | |
| 263 | + ;; | |
| 264 | + 5) | |
| 265 | + warn "Skipping API key setup. Add keys to .env later." | |
| 266 | + ;; | |
| 267 | + esac | |
| 268 | +} | |
| 269 | + | |
| 270 | +# --- Main -------------------------------------------------------------------- | |
| 271 | + | |
| 272 | +main() { | |
| 273 | + printf "\n${BOLD}PlanOpticon Installer${NC}\n" | |
| 274 | + echo "=====================================" | |
| 275 | + echo "" | |
| 276 | + | |
| 277 | + detect_os | |
| 278 | + info "Detected OS: $OS" | |
| 279 | + | |
| 280 | + # --- Python --- | |
| 281 | + step "Checking Python" | |
| 282 | + if find_python; then | |
| 283 | + ok "Python $PYTHON_VERSION found ($PYTHON)" | |
| 284 | + else | |
| 285 | + warn "Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ not found" | |
| 286 | + install_python | |
| 287 | + if ! find_python; then | |
| 288 | + err "Python installation failed. Please install Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ manually." | |
| 289 | + exit 1 | |
| 290 | + fi | |
| 291 | + ok "Python $PYTHON_VERSION installed" | |
| 292 | + fi | |
| 293 | + | |
| 294 | + # --- FFmpeg --- | |
| 295 | + step "Checking FFmpeg" | |
| 296 | + if command_exists ffmpeg; then | |
| 297 | + local ffmpeg_ver | |
| 298 | + ffmpeg_ver=$(ffmpeg -version 2>/dev/null | head -1 | awk '{print $3}') || ffmpeg_ver="unknown" | |
| 299 | + ok "FFmpeg found ($ffmpeg_ver)" | |
| 300 | + else | |
| 301 | + warn "FFmpeg not found" | |
| 302 | + install_ffmpeg | |
| 303 | + if command_exists ffmpeg; then | |
| 304 | + ok "FFmpeg installed" | |
| 305 | + else | |
| 306 | + err "FFmpeg installation failed. Please install manually." | |
| 307 | + exit 1 | |
| 308 | + fi | |
| 309 | + fi | |
| 310 | + | |
| 311 | + # --- pip install planopticon --- | |
| 312 | + step "Installing PlanOpticon" | |
| 313 | + select_extras | |
| 314 | + | |
| 315 | + local pip_target="planopticon${EXTRAS}" | |
| 316 | + info "Installing: $pip_target" | |
| 317 | + | |
| 318 | + if "$PYTHON" -m pip install --upgrade "$pip_target" 2>&1; then | |
| 319 | + ok "PlanOpticon installed" | |
| 320 | + else | |
| 321 | + warn "pip install failed, trying with --user flag" | |
| 322 | + "$PYTHON" -m pip install --user --upgrade "$pip_target" | |
| 323 | + ok "PlanOpticon installed (user)" | |
| 324 | + fi | |
| 325 | + | |
| 326 | + # --- Ollama (optional) --- | |
| 327 | + if [ -t 0 ]; then | |
| 328 | + printf "\n${BOLD}Would you like to install Ollama for local AI? [y/N]: ${NC}" | |
| 329 | + read -r install_ollama_choice | |
| 330 | + if [[ "${install_ollama_choice:-n}" =~ ^[Yy] ]]; then | |
| 331 | + install_ollama | |
| 332 | + if command_exists ollama; then | |
| 333 | + printf "Pull recommended models? (llama3.2 + llava, ~6GB) [y/N]: " | |
| 334 | + read -r pull_choice | |
| 335 | + if [[ "${pull_choice:-n}" =~ ^[Yy] ]]; then | |
| 336 | + pull_ollama_models | |
| 337 | + fi | |
| 338 | + fi | |
| 339 | + fi | |
| 340 | + fi | |
| 341 | + | |
| 342 | + # --- API keys --- | |
| 343 | + setup_api_keys | |
| 344 | + | |
| 345 | + # --- Verify --- | |
| 346 | + step "Verifying installation" | |
| 347 | + if command_exists planopticon; then | |
| 348 | + local version | |
| 349 | + version=$(planopticon --version 2>/dev/null || echo "installed") | |
| 350 | + ok "planopticon CLI ready ($version)" | |
| 351 | + else | |
| 352 | + # Might need PATH update | |
| 353 | + warn "planopticon not in PATH — you may need to restart your shell" | |
| 354 | + info "Or run: $PYTHON -m video_processor.cli.commands --version" | |
| 355 | + fi | |
| 356 | + | |
| 357 | + if planopticon list-models >/dev/null 2>&1; then | |
| 358 | + ok "Provider check passed" | |
| 359 | + else | |
| 360 | + warn "No AI providers configured yet — add an API key to .env" | |
| 361 | + fi | |
| 362 | + | |
| 363 | + # --- Done --- | |
| 364 | + printf "\n${GREEN}${BOLD}Installation complete!${NC}\n" | |
| 365 | + echo "" | |
| 366 | + echo "Quick start:" | |
| 367 | + echo " planopticon process video.mp4 # Analyze a video" | |
| 368 | + echo " planopticon companion # Start AI companion" | |
| 369 | + echo " planopticon list-models # Check available models" | |
| 370 | + echo "" | |
| 371 | + echo "Docs: https://planopticon.dev" | |
| 372 | + echo "" | |
| 373 | +} | |
| 374 | + | |
| 375 | +main "$@" |
| --- a/install.sh | |
| +++ b/install.sh | |
| @@ -0,0 +1,375 @@ | |
| --- a/install.sh | |
| +++ b/install.sh | |
| @@ -0,0 +1,375 @@ | |
| 1 | #!/usr/bin/env bash |
| 2 | # PlanOpticon installer — cross-platform one-command setup |
| 3 | # Usage: curl -fsSL https://planopticon.dev/install.sh | bash |
| 4 | # |
| 5 | # Supports: macOS (Homebrew), Ubuntu/Debian (apt), Fedora/RHEL (dnf), Arch (pacman) |
| 6 | # Idempotent — safe to re-run. |
| 7 | |
| 8 | set -euo pipefail |
| 9 | |
| 10 | # --- Colors & helpers -------------------------------------------------------- |
| 11 | |
| 12 | RED='\033[0;31m' |
| 13 | GREEN='\033[0;32m' |
| 14 | YELLOW='\033[1;33m' |
| 15 | BLUE='\033[0;34m' |
| 16 | BOLD='\033[1m' |
| 17 | NC='\033[0m' |
| 18 | |
| 19 | info() { printf "${BLUE}[info]${NC} %s\n" "$*"; } |
| 20 | ok() { printf "${GREEN}[ok]${NC} %s\n" "$*"; } |
| 21 | warn() { printf "${YELLOW}[warn]${NC} %s\n" "$*"; } |
| 22 | err() { printf "${RED}[error]${NC} %s\n" "$*" >&2; } |
| 23 | step() { printf "\n${BOLD}==> %s${NC}\n" "$*"; } |
| 24 | |
| 25 | command_exists() { command -v "$1" >/dev/null 2>&1; } |
| 26 | |
| 27 | # --- Detect OS & package manager --------------------------------------------- |
| 28 | |
| 29 | detect_os() { |
| 30 | case "$(uname -s)" in |
| 31 | Darwin) OS="macos" ;; |
| 32 | Linux) |
| 33 | if [ -f /etc/os-release ]; then |
| 34 | . /etc/os-release |
| 35 | case "$ID" in |
| 36 | ubuntu|debian|pop|linuxmint|elementary) OS="debian" ;; |
| 37 | fedora|rhel|centos|rocky|alma) OS="fedora" ;; |
| 38 | arch|manjaro|endeavouros) OS="arch" ;; |
| 39 | *) OS="linux-unknown" ;; |
| 40 | esac |
| 41 | else |
| 42 | OS="linux-unknown" |
| 43 | fi |
| 44 | ;; |
| 45 | MINGW*|MSYS*|CYGWIN*) |
| 46 | err "Windows detected. Please use install.ps1 instead:" |
| 47 | err " irm https://planopticon.dev/install.ps1 | iex" |
| 48 | exit 1 |
| 49 | ;; |
| 50 | *) |
| 51 | err "Unsupported OS: $(uname -s)" |
| 52 | exit 1 |
| 53 | ;; |
| 54 | esac |
| 55 | } |
| 56 | |
| 57 | # --- Python ------------------------------------------------------------------ |
| 58 | |
| 59 | MIN_PYTHON_MAJOR=3 |
| 60 | MIN_PYTHON_MINOR=10 |
| 61 | |
| 62 | find_python() { |
| 63 | for cmd in python3 python; do |
| 64 | if command_exists "$cmd"; then |
| 65 | local ver |
| 66 | ver=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null) || continue |
| 67 | local major minor |
| 68 | major=$(echo "$ver" | cut -d. -f1) |
| 69 | minor=$(echo "$ver" | cut -d. -f2) |
| 70 | if [ "$major" -ge "$MIN_PYTHON_MAJOR" ] && [ "$minor" -ge "$MIN_PYTHON_MINOR" ]; then |
| 71 | PYTHON="$cmd" |
| 72 | PYTHON_VERSION="$ver" |
| 73 | return 0 |
| 74 | fi |
| 75 | fi |
| 76 | done |
| 77 | return 1 |
| 78 | } |
| 79 | |
| 80 | install_python() { |
| 81 | step "Installing Python 3.10+" |
| 82 | case "$OS" in |
| 83 | macos) |
| 84 | if command_exists brew; then |
| 85 | brew install [email protected] |
| 86 | else |
| 87 | err "Homebrew not found. Install it first: https://brew.sh" |
| 88 | exit 1 |
| 89 | fi |
| 90 | ;; |
| 91 | debian) |
| 92 | sudo apt-get update -qq |
| 93 | sudo apt-get install -y -qq python3 python3-pip python3-venv |
| 94 | ;; |
| 95 | fedora) |
| 96 | sudo dnf install -y -q python3 python3-pip |
| 97 | ;; |
| 98 | arch) |
| 99 | sudo pacman -S --noconfirm python python-pip |
| 100 | ;; |
| 101 | *) |
| 102 | err "Cannot auto-install Python on this OS." |
| 103 | err "Please install Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ manually." |
| 104 | exit 1 |
| 105 | ;; |
| 106 | esac |
| 107 | } |
| 108 | |
| 109 | # --- FFmpeg ------------------------------------------------------------------ |
| 110 | |
| 111 | install_ffmpeg() { |
| 112 | step "Installing FFmpeg" |
| 113 | case "$OS" in |
| 114 | macos) |
| 115 | if command_exists brew; then |
| 116 | brew install ffmpeg |
| 117 | else |
| 118 | err "Homebrew not found. Install it first: https://brew.sh" |
| 119 | exit 1 |
| 120 | fi |
| 121 | ;; |
| 122 | debian) |
| 123 | sudo apt-get update -qq |
| 124 | sudo apt-get install -y -qq ffmpeg |
| 125 | ;; |
| 126 | fedora) |
| 127 | sudo dnf install -y -q ffmpeg-free || { |
| 128 | warn "ffmpeg-free not available, trying RPM Fusion..." |
| 129 | sudo dnf install -y -q \ |
| 130 | "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm" 2>/dev/null || true |
| 131 | sudo dnf install -y -q ffmpeg |
| 132 | } |
| 133 | ;; |
| 134 | arch) |
| 135 | sudo pacman -S --noconfirm ffmpeg |
| 136 | ;; |
| 137 | *) |
| 138 | err "Cannot auto-install FFmpeg on this OS." |
| 139 | err "Please install FFmpeg manually: https://ffmpeg.org/download.html" |
| 140 | exit 1 |
| 141 | ;; |
| 142 | esac |
| 143 | } |
| 144 | |
| 145 | # --- Ollama (optional) ------------------------------------------------------- |
| 146 | |
| 147 | install_ollama() { |
| 148 | step "Installing Ollama" |
| 149 | if command_exists ollama; then |
| 150 | ok "Ollama already installed" |
| 151 | return 0 |
| 152 | fi |
| 153 | case "$OS" in |
| 154 | macos) |
| 155 | if command_exists brew; then |
| 156 | brew install ollama |
| 157 | else |
| 158 | curl -fsSL https://ollama.com/install.sh | sh |
| 159 | fi |
| 160 | ;; |
| 161 | debian|fedora|arch|linux-unknown) |
| 162 | curl -fsSL https://ollama.com/install.sh | sh |
| 163 | ;; |
| 164 | esac |
| 165 | } |
| 166 | |
| 167 | pull_ollama_models() { |
| 168 | info "Pulling recommended local models..." |
| 169 | ollama pull llama3.2 2>/dev/null || warn "Failed to pull llama3.2" |
| 170 | ollama pull llava 2>/dev/null || warn "Failed to pull llava (vision model)" |
| 171 | ok "Ollama models ready" |
| 172 | } |
| 173 | |
| 174 | # --- Extras selection -------------------------------------------------------- |
| 175 | |
| 176 | select_extras() { |
| 177 | EXTRAS="" |
| 178 | printf "\n${BOLD}Optional extras:${NC}\n" |
| 179 | echo " 1) core — just the basics (default)" |
| 180 | echo " 2) cloud — Google Drive, Dropbox, S3 integrations" |
| 181 | echo " 3) pdf — PDF document ingestion" |
| 182 | echo " 4) sources — YouTube, RSS, web scraping" |
| 183 | echo " 5) all — everything" |
| 184 | echo "" |
| 185 | printf "Choose extras [1-5, default=1]: " |
| 186 | |
| 187 | if [ -t 0 ]; then |
| 188 | read -r choice |
| 189 | else |
| 190 | choice="1" |
| 191 | echo "1 (non-interactive, using default)" |
| 192 | fi |
| 193 | |
| 194 | case "${choice:-1}" in |
| 195 | 2) EXTRAS="[cloud]" ;; |
| 196 | 3) EXTRAS="[pdf]" ;; |
| 197 | 4) EXTRAS="[sources]" ;; |
| 198 | 5) EXTRAS="[all]" ;; |
| 199 | *) EXTRAS="" ;; |
| 200 | esac |
| 201 | } |
| 202 | |
| 203 | # --- API key setup ----------------------------------------------------------- |
| 204 | |
| 205 | setup_api_keys() { |
| 206 | step "API key setup" |
| 207 | local env_file=".env" |
| 208 | |
| 209 | if [ -f "$env_file" ]; then |
| 210 | ok "Found existing .env file, skipping API key setup" |
| 211 | return 0 |
| 212 | fi |
| 213 | |
| 214 | if [ ! -t 0 ]; then |
| 215 | warn "Non-interactive mode — skipping API key setup" |
| 216 | info "Create a .env file with at least one API key:" |
| 217 | info " OPENAI_API_KEY=sk-..." |
| 218 | info " ANTHROPIC_API_KEY=sk-ant-..." |
| 219 | info " GEMINI_API_KEY=..." |
| 220 | return 0 |
| 221 | fi |
| 222 | |
| 223 | printf "\n${BOLD}PlanOpticon needs at least one AI provider API key.${NC}\n" |
| 224 | echo "You can add more later in .env" |
| 225 | echo "" |
| 226 | echo " 1) OpenAI (OPENAI_API_KEY)" |
| 227 | echo " 2) Anthropic (ANTHROPIC_API_KEY)" |
| 228 | echo " 3) Google (GEMINI_API_KEY)" |
| 229 | echo " 4) Ollama (local, no key needed)" |
| 230 | echo " 5) Skip for now" |
| 231 | echo "" |
| 232 | printf "Choose provider [1-5]: " |
| 233 | read -r provider_choice |
| 234 | |
| 235 | case "${provider_choice:-5}" in |
| 236 | 1) |
| 237 | printf "Enter your OpenAI API key: " |
| 238 | read -r api_key |
| 239 | if [ -n "$api_key" ]; then |
| 240 | echo "OPENAI_API_KEY=$api_key" > "$env_file" |
| 241 | ok "Saved to .env" |
| 242 | fi |
| 243 | ;; |
| 244 | 2) |
| 245 | printf "Enter your Anthropic API key: " |
| 246 | read -r api_key |
| 247 | if [ -n "$api_key" ]; then |
| 248 | echo "ANTHROPIC_API_KEY=$api_key" > "$env_file" |
| 249 | ok "Saved to .env" |
| 250 | fi |
| 251 | ;; |
| 252 | 3) |
| 253 | printf "Enter your Google/Gemini API key: " |
| 254 | read -r api_key |
| 255 | if [ -n "$api_key" ]; then |
| 256 | echo "GEMINI_API_KEY=$api_key" > "$env_file" |
| 257 | ok "Saved to .env" |
| 258 | fi |
| 259 | ;; |
| 260 | 4) |
| 261 | info "Using Ollama — no API key needed" |
| 262 | echo "OLLAMA_HOST=http://localhost:11434" > "$env_file" |
| 263 | ;; |
| 264 | 5) |
| 265 | warn "Skipping API key setup. Add keys to .env later." |
| 266 | ;; |
| 267 | esac |
| 268 | } |
| 269 | |
| 270 | # --- Main -------------------------------------------------------------------- |
| 271 | |
| 272 | main() { |
| 273 | printf "\n${BOLD}PlanOpticon Installer${NC}\n" |
| 274 | echo "=====================================" |
| 275 | echo "" |
| 276 | |
| 277 | detect_os |
| 278 | info "Detected OS: $OS" |
| 279 | |
| 280 | # --- Python --- |
| 281 | step "Checking Python" |
| 282 | if find_python; then |
| 283 | ok "Python $PYTHON_VERSION found ($PYTHON)" |
| 284 | else |
| 285 | warn "Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ not found" |
| 286 | install_python |
| 287 | if ! find_python; then |
| 288 | err "Python installation failed. Please install Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ manually." |
| 289 | exit 1 |
| 290 | fi |
| 291 | ok "Python $PYTHON_VERSION installed" |
| 292 | fi |
| 293 | |
| 294 | # --- FFmpeg --- |
| 295 | step "Checking FFmpeg" |
| 296 | if command_exists ffmpeg; then |
| 297 | local ffmpeg_ver |
| 298 | ffmpeg_ver=$(ffmpeg -version 2>/dev/null | head -1 | awk '{print $3}') || ffmpeg_ver="unknown" |
| 299 | ok "FFmpeg found ($ffmpeg_ver)" |
| 300 | else |
| 301 | warn "FFmpeg not found" |
| 302 | install_ffmpeg |
| 303 | if command_exists ffmpeg; then |
| 304 | ok "FFmpeg installed" |
| 305 | else |
| 306 | err "FFmpeg installation failed. Please install manually." |
| 307 | exit 1 |
| 308 | fi |
| 309 | fi |
| 310 | |
| 311 | # --- pip install planopticon --- |
| 312 | step "Installing PlanOpticon" |
| 313 | select_extras |
| 314 | |
| 315 | local pip_target="planopticon${EXTRAS}" |
| 316 | info "Installing: $pip_target" |
| 317 | |
| 318 | if "$PYTHON" -m pip install --upgrade "$pip_target" 2>&1; then |
| 319 | ok "PlanOpticon installed" |
| 320 | else |
| 321 | warn "pip install failed, trying with --user flag" |
| 322 | "$PYTHON" -m pip install --user --upgrade "$pip_target" |
| 323 | ok "PlanOpticon installed (user)" |
| 324 | fi |
| 325 | |
| 326 | # --- Ollama (optional) --- |
| 327 | if [ -t 0 ]; then |
| 328 | printf "\n${BOLD}Would you like to install Ollama for local AI? [y/N]: ${NC}" |
| 329 | read -r install_ollama_choice |
| 330 | if [[ "${install_ollama_choice:-n}" =~ ^[Yy] ]]; then |
| 331 | install_ollama |
| 332 | if command_exists ollama; then |
| 333 | printf "Pull recommended models? (llama3.2 + llava, ~6GB) [y/N]: " |
| 334 | read -r pull_choice |
| 335 | if [[ "${pull_choice:-n}" =~ ^[Yy] ]]; then |
| 336 | pull_ollama_models |
| 337 | fi |
| 338 | fi |
| 339 | fi |
| 340 | fi |
| 341 | |
| 342 | # --- API keys --- |
| 343 | setup_api_keys |
| 344 | |
| 345 | # --- Verify --- |
| 346 | step "Verifying installation" |
| 347 | if command_exists planopticon; then |
| 348 | local version |
| 349 | version=$(planopticon --version 2>/dev/null || echo "installed") |
| 350 | ok "planopticon CLI ready ($version)" |
| 351 | else |
| 352 | # Might need PATH update |
| 353 | warn "planopticon not in PATH — you may need to restart your shell" |
| 354 | info "Or run: $PYTHON -m video_processor.cli.commands --version" |
| 355 | fi |
| 356 | |
| 357 | if planopticon list-models >/dev/null 2>&1; then |
| 358 | ok "Provider check passed" |
| 359 | else |
| 360 | warn "No AI providers configured yet — add an API key to .env" |
| 361 | fi |
| 362 | |
| 363 | # --- Done --- |
| 364 | printf "\n${GREEN}${BOLD}Installation complete!${NC}\n" |
| 365 | echo "" |
| 366 | echo "Quick start:" |
| 367 | echo " planopticon process video.mp4 # Analyze a video" |
| 368 | echo " planopticon companion # Start AI companion" |
| 369 | echo " planopticon list-models # Check available models" |
| 370 | echo "" |
| 371 | echo "Docs: https://planopticon.dev" |
| 372 | echo "" |
| 373 | } |
| 374 | |
| 375 | main "$@" |