Skip to content

Commit 548877b

Browse files
dom-ravenDominic Ravenclaudekunallanjewar
authored
docs(readme): add Windows install path + install.ps1 (#101)
* docs(readme): add Windows install path + install.ps1 Releases already build and ship windows_amd64/windows_arm64 zips (goreleaser 6-target matrix), but the Quick Start said "Requires macOS or Linux" and offered no Windows instructions. - add install.ps1, a PowerShell mirror of install.sh: resolves the latest tag, SHA256-verifies the zip before extraction, installs to %LOCALAPPDATA%\Programs\guild, and appends that dir to the user PATH (Windows has no single shell profile to hint at, so the installer does it; parity divergence from install.sh noted inline) - ship install.ps1 as a release asset via goreleaser extra_files - document the one-liner in README Quick Start with an honest caveat: semantic retrieval is disabled on Windows (onnxruntime-purego has no Windows Dlopen surface; embedder_state='disabled'), so search is BM25-only Verified on Windows 11 (PowerShell 5.1): file and irm|iex invocation paths both install v0.3.2 with checksum verification; schema migrations and the CLI surface work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): add cmd.exe one-liner; fix upgrade-while-running in install.ps1 - README: cmd.exe users get a one-liner that shells into PowerShell (a separate batch installer would duplicate download/verify logic for no audience -- PowerShell 5.1 ships with every supported Windows; same pattern as winget/Chocolatey/Scoop docs) - install.ps1: Move-Item -Force cannot replace guild.exe while it is running (e.g. as a live MCP server) -- Windows locks running executables against overwrite/delete but allows rename. Rename the old binary aside with a unique suffix, move the new one in, and best-effort clean up .old-* leftovers on the next install. Found by testing an upgrade while Claude Code had guild mcp serve attached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Dominic Raven <dominicraven@pm.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Kunal Lanjewar <kunallanjewar@gmail.com>
1 parent 2336e9b commit 548877b

3 files changed

Lines changed: 170 additions & 1 deletion

File tree

.goreleaser.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ release:
166166
# tarball before extracting.
167167
extra_files:
168168
- glob: install.sh
169+
- glob: install.ps1
169170

170171
# ─── Homebrew cask ─────────────────────────────────────────────────────────────
171172
#

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ On session start, an agent makes a single call to recover the project oath, the
3636
3737
## Quick Start
3838

39-
Requires macOS or Linux and an MCP-enabled editor (Claude Code, Codex, Cursor, etc.). No account, no API key.
39+
Requires macOS, Linux, or Windows and an MCP-enabled editor (Claude Code, Codex, Cursor, etc.). No account, no API key.
4040

4141
### 1. Install
4242

@@ -56,6 +56,28 @@ brew install mathomhaus/tap/guild
5656
Both paths install a binary built with `-tags=withembed`, so semantic
5757
retrieval works out of the box with no extra steps.
5858

59+
**Windows (pre-built zip, keyword-only retrieval):**
60+
61+
```powershell
62+
irm https://github.com/mathomhaus/guild/releases/latest/download/install.ps1 | iex
63+
guild --version # in a new terminal
64+
```
65+
66+
Or from cmd.exe:
67+
68+
```cmd
69+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/mathomhaus/guild/releases/latest/download/install.ps1 | iex"
70+
```
71+
72+
The installer SHA256-verifies the zip, installs to
73+
`%LOCALAPPDATA%\Programs\guild`, and adds it to your user PATH.
74+
On Windows, semantic (vector) retrieval is currently disabled —
75+
`onnxruntime-purego` has no Windows `Dlopen` surface (see
76+
[`internal/lore/embed/assets/README.md`](./internal/lore/embed/assets/README.md)),
77+
so search runs the BM25 keyword arm only. Everything else — quests,
78+
lore, briefs, MCP server, SQLite state under `~\.guild\` — works the
79+
same as on macOS/Linux.
80+
5981
**Clone and build (ship-ready, embed included):**
6082

6183
```bash

install.ps1

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# install.ps1 -- PowerShell installer for guild (Windows)
2+
#
3+
# Mirror of install.sh for Windows. Downloads the matching release zip
4+
# + checksums.txt from GitHub, verifies SHA256 BEFORE extraction, and
5+
# installs the binary to %LOCALAPPDATA%\Programs\guild by default.
6+
#
7+
# Supported: windows x amd64, arm64. Works on Windows PowerShell 5.1
8+
# and PowerShell 7+. This file is deliberately ASCII-only: PowerShell
9+
# 5.1 reads BOM-less scripts as ANSI, which mangles multibyte chars.
10+
#
11+
# Usage:
12+
# irm https://github.com/mathomhaus/guild/releases/latest/download/install.ps1 | iex
13+
# .\install.ps1 [-Version vX.Y.Z] [-Prefix DIR]
14+
# Environment overrides (useful with irm | iex, which takes no args):
15+
# GUILD_VERSION -- same as -Version
16+
# GUILD_INSTALL_PREFIX -- same as -Prefix
17+
#
18+
# Signature (cosign) verification is deliberately NOT performed here;
19+
# this script checks SHA256 only -- same policy as install.sh. See
20+
# SECURITY.md and .goreleaser.yml for the cosign verify-blob steps.
21+
#
22+
# No telemetry, no phone-home. The only network calls are to
23+
# api.github.com (to resolve the latest tag) and to
24+
# github.com/mathomhaus/guild/releases/download/... (for the zip and
25+
# checksums.txt).
26+
27+
param(
28+
[string]$Version = $env:GUILD_VERSION,
29+
[string]$Prefix = $env:GUILD_INSTALL_PREFIX
30+
)
31+
32+
$ErrorActionPreference = 'Stop'
33+
34+
$Repo = 'mathomhaus/guild'
35+
$BinName = 'guild'
36+
37+
if (-not $Prefix) {
38+
$Prefix = Join-Path $env:LOCALAPPDATA 'Programs\guild'
39+
}
40+
41+
# Windows PowerShell 5.1 defaults to TLS 1.0; GitHub requires >= 1.2.
42+
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
43+
44+
# --- platform detection ----------------------------------------------
45+
$archRaw = $env:PROCESSOR_ARCHITECTURE
46+
switch ($archRaw) {
47+
'AMD64' { $arch = 'amd64' }
48+
'ARM64' { $arch = 'arm64' }
49+
default { throw "unsupported architecture: $archRaw (supported: AMD64, ARM64)" }
50+
}
51+
52+
$tmpDir = Join-Path $env:TEMP "guild-install-$([System.IO.Path]::GetRandomFileName())"
53+
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
54+
55+
try {
56+
# --- version resolution ------------------------------------------
57+
if (-not $Version) {
58+
Write-Host "resolving latest release from github.com/$Repo..."
59+
try {
60+
$latest = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest"
61+
} catch {
62+
throw "failed to query the GitHub API ($($_.Exception.Message)). If you are rate-limited, set `$env:GUILD_VERSION='vX.Y.Z' to bypass it."
63+
}
64+
$Version = $latest.tag_name
65+
if (-not $Version) {
66+
throw "could not read tag_name from the GitHub API response. Set `$env:GUILD_VERSION='vX.Y.Z' to bypass the API."
67+
}
68+
}
69+
70+
# goreleaser uses .Version (without the leading v) in name_template.
71+
$versionNum = $Version -replace '^v', ''
72+
73+
# --- download ----------------------------------------------------
74+
$zipName = "${BinName}_${versionNum}_windows_${arch}.zip"
75+
$baseUrl = "https://github.com/$Repo/releases/download/$Version"
76+
$zipPath = Join-Path $tmpDir $zipName
77+
$checksumsPath = Join-Path $tmpDir 'checksums.txt'
78+
79+
Write-Host "downloading $zipName ($Version)..."
80+
try {
81+
Invoke-WebRequest "$baseUrl/$zipName" -OutFile $zipPath -UseBasicParsing
82+
} catch {
83+
throw "failed to download $baseUrl/$zipName -- does that release exist for windows/$arch?"
84+
}
85+
86+
Write-Host 'downloading checksums.txt...'
87+
Invoke-WebRequest "$baseUrl/checksums.txt" -OutFile $checksumsPath -UseBasicParsing
88+
89+
# --- verify SHA256 BEFORE extracting -----------------------------
90+
$expectedLine = Get-Content $checksumsPath | Where-Object { $_ -match "\s+$([regex]::Escape($zipName))$" } | Select-Object -First 1
91+
if (-not $expectedLine) {
92+
throw "no checksum entry for $zipName in checksums.txt"
93+
}
94+
$expected = ($expectedLine -split '\s+')[0].ToLowerInvariant()
95+
$actual = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
96+
if ($expected -ne $actual) {
97+
throw "SHA256 mismatch for ${zipName}: expected $expected, got $actual"
98+
}
99+
Write-Host 'sha256 verified'
100+
101+
# --- extract + install -------------------------------------------
102+
$extractDir = Join-Path $tmpDir 'extract'
103+
Expand-Archive $zipPath -DestinationPath $extractDir -Force
104+
105+
$binSrc = Join-Path $extractDir "$BinName.exe"
106+
if (-not (Test-Path $binSrc)) {
107+
throw "binary '$BinName.exe' not found inside $zipName"
108+
}
109+
110+
New-Item -ItemType Directory -Path $Prefix -Force | Out-Null
111+
$installPath = Join-Path $Prefix "$BinName.exe"
112+
# Windows locks running executables against overwrite/delete but
113+
# allows rename. If guild.exe is running (e.g. as an MCP server),
114+
# rename it aside, move the new binary in, then best-effort delete
115+
# the old one (next install cleans it up if it is still locked).
116+
Get-ChildItem -Path $Prefix -Filter "$BinName.exe.old-*" -ErrorAction SilentlyContinue |
117+
Remove-Item -Force -ErrorAction SilentlyContinue
118+
Copy-Item $binSrc "$installPath.tmp" -Force
119+
$oldPath = "$installPath.old-" + [System.IO.Path]::GetRandomFileName()
120+
if (Test-Path $installPath) {
121+
Move-Item $installPath $oldPath
122+
}
123+
Move-Item "$installPath.tmp" $installPath
124+
Remove-Item $oldPath -Force -ErrorAction SilentlyContinue
125+
126+
# --- post-install ------------------------------------------------
127+
Write-Host ''
128+
Write-Host "installed $BinName $Version -> $installPath"
129+
130+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
131+
$onPath = ($env:Path -split ';') -contains $Prefix -or ($userPath -split ';') -contains $Prefix
132+
if (-not $onPath) {
133+
[Environment]::SetEnvironmentVariable('Path', "$userPath;$Prefix", 'User')
134+
Write-Host "added $Prefix to your user PATH -- open a new terminal to pick it up"
135+
}
136+
137+
Write-Host ''
138+
Write-Host 'next step:'
139+
Write-Host " $BinName mcp install # register guild with your MCP client"
140+
Write-Host ''
141+
Write-Host 'note: semantic (vector) retrieval is currently unavailable on'
142+
Write-Host 'Windows; search runs keyword (BM25) only. See'
143+
Write-Host 'internal/lore/embed/assets/README.md for why.'
144+
} finally {
145+
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
146+
}

0 commit comments

Comments
 (0)