just-bash 1.4.2 → 1.5.3

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.
package/README.md CHANGED
@@ -239,6 +239,10 @@ pnpm shell --no-network
239
239
 
240
240
  `awk`, `base64`, `comm`, `cut`, `diff`, `grep` (+ `egrep`, `fgrep`), `head`, `jq`, `md5sum`, `od`, `paste`, `printf`, `sed`, `sha1sum`, `sha256sum`, `sort`, `tac`, `tail`, `tr`, `uniq`, `wc`, `xargs`
241
241
 
242
+ ### Compression
243
+
244
+ `gzip` (+ `gunzip`, `zcat`)
245
+
242
246
  ### Navigation & Environment
243
247
 
244
248
  `basename`, `cd`, `dirname`, `du`, `echo`, `env`, `export`, `find`, `hostname`, `printenv`, `pwd`, `tee`
@@ -361,6 +365,16 @@ pnpm build # Build TypeScript
361
365
  pnpm shell # Run interactive shell
362
366
  ```
363
367
 
368
+ ## AI Agent Instructions
369
+
370
+ For AI agents working with just-bash, additional guidance is available in `AGENTS.md`:
371
+
372
+ ```bash
373
+ cat node_modules/just-bash/dist/AGENTS.md
374
+ ```
375
+
376
+ This file contains quick reference patterns, common pitfalls, and debugging tips specifically for AI agents.
377
+
364
378
  ## License
365
379
 
366
380
  Apache-2.0
package/dist/AGENTS.md ADDED
@@ -0,0 +1,202 @@
1
+
2
+ # AGENTS.md - just-bash
3
+
4
+ Instructions for AI agents using just-bash in projects.
5
+
6
+ ## What is just-bash?
7
+
8
+ A sandboxed bash interpreter with an in-memory virtual filesystem. Use it when you need to:
9
+
10
+ - Execute shell commands without real filesystem access
11
+ - Provide a bash tool for AI agents
12
+ - Run untrusted scripts safely
13
+ - Process text with standard Unix tools (grep, sed, awk, jq, etc.)
14
+
15
+ ## Quick Reference
16
+
17
+ ```typescript
18
+ import { Bash } from "just-bash";
19
+
20
+ const bash = new Bash({
21
+ files: { "/data/input.txt": "content" }, // Initial files
22
+ cwd: "/data", // Working directory
23
+ });
24
+
25
+ const result = await bash.exec("cat input.txt | grep pattern");
26
+ // result.stdout - command output
27
+ // result.stderr - error output
28
+ // result.exitCode - 0 = success, non-zero = failure
29
+ ```
30
+
31
+ ## Building an AI Agent
32
+
33
+ just-bash integrates with the [AI SDK](https://ai-sdk.dev/) to provide a bash tool for AI agents.
34
+
35
+ ### Basic Agent Setup
36
+
37
+ ```typescript
38
+ import { createBashTool } from "just-bash/ai";
39
+ import { generateText } from "ai";
40
+
41
+ const bashTool = createBashTool({
42
+ files: {
43
+ "/data/users.json": '[{"name": "Alice"}, {"name": "Bob"}]',
44
+ "/data/config.yaml": "debug: true\nport: 3000",
45
+ },
46
+ });
47
+
48
+ const result = await generateText({
49
+ model: "anthropic/claude-haiku-4.5",
50
+ tools: { bash: bashTool },
51
+ prompt: "Count the users in /data/users.json",
52
+ });
53
+ ```
54
+
55
+ ### With ToolLoopAgent
56
+
57
+ For multi-step tasks, use `ToolLoopAgent` which automatically loops until completion:
58
+
59
+ ```typescript
60
+ import { ToolLoopAgent } from "ai";
61
+ import { createBashTool } from "just-bash/ai";
62
+
63
+ const bashTool = createBashTool({
64
+ files: {
65
+ "/project/src/index.ts": "export const version = '1.0.0';",
66
+ "/project/src/utils.ts": "// TODO: implement\nexport function helper() {}",
67
+ "/project/package.json": '{"name": "my-app", "version": "1.0.0"}',
68
+ },
69
+ });
70
+
71
+ const agent = new ToolLoopAgent({
72
+ model: "anthropic/claude-haiku-4.5",
73
+ tools: { bash: bashTool },
74
+ });
75
+
76
+ const result = await agent.generate({
77
+ prompt: "Find all TODO comments in the project and list the files containing them",
78
+ });
79
+ ```
80
+
81
+ ### With Network Access
82
+
83
+ ```typescript
84
+ import { createBashTool } from "just-bash/ai";
85
+
86
+ const bashTool = createBashTool({
87
+ network: {
88
+ allowedUrlPrefixes: ["https://api.github.com/"],
89
+ },
90
+ });
91
+ ```
92
+
93
+ ### With Real Filesystem (Read-Only)
94
+
95
+ ```typescript
96
+ import { createBashTool } from "just-bash/ai";
97
+ import { OverlayFs } from "just-bash/fs/overlay-fs";
98
+
99
+ const overlay = new OverlayFs({ root: "/path/to/project" });
100
+ const bashTool = createBashTool({
101
+ fs: overlay,
102
+ cwd: overlay.getMountPoint(),
103
+ });
104
+ ```
105
+
106
+ ### Tool Options
107
+
108
+ - `files` - Initial virtual files
109
+ - `fs` - Custom filesystem (e.g., OverlayFs)
110
+ - `network` - URL allowlist for curl
111
+ - `commands` - Restrict available commands
112
+ - `customCommands` - Add custom commands
113
+ - `onCall` - Callback before each execution
114
+ - `logger` - Execution tracing
115
+
116
+ ## Key Behaviors
117
+
118
+ 1. **Isolation**: Each `exec()` call is isolated. Environment variables, functions, and cwd changes don't persist between calls. Only filesystem changes persist.
119
+
120
+ 2. **No real filesystem**: By default, commands only see the virtual filesystem. Use `OverlayFs` to read from a real directory (writes stay in memory).
121
+
122
+ 3. **No network by default**: `curl` doesn't exist unless you configure `network` options with URL allowlists.
123
+
124
+ 4. **No binaries/WASM**: Only built-in commands work. You cannot run node, python, or other binaries.
125
+
126
+ ## Available Commands
127
+
128
+ **Text processing**: `awk`, `cat`, `cut`, `grep`, `head`, `jq`, `sed`, `sort`, `tail`, `tr`, `uniq`, `wc`, `xargs`
129
+
130
+ **File operations**: `cp`, `find`, `ls`, `mkdir`, `mv`, `rm`, `touch`, `tree`
131
+
132
+ **Utilities**: `base64`, `date`, `diff`, `echo`, `env`, `printf`, `seq`, `tee`
133
+
134
+ All commands support `--help` for usage details.
135
+
136
+ ## Common Patterns
137
+
138
+ ### Process JSON with jq
139
+
140
+ ```bash
141
+ cat data.json | jq '.items[] | select(.active) | .name'
142
+ ```
143
+
144
+ ### Find and process files
145
+
146
+ ```bash
147
+ find . -name "*.ts" -type f | xargs grep -l "TODO"
148
+ ```
149
+
150
+ ### Text transformation pipeline
151
+
152
+ ```bash
153
+ cat input.txt | grep -v "^#" | sort | uniq -c | sort -rn | head -10
154
+ ```
155
+
156
+ ### AWK for columnar data
157
+
158
+ ```bash
159
+ cat data.csv | awk -F',' '{sum += $3} END {print sum}'
160
+ ```
161
+
162
+ ## Limitations
163
+
164
+ - **32-bit integers only**: Arithmetic operations use 32-bit signed integers
165
+ - **No job control**: No `&`, `bg`, `fg`, or process suspension
166
+ - **No external binaries**: Only built-in commands are available
167
+ - **Execution limits**: Loops, recursion, and command counts have configurable limits to prevent runaway execution
168
+
169
+ ## Error Handling
170
+
171
+ Always check `exitCode`:
172
+
173
+ ```typescript
174
+ import { Bash } from "just-bash";
175
+
176
+ const bash = new Bash({ files: { "/file.txt": "some content" } });
177
+ const result = await bash.exec("grep pattern file.txt");
178
+ if (result.exitCode !== 0) {
179
+ // Command failed - check result.stderr for details
180
+ }
181
+ ```
182
+
183
+ Common exit codes:
184
+
185
+ - `0` - Success
186
+ - `1` - General error or no matches (grep)
187
+ - `2` - Misuse of command (invalid options)
188
+ - `127` - Command not found
189
+
190
+ ## Debugging Tips
191
+
192
+ 1. **Check stderr**: Error messages go to `result.stderr`
193
+ 2. **Use --help**: All commands support `--help` for usage
194
+ 3. **Test incrementally**: Build pipelines step by step
195
+ 4. **Quote variables**: Use `"$var"` to handle spaces in values
196
+
197
+ ## Security Model
198
+
199
+ - Virtual filesystem is isolated from the real system
200
+ - Network access requires explicit URL allowlists
201
+ - Execution limits prevent infinite loops
202
+ - No shell injection possible (commands are parsed, not eval'd)
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import{a as z}from"./chunk-TA7RUHGQ.js";import{a as w,b as $}from"./chunk-GTNBSMZR.js";import"./chunk-SJXDWN5X.js";import{constants as x,gunzipSync as C,gzipSync as b}from"node:zlib";var I={name:"gzip",summary:"compress or expand files",usage:"gzip [OPTION]... [FILE]...",description:`Compress FILEs (by default, in-place).
3
+
4
+ When no FILE is given, or when FILE is -, read from standard input.
5
+
6
+ With -d, decompress instead.`,options:["-c, --stdout write to standard output, keep original files","-d, --decompress decompress","-f, --force force overwrite of output file","-k, --keep keep (don't delete) input files","-l, --list list compressed file contents","-n, --no-name do not save or restore the original name and timestamp","-N, --name save or restore the original file name and timestamp","-q, --quiet suppress all warnings","-r, --recursive operate recursively on directories","-S, --suffix=SUF use suffix SUF on compressed files (default: .gz)","-t, --test test compressed file integrity","-v, --verbose verbose mode","-1, --fast compress faster","-9, --best compress better"," --help display this help and exit"]},q={name:"gunzip",summary:"decompress files",usage:"gunzip [OPTION]... [FILE]...",description:`Decompress FILEs (by default, in-place).
7
+
8
+ When no FILE is given, or when FILE is -, read from standard input.`,options:["-c, --stdout write to standard output, keep original files","-f, --force force overwrite of output file","-k, --keep keep (don't delete) input files","-l, --list list compressed file contents","-n, --no-name do not restore the original name and timestamp","-N, --name restore the original file name and timestamp","-q, --quiet suppress all warnings","-r, --recursive operate recursively on directories","-S, --suffix=SUF use suffix SUF on compressed files (default: .gz)","-t, --test test compressed file integrity","-v, --verbose verbose mode"," --help display this help and exit"]},P={name:"zcat",summary:"decompress files to stdout",usage:"zcat [OPTION]... [FILE]...",description:`Decompress FILEs to standard output.
9
+
10
+ When no FILE is given, or when FILE is -, read from standard input.`,options:["-f, --force force; read compressed data even from a terminal","-l, --list list compressed file contents","-q, --quiet suppress all warnings","-S, --suffix=SUF use suffix SUF on compressed files (default: .gz)","-t, --test test compressed file integrity","-v, --verbose verbose mode"," --help display this help and exit"]},D={stdout:{short:"c",long:"stdout",type:"boolean"},toStdout:{long:"to-stdout",type:"boolean"},decompress:{short:"d",long:"decompress",type:"boolean"},uncompress:{long:"uncompress",type:"boolean"},force:{short:"f",long:"force",type:"boolean"},keep:{short:"k",long:"keep",type:"boolean"},list:{short:"l",long:"list",type:"boolean"},noName:{short:"n",long:"no-name",type:"boolean"},name:{short:"N",long:"name",type:"boolean"},quiet:{short:"q",long:"quiet",type:"boolean"},recursive:{short:"r",long:"recursive",type:"boolean"},suffix:{short:"S",long:"suffix",type:"string",default:".gz"},test:{short:"t",long:"test",type:"boolean"},verbose:{short:"v",long:"verbose",type:"boolean"},fast:{short:"1",long:"fast",type:"boolean"},level2:{short:"2",type:"boolean"},level3:{short:"3",type:"boolean"},level4:{short:"4",type:"boolean"},level5:{short:"5",type:"boolean"},level6:{short:"6",type:"boolean"},level7:{short:"7",type:"boolean"},level8:{short:"8",type:"boolean"},best:{short:"9",long:"best",type:"boolean"}};function F(e){return e.best?x.Z_BEST_COMPRESSION:e.level8?8:e.level7?7:e.level6?6:e.level5?5:e.level4?4:e.level3?3:e.level2?2:e.fast?x.Z_BEST_SPEED:x.Z_DEFAULT_COMPRESSION}function S(e){if(e.length<10)return{originalName:null,mtime:null,headerSize:0};if(e[0]!==31||e[1]!==139)return{originalName:null,mtime:null,headerSize:0};let t=e[3],o=e[4]|e[5]<<8|e[6]<<16|e[7]<<24,r=10;if(t&4){if(r+2>e.length)return{originalName:null,mtime:null,headerSize:0};let n=e[r]|e[r+1]<<8;r+=2+n}let s=null;if(t&8){let n=r;for(;r<e.length&&e[r]!==0;)r++;r<e.length&&(s=new TextDecoder().decode(e.slice(n,r)),r++)}if(t&16){for(;r<e.length&&e[r]!==0;)r++;r++}return t&2&&(r+=2),{originalName:s,mtime:o>0?new Date(o*1e3):null,headerSize:r}}function L(e){if(e.length<4)return 0;let t=e.length;return e[t-4]|e[t-3]<<8|e[t-2]<<16|e[t-1]<<24}function y(e){return e.length>=2&&e[0]===31&&e[1]===139}async function E(e,t,o,r,s,n){let u=o.suffix,c,p,d;if(t==="-"||t==="")if(d=new TextEncoder().encode(e.stdin),s){if(!y(d))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: stdin: not in gzip format
11
+ `,exitCode:1};try{let l=C(d);return{stdout:new TextDecoder().decode(l),stderr:"",exitCode:0}}catch(l){let i=l instanceof Error?l.message:"unknown error";return{stdout:"",stderr:`${r}: stdin: ${i}
12
+ `,exitCode:1}}}else{let l=F(o),i=b(d,{level:l});return{stdout:String.fromCharCode(...i),stderr:"",exitCode:0}}c=e.fs.resolvePath(e.cwd,t);try{if((await e.fs.stat(c)).isDirectory)return o.recursive?await k(e,c,o,r,s,n):o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t}: is a directory -- ignored
13
+ `,exitCode:1}}catch{return{stdout:"",stderr:`${r}: ${t}: No such file or directory
14
+ `,exitCode:1}}try{d=await e.fs.readFileBuffer(c)}catch{return{stdout:"",stderr:`${r}: ${t}: No such file or directory
15
+ `,exitCode:1}}if(s){if(!t.endsWith(u))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t}: unknown suffix -- ignored
16
+ `,exitCode:1};if(!y(d))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t}: not in gzip format
17
+ `,exitCode:1};let l;try{l=C(d)}catch(i){let f=i instanceof Error?i.message:"unknown error";return{stdout:"",stderr:`${r}: ${t}: ${f}
18
+ `,exitCode:1}}if(n)return{stdout:new TextDecoder().decode(l),stderr:"",exitCode:0};if(o.name){let i=S(d);i.originalName?p=e.fs.resolvePath(e.cwd,i.originalName):p=c.slice(0,-u.length)}else p=c.slice(0,-u.length);if(!o.force)try{return await e.fs.stat(p),{stdout:"",stderr:`${r}: ${p} already exists; not overwritten
19
+ `,exitCode:1}}catch{}if(await e.fs.writeFile(p,l),!o.keep&&!n&&await e.fs.rm(c),o.verbose){let i=d.length>0?((1-d.length/l.length)*100).toFixed(1):"0.0";return{stdout:"",stderr:`${t}: ${i}% -- replaced with ${p.split("/").pop()}
20
+ `,exitCode:0}}return{stdout:"",stderr:"",exitCode:0}}else{if(t.endsWith(u))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t} already has ${u} suffix -- unchanged
21
+ `,exitCode:1};let l=F(o),i;try{i=b(d,{level:l})}catch(f){let a=f instanceof Error?f.message:"unknown error";return{stdout:"",stderr:`${r}: ${t}: ${a}
22
+ `,exitCode:1}}if(n)return{stdout:String.fromCharCode(...i),stderr:"",exitCode:0};if(p=c+u,!o.force)try{return await e.fs.stat(p),{stdout:"",stderr:`${r}: ${p} already exists; not overwritten
23
+ `,exitCode:1}}catch{}if(await e.fs.writeFile(p,i),!o.keep&&!n&&await e.fs.rm(c),o.verbose){let f=d.length>0?((1-i.length/d.length)*100).toFixed(1):"0.0";return{stdout:"",stderr:`${t}: ${f}% -- replaced with ${p.split("/").pop()}
24
+ `,exitCode:0}}return{stdout:"",stderr:"",exitCode:0}}}async function k(e,t,o,r,s,n){let u=await e.fs.readdir(t),c="",p="",d=0;for(let l of u){let i=e.fs.resolvePath(t,l),f=await e.fs.stat(i);if(f.isDirectory){let a=await k(e,i,o,r,s,n);c+=a.stdout,p+=a.stderr,a.exitCode!==0&&(d=a.exitCode)}else if(f.isFile){let a=o.suffix;if(s&&!l.endsWith(a)||!s&&l.endsWith(a))continue;let m=i.startsWith(`${e.cwd}/`)?i.slice(e.cwd.length+1):i,g=await E(e,m,o,r,s,n);c+=g.stdout,p+=g.stderr,g.exitCode!==0&&(d=g.exitCode)}}return{stdout:c,stderr:p,exitCode:d}}async function T(e,t,o,r){let s;if(t==="-"||t==="")s=new TextEncoder().encode(e.stdin);else{let i=e.fs.resolvePath(e.cwd,t);try{s=await e.fs.readFileBuffer(i)}catch{return{stdout:"",stderr:`${r}: ${t}: No such file or directory
25
+ `,exitCode:1}}}if(!y(s))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t}: not in gzip format
26
+ `,exitCode:1};let n=s.length,u=L(s),c=u>0?((1-n/u)*100).toFixed(1):"0.0",d=S(s).originalName||(t==="-"?"":t.replace(/\.gz$/,""));return{stdout:`${n.toString().padStart(10)} ${u.toString().padStart(10)} ${c.padStart(5)}% ${d}
27
+ `,stderr:"",exitCode:0}}async function O(e,t,o,r){let s;if(t==="-"||t==="")s=new TextEncoder().encode(e.stdin);else{let n=e.fs.resolvePath(e.cwd,t);try{s=await e.fs.readFileBuffer(n)}catch{return{stdout:"",stderr:`${r}: ${t}: No such file or directory
28
+ `,exitCode:1}}}if(!y(s))return o.quiet?{stdout:"",stderr:"",exitCode:1}:{stdout:"",stderr:`${r}: ${t}: not in gzip format
29
+ `,exitCode:1};try{return C(s),o.verbose?{stdout:"",stderr:`${t}: OK
30
+ `,exitCode:0}:{stdout:"",stderr:"",exitCode:0}}catch(n){let u=n instanceof Error?n.message:"invalid";return{stdout:"",stderr:`${r}: ${t}: ${u}
31
+ `,exitCode:1}}}async function v(e,t,o){let r=o==="zcat"?P:o==="gunzip"?q:I;if($(e))return w(r);let s=z(o,e,D);if(!s.ok)return s.error.stderr.includes("unrecognized option"),s.error;let n=s.result.flags,u=s.result.positional,c=o==="gunzip"||o==="zcat"||n.decompress||n.uncompress,p=o==="zcat"||n.stdout||n.toStdout;if(n.list){u.length===0&&(u=["-"]);let f=` compressed uncompressed ratio uncompressed_name
32
+ `,a="",m=0;for(let g of u){let h=await T(t,g,n,o);f+=h.stdout,a+=h.stderr,h.exitCode!==0&&(m=h.exitCode)}return{stdout:f,stderr:a,exitCode:m}}if(n.test){u.length===0&&(u=["-"]);let f="",a="",m=0;for(let g of u){let h=await O(t,g,n,o);f+=h.stdout,a+=h.stderr,h.exitCode!==0&&(m=h.exitCode)}return{stdout:f,stderr:a,exitCode:m}}u.length===0&&(u=["-"]);let d="",l="",i=0;for(let f of u){let a=await E(t,f,n,o,c,p);d+=a.stdout,l+=a.stderr,a.exitCode!==0&&(i=a.exitCode)}return{stdout:d,stderr:l,exitCode:i}}var H={name:"gzip",async execute(e,t){return v(e,t,"gzip")}},B={name:"gunzip",async execute(e,t){return v(e,t,"gunzip")}},G={name:"zcat",async execute(e,t){return v(e,t,"zcat")}};export{B as gunzipCommand,H as gzipCommand,G as zcatCommand};