<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Standard Beagle</title>
    <description>The latest articles on DEV Community by Standard Beagle (standardbeagle).</description>
    <link>https://dev.to/standardbeagle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14219%2F5a84bab3-024e-472a-9c08-598cc62e64ee.jpg</url>
      <title>DEV Community: Standard Beagle</title>
      <link>https://dev.to/standardbeagle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/standardbeagle"/>
    <language>en</language>
    <item>
      <title>Setting up tman with a Vite project</title>
      <dc:creator>Andy Brummer</dc:creator>
      <pubDate>Sat, 01 Aug 2026 03:12:17 +0000</pubDate>
      <link>https://dev.to/standardbeagle/setting-up-tman-with-a-vite-project-5gg1</link>
      <guid>https://dev.to/standardbeagle/setting-up-tman-with-a-vite-project-5gg1</guid>
      <description>&lt;p&gt;If you let AI coding agents loose on a frontend repo, they run &lt;code&gt;npm run build&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt; &lt;em&gt;constantly&lt;/em&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/standardbeagle/tman" rel="noopener noreferrer"&gt;tman&lt;/a&gt; fixes the everyday version of that problem: it gives every run a &lt;strong&gt;name&lt;/strong&gt;, a &lt;strong&gt;slot&lt;/strong&gt;, and a &lt;strong&gt;record&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshare.sbdev.io%2Ftman-vite%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshare.sbdev.io%2Ftman-vite%2Fdemo.gif" alt="Full walkthrough: init, supervised build, dev-server dedup, run records" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @standardbeagle/tman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the shell one-liner if you don't want it in your global node modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/standardbeagle/tman/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A stock Vite project
&lt;/h2&gt;

&lt;p&gt;Nothing special about the target project — this is the plain scaffold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest vite-demo &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react-ts
&lt;span class="nb"&gt;cd &lt;/span&gt;vite-demo
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gives you the usual scripts in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc -b &amp;amp;&amp;amp; vite build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oxlint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite preview"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1 — adopt tman
&lt;/h2&gt;

&lt;p&gt;One command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman init &lt;span class="nt"&gt;--shims&lt;/span&gt; &lt;span class="nt"&gt;--gitignore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F980kuw0407n34p3ybk1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F980kuw0407n34p3ybk1v.png" alt="tman init detecting the Vite scripts and writing .tman.kdl plus build and lint shims" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tman init&lt;/code&gt; reads &lt;code&gt;package.json&lt;/code&gt;, detects the scripts worth supervising, and writes three things:&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Step 2 — look at &lt;code&gt;.tman.kdl&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxtnhx5s2b4amrw252u8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxtnhx5s2b4amrw252u8.png" alt="The generated .tman.kdl: a defaults block and one alias per detected npm script" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;defaults&lt;/code&gt; block is deliberately conservative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stall "30m"&lt;/code&gt; — a &lt;em&gt;hang&lt;/em&gt; backstop, not a runtime budget. It only fires when a run is silent &lt;strong&gt;and&lt;/strong&gt; idle for 30 minutes. A cold &lt;code&gt;tsc -b&lt;/code&gt; can legitimately think for a long while without printing; a stall sized like an expected runtime would kill healthy work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-parallel 2&lt;/code&gt; — at most two runs per bucket at once; the rest queue instead of racing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retain "24h"&lt;/code&gt; — how long finished run records stick around.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Step 3 — run a supervised build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Firdopeorq7dn44ebsimi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Firdopeorq7dn44ebsimi.png" alt="Running ./build: identical vite build output, now with a name, a lock, and a record" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Identical output to &lt;code&gt;npm run build&lt;/code&gt;, because it &lt;em&gt;is&lt;/em&gt; &lt;code&gt;npm run build&lt;/code&gt; — 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — the dev server problem
&lt;/h2&gt;

&lt;p&gt;Here's the case that sold me. Two agent sessions both decide the dev server should be running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman run &lt;span class="nt"&gt;--name&lt;/span&gt; dev &lt;span class="nt"&gt;--&lt;/span&gt; npm run dev &amp;amp;
tman run &lt;span class="nt"&gt;--name&lt;/span&gt; dev &lt;span class="nt"&gt;--&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumjcw4xrn8rw518c0xkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumjcw4xrn8rw518c0xkw.png" alt="The second dev server is refused: run 'dev' already active, use --replace to kill it" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second invocation is refused immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tman: run 'dev' already active (pid 2244513, id f281b5c2ad30); use --replace to kill it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Locks bucket by name &lt;strong&gt;and&lt;/strong&gt; directory, so a &lt;code&gt;dev&lt;/code&gt; run in this repo never blocks a &lt;code&gt;dev&lt;/code&gt; run in another checkout.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step 5 — what ran, and how did it go?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman list &lt;span class="nt"&gt;--all&lt;/span&gt;
tman status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kefazabla9sc5j461nq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kefazabla9sc5j461nq.png" alt="tman list --all showing the dev server still running and the build exited, with peak memory" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every run leaves one JSON record: command, cwd, exit code, peak memory, start and finish, and the caps it ran under. &lt;code&gt;tman status --json&lt;/code&gt; 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 &lt;em&gt;you&lt;/em&gt; ask what your agents ran overnight.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — catch the runs that bypass the shims
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tman hook pretooluse"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It rewrites bare test/build commands through &lt;code&gt;tman run&lt;/code&gt;, leaves everything else untouched, and &lt;strong&gt;never blocks&lt;/strong&gt;: every failure path (tman missing, malformed request, unreadable project) leaves the command exactly as written. There are equivalent guides for &lt;a href="https://dev.standardbeagle.com/tman/" rel="noopener noreferrer"&gt;Codex CLI, Gemini CLI, Cursor, opencode, and others&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Total setup cost for a Vite project: one &lt;code&gt;npm install -g&lt;/code&gt;, one &lt;code&gt;tman init&lt;/code&gt;. From then on:&lt;/p&gt;

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

&lt;p&gt;Docs: &lt;a href="https://dev.standardbeagle.com/tman/" rel="noopener noreferrer"&gt;dev.standardbeagle.com/tman&lt;/a&gt; · Source: &lt;a href="https://github.com/standardbeagle/tman" rel="noopener noreferrer"&gt;github.com/standardbeagle/tman&lt;/a&gt; · npm: &lt;a href="https://www.npmjs.com/package/@standardbeagle/tman" rel="noopener noreferrer"&gt;&lt;code&gt;@standardbeagle/tman&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vite</category>
      <category>node</category>
      <category>devtools</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
