agentleFS
jellydnjellydn/my-ai-tools/skills/portless-local/SKILL.md

portless-local

Skill127 starsChanged 28 days agoMITView on GitHub

Replace port numbers with stable named .localhost URLs for local development

  • Reads credentials
  • Installs packages

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 4 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read existing proxy configuration before starting an app: auto-start reuses saved settings, including LAN mode.

    line 24
  2. Distinctive:

    Never print or commit TLS private keys or ngrok tokens.

    line 30
  3. Very specific:

    If you skipped the trust prompt on first run, run portless trust to add the CA later.

    line 145
  4. Very specific:

    When HTTPS stays on, the first run may generate a local CA and server certificates.

    line 151
  5. Distinctive:

    Custom certificates: Use your own certs (e.g., from mkcert):

    line 153
  6. Very specific:

    Next.js: Add allowedDevOrigins: ['myapp.local', '*.myapp.local'] to next.config.js

    line 214
  7. Very specific:

    Expo / React Native: Add NSAllowsLocalNetworking to app.json for iOS ATS

    line 216
  8. Distinctive:

    Only run these after approval for the stated exposure or persistent system change:

    line 245
  9. Very specific:

    Put portless run in your package.json once and it works everywhere - no collisions, no --force.

    line 317
  10. Very specific:

    Default HTTPS: auto-generates a local CA.

    line 364
  11. Very specific:

    When sudo is unavailable, portless falls back to port 1355.

    line 460

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Portless - Named .localhost URLs

Replace port numbers with stable, named .localhost URLs for local development. For humans and agents.

Note: The portless CLI enables HTTPS on port 443 by default (https://myapp.localhost). Pass --no-tls to portless proxy start, or set PORTLESS_HTTPS=0, for plain HTTP. Keep HTTPS when the app needs OAuth, secure cookies, or HTTP/2.

Safety and Approval

Read existing proxy configuration before starting an app: auto-start reuses saved settings, including LAN mode. Outside LAN mode the proxy binds only to IPv4/IPv6 loopback; LAN mode binds all interfaces.

Ask for explicit approval before first-run sudo/elevation, CA trust-store changes, /etc/hosts writes, LAN exposure, Tailscale sharing, public Funnel/ngrok tunnels, startup-service installation/removal, or destructive cleanup. A request for local development does not authorize these actions. Use PORTLESS_SYNC_HOSTS=0 when hosts writes are not approved. In non-interactive environments, first-run prompts fail rather than granting permission; pre-start only an approved proxy configuration.

--force kills the existing process and takes over its route. prune terminates orphaned process groups, and prune --force uses SIGKILL. Do not use them without checking ownership and approval. clean also removes startup services and trust entries; it is not routine project cleanup.

Never print or commit TLS private keys or ngrok tokens. Let the user configure authentication through the provider's private credential workflow. Do not disable TLS verification to resolve proxy errors.

Why Portless?

Local dev with port numbers is fragile. Portless fixes that by giving each dev server a stable, named .localhost URL.

ProblemWith PortsWith Portless
Port conflictsTwo projects on :3000 = EADDRINUSEAuto-assigned ports, named URLs - no collisions
Memorizing ports"Was the API on 3001 or 8080?"Always https://api.localhost
Wrong app on refreshStop one server, start another on same port = confusionNamed URLs eliminate this
Monorepo chaosEvery service needs a unique portDistinct hostnames for each service
Agent confusionAI agents guess/hardcode wrong portshttps://myapp.localhost is deterministic
Cookie/storage clashesCookies bleed across ports on localhostEach .localhost subdomain gets its own scope
Hardcoded configCORS, OAuth, .env break when ports changeURLs are stable across restarts
Sharing URLs"What port is that on?" in SlackEveryone uses the same named URL
Browser historylocalhost:3000 history is a jumbleNamed URLs keep things organized

Installation

# Global (recommended)
npm install -g portless

# Or as a project dev dependency
npm install -D portless

Requires Node.js 24+ and OpenSSL for certificate generation. Install globally or as a project dependency; do not use a one-off npx/pnpm dlx download. A local package may be invoked through package scripts or npx portless without downloading it.

Usage

Invoke via skill command or use CLI directly:

# Via skill command
/portless-local <NAME> <COMMAND> [OPTIONS]

# Or use CLI directly
portless <NAME> <COMMAND> [OPTIONS]

Commands

Run an App

portless run [--name <name>] <cmd> [args...]   # Infers name from package.json, git root, or directory
portless <name> <cmd> [args...]                # Explicit name, no inference

portless run infers the project name from package.json, git root, or directory name. Use --name to override the inferred name while still applying worktree prefixes.

FlagDescription
--name <name>Override the inferred base name (worktree prefix still applies). Only for portless run.
--app-port <number>Use a fixed port for the app instead of auto-assignment. Also configurable via PORTLESS_APP_PORT.
--forceKill the existing process and take over its route; requires ownership checks and approval

Examples:

portless run next dev                    # Infer name from project
portless run --name myapp next dev       # Override inferred name
portless myapp next dev                  # Explicit name
portless api pnpm start                  # API service
portless docs.myapp next dev             # Subdomain

Get a Service URL

portless get <name>

Print the URL for a service. Useful for wiring services together in scripts or env vars:

BACKEND_URL=$(portless get backend)

Applies worktree prefix detection by default. Use --no-worktree to skip it.

Alias (Static Routes)

portless alias <name> <port>              # Register a static route
portless alias <name> <port> --force     # Force override existing
portless alias --remove <name>            # Remove the alias

Register a route for a service not managed by portless (e.g. a Docker container). Aliases persist across stale-route cleanup.

portless alias my-postgres 5432     # -> https://my-postgres.localhost
portless alias redis 6379           # -> https://redis.localhost
portless alias --remove my-postgres # Remove the alias

List Routes

portless list

Shows active routes and their assigned ports.

Trust the CA

portless trust

Adds the portless certificate authority to your system trust store. Required once for HTTPS with auto-generated certs.

If you skipped the trust prompt on first run, run portless trust to add the CA later.

HTTPS & HTTP/2

The CLI enables HTTPS on port 443 by default. That covers OAuth callbacks, secure cookies, and HTTP/2. Pass --no-tls or set PORTLESS_HTTPS=0 only when the app should stay on plain HTTP.

When HTTPS stays on, the first run may generate a local CA and server certificates. Run portless trust if the host or browser must trust that certificate.

Custom certificates: Use your own certs (e.g., from mkcert):

portless proxy start --cert ./cert.pem --key ./key.pem

Disable HTTPS: HTTPS on port 443 is the CLI default. portless proxy start --no-tls or PORTLESS_HTTPS=0 selects plain HTTP on port 80. Do not put --no-tls after the app command; portless forwards those arguments to the child process.

portless proxy start --no-tls
PORTLESS_HTTPS=0 portless myapp next dev

Clean Up

portless clean

Stops the proxy, removes the CA from OS trust store, deletes allowlisted files under ~/.portless, the system state directory, and removes the portless block from /etc/hosts. May prompt for elevated privileges.

Proxy Control

Start Proxy

portless proxy start
FlagDescription
-p, --port <number>Proxy port (default: 443, or 80 with --no-tls).
--no-tlsDisable HTTPS (plain HTTP on port 80).
--httpsEnable HTTPS (CLI default; kept for compatibility).
--lanEnable LAN mode (mDNS .local domains for real device testing)
--ip <address>Override auto-detected LAN IP (use with --lan)
--tld <tld>Use a custom TLD instead of .localhost (e.g. .test)
--cert <path>Custom TLS certificate
--key <path>Custom TLS private key
--foregroundRun in foreground instead of daemon mode

Stop Proxy

portless proxy stop

LAN Mode

Access services from phones and other devices on the same WiFi via mDNS (.local domains):

portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42   # Manual IP override

Make it permanent by adding export PORTLESS_LAN=1 to your shell profile. Portless also remembers LAN mode via proxy.lan, so a stopped LAN proxy starts in LAN mode again.

Framework notes for LAN:

  • Next.js: Add allowedDevOrigins: ['myapp.local', '*.myapp.local'] to next.config.js
  • Vite / React Router / SvelteKit / Astro: Handled automatically via __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS
  • Expo / React Native: Add NSAllowsLocalNetworking to app.json for iOS ATS

Hosts

portless hosts sync     # Add current routes to /etc/hosts
portless hosts clean    # Remove portless entries from /etc/hosts

Auto-sync is on by default. Set PORTLESS_SYNC_HOSTS=0 to disable.

Bypass Portless

PORTLESS=0 pnpm dev

Runs the command directly without the proxy.

Info

portless --help
portless --version
portless doctor    # Read-only diagnostics for proxy, DNS, trust, routes, and LAN prerequisites

Sharing and Startup Services

Only run these after approval for the stated exposure or persistent system change:

portless myapp --tailscale next dev   # Tailnet; requires connected Tailscale CLI and HTTPS certificates
portless myapp --funnel next dev      # Public internet; requires Funnel enabled for tailnet and node
portless myapp --ngrok next dev       # Public internet; requires authenticated ngrok CLI
portless service status              # Inspect installed service configuration
portless service install             # Persist proxy at OS startup; may require administrator privileges
portless service uninstall           # Remove startup service

Tailscale and ngrok registrations are removed when the app exits. PORTLESS_TAILSCALE, PORTLESS_FUNNEL, and PORTLESS_NGROK can enable sharing by default; inspect them before starting an app. Startup services save their options in launchd, systemd, or Task Scheduler, and may run as root or SYSTEM.

Common Use Cases

1. Basic Development Server

# Next.js
portless myapp next dev
# -> https://myapp.localhost

# Vite (auto-detected, --port injected)
portless myapp vite dev
# -> https://myapp.localhost

# Express
portless api node server.js
# -> https://api.localhost

2. Multiple Services with Subdomains

# API service
portless api.myapp pnpm start
# -> https://api.myapp.localhost

# Documentation
portless docs.myapp next dev
# -> https://docs.myapp.localhost

# Admin dashboard
portless admin.myapp npm run dev
# -> https://admin.myapp.localhost

3. Use in package.json

{
	"scripts": {
		"dev": "portless myapp next dev",
		"dev:http": "PORTLESS_HTTPS=0 portless myapp next dev"
	}
}

4. Git Worktree Support

portless run auto-detects git worktrees. The branch name is prepended as a subdomain:

# Main worktree
portless run next dev
# -> https://myapp.localhost

# Linked worktree on branch "fix-ui"
portless run next dev
# -> https://fix-ui.myapp.localhost

Put portless run in your package.json once and it works everywhere - no collisions, no --force.

5. Custom TLD

# Use .test TLD instead of .localhost
portless proxy start --tld test
portless myapp next dev
# -> https://myapp.test

Recommended TLDs:

  • .localhost - Default, auto-resolves to 127.0.0.1 in most browsers
  • .test - IANA-reserved, no collision risk (recommended)
  • Avoid: .local (conflicts with mDNS/Bonjour), .dev (Google-owned, forces HTTPS via HSTS)

6. Static Aliases for External Services

# Docker container running Postgres
portless alias my-postgres 5432
# -> https://my-postgres.localhost

# Redis server
portless alias redis 6379
# -> https://redis.localhost

7. Wire Services Together

# Get backend URL for frontend env
BACKEND_URL=$(portless get backend)
echo "VITE_API_URL=$BACKEND_URL" > .env.local
portless frontend vite dev

How It Works

Browser (myapp.localhost) -> HTTPS Proxy (port 443) -> App (random port 4000-4999)
  1. Portless runs an HTTPS reverse proxy on port 443 (or HTTP on port 80 with --no-tls)
  2. Each app registers a route mapping hostname to assigned port
  3. Requests to https://<name>.localhost are proxied to the app
  4. Default HTTPS: auto-generates a local CA. Run portless trust when the host or browser must trust it
  5. Auto-elevates with sudo on macOS/Linux for port binding

Framework Support

Portless auto-detects and configures:

FrameworkSupportNotes
Next.js✅ NativeRespects PORT env var
Express✅ NativeRespects PORT env var
Nuxt✅ NativeRespects PORT env var
Vite✅ InjectedAuto-adds --port flag
Astro✅ InjectedAuto-adds --port flag
React Router✅ InjectedAuto-adds --port flag
Angular✅ InjectedAuto-adds --port and --host
Expo✅ InjectedAuto-adds --port and --host
React Native✅ InjectedAuto-adds --port and --host

Configuration

Zero-config and Workspaces

Bare portless runs the package's dev script with an inferred name. An optional portless.json or package.json portless key can set name, script, appPort, and proxy (false for non-proxied tasks).

{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}

From a workspace root, portless discovers packages in pnpm-workspace.yaml or package.json workspaces and starts their dev scripts. Unlisted packages use inferred names. Use --script start for a different script. Readable turbo.json or turbo.jsonc preserves Turbo task ordering; root "turbo": false selects direct spawning.

Precedence: CLI flags > package.json portless key > portless.json app entry > defaults. To avoid recursion when dev runs portless, put the real command in dev:app and configure "portless": { "script": "dev:app" }.

Strict routing is the default. proxy start --wildcard allows unregistered child subdomains to fall back to the most specific registered parent. Multiple --tld flags (or comma-separated PORTLESS_TLD) support single- and multi-segment domains. Review these routing changes before use.

Environment Variables

VariableDescriptionDefault
PORTLESS_PORTProxy port443; 80 with --no-tls
PORTLESS_HTTPSSet to 0 to disable HTTPS (same as --no-tls)on
PORTLESS_LANSet to 1 to always enable LAN mode (mDNS .local domains)off
PORTLESS_TLDOne or more comma-separated TLDs (e.g. localhost,dev.example.com)localhost
PORTLESS_WILDCARDSet to 1 for parent-route fallbackoff
PORTLESS_LAN_IPPin a LAN IP instead of auto-detectionauto-detected
PORTLESS_TAILSCALEShare apps on the tailnet; requires approvaloff
PORTLESS_FUNNELShare apps publicly; requires approvaloff
PORTLESS_NGROKShare apps publicly through ngrok; requires approvaloff
PORTLESS_APP_PORTUse a fixed port for the app (skip auto-assignment)random 4000-4999
PORTLESS_SYNC_HOSTSSet to 0 to disable auto-sync of /etc/hostson
PORTLESS_STATE_DIROverride the state directorysee below
PORTLESSSet to 0 to bypass the proxyenabled

State Directory

Portless stores state (routes, PID file, port file, TLS marker) in ~/.portless. Under sudo, this remains the invoking user's home so apps and the proxy share registrations.

Override with PORTLESS_STATE_DIR.

State Files

FilePurpose
routes.jsonMaps hostnames to ports
routes.lockPrevents concurrent writes
proxy.pidPID of the running proxy
proxy.portPort the proxy is listening on
proxy.logProxy daemon log output
proxy.lanRemembers LAN mode and stores the last known LAN IP

Port Assignment

Apps get a random port in the 4000-4999 range. Portless sets PORT and usually HOST before running your command. Most frameworks respect PORT automatically. For frameworks that ignore it (Vite, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right --port flag and, when needed, a matching --host flag.

Injection applies to recognized server commands, including VitePlus, and can reach through simple package scripts. It skips build/test/check commands, compound shell commands, env prefixes, script delegation, option terminators, comments, and ambiguous runner flags. Those scripts must set their own port. Expo's --localhost, --lan, and --tunnel modes are preserved.

Portless sets NODE_EXTRA_CA_CERTS for child Node.js processes. For a separate process that must trust the local CA, use NODE_EXTRA_CA_CERTS=~/.portless/ca.pem rather than disabling certificate verification.

Troubleshooting

Port 443 permission denied

# Only after explicit approval for elevation:
sudo portless proxy start

# Or configure an approved unprivileged proxy before the app starts.
# Flags after the child command are forwarded to Next.js, which rejects --no-tls.
PORTLESS_HTTPS=0 PORTLESS_PORT=8080 portless myapp next dev

When sudo is unavailable, portless falls back to port 1355. Check the actual URL rather than assuming 443. Use portless doctor first for routing, DNS, or trust failures. For cross-app proxy loops, set changeOrigin: true in the forwarding proxy so the Host header matches the target route.

Certificate warning

Trust the local CA on first run. Run portless trust if needed.

Name collision

# Each worktree gets unique subdomain automatically
# Or use different names:
portless myapp-v2 next dev

Comparison

ToolTypeURLsUse Case
portlessLocal proxyhttps://myapp.localhostClean local dev URLs
ngrokPublic tunnelhttps://random.ngrok.ioShare with others
cloudflaredPublic tunnelhttps://myapp.trycloudflare.comShare with others

Related

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.