Add Windows PowerShell installer - #2152
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Windows-native PowerShell installer to the CLI repo, wiring it into user-facing install documentation and adding a Windows CI smoke check to validate the script parses under both Windows PowerShell 5.x and PowerShell 7+.
Changes:
- Add
scripts/install.ps1to install stable/nightly Windows releases (with checksum verification) and optionally delegate stable installs to Scoop. - Update install/docs messaging to direct Windows users to the PowerShell installer.
- Add Windows CI steps to run
.\scripts\install.ps1 -Helpunder bothpowershellandpwsh.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/install.sh | Updates Windows error guidance to point to the new PowerShell installer. |
| scripts/install.ps1 | Introduces a PowerShell 5.1/7+ installer that resolves releases, verifies SHA-256, and installs binaries. |
| README.md | Documents the PowerShell installation flow and updates channel install examples. |
| .github/workflows/ci.yml | Adds Windows CI steps to run installer help under PowerShell 5.x and 7+. |
Suppressed comments (2)
scripts/install.ps1:174
-UseBasicParsingis not supported in PowerShell 6+/7+. This download helper will fail underpwsh; gate the parameter so the script works in both Windows PowerShell 5.1 and PowerShell 7+.
Invoke-WebRequest -Uri $Uri -OutFile $Destination -UseBasicParsing
README.md:141
- These channel examples also use
-UseBasicParsing, which fails in PowerShell 7+. Either make the examples version-conditional (like the Windows section above) or refer readers to the Windows section snippet.
- `install.ps1` stable (uses Scoop when available): `irm https://raw.githubusercontent.com/entireio/cli/main/scripts/install.ps1 -UseBasicParsing | iex`
- `install.ps1` nightly: `& ([scriptblock]::Create((irm https://raw.githubusercontent.com/entireio/cli/main/scripts/install.ps1 -UseBasicParsing))) -Channel nightly`
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8453cc9. Configure here.
0afc4ef to
18f7d5c
Compare
Soph
left a comment
There was a problem hiding this comment.
Read the script end to end; no Windows or pwsh on this machine, so this is code reading plus live checks of the published install URLs — I could not execute it.
On the existing bot findings: @gtrrz-victor is right and Copilot is wrong on all three -UseBasicParsing comments — the parameter was retained on Invoke-WebRequest/Invoke-RestMethod in PS 6+ as an accepted no-op, so nothing throws. Cursor's finding was real, but 18f7d5c fixed the ranking rather than the ordering, so it still reproduces; that's the one blocker below.
Worth keeping: the checksum handling is stricter than install.sh's (anchored match with [regex]::Escape plus a hash-shape check, vs. the unanchored grep -iE "${archive_name}$" on install.sh:229), and Get-FileHash is always present so there's no "no checksum tool found, skipping verification" escape hatch. GITHUB_TOKEN goes only to api.github.com and not to the release download, which avoids leaking it to the objects.githubusercontent.com redirect. TLS 1.2 via numeric -bor 3072, strict mode 2.0, -LiteralPath throughout, and the exit-vs-throw split for file-vs-pipe are all correct.
One nit not worth its own thread: the commented-out nightly line at README.md:71 sits inside a copy-paste fence and reads like a leftover — it's already given uncommented in the channels list at line 141. And neither web call passes -TimeoutSec, so a hung connection hangs the installer indefinitely.
The previous check only examined the first entire on PATH, so when ~\.local\bin was already present from a prior install the just-installed binary matched itself and the Scoop shim was never noticed. Now all entire binaries are checked: if the first-on-PATH is ours but others exist, warn without erroring; if another binary takes priority, error as before. Also drop the no-op -UseBasicParsing from Invoke-RestMethod. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1ECJMD2SK13CG4NGHSP5W17
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1ED3DS3WHWMDAR4RMPN8T30
Entire-Checkpoint: 01M1EKMWZB73ZC9XXT91ATA5H3
Get-EntireOnPath returned an unrolled scalar when only one entire.exe was found, breaking .Count under Set-StrictMode -Version 2.0. Use Write-Output -NoEnumerate to preserve the array. Also re-query PATH after install instead of reusing the stale pre-install result. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1EM1Y2DTR8TRMH48ET4CY1Q
Test-SamePath uses GetFullPath which can fail on non-existent paths under the try/catch, returning false for every comparison. This broke reinstalls to the same directory by incorrectly reporting a conflict. Move the check after the copy so both paths exist, matching install.sh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1EMB6G9NRGZDVH6A4ACR403
Nightly install next to Scoop otherwise threw before the new directory was added, so a rerun failed the same way. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GQXE8FG3WC0BPSH44M82XS
Windows PowerShell 5.1 does not keep [Environment]::CurrentDirectory in sync with Set-Location, and GetFullPath does not expand ~. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GSQYSNKP0TXGRWF03J6N62
x64 PowerShell on ARM64 Windows reports AMD64 via PROCESSOR_ARCHITECTURE and would download the amd64 zip. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GTBSZ1ZQSM7SNJPCAX4VDC
RuntimeInformation needs .NET 4.7.1+, which stock Windows PowerShell 5.1 installs may not have; the registry value is not rewritten for emulated processes. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GWST5TQ5WWGQ6VWGQBT4NN
…oads. The copy-paste fence already has a stable one-liner; nightly lives in the channels list. Hung GitHub requests otherwise block the installer indefinitely. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GX9NTXPBPKPRCF9AV8A18K
The commented command is a valid copy-paste for -Channel nightly, not a leftover. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1GXM1EQQ1ST63BB6HPPGX5A
Same short URL as install.sh; the site redirect can be wired independently of the CLI docs. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1H0NSZW842Y60QYQ83W40RP
Redirected scoop stderr is no longer terminating under ErrorAction Stop, and Invoke-RestMethod uses -UseBasicParsing like the file download path. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 01M1H2HNV3J5R2HGNZ75DDJJ1A
The pre-install `scoop prefix entire` probe is expected to fail on a first install, but scoop's `abort` uses Write-Host — the information stream, which 2>&1 neither captures nor suppresses — so "Could not find app path for 'entire'." printed straight to the console mid-install and read as a failure. Probe through a helper that discards every stream and keeps only the exit code. Not folded into Invoke-Scoop: a blanket redirect there would also hide scoop's install and update progress. Also record, in -Help and at the branch itself, that -InstallDir and -NoPathUpdate apply only to release-archive installs, since Scoop chooses its own install location and manages its own PATH entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1H66A49H96N8BXN8B4YA55G
The post-install PATH check runs against the in-process $env:Path, which the installer has just prepended its own directory to, so it reports that the new install takes priority. That verdict holds only for the current session: the installer writes to the user PATH, and Windows composes a new session as machine-then-user, so an entire.exe on the machine PATH wins in every new terminal and cannot be outranked by reordering the user PATH. Name that case where the conflicting binaries are already listed. The verdict itself is unchanged -- nothing that passes today starts failing -- so a machine-level conflict now prints an accurate explanation alongside a session-scoped all-clear rather than instead of one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1H6YRQ328EJTMAZD0XX8H8A
A machine-PATH conflict printed both the explanation that the other install wins in a new terminal and, three lines later, that the installed version takes priority -- about the same binary, leaving the reader nothing to act on. Suppress the priority paragraph in that case; its remedy has already been given. Also pluralize it, since more than one other installation can be listed above it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1H7T30EKV1NDB6YM0DB1Z7D
"Run entire from <dir> explicitly" was not a solution: the point of the install is having entire on PATH, and Entire's own git hooks invoke it by name, so an install reachable only by absolute path is not installed. Replace it with the three options that actually resolve the conflict -- remove the other copy, drop its directory from the machine PATH, or install over it -- and print the -InstallDir line for the last one ready to paste. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1H7XJJBXJX6266R5NRYQX27
CI only ran install.ps1 -Help, which exercises the parse and the early return. PSScriptAnalyzer at Error+Warning now runs over scripts/ in both Windows PowerShell 5.1 and pwsh, through a Pester suite so behaviour tests can join it, plus `mise run test:ps1` locally (skips when pwsh is absent). PSAvoidUsingWriteHost is excluded: the installer talks to the user only through the host. The remaining findings are answered in install.ps1 with per-parameter suppressions for a PSSA false positive on the wrapper's parameters, a suppression on Test-PathContains, a named -InputObject, and an ASCII-only comment. The runner uses Import-Module instead of `#Requires -Modules`, which under `pwsh -File` returns the script's exit code as 0 and would let a failing suite pass CI. A canary test asserts the analyzer reports a known-bad file so a green suite cannot be one that evaluated nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1NXGVSJBSHEAKH5B5E6B1WK
[Environment]::GetEnvironmentVariable expands REG_EXPAND_SZ on read and SetEnvironmentVariable writes REG_SZ, so one run of the installer baked the current %USERPROFILE% into every entry of the user PATH and downgraded the value kind, after which changes to those variables stopped reaching it. The PATH is now read and written as stored through the registry key, keeping REG_EXPAND_SZ when the value contains % or already had that kind, and a WM_SETTINGCHANGE broadcast follows the write. Entries are compared after expansion, so an entry stored as %USERPROFILE%\.local\bin is recognised as the install directory, but they are written back as read: the value is someone's registry string and the installer owns one entry in it. Other entries, empty entries and a trailing semicolon are kept verbatim, and an existing entry for the directory is moved to the front rather than replaced with its expansion. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1NZQ4NKBNRK1S1GZGJ4DM51
Copy-Item -Force over a binary held by a running process ("entire mcp", an
in-flight hook) failed with a raw sharing violation, and reinstalling over
an existing install is the common path. Windows forbids overwriting or
deleting a mapped executable image but permits renaming it, so the two
binaries are now installed by moving the existing file to <name>.old,
copying the verified file in, and removing the old image if nothing holds
it. A copy failure moves the old binary back before the error propagates,
a .old that is itself still running is moved aside under a unique name,
and stale *.old files are removed on the next run. When the rename itself
is refused the error names the file and the remedy.
Directory creation uses [IO.Directory]::CreateDirectory: New-Item has no
-LiteralPath, and CreateDirectory takes the path as written on both
Windows PowerShell 5.1 and pwsh.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Entire-Checkpoint: 01M1P2YQ661SPW220PYZ74FP22
Install-EntireWithScoop decided whether to add the bucket by matching a regex against the rendered table from `scoop bucket list`: a fifth column would switch the output to list format and break the match, and on a Scoop with no buckets at all `list` exits 2 before anything is matched. It now calls `bucket add` directly and accepts exit 0 (added) and 2 (already present; Scoop exited 0 for that case before v0.3.0 and has never used 1). Every Scoop failure now includes Scoop's own output. Get-ReleaseVersion blamed the network when GitHub had answered without a usable release; a network failure never reaches that check because Invoke-RestMethod throws first. Invoke-GitHubApi now names the cause itself, distinguishing an HTTP status from no response at all, and reads the Response property only when the exception has one: a timeout surfaces as TaskCanceledException, which does not, and StrictMode would otherwise turn the report into a property error. The no-nightly case says how many releases were checked, counting inside the loop because @($null).Count is 1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P3RFS3GVHBJ2GT8J6VFWR5
One 60-second -TimeoutSec served both the GitHub API calls and the 21 MB archive download. What that meant depended on the host: Windows PowerShell 5.1 applied it to the response headers only; PowerShell 7.0-7.3 applied it to the whole transfer, so a link below about 3 Mbit/s was cancelled with "The operation was canceled."; PowerShell 7.4 made it an alias of -ConnectionTimeoutSeconds, which caps the connection only. The API calls keep the 60-second cap. The download passes no -TimeoutSec and, where the host has -OperationTimeoutSeconds (7.4+), is cut only when no data arrives for 60 seconds: a slow link finishes, a dead connection is reported instead of hanging. The two limits are separate constants so they cannot be merged back into one. Measured on pwsh 7.6.5 against a throttled local server: -TimeoutSec 3 on a 5-second steady body completed (not cancelled); -OperationTimeoutSeconds 2 on the same body completed; -OperationTimeoutSeconds 3 on a body that stalled for 30 seconds was cut at 3 seconds with "The request was canceled due to the configured OperationTimeout of 3 seconds elapsing"; with no timeout parameters the stalled body completed after 30 seconds, so the host has no stall detector of its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P4NTYAY4646NWH0R57ER86
Everything else in the installer's test suite runs offline with stubs. This job installs on a Windows runner in both Windows PowerShell 5.1 and pwsh, one fresh runner each so every assertion is about a first install, and checks what the installer leaves behind: the two binaries, the raw user PATH with the install directory first and the rest untouched, the value kind the registry write promises, a reinstall while entire.exe is running that leaves a .old behind and clears it once the process exits, and a nightly install into a second directory that reports the stable copy and moves it down rather than removing it. The script refuses to run outside GitHub Actions because it rewrites the user PATH, and restores the raw value and its kind when it finishes. The job runs on changes to the installer or its tests and weekly, an hour after the nightly build, so drift on the releases side is caught between installer changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P60JXYP6Q3CSB6JP0SBFSY
The Homebrew and install.sh fences say which line is stable and which is nightly; the Windows fence did not, so the commented second line read as a stray alternative rather than the nightly variant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P60MDARRNH4SJZJ16AMSKH
6bbd464 to
f20410f
Compare
The first Windows CI run passed the installer and failed two test scripts. e2e.ps1 called the installer with no arguments through a [string[]] splat, which passes one empty string that binds positionally to -Channel and fails its ValidateSet; the helper now splats nothing in that case. The double-reinstall Pester case tried to start a process from a file named entire.exe.old, which Windows refuses; it now starts the copy as entire.exe and renames the running image, which is how the installer produces that state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P6TSMV25D1GZCHRNG1KBF2
Invoke-Installer splatted a [string[]], and an array splat binds its elements positionally, so phase 3's "-Channel" was handed to -Channel as its value. The helper now takes a hashtable, whose splat binds by name; its empty default splats nothing, which also replaces the guard the last fix added for the no-argument case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P73QA3W0SNCX7CCPH19RY8
The unknown-location fallback now prints the scriptblock one-liner with -InstallDir "<dir of entire.exe>", so install.ps1 replaces the binary in place. This relies on the installer treating an explicit -InstallDir as opting out of its Scoop-first default (landing on #2152); until then the flag is ignored on machines with Scoop. Without an exec path the hint stays bare. The stable form switches to the scriptblock shape because irm | iex cannot bind arguments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P8DVXVHX7D5CWEVFDSST5E
On stable with Scoop on PATH the installer took the Scoop branch and ignored -InstallDir. The update hint is about to print -InstallDir "<directory of the running entire.exe>" so a hand-placed binary is replaced in place; on such a machine that would have installed a second entire under scoop\apps while the old one stayed first on PATH. A caller who names a directory now gets the release-archive install there, whatever is on PATH. Without -InstallDir nothing changes: Scoop when present, the default directory otherwise, nightly always from the archive. Whether -InstallDir was given is recorded at script scope, because $PSBoundParameters inside a function is that function's own; the functions read it the way they read $InstallDir. -NoPathUpdate is unchanged: Scoop manages its own PATH entry, so the switch has nothing to do on that branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P8PMGH9B31XYYZ3X08ZD0S
The unknown-location fallback now prints the scriptblock one-liner with -InstallDir "<dir of entire.exe>", so install.ps1 replaces the binary in place. This relies on the installer treating an explicit -InstallDir as opting out of its Scoop-first default (landing on #2152); until then the flag is ignored on machines with Scoop. Without an exec path the hint stays bare. The stable form switches to the scriptblock shape because irm | iex cannot bind arguments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P8DVXVHX7D5CWEVFDSST5E
The previous change replaced the end of the sentence about Scoop owning its install location but left its opening ", so", which then ran into the next sentence. The paragraph now reads as two sentences, and the -Help test pins them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P9HK4JYARP4NSNAXSYAGSG
Install-BinaryFile answered every failed rename with "another program holds it open", so a non-elevated shell installing into a machine-owned directory was told to close a program that was not running. The update hint is about to pass -InstallDir with the running binary's directory, which can be exactly such a place. Failures are now classified by cause before a remedy is offered: access denied says to run as Administrator or choose a writable -InstallDir, a sharing or lock violation keeps the held-open message, and anything else is reported as it is with no guessed remedy. The classifier walks the exception chain, type before HResult, because PowerShell wraps a denied Move-Item as UnauthorizedAccessException directly and a denied .NET call as MethodInvocationException around it. Directory creation, which had no handler, goes through the same classification. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PAKH9R0VGMEQGVK28WE9N3
The report built its list from Get-Command, which resolves against the process PATH. A shell started before an earlier run wrote the registry cannot see that run's directory, so a user who installs stable, restarts the terminal as told, and installs nightly was never shown the copy the installer itself had put down. The list now also walks the stored user and machine PATH for directories holding an entire.exe, expanded for lookup and deduplicated against the process copies. The verdict about which copy this shell runs still comes from the process PATH; only the "Also found" list widens. That list can now be non-empty when the process PATH has no entire at all, which the report says instead of dereferencing nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PAXDCKCRKCNN6C357AB4K9
Stock Windows PowerShell 5.1 ships PowerShellGet without the NuGet package provider, and its bootstrap is an interactive prompt that -Force does not suppress, so Install-Module fails in a non-interactive session until the provider is installed. CI never sees this because the runners preinstall Pester and PSScriptAnalyzer. scripts/test/init.ps1 installs the provider on Windows PowerShell, then Pester and PSScriptAnalyzer, all for the current user, skipping each step that is already satisfied. run.ps1 and the CLAUDE.md testing section point at it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PAXFSJXNJ6PVV89SWB3SCK
Phase 3 captures the installer's output to assert on it and never printed it, so twice now an assertion has failed in CI with the text it judged invisible. The captured report is echoed before the assertions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PB990TY9KJS5W1M9EKSTR7
Get-EntireCopy returns its array with -NoEnumerate, and its result was piped straight into Where-Object. On pwsh a -NoEnumerate array reaches a pipeline as one object, so the filter saw a single array whose .Source enumerated every path, and the conflict report printed all copies on one "Also found" line; on Windows PowerShell 5.1 the array enumerates and the report was right, which is why only the pwsh E2E leg failed. The result is assigned before it is piped, as Get-EntireOnPath's always was, and the unit test indexes the assigned result instead of piping it, since piping hid the wrong shape there too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PB9BBY0N0J97NRQNQKCHDW
The unknown-location fallback now prints the scriptblock one-liner with -InstallDir "<dir of entire.exe>", so install.ps1 replaces the binary in place. This relies on the installer treating an explicit -InstallDir as opting out of its Scoop-first default (landing on #2152); until then the flag is ignored on machines with Scoop. Without an exec path the hint stays bare. The stable form switches to the scriptblock shape because irm | iex cannot bind arguments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P8DVXVHX7D5CWEVFDSST5E
The Scoop branch's "Also found" list still came from Get-Command alone, so a copy that only an earlier run had put on the stored user or machine PATH went unreported there while the archive branch had learned to name it. Both branches now build the list from Get-EntireCopy, assigned before it is piped; the verdict about whether the shim takes priority in this shell still comes from the process PATH. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PBKB4B5P3HYT483EHTPFS5
The "Also found" lines read as a ranking, but after the list started including copies known only from the stored PATH, a copy that was first in the saved user PATH printed last, behind the Scoop shim: correct as a set, misleading as a list for someone deciding what to delete first. Only the copies this shell can run are ranked; the rest go under their own heading, "Also on your saved PATH, not active in this shell", because they will compete after a restart and are not competing now. Both conflict blocks use the same report. The architecture read is factored out so the mapping from the native processor name to a release suffix is tested; it had no coverage. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PC1MPWFWGAJATVG2HBNB6W
Two report branches had only ever been seen on a developer's laptop. The nightly phase now installs from a child shell whose PATH predates the first install, as a terminal opened before that install would, and asserts the stable copy is reported from the stored PATH under its own heading; the child proves the precondition itself. A new phase plants an entire.exe in a directory appended to the machine PATH and asserts the paragraph that says such a copy wins in every new terminal, with the -InstallDir remedy naming that directory. The machine PATH is restored and the stub removed when the run ends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PC1PBSSF0E6DW1779DVC41
run.ps1 exited with Pester's FailedCount, which stays 0 when a test file fails to parse or a BeforeAll throws: the tests never exist to fail, the container does, and CI stayed green. The exit now keys on the run's overall Result. run.ps1 takes -Path so a test can point it at its own directory, and Runner.Tests.ps1 runs it against a passing container, a file with a missing brace, and a throwing BeforeAll. Two comments in install.ps1 catch up with the code: Get-EntireOnPath has used -NoEnumerate rather than the unary comma since the lint layer landed, and both it and Get-EntireCopy now state that callers assign the result before piping it. Assert-Checksum says what the checksum defends against, transfer corruption and the wrong asset, and what it does not, a compromised release origin, since checksum and archive share a trust root. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PCWTECF3KCA9E99QXXZVPM
ci.yml's test-windows job ran the installer's Pester suite on every pull request, twice, including ones that could not have affected it. The suite now runs as the first job of install-ps1-e2e.yml, which is already gated to the installer and its tests, in both Windows PowerShell 5.1 and pwsh; the real-install job waits for it, so a broken script fails offline before a network run is spent. test-windows keeps its name for ci.yml's aggregate and runs only the Go Windows tests. The weekly schedule is gone; workflow_dispatch exercises the installer against current releases on demand. The suite job is not called "test", because that check name is required by the main ruleset and belongs to ci.yml. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PDAY0P02HGG801HWPPJA72
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1PDHQPGGC6APH8HQJMY3FPA
-UseBasicParsing matters only for Invoke-WebRequest, whose HTML parser on
Windows PowerShell 5.1 needs Internet Explorer; Invoke-RestMethod decodes
the body itself, so the flag was noise on both one-liners and on the API
call inside the installer. The archive download keeps it.
The nightly form no longer spells out [scriptblock]::Create. The fetched
text is wrapped in braces and called with its arguments, as Scoop
documents for its own options:
irm https://entire.io/install.ps1 | iex
iex "& {$(irm https://entire.io/install.ps1)} -Channel nightly"
Both shapes are pinned by the suite; the scriptblock form keeps working.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Entire-Checkpoint: 01M1PENJ3Q84HECQSN4APZSC3V
The unknown-location fallback now prints the scriptblock one-liner with -InstallDir "<dir of entire.exe>", so install.ps1 replaces the binary in place. This relies on the installer treating an explicit -InstallDir as opting out of its Scoop-first default (landing on #2152); until then the flag is ignored on machines with Scoop. Without an exec path the hint stays bare. The stable form switches to the scriptblock shape because irm | iex cannot bind arguments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M1P8DVXVHX7D5CWEVFDSST5E

https://entire.io/gh/entireio/cli/trails/1164
Summary
entire.exeplusgit-remote-entire.exepowershellandpwshin the Windows CI jobVerification
mise run lint— passes with 0 issues after formatting and immediately before pushentire.exeexecution andgit-remote-entire.exepresenceKnown local test failures
mise run checkcompleted formatting and lint successfully, but its race-enabled Go test phase hit existing/unrelated failures on this macOS checkout:/usr/localexecutable path as Homebrew on this machineTestCodexAppServerHooksList_BareWorktreeUsesLayoutRootcompares/var/...with the canonical/private/var/...pathTestAttach_DiscoversExternalAgentsThis change does not modify Go production or test code.
Note
Low Risk
Changes are limited to install scripts, docs, and CI smoke checks; no CLI runtime or auth logic is modified.
Overview
Adds
scripts/install.ps1, a Windows-native installer for PowerShell 5.1 and 7, with stable and nightly channels. Stable installs delegate to Scoop when it is on PATH (bucket add, install vs update); otherwise the script downloads the release zip, verifies SHA-256 againstchecksums.txt, and installsentire.exeandgit-remote-entire.exeunder%USERPROFILE%\.local\bin, optionally updating the user PATH and surfacing PATH conflicts likeinstall.sh.README now leads with the
irm … | iexflow, documents Scoop vs direct install behavior, and listsinstall.ps1in release channels.install.sh’s MSYS/Git Bash error text points users at the PowerShell installer instead of Scoop-only wording.CI runs
.\scripts\install.ps1 -Helpunder bothpowershell(5.x) andpwsh(7+) in the Windows job before existing Go tests.Reviewed by Cursor Bugbot for commit 8453cc9. Configure here.