tools/install-from-web.ps1

<#
.SYNOPSIS
Web bootstrap for Maestro — downloads the repo archive and runs the bundled installer.
No clone required.

.DESCRIPTION
Fetch a zip/tarball of the repository, extract to a temp dir, and invoke the
bundled Install-Maestro.ps1 with your parameters. Only the TARGET repo keeps
anything afterwards; the downloaded sources are cleaned up.

Canonical one-liner (note the scriptblock wrapper — plain `irm | iex` cannot
pass parameters):

  & ([scriptblock]::Create((irm https://raw.githubusercontent.com/abmach/maestro/main/tools/install-from-web.ps1))) `
      -Target D:\repos\my-app -Locations .omp,.claude -Force

The same bootstrap is also served from GitLab:
https://gitlab.com/arthur_b_machado/maestro/-/raw/main/tools/install-from-web.ps1

Pin an immutable release instead of the moving main branch:

  ... -Version v0.4.0-public ...

Remote code disclosure: this downloads and EXECUTES code from the given URL.
Review the source or pin -Version before running blind.
#>

[CmdletBinding()]
param(
    [string]$Target = (Get-Location).Path,
    [string[]]$Locations = @(".agents"),
    [string]$Version,                       # e.g. v0.4.0-public; omit for main branch
    [switch]$Clean,
    [switch]$Force,
    [string]$RepoBase = 'https://gitlab.com/arthur_b_machado/maestro',
    [string]$ArchivePath                    # offline/testing override: use a local archive
)

$ErrorActionPreference = "Stop"

$ref = if ($Version) { $Version } else { 'main' }
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ('maestro-web-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
New-Item -ItemType Directory -Path $tmp -Force | Out-Null

try {
    if ($ArchivePath) {
        Write-Host "Using local archive: $ArchivePath"
        $archive = Join-Path $tmp 'bundle-archive'
        Copy-Item -LiteralPath $ArchivePath -Destination $archive -Force
    }
    else {
        $url = "$RepoBase/-/archive/$ref/" + $(if ($Version) { "maestro-$ref" } else { "maestro-main" }) + '.zip'
        Write-Host "Downloading $url"
        $archive = Join-Path $tmp ('maestro-' + $ref + '.zip')
        Invoke-WebRequest -Uri $url -OutFile $archive -UseBasicParsing
    }

    # Access sanity: private repos / bot-protection return HTML (sign-in page or
    # Cloudflare challenge) instead of an archive. Fail with actionable guidance
    # BEFORE extraction. Real zips start with the PK magic bytes.
    $bytes = [System.IO.File]::ReadAllBytes($archive)
    $head = [System.Text.Encoding]::ASCII.GetString($bytes, 0, [Math]::Min(4096, $bytes.Length))
    if ($head -notmatch '^PK' -or $head -match '<html' -or $head -match '_cf_chl_opt' -or $head -match 'sign_in') {
        Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
        throw "GitLab did not return an archive (repo is private, or bot-protection challenged this client). Make the project public / run from an unrestricted network, or clone the repo and run .\Install-Maestro.ps1 locally."
    }

    Expand-Archive -LiteralPath $archive -DestinationPath $tmp

    # Locate the extracted source root: either a wrapped "<repo>-<ref>" dir
    # (web archives) or files at the extraction root (git archive / local zips).
    $src = Get-ChildItem -LiteralPath $tmp -Directory |
        Where-Object { Test-Path (Join-Path $_.FullName 'Install-Maestro.ps1') } |
        Select-Object -First 1
    if (-not $src) { $src = Get-Item -LiteralPath $tmp }
    if (-not (Test-Path (Join-Path $src.FullName 'Install-Maestro.ps1'))) {
        throw "Installer not found inside archive (looked under $($tmp))"
    }

    Write-Host "Running bundled installer from $($src.FullName)"
    & (Join-Path $src.FullName 'Install-Maestro.ps1') -Target $Target -Locations $Locations -Clean:$Clean -Force:$Force
}
finally {
    Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue
}