|
# World Engine Tool Launcher (we) |
|
# Downloads, caches, and runs World Engine tools from GitHub releases |
|
# |
|
# Usage: |
|
# we <app> [args...] Run app (auto-updates if new version available) |
|
# we update <app> Force update an app |
|
# we list List available apps |
|
# we clean Clean download cache |
|
# we help Show this help |
|
# |
|
# One-liner install: |
|
# irm https://gist.githubusercontent.com/USER/GIST_ID/raw/we.ps1 | iex |
|
# we gui_test |
|
# |
|
|
|
# ============================================================================ |
|
# Configuration |
|
# ============================================================================ |
|
$script:REPO = "GoNeuralAI/world-engine" |
|
# Fine-grained PAT with contents:read permission only |
|
$script:PAT = "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" |
|
$script:WE_HOME = if ($env:WE_HOME) { $env:WE_HOME } else { "$env:LOCALAPPDATA\WorldEngine" } |
|
$script:CONFIG = if ($env:WE_CONFIG) { $env:WE_CONFIG } else { "release" } |
|
$script:AVAILABLE_APPS = @("gpu_info", "gui_test", "pkg_viewer", "client", "asset_cooker") |
|
|
|
# ============================================================================ |
|
# Helper Functions |
|
# ============================================================================ |
|
function Write-Log { |
|
param([string]$Message) |
|
Write-Host "[we] $Message" -ForegroundColor Cyan |
|
} |
|
|
|
function Write-Err { |
|
param([string]$Message) |
|
Write-Host "[we] ERROR: $Message" -ForegroundColor Red |
|
exit 1 |
|
} |
|
|
|
function Ensure-Dirs { |
|
$dirs = @( |
|
"$script:WE_HOME\apps", |
|
"$script:WE_HOME\cache", |
|
"$script:WE_HOME\bin" |
|
) |
|
foreach ($dir in $dirs) { |
|
if (-not (Test-Path $dir)) { |
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null |
|
} |
|
} |
|
} |
|
|
|
function Get-ReleaseInfo { |
|
param([string]$App) |
|
|
|
$headers = @{ |
|
"Authorization" = "token $script:PAT" |
|
"Accept" = "application/vnd.github.v3+json" |
|
} |
|
|
|
try { |
|
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/$script:REPO/releases/tags/$App" ` |
|
-Headers $headers -ErrorAction Stop |
|
return $response |
|
} |
|
catch { |
|
return $null |
|
} |
|
} |
|
|
|
function Get-VersionFromRelease { |
|
param($ReleaseInfo) |
|
|
|
if ($ReleaseInfo -and $ReleaseInfo.name) { |
|
# Extract version from "app_name (version)" |
|
if ($ReleaseInfo.name -match '\(([^)]+)\)') { |
|
return $matches[1] |
|
} |
|
} |
|
return $null |
|
} |
|
|
|
function Find-Asset { |
|
param($ReleaseInfo, [string]$Pattern) |
|
|
|
foreach ($asset in $ReleaseInfo.assets) { |
|
if ($asset.name -like "*$Pattern*") { |
|
return @{ |
|
Id = $asset.id |
|
Name = $asset.name |
|
} |
|
} |
|
} |
|
return $null |
|
} |
|
|
|
function Download-Asset { |
|
param([int]$AssetId, [string]$OutputPath) |
|
|
|
$headers = @{ |
|
"Authorization" = "token $script:PAT" |
|
"Accept" = "application/octet-stream" |
|
} |
|
|
|
Write-Log "Downloading..." |
|
|
|
# Use WebClient for download progress |
|
$webClient = New-Object System.Net.WebClient |
|
$webClient.Headers.Add("Authorization", "token $script:PAT") |
|
$webClient.Headers.Add("Accept", "application/octet-stream") |
|
|
|
# GitHub API redirects, so we need to follow |
|
$uri = "https://api.github.com/repos/$script:REPO/releases/assets/$AssetId" |
|
|
|
try { |
|
# Get redirect URL |
|
$request = [System.Net.HttpWebRequest]::Create($uri) |
|
$request.Headers.Add("Authorization", "token $script:PAT") |
|
$request.Accept = "application/octet-stream" |
|
$request.AllowAutoRedirect = $false |
|
$response = $request.GetResponse() |
|
$redirectUrl = $response.GetResponseHeader("Location") |
|
$response.Close() |
|
|
|
if ($redirectUrl) { |
|
# Download from redirect URL |
|
Invoke-WebRequest -Uri $redirectUrl -OutFile $OutputPath -UseBasicParsing |
|
} |
|
else { |
|
Write-Err "Failed to get download URL" |
|
} |
|
} |
|
catch { |
|
Write-Err "Download failed: $_" |
|
} |
|
} |
|
|
|
function Install-App { |
|
param([string]$App, [string]$Archive) |
|
|
|
$appDir = "$script:WE_HOME\apps\$App" |
|
|
|
# Clean previous installation |
|
if (Test-Path $appDir) { |
|
Remove-Item -Recurse -Force $appDir |
|
} |
|
New-Item -ItemType Directory -Path $appDir -Force | Out-Null |
|
|
|
Write-Log "Extracting..." |
|
Expand-Archive -Path $Archive -DestinationPath $appDir -Force |
|
|
|
# Handle Windows Defender |
|
try { |
|
$exclusionPath = $script:WE_HOME |
|
$existingExclusions = Get-MpPreference -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ExclusionPath |
|
|
|
if ($existingExclusions -notcontains $exclusionPath) { |
|
Write-Log "Note: You may want to add Windows Defender exclusion for better performance:" |
|
Write-Log " Add-MpPreference -ExclusionPath '$exclusionPath'" |
|
} |
|
} |
|
catch { |
|
# Windows Defender cmdlets not available, skip |
|
} |
|
} |
|
|
|
function Get-BinaryPath { |
|
param([string]$App) |
|
return "$script:WE_HOME\apps\$App\$App.exe" |
|
} |
|
|
|
# ============================================================================ |
|
# Commands |
|
# ============================================================================ |
|
function Show-Help { |
|
@" |
|
World Engine Tool Launcher (we) |
|
|
|
Usage: |
|
we <app> [args...] Run app (auto-updates if new version available) |
|
we update <app> Force update an app |
|
we list List available apps |
|
we clean Clean download cache |
|
we installed List installed apps with versions |
|
we help Show this help |
|
|
|
Available apps: |
|
gpu_info - GPU information tool |
|
gui_test - GUI testing application |
|
pkg_viewer - Package viewer |
|
client - Game engine client |
|
asset_cooker - Asset processing tool |
|
|
|
Environment variables: |
|
WE_HOME Installation directory (default: %LOCALAPPDATA%\WorldEngine) |
|
WE_CONFIG Build config: release or debug (default: release) |
|
|
|
Examples: |
|
we gui_test # Run gui_test |
|
we gui_test --help # Pass args to gui_test |
|
`$env:WE_CONFIG="debug"; we client # Run debug build |
|
we update gui_test # Force update |
|
"@ |
|
} |
|
|
|
function Show-List { |
|
Write-Host "Available apps:" |
|
foreach ($app in $script:AVAILABLE_APPS) { |
|
Write-Host " $app" |
|
} |
|
} |
|
|
|
function Show-Installed { |
|
Write-Host "Installed apps:" |
|
foreach ($app in $script:AVAILABLE_APPS) { |
|
$versionFile = "$script:WE_HOME\apps\$app\version.txt" |
|
if (Test-Path $versionFile) { |
|
$version = Get-Content $versionFile -Raw |
|
Write-Host " $app ($($version.Trim()))" |
|
} |
|
} |
|
} |
|
|
|
function Invoke-Clean { |
|
Write-Log "Cleaning cache..." |
|
$cachePath = "$script:WE_HOME\cache" |
|
if (Test-Path $cachePath) { |
|
Remove-Item -Recurse -Force $cachePath |
|
} |
|
Write-Log "Cache cleaned" |
|
} |
|
|
|
function Invoke-Update { |
|
param([string]$App) |
|
|
|
if (-not $App) { |
|
Write-Err "Usage: we update <app>" |
|
} |
|
|
|
# Remove version file to force update |
|
$versionFile = "$script:WE_HOME\apps\$App\version.txt" |
|
if (Test-Path $versionFile) { |
|
Remove-Item $versionFile |
|
} |
|
|
|
Invoke-Run -App $App |
|
} |
|
|
|
function Invoke-Run { |
|
param( |
|
[string]$App, |
|
[string[]]$AppArgs = @() |
|
) |
|
|
|
if (-not $App) { |
|
Show-Help |
|
return |
|
} |
|
|
|
# Validate app name |
|
if ($App -notin $script:AVAILABLE_APPS) { |
|
Write-Err "Unknown app: $App. Run 'we list' to see available apps." |
|
} |
|
|
|
Ensure-Dirs |
|
|
|
$versionFile = "$script:WE_HOME\apps\$App\version.txt" |
|
$localVersion = "" |
|
if (Test-Path $versionFile) { |
|
$localVersion = (Get-Content $versionFile -Raw).Trim() |
|
} |
|
|
|
# Get latest release info |
|
Write-Log "Checking for updates..." |
|
$releaseInfo = Get-ReleaseInfo -App $App |
|
|
|
if (-not $releaseInfo) { |
|
if ($localVersion) { |
|
Write-Log "Could not check for updates, using cached version" |
|
} |
|
else { |
|
Write-Err "Release not found for $App. The release may not exist yet." |
|
} |
|
} |
|
else { |
|
$latestVersion = Get-VersionFromRelease -ReleaseInfo $releaseInfo |
|
|
|
if ($localVersion -ne $latestVersion) { |
|
Write-Log "Updating $App: $localVersion -> $latestVersion" |
|
|
|
# Find matching asset |
|
$assetPattern = "$App-windows-$script:CONFIG-" |
|
$asset = Find-Asset -ReleaseInfo $releaseInfo -Pattern $assetPattern |
|
|
|
if (-not $asset) { |
|
Write-Err "No matching asset found for pattern: $assetPattern" |
|
} |
|
|
|
# Download |
|
$cacheFile = "$script:WE_HOME\cache\$($asset.Name)" |
|
New-Item -ItemType Directory -Path "$script:WE_HOME\cache" -Force | Out-Null |
|
Download-Asset -AssetId $asset.Id -OutputPath $cacheFile |
|
|
|
# Install |
|
Install-App -App $App -Archive $cacheFile |
|
|
|
# Save version |
|
$latestVersion | Out-File -FilePath $versionFile -NoNewline |
|
Write-Log "$App updated to $latestVersion" |
|
} |
|
else { |
|
Write-Log "$App is up to date ($localVersion)" |
|
} |
|
} |
|
|
|
# Run the app |
|
$binary = Get-BinaryPath -App $App |
|
|
|
if (-not (Test-Path $binary)) { |
|
Write-Err "Binary not found: $binary" |
|
} |
|
|
|
& $binary @AppArgs |
|
} |
|
|
|
function Invoke-Bootstrap { |
|
Ensure-Dirs |
|
|
|
$weScript = "$script:WE_HOME\bin\we.ps1" |
|
$weCmd = "$script:WE_HOME\bin\we.cmd" |
|
|
|
Write-Log "Installing 'we' command..." |
|
|
|
# Download this script |
|
$gistUrl = "https://gist.githubusercontent.com/__GIST_USER__/__GIST_ID__/raw/we.ps1" |
|
try { |
|
Invoke-WebRequest -Uri $gistUrl -OutFile $weScript -UseBasicParsing |
|
} |
|
catch { |
|
# If gist URL fails, copy from current invocation if possible |
|
$scriptContent = $MyInvocation.MyCommand.ScriptBlock.ToString() |
|
if ($scriptContent) { |
|
$scriptContent | Out-File -FilePath $weScript -Encoding UTF8 |
|
} |
|
} |
|
|
|
# Create .cmd wrapper |
|
@" |
|
@echo off |
|
powershell -ExecutionPolicy Bypass -File "%~dp0we.ps1" %* |
|
"@ | Out-File -FilePath $weCmd -Encoding ASCII |
|
|
|
Write-Log "Installed to: $weScript" |
|
|
|
# Check if already in PATH |
|
$binPath = "$script:WE_HOME\bin" |
|
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") |
|
|
|
if ($currentPath -notlike "*$binPath*") { |
|
Write-Log "" |
|
Write-Log "Add to PATH by running (requires new terminal):" |
|
Write-Log " `$env:PATH += `";$binPath`"" |
|
Write-Log " [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$binPath', 'User')" |
|
Write-Log "" |
|
Write-Log "Or run directly: $weCmd <app>" |
|
} |
|
else { |
|
Write-Log "'we' command is ready to use" |
|
} |
|
} |
|
|
|
# ============================================================================ |
|
# Main |
|
# ============================================================================ |
|
function Main { |
|
param([string[]]$Arguments) |
|
|
|
# If being piped (no args, not interactive), bootstrap |
|
if ($Arguments.Count -eq 0) { |
|
# Check if running interactively |
|
$isInteractive = [Environment]::UserInteractive -and -not $Host.Name.Contains("ISE") |
|
if (-not $isInteractive -or $MyInvocation.InvocationName -eq "&") { |
|
Invoke-Bootstrap |
|
return |
|
} |
|
Show-Help |
|
return |
|
} |
|
|
|
$command = $Arguments[0] |
|
$remaining = if ($Arguments.Count -gt 1) { $Arguments[1..($Arguments.Count-1)] } else { @() } |
|
|
|
switch ($command) { |
|
{ $_ -in @("help", "--help", "-h", "/?") } { |
|
Show-Help |
|
} |
|
"list" { |
|
Show-List |
|
} |
|
"installed" { |
|
Show-Installed |
|
} |
|
"clean" { |
|
Invoke-Clean |
|
} |
|
"update" { |
|
if ($remaining.Count -gt 0) { |
|
Invoke-Update -App $remaining[0] |
|
} |
|
else { |
|
Write-Err "Usage: we update <app>" |
|
} |
|
} |
|
{ $_ -in @("bootstrap", "install") } { |
|
Invoke-Bootstrap |
|
} |
|
default { |
|
Invoke-Run -App $command -AppArgs $remaining |
|
} |
|
} |
|
} |
|
|
|
# Handle invocation |
|
if ($MyInvocation.InvocationName -ne ".") { |
|
Main -Arguments $args |
|
} |