just-bash 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +302 -314
  2. package/dist/AGENTS.md +2 -0
  3. package/dist/bin/chunks/{chunk-N6KA6G3Q.js → chunk-3KAVXQP4.js} +1 -1
  4. package/dist/bin/{shell/chunks/chunk-OQV5J23L.js → chunks/chunk-7G3MC56B.js} +1 -1
  5. package/dist/bin/{shell/chunks/chunk-YX7OOTPO.js → chunks/chunk-AZ3RUDR2.js} +1 -1
  6. package/dist/bin/chunks/{chunk-UPUMZYZE.js → chunk-CM4532DS.js} +18 -18
  7. package/dist/bin/{shell/chunks/chunk-R2LDP472.js → chunks/chunk-EPPBDXOG.js} +1 -1
  8. package/dist/bin/{shell/chunks/chunk-LGF54XJQ.js → chunks/chunk-L2JBII6Z.js} +1 -1
  9. package/dist/bin/chunks/chunk-LIYVQA3X.js +2 -0
  10. package/dist/bin/{shell/chunks/chunk-SYL34GE7.js → chunks/chunk-NAX7MTAR.js} +1 -1
  11. package/dist/bin/{shell/chunks/chunk-EZS766DD.js → chunks/chunk-NLBRLRWD.js} +1 -1
  12. package/dist/bin/chunks/chunk-RLNOQILG.js +2 -0
  13. package/dist/bin/chunks/{chunk-VVFGRIJZ.js → chunk-RMQC3GS7.js} +1 -1
  14. package/dist/bin/{shell/chunks/chunk-GR23MPTT.js → chunks/chunk-VHUYNUT7.js} +1 -1
  15. package/dist/bin/chunks/{chunk-3YMCKZTY.js → chunk-WXMBDX4P.js} +1 -1
  16. package/dist/bin/chunks/curl-SRTMGOVV.js +26 -0
  17. package/dist/bin/chunks/expr-4CJYC4LY.js +2 -0
  18. package/dist/bin/{shell/chunks/flag-coverage-IRM4GISL.js → chunks/flag-coverage-LBMWMYOO.js} +1 -1
  19. package/dist/bin/{shell/chunks/jq-V7FYGIKO.js → chunks/jq-FIV5Q5T4.js} +1 -1
  20. package/dist/bin/{shell/chunks/js-exec-VYG74FQ3.js → chunks/js-exec-BDQGEAU6.js} +8 -8
  21. package/dist/bin/chunks/js-exec-worker.js +1 -0
  22. package/dist/bin/{shell/chunks/ln-VAOSD4HK.js → chunks/ln-EGC4HRXZ.js} +1 -1
  23. package/dist/bin/chunks/{mkdir-CH7JGW4N.js → mkdir-Z47OISSR.js} +1 -1
  24. package/dist/bin/chunks/python3-VCIXXAXF.js +12 -0
  25. package/dist/bin/chunks/{rm-GWYJO4W7.js → rm-L3NZOLLG.js} +1 -1
  26. package/dist/bin/chunks/{sed-3C6IBX5L.js → sed-HALRQZKY.js} +1 -1
  27. package/dist/bin/chunks/{sqlite3-TZEE4O7U.js → sqlite3-CVNFMP3Z.js} +1 -1
  28. package/dist/bin/chunks/time-5R4QWCYF.js +2 -0
  29. package/dist/bin/{shell/chunks/tr-LZF57GYP.js → chunks/tr-4FPGAEVB.js} +1 -1
  30. package/dist/bin/chunks/worker.js +20 -7
  31. package/dist/bin/chunks/{yq-JJLSDDST.js → yq-Q47JUWL6.js} +1 -1
  32. package/dist/bin/just-bash.js +178 -178
  33. package/dist/bin/shell/chunks/{chunk-N6KA6G3Q.js → chunk-3KAVXQP4.js} +1 -1
  34. package/dist/bin/{chunks/chunk-OQV5J23L.js → shell/chunks/chunk-7G3MC56B.js} +1 -1
  35. package/dist/bin/{chunks/chunk-YX7OOTPO.js → shell/chunks/chunk-AZ3RUDR2.js} +1 -1
  36. package/dist/bin/shell/chunks/{chunk-UPUMZYZE.js → chunk-CM4532DS.js} +18 -18
  37. package/dist/bin/{chunks/chunk-R2LDP472.js → shell/chunks/chunk-EPPBDXOG.js} +1 -1
  38. package/dist/bin/{chunks/chunk-LGF54XJQ.js → shell/chunks/chunk-L2JBII6Z.js} +1 -1
  39. package/dist/bin/shell/chunks/chunk-LIYVQA3X.js +2 -0
  40. package/dist/bin/{chunks/chunk-SYL34GE7.js → shell/chunks/chunk-NAX7MTAR.js} +1 -1
  41. package/dist/bin/{chunks/chunk-EZS766DD.js → shell/chunks/chunk-NLBRLRWD.js} +1 -1
  42. package/dist/bin/shell/chunks/chunk-RLNOQILG.js +2 -0
  43. package/dist/bin/shell/chunks/{chunk-VVFGRIJZ.js → chunk-RMQC3GS7.js} +1 -1
  44. package/dist/bin/{chunks/chunk-GR23MPTT.js → shell/chunks/chunk-VHUYNUT7.js} +1 -1
  45. package/dist/bin/shell/chunks/{chunk-3YMCKZTY.js → chunk-WXMBDX4P.js} +1 -1
  46. package/dist/bin/shell/chunks/curl-SRTMGOVV.js +26 -0
  47. package/dist/bin/shell/chunks/expr-4CJYC4LY.js +2 -0
  48. package/dist/bin/{chunks/flag-coverage-IRM4GISL.js → shell/chunks/flag-coverage-LBMWMYOO.js} +1 -1
  49. package/dist/bin/{chunks/jq-V7FYGIKO.js → shell/chunks/jq-FIV5Q5T4.js} +1 -1
  50. package/dist/bin/{chunks/js-exec-YGYYZEEQ.js → shell/chunks/js-exec-HPXZV7UJ.js} +8 -8
  51. package/dist/bin/{chunks/ln-VAOSD4HK.js → shell/chunks/ln-EGC4HRXZ.js} +1 -1
  52. package/dist/bin/shell/chunks/{mkdir-CH7JGW4N.js → mkdir-Z47OISSR.js} +1 -1
  53. package/dist/bin/shell/chunks/python3-KFZH67GD.js +12 -0
  54. package/dist/bin/shell/chunks/{rm-GWYJO4W7.js → rm-L3NZOLLG.js} +1 -1
  55. package/dist/bin/shell/chunks/{sed-3C6IBX5L.js → sed-HALRQZKY.js} +1 -1
  56. package/dist/bin/shell/chunks/{sqlite3-TZEE4O7U.js → sqlite3-CVNFMP3Z.js} +1 -1
  57. package/dist/bin/shell/chunks/time-5R4QWCYF.js +2 -0
  58. package/dist/bin/{chunks/tr-LZF57GYP.js → shell/chunks/tr-4FPGAEVB.js} +1 -1
  59. package/dist/bin/shell/chunks/{yq-JJLSDDST.js → yq-Q47JUWL6.js} +1 -1
  60. package/dist/bin/shell/shell.js +180 -180
  61. package/dist/bundle/browser.js +485 -485
  62. package/dist/bundle/chunks/chunk-3THT3N7L.js +1 -0
  63. package/dist/bundle/chunks/{chunk-XXZ46GOX.js → chunk-62RKD26F.js} +1 -1
  64. package/dist/bundle/chunks/chunk-CWQS3NFK.js +1 -0
  65. package/dist/bundle/chunks/{chunk-IUWCBQII.js → chunk-HWBSOZZR.js} +18 -18
  66. package/dist/bundle/chunks/{chunk-64CW2LGZ.js → chunk-MDDMCKUK.js} +1 -1
  67. package/dist/bundle/chunks/{chunk-IP7G3BNA.js → chunk-MIZPJHVH.js} +1 -1
  68. package/dist/bundle/chunks/{chunk-V3IEYMEA.js → chunk-OL3S66CO.js} +1 -1
  69. package/dist/bundle/chunks/{chunk-FGALERPA.js → chunk-PBXLG62G.js} +1 -1
  70. package/dist/bundle/chunks/{chunk-MJWMXCEJ.js → chunk-S4EYC6T6.js} +1 -1
  71. package/dist/bundle/chunks/{chunk-O2DBFL6Z.js → chunk-XORM457F.js} +1 -1
  72. package/dist/bundle/chunks/{chunk-RUF7WQ7U.js → chunk-YCFVLTST.js} +1 -1
  73. package/dist/bundle/chunks/{chunk-RVT3MU3A.js → chunk-YFG2CMIF.js} +1 -1
  74. package/dist/bundle/chunks/{chunk-JCMONG3T.js → chunk-Z6LRHWXI.js} +1 -1
  75. package/dist/bundle/chunks/curl-COE4TZE6.js +25 -0
  76. package/dist/bundle/chunks/expr-5T3UU5KE.js +1 -0
  77. package/dist/bundle/chunks/{flag-coverage-SPT2DN2I.js → flag-coverage-LMTH7T5F.js} +1 -1
  78. package/dist/bundle/chunks/{jq-DIRZBOTX.js → jq-ODXZBPLY.js} +1 -1
  79. package/dist/bundle/chunks/{js-exec-R2LSP7M4.js → js-exec-4CW5N6RM.js} +8 -8
  80. package/dist/bundle/chunks/js-exec-worker.js +1 -0
  81. package/dist/bundle/chunks/{ln-UJ6YJVBK.js → ln-4TRFBYAT.js} +1 -1
  82. package/dist/bundle/chunks/{mkdir-F6XHPXZC.js → mkdir-TDEMSB6C.js} +1 -1
  83. package/dist/bundle/chunks/python3-SG3DOKBZ.js +11 -0
  84. package/dist/bundle/chunks/{rm-FYNVTQIU.js → rm-RTZG23RL.js} +1 -1
  85. package/dist/bundle/chunks/{sed-DISNI47D.js → sed-VFTTATXJ.js} +1 -1
  86. package/dist/bundle/chunks/{sqlite3-7F22DOIP.js → sqlite3-56UMWEY3.js} +1 -1
  87. package/dist/bundle/chunks/time-DEUO3QV2.js +1 -0
  88. package/dist/bundle/chunks/{tr-GVTWMRZB.js → tr-2HXZRDSW.js} +1 -1
  89. package/dist/bundle/chunks/worker.js +20 -7
  90. package/dist/bundle/chunks/{yq-XMVSIL6Z.js → yq-MJMAR36V.js} +1 -1
  91. package/dist/bundle/index.cjs +771 -771
  92. package/dist/bundle/index.js +185 -185
  93. package/dist/commands/curl/types.d.ts +1 -1
  94. package/dist/fs/sanitize-error.d.ts +9 -6
  95. package/dist/index.d.cts +1 -1
  96. package/dist/index.d.ts +1 -1
  97. package/dist/network/allow-list.d.ts +8 -5
  98. package/dist/network/fetch.d.ts +1 -1
  99. package/dist/network/index.d.ts +1 -1
  100. package/dist/network/types.d.ts +35 -1
  101. package/package.json +2 -2
  102. package/dist/bin/chunks/chunk-QZNF3Y3J.js +0 -2
  103. package/dist/bin/chunks/chunk-SYG3IW7P.js +0 -2
  104. package/dist/bin/chunks/curl-B64SIJOD.js +0 -26
  105. package/dist/bin/chunks/expr-VEFRBJT4.js +0 -2
  106. package/dist/bin/chunks/python3-EIXZW3LO.js +0 -12
  107. package/dist/bin/chunks/time-VSKBXRQH.js +0 -2
  108. package/dist/bin/shell/chunks/chunk-QZNF3Y3J.js +0 -2
  109. package/dist/bin/shell/chunks/chunk-SYG3IW7P.js +0 -2
  110. package/dist/bin/shell/chunks/curl-B64SIJOD.js +0 -26
  111. package/dist/bin/shell/chunks/expr-VEFRBJT4.js +0 -2
  112. package/dist/bin/shell/chunks/python3-WO3WFGMS.js +0 -12
  113. package/dist/bin/shell/chunks/time-VSKBXRQH.js +0 -2
  114. package/dist/bundle/chunks/chunk-CCNMISUL.js +0 -1
  115. package/dist/bundle/chunks/chunk-RH6GWZAJ.js +0 -1
  116. package/dist/bundle/chunks/curl-FCIO57JJ.js +0 -25
  117. package/dist/bundle/chunks/expr-74QHYJL5.js +0 -1
  118. package/dist/bundle/chunks/python3-DH2SBOI3.js +0 -11
  119. package/dist/bundle/chunks/time-FABCOJJU.js +0 -1
package/README.md CHANGED
@@ -1,68 +1,108 @@
1
1
  # just-bash
2
2
 
3
- A simulated bash environment with an in-memory virtual filesystem, written in TypeScript.
4
-
5
- Designed for AI agents that need a secure, sandboxed bash environment.
6
-
7
- Supports optional network access via `curl` with secure-by-default URL filtering.
8
-
9
- **Note**: This is beta software. Use at your own risk and please provide feedback.
10
-
11
- ## Table of Contents
12
-
13
- - [Security model](#security-model)
14
- - [Installation](#installation)
15
- - [Usage](#usage)
16
- - [Basic API](#basic-api)
17
- - [Configuration](#configuration)
18
- - [Custom Commands](#custom-commands)
19
- - [Filesystem Options](#filesystem-options)
20
- - [AI SDK Tool](#ai-sdk-tool)
21
- - [Vercel Sandbox Compatible API](#vercel-sandbox-compatible-api)
22
- - [CLI Binary](#cli-binary)
23
- - [Interactive Shell](#interactive-shell)
24
- - [Supported Commands](#supported-commands)
25
- - [Shell Features](#shell-features)
26
- - [Default Layout](#default-layout)
27
- - [Network Access](#network-access)
28
- - [JavaScript Support](#javascript-support)
29
- - [Execution Protection](#execution-protection)
30
- - [AST Transform Plugins](#ast-transform-plugins)
31
- - [Development](#development)
32
-
33
- ## Security model
34
-
35
- - The shell only has access to the provided file system.
36
- - Execution is protected against infinite loops or recursion. However, Bash is not fully robust against DOS from input. If you need to be robust against this, use process isolation at the OS level.
37
- - Binaries or even WASM are inherently unsupported (Use [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox) or a similar product if a full VM is needed).
38
- - There is no network access by default.
39
- - Network access can be enabled, but requests are checked against URL prefix allow-lists and HTTP-method allow-lists. See [network access](#network-access) for details.
40
- - Python and JS execution are off by default as they may represent additional security surface.
41
-
42
- ## Installation
3
+ A virtual bash environment with an in-memory filesystem, written in TypeScript and designed for AI agents.
4
+
5
+ Broad support for standard unix commands and bash syntax with optional curl, Python, JS/TS, and sqlite support.
6
+
7
+ **Note**: This is beta software. Use at your own risk and please provide feedback. See [security model](#security-model).
8
+
9
+ ## Quick Start
43
10
 
44
11
  ```bash
45
12
  npm install just-bash
46
13
  ```
47
14
 
48
- ## Usage
49
-
50
- ### Basic API
51
-
52
15
  ```typescript
53
16
  import { Bash } from "just-bash";
54
17
 
55
- const env = new Bash();
56
- await env.exec('echo "Hello" > greeting.txt');
57
- const result = await env.exec("cat greeting.txt");
18
+ const bash = new Bash();
19
+ await bash.exec('echo "Hello" > greeting.txt');
20
+ const result = await bash.exec("cat greeting.txt");
58
21
  console.log(result.stdout); // "Hello\n"
59
22
  console.log(result.exitCode); // 0
60
- console.log(result.env); // Final environment after execution
61
23
  ```
62
24
 
63
- Each `exec()` is isolated—env vars, functions, and cwd don't persist across calls (filesystem does).
25
+ Each `exec()` call gets its own isolated shell state environment variables, functions, and working directory reset between calls. The **filesystem is shared** across calls, so files written in one `exec()` are visible in the next.
26
+
27
+ ## Custom Commands
28
+
29
+ Extend just-bash with your own TypeScript commands using `defineCommand`:
30
+
31
+ ```typescript
32
+ import { Bash, defineCommand } from "just-bash";
33
+
34
+ const hello = defineCommand("hello", async (args, ctx) => {
35
+ const name = args[0] || "world";
36
+ return { stdout: `Hello, ${name}!\n`, stderr: "", exitCode: 0 };
37
+ });
38
+
39
+ const upper = defineCommand("upper", async (args, ctx) => {
40
+ return { stdout: ctx.stdin.toUpperCase(), stderr: "", exitCode: 0 };
41
+ });
42
+
43
+ const bash = new Bash({ customCommands: [hello, upper] });
44
+
45
+ await bash.exec("hello Alice"); // "Hello, Alice!\n"
46
+ await bash.exec("echo 'test' | upper"); // "TEST\n"
47
+ ```
48
+
49
+ Custom commands receive a `CommandContext` with `fs`, `cwd`, `env`, `stdin`, and `exec` (for subcommands), and work with pipes, redirections, and all shell features.
50
+
51
+ <details>
52
+ <summary><h2>Supported Commands</h2></summary>
53
+
54
+ ### File Operations
55
+
56
+ `cat`, `cp`, `file`, `ln`, `ls`, `mkdir`, `mv`, `readlink`, `rm`, `rmdir`, `split`, `stat`, `touch`, `tree`
57
+
58
+ ### Text Processing
59
+
60
+ `awk`, `base64`, `column`, `comm`, `cut`, `diff`, `expand`, `fold`, `grep` (+ `egrep`, `fgrep`), `head`, `join`, `md5sum`, `nl`, `od`, `paste`, `printf`, `rev`, `rg`, `sed`, `sha1sum`, `sha256sum`, `sort`, `strings`, `tac`, `tail`, `tr`, `unexpand`, `uniq`, `wc`, `xargs`
61
+
62
+ ### Data Processing
63
+
64
+ `jq` (JSON), `sqlite3` (SQLite), `xan` (CSV), `yq` (YAML/XML/TOML/CSV)
65
+
66
+ ### Optional Runtimes
67
+
68
+ `js-exec` (JavaScript/TypeScript via QuickJS; requires `javascript: true`), `python3`/`python` (Python via CPython; requires `python: true`)
69
+
70
+ ### Compression & Archives
71
+
72
+ `gzip` (+ `gunzip`, `zcat`), `tar`
73
+
74
+ ### Navigation & Environment
75
+
76
+ `basename`, `cd`, `dirname`, `du`, `echo`, `env`, `export`, `find`, `hostname`, `printenv`, `pwd`, `tee`
77
+
78
+ ### Shell Utilities
79
+
80
+ `alias`, `bash`, `chmod`, `clear`, `date`, `expr`, `false`, `help`, `history`, `seq`, `sh`, `sleep`, `time`, `timeout`, `true`, `unalias`, `which`, `whoami`
81
+
82
+ ### Network
83
+
84
+ `curl`, `html-to-markdown` (require [network configuration](#network-access))
64
85
 
65
- ### Configuration
86
+ All commands support `--help` for usage information.
87
+
88
+ ### Shell Features
89
+
90
+ - **Pipes**: `cmd1 | cmd2`
91
+ - **Redirections**: `>`, `>>`, `2>`, `2>&1`, `<`
92
+ - **Command chaining**: `&&`, `||`, `;`
93
+ - **Variables**: `$VAR`, `${VAR}`, `${VAR:-default}`
94
+ - **Positional parameters**: `$1`, `$2`, `$@`, `$#`
95
+ - **Glob patterns**: `*`, `?`, `[...]`
96
+ - **If statements**: `if COND; then CMD; elif COND; then CMD; else CMD; fi`
97
+ - **Functions**: `function name { ... }` or `name() { ... }`
98
+ - **Local variables**: `local VAR=value`
99
+ - **Loops**: `for`, `while`, `until`
100
+ - **Symbolic links**: `ln -s target link`
101
+ - **Hard links**: `ln target link`
102
+
103
+ </details>
104
+
105
+ ## Configuration
66
106
 
67
107
  ```typescript
68
108
  const env = new Bash({
@@ -108,57 +148,25 @@ await env.exec("cat <<EOF\n indented\nEOF", { rawScript: true });
108
148
  | `signal` | `AbortSignal` | Cooperative cancellation; stops at next statement boundary |
109
149
  | `rawScript` | `boolean` | Skip leading-whitespace normalization (default: `false`) |
110
150
 
111
- #### Lazy Files
151
+ ## Filesystem Options
112
152
 
113
- File values can be functions (sync or async). The function is called on first read and the result is cached — if the file is written to before being read, the function is never called:
153
+ Four filesystem implementations:
154
+
155
+ **InMemoryFs** (default) - Pure in-memory filesystem, no disk access:
114
156
 
115
157
  ```typescript
158
+ import { Bash } from "just-bash";
159
+
116
160
  const env = new Bash({
117
161
  files: {
118
- "/data/config.json": () => JSON.stringify({ key: "value" }),
162
+ "/data/config.json": '{"key": "value"}',
163
+ // Lazy: called on first read, cached. Never called if written before read.
164
+ "/data/large.csv": () => "col1,col2\na,b\n",
119
165
  "/data/remote.txt": async () => (await fetch("https://example.com")).text(),
120
- "/data/static.txt": "always loaded",
121
166
  },
122
167
  });
123
168
  ```
124
169
 
125
- This is useful for large or expensive-to-compute content that may not be needed.
126
-
127
- ### Custom Commands
128
-
129
- Extend just-bash with your own TypeScript commands using `defineCommand`:
130
-
131
- ```typescript
132
- import { Bash, defineCommand } from "just-bash";
133
-
134
- const hello = defineCommand("hello", async (args, ctx) => {
135
- const name = args[0] || "world";
136
- return { stdout: `Hello, ${name}!\n`, stderr: "", exitCode: 0 };
137
- });
138
-
139
- const upper = defineCommand("upper", async (args, ctx) => {
140
- return { stdout: ctx.stdin.toUpperCase(), stderr: "", exitCode: 0 };
141
- });
142
-
143
- const bash = new Bash({ customCommands: [hello, upper] });
144
-
145
- await bash.exec("hello Alice"); // "Hello, Alice!\n"
146
- await bash.exec("echo 'test' | upper"); // "TEST\n"
147
- ```
148
-
149
- Custom commands receive the full `CommandContext` with access to `fs`, `cwd`, `env`, `stdin`, and `exec` for running subcommands.
150
-
151
- ### Filesystem Options
152
-
153
- Four filesystem implementations are available:
154
-
155
- **InMemoryFs** (default) - Pure in-memory filesystem, no disk access:
156
-
157
- ```typescript
158
- import { Bash } from "just-bash";
159
- const env = new Bash(); // Uses InMemoryFs by default
160
- ```
161
-
162
170
  **OverlayFs** - Copy-on-write over a real directory. Reads come from disk, writes stay in memory:
163
171
 
164
172
  ```typescript
@@ -172,7 +180,7 @@ await env.exec("cat package.json"); // reads from disk
172
180
  await env.exec('echo "modified" > package.json'); // stays in memory
173
181
  ```
174
182
 
175
- **ReadWriteFs** - Direct read-write access to a real directory. Use this if you want the agent to be agle to write to your disk:
183
+ **ReadWriteFs** - Direct read-write access to a real directory. Use this if you want the agent to be able to write to your disk:
176
184
 
177
185
  ```typescript
178
186
  import { Bash } from "just-bash";
@@ -184,6 +192,8 @@ const env = new Bash({ fs: rwfs });
184
192
  await env.exec('echo "hello" > file.txt'); // writes to real filesystem
185
193
  ```
186
194
 
195
+ Keep `ReadWriteFs` pointed at a workspace directory, not at the installed `just-bash` package or any other trusted runtime code. Guest-writable roots should stay separate from trusted code.
196
+
187
197
  **MountableFs** - Mount multiple filesystems at different paths. Combines read-only and read-write filesystems into a unified namespace:
188
198
 
189
199
  ```typescript
@@ -222,167 +232,11 @@ const fs = new MountableFs({
222
232
  });
223
233
  ```
224
234
 
225
- ### AI SDK Tool
235
+ ## Optional Capabilities
226
236
 
227
- For AI agents, use [`bash-tool`](https://github.com/vercel-labs/bash-tool) which is optimized for just-bash and provides a ready-to-use [AI SDK](https://ai-sdk.dev/) tool:
237
+ ### Network Access
228
238
 
229
- ```bash
230
- npm install bash-tool
231
- ```
232
-
233
- ```typescript
234
- import { createBashTool } from "bash-tool";
235
- import { generateText } from "ai";
236
-
237
- const bashTool = createBashTool({
238
- files: { "/data/users.json": '[{"name": "Alice"}, {"name": "Bob"}]' },
239
- });
240
-
241
- const result = await generateText({
242
- model: "anthropic/claude-sonnet-4",
243
- tools: { bash: bashTool },
244
- prompt: "Count the users in /data/users.json",
245
- });
246
- ```
247
-
248
- See the [bash-tool documentation](https://github.com/vercel-labs/bash-tool) for more details and examples.
249
-
250
- ### Vercel Sandbox Compatible API
251
-
252
- Bash provides a `Sandbox` class that's API-compatible with [`@vercel/sandbox`](https://vercel.com/docs/vercel-sandbox), making it easy to swap implementations. You can start with Bash and switch to a real sandbox when you need the power of a full VM (e.g. to run node, python, or custom binaries).
253
-
254
- ```typescript
255
- import { Sandbox } from "just-bash";
256
-
257
- // Create a sandbox instance
258
- const sandbox = await Sandbox.create({ cwd: "/app" });
259
-
260
- // Write files to the virtual filesystem
261
- await sandbox.writeFiles({
262
- "/app/script.sh": 'echo "Hello World"',
263
- "/app/data.json": '{"key": "value"}',
264
- });
265
-
266
- // Run commands and get results
267
- const cmd = await sandbox.runCommand("bash /app/script.sh");
268
- const output = await cmd.stdout(); // "Hello World\n"
269
- const exitCode = (await cmd.wait()).exitCode; // 0
270
-
271
- // Read files back
272
- const content = await sandbox.readFile("/app/data.json");
273
-
274
- // Create directories
275
- await sandbox.mkDir("/app/logs", { recursive: true });
276
-
277
- // Clean up (no-op for Bash, but API-compatible)
278
- await sandbox.stop();
279
- ```
280
-
281
- ### CLI Binary
282
-
283
- After installing globally (`npm install -g just-bash`), use the `just-bash` command as a secure alternative to `bash` for AI agents:
284
-
285
- ```bash
286
- # Execute inline script
287
- just-bash -c 'ls -la && cat package.json | head -5'
288
-
289
- # Execute with specific project root
290
- just-bash -c 'grep -r "TODO" src/' --root /path/to/project
291
-
292
- # Pipe script from stdin
293
- echo 'find . -name "*.ts" | wc -l' | just-bash
294
-
295
- # Execute a script file
296
- just-bash ./scripts/deploy.sh
297
-
298
- # Get JSON output for programmatic use
299
- just-bash -c 'echo hello' --json
300
- # Output: {"stdout":"hello\n","stderr":"","exitCode":0}
301
- ```
302
-
303
- The CLI uses OverlayFS - reads come from the real filesystem, but all writes stay in memory and are discarded after execution. The project root is mounted at `/home/user/project`.
304
-
305
- Options:
306
-
307
- - `-c <script>` - Execute script from argument
308
- - `--root <path>` - Root directory (default: current directory)
309
- - `--cwd <path>` - Working directory in sandbox
310
- - `-e, --errexit` - Exit on first error
311
- - `--json` - Output as JSON
312
-
313
- ### Interactive Shell
314
-
315
- ```bash
316
- pnpm shell
317
- ```
318
-
319
- The interactive shell has full internet access enabled by default, allowing you to use `curl` to fetch data from any URL. Use `--no-network` to disable this:
320
-
321
- ```bash
322
- pnpm shell --no-network
323
- ```
324
-
325
- ## Supported Commands
326
-
327
- ### File Operations
328
-
329
- `cat`, `cp`, `file`, `ln`, `ls`, `mkdir`, `mv`, `readlink`, `rm`, `rmdir`, `split`, `stat`, `touch`, `tree`
330
-
331
- ### Text Processing
332
-
333
- `awk`, `base64`, `column`, `comm`, `cut`, `diff`, `expand`, `fold`, `grep` (+ `egrep`, `fgrep`), `head`, `join`, `md5sum`, `nl`, `od`, `paste`, `printf`, `rev`, `rg`, `sed`, `sha1sum`, `sha256sum`, `sort`, `strings`, `tac`, `tail`, `tr`, `unexpand`, `uniq`, `wc`, `xargs`
334
-
335
- ### Data Processing
336
-
337
- `jq` (JSON), `js-exec` (JavaScript/TypeScript via QuickJS; requires opt-in), `python3`/`python` (Python via WASM/CPython; requires opt-in), `sqlite3` (SQLite), `xan` (CSV), `yq` (YAML/XML/TOML/CSV)
338
-
339
- ### Compression & Archives
340
-
341
- `gzip` (+ `gunzip`, `zcat`), `tar`
342
-
343
- ### Navigation & Environment
344
-
345
- `basename`, `cd`, `dirname`, `du`, `echo`, `env`, `export`, `find`, `hostname`, `printenv`, `pwd`, `tee`
346
-
347
- ### Shell Utilities
348
-
349
- `alias`, `bash`, `chmod`, `clear`, `date`, `expr`, `false`, `help`, `history`, `seq`, `sh`, `sleep`, `time`, `timeout`, `true`, `unalias`, `which`, `whoami`
350
-
351
- ### Network Commands
352
-
353
- `curl`, `html-to-markdown`
354
-
355
- All commands support `--help` for usage information.
356
-
357
- ## Shell Features
358
-
359
- - **Pipes**: `cmd1 | cmd2`
360
- - **Redirections**: `>`, `>>`, `2>`, `2>&1`, `<`
361
- - **Command chaining**: `&&`, `||`, `;`
362
- - **Variables**: `$VAR`, `${VAR}`, `${VAR:-default}`
363
- - **Positional parameters**: `$1`, `$2`, `$@`, `$#`
364
- - **Glob patterns**: `*`, `?`, `[...]`
365
- - **If statements**: `if COND; then CMD; elif COND; then CMD; else CMD; fi`
366
- - **Functions**: `function name { ... }` or `name() { ... }`
367
- - **Local variables**: `local VAR=value`
368
- - **Loops**: `for`, `while`, `until`
369
- - **Symbolic links**: `ln -s target link`
370
- - **Hard links**: `ln target link`
371
-
372
- ## Default Layout
373
-
374
- When created without options, Bash provides a Unix-like directory structure:
375
-
376
- - `/home/user` - Default working directory (and `$HOME`)
377
- - `/bin` - Contains stubs for all built-in commands
378
- - `/usr/bin` - Additional binary directory
379
- - `/tmp` - Temporary files directory
380
-
381
- Commands can be invoked by path (e.g., `/bin/ls`) or by name.
382
-
383
- ## Network Access
384
-
385
- Network access (and the `curl` command) is disabled by default for security. To enable it, configure the `network` option:
239
+ Network access is disabled by default. Enable it with the `network` option:
386
240
 
387
241
  ```typescript
388
242
  // Allow specific URLs with GET/HEAD only (safest)
@@ -403,6 +257,19 @@ const env = new Bash({
403
257
  },
404
258
  });
405
259
 
260
+ // Inject credentials via header transforms (secrets never enter the sandbox)
261
+ const env = new Bash({
262
+ network: {
263
+ allowedUrlPrefixes: [
264
+ "https://public-api.com", // plain string — no transforms
265
+ {
266
+ url: "https://ai-gateway.vercel.sh",
267
+ transform: [{ headers: { Authorization: "Bearer secret" } }],
268
+ },
269
+ ],
270
+ },
271
+ });
272
+
406
273
  // Allow all URLs and methods (use with caution)
407
274
  const env = new Bash({
408
275
  network: { dangerouslyAllowFullInternetAccess: true },
@@ -411,27 +278,33 @@ const env = new Bash({
411
278
 
412
279
  **Note:** The `curl` command only exists when network is configured. Without network configuration, `curl` returns "command not found".
413
280
 
414
- ## Python Support
281
+ #### Allow-List Security
415
282
 
416
- Python support via WASM/CPython is opt-in due to additional security surface. Enable it explicitly, but be aware of the risk:
283
+ The allow-list enforces:
417
284
 
418
- ```typescript
419
- const env = new Bash({
420
- python: true,
421
- });
285
+ - **Origin matching**: URLs must match the exact origin (scheme + host + port)
286
+ - **Path prefix**: Only paths starting with the specified prefix are allowed
287
+ - **HTTP method restrictions**: Only GET and HEAD by default (configure `allowedMethods` for more)
288
+ - **Redirect protection**: Redirects to non-allowed URLs are blocked
289
+ - **Header transforms**: Firewall headers are injected at the fetch boundary and override any user-supplied headers with the same name, preventing credential substitution from inside the sandbox. Headers are re-evaluated on each redirect so credentials are never leaked to non-transform hosts
422
290
 
423
- // Execute Python code
424
- await env.exec('python3 -c "print(1 + 2)"');
291
+ #### Using curl
425
292
 
426
- // Run Python scripts
427
- await env.exec('python3 script.py');
428
- ```
293
+ ```bash
294
+ # Fetch and process data
295
+ curl -s https://api.example.com/data | grep pattern
429
296
 
430
- **Note:** The `python3` and `python` commands only exist when `python: true` is configured. Python is not available in browser environments.
297
+ # Download and convert HTML to Markdown
298
+ curl -s https://example.com | html-to-markdown
431
299
 
432
- ## JavaScript Support
300
+ # POST JSON data
301
+ curl -X POST -H "Content-Type: application/json" \
302
+ -d '{"key":"value"}' https://api.example.com/endpoint
303
+ ```
304
+
305
+ ### JavaScript Support
433
306
 
434
- JavaScript and TypeScript execution via QuickJS is opt-in due to additional security surface. Enable it with the `javascript` option:
307
+ JavaScript and TypeScript execution via QuickJS is opt-in due to additional security surface. Enable with `javascript: true`:
435
308
 
436
309
  ```typescript
437
310
  const env = new Bash({
@@ -448,9 +321,9 @@ await env.exec('js-exec script.js');
448
321
  await env.exec('js-exec -m -c "import fs from \'fs\'; console.log(fs.readFileSync(\'/data/file.txt\', \'utf8\'))"');
449
322
  ```
450
323
 
451
- ### Bootstrap Code
324
+ #### Bootstrap Code
452
325
 
453
- You can run setup code before every `js-exec` invocation using the `bootstrap` option. This is useful for injecting polyfills, global utilities, or environment setup:
326
+ Run setup code before every `js-exec` invocation with the `bootstrap` option:
454
327
 
455
328
  ```typescript
456
329
  const env = new Bash({
@@ -466,9 +339,9 @@ await env.exec('js-exec -c "console.log(API_BASE)"');
466
339
  // Output: https://api.example.com
467
340
  ```
468
341
 
469
- ### Node.js Compatibility
342
+ #### Node.js Compatibility
470
343
 
471
- `js-exec` provides broad Node.js compatibility. Both `require()` and `import` are supported:
344
+ `js-exec` supports `require()` and `import` with these Node.js modules:
472
345
 
473
346
  - **fs**: `readFileSync`, `writeFileSync`, `readdirSync`, `statSync`, `existsSync`, `mkdirSync`, `rmSync`, `fs.promises.*`
474
347
  - **path**: `join`, `resolve`, `dirname`, `basename`, `extname`, `relative`, `normalize`
@@ -479,11 +352,29 @@ await env.exec('js-exec -c "console.log(API_BASE)"');
479
352
 
480
353
  `fs.readFileSync()` returns a `Buffer` by default (matching Node.js). Pass an encoding like `'utf8'` to get a string.
481
354
 
482
- **Note:** The `js-exec` command only exists when `javascript` is configured. It is not available in browser environments. Execution runs in a QuickJS WASM sandbox with a 64 MB memory limit and configurable timeout (default: 10s, 60s with network). Requires Node.js >= 22.6 (uses `stripTypeScriptTypes` from `node:module`).
355
+ **Note:** The `js-exec` command only exists when `javascript` is configured. It is not available in browser environments. Execution runs in a QuickJS WASM sandbox with a 64 MB memory limit and configurable timeout (default: 10s, 60s with network).
356
+
357
+ ### Python Support
358
+
359
+ Python (CPython compiled to WASM) is opt-in due to additional security surface. Enable with `python: true`:
360
+
361
+ ```typescript
362
+ const env = new Bash({
363
+ python: true,
364
+ });
365
+
366
+ // Execute Python code
367
+ await env.exec('python3 -c "print(1 + 2)"');
368
+
369
+ // Run Python scripts
370
+ await env.exec('python3 script.py');
371
+ ```
483
372
 
484
- ## SQLite Support
373
+ **Note:** The `python3` and `python` commands only exist when `python: true` is configured. Python is not available in browser environments.
374
+
375
+ ### SQLite Support
485
376
 
486
- The `sqlite3` command uses sql.js (WASM-based SQLite) which is fully sandboxed and cannot access the real filesystem:
377
+ `sqlite3` uses sql.js (SQLite compiled to WASM), sandboxed from the real filesystem:
487
378
 
488
379
  ```typescript
489
380
  const env = new Bash();
@@ -497,27 +388,134 @@ await env.exec('sqlite3 data.db "SELECT * FROM users"');
497
388
 
498
389
  **Note:** SQLite is not available in browser environments. Queries run in a worker thread with a configurable timeout (default: 5 seconds) to prevent runaway queries from blocking execution.
499
390
 
500
- ### Allow-List Security
391
+ ## AST Transform Plugins
501
392
 
502
- The allow-list enforces:
393
+ Parse bash scripts into an AST, transform them, and serialize back to bash. Good for instrumenting scripts (e.g., capturing per-command stdout/stderr) or extracting metadata before execution.
503
394
 
504
- - **Origin matching**: URLs must match the exact origin (scheme + host + port)
505
- - **Path prefix**: Only paths starting with the specified prefix are allowed
506
- - **HTTP method restrictions**: Only GET and HEAD by default (configure `allowedMethods` for more)
507
- - **Redirect protection**: Redirects to non-allowed URLs are blocked
395
+ ```typescript
396
+ import { Bash, BashTransformPipeline, TeePlugin, CommandCollectorPlugin } from "just-bash";
508
397
 
509
- ### Using curl
398
+ // Standalone pipeline — output can be run by any shell
399
+ const pipeline = new BashTransformPipeline()
400
+ .use(new TeePlugin({ outputDir: "/tmp/logs" }))
401
+ .use(new CommandCollectorPlugin());
402
+ const result = pipeline.transform("echo hello | grep hello");
403
+ result.script; // transformed bash string
404
+ result.metadata.commands; // ["echo", "grep", "tee"]
405
+
406
+ // Integrated API — exec() auto-applies transforms and returns metadata
407
+ const bash = new Bash();
408
+ bash.registerTransformPlugin(new CommandCollectorPlugin());
409
+ const execResult = await bash.exec("echo hello | grep hello");
410
+ execResult.metadata?.commands; // ["echo", "grep"]
411
+ ```
412
+
413
+ See [src/transform/README.md](src/transform/README.md) for the full API, built-in plugins, and how to write custom plugins.
414
+
415
+ ## Integrations
416
+
417
+ ### AI SDK Tool
418
+
419
+ [`bash-tool`](https://github.com/vercel-labs/bash-tool) wraps just-bash as an [AI SDK](https://ai-sdk.dev/) tool:
510
420
 
511
421
  ```bash
512
- # Fetch and process data
513
- curl -s https://api.example.com/data | grep pattern
422
+ npm install bash-tool
423
+ ```
514
424
 
515
- # Download and convert HTML to Markdown
516
- curl -s https://example.com | html-to-markdown
425
+ ```typescript
426
+ import { createBashTool } from "bash-tool";
427
+ import { generateText } from "ai";
517
428
 
518
- # POST JSON data
519
- curl -X POST -H "Content-Type: application/json" \
520
- -d '{"key":"value"}' https://api.example.com/endpoint
429
+ const bashTool = createBashTool({
430
+ files: { "/data/users.json": '[{"name": "Alice"}, {"name": "Bob"}]' },
431
+ });
432
+
433
+ const result = await generateText({
434
+ model: "anthropic/claude-sonnet-4",
435
+ tools: { bash: bashTool },
436
+ prompt: "Count the users in /data/users.json",
437
+ });
438
+ ```
439
+
440
+ See [bash-tool](https://github.com/vercel-labs/bash-tool) for more.
441
+
442
+ ### Vercel Sandbox Compatible API
443
+
444
+ `Sandbox` is a drop-in replacement for [`@vercel/sandbox`](https://vercel.com/docs/vercel-sandbox) — same API, but runs entirely in-process with the virtual filesystem. Start with just-bash for development and testing, swap in a real sandbox when you need a full VM.
445
+
446
+ ```typescript
447
+ import { Sandbox } from "just-bash";
448
+
449
+ // Create a sandbox instance
450
+ const sandbox = await Sandbox.create({ cwd: "/app" });
451
+
452
+ // Write files to the virtual filesystem
453
+ await sandbox.writeFiles({
454
+ "/app/script.sh": 'echo "Hello World"',
455
+ "/app/data.json": '{"key": "value"}',
456
+ });
457
+
458
+ // Run commands and get results
459
+ const cmd = await sandbox.runCommand("bash /app/script.sh");
460
+ const output = await cmd.stdout(); // "Hello World\n"
461
+ const exitCode = (await cmd.wait()).exitCode; // 0
462
+
463
+ // Read files back
464
+ const content = await sandbox.readFile("/app/data.json");
465
+
466
+ // Create directories
467
+ await sandbox.mkDir("/app/logs", { recursive: true });
468
+
469
+ // Clean up (no-op for Bash, but API-compatible)
470
+ await sandbox.stop();
471
+ ```
472
+
473
+ ## CLI
474
+
475
+ ### CLI Binary
476
+
477
+ Install globally (`npm install -g just-bash`) for a sandboxed CLI:
478
+
479
+ ```bash
480
+ # Execute inline script
481
+ just-bash -c 'ls -la && cat package.json | head -5'
482
+
483
+ # Execute with specific project root
484
+ just-bash -c 'grep -r "TODO" src/' --root /path/to/project
485
+
486
+ # Pipe script from stdin
487
+ echo 'find . -name "*.ts" | wc -l' | just-bash
488
+
489
+ # Execute a script file
490
+ just-bash ./scripts/deploy.sh
491
+
492
+ # Get JSON output for programmatic use
493
+ just-bash -c 'echo hello' --json
494
+ # Output: {"stdout":"hello\n","stderr":"","exitCode":0}
495
+ ```
496
+
497
+ The CLI uses OverlayFS — reads come from the real filesystem, but all writes stay in memory and are discarded after execution.
498
+
499
+ **Important**: The project root is mounted at `/home/user/project`. Use this path (or relative paths from the default cwd) to access your files inside the sandbox.
500
+
501
+ Options:
502
+
503
+ - `-c <script>` - Execute script from argument
504
+ - `--root <path>` - Root directory (default: current directory)
505
+ - `--cwd <path>` - Working directory in sandbox
506
+ - `-e, --errexit` - Exit on first error
507
+ - `--json` - Output as JSON
508
+
509
+ ### Interactive Shell
510
+
511
+ ```bash
512
+ pnpm shell
513
+ ```
514
+
515
+ The interactive shell has full internet access by default. Disable with `--no-network`:
516
+
517
+ ```bash
518
+ pnpm shell --no-network
521
519
  ```
522
520
 
523
521
  ## Execution Protection
@@ -536,45 +534,35 @@ const env = new Bash({
536
534
  });
537
535
  ```
538
536
 
539
- All limits have sensible defaults. Error messages include hints on which limit to increase. Feel free to increase if your scripts intentionally go beyond them.
537
+ All limits have defaults. Error messages tell you which limit was hit. Increase as needed for your workload.
540
538
 
541
- ## AST Transform Plugins
539
+ ## Security Model
542
540
 
543
- Parse bash scripts into an AST, run transform plugins, and serialize back to executable bash. Useful for instrumenting scripts (e.g., capturing per-command stdout/stderr) or analyzing them (e.g., extracting command names) before execution.
541
+ - The shell only has access to the provided filesystem.
542
+ - All execution happens without VM isolation. This does introduce additional risk. The code base was designed to be robust against prototype-pollution attacks and other break outs to the host JS engine and filesystem.
543
+ - There is no network access by default. When enabled, requests are checked against URL prefix allow-lists and HTTP-method allow-lists.
544
+ - Python and JavaScript execution are off by default as they represent additional security surface.
545
+ - Execution is protected against infinite loops and deep recursion with configurable limits.
546
+ - Use [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox) if you need a full VM with arbitrary binary execution.
544
547
 
545
- ```typescript
546
- import { Bash, BashTransformPipeline, TeePlugin, CommandCollectorPlugin } from "just-bash";
548
+ ## Browser Support
547
549
 
548
- // Standalone pipeline output can be run by any shell
549
- const pipeline = new BashTransformPipeline()
550
- .use(new TeePlugin({ outputDir: "/tmp/logs" }))
551
- .use(new CommandCollectorPlugin());
552
- const result = pipeline.transform("echo hello | grep hello");
553
- result.script; // transformed bash string
554
- result.metadata.commands; // ["echo", "grep", "tee"]
550
+ The core shell (parsing, execution, filesystem, and all built-in commands) works in browser environments. The following features require Node.js and are unavailable in browsers: `python3`/`python`, `sqlite3`, `js-exec`, and `OverlayFs`/`ReadWriteFs` (which access the real filesystem).
555
551
 
556
- // Integrated API — exec() auto-applies transforms and returns metadata
557
- const bash = new Bash();
558
- bash.registerTransformPlugin(new CommandCollectorPlugin());
559
- const execResult = await bash.exec("echo hello | grep hello");
560
- execResult.metadata?.commands; // ["echo", "grep"]
561
- ```
552
+ ## Default Layout
562
553
 
563
- See [src/transform/README.md](src/transform/README.md) for the full API, built-in plugins, and how to write custom plugins.
554
+ When created without options, Bash provides a Unix-like directory structure:
564
555
 
565
- ## Development
556
+ - `/home/user` - Default working directory (and `$HOME`)
557
+ - `/bin` - Contains stubs for all built-in commands
558
+ - `/usr/bin` - Additional binary directory
559
+ - `/tmp` - Temporary files directory
566
560
 
567
- ```bash
568
- pnpm test # Run tests in watch mode
569
- pnpm test:run # Run tests once
570
- pnpm typecheck # Type check without emitting
571
- pnpm build # Build TypeScript
572
- pnpm shell # Run interactive shell
573
- ```
561
+ Commands can be invoked by path (e.g., `/bin/ls`) or by name.
574
562
 
575
563
  ## AI Agent Instructions
576
564
 
577
- For AI agents, we recommend using [`bash-tool`](https://github.com/vercel-labs/bash-tool) which is optimized for just-bash and provides additional guidance in its `AGENTS.md`:
565
+ For AI agents, [`bash-tool`](https://github.com/vercel-labs/bash-tool) provides additional guidance in its `AGENTS.md`:
578
566
 
579
567
  ```bash
580
568
  cat node_modules/bash-tool/dist/AGENTS.md