feat: Windows support#45
Conversation
herdr's IPC on Windows is a named pipe at \.\pipe\<socketPath>, not a unix socket. Add build-tagged dialHerdr: named pipe on Windows (retrying on ERROR_PIPE_BUSY), unix socket elsewhere. Stdlib only.
One platform descriptor selected once replaces scattered runtime.GOOS checks: PowerShell vs sh, powershellQuote vs posixQuote, pane -windows suffix, and the {{opener}} command (Start-Process/open/xdg-open). Quick-action examples use {{opener}} so they work cross-OS.
Windows can't spawn the extensionless PE, so each action/pane gets a -windows twin running herdr-plus.exe via a powershell wrapper (relative-path spawn needs a shell). Panes omit -NonInteractive (interactive TUIs); actions/events keep it. manifest_test scans command args for the .exe.
os.UserHomeDir error was silently discarded; on Windows a missing USERPROFILE would resolve ~ to a wrong path. Return the error when the path needs home; add displayWorkingDir for UI paths that can't surface one. Also routes pane opens through paneEntrypoint.
Add windows/amd64 to goreleaser (skip windows/arm64) and override the archive format to .zip for Windows.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class Windows support to the herdr-plus plugin by introducing Windows-specific IPC dialing (named pipes), centralizing shell/quoting behavior behind a platform descriptor, and updating the plugin manifest and release packaging to include Windows binaries and entrypoints.
Changes:
- Add OS-specific abstractions for shell execution/quoting, pane entrypoint selection, and “open” command templating (
{{opener}}). - Add build-tagged IPC dialing to support Windows named pipes while preserving Unix domain socket behavior elsewhere.
- Update plugin manifest, examples, tests, and GoReleaser packaging to support Windows install/run flows.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| util.go | Removes POSIX-only shellQuote helper now centralized elsewhere. |
| shell.go | Introduces platform descriptor (shell, quoting, pane suffix, opener) and helpers. |
| shell_test.go | Adds cross-platform unit tests for quoting/shell dispatch/entrypoint/opener. |
| quickactions.go | Uses paneEntrypoint() so the correct pane id is opened per OS. |
| projectsmodel.go | Switches UI display to use displayWorkingDir() for non-error UI contexts. |
| projectsmodel_test.go | Adjusts assertions for platform-native path normalization. |
| projects.go | Uses paneEntrypoint() and propagates expandedWorkingDir() errors when opening projects. |
| project.go | Makes expandedWorkingDir() return (string, error), adds displayWorkingDir() helper. |
| project_test.go | Updates tests for expandedWorkingDir() error return and separator normalization. |
| manifest_test.go | Adds test ensuring manifest includes required Windows entries/commands. |
| herdrdial_windows.go | Adds Windows dialHerdr() using named pipes with retry on ERROR_PIPE_BUSY. |
| herdrdial_other.go | Adds non-Windows dialHerdr() using unix domain sockets. |
| herdr.go | Switches client transport to dialHerdr() to unify Windows/Unix IPC. |
| herdr-plugin.toml | Adds Windows platform + build step + windows-twin actions/panes/events. |
| examples/quick-actions/open-working-dir.toml | Uses {{opener}} and updates description to be cross-platform. |
| examples/quick-actions/open-repo.toml | Replaces hardcoded open with {{opener}}. |
| examples/quick-actions/google.toml | Replaces hardcoded open with {{opener}}. |
| examples/quick-actions/google-search.toml | Replaces hardcoded open with {{opener}}. |
| examples/quick-actions/github.toml | Replaces hardcoded open with {{opener}} and documents it. |
| action.go | Adds template funcs ({{opener}}) and routes execution via shellCommand(). |
| action_test.go | Updates expected quoting to be OS-specific; adds opener helper test. |
| action_run_integration_test.go | Adds integration test to validate quoting round-trip through real shells. |
| .goreleaser.yml | Adds Windows builds and ships Windows archives as .zip. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // expandedWorkingDir resolves the project's working directory to an absolute | ||
| // path, expanding a leading ~ to the home directory and any $VARS in the path. | ||
| // An empty working_dir defaults to the user's home directory, so a minimal | ||
| // project still opens somewhere sensible. |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
cloudmanic
left a comment
There was a problem hiding this comment.
Thanks for this, @shakedarazi — this is exactly the work that was missing. When Windows got dropped back in #20 it was specifically because the socket client and the sh-based build step had never been validated there, so declaring the platform would have been dishonest. This resolves precisely that: a real named-pipe transport (with the ERROR_PIPE_BUSY retry), a Windows build step, and end-to-end validation against the herdr Windows beta.
The engineering is clean. Centralizing every OS difference into the platform descriptor in shell.go rather than scattering runtime.GOOS checks is the right call, the PowerShell single-quote-doubling keeps injected quick-action values safe, and the build-tagged dialHerdr keeps the unix path untouched — I ran the Mac suite with -race and it's green, and it cross-compiles for all three OSes. Approving, and glad to have Windows back.
One non-blocking follow-up: the README doesn't yet mention that Windows installs need Go on PATH (the Windows build step has no prebuilt-binary fallback like the sh script does). Worth a short note there.
The Windows build step compiles from source with the Go toolchain and has no prebuilt-binary fallback like the Linux/macOS sh script, so Windows installs require Go on PATH. Document that in the README and the installation docs, and refresh the now-stale "not tested on Windows" / Linux+macOS-only platform lines now that Windows is supported and validated against herdr's Windows beta.
Adds Windows support to the herdr-plus plugin. Verified end-to-end against a live herdr 0.7.4-preview on Windows 11 (Go 1.26.2); Linux/macOS behaviour unchanged.
What was needed on Windows
Three platform differences drove the change:
\\.\pipe\<socketPath>(the on-diskherdr.sockis just a liveness marker). Protocol is identical newline-delimited JSON, no token handshake. Added build-taggeddialHerdr: named pipe on Windows (retrying onERROR_PIPE_BUSY), unix socket elsewhere. Stdlib only, no new deps.CreateProcessWappends.exe, so the binary isherdr-plus.exeand each action/pane gets a-windowstwin (herdr requires unique action/pane ids; duplicate eventon=differentiated byplatformsis allowed). Relative-path spawn resolves against herdr's cwd, so Windows entries shell throughpowershell -Command "& .\bin\herdr-plus.exe <sub>". Panes omit-NonInteractive(interactive TUIs); actions/events keep it.platformdescriptor inshell.go(PowerShell vssh, PowerShell vs POSIX quoting, pane suffix,{{opener}}=Start-Process/open/xdg-open) — no scatteredruntime.GOOS.Verified
go build(windows/linux/darwin),go vet ./...,go test ./...all green.platforms = [linux, macos, windows].worktree.created→ auto-layout all exercised on the live host.~/$VARSexpansion unit-tested on Windows.Also
expandedWorkingDirnow surfaces a home-dir resolution error instead of silently discarding it (a missingUSERPROFILEwould have resolved~to a wrong path).amd64binaries as.zip.🤖 Generated with Claude Code