## Why Portless currently solves stable local URLs and proxy lifecycle well, but foreground app processes still need another tool when a user wants them to keep running after the terminal command returns. Today the practical workaround is: ```bash bgproc start -n web -- portless run -- pnpm dev ``` That works, but it makes every app setup more cumbersome because the user has to compose two process layers: 1. bgproc manages the background process. 2. Portless manages route registration, proxy startup, TLS, sharing integrations, and app environment. 3. The real dev command runs underneath both. The goal is to make the common background dev-server workflow first-class in Portless, while keeping the feature small enough to have a realistic chance of merging upstream. This "background dev server" workflow is suuuper practical for developing with agents <3 ## What Add a new short namespace: ```bash portless bg ``` Initial command surface: ```bash portless bg start [options] [command...] portless bg stop [name] portless bg restart [name] portless bg status [name] portless bg list portless bg logs [name] portless bg clean [name] portless bg clean --all ``` The background layer should manage a background `portless run` process, not reimplement app launch directly. This keeps the implementation small and reuses existing Portless behavior for: - name inference - worktree prefixes - proxy startup - route registration - TLS and `NODE_EXTRA_CA_CERTS` - Tailscale and ngrok integration - route cleanup on normal exit - existing app command parsing A typical workflow should become: ```bash portless bg start --wait portless bg status portless bg logs portless bg restart portless bg stop ``` ## Design decisions from grilling ### Namespace Use `portless bg`, not top-level `start`, `stop`, or `restart`. Reason: top-level names would collide with existing explicit-name mode where `start` or `stop` could be app names. `bg` adds one reserved namespace and keeps the feature easy to explain. ### Process model `portless bg start` backgrounds Portless itself: ```bash portless bg start --name web -- pnpm dev ``` spawns a managed background process equivalent to: ```bash portless run --name web -- pnpm dev ``` Reason: direct app management would duplicate too much of `runApp()` and increase merge risk. ### Names The bg registry key and the Portless route name are the same. ```bash portless bg start --name api.myapp ``` uses `api.myapp` for both background process lookup and the Portless route. No separate process-name flag in v1. ### Omitted names For single-app contexts, commands may omit the name and infer the same effective name that `portless run` would use, including worktree prefix: ```bash portless bg start portless bg status portless bg logs portless bg restart portless bg stop ``` At monorepo roots where `portless` would start multiple apps, v1 should error and ask the user to choose an app or run from a package directory. ### Monorepos Do not implement root-level multi-app background mode in v1. V1 supports one background app per command. Monorepo support can come later as an explicit design, for example `portless bg start --all`. Reason: multi-app background mode raises separate questions about registry shape, per-app logs, partial restarts, Turbo behavior, and one supervisor process versus one process per app. ### State location Store bg state inside the active Portless state directory: ```text <portless-state-dir>/bg/ registry.json logs/ <name>.stdout.log <name>.stderr.log <name>.bg.log ``` Default location: ```text ~/.portless/bg/ ``` If `PORTLESS_STATE_DIR` is set, bg state follows it. ### Logs Use bgproc-inspired log collection with Portless-native code: ```text <name>.stdout.log <name>.stderr.log <name>.bg.log ``` `stdout` and `stderr` contain only child process output. The `bg.log` file contains background lifecycle events such as start, wait timeout, stop, force stop, restart, clean, spawned PID, and command. All three logs should be capped like bgproc: - cap at 1 MB - when oversized, keep the last 512 KB - trim to the next newline where possible `portless bg logs` behavior: ```bash portless bg logs web # last 100 stdout lines portless bg logs web --errors # stderr portless bg logs web --bg # bg lifecycle log portless bg logs web --tail 300 portless bg logs web --all portless bg logs web --follow ``` ### Restart semantics Restart should re-evaluate current config and environment. Do not persist the exact spawned command as the only restart source, because users often restart after changing config that was not picked up automatically. Persist the start intent instead: - cwd - name - run options such as `--app-port`, `--tailscale`, `--funnel`, `--ngrok` - whether an explicit app command was supplied - the explicit app command args, if supplied On restart: - use the current shell environment - rebuild a fresh `portless run ...` command - preserve explicit app command args if the original start used them - if no explicit command was supplied, let current Portless config and package scripts resolve the command again Examples: ```bash portless bg start --name api -- pnpm dev:api portless bg restart api ``` restarts with the same explicit `pnpm dev:api` command through current Portless code. ```bash portless bg start --name api portless bg restart api ``` restarts without an explicit command, so current config and package scripts decide what to run. ### Wait behavior Waiting is opt-in. ```bash portless bg start # detach and return quickly portless bg start --wait # wait up to 30 seconds portless bg start --wait 60 # wait up to 60 seconds portless bg start --wait --keep ``` `--wait` should watch for Portless readiness by reading child output and detecting the existing URL line, rather than generic port detection. Default timeout: 30 seconds. On timeout, kill the managed process unless `--keep` is passed. ### Force behavior `portless bg start --force` should force both layers: 1. stop any existing bg-managed process with the same name 2. pass `--force` to `portless run` so it can take over an existing route Reason: users expect one force flag to make the name usable. ### Stop behavior `portless bg stop <name>` should gracefully signal the tracked Portless CLI process first. This lets existing Portless cleanup run: - remove route - stop ngrok tunnel - unregister Tailscale serve - terminate child app through existing process-tree cleanup If the tracked PID is already dead, remove the stale bg registry entry and suggest cleanup where useful. Plain `stop` should not kill arbitrary live route owners by name. ### Force stop behavior `portless bg stop --force <name>` may use `SIGKILL`, but must do scoped cleanup afterward because normal Portless cleanup handlers will not run. Scoped cleanup should: 1. kill the tracked Portless CLI process group 2. remove the bg registry entry 3. clean the route only if it is still owned by the killed bg PID 4. kill app processes still listening on that tracked route port It should not kill unrelated foreground Portless processes that happen to use the same route name. ### Status behavior `portless bg status <name>` should combine bg registry state and Portless route state. It should show: - running or stopped state - PID - URL when route is registered - route status - command or inferred command intent - cwd - log paths - started time and uptime If the process is alive but the route is missing, say so. If the route exists but the bg process is dead, mark it stale and suggest `portless bg clean` and possibly `portless prune`. ### List behavior `portless bg list` lists all bg-managed processes in the active Portless state dir, matching both Portless route listing and bgproc default behavior. Include cwd in human output. ### Clean behavior Add narrow bg metadata cleanup: ```bash portless bg clean <name> portless bg clean --all ``` It should remove dead bg registry entries and stale logs. It should not kill live processes. Keep `portless prune` responsible for orphaned dev servers and stale route cleanup. ### Output Human-readable output by default, matching Portless. Add `--json` for machine-readable output on: - `start` - `status` - `list` - `stop` - `restart` - `clean` `logs` can stay raw log text in v1. ### Flags for v1 `portless bg start` should support the existing single-app run options: ```bash --name <name> --force --app-port <number> --tailscale --funnel --ngrok ``` and bg-specific options: ```bash --wait [seconds] --keep --json ``` Explicit commands should work with or without `--`, matching existing Portless style: ```bash portless bg start --name web pnpm dev portless bg start --name web -- pnpm dev ``` Unknown flags should error. ### Platform support V1 should support macOS and Linux only. On Windows, print a clear error: ```text Error: portless bg is currently supported on macOS and Linux only. ``` Reason: bgproc itself is macOS/Linux only, and reliable process-group cleanup is harder on Windows. ### Implementation approach Write a small Portless-native module inspired by bgproc. Do not vendor bgproc or add `citty`. Mirror the proven parts: - registry JSON - separate capped stdout/stderr logs - bg lifecycle log - tail/follow/all/errors/bg log reading - start/restart/status/list/stop/clean workflow Adapt for Portless needs: - Portless state dir - Portless name validation and dotted names - route-aware status and cleanup - existing CLI parser style ### Docs and help Update all user-facing documentation affected by the new CLI behavior: - `README.md` - `skills/portless/SKILL.md` - `packages/portless/src/cli.ts` help output ## Non-goals for v1 - no monorepo root multi-app background mode - no Windows support - no separate process-name flag - no direct app launch reimplementation - no daemon supervisor beyond detached `portless run` - no attempt to replace `portless prune` ## Inspiration This proposal is inspired by bgproc: https://github.com/ascorbic/bgproc. The Portless implementation should remain small and native to Portless, while crediting bgproc for the proven background-process workflow, registry shape, and capped log handling ideas.