clabox 0.0.1 โ 0.1.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/LICENSE +21 -0
- package/README.md +219 -0
- package/clabox.config.example.mjs +90 -0
- package/docs/guideline.md +196 -0
- package/docs/logo.png +0 -0
- package/lib/aliases-DKGcMHHe.js +60 -0
- package/lib/aliases-DKGcMHHe.js.map +1 -0
- package/lib/aliases-DXyz-ufw.d.ts +31 -0
- package/lib/app-CieBa29D.js +246 -0
- package/lib/app-CieBa29D.js.map +1 -0
- package/lib/app-CpuMtOoj.d.ts +30 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +71 -0
- package/lib/cli.js.map +1 -0
- package/lib/config-BQ44iVWT.js +155 -0
- package/lib/config-BQ44iVWT.js.map +1 -0
- package/lib/config-DQWueb4a.d.ts +134 -0
- package/lib/ghostty-Ca0g9P9P.js +74 -0
- package/lib/ghostty-Ca0g9P9P.js.map +1 -0
- package/lib/ghostty-DemKkfqf.d.ts +34 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +9 -0
- package/lib/init/aliases.d.ts +2 -0
- package/lib/init/aliases.js +2 -0
- package/lib/init/app.d.ts +2 -0
- package/lib/init/app.js +2 -0
- package/lib/init/ghostty.d.ts +2 -0
- package/lib/init/ghostty.js +2 -0
- package/lib/init/raycast.d.ts +2 -0
- package/lib/init/raycast.js +2 -0
- package/lib/init/scaffold.d.ts +2 -0
- package/lib/init/scaffold.js +2 -0
- package/lib/profile-BeM41NXc.d.ts +29 -0
- package/lib/profile-DM6NAgb-.js +100 -0
- package/lib/profile-DM6NAgb-.js.map +1 -0
- package/lib/raycast-BCdO2Se1.js +35 -0
- package/lib/raycast-BCdO2Se1.js.map +1 -0
- package/lib/raycast-DM7c559f.d.ts +22 -0
- package/lib/run-CNehSQ-S.js +113 -0
- package/lib/run-CNehSQ-S.js.map +1 -0
- package/lib/run-Cx8cuTh5.d.ts +25 -0
- package/lib/sandbox/profile.d.ts +2 -0
- package/lib/sandbox/profile.js +2 -0
- package/lib/sandbox/run.d.ts +2 -0
- package/lib/sandbox/run.js +2 -0
- package/lib/scaffold-ByIbYAeS.d.ts +46 -0
- package/lib/scaffold-CRzC5KYe.js +141 -0
- package/lib/scaffold-CRzC5KYe.js.map +1 -0
- package/lib/utils/config.d.ts +2 -0
- package/lib/utils/config.js +2 -0
- package/package.json +141 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Suvorov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# ๐ฆ clabox
|
|
2
|
+
|
|
3
|
+
[](https://github.com/lskjs)
|
|
4
|
+
[](https://www.npmjs.com/package/clabox)
|
|
5
|
+
[](https://www.npmjs.com/package/clabox)
|
|
6
|
+
[](https://www.npmjs.com/package/clabox)
|
|
7
|
+
[](https://www.npmjs.com/package/clabox)
|
|
8
|
+
[](https://github.com/ycmds/clabox/blob/main/LICENSE)
|
|
9
|
+
[](https://t.me/isuvorov)
|
|
10
|
+
|
|
11
|
+
<div align="center">
|
|
12
|
+
<h3><p><strong>๐ก๏ธ Run Claude Code in a sandbox for super-safe YOLO mode ๐ก๏ธ</strong></p></h3>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<img src="./docs/logo.png" align="right" width="200" height="200" alt="clabox logo" />
|
|
16
|
+
|
|
17
|
+
**๐ก๏ธ Tight Seatbelt sandbox** โ the profile starts with `(deny default)` <br/>
|
|
18
|
+
**๐ Project-scoped access** โ only the CWD and explicitly allowed paths <br/>
|
|
19
|
+
**๐ Secrets stay out of reach** โ SSH keys, `~/.aws`, `~/.ssh/id_*`, private dirs <br/>
|
|
20
|
+
**๐ฆ Declarative JS config** instead of sed surgery over a heredoc <br/>
|
|
21
|
+
**๐ค Bot identity** for git/ssh inside the sandbox <br/>
|
|
22
|
+
**๐งจ Fork-bomb guard** via `ulimit -u` <br/>
|
|
23
|
+
**โก YOLO mode, safely** (`--dangerously-skip-permissions`) <br/>
|
|
24
|
+
**๐ macOS only**, Node โฅ 18, no runtime deps beyond `yargs` <br/>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g clabox # exposes the global `clabox` command
|
|
32
|
+
# or run without installing:
|
|
33
|
+
bunx clabox โฆ
|
|
34
|
+
npx clabox โฆ
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Default profile (~/.claude), YOLO mode
|
|
41
|
+
clabox run --dangerously-skip-permissions
|
|
42
|
+
|
|
43
|
+
# A different Claude profile
|
|
44
|
+
CLAUDE_CONFIG_DIR=~/.claude_work clabox run --dangerously-skip-permissions
|
|
45
|
+
|
|
46
|
+
# A named box from ~/.config/clabox/configs/<name>.config.mjs
|
|
47
|
+
clabox -b ax-root --dangerously-skip-permissions
|
|
48
|
+
|
|
49
|
+
# Debugging
|
|
50
|
+
clabox generate # build the profile, print the .sb path
|
|
51
|
+
clabox profile # just the path (no build)
|
|
52
|
+
CLABOX_DEBUG=1 clabox # print profile/config/dir on launch
|
|
53
|
+
clabox --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Unknown flags are passed straight through to `claude`, so anything after the
|
|
57
|
+
command (`--dangerously-skip-permissions`, `--model โฆ`, etc.) just works.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
Three layers, later wins: **defaults โ environment variables โ JS config file**.
|
|
64
|
+
|
|
65
|
+
The config file is looked up in this order: `--config /path` โ
|
|
66
|
+
`CLABOX_CONFIG=/path` โ `./clabox.config.mjs` (project root) โ
|
|
67
|
+
`~/.config/clabox/config.mjs`.
|
|
68
|
+
See [`clabox.config.example.mjs`](clabox.config.example.mjs).
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
clabox --config ./my.clabox.mjs run --dangerously-skip-permissions
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// clabox.config.mjs
|
|
76
|
+
export default {
|
|
77
|
+
configDir: '~/.claude_work',
|
|
78
|
+
bot: { name: 'workBOT', email: 'bot@work.dev', sshDir: '~/.ssh/workbot' },
|
|
79
|
+
network: true,
|
|
80
|
+
paths: {
|
|
81
|
+
readWrite: ['~/scratch'], // RW on top of project / configDir / tmp
|
|
82
|
+
readOnly: ['~/reference'], // RO
|
|
83
|
+
exec: ['/opt/tool/bin'], // process-exec
|
|
84
|
+
deny: ['~/secret'], // explicit deny (read + write)
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You may also export a function `(defaults) => config` for full control. `~` is
|
|
90
|
+
expanded to `$HOME`.
|
|
91
|
+
|
|
92
|
+
### Named boxes (`-b` / `--box`)
|
|
93
|
+
|
|
94
|
+
Keep a directory of named configs and switch between them by name from anywhere:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# ~/.config/clabox/configs/ax-root.config.mjs
|
|
98
|
+
clabox -b ax-root --dangerously-skip-permissions
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`-b <name>` resolves `~/.config/clabox/configs/<name>.config.mjs` (falling back
|
|
102
|
+
to a bare `<name>.mjs`) and loads it like `--config` โ so it wins over `--config`
|
|
103
|
+
/ `CLABOX_CONFIG`. Override the dir with `CLABOX_CONFIGS_DIR`. Files named
|
|
104
|
+
`_*.mjs` are treated as shared partials (e.g. `_presets.mjs`), not boxes.
|
|
105
|
+
|
|
106
|
+
A box can pin its own `cwd` so it always targets one project, no matter where you
|
|
107
|
+
run `clabox` from:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
// ~/.config/clabox/configs/ax-root.config.mjs
|
|
111
|
+
export default {
|
|
112
|
+
cwd: '~/projects/my-app', // claude runs here; this dir is the RW project dir
|
|
113
|
+
configDir: '~/.claude_axiomus',
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Environment variables
|
|
118
|
+
|
|
119
|
+
| Variable | Purpose | Default |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `CLAUDE_CONFIG_DIR` | Claude config/profile dir (multi-account); passed through to `claude` | `~/.claude` |
|
|
122
|
+
| `CLABOX_CLAUDE_BIN` | path to the `claude` binary | `PATH`, then `~/.local/bin/claude` |
|
|
123
|
+
| `CLABOX_BOT_NAME` / `CLABOX_BOT_EMAIL` | git identity | `claudeBOT` / `bot@example.com` |
|
|
124
|
+
| `CLABOX_BOT_SSH_DIR` | bot key dir (`id_ed25519`, `config`) | `~/.ssh/claudebot` |
|
|
125
|
+
| `CLABOX_CONFIG` | path to the JS config file (the `--config` flag overrides it) | โ |
|
|
126
|
+
| `CLABOX_CONFIGS_DIR` | global dir of named boxes for `-b`/`--box <name>` (`<name>.config.mjs`) | `~/.config/clabox/configs` |
|
|
127
|
+
| `CLABOX_CWD` | working dir to run `claude` in (also the RW project dir); `~` expanded | โ (the shell CWD) |
|
|
128
|
+
| `CLABOX_HOOKS_DIR` | hooks dir (RO + exec inside the sandbox) | โ (off) |
|
|
129
|
+
| `CLABOX_DEBUG` | print diagnostics on launch | โ |
|
|
130
|
+
| `TMPDIR` | where the generated profile is stored | `/tmp` |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## How it works
|
|
135
|
+
|
|
136
|
+
`sandbox-exec` runs a process inside a Seatbelt profile that starts with
|
|
137
|
+
`(deny default)` โ everything is forbidden unless explicitly allowed.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
clabox run โ loadConfig() โ buildProfile() โ <TMPDIR>/โฆsb
|
|
141
|
+
โ sh -c 'ulimit -u N; exec sandbox-exec -f <sb> env โฆ claude โฆ'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Module | Responsibility |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `src/utils/config.ts` | defaults, env, loading/merging the JS config, `~` expansion |
|
|
147
|
+
| `src/sandbox/profile.ts` | assembling the SBPL profile from config (typed helpers `subpath`/`literal`/`regex`/โฆ) |
|
|
148
|
+
| `src/sandbox/run.ts` | locating `claude`/`sandbox-exec`, generating the profile, launching with bot env + `ulimit` |
|
|
149
|
+
| `src/cli.ts` | the CLI (`run` / `generate` / `profile`), built on yargs |
|
|
150
|
+
|
|
151
|
+
Profile path: `$TMPDIR/clabox-<dir-name>-<hash>.sb` (hash of the absolute
|
|
152
|
+
project path โ each project gets its own cached profile).
|
|
153
|
+
|
|
154
|
+
Package managers are autodetected (`src/sandbox/profile.ts`) and added to the
|
|
155
|
+
read/exec sections: Homebrew (`/opt/homebrew` or `/usr/local/Homebrew`),
|
|
156
|
+
`~/.local`, Nix (`/nix/store`).
|
|
157
|
+
|
|
158
|
+
### What the profile allows and denies
|
|
159
|
+
|
|
160
|
+
**Read-only:** system dirs `/System`, `/usr`, `/bin`, `/sbin`,
|
|
161
|
+
`/Library/Frameworks`, Command Line Tools / Xcode, tzdata, system and user
|
|
162
|
+
`Library/Preferences`, detected package paths.
|
|
163
|
+
|
|
164
|
+
**Read-write:** the project dir (CWD), the Claude config dir (`configDir`),
|
|
165
|
+
`/tmp`, `/private/tmp`, `/private/var/folders/โฆ`, `~/Library/Keychains` (for
|
|
166
|
+
OAuth refresh), plus `paths.readWrite` from your config.
|
|
167
|
+
|
|
168
|
+
**Network:** `(allow network*)` when `network: true` (the default).
|
|
169
|
+
|
|
170
|
+
**Explicit deny โ wins even over the allows above:**
|
|
171
|
+
- private dirs: `denyHome` (`~/Documents`, `~/Desktop`, `~/Downloads`,
|
|
172
|
+
`~/Pictures`, `~/Movies`, `~/Music`);
|
|
173
|
+
- secrets: `denyDotConfigs` (`~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`,
|
|
174
|
+
`~/.config`) with a carve-out for `~/.config/git`;
|
|
175
|
+
- personal SSH keys `~/.ssh/id_*`, `*.pem`, `*.key` โ Claude physically cannot
|
|
176
|
+
read them. Only the bot key subdir (`bot.sshDir`) is readable.
|
|
177
|
+
|
|
178
|
+
### Git/ssh bot identity
|
|
179
|
+
|
|
180
|
+
- `ulimit -u <ulimitProcs>` โ fork-bomb guard (`0` to disable);
|
|
181
|
+
- `GIT_AUTHOR_*` / `GIT_COMMITTER_*` โ bot name/email from config;
|
|
182
|
+
- if `bot.sshDir/id_ed25519` exists, `GIT_SSH_COMMAND` is pinned to it
|
|
183
|
+
(`IdentitiesOnly=yes`, `IdentityAgent=none`);
|
|
184
|
+
- gpg signing disabled, `NPM_CONFIG_USERCONFIG=/dev/null`, `DISABLE_AUTOUPDATER=1`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Tests
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
bun test # unit + functional (bun:test)
|
|
192
|
+
bun run test # full gate: lint + types + unit + size
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The suite tests the wrapper, not `claude`:
|
|
196
|
+
|
|
197
|
+
- **Unit** โ the generated profile text: SBPL preamble, project RW/exec, network
|
|
198
|
+
toggle, config dir, ssh-key denials, the deny list, extra config paths, hooks.
|
|
199
|
+
- **Functional** โ runs real `sandbox-exec` against a generated profile and
|
|
200
|
+
asserts that reads/writes inside the project succeed while denied paths are
|
|
201
|
+
blocked. Auto-skipped off macOS or when running nested inside another sandbox.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Limitations
|
|
206
|
+
|
|
207
|
+
- **macOS only** โ needs `sandbox-exec` (Seatbelt). Formally deprecated, still
|
|
208
|
+
works on macOS 14/15.
|
|
209
|
+
- **No nested sandbox** โ you cannot launch the sandbox from inside another
|
|
210
|
+
sandbox (`sandbox_apply: Operation not permitted`). Run from a bare host.
|
|
211
|
+
- **Keychain is writable** for OAuth refresh (otherwise tokens hit 401 after
|
|
212
|
+
~24h). For a stricter setup, swap the RW Keychain block for RO in
|
|
213
|
+
`src/sandbox/profile.ts` (the "Keychain access" section).
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Example clabox config. Copy to `clabox.config.mjs` (in your
|
|
2
|
+
// project root) or `~/.config/clabox/config.mjs`, then edit.
|
|
3
|
+
//
|
|
4
|
+
// Default-export either a plain object (merged over the built-in defaults) or
|
|
5
|
+
// a function `(defaults) => config` for full control. `~` is expanded to $HOME.
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
// Working directory to run `claude` in (also granted RW as the project dir).
|
|
9
|
+
// null โ the shell's CWD. Set it on a named box that should always target one
|
|
10
|
+
// project regardless of where you launch `clabox` from. `~` is expanded.
|
|
11
|
+
cwd: null, // e.g. '~/projects/my-app'
|
|
12
|
+
|
|
13
|
+
// Which Claude profile/account to use.
|
|
14
|
+
configDir: '~/.claude',
|
|
15
|
+
|
|
16
|
+
// Args always passed to `claude`, before any args from the CLI.
|
|
17
|
+
claudeArgs: ['--settings', '{"includeCoAuthoredBy": false}'],
|
|
18
|
+
|
|
19
|
+
// Identity forced onto git commits/pushes made from inside the sandbox.
|
|
20
|
+
bot: {
|
|
21
|
+
name: 'claudeBOT',
|
|
22
|
+
email: 'bot@example.com',
|
|
23
|
+
// If `${sshDir}/id_ed25519` exists, git ssh is pinned to it and your
|
|
24
|
+
// personal keys (~/.ssh/id_*, *.pem, *.key) stay denied either way.
|
|
25
|
+
sshDir: '~/.ssh/claudebot',
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Extra environment variables forced onto the sandboxed `claude` process.
|
|
29
|
+
// Layered after the built-in vars (so a key here wins) and on top of the
|
|
30
|
+
// inherited shell env. Handy for secrets like GITHUB_TOKEN. Don't hard-code
|
|
31
|
+
// secrets in a config committed to the repo โ read them from process.env, or
|
|
32
|
+
// keep this file in ~/.config/clabox/config.mjs (outside the project).
|
|
33
|
+
env: {
|
|
34
|
+
// GITHUB_TOKEN: process.env.MY_GH_TOKEN ?? '',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Outbound network (set false to cut it off entirely).
|
|
38
|
+
network: true,
|
|
39
|
+
|
|
40
|
+
// Process-table cap inside the sandbox (fork-bomb guard); 0 to disable.
|
|
41
|
+
ulimitProcs: 1024,
|
|
42
|
+
|
|
43
|
+
// Extra directory granted read + execute (e.g. shared Claude hooks).
|
|
44
|
+
hooksDir: null, // '~/some/hooks'
|
|
45
|
+
|
|
46
|
+
// Extra rules layered on top of the base profile.
|
|
47
|
+
paths: {
|
|
48
|
+
readWrite: [], // e.g. ['~/scratch', '/Volumes/work']
|
|
49
|
+
readOnly: [], // e.g. ['~/reference-data']
|
|
50
|
+
exec: [], // e.g. ['/opt/some/tool/bin']
|
|
51
|
+
deny: [], // e.g. ['~/secret-project']
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// Home subdirectories denied entirely (read + write).
|
|
55
|
+
denyHome: ['Documents', 'Desktop', 'Downloads', 'Pictures', 'Movies', 'Music'],
|
|
56
|
+
|
|
57
|
+
// Dotfile config dirs under $HOME denied entirely (.config/git is re-allowed
|
|
58
|
+
// read-only regardless, so git keeps working).
|
|
59
|
+
denyDotConfigs: ['aws', 'gnupg', 'kube', 'docker', 'config'],
|
|
60
|
+
|
|
61
|
+
// Opt-in: turn this box into a standalone Ghostty app. With `app` present,
|
|
62
|
+
// `clabox init` writes a Ghostty config (with a `command` that runs
|
|
63
|
+
// `clabox -b <box>`), a Raycast command (`<dir>/raycast/<name>.sh` โ opens the
|
|
64
|
+
// app), and clones Ghostty.app into <appsDir>/<name>.app. Omit `app` entirely
|
|
65
|
+
// on boxes that should only get a shell alias (the default).
|
|
66
|
+
// app: {
|
|
67
|
+
// name: 'AX Manager', // โ ~/Applications/AX Manager.app
|
|
68
|
+
// title: '๐โโฌ AX Manager', // ghostty window title (default: name)
|
|
69
|
+
// emoji: '๐โโฌ', // Raycast icon (default: the title's emoji)
|
|
70
|
+
// icon: '~/icons/ax.png', // .icns or .png (.png is converted); .app icon
|
|
71
|
+
// macosIcon: 'retro', // ghostty built-in macos-icon
|
|
72
|
+
// ghostty: { // extra raw `key = value` ghostty lines
|
|
73
|
+
// background: '#0d1117',
|
|
74
|
+
// 'background-opacity': '0.92',
|
|
75
|
+
// },
|
|
76
|
+
// // bundleId: 'com.me.ax', // default: com.ghostty.custom.<box-dotted>
|
|
77
|
+
// },
|
|
78
|
+
|
|
79
|
+
// Machine-wide settings for the `clabox init` Ghostty-app builder. Shared by
|
|
80
|
+
// every `app` box โ handy to set once in a shared preset. Env overrides:
|
|
81
|
+
// CLABOX_GHOSTTY_APP, CLABOX_APPS_DIR, CLABOX_SIGN_ID,
|
|
82
|
+
// CLABOX_GHOSTTY_BASE_CONFIG, CLABOX_CLABOX_BIN.
|
|
83
|
+
appBuilder: {
|
|
84
|
+
ghosttyApp: '/Applications/Ghostty.app', // donor app to clone
|
|
85
|
+
appsDir: '~/Applications', // where built apps land
|
|
86
|
+
signId: null, // codesign identity; null โ ad-hoc (`codesign -s -`)
|
|
87
|
+
baseGhosttyConfig: null, // optional leading `config-file = โฆ`
|
|
88
|
+
claboxBin: null, // clabox path baked into `command`; null โ autodetect
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Project Guidelines
|
|
2
|
+
|
|
3
|
+
> **This file contains project documentation for developers.** For AI assistant instructions see [CLAUDE.md](../CLAUDE.md).
|
|
4
|
+
|
|
5
|
+
Guidelines for clabox โ run Claude Code in a sandbox for super-safe YOLO mode, configured in plain JavaScript.
|
|
6
|
+
|
|
7
|
+
**Important:**
|
|
8
|
+
- Update this file after large project changes
|
|
9
|
+
- Run `bun run fix` and `bun run test` after each code change
|
|
10
|
+
|
|
11
|
+
## Stack
|
|
12
|
+
|
|
13
|
+
| Tool | Choice | Notes |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Runtime (published) | Node.js โฅ 18 | `lib/` is plain ESM; macOS only (`sandbox-exec`) |
|
|
16
|
+
| Runtime (dev) | Bun | install / test / run TS source directly |
|
|
17
|
+
| Language | TypeScript (ESM) | strict `tsconfig`, `module`/`moduleResolution` nodenext |
|
|
18
|
+
| Build | tsdown (rolldown) | `src/**/*.ts` โ `lib/` (ESM + `.d.ts` + sourcemaps) |
|
|
19
|
+
| Lint / Format | Biome | `recommended` preset, `.js`-import enforcement |
|
|
20
|
+
| Test | bun:test | `tests/` (configured in `bunfig.toml`) |
|
|
21
|
+
| Type-check | `tsc --noEmit` | strict, `src/` only |
|
|
22
|
+
| Size budget | size-limit | `@size-limit/preset-small-lib`, `lib/index.js` |
|
|
23
|
+
| Release | semantic-release | fully automatic on push to `main` |
|
|
24
|
+
| CI/CD | GitHub Actions | `macos-latest` (real `sandbox-exec`) |
|
|
25
|
+
| CLI | yargs ^17 | the only runtime dependency |
|
|
26
|
+
| Sandbox | macOS `sandbox-exec` (Seatbelt/SBPL) | profile generated as plain text |
|
|
27
|
+
|
|
28
|
+
## Project Structure
|
|
29
|
+
|
|
30
|
+
**Rule:** Only entry-point files live in `src/` root โ `index.ts` (public API aggregator) and `cli.ts` (the CLI, built to `lib/cli.js` = the `clabox` bin). All other code lives in subdirectories. The build emits to `lib/`; `bin`/`main`/`exports` point at built `lib/*.js`.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
src/
|
|
34
|
+
โโโ index.ts # public API aggregator โ re-exports config / profile / run / init
|
|
35
|
+
โโโ cli.ts # CLI entry (yargs): run / generate / profile / init โ lib/cli.js (bin)
|
|
36
|
+
โโโ sandbox/
|
|
37
|
+
โ โโโ profile.ts # pure SBPL builder: buildProfile, detectPackagePaths,
|
|
38
|
+
โ โ # subpath/literal/regex/globalName/ipcName/reEscape helpers
|
|
39
|
+
โ โโโ run.ts # I/O: profilePath, generateProfile, runClaude (sandbox-exec launch)
|
|
40
|
+
โโโ init/
|
|
41
|
+
โ โโโ aliases.ts # pure: aliasName, buildIndex, buildWrapper, buildAliasFiles
|
|
42
|
+
โ โโโ ghostty.ts # pure: buildGhosttyConfig, buildCommand, buildLauncherSource,
|
|
43
|
+
โ โ # appBundlePath, bundleId
|
|
44
|
+
โ โโโ raycast.ts # pure: buildRaycastCommand, raycastIcon
|
|
45
|
+
โ โโโ app.ts # I/O (macOS): buildApp, canBuildApps (clone Ghostty.app + sign)
|
|
46
|
+
โ โโโ scaffold.ts # I/O: discoverProfiles, runInit (scan configs โ scripts + apps + raycast)
|
|
47
|
+
โโโ utils/
|
|
48
|
+
โโโ config.ts # defaultConfig, expandHome, mergeConfig, findConfigFile, loadConfig,
|
|
49
|
+
# configsDir/resolveBox/listBoxes (named-box resolution)
|
|
50
|
+
tests/
|
|
51
|
+
โโโ profile.test.ts # bun:test โ unit (profile text) + functional (real sandbox-exec)
|
|
52
|
+
โโโ init.test.ts # bun:test โ alias/ghostty text + scaffold & app boxes (tmp-dir fs)
|
|
53
|
+
โโโ box.test.ts # bun:test โ named-box resolution (tmp-dir fs)
|
|
54
|
+
lib/ # build output (tsdown) โ gitignored
|
|
55
|
+
docs/
|
|
56
|
+
โโโ guideline.md # this file
|
|
57
|
+
โโโ logo.png # README logo
|
|
58
|
+
.github/workflows/
|
|
59
|
+
โโโ test.yml # PR โ install + build + test (macos-latest)
|
|
60
|
+
โโโ release.yml # push main โ semantic-release (macos-latest)
|
|
61
|
+
clabox.config.example.mjs # copyable user config (object or (defaults) => config)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Build
|
|
68
|
+
bun run build # tsdown --out-dir lib (release build: ESM + .d.ts + maps)
|
|
69
|
+
bun run build:tsdown # tsdown โ lib-tsdown (default outDir)
|
|
70
|
+
bun run build:tsdown:release # tsdown --out-dir lib
|
|
71
|
+
bun run dev # tsdown --watch
|
|
72
|
+
|
|
73
|
+
# Run
|
|
74
|
+
bun run cli # bun run src/cli.ts (pass args after `--`)
|
|
75
|
+
bun run generate # bun run src/cli.ts generate (build a profile, print its path)
|
|
76
|
+
bun run cli -- init # aliases per box + build Ghostty apps for `app` boxes
|
|
77
|
+
bun run cli -- init --no-apps # aliases only (skip the Ghostty-app build)
|
|
78
|
+
bun run cli -- init --app "AX Manager" # (re)build just one app box
|
|
79
|
+
|
|
80
|
+
# Testing
|
|
81
|
+
bun run test # lint + types + unit + size (the full gate)
|
|
82
|
+
bun run test:unit # bun test
|
|
83
|
+
bun run test:unit:coverage # bun test --coverage
|
|
84
|
+
bun run test:unit:watch # bun test --watch
|
|
85
|
+
bun run test:types # tsc --noEmit
|
|
86
|
+
bun run test:lint # biome lint
|
|
87
|
+
bun run test:size # size-limit
|
|
88
|
+
|
|
89
|
+
# Fixing
|
|
90
|
+
bun run fix # biome check --write
|
|
91
|
+
bun run fix:lint # biome check --write
|
|
92
|
+
bun run fix:lint:unsafe # biome check --write --unsafe
|
|
93
|
+
|
|
94
|
+
# Release (normally automatic in CI)
|
|
95
|
+
bun run version:release # semantic-release --no-ci --dry-run (preview next version)
|
|
96
|
+
bun run release # build + test + dry-run + npm publish (local fallback)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
`sandbox-exec` runs a process inside a Seatbelt profile that starts with `(deny default)` โ everything is forbidden unless explicitly allowed.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
clabox run โ loadConfig() โ buildProfile() โ <TMPDIR>/โฆsb
|
|
105
|
+
โ sh -c 'ulimit -u N; exec sandbox-exec -f <sb> env โฆ claude โฆ'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `utils/config.ts`
|
|
109
|
+
Builds the effective config in three layers (later wins): `defaultConfig` โ env vars โ a JS config file. `findConfigFile(explicit?)` looks up the explicit path (the `--config` CLI flag, falling back to `CLABOX_CONFIG`) โ `./clabox.config.mjs` / `./clabox.config.js` โ `~/.config/clabox/config.mjs`; the `--config` flag wins over `CLABOX_CONFIG`. `loadConfig(explicit?)` forwards that path, dynamically imports the file and accepts either a plain object (merged via `mergeConfig`, a deep merge over the defaults) or a function `(defaults) => config`. `expandHome()` expands a leading `~`. Exports the `Config`/`BotConfig`/`PathRules`/`LoadedConfig` types.
|
|
110
|
+
|
|
111
|
+
**Named boxes.** `configsDir()` returns the global box dir (`~/.config/clabox/configs`, overridable via `CLABOX_CONFIGS_DIR`). `resolveBox(name, dir?)` maps a name to its config file there, preferring `<name>.config.mjs` over a bare `<name>.mjs` and throwing (with the available boxes listed) when none exists; a `_`-prefixed name is refused (it's a shared partial, not a box), so `-b` matches exactly what `listBoxes` advertises. `listBoxes(dir?)` returns the sorted, de-duplicated box names, skipping `_`-prefixed shared partials (e.g. `_presets.mjs`). The CLI's `-b`/`--box <name>` flag resolves the name and feeds the path to `loadConfig` as the explicit config (so `-b` wins over `--config`).
|
|
112
|
+
|
|
113
|
+
### `sandbox/profile.ts` (pure)
|
|
114
|
+
Assembles the SBPL profile text from typed helpers (`subpath`, `literal`, `regex`, `globalName`, `ipcName`, `reEscape`) โ no I/O beyond `fs.existsSync` for autodetection. `detectPackagePaths()` finds installed package managers (Homebrew `/opt/homebrew` or `/usr/local/Homebrew`, `~/.local`, Nix `/nix/store`) to grant read/exec. `buildProfile(config, { projectDir, detectedPaths })` returns the full profile and sanity-checks it carries `(version 1)`.
|
|
115
|
+
|
|
116
|
+
### `sandbox/run.ts` (I/O)
|
|
117
|
+
`profilePath()` returns the deterministic `$TMPDIR/clabox-<dir>-<hash>.sb` path. `resolveProjectDir(config)` returns the effective project dir โ `config.cwd` (with `~` expanded) when set, else `process.cwd()` โ and is what `generateProfile`/`runClaude` default to. `generateProfile()` requires `sandbox-exec`, builds the profile and writes it. `runClaude()` resolves the project dir + `claude` binary (config โ PATH โ `~/.local/bin/claude`), forces the bot git identity + hardening env (`buildEnvArgs`, with `config.env` appended last so it wins), sets the terminal title, and execs `sh -c 'ulimit -u N; exec sandbox-exec -f <sb> env โฆ claude โฆ'` **in the resolved project dir** (`spawnSync` `cwd`), returning the exit code.
|
|
118
|
+
|
|
119
|
+
### `init/aliases.ts` (pure) + `init/scaffold.ts` (I/O)
|
|
120
|
+
`clabox init` turns a directory of box configs into ready-to-use shell commands. `aliases.ts` is pure text: `aliasName(profile)` yields `clabox-<name>`; `buildIndex()` renders the source-able `index.sh` (a `_clabox_run` helper that runs `CLABOX_CONFIGS_DIR=<configsDir> clabox -b "$1"` plus one function per box); `buildWrapper()` renders a standalone `.sh` that sources `index.sh` and calls one function; `buildAliasFiles()` returns the full file set. There is **one command per box** โ yolo vs. safe is decided by the box's own preset (`claudeArgs`), not by a `-safe` alias. `scaffold.ts` does the I/O: `discoverProfiles(configsDir)` returns the sorted box names via `listBoxes` (both `<name>.mjs`/`<name>.config.mjs`, `_`-partials skipped; throws if the dir is missing or has no boxes), and `runInit({ baseDir, buildApps, only })` (async) resolves `<baseDir>/configs` + `<baseDir>/scripts` (default `<cwd>/__`), prunes its own prior artifacts (`index.sh`, `clabox-*.sh`), then writes the new ones `chmod +x`. Absolute paths are baked in so the scripts run from any cwd. It returns `{ profiles, written, apps, ghosttyConfigs, warnings, โฆ }`.
|
|
121
|
+
|
|
122
|
+
### `init/ghostty.ts` + `init/raycast.ts` (pure) + `init/app.ts` (I/O) โ standalone Ghostty apps
|
|
123
|
+
A box becomes a real macOS app by carrying an `app` object (`AppConfig`: `name`, `title?`, `emoji?`, `icon?`, `macosIcon?`, `ghostty?`, `bundleId?`). When `runInit` runs with `buildApps` (the default), it `loadConfig`s each box and, for every box with an `app`, writes `<baseDir>/ghostty/<name>.config`, a `<baseDir>/raycast/<name>.sh` Raycast command, and clones a `.app`. `ghostty.ts` is pure text: `buildGhosttyConfig()` renders the config (a `command = bash -c 'cd <cwd> && CLABOX_CONFIGS_DIR=<dir> <claboxBin> -b <name>; exec zsh'` plus `title`/`macos-icon`/extra `app.ghostty` lines and an optional leading `config-file`); `buildLauncherSource()` renders a tiny C launcher that finds `ghostty.real` next to itself and re-execs it with `--config-file=<config>` baked in; `appBundlePath()`/`bundleId()` derive the bundle path/id. `raycast.ts` renders the Raycast script command (`buildRaycastCommand()`: `@raycast.*` metadata + `open <appPath>`; `raycastIcon()` picks `app.emoji`, else the title's leading emoji, else ๐ป). `app.ts` is the macOS-only I/O (replaces the old `ghostty-app-builder.sh`): `canBuildApps(builder)` gates on darwin + donor app + `cc`; `buildApp()` extracts the donor's entitlements, `cp -R` clones Ghostty.app into `<appsDir>/<name>.app`, patches `Info.plist` (identity + Sparkle off), swaps the binary for the compiled launcher, installs the icon (`.icns` copy or `.png`โ`sips`+`iconutil`), and `codesign`s (`appBuilder.signId`, else ad-hoc `-`). Machine-wide build settings live in `config.appBuilder` (`ghosttyApp`, `appsDir`, `signId`, `baseGhosttyConfig`, `claboxBin`). Builds are best-effort: a non-buildable host or a thrown build records a `warning` and the aliases (plus the ghostty config + raycast script) are still emitted. `init` prunes its own `<baseDir>/ghostty/*.config` and `<baseDir>/raycast/*.sh` on a full run (not under `--app`, which would orphan other apps' artifacts); built `.app` bundles are **not** auto-deleted.
|
|
124
|
+
|
|
125
|
+
### `cli.ts`
|
|
126
|
+
The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default), `generate`, `profile`, `init`. `unknown-options-as-args` keeps unknown flags as positionals so they pass straight through to `claude` (e.g. `--dangerously-skip-permissions`). clabox-owned flags: `--config <path>` (a JS config file that overrides `CLABOX_CONFIG`) and `-b`/`--box <name>` (a named box from the global configs dir; resolved via `resolveBox` and winning over `--config`) โ both forwarded to `loadConfig()` by `run` and `generate` through the `explicitConfig(argv)` helper. (`-b` deliberately avoids `-p`/`-c`/`-r`/`-d`/`-v`, which are claude's own `--print`/`--continue`/`--resume`/`--debug`/`--verbose`.) `init` takes `--dir <path>` (the base dir holding `configs/` and `scripts/`, default `./__`), `--no-apps` (skip the Ghostty-app build), and `--app <box|name>` ((re)build a single app box, by box name or `app.name`).
|
|
127
|
+
|
|
128
|
+
### What the profile allows and denies
|
|
129
|
+
- **Read-only:** system dirs (`/System`, `/usr`, `/bin`, `/sbin`, `/Library/Frameworks`), Command Line Tools / Xcode, tzdata, system + user `Library/Preferences`, detected package paths.
|
|
130
|
+
- **Read-write:** the project dir (`config.cwd` if set, else the shell CWD), the Claude config dir (`configDir`), `/tmp`, `/private/tmp`, `/private/var/folders/โฆ`, `~/Library/Keychains` (OAuth refresh), plus `paths.readWrite`.
|
|
131
|
+
- **Network:** `(allow network*)` when `network: true` (default).
|
|
132
|
+
- **Two deny tiers** (Seatbelt evaluates rules in order โ the *last* match wins โ so placement is deliberate):
|
|
133
|
+
- **Soft privacy deny (overridable):** `denyHome` (`~/Documents`, `~/Desktop`, โฆ) and `paths.deny`, emitted *before* the extra `readOnly`/`readWrite` and the project dir. An explicit grant therefore overrides it โ handy for a project that lives under `~/Documents`, or a debug box that wants `readOnly: ['/']` to roam the disk.
|
|
134
|
+
- **Hard secret deny (always wins):** `denyDotConfigs` (`~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`, `~/.config`) and personal SSH keys `~/.ssh/id_*`, `*.pem`, `*.key`, emitted as the **very last file rule** โ after every allow, the extra paths and the project dir. No `readOnly`/`readWrite` grant can re-expose these. Only the bot key subdir (`bot.sshDir`, not matched by the patterns) stays readable. (The `~/.config/git` RO carve-out is shadowed by the hard deny on `~/.config`; git still reads `~/.gitconfig` outside `~/.config`.)
|
|
135
|
+
|
|
136
|
+
### Git/ssh bot identity
|
|
137
|
+
`ulimit -u <ulimitProcs>` (fork-bomb guard, `0` to disable); `GIT_AUTHOR_*` / `GIT_COMMITTER_*` from `bot.name`/`bot.email`; if `bot.sshDir/id_ed25519` exists, `GIT_SSH_COMMAND` is pinned to it (`IdentitiesOnly=yes`, `IdentityAgent=none`); gpg signing disabled; `NPM_CONFIG_USERCONFIG=/dev/null`; `DISABLE_AUTOUPDATER=1`.
|
|
138
|
+
|
|
139
|
+
### Passing environment variables
|
|
140
|
+
`sandbox-exec` restricts files and network, not the environment, and `runClaude` spawns through `/bin/sh` without an `env` option โ so the sandboxed `claude` **inherits the parent shell env** (e.g. `export GITHUB_TOKEN=โฆ && clabox`). For a declarative alternative, `config.env` (a `KEY=VALUE` map) is appended **last** in `buildEnvArgs`, so it layers over both the inherited env and the built-in hardening vars and a colliding key wins. Don't hard-code secrets in a repo-committed `clabox.config.mjs` (the project dir is mounted RW and readable from inside): read them from `process.env`, or keep the config in `~/.config/clabox/config.mjs`. Anything in the env is readable by `claude` and, with `network: true`, exfiltratable.
|
|
141
|
+
|
|
142
|
+
## Lint
|
|
143
|
+
|
|
144
|
+
Biome (`biome.json`), scoped to `src/**/*.ts` + `tests/**/*.ts`:
|
|
145
|
+
- `recommended` preset; `noExplicitAny` and `noNonNullAssertion` off.
|
|
146
|
+
- `useImportExtensions` (error, `forceJsExtensions`) โ relative imports must use `.js` specifiers.
|
|
147
|
+
- Formatter: 2-space indent, line width 100, single quotes, always semicolons.
|
|
148
|
+
|
|
149
|
+
## CI/CD
|
|
150
|
+
|
|
151
|
+
Both workflows run on `macos-latest` so the functional tests can exercise the real `sandbox-exec`.
|
|
152
|
+
|
|
153
|
+
- **`test.yml`** โ on PR to `main`: checkout โ setup Bun + Node 20 โ `bun install --frozen-lockfile` โ `bun run build` โ `bun run test`.
|
|
154
|
+
- **`release.yml`** โ on push to `main`: checkout (`fetch-depth: 0`) โ setup Bun + Node 20 (`registry-url`) โ install โ build โ test โ `npx semantic-release`. Releases are **fully automatic**: semantic-release reads the Conventional Commits, decides the version, updates `CHANGELOG.md`, publishes to npm (provenance) + GitHub Releases, and commits the bump back with `[skip ci]`. Nobody bumps a version by hand.
|
|
155
|
+
|
|
156
|
+
## Size Limits
|
|
157
|
+
|
|
158
|
+
| Entry | Limit | Note |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `lib/index.js` | 10 kB | brotlied, `node:*` ignored (the CLI bin uses top-level await and is not size-budgeted) |
|
|
161
|
+
|
|
162
|
+
## Package Exports
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { loadConfig, buildProfile, runClaude, runInit } from 'clabox'; // lib/index.js
|
|
166
|
+
import { loadConfig, defaultConfig, mergeConfig } from 'clabox/config'; // lib/utils/config.js
|
|
167
|
+
import { buildProfile, detectPackagePaths } from 'clabox/profile'; // lib/sandbox/profile.js
|
|
168
|
+
import { generateProfile, profilePath, resolveProjectDir, runClaude } from 'clabox/run'; // lib/sandbox/run.js
|
|
169
|
+
// CLI entry: clabox/cli (lib/cli.js) โ also the `clabox` bin
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Environment Variables
|
|
173
|
+
|
|
174
|
+
| Variable | Purpose | Default |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `CLAUDE_CONFIG_DIR` | Claude config/profile dir (multi-account); passed through to `claude` | `~/.claude` |
|
|
177
|
+
| `CLABOX_CWD` | working dir to run `claude` in (also the RW project dir); `~` expanded | โ (the shell CWD) |
|
|
178
|
+
| `CLABOX_CLAUDE_BIN` | path to the `claude` binary | PATH, then `~/.local/bin/claude` |
|
|
179
|
+
| `CLABOX_BOT_NAME` / `CLABOX_BOT_EMAIL` | git identity inside the sandbox | `claudeBOT` / `bot@example.com` |
|
|
180
|
+
| `CLABOX_BOT_SSH_DIR` | bot key dir (`id_ed25519`, `config`) | `~/.ssh/claudebot` |
|
|
181
|
+
| `CLABOX_CONFIG` | path to the JS config file (the `--config` flag overrides it) | โ |
|
|
182
|
+
| `CLABOX_CONFIGS_DIR` | global dir of named boxes for `-b`/`--box <name>` (`<name>.config.mjs`) | `~/.config/clabox/configs` |
|
|
183
|
+
| `CLABOX_HOOKS_DIR` | hooks dir (RO + exec inside the sandbox) | โ (off) |
|
|
184
|
+
| `CLABOX_GHOSTTY_APP` | donor app cloned by `init` for `app` boxes (`config.appBuilder.ghosttyApp`) | `/Applications/Ghostty.app` |
|
|
185
|
+
| `CLABOX_APPS_DIR` | where `init` writes built `.app`s (`config.appBuilder.appsDir`) | `~/Applications` |
|
|
186
|
+
| `CLABOX_SIGN_ID` | codesign identity for built apps (`config.appBuilder.signId`); unset โ ad-hoc | โ |
|
|
187
|
+
| `CLABOX_GHOSTTY_BASE_CONFIG` | leading `config-file = โฆ` in generated Ghostty configs | โ |
|
|
188
|
+
| `CLABOX_CLABOX_BIN` | `clabox` path baked into the Ghostty `command` (`config.appBuilder.claboxBin`) | autodetect |
|
|
189
|
+
| `CLABOX_DEBUG` | print profile/config/dir diagnostics on launch | โ |
|
|
190
|
+
| `TMPDIR` | where the generated profile is stored | `/tmp` |
|
|
191
|
+
|
|
192
|
+
## Limitations
|
|
193
|
+
|
|
194
|
+
- **macOS only** โ needs `sandbox-exec` (Seatbelt). Formally deprecated, still works on macOS 14/15.
|
|
195
|
+
- **No nested sandbox** โ you cannot launch the sandbox from inside another sandbox (`sandbox_apply: Operation not permitted`). Run from a bare host.
|
|
196
|
+
- **Keychain is writable** for OAuth refresh (else tokens hit 401 after ~24h). For a stricter setup, swap the RW Keychain block for RO in `src/sandbox/profile.ts` (the "Keychain access" section).
|
package/docs/logo.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
//#region src/init/aliases.ts
|
|
3
|
+
/** The `clabox-<name>` command name for a box. */
|
|
4
|
+
function aliasName(profile) {
|
|
5
|
+
return `clabox-${profile}`;
|
|
6
|
+
}
|
|
7
|
+
/** Build the source-able `index.sh` defining one function per box. */
|
|
8
|
+
function buildIndex(profiles, { configsDir, scriptsDir }) {
|
|
9
|
+
const indexPath = path.join(scriptsDir, "index.sh");
|
|
10
|
+
const usage = profiles.map((p) => `# ${aliasName(p)}`);
|
|
11
|
+
const defs = profiles.map((p) => `${aliasName(p)}() { _clabox_run ${p} "$@"; }`);
|
|
12
|
+
return `${[
|
|
13
|
+
"#!/usr/bin/env bash",
|
|
14
|
+
"# Generated by `clabox init` โ do not edit; rerun it after changing the configs dir.",
|
|
15
|
+
"#",
|
|
16
|
+
"# Source this from ~/.zshrc or ~/.bashrc:",
|
|
17
|
+
`# source ${indexPath}`,
|
|
18
|
+
"#",
|
|
19
|
+
"# Commands (run from any cwd). yolo vs. safe is set by each box's preset:",
|
|
20
|
+
...usage,
|
|
21
|
+
"",
|
|
22
|
+
"_clabox_run() {",
|
|
23
|
+
` CLABOX_CONFIGS_DIR="${configsDir}" clabox -b "$1" "\${@:2}"`,
|
|
24
|
+
"}",
|
|
25
|
+
"",
|
|
26
|
+
...defs
|
|
27
|
+
].join("\n")}\n`;
|
|
28
|
+
}
|
|
29
|
+
/** Build a standalone wrapper script that sources `index.sh` and calls one fn. */
|
|
30
|
+
function buildWrapper(fnName, indexPath) {
|
|
31
|
+
return `${[
|
|
32
|
+
"#!/usr/bin/env bash",
|
|
33
|
+
"# Generated by `clabox init`.",
|
|
34
|
+
"set -euo pipefail",
|
|
35
|
+
`source "${indexPath}"`,
|
|
36
|
+
`${fnName} "$@"`
|
|
37
|
+
].join("\n")}\n`;
|
|
38
|
+
}
|
|
39
|
+
/** All files `clabox init` writes: the `index.sh` plus a wrapper per box. */
|
|
40
|
+
function buildAliasFiles(profiles, paths) {
|
|
41
|
+
const indexPath = path.join(paths.scriptsDir, "index.sh");
|
|
42
|
+
const files = [{
|
|
43
|
+
path: indexPath,
|
|
44
|
+
content: buildIndex(profiles, paths),
|
|
45
|
+
executable: true
|
|
46
|
+
}];
|
|
47
|
+
for (const p of profiles) {
|
|
48
|
+
const fn = aliasName(p);
|
|
49
|
+
files.push({
|
|
50
|
+
path: path.join(paths.scriptsDir, `${fn}.sh`),
|
|
51
|
+
content: buildWrapper(fn, indexPath),
|
|
52
|
+
executable: true
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { buildWrapper as i, buildAliasFiles as n, buildIndex as r, aliasName as t };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=aliases-DKGcMHHe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases-DKGcMHHe.js","names":[],"sources":["../src/init/aliases.ts"],"sourcesContent":["// Pure builder for the shell aliases emitted by `clabox init`.\n//\n// For each box config (`<name>.mjs` / `<name>.config.mjs`) it produces one\n// command `clabox-<name>` that runs that box. Whether it runs yolo or asks for\n// confirmation is decided by the box's own preset (its `claudeArgs`), not here.\n// Plus an `index.sh` defining them all as shell functions to `source`.\n\nimport path from 'node:path';\n\n/** A file to be written by `clabox init`. */\nexport interface InitFile {\n /** Absolute path to write. */\n path: string;\n /** File contents. */\n content: string;\n /** Whether to `chmod +x` after writing. */\n executable: boolean;\n}\n\n/** Absolute locations for the generated artifacts. */\nexport interface AliasPaths {\n /** Dir holding the box config files. */\n configsDir: string;\n /** Dir to emit `index.sh` and the per-box wrappers into. */\n scriptsDir: string;\n}\n\n/** The `clabox-<name>` command name for a box. */\nexport function aliasName(profile: string): string {\n return `clabox-${profile}`;\n}\n\n/** Build the source-able `index.sh` defining one function per box. */\nexport function buildIndex(profiles: string[], { configsDir, scriptsDir }: AliasPaths): string {\n const indexPath = path.join(scriptsDir, 'index.sh');\n const usage = profiles.map((p) => `# ${aliasName(p)}`);\n const defs = profiles.map((p) => `${aliasName(p)}() { _clabox_run ${p} \"$@\"; }`);\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init` โ do not edit; rerun it after changing the configs dir.',\n '#',\n '# Source this from ~/.zshrc or ~/.bashrc:',\n `# source ${indexPath}`,\n '#',\n \"# Commands (run from any cwd). yolo vs. safe is set by each box's preset:\",\n ...usage,\n '',\n '_clabox_run() {',\n // Point `-b` at this dir so resolveBox picks the right .mjs/.config.mjs file.\n ` CLABOX_CONFIGS_DIR=\"${configsDir}\" clabox -b \"$1\" \"\\${@:2}\"`,\n '}',\n '',\n ...defs,\n ].join('\\n')}\\n`;\n}\n\n/** Build a standalone wrapper script that sources `index.sh` and calls one fn. */\nexport function buildWrapper(fnName: string, indexPath: string): string {\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init`.',\n 'set -euo pipefail',\n `source \"${indexPath}\"`,\n `${fnName} \"$@\"`,\n ].join('\\n')}\\n`;\n}\n\n/** All files `clabox init` writes: the `index.sh` plus a wrapper per box. */\nexport function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[] {\n const indexPath = path.join(paths.scriptsDir, 'index.sh');\n const files: InitFile[] = [\n { path: indexPath, content: buildIndex(profiles, paths), executable: true },\n ];\n for (const p of profiles) {\n const fn = aliasName(p);\n files.push({\n path: path.join(paths.scriptsDir, `${fn}.sh`),\n content: buildWrapper(fn, indexPath),\n executable: true,\n });\n }\n return files;\n}\n"],"mappings":";;;AA4BA,SAAgB,UAAU,SAAyB;CACjD,OAAO,UAAU;AACnB;;AAGA,SAAgB,WAAW,UAAoB,EAAE,YAAY,cAAkC;CAC7F,MAAM,YAAY,KAAK,KAAK,YAAY,UAAU;CAClD,MAAM,QAAQ,SAAS,KAAK,MAAM,OAAO,UAAU,CAAC,GAAG;CACvD,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,UAAU,CAAC,EAAE,mBAAmB,EAAE,SAAS;CAC/E,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACA,GAAG;EACH;EACA;EAEA,yBAAyB,WAAW;EACpC;EACA;EACA,GAAG;CACL,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,aAAa,QAAgB,WAA2B;CACtE,OAAO,GAAG;EACR;EACA;EACA;EACA,WAAW,UAAU;EACrB,GAAG,OAAO;CACZ,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,gBAAgB,UAAoB,OAA+B;CACjF,MAAM,YAAY,KAAK,KAAK,MAAM,YAAY,UAAU;CACxD,MAAM,QAAoB,CACxB;EAAE,MAAM;EAAW,SAAS,WAAW,UAAU,KAAK;EAAG,YAAY;CAAK,CAC5E;CACA,KAAK,MAAM,KAAK,UAAU;EACxB,MAAM,KAAK,UAAU,CAAC;EACtB,MAAM,KAAK;GACT,MAAM,KAAK,KAAK,MAAM,YAAY,GAAG,GAAG,IAAI;GAC5C,SAAS,aAAa,IAAI,SAAS;GACnC,YAAY;EACd,CAAC;CACH;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/init/aliases.d.ts
|
|
2
|
+
/** A file to be written by `clabox init`. */
|
|
3
|
+
interface InitFile {
|
|
4
|
+
/** Absolute path to write. */
|
|
5
|
+
path: string;
|
|
6
|
+
/** File contents. */
|
|
7
|
+
content: string;
|
|
8
|
+
/** Whether to `chmod +x` after writing. */
|
|
9
|
+
executable: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Absolute locations for the generated artifacts. */
|
|
12
|
+
interface AliasPaths {
|
|
13
|
+
/** Dir holding the box config files. */
|
|
14
|
+
configsDir: string;
|
|
15
|
+
/** Dir to emit `index.sh` and the per-box wrappers into. */
|
|
16
|
+
scriptsDir: string;
|
|
17
|
+
}
|
|
18
|
+
/** The `clabox-<name>` command name for a box. */
|
|
19
|
+
declare function aliasName(profile: string): string;
|
|
20
|
+
/** Build the source-able `index.sh` defining one function per box. */
|
|
21
|
+
declare function buildIndex(profiles: string[], {
|
|
22
|
+
configsDir,
|
|
23
|
+
scriptsDir
|
|
24
|
+
}: AliasPaths): string;
|
|
25
|
+
/** Build a standalone wrapper script that sources `index.sh` and calls one fn. */
|
|
26
|
+
declare function buildWrapper(fnName: string, indexPath: string): string;
|
|
27
|
+
/** All files `clabox init` writes: the `index.sh` plus a wrapper per box. */
|
|
28
|
+
declare function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[];
|
|
29
|
+
//#endregion
|
|
30
|
+
export { buildIndex as a, buildAliasFiles as i, InitFile as n, buildWrapper as o, aliasName as r, AliasPaths as t };
|
|
31
|
+
//# sourceMappingURL=aliases-DXyz-ufw.d.ts.map
|