Read this line and look at its shape: bunx bun ./dist/server.ts. The thing doing the work is not the first word, because the first word is the runner and the tool is its argument, and the tool's own file is an argument of an argument, which is a strange sentence to read and a stranger one to write a hundred times a week. The idea that shapes how we build our toolchain is the opposite one, and it fits in a single sentence.
A tool should run with arguments, not be one.
The command line is a sentence, the tool is the verb, and the files and flags that follow are the objects, and every daily tool in our projects is installed so that it can hold the front of that sentence.
The Sentence You Read a Hundred Times
The command line is the interface we read more than any other, in scripts, in shell history, in documentation, in the notes a colleague left about debugging something at three in the morning, and a command that reads as a sentence is worth more than one that reads as a pile of flags, because the first word carries the intent. When you scan a script, your eye builds a map of what it does from the first word of every line, and a script whose lines begin with the actual tools is a map you can trust at a glance. tailwindcss -i ./css/app.css -o ./static/app.css --minify tells you in its first word what is happening and in the rest how it is done, reettier needs no explanation at all, conc -n img,tw,dev -c magenta,yellow,blue "bun prepare:images" "bun css:watch" "bun development" names the process runner and then the work it runs, and bun --hot --no-clear-screen server.ts --dev is just the runtime doing the thing the runtime is for. The runner-first shape has the opposite property, because bunx prettier foo.js is a sentence about bunx with the tool you actually meant buried mid-line like a noun in someone else's sentence, and we avoid that shape because we read these lines too often for the meaning to be hidden one level down, which is the same reason we wrote about scripting in general: a line that reads top to bottom is a line you can trust.
What Makes a Tool a Verb
None of this is magic, and the requirement is a single boring one, which is that the tool sits on the path, because a shell can only put a word at the front of a sentence if it knows that word, and a machine that has the tool installed knows it, while a machine that does not must route the request through a runner, and the moment a runner is involved the tool becomes its argument. This is why the get: commands in our projects install the daily tools once, globally, at versions we chose and recorded, the Tailwind CSS CLI at exactly 4.3.3, concurrently at a pinned version, reettier and reesql fetched from their releases, so that the verb exists at a known version on every machine in the team, because a tooling set like this is built for a known team with an agreed way of work, and the agreement is what keeps the sentence the same on every desk. The promotion is recorded as well, because a tool becomes a verb by appearing in a get: command, which means the list of get: commands is the visible history of the agreement, and the team can watch that history grow as a new tool earns its place in the set. When the version has to move, the move is an easy sync across the team in a regular call or a priority entry in the backlog, tested thoroughly by one person and decided by the one who carries the release, which is the arrangement we argued for in deciding is not a privilege. A machine that somehow misses a tool announces it loudly and immediately, which is the failure mode we prefer, because a missing verb is better than a silent substitution.
The Runner Still Has Its Place
None of this is an argument that the on-demand runner is wrong, because there is a class of tool for which being an argument is exactly right, which is the tool you meet occasionally rather than live with, since a verb you use once a year is not worth installing on every machine, and a tool you are trying before you commit to it should not be carved into the path on the strength of an afternoon. The shape is not fixed either, because a tool can earn its promotion, and the honest example is the typecheck, which ran for a while as bunx tsc --noEmit while it was still settling and now runs as plain tsc --noEmit, because a tool that keeps showing up eventually stops being an occasional guest, gets installed like the rest, and takes the front of the sentence it has earned. The boundary is frequency: the tool we run today, tomorrow, and next year deserves the front of the sentence, and the tool we meet occasionally can stay a noun, and both shapes are correct as long as the choice is deliberate rather than accidental.
A Verb on Every Desk
I started in the days when the tool was the first word of every command, because there was no runner to demote it, and the habit has aged well, because the test we apply to a daily tool is a simple one, which is whether it can hold the front of the sentence, and if it cannot, either it does not belong in the daily set or it has not been installed properly, and both of those are decisions rather than accidents. The applications and sites we build with Reepolee and reeweb are meant to be maintained for years, by people we will never meet, and for that kind of software the command line should read like a sentence written once and understood by everyone after, because the tools that work for you are the ones you stop noticing, and the first step to not noticing a tool is giving it the front of the sentence.