Your projects already have commands. npm run dev, cargo run, make test, poetry run pytest, they're all there, just spelled differently every time.
samecmd reads whatever project you're in and gives you the same short names (dev, build, test, fmt, and more) everywhere.
cd ~/projects/web-app # Node/pnpm
dev # → pnpm run dev
cd ~/projects/api # Rust
dev # → cargo run
cd ~/projects/data-pipeline # Python/uv + Makefile
test # → make test
fmt # → make fmtNo config needed. Just cd and start typing.
- You jump between projects and keep mistyping commands.
testshould just work — whether the project uses Jest, pytest,cargo test, or a Makefile. - Your team keeps asking "how do I run this?" Drop a
samecmd.ymlin the repo. Everyone gets the same commands, no docs needed. - You're switching from yarn to pnpm, or Poetry to uv. The underlying tool changes —
devandtestdon't.
curl -fsSL https://raw.githubusercontent.com/behnamazimi/samecmd/main/install.sh | bashInstalls the prebuilt binary for your platform from the latest release to ~/.local/bin/samecmd. On Windows, run it from WSL or Git Bash; native cmd.exe/PowerShell can't run .sh scripts directly.
Tip
Installing somewhere else? Set SAMECMD_INSTALL_DIR first, e.g. SAMECMD_INSTALL_DIR=/usr/local/bin curl -fsSL .../install.sh | bash for a system-wide install. Pin a specific release with SAMECMD_VERSION=0.1.0 curl -fsSL .../install.sh | bash.
Requires Bun.
git clone https://github.com/behnamazimi/samecmd.git
cd samecmd
bun install
make installThis installs the binary to ~/.local/bin/samecmd. For a system-wide install, use sudo make install-system instead.
Whichever way you installed, wire it into your shell:
samecmd setupThis detects your shell (zsh or bash), adds the hook to your rc file, and tells you to reload it. Do this once — after that, commands activate automatically every time you cd.
Tip
Uninstalling later? No need to clone the repo:
curl -fsSL https://raw.githubusercontent.com/behnamazimi/samecmd/main/uninstall.sh | bashThis removes the shell hook, the binary, and ~/.config/samecmd — wherever it was installed. If you have a cloned copy of the repo, make uninstall does the same thing.
cd into a directory and samecmd looks for files it recognizes — package.json, Cargo.toml, go.mod, Makefile, and more — then maps the tool those files imply onto standard command names.
samecmd listsamecmd common: Node/pnpm (pnpm-lock.yaml)
dev pnpm run dev [common]
build pnpm run build [common]
test pnpm test [common]
lint pnpm run lint [common]
fmt pnpm run fmt [common]
typecheck pnpm run tsc [common]
For Node projects, it checks your actual package.json scripts, so a command only shows up if it really exists — and it recognizes near-misses too (a project with lint:format gets fmt working automatically).
Not sure why a command resolved the way it did? Run samecmd why for a full explanation.
Arguments after a command pass straight through to the underlying tool:
dev --host 0.0.0.0 --port 3000 # → pnpm run dev -- --host 0.0.0.0 --port 3000
test --watch # → pnpm test -- --watch
build --no-cache # → docker compose build --no-cacheAdd --dry-run to any command to print the resolved command instead of running it:
build --dry-run # → pnpm run build (printed, not executed)| Stack | Detected by |
|---|---|
| Make | Makefile |
| Node/pnpm | pnpm-lock.yaml |
| Node/yarn | yarn.lock |
| Node/bun | bun.lockb or bun.lock |
| Node/npm | package.json |
| Rust | Cargo.toml |
| Go | go.mod |
| Python/uv | uv.lock |
| Python/PDM | pdm.lock |
| Python/Poetry | poetry.lock |
| Python/Hatch | pyproject.toml with [tool.hatch] |
| Python/pip | setup.py |
| Gradle | gradlew, build.gradle, or build.gradle.kts |
| Maven | pom.xml |
| Docker | docker-compose.yml / compose.yaml |
If a directory matches more than one (say, a Makefile sitting on top of a Python project), the more specific tool wins — Makefile always takes priority.
Every stack maps onto the same short names, where they make sense for that ecosystem:
| Command | What it does |
|---|---|
dev |
Start the dev server / run the app |
build |
Compile or bundle for production |
test |
Run the test suite |
e2e |
Run end-to-end tests |
lint |
Check for code issues |
fmt |
Format code |
typecheck |
Type-check without building |
start |
Start in production mode |
clean |
Remove build artifacts |
migrate |
Run database migrations |
seed |
Seed the database |
docs |
Serve or build documentation |
samecmd also recognizes common near-misses, so graphql:codegen, test:e2e, type-check, db:migrate, and similar variants all resolve to the right canonical command automatically.
Auto-detection covers most cases. When it doesn't, add a samecmd.yml to the project (or any directory — it doesn't need to be a recognized project):
commands:
dev: "node --inspect server.js" # override a common command
deploy:
cmd: "fly deploy"
description: "Deploy to production"Anything here overrides the common command of the same name; new names are added alongside it.
Important
A samecmd.yml runs arbitrary shell and ships with the repo — so the first time you run a command from an untrusted one, samecmd shows you what it will run and asks first.
samecmd trust # review and approve everything in the current project
samecmd trust --yes # approve without the interactive prompt
samecmd distrust # stop trusting: ignore samecmd.yml, don't ask againEditing the file (e.g. after a git pull) re-triggers the prompt. Records live in ~/.config/samecmd/trust.json; common commands like npm test or cargo build are never gated.
If you'd rather have samecmd expose every package.json script or Makefile target — not just the canonical set — turn on expose_scripts:
samecmd config set expose_scripts trueRunning these custom commands/scrips will go through the same trust review before they run.
samecmd config list # show all settings
samecmd config get disabled # read a single setting
samecmd config set list_on_enter true # print commands on every cd
samecmd config set disabled true # pause samecmd without touching your shell config| Setting | Default | What it does |
|---|---|---|
disabled |
false |
Kill switch — disables samecmd entirely without touching your shell config |
list_on_enter |
false |
Print available commands whenever you enter a project |
expose_scripts |
false |
Expose scripts/targets outside the canonical set |
Some directories — a monorepo root, a scratch folder, home itself — shouldn't activate samecmd even if they look like a project:
samecmd ignore add # ignore the current directory
samecmd ignore add ~/scratch # ignore a specific path
samecmd ignore remove # unignore the current directory
samecmd ignore list # show everything ignoredA command isn't showing up. Run samecmd list to see what's available and samecmd why to see how it was resolved. Make sure the underlying tool is actually installed (which pnpm, which cargo, …).
Multiple lockfiles warning:
[samecmd] Warning: multiple lockfiles found (pnpm-lock.yaml, package-lock.json).
Using pnpm. Remove package-lock.json if you've fully migrated.
Delete the stale lockfile and the warning goes away.
