Created
April 14, 2026 02:11
-
-
Save ekaone/e8baa3348b23142c10c1d9c15d59c2f7 to your computer and use it in GitHub Desktop.
comparison just-bash vs @ekaone/vellum
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>just-bash vs @ekaone/vellum</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&family=DM+Serif+Display:ital@0;1&display=swap'); | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| --bg: #0a0a0f; | |
| --surface: #111118; | |
| --border: #1e1e2e; | |
| --jb: #00e5ff; | |
| --jb-dim: #003d44; | |
| --vellum: #ff6b35; | |
| --vellum-dim: #3d1a0d; | |
| --text: #e8e8f0; | |
| --muted: #6b6b80; | |
| --green: #39ff14; | |
| --red: #ff1744; | |
| --yellow: #ffd60a; | |
| } | |
| html { scroll-behavior: smooth; } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: 'Space Mono', monospace; | |
| font-size: 13px; | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Noise overlay */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| z-index: 9999; | |
| opacity: 0.4; | |
| } | |
| /* Scanline effect */ | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: repeating-linear-gradient( | |
| 0deg, | |
| transparent, | |
| transparent 2px, | |
| rgba(0,0,0,0.03) 2px, | |
| rgba(0,0,0,0.03) 4px | |
| ); | |
| pointer-events: none; | |
| z-index: 9998; | |
| } | |
| /* ── HEADER ── */ | |
| header { | |
| padding: 60px 40px 40px; | |
| border-bottom: 1px solid var(--border); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .header-glow-jb { | |
| position: absolute; | |
| top: -80px; left: -80px; | |
| width: 400px; height: 400px; | |
| background: radial-gradient(circle, rgba(0,229,255,0.08) 0%, transparent 70%); | |
| pointer-events: none; | |
| } | |
| .header-glow-vellum { | |
| position: absolute; | |
| top: -80px; right: -80px; | |
| width: 400px; height: 400px; | |
| background: radial-gradient(circle, rgba(255,107,53,0.08) 0%, transparent 70%); | |
| pointer-events: none; | |
| } | |
| .vs-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 10px; | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| border: 1px solid var(--border); | |
| padding: 4px 10px; | |
| margin-bottom: 24px; | |
| } | |
| .vs-badge::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 6px; height: 6px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| } | |
| h1 { | |
| font-family: 'DM Serif Display', serif; | |
| font-size: clamp(32px, 5vw, 56px); | |
| font-weight: 400; | |
| line-height: 1.1; | |
| margin-bottom: 16px; | |
| } | |
| h1 .jb { color: var(--jb); } | |
| h1 .vellum { color: var(--vellum); } | |
| h1 .vs { color: var(--muted); font-style: italic; } | |
| .subtitle { | |
| color: var(--muted); | |
| max-width: 560px; | |
| font-size: 12px; | |
| line-height: 1.8; | |
| } | |
| /* ── HERO SCORECARDS ── */ | |
| .scorecards { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1px; | |
| background: var(--border); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .scorecard { | |
| background: var(--surface); | |
| padding: 32px 40px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .scorecard::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; | |
| height: 2px; | |
| } | |
| .scorecard.jb::before { background: var(--jb); } | |
| .scorecard.vellum::before { background: var(--vellum); } | |
| .scorecard-tag { | |
| font-size: 10px; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| margin-bottom: 12px; | |
| } | |
| .scorecard.jb .scorecard-tag { color: var(--jb); } | |
| .scorecard.vellum .scorecard-tag { color: var(--vellum); } | |
| .scorecard-name { | |
| font-family: 'DM Serif Display', serif; | |
| font-size: 28px; | |
| margin-bottom: 8px; | |
| } | |
| .scorecard-desc { | |
| color: var(--muted); | |
| font-size: 11px; | |
| line-height: 1.7; | |
| margin-bottom: 24px; | |
| max-width: 380px; | |
| } | |
| .pill-row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| } | |
| .pill { | |
| font-size: 10px; | |
| padding: 3px 8px; | |
| border: 1px solid; | |
| letter-spacing: 0.05em; | |
| } | |
| .pill.jb { border-color: var(--jb-dim); color: var(--jb); } | |
| .pill.vellum { border-color: var(--vellum-dim); color: var(--vellum); } | |
| /* ── SECTION ── */ | |
| .section { | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .section-header { | |
| padding: 24px 40px 20px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: baseline; | |
| gap: 12px; | |
| } | |
| .section-num { | |
| font-size: 10px; | |
| color: var(--muted); | |
| letter-spacing: 0.1em; | |
| } | |
| .section-title { | |
| font-family: 'DM Serif Display', serif; | |
| font-size: 20px; | |
| font-weight: 400; | |
| } | |
| /* ── COMPARISON TABLE ── */ | |
| .comp-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| .comp-table th { | |
| padding: 14px 40px; | |
| text-align: left; | |
| font-size: 10px; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| border-bottom: 1px solid var(--border); | |
| background: var(--bg); | |
| } | |
| .comp-table th.jb-col { color: var(--jb); } | |
| .comp-table th.vellum-col { color: var(--vellum); } | |
| .comp-table tr { | |
| border-bottom: 1px solid var(--border); | |
| transition: background 0.15s; | |
| } | |
| .comp-table tr:hover { background: rgba(255,255,255,0.02); } | |
| .comp-table td { | |
| padding: 16px 40px; | |
| vertical-align: top; | |
| font-size: 12px; | |
| } | |
| .comp-table td:first-child { | |
| color: var(--muted); | |
| font-size: 11px; | |
| letter-spacing: 0.05em; | |
| width: 22%; | |
| border-right: 1px solid var(--border); | |
| } | |
| .comp-table td:nth-child(2) { | |
| border-right: 1px solid var(--border); | |
| color: var(--text); | |
| width: 39%; | |
| } | |
| .comp-table td:nth-child(3) { | |
| color: var(--text); | |
| width: 39%; | |
| } | |
| /* ── STATUS ICONS ── */ | |
| .yes { color: var(--green); } | |
| .no { color: var(--red); } | |
| .partial { color: var(--yellow); } | |
| .na { color: var(--muted); } | |
| /* ── CODE BLOCKS ── */ | |
| .code-split { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1px; | |
| background: var(--border); | |
| } | |
| .code-pane { | |
| background: var(--bg); | |
| padding: 0; | |
| } | |
| .code-pane-header { | |
| padding: 10px 20px; | |
| font-size: 10px; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .code-pane.jb .code-pane-header { color: var(--jb); border-top: 2px solid var(--jb); } | |
| .code-pane.vellum .code-pane-header { color: var(--vellum); border-top: 2px solid var(--vellum); } | |
| .code-pane pre { | |
| padding: 20px; | |
| font-size: 11px; | |
| line-height: 1.7; | |
| overflow-x: auto; | |
| color: var(--text); | |
| } | |
| .kw { color: #c792ea; } | |
| .fn { color: #82aaff; } | |
| .str { color: #c3e88d; } | |
| .cm { color: var(--muted); font-style: italic; } | |
| .num { color: #f78c6c; } | |
| .jb-c { color: var(--jb); } | |
| .vc { color: var(--vellum); } | |
| .prop { color: #ffcb6b; } | |
| /* ── WINNER BADGE ── */ | |
| .winner-row { | |
| display: grid; | |
| grid-template-columns: 22% 39% 39%; | |
| border-bottom: 1px solid var(--border); | |
| font-size: 11px; | |
| } | |
| .winner-cell { | |
| padding: 12px 40px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .winner-cell:first-child { | |
| border-right: 1px solid var(--border); | |
| color: var(--muted); | |
| font-size: 10px; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| } | |
| .winner-cell:nth-child(2) { | |
| border-right: 1px solid var(--border); | |
| } | |
| .winner-tag { | |
| font-size: 9px; | |
| padding: 2px 6px; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| font-weight: 700; | |
| } | |
| .winner-tag.jb { background: var(--jb); color: #000; } | |
| .winner-tag.vellum { background: var(--vellum); color: #000; } | |
| .winner-tag.tie { background: var(--border); color: var(--muted); } | |
| /* ── VERDICT ── */ | |
| .verdict { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1px; | |
| background: var(--border); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .verdict-card { | |
| padding: 36px 40px; | |
| background: var(--surface); | |
| position: relative; | |
| } | |
| .verdict-card.jb::after { | |
| content: 'just-bash'; | |
| position: absolute; | |
| bottom: 20px; right: 20px; | |
| font-size: 48px; | |
| font-family: 'DM Serif Display', serif; | |
| color: rgba(0,229,255,0.04); | |
| white-space: nowrap; | |
| pointer-events: none; | |
| } | |
| .verdict-card.vellum::after { | |
| content: 'vellum'; | |
| position: absolute; | |
| bottom: 20px; right: 20px; | |
| font-size: 48px; | |
| font-family: 'DM Serif Display', serif; | |
| color: rgba(255,107,53,0.04); | |
| white-space: nowrap; | |
| pointer-events: none; | |
| } | |
| .verdict-label { | |
| font-size: 10px; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| margin-bottom: 12px; | |
| } | |
| .verdict-card.jb .verdict-label { color: var(--jb); } | |
| .verdict-card.vellum .verdict-label { color: var(--vellum); } | |
| .verdict-title { | |
| font-family: 'DM Serif Display', serif; | |
| font-size: 18px; | |
| margin-bottom: 12px; | |
| font-weight: 400; | |
| } | |
| .verdict-text { | |
| color: var(--muted); | |
| font-size: 11px; | |
| line-height: 1.8; | |
| } | |
| .verdict-use { | |
| margin-top: 20px; | |
| padding-top: 20px; | |
| border-top: 1px solid var(--border); | |
| font-size: 11px; | |
| } | |
| .verdict-use strong { | |
| display: block; | |
| font-size: 10px; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| margin-bottom: 8px; | |
| } | |
| .verdict-use ul { | |
| list-style: none; | |
| padding: 0; | |
| } | |
| .verdict-use li { | |
| padding: 3px 0; | |
| padding-left: 14px; | |
| position: relative; | |
| color: var(--text); | |
| } | |
| .verdict-use li::before { | |
| content: '→'; | |
| position: absolute; | |
| left: 0; | |
| font-size: 10px; | |
| } | |
| .verdict-card.jb .verdict-use li::before { color: var(--jb); } | |
| .verdict-card.vellum .verdict-use li::before { color: var(--vellum); } | |
| /* ── FOOTER ── */ | |
| footer { | |
| padding: 24px 40px; | |
| color: var(--muted); | |
| font-size: 10px; | |
| letter-spacing: 0.05em; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .footer-dot { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .footer-dot::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 5px; height: 5px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| } | |
| /* ── ANIMATIONS ── */ | |
| .fade-in { | |
| opacity: 0; | |
| transform: translateY(12px); | |
| animation: fadeIn 0.5s ease forwards; | |
| } | |
| @keyframes fadeIn { | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .fade-in:nth-child(1) { animation-delay: 0.05s; } | |
| .fade-in:nth-child(2) { animation-delay: 0.15s; } | |
| .fade-in:nth-child(3) { animation-delay: 0.25s; } | |
| @media (max-width: 768px) { | |
| header { padding: 32px 20px; } | |
| .scorecards, .code-split, .verdict { grid-template-columns: 1fr; } | |
| .comp-table th, .comp-table td { padding: 12px 20px; } | |
| .comp-table td:first-child { display: none; } | |
| .winner-row { grid-template-columns: 1fr 1fr; } | |
| .winner-cell:first-child { display: none; } | |
| .section-header { padding: 20px; } | |
| .verdict-card { padding: 28px 20px; } | |
| footer { flex-direction: column; gap: 8px; align-items: flex-start; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- HEADER --> | |
| <header> | |
| <div class="header-glow-jb"></div> | |
| <div class="header-glow-vellum"></div> | |
| <div class="vs-badge">Package Comparison · 2026</div> | |
| <h1 class="fade-in"> | |
| <span class="jb">just-bash</span> | |
| <span class="vs"> vs </span> | |
| <span class="vellum">@ekaone/vellum</span> | |
| </h1> | |
| <p class="subtitle fade-in"> | |
| Two different philosophies for running shell commands in Node.js — one simulates the environment entirely in-process, the other spawns real isolated workspaces. | |
| </p> | |
| </header> | |
| <!-- SCORECARDS --> | |
| <div class="scorecards"> | |
| <div class="scorecard jb fade-in"> | |
| <div class="scorecard-tag">Vercel Labs · v2.14.1</div> | |
| <div class="scorecard-name">just-bash</div> | |
| <p class="scorecard-desc">A fully simulated bash interpreter running in-process with a virtual in-memory filesystem. No real processes, no real disk — pure TypeScript execution. Built for AI agents and sandboxed tool-calling.</p> | |
| <div class="pill-row"> | |
| <span class="pill jb">in-process</span> | |
| <span class="pill jb">virtual fs</span> | |
| <span class="pill jb">AI agents</span> | |
| <span class="pill jb">tool executor</span> | |
| <span class="pill jb">no real shell</span> | |
| </div> | |
| </div> | |
| <div class="scorecard vellum fade-in"> | |
| <div class="scorecard-tag">@ekaone · v0.1.0</div> | |
| <div class="scorecard-name">@ekaone/vellum</div> | |
| <p class="scorecard-desc">A programmable shell runtime that spawns real Node.js processes in isolated temp workspaces. Designed for executing real npm workflows — installs, builds, dev servers — with full process control.</p> | |
| <div class="pill-row"> | |
| <span class="pill vellum">real processes</span> | |
| <span class="pill vellum">real fs</span> | |
| <span class="pill vellum">npm/npx</span> | |
| <span class="pill vellum">Next.js</span> | |
| <span class="pill vellum">tree-kill</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 1: FUNDAMENTALS --> | |
| <div class="section"> | |
| <div class="section-header"> | |
| <span class="section-num">01</span> | |
| <span class="section-title">Architecture & Runtime</span> | |
| </div> | |
| <table class="comp-table"> | |
| <thead> | |
| <tr> | |
| <th>Attribute</th> | |
| <th class="jb-col">just-bash</th> | |
| <th class="vellum-col">@ekaone/vellum</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>Execution model</td> | |
| <td>In-process TypeScript simulation of bash</td> | |
| <td>Real child processes via <code>spawn()</code></td> | |
| </tr> | |
| <tr> | |
| <td>Filesystem</td> | |
| <td>Virtual in-memory (InMemoryFs, OverlayFs, ReadWriteFs)</td> | |
| <td>Real OS temp directory, isolated per sandbox</td> | |
| </tr> | |
| <tr> | |
| <td>Shell support</td> | |
| <td>Simulated bash built-ins only (no system binaries)</td> | |
| <td>Any real shell: <code>bash</code>, <code>sh</code>, <code>cmd.exe</code></td> | |
| </tr> | |
| <tr> | |
| <td>Network access</td> | |
| <td><span class="partial">⚠</span> Configurable URL allowlist, disabled by default</td> | |
| <td><span class="yes">✓</span> Full network (real npm registry access)</td> | |
| </tr> | |
| <tr> | |
| <td>Cross-platform</td> | |
| <td><span class="yes">✓</span> Pure JS — works anywhere Node runs</td> | |
| <td><span class="yes">✓</span> Detects shell per OS (bash/sh/cmd.exe)</td> | |
| </tr> | |
| <tr> | |
| <td>Author</td> | |
| <td>Vercel Labs (Apache-2.0)</td> | |
| <td>@ekaone (MIT)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- CODE EXAMPLES --> | |
| <div class="section"> | |
| <div class="section-header"> | |
| <span class="section-num">02</span> | |
| <span class="section-title">API — Basic Usage</span> | |
| </div> | |
| <div class="code-split"> | |
| <div class="code-pane jb"> | |
| <div class="code-pane-header">just-bash</div> | |
| <pre><span class="kw">import</span> { <span class="jb-c">Bash</span> } <span class="kw">from</span> <span class="str">"just-bash"</span>; | |
| <span class="cm">// No real filesystem, no real processes</span> | |
| <span class="kw">const</span> bash = <span class="kw">new</span> <span class="jb-c">Bash</span>({ | |
| files: { | |
| <span class="str">"/data/hello.txt"</span>: <span class="str">"world"</span>, | |
| }, | |
| cwd: <span class="str">"/data"</span>, | |
| }); | |
| <span class="kw">const</span> result = <span class="kw">await</span> bash.<span class="fn">exec</span>( | |
| <span class="str">"cat hello.txt | grep world"</span> | |
| ); | |
| console.<span class="fn">log</span>(result.stdout); <span class="cm">// "world\n"</span> | |
| console.<span class="fn">log</span>(result.exitCode); <span class="cm">// 0</span> | |
| <span class="cm">// Filesystem changes persist between exec()</span> | |
| <span class="cm">// Environment vars do NOT persist</span></pre> | |
| </div> | |
| <div class="code-pane vellum"> | |
| <div class="code-pane-header">@ekaone/vellum</div> | |
| <pre><span class="kw">import</span> { <span class="vc">createSandbox</span> } <span class="kw">from</span> <span class="str">"@ekaone/vellum"</span>; | |
| <span class="cm">// Real temp dir, real processes</span> | |
| <span class="kw">const</span> sandbox = <span class="kw">await</span> <span class="vc">createSandbox</span>({ | |
| defaultTimeout: <span class="num">30_000</span>, | |
| autoCleanup: <span class="kw">true</span>, | |
| }); | |
| <span class="kw">const</span> result = <span class="kw">await</span> sandbox.<span class="fn">run</span>( | |
| <span class="str">"echo hello && echo world"</span> | |
| ); | |
| console.<span class="fn">log</span>(result.stdout); <span class="cm">// "hello\nworld\n"</span> | |
| console.<span class="fn">log</span>(result.exitCode); <span class="cm">// 0</span> | |
| console.<span class="fn">log</span>(result.durationMs); <span class="cm">// wall time</span> | |
| <span class="kw">await</span> sandbox.<span class="fn">destroy</span>(); <span class="cm">// removes temp dir</span></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- CODE: NPM --> | |
| <div class="section"> | |
| <div class="section-header"> | |
| <span class="section-num">03</span> | |
| <span class="section-title">API — npm Workflows</span> | |
| </div> | |
| <div class="code-split"> | |
| <div class="code-pane jb"> | |
| <div class="code-pane-header">just-bash — not supported</div> | |
| <pre><span class="kw">const</span> bash = <span class="kw">new</span> <span class="jb-c">Bash</span>(); | |
| <span class="cm">// ✗ Cannot run npm install</span> | |
| <span class="cm">// No real Node.js runtime available</span> | |
| <span class="cm">// No binary execution of any kind</span> | |
| <span class="cm">// No network by default</span> | |
| <span class="kw">await</span> bash.<span class="fn">exec</span>(<span class="str">"npm install lodash"</span>); | |
| <span class="cm">// Error: npm: command not found</span> | |
| <span class="kw">await</span> bash.<span class="fn">exec</span>(<span class="str">"node -e 'console.log(1)'"</span>); | |
| <span class="cm">// Error: node: command not found</span> | |
| <span class="cm">// just-bash is for shell text processing,</span> | |
| <span class="cm">// not running real node/npm workflows</span></pre> | |
| </div> | |
| <div class="code-pane vellum"> | |
| <div class="code-pane-header">@ekaone/vellum — full npm support</div> | |
| <pre><span class="kw">const</span> sandbox = <span class="kw">await</span> <span class="vc">createSandbox</span>({ | |
| defaultTimeout: <span class="num">120_000</span>, | |
| }); | |
| <span class="cm">// Write package.json, then install</span> | |
| <span class="kw">await</span> sandbox.<span class="fn">writeFile</span>(<span class="str">"package.json"</span>, | |
| JSON.<span class="fn">stringify</span>({ name: <span class="str">"app"</span>, type: <span class="str">"module"</span> }) | |
| ); | |
| <span class="kw">await</span> sandbox.<span class="fn">run</span>(<span class="str">"npm install lodash"</span>); | |
| <span class="cm">// Run a script that uses the package</span> | |
| <span class="kw">await</span> sandbox.<span class="fn">writeFile</span>(<span class="str">"index.mjs"</span>, | |
| <span class="str">"import _ from 'lodash'; console.log(_.sum([1,2,3]))"</span> | |
| ); | |
| <span class="kw">const</span> { stdout } = <span class="kw">await</span> sandbox.<span class="fn">run</span>(<span class="str">"node index.mjs"</span>); | |
| <span class="cm">// stdout: "6\n"</span></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- CODE: AI AGENTS --> | |
| <div class="section"> | |
| <div class="section-header"> | |
| <span class="section-num">04</span> | |
| <span class="section-title">API — AI Agent Tooling</span> | |
| </div> | |
| <div class="code-split"> | |
| <div class="code-pane jb"> | |
| <div class="code-pane-header">just-bash — first-class agent support</div> | |
| <pre><span class="kw">const</span> bash = <span class="kw">new</span> <span class="jb-c">Bash</span>({ | |
| executor: { | |
| tools: { | |
| <span class="str">"util.timestamp"</span>: { | |
| execute: () => ({ ts: Math.<span class="fn">floor</span>(Date.<span class="fn">now</span>() / <span class="num">1000</span>) }), | |
| }, | |
| }, | |
| setup: <span class="kw">async</span> (sdk) => { | |
| <span class="kw">await</span> sdk.sources.<span class="fn">add</span>({ | |
| kind: <span class="str">"graphql"</span>, | |
| endpoint: <span class="str">"https://countries.trevorblades.com/graphql"</span>, | |
| name: <span class="str">"countries"</span>, | |
| }); | |
| }, | |
| onToolApproval: <span class="str">"allow-all"</span>, | |
| }, | |
| }); | |
| <span class="cm">// Tools callable directly from shell scripts</span> | |
| <span class="kw">await</span> bash.<span class="fn">exec</span>(<span class="str">`js-exec -c ' | |
| const jp = await tools.countries.country({ code: "JP" }); | |
| console.log(jp.data.name); | |
| '`</span>);</pre> | |
| </div> | |
| <div class="code-pane vellum"> | |
| <div class="code-pane-header">@ekaone/vellum — bring your own agent layer</div> | |
| <pre><span class="cm">// Vellum is the execution primitive —</span> | |
| <span class="cm">// compose it with your agent SDK of choice</span> | |
| <span class="kw">import</span> Anthropic <span class="kw">from</span> <span class="str">"@anthropic-ai/sdk"</span>; | |
| <span class="kw">import</span> { <span class="vc">createSandbox</span> } <span class="kw">from</span> <span class="str">"@ekaone/vellum"</span>; | |
| <span class="kw">const</span> sandbox = <span class="kw">await</span> <span class="vc">createSandbox</span>(); | |
| <span class="cm">// Pass sandbox.run as a tool to your LLM</span> | |
| <span class="kw">const</span> bashTool = { | |
| name: <span class="str">"bash"</span>, | |
| description: <span class="str">"Run a shell command"</span>, | |
| input_schema: { ... }, | |
| handler: ({ command }) => sandbox.<span class="fn">run</span>(command), | |
| }; | |
| <span class="cm">// Agent gets real npm, real node, real build tools</span> | |
| <span class="cm">// Pipeline keeps workspace alive across calls</span> | |
| <span class="kw">await</span> sandbox.<span class="fn">pipeline</span>([ | |
| { command: <span class="str">"npm install"</span> }, | |
| { command: <span class="str">"npm run build"</span> }, | |
| { command: <span class="str">"npm test"</span> }, | |
| ]);</pre> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION: FEATURE MATRIX --> | |
| <div class="section"> | |
| <div class="section-header"> | |
| <span class="section-num">05</span> | |
| <span class="section-title">Feature Matrix</span> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Category</div> | |
| <div class="winner-cell" style="color:var(--jb)">just-bash</div> | |
| <div class="winner-cell" style="color:var(--vellum)">@ekaone/vellum</div> | |
| </div> | |
| <!-- rows --> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Run real npm install</div> | |
| <div class="winner-cell"><span class="no">✗</span> Not supported</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Core use case <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Next.js / Vite builds</div> | |
| <div class="winner-cell"><span class="no">✗</span> No binary execution</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Full framework support <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Dev server lifecycle</div> | |
| <div class="winner-cell"><span class="no">✗</span> Not possible</div> | |
| <div class="winner-cell"><span class="yes">✓</span> persistent mode + tree-kill <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">AI agent tool-calling</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Built-in executor API <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="partial">~</span> Manual wiring required</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">GraphQL / OpenAPI sources</div> | |
| <div class="winner-cell"><span class="yes">✓</span> sdk.sources.add() <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="no">✗</span> Out of scope</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Security / isolation</div> | |
| <div class="winner-cell"><span class="yes">✓</span> True sandbox, no real FS access <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="partial">~</span> OS-level temp dir, command filter</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Pipeline / sequential steps</div> | |
| <div class="winner-cell"><span class="partial">~</span> Manual chaining</div> | |
| <div class="winner-cell"><span class="yes">✓</span> sandbox.pipeline() built-in <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Timeout + process kill</div> | |
| <div class="winner-cell"><span class="partial">~</span> Basic timeout</div> | |
| <div class="winner-cell"><span class="yes">✓</span> timeout + tree-kill + timedOut flag <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Streaming output hooks</div> | |
| <div class="winner-cell"><span class="partial">~</span> Result only after completion</div> | |
| <div class="winner-cell"><span class="yes">✓</span> onStdout / onStderr per chunk <span class="winner-tag vellum">WINS</span></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Custom FS backends</div> | |
| <div class="winner-cell"><span class="yes">✓</span> InMemoryFs, OverlayFs, ReadWriteFs, MountableFs <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="na">—</span> OS temp dir only</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Cross-platform (Windows)</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Pure JS, no OS dependency <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="yes">✓</span> cmd.exe detection, functional</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">CLI binary</div> | |
| <div class="winner-cell"><span class="yes">✓</span> just-bash CLI with --json output <span class="winner-tag tie">TIE</span></div> | |
| <div class="winner-cell"><span class="yes">✓</span> vellum run <command></div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Execution speed</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Near-zero overhead (in-process) <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="partial">~</span> Process spawn overhead per call</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">File utilities</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Virtual FS read/write API <span class="winner-tag tie">TIE</span></div> | |
| <div class="winner-cell"><span class="yes">✓</span> writeFile / readFile / listFiles</div> | |
| </div> | |
| <div class="winner-row"> | |
| <div class="winner-cell">Maintenance / maturity</div> | |
| <div class="winner-cell"><span class="yes">✓</span> Vercel Labs, 47k downloads/week, v2.14 <span class="winner-tag jb">WINS</span></div> | |
| <div class="winner-cell"><span class="partial">~</span> v0.1.0, indie OSS, early stage</div> | |
| </div> | |
| </div> | |
| <!-- VERDICT --> | |
| <div class="verdict"> | |
| <div class="verdict-card jb"> | |
| <div class="verdict-label">Pick this when →</div> | |
| <div class="verdict-title">just-bash</div> | |
| <p class="verdict-text">You're building AI agents that need safe, reproducible tool execution without touching the real filesystem. You want to simulate shell environments, call GraphQL/OpenAPI tools from scripts, or run untrusted code in a zero-risk sandbox with no external dependencies.</p> | |
| <div class="verdict-use"> | |
| <strong>Best for</strong> | |
| <ul> | |
| <li>LLM tool-calling runtimes</li> | |
| <li>CI script simulation without side effects</li> | |
| <li>Text processing (grep, awk, sed, jq) in-process</li> | |
| <li>Embedding a shell into a browser or serverless environment</li> | |
| <li>Testing shell scripts without touching real disk</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="verdict-card vellum"> | |
| <div class="verdict-label">Pick this when →</div> | |
| <div class="verdict-title">@ekaone/vellum</div> | |
| <p class="verdict-text">You need to run actual npm workflows — installing packages, building frameworks, starting dev servers. You're building developer tooling, CI runners, or scaffolding systems that require the real Node.js ecosystem to be available and executable.</p> | |
| <div class="verdict-use"> | |
| <strong>Best for</strong> | |
| <ul> | |
| <li>Running npm install / npm run build in isolation</li> | |
| <li>Scaffolding Next.js / Vite / React projects</li> | |
| <li>Dev server lifecycle management</li> | |
| <li>Streaming build output in real-time</li> | |
| <li>Sequential pipeline execution (install → build → test)</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <footer> | |
| <span class="footer-dot">Generated April 2026 · just-bash v2.14.1 · @ekaone/vellum v0.1.0</span> | |
| <span style="color:var(--muted)">Both packages MIT/Apache-2.0 licensed</span> | |
| </footer> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment