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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/clabox.config.example.mjs +90 -0
  4. package/docs/guideline.md +196 -0
  5. package/docs/logo.png +0 -0
  6. package/lib/aliases-DKGcMHHe.js +60 -0
  7. package/lib/aliases-DKGcMHHe.js.map +1 -0
  8. package/lib/aliases-DXyz-ufw.d.ts +31 -0
  9. package/lib/app-CieBa29D.js +246 -0
  10. package/lib/app-CieBa29D.js.map +1 -0
  11. package/lib/app-CpuMtOoj.d.ts +30 -0
  12. package/lib/cli.d.ts +1 -0
  13. package/lib/cli.js +71 -0
  14. package/lib/cli.js.map +1 -0
  15. package/lib/config-BQ44iVWT.js +155 -0
  16. package/lib/config-BQ44iVWT.js.map +1 -0
  17. package/lib/config-DQWueb4a.d.ts +134 -0
  18. package/lib/ghostty-Ca0g9P9P.js +74 -0
  19. package/lib/ghostty-Ca0g9P9P.js.map +1 -0
  20. package/lib/ghostty-DemKkfqf.d.ts +34 -0
  21. package/lib/index.d.ts +9 -0
  22. package/lib/index.js +9 -0
  23. package/lib/init/aliases.d.ts +2 -0
  24. package/lib/init/aliases.js +2 -0
  25. package/lib/init/app.d.ts +2 -0
  26. package/lib/init/app.js +2 -0
  27. package/lib/init/ghostty.d.ts +2 -0
  28. package/lib/init/ghostty.js +2 -0
  29. package/lib/init/raycast.d.ts +2 -0
  30. package/lib/init/raycast.js +2 -0
  31. package/lib/init/scaffold.d.ts +2 -0
  32. package/lib/init/scaffold.js +2 -0
  33. package/lib/profile-BeM41NXc.d.ts +29 -0
  34. package/lib/profile-DM6NAgb-.js +100 -0
  35. package/lib/profile-DM6NAgb-.js.map +1 -0
  36. package/lib/raycast-BCdO2Se1.js +35 -0
  37. package/lib/raycast-BCdO2Se1.js.map +1 -0
  38. package/lib/raycast-DM7c559f.d.ts +22 -0
  39. package/lib/run-CNehSQ-S.js +113 -0
  40. package/lib/run-CNehSQ-S.js.map +1 -0
  41. package/lib/run-Cx8cuTh5.d.ts +25 -0
  42. package/lib/sandbox/profile.d.ts +2 -0
  43. package/lib/sandbox/profile.js +2 -0
  44. package/lib/sandbox/run.d.ts +2 -0
  45. package/lib/sandbox/run.js +2 -0
  46. package/lib/scaffold-ByIbYAeS.d.ts +46 -0
  47. package/lib/scaffold-CRzC5KYe.js +141 -0
  48. package/lib/scaffold-CRzC5KYe.js.map +1 -0
  49. package/lib/utils/config.d.ts +2 -0
  50. package/lib/utils/config.js +2 -0
  51. 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
+ [![LSK.js](https://github.com/lskjs/presets/raw/main/docs/badge.svg)](https://github.com/lskjs)
4
+ [![NPM version](https://badgen.net/npm/v/clabox)](https://www.npmjs.com/package/clabox)
5
+ [![NPM downloads](https://badgen.net/npm/dt/clabox)](https://www.npmjs.com/package/clabox)
6
+ [![Have TypeScript types](https://badgen.net/npm/types/clabox)](https://www.npmjs.com/package/clabox)
7
+ [![Package size](https://img.shields.io/npm/unpacked-size/clabox?label=size&color=blue)](https://www.npmjs.com/package/clabox)
8
+ [![License](https://badgen.net/github/license/ycmds/clabox)](https://github.com/ycmds/clabox/blob/main/LICENSE)
9
+ [![Write us in Telegram](https://img.shields.io/badge/write%20us-0088CC?logo=telegram&logoColor=white)](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