clabox 0.1.1 → 0.2.1

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 (47) hide show
  1. package/docs/guideline.md +10 -5
  2. package/lib/{aliases-DXyz-ufw.d.ts → aliases-BNdrOn9E.d.ts} +7 -3
  3. package/lib/{aliases-DKGcMHHe.js → aliases-KGjds7dK.js} +2 -2
  4. package/lib/aliases-KGjds7dK.js.map +1 -0
  5. package/lib/{app-DzQ5yZfD.d.ts → app-B1djcEnN.d.ts} +2 -2
  6. package/lib/{app-CQmESEdh.js → app-ChnKbgkD.js} +3 -3
  7. package/lib/{app-CQmESEdh.js.map → app-ChnKbgkD.js.map} +1 -1
  8. package/lib/cli.js +4 -3
  9. package/lib/cli.js.map +1 -1
  10. package/lib/config-BQ44iVWT.js.map +1 -1
  11. package/lib/{config-DQWueb4a.d.ts → config-jZAB2Zg8.d.ts} +29 -2
  12. package/lib/extras-B2ss2Cgh.js +46 -0
  13. package/lib/extras-B2ss2Cgh.js.map +1 -0
  14. package/lib/extras-B_dzBrCF.d.ts +34 -0
  15. package/lib/{ghostty-By4zmOuk.d.ts → ghostty-DFwFh4aL.d.ts} +14 -5
  16. package/lib/{ghostty-DcMEZ6Ey.js → ghostty-Dw4QLyl-.js} +2 -2
  17. package/lib/ghostty-Dw4QLyl-.js.map +1 -0
  18. package/lib/index.d.ts +10 -9
  19. package/lib/index.js +7 -6
  20. package/lib/init/aliases.d.ts +1 -1
  21. package/lib/init/aliases.js +1 -1
  22. package/lib/init/app.d.ts +1 -1
  23. package/lib/init/app.js +1 -1
  24. package/lib/init/ghostty.d.ts +1 -1
  25. package/lib/init/ghostty.js +1 -1
  26. package/lib/init/raycast.d.ts +1 -1
  27. package/lib/init/scaffold.d.ts +1 -1
  28. package/lib/init/scaffold.js +1 -1
  29. package/lib/{profile-BeM41NXc.d.ts → profile-7CiMUPu9.d.ts} +2 -2
  30. package/lib/{raycast-DM7c559f.d.ts → raycast-fc5U1x7E.d.ts} +2 -2
  31. package/lib/{run-Cx8cuTh5.d.ts → run-BWGDNJiY.d.ts} +11 -3
  32. package/lib/{run-CNehSQ-S.js → run-yUsOaKFx.js} +22 -3
  33. package/lib/run-yUsOaKFx.js.map +1 -0
  34. package/lib/sandbox/extras.d.ts +2 -0
  35. package/lib/sandbox/extras.js +2 -0
  36. package/lib/sandbox/profile.d.ts +1 -1
  37. package/lib/sandbox/run.d.ts +2 -2
  38. package/lib/sandbox/run.js +2 -2
  39. package/lib/{scaffold-B7pUVGoC.js → scaffold-C1tqCL_8.js} +47 -15
  40. package/lib/scaffold-C1tqCL_8.js.map +1 -0
  41. package/lib/{scaffold-ByIbYAeS.d.ts → scaffold-CbsKxlL0.d.ts} +3 -1
  42. package/lib/utils/config.d.ts +2 -2
  43. package/package.json +1 -1
  44. package/lib/aliases-DKGcMHHe.js.map +0 -1
  45. package/lib/ghostty-DcMEZ6Ey.js.map +0 -1
  46. package/lib/run-CNehSQ-S.js.map +0 -1
  47. package/lib/scaffold-B7pUVGoC.js.map +0 -1
package/docs/guideline.md CHANGED
@@ -36,7 +36,8 @@ src/
36
36
  ├── sandbox/
37
37
  │ ├── profile.ts # pure SBPL builder: buildProfile, detectPackagePaths,
38
38
  │ │ # subpath/literal/regex/globalName/ipcName/reEscape helpers
39
- └── run.ts # I/O: profilePath, generateProfile, runClaude (sandbox-exec launch)
39
+ ├── extras.ts # pure: boxSlug, buildBoxExtras (per-box mcp/systemPrompt → args+files)
40
+ │ └── run.ts # I/O: profilePath, generateProfile, writeExtraFiles, runClaude
40
41
  ├── init/
41
42
  │ ├── aliases.ts # pure: aliasName, buildIndex, buildWrapper, buildAliasFiles
42
43
  │ ├── ghostty.ts # pure: buildGhosttyConfig, buildCommand, buildLauncherSource,
@@ -50,6 +51,7 @@ src/
50
51
  tests/
51
52
  ├── profile.test.ts # bun:test — unit (profile text) + functional (real sandbox-exec)
52
53
  ├── 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
53
55
  └── box.test.ts # bun:test — named-box resolution (tmp-dir fs)
54
56
  lib/ # build output (tsdown) — gitignored
55
57
  docs/
@@ -106,18 +108,21 @@ clabox run → loadConfig() → buildProfile() → <TMPDIR>/…sb
106
108
  ```
107
109
 
108
110
  ### `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.
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>`) and `systemPrompt` (`string | string[]`) — declarative per-box MCP / pre-prompt compiled by `sandbox/extras.ts` (see below).
110
112
 
111
113
  **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
114
 
113
115
  ### `sandbox/profile.ts` (pure)
114
116
  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
117
 
118
+ ### `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 `<configDir>/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). The json lives under `configDir` because the sandbox grants it RW — and because `~/.config/*` is hard-denied, it can't sit next to the box config.
120
+
116
121
  ### `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.
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 MCP json). `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.)
118
123
 
119
124
  ### `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, … }`.
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 `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`. It also materializes each box's MCP json (`materializeExtras`, best-effort per box). Absolute paths are baked in so the scripts run from any cwd. It returns `{ profiles, written, apps, ghosttyConfigs, raycastCommands, extraFiles, warnings, … }`.
121
126
 
122
127
  ### `init/ghostty.ts` + `init/raycast.ts` (pure) + `init/app.ts` (I/O) — standalone Ghostty apps
123
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 — 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`, 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.
@@ -130,7 +135,7 @@ The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default)
130
135
  - **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
136
  - **Network:** `(allow network*)` when `network: true` (default).
132
137
  - **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.
138
+ - **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).
134
139
  - **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
140
 
136
141
  ### Git/ssh bot identity
@@ -10,8 +10,12 @@ interface InitFile {
10
10
  }
11
11
  /** Absolute locations for the generated artifacts. */
12
12
  interface AliasPaths {
13
- /** Dir holding the box config files. */
14
- configsDir: string;
13
+ /**
14
+ * Dir holding the box config files, baked as `CLABOX_CONFIGS_DIR` so `-b`
15
+ * resolves boxes from any cwd, or null to omit it — when null, `-b` finds the
16
+ * box via the runtime default (`~/.config/clabox/configs`) at run time.
17
+ */
18
+ configsDir: string | null;
15
19
  /** Dir to emit `index.sh` and the per-box wrappers into. */
16
20
  scriptsDir: string;
17
21
  }
@@ -28,4 +32,4 @@ declare function buildWrapper(fnName: string, indexPath: string): string;
28
32
  declare function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[];
29
33
  //#endregion
30
34
  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
35
+ //# sourceMappingURL=aliases-BNdrOn9E.d.ts.map
@@ -20,7 +20,7 @@ function buildIndex(profiles, { configsDir, scriptsDir }) {
20
20
  ...usage,
21
21
  "",
22
22
  "_clabox_run() {",
23
- ` CLABOX_CONFIGS_DIR="${configsDir}" clabox -b "$1" "\${@:2}"`,
23
+ configsDir ? ` CLABOX_CONFIGS_DIR="${configsDir}" clabox -b "$1" "\${@:2}"` : ` clabox -b "$1" "\${@:2}"`,
24
24
  "}",
25
25
  "",
26
26
  ...defs
@@ -57,4 +57,4 @@ function buildAliasFiles(profiles, paths) {
57
57
  //#endregion
58
58
  export { buildWrapper as i, buildAliasFiles as n, buildIndex as r, aliasName as t };
59
59
 
60
- //# sourceMappingURL=aliases-DKGcMHHe.js.map
60
+ //# sourceMappingURL=aliases-KGjds7dK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aliases-KGjds7dK.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 /**\n * Dir holding the box config files, baked as `CLABOX_CONFIGS_DIR` so `-b`\n * resolves boxes from any cwd, or null to omit it — when null, `-b` finds the\n * box via the runtime default (`~/.config/clabox/configs`) at run time.\n */\n configsDir: string | null;\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 // — unless it's the runtime default, then let `-b` resolve it at run time.\n configsDir\n ? ` CLABOX_CONFIGS_DIR=\"${configsDir}\" clabox -b \"$1\" \"\\${@:2}\"`\n : ` 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":";;;AAgCA,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;EAGA,aACI,yBAAyB,WAAW,8BACpC;EACJ;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"}
@@ -1,4 +1,4 @@
1
- import { n as AppConfig, t as AppBuilderConfig } from "./config-DQWueb4a.js";
1
+ import { n as AppConfig, t as AppBuilderConfig } from "./config-jZAB2Zg8.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-DzQ5yZfD.d.ts.map
32
+ //# sourceMappingURL=app-B1djcEnN.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import { i as expandHome } from "./config-BQ44iVWT.js";
2
- import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
2
+ import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
3
3
  import path from "node:path";
4
- import { execFileSync } from "node:child_process";
5
4
  import fs from "node:fs";
6
5
  import os from "node:os";
6
+ import { execFileSync } from "node:child_process";
7
7
  //#region src/init/app.ts
8
8
  function run(cmd, args) {
9
9
  execFileSync(cmd, args, { stdio: [
@@ -251,4 +251,4 @@ function buildApp(opts) {
251
251
  //#endregion
252
252
  export { canBuildApps as n, installIcon as r, buildApp as t };
253
253
 
254
- //# sourceMappingURL=app-CQmESEdh.js.map
254
+ //# sourceMappingURL=app-ChnKbgkD.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-CQmESEdh.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 const plist = path.join(appPath, '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(appPath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, appPath]);\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(appPath, '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, appPath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle.\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, appPath]);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-ChnKbgkD.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 const plist = path.join(appPath, '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(appPath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, appPath]);\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(appPath, '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, appPath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle.\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, appPath]);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
package/lib/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { l as resolveBox, s as loadConfig } from "./config-BQ44iVWT.js";
3
- import { n as runInit } from "./scaffold-B7pUVGoC.js";
4
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-CNehSQ-S.js";
3
+ import { n as runInit } from "./scaffold-C1tqCL_8.js";
4
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-yUsOaKFx.js";
5
5
  import path from "node:path";
6
6
  import yargs from "yargs";
7
7
  import { hideBin } from "yargs/helpers";
@@ -43,13 +43,14 @@ await yargs(hideBin(process.argv)).scriptName("clabox").parserConfiguration({ "u
43
43
  type: "string",
44
44
  describe: "Build only this app box (by box name or app display name)"
45
45
  }), async (argv) => {
46
- const { profiles, indexFile, written, apps, raycastCommands, warnings } = await runInit({
46
+ const { profiles, indexFile, written, apps, raycastCommands, extraFiles, warnings } = await runInit({
47
47
  baseDir: argv.dir,
48
48
  buildApps: argv.apps,
49
49
  only: argv.app ?? null
50
50
  });
51
51
  console.log(`clabox init: ${profiles.length} profile(s) → ${profiles.join(", ")}`);
52
52
  for (const f of written) console.log(` ${path.basename(f)}`);
53
+ for (const f of extraFiles) console.log(` 🔌 ${f}`);
53
54
  for (const a of apps) console.log(` 📦 ${a.appPath} (${a.signed})`);
54
55
  for (const r of raycastCommands) console.log(` 🚀 ${r}`);
55
56
  for (const w of warnings) console.warn(` ⚠️ ${w}`);
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: ./__)',\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, warnings } = 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 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 init', 'Generate shell aliases from __/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":";;;;;;;;;AAiBA,SAAS,eAAe,MAID;CACrB,IAAI,KAAK,KAAK,OAAO,WAAW,KAAK,GAAa;CAClD,OAAQ,KAAK,UAAiC,KAAA;AAChD;AAEA,MAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,CAC/B,WAAW,QAAQ,CAAC,CAGpB,oBAAoB,EAAE,2BAA2B,KAAK,CAAC,CAAC,CAExD,OAAO,UAAU;CAChB,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CAGD,OAAO,OAAO;CACb,OAAO;CACP,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CACD,QACC,CAAC,sBAAsB,mBAAmB,GAC1C,qEACC,MACC,EAAE,WAAW,cAAc;CACzB,UAAU;CACV,OAAO;CACP,SAAS,CAAC;AACZ,CAAC,GACH,OAAO,SAAS;CACd,MAAM,EAAE,QAAQ,eAAe,MAAM,WAAW,eAAe,IAAI,CAAC;CAEpE,MAAM,OAAO,UAAU,QADH,KAAK,cAAc,CAAC,GACG,EAAE,WAAW,CAAC;CACzD,QAAQ,KAAK,IAAI;AACnB,CACF,CAAC,CACA,QACC,YACA,sDACC,MAAM,GACP,OAAO,SAAS;CACd,MAAM,EAAE,WAAW,MAAM,WAAW,eAAe,IAAI,CAAC;CACxD,QAAQ,IAAI,gBAAgB,MAAM,CAAC;AACrC,CACF,CAAC,CACA,QACC,WACA,8CACC,MAAM,GACP,OAAO,SAAS;CACd,MAAM,EAAE,WAAW,MAAM,WAAW,eAAe,IAAI,CAAC;CACxD,QAAQ,IAAI,YAAY,kBAAkB,MAAM,CAAC,CAAC;AACpD,CACF,CAAC,CACA,QACC,QACA,gFACC,MACC,EACG,OAAO,OAAO;CACb,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CACD,OAAO,QAAQ;CACd,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC,CAAC,CACD,OAAO,OAAO;CACb,MAAM;CACN,UAAU;AACZ,CAAC,GACL,OAAO,SAAS;CACd,MAAM,EAAE,UAAU,WAAW,SAAS,MAAM,iBAAiB,aAAa,MAAM,QAAQ;EACtF,SAAS,KAAK;EACd,WAAW,KAAK;EAChB,MAAO,KAAK,OAA8B;CAC5C,CAAC;CACD,QAAQ,IAAI,gBAAgB,SAAS,OAAO,gBAAgB,SAAS,KAAK,IAAI,GAAG;CACjF,KAAK,MAAM,KAAK,SAAS,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,GAAG;CAC5D,KAAK,MAAM,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,QAAQ,IAAI,EAAE,OAAO,EAAE;CACnE,KAAK,MAAM,KAAK,iBAAiB,QAAQ,IAAI,QAAQ,GAAG;CACxD,KAAK,MAAM,KAAK,UAAU,QAAQ,KAAK,SAAS,GAAG;CACnD,QAAQ,IAAI,8BAA8B,WAAW;CACrD,IAAI,gBAAgB,SAAS,GAC3B,QAAQ,IAAI,0CAA0C,KAAK,QAAQ,gBAAgB,EAAE,GAAG;AAE5F,CACF,CAAC,CACA,QAAQ,yCAAyC,8BAA8B,CAAC,CAChF,QAAQ,iBAAiB,yDAAyD,CAAC,CACnF,QAAQ,WAAW,qDAAqD,CAAC,CACzE,QAAQ,mCAAmC,+BAA+B,CAAC,CAC3E,QAAQ,2CAA2C,gCAAgC,CAAC,CACpF,SACC;CACE;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI,CACb,CAAC,CACA,QAAQ,KAAK,CAAC,CACd,KAAK,CAAC,CACN,MAAM,KAAK,MAAM,CAAC,CAClB,MAAM,KAAK,QAAQ;CAClB,QAAQ,MAAM,UAAU,KAAK,WAAW,KAAK;CAC7C,QAAQ,KAAK,CAAC;AAChB,CAAC,CAAC,CACD,WAAW"}
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: ./__)',\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 init', 'Generate shell aliases from __/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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config-BQ44iVWT.js","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["// Configuration: sane defaults, env overrides, and an optional JS config file.\n//\n// Resolution order (later wins):\n// 1. defaultConfig (below)\n// 2. env vars (CLAUDE_CONFIG_DIR, CLABOX_*, …)\n// 3. a JS config file (see loadConfig)\n//\n// A config file default-exports either a plain object (merged over the defaults)\n// or a function `(defaults) => config` for full programmatic control.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport const HOME = os.homedir();\n\n/** Expand a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (!p) return p;\n if (p === '~') return HOME;\n if (p.startsWith('~/')) return path.join(HOME, p.slice(2));\n return p;\n}\n\nconst env = process.env;\n\n/** Dedicated git/ssh identity for commits made from inside the sandbox. */\nexport interface BotConfig {\n name: string;\n email: string;\n /** If `${sshDir}/id_ed25519` exists, git ssh is pinned to it. */\n sshDir: string;\n}\n\n/**\n * Opt-in marker that turns a box into a standalone Ghostty app. When a box\n * config carries an `app`, `clabox init` generates a Ghostty config for it and\n * builds a cloned `<appsDir>/<name>.app` that launches `clabox -b <box>`.\n */\nexport interface AppConfig {\n /** App display name → `<appsDir>/<name>.app` + CFBundleName. */\n name: string;\n /** Ghostty window title. Defaults to {@link AppConfig.name}. */\n title?: string;\n /** Emoji for the generated Raycast command. Default: the title's leading emoji. */\n emoji?: string;\n /** Path to a `.icns`/`.png` icon for the .app. `.png` is converted. `~` ok. */\n icon?: string;\n /** Ghostty built-in `macos-icon` (e.g. `retro`, `holographic`). */\n macosIcon?: string;\n /** Extra raw `key = value` lines appended to the generated Ghostty config. */\n ghostty?: Record<string, string>;\n /** Bundle id. Default: `com.ghostty.custom.<name dot-joined>`. */\n bundleId?: string;\n}\n\n/** Machine-wide settings for the `clabox init` Ghostty-app builder. */\nexport interface AppBuilderConfig {\n /** Donor app to clone. `~` is expanded. */\n ghosttyApp: string;\n /** Where built apps land. `~` is expanded. */\n appsDir: string;\n /** codesign identity. null → ad-hoc (`codesign -s -`). */\n signId: string | null;\n /** Optional base Ghostty config emitted as a leading `config-file = …`. */\n baseGhosttyConfig: string | null;\n /** `clabox` binary baked into the generated `command`. null → autodetect. */\n claboxBin: string | null;\n}\n\n/** Extra rules layered on top of the built-in base profile. */\nexport interface PathRules {\n /** RW subpaths (beyond project dir + configDir + /tmp). */\n readWrite: string[];\n /** RO subpaths. */\n readOnly: string[];\n /** process-exec subpaths. */\n exec: string[];\n /** explicit deny subpaths (read + write). */\n deny: string[];\n}\n\n/** Effective clabox configuration. */\nexport interface Config {\n /**\n * Working directory to run `claude` in (and grant RW as the project dir).\n * null → the shell's CWD. Handy for named boxes that always target one\n * project regardless of where `clabox` is invoked from. `~` is expanded.\n */\n cwd: string | null;\n /** Path to the `claude` binary. null → autodetect (PATH, then ~/.local/bin). */\n claudeBin: string | null;\n /** Claude config/profile directory — supports multiple accounts. */\n configDir: string;\n /** Extra args always passed to `claude`, before any args from the CLI. */\n claudeArgs: string[];\n bot: BotConfig;\n /**\n * Extra environment variables forced onto the sandboxed `claude` process,\n * layered over the inherited shell env and after the built-in hardening vars\n * (so a key set here wins). Use it to pass secrets like `GITHUB_TOKEN`.\n */\n env: Record<string, string>;\n /** Allow outbound network. `false` → no `(allow network*)` line. */\n network: boolean;\n /** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */\n ulimitProcs: number;\n /** Extra directory granted read + execute inside the sandbox. null → disabled. */\n hooksDir: string | null;\n paths: PathRules;\n /** Home subdirectories denied entirely (read + write). */\n denyHome: string[];\n /** Dotfile config dirs under $HOME denied entirely. */\n denyDotConfigs: string[];\n /**\n * Opt-in: build a standalone Ghostty app for this box during `clabox init`.\n * Absent → the box only gets a shell alias (the default).\n */\n app?: AppConfig;\n /** Machine-wide settings for the `clabox init` Ghostty-app builder. */\n appBuilder: AppBuilderConfig;\n}\n\n/** Built-in defaults. Everything here is meant to be overridable. */\nexport const defaultConfig: Config = {\n cwd: env.CLABOX_CWD ?? null,\n claudeBin: env.CLABOX_CLAUDE_BIN ?? null,\n configDir: env.CLAUDE_CONFIG_DIR ?? '~/.claude',\n claudeArgs: ['--settings', '{\"includeCoAuthoredBy\": false}'],\n bot: {\n name: env.CLABOX_BOT_NAME ?? 'claudeBOT',\n email: env.CLABOX_BOT_EMAIL ?? 'bot@example.com',\n sshDir: env.CLABOX_BOT_SSH_DIR ?? '~/.ssh/claudebot',\n },\n env: {},\n network: true,\n ulimitProcs: 1024,\n hooksDir: env.CLABOX_HOOKS_DIR ?? null,\n paths: {\n readWrite: [],\n readOnly: [],\n exec: [],\n deny: [],\n },\n denyHome: ['Documents', 'Desktop', 'Downloads', 'Pictures', 'Movies', 'Music'],\n // `.config/git` is always carved back out for git RO config in the profile.\n denyDotConfigs: ['aws', 'gnupg', 'kube', 'docker', 'config'],\n // `app` is opt-in per box, so there's no default — it stays undefined.\n appBuilder: {\n ghosttyApp: env.CLABOX_GHOSTTY_APP ?? '/Applications/Ghostty.app',\n appsDir: env.CLABOX_APPS_DIR ?? '~/Applications',\n signId: env.CLABOX_SIGN_ID ?? null,\n baseGhosttyConfig: env.CLABOX_GHOSTTY_BASE_CONFIG ?? null,\n claboxBin: env.CLABOX_CLABOX_BIN ?? null,\n },\n};\n\ntype Plain = Record<string, unknown>;\n\nfunction isPlainObject(v: unknown): v is Plain {\n return v != null && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** Shallow-deep merge: nested plain objects merge, everything else replaces. */\nfunction deepMerge(base: Plain, override: Plain): Plain {\n const out: Plain = { ...base };\n for (const [key, val] of Object.entries(override)) {\n if (val === undefined) continue;\n const baseVal = base[key];\n out[key] = isPlainObject(val) && isPlainObject(baseVal) ? deepMerge(baseVal, val) : val;\n }\n return out;\n}\n\n/** Merge a (partial) override over a full config, returning a new config. */\nexport function mergeConfig(base: Config, override: unknown): Config {\n if (!isPlainObject(override)) return base;\n return deepMerge(base as unknown as Plain, override) as unknown as Config;\n}\n\n/**\n * Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),\n * then CWD, then ~/.config. The CLI arg wins over the env var.\n */\nexport function findConfigFile(explicit?: string | null): string | null {\n const chosen = explicit ?? env.CLABOX_CONFIG;\n if (chosen) return expandHome(chosen);\n const candidates = [\n path.join(process.cwd(), 'clabox.config.mjs'),\n path.join(process.cwd(), 'clabox.config.js'),\n path.join(HOME, '.config', 'clabox', 'config.mjs'),\n ];\n return candidates.find((c) => fs.existsSync(c)) ?? null;\n}\n\n/**\n * Global directory holding named \"box\" configs (`<name>.config.mjs`), used by\n * the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.\n */\nexport function configsDir(): string {\n return expandHome(env.CLABOX_CONFIGS_DIR ?? '~/.config/clabox/configs');\n}\n\nconst BOX_SUFFIXES = ['.config.mjs', '.mjs'];\n\n/** Candidate file paths for a box name, in resolution order. */\nfunction boxCandidates(name: string, dir: string): string[] {\n return BOX_SUFFIXES.map((s) => path.join(dir, `${name}${s}`));\n}\n\n/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */\nexport function listBoxes(dir: string = configsDir()): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n const names = new Set<string>();\n for (const f of entries) {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes.\n if (f.startsWith('_')) continue;\n const suffix = BOX_SUFFIXES.find((s) => f.endsWith(s));\n if (suffix) names.add(f.slice(0, -suffix.length));\n }\n return [...names].sort();\n}\n\n/**\n * Resolve a box name to its config file in {@link configsDir}, preferring\n * `<name>.config.mjs` over a bare `<name>.mjs`.\n *\n * @throws if no matching file exists (the message lists the available boxes).\n */\nexport function resolveBox(name: string, dir: string = configsDir()): string {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes —\n // keep them un-resolvable so `-b` matches what `listBoxes` advertises.\n if (!name.startsWith('_')) {\n const found = boxCandidates(name, dir).find((c) => fs.existsSync(c));\n if (found) return found;\n }\n const available = listBoxes(dir);\n const hint = available.length ? `available: ${available.join(', ')}` : `none found in ${dir}`;\n throw new Error(`clabox: box '${name}' not found in ${dir} (${hint})`);\n}\n\n/** Result of {@link loadConfig}: the effective config and the file it came from. */\nexport interface LoadedConfig {\n config: Config;\n configFile: string | null;\n}\n\n/**\n * Build the effective config: defaults ⊕ env ⊕ config file.\n *\n * @param explicitConfig optional config-file path (e.g. from `--config`);\n * takes precedence over `CLABOX_CONFIG` and the default lookup locations.\n */\nexport async function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig> {\n let cfg: Config = defaultConfig;\n const file = findConfigFile(explicitConfig);\n if (file) {\n const mod = await import(pathToFileURL(file).href);\n const exported = mod.default ?? mod.config ?? mod;\n const resolved = typeof exported === 'function' ? await exported(defaultConfig) : exported;\n cfg = mergeConfig(defaultConfig, resolved);\n }\n return { config: cfg, configFile: file };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-BQ44iVWT.js","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["// Configuration: sane defaults, env overrides, and an optional JS config file.\n//\n// Resolution order (later wins):\n// 1. defaultConfig (below)\n// 2. env vars (CLAUDE_CONFIG_DIR, CLABOX_*, …)\n// 3. a JS config file (see loadConfig)\n//\n// A config file default-exports either a plain object (merged over the defaults)\n// or a function `(defaults) => config` for full programmatic control.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport const HOME = os.homedir();\n\n/** Expand a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (!p) return p;\n if (p === '~') return HOME;\n if (p.startsWith('~/')) return path.join(HOME, p.slice(2));\n return p;\n}\n\nconst env = process.env;\n\n/** Dedicated git/ssh identity for commits made from inside the sandbox. */\nexport interface BotConfig {\n name: string;\n email: string;\n /** If `${sshDir}/id_ed25519` exists, git ssh is pinned to it. */\n sshDir: string;\n}\n\n/**\n * Opt-in marker that turns a box into a standalone Ghostty app. When a box\n * config carries an `app`, `clabox init` generates a Ghostty config for it and\n * builds a cloned `<appsDir>/<name>.app` that launches `clabox -b <box>`.\n */\nexport interface AppConfig {\n /** App display name → `<appsDir>/<name>.app` + CFBundleName. */\n name: string;\n /** Ghostty window title. Defaults to {@link AppConfig.name}. */\n title?: string;\n /** Emoji for the generated Raycast command. Default: the title's leading emoji. */\n emoji?: string;\n /** Path to a `.icns`/`.png` icon for the .app. `.png` is converted. `~` ok. */\n icon?: string;\n /** Ghostty built-in `macos-icon` (e.g. `retro`, `holographic`). */\n macosIcon?: string;\n /** Extra raw `key = value` lines appended to the generated Ghostty config. */\n ghostty?: Record<string, string>;\n /** Bundle id. Default: `com.ghostty.custom.<name dot-joined>`. */\n bundleId?: string;\n}\n\n/** Machine-wide settings for the `clabox init` Ghostty-app builder. */\nexport interface AppBuilderConfig {\n /** Donor app to clone. `~` is expanded. */\n ghosttyApp: string;\n /** Where built apps land. `~` is expanded. */\n appsDir: string;\n /** codesign identity. null → ad-hoc (`codesign -s -`). */\n signId: string | null;\n /** Optional base Ghostty config emitted as a leading `config-file = …`. */\n baseGhosttyConfig: string | null;\n /** `clabox` binary baked into the generated `command`. null → autodetect. */\n claboxBin: string | null;\n}\n\n/**\n * A single MCP server entry — the value under a key in claude's `mcpServers`\n * map. Loose on purpose (mirrors claude's mcp.json schema): `stdio` servers use\n * `command`/`args`/`env`, remote `http`/`sse` servers use `url`/`headers`.\n */\nexport interface McpServer {\n type?: 'stdio' | 'http' | 'sse';\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n url?: string;\n headers?: Record<string, string>;\n}\n\n/** Extra rules layered on top of the built-in base profile. */\nexport interface PathRules {\n /** RW subpaths (beyond project dir + configDir + /tmp). */\n readWrite: string[];\n /** RO subpaths. */\n readOnly: string[];\n /** process-exec subpaths. */\n exec: string[];\n /** explicit deny subpaths (read + write). */\n deny: string[];\n}\n\n/** Effective clabox configuration. */\nexport interface Config {\n /**\n * Working directory to run `claude` in (and grant RW as the project dir).\n * null → the shell's CWD. Handy for named boxes that always target one\n * project regardless of where `clabox` is invoked from. `~` is expanded.\n */\n cwd: string | null;\n /** Path to the `claude` binary. null → autodetect (PATH, then ~/.local/bin). */\n claudeBin: string | null;\n /** Claude config/profile directory — supports multiple accounts. */\n configDir: string;\n /** Extra args always passed to `claude`, before any args from the CLI. */\n claudeArgs: string[];\n /**\n * Per-box MCP servers (the `mcpServers` map). clabox compiles them to\n * `<configDir>/mcp/<slug>.json` and launches claude with\n * `--strict-mcp-config --mcp-config <file>`, so a shared configDir's global /\n * plugin MCP servers are ignored — each box gets exactly these and no more.\n * Materialized on every `run` and during `init`. Absent → no MCP flags.\n */\n mcp?: Record<string, McpServer>;\n /**\n * Text appended to claude's system prompt via `--append-system-prompt`.\n * `string[]` is joined with blank lines. Use it for per-box pre-prompts while\n * sharing one configDir (the user-level CLAUDE.md is shared; this is not).\n */\n systemPrompt?: string | string[];\n bot: BotConfig;\n /**\n * Extra environment variables forced onto the sandboxed `claude` process,\n * layered over the inherited shell env and after the built-in hardening vars\n * (so a key set here wins). Use it to pass secrets like `GITHUB_TOKEN`.\n */\n env: Record<string, string>;\n /** Allow outbound network. `false` → no `(allow network*)` line. */\n network: boolean;\n /** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */\n ulimitProcs: number;\n /** Extra directory granted read + execute inside the sandbox. null → disabled. */\n hooksDir: string | null;\n paths: PathRules;\n /** Home subdirectories denied entirely (read + write). */\n denyHome: string[];\n /** Dotfile config dirs under $HOME denied entirely. */\n denyDotConfigs: string[];\n /**\n * Opt-in: build a standalone Ghostty app for this box during `clabox init`.\n * Absent → the box only gets a shell alias (the default).\n */\n app?: AppConfig;\n /** Machine-wide settings for the `clabox init` Ghostty-app builder. */\n appBuilder: AppBuilderConfig;\n}\n\n/** Built-in defaults. Everything here is meant to be overridable. */\nexport const defaultConfig: Config = {\n cwd: env.CLABOX_CWD ?? null,\n claudeBin: env.CLABOX_CLAUDE_BIN ?? null,\n configDir: env.CLAUDE_CONFIG_DIR ?? '~/.claude',\n claudeArgs: ['--settings', '{\"includeCoAuthoredBy\": false}'],\n bot: {\n name: env.CLABOX_BOT_NAME ?? 'claudeBOT',\n email: env.CLABOX_BOT_EMAIL ?? 'bot@example.com',\n sshDir: env.CLABOX_BOT_SSH_DIR ?? '~/.ssh/claudebot',\n },\n env: {},\n network: true,\n ulimitProcs: 1024,\n hooksDir: env.CLABOX_HOOKS_DIR ?? null,\n paths: {\n readWrite: [],\n readOnly: [],\n exec: [],\n deny: [],\n },\n denyHome: ['Documents', 'Desktop', 'Downloads', 'Pictures', 'Movies', 'Music'],\n // `.config/git` is always carved back out for git RO config in the profile.\n denyDotConfigs: ['aws', 'gnupg', 'kube', 'docker', 'config'],\n // `app` is opt-in per box, so there's no default — it stays undefined.\n appBuilder: {\n ghosttyApp: env.CLABOX_GHOSTTY_APP ?? '/Applications/Ghostty.app',\n appsDir: env.CLABOX_APPS_DIR ?? '~/Applications',\n signId: env.CLABOX_SIGN_ID ?? null,\n baseGhosttyConfig: env.CLABOX_GHOSTTY_BASE_CONFIG ?? null,\n claboxBin: env.CLABOX_CLABOX_BIN ?? null,\n },\n};\n\ntype Plain = Record<string, unknown>;\n\nfunction isPlainObject(v: unknown): v is Plain {\n return v != null && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** Shallow-deep merge: nested plain objects merge, everything else replaces. */\nfunction deepMerge(base: Plain, override: Plain): Plain {\n const out: Plain = { ...base };\n for (const [key, val] of Object.entries(override)) {\n if (val === undefined) continue;\n const baseVal = base[key];\n out[key] = isPlainObject(val) && isPlainObject(baseVal) ? deepMerge(baseVal, val) : val;\n }\n return out;\n}\n\n/** Merge a (partial) override over a full config, returning a new config. */\nexport function mergeConfig(base: Config, override: unknown): Config {\n if (!isPlainObject(override)) return base;\n return deepMerge(base as unknown as Plain, override) as unknown as Config;\n}\n\n/**\n * Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),\n * then CWD, then ~/.config. The CLI arg wins over the env var.\n */\nexport function findConfigFile(explicit?: string | null): string | null {\n const chosen = explicit ?? env.CLABOX_CONFIG;\n if (chosen) return expandHome(chosen);\n const candidates = [\n path.join(process.cwd(), 'clabox.config.mjs'),\n path.join(process.cwd(), 'clabox.config.js'),\n path.join(HOME, '.config', 'clabox', 'config.mjs'),\n ];\n return candidates.find((c) => fs.existsSync(c)) ?? null;\n}\n\n/**\n * Global directory holding named \"box\" configs (`<name>.config.mjs`), used by\n * the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.\n */\nexport function configsDir(): string {\n return expandHome(env.CLABOX_CONFIGS_DIR ?? '~/.config/clabox/configs');\n}\n\nconst BOX_SUFFIXES = ['.config.mjs', '.mjs'];\n\n/** Candidate file paths for a box name, in resolution order. */\nfunction boxCandidates(name: string, dir: string): string[] {\n return BOX_SUFFIXES.map((s) => path.join(dir, `${name}${s}`));\n}\n\n/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */\nexport function listBoxes(dir: string = configsDir()): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n const names = new Set<string>();\n for (const f of entries) {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes.\n if (f.startsWith('_')) continue;\n const suffix = BOX_SUFFIXES.find((s) => f.endsWith(s));\n if (suffix) names.add(f.slice(0, -suffix.length));\n }\n return [...names].sort();\n}\n\n/**\n * Resolve a box name to its config file in {@link configsDir}, preferring\n * `<name>.config.mjs` over a bare `<name>.mjs`.\n *\n * @throws if no matching file exists (the message lists the available boxes).\n */\nexport function resolveBox(name: string, dir: string = configsDir()): string {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes —\n // keep them un-resolvable so `-b` matches what `listBoxes` advertises.\n if (!name.startsWith('_')) {\n const found = boxCandidates(name, dir).find((c) => fs.existsSync(c));\n if (found) return found;\n }\n const available = listBoxes(dir);\n const hint = available.length ? `available: ${available.join(', ')}` : `none found in ${dir}`;\n throw new Error(`clabox: box '${name}' not found in ${dir} (${hint})`);\n}\n\n/** Result of {@link loadConfig}: the effective config and the file it came from. */\nexport interface LoadedConfig {\n config: Config;\n configFile: string | null;\n}\n\n/**\n * Build the effective config: defaults ⊕ env ⊕ config file.\n *\n * @param explicitConfig optional config-file path (e.g. from `--config`);\n * takes precedence over `CLABOX_CONFIG` and the default lookup locations.\n */\nexport async function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig> {\n let cfg: Config = defaultConfig;\n const file = findConfigFile(explicitConfig);\n if (file) {\n const mod = await import(pathToFileURL(file).href);\n const exported = mod.default ?? mod.config ?? mod;\n const resolved = typeof exported === 'function' ? await exported(defaultConfig) : exported;\n cfg = mergeConfig(defaultConfig, resolved);\n }\n return { config: cfg, configFile: file };\n}\n"],"mappings":"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"}
@@ -43,6 +43,19 @@ interface AppBuilderConfig {
43
43
  /** `clabox` binary baked into the generated `command`. null → autodetect. */
44
44
  claboxBin: string | null;
45
45
  }
46
+ /**
47
+ * A single MCP server entry — the value under a key in claude's `mcpServers`
48
+ * map. Loose on purpose (mirrors claude's mcp.json schema): `stdio` servers use
49
+ * `command`/`args`/`env`, remote `http`/`sse` servers use `url`/`headers`.
50
+ */
51
+ interface McpServer {
52
+ type?: 'stdio' | 'http' | 'sse';
53
+ command?: string;
54
+ args?: string[];
55
+ env?: Record<string, string>;
56
+ url?: string;
57
+ headers?: Record<string, string>;
58
+ }
46
59
  /** Extra rules layered on top of the built-in base profile. */
47
60
  interface PathRules {
48
61
  /** RW subpaths (beyond project dir + configDir + /tmp). */
@@ -68,6 +81,20 @@ interface Config {
68
81
  configDir: string;
69
82
  /** Extra args always passed to `claude`, before any args from the CLI. */
70
83
  claudeArgs: string[];
84
+ /**
85
+ * Per-box MCP servers (the `mcpServers` map). clabox compiles them to
86
+ * `<configDir>/mcp/<slug>.json` and launches claude with
87
+ * `--strict-mcp-config --mcp-config <file>`, so a shared configDir's global /
88
+ * plugin MCP servers are ignored — each box gets exactly these and no more.
89
+ * Materialized on every `run` and during `init`. Absent → no MCP flags.
90
+ */
91
+ mcp?: Record<string, McpServer>;
92
+ /**
93
+ * Text appended to claude's system prompt via `--append-system-prompt`.
94
+ * `string[]` is joined with blank lines. Use it for per-box pre-prompts while
95
+ * sharing one configDir (the user-level CLAUDE.md is shared; this is not).
96
+ */
97
+ systemPrompt?: string | string[];
71
98
  bot: BotConfig;
72
99
  /**
73
100
  * Extra environment variables forced onto the sandboxed `claude` process,
@@ -130,5 +157,5 @@ interface LoadedConfig {
130
157
  */
131
158
  declare function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig>;
132
159
  //#endregion
133
- export { HOME as a, configsDir as c, findConfigFile as d, listBoxes as f, resolveBox as h, Config as i, defaultConfig as l, mergeConfig as m, AppConfig as n, LoadedConfig as o, loadConfig as p, BotConfig as r, PathRules as s, AppBuilderConfig as t, expandHome as u };
134
- //# sourceMappingURL=config-DQWueb4a.d.ts.map
160
+ export { HOME as a, PathRules as c, expandHome as d, findConfigFile as f, resolveBox as g, mergeConfig as h, Config as i, configsDir as l, loadConfig as m, AppConfig as n, LoadedConfig as o, listBoxes as p, BotConfig as r, McpServer as s, AppBuilderConfig as t, defaultConfig as u };
161
+ //# sourceMappingURL=config-jZAB2Zg8.d.ts.map
@@ -0,0 +1,46 @@
1
+ import { i as expandHome } from "./config-BQ44iVWT.js";
2
+ import path from "node:path";
3
+ //#region src/sandbox/extras.ts
4
+ /**
5
+ * Stable per-box slug used to name the materialized files. Prefers the config
6
+ * file's basename (so `-b is-mg` → `is-mg`, matching `init`'s box name), else
7
+ * falls back to the project dir's basename.
8
+ */
9
+ function boxSlug(configFile, projectDir) {
10
+ if (configFile) return path.basename(configFile).replace(/\.(config\.)?(mjs|cjs|js)$/, "");
11
+ return path.basename(projectDir);
12
+ }
13
+ /**
14
+ * Compile `config.mcp` / `config.systemPrompt` into claude args.
15
+ *
16
+ * - MCP: written to `<configDir>/mcp/<slug>.json` and loaded with
17
+ * `--strict-mcp-config --mcp-config <file>` (global/plugin servers ignored).
18
+ * configDir is sandbox-RW, so the json is readable inside the box.
19
+ * - systemPrompt: appended inline via `--append-system-prompt` (no file —
20
+ * nothing to leave stale; `string[]` is joined with blank lines).
21
+ */
22
+ function buildBoxExtras(config, slug) {
23
+ const configDir = expandHome(config.configDir);
24
+ const claudeArgs = [];
25
+ const files = [];
26
+ const servers = config.mcp;
27
+ if (servers && Object.keys(servers).length > 0) {
28
+ const mcpFile = path.join(configDir, "mcp", `${slug}.json`);
29
+ files.push({
30
+ path: mcpFile,
31
+ content: `${JSON.stringify({ mcpServers: servers }, null, 2)}\n`
32
+ });
33
+ claudeArgs.push("--strict-mcp-config", "--mcp-config", mcpFile);
34
+ }
35
+ const sp = config.systemPrompt;
36
+ const text = (Array.isArray(sp) ? sp.join("\n\n") : sp ?? "").trim();
37
+ if (text) claudeArgs.push("--append-system-prompt", text);
38
+ return {
39
+ claudeArgs,
40
+ files
41
+ };
42
+ }
43
+ //#endregion
44
+ export { buildBoxExtras as n, boxSlug as t };
45
+
46
+ //# sourceMappingURL=extras-B2ss2Cgh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extras-B2ss2Cgh.js","names":[],"sources":["../src/sandbox/extras.ts"],"sourcesContent":["// Per-box \"extras\": compile a box's declarative `mcp` / `systemPrompt` config\n// into the claude args (and the files they reference) — so user config stays\n// pure data and clabox owns the wiring. Pure builder; callers (run.ts at launch,\n// scaffold.ts at `init`) do the actual fs writes.\n\nimport path from 'node:path';\nimport { type Config, expandHome } from '../utils/config.js';\n\n/** A file to materialize before launching claude (absolute path + content). */\nexport interface ExtraFile {\n path: string;\n content: string;\n}\n\n/** Compiled box extras: the claude args to inject + the files they reference. */\nexport interface BoxExtras {\n /** Appended after `config.claudeArgs`, before any CLI args. */\n claudeArgs: string[];\n /** Files to write (under configDir, which the sandbox grants RW). */\n files: ExtraFile[];\n}\n\n/**\n * Stable per-box slug used to name the materialized files. Prefers the config\n * file's basename (so `-b is-mg` → `is-mg`, matching `init`'s box name), else\n * falls back to the project dir's basename.\n */\nexport function boxSlug(configFile: string | null | undefined, projectDir: string): string {\n if (configFile) {\n return path.basename(configFile).replace(/\\.(config\\.)?(mjs|cjs|js)$/, '');\n }\n return path.basename(projectDir);\n}\n\n/**\n * Compile `config.mcp` / `config.systemPrompt` into claude args.\n *\n * - MCP: written to `<configDir>/mcp/<slug>.json` and loaded with\n * `--strict-mcp-config --mcp-config <file>` (global/plugin servers ignored).\n * configDir is sandbox-RW, so the json is readable inside the box.\n * - systemPrompt: appended inline via `--append-system-prompt` (no file —\n * nothing to leave stale; `string[]` is joined with blank lines).\n */\nexport function buildBoxExtras(config: Config, slug: string): BoxExtras {\n const configDir = expandHome(config.configDir);\n const claudeArgs: string[] = [];\n const files: ExtraFile[] = [];\n\n const servers = config.mcp;\n if (servers && Object.keys(servers).length > 0) {\n const mcpFile = path.join(configDir, 'mcp', `${slug}.json`);\n files.push({\n path: mcpFile,\n content: `${JSON.stringify({ mcpServers: servers }, null, 2)}\\n`,\n });\n claudeArgs.push('--strict-mcp-config', '--mcp-config', mcpFile);\n }\n\n const sp = config.systemPrompt;\n const text = (Array.isArray(sp) ? sp.join('\\n\\n') : (sp ?? '')).trim();\n if (text) claudeArgs.push('--append-system-prompt', text);\n\n return { claudeArgs, files };\n}\n"],"mappings":";;;;;;;;AA2BA,SAAgB,QAAQ,YAAuC,YAA4B;CACzF,IAAI,YACF,OAAO,KAAK,SAAS,UAAU,CAAC,CAAC,QAAQ,8BAA8B,EAAE;CAE3E,OAAO,KAAK,SAAS,UAAU;AACjC;;;;;;;;;;AAWA,SAAgB,eAAe,QAAgB,MAAyB;CACtE,MAAM,YAAY,WAAW,OAAO,SAAS;CAC7C,MAAM,aAAuB,CAAC;CAC9B,MAAM,QAAqB,CAAC;CAE5B,MAAM,UAAU,OAAO;CACvB,IAAI,WAAW,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG;EAC9C,MAAM,UAAU,KAAK,KAAK,WAAW,OAAO,GAAG,KAAK,MAAM;EAC1D,MAAM,KAAK;GACT,MAAM;GACN,SAAS,GAAG,KAAK,UAAU,EAAE,YAAY,QAAQ,GAAG,MAAM,CAAC,EAAE;EAC/D,CAAC;EACD,WAAW,KAAK,uBAAuB,gBAAgB,OAAO;CAChE;CAEA,MAAM,KAAK,OAAO;CAClB,MAAM,QAAQ,MAAM,QAAQ,EAAE,IAAI,GAAG,KAAK,MAAM,IAAK,MAAM,GAAA,CAAK,KAAK;CACrE,IAAI,MAAM,WAAW,KAAK,0BAA0B,IAAI;CAExD,OAAO;EAAE;EAAY;CAAM;AAC7B"}
@@ -0,0 +1,34 @@
1
+ import { i as Config } from "./config-jZAB2Zg8.js";
2
+
3
+ //#region src/sandbox/extras.d.ts
4
+ /** A file to materialize before launching claude (absolute path + content). */
5
+ interface ExtraFile {
6
+ path: string;
7
+ content: string;
8
+ }
9
+ /** Compiled box extras: the claude args to inject + the files they reference. */
10
+ interface BoxExtras {
11
+ /** Appended after `config.claudeArgs`, before any CLI args. */
12
+ claudeArgs: string[];
13
+ /** Files to write (under configDir, which the sandbox grants RW). */
14
+ files: ExtraFile[];
15
+ }
16
+ /**
17
+ * Stable per-box slug used to name the materialized files. Prefers the config
18
+ * file's basename (so `-b is-mg` → `is-mg`, matching `init`'s box name), else
19
+ * falls back to the project dir's basename.
20
+ */
21
+ declare function boxSlug(configFile: string | null | undefined, projectDir: string): string;
22
+ /**
23
+ * Compile `config.mcp` / `config.systemPrompt` into claude args.
24
+ *
25
+ * - MCP: written to `<configDir>/mcp/<slug>.json` and loaded with
26
+ * `--strict-mcp-config --mcp-config <file>` (global/plugin servers ignored).
27
+ * configDir is sandbox-RW, so the json is readable inside the box.
28
+ * - systemPrompt: appended inline via `--append-system-prompt` (no file —
29
+ * nothing to leave stale; `string[]` is joined with blank lines).
30
+ */
31
+ declare function buildBoxExtras(config: Config, slug: string): BoxExtras;
32
+ //#endregion
33
+ export { buildBoxExtras as i, ExtraFile as n, boxSlug as r, BoxExtras as t };
34
+ //# sourceMappingURL=extras-B_dzBrCF.d.ts.map
@@ -1,4 +1,4 @@
1
- import { n as AppConfig } from "./config-DQWueb4a.js";
1
+ import { n as AppConfig } from "./config-jZAB2Zg8.js";
2
2
 
3
3
  //#region src/init/ghostty.d.ts
4
4
  /** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */
@@ -8,9 +8,18 @@ interface GhosttyConfigOptions {
8
8
  boxName: string;
9
9
  /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */
10
10
  projectDir: string | null;
11
- /** Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd. */
12
- configsDir: string;
13
- /** Absolute path to the `clabox` binary. */
11
+ /**
12
+ * Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd, or
13
+ * null to omit it — when null, `-b` finds the box via the runtime default
14
+ * (`~/.config/clabox/configs`) at launch time.
15
+ */
16
+ configsDir: string | null;
17
+ /**
18
+ * The `clabox` command baked into the launcher. Default is a bare `clabox`
19
+ * resolved from PATH at launch time by the `zsh -lic` login shell (survives
20
+ * package-manager moves, e.g. bun → npm/homebrew); pass an absolute path only
21
+ * to pin a specific binary.
22
+ */
14
23
  claboxBin: string;
15
24
  /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */
16
25
  baseGhosttyConfig?: string | null;
@@ -31,4 +40,4 @@ declare function appBundlePath(appsDir: string, app: AppConfig): string;
31
40
  declare function bundleId(boxName: string, app: AppConfig): string;
32
41
  //#endregion
33
42
  export { buildLauncherSource as a, buildGhosttyConfig as i, appBundlePath as n, bundleId as o, buildCommand as r, GhosttyConfigOptions as t };
34
- //# sourceMappingURL=ghostty-By4zmOuk.d.ts.map
43
+ //# sourceMappingURL=ghostty-DFwFh4aL.d.ts.map
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  //#region src/init/ghostty.ts
3
3
  /** The `command = zsh -lic '…'` line that boots clabox for the box. */
4
4
  function buildCommand(opts) {
5
- return `command = zsh -lic '${`${opts.projectDir ? `cd ${opts.projectDir} && ` : ""}CLABOX_CONFIGS_DIR=${opts.configsDir} ${opts.claboxBin} -b ${opts.boxName}; exec zsh`}'`;
5
+ return `command = zsh -lic '${`${opts.projectDir ? `cd ${opts.projectDir} && ` : ""}${opts.configsDir ? `CLABOX_CONFIGS_DIR=${opts.configsDir} ` : ""}${opts.claboxBin} -b ${opts.boxName}; exec zsh`}'`;
6
6
  }
7
7
  /** Build the Ghostty config text for an app box. */
8
8
  function buildGhosttyConfig(opts) {
@@ -71,4 +71,4 @@ function bundleId(boxName, app) {
71
71
  //#endregion
72
72
  export { bundleId as a, buildLauncherSource as i, buildCommand as n, buildGhosttyConfig as r, appBundlePath as t };
73
73
 
74
- //# sourceMappingURL=ghostty-DcMEZ6Ey.js.map
74
+ //# sourceMappingURL=ghostty-Dw4QLyl-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ghostty-Dw4QLyl-.js","names":[],"sources":["../src/init/ghostty.ts"],"sourcesContent":["// Pure builders for the Ghostty-app artifacts emitted by `clabox init`.\n//\n// For a box that opts in via `app` (see AppConfig), `init` generates a Ghostty\n// config whose `command` launches `clabox -b <box>`, then clones Ghostty.app\n// with a tiny C launcher that bakes in `--config-file=<that config>`. Everything\n// here is text-only (no I/O) so it can be unit-tested without macOS; the actual\n// build lives in init/app.ts.\n\nimport path from 'node:path';\nimport type { AppConfig } from '../utils/config.js';\n\n/** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */\nexport interface GhosttyConfigOptions {\n app: AppConfig;\n /** The `-b` box name this config launches. */\n boxName: string;\n /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */\n projectDir: string | null;\n /**\n * Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd, or\n * null to omit it — when null, `-b` finds the box via the runtime default\n * (`~/.config/clabox/configs`) at launch time.\n */\n configsDir: string | null;\n /**\n * The `clabox` command baked into the launcher. Default is a bare `clabox`\n * resolved from PATH at launch time by the `zsh -lic` login shell (survives\n * package-manager moves, e.g. bun → npm/homebrew); pass an absolute path only\n * to pin a specific binary.\n */\n claboxBin: string;\n /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */\n baseGhosttyConfig?: string | null;\n}\n\n/** The `command = zsh -lic '…'` line that boots clabox for the box. */\nexport function buildCommand(opts: GhosttyConfigOptions): string {\n const cd = opts.projectDir ? `cd ${opts.projectDir} && ` : '';\n const env = opts.configsDir ? `CLABOX_CONFIGS_DIR=${opts.configsDir} ` : '';\n const inner = `${cd}${env}${opts.claboxBin} -b ${opts.boxName}; exec zsh`;\n // Login + interactive zsh so the GUI-launched app inherits the user's PATH\n // (/etc/zprofile→path_helper for Homebrew, ~/.zshrc for fnm/nvm/volta). A bare\n // `bash -c` gets only launchd's minimal PATH and can't find `node` (clabox's\n // shebang is `#!/usr/bin/env node`).\n return `command = zsh -lic '${inner}'`;\n}\n\n/** Build the Ghostty config text for an app box. */\nexport function buildGhosttyConfig(opts: GhosttyConfigOptions): string {\n const { app } = opts;\n const lines: string[] = [\n '# Generated by `clabox init` — do not edit; rerun it after changing the box config.',\n ];\n if (opts.baseGhosttyConfig) lines.push(`config-file = ${opts.baseGhosttyConfig}`);\n lines.push('', `title = \"${app.title ?? app.name}\"`);\n if (app.macosIcon) lines.push(`macos-icon = ${app.macosIcon}`);\n if (app.ghostty && Object.keys(app.ghostty).length > 0) {\n lines.push('');\n for (const [key, value] of Object.entries(app.ghostty)) lines.push(`${key} = ${value}`);\n }\n lines.push('', buildCommand(opts));\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Escape a string for embedding as a C double-quoted literal. */\nfunction cEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Build the C launcher source. It finds itself, locates `ghostty.real` next to\n * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone\n * always boots with its own config regardless of how it's launched.\n */\nexport function buildLauncherSource(configPath: string): string {\n return `// Generated by clabox init. Launches ghostty.real with a baked config.\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <libgen.h>\n#include <mach-o/dyld.h>\n\nstatic const char *CONFIG_PATH = \"${cEscape(configPath)}\";\n\nint main(int argc, char *argv[]) {\n char path[4096];\n uint32_t size = sizeof(path);\n _NSGetExecutablePath(path, &size);\n\n char *dir = dirname(path);\n char real_path[4096];\n snprintf(real_path, sizeof(real_path), \"%s/ghostty.real\", dir);\n\n char config_arg[4096];\n snprintf(config_arg, sizeof(config_arg), \"--config-file=%s\", CONFIG_PATH);\n\n char **new_argv = malloc(sizeof(char *) * (argc + 2));\n new_argv[0] = real_path;\n new_argv[1] = config_arg;\n for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i];\n new_argv[argc + 1] = NULL;\n\n execv(real_path, new_argv);\n return 1;\n}\n`;\n}\n\n/** Absolute path to the built `.app` bundle. */\nexport function appBundlePath(appsDir: string, app: AppConfig): string {\n return path.join(appsDir, `${app.name}.app`);\n}\n\n/** Bundle identifier for the clone (explicit, or derived from the box name). */\nexport function bundleId(boxName: string, app: AppConfig): string {\n return app.bundleId ?? `com.ghostty.custom.${boxName.replace(/-/g, '.')}`;\n}\n"],"mappings":";;;AAoCA,SAAgB,aAAa,MAAoC;CAQ/D,OAAO,uBAAuB,GAPnB,KAAK,aAAa,MAAM,KAAK,WAAW,QAAQ,KAC/C,KAAK,aAAa,sBAAsB,KAAK,WAAW,KAAK,KAC7C,KAAK,UAAU,MAAM,KAAK,QAAQ,YAK1B;AACtC;;AAGA,SAAgB,mBAAmB,MAAoC;CACrE,MAAM,EAAE,QAAQ;CAChB,MAAM,QAAkB,CACtB,qFACF;CACA,IAAI,KAAK,mBAAmB,MAAM,KAAK,iBAAiB,KAAK,mBAAmB;CAChF,MAAM,KAAK,IAAI,YAAY,IAAI,SAAS,IAAI,KAAK,EAAE;CACnD,IAAI,IAAI,WAAW,MAAM,KAAK,gBAAgB,IAAI,WAAW;CAC7D,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC,SAAS,GAAG;EACtD,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO;CACxF;CACA,MAAM,KAAK,IAAI,aAAa,IAAI,CAAC;CACjC,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B;;AAGA,SAAS,QAAQ,GAAmB;CAClC,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAK;AACrD;;;;;;AAOA,SAAgB,oBAAoB,YAA4B;CAC9D,OAAO;;;;;;;oCAO2B,QAAQ,UAAU,EAAE;;;;;;;;;;;;;;;;;;;;;;;;AAwBxD;;AAGA,SAAgB,cAAc,SAAiB,KAAwB;CACrE,OAAO,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,KAAK;AAC7C;;AAGA,SAAgB,SAAS,SAAiB,KAAwB;CAChE,OAAO,IAAI,YAAY,sBAAsB,QAAQ,QAAQ,MAAM,GAAG;AACxE"}
package/lib/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "./aliases-DXyz-ufw.js";
2
- import { a as HOME, c as configsDir, d as findConfigFile, f as listBoxes, h as resolveBox, i as Config, l as defaultConfig, m as mergeConfig, n as AppConfig, o as LoadedConfig, p as loadConfig, r as BotConfig, s as PathRules, t as AppBuilderConfig, u as expandHome } from "./config-DQWueb4a.js";
3
- import { i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "./app-DzQ5yZfD.js";
4
- import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "./ghostty-By4zmOuk.js";
5
- import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "./raycast-DM7c559f.js";
6
- import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "./scaffold-ByIbYAeS.js";
7
- import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "./profile-BeM41NXc.js";
8
- import { a as resolveProjectDir, i as profilePath, o as runClaude, r as generateProfile, t as RunOptions } from "./run-Cx8cuTh5.js";
9
- export { type AliasPaths, type AppBuilderConfig, type AppConfig, type BotConfig, type BuildAppOptions, type BuildAppResult, type BuiltApp, type Config, type GhosttyConfigOptions, HOME, type InitFile, type InitOptions, type InitResult, type LoadedConfig, type PathRules, type ProfileContext, type RaycastCommandOptions, type RunOptions, aliasName, appBundlePath, buildAliasFiles, buildApp, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
1
+ import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "./aliases-BNdrOn9E.js";
2
+ import { a as HOME, c as PathRules, d as expandHome, f as findConfigFile, g as resolveBox, h as mergeConfig, i as Config, l as configsDir, m as loadConfig, n as AppConfig, o as LoadedConfig, p as listBoxes, r as BotConfig, s as McpServer, t as AppBuilderConfig, u as defaultConfig } from "./config-jZAB2Zg8.js";
3
+ import { i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "./app-B1djcEnN.js";
4
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "./ghostty-DFwFh4aL.js";
5
+ import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "./raycast-fc5U1x7E.js";
6
+ import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "./scaffold-CbsKxlL0.js";
7
+ import { i as buildBoxExtras, n as ExtraFile, r as boxSlug, t as BoxExtras } from "./extras-B_dzBrCF.js";
8
+ import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "./profile-7CiMUPu9.js";
9
+ import { a as resolveProjectDir, i as profilePath, o as runClaude, r as generateProfile, t as RunOptions } from "./run-BWGDNJiY.js";
10
+ export { type AliasPaths, type AppBuilderConfig, type AppConfig, type BotConfig, type BoxExtras, type BuildAppOptions, type BuildAppResult, type BuiltApp, type Config, type ExtraFile, type GhosttyConfigOptions, HOME, type InitFile, type InitOptions, type InitResult, type LoadedConfig, type McpServer, type PathRules, type ProfileContext, type RaycastCommandOptions, type RunOptions, aliasName, appBundlePath, boxSlug, buildAliasFiles, buildApp, buildBoxExtras, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
package/lib/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { a as findConfigFile, c as mergeConfig, i as expandHome, l as resolveBox, n as configsDir, o as listBoxes, r as defaultConfig, s as loadConfig, t as HOME } from "./config-BQ44iVWT.js";
2
- import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-DKGcMHHe.js";
3
- import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
4
- import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
2
+ import { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
3
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-KGjds7dK.js";
4
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
5
+ import { n as canBuildApps, t as buildApp } from "./app-ChnKbgkD.js";
5
6
  import { n as raycastIcon, t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
6
- import { n as runInit, t as discoverProfiles } from "./scaffold-B7pUVGoC.js";
7
+ import { n as runInit, t as discoverProfiles } from "./scaffold-C1tqCL_8.js";
7
8
  import { a as literal, c as subpath, i as ipcName, n as detectPackagePaths, o as reEscape, r as globalName, s as regex, t as buildProfile } from "./profile-DM6NAgb-.js";
8
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-CNehSQ-S.js";
9
- export { HOME, aliasName, appBundlePath, buildAliasFiles, buildApp, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
9
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-yUsOaKFx.js";
10
+ export { HOME, aliasName, appBundlePath, boxSlug, buildAliasFiles, buildApp, buildBoxExtras, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
@@ -1,2 +1,2 @@
1
- import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "../aliases-DXyz-ufw.js";
1
+ import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "../aliases-BNdrOn9E.js";
2
2
  export { AliasPaths, InitFile, aliasName, buildAliasFiles, buildIndex, buildWrapper };
@@ -1,2 +1,2 @@
1
- import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "../aliases-DKGcMHHe.js";
1
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "../aliases-KGjds7dK.js";
2
2
  export { aliasName, buildAliasFiles, buildIndex, buildWrapper };
package/lib/init/app.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as installIcon, i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "../app-DzQ5yZfD.js";
1
+ import { a as installIcon, i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "../app-B1djcEnN.js";
2
2
  export { BuildAppOptions, BuildAppResult, buildApp, canBuildApps, installIcon };
package/lib/init/app.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as canBuildApps, r as installIcon, t as buildApp } from "../app-CQmESEdh.js";
1
+ import { n as canBuildApps, r as installIcon, t as buildApp } from "../app-ChnKbgkD.js";
2
2
  export { buildApp, canBuildApps, installIcon };
@@ -1,2 +1,2 @@
1
- import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-By4zmOuk.js";
1
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-DFwFh4aL.js";
2
2
  export { GhosttyConfigOptions, appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -1,2 +1,2 @@
1
- import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "../ghostty-DcMEZ6Ey.js";
1
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "../ghostty-Dw4QLyl-.js";
2
2
  export { appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -1,2 +1,2 @@
1
- import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "../raycast-DM7c559f.js";
1
+ import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "../raycast-fc5U1x7E.js";
2
2
  export { RaycastCommandOptions, buildRaycastCommand, raycastIcon };
@@ -1,2 +1,2 @@
1
- import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "../scaffold-ByIbYAeS.js";
1
+ import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "../scaffold-CbsKxlL0.js";
2
2
  export { BuiltApp, InitOptions, InitResult, discoverProfiles, runInit };
@@ -1,2 +1,2 @@
1
- import { n as runInit, t as discoverProfiles } from "../scaffold-B7pUVGoC.js";
1
+ import { n as runInit, t as discoverProfiles } from "../scaffold-C1tqCL_8.js";
2
2
  export { discoverProfiles, runInit };
@@ -1,4 +1,4 @@
1
- import { i as Config } from "./config-DQWueb4a.js";
1
+ import { i as Config } from "./config-jZAB2Zg8.js";
2
2
 
3
3
  //#region src/sandbox/profile.d.ts
4
4
  declare const subpath: (p: string) => string;
@@ -26,4 +26,4 @@ declare function buildProfile(config: Config, {
26
26
  }: ProfileContext): string;
27
27
  //#endregion
28
28
  export { ipcName as a, regex as c, globalName as i, subpath as l, buildProfile as n, literal as o, detectPackagePaths as r, reEscape as s, ProfileContext as t };
29
- //# sourceMappingURL=profile-BeM41NXc.d.ts.map
29
+ //# sourceMappingURL=profile-7CiMUPu9.d.ts.map
@@ -1,4 +1,4 @@
1
- import { n as AppConfig } from "./config-DQWueb4a.js";
1
+ import { n as AppConfig } from "./config-jZAB2Zg8.js";
2
2
 
3
3
  //#region src/init/raycast.d.ts
4
4
  /** Inputs for {@link buildRaycastCommand}. */
@@ -19,4 +19,4 @@ declare function buildRaycastCommand({
19
19
  }: RaycastCommandOptions): string;
20
20
  //#endregion
21
21
  export { buildRaycastCommand as n, raycastIcon as r, RaycastCommandOptions as t };
22
- //# sourceMappingURL=raycast-DM7c559f.d.ts.map
22
+ //# sourceMappingURL=raycast-fc5U1x7E.d.ts.map
@@ -1,4 +1,5 @@
1
- import { i as Config } from "./config-DQWueb4a.js";
1
+ import { i as Config } from "./config-jZAB2Zg8.js";
2
+ import { n as ExtraFile } from "./extras-B_dzBrCF.js";
2
3
 
3
4
  //#region src/sandbox/run.d.ts
4
5
  /** Deterministic per-project profile path under TMPDIR. */
@@ -12,6 +13,13 @@ declare function resolveProjectDir(config: Config): string;
12
13
  declare function generateProfile(config: Config, projectDir?: string): string;
13
14
  /** Build the `env KEY=VALUE …` argument list forced onto the sandboxed claude. */
14
15
  declare function buildEnvArgs(config: Config): string[];
16
+ /**
17
+ * Write the per-box extra files (mkdir -p their dirs) `0600`. They live under
18
+ * configDir and can carry secrets (e.g. an MCP auth token in a URL/header), so
19
+ * they're kept out of argv (vs. an inline `--mcp-config '<json>'`) AND off the
20
+ * world-readable bit on disk. Returns the paths.
21
+ */
22
+ declare function writeExtraFiles(files: ExtraFile[]): string[];
15
23
  /** Options accepted by {@link runClaude}. */
16
24
  interface RunOptions {
17
25
  configFile?: string | null;
@@ -21,5 +29,5 @@ declare function runClaude(config: Config, claudeArgs: string[], {
21
29
  configFile
22
30
  }?: RunOptions): number;
23
31
  //#endregion
24
- export { resolveProjectDir as a, profilePath as i, buildEnvArgs as n, runClaude as o, generateProfile as r, RunOptions as t };
25
- //# sourceMappingURL=run-Cx8cuTh5.d.ts.map
32
+ export { resolveProjectDir as a, profilePath as i, buildEnvArgs as n, runClaude as o, generateProfile as r, writeExtraFiles as s, RunOptions as t };
33
+ //# sourceMappingURL=run-BWGDNJiY.d.ts.map
@@ -1,8 +1,9 @@
1
1
  import { i as expandHome, t as HOME } from "./config-BQ44iVWT.js";
2
+ import { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
2
3
  import { n as detectPackagePaths, t as buildProfile } from "./profile-DM6NAgb-.js";
3
4
  import path from "node:path";
4
- import { execFileSync, spawnSync } from "node:child_process";
5
5
  import fs from "node:fs";
6
+ import { execFileSync, spawnSync } from "node:child_process";
6
7
  import crypto from "node:crypto";
7
8
  //#region src/sandbox/run.ts
8
9
  const TMPDIR = (process.env.TMPDIR || "/tmp").replace(/\/$/, "");
@@ -69,16 +70,33 @@ function buildEnvArgs(config) {
69
70
  for (const [key, value] of Object.entries(config.env ?? {})) args.push(`${key}=${value}`);
70
71
  return args;
71
72
  }
73
+ /**
74
+ * Write the per-box extra files (mkdir -p their dirs) `0600`. They live under
75
+ * configDir and can carry secrets (e.g. an MCP auth token in a URL/header), so
76
+ * they're kept out of argv (vs. an inline `--mcp-config '<json>'`) AND off the
77
+ * world-readable bit on disk. Returns the paths.
78
+ */
79
+ function writeExtraFiles(files) {
80
+ for (const f of files) {
81
+ fs.mkdirSync(path.dirname(f.path), { recursive: true });
82
+ fs.writeFileSync(f.path, f.content, { mode: 384 });
83
+ fs.chmodSync(f.path, 384);
84
+ }
85
+ return files.map((f) => f.path);
86
+ }
72
87
  /** Generate the profile and exec claude under sandbox-exec. Returns exit code. */
73
88
  function runClaude(config, claudeArgs, { configFile } = {}) {
74
89
  const projectDir = resolveProjectDir(config);
75
90
  const claudeBin = resolveClaudeBin(config);
76
91
  const profileFile = generateProfile(config, projectDir);
92
+ const extras = buildBoxExtras(config, boxSlug(configFile, projectDir));
93
+ const extraFiles = writeExtraFiles(extras.files);
77
94
  if (process.env.CLABOX_DEBUG) {
78
95
  console.error(`→ Running Claude Code sandboxed in: ${projectDir}`);
79
96
  console.error(`→ Profile: ${profileFile}`);
80
97
  console.error(`→ Config: ${expandHome(config.configDir)}`);
81
98
  if (configFile) console.error(`→ Config file: ${configFile}`);
99
+ for (const f of extraFiles) console.error(`→ MCP: ${f}`);
82
100
  }
83
101
  const title = projectDir.startsWith(HOME) ? `~${projectDir.slice(HOME.length)}` : projectDir;
84
102
  process.stdout.write(`\x1b]0;${title}\x07`);
@@ -92,6 +110,7 @@ function runClaude(config, claudeArgs, { configFile } = {}) {
92
110
  ...envArgs,
93
111
  claudeBin,
94
112
  ...defaultArgs,
113
+ ...extras.claudeArgs,
95
114
  ...claudeArgs
96
115
  ];
97
116
  const res = spawnSync("/bin/sh", [
@@ -108,6 +127,6 @@ function runClaude(config, claudeArgs, { configFile } = {}) {
108
127
  return res.status ?? 0;
109
128
  }
110
129
  //#endregion
111
- export { runClaude as a, resolveProjectDir as i, generateProfile as n, profilePath as r, buildEnvArgs as t };
130
+ export { runClaude as a, resolveProjectDir as i, generateProfile as n, writeExtraFiles as o, profilePath as r, buildEnvArgs as t };
112
131
 
113
- //# sourceMappingURL=run-CNehSQ-S.js.map
132
+ //# sourceMappingURL=run-yUsOaKFx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-yUsOaKFx.js","names":[],"sources":["../src/sandbox/run.ts"],"sourcesContent":["// Profile materialization + launching `claude` under sandbox-exec.\n\nimport { execFileSync, spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { type Config, expandHome, HOME } from '../utils/config.js';\nimport { boxSlug, buildBoxExtras, type ExtraFile } from './extras.js';\nimport { buildProfile, detectPackagePaths } from './profile.js';\n\nconst TMPDIR = (process.env.TMPDIR || '/tmp').replace(/\\/$/, '');\n\n/** Deterministic per-project profile path under TMPDIR. */\nexport function profilePath(projectDir: string = process.cwd()): string {\n const hash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);\n return path.join(TMPDIR, `clabox-${path.basename(projectDir)}-${hash}.sb`);\n}\n\n/**\n * Effective project dir: `config.cwd` (with `~` expanded, resolved to an\n * absolute path so SBPL `subpath` rules stay valid) if set, else the shell CWD.\n */\nexport function resolveProjectDir(config: Config): string {\n return config.cwd ? path.resolve(expandHome(config.cwd)) : process.cwd();\n}\n\nfunction which(bin: string): string | null {\n try {\n return (\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', encoding: 'utf8' }).trim() || null\n );\n } catch {\n return null;\n }\n}\n\nfunction requireSandboxExec(): void {\n if (!which('sandbox-exec')) {\n throw new Error('sandbox-exec not found. This tool requires macOS with sandbox-exec.');\n }\n}\n\nfunction resolveClaudeBin(config: Config): string {\n const candidate = config.claudeBin || which('claude') || path.join(HOME, '.local/bin/claude');\n if (!candidate || !fs.existsSync(candidate)) {\n throw new Error(`claude not found at '${candidate}'`);\n }\n return candidate;\n}\n\n/** Generate the profile file for the current project, return its path. */\nexport function generateProfile(\n config: Config,\n projectDir: string = resolveProjectDir(config),\n): string {\n requireSandboxExec();\n const file = profilePath(projectDir);\n const text = buildProfile(config, { projectDir, detectedPaths: detectPackagePaths() });\n fs.writeFileSync(file, text);\n return file;\n}\n\n/** Build the `env KEY=VALUE …` argument list forced onto the sandboxed claude. */\nexport function buildEnvArgs(config: Config): string[] {\n const sshDir = expandHome(config.bot.sshDir);\n const botKey = path.join(sshDir, 'id_ed25519');\n const botCfg = path.join(sshDir, 'config');\n const args = [\n `PATH=${path.join(HOME, '.local/bin')}:${process.env.PATH || ''}`,\n `CLAUDE_CONFIG_DIR=${expandHome(config.configDir)}`,\n `GIT_AUTHOR_NAME=${config.bot.name}`,\n `GIT_AUTHOR_EMAIL=${config.bot.email}`,\n `GIT_COMMITTER_NAME=${config.bot.name}`,\n `GIT_COMMITTER_EMAIL=${config.bot.email}`,\n 'GIT_CONFIG_COUNT=2',\n 'GIT_CONFIG_KEY_0=commit.gpgsign',\n 'GIT_CONFIG_VALUE_0=false',\n 'GIT_CONFIG_KEY_1=tag.gpgsign',\n 'GIT_CONFIG_VALUE_1=false',\n ];\n // Pin git ssh to the bot key only when it actually exists, so the sandbox\n // stays usable without a dedicated bot key configured.\n if (fs.existsSync(botKey)) {\n args.push(\n `GIT_SSH_COMMAND=ssh -F ${botCfg} -i ${botKey} -o IdentitiesOnly=yes -o IdentityAgent=none`,\n );\n }\n // User-declared extras go last so they win over the built-in vars above\n // (duplicate keys: `env` keeps the last assignment).\n for (const [key, value] of Object.entries(config.env ?? {})) {\n args.push(`${key}=${value}`);\n }\n return args;\n}\n\n/**\n * Write the per-box extra files (mkdir -p their dirs) `0600`. They live under\n * configDir and can carry secrets (e.g. an MCP auth token in a URL/header), so\n * they're kept out of argv (vs. an inline `--mcp-config '<json>'`) AND off the\n * world-readable bit on disk. Returns the paths.\n */\nexport function writeExtraFiles(files: ExtraFile[]): string[] {\n for (const f of files) {\n fs.mkdirSync(path.dirname(f.path), { recursive: true });\n fs.writeFileSync(f.path, f.content, { mode: 0o600 });\n fs.chmodSync(f.path, 0o600); // enforce even if the file pre-existed\n }\n return files.map((f) => f.path);\n}\n\n/** Options accepted by {@link runClaude}. */\nexport interface RunOptions {\n configFile?: string | null;\n}\n\n/** Generate the profile and exec claude under sandbox-exec. Returns exit code. */\nexport function runClaude(\n config: Config,\n claudeArgs: string[],\n { configFile }: RunOptions = {},\n): number {\n const projectDir = resolveProjectDir(config);\n const claudeBin = resolveClaudeBin(config);\n const profileFile = generateProfile(config, projectDir);\n\n // Compile the box's declarative mcp / systemPrompt into claude args, and\n // materialize the files they reference (under configDir, which is sandbox-RW).\n const extras = buildBoxExtras(config, boxSlug(configFile, projectDir));\n const extraFiles = writeExtraFiles(extras.files);\n\n if (process.env.CLABOX_DEBUG) {\n console.error(`→ Running Claude Code sandboxed in: ${projectDir}`);\n console.error(`→ Profile: ${profileFile}`);\n console.error(`→ Config: ${expandHome(config.configDir)}`);\n if (configFile) console.error(`→ Config file: ${configFile}`);\n for (const f of extraFiles) console.error(`→ MCP: ${f}`);\n }\n\n // Terminal title = cwd (with ~ for $HOME), matching the bash version.\n const title = projectDir.startsWith(HOME) ? `~${projectDir.slice(HOME.length)}` : projectDir;\n process.stdout.write(`\\x1b]0;${title}\\x07`);\n\n const envArgs = buildEnvArgs(config);\n const defaultArgs = Array.isArray(config.claudeArgs) ? config.claudeArgs : [];\n const inner = [\n 'sandbox-exec',\n '-f',\n profileFile,\n 'env',\n ...envArgs,\n claudeBin,\n ...defaultArgs,\n ...extras.claudeArgs,\n ...claudeArgs,\n ];\n\n // `ulimit` is a shell builtin; run the whole thing under sh so we can set it.\n // `exec \"$@\"` keeps argv intact without re-quoting (args start after $0=sh).\n const ulimit = config.ulimitProcs > 0 ? `ulimit -u ${config.ulimitProcs} 2>/dev/null; ` : '';\n const res = spawnSync('/bin/sh', ['-c', `${ulimit}exec \"$@\"`, 'sh', ...inner], {\n cwd: projectDir,\n stdio: 'inherit',\n });\n if (res.error) throw res.error;\n if (res.signal) return 1;\n return res.status ?? 0;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { i as buildBoxExtras, n as ExtraFile, r as boxSlug, t as BoxExtras } from "../extras-B_dzBrCF.js";
2
+ export { BoxExtras, ExtraFile, boxSlug, buildBoxExtras };
@@ -0,0 +1,2 @@
1
+ import { n as buildBoxExtras, t as boxSlug } from "../extras-B2ss2Cgh.js";
2
+ export { boxSlug, buildBoxExtras };
@@ -1,2 +1,2 @@
1
- import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "../profile-BeM41NXc.js";
1
+ import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "../profile-7CiMUPu9.js";
2
2
  export { ProfileContext, buildProfile, detectPackagePaths, globalName, ipcName, literal, reEscape, regex, subpath };
@@ -1,2 +1,2 @@
1
- import { a as resolveProjectDir, i as profilePath, n as buildEnvArgs, o as runClaude, r as generateProfile, t as RunOptions } from "../run-Cx8cuTh5.js";
2
- export { RunOptions, buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude };
1
+ import { a as resolveProjectDir, i as profilePath, n as buildEnvArgs, o as runClaude, r as generateProfile, s as writeExtraFiles, t as RunOptions } from "../run-BWGDNJiY.js";
2
+ export { RunOptions, buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
@@ -1,2 +1,2 @@
1
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath, t as buildEnvArgs } from "../run-CNehSQ-S.js";
2
- export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude };
1
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, o as writeExtraFiles, r as profilePath, t as buildEnvArgs } from "../run-yUsOaKFx.js";
2
+ export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
@@ -1,10 +1,10 @@
1
1
  import { i as expandHome, l as resolveBox, o as listBoxes, s as loadConfig } from "./config-BQ44iVWT.js";
2
- import { n as buildAliasFiles } from "./aliases-DKGcMHHe.js";
3
- import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
4
- import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
2
+ import { n as buildBoxExtras } from "./extras-B2ss2Cgh.js";
3
+ import { n as buildAliasFiles } from "./aliases-KGjds7dK.js";
4
+ import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
5
+ import { n as canBuildApps, t as buildApp } from "./app-ChnKbgkD.js";
5
6
  import { t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
6
7
  import path from "node:path";
7
- import { execFileSync } from "node:child_process";
8
8
  import fs from "node:fs";
9
9
  //#region src/init/scaffold.ts
10
10
  /**
@@ -28,17 +28,47 @@ function pruneByExt(dir, ext) {
28
28
  if (!fs.existsSync(dir)) return;
29
29
  for (const f of fs.readdirSync(dir)) if (f.endsWith(ext)) fs.rmSync(path.join(dir, f), { force: true });
30
30
  }
31
- /** Locate the `clabox` binary to bake into the generated Ghostty `command`. */
31
+ /**
32
+ * The `clabox` command baked into the generated Ghostty `command`. Default is a
33
+ * bare `clabox` resolved from PATH at launch time by the `zsh -lic` login shell
34
+ * — this survives package-manager moves (e.g. bun → npm/homebrew) that change
35
+ * the binary's absolute path. Only an explicit `appBuilder.claboxBin` pins an
36
+ * absolute path.
37
+ */
32
38
  function resolveClaboxBin(configured) {
33
- if (configured) return expandHome(configured);
39
+ return configured ? expandHome(configured) : "clabox";
40
+ }
41
+ /**
42
+ * The `CLABOX_CONFIGS_DIR` value to bake into generated commands, or null to
43
+ * omit it. Omit when `<dir>` resolves (realpath, symlinks included) to the
44
+ * runtime default `~/.config/clabox/configs` — then `-b` finds the box via that
45
+ * default at launch time, so no path needs baking (and it stays correct if the
46
+ * dir later moves behind the same symlink). Otherwise bake the absolute path so
47
+ * `-b` resolves the box regardless of the launcher's cwd.
48
+ */
49
+ function bakeConfigsDir(dir) {
34
50
  try {
35
- const found = execFileSync("command", ["-v", "clabox"], {
36
- shell: "/bin/sh",
37
- encoding: "utf8"
38
- }).trim();
39
- if (found) return found;
51
+ if (fs.realpathSync(dir) === fs.realpathSync(expandHome("~/.config/clabox/configs"))) return null;
40
52
  } catch {}
41
- return "clabox";
53
+ return dir;
54
+ }
55
+ /**
56
+ * Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the
57
+ * files exist ahead of the first run (the same files `run` writes; slug = box
58
+ * name). Best-effort per box: a config that fails to load becomes a warning.
59
+ */
60
+ async function materializeExtras(configsDir, profiles, result) {
61
+ for (const name of profiles) try {
62
+ const { config } = await loadConfig(resolveBox(name, configsDir));
63
+ for (const f of buildBoxExtras(config, name).files) {
64
+ fs.mkdirSync(path.dirname(f.path), { recursive: true });
65
+ fs.writeFileSync(f.path, f.content, { mode: 384 });
66
+ fs.chmodSync(f.path, 384);
67
+ result.extraFiles.push(f.path);
68
+ }
69
+ } catch (e) {
70
+ result.warnings.push(`${name}: extras not materialized — ${e.message}`);
71
+ }
42
72
  }
43
73
  /** Generate the Ghostty configs and build the apps for every `app` box. */
44
74
  async function buildAppArtifacts(base, configsDir, profiles, only, result) {
@@ -71,7 +101,7 @@ async function buildAppArtifacts(base, configsDir, profiles, only, result) {
71
101
  app,
72
102
  boxName: name,
73
103
  projectDir,
74
- configsDir,
104
+ configsDir: bakeConfigsDir(configsDir),
75
105
  claboxBin: resolveClaboxBin(config.appBuilder.claboxBin),
76
106
  baseGhosttyConfig: baseGhostty
77
107
  }));
@@ -115,7 +145,7 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
115
145
  fs.mkdirSync(scriptsDir, { recursive: true });
116
146
  pruneGenerated(scriptsDir);
117
147
  const files = buildAliasFiles(profiles, {
118
- configsDir,
148
+ configsDir: bakeConfigsDir(configsDir),
119
149
  scriptsDir
120
150
  });
121
151
  for (const f of files) {
@@ -130,12 +160,14 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
130
160
  apps: [],
131
161
  ghosttyConfigs: [],
132
162
  raycastCommands: [],
163
+ extraFiles: [],
133
164
  warnings: []
134
165
  };
166
+ await materializeExtras(configsDir, profiles, result);
135
167
  if (buildApps) await buildAppArtifacts(base, configsDir, profiles, only, result);
136
168
  return result;
137
169
  }
138
170
  //#endregion
139
171
  export { runInit as n, discoverProfiles as t };
140
172
 
141
- //# sourceMappingURL=scaffold-B7pUVGoC.js.map
173
+ //# sourceMappingURL=scaffold-C1tqCL_8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-C1tqCL_8.js","names":[],"sources":["../src/init/scaffold.ts"],"sourcesContent":["// I/O for `clabox init`: discover the box configs in `<base>/configs/`,\n// (re)write the shell aliases into `<base>/scripts/`, and — for boxes that opt\n// in via `app` — generate a Ghostty config in `<base>/ghostty/` and build a\n// standalone `<appsDir>/<name>.app` (see init/app.ts).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { buildBoxExtras } from '../sandbox/extras.js';\nimport { type Config, expandHome, listBoxes, loadConfig, resolveBox } from '../utils/config.js';\nimport { buildAliasFiles } from './aliases.js';\nimport { buildApp, canBuildApps } from './app.js';\nimport { appBundlePath, buildGhosttyConfig } from './ghostty.js';\nimport { buildRaycastCommand } from './raycast.js';\n\n/**\n * Box names (sorted, de-duplicated) discovered in `<configsDir>` via the same\n * rules as `-b`: both `<name>.mjs` and `<name>.config.mjs`, `_`-prefixed\n * shared partials (e.g. `_presets.mjs`) skipped.\n */\nexport function discoverProfiles(configsDir: string): string[] {\n if (!fs.existsSync(configsDir)) {\n throw new Error(`clabox init: configs dir not found: ${configsDir}`);\n }\n const names = listBoxes(configsDir);\n if (names.length === 0) {\n throw new Error(`clabox init: no box configs (*.mjs) in ${configsDir}`);\n }\n return names;\n}\n\n/** Remove previously generated artifacts (`index.sh`, `clabox-*.sh`). */\nfunction pruneGenerated(scriptsDir: string): void {\n if (!fs.existsSync(scriptsDir)) return;\n for (const f of fs.readdirSync(scriptsDir)) {\n if (f === 'index.sh' || (f.startsWith('clabox-') && f.endsWith('.sh'))) {\n fs.rmSync(path.join(scriptsDir, f), { force: true });\n }\n }\n}\n\n/** Remove files in `dir` whose name ends with `ext` (a no-op if dir is absent). */\nfunction pruneByExt(dir: string, ext: string): void {\n if (!fs.existsSync(dir)) return;\n for (const f of fs.readdirSync(dir)) {\n if (f.endsWith(ext)) fs.rmSync(path.join(dir, f), { force: true });\n }\n}\n\n/**\n * The `clabox` command baked into the generated Ghostty `command`. Default is a\n * bare `clabox` resolved from PATH at launch time by the `zsh -lic` login shell\n * — this survives package-manager moves (e.g. bun → npm/homebrew) that change\n * the binary's absolute path. Only an explicit `appBuilder.claboxBin` pins an\n * absolute path.\n */\nfunction resolveClaboxBin(configured: string | null): string {\n return configured ? expandHome(configured) : 'clabox';\n}\n\n/**\n * The `CLABOX_CONFIGS_DIR` value to bake into generated commands, or null to\n * omit it. Omit when `<dir>` resolves (realpath, symlinks included) to the\n * runtime default `~/.config/clabox/configs` — then `-b` finds the box via that\n * default at launch time, so no path needs baking (and it stays correct if the\n * dir later moves behind the same symlink). Otherwise bake the absolute path so\n * `-b` resolves the box regardless of the launcher's cwd.\n */\nfunction bakeConfigsDir(dir: string): string | null {\n try {\n // The launched process has no CLABOX_CONFIGS_DIR set (we omit it), so its\n // runtime default is always ~/.config/clabox/configs — compare against that.\n if (fs.realpathSync(dir) === fs.realpathSync(expandHome('~/.config/clabox/configs'))) {\n return null;\n }\n } catch {\n // default dir missing/unresolvable → bake the explicit path.\n }\n return dir;\n}\n\n/** Options for {@link runInit}. */\nexport interface InitOptions {\n /** Base dir holding `configs/` and `scripts/`. Default: `<cwd>/__`. */\n baseDir?: string;\n /** Build the Ghostty apps for `app` boxes. Default: true. */\n buildApps?: boolean;\n /** Limit app building to a single box (by box name or app display name). */\n only?: string | null;\n}\n\n/** A standalone Ghostty app built by `clabox init`. */\nexport interface BuiltApp {\n box: string;\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\n/** Result of {@link runInit}. */\nexport interface InitResult {\n profiles: string[];\n scriptsDir: string;\n indexFile: string;\n written: string[];\n /** Apps successfully built. */\n apps: BuiltApp[];\n /** Generated Ghostty config files. */\n ghosttyConfigs: string[];\n /** Generated Raycast command scripts. */\n raycastCommands: string[];\n /** Compiled per-box MCP json files (from `config.mcp`). */\n extraFiles: string[];\n /** Non-fatal issues (e.g. app build skipped/failed). */\n warnings: string[];\n}\n\n/**\n * Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the\n * files exist ahead of the first run (the same files `run` writes; slug = box\n * name). Best-effort per box: a config that fails to load becomes a warning.\n */\nasync function materializeExtras(\n configsDir: string,\n profiles: string[],\n result: InitResult,\n): Promise<void> {\n for (const name of profiles) {\n try {\n const { config } = await loadConfig(resolveBox(name, configsDir));\n for (const f of buildBoxExtras(config, name).files) {\n fs.mkdirSync(path.dirname(f.path), { recursive: true });\n // 0600 — the MCP json can carry an auth token (see run.ts#writeExtraFiles).\n fs.writeFileSync(f.path, f.content, { mode: 0o600 });\n fs.chmodSync(f.path, 0o600);\n result.extraFiles.push(f.path);\n }\n } catch (e) {\n result.warnings.push(`${name}: extras not materialized — ${(e as Error).message}`);\n }\n }\n}\n\n/** Generate the Ghostty configs and build the apps for every `app` box. */\nasync function buildAppArtifacts(\n base: string,\n configsDir: string,\n profiles: string[],\n only: string | null,\n result: InitResult,\n): Promise<void> {\n // Load each box config; keep the ones that opt into an app (and match `only`).\n const appBoxes: { name: string; config: Config }[] = [];\n for (const name of profiles) {\n const { config } = await loadConfig(resolveBox(name, configsDir));\n if (!config.app) continue;\n if (only && name !== only && config.app.name !== only) continue;\n appBoxes.push({ name, config });\n }\n if (appBoxes.length === 0) return;\n\n const ghosttyDir = path.join(base, 'ghostty');\n const raycastDir = path.join(base, 'raycast');\n fs.mkdirSync(ghosttyDir, { recursive: true });\n fs.mkdirSync(raycastDir, { recursive: true });\n // Only prune on a full run — with `only` we'd orphan other apps' artifacts.\n if (!only) {\n pruneByExt(ghosttyDir, '.config');\n pruneByExt(raycastDir, '.sh');\n }\n\n for (const { name, config } of appBoxes) {\n const app = config.app;\n if (!app) continue; // narrowed above; keeps the type checker happy\n const configPath = path.join(ghosttyDir, `${name}.config`);\n const projectDir = config.cwd ? path.resolve(expandHome(config.cwd)) : null;\n const baseGhostty = config.appBuilder.baseGhosttyConfig\n ? expandHome(config.appBuilder.baseGhosttyConfig)\n : null;\n fs.writeFileSync(\n configPath,\n buildGhosttyConfig({\n app,\n boxName: name,\n projectDir,\n configsDir: bakeConfigsDir(configsDir),\n claboxBin: resolveClaboxBin(config.appBuilder.claboxBin),\n baseGhosttyConfig: baseGhostty,\n }),\n );\n result.ghosttyConfigs.push(configPath);\n\n // Raycast command that opens the (to be) built bundle.\n const appPath = appBundlePath(expandHome(config.appBuilder.appsDir), app);\n const raycastPath = path.join(raycastDir, `${name}.sh`);\n fs.writeFileSync(raycastPath, buildRaycastCommand({ app, appPath }));\n fs.chmodSync(raycastPath, 0o755);\n result.raycastCommands.push(raycastPath);\n\n const check = canBuildApps(config.appBuilder);\n if (!check.ok) {\n result.warnings.push(`${name}: app not built (${check.reason})`);\n continue;\n }\n try {\n const built = buildApp({ boxName: name, app, builder: config.appBuilder, configPath });\n result.apps.push({ box: name, appPath: built.appPath, signed: built.signed });\n } catch (e) {\n result.warnings.push(`${name}: app build failed — ${(e as Error).message}`);\n }\n }\n}\n\n/** Scan the configs dir, (re)write the alias scripts, and build `app` apps. */\nexport async function runInit({\n baseDir,\n buildApps = true,\n only = null,\n}: InitOptions = {}): Promise<InitResult> {\n const base = path.resolve(baseDir ?? path.join(process.cwd(), '__'));\n const configsDir = path.join(base, 'configs');\n const scriptsDir = path.join(base, 'scripts');\n const profiles = discoverProfiles(configsDir);\n\n fs.mkdirSync(scriptsDir, { recursive: true });\n pruneGenerated(scriptsDir);\n\n const files = buildAliasFiles(profiles, { configsDir: bakeConfigsDir(configsDir), scriptsDir });\n for (const f of files) {\n fs.writeFileSync(f.path, f.content);\n if (f.executable) fs.chmodSync(f.path, 0o755);\n }\n\n const result: InitResult = {\n profiles,\n scriptsDir,\n indexFile: path.join(scriptsDir, 'index.sh'),\n written: files.map((f) => f.path),\n apps: [],\n ghosttyConfigs: [],\n raycastCommands: [],\n extraFiles: [],\n warnings: [],\n };\n\n await materializeExtras(configsDir, profiles, result);\n\n if (buildApps) {\n await buildAppArtifacts(base, configsDir, profiles, only, result);\n }\n return result;\n}\n"],"mappings":"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"}
@@ -32,6 +32,8 @@ interface InitResult {
32
32
  ghosttyConfigs: string[];
33
33
  /** Generated Raycast command scripts. */
34
34
  raycastCommands: string[];
35
+ /** Compiled per-box MCP json files (from `config.mcp`). */
36
+ extraFiles: string[];
35
37
  /** Non-fatal issues (e.g. app build skipped/failed). */
36
38
  warnings: string[];
37
39
  }
@@ -43,4 +45,4 @@ declare function runInit({
43
45
  }?: InitOptions): Promise<InitResult>;
44
46
  //#endregion
45
47
  export { runInit as a, discoverProfiles as i, InitOptions as n, InitResult as r, BuiltApp as t };
46
- //# sourceMappingURL=scaffold-ByIbYAeS.d.ts.map
48
+ //# sourceMappingURL=scaffold-CbsKxlL0.d.ts.map
@@ -1,2 +1,2 @@
1
- import { a as HOME, c as configsDir, d as findConfigFile, f as listBoxes, h as resolveBox, i as Config, l as defaultConfig, m as mergeConfig, n as AppConfig, o as LoadedConfig, p as loadConfig, r as BotConfig, s as PathRules, t as AppBuilderConfig, u as expandHome } from "../config-DQWueb4a.js";
2
- export { AppBuilderConfig, AppConfig, BotConfig, Config, HOME, LoadedConfig, PathRules, configsDir, defaultConfig, expandHome, findConfigFile, listBoxes, loadConfig, mergeConfig, resolveBox };
1
+ import { a as HOME, c as PathRules, d as expandHome, f as findConfigFile, g as resolveBox, h as mergeConfig, i as Config, l as configsDir, m as loadConfig, n as AppConfig, o as LoadedConfig, p as listBoxes, r as BotConfig, s as McpServer, t as AppBuilderConfig, u as defaultConfig } from "../config-jZAB2Zg8.js";
2
+ export { AppBuilderConfig, AppConfig, BotConfig, Config, HOME, LoadedConfig, McpServer, PathRules, configsDir, defaultConfig, expandHome, findConfigFile, listBoxes, loadConfig, mergeConfig, resolveBox };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clabox",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Run Claude Code in a sandbox for super-safe YOLO mode",
5
5
  "type": "module",
6
6
  "author": "Igor Suvorov",
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ghostty-DcMEZ6Ey.js","names":[],"sources":["../src/init/ghostty.ts"],"sourcesContent":["// Pure builders for the Ghostty-app artifacts emitted by `clabox init`.\n//\n// For a box that opts in via `app` (see AppConfig), `init` generates a Ghostty\n// config whose `command` launches `clabox -b <box>`, then clones Ghostty.app\n// with a tiny C launcher that bakes in `--config-file=<that config>`. Everything\n// here is text-only (no I/O) so it can be unit-tested without macOS; the actual\n// build lives in init/app.ts.\n\nimport path from 'node:path';\nimport type { AppConfig } from '../utils/config.js';\n\n/** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */\nexport interface GhosttyConfigOptions {\n app: AppConfig;\n /** The `-b` box name this config launches. */\n boxName: string;\n /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */\n projectDir: string | null;\n /** Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd. */\n configsDir: string;\n /** Absolute path to the `clabox` binary. */\n claboxBin: string;\n /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */\n baseGhosttyConfig?: string | null;\n}\n\n/** The `command = zsh -lic '…'` line that boots clabox for the box. */\nexport function buildCommand(opts: GhosttyConfigOptions): string {\n const cd = opts.projectDir ? `cd ${opts.projectDir} && ` : '';\n const inner = `${cd}CLABOX_CONFIGS_DIR=${opts.configsDir} ${opts.claboxBin} -b ${opts.boxName}; exec zsh`;\n // Login + interactive zsh so the GUI-launched app inherits the user's PATH\n // (/etc/zprofile→path_helper for Homebrew, ~/.zshrc for fnm/nvm/volta). A bare\n // `bash -c` gets only launchd's minimal PATH and can't find `node` (clabox's\n // shebang is `#!/usr/bin/env node`).\n return `command = zsh -lic '${inner}'`;\n}\n\n/** Build the Ghostty config text for an app box. */\nexport function buildGhosttyConfig(opts: GhosttyConfigOptions): string {\n const { app } = opts;\n const lines: string[] = [\n '# Generated by `clabox init` — do not edit; rerun it after changing the box config.',\n ];\n if (opts.baseGhosttyConfig) lines.push(`config-file = ${opts.baseGhosttyConfig}`);\n lines.push('', `title = \"${app.title ?? app.name}\"`);\n if (app.macosIcon) lines.push(`macos-icon = ${app.macosIcon}`);\n if (app.ghostty && Object.keys(app.ghostty).length > 0) {\n lines.push('');\n for (const [key, value] of Object.entries(app.ghostty)) lines.push(`${key} = ${value}`);\n }\n lines.push('', buildCommand(opts));\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Escape a string for embedding as a C double-quoted literal. */\nfunction cEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Build the C launcher source. It finds itself, locates `ghostty.real` next to\n * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone\n * always boots with its own config regardless of how it's launched.\n */\nexport function buildLauncherSource(configPath: string): string {\n return `// Generated by clabox init. Launches ghostty.real with a baked config.\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <libgen.h>\n#include <mach-o/dyld.h>\n\nstatic const char *CONFIG_PATH = \"${cEscape(configPath)}\";\n\nint main(int argc, char *argv[]) {\n char path[4096];\n uint32_t size = sizeof(path);\n _NSGetExecutablePath(path, &size);\n\n char *dir = dirname(path);\n char real_path[4096];\n snprintf(real_path, sizeof(real_path), \"%s/ghostty.real\", dir);\n\n char config_arg[4096];\n snprintf(config_arg, sizeof(config_arg), \"--config-file=%s\", CONFIG_PATH);\n\n char **new_argv = malloc(sizeof(char *) * (argc + 2));\n new_argv[0] = real_path;\n new_argv[1] = config_arg;\n for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i];\n new_argv[argc + 1] = NULL;\n\n execv(real_path, new_argv);\n return 1;\n}\n`;\n}\n\n/** Absolute path to the built `.app` bundle. */\nexport function appBundlePath(appsDir: string, app: AppConfig): string {\n return path.join(appsDir, `${app.name}.app`);\n}\n\n/** Bundle identifier for the clone (explicit, or derived from the box name). */\nexport function bundleId(boxName: string, app: AppConfig): string {\n return app.bundleId ?? `com.ghostty.custom.${boxName.replace(/-/g, '.')}`;\n}\n"],"mappings":";;;AA2BA,SAAgB,aAAa,MAAoC;CAO/D,OAAO,uBAAuB,GANnB,KAAK,aAAa,MAAM,KAAK,WAAW,QAAQ,GACvC,qBAAqB,KAAK,WAAW,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,YAK1D;AACtC;;AAGA,SAAgB,mBAAmB,MAAoC;CACrE,MAAM,EAAE,QAAQ;CAChB,MAAM,QAAkB,CACtB,qFACF;CACA,IAAI,KAAK,mBAAmB,MAAM,KAAK,iBAAiB,KAAK,mBAAmB;CAChF,MAAM,KAAK,IAAI,YAAY,IAAI,SAAS,IAAI,KAAK,EAAE;CACnD,IAAI,IAAI,WAAW,MAAM,KAAK,gBAAgB,IAAI,WAAW;CAC7D,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC,SAAS,GAAG;EACtD,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO;CACxF;CACA,MAAM,KAAK,IAAI,aAAa,IAAI,CAAC;CACjC,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B;;AAGA,SAAS,QAAQ,GAAmB;CAClC,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAK;AACrD;;;;;;AAOA,SAAgB,oBAAoB,YAA4B;CAC9D,OAAO;;;;;;;oCAO2B,QAAQ,UAAU,EAAE;;;;;;;;;;;;;;;;;;;;;;;;AAwBxD;;AAGA,SAAgB,cAAc,SAAiB,KAAwB;CACrE,OAAO,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,KAAK;AAC7C;;AAGA,SAAgB,SAAS,SAAiB,KAAwB;CAChE,OAAO,IAAI,YAAY,sBAAsB,QAAQ,QAAQ,MAAM,GAAG;AACxE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-CNehSQ-S.js","names":[],"sources":["../src/sandbox/run.ts"],"sourcesContent":["// Profile materialization + launching `claude` under sandbox-exec.\n\nimport { execFileSync, spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { type Config, expandHome, HOME } from '../utils/config.js';\nimport { buildProfile, detectPackagePaths } from './profile.js';\n\nconst TMPDIR = (process.env.TMPDIR || '/tmp').replace(/\\/$/, '');\n\n/** Deterministic per-project profile path under TMPDIR. */\nexport function profilePath(projectDir: string = process.cwd()): string {\n const hash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);\n return path.join(TMPDIR, `clabox-${path.basename(projectDir)}-${hash}.sb`);\n}\n\n/**\n * Effective project dir: `config.cwd` (with `~` expanded, resolved to an\n * absolute path so SBPL `subpath` rules stay valid) if set, else the shell CWD.\n */\nexport function resolveProjectDir(config: Config): string {\n return config.cwd ? path.resolve(expandHome(config.cwd)) : process.cwd();\n}\n\nfunction which(bin: string): string | null {\n try {\n return (\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', encoding: 'utf8' }).trim() || null\n );\n } catch {\n return null;\n }\n}\n\nfunction requireSandboxExec(): void {\n if (!which('sandbox-exec')) {\n throw new Error('sandbox-exec not found. This tool requires macOS with sandbox-exec.');\n }\n}\n\nfunction resolveClaudeBin(config: Config): string {\n const candidate = config.claudeBin || which('claude') || path.join(HOME, '.local/bin/claude');\n if (!candidate || !fs.existsSync(candidate)) {\n throw new Error(`claude not found at '${candidate}'`);\n }\n return candidate;\n}\n\n/** Generate the profile file for the current project, return its path. */\nexport function generateProfile(\n config: Config,\n projectDir: string = resolveProjectDir(config),\n): string {\n requireSandboxExec();\n const file = profilePath(projectDir);\n const text = buildProfile(config, { projectDir, detectedPaths: detectPackagePaths() });\n fs.writeFileSync(file, text);\n return file;\n}\n\n/** Build the `env KEY=VALUE …` argument list forced onto the sandboxed claude. */\nexport function buildEnvArgs(config: Config): string[] {\n const sshDir = expandHome(config.bot.sshDir);\n const botKey = path.join(sshDir, 'id_ed25519');\n const botCfg = path.join(sshDir, 'config');\n const args = [\n `PATH=${path.join(HOME, '.local/bin')}:${process.env.PATH || ''}`,\n `CLAUDE_CONFIG_DIR=${expandHome(config.configDir)}`,\n `GIT_AUTHOR_NAME=${config.bot.name}`,\n `GIT_AUTHOR_EMAIL=${config.bot.email}`,\n `GIT_COMMITTER_NAME=${config.bot.name}`,\n `GIT_COMMITTER_EMAIL=${config.bot.email}`,\n 'GIT_CONFIG_COUNT=2',\n 'GIT_CONFIG_KEY_0=commit.gpgsign',\n 'GIT_CONFIG_VALUE_0=false',\n 'GIT_CONFIG_KEY_1=tag.gpgsign',\n 'GIT_CONFIG_VALUE_1=false',\n ];\n // Pin git ssh to the bot key only when it actually exists, so the sandbox\n // stays usable without a dedicated bot key configured.\n if (fs.existsSync(botKey)) {\n args.push(\n `GIT_SSH_COMMAND=ssh -F ${botCfg} -i ${botKey} -o IdentitiesOnly=yes -o IdentityAgent=none`,\n );\n }\n // User-declared extras go last so they win over the built-in vars above\n // (duplicate keys: `env` keeps the last assignment).\n for (const [key, value] of Object.entries(config.env ?? {})) {\n args.push(`${key}=${value}`);\n }\n return args;\n}\n\n/** Options accepted by {@link runClaude}. */\nexport interface RunOptions {\n configFile?: string | null;\n}\n\n/** Generate the profile and exec claude under sandbox-exec. Returns exit code. */\nexport function runClaude(\n config: Config,\n claudeArgs: string[],\n { configFile }: RunOptions = {},\n): number {\n const projectDir = resolveProjectDir(config);\n const claudeBin = resolveClaudeBin(config);\n const profileFile = generateProfile(config, projectDir);\n\n if (process.env.CLABOX_DEBUG) {\n console.error(`→ Running Claude Code sandboxed in: ${projectDir}`);\n console.error(`→ Profile: ${profileFile}`);\n console.error(`→ Config: ${expandHome(config.configDir)}`);\n if (configFile) console.error(`→ Config file: ${configFile}`);\n }\n\n // Terminal title = cwd (with ~ for $HOME), matching the bash version.\n const title = projectDir.startsWith(HOME) ? `~${projectDir.slice(HOME.length)}` : projectDir;\n process.stdout.write(`\\x1b]0;${title}\\x07`);\n\n const envArgs = buildEnvArgs(config);\n const defaultArgs = Array.isArray(config.claudeArgs) ? config.claudeArgs : [];\n const inner = [\n 'sandbox-exec',\n '-f',\n profileFile,\n 'env',\n ...envArgs,\n claudeBin,\n ...defaultArgs,\n ...claudeArgs,\n ];\n\n // `ulimit` is a shell builtin; run the whole thing under sh so we can set it.\n // `exec \"$@\"` keeps argv intact without re-quoting (args start after $0=sh).\n const ulimit = config.ulimitProcs > 0 ? `ulimit -u ${config.ulimitProcs} 2>/dev/null; ` : '';\n const res = spawnSync('/bin/sh', ['-c', `${ulimit}exec \"$@\"`, 'sh', ...inner], {\n cwd: projectDir,\n stdio: 'inherit',\n });\n if (res.error) throw res.error;\n if (res.signal) return 1;\n return res.status ?? 0;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"scaffold-B7pUVGoC.js","names":[],"sources":["../src/init/scaffold.ts"],"sourcesContent":["// I/O for `clabox init`: discover the box configs in `<base>/configs/`,\n// (re)write the shell aliases into `<base>/scripts/`, and — for boxes that opt\n// in via `app` — generate a Ghostty config in `<base>/ghostty/` and build a\n// standalone `<appsDir>/<name>.app` (see init/app.ts).\n\nimport { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { type Config, expandHome, listBoxes, loadConfig, resolveBox } from '../utils/config.js';\nimport { buildAliasFiles } from './aliases.js';\nimport { buildApp, canBuildApps } from './app.js';\nimport { appBundlePath, buildGhosttyConfig } from './ghostty.js';\nimport { buildRaycastCommand } from './raycast.js';\n\n/**\n * Box names (sorted, de-duplicated) discovered in `<configsDir>` via the same\n * rules as `-b`: both `<name>.mjs` and `<name>.config.mjs`, `_`-prefixed\n * shared partials (e.g. `_presets.mjs`) skipped.\n */\nexport function discoverProfiles(configsDir: string): string[] {\n if (!fs.existsSync(configsDir)) {\n throw new Error(`clabox init: configs dir not found: ${configsDir}`);\n }\n const names = listBoxes(configsDir);\n if (names.length === 0) {\n throw new Error(`clabox init: no box configs (*.mjs) in ${configsDir}`);\n }\n return names;\n}\n\n/** Remove previously generated artifacts (`index.sh`, `clabox-*.sh`). */\nfunction pruneGenerated(scriptsDir: string): void {\n if (!fs.existsSync(scriptsDir)) return;\n for (const f of fs.readdirSync(scriptsDir)) {\n if (f === 'index.sh' || (f.startsWith('clabox-') && f.endsWith('.sh'))) {\n fs.rmSync(path.join(scriptsDir, f), { force: true });\n }\n }\n}\n\n/** Remove files in `dir` whose name ends with `ext` (a no-op if dir is absent). */\nfunction pruneByExt(dir: string, ext: string): void {\n if (!fs.existsSync(dir)) return;\n for (const f of fs.readdirSync(dir)) {\n if (f.endsWith(ext)) fs.rmSync(path.join(dir, f), { force: true });\n }\n}\n\n/** Locate the `clabox` binary to bake into the generated Ghostty `command`. */\nfunction resolveClaboxBin(configured: string | null): string {\n if (configured) return expandHome(configured);\n try {\n const found = execFileSync('command', ['-v', 'clabox'], {\n shell: '/bin/sh',\n encoding: 'utf8',\n }).trim();\n if (found) return found;\n } catch {\n // fall through\n }\n return 'clabox';\n}\n\n/** Options for {@link runInit}. */\nexport interface InitOptions {\n /** Base dir holding `configs/` and `scripts/`. Default: `<cwd>/__`. */\n baseDir?: string;\n /** Build the Ghostty apps for `app` boxes. Default: true. */\n buildApps?: boolean;\n /** Limit app building to a single box (by box name or app display name). */\n only?: string | null;\n}\n\n/** A standalone Ghostty app built by `clabox init`. */\nexport interface BuiltApp {\n box: string;\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\n/** Result of {@link runInit}. */\nexport interface InitResult {\n profiles: string[];\n scriptsDir: string;\n indexFile: string;\n written: string[];\n /** Apps successfully built. */\n apps: BuiltApp[];\n /** Generated Ghostty config files. */\n ghosttyConfigs: string[];\n /** Generated Raycast command scripts. */\n raycastCommands: string[];\n /** Non-fatal issues (e.g. app build skipped/failed). */\n warnings: string[];\n}\n\n/** Generate the Ghostty configs and build the apps for every `app` box. */\nasync function buildAppArtifacts(\n base: string,\n configsDir: string,\n profiles: string[],\n only: string | null,\n result: InitResult,\n): Promise<void> {\n // Load each box config; keep the ones that opt into an app (and match `only`).\n const appBoxes: { name: string; config: Config }[] = [];\n for (const name of profiles) {\n const { config } = await loadConfig(resolveBox(name, configsDir));\n if (!config.app) continue;\n if (only && name !== only && config.app.name !== only) continue;\n appBoxes.push({ name, config });\n }\n if (appBoxes.length === 0) return;\n\n const ghosttyDir = path.join(base, 'ghostty');\n const raycastDir = path.join(base, 'raycast');\n fs.mkdirSync(ghosttyDir, { recursive: true });\n fs.mkdirSync(raycastDir, { recursive: true });\n // Only prune on a full run — with `only` we'd orphan other apps' artifacts.\n if (!only) {\n pruneByExt(ghosttyDir, '.config');\n pruneByExt(raycastDir, '.sh');\n }\n\n for (const { name, config } of appBoxes) {\n const app = config.app;\n if (!app) continue; // narrowed above; keeps the type checker happy\n const configPath = path.join(ghosttyDir, `${name}.config`);\n const projectDir = config.cwd ? path.resolve(expandHome(config.cwd)) : null;\n const baseGhostty = config.appBuilder.baseGhosttyConfig\n ? expandHome(config.appBuilder.baseGhosttyConfig)\n : null;\n fs.writeFileSync(\n configPath,\n buildGhosttyConfig({\n app,\n boxName: name,\n projectDir,\n configsDir,\n claboxBin: resolveClaboxBin(config.appBuilder.claboxBin),\n baseGhosttyConfig: baseGhostty,\n }),\n );\n result.ghosttyConfigs.push(configPath);\n\n // Raycast command that opens the (to be) built bundle.\n const appPath = appBundlePath(expandHome(config.appBuilder.appsDir), app);\n const raycastPath = path.join(raycastDir, `${name}.sh`);\n fs.writeFileSync(raycastPath, buildRaycastCommand({ app, appPath }));\n fs.chmodSync(raycastPath, 0o755);\n result.raycastCommands.push(raycastPath);\n\n const check = canBuildApps(config.appBuilder);\n if (!check.ok) {\n result.warnings.push(`${name}: app not built (${check.reason})`);\n continue;\n }\n try {\n const built = buildApp({ boxName: name, app, builder: config.appBuilder, configPath });\n result.apps.push({ box: name, appPath: built.appPath, signed: built.signed });\n } catch (e) {\n result.warnings.push(`${name}: app build failed — ${(e as Error).message}`);\n }\n }\n}\n\n/** Scan the configs dir, (re)write the alias scripts, and build `app` apps. */\nexport async function runInit({\n baseDir,\n buildApps = true,\n only = null,\n}: InitOptions = {}): Promise<InitResult> {\n const base = path.resolve(baseDir ?? path.join(process.cwd(), '__'));\n const configsDir = path.join(base, 'configs');\n const scriptsDir = path.join(base, 'scripts');\n const profiles = discoverProfiles(configsDir);\n\n fs.mkdirSync(scriptsDir, { recursive: true });\n pruneGenerated(scriptsDir);\n\n const files = buildAliasFiles(profiles, { configsDir, scriptsDir });\n for (const f of files) {\n fs.writeFileSync(f.path, f.content);\n if (f.executable) fs.chmodSync(f.path, 0o755);\n }\n\n const result: InitResult = {\n profiles,\n scriptsDir,\n indexFile: path.join(scriptsDir, 'index.sh'),\n written: files.map((f) => f.path),\n apps: [],\n ghosttyConfigs: [],\n raycastCommands: [],\n warnings: [],\n };\n\n if (buildApps) {\n await buildAppArtifacts(base, configsDir, profiles, only, result);\n }\n return result;\n}\n"],"mappings":"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"}