Skip to content

Standalone Devtools CLI

Open the complete nice-code devtools without an app or Vite server hosting them. Manage relay sessions, print producer URLs, and remember development credentials in one OS-level data store.

@nice-code/devtools-cli is the preferred way to run devtools when an app’s Vite server is not the right place to host the window. It ships the complete pre-built browser application and works under Node 22+ or Bun. Running it beside a Vite app is fine too: the dev server discovers it and hands devtools clicks to its workspace. See Vite-to-CLI Handoff.

Terminal window
$env:RELAY_ADMIN_SECRET = "<secret>"
bunx @nice-code/devtools-cli `
--topology pixel-plaza `
--relay staging=https://staging.devtools-relay.example.com `
--producer-url staging=https://staging.example.com/

The CLI opens http://127.0.0.1:5200. Each selected topology, deployed session, or direct backend gets its own labelled workspace, so commands and clients from unrelated sources can never mix. If the default port is occupied, a no-argument or default-port launch tries a small following range. An explicit --port fails unless --fallback-port is passed.

Arguments only set the starting configuration; none are required. Keep the dashboard at / open as the Workspaces & settings home, or return to it from the ⌂ Workspaces & settings link in any workspace window’s header. Dashboard and windows are one product, and navigation runs both ways.

The dashboard is organized around the two things you come to do:

  • Local development — observe apps running on this machine through the CLI’s local relay. Add a topology workspace by its exact id; the card then reports the relay and its producers live (see live status below). Workspaces a Vite dev server registered through handoff appear here labelled discovered.
  • Remote observation — watch a deployed app through an authenticated relay session, or dial a backend directly. Sessions live inside their relay profile’s card.

A first launch with no arguments deliberately starts with no workspace. It asks what you would like to observe, with both paths one click away. Later launches restore only the workspaces you selected before.

Every add, edit and remove is a guided dialog, with no browser prompts, and applies live, only to its own workspace. Removing a workspace only stops observing it; forget removes its local credential; close and forget also closes a deployed relay session.

Connecting a deployed relay: mint or import

Section titled “Connecting a deployed relay: mint or import”

The guided flow asks for the relay URL first and probes it before anything is saved. The CLI fetches <url>/health server-side and checks that the answer identifies a nice-devtools relay. A URL that answers but is not a relay is refused on the spot, with the probe’s finding. Pasting your app’s URL here is the classic mistake, and without the probe it would show up later as a confusing error when minting. A reachability failure can be overridden with Continue anyway; a wrong identity should be corrected. The profile name is optional and defaults to the relay’s hostname.

From a verified relay you either mint a new session or import one someone else minted. Minting needs the relay’s admin secret; the label is optional and generated from the profile and date. Importing needs the session id and token, no admin secret. Minting ends with the producer URL step and a revealable join URL to share. Importing goes straight to the workspace.

Each local topology card carries two live lines. Both claim only what the CLI can verify.

The relay line distinguishes five states: running and hosted by this CLI, which the CLI can prove because it holds the server; running but hosted by another process (shared), typically a Vite plugin’s relay; the port held by something that is not a devtools relay, found by a service marker check and not a port check (see troubleshooting); not running; or disabled in settings.

The producer line counts who is in the topology’s relay room. Participants declare a diagnostic role when they join: an app bridge joins as a producer, a devtools window as a window. The counts read accordingly: “2 producers connected, 1 window”. These roles are self-declared and used for diagnostics only, never for authentication. A participant that declares none is counted as an unlabelled participant, and a room with only unlabelled participants says so and does not guess (“roles unknown — producers cannot be counted exactly”).

That care is what makes the topology-mismatch detection trustworthy. When producers are connected on some other room key while your workspace listens on yours, the card says exactly that: Producers are connected on "tank-shooter"; this workspace listens on "tank-shoter". That is a proven mismatch. Unlabelled participants on another key are reported only as a possible mismatch. Against an older or external relay that reports no room diagnostics at all, the card keeps the plain waiting hint, explicitly labelled as such, and invents no counts.

A workspace card also shows a window open chip while its workspace page is open somewhere. The page heartbeats a server-side lease, released when the page closes, with lease expiry as the fallback. So the chip is trustworthy across tabs, browsers, and crashed windows: it clears within seconds of the window going away.

The Settings section maps every CLI option to an in-app control and labels its source. Explicit CLI arguments win at startup, followed by environment fallbacks, saved UI preferences, then defaults. A UI change takes effect immediately where possible. Dashboard host/port, fallback-port, and auto-open settings are saved for the next launch and stay visibly marked restart required. Local-relay host/port changes reconnect producers and require confirmation.

--json, --show-credentials, --yes, and help/version would be meaningless as global toggles, so they map to native actions: copy/export JSON, reveal/reprint, destructive confirmation, and Help/About.

The CLI cannot infer an app’s topology. Pass the exact topologyId used by createNiceDevtools:

Terminal window
bunx @nice-code/devtools-cli --topology pixel-plaza

With fresh state and no workspace option, the CLI selects default. That observes only producers configured with topologyId: "default"; it does not discover arbitrary topology names.

An empty connected window usually means the producer uses another topology. The dashboard shows the selected id so you can spot that. Repeat --topology for isolated windows.

The CLI starts the existing local relay on port 5199. A non-default --relay-port must match the producer’s niceDevtools({ port }) configuration.

Name every relay explicitly, so profiles and credentials cannot change meaning when the order changes:

Terminal window
nice-devtools session create \
--relay staging=https://relay.example.com/prefix \
--secret-env staging=STAGING_RELAY_ADMIN_SECRET \
--ttl 8h --label "tank staging"

The CLI reports the lifetime the relay actually granted. A relay caps --ttl at its own maximum, and when it shortens your request the CLI prints both: requested 8h 0m, relay granted 2h 0m. New relays return the exact grant; against an older compatible relay the remaining duration is labelled approximate. session list shows sessions known to this nice-devtools installation, not a relay-wide active list.

Use session forget to remove only the local credential. Use session close or the dashboard’s close and forget to end the relay room and remove it locally. Closing requires an admin secret; when none is available the dashboard offers only local forgetting.

The session workspace reports connecting, connected, retrying, expired, and proven terminal-close states. A failure before the socket first opens is labelled as ambiguous, because browsers cannot distinguish bad credentials, capacity, origin policy, and relay unavailability.

--producer-url staging=https://staging.example.com/ prints one full URL that preserves the app’s existing query and hash route. See Remote Devtools Sessions for producer wiring and production gates.

For development convenience, remembered session tokens, backend tokens, and opted-in admin secrets are stored plaintext in state.json:

  • Windows: %LOCALAPPDATA%\nice-devtools
  • macOS: ~/Library/Application Support/nice-devtools
  • Linux: $XDG_STATE_HOME/nice-devtools, then ~/.local/state/nice-devtools

Set NICE_DEVTOOLS_DATA_DIR for a portable/test location. nice-devtools data path prints the exact file. An admin secret or backend token that came from an env variable or a flag is saved only with --remember-credentials. One you typed at the CLI’s hidden prompt is saved only if you answer yes to the “remember this plaintext?” question that follows. On a loopback dashboard, you can also enter a credential through a one-way field. It is cleared after submission and is never prefilled, returned, cached, or stored in browser storage. You can save an environment-variable name without its value; copying the resolved value to plaintext requires a separate confirmation. Credential entry and all credential-bearing mutations remain absent in LAN mode.

A remembered admin secret has broader, longer-lived authority than a session token: it can mint and close sessions until rotated or forgotten locally. The CLI prints where each newly persisted credential was written and the command that removes it.

The dashboard can forget a session, forget credentials for one target, or clear all local data. CLI equivalents are session forget, credentials forget, and data clear. Forgetting local state does not close a live relay session unless close and forget is selected.

--json writes JSON only to stdout and diagnostics to stderr. Non-TTY mode never prompts. session create --json includes the new token because automation needs it; session list and session show --json redact credentials unless --show-credentials is explicit.

Credential-bearing workspaces remain loopback-only. A local topology may be exposed deliberately:

Terminal window
nice-devtools --topology pixel --host 0.0.0.0 --relay-host 0.0.0.0

The local relay is unauthenticated: anyone on that network can observe application state/actions. The CLI warns at startup and removes all credential/data controls from the LAN dashboard. A producer page opened from another device needs niceDevtools({ host: "0.0.0.0", port: 5199 }) too.

A LAN dashboard also omits relay room names and the local-relay status line entirely, because those are disclosed on loopback only. A LAN-mode CLI serves no dev-server discovery or handoff.

The dashboard and the devtools window are deliberately built from the same shared design system. They share one token set, one type ramp (sans for prose, monospace for ids, URLs, and snippets), and the same accessible primitives: native <dialog> modals that take focus and close on Escape, labelled form fields, and status lines whose tone is carried in words (“Error:”, ”✓”, “Note:”) and not by color alone. Arbitrary names such as topology ids, labels and URLs always render as text, never as markup. If a dashboard screen and a window screen read as two different products, that is a bug worth reporting.

“Relay URL … did not answer as a nice-devtools relay.” The guided flow probes <url>/health before saving. This message almost always means the URL is your app’s URL, not the relay’s. A URL that answers is not necessarily a relay. Enter the relay deployment’s URL (the one your producer config calls relayUrl). Continue anyway exists for a relay that is temporarily unreachable, not for a wrong identity.

“Port … is held by something that is not a devtools relay.” The relay status line checks the service marker, not just the port. Another dev server (or anything else) is sitting on the relay port; stop it or move the relay port in Settings. The dashboard’s own port follows the --port / --fallback-port rule from the intro: an explicit port fails when taken, so a script that pinned a port never silently ends up on another one.

Producers connected, but on a different topology id. The card’s mismatch line quotes both ids. The fix is making the app’s topologyId and the workspace id exactly equal. They are case-sensitive, and a one-letter typo is the common case. If the card only reports possible mismatch, the connected participants declared no role, so the CLI won’t claim more than it knows.

“Waiting for producers … (This relay does not report room diagnostics.)” The workspace is attached to an older or externally started relay that predates room introspection. Everything still works; the card just cannot count producers. Restart the relay from this CLI (or update the process hosting it) to get live counts.

The browser blocked the devtools window. The in-app launcher shows an inline alert with a Retry button. Allow popups for the site and retry from that fresh click. Nothing was registered or half-opened in the blocked attempt.

A devtools click opened the app-local window instead of the CLI workspace. The dev server logs a stable reason once per cause, such as cli-gone, endpoint-mismatch or announcements-disabled. The full table, and what each one means, is in Vite-to-CLI Handoff.