Appearance
Run Panel
The Run tab in the right sidebar launches a project's dev servers (e.g. npm run dev, cargo run, a Makefile target) without opening a terminal and typing the command by hand. It reads the project's manifests, lists the runnable commands, runs the chosen one as a hidden background process, detects the port it binds to, and surfaces clickable URLs - localhost for the machine that runs it, plus the owning node's tailnet (Tailscale) and LAN addresses so you can open it from another device.
What it does
- Manifest detection. On opening a project, the panel walks the project tree (up to 3 directories deep, skipping
node_modules,target,dist,build,out,vendor, virtualenvs, and dotfolders) for supported manifests and lists every runnable command, grouped by ecosystem. Manifests nested in monorepo members - e.g. a Cargo workspace'scrates/<app>/package.json- are found and labelled by their relative directory; running one launches it in that subdirectory. Supported manifests:package.json- one entry perscriptskey; the package manager is inferred from the nearest lockfile (pnpm-lock.yaml→ pnpm,yarn.lock→ yarn,bun.lockb/bun.lock→ bun, otherwise npm), falling back to the root's manager when a nested package has no lockfile of its own.Cargo.toml-cargo run, pluscargo run --bin <name>per[[bin]].Makefile/makefile/GNUmakefile-make <target>per target.justfile-just <recipe>per recipe.pyproject.toml-[tool.poetry.scripts]/[tool.pdm.scripts]entries.- A free-form Run a command… input runs any ad-hoc command line. A bookmark button beside it saves the typed command as a pinned custom runner (see Save a command below) without running it.
- Run. Starts the command as a PTY session on the project's owning node, marked hidden and tagged
dev-server. Hidden sessions are kept out of the normal terminals list - they appear only under Running in this panel. - Pin / Unpin. Any runner, detected or custom, can be pinned. Pinned runners float to a Pinned section at the top of the list, the same idea as the Commands panel. Pins are remembered per project.
- Save a command. The bookmark button next to Run a command… persists the typed command as a pinned custom runner instead of running it. Custom runners appear under a Custom group (or the Pinned section when pinned) and carry a trash button to remove them. Detected runners cannot be removed; they are re-derived on each manifest scan.
- Save to Cmds. Each runner row has a button that copies its command into the Commands panel as a pinned command, so you can also type it into a terminal without submitting. Commands move both ways between the two panels - the Commands panel has a matching Save to Run button.
- URL detection. The daemon watches the process output for the
host:portit prints (e.g. Vite'shttp://localhost:5173/) and computes the reachable URLs on the owning node, so the tailnet/LAN addresses are correct even when you are connected to a different cluster node. Clicking a URL chip opens it in your system default browser. - Open. Reveals the hidden process's terminal in the focused pane so you can watch logs or interact with it. It stays hidden from the normal session list.
- Stop. Kills the session. The daemon tears down the entire process tree (SIGTERM, then SIGKILL after a grace period), so the port is released and no orphaned
node/child process is left holding it.
Remote reachability
URLs are computed from the owning node's addresses:
- Local (
http://localhost:PORT) - only reachable from the machine running the server. - Tailnet - the node's MagicDNS name (e.g.
host.tailnet.ts.net) or its100.xTailscale IP. - LAN - the node's LAN/DHCP address.
For the tailnet/LAN URLs to actually load, the dev server must listen on a non-loopback interface (0.0.0.0). When the server only printed localhost / 127.0.0.1, the panel still shows the remote URLs but adds a hint that remote access requires binding 0.0.0.0 (e.g. Vite --host, Next -H 0.0.0.0). Hive does not inject host flags automatically, since the flag differs per tool.
Implementation notes
Dev servers reuse the normal session machinery - there is no separate process registry:
- Protocol.
ClientMessage::NewSessioncarriestags; a session taggeddev-server([DEV_SERVER_TAG]) is created hidden. The detected URLs live onSessionInfo.dev_server(DevServerInfo { port, bound_host, urls }), so they flow through the existing session list /SessionUpdated/ cluster replication paths - no new push or list message. - Daemon.
SessionManager::spawn_dev_server_watchersubscribes to the session's output broadcast;session/dev_urls.rsscans each line (ANSI stripped) for ahost:portand computes the URLs fromlocal_addrs::detect_local_addresses+ the TailscaleSelf.DNSName. The detected info is stored on the session and announced viasession_resized_tx(live push to local clients) plus aSessionUpdatedcluster mutation. - Frontend.
lib/projectRunners.tsholds the pure manifest parsers plus the depth-bounded tree walk (detectRunners) that finds nested manifests;stores/devServers.tsorchestrates detection (via the files store), start (hidden + tagged session), stop (KillSession), open (switchTab), opening URLs (lib/openUrl.ts, which uses the Tauri shell plugin on desktop andwindow.openin the browser), and the saved runners (togglePin,addCustom,removeSaved). The UI iscomponents/sidebar-right/RunPanel.vue.
Persistence
Custom runners and pins are stored per project; plain detected runners are not, they are re-derived on each manifest scan. Only entries worth remembering (custom or pinned) are saved.
- In the Tauri desktop/mobile app, they live under a
runnersmap in the sameproject-ui-state.jsonthe Commands panel uses, via theload_project_runners/save_project_runnerscommands (local app-state only, not the daemon protocol). - In browser-shim mode, the same shape is persisted in
localStorage["hive:project-runners"].
Detected URLs are not persisted: after a daemon restart the process is gone, and the port is re-detected from output when a server is started again.