DEV Community

Andy Brummer for Standard Beagle

Posted on

Setting up tman with a Vite project

If you let AI coding agents loose on a frontend repo, they run npm run build and npm run dev constantly — and often from several sessions at once. Nothing is broken, exactly. You just have four builds racing in one checkout, two dev servers fighting over a port, no way to tell them apart, and no record of what any of them did.

tman fixes the everyday version of that problem: it gives every run a name, a slot, and a record. The same suite never runs twice at once, excess runs queue instead of stampeding your cores, and every run leaves a JSON record you can query afterwards. Underneath sits a backstop that kills the rare run that genuinely hangs or leaks.

It's a single ~3.8 MB NativeAOT binary with zero runtime dependencies (Linux, macOS, Windows). This post walks through adopting it in a stock Vite project.

Full walkthrough: init, supervised build, dev-server dedup, run records

Install

npm install -g @standardbeagle/tman
Enter fullscreen mode Exit fullscreen mode

Or the shell one-liner if you don't want it in your global node modules:

curl -fsSL https://raw.githubusercontent.com/standardbeagle/tman/main/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

A stock Vite project

Nothing special about the target project — this is the plain scaffold:

npm create vite@latest vite-demo -- --template react-ts
cd vite-demo
npm install
Enter fullscreen mode Exit fullscreen mode

Which gives you the usual scripts in package.json:

"scripts": {
  "dev": "vite",
  "build": "tsc -b && vite build",
  "lint": "oxlint",
  "preview": "vite preview"
}
Enter fullscreen mode Exit fullscreen mode

Step 1 — adopt tman

One command:

tman init --shims --gitignore
Enter fullscreen mode Exit fullscreen mode

tman init detecting the Vite scripts and writing .tman.kdl plus build and lint shims

tman init reads package.json, detects the scripts worth supervising, and writes three things:

  • .tman.kdl — the per-project config with an alias per detected script
  • shims — tiny ./build and ./lint wrappers at the repo root, so existing muscle memory (and agents that just run ./build) go through tman transparently
  • .gitignore entries for the shims

Note what it did not scaffold: there's no test script in a fresh Vite app, so there's no ./test shim pretending otherwise. Aliases tman can't detect are left commented out — a missing suite fails loudly instead of faking a pass.

Step 2 — look at .tman.kdl

The generated .tman.kdl: a defaults block and one alias per detected npm script

The defaults block is deliberately conservative:

  • stall "30m" — a hang backstop, not a runtime budget. It only fires when a run is silent and idle for 30 minutes. A cold tsc -b can legitimately think for a long while without printing; a stall sized like an expected runtime would kill healthy work.
  • max-parallel 2 — at most two runs per bucket at once; the rest queue instead of racing.
  • retain "24h" — how long finished run records stick around.
  • The memory/CPU/wall-clock ceilings are commented out because builds legitimately saturate cores and eat RAM. You opt in when a suite has actually misbehaved.

Each alias block maps a name to a command, so tman build (or the ./build shim) means npm run build — supervised.

Step 3 — run a supervised build

./build
Enter fullscreen mode Exit fullscreen mode

Running ./build: identical vite build output, now with a name, a lock, and a record

Identical output to npm run build, because it is npm run build — plus a name, a dedup lock, a slot in the queue, and a run record. The supervision is invisible until the day you need it.

Step 4 — the dev server problem

Here's the case that sold me. Two agent sessions both decide the dev server should be running:

tman run --name dev -- npm run dev &
tman run --name dev -- npm run dev
Enter fullscreen mode Exit fullscreen mode

The second dev server is refused: run 'dev' already active, use --replace to kill it

The second invocation is refused immediately:

tman: run 'dev' already active (pid 2244513, id f281b5c2ad30); use --replace to kill it
Enter fullscreen mode Exit fullscreen mode

No second Vite instance, no port fallback to 5174, no zombie server owning the port after the session that started it dies. If you want takeover semantics — a fresh server after a config change — that's --replace, which kills the old run and waits for it to hand the name back.

Locks bucket by name and directory, so a dev run in this repo never blocks a dev run in another checkout.

One tuning note for Vite specifically: never give a dev server --max-time. It's supposed to run forever. The wall-clock cap is for suites and builds, not servers.

Step 5 — what ran, and how did it go?

tman list --all
tman status
Enter fullscreen mode Exit fullscreen mode

tman list --all showing the dev server still running and the build exited, with peak memory

Every run leaves one JSON record: command, cwd, exit code, peak memory, start and finish, and the caps it ran under. tman status --json emits the same thing machine-readable, which is exactly what you want when an agent asks "did the build I started actually finish?" — or when you ask what your agents ran overnight.

Orphan reaping rides along for free: every tman command kills children whose runner died and prunes expired records. A crashed agent session doesn't leave a Vite server squatting on the port.

Step 6 — catch the runs that bypass the shims

Shims only catch commands that go through a shell lookup. An agent calling its Bash tool with a bare npm run build walks straight past them. For Claude Code, one hook applies the same policy a level up:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "tman hook pretooluse" }] }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

It rewrites bare test/build commands through tman run, leaves everything else untouched, and never blocks: every failure path (tman missing, malformed request, unreadable project) leaves the command exactly as written. There are equivalent guides for Codex CLI, Gemini CLI, Cursor, opencode, and others.

Wrap-up

Total setup cost for a Vite project: one npm install -g, one tman init. From then on:

  • duplicate builds and dev servers are refused, not raced
  • excess runs queue for a slot instead of saturating cores
  • every run leaves a record you (or your agents) can query
  • hangs and leaks get killed by a backstop that a healthy week never triggers

Docs: dev.standardbeagle.com/tman · Source: github.com/standardbeagle/tman · npm: @standardbeagle/tman

Top comments (0)