- 
                Notifications
    You must be signed in to change notification settings 
- Fork 380
Add Windows support via PowerShell scripts #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| WalkthroughThe pull request adds Windows PowerShell support and a runtime PowerShell-vs-shell selection for superpowers hooks, implements a SessionStart PowerShell hook that validates and assembles skill content into a JSON payload, and adds a PowerShell script to initialize or update the local skills repository with git and optional gh CLI integration. Changes
 Sequence Diagram(s)sequenceDiagram
    actor User
    participant HookRunner as Hook Runner
    participant SessionStartPS as session-start.ps1
    participant SessionStartSH as session-start.sh
    participant FS as File System
    participant Output as JSON Output
    User->>HookRunner: Start session
    HookRunner->>HookRunner: Check for pwsh/powershell
    alt PowerShell available
        HookRunner->>SessionStartPS: Execute PowerShell hook
        SessionStartPS->>FS: Check skills/using-superpowers/SKILL.md
        alt File exists
            FS-->>SessionStartPS: Return content
            SessionStartPS->>Output: Produce JSON with hookEventName "SessionStart" and additionalContext
            note right of SessionStartPS: Exit 0
        else File missing
            SessionStartPS->>Output: Produce error JSON and exit 1
        end
    else PowerShell not available
        HookRunner->>SessionStartSH: Execute shell hook via bash
        SessionStartSH->>FS: (shell flow) check/read SKILL.md
        SessionStartSH->>Output: Produce JSON or error
    end
sequenceDiagram
    actor User
    participant InitSkills as initialize-skills.ps1
    participant FS as File System
    participant Git as Git
    participant GH as gh CLI
    participant Remote as Remote Repository
    User->>InitSkills: Run initialize/update
    InitSkills->>FS: Check skills directory
    alt Directory exists & is git repo
        FS-->>InitSkills: Repo found
        InitSkills->>Git: Determine tracking remote & fetch
        Git->>Remote: Fetch
        Git-->>InitSkills: Compare local vs remote
        alt Local behind remote
            InitSkills->>Git: Fast-forward update
            note right of InitSkills: Exit 0
        else Up-to-date
            note right of InitSkills: Exit 0
        end
    else Missing or not a repo
        FS-->>InitSkills: Not present
        InitSkills->>FS: Backup legacy install (if any) and create config dirs
        InitSkills->>Git: Clone repository
        InitSkills->>GH: Check gh CLI
        alt gh available
            InitSkills->>User: Prompt to fork
            alt User confirms
                GH-->>InitSkills: Fork created (remote added)
            else
                InitSkills->>Git: Add upstream remote
            end
        else
            InitSkills->>Git: Add upstream remote
        end
        note right of InitSkills: Exit 0
    end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
 Pre-merge checks and finishing touches✅ Passed checks (3 passed)
 ✨ Finishing touches🧪 Generate unit tests (beta)
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
hooks/session-start.ps1 (1)
14-25: Verify JSON output format consistency.The error case uses
ConvertTo-Json -Compress(single-line JSON), while the success case on Line 50 usesConvertTo-Json -Depth 10(multi-line JSON). Ensure this difference is intentional for your parsing requirements.If consistent formatting is preferred, apply this diff:
- $output | ConvertTo-Json -Compress + $output | ConvertTo-Json -Depth 10 exit 1lib/initialize-skills.ps1 (2)
3-3: Use Join-Path for cross-platform path construction.Line 3 hardcodes backslashes in the path, while Line 72 uses
Join-Pathwhich is more robust and handles path separators correctly. Consider applying the same approach consistently.Apply this diff for consistency:
-$skillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" +$skillsDir = Join-Path $env:USERPROFILE ".config" | Join-Path -ChildPath "superpowers" | Join-Path -ChildPath "skills"Or more concisely:
-$skillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" +$skillsDir = Join-Path $env:USERPROFILE ".config/superpowers/skills"Note: PowerShell accepts forward slashes and Join-Path normalizes them.
32-50: Remove unused variable assignment.Line 38 assigns
git mergeoutput to$mergeOutputbut never uses it. This variable can be removed to simplify the code.Apply this diff:
- $mergeOutput = git merge --ff-only '@{u}' 2>&1 + git merge --ff-only '@{u}' 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
- hooks/hooks.json(1 hunks)
- hooks/session-start.ps1(1 hunks)
- lib/initialize-skills.ps1(1 hunks)
🔇 Additional comments (8)
hooks/session-start.ps1 (3)
1-6: LGTM!The script setup correctly determines the plugin root directory using standard PowerShell patterns.
27-40: LGTM!The context building correctly uses PowerShell's here-string syntax to construct the multi-line payload with embedded content and optional warning.
42-51: LGTM!The JSON output structure correctly matches the expected hook format, and the depth parameter ensures proper serialization of nested objects.
hooks/hooks.json (2)
9-10: Verify the ExecutionPolicy Bypass security consideration.Using
-ExecutionPolicy Bypassallows unsigned PowerShell scripts to execute, which is necessary for the hook but bypasses Windows security policies. Ensure this aligns with your security requirements and consider documenting this behavior for users.
12-15: LGTM!The Unix/Linux command configuration correctly preserves existing functionality while explicitly defining platform constraints.
lib/initialize-skills.ps1 (3)
71-79: LGTM!The repository cloning logic correctly creates necessary directories and clones the skills repository.
101-102: LGTM!The script properly concludes with a success message and clean exit.
81-99: No changes needed—GitHub CLI flag is well-established.The
--remote=trueflag forgh repo forkhas been available since GitHub CLI v0.6 (released March 2020) and is widely supported across all modern installations. This code will work reliably with any reasonably current version of GitHub CLI.
| $legacySkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" | ||
| if (Test-Path $legacySkillsDir) { | ||
| $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read." | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify the path in the warning message for Windows users.
The warning message displays ~/.config/superpowers/skills, but Windows users may not be familiar with tilde notation. The actual path being checked uses $env:USERPROFILE\.config\superpowers\skills.
Apply this diff to use a Windows-friendly path format:
 $legacySkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills"
 if (Test-Path $legacySkillsDir) {
-    $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read."
+    $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in $legacySkillsDir will not be read."
 }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $legacySkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" | |
| if (Test-Path $legacySkillsDir) { | |
| $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read." | |
| $legacySkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" | |
| if (Test-Path $legacySkillsDir) { | |
| $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in $legacySkillsDir will not be read." | |
| } | 
🤖 Prompt for AI Agents
In hooks/session-start.ps1 around lines 9 to 11, the warning message shows the
Unix-style path (~/.config/superpowers/skills) which is unclear for Windows
users; change the message to display the actual Windows-friendly path being
checked (use $env:USERPROFILE\.config\superpowers\skills or the resolved
$legacySkillsDir) so users see the real location, and update the string
accordingly to interpolate or insert the resolved path instead of the tilde
notation.
| # Fetch from tracking remote if set, otherwise try upstream then origin | ||
| if ($trackingRemote) { | ||
| git fetch $trackingRemote 2>$null | Out-Null | ||
| } else { | ||
| git fetch upstream 2>$null | Out-Null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| git fetch origin 2>$null | Out-Null | ||
| } | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling consistency and verify origin fetch success.
The error handling patterns are inconsistent:
- Line 19: git fetch $trackingRemote 2>$null | Out-Null
- Lines 21, 23: git fetch upstream/origin 2>$null | Out-Null
Additionally, the fallback logic checks if upstream fetch fails (Line 22) but doesn't verify whether the origin fetch succeeds. If both fail, the script continues without confirmation that remote refs are available.
Apply this diff to verify the origin fetch and unify error suppression:
     # Fetch from tracking remote if set, otherwise try upstream then origin
     if ($trackingRemote) {
-        git fetch $trackingRemote 2>$null | Out-Null
+        git fetch $trackingRemote 2>&1 | Out-Null
     } else {
-        git fetch upstream 2>$null | Out-Null
+        git fetch upstream 2>&1 | Out-Null
         if ($LASTEXITCODE -ne 0) {
-            git fetch origin 2>$null | Out-Null
+            git fetch origin 2>&1 | Out-Null
+            if ($LASTEXITCODE -ne 0) {
+                Write-Host "Warning: Unable to fetch remote updates"
+            }
         }
     }🤖 Prompt for AI Agents
In lib/initialize-skills.ps1 around lines 17 to 25, the git fetch error handling
is inconsistent and the fallback doesn't verify origin succeeded; change all
fetch calls to consistently suppress output with "2>$null | Out-Null" and check
$LASTEXITCODE after each fetch: if $trackingRemote is set, run "git fetch
$trackingRemote 2>$null | Out-Null" and if it fails (check $LASTEXITCODE) exit
with a clear error; if no trackingRemote, attempt "git fetch upstream 2>$null |
Out-Null" and if that fails attempt "git fetch origin 2>$null | Out-Null" and if
origin also fails exit with an error message so the script never continues
without successful remote fetch.
| # Handle migration from old installation | ||
| $oldGitDir = Join-Path $env:USERPROFILE ".config\superpowers\.git" | ||
| if (Test-Path $oldGitDir) { | ||
| Write-Host "Found existing installation. Backing up..." | ||
| Move-Item $oldGitDir "$oldGitDir.bak" -Force -ErrorAction SilentlyContinue | ||
|  | ||
| $oldSkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" | ||
| if (Test-Path $oldSkillsDir) { | ||
| Move-Item $oldSkillsDir "$oldSkillsDir.bak" -Force -ErrorAction SilentlyContinue | ||
| Write-Host "Your old skills are in $env:USERPROFILE\.config\superpowers\skills.bak" | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent accidental backup overwrites.
Lines 62 and 66 use Move-Item -Force, which will silently overwrite existing .bak files if the initialization script runs multiple times. This could lead to loss of previous backups.
Consider one of these approaches:
- Check for existing backups before moving:
 if (Test-Path $oldGitDir) {
     Write-Host "Found existing installation. Backing up..."
+    $backupPath = "$oldGitDir.bak"
+    if (Test-Path $backupPath) {
+        Write-Host "Warning: Backup already exists at $backupPath"
+        $reply = Read-Host "Overwrite existing backup? (y/N)"
+        if ($reply -notmatch '^[Yy]$') {
+            Write-Host "Skipping backup of .git directory"
+            $backupPath = $null
+        }
+    }
+    if ($backupPath) {
+        Move-Item $oldGitDir $backupPath -Force -ErrorAction SilentlyContinue
+    }
-    Move-Item $oldGitDir "$oldGitDir.bak" -Force -ErrorAction SilentlyContinue- Or use timestamped backups:
-    Move-Item $oldGitDir "$oldGitDir.bak" -Force -ErrorAction SilentlyContinue
+    $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
+    Move-Item $oldGitDir "$oldGitDir.bak.$timestamp" -Force -ErrorAction SilentlyContinue🤖 Prompt for AI Agents
In lib/initialize-skills.ps1 around lines 58 to 68, the script uses Move-Item
-Force to rename existing .git and skills dirs to .bak which will overwrite
prior backups; change this to detect if the target .bak already exists and avoid
overwriting by either (a) if a .bak exists, append a timestamp (or incremental
suffix) to the backup name before moving, or (b) if a .bak exists, skip the move
and warn the user; remove -Force so Move-Item will not silently overwrite and
implement a small helper to generate a safe unique backup filename (e.g.,
basename + yyyyMMddHHmmss or + -1/-2) and use that when calling Move-Item.
| I'd love to hear a report or two from windows users on this PR | 
| @obra @simnJS This is not working for me under Windows 11, and I don't see anything in the claude code docs about platform for startup hooks? I think I actually saw some hallucinations (or at least, what I thought were hallucinations) about platform in my original attempt at this feature... On my machine, under Powershell and CMD, claude just opens the .sh file in my editor (because that's the explorer.exe default for .sh files). Made sure to npm update claude to the latest version, but no luck. | 
| On Windows I typically run Claude Code in git bash to avoid such problems. | 
| 
 It's really convenient for certain projects to be in a Windows native terminal and not git bash, but that does work. Doing so would also defeat the purpose of the windows platform support in this script 🤷 | 
| I agree having both supported would be nice. Supporting only powershell not so. | 
This adds native Windows support to the superpowers plugin by creating PowerShell equivalents of the bash scripts. Changes: - Add hooks/session-start.ps1 for Windows SessionStart hook - Add lib/initialize-skills.ps1 for Windows skills initialization - Update hooks/hooks.json to detect platform and use appropriate script - Windows (win32): uses PowerShell scripts - macOS/Linux (darwin/linux): uses existing bash scripts The PowerShell scripts provide the same functionality as the bash versions: - Load and inject skills context on session start - Clone and update the superpowers-skills repository - Handle legacy installation migration - Offer GitHub fork option when gh CLI is available Tested on Windows 10/11 with successful JSON output generation.
bff22cd    to
    48430ca      
    Compare
  
    | My bad, the first hook was vibe-coded and didn’t work correctly, this one is the right solution. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
hooks/session-start.ps1 (1)
9-11: The warning message still uses Unix-style path notation.As previously noted, Windows users may not recognize
~/.config/superpowers/skills. The actual path being checked is$env:USERPROFILE\.config\superpowers\skills.Apply this diff to use the resolved Windows path:
$legacySkillsDir = Join-Path $env:USERPROFILE ".config\superpowers\skills" if (Test-Path $legacySkillsDir) { - $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read." + $warningMessage = "`n`n⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in $legacySkillsDir will not be read." }
🧹 Nitpick comments (3)
hooks/session-start.ps1 (3)
15-15: Consider using path-separator-agnostic approach.While the current approach works, hardcoding backslashes is less idiomatic. PowerShell's
Join-Pathis designed to handle path separators cross-platform.Apply this diff for a more robust approach:
-$skillFile = Join-Path $pluginRoot "skills\using-superpowers\SKILL.md" +$skillFile = Join-Path (Join-Path (Join-Path $pluginRoot "skills") "using-superpowers") "SKILL.md"
23-23: Use consistent JSON output formatting.The error path (line 23) uses
-Compresswhile the success path (line 50) uses-Depth 10. For consistency and to prevent potential truncation in error messages, both should use the same flags.Apply this diff to make the error output consistent:
- $output | ConvertTo-Json -Compress + $output | ConvertTo-Json -Depth 10Also applies to: 50-50
29-40: Empty warning adds unnecessary blank lines.When the legacy skills directory doesn't exist,
$warningMessageis empty and line 38 will add extra blank lines to the output. Consider conditionally constructing the context.Here's a cleaner approach:
# Build the additional context +$contextParts = @( + "<EXTREMELY_IMPORTANT>" + "You have superpowers." + "" + "**The content below is from skills/using-superpowers/SKILL.md - your introduction to using skills:**" + "" + $usingSuperpowersContent +) + +if ($warningMessage) { + $contextParts += @("", $warningMessage) +} + +$contextParts += "</EXTREMELY_IMPORTANT>" + -$additionalContext = @" -<EXTREMELY_IMPORTANT> -You have superpowers. - -**The content below is from skills/using-superpowers/SKILL.md - your introduction to using skills:** - -$usingSuperpowersContent - -$warningMessage -</EXTREMELY_IMPORTANT> -"@ +$additionalContext = $contextParts -join "`n"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
- hooks/hooks.json(1 hunks)
- hooks/session-start.ps1(1 hunks)
- lib/initialize-skills.ps1(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/initialize-skills.ps1
- hooks/hooks.json
| @obra @joegoldin @dalito I tried direct PowerShell, direct bash, the conditional wrapper from @simnJS, running from Git Bash vs PowerShell, paths with/without spaces - all failed identically. The error "-v was unexpected at this time" is a CMD error, not bash. Even though Claude detects Git Bash correctly, hook execution appears to spawn Windows CMD internally, which can't parse bash  Conditional wrapper used: "command": "if command -v pwsh &>/dev/null || command -v powershell &>/dev/null; then powershell -ExecutionPolicy Bypass -File \"${CLAUDE_PLUGIN_ROOT}/scripts/inject-design-context.ps1\"; else bash \"${CLAUDE_PLUGIN_ROOT}/scripts/inject-design-context.sh\"; fi"Debug output:  | 
| 
 I didn't try everything the way @spxangelo did, but experienced the same thing | 
| for me, just changing the line in "C:\Users\username.claude\plugins\cache\superpowers\hooks\hooks.json" from 
 has it working fine as far as I can tell (claude will show me that the session start hook injected the prompt). I have git-bash installed on my system and it is in path and I suspect that most people running claude on windows will too. | 
| @dizzlkheinz Is that bash always going to be available on windows these days? That'd be a nice change | 
| @obra when you install git on windows it ?optionally installs bash for you 
 I'm pretty certain it is installed by default when you install git using winget   you can see here that it ends up as a profile in the windows terminal app as well So I think that most anyone who has claude installed on windows also has bash available as well and probably the easiest thing to do is just to have the hook call bash explicitly. You could just list bash on windows as a requirement in the readme. It seems to work for me at least. | 
This adds native Windows support to the superpowers plugin by creating PowerShell equivalents of the bash scripts.
Changes:
The PowerShell scripts provide the same functionality as the bash versions:
Tested on Windows 10/11 with successful JSON output generation.
Motivation and Context
Windows users were unable to use the superpowers plugin because it relied on bash scripts that cannot be executed natively on Windows. The SessionStart hook would fail, preventing the plugin from loading skills context.
How Has This Been Tested?
Breaking Changes
None. This is fully backward compatible:
Types of changes
Checklist
Additional context
PowerShell was chosen over batch files because:
Summary by CodeRabbit