/ install.ps1
install.ps1
1 # InterBrain Installation Script for Windows 2 # PowerShell 5.1+ required 3 # 4 # One-command setup (interactive): 5 # irm https://raw.githubusercontent.com/ProjectLiminality/InterBrain/main/install.ps1 | iex 6 # 7 # With specific branch (for testing): 8 # $env:INTERBRAIN_BRANCH = "feature/test"; irm https://raw.githubusercontent.com/ProjectLiminality/InterBrain/main/install.ps1 | iex 9 # 10 # CI mode (non-interactive): 11 # .\install.ps1 -CI 12 # 13 # Note: Full Radicle P2P support on Windows is in development by the Radicle team. 14 15 param( 16 [switch]$CI, 17 [string]$Branch = "main", 18 [string]$Uri = "", 19 [string]$DreamerUuid = "" 20 ) 21 22 # Use environment variable for branch if set (allows piped execution with branch) 23 if ($env:INTERBRAIN_BRANCH) { 24 $Branch = $env:INTERBRAIN_BRANCH 25 } 26 27 $ErrorActionPreference = "Stop" 28 29 # Total steps 30 $TOTAL_STEPS = 11 31 32 # Log file 33 $LOG_FILE = Join-Path $env:TEMP "interbrain-install-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" 34 35 # Helper functions 36 function Write-Step { 37 param([int]$Step, [string]$Message) 38 Write-Host "" 39 Write-Host ("=" * 60) -ForegroundColor Cyan 40 Write-Host "Step $Step/$TOTAL_STEPS`: $Message" -ForegroundColor Cyan 41 Write-Host ("=" * 60) -ForegroundColor Cyan 42 } 43 44 function Write-Success { 45 param([string]$Message) 46 Write-Host "[OK] $Message" -ForegroundColor Green 47 Add-Content -Path $LOG_FILE -Value "[OK] $Message" 48 } 49 50 function Write-Warning { 51 param([string]$Message) 52 Write-Host "[!] $Message" -ForegroundColor Yellow 53 Add-Content -Path $LOG_FILE -Value "[!] $Message" 54 } 55 56 function Write-Error { 57 param([string]$Message) 58 Write-Host "[X] $Message" -ForegroundColor Red 59 Add-Content -Path $LOG_FILE -Value "[X] $Message" 60 } 61 62 function Write-Info { 63 param([string]$Message) 64 Write-Host "[i] $Message" -ForegroundColor Blue 65 Add-Content -Path $LOG_FILE -Value "[i] $Message" 66 } 67 68 function Test-Command { 69 param([string]$Command) 70 $null = Get-Command $Command -ErrorAction SilentlyContinue 71 return $? 72 } 73 74 function Install-WithWinget { 75 param([string]$PackageId, [string]$Name) 76 77 if (Test-Command "winget") { 78 Write-Info "Installing $Name via winget..." 79 winget install --id $PackageId --accept-source-agreements --accept-package-agreements -e 80 return $true 81 } 82 return $false 83 } 84 85 function Install-WithChoco { 86 param([string]$PackageName, [string]$Name) 87 88 if (Test-Command "choco") { 89 Write-Info "Installing $Name via Chocolatey..." 90 choco install $PackageName -y 91 return $true 92 } 93 return $false 94 } 95 96 function Refresh-Path { 97 $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") 98 } 99 100 # Windows tracking issue number 101 $WINDOWS_TRACKING_ISSUE = 363 102 103 # Function to sanitize log (remove sensitive data) 104 function Get-SanitizedLog { 105 $content = Get-Content -Path $LOG_FILE -Raw -ErrorAction SilentlyContinue 106 if (-not $content) { return "" } 107 108 # Sanitize sensitive info 109 $content = $content -replace [regex]::Escape($env:USERPROFILE), "~" 110 $content = $content -replace [regex]::Escape($env:USERNAME), "<USER>" 111 $content = $content -replace '(api[_-]?key|token|secret|password|passphrase)[:=]\s*\S+', '$1=<REDACTED>' 112 return $content 113 } 114 115 # Function to report error to GitHub tracking issue 116 function Report-ToGitHub { 117 if (-not (Test-Command "gh")) { 118 Write-Warning "GitHub CLI not installed - cannot report automatically" 119 Write-Info "Report manually at: https://github.com/ProjectLiminality/InterBrain/issues/$WINDOWS_TRACKING_ISSUE" 120 return 121 } 122 123 # Check if authenticated 124 $authStatus = gh auth status 2>&1 125 if ($LASTEXITCODE -ne 0) { 126 Write-Warning "GitHub CLI not authenticated - cannot report automatically" 127 Write-Info "Authenticate with 'gh auth login' or report manually at:" 128 Write-Info "https://github.com/ProjectLiminality/InterBrain/issues/$WINDOWS_TRACKING_ISSUE" 129 return 130 } 131 132 Write-Info "Adding error report to Windows tracking issue (#$WINDOWS_TRACKING_ISSUE)..." 133 134 $sanitizedLog = Get-SanitizedLog 135 $commentBody = @" 136 ## Installation Error Report 137 138 **Generated**: $(Get-Date) 139 **OS**: $([System.Environment]::OSVersion.VersionString) 140 **PowerShell**: $($PSVersionTable.PSVersion) 141 142 --- 143 144 ### Installation Log 145 146 `````` 147 $sanitizedLog 148 `````` 149 150 --- 151 152 *Automatically reported by install script* 153 "@ 154 155 gh issue comment $WINDOWS_TRACKING_ISSUE --repo ProjectLiminality/InterBrain --body $commentBody 156 157 if ($LASTEXITCODE -eq 0) { 158 Write-Success "Error report added to tracking issue!" 159 Write-Info "Opening the issue in your browser..." 160 Start-Process "https://github.com/ProjectLiminality/InterBrain/issues/$WINDOWS_TRACKING_ISSUE" 161 Write-Info "Check the 'Known Solutions' section at the top of the issue." 162 } else { 163 Write-Error "Failed to add comment to tracking issue" 164 Write-Info "Report manually at: https://github.com/ProjectLiminality/InterBrain/issues/$WINDOWS_TRACKING_ISSUE" 165 } 166 } 167 168 # Error handler 169 function Handle-InstallError { 170 param([string]$Step, [string]$ErrorMessage) 171 172 Write-Host "" 173 Write-Host ("=" * 50) -ForegroundColor Red 174 Write-Error "Installation failed at: $Step" 175 Write-Host ("=" * 50) -ForegroundColor Red 176 Write-Host "" 177 Write-Info "Installation log saved to: $LOG_FILE" 178 Write-Host "" 179 Write-Info "You can safely rerun this script - it won't destroy existing data" 180 Write-Host "" 181 182 if (-not $CI) { 183 Write-Host "Would you like to report this issue?" -ForegroundColor Yellow 184 Write-Host "" 185 Write-Host " 1) Report to GitHub (adds to tracking issue + opens browser)" 186 Write-Host " 2) Just show me the log location" 187 Write-Host "" 188 $choice = Read-Host "Choose [1/2]" 189 190 switch ($choice) { 191 "1" { Report-ToGitHub } 192 default { Write-Info "Log: $LOG_FILE" } 193 } 194 } 195 } 196 197 # Start logging 198 Add-Content -Path $LOG_FILE -Value "=== InterBrain Windows Installation Log ===" 199 Add-Content -Path $LOG_FILE -Value "Date: $(Get-Date)" 200 Add-Content -Path $LOG_FILE -Value "OS: $([System.Environment]::OSVersion.VersionString)" 201 Add-Content -Path $LOG_FILE -Value "PowerShell: $($PSVersionTable.PSVersion)" 202 Add-Content -Path $LOG_FILE -Value "CI Mode: $CI" 203 Add-Content -Path $LOG_FILE -Value "============================================" 204 205 Write-Host "" 206 Write-Host "InterBrain Installation Script for Windows" -ForegroundColor Magenta 207 Write-Host ("=" * 45) -ForegroundColor Magenta 208 Write-Host "" 209 210 if ($CI) { 211 Write-Info "Running in CI mode - non-interactive with defaults" 212 $VaultParent = $env:TEMP 213 $VaultName = "interbrain-ci-test-$PID" 214 } else { 215 $VaultParent = $env:USERPROFILE 216 $VaultName = "DreamVault" 217 } 218 219 # ============================================================ 220 # Step 1: GitHub CLI setup (enables error reporting) 221 # ============================================================ 222 Write-Step -Step 1 -Message "GitHub CLI setup (enables error reporting)" 223 224 Write-Info "Setting up GitHub CLI first so errors can be automatically reported." 225 Write-Host "" 226 227 # Check for winget or chocolatey 228 $HasWinget = Test-Command "winget" 229 $HasChoco = Test-Command "choco" 230 231 if (-not $HasWinget -and -not $HasChoco) { 232 Write-Warning "Neither winget nor Chocolatey found." 233 Write-Info "Attempting to use winget (built into Windows 10/11)..." 234 235 # Check Windows version 236 $WinVersion = [System.Environment]::OSVersion.Version 237 if ($WinVersion.Major -lt 10) { 238 Write-Error "Windows 10 or later required for winget." 239 Write-Info "Please install Chocolatey manually: https://chocolatey.org/install" 240 exit 1 241 } 242 243 # winget should be available on Windows 10 1709+ 244 Write-Info "If winget is not working, install App Installer from Microsoft Store" 245 } 246 247 # Check for GitHub CLI 248 if (-not (Test-Command "gh")) { 249 Write-Warning "GitHub CLI not found. Installing..." 250 if (-not (Install-WithWinget "GitHub.cli" "GitHub CLI")) { 251 if (-not (Install-WithChoco "gh" "GitHub CLI")) { 252 Write-Error "Failed to install GitHub CLI. Please install manually from https://cli.github.com" 253 exit 1 254 } 255 } 256 Refresh-Path 257 Write-Success "GitHub CLI installed" 258 } else { 259 Write-Success "GitHub CLI found ($(gh --version | Select-Object -First 1))" 260 } 261 262 # Authenticate GitHub CLI (enables error reporting) 263 if (Test-Command "gh") { 264 $authStatus = gh auth status 2>&1 265 if ($LASTEXITCODE -eq 0) { 266 Write-Success "GitHub CLI already authenticated" 267 try { 268 $ghUser = gh api user -q .login 2>$null 269 Write-Info "Logged in as: $ghUser" 270 } catch { } 271 } else { 272 Write-Host "" 273 Write-Info "GitHub authentication enables:" 274 Write-Info " - Automatic error reporting if installation fails" 275 Write-Info " - Collaborative DreamNode sharing" 276 Write-Info " - Version control and backups" 277 Write-Host "" 278 279 if ($CI) { 280 Write-Info "CI mode: Skipping GitHub authentication" 281 } else { 282 $authChoice = Read-Host "Authenticate GitHub now? [Y/n]" 283 if ($authChoice -eq "" -or $authChoice -match "^[Yy]") { 284 gh auth login -h github.com -p https -w 285 $authStatus = gh auth status 2>&1 286 if ($LASTEXITCODE -eq 0) { 287 Write-Success "GitHub authenticated" 288 try { 289 $ghUser = gh api user -q .login 2>$null 290 Write-Info "Logged in as: $ghUser" 291 } catch { } 292 } else { 293 Write-Warning "Authentication incomplete - you can complete it later with: gh auth login" 294 } 295 } else { 296 Write-Info "Skipping - you can authenticate later with: gh auth login" 297 } 298 } 299 } 300 } 301 302 Write-Success "Error reporting is now available for subsequent steps" 303 Write-Host "" 304 305 # ============================================================ 306 # Step 2: Installing other prerequisites 307 # ============================================================ 308 Write-Step -Step 2 -Message "Installing other prerequisites" 309 310 # Check for Git 311 if (-not (Test-Command "git")) { 312 Write-Warning "Git not found. Installing..." 313 if (-not (Install-WithWinget "Git.Git" "Git")) { 314 if (-not (Install-WithChoco "git" "Git")) { 315 Handle-InstallError -Step "Git installation" -ErrorMessage "Failed to install Git" 316 Write-Error "Failed to install Git. Please install manually from https://git-scm.com" 317 exit 1 318 } 319 } 320 Refresh-Path 321 Write-Success "Git installed" 322 } else { 323 Write-Success "Git found ($(git --version))" 324 } 325 326 # Check for Node.js 327 if (-not (Test-Command "node")) { 328 Write-Warning "Node.js not found. Installing..." 329 if (-not (Install-WithWinget "OpenJS.NodeJS.LTS" "Node.js")) { 330 if (-not (Install-WithChoco "nodejs-lts" "Node.js")) { 331 Handle-InstallError -Step "Node.js installation" -ErrorMessage "Failed to install Node.js" 332 Write-Error "Failed to install Node.js. Please install manually from https://nodejs.org" 333 exit 1 334 } 335 } 336 Refresh-Path 337 338 # After fresh install, verify node is accessible 339 if (-not (Test-Command "node")) { 340 Write-Warning "Node.js installed but not in PATH yet." 341 Write-Info "Please close this PowerShell window and open a new one, then run the installer again." 342 Write-Info "This is needed for Windows to recognize the newly installed Node.js." 343 exit 0 344 } 345 Write-Success "Node.js installed" 346 } else { 347 Write-Success "Node.js found ($(node --version))" 348 } 349 350 # ============================================================ 351 # Step 2: Check for Obsidian 352 # ============================================================ 353 Write-Step -Step 3 -Message "Checking for Obsidian" 354 355 $ObsidianPath = Join-Path $env:LOCALAPPDATA "Obsidian\Obsidian.exe" 356 if (Test-Path $ObsidianPath) { 357 Write-Success "Obsidian found" 358 $ObsidianInstalled = $true 359 } else { 360 Write-Warning "Obsidian not found. Installing..." 361 if (-not (Install-WithWinget "Obsidian.Obsidian" "Obsidian")) { 362 if (-not (Install-WithChoco "obsidian" "Obsidian")) { 363 Write-Warning "Could not auto-install Obsidian. Please install from https://obsidian.md" 364 $ObsidianInstalled = $false 365 } else { 366 $ObsidianInstalled = $true 367 } 368 } else { 369 $ObsidianInstalled = $true 370 } 371 if ($ObsidianInstalled) { 372 Write-Success "Obsidian installed" 373 } 374 } 375 376 # ============================================================ 377 # Step 3: Set up vault 378 # ============================================================ 379 Write-Step -Step 4 -Message "Setting up vault" 380 381 if (-not $CI) { 382 Write-Info "InterBrain works best in a dedicated vault (not mixed with regular notes)" 383 $UserVaultName = Read-Host "Vault name (press Enter for default '$VaultName')" 384 if ($UserVaultName) { 385 $VaultName = $UserVaultName 386 } 387 } 388 389 $VaultPath = Join-Path $VaultParent $VaultName 390 391 if (Test-Path $VaultPath) { 392 $InterBrainPluginPath = Join-Path $VaultPath ".obsidian\plugins\interbrain" 393 if (Test-Path $InterBrainPluginPath) { 394 Write-Success "Found existing InterBrain vault: $VaultPath" 395 Write-Info "Re-running setup to ensure everything is up to date..." 396 } else { 397 Write-Warning "Vault '$VaultName' exists but is not an InterBrain vault" 398 if (-not $CI) { 399 $Confirm = Read-Host "Continue anyway? [y/N]" 400 if ($Confirm -ne "y" -and $Confirm -ne "Y") { 401 Write-Info "Installation cancelled. Please rerun with a different vault name." 402 exit 0 403 } 404 } else { 405 Write-Error "Cannot proceed in CI mode with existing non-InterBrain vault" 406 exit 1 407 } 408 } 409 } else { 410 New-Item -ItemType Directory -Path $VaultPath -Force | Out-Null 411 Write-Success "Created new InterBrain vault: $VaultPath" 412 } 413 414 # Create .obsidian directory structure 415 $ObsidianDir = Join-Path $VaultPath ".obsidian\plugins" 416 New-Item -ItemType Directory -Path $ObsidianDir -Force | Out-Null 417 418 # ============================================================ 419 # Step 4: Clone InterBrain 420 # ============================================================ 421 Write-Step -Step 5 -Message "Cloning InterBrain" 422 423 $InterBrainPath = Join-Path $VaultPath "InterBrain" 424 425 if (Test-Path $InterBrainPath) { 426 if (Test-Path (Join-Path $InterBrainPath ".git")) { 427 Set-Location $InterBrainPath 428 $RepoUrl = git config --get remote.origin.url 2>$null 429 if ($RepoUrl -match "ProjectLiminality/InterBrain") { 430 Write-Warning "InterBrain already exists. Updating..." 431 git fetch origin $Branch 432 git checkout $Branch 433 git pull origin $Branch 434 } else { 435 Write-Error "Directory exists but is a different repository." 436 Write-Info "Please rename or move: $InterBrainPath" 437 exit 1 438 } 439 } else { 440 Write-Error "Directory exists but is not a git repository." 441 Write-Info "Please rename or move: $InterBrainPath" 442 exit 1 443 } 444 } else { 445 Write-Info "Cloning from GitHub (branch: $Branch)..." 446 Set-Location $VaultPath 447 git clone --branch $Branch https://github.com/ProjectLiminality/InterBrain.git 448 Set-Location $InterBrainPath 449 } 450 451 Write-Success "InterBrain code ready at: $InterBrainPath" 452 453 # ============================================================ 454 # Step 5: Build plugin 455 # ============================================================ 456 Write-Step -Step 6 -Message "Building plugin" 457 458 Set-Location $InterBrainPath 459 460 # Determine how to run npm (direct command vs node path) 461 $NpmCommand = $null 462 if (Test-Command "npm") { 463 $NpmCommand = "npm" 464 } else { 465 # npm not in PATH - try to find it via node installation 466 $NodePath = (Get-Command node -ErrorAction SilentlyContinue).Source 467 if ($NodePath) { 468 $NodeDir = Split-Path $NodePath -Parent 469 $NpmCliPath = Join-Path $NodeDir "node_modules\npm\bin\npm-cli.js" 470 if (Test-Path $NpmCliPath) { 471 $NpmCommand = "node `"$NpmCliPath`"" 472 Write-Info "Using npm via node directly (npm not in PATH)" 473 } 474 } 475 } 476 477 if (-not $NpmCommand) { 478 Write-Error "npm not found. Please close this PowerShell window, open a new one, and run the installer again." 479 Write-Info "If the problem persists, reinstall Node.js from https://nodejs.org" 480 exit 1 481 } 482 483 Write-Info "Installing Node.js dependencies..." 484 Invoke-Expression "$NpmCommand install --silent 2>`$null" 485 if ($LASTEXITCODE -ne 0) { 486 Write-Error "npm install failed" 487 exit 1 488 } 489 490 Write-Info "Building InterBrain plugin..." 491 Invoke-Expression "$NpmCommand run build 2>`$null" 492 if ($LASTEXITCODE -ne 0) { 493 Write-Error "npm run build failed" 494 exit 1 495 } 496 497 Write-Success "Plugin built successfully" 498 499 # ============================================================ 500 # Step 6: Install theme 501 # ============================================================ 502 Write-Step -Step 7 -Message "Installing InterBrain theme" 503 504 $SnippetsDir = Join-Path $VaultPath ".obsidian\snippets" 505 New-Item -ItemType Directory -Path $SnippetsDir -Force | Out-Null 506 507 $ThemeSource = Join-Path $InterBrainPath "theme\interbrain.css" 508 if (Test-Path $ThemeSource) { 509 Copy-Item $ThemeSource -Destination $SnippetsDir -Force 510 Write-Success "InterBrain theme installed" 511 } else { 512 Write-Warning "Theme file not found, skipping theme installation" 513 } 514 515 # Create appearance.json 516 $AppearanceJson = @{ 517 accentColor = "#00A2FF" 518 theme = "obsidian" 519 baseFontSize = 16 520 enabledCssSnippets = @("interbrain") 521 } | ConvertTo-Json 522 523 Set-Content -Path (Join-Path $VaultPath ".obsidian\appearance.json") -Value $AppearanceJson 524 Write-Success "Theme configuration created" 525 526 # ============================================================ 527 # Step 7: Install Ollama 528 # ============================================================ 529 Write-Step -Step 8 -Message "Installing Ollama for semantic search" 530 531 if (-not (Test-Command "ollama")) { 532 Write-Info "Installing Ollama..." 533 if (-not (Install-WithWinget "Ollama.Ollama" "Ollama")) { 534 Write-Warning "Could not auto-install Ollama." 535 Write-Info "Please install manually from https://ollama.ai" 536 } else { 537 Refresh-Path 538 Write-Success "Ollama installed" 539 } 540 } else { 541 Write-Success "Ollama found" 542 } 543 544 # Pull embedding model if Ollama is available (with timeout/skip option) 545 if (Test-Command "ollama") { 546 $OllamaList = ollama list 2>$null 547 if ($OllamaList -match "nomic-embed-text") { 548 Write-Success "nomic-embed-text model already installed" 549 } else { 550 Write-Info "Downloading nomic-embed-text model..." 551 Write-Info "This may take 1-2 minutes depending on your connection." 552 Write-Host "" 553 554 # Start download in background job 555 $Job = Start-Job -ScriptBlock { ollama pull nomic-embed-text 2>$null } 556 557 # Timeout after 2 minutes 558 $Timeout = 120 559 $Elapsed = 0 560 561 while ($Job.State -eq "Running") { 562 if ($Elapsed -ge $Timeout) { 563 Write-Host "" 564 Write-Warning "Download is taking longer than expected." 565 Write-Host "" 566 567 if (-not $CI) { 568 Write-Host "What would you like to do?" 569 Write-Host " 1) Keep waiting" 570 Write-Host " 2) Skip for now (you can download later in InterBrain settings)" 571 $OllamaChoice = Read-Host "Choose [1/2]" 572 573 if ($OllamaChoice -eq "2") { 574 Stop-Job -Job $Job 575 Remove-Job -Job $Job -Force 576 Write-Warning "Skipped Ollama model download" 577 Write-Info "You can download it later via InterBrain settings or run:" 578 Write-Info " ollama pull nomic-embed-text" 579 break 580 } else { 581 # Reset timeout and continue waiting 582 $Elapsed = 0 583 Write-Info "Continuing to wait..." 584 } 585 } else { 586 # CI mode, just keep waiting 587 $Elapsed = 0 588 } 589 } 590 Start-Sleep -Seconds 1 591 $Elapsed++ 592 # Show progress dots every 10 seconds 593 if ($Elapsed % 10 -eq 0) { 594 Write-Host "." -NoNewline 595 } 596 } 597 598 # Clean up job 599 if ($Job.State -eq "Completed") { 600 Remove-Job -Job $Job 601 Write-Host "" 602 Write-Success "nomic-embed-text model installed" 603 } 604 } 605 } 606 607 # ============================================================ 608 # Step 8: Link plugin to vault 609 # ============================================================ 610 Write-Step -Step 9 -Message "Linking plugin to vault" 611 612 $PluginsDir = Join-Path $VaultPath ".obsidian\plugins" 613 New-Item -ItemType Directory -Path $PluginsDir -Force | Out-Null 614 615 $SymlinkPath = Join-Path $PluginsDir "InterBrain" 616 617 # Remove existing symlink or directory 618 if (Test-Path $SymlinkPath) { 619 Remove-Item $SymlinkPath -Force -Recurse 620 Write-Warning "Removed old plugin link" 621 } 622 623 # Create junction (Windows equivalent of symlink, works without admin) 624 cmd /c mklink /J "$SymlinkPath" "$InterBrainPath" 2>$null 625 if ($LASTEXITCODE -eq 0) { 626 Write-Success "Plugin linked to vault" 627 } else { 628 # Fallback: try with admin symlink 629 Write-Warning "Junction failed, trying symlink (may require admin)..." 630 New-Item -ItemType SymbolicLink -Path $SymlinkPath -Target $InterBrainPath -Force 631 Write-Success "Plugin symlinked to vault" 632 } 633 634 # Create community-plugins.json 635 Set-Content -Path (Join-Path $VaultPath ".obsidian\community-plugins.json") -Value '["interbrain"]' 636 637 # ============================================================ 638 # Step 10: Python setup for transcription 639 # ============================================================ 640 Write-Step -Step 10 -Message "Python setup for transcription" 641 642 Write-Info "Note: Full Radicle P2P support on Windows is in development by the Radicle team." 643 Write-Info "GitHub-based sharing is available now. P2P sharing will be enabled once Radicle has full Windows support." 644 Write-Host "" 645 646 # Check for compatible Python version (3.9-3.12 required by whisper dependencies) 647 # Python 3.13+ doesn't have pre-built wheels for scipy/numpy yet 648 $PythonCmd = $null 649 if (Get-Command "py" -ErrorAction SilentlyContinue) { 650 # Use py launcher to find compatible version 651 $pyVersions = @("3.12", "3.11", "3.10", "3.9") 652 foreach ($ver in $pyVersions) { 653 $testResult = py -$ver --version 2>$null 654 if ($LASTEXITCODE -eq 0) { 655 $PythonCmd = "py -$ver" 656 break 657 } 658 } 659 } 660 661 if (-not $PythonCmd) { 662 # Fall back to checking specific python commands 663 if (Get-Command "python3.12" -ErrorAction SilentlyContinue) { $PythonCmd = "python3.12" } 664 elseif (Get-Command "python3.11" -ErrorAction SilentlyContinue) { $PythonCmd = "python3.11" } 665 elseif (Get-Command "python3.10" -ErrorAction SilentlyContinue) { $PythonCmd = "python3.10" } 666 elseif (Get-Command "python3.9" -ErrorAction SilentlyContinue) { $PythonCmd = "python3.9" } 667 } 668 669 if (-not $PythonCmd) { 670 Write-Warning "Python 3.9-3.12 not found. Installing Python 3.11..." 671 if (-not (Install-WithWinget "Python.Python.3.11" "Python 3.11")) { 672 if (-not (Install-WithChoco "python311" "Python 3.11")) { 673 Write-Warning "Could not auto-install Python 3.11." 674 Write-Info "Please install Python 3.11 from https://python.org" 675 } else { 676 Refresh-Path 677 Write-Success "Python 3.11 installed" 678 } 679 } else { 680 Refresh-Path 681 Write-Success "Python 3.11 installed" 682 } 683 $PythonCmd = "py -3.11" 684 } else { 685 Write-Success "Python found (compatible version)" 686 } 687 688 # Set up transcription environment 689 $TranscriptionDir = Join-Path $InterBrainPath "src\features\realtime-transcription\scripts" 690 if (Test-Path $TranscriptionDir) { 691 $VenvPath = Join-Path $TranscriptionDir "venv" 692 if (-not (Test-Path $VenvPath)) { 693 Write-Info "Setting up Python transcription environment..." 694 Set-Location $TranscriptionDir 695 Invoke-Expression "$PythonCmd -m venv venv" 696 & "$VenvPath\Scripts\Activate.ps1" 697 pip install --upgrade pip --quiet 2>$null 698 pip install -r requirements.txt --quiet 2>$null 699 deactivate 700 Write-Success "Transcription environment ready" 701 } else { 702 Write-Success "Transcription environment already exists" 703 } 704 } else { 705 Write-Warning "Transcription directory not found" 706 } 707 708 # ============================================================ 709 # Step 12: Final verification and summary 710 # ============================================================ 711 Write-Step -Step 11 -Message "Final verification" 712 713 $AllGood = $true 714 715 # Check plugin build 716 if (Test-Path (Join-Path $InterBrainPath "main.js")) { 717 Write-Success "Plugin built (main.js exists)" 718 } else { 719 Write-Error "Plugin not built (main.js missing)" 720 $AllGood = $false 721 } 722 723 # Check plugin link 724 if (Test-Path $SymlinkPath) { 725 Write-Success "Plugin linked to vault" 726 } else { 727 Write-Error "Plugin link missing" 728 $AllGood = $false 729 } 730 731 # Check Obsidian 732 if ($ObsidianInstalled) { 733 Write-Success "Obsidian installed" 734 } else { 735 Write-Warning "Obsidian not installed" 736 } 737 738 # Check Ollama 739 if (Test-Command "ollama") { 740 Write-Success "Ollama available" 741 } else { 742 Write-Warning "Ollama not available" 743 } 744 745 # Check WSL 746 if (Test-WslReady) { 747 Write-Success "WSL installed" 748 } else { 749 Write-Warning "WSL not installed (required for P2P)" 750 } 751 752 # Check Radicle in WSL 753 if (Test-WslReady) { 754 if (Test-RadicleInWsl) { 755 Write-Success "Radicle installed in WSL" 756 if (Test-RadicleIdentity) { 757 Write-Success "Radicle identity configured" 758 } else { 759 Write-Warning "Radicle identity not configured (run: wsl rad auth)" 760 } 761 } else { 762 Write-Warning "Radicle not installed in WSL" 763 } 764 } 765 766 Write-Host "" 767 Write-Host ("=" * 60) -ForegroundColor Green 768 if ($AllGood) { 769 Write-Host "Installation complete!" -ForegroundColor Green 770 } else { 771 Write-Host "Installation complete with some optional steps remaining" -ForegroundColor Yellow 772 } 773 Write-Host ("=" * 60) -ForegroundColor Green 774 Write-Host "" 775 776 Write-Host "Installation log saved to: $LOG_FILE" 777 Write-Host "" 778 779 Write-Host "Next steps:" -ForegroundColor Cyan 780 Write-Host "1. Open Obsidian and select vault: $VaultPath" 781 Write-Host "2. Click 'Trust author and enable plugins' when prompted" 782 Write-Host "3. Look for the InterBrain icon in the left ribbon" 783 Write-Host "4. Configure settings (Anthropic API key, Radicle passphrase)" 784 Write-Host "" 785 786 if ($NeedsReboot) { 787 Write-Host "IMPORTANT: Restart required!" -ForegroundColor Yellow 788 Write-Host " 1. Restart your computer to complete WSL setup" 789 Write-Host " 2. Run this installer again to install Radicle" 790 Write-Host "" 791 } elseif (-not (Test-WslReady)) { 792 Write-Host "For full P2P features:" -ForegroundColor Yellow 793 Write-Host " 1. Run this script as Administrator to install WSL" 794 Write-Host " 2. Or manually: wsl --install" 795 Write-Host " 3. Restart and run this installer again" 796 Write-Host "" 797 } elseif (-not $RadicleReady) { 798 Write-Host "To complete P2P setup:" -ForegroundColor Yellow 799 Write-Host " Run: wsl rad auth" 800 Write-Host "" 801 } 802 803 Write-Host "Happy dreaming!" -ForegroundColor Magenta 804 Write-Host "" 805 806 # Open Obsidian with vault if not in CI mode 807 if (-not $CI -and $ObsidianInstalled) { 808 Write-Info "Opening Obsidian with your vault..." 809 Start-Process "obsidian://open?vault=$VaultName" 810 }