Secure cross-platform command router. Type
v dev, runvite dev. Without shell aliases. Without surprises.
Shell aliases work, until they don't:
aliasrules differ between bash / zsh / fish / PowerShell. Sharing config across shells is painful.- Aliases run inside a shell —
alias g='git'is just a string macro. There's no guarantee that what you type actually maps to the binary you think it does. - They live in dotfiles. Move to a new machine, lose them. Switch shells, lose them.
Relay routes commands without invoking a shell. Each alias becomes a tiny launcher (a "shim") that calls relay's runner, which spawns the target program directly — no sh -c, no string evaluation, no surprises.
relay add v vite # register
v dev # runs `vite dev` — never goes through a shell
v build # runs `vite build`Same syntax on Linux, macOS, and Windows. Same config file. Same behaviour.
Pick the method that fits your workflow — no Node.js required for Homebrew or the shell installer.
brew tap ffgenius/tap
brew install relay
relay initHomebrew installs the binary and keeps it up to date with brew upgrade.
curl -fsSL https://raw.githubusercontent.com/ffgenius/relay/master/install.sh | shOr with wget:
wget -qO- https://raw.githubusercontent.com/ffgenius/relay/master/install.sh | shThe script detects your OS and architecture, downloads the right binary from
GitHub Releases, installs it to ~/.relay/bin/, and runs relay init for
automatic shell integration (bash / zsh / fish).
Options: --version 0.1.0 to pin a version; --no-init to skip
shell-profile changes.
irm https://raw.githubusercontent.com/ffgenius/relay/master/install.ps1 | iexSame behaviour as the shell installer: downloads the right binary, installs to
~\.relay\bin\, and adds it to your user PATH via the registry.
Options: -Version 0.1.0 to pin a version; -NoInit to skip PATH setup.
npm install -g @ffgenius/relayThe npm package selects the right binary for your platform automatically
(linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64,
win32-arm64).
All methods above run relay init for you (unless you opt out). This creates
~/.relay/ and adds ~/.relay/bin to your PATH.
Open a new terminal for the PATH change to take effect — then you're ready to go.
# Register a prefix alias — `v <anything>` runs `vite <anything>`.
relay add v vite
# Register an exact alias — `vd` always runs `vite dev`, no arguments.
relay add vd vite dev
# Use them.
v dev # → vite dev
v build # → vite build
vd # → vite dev
# Inspect.
relay list # all aliases (also: relay ls)
relay info v # details for one alias
relay discover vite # aliases grouped by target program
# Store and run shell snippets (with cross-shell auto-translation).
relay snippet add goback "cd ../"
relay snippet run goback --dry-run
# Diagnose.
relay doctor # check PATH, shims, config
relay doctor --fix # auto-repair missing shims and PATH entriesRelay has three kinds of items:
relay add <name> <program> — every argument you type after <name> is forwarded.
relay add v vite
v dev # → vite dev
v build # → vite build
v --help # → vite --helprelay add <name> <program> <args...> — the arguments are baked in; runtime args are ignored.
relay add vd vite dev
vd # → vite dev (always)Use prefix for tools you call with many subcommands (v, g, n). Use exact for one-liners you run all the time (vd, gp, nci).
relay snippet add <name> <content...> — store an arbitrary shell code fragment. Unlike regular aliases (which bypass the shell), snippets are executed through a shell interpreter and support automatic cross-shell translation via polysh.
# Create a snippet — relay auto-detects your current shell.
relay snippet add goback "cd ../"
# Run it — if your current shell differs from the one it was written in,
# relay translates the command automatically (Unix ↔ PowerShell ↔ CMD).
relay snippet run goback
# Preview the translated command without executing.
relay snippet run goback --dry-runWhy snippets? Commands like cd, export, complex pipes, and shell built-ins can't work through relay's direct-execution model. Snippets fill that gap while keeping cross-shell portability.
| Command | Description |
|---|---|
relay init |
Create ~/.relay, write empty config, add ~/.relay/bin to PATH |
relay add <name> <program> [args...] |
Register an alias (prefix if no args, exact otherwise) |
relay remove <name> (alias: rm) |
Delete an alias |
relay update <name> <program> [args...] |
Replace an existing alias |
relay list (alias: ls) |
List all aliases by name |
relay info <name> |
Show details for one alias |
relay clear (alias: cls) |
Remove every alias (asks for confirmation) |
relay clear --yes |
Same, no confirmation |
| Command | Description |
|---|---|
relay discover |
Group aliases by their target program |
relay discover <program> |
Show all aliases targeting <program> |
| Command | Description |
|---|---|
relay export |
Print config to stdout (YAML). Includes snippets by default |
relay export -o <file> |
Write to file (.yaml auto-appended if missing) |
relay export --no-snippet |
Export only commands, exclude snippets |
relay import <file> |
Merge another config. Snippets are skipped by default for security |
relay import <file> --overwrite |
Merge, overwriting conflicting aliases |
relay import <file> --allow-snippet |
Also import snippets from the file |
relay sync init |
Create a private GitHub Gist and link this machine to it |
relay sync link <gist_id> |
Link this machine to an existing Gist |
relay sync unlink |
Forget the linked Gist on this machine (remote Gist is kept) |
relay sync push |
Upload local config (commands + snippets) to the linked Gist |
relay sync push --no-snippet |
Upload only commands, exclude snippets |
relay sync pull |
Download config from the Gist. Snippets skipped by default |
relay sync pull --allow-snippet |
Download and include snippets |
relay sync status |
Show sync status, command and snippet counts |
| Command | Description |
|---|---|
relay snippet add <name> <content...> |
Create a snippet (auto-detects current shell) |
relay snippet add <name> <content...> --shell <d> |
Create with explicit shell dialect (unix, powershell, cmd) |
relay snippet add <name> <content...> --desc <d> |
Create with a description |
relay snippet remove <name> (alias: rm) |
Delete a snippet |
relay snippet list (alias: ls) |
List all snippets |
relay snippet info <name> |
Show full details of one snippet |
relay snippet edit <name> --content <c> |
Update a snippet's content |
relay snippet edit <name> --desc <d> |
Update description (pass "" to clear) |
relay snippet edit <name> --shell <d> |
Change the shell dialect |
relay snippet run <name> |
Execute a snippet (auto-translates to current shell) |
relay snippet run <name> --dry-run |
Print the translated command without executing |
relay snippet run <name> --no-translate |
Run as-is, skip cross-shell translation |
relay snippet clear |
Remove all snippets (asks for confirmation) |
relay snippet clear --yes |
Same, no confirmation |
| Command | Description |
|---|---|
relay doctor |
Validate PATH, shims, config |
relay doctor --fix |
Re-generate missing shims and auto-add PATH entries |
relay rebuild |
Full reset: regenerate every shim from the current config |
Relay syncs to a private GitHub Gist through your existing gh CLI session. No new tokens to manage.
On your first machine:
gh auth login # if you haven't already
relay add v vite # register a few aliases
relay add g git
relay sync init # → creates a Gist, prints its IDOn your second machine:
gh auth login
relay sync link <gist_id> # the ID from `sync init` above
relay sync pull # downloads the aliases, regenerates shimsDay-to-day:
relay add p pnpm # add a new alias on machine A
relay sync push # upload the change
# ...later, on machine B:
relay sync pull # pull the changerelay sync status shows whether your local config is in sync with the Gist; pull warns before overwriting un-pushed local changes.
Relay's whole point is to be safe by construction — running v dev should be boringly equivalent to running vite dev directly. The four principles below are enforced at code level:
Principle 1 — Relay does not execute a shell (except for snippets). Regular aliases use
std::process::Commandto spawn the target binary directly — nosh -c, nocmd /c, nopowershell -Command. Snippets are the deliberate exception: since they are shell code by nature, they run through a shell interpreter. This is why import/pull require--allow-snippet— snippets are opt-in trusted code.
Principle 2 — Relay does not execute strings. An alias is a
(program, args)tuple. There is noexec: "vite dev && rm -rf /"field. Strings as commands are not a representable state.
Principle 3 — Relay only executes registered binaries that exist.
relay addcallswhich(<program>)and refuses to register anything that isn't onPATH. The path separator (/,\) is also rejected — only bare command names allowed — so a malicious gist can't sneak/tmp/evil-cargointo your config viarelay sync pull.
Principle 4 — Shells are on the blocklist.
sh,bash,zsh,cmd,powershell,pwshcannot be registered as the target of an alias. Even if you tryrelay add x sh, it's refused.
These rules also mean Relay can never be the attack — there's no shell escape in the path between your shim and your binary.
Everything lives in ~/.relay/:
~/.relay/
├── config.yaml # registered aliases + snippets
├── sync-state.yaml # (optional) linked Gist ID + sync hash
└── bin/ # generated shims; this dir goes on PATH
├── v # or v.cmd on Windows
├── vd
└── ...
config.yaml is intentionally readable and hand-editable (re-run relay rebuild after manual edits):
version: 1
commands:
v:
type: prefix
program: vite
vd:
type: exact
program: vite
args:
- dev
snippets:
goback:
type: snippet
content: "cd ../"
shell: unix
serve:
type: snippet
content: "python3 -m http.server 8080"
shell: unix
description: "start a local file server"Your shell hasn't picked up the new PATH yet. Run:
relay doctorIf it says shim dir is NOT on PATH, run relay doctor --fix and then open a new terminal.
On Windows, the registry write may not propagate to existing cmd windows until you log out and back in. Open a fresh terminal first; if it's still wrong, run relay doctor from the fresh terminal.
Windows Defender / SmartScreen is scanning the newly-installed relay.exe on its first run. The scan releases the file within a second or two — just re-run the same command. This only happens once per install.
Windows truncates the combined PATH at process creation if it exceeds ~2047 characters. relay init writes the shim directory to the front of the user PATH to dodge truncation, but a heavily-loaded PATH can still hide entries. relay doctor warns when the user PATH exceeds 1900 characters.
Run gh auth login once. Relay piggybacks on your GitHub CLI session — it never asks for tokens directly.
git clone https://github.com/ffgenius/relay
cd relay
cargo build
cargo testIssues and PRs welcome.
MIT © Bin