[CmdletBinding()] param( [string]$PythonPath, [string]$FfmpegPath, [string]$NodePath, [switch]$RefreshDependencies, [switch]$SkipRegistration, [switch]$HelperOnly ) $ErrorActionPreference = 'Stop' $taskProjectRoot = $PSScriptRoot $taskRuntime = Join-Path $taskProjectRoot '.runtime' New-Item -ItemType Directory -Path $taskRuntime -Force | Out-Null $taskSavedConfig = $null if (Test-Path -LiteralPath (Join-Path $taskRuntime 'config.json')) { try { $taskSavedConfig = Get-Content -LiteralPath (Join-Path $taskRuntime 'config.json') -Raw | ConvertFrom-Json } catch {} } function Install-PrivatePython { $taskPythonRoot = Join-Path $taskRuntime 'python' $taskPythonInstaller = Join-Path $taskRuntime 'python-setup.exe' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Host 'Installing Python from python.org...' Invoke-WebRequest -UseBasicParsing -Uri 'https://www.python.org/ftp/python/3.14.8/python-3.14.8-amd64.exe' -OutFile $taskPythonInstaller $taskSignature = Get-AuthenticodeSignature -LiteralPath $taskPythonInstaller if ($taskSignature.Status -ne 'Valid') { throw 'Python installer signature verification failed.' } $taskArguments = @('/quiet','InstallAllUsers=0',"TargetDir=`"$taskPythonRoot`"",'PrependPath=0','Include_launcher=0','Include_test=0','Include_doc=0','Shortcuts=0','AssociateFiles=0') $taskInstalled = Start-Process -FilePath $taskPythonInstaller -ArgumentList $taskArguments -WindowStyle Hidden -Wait -PassThru if ($taskInstalled.ExitCode -notin @(0,3010)) { throw 'Python installation failed.' } return (Join-Path $taskPythonRoot 'python.exe') } function Find-Tool([string]$ExplicitPath, [string]$Name) { if ($ExplicitPath) { if (-not (Test-Path -LiteralPath $ExplicitPath -PathType Leaf)) { throw "File not found: $ExplicitPath" } return (Resolve-Path -LiteralPath $ExplicitPath).Path } $taskCommand = Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1 if ($taskCommand) { return $taskCommand.Source } return $null } if (-not $PythonPath) { $taskExistingPython = Join-Path $env:USERPROFILE '.agent-reach-venv\Scripts\python.exe' $taskProjectPython = Join-Path $taskRuntime 'venv\Scripts\python.exe' if ($taskSavedConfig.python -and (Test-Path -LiteralPath $taskSavedConfig.python)) { $PythonPath = $taskSavedConfig.python } elseif (Test-Path -LiteralPath $taskExistingPython) { $PythonPath = $taskExistingPython } elseif (Test-Path -LiteralPath $taskProjectPython) { $PythonPath = $taskProjectPython } else { $taskSystemPython = Find-Tool '' 'python.exe' if (-not $taskSystemPython -or $taskSystemPython -like '*\WindowsApps\*') { $taskSystemPython = Install-PrivatePython } & $taskSystemPython -c 'import sys; assert sys.version_info >= (3,10)' if ($LASTEXITCODE -ne 0) { $taskSystemPython = Install-PrivatePython } & $taskSystemPython -m venv (Join-Path $taskRuntime 'venv') if ($LASTEXITCODE -ne 0) { throw 'Python virtual environment creation failed.' } $PythonPath = $taskProjectPython } } $PythonPath = Find-Tool $PythonPath 'python.exe' & $PythonPath -c 'import sys; assert sys.version_info >= (3,10), "Python 3.10+ required"' if ($LASTEXITCODE -ne 0) { throw 'Unsupported Python version.' } & $PythonPath -c 'import yt_dlp, yt_dlp_ejs' if ($LASTEXITCODE -ne 0 -or $RefreshDependencies) { & $PythonPath -m pip install --disable-pip-version-check --upgrade -r (Join-Path $taskProjectRoot 'requirements.txt') if ($LASTEXITCODE -ne 0) { throw 'Installing yt-dlp failed.' } } if (-not $FfmpegPath -and $taskSavedConfig.ffmpeg -and (Test-Path -LiteralPath $taskSavedConfig.ffmpeg)) { $FfmpegPath = $taskSavedConfig.ffmpeg } $taskFfmpeg = Find-Tool $FfmpegPath 'ffmpeg.exe' $taskFfprobe = if ($taskFfmpeg) { Join-Path (Split-Path -Parent $taskFfmpeg) 'ffprobe.exe' } else { $null } if (-not $taskFfprobe -or -not (Test-Path -LiteralPath $taskFfprobe)) { $taskFfprobe = Find-Tool '' 'ffprobe.exe' } if (-not $NodePath) { if ($taskSavedConfig.node -and (Test-Path -LiteralPath $taskSavedConfig.node)) { $NodePath = $taskSavedConfig.node } $taskPortableNode = Join-Path $env:USERPROFILE '.agent-reach\tools\node-v24.21.0-win-x64\node.exe' if (-not $NodePath -and (Test-Path -LiteralPath $taskPortableNode)) { $NodePath = $taskPortableNode } } $taskNode = Find-Tool $NodePath 'node.exe' $taskUtf8 = [System.Text.UTF8Encoding]::new($false) $taskConfig = @{ python=$PythonPath; ffmpeg=$taskFfmpeg; ffprobe=$taskFfprobe; node=$taskNode } [System.IO.File]::WriteAllText((Join-Path $taskRuntime 'config.json'), ($taskConfig | ConvertTo-Json), $taskUtf8) Push-Location -LiteralPath $taskProjectRoot try { if (-not $taskFfmpeg -or -not $taskFfprobe) { & $PythonPath -m host.dependencies ffmpeg if ($LASTEXITCODE -ne 0) { throw 'Installing FFmpeg failed.' } } $taskNodeGood = $false if ($taskNode) { $taskNodeVersion = & $taskNode --version $taskNodeGood = $taskNodeVersion -match '^v(\d+)\.' -and [int]$Matches[1] -ge 22 } if (-not $taskNodeGood) { & $PythonPath -m host.dependencies node if ($LASTEXITCODE -ne 0) { throw 'Installing Node.js failed.' } } } finally { Pop-Location } $taskLauncherPath = Join-Path $taskRuntime 'launch-host.cmd' $taskMainPath = Join-Path $taskProjectRoot 'host\main.py' $taskLauncher = "@echo off`r`nchcp 65001 >nul`r`nset PYTHONIOENCODING=utf-8`r`nset PYTHONUTF8=1`r`n`"$PythonPath`" -u `"$taskMainPath`" %*`r`n" [System.IO.File]::WriteAllText($taskLauncherPath, $taskLauncher, $taskUtf8) $taskExtensionId = 'ndnoeiaecgmjnjneekaeapeecmmjheli' $taskHostName = 'com.youtubedl.native' $taskNativeManifestPath = Join-Path $taskRuntime 'native-host.json' $taskNativeManifest = @{ name=$taskHostName; description='YouTubeDL download and NVIDIA NVENC helper'; path=$taskLauncherPath; type='stdio'; allowed_origins=@("chrome-extension://$taskExtensionId/") } [System.IO.File]::WriteAllText($taskNativeManifestPath, ($taskNativeManifest | ConvertTo-Json), $taskUtf8) if (-not $SkipRegistration) { foreach ($taskBrowser in @('Google\Chrome','Microsoft\Edge','Chromium')) { $taskRegistryPath = "HKCU:\Software\$taskBrowser\NativeMessagingHosts\$taskHostName" New-Item -Path $taskRegistryPath -Force | Out-Null Set-Item -Path $taskRegistryPath -Value $taskNativeManifestPath } } Write-Host $(if ($SkipRegistration) { 'Native helper files prepared.' } else { 'Native helper registered for Edge and Chrome (current user).' }) Write-Host "Extension ID: $taskExtensionId" if ($HelperOnly) { Write-Host 'Return to YouTubeDL settings and press Start.' } else { Write-Host "Extension folder: $(Join-Path $taskProjectRoot 'extension')" Write-Host 'Open edge://extensions or chrome://extensions, enable Developer mode, and load the extension folder.' Write-Host 'Then refresh the YouTube page. The Download and Settings buttons are next to Subscribe.' }