Portless refuses to touch eight shapes of command, and says why for each

1 month ago

Everything you build on your own computer answers at a number. You never remember which is which. This is portless. You put one word in front of the command that starts your project, and the project answers at a name with a padlock instead. That number is a port number: your computer has thousands of numbered slots, and every program picks one to answer on.

Ask

Ask about this presentation

Answers are generated from this presentation.

Chapters

  1. 0:00portless · a name instead of a number
  2. 0:19You choose the name when you start the app
  3. 0:33Handing your app a name sounds like it needs setup
  4. 0:47Portless takes the secure door and writes its own certificate
  5. 1:16Your app still gets a number, and you never have to see it
  6. 1:36Portless recognises eight command names
  7. 2:03Portless refuses to touch eight shapes of command
  8. 2:37One line installs it · v0.15.6
  9. 2:51Signs its own certificate · refuses eight shapes
Show transcript

portless · a name instead of a number

- "dev": "next dev" # http://localhost:3000
+ "dev": "portless run next dev" # https://myapp.localhost

portless

your local app answers at a name instead of a number
TypeScriptApache 2.0macOS, Linux, Windows
vercel-labs/portless · README.md L5-8 · v0.15.6

Everything you build on your own computer answers at a number. You never remember which is which. This is portless. You put one word in front of the command that starts your project, and the project answers at a name with a padlock instead. That number is a port number: your computer has thousands of numbered slots, and every program picks one to answer on.

You choose the name when you start the app

portless myapp next dev
# -> https://myapp.localhost
https://myapp.localhost
the name is the one you typed, and it is the address
README.md L27-31

You type portless, then a name you choose, then the command you already run. The copy of the site you are building starts up, and portless hands you that name, dot localhost, with a padlock on it. Your browser trusts that padlock, so the page loads clean.

Handing your app a name sounds like it needs setup

myapp.localhost
?
browsers already send anything ending dot localhost back to your own machine
packages/portless/src/cli.ts L1827

So how does your computer know where myapp dot localhost is? It already does. Any address ending in dot localhost goes straight back to your own machine. Portless never teaches your computer that name. What is left is answering the door.

Portless takes the secure door and writes its own certificate

your browser 443 the door portless
openssl ecparam -genkey -name prime256v1 \
-noout -out ca-key.pem
openssl req -new -x509 -sha256 \
-key ca-key.pem -out ca.pem \
-days 3650 \
-subj "/CN=portless Local CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
ten years, signed in its own name
key 600 · certificate 644
installed where the browser looks: the macOS keychain · four Linux families · Windows · and both stores when Linux runs inside Windows
taking port 443 needs your password on macOS and Linux
certs.ts L286-306, L307-308 · certs.ts L546-563, L908-951 · cli-utils.ts L264 · cli.ts L752, L1088

Your machine has one door that every secure website on the internet uses, and portless takes that door. That is port four four three. A browser only shows a padlock when the certificate comes from an outfit it already trusts. So portless makes itself into one of those, on your machine only. Two commands: a key, and a certificate good for ten years that portless signs in its own name. Then portless installs it where the browser looks, on macOS, Linux and Windows. Taking that door needs your password, and portless asks each time it starts.

Your app still gets a number, and you never have to see it

your browser 443 the door :4123 example :4567 example
a free number in 4000 to 4999
PORT
the note that says which number to use
cli-utils.ts L67, L70, L838 · cli.ts L1825 · README.md L35

Think of a building with one front door, and rooms behind it with numbers visitors never learn. Portless picks a free number between four thousand and four thousand nine hundred ninety nine, and starts your app on it. Portless hands that number over in an environment variable. That is just a note the program starting your app leaves for it to read. Then the door forwards your name to that room.

Portless recognises eight command names

Portless recognises eight command names. Its own README lists seven.

vite
Vite
vp
VitePlus
react-router
React Router
rsbuild
Rsbuild
astro
Astro
ng
Angular
react-native
React Native
expo
Expo
Rsbuild is the one the README’s prose leaves out
astro: {
strictPort: false,
serverSubcommands: ["dev", "preview"],
defaultIsServer: false,
},
a command that does not serve gets nothing: vite build · vite optimize · vp test · astro check
cli-utils.ts L1227-1294 · README.md L35

Most tools that build websites read that note and use the number. A few ignore it. So portless keeps a list of eight command names: Vite, VitePlus, React Router, Rsbuild, Astro, Angular, React Native, Expo. Those are frameworks, the toolkits a website gets built with. For those eight, portless puts the port into the command itself. Each entry also says which of that tool's own commands actually start a server. Ask one to build instead of to serve and portless adds nothing.

Portless refuses to touch eight shapes of command

Portless refuses to touch eight shapes of command, and says why for each

✗;starts a new command
✗|starts a new command
✗a new linestarts a new command
✗&starts a new command
✗# at the start of a wordthe shell throws away everything after it
✗the script’s own --everything after it arrives as data, not as settings
✗NODE_ENV=production vitethe first word is not a name portless knows
✗npm run dev:viteit hands off to another script, so there is nothing here to edit
the appended text would land on the wrong command
the appended text is thrown away
and these three are let through: '/foo&bar' · 2>&1 · vite --tag v1#2
a quoted ampersand, a redirection, and a hash that does not start a word
cli-utils.ts L1527-1582 · cli-utils.ts L1631-1637 · cli-utils.ts L1610-1611, L1329-1337

This part is clever. Portless adds text to a command you already wrote, and the shell, the program that runs what you type, reads that command again. So there is a scanner in here that walks your command one character at a time, tracking quotation marks and backslashes, to work out when to leave your command alone. A semicolon, a vertical bar, an ampersand or a new line each start a second command, so the added text would land on the wrong one. A hash mark at the start of a word opens a comment, and the shell throws away the rest. And an ampersand inside quotation marks is just your text, so it goes through. Somebody wrote that by hand.

One line installs it · v0.15.6

One line installs it, and the version today is 0.15.6

npm install -g portless
0.15.6
Apache 2.0
Node 24 or newer
macOS, Linux, Windows
the project calls itself pre 1.0, and says the way it stores its settings may change between releases
registry.npmjs.org/portless, pulled 2026-09-03 · packages/portless/package.json at v0.15.6 · README.md L24

One line installs portless, from npm, where developers publish their tools. The licence is Apache two point oh, so portless is free to use at work. The project says it is still pre one point oh, so the way it stores settings may change.

Signs its own certificate · refuses eight shapes

Portless signs its own certificate and refuses eight shapes of command
github.com/vercel-labs/portless
TypeScript · Apache 2.0 · 0.15.6

So: one word in front of the command you already run, a name that carries a padlock, and a scanner that decides when to leave your command alone. That is vercel dash labs slash portless, on GitHub. New repo tomorrow.