@remcostoeten/dev-menu
v0.2.0
Published
Zero-config dev process launcher and TUI for mono- and single-package repos
Downloads
389
Maintainers
Readme
dev-menu
Run all your dev servers from one terminal, with a keyboard bar for the things you do all day: open the app, restart a crashed process, free a stuck port, flip an env flag, read a grouped git status.
It works with no setup. Point it at a repo and it finds your apps (workspace packages, framework ports, a docker compose stack), starts them together, and prints colored, prefixed logs so you can tell who said what.
Requirements
Node 18+ or Bun — it runs on either, on Linux, macOS, or Windows. Optional: lsof (Linux/macOS) or netstat (Windows) for killing port listeners, and a clipboard tool for copying captured errors (wl-copy / xclip / xsel on Linux; pbcopy and clip ship with macOS and Windows). Run dev-menu doctor to check what your machine has.
Install
Run it once without installing:
bunx @remcostoeten/dev-menu
npx @remcostoeten/dev-menuOr install it globally:
bun add -g @remcostoeten/dev-menu
npm install -g @remcostoeten/dev-menuEither way the installed command is dev-menu. On Bun it runs natively; on Node it loads its TypeScript through jiti, so there is no build step and no Bun requirement.
First run
In a repo with no dev-menu.config.ts, a short wizard offers to detect your processes and write one. Say no and it auto-detects on every run instead. There is nothing you have to configure to get started.
Commands
Every command also accepts a short -x and a long --name form.
dev-menu run the TUI (dev mode)
dev-menu prod -p run the production build (start|preview|serve)
dev-menu run <tag...> run only the named processes
dev-menu doctor -d check your setup (runtime, config, ports, tools)
dev-menu tags print the detected process tags
dev-menu init -i run the setup wizard again
dev-menu check-source-clean -c exit non-zero if guarded paths have uncommitted changes
dev-menu install-shell -s add `dm` / `devmenu` aliases + tab-completion (fish|bash|zsh)
dev-menu help -h show help
dev-menu version -v print the installed versionAn unknown command errors and suggests the closest match instead of silently launching the TUI.
Running a subset
dev-menu run web api starts only those processes and skips the rest. Tags come from your config or from auto-detection — dev-menu tags lists them, and with shell integration installed, dev-menu run <Tab> completes them.
Doctor
dev-menu doctor checks the runtime version, whether your config loads, which processes were detected, and whether the package manager, port-killing tool, clipboard tool, and docker (when a compose stack is detected) are available. It also probes each configured port and warns when one is already taken. Exits non-zero when a required check fails.
Production mode
dev-menu prod (also dev-menu start or dev-menu preview) launches the same TUI, but instead of each app's dev script it auto-detects and runs the first of start, preview, or serve it finds in that app's package.json — via your package manager (bun/npm/pnpm/yarn). No config is needed; apps without one of those scripts are skipped. Build your apps first if their preview script expects a prior build.
Shell aliases and completion
install-shell writes short dm and devmenu aliases plus tab-completion for your shell. It auto-detects from $SHELL, or you name one explicitly, and you can add your own alias names after the shell:
dev-menu install-shell # autodetect, installs dm + devmenu
dev-menu install-shell fish # or bash | zsh
dev-menu install-shell fish men # also install a custom `men` alias
dev-menu install-shell zsh --print # print the snippet instead of writing itFor bash and zsh it appends a marked block to ~/.bashrc / ~/.zshrc (re-running replaces it, never duplicates). For fish it writes a function and completion file per alias under ~/.config/fish. After that, each alias runs dev-menu, <Tab> completes every command and flag, and dev-menu run <Tab> completes your project's process tags.
check-source-clean
check-source-clean is meant for a pre-format or pre-lint step, so a formatter cannot rewrite source you have not committed yet:
{
"scripts": {
"format": "dev-menu check-source-clean && oxfmt ."
}
}Keys
o open a URL i incognito (next open in a private window)
r restart (pick one/all) E captured errors (copy, clear, kill listeners, E+I hands them to an AI CLI)
p stop/start one process A launch an AI CLI
k kill port listeners O toggle .env options
s git panel x run a configured script
q quitGit panel
s opens an interactive git panel instead of a read-only status: files are grouped into conflicts / staged / changes / untracked, and a cursor (j/k or arrows) selects one.
space stage / unstage the selected file c commit staged (prompts for a message)
a stage everything P push
u unstage everything U pull
B checkout a local branch Esc backStaging per file means partial commits without leaving the TUI. Push, pull, commit, and checkout show git's own output afterwards; any key returns to the panel.
The top status bar shows a dot per process — yellow while starting, green once its ready line appears in the logs, red after a crash, hollow when stopped with p — plus live CPU and memory usage for each process tree (Linux/macOS).
Configuration
Everything is optional. A field you leave out falls back to auto-detection or a sensible default. Create dev-menu.config.ts at the repo root:
import { defineConfig } from '@remcostoeten/dev-menu'
export default defineConfig({
processes: [
{
tag: 'api',
color: '33',
cmd: 'bun',
args: ['run', '--hot', 'src/index.ts'],
cwd: 'apps/api',
url: 'http://localhost:3000',
port: 3000,
openKey: 'a',
},
{
tag: 'web',
color: '36',
cmd: 'bun',
args: ['run', 'dev'],
cwd: 'apps/web',
url: 'http://localhost:5173',
port: 5173,
openKey: 'f',
},
],
envToggles: [
{ label: 'dev login bypass', key: 'DEV_LOGIN_BYPASS', file: 'apps/api/.env', restart: true },
],
links: [{ label: 'dashboard', url: 'http://localhost:3000/admin', openKey: 'd' }],
scripts: [
{ label: 'db migrate', cmd: 'bun', args: ['run', 'db:migrate'], cwd: 'apps/api', key: 'm' },
{ label: 'db seed', cmd: 'bun', args: ['run', 'db:seed'], cwd: 'apps/api' },
],
guardedPaths: ['apps/web/src/', 'apps/api/src/'],
autoRestart: false,
})defineConfig is typed, so your editor lists and describes every field.
Where the config lives
The wizard can write the config to the repo root (committed) or to ~/.dev-menu, where it is symlinked in and git-ignored. Use the second option when you do not want dev tooling in the repo. Set DEV_MENU_HOME to change that directory.
Error patterns
dev-menu watches process output for errors and collects them in the E panel, starting a new block whenever an error header appears. It ships a generic set of patterns. Add your stack's own signatures with errorPatterns, which extend the defaults rather than replacing them:
export default defineConfig({
errorPatterns: {
line: [/\bDrizzleQueryError\b/, /\[better-auth\]/i],
header: [/^\s*(NeonDbError|APIError):/i],
},
})Scripts
scripts adds one-off tasks (migrations, seeds, codegen) to the x menu, so you run them without leaving the TUI. Output is prefixed like any other process, and the exit code is reported. args, cwd, and key are optional.
Ready patterns
A process dot turns from yellow to green when its logs match a ready line (ready in …, Local: http://…, listening on …, and other framework defaults). Add your own with readyPatterns, which extend the defaults:
export default defineConfig({
readyPatterns: [/API booted/i],
})What auto-detection finds
- Workspace packages from
workspacesin package.json or frompnpm-workspace.yaml, running each one that has adevscript. Libraries and native shells like Tauri or Electron are skipped unless nothing else can run. - The package manager, from the lockfile (bun, pnpm, yarn, npm).
- Ports, from a
--port/-p/PORTflag in the dev script, known framework defaults (Next, Vite, Astro, Nuxt, and others), or dependencies. - A docker compose stack, added as a
docker compose upprocess with live per-container health, unless your dev script already runs compose. - Each package's
src/folder, guarded forcheck-source-clean.
License
MIT
