Skip to content

amoxideThe right aliases, at the right time

Like direnv, but for aliases. Define aliases per project, per toolchain, or globally — and load the right ones automatically.

amoxide logo

Install

sh
brew install sassman/tap/amoxide sassman/tap/amoxide-tui
sh
curl -fsSL https://github.com/sassman/amoxide-rs/releases/latest/download/amoxide-installer.sh | sh
curl -fsSL https://github.com/sassman/amoxide-rs/releases/latest/download/amoxide-tui-installer.sh | sh
powershell
powershell -ExecutionPolicy Bypass -c "irm https://github.com/sassman/amoxide-rs/releases/latest/download/amoxide-installer.ps1 | iex"
powershell -ExecutionPolicy Bypass -c "irm https://github.com/sassman/amoxide-rs/releases/latest/download/amoxide-tui-installer.ps1 | iex"
sh
cargo binstall amoxide amoxide-tui
sh
cargo install amoxide amoxide-tui

Why amoxide?

You know what a good alias system looks like — you've tried building one. A block in .zshrc, a few alias lines, maybe a Makefile. It works until the third project, or until a colleague asks why l runs something surprising on their machine.

The real problem is scope. Shell aliases are global by default. A shortcut that makes sense inside one project leaks into every terminal window. Cleaning it up means editing dotfiles, sourcing, checking. The alias you added for client A still fires in client B's directory six months later.

amoxide solves scope. Aliases can live in a project directory (.aliases file, auto-loaded on cd), in a named profile (activated explicitly with am use <name>), or globally. Each layer loads independently and unloads cleanly when it leaves scope. The TUI gives you a live map of what's active — no mental overhead, no guessing.

The subcommand alias system is where it gets interesting. Instead of a flat namespace full of cryptic prefixes, you define a routing scheme that makes sense to you: k get po expands to kubectl get pods, tab completion still works, and you chose the abbreviations.

amoxide subcommand alias showcase
Get Started →

Use Cases

01
Profiles

The Polyglot Developer

Two stacks. One set of aliases. Zero mental switching.

You work on a Node service at work and a Rust side project in the evenings. The context switch between them is small — same editor, same terminal, same workflow instincts. But the moment you need to lint or run tests, you have to stop and remember which world you're in. Is it npm run lint or cargo clippy? Is it npm run dev or cargo run?

The usual answer is prefixed aliases: nl for node-lint, rl for rust-lint. It works, but it means your hands never build real muscle memory. You're always thinking one step ahead — "which prefix applies here?" — instead of just typing. That small friction compounds across a day.

With two amoxide profiles you give the same short name to each task in both contexts. l is always lint. t is always test. b is always build. r is always run. After a few days it stops feeling like a tool and starts feeling like a habit. The profile switch — am use node or am use rust — is the one decision you make at the start of a session.

AliasNode profileRust profile
lnpm run lintcargo clippy --locked --all-targets -- -D warnings
tnpm testcargo test
bnpm run buildcargo build --release
rnpm run devcargo run

All profiles live in ~/.config/amoxide/profiles.toml — plain TOML, versionable alongside your dotfiles.

set up once
am profile add node
am add -p node l "npm run lint"
am add -p node t "npm test"
am add -p node b "npm run build"
am add -p node r "npm run dev"

am profile add rust
am add -p rust l "cargo clippy --locked --all-targets -- -D warnings"
am add -p rust t "cargo test"
am add -p rust b "cargo build --release"
am add -p rust r "cargo run"
every day after that
am use node
am: profile node activated — 4 loaded: l, b, r, t
l      # npm run lint
t      # npm test

am use rust
am: profile rust activated — 4 loaded: l, b, r, t
l      # cargo clippy --locked --all-targets -- -D warnings
t      # cargo test
02
Project aliases

The Shared Team Project

Clone the repo, get the shortcuts. No README, no onboarding doc.

Your team's repo has a Justfile with 30-odd recipes: CI checks, integration test runs, database migrations, staging deployments. New joiners don't know just exists. Veterans forget the recipe they haven't typed in three weeks. The command to run integration tests is cargo test --features integration -- --test-threads 1 and lives only in the CI config.

Everyone has their own shortcuts. Some run make, some run just, some copy-paste from Slack. The result is a team that's technically using the same codebase but working with completely different muscle memory. New people spend their first week asking "how do I run the tests?"

A .aliases file committed to the repo root fixes this for everyone. When a developer cds into the project, amoxide loads the file automatically. am ls shows every shortcut available — no asking, no guessing. When a command changes, one commit updates it for the whole team.

The .aliases file uses the same TOML format as profiles. Teams can also add subcommand aliases for complex invocations that would otherwise live only in someone's head.

add to the repo once
# in the project root
am add -l ti     "cargo test --features integration -- --test-threads 1"
am add -l ci     "just ci-check"
am add -l db     "just db-migrate --env staging"
am add -l deploy "just deploy --target production"

# commit it
git add .aliases && git commit -m "add project aliases"
every developer, automatically
cd ~/work/myproject
am: loaded .aliases
  ci     → just ci-check
  db     → just db-migrate --env staging
  deploy → just deploy --target production
  ti     → cargo test --features integration -- --test-threads 1

am ls
📁 project (.aliases)
  ├─ ci     → just ci-check
  ├─ db     → just db-migrate --env staging
  ├─ deploy → just deploy --target production
  ╰─ ti     → cargo test --features integration ...

ti      # cargo test --features integration -- --test-threads 1
03
Subcommand aliases

The Kubernetes Operator

Replace 120 flat aliases with a scheme you actually designed.

Oh-my-zsh ships over 120 kubectl aliases. kgp is get pods. kdp is describe pod. kgpw is get pods --watch. The scheme was designed by someone else, uses abbreviations you didn't choose, and can't be changed without forking the plugin. You either take all 120 or you take none.

And the aliases you actually need aren't in there. kubectl logs deployment/api --since=10m --tail=100 — the one you run every incident — has no alias. So you type it in full, or you add klogs-api to your .zshrc and forget it exists three weeks later, buried next to the other one-offs.

amoxide subcommand aliases let you define the scheme yourself. A base alias routes to a command, and short subcommand sequences expand to whatever flags make sense to you. k get po becomes kubectl get pods. k logs api becomes the exact invocation you always reach for. The whole thing lives in one place, is visible with am ls, and is yours to adjust without touching anyone else's config.

Subcommand aliases can live in a global profile (always available) or a project .aliases file (only active in that repo). Cluster-specific aliases belong in the project; daily kubectl verbs belong globally.

replace oh-my-zsh kubectl plugin
# base alias — k routes to kubectl
am add -g k kubectl

# subcommand routing
am add -g k:get:po   "get pods"
am add -g k:get:svc  "get svc"
am add -g k:desc:po  "describe pod"
am add -g k:logs:api "logs deployment/api --since=10m --tail=100"
am add -g k:rr      "rollout restart deployment"
then just type
k get po
NAME                   READY   STATUS    RESTARTS
api-7d4f9b8c6-xk2pm    1/1     Running   0

k logs api
2026-04-15T09:12:44Z INFO  server started on :8080

k rr api
deployment.apps/api restarted

# completions still work — press Tab after k get
04
Project aliases

The Freelancer Between Clients

Each client directory loads its own context. Nothing leaks between them.

You work with three clients. Each has a different cloud provider, a different staging URL, a different deployment pipeline, a different VPN. Your .zshrc has three commented-out blocks you toggle by hand when you switch context. The wrong deploy alias once pointed at the wrong staging environment for ten minutes before you noticed.

The deeper problem is that global aliases don't belong to anyone. They accumulate — old client shortcuts sitting next to current ones, some broken, some shadowing each other. Every few months you do a cleanup that takes an afternoon and still doesn't feel finished.

With amoxide, each client lives in its own directory. cd client-a/ loads their aliases automatically — their AWS shortcut, their deploy command, their staging URL opener. cd client-b/ unloads all of it and loads theirs. Nothing leaks between contexts. The .aliases file for each client can live in the project repo — versioned, auditable, shared with anyone else who works there.

Project aliases require a one-time am trust per repository — amoxide won't auto-load a file it hasn't seen before.

client-a/.aliases
# added once, committed to the repo
am add -l deploy  "./scripts/deploy.sh --env staging"
am add -l logs    "ssh app@client-a.internal journalctl -u api -f"
am add -l stage   "open https://staging.client-a.internal"
am add -l tf:plan "terraform plan -var-file=client-a.tfvars"
switching clients — nothing to remember
cd ~/clients/client-a
am: loaded .aliases
  deploy  → ./scripts/deploy.sh --env staging
  logs    → ssh app@client-a.internal journalctl -u api -f
  stage   → open https://staging.client-a.internal
  tf:plan → terraform plan -var-file=client-a.tfvars
deploy

cd ~/clients/client-b
am: loaded .aliases
  infra:plan → terraform -chdir=infra plan
  preview    → open https://preview.client-b.internal
  ship       → ./scripts/ship.sh
preview

# no stale aliases. no cross-contamination.
05
Profiles

The Great Dotfile Cleanup

Eight years of aliases. One afternoon to make sense of them.

Eight years of development, 200-odd aliases. gcm might be git commit -m or git checkout master depending on which year you wrote it. Some refer to projects you no longer work on. Some are broken because the tool they wrapped was renamed. alias | grep git returns 40 lines and you still can't find the one you want.

The only way to discover an alias is to remember you made it. The only way to clean them up is to scroll through a file you haven't fully read in years. You know this needs doing. You keep putting it off.

amoxide turns this into a tractable afternoon. Move aliases into named profiles — work, personal, git, rust, ops — each a plain TOML section you can read, annotate, and understand. am ls shows exactly what's active right now. Nothing loads unless you, or a project directory, asked for it. The 200-alias pile becomes five profiles you actually trust.

Profiles can be activated together: am use git work layers both, with later profiles taking precedence on name conflicts.

migrate from .zshrc
# create organised profiles
am profile add git
am add -p git gs      "git status --short"
am add -p git gp      "git push"
am add -p git gl      "git log --oneline --graph -20"
am add -p git gm      "git commit -m"

am profile add work
am add -p work vpn    "sudo openconnect vpn.company.com"
am add -p work jira   "open https://company.atlassian.net"
am add -p work standup "open https://meet.google.com/xyz"
what's actually loaded, right now
am use git work
am: profile git activated — 4 loaded: gl, gm, gp, gs
am: profile work activated — 3 loaded: jira, standup, vpn

am ls
├─● git (active)
│   ├─ gl     → git log --oneline --graph -20
│   ├─ gm     → git commit -m
│   ├─ gp     → git push
│   ╰─ gs     → git status --short
│
╰─● work (active)
    ├─ jira    → open https://company.atlassian.net
    ├─ standup → open https://meet.google.com/xyz
    ╰─ vpn     → sudo openconnect vpn.company.com

See It in Action

am tui screenshot

Interactive TUI — am tui

am ls screenshot

CLI Listing — am ls