/install zx
\r \r
Zx — Write Better Shell Scripts with JavaScript\r
\r
Overview\r
\r
zx is Google's tool for writing shell scripts in JavaScript/TypeScript. It wraps child_process, auto-escapes arguments, and provides sensible defaults — giving you the power of the JavaScript ecosystem in your scripts.\r
\r
#!/usr/bin/env zx\r
\r
await $`cat package.json | grep name`\r
\r
const branch = await $`git branch --show-current`\r
await $`dep deploy --branch=${branch}`\r
\r
const name = 'foo & bar'\r
await $`mkdir /tmp/${name}` // No quotes needed — auto-escaped\r
```\r
\r
Bash is great for simple tasks, but when scripts grow complex, a full programming language helps. zx adds helpful wrappers around `child_process`, escapes arguments, and gives sensible defaults. **Think: bash + JavaScript in one script.**\r
\r
## Triggers\r
\r
Also triggers when users ask about running shell commands in JavaScript, converting bash scripts to zx, executing remote scripts, Markdown scripts, or TypeScript shell scripts.\r
\r
## Quick Start\r
\r
```bash\r
npm install zx\r
```\r
\r
Write scripts as `.mjs` files (supports top-level `await`). Add `#!/usr/bin/env zx` shebang or run via CLI:\r
\r
```bash\r
zx ./script.mjs # Direct execution\r
npx zx ./script.mjs # Via npx\r
node --import zx/globals # As Node.js loader\r
```\r
\r
All functions (`$`, `cd`, `fetch`, etc.) are globally available in zx scripts without imports. For explicit imports (better VS Code autocomplete):\r
\r
```js\r
import 'zx/globals'\r
```\r
\r
## Core Concepts\r
\r
### `` $`command` `` — Execute Shell Commands\r
\r
The tagged template literal is the heart of zx. Everything in `${...}` is auto-escaped and quoted.\r
\r
```js\r
// Async (standard) — returns ProcessPromise\r
const output = await $`ls -la`\r
\r
// Sync variant — returns ProcessOutput directly\r
const dir = $.sync`pwd`\r
\r
// Arrays are flattened\r
const flags = ['--oneline', '--decorate', '--color']\r
await $`git log ${flags}`\r
\r
// Non-zero exit codes throw ProcessOutput\r
try {\r
await $`exit 1`\r
} catch (p) {\r
console.log(`Exit: ${p.exitCode}, Error: ${p.stderr}`)\r
}\r
```\r
\r
### Preset Configuration with `$({...})`\r
\r
Create custom `$` instances with preset options — chainable and composable:\r
\r
```js\r
const $$ = $({ verbose: false, env: { NODE_ENV: 'production' } })\r
const pwd = $$.sync`pwd`\r
\r
// Presets are chainable\r
const $1 = $({ nothrow: true })\r
const $2 = $1({ sync: true }) // Both nothrow + sync applied\r
```\r
\r
### ProcessPromise & ProcessOutput\r
\r
```\r
$`cmd` ProcessPromise (extends Promise)\r
├── .pipe() Stream piping\r
├── .kill() Terminate process\r
├── .text() Output as string\r
├── .json() Output as parsed JSON\r
├── .lines() Output split by lines\r
├── .nothrow() Suppress errors for this command\r
├── .quiet() Suppress output for this command\r
├── .timeout() Auto-kill after duration\r
├── .stdio() Configure I/O\r
├── .exitCode Promise\x3Cexit code>\r
├── .stdout Readable stream\r
├── .stderr Readable stream\r
├── .stdin Writable stream\r
├── .pid / .cmd Process metadata\r
└── await → ProcessOutput\r
├── .stdout string\r
├── .stderr string\r
├── .exitCode number\r
├── .signal string|null\r
├── .text() / .json() / .lines() / .buffer() / .blob()\r
└── .ok boolean (when nothrow)\r
```\r
\r
## Decision Tree\r
\r
When writing zx scripts, use this decision tree:\r
\r
| Goal | Approach |\r
|------|----------|\r
| Run a command | `` await $`cmd` `` |\r
| Run synchronously | `` $.sync`cmd` `` |\r
| Pipe output | `` .pipe($`next`) `` / `` .pipe('file.txt') `` |\r
| Handle errors gracefully | `` $({nothrow: true}) `` / `` .nothrow() `` |\r
| Set timeout | `` $({timeout: '30s'}) `` / `` .timeout('30s') `` |\r
| Parse JSON output | `` (await $`cmd`).json() `` |\r
| Real-time streaming | `` for await (const line of $`cmd`) `` |\r
| Retry on failure | `` retry(5, () => $`cmd`) `` |\r
| User prompt | `` question('Name: ') `` |\r
| Progress indicator | `` await spinner('Working...', () => $`cmd`) `` |\r
| Change directory | `` cd('/path') `` or `` within(() => { $.cwd = '/tmp' }) `` |\r
| Temp files/dirs | `` tmpfile() `` / `` tmpdir() `` |\r
| Parse CLI args | `` argv.flag `` or `` minimist(process.argv.slice(2)) `` |\r
| Load .env file | `` dotenv.config('.env') `` |\r
\r
## Writing Effective zx Scripts\r
\r
### Parallel Execution\r
\r
```js\r
const results = await Promise.all([\r
$`sleep 1; echo 1`,\r
$`sleep 2; echo 2`,\r
$`sleep 3; echo 3`,\r
])\r
```\r
\r
### Error Handling with nothrow\r
\r
```js\r
$.nothrow = true\r
\r
const repos = ['zx', 'webpod']\r
const clones = repos.map(n => $`git clone https://github.com/google/${n}`)\r
\r
const results = await Promise.all(clones)\r
const errors = results.filter(o => !o.ok).map(o => o.stderr.trim())\r
console.log('Errors:', errors.join('\
'))\r
```\r
\r
### Stream Piping\r
\r
```js\r
// Chain commands like bash pipes\r
const greeting = await $`printf "hello"`\r
.pipe($`awk '{printf $1", world!"}'`)\r
.pipe($`tr '[a-z]' '[A-Z]'`)\r
\r
// Pipe to file\r
await $`echo "Hello!"`.pipe('/tmp/output.txt')\r
\r
// Real-time output to terminal\r
await $`echo 1; sleep 1; echo 2; sleep 1; echo 3`.pipe(process.stdout)\r
```\r
\r
### Stream Splitting & Merging\r
\r
```js\r
// Split one source to multiple consumers\r
const p = $`some-command`\r
const [o1, o2] = await Promise.all([\r
p.pipe`log`,\r
p.pipe`extract`,\r
])\r
\r
// Merge multiple sources\r
const $h = $({ halt: true })\r
const p1 = $`echo foo`\r
const p2 = $h`echo a && sleep 0.1 && echo b`\r
const p3 = $h`echo c && sleep 0.1 && echo d`\r
const cat = $h`cat`\r
p1.pipe(cat); p2.pipe(cat); p3.pipe(cat)\r
await cat.run()\r
```\r
\r
### Output Formatters\r
\r
```js\r
const p = $`echo '{"foo":"bar"}\
line2'`\r
\r
await p.json() // { foo: 'bar' }\r
await p.lines() // ['{"foo":"bar"}', 'line2']\r
await p.text() // '{"foo":"bar"}\
line2\
'\r
```\r
\r
### Async Iteration\r
\r
```js\r
for await (const line of $`git log --oneline --max-count=5`) {\r
console.log(line)\r
}\r
```\r
\r
## Shell Configuration\r
\r
zx defaults to bash. Switch shells as needed:\r
\r
```js\r
import { useBash, usePowerShell, usePwsh } from 'zx'\r
\r
usePowerShell() // PowerShell.exe\r
usePwsh() // PowerShell v7+\r
useBash() // Back to bash\r
\r
// Manual override\r
$.shell = '/bin/zsh'\r
```\r
\r
On Windows, consider using WSL or Git Bash for bash support, or switch to PowerShell via `usePowerShell()`.\r
\r
## Built-in Helpers Summary\r
\r
| Helper | Purpose | Example |\r
|--------|---------|---------|\r
| `cd()` | Change directory | `cd('/tmp')` |\r
| `fetch()` | HTTP requests, supports `.pipe()` | `fetch('https://api.example.com')` |\r
| `question()` | Interactive user input | `question('Name: ')` |\r
| `sleep()` | Delay execution | `await sleep(1000)` |\r
| `echo()` | Print to stdout | `` echo`Status: ${p}` `` |\r
| `stdin()` | Read stdin | `JSON.parse(await stdin())` |\r
| `within()` | Isolated async config context | `within(() => { $.cwd = '/tmp' })` |\r
| `retry()` | Retry with delay/backoff | `retry(5, () => $`curl url`)` |\r
| `spinner()` | CLI progress indicator | `await spinner(() => $`long-cmd`)` |\r
| `glob()` | File glob matching (globby) | `glob('**/*.js')` |\r
| `which()` | Find executable path | `await which('node')` |\r
| `ps` | Cross-platform process list | `ps.lookup({ command: 'node' })` |\r
| `tmpdir()` | Temp directory | `tmpdir('sub')` |\r
| `tmpfile()` | Temp file | `tmpfile('f.txt', 'content')` |\r
| `argv` | Parsed CLI arguments | `argv.verbose` |\r
| `dotenv` | .env file loading | `dotenv.config('.env')` |\r
\r
**Exposed npm packages:** `chalk` (colors), `fs` (fs-extra), `os`, `path`, `YAML` (yaml), `minimist`.\r
\r
## Resources\r
\r
- **[references/api.md](references/api.md)** — Full API reference: `$` options, `cd()`, `fetch()`, `question()`, `sleep()`, `echo()`, `stdin()`, `within()`, `retry()`, `spinner()`, `glob()`, `which()`, `ps`, `kill()`, `tmpdir()`, `tmpfile()`, `minimist`, `argv`, `chalk`, `fs`, `os`, `path`, `YAML`, `dotenv`, `quote()`, `useBash()`, `usePowerShell()`, `usePwsh()`\r
- **[references/configuration.md](references/configuration.md)** — All `$.options`: `shell`, `prefix`, `postfix`, `quote`, `verbose`, `quiet`, `env`, `cwd`, `timeout`, `nothrow`, `detached`, `preferLocal`, `spawn`, `kill`, `log`, `input`, `signal`, `stdio`, `halt`, `delimiter`, `defaults`\r
- **[references/cli.md](references/cli.md)** — CLI usage: flags, env vars, Markdown scripts, remote scripts, stdin execution, REPL mode\r
- **[references/process.md](references/process.md)** — ProcessPromise/ProcessOutput lifecycle, piping, killing, aborting, output formatters, stream handling\r
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install zx - After installation, invoke the skill by name or use
/zx - Provide required inputs per the skill's parameter spec and get structured output
What is zx?
Comprehensive guide for writing shell scripts with Google zx — a tool for writing better scripts using JavaScript/TypeScript. Use when writing, debugging, or... It is an AI Agent Skill for Claude Code / OpenClaw, with 65 downloads so far.
How do I install zx?
Run "/install zx" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is zx free?
Yes, zx is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does zx support?
zx is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created zx?
It is built and maintained by OpenLark (@openlark); the current version is v1.0.0.