2026-06-01 15:09:47 +09:00
# Requires -Version 5.1
<#
Odysseus - native Windows launcher ( no Docker ) .
One command to : create a virtualenv , install dependencies , run first-time
setup ( prints an admin password on first run ) , and start the server .
Safe to re-run - it skips whatever already exists .
Usage :
powershell -ExecutionPolicy Bypass -File . \ launch-windows . ps1
powershell -ExecutionPolicy Bypass -File . \ launch-windows . ps1 -Port 7000 -BindHost 127.0 . 0 . 1
Tip : bind 127.0 . 0 . 1 ( default ) for local-only use . Use 0.0 . 0 . 0 only when you
intentionally want other devices on your LAN to reach it .
#>
param (
[ int ] $Port = 7000 ,
[ string ] $BindHost = " 127.0.0.1 "
)
$ErrorActionPreference = " Stop "
Set-Location -Path $PSScriptRoot
function Write-Step($msg ) { Write-Host " " ; Write-Host ( " ==> " + $msg ) -ForegroundColor Cyan }
function Fail($msg ) {
Write-Host " "
Write-Host ( " ERROR: " + $msg ) -ForegroundColor Red
Write-Host " "
Read-Host " Press Enter to exit "
exit 1
}
2026-06-11 13:44:39 +02:00
function Test-WindowsBashStub($path ) {
if ( -not $path ) { return $false }
$lowered = $path . ToLowerInvariant ( )
foreach ( $stub in @ ( " system32\bash.exe " , " sysnative\bash.exe " , " windowsapps\bash.exe " ) ) {
if ( $lowered . Contains ( $stub ) ) { return $true }
}
return $false
}
2026-06-02 06:45:48 -05:00
function Find-GitBash {
$cmd = Get-Command bash -ErrorAction SilentlyContinue
2026-06-11 13:44:39 +02:00
if ( $cmd -and -not ( Test-WindowsBashStub $cmd . Source ) ) { return $cmd . Source }
2026-06-02 06:45:48 -05:00
$roots = @ ( )
foreach ( $name in @ ( " ProgramFiles " , " ProgramW6432 " , " ProgramFiles(x86) " , " LocalAppData " ) ) {
$base = [ Environment ] :: GetEnvironmentVariable ( $name )
2026-06-11 13:44:39 +02:00
if ( $base ) {
$roots + = ( Join-Path $base " Git " )
if ( $name -eq " LocalAppData " ) { $roots + = ( Join-Path $base " Programs\Git " ) }
}
2026-06-02 06:45:48 -05:00
}
$roots + = @ ( " C:\Program Files\Git " , " C:\Program Files (x86)\Git " )
foreach ( $root in ( $roots | Select-Object -Unique ) ) {
foreach ( $relative in @ ( " bin\bash.exe " , " usr\bin\bash.exe " ) ) {
$candidate = Join-Path $root $relative
if ( Test-Path $candidate ) { return $candidate }
}
}
return $null
}
2026-06-01 23:50:58 -04:00
# 1. Locate a Python interpreter (3.11+ required)
2026-06-01 15:09:47 +09:00
Write-Step " Checking for Python "
2026-06-01 23:50:58 -04:00
function Get-PythonVersionText($launcher , $launcherArgs ) {
try {
return ( & $launcher @launcherArgs -c " import sys; print('.'.join(map(str, sys.version_info[:3]))) " 2 > $null ) . Trim ( )
} catch {
return $null
}
}
2026-06-01 15:09:47 +09:00
$pyExe = $null
2026-06-01 23:50:58 -04:00
$pyArgs = @ ( )
$pyVersion = $null
$pyLauncher = Get-Command py -ErrorAction SilentlyContinue
if ( $pyLauncher ) {
foreach ( $v in @ ( " -3.13 " , " -3.12 " , " -3.11 " ) ) {
$ver = Get-PythonVersionText $pyLauncher . Source @ ( $v )
if ( $ver ) {
$pyExe = $pyLauncher . Source
$pyArgs = @ ( $v )
$pyVersion = $ver
break
}
}
2026-06-01 15:09:47 +09:00
}
2026-06-01 23:50:58 -04:00
if ( -not $pyExe ) {
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
if ( $pythonCmd ) {
$ver = Get-PythonVersionText $pythonCmd . Source @ ( )
if ( $ver ) {
$versionParts = $ver . Split ( '.' )
$major = [ int ] $versionParts [ 0 ]
$minor = [ int ] $versionParts [ 1 ]
if ( $major -gt 3 -or ( $major -eq 3 -and $minor -ge 11 ) ) {
$pyExe = $pythonCmd . Source
$pyVersion = $ver
}
}
}
}
2026-06-16 06:58:16 +03:00
if ( $pyExe -like " *WindowsApps*python.exe " ) {
$pyCmd = Get-Command py -ErrorAction SilentlyContinue
if ( $pyCmd ) {
$pyExe = $pyCmd . Source
$pyArgs = @ ( " -3.11 " )
}
}
2026-06-01 15:09:47 +09:00
if ( -not $pyExe ) {
2026-06-01 23:50:58 -04:00
Fail " Couldn't find Python 3.11+ for Windows setup. Install Python 3.11+ (or open the Python launcher with 'py -3.11') from https://www.python.org/downloads/, then re-run this script. "
2026-06-01 15:09:47 +09:00
}
2026-06-01 23:50:58 -04:00
$pythonLabel = ( " Using Python {0}: {1} {2} " -f $pyVersion , $pyExe , ( $pyArgs -join ' ' ) ) . TrimEnd ( )
Write-Host $pythonLabel
2026-06-01 15:09:47 +09:00
# 2. Create the virtualenv if missing
$venvPy = Join-Path $PSScriptRoot " venv\Scripts\python.exe "
if ( -not ( Test-Path $venvPy ) ) {
Write-Step " Creating virtual environment (venv) "
2026-06-01 23:50:58 -04:00
& $pyExe @pyArgs -m venv venv
2026-06-01 15:09:47 +09:00
if ( $LASTEXITCODE -ne 0 -or -not ( Test-Path $venvPy ) ) { Fail " Failed to create the virtual environment. " }
} else {
Write-Host " venv already exists - skipping creation. "
}
# 3. Install / update dependencies
Write-Step " Installing dependencies (first run can take a few minutes) "
& $venvPy -m pip install - -upgrade pip - -quiet
& $venvPy -m pip install -r requirements . txt
if ( $LASTEXITCODE -ne 0 ) { Fail " Dependency install failed. Scroll up for the pip error. " }
# 4. First-time setup (creates data dirs, DB, .env, admin user)
Write-Step " Running first-time setup "
& $venvPy setup . py
if ( $LASTEXITCODE -ne 0 ) { Fail " setup.py failed. " }
# 5. Friendly note about Git Bash (full Cookbook / agent-shell parity)
2026-06-02 06:45:48 -05:00
if ( -not ( Find-GitBash ) ) {
2026-06-01 15:09:47 +09:00
Write-Host " "
Write-Host " NOTE: Git Bash (bash.exe) was not found on PATH. " -ForegroundColor Yellow
Write-Host " The core app works without it. For full Cookbook background " -ForegroundColor Yellow
Write-Host " downloads and the agent shell tool, install Git for Windows: " -ForegroundColor Yellow
Write-Host " https://git-scm.com/download/win " -ForegroundColor Yellow
}
2026-06-15 07:26:07 -04:00
# 6. Point CUDA_PATH at a real CUDA toolkit so GPU llama-cpp-python can import.
$cudaBase = " C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA "
if ( Test-Path $cudaBase ) {
$cudaBest = Get-ChildItem $cudaBase -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path ( Join-Path $_ . FullName " bin " ) } |
Sort-Object { try { [ version ] ( $_ . Name -replace " ^v " , " " ) } catch { [ version ] " 0.0 " } } -Descending |
Select-Object -First 1
if ( $cudaBest ) {
$env:CUDA_PATH = $cudaBest . FullName
Write-Host ( " Using CUDA_PATH = " + $cudaBest . FullName ) -ForegroundColor Cyan
}
}
# 7. Start the server (use `python -m uvicorn` - bare `uvicorn` may not be on PATH)
2026-06-01 15:09:47 +09:00
Write-Step ( " Starting Odysseus at http://{0}:{1} " -f $BindHost , $Port )
Write-Host " Press Ctrl+C to stop. "
Write-Host " "
& $venvPy -m uvicorn app : app - -host $BindHost - -port $Port