Skip to content

Commit bb18d2f

Browse files
release v0.12.0: Windows support
- Cross-platform config paths (os.UserHomeDir, %APPDATA%\aipack on Windows) - Cline Documents folder resolved via Windows shell API for OneDrive support - PowerShell installer (install.ps1) with AIPACK_VERSION pinning - PowerShell shell completion, clip.exe clipboard support - Windows self-update with locked-executable handling - pack install --link falls back to directory junction on Windows - WSL detection with doctor warning for Cline cross-filesystem paths - Platform-aware git error hints (credential helper, winget suggestion) - Symlink test guards for Windows without Developer Mode - CI validates on both Ubuntu and Windows - windows/amd64 and windows/arm64 release binaries
1 parent c9522b1 commit bb18d2f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+714
-278
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ on:
1010

1111
jobs:
1212
validate:
13-
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
os: [ubuntu-latest, windows-latest]
16+
runs-on: ${{ matrix.os }}
1417
permissions:
1518
contents: read
1619
steps:
@@ -20,7 +23,9 @@ jobs:
2023
go-version-file: go.mod
2124
cache: true
2225
- run: make fmt-check
23-
- run: make test
26+
if: runner.os != 'Windows'
27+
- name: Run tests
28+
run: go test ./...
2429
- run: go vet ./...
2530

2631
release:

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ The format is based on Keep a Changelog, and releases use semantic versioning ta
66

77
## Unreleased
88

9+
## [0.12.0]
10+
11+
### Added
12+
13+
- **Windows support (amd64 + arm64).** Cross-platform config paths (`%APPDATA%\aipack` on Windows, `~/.config/aipack` elsewhere), PowerShell installer (`install.ps1`) with `AIPACK_VERSION` support, PowerShell shell completion, Windows self-update with locked-executable handling, `clip.exe` clipboard support, and `windows/amd64` + `windows/arm64` release binaries.
14+
- **CI Windows test runner.** Tests now run on both Ubuntu and Windows in the validate pipeline.
15+
- **WSL detection.** `aipack doctor` warns when running in WSL with Cline configured, since global-scope Cline rules target the Windows filesystem which WSL cannot reach.
16+
- **Symlink test portability.** Tests that create symlinks skip gracefully on Windows without Developer Mode instead of failing.
17+
18+
### Changed
19+
20+
- Home directory resolution uses `os.UserHomeDir()` instead of `$HOME`, which works across all platforms (HOME on Unix, USERPROFILE on Windows).
21+
- Cline Documents folder is resolved via the Windows shell API (`SHGetKnownFolderPath`) to handle OneDrive folder redirection. Non-Windows platforms use the conventional `~/Documents` path.
22+
- `pack install --link` falls back to a directory junction (`mklink /J`) on Windows when symlinks require elevated privileges.
23+
- Git error hints are platform-aware: credential helper suggestions use `manager` on Windows, `store` on Linux, `osxkeychain` on macOS. Git-not-found on Windows suggests `winget install Git.Git`.
24+
- Cline global paths changed from a package-level variable to a `GlobalPathsFor(home)` function to support platform-dependent Documents folder resolution.
25+
- Ledger path encoding handles Windows drive letters and backslashes.
26+
- Test assertions use `filepath.Join` and `t.TempDir()` instead of hardcoded Unix path literals.
27+
28+
### Known limitations
29+
30+
- **WSL + Cline global scope:** aipack in WSL writes to the Linux filesystem, but Cline reads from the Windows filesystem. Use `aipack sync --scope project` in WSL, or run aipack natively on Windows for global scope.
31+
- **OpenCode harness:** Global paths still use `.config/opencode` (Unix convention). Windows-specific resolution is not yet implemented.
32+
933
## [0.11.7]
1034

1135
### Added

Makefile

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,18 @@ validate: ## Validate pack content (PACK_ROOT required)
5454

5555
dist: ## Cross-compile for all platforms
5656
@mkdir -p $(DIST)
57-
@for platform in darwin/arm64 darwin/amd64 linux/amd64; do \
58-
GOOS=$${platform%/*} GOARCH=$${platform#*/} \
57+
@for platform in darwin/arm64 darwin/amd64 linux/amd64 windows/amd64 windows/arm64; do \
58+
goos=$${platform%/*}; goarch=$${platform#*/}; \
59+
ext=""; if [ "$$goos" = "windows" ]; then ext=".exe"; fi; \
60+
outname=$(BINARY)-$${goos}-$${goarch}$${ext}; \
61+
GOOS=$$goos GOARCH=$$goarch \
5962
go build $(GO_TAGS) -ldflags "$(LDFLAGS)" \
60-
-o $(DIST)/$(BINARY)-$${platform%/*}-$${platform#*/} ./cmd/aipack || exit 1; \
61-
case "$${platform%/*}" in darwin) \
63+
-o $(DIST)/$$outname ./cmd/aipack || exit 1; \
64+
case "$$goos" in darwin) \
6265
if [ "$$(uname)" = "Darwin" ] && command -v codesign >/dev/null 2>&1; then \
63-
codesign -s - -f $(DIST)/$(BINARY)-$${platform%/*}-$${platform#*/} 2>/dev/null; \
66+
codesign -s - -f $(DIST)/$$outname 2>/dev/null; \
6467
fi ;; esac; \
65-
echo " $(DIST)/$(BINARY)-$${platform%/*}-$${platform#*/}"; \
68+
echo " $(DIST)/$$outname"; \
6669
done
6770

6871
clean: ## Remove build artifacts

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,16 @@ On macOS and Linux, you can also use the release-backed installer script:
5353
curl -fsSL https://raw.githubusercontent.com/shrug-labs/aipack/main/install.sh | sh
5454
```
5555

56-
The installer detects your platform, downloads the matching release binary, verifies
57-
`SHA256SUMS`, installs `aipack`, and prints the installed version.
56+
On Windows (PowerShell):
5857

59-
Useful overrides:
58+
```powershell
59+
irm https://raw.githubusercontent.com/shrug-labs/aipack/main/install.ps1 | iex
60+
```
61+
62+
The installers detect your platform, download the matching release binary, verify
63+
`SHA256SUMS`, install `aipack`, and print the installed version.
64+
65+
Useful overrides (macOS/Linux):
6066

6167
```bash
6268
# Pin a specific release tag
@@ -69,7 +75,13 @@ curl -fsSL https://raw.githubusercontent.com/shrug-labs/aipack/main/install.sh |
6975
curl -fsSL https://raw.githubusercontent.com/shrug-labs/aipack/main/install.sh | PREFIX=$HOME/.local sh
7076
```
7177

72-
Release binaries are published for `darwin/arm64`, `darwin/amd64`, and `linux/amd64`. Stable releases also update the Homebrew formula in `dfoster-oracle/homebrew-tap`. If you prefer a manual install, use the matching release asset from <https://github.com/shrug-labs/aipack/releases> together with `SHA256SUMS`.
78+
Windows override (PowerShell):
79+
80+
```powershell
81+
$env:AIPACK_VERSION = "v0.12.0"; irm https://raw.githubusercontent.com/shrug-labs/aipack/main/install.ps1 | iex
82+
```
83+
84+
Release binaries are published for `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `windows/amd64`, and `windows/arm64`. Stable releases also update the Homebrew formula in `dfoster-oracle/homebrew-tap`. If you prefer a manual install, use the matching release asset from <https://github.com/shrug-labs/aipack/releases> together with `SHA256SUMS`.
7385

7486
### Build from source
7587

@@ -194,7 +206,7 @@ make fmt-check # fail if formatting is stale
194206
make help # show all targets
195207
make build # build for current platform → dist/
196208
make test # run Go tests
197-
make dist # cross-compile for darwin/arm64, darwin/amd64, linux/amd64
209+
make dist # cross-compile for darwin/arm64, darwin/amd64, linux/amd64, windows/amd64, windows/arm64
198210
make install # build + copy to ~/.local/bin/
199211
make clean # remove build artifacts
200212
```

RELEASING.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ Examples:
5757
3. GitHub Actions will:
5858
- run formatting, test, and vet checks
5959
- verify the pushed tag matches `VERSION`
60-
- build `darwin/arm64`, `darwin/amd64`, and `linux/amd64` binaries
60+
- build `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `windows/amd64`, and `windows/arm64` binaries
6161
- generate `SHA256SUMS`
6262
- publish GitHub Release assets
6363
- update `dfoster-oracle/homebrew-tap` for stable tags (requires `HOMEBREW_TAP_GITHUB_TOKEN`)
6464

6565
## After publish
6666

6767
1. Confirm the release page contains:
68-
- all three binaries
68+
- all five binaries (darwin/arm64, darwin/amd64, linux/amd64, windows/amd64, windows/arm64)
6969
- `SHA256SUMS`
7070
- generated release notes
7171
2. Confirm the release is marked as a prerelease when the tag contains a prerelease suffix.
@@ -78,18 +78,25 @@ Examples:
7878
Then run the downloaded binary and confirm `aipack version` reports the
7979
published release line.
8080

81-
4. Run the installer against the published release:
81+
4. Run the Unix installer against the published release:
8282

8383
```bash
8484
VERSION=vX.Y.Z BIN_DIR="$PWD/bin" ./install.sh
8585
./bin/aipack version
8686
```
8787

88-
5. For stable tags, confirm the Homebrew tap updated to the published version:
88+
5. Run the Windows installer (PowerShell) against the published release:
89+
90+
```powershell
91+
$env:AIPACK_VERSION = "vX.Y.Z"; irm https://raw.githubusercontent.com/shrug-labs/aipack/main/install.ps1 | iex
92+
aipack version
93+
```
94+
95+
6. For stable tags, confirm the Homebrew tap updated to the published version:
8996

9097
```bash
9198
brew install dfoster-oracle/tap/aipack
9299
brew info dfoster-oracle/tap/aipack
93100
```
94101

95-
6. If install instructions changed, update `README.md` in the same release-prep change.
102+
7. If install instructions changed, update `README.md` in the same release-prep change.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.11.7
1+
0.12.0

cmd/aipack/clean.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (c *CleanCmd) Validate() error {
5353
func (c *CleanCmd) Run(ctx context.Context, g *Globals) error {
5454
// Load sync-config for scope and harness resolution.
5555
var syncCfg config.SyncConfig
56-
if cfgDir, err := cmdutil.ResolveConfigDir(c.ConfigDir, os.Getenv("HOME")); err == nil {
56+
if cfgDir, err := cmdutil.ResolveConfigDir(c.ConfigDir, config.HomeDir()); err == nil {
5757
if sc, serr := config.LoadSyncConfig(config.SyncConfigPath(cfgDir)); serr == nil {
5858
syncCfg = sc
5959
}
@@ -92,7 +92,7 @@ func (c *CleanCmd) Run(ctx context.Context, g *Globals) error {
9292
Scope: scope,
9393
ProjectDir: projectAbs,
9494
Harnesses: harnesses,
95-
Home: os.Getenv("HOME"),
95+
Home: config.HomeDir(),
9696
},
9797
WipeLedger: c.Ledger,
9898
Yes: c.Yes,

cmd/aipack/completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,5 @@ func predictResources(a complete.Args) []string {
128128
}
129129

130130
func configBaseDir() (string, error) {
131-
return config.DefaultConfigDir(os.Getenv("HOME"))
131+
return config.DefaultConfigDir(config.HomeDir())
132132
}

cmd/aipack/completion_install.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"strings"
910
)
1011

@@ -134,10 +135,20 @@ func confirmPrompt(ctx context.Context, g *Globals) bool {
134135

135136
func detectShell() string {
136137
shell := os.Getenv("SHELL")
137-
if shell == "" {
138-
return "bash"
138+
if shell != "" {
139+
name := filepath.Base(shell)
140+
// Strip .exe suffix on Windows (e.g. "bash.exe" → "bash").
141+
name = strings.TrimSuffix(name, ".exe")
142+
return name
139143
}
140-
return filepath.Base(shell)
144+
// On Windows, $SHELL is not set. Detect PowerShell via PSModulePath
145+
// (always set inside pwsh/powershell). Fall back to bash for Git Bash users.
146+
if runtime.GOOS == "windows" {
147+
if os.Getenv("PSModulePath") != "" {
148+
return "powershell"
149+
}
150+
}
151+
return "bash"
141152
}
142153

143154
func defaultRCFile(shell string) string {
@@ -152,6 +163,8 @@ func defaultRCFile(shell string) string {
152163
return filepath.Join(home, ".bashrc")
153164
case "fish":
154165
return filepath.Join(home, ".config", "fish", "config.fish")
166+
case "powershell":
167+
return filepath.Join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
155168
default:
156169
return ""
157170
}
@@ -175,6 +188,14 @@ func completionSnippet(shell, bin, cmd string) string {
175188
end
176189
complete -f -c %s -a "(__complete_%s)"
177190
`, cmd, bin, cmd, cmd)
191+
case "powershell":
192+
body = fmt.Sprintf(`Register-ArgumentCompleter -CommandName %s -Native -ScriptBlock {
193+
param($wordToComplete, $commandAst, $cursorPosition)
194+
$env:COMP_LINE = $commandAst.ToString()
195+
& %s | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
196+
Remove-Item env:COMP_LINE
197+
}
198+
`, cmd, bin)
178199
default:
179200
return ""
180201
}

cmd/aipack/completion_install_cmd_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ func TestInstallCompletions_Print(t *testing.T) {
1515
if code != cmdutil.ExitOK {
1616
t.Fatalf("exit=%d, want %d", code, cmdutil.ExitOK)
1717
}
18-
if !strings.Contains(stdout, "complete") {
18+
// Bash/zsh snippets contain "complete"; PowerShell uses "Completer"/"Completion".
19+
if !strings.Contains(strings.ToLower(stdout), "complet") {
1920
t.Fatalf("expected completion snippet in stdout, got: %s", stdout)
2021
}
2122
}

0 commit comments

Comments
 (0)