dollar-shell is a micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. It works in Node, Deno, Bun with the same API. Web streams, TypeScript typings, zero dependencies.
The idea is to run OS/shell commands and/or use them in stream pipelines as sources, sinks, and transformation steps using web streams. It can be used together with stream-chain and stream-json to create efficient pipelines. It helps using shell commands in utilities written in JavaScript/TypeScript running with Node, Deno, or Bun.
Available components:
-
$— spawn a process using a template string.-
$.from— spawn a process and use itsstdoutas a source stream. -
$.to— spawn a process and use itsstdinas a sink stream. -
$.ioAKA$.through— spawn a process and use it as a transformation step in our pipeline.
-
-
$sh— run a shell command using a template string.-
$sh.from— run a shell command and use itsstdoutas a source stream. -
$sh.to— run a shell command and use itsstdinas a sink stream. -
$sh.ioAKA$sh.through— run a shell command and use it as a transformation step in our pipeline.
-
- Advanced components:
-
spawn()— spawn a process with advanced ways to configure and control it. -
$$— spawn a process using a template string based onspawn(). -
shell()— a helper to spawn a shell command using a template string based onspawn(). - Various helpers for them.
-
Run a command:
import $ from 'dollar-shell';
const result = await $`echo hello`;
console.log(result.code, result.signal, result.killed);Run a shell command:
import {$sh} from 'dollar-shell';
const result = await $sh`ls .`;
console.log(result.code, result.signal, result.killed);Run a shell command (an alias or a function) and show its result:
import {$sh} from 'dollar-shell';
// custom alias that prints `stdout` and runs an interactive shell
const $p = $sh({shellArgs: ['-ic'], stdout: 'inherit'});
const result = await $p`nvm ls`;
// prints to the console the result of the commandRun a pipeline:
import $ from 'dollar-shell';
import chain from 'stream-chain';
import lines from 'stream-chain/utils/lines.js';
chain([
$.from`ls -l .`,
$.io`grep LICENSE`,
$.io`wc`,
new TextDecoderStream(),
lines(),
line => console.log(line)
]);npm i --save dollar-shelldollar-shell/
├── src/ # Source code
│ ├── index.js # Main entry point, wires everything together
│ ├── index.d.ts # TypeScript declarations for the full public API
│ ├── bq-spawn.js # Template tag factory for spawn-based functions ($, $$)
│ ├── bq-shell.js # Template tag factory for shell-based functions ($sh, shell)
│ ├── utils.js # Shared utilities (raw, isWindows, winCmdEscape, etc.)
│ ├── spawn/ # Runtime-specific Subprocess implementations
│ └── shell/ # Platform-specific shell escaping and command building
├── tests/ # Automated tests (tape-six): .js, .cjs, .ts
├── tests/manual/ # Manual verification scripts
└── wiki/ # GitHub wiki documentation (git submodule)
The documentation can be found in the wiki. See how it can be used in tests/.
For AI assistants: see llms.txt and llms-full.txt for LLM-optimized documentation.
Below is the documentation for the main components: spawn(), $$, $ and $sh.
Spawn a process with advanced ways to configure and control it.
The signature: spawn(command, options)
Arguments:
-
command— an array of strings. The first element is the command to run. The rest are its arguments. -
options— an optional object with options to configure the process:-
cwd— the optional current working directory as a string. Defaults toprocess.cwd(). -
env— the optional environment variables as an object (key-value pairs). Defaults toprocess.env. -
stdin— the optional source stream. Defaults tonull. -
stdout— the optional destination stream. Defaults tonull. -
stderr— the optional destination stream. Defaults tonull.
-
stdin, stdout and stderr can be a string (one of 'inherit', 'ignore', 'pipe' or 'piped')
or null. The latter is equivalent to 'ignore'. 'piped' is an alias of 'pipe':
-
'inherit'— inherit streams from the parent process. For output steams (stdoutandstderr), it means that they will be piped to the same target, e.g., the console. -
'ignore'— the stream is ignored. -
'pipe'— the stream is available for reading or writing.
Returns a sub-process object with the following properties:
-
command— the command that was run as an array of strings. -
options— the options that were passed tospawn(). -
exited— a promise that resolves to the exit code of the process. It is used to wait for the process to exit. -
finished— a boolean. It istruewhen the process has finished andfalseotherwise. -
killed— a boolean. It istruewhen the process has been killed andfalseotherwise. -
exitCode— the exit code of the process as a number. It isnullif the process hasn't exited yet. -
signalCode— the signal code of the process as a string. It isnullif the process hasn't exited yet. -
stdin— the source stream of the process ifoptions.stdinwas'pipe'. It isnullotherwise. -
stdout— the destination stream of the process ifoptions.stdoutwas'pipe'. It isnullotherwise. -
stderr— the destination stream of the process ifoptions.stderrwas'pipe'. It isnullotherwise. -
kill()— kills the process.killedwill betrueas soon as the process has been killed. It can be used to pipe the input and output. Seespawn()'sstdinandstdoutabove for more details.
Important: all streams are exposed as web streams.
import {spawn} from 'dollar-shell';
const sp = spawn(['sleep', '5']);
await new Promise(resolve => setTimeout(resolve, 1000));
sp.kill();
await sp.exited;
sp.finished === true;
sp.killed === true;The same as spawn(), but it returns a tag function that can be used as a template string.
The signatures:
const sp1 = $$`ls -l ${myFile}`; // runs a command the defaults
const sp2 = $$(options)`ls -l .`; // runs a command with custom spawn options
const $tag = $$(options); // returns a tag function
const sp3 = $tag`ls -l .`; // runs a command with custom spawn optionsThis function is effectively a helper for spawn(). It parses the template string
into an array of string arguments. Each inserted value is included
as a separate argument if it was surrounded by whitespaces.
The second signature is used to run a command with custom spawn options. See spawn() above for more details.
The first signature returns a sub-process object. See spawn() for more details. The second signature
returns a tag function that can be used as a template string.
This function is similar to $$ but it uses different default spawn options related to streams and
different (simpler) return values:
-
$— all streams are ignored. It returns a promise that resolves to an object with the following properties:-
code— the exit code of the process. Seespawn()'sexitCodeabove for more details. -
signal— the signal code of the process. Seespawn()'ssignalCodeabove for more details. -
killed— a boolean. It istruewhen the process has been killed andfalseotherwise. Seespawn()'skilledabove for more details.
-
-
$.from— setsstdouttopipeand returnsstdoutof the process. It can be used to process the output. Seespawn()'sstdoutabove for more details. -
$.to— setsstdintopipeand returnsstdinof the process. It can be used to pipe the input. Seespawn()'sstdinabove for more details. -
$.ioAKA$.through— setsstdinandstdouttopipeand returnsstdinandstdoutof the process as a{readable, writable}pair. It can be used to create a pipeline where an external process can be used as a transform step.
This function mirrors $ but runs the command with the shell. It takes an options object that extends
the spawn options with the following properties:
-
shellPath— the path to the shell.- On Unix-like systems it defaults to the value of
the
SHELLenvironment variable if specified. Otherwise it is'/bin/sh'or'/system/bin/sh'on Android. - On Windows it defaults to the value of the
ComSpecenvironment variable if specified. Otherwise it iscmd.exe.
- On Unix-like systems it defaults to the value of
the
-
shellArgs— an array of strings that are passed to the shell as arguments.- On Unix-like systems it defaults to
['-c']. - On Windows it defaults to
['/d', '/s', '/c']forcmd.exeor['-c']forpwsh.exeorpowershell.exe.
- On Unix-like systems it defaults to
The rest is identical to $: $sh, $sh.from, $sh.to and $sh.io/$sh.through.
Each runtime uses its own backend by default (node:child_process on Node, Bun.spawn on Bun,
Deno.Command on Deno). Set the DSH_FORCE_NODE environment variable (e.g. DSH_FORCE_NODE=1) to
make every runtime spawn through the Node backend — Bun and Deno then run node:child_process on
their compatibility layer. This swaps only the spawn mechanism: the runtime that runs your code and how
it's re-launched stay native, so a forced child of Bun/Deno is still bun run … / deno run …, never a
bare node. Handy for sidestepping runtime-specific quirks (e.g. Bun intermittently dropping the last
chunk of a child's piped output).
Because dollar-shell spawns children with env defaulting to process.env, the variable is inherited by
those children — so it forces the Node backend across the whole process tree. To force only the
current process (no leak to spawned children), set globalThis.DSH_FORCE_NODE = true before importing
instead; it's process-local, but requires a dynamic import() (the backend is chosen once, at import time).
See the Cross-runtime notes for details.
The default entry exposes web streams on
stdin/stdout/stderr. If you'd rather work with Node streams — to pipe straight into fs/zlib/etc.
with no Web↔Node adapter, or to skip the conversion — import from dollar-shell/node instead:
import {spawn} from 'dollar-shell/node';
const sp = spawn(['cat', 'file.txt'], {stdout: 'pipe'});
sp.stdout.pipe(process.stdout); // sp.stdout is a Node ReadableThe API is identical to the main entry — same $, $$, $sh, shell, helpers, and
.from/.to/.through/.io — only the stream types differ (stdin is a Node Writable, stdout/stderr
are Node Readables, and asDuplex / .io / .through return a Node Duplex). It always spawns through the Node backend, so it also runs on Bun and Deno through their
node:child_process compatibility layer (only the spawn mechanism changes — the runtime launch stays native).
This package ships with files to help AI coding agents and LLMs find, understand, and use it:
- AGENTS.md — Project conventions, architecture, commands, and coding guidelines for AI agents.
- CLAUDE.md — Claude Code specific instructions (redirects to AGENTS.md).
- CONTRIBUTING.md — Contribution guidelines for humans and AI agents.
- llms.txt — Concise project overview following the llms.txt standard.
- llms-full.txt — Self-contained complete API reference (no external links needed).
The machine-readable llms.txt and llms-full.txt ship inside the npm package, so AI tools can read them straight from node_modules. AGENTS.md and CLAUDE.md are authoring-side docs kept in the repository.
BSD-3-Clause
- 1.2.1 Bugfix:
DSH_FORCE_NODEanddollar-shell/nodenow switch only the spawn mechanism — spawned children stay native (bun run …/deno run …). - 1.2.0 Added
dollar-shell/nodewith Node streams and aDSH_FORCE_NODEflag to force the Node backend on any runtime. - 1.1.14 Fixed Bun stdin abort path, added js-check, Bun + Deno wired into CI.
- 1.1.13 Updated dev dependencies.
- 1.1.12 Consolidated TypeScript tests into
tests/, removedts-check/, added CJS test, improved test coverage and documentation. - 1.1.11 Updated dev dependencies.
- 1.1.10 Fixed a bug with options chaining for attached functions, fixed Bun spawn on invalid commands, Windows-compatible tests, updated dev dependencies.
- 1.1.9 Updated dev dependencies, cleaned up docs, added info for AI agents.
- 1.1.8 Updated dev dependencies.
- 1.1.7 Updated dev dependencies.
- 1.1.6 Updated dev dependencies.
- 1.1.5 Updated dev dependencies.
- 1.1.4 Updated dev dependencies.
- 1.1.3 Updated dev dependencies.
- 1.1.2 Updated dev dependencies.
- 1.1.1 Updated dev dependencies.
- 1.1.0 Added
asDuplexto the sub-process object. - 1.0.5 Updated dev dependencies.
- 1.0.4 Fixed
raw()for spawn commands. - 1.0.3 Added TSDoc comments, improved docs, fixed typos, added the missing copying of properties.
- 1.0.2 Technical release: fixed references in the package file.
- 1.0.1 Technical release: more tests, better documentation.
- 1.0.0 The initial release.
The full release notes are in the wiki: Release notes.