From a0862126b5cb02371802167832a75d04ad33bf54 Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:04:08 -0400 Subject: [PATCH 1/7] ci: one-off winget bootstrap (delete after use) --- .github/workflows/winget-bootstrap.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/winget-bootstrap.yml diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml new file mode 100644 index 00000000..eb3f6e96 --- /dev/null +++ b/.github/workflows/winget-bootstrap.yml @@ -0,0 +1,24 @@ +name: WinGet bootstrap (one-off, delete after use) + +on: + workflow_dispatch: + +jobs: + bootstrap: + runs-on: windows-latest + steps: + - name: Run wingetcreate new + shell: pwsh + env: + WINGET_TOKEN: ${{ secrets.WINGET_SUBMIT_TOKEN }} + run: | + $ErrorActionPreference = "Stop" + $installerUrl = "https://github.com/Pythoughts-labs/pythinker-code/releases/download/v0.30.0/PythinkerSetup-0.30.0.exe" + $wingetCreateUrl = "https://github.com/microsoft/winget-create/releases/download/v1.12.8.0/wingetcreate.exe" + $wingetCreateSha256 = "8BD738851B524885410112678E3771B341C5C716DE60FBBECB88AB0A363ED85D" + Invoke-WebRequest -Uri $wingetCreateUrl -OutFile wingetcreate.exe + $actualHash = (Get-FileHash -Algorithm SHA256 wingetcreate.exe).Hash + if ($actualHash -ne $wingetCreateSha256) { + throw "wingetcreate.exe SHA-256 mismatch: expected $wingetCreateSha256, got $actualHash" + } + .\wingetcreate.exe new "$installerUrl" --token $env:WINGET_TOKEN --submit From 8110450d72e1693d3a80be15c82c29d9cf711b3d Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:04:25 -0400 Subject: [PATCH 2/7] ci: switch to push trigger for bootstrap --- .github/workflows/winget-bootstrap.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml index eb3f6e96..d4ac9426 100644 --- a/.github/workflows/winget-bootstrap.yml +++ b/.github/workflows/winget-bootstrap.yml @@ -1,7 +1,8 @@ name: WinGet bootstrap (one-off, delete after use) on: - workflow_dispatch: + push: + branches: ["ci/winget-bootstrap"] jobs: bootstrap: From 4b1c8f9ed7d0454e9b1532c4b141d218388e1d32 Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:05:09 -0400 Subject: [PATCH 3/7] ci: fix wingetcreate new flags (no --submit, add --no-open) --- .github/workflows/winget-bootstrap.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml index d4ac9426..722881bc 100644 --- a/.github/workflows/winget-bootstrap.yml +++ b/.github/workflows/winget-bootstrap.yml @@ -22,4 +22,5 @@ jobs: if ($actualHash -ne $wingetCreateSha256) { throw "wingetcreate.exe SHA-256 mismatch: expected $wingetCreateSha256, got $actualHash" } - .\wingetcreate.exe new "$installerUrl" --token $env:WINGET_TOKEN --submit + # --token triggers auto-submit to winget-pkgs; --no-open suppresses browser + .\wingetcreate.exe new "$installerUrl" --token $env:WINGET_TOKEN --no-open From 95d837713b7c6459ff8d187fcbf17cf343d5526c Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:08:45 -0400 Subject: [PATCH 4/7] ci: use wingetcreate submit with generated manifests instead of interactive new --- .github/workflows/winget-bootstrap.yml | 100 ++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml index 722881bc..cc38fa5d 100644 --- a/.github/workflows/winget-bootstrap.yml +++ b/.github/workflows/winget-bootstrap.yml @@ -8,19 +8,99 @@ jobs: bootstrap: runs-on: windows-latest steps: - - name: Run wingetcreate new + - name: Generate manifests and submit to winget-pkgs shell: pwsh env: WINGET_TOKEN: ${{ secrets.WINGET_SUBMIT_TOKEN }} run: | $ErrorActionPreference = "Stop" - $installerUrl = "https://github.com/Pythoughts-labs/pythinker-code/releases/download/v0.30.0/PythinkerSetup-0.30.0.exe" - $wingetCreateUrl = "https://github.com/microsoft/winget-create/releases/download/v1.12.8.0/wingetcreate.exe" - $wingetCreateSha256 = "8BD738851B524885410112678E3771B341C5C716DE60FBBECB88AB0A363ED85D" - Invoke-WebRequest -Uri $wingetCreateUrl -OutFile wingetcreate.exe - $actualHash = (Get-FileHash -Algorithm SHA256 wingetcreate.exe).Hash - if ($actualHash -ne $wingetCreateSha256) { - throw "wingetcreate.exe SHA-256 mismatch: expected $wingetCreateSha256, got $actualHash" + $version = "0.30.0" + $pkgId = "PythoughtsLabs.PythinkerCode" + $installerUrl = "https://github.com/Pythoughts-labs/pythinker-code/releases/download/v$version/PythinkerSetup-$version.exe" + + # Download wingetcreate (pinned SHA) + $wcUrl = "https://github.com/microsoft/winget-create/releases/download/v1.12.8.0/wingetcreate.exe" + $wcSha = "8BD738851B524885410112678E3771B341C5C716DE60FBBECB88AB0A363ED85D" + Invoke-WebRequest -Uri $wcUrl -OutFile wingetcreate.exe + if ((Get-FileHash wingetcreate.exe -Algorithm SHA256).Hash -ne $wcSha) { + throw "wingetcreate.exe hash mismatch" } - # --token triggers auto-submit to winget-pkgs; --no-open suppresses browser - .\wingetcreate.exe new "$installerUrl" --token $env:WINGET_TOKEN --no-open + + # Get installer SHA256 from release sidecar + $sha256 = ((Invoke-WebRequest -Uri "$installerUrl.sha256").Content.Trim() -split '\s+')[0] + Write-Host "Installer SHA256: $sha256" + + # Detect installer type by inspecting the binary header + Invoke-WebRequest -Uri $installerUrl -OutFile installer.exe + $bytes = [System.IO.File]::ReadAllBytes("installer.exe") + $header = [System.Text.Encoding]::ASCII.GetString($bytes[0..65535]) + if ($header -match "Inno Setup") { + $installerType = "inno" + } elseif ($header -match "Nullsoft") { + $installerType = "nullsoft" + } else { + $installerType = "exe" + } + Write-Host "Detected installer type: $installerType" + + # Build manifest directory + $dir = "manifests\p\PythoughtsLabs\PythinkerCode\$version" + New-Item -ItemType Directory -Force -Path $dir | Out-Null + + # version manifest + @" +PackageIdentifier: $pkgId +PackageVersion: $version +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0 +"@ | Set-Content "$dir\$pkgId.yaml" -Encoding UTF8 + + # installer manifest + $silentSwitch = if ($installerType -eq "inno") { "/VERYSILENT /NORESTART" } + elseif ($installerType -eq "nullsoft") { "/S" } + else { "/S" } + @" +PackageIdentifier: $pkgId +PackageVersion: $version +Platform: +- Windows.Desktop +MinimumOSVersion: 10.0.17763.0 +InstallerType: $installerType +Installers: +- Architecture: x64 + InstallerUrl: $installerUrl + InstallerSha256: $sha256 + InstallerSwitches: + Silent: $silentSwitch + SilentWithProgress: $silentSwitch +ManifestType: installer +ManifestVersion: 1.6.0 +"@ | Set-Content "$dir\$pkgId.installer.yaml" -Encoding UTF8 + + # locale manifest + @" +PackageIdentifier: $pkgId +PackageVersion: $version +PackageLocale: en-US +Publisher: Pythoughts Labs +PublisherUrl: https://pythinker.com +PackageName: Pythinker Code +PackageUrl: https://pythinker.com +License: Apache-2.0 +LicenseUrl: https://github.com/Pythoughts-labs/pythinker-code/blob/main/LICENSE +ShortDescription: Terminal-native review-first AI engineering agent +Moniker: pythinker +Tags: +- ai +- agent +- cli +ManifestType: defaultLocale +ManifestVersion: 1.6.0 +"@ | Set-Content "$dir\$pkgId.locale.en-US.yaml" -Encoding UTF8 + + Write-Host "Manifest files generated:" + Get-ChildItem $dir + + # Submit via wingetcreate submit (non-interactive) + .\wingetcreate.exe submit $dir --token $env:WINGET_TOKEN From dcc75b15baec8fee5b7ba7bc7d97a743a65ab25e Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:12:29 -0400 Subject: [PATCH 5/7] ci: retrigger bootstrap From 3902a2862106d09146df7f3b3fa02ac34a6e1915 Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:13:54 -0400 Subject: [PATCH 6/7] ci: fix YAML parse error in bootstrap (replace here-strings with array joins) --- .github/workflows/winget-bootstrap.yml | 126 ++++++++++--------------- 1 file changed, 51 insertions(+), 75 deletions(-) diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml index cc38fa5d..ef585330 100644 --- a/.github/workflows/winget-bootstrap.yml +++ b/.github/workflows/winget-bootstrap.yml @@ -14,93 +14,69 @@ jobs: WINGET_TOKEN: ${{ secrets.WINGET_SUBMIT_TOKEN }} run: | $ErrorActionPreference = "Stop" - $version = "0.30.0" - $pkgId = "PythoughtsLabs.PythinkerCode" + $version = "0.30.0" + $pkgId = "PythoughtsLabs.PythinkerCode" $installerUrl = "https://github.com/Pythoughts-labs/pythinker-code/releases/download/v$version/PythinkerSetup-$version.exe" - # Download wingetcreate (pinned SHA) - $wcUrl = "https://github.com/microsoft/winget-create/releases/download/v1.12.8.0/wingetcreate.exe" - $wcSha = "8BD738851B524885410112678E3771B341C5C716DE60FBBECB88AB0A363ED85D" + $wcUrl = "https://github.com/microsoft/winget-create/releases/download/v1.12.8.0/wingetcreate.exe" + $wcSha = "8BD738851B524885410112678E3771B341C5C716DE60FBBECB88AB0A363ED85D" Invoke-WebRequest -Uri $wcUrl -OutFile wingetcreate.exe - if ((Get-FileHash wingetcreate.exe -Algorithm SHA256).Hash -ne $wcSha) { - throw "wingetcreate.exe hash mismatch" - } + if ((Get-FileHash wingetcreate.exe -Algorithm SHA256).Hash -ne $wcSha) { throw "hash mismatch" } - # Get installer SHA256 from release sidecar $sha256 = ((Invoke-WebRequest -Uri "$installerUrl.sha256").Content.Trim() -split '\s+')[0] - Write-Host "Installer SHA256: $sha256" + Write-Host "SHA256: $sha256" - # Detect installer type by inspecting the binary header Invoke-WebRequest -Uri $installerUrl -OutFile installer.exe - $bytes = [System.IO.File]::ReadAllBytes("installer.exe") - $header = [System.Text.Encoding]::ASCII.GetString($bytes[0..65535]) - if ($header -match "Inno Setup") { - $installerType = "inno" - } elseif ($header -match "Nullsoft") { - $installerType = "nullsoft" - } else { - $installerType = "exe" - } - Write-Host "Detected installer type: $installerType" + $header = [System.Text.Encoding]::ASCII.GetString([System.IO.File]::ReadAllBytes("installer.exe")[0..65535]) + $installerType = if ($header -match "Inno Setup") { "inno" } elseif ($header -match "Nullsoft") { "nullsoft" } else { "exe" } + $silentSwitch = if ($installerType -eq "inno") { "/VERYSILENT /NORESTART" } else { "/S" } + Write-Host "Installer type: $installerType" - # Build manifest directory $dir = "manifests\p\PythoughtsLabs\PythinkerCode\$version" New-Item -ItemType Directory -Force -Path $dir | Out-Null - # version manifest - @" -PackageIdentifier: $pkgId -PackageVersion: $version -DefaultLocale: en-US -ManifestType: version -ManifestVersion: 1.6.0 -"@ | Set-Content "$dir\$pkgId.yaml" -Encoding UTF8 + @("PackageIdentifier: $pkgId", + "PackageVersion: $version", + "DefaultLocale: en-US", + "ManifestType: version", + "ManifestVersion: 1.6.0") -join "`n" | + Set-Content -Path "$dir\$pkgId.yaml" -Encoding UTF8 - # installer manifest - $silentSwitch = if ($installerType -eq "inno") { "/VERYSILENT /NORESTART" } - elseif ($installerType -eq "nullsoft") { "/S" } - else { "/S" } - @" -PackageIdentifier: $pkgId -PackageVersion: $version -Platform: -- Windows.Desktop -MinimumOSVersion: 10.0.17763.0 -InstallerType: $installerType -Installers: -- Architecture: x64 - InstallerUrl: $installerUrl - InstallerSha256: $sha256 - InstallerSwitches: - Silent: $silentSwitch - SilentWithProgress: $silentSwitch -ManifestType: installer -ManifestVersion: 1.6.0 -"@ | Set-Content "$dir\$pkgId.installer.yaml" -Encoding UTF8 + @("PackageIdentifier: $pkgId", + "PackageVersion: $version", + "Platform:", + "- Windows.Desktop", + "MinimumOSVersion: 10.0.17763.0", + "InstallerType: $installerType", + "Installers:", + "- Architecture: x64", + " InstallerUrl: $installerUrl", + " InstallerSha256: $sha256", + " InstallerSwitches:", + " Silent: $silentSwitch", + " SilentWithProgress: $silentSwitch", + "ManifestType: installer", + "ManifestVersion: 1.6.0") -join "`n" | + Set-Content -Path "$dir\$pkgId.installer.yaml" -Encoding UTF8 - # locale manifest - @" -PackageIdentifier: $pkgId -PackageVersion: $version -PackageLocale: en-US -Publisher: Pythoughts Labs -PublisherUrl: https://pythinker.com -PackageName: Pythinker Code -PackageUrl: https://pythinker.com -License: Apache-2.0 -LicenseUrl: https://github.com/Pythoughts-labs/pythinker-code/blob/main/LICENSE -ShortDescription: Terminal-native review-first AI engineering agent -Moniker: pythinker -Tags: -- ai -- agent -- cli -ManifestType: defaultLocale -ManifestVersion: 1.6.0 -"@ | Set-Content "$dir\$pkgId.locale.en-US.yaml" -Encoding UTF8 + @("PackageIdentifier: $pkgId", + "PackageVersion: $version", + "PackageLocale: en-US", + "Publisher: Pythoughts Labs", + "PublisherUrl: https://pythinker.com", + "PackageName: Pythinker Code", + "PackageUrl: https://pythinker.com", + "License: Apache-2.0", + "LicenseUrl: https://github.com/Pythoughts-labs/pythinker-code/blob/main/LICENSE", + "ShortDescription: Terminal-native review-first AI engineering agent", + "Moniker: pythinker", + "Tags:", + "- ai", + "- agent", + "- cli", + "ManifestType: defaultLocale", + "ManifestVersion: 1.6.0") -join "`n" | + Set-Content -Path "$dir\$pkgId.locale.en-US.yaml" -Encoding UTF8 - Write-Host "Manifest files generated:" - Get-ChildItem $dir - - # Submit via wingetcreate submit (non-interactive) + Write-Host "Generated:"; Get-ChildItem $dir .\wingetcreate.exe submit $dir --token $env:WINGET_TOKEN From d5c092127fbaf4999f1e241c39bf9486f861ad8d Mon Sep 17 00:00:00 2001 From: Mohamed Elkholy Date: Tue, 2 Jun 2026 20:14:38 -0400 Subject: [PATCH 7/7] ci: fix PS7 byte[] from Invoke-WebRequest Content --- .github/workflows/winget-bootstrap.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/winget-bootstrap.yml b/.github/workflows/winget-bootstrap.yml index ef585330..540390b3 100644 --- a/.github/workflows/winget-bootstrap.yml +++ b/.github/workflows/winget-bootstrap.yml @@ -23,7 +23,7 @@ jobs: Invoke-WebRequest -Uri $wcUrl -OutFile wingetcreate.exe if ((Get-FileHash wingetcreate.exe -Algorithm SHA256).Hash -ne $wcSha) { throw "hash mismatch" } - $sha256 = ((Invoke-WebRequest -Uri "$installerUrl.sha256").Content.Trim() -split '\s+')[0] + $sha256 = [System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -Uri "$installerUrl.sha256").Content).Trim().Split()[0] Write-Host "SHA256: $sha256" Invoke-WebRequest -Uri $installerUrl -OutFile installer.exe