# AI coding agents write bash. Windows shells don't speak it.

## The problem

Claude Code, Cursor, Windsurf, all of them learned how to use a shell from the internet, and the internet runs on Linux. So when your agent needs to list files, it writes `ls -la`. It does not check what operating system you are on first.

On macOS or Linux this is fine. On Windows it breaks in an annoying way. PowerShell actually has an `ls` alias, so the command looks like it should work, but `-la` is not a valid PowerShell flag. The call fails. The agent reads the error, tries something else, maybe fails again. When it finally gets the files, the conversation now contains the failed command, the full error trace, and one or two retries.

That residue is the expensive part. Every token of it goes back into the context on every later turn. My rough estimate: one failed shell call with its retry loop costs around 45k tokens over a session, and agents on Windows hit this a few times per session. You are paying for errors you never asked for, and the error text keeps distracting the model long after the problem is over.

## What I built

ShellSage sits between the agent and the shell. Before a command executes, it checks whether the command is bash syntax aimed at a PowerShell shell, and if so it rewrites it. The agent keeps writing bash, the shell receives PowerShell, and neither of them knows anything happened.

It resolves a command in three steps:

1.  **Rule engine.** 165 regex patterns covering the common bash constructs, ordered from most specific to least specific so the first match wins. Instant, no database, works on cold start.
    
2.  **SQLite memory.** A local database with 428 curated translation pairs, plus whatever it learned from your own sessions. Lookup is BM25-style ranking over the stored commands.
    
3.  **Passthrough.** If nothing matches, the command goes through unchanged. Git, docker and npm commands are identical on every platform, so they always take this path.
    

Real output from the translator, with the target shell set to PowerShell:

```plaintext
$ ls -la
  -> Get-ChildItem -Force   [source=rules, conf=0.95]

$ find . -name '*.py'
  -> Get-ChildItem -Recurse -Filter '*.py'   [source=rules, conf=0.95]

$ grep -rn 'TODO' src/
  -> Get-ChildItem -Recurse 'src/' | Select-String -Pattern 'TODO' | Select-Object Path, LineNumber, Line

$ export NODE_ENV=production
  -> $env:NODE_ENV = 'production'   [source=rules, conf=0.95]

$ tail -f server.log
  -> Get-Content -Wait 'server.log'   [source=rules, conf=0.95]

$ git status
  -> git status   [source=passthrough]
```

The learning loop is the second half. After a translated command runs, a post-execution hook records whether it worked. A success gets stored with confidence 0.99, a failure gets stored as a pattern you can inspect with `shellsage replay`. So the 428 seeds are the floor, not the ceiling.

## Decisions I think are worth explaining

**Why regex rules first.** Regex sounds primitive next to a model, but it is instant, it has zero dependencies, and it works the moment you install the package before any database exists. Most of what agents actually type is a small set of commands with flags, which is exactly what regex is good at. The database only matters for the long tail.

**Why SQLite.** It is in the Python standard library, so there is nothing to install, no server, no Docker container, no API key. The whole memory lives in one file at `~/.shellsage/memory.db`. Local-first was a hard requirement for me: a tool that watches every shell command your agent runs should not be sending those commands anywhere.

**Honest failure instead of confident guessing.** Some bash has no real PowerShell equivalent. `chmod`, `sudo`, shebang lines, heredocs. Instead of producing a wrong translation, the rule engine returns a comment explaining what to do instead, like `# ShellSage: heredoc not supported in PowerShell - use @'...'@ here-string syntax`. Same principle for documentation links: translations come with a reference URL for the PowerShell cmdlet they used, but only for commands the tool actually knows. Unknown commands get no link rather than a fabricated one.

**Almost no dependencies.** The core package installs `click` and `rich` and nothing else. The MCP (Model Context Protocol) server support is an optional extra. Small install, fast cold start, little to break.

**Tests.** 91 tests covering the rules, the seed data and the models. CI runs them on Python 3.10, 3.11 and 3.12, plus ruff for lint and format, mypy for types, and a job that validates the seed corpus loads. I ran the suite again while writing this: 91 passed in about a fifth of a second.

## Setup and honest limits

Two commands:

```bash
pip install "shellsage[mcp] @ git+https://github.com/inamdarmihir/shellsage.git"
shellsage setup
```

The setup wizard detects which agent IDE you have (Claude Code, Cursor, Windsurf), seeds the local database, starts the background MCP server and registers it with your IDE. On Claude Code it can also install pre-execution hooks, which is the transparent option: translation happens before the shell ever sees the command. For any other MCP-compatible IDE there is a stdio mode.

One note on the install line: ShellSage is not on PyPI. The closest name there belongs to a different, unrelated project, so for now you install straight from the GitHub repo, which is also where the README's full translation reference table lives.

Limits, because every tool has them. ShellSage is a dictionary with a learning loop, not a model. It translates what it knows and passes through what it does not, so a weird compound command it has never seen will go to the shell as-is. On Linux and macOS it intentionally does nothing at all, because there the agent's bash is already correct. And it only fixes the shell problem. It will not stop your agent from making other kinds of mistakes.

If you run an AI coding agent on Windows and you have watched it burn a retry loop on `ls -la`, this is the fix I wanted, so I built it. Code, tests and the full command reference: https://github.com/inamdarmihir/shellsage
