Skip to content

Commit 870efae

Browse files
authored
Merge pull request #36 from alex-feel/alex-feel-dev
Implement working PowerShell/CMD wrappers for claude-python command
2 parents af4569c + 618c3bc commit 870efae

File tree

6 files changed

+233
-45
lines changed

6 files changed

+233
-45
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ This automated setup includes:
3636
- 🔧 Comprehensive Python developer system prompt
3737
- 🚀 Convenience launchers for quick startup
3838

39-
**⚠️ IMPORTANT: After setup, use the simple command:**
39+
** After setup, use the simple command:**
4040
```bash
41-
claude-python
41+
claude-python # Works in all Windows shells (PowerShell, CMD, Git Bash)
4242
```
43-
That's it! The setup script registers this command globally.
43+
44+
The setup automatically creates properly escaped wrappers for each Windows shell, ensuring the Python developer system prompt loads correctly regardless of which shell you use.
4445

4546
---
4647

@@ -205,12 +206,14 @@ After running the Python setup script:
205206
claude doctor
206207

207208
# 2. Start Claude with Python configuration - just run:
208-
claude-python
209+
claude-python # Windows: Git Bash ONLY!
209210

210211
# That's it! The command is registered globally during setup
211212
```
212213

213-
**⚠️ Common Mistake:** Running `claude` directly won't load the Python system prompt! Always use `claude-python` command.
214+
**⚠️ Common Mistakes:**
215+
- Running `claude` directly won't load the Python system prompt!
216+
- On Windows: `claude-python` ONLY works in Git Bash, NOT in PowerShell or CMD!
214217

215218
For IDE integration:
216219
- **VS Code**: Configure terminal to use the launcher script

scripts/README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Complete Python development environment that installs:
126126
- /test (test generation)
127127
- **Python developer system prompt** with SOLID, DRY, KISS, YAGNI principles
128128
- **Context7 MCP server** for up-to-date library documentation
129-
- **Global `claude-python` command** that works in all terminals (PowerShell, CMD, Git Bash)
129+
- **Global `claude-python` command** (works in PowerShell, CMD, and Git Bash on Windows)
130130

131131
## 🔧 Script Options
132132

@@ -193,8 +193,8 @@ chmod +x setup-python-environment.sh
193193
- **Python Version**: Requires Python 3.12+ (automatically handled by uv)
194194
- **Package Manager**: Uses uv for fast, reliable Python management
195195
- **Windows**: PowerShell 5.1+ for bootstrap, full Windows 10/11 support
196-
- `claude-python` command works in PowerShell, CMD, and Git Bash
197-
- Automatic creation of both .cmd and bash wrappers
196+
- `claude-python` command works in **all Windows shells** (PowerShell, CMD, Git Bash)
197+
- Automated wrappers handle shell-specific escaping requirements
198198
- **Linux**: Bash 4.0+ for bootstrap, tested on Ubuntu, Debian, Fedora, Arch
199199
- **macOS**: Compatible with macOS 10.15+ (Catalina and later)
200200

@@ -205,10 +205,9 @@ chmod +x setup-python-environment.sh
205205
- **Intelligent Path Management**: Automatic PATH configuration
206206
- **Git Bash Detection**: Multiple detection strategies on Windows
207207
- **Node.js Management**: Automatic LTS installation if needed
208-
- **Cross-Terminal Support**: Windows commands work in all terminal types
209-
- Creates both .cmd files for PowerShell/CMD
210-
- Creates bash wrappers for Git Bash compatibility
211-
- Single `claude-python` command works everywhere
208+
- **Cross-Shell Support on Windows**: Automated wrappers for all shells
209+
- `claude-python` command works in PowerShell, CMD, and Git Bash
210+
- Each shell has a properly escaped wrapper for reliable operation
212211

213212
### Error Handling
214213

scripts/setup-python-environment.py

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def create_launcher_script(claude_user_dir: Path) -> Path | None:
330330

331331
try:
332332
if system == 'Windows':
333-
# PowerShell launcher
333+
# Create PowerShell launcher for Windows
334334
launcher_path = launcher_path.with_suffix('.ps1')
335335
launcher_content = '''# Claude Code Python Environment Launcher
336336
# This script starts Claude Code with the Python developer system prompt
@@ -346,51 +346,64 @@ def create_launcher_script(claude_user_dir: Path) -> Path | None:
346346
347347
Write-Host "Starting Claude Code with Python developer configuration..." -ForegroundColor Green
348348
349-
# Find Git Bash (it's required for Claude Code on Windows)
349+
# Find Git Bash (required for Claude Code on Windows)
350350
$bashPath = $null
351-
if ($env:CLAUDE_CODE_GIT_BASH_PATH) {
352-
$bashPath = $env:CLAUDE_CODE_GIT_BASH_PATH
353-
} elseif (Test-Path "C:\\Program Files\\Git\\bin\\bash.exe") {
351+
if (Test-Path "C:\\Program Files\\Git\\bin\\bash.exe") {
354352
$bashPath = "C:\\Program Files\\Git\\bin\\bash.exe"
355353
} elseif (Test-Path "C:\\Program Files (x86)\\Git\\bin\\bash.exe") {
356354
$bashPath = "C:\\Program Files (x86)\\Git\\bin\\bash.exe"
357355
} else {
358-
$bashCmd = Get-Command bash -ErrorAction SilentlyContinue
359-
if ($bashCmd) {
360-
$bashPath = $bashCmd.Source
361-
}
362-
}
363-
364-
if (-not $bashPath) {
365356
Write-Host "Error: Git Bash not found! Please install Git for Windows." -ForegroundColor Red
366357
exit 1
367358
}
368359
369-
# Convert Windows path to Unix path for bash
370-
$unixPromptPath = $promptPath -replace '\\\\', '/' -replace 'C:', '/c'
371-
372-
# Build the bash command with proper escaping
373-
# The inner quotes need to be escaped for bash -c
374-
$bashCommand = "claude --append-system-prompt \\`"\\$`(cat '$unixPromptPath'`)\\`""
360+
# Build the bash command with all arguments
375361
if ($args.Count -gt 0) {
376362
Write-Host "Passing additional arguments: $args" -ForegroundColor Cyan
377-
$bashCommand += " " + ($args -join " ")
363+
# When we have arguments, we can't use --% so we escape properly
364+
$argsString = ($args -join ' ')
365+
$bashCmd = "p=`$(tr -d '\\\\r' < ~/.claude/prompts/python-developer.md); "
366+
$bashCmd += "exec claude --append-system-prompt=\\`"`$p\\`" $argsString"
367+
$bashCommand = $bashCmd
368+
& $bashPath -lc $bashCommand
369+
} else {
370+
$cmd = "p=$(tr -d '\\\\r' < ~/.claude/prompts/python-developer.md); "
371+
$cmd += "exec claude --append-system-prompt=\"$p\""
372+
& $bashPath --% -lc "$cmd"
378373
}
379-
380-
Write-Host "Executing command via Git Bash..." -ForegroundColor Yellow
381-
382-
# Execute via Git Bash
383-
& $bashPath -c $bashCommand
384374
'''
385375
launcher_path.write_text(launcher_content)
386376

387-
# Also create a batch file wrapper
377+
# Also create a CMD batch file wrapper
388378
batch_path = claude_user_dir / 'start-python-claude.cmd'
389-
batch_content = f'@echo off\npowershell -NoProfile -ExecutionPolicy Bypass -File "{launcher_path}" %*'
379+
batch_content = '''@echo off
380+
REM Claude Code Python Environment Launcher for CMD
381+
REM This script starts Claude Code with the Python developer system prompt
382+
383+
set PROMPT_PATH=%USERPROFILE%\\.claude\\prompts\\python-developer.md
384+
385+
if not exist "%PROMPT_PATH%" (
386+
echo Error: Python developer prompt not found at %PROMPT_PATH%
387+
echo Please run setup-python-environment.py first
388+
exit /b 1
389+
)
390+
391+
echo Starting Claude Code with Python developer configuration...
392+
393+
REM Build the command with all arguments
394+
set BASH_EXE="C:\\Program Files\\Git\\bin\\bash.exe"
395+
set CMD_PREFIX=p=$(tr -d '\\r' ^< ~/.claude/prompts/python-developer.md)
396+
if "%~1"=="" (
397+
%BASH_EXE% -lc "%CMD_PREFIX%; exec claude --append-system-prompt=\\"$p\\""
398+
) else (
399+
echo Passing additional arguments: %*
400+
%BASH_EXE% -lc "%CMD_PREFIX%; exec claude --append-system-prompt=\\"$p\\" %*"
401+
)
402+
'''
390403
batch_path.write_text(batch_content)
391404

392405
else:
393-
# Bash launcher
406+
# Create bash launcher for Unix-like systems
394407
launcher_path = launcher_path.with_suffix('.sh')
395408
launcher_content = '''#!/usr/bin/env bash
396409
# Claude Code Python Environment Launcher
@@ -441,11 +454,29 @@ def register_global_command(launcher_path: Path) -> bool:
441454
local_bin = Path.home() / '.local' / 'bin'
442455
local_bin.mkdir(parents=True, exist_ok=True)
443456

457+
# Create wrappers for all Windows shells
458+
# CMD wrapper
444459
batch_path = local_bin / 'claude-python.cmd'
445-
batch_content = f'@echo off\npowershell -NoProfile -ExecutionPolicy Bypass -File "{launcher_path}" %*'
460+
batch_content = '''@echo off
461+
REM Global claude-python command for CMD
462+
set BASH_EXE="C:\\Program Files\\Git\\bin\\bash.exe"
463+
set CMD_PREFIX=p=$(tr -d '\\r' ^< ~/.claude/prompts/python-developer.md)
464+
if "%~1"=="" (
465+
%BASH_EXE% -lc "%CMD_PREFIX%; exec claude --append-system-prompt=\\"$p\\""
466+
) else (
467+
%BASH_EXE% -lc "%CMD_PREFIX%; exec claude --append-system-prompt=\\"$p\\" %*"
468+
)
469+
'''
446470
batch_path.write_text(batch_content)
447471

448-
# Also create a bash wrapper for Git Bash compatibility
472+
# PowerShell wrapper (as a simple forwarder to the PS1 launcher)
473+
ps1_wrapper_path = local_bin / 'claude-python.ps1'
474+
ps1_wrapper_content = f'''# Global claude-python command for PowerShell
475+
& "{launcher_path}" @args
476+
'''
477+
ps1_wrapper_path.write_text(ps1_wrapper_content)
478+
479+
# Git Bash wrapper
449480
bash_wrapper_path = local_bin / 'claude-python'
450481
bash_content = '''#!/bin/bash
451482
# Bash wrapper for claude-python to work in Git Bash
@@ -476,7 +507,7 @@ def register_global_command(launcher_path: Path) -> bool:
476507
# Make it executable (Git Bash respects this even on Windows)
477508
bash_wrapper_path.chmod(0o755)
478509

479-
info('Created both .cmd (for PowerShell/CMD) and bash wrapper (for Git Bash)')
510+
info('Created wrappers for all Windows shells (PowerShell, CMD, Git Bash)')
480511

481512
# Add .local/bin to PATH if not already there
482513
user_path = os.environ.get('PATH', '')
@@ -507,6 +538,7 @@ def register_global_command(launcher_path: Path) -> bool:
507538

508539
if system == 'Windows':
509540
success('Created global command: claude-python (works in PowerShell, CMD, and Git Bash)')
541+
info('The command now works in all Windows shells!')
510542
else:
511543
success('Created global command: claude-python')
512544
return True

system-prompts/README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,36 @@ As of Claude Code v1.0.51, the `--append-system-prompt` flag works in interactiv
4747

4848
```bash
4949
# Start Claude Code with a specific role
50+
# Git Bash/Linux/macOS:
5051
claude --append-system-prompt "$(cat system-prompts/examples/python-developer.md)"
5152

52-
# Or reference the file directly
53+
# Or reference the file directly (may not work on all systems)
5354
claude --append-system-prompt @system-prompts/examples/python-developer.md
55+
56+
# Windows (PowerShell/CMD) - use the automated setup script which creates working wrappers
5457
```
5558

59+
**Windows Users**: The `$(cat file)` syntax works in Git Bash. For PowerShell and CMD, use the automated setup which creates proper wrappers.
60+
5661
### Using System Prompts in Non-Interactive Mode
5762

5863
```bash
5964
# Execute a task with a specific system prompt
65+
# Git Bash/Linux/macOS:
6066
claude -p "Review this codebase for security issues" \
61-
--append-system-prompt @system-prompts/examples/python-developer.md
67+
--append-system-prompt "$(cat system-prompts/examples/python-developer.md)"
68+
69+
# Windows users: Use the claude-python command after running setup
6270

6371
# Combine with other flags
6472
claude -p "Optimize database queries" \
65-
--append-system-prompt @system-prompts/examples/python-developer.md \
73+
--append-system-prompt "$(cat system-prompts/examples/python-developer.md)" \
6674
--model opus \
6775
--max-turns 10
6876
```
6977

78+
**Note**: The `@file` syntax may not work reliably. Use `$(cat file)` for better compatibility.
79+
7080
## 📚 Available System Prompts
7181

7282
### Python Developer (`python-developer.md`)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
Here is a precise, end-to-end explanation of why the **CMD** one-liner works and what each part does:
2+
3+
```cmd
4+
"C:\Program Files\Git\bin\bash.exe" -lc "p=$(tr -d '\r' < ~/.claude/prompts/python-developer.md); exec claude --append-system-prompt=\"$p\""
5+
```
6+
7+
# 1) What CMD contributes
8+
9+
* **`"C:\Program Files\Git\bin\bash.exe"`**
10+
In `cmd.exe`, double quotes group a path with spaces into a single token, so the program launched is Git Bash. Inside a quoted argument for a Windows program, backslash-quote `\"` is the standard way to embed a literal double quote in the same argument, per the Microsoft C runtime command-line parsing rules that most Windows programs use. ([Microsoft Learn][1])
11+
12+
* **Everything after that becomes arguments to `bash.exe`**
13+
`-l` asks Bash to run as a login shell, and `-c "…"` tells Bash to execute the given command string. CMD passes that entire string as one argument because it is wrapped in double quotes. Also note that characters like `&` and `|` are special in CMD, which is why wrapping the `-c` payload in quotes is required to avoid CMD’s own metacharacter parsing. ([Microsoft Learn][2])
14+
15+
* **Command-line length caveat**
16+
CMD itself imposes about **8191** characters as the maximum command-line length. Your line stays far under that, so it executes reliably, but it is a useful limit to remember when you embed long data. The underlying Win32 `CreateProcess` API allows up to **32767** characters, however when you invoke via CMD you are bound by CMD’s lower limit. ([Microsoft Learn][3], [Microsoft for Developers][4])
17+
18+
# 2) What Bash does with `-lc "…"`
19+
20+
The string given to `-c` is parsed by Bash, not by CMD. Inside that string:
21+
22+
* **`~/.claude/prompts/python-developer.md`**
23+
Tilde expansion happens in Bash, `~` becomes the current user’s home. In Git Bash, home maps to your Windows profile directory and POSIX-style drive prefixing is used, for example `/c/Users/<name>`. ([GNU][5], [MSYS2][6])
24+
25+
* **`< file` input redirection**
26+
The `<` operator redirects the named file to the standard input of the command on its left. Here it feeds the prompt file into `tr`. ([GNU][7])
27+
28+
* **`tr -d '\r'`**
29+
`tr` is run from GNU coreutils. With `-d`, `tr` deletes every occurrence of the characters listed, so `'\r'` strips Windows carriage returns, leaving clean LF line endings. That prevents odd parsing issues when multi-line text later becomes a single shell argument. ([GNU][8])
30+
31+
* **`p=$( … )` command substitution**
32+
`$( … )` runs the command and substitutes its standard output. Bash captures all bytes from `tr` and assigns them to the variable `p`. Trailing newlines are removed, embedded newlines are preserved, which is exactly what you want for a multi-line system prompt. ([GNU][9])
33+
34+
* **`exec claude --append-system-prompt="$p"`**
35+
`exec` replaces the current Bash process with the `claude` CLI, so you do not keep an extra shell in the foreground. The value expansion is **double-quoted**, which is the critical part. In Bash, double quotes suppress word splitting and globbing, so the entire multi-line value in `p` is passed to `--append-system-prompt` as one argument, even if the first line begins with `---`. This avoids the classic “unknown option '---'” failure that happens when a multi-line value is accidentally split into separate argv tokens. ([GNU][10])
36+
37+
# 3) Why this pattern succeeds where others fail
38+
39+
* **CMD’s quoting and metacharacters are tricky**
40+
Without the outer double quotes, CMD would try to interpret `&`, `|`, `(`, and `)` in your Bash payload, which would corrupt the command. Quoting once at the CMD layer hands one opaque argument to `bash.exe`, and all further parsing is done by Bash, which is what you want here. ([Microsoft Learn][2])
41+
42+
* **The value is bound to the flag as a single argv element**
43+
The combination of Bash variable assignment, quoting, and `exec` ensures `claude` receives exactly two things for this feature, the flag name and one value string. Because the value is already the next argv item, the CLI parser treats it as data, not as another option, even if its first characters are `---`. The “single argument” property comes from Bash’s word-splitting rules, which say that expansions enclosed in double quotes are not split. ([GNU][11])
44+
45+
* **CRLF normalization prevents stray `\r` from leaking**
46+
CR characters in Windows-created files can sneak into an argument and confuse downstream parsers. Removing `\r` with `tr -d '\r'` is a simple, POSIX-friendly way to normalize the text before it becomes an argument. ([GNU][8])
47+
48+
# 4) Micro-timeline of execution
49+
50+
1. `cmd.exe` launches `bash.exe` with `-lc "<payload>"`. The quotes make the payload one argument, and embedded `\"` sequences survive as literal quotes inside that argument per the CRT argument rules. ([Microsoft Learn][1])
51+
2. Bash, as a login shell due to `-l`, executes the `-c` string. It expands `~`, redirects the file into `tr`, deletes `\r`, and captures the result in `p` via command substitution. ([GNU][5])
52+
3. Bash runs `exec claude --append-system-prompt="$p"`. Because `$p` is double-quoted, it is passed as one multi-line value. The shell process is replaced by the CLI, so the terminal is attached directly to `claude`. ([GNU][10])
53+
54+
That is the complete mechanics behind your working CMD line.
55+
56+
[1]: https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=msvc-170&utm_source=chatgpt.com "Parsing C command-line arguments"
57+
[2]: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd?utm_source=chatgpt.com "cmd"
58+
[3]: https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation?utm_source=chatgpt.com "Command prompt line string limitation - Windows Client"
59+
[4]: https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553&utm_source=chatgpt.com "What is the command line length limit? - The Old New Thing"
60+
[5]: https://www.gnu.org/s/bash/manual/html_node/Tilde-Expansion.html?utm_source=chatgpt.com "Tilde Expansion (Bash Reference Manual)"
61+
[6]: https://www.msys2.org/docs/filesystem-paths/?utm_source=chatgpt.com "Filesystem Paths"
62+
[7]: https://www.gnu.org/s/bash/manual/html_node/Redirections.html?utm_source=chatgpt.com "Redirections (Bash Reference Manual)"
63+
[8]: https://www.gnu.org/software/coreutils/manual/html_node/index.html?utm_source=chatgpt.com "Top (GNU Coreutils 9.7)"
64+
[9]: https://www.gnu.org/s/bash/manual/html_node/Command-Substitution.html?utm_source=chatgpt.com "Command Substitution (Bash Reference Manual)"
65+
[10]: https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html?utm_source=chatgpt.com "Shell Builtin Commands (Bash Reference Manual)"
66+
[11]: https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html?utm_source=chatgpt.com "Word Splitting (Bash Reference Manual)"

0 commit comments

Comments
 (0)