boxsh.js 0.1.0 → 1.0.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.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # boxsh.js
2
2
 
3
- Node.js SDK for [boxsh](../../README.md) — a sandboxed POSIX shell with Linux namespace isolation and copy-on-write overlay filesystem.
3
+ Node.js SDK for [boxsh](../../README.md) — a sandboxed POSIX shell with OS-native isolation and copy-on-write overlay filesystem.
4
4
 
5
5
  boxsh.js lets you drive a long-lived boxsh instance from Node.js: execute shell commands, read/write files, and perform search-and-replace edits — all inside an isolated sandbox.
6
6
 
7
- **Requirements:** Node.js ≥ 18, Linux, `boxsh` binary on `$PATH` (or set `BOXSH` env var).
7
+ **Requirements:** Node.js ≥ 18, Linux or macOS, `boxsh` binary on `$PATH` (or set `BOXSH` env var).
8
8
 
9
9
  ## Install
10
10
 
11
11
  ```sh
12
- npm install ./sdk/js
12
+ npm install boxsh.js
13
13
  ```
14
14
 
15
15
  ---
@@ -82,72 +82,64 @@ console.log(diff); // unified diff format
82
82
 
83
83
  ## Sandbox isolation
84
84
 
85
- With `sandbox` enabled, commands run inside isolated Linux namespaces (user, mount), separated from the host. You can further isolate the network and PID tree:
85
+ With `sandbox` enabled, commands run inside an OS-native sandbox (Linux namespaces or macOS Seatbelt), separated from the host. You can further isolate the network:
86
86
 
87
87
  ```js
88
88
  const client = new BoxshClient({
89
89
  sandbox: true,
90
90
  newNetNs: true, // Isolated network namespace (no external access)
91
- newPidNs: true, // Isolated PID namespace
92
91
  });
93
92
  ```
94
93
 
95
94
  ---
96
95
 
97
- ## Overlay filesystem
96
+ ## COW Bind (Overlay Filesystem)
98
97
 
99
- Overlay is the primary usage pattern for boxsh: mount a read-only base directory as a copy-on-write workspace. Commands can read and write freely, but all modifications land in the upper directory while the base remains untouched.
98
+ COW bind is the primary usage pattern for boxsh: mount a read-only source directory as a copy-on-write workspace. Commands can read and write freely, but all modifications land in the destination directory while the source remains untouched.
100
99
 
101
100
  ```
102
- Overlay parameters:
103
- lower Read-only base directory (your project/repository)
104
- upper Writable upper directory (all modifications go here)
105
- work Working directory required by overlayfs (must be on the same filesystem as upper)
106
- dst Mount point (the path visible inside the sandbox)
101
+ Bind parameters:
102
+ src Read-only base directory (your project/repository)
103
+ dst Writable destination directory (all modifications go here)
107
104
  ```
108
105
 
109
106
  ```js
110
107
  import { BoxshClient } from 'boxsh.js';
111
108
  import fs from 'node:fs';
112
109
 
113
- // Prepare overlay directories
114
- const upper = '/tmp/sandbox/upper';
115
- const work = '/tmp/sandbox/work';
116
- const mnt = '/tmp/sandbox/mnt';
117
- fs.mkdirSync(upper, { recursive: true });
118
- fs.mkdirSync(work, { recursive: true });
119
- fs.mkdirSync(mnt, { recursive: true });
110
+ // Prepare destination directory
111
+ const dst = '/tmp/sandbox/dst';
112
+ fs.mkdirSync(dst, { recursive: true });
120
113
 
121
114
  const client = new BoxshClient({
122
115
  sandbox: true,
123
- overlay: {
124
- lower: '/home/user/myproject', // read-only base
125
- upper, // modifications land here
126
- work,
127
- dst: mnt, // mount point inside the sandbox
128
- },
116
+ binds: [{
117
+ mode: 'cow',
118
+ src: '/home/user/myproject', // read-only base
119
+ dst, // modifications land here
120
+ }],
129
121
  });
130
122
 
131
- // Inside the sandbox, /tmp/sandbox/mnt is a COW copy of myproject
132
- await client.exec('npm install', mnt);
123
+ // Inside the sandbox, dst is a COW copy of myproject
124
+ await client.exec('npm install', dst);
133
125
 
134
126
  // Read/write files via built-in tools (RPC, no shell round-trip needed)
135
- const pkg = await client.read(`${mnt}/package.json`);
136
- await client.write(`${mnt}/result.txt`, 'done\n');
127
+ const pkg = await client.read(`${dst}/package.json`);
128
+ await client.write(`${dst}/result.txt`, 'done\n');
137
129
 
138
130
  await client.close();
139
131
 
140
- // At this point upper/ contains all modifications; base is completely untouched.
141
- // You can commit, archive, or simply delete upper/ to discard changes.
132
+ // At this point dst/ contains all modifications; base is completely untouched.
133
+ // You can commit, archive, or simply delete dst/ to discard changes.
142
134
  ```
143
135
 
144
- The upper directory persists across sessions. To resume a previous session, create a new BoxshClient pointing at the same `upper`/`work`/`mnt` directories.
136
+ The destination directory persists across sessions. To resume a previous session, create a new BoxshClient pointing at the same `dst` directory.
145
137
 
146
138
  ---
147
139
 
148
140
  ## Inspecting changes
149
141
 
150
- `getChanges` scans the overlay's upper directory against the base and returns all added, modified, and deleted files. `formatChanges` formats the result as human-readable text.
142
+ `getChanges` scans the COW destination directory against the base and returns all added, modified, and deleted files. `formatChanges` formats the result as human-readable text.
151
143
 
152
144
  Both functions run on the host side (inside the Node.js process) and do not require a running boxsh instance.
153
145
 
@@ -155,7 +147,7 @@ Both functions run on the host side (inside the Node.js process) and do not requ
155
147
  import { getChanges, formatChanges } from 'boxsh.js';
156
148
 
157
149
  const changes = getChanges({
158
- upper: '/tmp/sandbox/upper',
150
+ upper: '/tmp/sandbox/dst',
159
151
  base: '/home/user/myproject',
160
152
  });
161
153
  // [{ path: 'package-lock.json', type: 'modified' },
@@ -194,8 +186,7 @@ await client.exec(`echo ${shellQuote(userInput)}`);
194
186
  | `workers` | `number` | `1` | Number of pre-forked workers |
195
187
  | `sandbox` | `boolean` | `false` | Enable namespace sandbox |
196
188
  | `newNetNs` | `boolean` | `false` | Isolate network |
197
- | `newPidNs` | `boolean` | `false` | Isolate PID tree |
198
- | `overlay` | `{ lower, upper, work, dst }` | — | Overlay mount configuration |
189
+ | `binds` | `BoxshBindOption[]` | | Bind mount configuration (ro/wr/cow) |
199
190
 
200
191
  ### `client.exec(cmd, cwd?, timeout?) → Promise<{ exitCode, stdout, stderr }>`
201
192
 
@@ -223,7 +214,7 @@ Send SIGTERM immediately.
223
214
 
224
215
  ### `getChanges({ upper, base }) → Array<{ path, type }>`
225
216
 
226
- Scan the upper directory and return a list of changes relative to base. `type` is `'added'`, `'modified'`, or `'deleted'`.
217
+ Scan the destination directory and return a list of changes relative to base. `type` is `'added'`, `'modified'`, or `'deleted'`.
227
218
 
228
219
  ### `formatChanges(changes) → string`
229
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boxsh.js",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Node.js SDK for boxsh — sandboxed shell execution with overlay copy-on-write and JSON-line RPC",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
package/src/client.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * BoxshClient — manages a long-lived boxsh RPC process.
3
3
  *
4
- * Spawns boxsh with --rpc and optional --sandbox/--overlay flags.
4
+ * Spawns boxsh with --rpc and optional --sandbox/--bind flags.
5
5
  * All commands and tool calls are sent as JSON lines to stdin and
6
6
  * responses are read back as JSON lines from stdout.
7
7
  *
@@ -36,12 +36,11 @@ export class BoxshClient {
36
36
 
37
37
  /**
38
38
  * @param {object} [options]
39
- * @param {string} [options.boxshPath] Path to boxsh binary (default: BOXSH env var → 'boxsh')
39
+ * @param {string} [options.boxshPath] Path to boxsh binary (default: BOXSH env var → 'boxsh' in PATH)
40
40
  * @param {number} [options.workers] Worker count (default: 1)
41
41
  * @param {boolean} [options.sandbox] Enable --sandbox flag
42
42
  * @param {boolean} [options.newNetNs] Enable --new-net-ns flag
43
- * @param {boolean} [options.newPidNs] Enable --new-pid-ns flag
44
- * @param {{ lower: string, upper: string, work: string, dst: string }} [options.overlay]
43
+ * @param {Array<{ mode: 'ro'|'wr', path: string } | { mode: 'cow', src: string, dst: string }>} [options.binds]
45
44
  */
46
45
  constructor(options = {}) {
47
46
  const boxsh = options.boxshPath ?? process.env['BOXSH'] ?? 'boxsh';
@@ -49,10 +48,14 @@ export class BoxshClient {
49
48
 
50
49
  if (options.sandbox) args.push('--sandbox');
51
50
  if (options.newNetNs) args.push('--new-net-ns');
52
- if (options.newPidNs) args.push('--new-pid-ns');
53
- if (options.overlay) {
54
- const { lower, upper, work, dst } = options.overlay;
55
- args.push('--overlay', `${lower}:${upper}:${work}:${dst}`);
51
+ if (options.binds) {
52
+ for (const b of options.binds) {
53
+ if (b.mode === 'cow') {
54
+ args.push('--bind', `cow:${b.src}:${b.dst}`);
55
+ } else {
56
+ args.push('--bind', `${b.mode}:${b.path}`);
57
+ }
58
+ }
56
59
  }
57
60
 
58
61
  this.#proc = spawn(boxsh, args, { stdio: ['pipe', 'pipe', 'inherit'] });
package/src/index.d.ts CHANGED
@@ -1,17 +1,14 @@
1
- export interface BoxshOverlayOptions {
2
- lower: string;
3
- upper: string;
4
- work: string;
5
- dst: string;
6
- }
1
+ export type BoxshBindOption =
2
+ | { mode: 'ro'; path: string }
3
+ | { mode: 'wr'; path: string }
4
+ | { mode: 'cow'; src: string; dst: string };
7
5
 
8
6
  export interface BoxshClientOptions {
9
7
  boxshPath?: string;
10
8
  workers?: number;
11
9
  sandbox?: boolean;
12
10
  newNetNs?: boolean;
13
- newPidNs?: boolean;
14
- overlay?: BoxshOverlayOptions;
11
+ binds?: BoxshBindOption[];
15
12
  }
16
13
 
17
14
  export interface ExecResult {
package/src/exec/boxsh DELETED
Binary file