The problem
Every time I clone a repo, I have to figure out how to run it.
- Go project?
go test ./...— unless it usesmake test - Node project?
npm test— unless it usespnpmoryarnorbun - Rust?
cargo test. Python? Could bepytest,python -m unittest, orpoetry run pytest.
This is fine for my own projects. But when I'm reviewing a PR, onboarding a new teammate, or setting up CI for a polyglot team, it becomes noise.
What I built
rex is a single Go binary that detects your project's stack and runs the right command. No config file. No reading the README.
git clone https://github.com/someone/unknown-project
cd unknown-project
rex test # it just works
How it works under the hood
The detection logic is surprisingly simple — and that's the point.
Step 1: File scanning
rex walks your project root and looks for known files:
| File | Stack | Package manager |
|---|---|---|
go.mod |
Go | go modules |
package.json + pnpm-lock.yaml
|
Node | pnpm |
Cargo.toml |
Rust | cargo |
pyproject.toml + uv.lock
|
Python | uv |
pom.xml |
Java | Maven |
build.gradle |
Java | Gradle |
Gemfile |
Ruby | bundler |
composer.json |
PHP | composer |
mix.exs |
Elixir | mix |
build.zig |
Zig | zig |
This happens in <50ms because it's just os.Stat calls — no network, no parsing heavy files.
Step 2: Command mapping
Once the stack is known, rex maps standard verbs to the right tool:
// Go
test -> "go test ./..."
run -> "go run ./cmd/server"
build -> "go build ./..."
// Node (pnpm)
test -> "pnpm test"
run -> "pnpm run dev"
build -> "pnpm run build"
Step 3: Priority chain (the smart part)
rex doesn't blindly guess. It respects what's already there:
1. Justfile / Makefile (task runner overrides everything)
2. package.json scripts / Cargo.toml / go.mod (ecosystem native)
3. Language heuristics (fallback)
If a Makefile defines a test target, rex test runs make test — even in a Go project.
The GitHub Action
The most interesting part is how this became a GitHub Action.
Instead of requiring Go to be installed in the runner, the action downloads the correct binary directly from the release page:
- uses: rexrun-dev/rex@v0.4.0
with:
command: test
This single step replaces 10+ lines of stack-specific YAML. Works for all 12 supported languages.
Why composite actions?
I used GitHub's composite action type instead of Docker. Why?
- Faster: no container startup
- Smaller: no image to pull
- Portable: works on any runner (ubuntu, macOS, Windows)
The trade-off? The install script has to handle OS/arch detection:
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
esac
URL="https://github.com/rexrun-dev/rex/releases/download/${VERSION}/rex_${VERSION#v}_${OS}_${ARCH}.tar.gz"
curl -sL "$URL" | tar xz -C /usr/local/bin rex
Watch mode
A recent addition: rex watch polls the filesystem and re-runs your command on change.
rex watch test # re-run tests when files change
rex watch build # re-build on change
It's not using inotify or fsnotify — just a lightweight polling loop with hash-based change detection. This makes it portable across all platforms without CGO or platform-specific dependencies.
CI generator
rex ci generates a GitHub Actions workflow file customized for your detected stack:
$ rex ci
✓ created .github/workflows/ci.yml (go project)
The generated YAML uses the official setup actions for each language (setup-go, setup-node, setup-python, etc.) and runs the correct test command.
Numbers
- Binary size: ~3.5 MB
- Startup time: <50ms
- Languages supported: 12
- Lines of Go: ~2,000
- Zero runtime dependencies
Try it
brew tap rexrun-dev/tap && brew install rex
# or
go install rexrun.dev/rex/cmd/rex@latest
Or try the interactive playground at rexrun.dev — paste any GitHub repo URL and see what rex would detect.
GitHub: github.com/rexrun-dev/rex
Marketplace: github.com/marketplace/actions/rex-universal-project-runner
Top comments (2)
"Works with any stack" is the hard design constraint hiding behind a simple-sounding goal - the moment you go stack-agnostic you can't assume a package manager, a test command, or a build step, so the interesting engineering is how you detect-or-declare without becoming a config nightmare. The two honest paths are convention (sniff the repo: lockfile present, framework markers, infer the commands) or declaration (make the user state their build/test commands once), and the best tools do convention-with-an-escape-hatch so it just works for the common case but doesn't trap the unusual one. Curious which way you leaned.
This is the same tension I deal with in Moonshift, the thing I build - a multi-agent pipeline that takes a prompt to a deployed SaaS on your own GitHub + Vercel - where "handle whatever the generated app needs" means encoding sensible defaults but verifying them against the actual project rather than assuming. Same spirit as a stack-agnostic Action: detect, don't assume, and verify before you run. Multi-model routing keeps a build ~$3 flat, first run's free no card. Nice work. How are you handling the detection misses - a manual override in the workflow yaml, or does it fail loud with a "couldn't detect, tell me your commands" message? The graceful-failure path is what makes "any stack" actually trustworthy.
Really good point , that’s exactly the hard part behind the “any stack” claim.
I leaned toward convention-first, but not magic-only. Rex tries to detect the project from real signals first: lockfiles, package manifests, framework markers, known command patterns, and common stack conventions. The goal is to make the common path feel zero-config, especially for repos where the intent is obvious but the README is long or outdated.
But I agree with you: the trustworthy version of stack-agnostic is not “guess and hope.” It has to be:
detect → explain what was detected → run only when confidence is good → fail loud when it is not.
So for detection misses, the intended behavior is a clear “I couldn’t safely detect this, tell me your command” path rather than silently choosing something risky. Manual overrides are the right escape hatch, especially in workflow YAML or project-level config, because unusual repos should not be punished for being unusual.
The design tension I’m trying to keep is: zero-config for the 80% case, explicit control for the 20% case, and no hidden assumptions in between.
Moonshift sounds like it deals with the same problem at a bigger surface area: generated apps, deployment, models, Vercel, GitHub, etc. “Detect, don’t assume, and verify before you run” is exactly the principle I think these tools need.