clabox 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/guideline.md +9 -4
- package/lib/{app-DzQ5yZfD.d.ts → app-B1djcEnN.d.ts} +2 -2
- package/lib/cli.js +4 -3
- package/lib/cli.js.map +1 -1
- package/lib/config-BQ44iVWT.js.map +1 -1
- package/lib/{config-DQWueb4a.d.ts → config-jZAB2Zg8.d.ts} +29 -2
- package/lib/extras-B2ss2Cgh.js +46 -0
- package/lib/extras-B2ss2Cgh.js.map +1 -0
- package/lib/extras-B_dzBrCF.d.ts +34 -0
- package/lib/{ghostty-By4zmOuk.d.ts → ghostty-DIe4yNGZ.d.ts} +2 -2
- package/lib/index.d.ts +9 -8
- package/lib/index.js +4 -3
- package/lib/init/app.d.ts +1 -1
- package/lib/init/ghostty.d.ts +1 -1
- package/lib/init/raycast.d.ts +1 -1
- package/lib/init/scaffold.d.ts +1 -1
- package/lib/init/scaffold.js +1 -1
- package/lib/{profile-BeM41NXc.d.ts → profile-7CiMUPu9.d.ts} +2 -2
- package/lib/{raycast-DM7c559f.d.ts → raycast-fc5U1x7E.d.ts} +2 -2
- package/lib/{run-Cx8cuTh5.d.ts → run-BWGDNJiY.d.ts} +11 -3
- package/lib/{run-CNehSQ-S.js → run-GaW5zEbS.js} +21 -2
- package/lib/run-GaW5zEbS.js.map +1 -0
- package/lib/sandbox/extras.d.ts +2 -0
- package/lib/sandbox/extras.js +2 -0
- package/lib/sandbox/profile.d.ts +1 -1
- package/lib/sandbox/run.d.ts +2 -2
- package/lib/sandbox/run.js +2 -2
- package/lib/{scaffold-B7pUVGoC.js → scaffold-CDKyY8gn.js} +22 -1
- package/lib/scaffold-CDKyY8gn.js.map +1 -0
- package/lib/{scaffold-ByIbYAeS.d.ts → scaffold-CbsKxlL0.d.ts} +3 -1
- package/lib/utils/config.d.ts +2 -2
- package/package.json +1 -1
- package/lib/run-CNehSQ-S.js.map +0 -1
- 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
|
-
│
|
|
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
|
|
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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AppConfig, t as AppBuilderConfig } from "./config-
|
|
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-
|
|
32
|
+
//# sourceMappingURL=app-B1djcEnN.d.ts.map
|
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-
|
|
4
|
-
import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-
|
|
3
|
+
import { n as runInit } from "./scaffold-CDKyY8gn.js";
|
|
4
|
+
import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-GaW5zEbS.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 }
|
|
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":";;;;;AAeA,MAAa,OAAO,GAAG,QAAQ;;AAG/B,SAAgB,WAAW,GAAmB;CAC5C,IAAI,CAAC,GAAG,OAAO;CACf,IAAI,MAAM,KAAK,OAAO;CACtB,IAAI,EAAE,WAAW,IAAI,GAAG,OAAO,KAAK,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD,OAAO;AACT;AAEA,MAAM,MAAM,QAAQ;;AAgIpB,MAAa,gBAAwB;CACnC,KAAK,IAAI,cAAc;CACvB,WAAW,IAAI,qBAAqB;CACpC,WAAW,IAAI,qBAAqB;CACpC,YAAY,CAAC,cAAc,kCAAgC;CAC3D,KAAK;EACH,MAAM,IAAI,mBAAmB;EAC7B,OAAO,IAAI,oBAAoB;EAC/B,QAAQ,IAAI,sBAAsB;CACpC;CACA,KAAK,CAAC;CACN,SAAS;CACT,aAAa;CACb,UAAU,IAAI,oBAAoB;CAClC,OAAO;EACL,WAAW,CAAC;EACZ,UAAU,CAAC;EACX,MAAM,CAAC;EACP,MAAM,CAAC;CACT;CACA,UAAU;EAAC;EAAa;EAAW;EAAa;EAAY;EAAU;CAAO;CAE7E,gBAAgB;EAAC;EAAO;EAAS;EAAQ;EAAU;CAAQ;CAE3D,YAAY;EACV,YAAY,IAAI,sBAAsB;EACtC,SAAS,IAAI,mBAAmB;EAChC,QAAQ,IAAI,kBAAkB;EAC9B,mBAAmB,IAAI,8BAA8B;EACrD,WAAW,IAAI,qBAAqB;CACtC;AACF;AAIA,SAAS,cAAc,GAAwB;CAC7C,OAAO,KAAK,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAC/D;;AAGA,SAAS,UAAU,MAAa,UAAwB;CACtD,MAAM,MAAa,EAAE,GAAG,KAAK;CAC7B,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,QAAQ,GAAG;EACjD,IAAI,QAAQ,KAAA,GAAW;EACvB,MAAM,UAAU,KAAK;EACrB,IAAI,OAAO,cAAc,GAAG,KAAK,cAAc,OAAO,IAAI,UAAU,SAAS,GAAG,IAAI;CACtF;CACA,OAAO;AACT;;AAGA,SAAgB,YAAY,MAAc,UAA2B;CACnE,IAAI,CAAC,cAAc,QAAQ,GAAG,OAAO;CACrC,OAAO,UAAU,MAA0B,QAAQ;AACrD;;;;;AAMA,SAAgB,eAAe,UAAyC;CACtE,MAAM,SAAS,YAAY,IAAI;CAC/B,IAAI,QAAQ,OAAO,WAAW,MAAM;CAMpC,OAAO;EAJL,KAAK,KAAK,QAAQ,IAAI,GAAG,mBAAmB;EAC5C,KAAK,KAAK,QAAQ,IAAI,GAAG,kBAAkB;EAC3C,KAAK,KAAK,MAAM,WAAW,UAAU,YAAY;CAEnC,CAAC,CAAC,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,KAAK;AACrD;;;;;AAMA,SAAgB,aAAqB;CACnC,OAAO,WAAW,IAAI,sBAAsB,0BAA0B;AACxE;AAEA,MAAM,eAAe,CAAC,eAAe,MAAM;;AAG3C,SAAS,cAAc,MAAc,KAAuB;CAC1D,OAAO,aAAa,KAAK,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,GAAG,CAAC;AAC9D;;AAGA,SAAgB,UAAU,MAAc,WAAW,GAAa;CAC9D,IAAI;CACJ,IAAI;EACF,UAAU,GAAG,YAAY,GAAG;CAC9B,QAAQ;EACN,OAAO,CAAC;CACV;CACA,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,KAAK,SAAS;EAEvB,IAAI,EAAE,WAAW,GAAG,GAAG;EACvB,MAAM,SAAS,aAAa,MAAM,MAAM,EAAE,SAAS,CAAC,CAAC;EACrD,IAAI,QAAQ,MAAM,IAAI,EAAE,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC;CAClD;CACA,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK;AACzB;;;;;;;AAQA,SAAgB,WAAW,MAAc,MAAc,WAAW,GAAW;CAG3E,IAAI,CAAC,KAAK,WAAW,GAAG,GAAG;EACzB,MAAM,QAAQ,cAAc,MAAM,GAAG,CAAC,CAAC,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC;EACnE,IAAI,OAAO,OAAO;CACpB;CACA,MAAM,YAAY,UAAU,GAAG;CAC/B,MAAM,OAAO,UAAU,SAAS,cAAc,UAAU,KAAK,IAAI,MAAM,iBAAiB;CACxF,MAAM,IAAI,MAAM,gBAAgB,KAAK,iBAAiB,IAAI,IAAI,KAAK,EAAE;AACvE;;;;;;;AAcA,eAAsB,WAAW,gBAAuD;CACtF,IAAI,MAAc;CAClB,MAAM,OAAO,eAAe,cAAc;CAC1C,IAAI,MAAM;EACR,MAAM,MAAM,MAAM,OAAO,cAAc,IAAI,CAAC,CAAC;EAC7C,MAAM,WAAW,IAAI,WAAW,IAAI,UAAU;EAE9C,MAAM,YAAY,eADD,OAAO,aAAa,aAAa,MAAM,SAAS,aAAa,IAAI,QACzC;CAC3C;CACA,OAAO;EAAE,QAAQ;EAAK,YAAY;CAAK;AACzC"}
|
|
@@ -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,
|
|
134
|
-
//# sourceMappingURL=config-
|
|
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-
|
|
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). */
|
|
@@ -31,4 +31,4 @@ declare function appBundlePath(appsDir: string, app: AppConfig): string;
|
|
|
31
31
|
declare function bundleId(boxName: string, app: AppConfig): string;
|
|
32
32
|
//#endregion
|
|
33
33
|
export { buildLauncherSource as a, buildGhosttyConfig as i, appBundlePath as n, bundleId as o, buildCommand as r, GhosttyConfigOptions as t };
|
|
34
|
-
//# sourceMappingURL=ghostty-
|
|
34
|
+
//# sourceMappingURL=ghostty-DIe4yNGZ.d.ts.map
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
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
|
|
3
|
-
import { i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "./app-
|
|
4
|
-
import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "./ghostty-
|
|
5
|
-
import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "./raycast-
|
|
6
|
-
import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "./scaffold-
|
|
7
|
-
import {
|
|
8
|
-
import { a as
|
|
9
|
-
|
|
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-DIe4yNGZ.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 { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
|
|
2
3
|
import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-DKGcMHHe.js";
|
|
3
4
|
import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
|
|
4
5
|
import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
|
|
5
6
|
import { n as raycastIcon, t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
|
|
6
|
-
import { n as runInit, t as discoverProfiles } from "./scaffold-
|
|
7
|
+
import { n as runInit, t as discoverProfiles } from "./scaffold-CDKyY8gn.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-
|
|
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-GaW5zEbS.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 };
|
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-
|
|
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/ghostty.d.ts
CHANGED
|
@@ -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-
|
|
1
|
+
import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-DIe4yNGZ.js";
|
|
2
2
|
export { GhosttyConfigOptions, appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
|
package/lib/init/raycast.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "../raycast-
|
|
1
|
+
import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "../raycast-fc5U1x7E.js";
|
|
2
2
|
export { RaycastCommandOptions, buildRaycastCommand, raycastIcon };
|
package/lib/init/scaffold.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "../scaffold-
|
|
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 };
|
package/lib/init/scaffold.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as runInit, t as discoverProfiles } from "../scaffold-
|
|
1
|
+
import { n as runInit, t as discoverProfiles } from "../scaffold-CDKyY8gn.js";
|
|
2
2
|
export { discoverProfiles, runInit };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as Config } from "./config-
|
|
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-
|
|
29
|
+
//# sourceMappingURL=profile-7CiMUPu9.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AppConfig } from "./config-
|
|
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-
|
|
22
|
+
//# sourceMappingURL=raycast-fc5U1x7E.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { i as Config } from "./config-
|
|
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-
|
|
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,4 +1,5 @@
|
|
|
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
5
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
@@ -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-
|
|
132
|
+
//# sourceMappingURL=run-GaW5zEbS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-GaW5zEbS.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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"}
|
package/lib/sandbox/profile.d.ts
CHANGED
|
@@ -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-
|
|
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 };
|
package/lib/sandbox/run.d.ts
CHANGED
|
@@ -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-
|
|
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 };
|
package/lib/sandbox/run.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath, t as buildEnvArgs } from "../run-
|
|
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-GaW5zEbS.js";
|
|
2
|
+
export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { i as expandHome, l as resolveBox, o as listBoxes, s as loadConfig } from "./config-BQ44iVWT.js";
|
|
2
|
+
import { n as buildBoxExtras } from "./extras-B2ss2Cgh.js";
|
|
2
3
|
import { n as buildAliasFiles } from "./aliases-DKGcMHHe.js";
|
|
3
4
|
import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
|
|
4
5
|
import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
|
|
@@ -40,6 +41,24 @@ function resolveClaboxBin(configured) {
|
|
|
40
41
|
} catch {}
|
|
41
42
|
return "clabox";
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the
|
|
46
|
+
* files exist ahead of the first run (the same files `run` writes; slug = box
|
|
47
|
+
* name). Best-effort per box: a config that fails to load becomes a warning.
|
|
48
|
+
*/
|
|
49
|
+
async function materializeExtras(configsDir, profiles, result) {
|
|
50
|
+
for (const name of profiles) try {
|
|
51
|
+
const { config } = await loadConfig(resolveBox(name, configsDir));
|
|
52
|
+
for (const f of buildBoxExtras(config, name).files) {
|
|
53
|
+
fs.mkdirSync(path.dirname(f.path), { recursive: true });
|
|
54
|
+
fs.writeFileSync(f.path, f.content, { mode: 384 });
|
|
55
|
+
fs.chmodSync(f.path, 384);
|
|
56
|
+
result.extraFiles.push(f.path);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
result.warnings.push(`${name}: extras not materialized — ${e.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
43
62
|
/** Generate the Ghostty configs and build the apps for every `app` box. */
|
|
44
63
|
async function buildAppArtifacts(base, configsDir, profiles, only, result) {
|
|
45
64
|
const appBoxes = [];
|
|
@@ -130,12 +149,14 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
|
|
|
130
149
|
apps: [],
|
|
131
150
|
ghosttyConfigs: [],
|
|
132
151
|
raycastCommands: [],
|
|
152
|
+
extraFiles: [],
|
|
133
153
|
warnings: []
|
|
134
154
|
};
|
|
155
|
+
await materializeExtras(configsDir, profiles, result);
|
|
135
156
|
if (buildApps) await buildAppArtifacts(base, configsDir, profiles, only, result);
|
|
136
157
|
return result;
|
|
137
158
|
}
|
|
138
159
|
//#endregion
|
|
139
160
|
export { runInit as n, discoverProfiles as t };
|
|
140
161
|
|
|
141
|
-
//# sourceMappingURL=scaffold-
|
|
162
|
+
//# sourceMappingURL=scaffold-CDKyY8gn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-CDKyY8gn.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 { 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/** 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 /** 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,\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 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-
|
|
48
|
+
//# sourceMappingURL=scaffold-CbsKxlL0.d.ts.map
|
package/lib/utils/config.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as HOME, c as
|
|
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
package/lib/run-CNehSQ-S.js.map
DELETED
|
@@ -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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"}
|