Portless dev setup
Skill emaarco/hogwarts/plugins/felix-felicis/skills/portless-dev-setup
A magical place where my skills, rules, and plugins for AI agents are defined, which magically boost productivity π°πͺ
npx -y skills add emaarco/hogwarts --skill portless-dev-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Adopt portless for stable, git-worktree-aware .localhost dev URLs following its documented best practices (pinned devDependency + portless.json + dev/dev:app script split β never a hand-rolled slug or sh -c wrapper), then wire it into Conductor via .conductor/settings.toml. Detects the stack first, wraps ONLY the JS/TS frontend dev server, and researches per-workspace isolation for backends/DB/Docker that portless can't cover. Use when asked to set up portless, fix stable dev URLs across worktrees, or make a repo Conductor-friendly for parallel agents.
SKILL.md
11.9 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
Skill: portless-dev-setup
Adopts portless for stable, git-worktree-aware .localhost dev URLs, following its documented best practices β not a hand-rolled approach β then wires it into Conductor so every workspace gets its own URL with no port threading. portless replaces a dev server's port with a stable URL and is git-worktree-aware: in a linked worktree it auto-derives https://<worktree>.<project>.localhost.
Run this when asked to "set up portless", get stable dev URLs that survive across git worktrees, refactor a brittle hand-rolled port/host wrapper, or make a repo safe for several Conductor agents running the frontend in parallel.
Work in phases and report findings before any large change. The scope boundary is the whole point: portless wraps exactly one thing β a JS/TS HTTP dev server. Everything else (backends, databases, Docker/compose, brokers, fixed non-HTTP ports) needs a different isolation strategy, covered in Phase 3.
Ground rules
- Verify against the live docs and the installed CLI β do not trust memory. Run
portless --helpandnpm view portless versionbefore pinning anything, andWebFetchthe portless / Conductor docs before recommending config. - Pin dependency versions exactly, matching this repo's existing convention β check whether other deps use exact vs
^/~and match it (this repo'spackage-json-stylerule prefers exact). - Smallest change that follows the documented pattern. Never build a branch/host slug yourself β portless derives it from the git worktree.
- Use semantic commit messages. Do NOT commit unless explicitly asked β stage edits, then show the diff and stop.
Proxy privilege model β pick one, apply it consistently
The port-443 proxy is the only thing that can touch root, and only because binding a port below 1024 requires privilege on macOS/Linux. Your dev server always runs as the user who launched portless β portless elevates only the proxy child, never your app. So the whole "is my stuff running as root?" question reduces to two choices. Pick one (via AskUserQuestion if unclear) and wire it into both the docs (Phase 1) and .conductor/settings.toml (Phase 2):
- Option A β clean URLs (default). Install the proxy once as a root-owned service:
npx portless service install(onesudo, per machine). Only that small daemon is root; it never touches repo files, so there are no root-owned.next/.vite/node_modulesβ dev servers and build artifacts stay unprivileged. URLs are port-less:https://<worktree>.<project>.localhost. Never runnpm run dev(or the dev script) undersudoβ that would make the dev server and its artifacts root-owned, the exact failure this avoids. - Option B β fully rootless. Set
PORTLESS_PORT=<port β₯ 1024>(e.g.1355). Nothing ever needssudoβ not the proxy, not the app β so nothing can run as root and no pre-install step is required (works out of the box for the headless Conductor Run button). Cost: the port shows in the URL:https://<worktree>.<project>.localhost:1355.
Recommend A when the user wants clean URLs and only cares that their own code and artifacts aren't root; recommend B when "zero root anywhere" is a hard requirement. A root proxy daemon is not a contradiction to "rootless" β it is the clean separation: the one privileged thing isolated, all dev work unprivileged.
Phase 0 β Detect the stack
Inspect and report before changing anything:
# Frontend dev server? (the ONLY thing portless should wrap)
cat package.json | jq '.scripts.dev, .scripts'
git ls-files | grep -E 'vite\.config|next\.config|svelte\.config|astro\.config|slides\.md|nuxt\.config'
# Other components that portless does NOT cover
git ls-files | grep -E '(docker-compose|compose)\.ya?ml$|Dockerfile|Makefile|Taskfile|pom\.xml|build\.gradle|go\.mod|requirements\.txt|pyproject\.toml'
cat package.json | jq '.workspaces' # monorepo? if yes, repeat the script/config inspection for each workspace package.json β the frontend's dev script usually lives there, not at the root
# Is portless already wired in?
cat package.json | jq '.dependencies.portless, .devDependencies.portless, .portless, .scripts'
ls portless.json 2>/dev/null
Report: (a) the one JS/TS dev server, if any (Vite/Next/Slidev/Astro/SvelteKit/β¦); (b) other components β backend (Java/Go/Python/Node API), Docker/Podman/compose, databases, brokers, fixed ports, Make/Taskfile, monorepo workspaces; (c) current portless state β a portless dependency, a portless.json, a package.json "portless" key, or portless inside any npm script.
Phase 1 β portless for the frontend (JS/TS dev server ONLY)
If portless is not present, add it. If portless is present but hand-rolled β a sh -c wrapper just to expand $PORT, a manually built branch/host slug, an assumed global install, or inline name guessing β refactor it to the documented shape.
Target shape:
- Add
portlessas a pinned devDependency sonpm installis enough β no global install. Then apply the privilege model chosen above: for Option A, document the one-timenpx portless service install(onesudoper machine) and the port-less URL shape; for Option B, document thePORTLESS_PORT=<port β₯ 1024>env var and that nosudois ever needed. - Create
portless.jsonwith explicit config instead of inference:{ "name": "<project-name>", "script": "dev:app" } - Split the npm scripts so
devis only the portless entrypoint and the real command lives indev:app(lets non-portless users run the server directly):"dev": "portless", "dev:app": "<real dev command> --port $PORT"$PORTis set by portless and expands natively in thedev:appshell β nosh -c.- Add only framework flags that are actually required, and verify them for THIS framework. Vite-based servers on macOS often need
--host/--bind 127.0.0.1(becauselocalhostresolves to IPv6::1, which portless can't proxy) plus'.localhost'in Vite'sserver.allowedHosts.
- Remove leftover manual slug/hostname construction and any
npm install -g portlessassumptions from scripts and docs.
Update README / CLAUDE.md / AGENT.md dev instructions to match: devDependency (not global), the chosen privilege model (Option A: one-time npx portless service install; Option B: PORTLESS_PORT env var, no sudo), the <worktree>.<project>.localhost URL shape (with :<port> under Option B), and that the slug is portless-derived (not hand-built).
Phase 2 β Conductor scripts
Add or refresh .conductor/settings.toml (shared, committed) per the Conductor scripts reference:
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"
[scripts]
setup = "npm install"
run = "npm run dev" # Option A: proxy pre-installed as a service, clean URLs
# run = "PORTLESS_PORT=1355 npm run dev" # Option B: fully rootless, URL carries :1355
run_mode = "concurrent"
Keep the run line for the privilege model chosen above and drop the other. Substitute the package manager detected in Phase 0 (pnpm install / pnpm dev, yarn / yarn dev, bun install / bun dev) β the snippet shows npm only as the default.
run = "npm run dev"gives each Conductor workspace its own<workspace>.<project>.localhostβ noCONDUCTOR_PORTthreading needed for the frontend.concurrentis safe only because each worktree gets a distinct portless subdomain. If the repo has a shared single-instance resource (one fixed port, one DB, one Docker stack) that can't be made per-workspace, userun_mode = "nonconcurrent".- Never wrap the run command in
sudounder either option β the dev server would run as root and leave root-owned build artifacts. - Option A only: the headless Run button has no TTY for sudo, so the proxy service MUST already be installed (
npx portless service install) before the Run button is used β otherwise portless cannot bind 443 and the run fails. Option B needs no pre-install β the unprivileged port binds without sudo, so it works headless out of the box.
Phase 3 β Non-frontend components (IMPORTANT)
portless only helps a JS/TS HTTP dev server. It does not isolate backends, databases, Docker/compose stacks, brokers, or anything binding a fixed non-HTTP port. If Phase 0 found such components, do not force portless onto them. Instead:
- Explicitly call out which parts portless does not cover.
- Research current best practices for giving those parts the same per-workspace isolation philosophy β no cross-workspace collisions when several agents run in parallel. Evaluate and recommend what fits this repo:
- parameterize service ports off Conductor's
CONDUCTOR_PORT..CONDUCTOR_PORT+9range and thread them into the backend/compose env; - per-workspace Docker/compose namespacing (
COMPOSE_PROJECT_NAMEderived fromCONDUCTOR_WORKSPACE_NAME) to avoid container/port/volume clashes; - per-workspace DB schemas/names or disposable ephemeral DBs;
portless alias <name> <port>to put a stable.localhostURL in front of a backend/Docker service that already exposes a port (the supported bridge);- when isolation is impossible:
run_mode = "nonconcurrent"and/or Spotlight testing (run from repo root), and document the limitation.
- parameterize service ports off Conductor's
- Verify against current docs before recommending β Conductor scripts, environment variables, Spotlight testing, and the relevant tool docs. Present options with trade-offs via
AskUserQuestion; don't silently pick one for shared infra.
Deliverables
- Short report β detected stack, current portless state, what changed and why.
- The edits β
portless.json,package.jsonscripts + devDependency, lockfile, vite/build config (if needed),.conductor/settings.toml, doc updates. - For non-FE parts β a concrete, researched isolation recommendation with trade-offs.
- Verification, then stop:
Then a self-terminating dev-server check β start it in the background, watch its output for the portless URL banner (node_modules/.bin/portless --version # local binary resolves jq . portless.json # valid JSON # validate .conductor/settings.toml parseshttps://<worktree>.<project>.localhostβ that line appearing is the pass criterion), and always kill it afterwards:
Show the diff β do not commit.npm run dev > /tmp/portless-check.log 2>&1 & sleep 8; grep -m1 '\.localhost' /tmp/portless-check.log # pass: prints the portless URL kill %1 2>/dev/null
Sources
- portless: https://portless.sh Β· https://github.com/vercel-labs/portless
- Conductor scripts: https://conductor.build/docs/reference/scripts
- Conductor environment variables: https://conductor.build/docs/reference/environment-variables
- Conductor Spotlight testing: https://conductor.build/docs/reference/scripts/spotlight-testing
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.