clabox 0.2.1 → 0.3.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 +0 -1
- package/clabox.config.example.mjs +10 -4
- package/docs/guideline.md +25 -16
- package/lib/{app-ChnKbgkD.js → app-CuEM7S0i.js} +18 -8
- package/lib/app-CuEM7S0i.js.map +1 -0
- package/lib/{app-B1djcEnN.d.ts → app-MIaByu-I.d.ts} +2 -2
- package/lib/cli.js +17 -5
- package/lib/cli.js.map +1 -1
- package/lib/{config-jZAB2Zg8.d.ts → config-1vfbPBRi.d.ts} +46 -7
- package/lib/{config-BQ44iVWT.js → config-CY_Cf87P.js} +21 -4
- package/lib/config-CY_Cf87P.js.map +1 -0
- package/lib/extras-Bp4DpyZP.js +87 -0
- package/lib/extras-Bp4DpyZP.js.map +1 -0
- package/lib/{extras-B_dzBrCF.d.ts → extras-CITv2nRg.d.ts} +15 -6
- package/lib/{ghostty-DFwFh4aL.d.ts → ghostty-D8KUXOw7.d.ts} +2 -2
- package/lib/index.d.ts +10 -9
- package/lib/index.js +8 -7
- package/lib/info/info.d.ts +2 -0
- package/lib/info/info.js +2 -0
- package/lib/info-BRlaIckk.js +176 -0
- package/lib/info-BRlaIckk.js.map +1 -0
- package/lib/info-C0WvNVNS.d.ts +98 -0
- package/lib/init/app.d.ts +1 -1
- package/lib/init/app.js +1 -1
- package/lib/init/ghostty.d.ts +1 -1
- package/lib/init/raycast.d.ts +1 -1
- package/lib/init/scaffold.d.ts +2 -2
- package/lib/init/scaffold.js +2 -2
- package/lib/{profile-DM6NAgb-.js → profile-1oytMVlE.js} +25 -5
- package/lib/profile-1oytMVlE.js.map +1 -0
- package/lib/{profile-7CiMUPu9.d.ts → profile-Bzq0Y4PI.d.ts} +13 -3
- package/lib/{raycast-fc5U1x7E.d.ts → raycast-DuHEu0Vz.d.ts} +2 -2
- package/lib/{run-yUsOaKFx.js → run-9vQC2fCs.js} +10 -8
- package/lib/run-9vQC2fCs.js.map +1 -0
- package/lib/{run-BWGDNJiY.d.ts → run-gag6YjI9.d.ts} +10 -7
- package/lib/sandbox/extras.d.ts +1 -1
- package/lib/sandbox/extras.js +1 -1
- package/lib/sandbox/profile.d.ts +2 -2
- package/lib/sandbox/profile.js +2 -2
- package/lib/sandbox/run.d.ts +2 -2
- package/lib/sandbox/run.js +2 -2
- package/lib/{scaffold-CbsKxlL0.d.ts → scaffold-BiCJzELQ.d.ts} +10 -3
- package/lib/{scaffold-C1tqCL_8.js → scaffold-DiG5q28w.js} +18 -9
- package/lib/scaffold-DiG5q28w.js.map +1 -0
- package/lib/utils/config.d.ts +2 -2
- package/lib/utils/config.js +2 -2
- package/package.json +2 -1
- package/lib/app-ChnKbgkD.js.map +0 -1
- package/lib/config-BQ44iVWT.js.map +0 -1
- package/lib/extras-B2ss2Cgh.js +0 -46
- package/lib/extras-B2ss2Cgh.js.map +0 -1
- package/lib/profile-DM6NAgb-.js.map +0 -1
- package/lib/run-yUsOaKFx.js.map +0 -1
- package/lib/scaffold-C1tqCL_8.js.map +0 -1
package/README.md
CHANGED
|
@@ -125,7 +125,6 @@ export default {
|
|
|
125
125
|
| `CLABOX_CONFIG` | path to the JS config file (the `--config` flag overrides it) | — |
|
|
126
126
|
| `CLABOX_CONFIGS_DIR` | global dir of named boxes for `-b`/`--box <name>` (`<name>.config.mjs`) | `~/.config/clabox/configs` |
|
|
127
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
128
|
| `CLABOX_DEBUG` | print diagnostics on launch | — |
|
|
130
129
|
| `TMPDIR` | where the generated profile is stored | `/tmp` |
|
|
131
130
|
|
|
@@ -40,14 +40,20 @@ export default {
|
|
|
40
40
|
// Process-table cap inside the sandbox (fork-bomb guard); 0 to disable.
|
|
41
41
|
ulimitProcs: 1024,
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
|
|
43
|
+
// Per-box claude hooks (claude's settings.json `hooks` map). clabox merges
|
|
44
|
+
// them into a `--settings` file. For a hook script to actually run inside the
|
|
45
|
+
// sandbox its dir must also be granted read (`paths.readOnly`) + exec
|
|
46
|
+
// (`paths.exec`) below — `hooks` only registers it with claude.
|
|
47
|
+
// hooks: {
|
|
48
|
+
// Stop: [{ hooks: [{ type: 'command', command: '~/some/hooks/notify.sh' }] }],
|
|
49
|
+
// Notification: [{ hooks: [{ type: 'command', command: '~/some/hooks/notify.sh' }] }],
|
|
50
|
+
// },
|
|
45
51
|
|
|
46
52
|
// Extra rules layered on top of the base profile.
|
|
47
53
|
paths: {
|
|
48
54
|
readWrite: [], // e.g. ['~/scratch', '/Volumes/work']
|
|
49
|
-
readOnly: [], // e.g. ['~/reference-data']
|
|
50
|
-
exec: [], // e.g. ['/opt/some/tool/bin']
|
|
55
|
+
readOnly: [], // e.g. ['~/reference-data', '~/some/hooks']
|
|
56
|
+
exec: [], // e.g. ['/opt/some/tool/bin', '~/some/hooks'] (so hooks can run)
|
|
51
57
|
deny: [], // e.g. ['~/secret-project']
|
|
52
58
|
},
|
|
53
59
|
|
package/docs/guideline.md
CHANGED
|
@@ -22,7 +22,8 @@ Guidelines for clabox — run Claude Code in a sandbox for super-safe YOLO mode,
|
|
|
22
22
|
| Size budget | size-limit | `@size-limit/preset-small-lib`, `lib/index.js` |
|
|
23
23
|
| Release | semantic-release | fully automatic on push to `main` |
|
|
24
24
|
| CI/CD | GitHub Actions | `macos-latest` (real `sandbox-exec`) |
|
|
25
|
-
| CLI | yargs ^17 |
|
|
25
|
+
| CLI | yargs ^17 | command/flag parsing |
|
|
26
|
+
| Logging | @lsk4/log ^4 | pretty leveled CLI logging (`cli.ts` only) |
|
|
26
27
|
| Sandbox | macOS `sandbox-exec` (Seatbelt/SBPL) | profile generated as plain text |
|
|
27
28
|
|
|
28
29
|
## Project Structure
|
|
@@ -31,13 +32,15 @@ Guidelines for clabox — run Claude Code in a sandbox for super-safe YOLO mode,
|
|
|
31
32
|
|
|
32
33
|
```
|
|
33
34
|
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)
|
|
35
|
+
├── index.ts # public API aggregator — re-exports config / profile / run / init / info
|
|
36
|
+
├── cli.ts # CLI entry (yargs): run / generate / profile / info / init → lib/cli.js (bin)
|
|
36
37
|
├── sandbox/
|
|
37
38
|
│ ├── profile.ts # pure SBPL builder: buildProfile, detectPackagePaths,
|
|
38
39
|
│ │ # subpath/literal/regex/globalName/ipcName/reEscape helpers
|
|
39
|
-
│ ├── extras.ts # pure: boxSlug, buildBoxExtras (per-box mcp/systemPrompt → args+files)
|
|
40
|
-
│ └── run.ts # I/O: profilePath, generateProfile, writeExtraFiles, runClaude
|
|
40
|
+
│ ├── extras.ts # pure: boxSlug, buildBoxExtras (per-box mcp/systemPrompt/hooks → args+files)
|
|
41
|
+
│ └── run.ts # I/O: profilePath, generateProfile, writeExtraFiles, runClaude, which
|
|
42
|
+
├── info/
|
|
43
|
+
│ └── info.ts # pure: formatInfo; I/O: gatherInfo, resolveClaboxPackage, claboxVersion
|
|
41
44
|
├── init/
|
|
42
45
|
│ ├── aliases.ts # pure: aliasName, buildIndex, buildWrapper, buildAliasFiles
|
|
43
46
|
│ ├── ghostty.ts # pure: buildGhosttyConfig, buildCommand, buildLauncherSource,
|
|
@@ -51,7 +54,8 @@ src/
|
|
|
51
54
|
tests/
|
|
52
55
|
├── profile.test.ts # bun:test — unit (profile text) + functional (real sandbox-exec)
|
|
53
56
|
├── init.test.ts # bun:test — alias/ghostty text + scaffold & app boxes (tmp-dir fs)
|
|
54
|
-
├── extras.test.ts # bun:test — per-box mcp/systemPrompt → claude args + mcp json
|
|
57
|
+
├── extras.test.ts # bun:test — per-box mcp/systemPrompt/hooks → claude args + mcp/settings json
|
|
58
|
+
├── info.test.ts # bun:test — formatInfo text + gatherInfo/resolveClaboxPackage
|
|
55
59
|
└── box.test.ts # bun:test — named-box resolution (tmp-dir fs)
|
|
56
60
|
lib/ # build output (tsdown) — gitignored
|
|
57
61
|
docs/
|
|
@@ -75,6 +79,7 @@ bun run dev # tsdown --watch
|
|
|
75
79
|
# Run
|
|
76
80
|
bun run cli # bun run src/cli.ts (pass args after `--`)
|
|
77
81
|
bun run generate # bun run src/cli.ts generate (build a profile, print its path)
|
|
82
|
+
bun run cli -- info # version + resolved box/config/extras report (pretty via @lsk4/log)
|
|
78
83
|
bun run cli -- init # aliases per box + build Ghostty apps for `app` boxes
|
|
79
84
|
bun run cli -- init --no-apps # aliases only (skip the Ghostty-app build)
|
|
80
85
|
bun run cli -- init --app "AX Manager" # (re)build just one app box
|
|
@@ -108,7 +113,7 @@ clabox run → loadConfig() → buildProfile() → <TMPDIR>/…sb
|
|
|
108
113
|
```
|
|
109
114
|
|
|
110
115
|
### `utils/config.ts`
|
|
111
|
-
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`/`McpServer`/`LoadedConfig` types. The `Config` carries optional `mcp` (`Record<string, McpServer>`)
|
|
116
|
+
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`/`McpServer`/`HookCommand`/`HookMatcher`/`HooksConfig`/`LoadedConfig` types. The `Config` carries optional `mcp` (`Record<string, McpServer>`), `systemPrompt` (`string | string[]`) and `hooks` (`HooksConfig` = claude's settings.json `hooks` map) — declarative per-box MCP / pre-prompt / hooks compiled by `sandbox/extras.ts` (see below).
|
|
112
117
|
|
|
113
118
|
**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`).
|
|
114
119
|
|
|
@@ -116,19 +121,22 @@ Builds the effective config in three layers (later wins): `defaultConfig` → en
|
|
|
116
121
|
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)`.
|
|
117
122
|
|
|
118
123
|
### `sandbox/extras.ts` (pure)
|
|
119
|
-
Compiles a box's **declarative** `mcp` / `systemPrompt` config into claude args (and the files they reference), so user config stays pure data and clabox owns the wiring. `boxSlug(configFile, projectDir)` derives a stable slug — the config-file basename minus `.config.mjs`/`.mjs`, else the project-dir basename. `buildBoxExtras(config, slug)` returns `{ claudeArgs, files }`: when `config.mcp` (a `Record<string, McpServer>`) is non-empty it writes `<
|
|
124
|
+
Compiles a box's **declarative** `mcp` / `systemPrompt` / `hooks` config into claude args (and the files they reference), so user config stays pure data and clabox owns the wiring. `boxSlug(configFile, projectDir)` derives a stable slug — the config-file basename minus `.config.mjs`/`.mjs`, else the project-dir basename. `buildBoxExtras(config, slug)` returns `{ claudeArgs, files }`: when `config.mcp` (a `Record<string, McpServer>`) is non-empty it writes `<claboxHome>/mcp/<slug>.json` (`{ mcpServers: … }`) and emits `--strict-mcp-config --mcp-config <file>` so a **shared** `configDir`'s *global/plugin* MCP servers are ignored — each box loads exactly its own; `config.systemPrompt` (`string | string[]`, joined with blank lines) is appended inline via `--append-system-prompt` (no file); `config.hooks` (a `HooksConfig` = claude's settings.json `hooks` map) is merged into a settings object written to `<claboxHome>/settings/<slug>.json` and loaded with `--settings <file>`. The merge matters because a second `--settings` flag *replaces* the first rather than deep-merging, so `readInlineSettings()` first parses any inline `--settings` JSON already in `config.claudeArgs` (e.g. `{"includeCoAuthoredBy": false}`) and folds the hooks into it — emitted after `config.claudeArgs`, the file then wins the last-`--settings`-takes-all race while carrying the merged result. Both json files live under clabox's **own** home (`claboxHomeDir()` = `~/.config/clabox`, via `claboxMcpDir()`/`claboxSettingsDir()`) — **not** the Claude `configDir`, so clabox never writes into Claude's profile dir. Since `~/.config` is hard-denied, the sandbox profile re-grants **READ+WRITE** to the whole `~/.config/clabox` home *after* the hard deny so the box can read them (and edit its own configs in-box).
|
|
120
125
|
|
|
121
126
|
### `sandbox/run.ts` (I/O)
|
|
122
|
-
`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. `writeExtraFiles(files)` `mkdir -p`s and writes the per-box extras (the
|
|
127
|
+
`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. `writeExtraFiles(files)` `mkdir -p`s and writes the per-box extras (the mcp/settings json under `~/.config/clabox`). `runClaude()` resolves the project dir + `claude` binary (config → PATH → `~/.local/bin/claude`), compiles + materializes the box extras (`buildBoxExtras` + `writeExtraFiles`), 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 <config.claudeArgs> <extras> <CLI args>'` **in the resolved project dir** (`spawnSync` `cwd`), returning the exit code. (`runInit` also materializes the extras at `init` time via `scaffold.ts#materializeExtras`, slug = box name.)
|
|
128
|
+
|
|
129
|
+
### `info/info.ts` (pure `formatInfo` + I/O `gatherInfo`)
|
|
130
|
+
Backs `clabox info`, the introspection report — version, the resolved box/project/profile, the effective config, the compiled per-box extras, and the clabox-relevant env vars. `gatherInfo(config, { configFile, box })` is the I/O snapshot: it resolves `claude` and `sandbox-exec` (via the now-exported `run.ts#which`), records the running `process.argv[1]`/`process.execPath`, the deterministic profile path (+ whether it's been built), the compiled `buildBoxExtras` args/files, and the present `CLABOX_*` / `CLAUDE_CONFIG_DIR` env, returning a flat serializable `InfoData`. `formatInfo(data, { color })` is the **pure** aligned-text builder — sections (`[clabox]`/`[box]`/`[config]`/`[extras]`/`[env]`), label-padded rows, multiline args collapsed to one line; `color` (off by default, on for a TTY) wraps headers/labels/placeholders in ANSI. **Version + self-path** come from `resolveClaboxPackage()`, which walks up from `import.meta.url` to the `package.json` whose `name === 'clabox'`: a fixed `../../package.json` is wrong once tsdown hoists shared code into a hashed chunk (`lib/info-<hash>.js`) at a different depth, but the walk-up holds across the source tree, the built tree and an installed `node_modules/clabox/`. `cli.ts` prints the report through `@lsk4/log` (`createLogger('clabox').log(report)` keeps the table clean; `.warn(…)` flags a missing `claude`/`sandbox-exec`).
|
|
123
131
|
|
|
124
132
|
### `init/aliases.ts` (pure) + `init/scaffold.ts` (I/O)
|
|
125
|
-
`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 `
|
|
133
|
+
`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 -b "$1"`, prefixed with `CLABOX_CONFIGS_DIR=<configsDir>` only when `configsDir` is non-null — see `bakeConfigsDir` below — 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 `baseDir` = `defaultBaseDir()` = the parent of the global `configsDir()`, i.e. `~/.config/clabox`, honoring `CLABOX_CONFIGS_DIR`; pass `--dir` for a project-local `<dir>/configs`), prunes its own prior artifacts (`index.sh`, `clabox-*.sh`), then writes the new ones `chmod +x`. It also materializes each box's MCP json (`materializeExtras`, best-effort per box). **Generated commands resolve at run time, not init time**: `clabox` is emitted bare (PATH-resolved at launch — survives package-manager moves like bun → npm/homebrew), and `bakeConfigsDir(dir)` returns `null` (omit the `CLABOX_CONFIGS_DIR` prefix) when `<dir>/configs` already resolves (realpath, symlinks included) to the runtime default `~/.config/clabox/configs`, else the absolute path. The project `cd` dir stays absolute. It returns `{ profiles, written, apps, ghosttyConfigs, raycastCommands, extraFiles, warnings, … }`.
|
|
126
134
|
|
|
127
135
|
### `init/ghostty.ts` + `init/raycast.ts` (pure) + `init/app.ts` (I/O) — standalone Ghostty apps
|
|
128
|
-
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 = zsh -lic 'cd <cwd> && CLABOX_CONFIGS_DIR=<dir> <claboxBin> -b <name>; exec zsh'` — a **login + interactive** zsh so the GUI-launched app inherits the user's PATH (`/etc/zprofile`→`path_helper` for Homebrew, `~/.zshrc` for fnm/nvm/volta); a bare `bash -c` gets only launchd's minimal PATH and can't find `node` for clabox's `#!/usr/bin/env node` shebang —
|
|
136
|
+
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 = zsh -lic 'cd <cwd> && [CLABOX_CONFIGS_DIR=<dir> ]<claboxBin> -b <name>; exec zsh'` — a **login + interactive** zsh so the GUI-launched app inherits the user's PATH (`/etc/zprofile`→`path_helper` for Homebrew, `~/.zshrc` for fnm/nvm/volta); a bare `bash -c` gets only launchd's minimal PATH and can't find `node` for clabox's `#!/usr/bin/env node` shebang. `<claboxBin>` defaults to a bare `clabox` (PATH-resolved at launch by that login shell — `appBuilder.claboxBin` pins an absolute path instead); the `CLABOX_CONFIGS_DIR=<dir> ` prefix is present only when `configsDir` is non-null (`bakeConfigsDir`, same rule as the aliases). 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()` builds into a staging `<appsDir>/<name>.app.new` and atomically `rename`s it onto `<appsDir>/<name>.app` **only after all steps succeed** (so a failed build never destroys an existing working bundle): it extracts the donor's entitlements, `cp -R` clones Ghostty.app into the stage, patches `Info.plist` (identity + Sparkle off), swaps the binary for the compiled launcher, installs the icon (`.icns` copy or `.png`→`sips`+`iconutil`, plus `plutil -remove CFBundleIconName` — Ghostty's plist references an icon inside its compiled `Assets.car`, which macOS prefers over the loose `.icns`, so the name must be dropped for our icon to take effect), 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.
|
|
129
137
|
|
|
130
138
|
### `cli.ts`
|
|
131
|
-
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
|
|
139
|
+
The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default), `generate`, `profile`, `info`, `init`. `unknown-options-as-args` keeps unknown flags as positionals so they pass straight through to `claude` (e.g. `--dangerously-skip-permissions`). `info` honors the same `--config`/`-b` selection (via `explicitConfig(argv)`) and renders `formatInfo(gatherInfo(...))` through `@lsk4/log` — the one place a non-`yargs` runtime dep is used. 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 `~/.config/clabox` = parent of the global `configsDir()`, honoring `CLABOX_CONFIGS_DIR`), `--no-apps` (skip the Ghostty-app build), and `--app <box|name>` ((re)build a single app box, by box name or `app.name`).
|
|
132
140
|
|
|
133
141
|
### What the profile allows and denies
|
|
134
142
|
- **Read-only:** system dirs (`/System`, `/usr`, `/bin`, `/sbin`, `/Library/Frameworks`), Command Line Tools / Xcode, tzdata, system + user `Library/Preferences`, detected package paths.
|
|
@@ -136,7 +144,8 @@ The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default)
|
|
|
136
144
|
- **Network:** `(allow network*)` when `network: true` (default).
|
|
137
145
|
- **Two deny tiers** (Seatbelt evaluates rules in order — the *last* match wins — so placement is deliberate):
|
|
138
146
|
- **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`, a debug box that wants `readOnly: ['/']` to roam the disk, or a **root box that wants `readWrite: ['/']`** to read *and write* anywhere on disk (the hard secret deny below still wins, so credentials and private keys stay protected even with a whole-disk RW grant).
|
|
139
|
-
- **Hard secret deny (always wins):** `denyDotConfigs` (`~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`, `~/.config`) and personal SSH keys `~/.ssh/id_*`, `*.pem`, `*.key`, emitted as the **
|
|
147
|
+
- **Hard secret deny (always wins):** `denyDotConfigs` (`~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`, `~/.config`) and personal SSH keys `~/.ssh/id_*`, `*.pem`, `*.key`, emitted as the **last deny** — 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`.)
|
|
148
|
+
- **clabox-extras RO carve-out (the only rule after the hard deny):** a single `allow file-read*` for `claboxMcpDir()` + `claboxSettingsDir()` (`~/.config/clabox/{mcp,settings}`). Those dirs hold clabox's **own** compiled `--mcp-config`/`--settings` json, which the `~/.config` hard deny above would otherwise block; re-granting **read** (last match wins) lets the sandboxed claude load them. It re-exposes exactly those two dirs and nothing credential-shaped, and grants read-only (clabox writes the files before launch, so no in-box write is needed), so the "credentials never readable" invariant still holds. **Symlinked home:** the macOS sandbox matches rules against the *symlink-resolved* path (the reason both `/tmp` and `/private/tmp` are granted), so when `~/.config/clabox` is a symlink — e.g. relocated *into* a project repo so the box configs + compiled extras live in-tree — the json files physically sit at the target and the nominal grant never matches, failing the in-box read with `EPERM`. `resolvedExtrasDirs()` (in `profile.ts`) `realpath`s `claboxHomeDir()` and, when it differs, appends the resolved `mcp`/`settings` dirs to this carve-out. It resolves the home (which always exists) and re-joins the leaf names, so it works even before the subdirs are materialized; it returns `[]` when the home is absent or already canonical. Only those two subdirs are exposed at the resolved location, so the invariant holds wherever the home points.
|
|
140
149
|
|
|
141
150
|
### Git/ssh bot identity
|
|
142
151
|
`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`.
|
|
@@ -148,7 +157,7 @@ The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default)
|
|
|
148
157
|
|
|
149
158
|
Biome (`biome.json`), scoped to `src/**/*.ts` + `tests/**/*.ts`:
|
|
150
159
|
- `recommended` preset; `noExplicitAny` and `noNonNullAssertion` off.
|
|
151
|
-
- `useImportExtensions` (error, `forceJsExtensions`) — relative imports must use `.js` specifiers.
|
|
160
|
+
- `useImportExtensions` (error, `forceJsExtensions`) — relative imports must use `.js` specifiers. (It also rewrites a literal `.json` specifier to `.js`, which is why `info.ts` reads its own `package.json` by walking up from `import.meta.url` rather than `import`/`require`-ing it.)
|
|
152
161
|
- Formatter: 2-space indent, line width 100, single quotes, always semicolons.
|
|
153
162
|
|
|
154
163
|
## CI/CD
|
|
@@ -168,6 +177,7 @@ Both workflows run on `macos-latest` so the functional tests can exercise the re
|
|
|
168
177
|
|
|
169
178
|
```typescript
|
|
170
179
|
import { loadConfig, buildProfile, runClaude, runInit } from 'clabox'; // lib/index.js
|
|
180
|
+
import { gatherInfo, formatInfo, resolveClaboxPackage } from 'clabox'; // lib/index.js (info report)
|
|
171
181
|
import { loadConfig, defaultConfig, mergeConfig } from 'clabox/config'; // lib/utils/config.js
|
|
172
182
|
import { buildProfile, detectPackagePaths } from 'clabox/profile'; // lib/sandbox/profile.js
|
|
173
183
|
import { generateProfile, profilePath, resolveProjectDir, runClaude } from 'clabox/run'; // lib/sandbox/run.js
|
|
@@ -185,12 +195,11 @@ import { generateProfile, profilePath, resolveProjectDir, runClaude } from 'clab
|
|
|
185
195
|
| `CLABOX_BOT_SSH_DIR` | bot key dir (`id_ed25519`, `config`) | `~/.ssh/claudebot` |
|
|
186
196
|
| `CLABOX_CONFIG` | path to the JS config file (the `--config` flag overrides it) | — |
|
|
187
197
|
| `CLABOX_CONFIGS_DIR` | global dir of named boxes for `-b`/`--box <name>` (`<name>.config.mjs`) | `~/.config/clabox/configs` |
|
|
188
|
-
| `CLABOX_HOOKS_DIR` | hooks dir (RO + exec inside the sandbox) | — (off) |
|
|
189
198
|
| `CLABOX_GHOSTTY_APP` | donor app cloned by `init` for `app` boxes (`config.appBuilder.ghosttyApp`) | `/Applications/Ghostty.app` |
|
|
190
199
|
| `CLABOX_APPS_DIR` | where `init` writes built `.app`s (`config.appBuilder.appsDir`) | `~/Applications` |
|
|
191
200
|
| `CLABOX_SIGN_ID` | codesign identity for built apps (`config.appBuilder.signId`); unset → ad-hoc | — |
|
|
192
201
|
| `CLABOX_GHOSTTY_BASE_CONFIG` | leading `config-file = …` in generated Ghostty configs | — |
|
|
193
|
-
| `CLABOX_CLABOX_BIN` | `clabox` path
|
|
202
|
+
| `CLABOX_CLABOX_BIN` | absolute `clabox` path to pin in the Ghostty `command` (`config.appBuilder.claboxBin`); unset → bare `clabox` (PATH-resolved at launch) | bare `clabox` |
|
|
194
203
|
| `CLABOX_DEBUG` | print profile/config/dir diagnostics on launch | — |
|
|
195
204
|
| `TMPDIR` | where the generated profile is stored | `/tmp` |
|
|
196
205
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as expandHome } from "./config-CY_Cf87P.js";
|
|
2
2
|
import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -153,19 +153,20 @@ function buildApp(opts) {
|
|
|
153
153
|
const ghosttyApp = expandHome(builder.ghosttyApp);
|
|
154
154
|
const appsDir = expandHome(builder.appsDir);
|
|
155
155
|
const appPath = appBundlePath(appsDir, app);
|
|
156
|
-
const
|
|
156
|
+
const stagePath = `${appPath}.new`;
|
|
157
|
+
const plist = path.join(stagePath, "Contents", "Info.plist");
|
|
157
158
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clabox-app-"));
|
|
158
159
|
try {
|
|
159
160
|
const entitlements = extractEntitlements(ghosttyApp, tmpDir);
|
|
160
161
|
fs.mkdirSync(appsDir, { recursive: true });
|
|
161
|
-
fs.rmSync(
|
|
162
|
+
fs.rmSync(stagePath, {
|
|
162
163
|
recursive: true,
|
|
163
164
|
force: true
|
|
164
165
|
});
|
|
165
166
|
run("cp", [
|
|
166
167
|
"-R",
|
|
167
168
|
ghosttyApp,
|
|
168
|
-
|
|
169
|
+
stagePath
|
|
169
170
|
]);
|
|
170
171
|
run("plutil", [
|
|
171
172
|
"-replace",
|
|
@@ -211,7 +212,7 @@ function buildApp(opts) {
|
|
|
211
212
|
plist
|
|
212
213
|
]);
|
|
213
214
|
} catch {}
|
|
214
|
-
const bin = path.join(
|
|
215
|
+
const bin = path.join(stagePath, "Contents", "MacOS", "ghostty");
|
|
215
216
|
fs.renameSync(bin, `${bin}.real`);
|
|
216
217
|
const src = path.join(tmpDir, "launcher.c");
|
|
217
218
|
fs.writeFileSync(src, buildLauncherSource(configPath));
|
|
@@ -220,7 +221,7 @@ function buildApp(opts) {
|
|
|
220
221
|
bin,
|
|
221
222
|
src
|
|
222
223
|
]);
|
|
223
|
-
installIcon(app,
|
|
224
|
+
installIcon(app, stagePath, tmpDir);
|
|
224
225
|
const signId = builder.signId;
|
|
225
226
|
const entArgs = entitlements ? ["--entitlements", entitlements] : [];
|
|
226
227
|
const idArgs = signId ? ["--sign", signId] : ["--sign", "-"];
|
|
@@ -235,8 +236,13 @@ function buildApp(opts) {
|
|
|
235
236
|
"--deep",
|
|
236
237
|
...idArgs,
|
|
237
238
|
...entArgs,
|
|
238
|
-
|
|
239
|
+
stagePath
|
|
239
240
|
]);
|
|
241
|
+
fs.rmSync(appPath, {
|
|
242
|
+
recursive: true,
|
|
243
|
+
force: true
|
|
244
|
+
});
|
|
245
|
+
fs.renameSync(stagePath, appPath);
|
|
240
246
|
return {
|
|
241
247
|
appPath,
|
|
242
248
|
signed: signId ? "identity" : "adhoc"
|
|
@@ -246,9 +252,13 @@ function buildApp(opts) {
|
|
|
246
252
|
recursive: true,
|
|
247
253
|
force: true
|
|
248
254
|
});
|
|
255
|
+
fs.rmSync(stagePath, {
|
|
256
|
+
recursive: true,
|
|
257
|
+
force: true
|
|
258
|
+
});
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
261
|
//#endregion
|
|
252
262
|
export { canBuildApps as n, installIcon as r, buildApp as t };
|
|
253
263
|
|
|
254
|
-
//# sourceMappingURL=app-
|
|
264
|
+
//# sourceMappingURL=app-CuEM7S0i.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-CuEM7S0i.js","names":[],"sources":["../src/init/app.ts"],"sourcesContent":["// I/O for the `clabox init` Ghostty-app builder (macOS-only).\n//\n// Clones Ghostty.app into `<appsDir>/<name>.app`, swaps the binary for a tiny\n// compiled launcher that bakes in `--config-file=<config>`, sets the icon,\n// disables Sparkle, and re-signs. Mirrors the old ghostty-app-builder.sh. The\n// pure text builders live in init/ghostty.ts.\n\nimport { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { type AppBuilderConfig, type AppConfig, expandHome } from '../utils/config.js';\nimport { appBundlePath, buildLauncherSource, bundleId } from './ghostty.js';\n\n/** Inputs for {@link buildApp}. */\nexport interface BuildAppOptions {\n /** The `-b` box name (drives the default bundle id). */\n boxName: string;\n app: AppConfig;\n builder: AppBuilderConfig;\n /** Absolute path to the already-written Ghostty config to bake in. */\n configPath: string;\n}\n\n/** Result of a successful {@link buildApp}. */\nexport interface BuildAppResult {\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\nfunction run(cmd: string, args: string[]): void {\n execFileSync(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n}\n\nfunction has(bin: string): boolean {\n try {\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/** True when the host can build apps (macOS with the donor app + a C compiler). */\nexport function canBuildApps(builder: AppBuilderConfig): { ok: boolean; reason?: string } {\n if (process.platform !== 'darwin') return { ok: false, reason: 'not macOS' };\n if (!fs.existsSync(expandHome(builder.ghosttyApp))) {\n return { ok: false, reason: `Ghostty not found at ${builder.ghosttyApp}` };\n }\n if (!has('cc')) return { ok: false, reason: 'no C compiler (cc) — install Xcode CLT' };\n return { ok: true };\n}\n\n/** Extract the donor app's entitlements to a tmp file, or null if it has none. */\nfunction extractEntitlements(ghosttyApp: string, tmpDir: string): string | null {\n let xml: string;\n try {\n xml = execFileSync('codesign', ['-d', '--entitlements', '-', '--xml', ghosttyApp], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n return null;\n }\n const start = xml.indexOf('<?xml');\n if (start < 0) return null;\n const file = path.join(tmpDir, 'entitlements.xml');\n fs.writeFileSync(file, xml.slice(start));\n return file;\n}\n\n/** Name of the icon resource referenced by the bundle (default Ghostty.icns). */\nfunction iconResourceName(plist: string): string {\n try {\n const name = execFileSync('plutil', ['-extract', 'CFBundleIconFile', 'raw', plist], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n return name.endsWith('.icns') ? name : `${name}.icns`;\n } catch {\n return 'Ghostty.icns';\n }\n}\n\n/** Convert a PNG into a multi-resolution .icns at `out`. */\nfunction pngToIcns(png: string, out: string, tmpDir: string): void {\n const iconset = path.join(tmpDir, 'icon.iconset');\n fs.mkdirSync(iconset, { recursive: true });\n for (const size of [16, 32, 128, 256, 512]) {\n run('sips', [\n '-z',\n `${size}`,\n `${size}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}.png`),\n ]);\n const d = size * 2;\n run('sips', [\n '-z',\n `${d}`,\n `${d}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}@2x.png`),\n ]);\n }\n run('iconutil', ['-c', 'icns', iconset, '-o', out]);\n}\n\n/** Install the box icon into the cloned bundle, if `app.icon` is set. */\nexport function installIcon(app: AppConfig, appPath: string, tmpDir: string): void {\n if (!app.icon) return;\n const icon = expandHome(app.icon);\n if (!fs.existsSync(icon)) throw new Error(`icon not found: ${app.icon}`);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const dest = path.join(appPath, 'Contents', 'Resources', iconResourceName(plist));\n if (icon.endsWith('.icns')) fs.copyFileSync(icon, dest);\n else if (icon.endsWith('.png')) pngToIcns(icon, dest, tmpDir);\n else throw new Error(`unsupported icon type (need .icns/.png): ${app.icon}`);\n\n // Ghostty ships a compiled asset catalog (Assets.car) and a `CFBundleIconName`\n // pointing into it, which macOS prefers over the loose `CFBundleIconFile`\n // .icns we just replaced — so our icon would be ignored. Drop the asset-catalog\n // reference so macOS falls back to the .icns. (May be absent on other donors.)\n try {\n run('plutil', ['-remove', 'CFBundleIconName', plist]);\n } catch {\n // donor app may not define CFBundleIconName — ignore\n }\n}\n\n/**\n * Build the standalone Ghostty app for a box. Throws on any failure (the caller\n * decides whether to abort or carry on with the other boxes).\n */\nexport function buildApp(opts: BuildAppOptions): BuildAppResult {\n const { app, builder, boxName, configPath } = opts;\n const check = canBuildApps(builder);\n if (!check.ok) throw new Error(`cannot build app: ${check.reason}`);\n\n const ghosttyApp = expandHome(builder.ghosttyApp);\n const appsDir = expandHome(builder.appsDir);\n const appPath = appBundlePath(appsDir, app);\n // Build into a staging clone next to the final bundle (same filesystem → the\n // final rename is atomic) and only swap it in once every step has succeeded.\n // A failed build (e.g. an unreadable donor) must never destroy an existing\n // working bundle — so we touch `appPath` only at the very end.\n const stagePath = `${appPath}.new`;\n const plist = path.join(stagePath, 'Contents', 'Info.plist');\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'clabox-app-'));\n\n try {\n const entitlements = extractEntitlements(ghosttyApp, tmpDir);\n\n // Full clone (cp -R keeps bundle symlinks/frameworks intact).\n fs.mkdirSync(appsDir, { recursive: true });\n fs.rmSync(stagePath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, stagePath]);\n\n // Identity.\n run('plutil', ['-replace', 'CFBundleIdentifier', '-string', bundleId(boxName, app), plist]);\n run('plutil', ['-replace', 'CFBundleName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleDisplayName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleExecutable', '-string', 'ghostty', plist]);\n\n // Disable Sparkle auto-update (would clobber the clone).\n run('plutil', ['-replace', 'SUEnableAutomaticChecks', '-bool', 'NO', plist]);\n try {\n run('plutil', ['-replace', 'SUFeedURL', '-string', '', plist]);\n } catch {\n // donor app may not define SUFeedURL — ignore\n }\n\n // Swap the binary for a launcher that prepends --config-file.\n const bin = path.join(stagePath, 'Contents', 'MacOS', 'ghostty');\n fs.renameSync(bin, `${bin}.real`);\n const src = path.join(tmpDir, 'launcher.c');\n fs.writeFileSync(src, buildLauncherSource(configPath));\n run('cc', ['-o', bin, src]);\n\n installIcon(app, stagePath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle. (The signature\n // seals the bundle contents, not its directory name, so the rename below\n // keeps it valid.)\n const signId = builder.signId;\n const entArgs = entitlements ? ['--entitlements', entitlements] : [];\n const idArgs = signId ? ['--sign', signId] : ['--sign', '-'];\n run('codesign', ['--force', ...idArgs, ...entArgs, `${bin}.real`]);\n run('codesign', ['--force', '--deep', ...idArgs, ...entArgs, stagePath]);\n\n // Everything succeeded — atomically replace the previous bundle.\n fs.rmSync(appPath, { recursive: true, force: true });\n fs.renameSync(stagePath, appPath);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n // Clean a leftover stage from a failed build (no-op after a successful swap).\n fs.rmSync(stagePath, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AppConfig, t as AppBuilderConfig } from "./config-
|
|
1
|
+
import { n as AppConfig, t as AppBuilderConfig } from "./config-1vfbPBRi.js";
|
|
2
2
|
|
|
3
3
|
//#region src/init/app.d.ts
|
|
4
4
|
/** Inputs for {@link buildApp}. */
|
|
@@ -29,4 +29,4 @@ declare function installIcon(app: AppConfig, appPath: string, tmpDir: string): v
|
|
|
29
29
|
declare function buildApp(opts: BuildAppOptions): BuildAppResult;
|
|
30
30
|
//#endregion
|
|
31
31
|
export { installIcon as a, canBuildApps as i, BuildAppResult as n, buildApp as r, BuildAppOptions as t };
|
|
32
|
-
//# sourceMappingURL=app-
|
|
32
|
+
//# sourceMappingURL=app-MIaByu-I.d.ts.map
|
package/lib/cli.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
2
|
+
import { f as resolveBox, u as loadConfig } from "./config-CY_Cf87P.js";
|
|
3
|
+
import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-9vQC2fCs.js";
|
|
4
|
+
import { n as formatInfo, r as gatherInfo } from "./info-BRlaIckk.js";
|
|
5
|
+
import { r as runInit } from "./scaffold-DiG5q28w.js";
|
|
5
6
|
import path from "node:path";
|
|
7
|
+
import { createLogger } from "@lsk4/log";
|
|
6
8
|
import yargs from "yargs";
|
|
7
9
|
import { hideBin } from "yargs/helpers";
|
|
8
10
|
//#region src/cli.ts
|
|
@@ -32,9 +34,19 @@ await yargs(hideBin(process.argv)).scriptName("clabox").parserConfiguration({ "u
|
|
|
32
34
|
}).command("profile", "Print the sandbox profile path (no build)", (y) => y, async (argv) => {
|
|
33
35
|
const { config } = await loadConfig(explicitConfig(argv));
|
|
34
36
|
console.log(profilePath(resolveProjectDir(config)));
|
|
37
|
+
}).command("info", "Print clabox/version/box/config diagnostics for the resolved config", (y) => y, async (argv) => {
|
|
38
|
+
const { config, configFile } = await loadConfig(explicitConfig(argv));
|
|
39
|
+
const data = gatherInfo(config, {
|
|
40
|
+
configFile,
|
|
41
|
+
box: argv.box
|
|
42
|
+
});
|
|
43
|
+
const log = createLogger("clabox");
|
|
44
|
+
log.log(formatInfo(data, { color: Boolean(process.stdout.isTTY) }));
|
|
45
|
+
if (!data.claudeBin) log.warn("claude binary not found on PATH");
|
|
46
|
+
if (!data.sandboxExec) log.warn("sandbox-exec not found — clabox needs macOS");
|
|
35
47
|
}).command("init", "Generate clabox-<name> shell aliases and build Ghostty apps for `app` boxes", (y) => y.option("dir", {
|
|
36
48
|
type: "string",
|
|
37
|
-
describe: "Base dir holding configs/ and scripts/ (default:
|
|
49
|
+
describe: "Base dir holding configs/ and scripts/ (default: ~/.config/clabox)"
|
|
38
50
|
}).option("apps", {
|
|
39
51
|
type: "boolean",
|
|
40
52
|
default: true,
|
|
@@ -56,7 +68,7 @@ await yargs(hideBin(process.argv)).scriptName("clabox").parserConfiguration({ "u
|
|
|
56
68
|
for (const w of warnings) console.warn(` ⚠️ ${w}`);
|
|
57
69
|
console.log(`\nAdd to ~/.zshrc: source ${indexFile}`);
|
|
58
70
|
if (raycastCommands.length > 0) console.log(`Add to Raycast (Script Commands dir): ${path.dirname(raycastCommands[0])}`);
|
|
59
|
-
}).example("$0 run --dangerously-skip-permissions", "YOLO mode inside the sandbox").example("$0 -b ax-root", "Run the ~/.config/clabox/configs/ax-root.config.mjs box").example("$0 init", "Generate shell aliases from
|
|
71
|
+
}).example("$0 run --dangerously-skip-permissions", "YOLO mode inside the sandbox").example("$0 -b ax-root", "Run the ~/.config/clabox/configs/ax-root.config.mjs box").example("$0 info", "Print version/box/config diagnostics for the resolved config").example("$0 init", "Generate shell aliases from ~/.config/clabox/configs/*.config.mjs").example("$0 --config ./my.clabox.mjs run", "Use a specific JS config file").example("CLAUDE_CONFIG_DIR=~/.claude_work $0 run", "Use a different Claude profile").epilogue([
|
|
60
72
|
"Config (later wins): defaults -> env vars -> JS config file.",
|
|
61
73
|
"File: ./clabox.config.mjs or ~/.config/clabox/config.mjs",
|
|
62
74
|
"(or --config /path, or CLABOX_CONFIG=/path).",
|
package/lib/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// clabox — run Claude Code in a sandbox for super-safe YOLO mode.\n// SPDX-License-Identifier: MIT\n//\n// Configure in plain JS: clabox.config.mjs (CWD) or\n// ~/.config/clabox/config.mjs. See clabox.config.example.mjs.\n\nimport path from 'node:path';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { runInit } from './init/scaffold.js';\nimport { generateProfile, profilePath, resolveProjectDir, runClaude } from './sandbox/run.js';\nimport { loadConfig, resolveBox } from './utils/config.js';\n\n/** Pick the explicit config path: a `--box <name>` wins over `--config <path>`. */\n// Index signature so any yargs argv (incl. commands with an empty builder)\n// is assignable — `box`/`config` are global options, present on every command.\nfunction explicitConfig(argv: {\n box?: unknown;\n config?: unknown;\n [k: string]: unknown;\n}): string | undefined {\n if (argv.box) return resolveBox(argv.box as string);\n return (argv.config as string | undefined) ?? undefined;\n}\n\nawait yargs(hideBin(process.argv))\n .scriptName('clabox')\n // Keep unknown flags (e.g. --dangerously-skip-permissions) as positionals so\n // they pass straight through to claude instead of erroring out.\n .parserConfiguration({ 'unknown-options-as-args': true })\n // clabox-owned flag: a config-file path that wins over CLABOX_CONFIG.\n .option('config', {\n type: 'string',\n describe: 'Path to a JS config file (overrides CLABOX_CONFIG)',\n })\n // clabox-owned flag: run a named config from the global configs dir.\n // (`-p` is left for claude's --print; `-c/-r/-d/-v` are claude flags too.)\n .option('box', {\n alias: 'b',\n type: 'string',\n describe: 'Run a named config from ~/.config/clabox/configs/<name>.config.mjs',\n })\n .command(\n ['run [claudeArgs..]', '$0 [claudeArgs..]'],\n 'Generate the profile and run claude inside the sandbox (default)',\n (y) =>\n y.positional('claudeArgs', {\n describe: 'Arguments passed through to claude',\n array: true,\n default: [] as string[],\n }),\n async (argv) => {\n const { config, configFile } = await loadConfig(explicitConfig(argv));\n const claudeArgs = (argv.claudeArgs ?? []) as string[];\n const code = runClaude(config, claudeArgs, { configFile });\n process.exit(code);\n },\n )\n .command(\n 'generate',\n 'Build the sandbox profile only and print its path',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(generateProfile(config));\n },\n )\n .command(\n 'profile',\n 'Print the sandbox profile path (no build)',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(profilePath(resolveProjectDir(config)));\n },\n )\n .command(\n 'init',\n 'Generate clabox-<name> shell aliases and build Ghostty apps for `app` boxes',\n (y) =>\n y\n .option('dir', {\n type: 'string',\n describe: 'Base dir holding configs/ and scripts/ (default:
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// clabox — run Claude Code in a sandbox for super-safe YOLO mode.\n// SPDX-License-Identifier: MIT\n//\n// Configure in plain JS: clabox.config.mjs (CWD) or\n// ~/.config/clabox/config.mjs. See clabox.config.example.mjs.\n\nimport path from 'node:path';\nimport { createLogger } from '@lsk4/log';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { formatInfo, gatherInfo } from './info/info.js';\nimport { runInit } from './init/scaffold.js';\nimport { generateProfile, profilePath, resolveProjectDir, runClaude } from './sandbox/run.js';\nimport { loadConfig, resolveBox } from './utils/config.js';\n\n/** Pick the explicit config path: a `--box <name>` wins over `--config <path>`. */\n// Index signature so any yargs argv (incl. commands with an empty builder)\n// is assignable — `box`/`config` are global options, present on every command.\nfunction explicitConfig(argv: {\n box?: unknown;\n config?: unknown;\n [k: string]: unknown;\n}): string | undefined {\n if (argv.box) return resolveBox(argv.box as string);\n return (argv.config as string | undefined) ?? undefined;\n}\n\nawait yargs(hideBin(process.argv))\n .scriptName('clabox')\n // Keep unknown flags (e.g. --dangerously-skip-permissions) as positionals so\n // they pass straight through to claude instead of erroring out.\n .parserConfiguration({ 'unknown-options-as-args': true })\n // clabox-owned flag: a config-file path that wins over CLABOX_CONFIG.\n .option('config', {\n type: 'string',\n describe: 'Path to a JS config file (overrides CLABOX_CONFIG)',\n })\n // clabox-owned flag: run a named config from the global configs dir.\n // (`-p` is left for claude's --print; `-c/-r/-d/-v` are claude flags too.)\n .option('box', {\n alias: 'b',\n type: 'string',\n describe: 'Run a named config from ~/.config/clabox/configs/<name>.config.mjs',\n })\n .command(\n ['run [claudeArgs..]', '$0 [claudeArgs..]'],\n 'Generate the profile and run claude inside the sandbox (default)',\n (y) =>\n y.positional('claudeArgs', {\n describe: 'Arguments passed through to claude',\n array: true,\n default: [] as string[],\n }),\n async (argv) => {\n const { config, configFile } = await loadConfig(explicitConfig(argv));\n const claudeArgs = (argv.claudeArgs ?? []) as string[];\n const code = runClaude(config, claudeArgs, { configFile });\n process.exit(code);\n },\n )\n .command(\n 'generate',\n 'Build the sandbox profile only and print its path',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(generateProfile(config));\n },\n )\n .command(\n 'profile',\n 'Print the sandbox profile path (no build)',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(profilePath(resolveProjectDir(config)));\n },\n )\n .command(\n 'info',\n 'Print clabox/version/box/config diagnostics for the resolved config',\n (y) => y,\n async (argv) => {\n const { config, configFile } = await loadConfig(explicitConfig(argv));\n const data = gatherInfo(config, { configFile, box: argv.box as string | undefined });\n const log = createLogger('clabox');\n // `.log` is the raw passthrough — keeps the aligned table intact (vs. the\n // per-line `ℹ clabox` prefix of `.info`); colorize only for a real TTY.\n log.log(formatInfo(data, { color: Boolean(process.stdout.isTTY) }));\n // Surface a hard blocker as a real warn (clabox can't run without these).\n if (!data.claudeBin) log.warn('claude binary not found on PATH');\n if (!data.sandboxExec) log.warn('sandbox-exec not found — clabox needs macOS');\n },\n )\n .command(\n 'init',\n 'Generate clabox-<name> shell aliases and build Ghostty apps for `app` boxes',\n (y) =>\n y\n .option('dir', {\n type: 'string',\n describe: 'Base dir holding configs/ and scripts/ (default: ~/.config/clabox)',\n })\n .option('apps', {\n type: 'boolean',\n default: true,\n describe: 'Build Ghostty apps for `app` boxes (use --no-apps to skip)',\n })\n .option('app', {\n type: 'string',\n describe: 'Build only this app box (by box name or app display name)',\n }),\n async (argv) => {\n const { profiles, indexFile, written, apps, raycastCommands, extraFiles, warnings } =\n await runInit({\n baseDir: argv.dir as string | undefined,\n buildApps: argv.apps as boolean,\n only: (argv.app as string | undefined) ?? null,\n });\n console.log(`clabox init: ${profiles.length} profile(s) → ${profiles.join(', ')}`);\n for (const f of written) console.log(` ${path.basename(f)}`);\n for (const f of extraFiles) console.log(` 🔌 ${f}`);\n for (const a of apps) console.log(` 📦 ${a.appPath} (${a.signed})`);\n for (const r of raycastCommands) console.log(` 🚀 ${r}`);\n for (const w of warnings) console.warn(` ⚠️ ${w}`);\n console.log(`\\nAdd to ~/.zshrc: source ${indexFile}`);\n if (raycastCommands.length > 0) {\n console.log(`Add to Raycast (Script Commands dir): ${path.dirname(raycastCommands[0])}`);\n }\n },\n )\n .example('$0 run --dangerously-skip-permissions', 'YOLO mode inside the sandbox')\n .example('$0 -b ax-root', 'Run the ~/.config/clabox/configs/ax-root.config.mjs box')\n .example('$0 info', 'Print version/box/config diagnostics for the resolved config')\n .example('$0 init', 'Generate shell aliases from ~/.config/clabox/configs/*.config.mjs')\n .example('$0 --config ./my.clabox.mjs run', 'Use a specific JS config file')\n .example('CLAUDE_CONFIG_DIR=~/.claude_work $0 run', 'Use a different Claude profile')\n .epilogue(\n [\n 'Config (later wins): defaults -> env vars -> JS config file.',\n 'File: ./clabox.config.mjs or ~/.config/clabox/config.mjs',\n '(or --config /path, or CLABOX_CONFIG=/path).',\n 'Named boxes: -b <name> -> ~/.config/clabox/configs/<name>.config.mjs',\n '(dir overridable via CLABOX_CONFIGS_DIR).',\n ].join('\\n'),\n )\n .version(false)\n .help()\n .alias('h', 'help')\n .fail((msg, err) => {\n console.error(`Error: ${err?.message ?? msg}`);\n process.exit(1);\n })\n .parseAsync();\n"],"mappings":"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"}
|
|
@@ -40,7 +40,7 @@ interface AppBuilderConfig {
|
|
|
40
40
|
signId: string | null;
|
|
41
41
|
/** Optional base Ghostty config emitted as a leading `config-file = …`. */
|
|
42
42
|
baseGhosttyConfig: string | null;
|
|
43
|
-
/** `clabox`
|
|
43
|
+
/** Absolute `clabox` path to pin in the generated `command`. null → bare `clabox` (PATH-resolved at launch). */
|
|
44
44
|
claboxBin: string | null;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
@@ -56,13 +56,32 @@ interface McpServer {
|
|
|
56
56
|
url?: string;
|
|
57
57
|
headers?: Record<string, string>;
|
|
58
58
|
}
|
|
59
|
+
/** A single hook command — one entry in claude's settings.json hook list. */
|
|
60
|
+
interface HookCommand {
|
|
61
|
+
type: 'command';
|
|
62
|
+
/** Shell command to run (e.g. an absolute path to a script). */
|
|
63
|
+
command: string;
|
|
64
|
+
/** Optional per-command timeout in seconds. */
|
|
65
|
+
timeout?: number;
|
|
66
|
+
}
|
|
67
|
+
/** A matcher group under a hook event. */
|
|
68
|
+
interface HookMatcher {
|
|
69
|
+
/** Tool-name matcher for `PreToolUse`/`PostToolUse`; omit for `Stop`/`Notification`/… */
|
|
70
|
+
matcher?: string;
|
|
71
|
+
hooks: HookCommand[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Per-box hooks, mirroring claude's settings.json `hooks` map: an event name
|
|
75
|
+
* (`Stop`, `Notification`, `PreToolUse`, …) → its matcher groups.
|
|
76
|
+
*/
|
|
77
|
+
type HooksConfig = Record<string, HookMatcher[]>;
|
|
59
78
|
/** Extra rules layered on top of the built-in base profile. */
|
|
60
79
|
interface PathRules {
|
|
61
80
|
/** RW subpaths (beyond project dir + configDir + /tmp). */
|
|
62
81
|
readWrite: string[];
|
|
63
82
|
/** RO subpaths. */
|
|
64
83
|
readOnly: string[];
|
|
65
|
-
/** process-exec subpaths. */
|
|
84
|
+
/** process-exec subpaths (e.g. a hook-scripts dir so `config.hooks` can run). */
|
|
66
85
|
exec: string[];
|
|
67
86
|
/** explicit deny subpaths (read + write). */
|
|
68
87
|
deny: string[];
|
|
@@ -83,7 +102,8 @@ interface Config {
|
|
|
83
102
|
claudeArgs: string[];
|
|
84
103
|
/**
|
|
85
104
|
* Per-box MCP servers (the `mcpServers` map). clabox compiles them to
|
|
86
|
-
* `<
|
|
105
|
+
* `<claboxHome>/mcp/<slug>.json` (i.e. `~/.config/clabox/mcp/…`, NOT the
|
|
106
|
+
* Claude configDir) and launches claude with
|
|
87
107
|
* `--strict-mcp-config --mcp-config <file>`, so a shared configDir's global /
|
|
88
108
|
* plugin MCP servers are ignored — each box gets exactly these and no more.
|
|
89
109
|
* Materialized on every `run` and during `init`. Absent → no MCP flags.
|
|
@@ -95,6 +115,15 @@ interface Config {
|
|
|
95
115
|
* sharing one configDir (the user-level CLAUDE.md is shared; this is not).
|
|
96
116
|
*/
|
|
97
117
|
systemPrompt?: string | string[];
|
|
118
|
+
/**
|
|
119
|
+
* Per-box hooks (claude's settings.json `hooks` map). clabox merges them into
|
|
120
|
+
* a settings JSON written to `<claboxHome>/settings/<slug>.json` (i.e.
|
|
121
|
+
* `~/.config/clabox/settings/…`, NOT the Claude configDir) and launches
|
|
122
|
+
* claude with `--settings <file>` — merging (not clobbering) any inline
|
|
123
|
+
* `--settings` already in `claudeArgs`, so `includeCoAuthoredBy` survives.
|
|
124
|
+
* Materialized on every `run` and during `init`. Absent → no settings flag.
|
|
125
|
+
*/
|
|
126
|
+
hooks?: HooksConfig;
|
|
98
127
|
bot: BotConfig;
|
|
99
128
|
/**
|
|
100
129
|
* Extra environment variables forced onto the sandboxed `claude` process,
|
|
@@ -106,8 +135,6 @@ interface Config {
|
|
|
106
135
|
network: boolean;
|
|
107
136
|
/** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */
|
|
108
137
|
ulimitProcs: number;
|
|
109
|
-
/** Extra directory granted read + execute inside the sandbox. null → disabled. */
|
|
110
|
-
hooksDir: string | null;
|
|
111
138
|
paths: PathRules;
|
|
112
139
|
/** Home subdirectories denied entirely (read + write). */
|
|
113
140
|
denyHome: string[];
|
|
@@ -135,6 +162,18 @@ declare function findConfigFile(explicit?: string | null): string | null;
|
|
|
135
162
|
* the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.
|
|
136
163
|
*/
|
|
137
164
|
declare function configsDir(): string;
|
|
165
|
+
/**
|
|
166
|
+
* Clabox's own home dir — the parent of {@link configsDir} (default
|
|
167
|
+
* `~/.config/clabox`, honoring `CLABOX_CONFIGS_DIR`). Holds clabox-owned
|
|
168
|
+
* generated artifacts (`scripts/`, `ghostty/`, `apps/`) and the per-box extras
|
|
169
|
+
* compiled by `buildBoxExtras` (`mcp/`, `settings/`). Deliberately kept OUT of
|
|
170
|
+
* Claude's `configDir` so clabox never pollutes Claude's own profile dir.
|
|
171
|
+
*/
|
|
172
|
+
declare function claboxHomeDir(): string;
|
|
173
|
+
/** `<claboxHome>/mcp` — per-box compiled `--mcp-config` json lives here. */
|
|
174
|
+
declare function claboxMcpDir(): string;
|
|
175
|
+
/** `<claboxHome>/settings` — per-box compiled `--settings` (hooks) json lives here. */
|
|
176
|
+
declare function claboxSettingsDir(): string;
|
|
138
177
|
/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */
|
|
139
178
|
declare function listBoxes(dir?: string): string[];
|
|
140
179
|
/**
|
|
@@ -157,5 +196,5 @@ interface LoadedConfig {
|
|
|
157
196
|
*/
|
|
158
197
|
declare function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig>;
|
|
159
198
|
//#endregion
|
|
160
|
-
export { HOME as a,
|
|
161
|
-
//# sourceMappingURL=config-
|
|
199
|
+
export { resolveBox as S, expandHome as _, HOME as a, loadConfig as b, HooksConfig as c, PathRules as d, claboxHomeDir as f, defaultConfig as g, configsDir as h, Config as i, LoadedConfig as l, claboxSettingsDir as m, AppConfig as n, HookCommand as o, claboxMcpDir as p, BotConfig as r, HookMatcher as s, AppBuilderConfig as t, McpServer as u, findConfigFile as v, mergeConfig as x, listBoxes as y };
|
|
200
|
+
//# sourceMappingURL=config-1vfbPBRi.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
+
import os from "node:os";
|
|
5
5
|
//#region src/utils/config.ts
|
|
6
6
|
const HOME = os.homedir();
|
|
7
7
|
/** Expand a leading `~` / `~/` to the user's home directory. */
|
|
@@ -26,7 +26,6 @@ const defaultConfig = {
|
|
|
26
26
|
env: {},
|
|
27
27
|
network: true,
|
|
28
28
|
ulimitProcs: 1024,
|
|
29
|
-
hooksDir: env.CLABOX_HOOKS_DIR ?? null,
|
|
30
29
|
paths: {
|
|
31
30
|
readWrite: [],
|
|
32
31
|
readOnly: [],
|
|
@@ -94,6 +93,24 @@ function findConfigFile(explicit) {
|
|
|
94
93
|
function configsDir() {
|
|
95
94
|
return expandHome(env.CLABOX_CONFIGS_DIR ?? "~/.config/clabox/configs");
|
|
96
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Clabox's own home dir — the parent of {@link configsDir} (default
|
|
98
|
+
* `~/.config/clabox`, honoring `CLABOX_CONFIGS_DIR`). Holds clabox-owned
|
|
99
|
+
* generated artifacts (`scripts/`, `ghostty/`, `apps/`) and the per-box extras
|
|
100
|
+
* compiled by `buildBoxExtras` (`mcp/`, `settings/`). Deliberately kept OUT of
|
|
101
|
+
* Claude's `configDir` so clabox never pollutes Claude's own profile dir.
|
|
102
|
+
*/
|
|
103
|
+
function claboxHomeDir() {
|
|
104
|
+
return path.dirname(configsDir());
|
|
105
|
+
}
|
|
106
|
+
/** `<claboxHome>/mcp` — per-box compiled `--mcp-config` json lives here. */
|
|
107
|
+
function claboxMcpDir() {
|
|
108
|
+
return path.join(claboxHomeDir(), "mcp");
|
|
109
|
+
}
|
|
110
|
+
/** `<claboxHome>/settings` — per-box compiled `--settings` (hooks) json lives here. */
|
|
111
|
+
function claboxSettingsDir() {
|
|
112
|
+
return path.join(claboxHomeDir(), "settings");
|
|
113
|
+
}
|
|
97
114
|
const BOX_SUFFIXES = [".config.mjs", ".mjs"];
|
|
98
115
|
/** Candidate file paths for a box name, in resolution order. */
|
|
99
116
|
function boxCandidates(name, dir) {
|
|
@@ -150,6 +167,6 @@ async function loadConfig(explicitConfig) {
|
|
|
150
167
|
};
|
|
151
168
|
}
|
|
152
169
|
//#endregion
|
|
153
|
-
export {
|
|
170
|
+
export { configsDir as a, findConfigFile as c, mergeConfig as d, resolveBox as f, claboxSettingsDir as i, listBoxes as l, claboxHomeDir as n, defaultConfig as o, claboxMcpDir as r, expandHome as s, HOME as t, loadConfig as u };
|
|
154
171
|
|
|
155
|
-
//# sourceMappingURL=config-
|
|
172
|
+
//# sourceMappingURL=config-CY_Cf87P.js.map
|