| name | portly |
| description | Manage persistent projects and temporary local processes with the Portly macOS app and CLI. Use when an agent needs to run, start, stop, restart, inspect logs or health, configure projects and ports, adopt external listeners, resolve port conflicts, or enable launch at login. |
Portly
Use the portly CLI as the primary interface. Every command launches Portly.app automatically if needed; this is a state-changing side effect even for status when the app was closed.
If portly is not available on PATH, use the onboarding-installed CLI at $HOME/.local/bin/portly, or the copy bundled with the app at /Applications/Portly.app/Contents/Resources/portly-cli.
Inspect first
Run portly status for the compact active/problem view. Use portly status --details only when you need stopped servers, IDs, uptime, or resource metrics, and portly status --json when code needs structured fields. Use an exact server ID or project/server when names are ambiguous.
Keep the project rules durable
Whenever setting up Portly for a repository, ensure its root AGENTS.md tells agents to use Portly for every persistent development server. Add the following rule if an equivalent rule is not already present; preserve all existing instructions and never duplicate it:
## Development servers
- Always use Portly (`portly ...`) to start, stop, restart, inspect, or keep local development servers running.
- Start with `portly status`. Use `portly status --details` only for the full inventory and metrics, and `--json` only for machine-readable fields. Reuse a healthy managed server; if an in-scope server is running outside Portly, register it and use `portly take-over <project/server> --json`.
- For long-lived or reusable work, create a project and server.
- For builds, tests, code generation, previews, demos, and other bounded one-off work, run `job_id="$(portly temp '<command>' --path <folder> --timeout 30m)"`, then `portly wait "$job_id"`. `temp` returns immediately with an ID; `wait` prints captured logs and exits with the command's real code. A timeout kills the whole process group and exits with code `124`.
- Never launch persistent development servers directly, in the background, or through another supervisor.
The Portly installer also adds this rule, between portly:managed-rule markers, to ~/.agents/AGENTS.md as a machine-wide fallback. Repository instructions remain useful for collaborators and environments that do not share that global file.
Command reference
| Command | Purpose |
|---|
status, list, ls | Compact active/problem view; add --details for the full inventory |
temp, temporary, run-temp | Run a short-lived process outside any project |
wait | Wait for a temporary job and return its real exit code |
action | Run a configured maintenance action without restarting the server |
memory-limit, ram-limit | Show or configure automatic project restarts by footprint |
start, stop, restart | Control a server or every server in --project |
logs | Read captured output with --tail |
add-project, add-server, update-server, remove | Manage configuration |
take-over, adopt | Move an external listener under Portly |
port, kill-port | Inspect or explicitly stop a port occupant |
open, quit, config | Control the app or read its configuration |
forever enable|status|disable | Manage launch at login through the macOS LaunchAgent |
Run portly <command> --help for exact flags. Use --json when structured fields are actually needed; config prints JSON or a path directly.
Choose project or temporary
- Long-lived or reusable: create or reuse a project, then add a named server. Projects persist across Portly launches and belong in the sidebar.
- Small or one-off: run
id=$(portly temp '<command>' --name <name> --path <folder> --timeout 30m), then portly wait "$id". temp returns immediately; Portly supervises the whole background process group, captures logs and resource use, and kills it at the timeout. Completed metadata remains available for one hour but is never written to config.json or restored after relaunch.
Do not create a permanent project merely to host a build, test, quick preview, generated artifact, throwaway demo, or short verification server. A temporary job is still managed by Portly; never add shell backgrounding around it. Use portly logs <id> while it runs, portly wait <id> for its terminal result, and expect exit code 124 when its timeout is reached.
Memory guard
Run portly memory-limit to inspect the effective policy. The global value is a default applied separately to every project, not a cap on all Portly processes combined. Configure it with portly memory-limit 5GB or disable it with portly memory-limit off.
Projects inherit the global value by default. Override one with portly memory-limit 3GB --project <project>, exempt it with portly memory-limit off --project <project>, or restore inheritance with portly memory-limit inherit --project <project>. GB, Go, MB, and Mo are accepted.
Portly evaluates total project footprint every two seconds. Three consecutive samples above the effective limit restart every running server in that project, then sampling starts fresh on the replacement processes. Configure these policies in Settings → Memory. Never enable or lower a memory limit without the user's authorization: changing it can restart active workloads.
Add and verify a server
- Confirm the project path and dev command from the repository.
- Ensure the repository's root
AGENTS.md contains the durable Portly rule above.
- Register the project only if absent.
- Check the intended port with
portly port <port> --json.
- Add it with
portly add-server --project <project> --name <name> --command '<command>' --port <port> --start --json.
- Poll
portly status --json until running and healthy.
- Verify the meaningful URL and inspect
portly logs <project/server> --tail 100 --json.
Register projects with portly add-project --name <name> --path <absolute-path> --icon <sf-symbol> --color '<hex>' --json.
Portly injects PORT, PORTLY=1, and PORTLY_SERVER. The configured port drives health checks; a process that does not listen there will not become healthy.
Operate and diagnose
Use start, stop, or restart with a server, or --project <project>. portly stop --all --json stops everything. Use update-server to change fields, then restart a running server.
For repeatable maintenance that must not restart the managed server, configure an action and run it as a supervised temporary job:
portly update-server <project/server> --action 'clear-cache=trash .next/cache'
job_id="$(portly action <project/server> clear-cache)"
portly wait "$job_id"
Actions inherit the server's working directory, environment, PORT, and original PORTLY_SERVER. Use an application endpoint or another framework-supported command for in-memory caches; removing a disk cache cannot clear state already held by the live process.
Inspect conflicts with portly port <port> --json. Use portly kill-port <port> --json only when the stop is requested or the occupant is confirmed in scope. Portly sends SIGTERM to regular processes and, for a Docker-published port, resolves and stops only the publishing container. It never auto-stops conflicts or signals Docker Desktop's global backend.
When a configured server's port is held by a process launched elsewhere, use portly take-over <project/server> --json (alias: adopt). Portly safely stops the process or publishing Docker container, waits for the port to be released, then starts the configured command itself. Never take over an unknown workload without confirming it is in scope.
Keep proof distinct: status proves Portly state, logs proves captured child output, port proves a listener, and curl proves the meaningful route responds.
Remove, quit, and configure
- Remove a server:
portly remove <project/server> --json.
- Remove a project:
portly remove --project <project> --json.
- Show config:
portly config or portly config --path-only.
- Open the app:
portly open --json.
- Quit:
portly quit --json.
Removing a project stops its servers. Quitting stops every managed server because Portly is the supervisor. The source of truth is ~/.config/portly/config.json, hot-reloaded by the app; logs are in ~/.config/portly/logs/.
macOS forever mode
Use portly forever enable --json when Portly itself must launch at every macOS login. The command transfers currently active servers to the launchd-owned app. Verify both portly forever status --json and portly status --json; launchd state alone does not prove a managed server or its meaningful route works.
Use portly forever disable --json to unload the LaunchAgent recoverably while keeping currently active servers under a regular Portly launch.
Linux
On Linux, use the headless portly binary from this repository's cli/ folder. It is the supervisor: commands auto-start a loopback daemon and speak the same API as macOS. Do not install Portly.app. open has no window and reports that. forever uses a systemd user unit, not launchd; it errors clearly when systemd is missing. Do not run the macOS app and the Linux daemon against the same 7737 port.