Skip to content

Instantly share code, notes, and snippets.

@ekaone
Created April 14, 2026 02:11
Show Gist options
  • Select an option

  • Save ekaone/e8baa3348b23142c10c1d9c15d59c2f7 to your computer and use it in GitHub Desktop.

Select an option

Save ekaone/e8baa3348b23142c10c1d9c15d59c2f7 to your computer and use it in GitHub Desktop.
comparison just-bash vs @ekaone/vellum
<!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> &nbsp;Not supported</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;No binary execution</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;Not possible</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;Built-in executor API <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="partial">~</span> &nbsp;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> &nbsp;sdk.sources.add() <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="no">✗</span> &nbsp;Out of scope</div>
</div>
<div class="winner-row">
<div class="winner-cell">Security / isolation</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;True sandbox, no real FS access <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="partial">~</span> &nbsp;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> &nbsp;Manual chaining</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;Basic timeout</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;Result only after completion</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;InMemoryFs, OverlayFs, ReadWriteFs, MountableFs <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="na">—</span> &nbsp;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> &nbsp;Pure JS, no OS dependency <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;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> &nbsp;just-bash CLI with --json output <span class="winner-tag tie">TIE</span></div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;vellum run &lt;command&gt;</div>
</div>
<div class="winner-row">
<div class="winner-cell">Execution speed</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;Near-zero overhead (in-process) <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="partial">~</span> &nbsp;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> &nbsp;Virtual FS read/write API <span class="winner-tag tie">TIE</span></div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;writeFile / readFile / listFiles</div>
</div>
<div class="winner-row">
<div class="winner-cell">Maintenance / maturity</div>
<div class="winner-cell"><span class="yes">✓</span> &nbsp;Vercel Labs, 47k downloads/week, v2.14 <span class="winner-tag jb">WINS</span></div>
<div class="winner-cell"><span class="partial">~</span> &nbsp;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