/portless
Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting
$ npx -y skills add vercel-labs/portless --skill portless --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/portless
Context preview
The summary Claude sees to decide when to auto-load this skill.
Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting
SKILL.md
portless.SKILL.mdname: portless
description: Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting port/proxy issues.
Portless
Replace port numbers with stable, named .localhost URLs. For humans and agents.
Why portless
- **Port conflicts**: `EADDRINUSE` when two projects default to the same port
- **Memorizing ports**: which app is on 3001 vs 8080?
- **Refreshing shows the wrong app**: stop one server, start another on the same port, stale tab shows wrong content
- **Monorepo multiplier**: every problem scales with each service in the repo
- **Agents test the wrong port**: AI agents guess or hardcode the wrong port
- **Cookie/storage clashes**: cookies on `localhost` bleed across apps; localStorage lost when ports shift
- **Hardcoded ports in config**: CORS allowlists, OAuth redirects, `.env` files break when ports change
- **Sharing URLs with teammates**: "what port is that on?" becomes a Slack question
- **Browser history is useless**: `localhost:3000` history is a mix of unrelated projects
Installation
Install globally (recommended) or as a project dev dependency. Do NOT use `npx` or `pnpm dlx` for one-off execution.
# Global (available everywhere)
npm install -g portless
# Or per-project dev dependency
npm install -D portless
When installed per-project, invoke via package.json scripts or `npx portless` (since the package is local, npx will not download anything).
Quick Start
# Install globally (or add -D to a project)
npm install -g portless
# Run your app (auto-starts the HTTPS proxy on port 443)
portless run next dev
# -> https://<project>.localhost
# Or with an explicit name
portless myapp next dev
# -> https://myapp.localhost
The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`. Auto-start reuses the configuration (port, TLS, TLDs) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars always take priority.
In non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting. Task runners like turborepo should pre-start the proxy.
Integration Patterns
Zero-config (recommended)
Bare `portless` works out of the box. It runs the `"dev"` script from `package.json` through the proxy, inferring the app name from the package name, git root, or directory:
portless # -> runs "dev" script, https://<project>.localhost
pnpm dev # -> works without portless, plain "next dev"
Use an optional `portless.json` to override defaults (name, script, port):
{ "name": "myapp" }portless # -> runs "dev" script, https://myapp.localhost
Monorepo
One `portless.json` at the repo root. Portless discovers packages from `pnpm-workspace.yaml`, or the `"workspaces"` field in `package.json` (npm, yarn, bun):
{
"apps": {
"apps/web": { "name": "myapp" },
"apps/api": { "name": "api.myapp" }
}
}portless # from repo root: start all packages with a "dev" script
cd apps/web && portless # start just one package
portless --script start # run "start" instead of "dev"
The `apps` map is optional and only provides name overrides. Unlisted packages auto-discover with inferred names.
Without an `apps` map, hostnames follow `<package>.<project>.localhost`. The project name comes from the most common npm scope (e.g. `@myorg/web` and `@myorg/api` produce `myorg`), falling back to the workspace root directory name. If a package's short name matches the project name, it uses the bare `<project>.localhost`.
Turborepo
For turborepo projects, use portless as the `dev` script with the real command in a separate script:
{
"scripts": { "dev": "portless", "dev:app": "next dev" },
"portless": { "name": "myapp", "script": "dev:app" }
}`pnpm dev` runs turbo, which runs `portless` in each package. Portless detects the package manager and runs `pnpm run dev:app` through the proxy.
package.json scripts
You can still use portless directly in scripts:
{
"scripts": {
"dev": "portless run next dev"
}
}The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`.
Multi-app setups with subdomains
portless myapp next dev # https://myapp.localhost
portless api.myapp pnpm start # https://api.myapp.localhost
portless docs.myapp next dev # https://docs.myapp.localhost
By default, only explicitly registered subdomains are routed (strict mode). Start the proxy with `--wildcard` to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app). Exact matches always take priority over wildcards.
Git worktrees
`portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain prefix so each worktree gets a unique URL:
# Main worktree (no prefix)
portless run next dev # -> https://myapp.localhost
# Linked worktree on branch "fix-ui"
portless run next dev # -> https://fix-ui.myapp.localhost
No config changes needed. Put `portless run` in `package.json` once and it works in all worktrees.
Bypassing portless
Set `PORTLESS=0` to run the command directly without the proxy:
PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
How It Works
1. `portless proxy start` starts an HTTPS reverse proxy on port 443 as a background daemon. Auto-elevates with sudo on macOS/Linux; falls back to port 1355 if sudo is unavailable. Use `--no-tls` for plain HTTP on port 80. Configurable with `-p` / `--port` or the `PORTLESS_PORT` env var. The proxy also auto-st
Read more
name: portless description: Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting port/proxy issues.
Portless
Replace port numbers with stable, named .localhost URLs. For humans and agents.
Why portless
- **Port conflicts**: `EADDRINUSE` when two projects default to the same port
- **Memorizing ports**: which app is on 3001 vs 8080?
- **Refreshing shows the wrong app**: stop one server, start another on the same port, stale tab shows wrong content
- **Monorepo multiplier**: every problem scales with each service in the repo
- **Agents test the wrong port**: AI agents guess or hardcode the wrong port
- **Cookie/storage clashes**: cookies on `localhost` bleed across apps; localStorage lost when ports shift
- **Hardcoded ports in config**: CORS allowlists, OAuth redirects, `.env` files break when ports change
- **Sharing URLs with teammates**: "what port is that on?" becomes a Slack question
- **Browser history is useless**: `localhost:3000` history is a mix of unrelated projects
Installation
Install globally (recommended) or as a project dev dependency. Do NOT use `npx` or `pnpm dlx` for one-off execution.
# Global (available everywhere) npm install -g portless # Or per-project dev dependency npm install -D portless
When installed per-project, invoke via package.json scripts or `npx portless` (since the package is local, npx will not download anything).
Quick Start
# Install globally (or add -D to a project) npm install -g portless # Run your app (auto-starts the HTTPS proxy on port 443) portless run next dev # -> https://<project>.localhost # Or with an explicit name portless myapp next dev # -> https://myapp.localhost
The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`. Auto-start reuses the configuration (port, TLS, TLDs) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars always take priority.
In non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting. Task runners like turborepo should pre-start the proxy.
Integration Patterns
Zero-config (recommended)
Bare `portless` works out of the box. It runs the `"dev"` script from `package.json` through the proxy, inferring the app name from the package name, git root, or directory:
portless # -> runs "dev" script, https://<project>.localhost pnpm dev # -> works without portless, plain "next dev"
Use an optional `portless.json` to override defaults (name, script, port):
{ "name": "myapp" }portless # -> runs "dev" script, https://myapp.localhost
Monorepo
One `portless.json` at the repo root. Portless discovers packages from `pnpm-workspace.yaml`, or the `"workspaces"` field in `package.json` (npm, yarn, bun):
{
"apps": {
"apps/web": { "name": "myapp" },
"apps/api": { "name": "api.myapp" }
}
}portless # from repo root: start all packages with a "dev" script cd apps/web && portless # start just one package portless --script start # run "start" instead of "dev"
The `apps` map is optional and only provides name overrides. Unlisted packages auto-discover with inferred names.
Without an `apps` map, hostnames follow `<package>.<project>.localhost`. The project name comes from the most common npm scope (e.g. `@myorg/web` and `@myorg/api` produce `myorg`), falling back to the workspace root directory name. If a package's short name matches the project name, it uses the bare `<project>.localhost`.
Turborepo
For turborepo projects, use portless as the `dev` script with the real command in a separate script:
{
"scripts": { "dev": "portless", "dev:app": "next dev" },
"portless": { "name": "myapp", "script": "dev:app" }
}`pnpm dev` runs turbo, which runs `portless` in each package. Portless detects the package manager and runs `pnpm run dev:app` through the proxy.
package.json scripts
You can still use portless directly in scripts:
{
"scripts": {
"dev": "portless run next dev"
}
}The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`.
Multi-app setups with subdomains
portless myapp next dev # https://myapp.localhost portless api.myapp pnpm start # https://api.myapp.localhost portless docs.myapp next dev # https://docs.myapp.localhost
By default, only explicitly registered subdomains are routed (strict mode). Start the proxy with `--wildcard` to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app). Exact matches always take priority over wildcards.
Git worktrees
`portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain prefix so each worktree gets a unique URL:
# Main worktree (no prefix) portless run next dev # -> https://myapp.localhost # Linked worktree on branch "fix-ui" portless run next dev # -> https://fix-ui.myapp.localhost
No config changes needed. Put `portless run` in `package.json` once and it works in all worktrees.
Bypassing portless
Set `PORTLESS=0` to run the command directly without the proxy:
PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
How It Works
1. `portless proxy start` starts an HTTPS reverse proxy on port 443 as a background daemon. Auto-elevates with sudo on macOS/Linux; falls back to port 1355 if sudo is unavailable. Use `--no-tls` for plain HTTP on port 80. Configurable with `-p` / `--port` or the `PORTLESS_PORT` env var. The proxy also auto-st
Replace port numbers with stable, named .localhost URLs for local development. For humans and agents.
Repo: vercel-labs/portless

