/ 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  }