Companion to cli-tools.md and modern-tools.md. Those pick which tool; this picks which shell syntax to hand the user.
Portability note: the worked example below is the author's setup — Windows PowerShell 5.1 on Windows. It ships as one concrete instance of the pattern, not as universal law. Treat it as a template: if your interactive shell differs (zsh, fish, bash, pwsh 7, cmd), replace that example's table with your own shell's syntax. The pattern — detect the user's shell, hand them native commands — is the portable part.
Hand the user commands native to THEIR interactive shell — never assume bash. Bash is one shell, not the default of the universe. Before emitting a user-facing command, infer which shell the user pastes into and use that shell's syntax.
A command in the wrong shell syntax errors on paste (e.g. && is not a valid separator
in every shell) and is pure friction every time. Speaking the user's own shell avoids
that — the whole point of this rule.
How to tell which shell the user runs, in rough priority:
| Signal | Reads as |
|---|---|
The user says so (I'm on pwsh, use zsh) |
authoritative — use it |
| OS / platform (Windows → PowerShell or cmd; macOS/Linux → bash, zsh, fish) | strong default |
cwd style (X:\… / C:\Users\… → Windows; /home/…, /mnt/… → WSL/Linux) |
corroborating |
| Commands that already ran cleanly this session | corroborating |
When the signals conflict or you can't tell, ask once rather than guess.
This governs user-facing commands — what the user pastes into their terminal. The
assistant's own Bash tool runs Git Bash internally and may keep using bash for its own
execution; that's invisible to the user and fine.
When the work is explicitly inside WSL / a Linux shell — the user says so, the cwd
is a WSL path like /home/… or /mnt/…, or the task is clearly Linux — the user's
interactive shell there is bash, so hand them bash. This isn't a special case bolted
on; it's the same rule (speak their shell) applied to a shell that happens not to be the
host's default.
The author of this plugin pastes into Windows PowerShell 5.1. Generate against its
limits: no &&/|| chaining, no export, no \ line-continuation, no unix coreutils.
The table below is the bash → PowerShell translation that keeps paste-error friction to
zero for that one user. If your shell differs, replace this whole section with your own
table — see the portability note above.
| Don't give the user | Give instead |
|---|---|
export NAME=value |
$env:NAME = "value" |
cmd1 && cmd2 |
two separate lines (PS 5.1 has no &&). ; runs sequentially but does not stop on failure |
cmd1 \` continuation
| one line, or backtick ` continuation |
cd /x/Roam/BlockLab |
cd X:\Roam\BlockLab |
grep PAT file |
Select-String -Path file -Pattern 'PAT' |
jq '.x' f.json |
Get-Content f.json \| ConvertFrom-Json \| % { $_.x } |
cat / cut -d= -f2 |
Get-Content / .Split('=')[1] |
$(cmd) |
(cmd) (or $(cmd) works too, but (cmd) is idiomatic) |
VAR=x cmd (inline env) |
$env:VAR='x'; cmd |