clabox 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/guideline.md CHANGED
@@ -135,7 +135,7 @@ The yargs CLI (`scriptName('clabox')`). Commands: `run [claudeArgs..]` (default)
135
135
  - **Read-write:** the project dir (`config.cwd` if set, else the shell CWD), the Claude config dir (`configDir`), `/tmp`, `/private/tmp`, `/private/var/folders/…`, `~/Library/Keychains` (OAuth refresh), plus `paths.readWrite`.
136
136
  - **Network:** `(allow network*)` when `network: true` (default).
137
137
  - **Two deny tiers** (Seatbelt evaluates rules in order — the *last* match wins — so placement is deliberate):
138
- - **Soft privacy deny (overridable):** `denyHome` (`~/Documents`, `~/Desktop`, …) and `paths.deny`, emitted *before* the extra `readOnly`/`readWrite` and the project dir. An explicit grant therefore overrides it — handy for a project that lives under `~/Documents`, or a debug box that wants `readOnly: ['/']` to roam the disk.
138
+ - **Soft privacy deny (overridable):** `denyHome` (`~/Documents`, `~/Desktop`, …) and `paths.deny`, emitted *before* the extra `readOnly`/`readWrite` and the project dir. An explicit grant therefore overrides it — handy for a project that lives under `~/Documents`, a debug box that wants `readOnly: ['/']` to roam the disk, or a **root box that wants `readWrite: ['/']`** to read *and write* anywhere on disk (the hard secret deny below still wins, so credentials and private keys stay protected even with a whole-disk RW grant).
139
139
  - **Hard secret deny (always wins):** `denyDotConfigs` (`~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`, `~/.config`) and personal SSH keys `~/.ssh/id_*`, `*.pem`, `*.key`, emitted as the **very last file rule** — after every allow, the extra paths and the project dir. No `readOnly`/`readWrite` grant can re-expose these. Only the bot key subdir (`bot.sshDir`, not matched by the patterns) stays readable. (The `~/.config/git` RO carve-out is shadowed by the hard deny on `~/.config`; git still reads `~/.gitconfig` outside `~/.config`.)
140
140
 
141
141
  ### Git/ssh bot identity
@@ -10,8 +10,12 @@ interface InitFile {
10
10
  }
11
11
  /** Absolute locations for the generated artifacts. */
12
12
  interface AliasPaths {
13
- /** Dir holding the box config files. */
14
- configsDir: string;
13
+ /**
14
+ * Dir holding the box config files, baked as `CLABOX_CONFIGS_DIR` so `-b`
15
+ * resolves boxes from any cwd, or null to omit it — when null, `-b` finds the
16
+ * box via the runtime default (`~/.config/clabox/configs`) at run time.
17
+ */
18
+ configsDir: string | null;
15
19
  /** Dir to emit `index.sh` and the per-box wrappers into. */
16
20
  scriptsDir: string;
17
21
  }
@@ -28,4 +32,4 @@ declare function buildWrapper(fnName: string, indexPath: string): string;
28
32
  declare function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[];
29
33
  //#endregion
30
34
  export { buildIndex as a, buildAliasFiles as i, InitFile as n, buildWrapper as o, aliasName as r, AliasPaths as t };
31
- //# sourceMappingURL=aliases-DXyz-ufw.d.ts.map
35
+ //# sourceMappingURL=aliases-BNdrOn9E.d.ts.map
@@ -20,7 +20,7 @@ function buildIndex(profiles, { configsDir, scriptsDir }) {
20
20
  ...usage,
21
21
  "",
22
22
  "_clabox_run() {",
23
- ` CLABOX_CONFIGS_DIR="${configsDir}" clabox -b "$1" "\${@:2}"`,
23
+ configsDir ? ` CLABOX_CONFIGS_DIR="${configsDir}" clabox -b "$1" "\${@:2}"` : ` clabox -b "$1" "\${@:2}"`,
24
24
  "}",
25
25
  "",
26
26
  ...defs
@@ -57,4 +57,4 @@ function buildAliasFiles(profiles, paths) {
57
57
  //#endregion
58
58
  export { buildWrapper as i, buildAliasFiles as n, buildIndex as r, aliasName as t };
59
59
 
60
- //# sourceMappingURL=aliases-DKGcMHHe.js.map
60
+ //# sourceMappingURL=aliases-KGjds7dK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aliases-KGjds7dK.js","names":[],"sources":["../src/init/aliases.ts"],"sourcesContent":["// Pure builder for the shell aliases emitted by `clabox init`.\n//\n// For each box config (`<name>.mjs` / `<name>.config.mjs`) it produces one\n// command `clabox-<name>` that runs that box. Whether it runs yolo or asks for\n// confirmation is decided by the box's own preset (its `claudeArgs`), not here.\n// Plus an `index.sh` defining them all as shell functions to `source`.\n\nimport path from 'node:path';\n\n/** A file to be written by `clabox init`. */\nexport interface InitFile {\n /** Absolute path to write. */\n path: string;\n /** File contents. */\n content: string;\n /** Whether to `chmod +x` after writing. */\n executable: boolean;\n}\n\n/** Absolute locations for the generated artifacts. */\nexport interface AliasPaths {\n /**\n * Dir holding the box config files, baked as `CLABOX_CONFIGS_DIR` so `-b`\n * resolves boxes from any cwd, or null to omit it — when null, `-b` finds the\n * box via the runtime default (`~/.config/clabox/configs`) at run time.\n */\n configsDir: string | null;\n /** Dir to emit `index.sh` and the per-box wrappers into. */\n scriptsDir: string;\n}\n\n/** The `clabox-<name>` command name for a box. */\nexport function aliasName(profile: string): string {\n return `clabox-${profile}`;\n}\n\n/** Build the source-able `index.sh` defining one function per box. */\nexport function buildIndex(profiles: string[], { configsDir, scriptsDir }: AliasPaths): string {\n const indexPath = path.join(scriptsDir, 'index.sh');\n const usage = profiles.map((p) => `# ${aliasName(p)}`);\n const defs = profiles.map((p) => `${aliasName(p)}() { _clabox_run ${p} \"$@\"; }`);\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init` — do not edit; rerun it after changing the configs dir.',\n '#',\n '# Source this from ~/.zshrc or ~/.bashrc:',\n `# source ${indexPath}`,\n '#',\n \"# Commands (run from any cwd). yolo vs. safe is set by each box's preset:\",\n ...usage,\n '',\n '_clabox_run() {',\n // Point `-b` at this dir so resolveBox picks the right .mjs/.config.mjs file\n // — unless it's the runtime default, then let `-b` resolve it at run time.\n configsDir\n ? ` CLABOX_CONFIGS_DIR=\"${configsDir}\" clabox -b \"$1\" \"\\${@:2}\"`\n : ` clabox -b \"$1\" \"\\${@:2}\"`,\n '}',\n '',\n ...defs,\n ].join('\\n')}\\n`;\n}\n\n/** Build a standalone wrapper script that sources `index.sh` and calls one fn. */\nexport function buildWrapper(fnName: string, indexPath: string): string {\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init`.',\n 'set -euo pipefail',\n `source \"${indexPath}\"`,\n `${fnName} \"$@\"`,\n ].join('\\n')}\\n`;\n}\n\n/** All files `clabox init` writes: the `index.sh` plus a wrapper per box. */\nexport function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[] {\n const indexPath = path.join(paths.scriptsDir, 'index.sh');\n const files: InitFile[] = [\n { path: indexPath, content: buildIndex(profiles, paths), executable: true },\n ];\n for (const p of profiles) {\n const fn = aliasName(p);\n files.push({\n path: path.join(paths.scriptsDir, `${fn}.sh`),\n content: buildWrapper(fn, indexPath),\n executable: true,\n });\n }\n return files;\n}\n"],"mappings":";;;AAgCA,SAAgB,UAAU,SAAyB;CACjD,OAAO,UAAU;AACnB;;AAGA,SAAgB,WAAW,UAAoB,EAAE,YAAY,cAAkC;CAC7F,MAAM,YAAY,KAAK,KAAK,YAAY,UAAU;CAClD,MAAM,QAAQ,SAAS,KAAK,MAAM,OAAO,UAAU,CAAC,GAAG;CACvD,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,UAAU,CAAC,EAAE,mBAAmB,EAAE,SAAS;CAC/E,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACA,GAAG;EACH;EACA;EAGA,aACI,yBAAyB,WAAW,8BACpC;EACJ;EACA;EACA,GAAG;CACL,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,aAAa,QAAgB,WAA2B;CACtE,OAAO,GAAG;EACR;EACA;EACA;EACA,WAAW,UAAU;EACrB,GAAG,OAAO;CACZ,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,gBAAgB,UAAoB,OAA+B;CACjF,MAAM,YAAY,KAAK,KAAK,MAAM,YAAY,UAAU;CACxD,MAAM,QAAoB,CACxB;EAAE,MAAM;EAAW,SAAS,WAAW,UAAU,KAAK;EAAG,YAAY;CAAK,CAC5E;CACA,KAAK,MAAM,KAAK,UAAU;EACxB,MAAM,KAAK,UAAU,CAAC;EACtB,MAAM,KAAK;GACT,MAAM,KAAK,KAAK,MAAM,YAAY,GAAG,GAAG,IAAI;GAC5C,SAAS,aAAa,IAAI,SAAS;GACnC,YAAY;EACd,CAAC;CACH;CACA,OAAO;AACT"}
@@ -1,9 +1,9 @@
1
1
  import { i as expandHome } from "./config-BQ44iVWT.js";
2
- import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
2
+ import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
3
3
  import path from "node:path";
4
- import { execFileSync } from "node:child_process";
5
4
  import fs from "node:fs";
6
5
  import os from "node:os";
6
+ import { execFileSync } from "node:child_process";
7
7
  //#region src/init/app.ts
8
8
  function run(cmd, args) {
9
9
  execFileSync(cmd, args, { stdio: [
@@ -251,4 +251,4 @@ function buildApp(opts) {
251
251
  //#endregion
252
252
  export { canBuildApps as n, installIcon as r, buildApp as t };
253
253
 
254
- //# sourceMappingURL=app-CQmESEdh.js.map
254
+ //# sourceMappingURL=app-ChnKbgkD.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-CQmESEdh.js","names":[],"sources":["../src/init/app.ts"],"sourcesContent":["// I/O for the `clabox init` Ghostty-app builder (macOS-only).\n//\n// Clones Ghostty.app into `<appsDir>/<name>.app`, swaps the binary for a tiny\n// compiled launcher that bakes in `--config-file=<config>`, sets the icon,\n// disables Sparkle, and re-signs. Mirrors the old ghostty-app-builder.sh. The\n// pure text builders live in init/ghostty.ts.\n\nimport { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { type AppBuilderConfig, type AppConfig, expandHome } from '../utils/config.js';\nimport { appBundlePath, buildLauncherSource, bundleId } from './ghostty.js';\n\n/** Inputs for {@link buildApp}. */\nexport interface BuildAppOptions {\n /** The `-b` box name (drives the default bundle id). */\n boxName: string;\n app: AppConfig;\n builder: AppBuilderConfig;\n /** Absolute path to the already-written Ghostty config to bake in. */\n configPath: string;\n}\n\n/** Result of a successful {@link buildApp}. */\nexport interface BuildAppResult {\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\nfunction run(cmd: string, args: string[]): void {\n execFileSync(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n}\n\nfunction has(bin: string): boolean {\n try {\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/** True when the host can build apps (macOS with the donor app + a C compiler). */\nexport function canBuildApps(builder: AppBuilderConfig): { ok: boolean; reason?: string } {\n if (process.platform !== 'darwin') return { ok: false, reason: 'not macOS' };\n if (!fs.existsSync(expandHome(builder.ghosttyApp))) {\n return { ok: false, reason: `Ghostty not found at ${builder.ghosttyApp}` };\n }\n if (!has('cc')) return { ok: false, reason: 'no C compiler (cc) — install Xcode CLT' };\n return { ok: true };\n}\n\n/** Extract the donor app's entitlements to a tmp file, or null if it has none. */\nfunction extractEntitlements(ghosttyApp: string, tmpDir: string): string | null {\n let xml: string;\n try {\n xml = execFileSync('codesign', ['-d', '--entitlements', '-', '--xml', ghosttyApp], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n return null;\n }\n const start = xml.indexOf('<?xml');\n if (start < 0) return null;\n const file = path.join(tmpDir, 'entitlements.xml');\n fs.writeFileSync(file, xml.slice(start));\n return file;\n}\n\n/** Name of the icon resource referenced by the bundle (default Ghostty.icns). */\nfunction iconResourceName(plist: string): string {\n try {\n const name = execFileSync('plutil', ['-extract', 'CFBundleIconFile', 'raw', plist], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n return name.endsWith('.icns') ? name : `${name}.icns`;\n } catch {\n return 'Ghostty.icns';\n }\n}\n\n/** Convert a PNG into a multi-resolution .icns at `out`. */\nfunction pngToIcns(png: string, out: string, tmpDir: string): void {\n const iconset = path.join(tmpDir, 'icon.iconset');\n fs.mkdirSync(iconset, { recursive: true });\n for (const size of [16, 32, 128, 256, 512]) {\n run('sips', [\n '-z',\n `${size}`,\n `${size}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}.png`),\n ]);\n const d = size * 2;\n run('sips', [\n '-z',\n `${d}`,\n `${d}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}@2x.png`),\n ]);\n }\n run('iconutil', ['-c', 'icns', iconset, '-o', out]);\n}\n\n/** Install the box icon into the cloned bundle, if `app.icon` is set. */\nexport function installIcon(app: AppConfig, appPath: string, tmpDir: string): void {\n if (!app.icon) return;\n const icon = expandHome(app.icon);\n if (!fs.existsSync(icon)) throw new Error(`icon not found: ${app.icon}`);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const dest = path.join(appPath, 'Contents', 'Resources', iconResourceName(plist));\n if (icon.endsWith('.icns')) fs.copyFileSync(icon, dest);\n else if (icon.endsWith('.png')) pngToIcns(icon, dest, tmpDir);\n else throw new Error(`unsupported icon type (need .icns/.png): ${app.icon}`);\n\n // Ghostty ships a compiled asset catalog (Assets.car) and a `CFBundleIconName`\n // pointing into it, which macOS prefers over the loose `CFBundleIconFile`\n // .icns we just replaced — so our icon would be ignored. Drop the asset-catalog\n // reference so macOS falls back to the .icns. (May be absent on other donors.)\n try {\n run('plutil', ['-remove', 'CFBundleIconName', plist]);\n } catch {\n // donor app may not define CFBundleIconName — ignore\n }\n}\n\n/**\n * Build the standalone Ghostty app for a box. Throws on any failure (the caller\n * decides whether to abort or carry on with the other boxes).\n */\nexport function buildApp(opts: BuildAppOptions): BuildAppResult {\n const { app, builder, boxName, configPath } = opts;\n const check = canBuildApps(builder);\n if (!check.ok) throw new Error(`cannot build app: ${check.reason}`);\n\n const ghosttyApp = expandHome(builder.ghosttyApp);\n const appsDir = expandHome(builder.appsDir);\n const appPath = appBundlePath(appsDir, app);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'clabox-app-'));\n\n try {\n const entitlements = extractEntitlements(ghosttyApp, tmpDir);\n\n // Full clone (cp -R keeps bundle symlinks/frameworks intact).\n fs.mkdirSync(appsDir, { recursive: true });\n fs.rmSync(appPath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, appPath]);\n\n // Identity.\n run('plutil', ['-replace', 'CFBundleIdentifier', '-string', bundleId(boxName, app), plist]);\n run('plutil', ['-replace', 'CFBundleName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleDisplayName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleExecutable', '-string', 'ghostty', plist]);\n\n // Disable Sparkle auto-update (would clobber the clone).\n run('plutil', ['-replace', 'SUEnableAutomaticChecks', '-bool', 'NO', plist]);\n try {\n run('plutil', ['-replace', 'SUFeedURL', '-string', '', plist]);\n } catch {\n // donor app may not define SUFeedURL — ignore\n }\n\n // Swap the binary for a launcher that prepends --config-file.\n const bin = path.join(appPath, 'Contents', 'MacOS', 'ghostty');\n fs.renameSync(bin, `${bin}.real`);\n const src = path.join(tmpDir, 'launcher.c');\n fs.writeFileSync(src, buildLauncherSource(configPath));\n run('cc', ['-o', bin, src]);\n\n installIcon(app, appPath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle.\n const signId = builder.signId;\n const entArgs = entitlements ? ['--entitlements', entitlements] : [];\n const idArgs = signId ? ['--sign', signId] : ['--sign', '-'];\n run('codesign', ['--force', ...idArgs, ...entArgs, `${bin}.real`]);\n run('codesign', ['--force', '--deep', ...idArgs, ...entArgs, appPath]);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-ChnKbgkD.js","names":[],"sources":["../src/init/app.ts"],"sourcesContent":["// I/O for the `clabox init` Ghostty-app builder (macOS-only).\n//\n// Clones Ghostty.app into `<appsDir>/<name>.app`, swaps the binary for a tiny\n// compiled launcher that bakes in `--config-file=<config>`, sets the icon,\n// disables Sparkle, and re-signs. Mirrors the old ghostty-app-builder.sh. The\n// pure text builders live in init/ghostty.ts.\n\nimport { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { type AppBuilderConfig, type AppConfig, expandHome } from '../utils/config.js';\nimport { appBundlePath, buildLauncherSource, bundleId } from './ghostty.js';\n\n/** Inputs for {@link buildApp}. */\nexport interface BuildAppOptions {\n /** The `-b` box name (drives the default bundle id). */\n boxName: string;\n app: AppConfig;\n builder: AppBuilderConfig;\n /** Absolute path to the already-written Ghostty config to bake in. */\n configPath: string;\n}\n\n/** Result of a successful {@link buildApp}. */\nexport interface BuildAppResult {\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\nfunction run(cmd: string, args: string[]): void {\n execFileSync(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n}\n\nfunction has(bin: string): boolean {\n try {\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/** True when the host can build apps (macOS with the donor app + a C compiler). */\nexport function canBuildApps(builder: AppBuilderConfig): { ok: boolean; reason?: string } {\n if (process.platform !== 'darwin') return { ok: false, reason: 'not macOS' };\n if (!fs.existsSync(expandHome(builder.ghosttyApp))) {\n return { ok: false, reason: `Ghostty not found at ${builder.ghosttyApp}` };\n }\n if (!has('cc')) return { ok: false, reason: 'no C compiler (cc) — install Xcode CLT' };\n return { ok: true };\n}\n\n/** Extract the donor app's entitlements to a tmp file, or null if it has none. */\nfunction extractEntitlements(ghosttyApp: string, tmpDir: string): string | null {\n let xml: string;\n try {\n xml = execFileSync('codesign', ['-d', '--entitlements', '-', '--xml', ghosttyApp], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n return null;\n }\n const start = xml.indexOf('<?xml');\n if (start < 0) return null;\n const file = path.join(tmpDir, 'entitlements.xml');\n fs.writeFileSync(file, xml.slice(start));\n return file;\n}\n\n/** Name of the icon resource referenced by the bundle (default Ghostty.icns). */\nfunction iconResourceName(plist: string): string {\n try {\n const name = execFileSync('plutil', ['-extract', 'CFBundleIconFile', 'raw', plist], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n return name.endsWith('.icns') ? name : `${name}.icns`;\n } catch {\n return 'Ghostty.icns';\n }\n}\n\n/** Convert a PNG into a multi-resolution .icns at `out`. */\nfunction pngToIcns(png: string, out: string, tmpDir: string): void {\n const iconset = path.join(tmpDir, 'icon.iconset');\n fs.mkdirSync(iconset, { recursive: true });\n for (const size of [16, 32, 128, 256, 512]) {\n run('sips', [\n '-z',\n `${size}`,\n `${size}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}.png`),\n ]);\n const d = size * 2;\n run('sips', [\n '-z',\n `${d}`,\n `${d}`,\n png,\n '--out',\n path.join(iconset, `icon_${size}x${size}@2x.png`),\n ]);\n }\n run('iconutil', ['-c', 'icns', iconset, '-o', out]);\n}\n\n/** Install the box icon into the cloned bundle, if `app.icon` is set. */\nexport function installIcon(app: AppConfig, appPath: string, tmpDir: string): void {\n if (!app.icon) return;\n const icon = expandHome(app.icon);\n if (!fs.existsSync(icon)) throw new Error(`icon not found: ${app.icon}`);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const dest = path.join(appPath, 'Contents', 'Resources', iconResourceName(plist));\n if (icon.endsWith('.icns')) fs.copyFileSync(icon, dest);\n else if (icon.endsWith('.png')) pngToIcns(icon, dest, tmpDir);\n else throw new Error(`unsupported icon type (need .icns/.png): ${app.icon}`);\n\n // Ghostty ships a compiled asset catalog (Assets.car) and a `CFBundleIconName`\n // pointing into it, which macOS prefers over the loose `CFBundleIconFile`\n // .icns we just replaced — so our icon would be ignored. Drop the asset-catalog\n // reference so macOS falls back to the .icns. (May be absent on other donors.)\n try {\n run('plutil', ['-remove', 'CFBundleIconName', plist]);\n } catch {\n // donor app may not define CFBundleIconName — ignore\n }\n}\n\n/**\n * Build the standalone Ghostty app for a box. Throws on any failure (the caller\n * decides whether to abort or carry on with the other boxes).\n */\nexport function buildApp(opts: BuildAppOptions): BuildAppResult {\n const { app, builder, boxName, configPath } = opts;\n const check = canBuildApps(builder);\n if (!check.ok) throw new Error(`cannot build app: ${check.reason}`);\n\n const ghosttyApp = expandHome(builder.ghosttyApp);\n const appsDir = expandHome(builder.appsDir);\n const appPath = appBundlePath(appsDir, app);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'clabox-app-'));\n\n try {\n const entitlements = extractEntitlements(ghosttyApp, tmpDir);\n\n // Full clone (cp -R keeps bundle symlinks/frameworks intact).\n fs.mkdirSync(appsDir, { recursive: true });\n fs.rmSync(appPath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, appPath]);\n\n // Identity.\n run('plutil', ['-replace', 'CFBundleIdentifier', '-string', bundleId(boxName, app), plist]);\n run('plutil', ['-replace', 'CFBundleName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleDisplayName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleExecutable', '-string', 'ghostty', plist]);\n\n // Disable Sparkle auto-update (would clobber the clone).\n run('plutil', ['-replace', 'SUEnableAutomaticChecks', '-bool', 'NO', plist]);\n try {\n run('plutil', ['-replace', 'SUFeedURL', '-string', '', plist]);\n } catch {\n // donor app may not define SUFeedURL — ignore\n }\n\n // Swap the binary for a launcher that prepends --config-file.\n const bin = path.join(appPath, 'Contents', 'MacOS', 'ghostty');\n fs.renameSync(bin, `${bin}.real`);\n const src = path.join(tmpDir, 'launcher.c');\n fs.writeFileSync(src, buildLauncherSource(configPath));\n run('cc', ['-o', bin, src]);\n\n installIcon(app, appPath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle.\n const signId = builder.signId;\n const entArgs = entitlements ? ['--entitlements', entitlements] : [];\n const idArgs = signId ? ['--sign', signId] : ['--sign', '-'];\n run('codesign', ['--force', ...idArgs, ...entArgs, `${bin}.real`]);\n run('codesign', ['--force', '--deep', ...idArgs, ...entArgs, appPath]);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
package/lib/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { l as resolveBox, s as loadConfig } from "./config-BQ44iVWT.js";
3
- import { n as runInit } from "./scaffold-CDKyY8gn.js";
4
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-GaW5zEbS.js";
3
+ import { n as runInit } from "./scaffold-C1tqCL_8.js";
4
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-yUsOaKFx.js";
5
5
  import path from "node:path";
6
6
  import yargs from "yargs";
7
7
  import { hideBin } from "yargs/helpers";
@@ -8,9 +8,18 @@ interface GhosttyConfigOptions {
8
8
  boxName: string;
9
9
  /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */
10
10
  projectDir: string | null;
11
- /** Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd. */
12
- configsDir: string;
13
- /** Absolute path to the `clabox` binary. */
11
+ /**
12
+ * Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd, or
13
+ * null to omit it — when null, `-b` finds the box via the runtime default
14
+ * (`~/.config/clabox/configs`) at launch time.
15
+ */
16
+ configsDir: string | null;
17
+ /**
18
+ * The `clabox` command baked into the launcher. Default is a bare `clabox`
19
+ * resolved from PATH at launch time by the `zsh -lic` login shell (survives
20
+ * package-manager moves, e.g. bun → npm/homebrew); pass an absolute path only
21
+ * to pin a specific binary.
22
+ */
14
23
  claboxBin: string;
15
24
  /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */
16
25
  baseGhosttyConfig?: string | null;
@@ -31,4 +40,4 @@ declare function appBundlePath(appsDir: string, app: AppConfig): string;
31
40
  declare function bundleId(boxName: string, app: AppConfig): string;
32
41
  //#endregion
33
42
  export { buildLauncherSource as a, buildGhosttyConfig as i, appBundlePath as n, bundleId as o, buildCommand as r, GhosttyConfigOptions as t };
34
- //# sourceMappingURL=ghostty-DIe4yNGZ.d.ts.map
43
+ //# sourceMappingURL=ghostty-DFwFh4aL.d.ts.map
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  //#region src/init/ghostty.ts
3
3
  /** The `command = zsh -lic '…'` line that boots clabox for the box. */
4
4
  function buildCommand(opts) {
5
- return `command = zsh -lic '${`${opts.projectDir ? `cd ${opts.projectDir} && ` : ""}CLABOX_CONFIGS_DIR=${opts.configsDir} ${opts.claboxBin} -b ${opts.boxName}; exec zsh`}'`;
5
+ return `command = zsh -lic '${`${opts.projectDir ? `cd ${opts.projectDir} && ` : ""}${opts.configsDir ? `CLABOX_CONFIGS_DIR=${opts.configsDir} ` : ""}${opts.claboxBin} -b ${opts.boxName}; exec zsh`}'`;
6
6
  }
7
7
  /** Build the Ghostty config text for an app box. */
8
8
  function buildGhosttyConfig(opts) {
@@ -71,4 +71,4 @@ function bundleId(boxName, app) {
71
71
  //#endregion
72
72
  export { bundleId as a, buildLauncherSource as i, buildCommand as n, buildGhosttyConfig as r, appBundlePath as t };
73
73
 
74
- //# sourceMappingURL=ghostty-DcMEZ6Ey.js.map
74
+ //# sourceMappingURL=ghostty-Dw4QLyl-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ghostty-Dw4QLyl-.js","names":[],"sources":["../src/init/ghostty.ts"],"sourcesContent":["// Pure builders for the Ghostty-app artifacts emitted by `clabox init`.\n//\n// For a box that opts in via `app` (see AppConfig), `init` generates a Ghostty\n// config whose `command` launches `clabox -b <box>`, then clones Ghostty.app\n// with a tiny C launcher that bakes in `--config-file=<that config>`. Everything\n// here is text-only (no I/O) so it can be unit-tested without macOS; the actual\n// build lives in init/app.ts.\n\nimport path from 'node:path';\nimport type { AppConfig } from '../utils/config.js';\n\n/** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */\nexport interface GhosttyConfigOptions {\n app: AppConfig;\n /** The `-b` box name this config launches. */\n boxName: string;\n /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */\n projectDir: string | null;\n /**\n * Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd, or\n * null to omit it — when null, `-b` finds the box via the runtime default\n * (`~/.config/clabox/configs`) at launch time.\n */\n configsDir: string | null;\n /**\n * The `clabox` command baked into the launcher. Default is a bare `clabox`\n * resolved from PATH at launch time by the `zsh -lic` login shell (survives\n * package-manager moves, e.g. bun → npm/homebrew); pass an absolute path only\n * to pin a specific binary.\n */\n claboxBin: string;\n /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */\n baseGhosttyConfig?: string | null;\n}\n\n/** The `command = zsh -lic '…'` line that boots clabox for the box. */\nexport function buildCommand(opts: GhosttyConfigOptions): string {\n const cd = opts.projectDir ? `cd ${opts.projectDir} && ` : '';\n const env = opts.configsDir ? `CLABOX_CONFIGS_DIR=${opts.configsDir} ` : '';\n const inner = `${cd}${env}${opts.claboxBin} -b ${opts.boxName}; exec zsh`;\n // Login + interactive zsh so the GUI-launched app inherits the user's PATH\n // (/etc/zprofile→path_helper for Homebrew, ~/.zshrc for fnm/nvm/volta). A bare\n // `bash -c` gets only launchd's minimal PATH and can't find `node` (clabox's\n // shebang is `#!/usr/bin/env node`).\n return `command = zsh -lic '${inner}'`;\n}\n\n/** Build the Ghostty config text for an app box. */\nexport function buildGhosttyConfig(opts: GhosttyConfigOptions): string {\n const { app } = opts;\n const lines: string[] = [\n '# Generated by `clabox init` — do not edit; rerun it after changing the box config.',\n ];\n if (opts.baseGhosttyConfig) lines.push(`config-file = ${opts.baseGhosttyConfig}`);\n lines.push('', `title = \"${app.title ?? app.name}\"`);\n if (app.macosIcon) lines.push(`macos-icon = ${app.macosIcon}`);\n if (app.ghostty && Object.keys(app.ghostty).length > 0) {\n lines.push('');\n for (const [key, value] of Object.entries(app.ghostty)) lines.push(`${key} = ${value}`);\n }\n lines.push('', buildCommand(opts));\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Escape a string for embedding as a C double-quoted literal. */\nfunction cEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Build the C launcher source. It finds itself, locates `ghostty.real` next to\n * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone\n * always boots with its own config regardless of how it's launched.\n */\nexport function buildLauncherSource(configPath: string): string {\n return `// Generated by clabox init. Launches ghostty.real with a baked config.\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <libgen.h>\n#include <mach-o/dyld.h>\n\nstatic const char *CONFIG_PATH = \"${cEscape(configPath)}\";\n\nint main(int argc, char *argv[]) {\n char path[4096];\n uint32_t size = sizeof(path);\n _NSGetExecutablePath(path, &size);\n\n char *dir = dirname(path);\n char real_path[4096];\n snprintf(real_path, sizeof(real_path), \"%s/ghostty.real\", dir);\n\n char config_arg[4096];\n snprintf(config_arg, sizeof(config_arg), \"--config-file=%s\", CONFIG_PATH);\n\n char **new_argv = malloc(sizeof(char *) * (argc + 2));\n new_argv[0] = real_path;\n new_argv[1] = config_arg;\n for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i];\n new_argv[argc + 1] = NULL;\n\n execv(real_path, new_argv);\n return 1;\n}\n`;\n}\n\n/** Absolute path to the built `.app` bundle. */\nexport function appBundlePath(appsDir: string, app: AppConfig): string {\n return path.join(appsDir, `${app.name}.app`);\n}\n\n/** Bundle identifier for the clone (explicit, or derived from the box name). */\nexport function bundleId(boxName: string, app: AppConfig): string {\n return app.bundleId ?? `com.ghostty.custom.${boxName.replace(/-/g, '.')}`;\n}\n"],"mappings":";;;AAoCA,SAAgB,aAAa,MAAoC;CAQ/D,OAAO,uBAAuB,GAPnB,KAAK,aAAa,MAAM,KAAK,WAAW,QAAQ,KAC/C,KAAK,aAAa,sBAAsB,KAAK,WAAW,KAAK,KAC7C,KAAK,UAAU,MAAM,KAAK,QAAQ,YAK1B;AACtC;;AAGA,SAAgB,mBAAmB,MAAoC;CACrE,MAAM,EAAE,QAAQ;CAChB,MAAM,QAAkB,CACtB,qFACF;CACA,IAAI,KAAK,mBAAmB,MAAM,KAAK,iBAAiB,KAAK,mBAAmB;CAChF,MAAM,KAAK,IAAI,YAAY,IAAI,SAAS,IAAI,KAAK,EAAE;CACnD,IAAI,IAAI,WAAW,MAAM,KAAK,gBAAgB,IAAI,WAAW;CAC7D,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC,SAAS,GAAG;EACtD,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO;CACxF;CACA,MAAM,KAAK,IAAI,aAAa,IAAI,CAAC;CACjC,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B;;AAGA,SAAS,QAAQ,GAAmB;CAClC,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAK;AACrD;;;;;;AAOA,SAAgB,oBAAoB,YAA4B;CAC9D,OAAO;;;;;;;oCAO2B,QAAQ,UAAU,EAAE;;;;;;;;;;;;;;;;;;;;;;;;AAwBxD;;AAGA,SAAgB,cAAc,SAAiB,KAAwB;CACrE,OAAO,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,KAAK;AAC7C;;AAGA,SAAgB,SAAS,SAAiB,KAAwB;CAChE,OAAO,IAAI,YAAY,sBAAsB,QAAQ,QAAQ,MAAM,GAAG;AACxE"}
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "./aliases-DXyz-ufw.js";
1
+ import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "./aliases-BNdrOn9E.js";
2
2
  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
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";
4
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "./ghostty-DFwFh4aL.js";
5
5
  import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "./raycast-fc5U1x7E.js";
6
6
  import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "./scaffold-CbsKxlL0.js";
7
7
  import { i as buildBoxExtras, n as ExtraFile, r as boxSlug, t as BoxExtras } from "./extras-B_dzBrCF.js";
package/lib/index.js CHANGED
@@ -1,10 +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
2
  import { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
3
- import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-DKGcMHHe.js";
4
- import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
5
- import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
3
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-KGjds7dK.js";
4
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
5
+ import { n as canBuildApps, t as buildApp } from "./app-ChnKbgkD.js";
6
6
  import { n as raycastIcon, t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
7
- import { n as runInit, t as discoverProfiles } from "./scaffold-CDKyY8gn.js";
7
+ import { n as runInit, t as discoverProfiles } from "./scaffold-C1tqCL_8.js";
8
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";
9
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-GaW5zEbS.js";
9
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-yUsOaKFx.js";
10
10
  export { HOME, aliasName, appBundlePath, boxSlug, buildAliasFiles, buildApp, buildBoxExtras, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
@@ -1,2 +1,2 @@
1
- import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "../aliases-DXyz-ufw.js";
1
+ import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "../aliases-BNdrOn9E.js";
2
2
  export { AliasPaths, InitFile, aliasName, buildAliasFiles, buildIndex, buildWrapper };
@@ -1,2 +1,2 @@
1
- import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "../aliases-DKGcMHHe.js";
1
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "../aliases-KGjds7dK.js";
2
2
  export { aliasName, buildAliasFiles, buildIndex, buildWrapper };
package/lib/init/app.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as canBuildApps, r as installIcon, t as buildApp } from "../app-CQmESEdh.js";
1
+ import { n as canBuildApps, r as installIcon, t as buildApp } from "../app-ChnKbgkD.js";
2
2
  export { buildApp, canBuildApps, installIcon };
@@ -1,2 +1,2 @@
1
- import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-DIe4yNGZ.js";
1
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-DFwFh4aL.js";
2
2
  export { GhosttyConfigOptions, appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -1,2 +1,2 @@
1
- import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "../ghostty-DcMEZ6Ey.js";
1
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "../ghostty-Dw4QLyl-.js";
2
2
  export { appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -1,2 +1,2 @@
1
- import { n as runInit, t as discoverProfiles } from "../scaffold-CDKyY8gn.js";
1
+ import { n as runInit, t as discoverProfiles } from "../scaffold-C1tqCL_8.js";
2
2
  export { discoverProfiles, runInit };
@@ -2,8 +2,8 @@ import { i as expandHome, t as HOME } from "./config-BQ44iVWT.js";
2
2
  import { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
3
3
  import { n as detectPackagePaths, t as buildProfile } from "./profile-DM6NAgb-.js";
4
4
  import path from "node:path";
5
- import { execFileSync, spawnSync } from "node:child_process";
6
5
  import fs from "node:fs";
6
+ import { execFileSync, spawnSync } from "node:child_process";
7
7
  import crypto from "node:crypto";
8
8
  //#region src/sandbox/run.ts
9
9
  const TMPDIR = (process.env.TMPDIR || "/tmp").replace(/\/$/, "");
@@ -129,4 +129,4 @@ function runClaude(config, claudeArgs, { configFile } = {}) {
129
129
  //#endregion
130
130
  export { runClaude as a, resolveProjectDir as i, generateProfile as n, writeExtraFiles as o, profilePath as r, buildEnvArgs as t };
131
131
 
132
- //# sourceMappingURL=run-GaW5zEbS.js.map
132
+ //# sourceMappingURL=run-yUsOaKFx.js.map
@@ -1 +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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"}
1
+ {"version":3,"file":"run-yUsOaKFx.js","names":[],"sources":["../src/sandbox/run.ts"],"sourcesContent":["// Profile materialization + launching `claude` under sandbox-exec.\n\nimport { execFileSync, spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { type Config, expandHome, HOME } from '../utils/config.js';\nimport { boxSlug, buildBoxExtras, type ExtraFile } from './extras.js';\nimport { buildProfile, detectPackagePaths } from './profile.js';\n\nconst TMPDIR = (process.env.TMPDIR || '/tmp').replace(/\\/$/, '');\n\n/** Deterministic per-project profile path under TMPDIR. */\nexport function profilePath(projectDir: string = process.cwd()): string {\n const hash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);\n return path.join(TMPDIR, `clabox-${path.basename(projectDir)}-${hash}.sb`);\n}\n\n/**\n * Effective project dir: `config.cwd` (with `~` expanded, resolved to an\n * absolute path so SBPL `subpath` rules stay valid) if set, else the shell CWD.\n */\nexport function resolveProjectDir(config: Config): string {\n return config.cwd ? path.resolve(expandHome(config.cwd)) : process.cwd();\n}\n\nfunction which(bin: string): string | null {\n try {\n return (\n execFileSync('command', ['-v', bin], { shell: '/bin/sh', encoding: 'utf8' }).trim() || null\n );\n } catch {\n return null;\n }\n}\n\nfunction requireSandboxExec(): void {\n if (!which('sandbox-exec')) {\n throw new Error('sandbox-exec not found. This tool requires macOS with sandbox-exec.');\n }\n}\n\nfunction resolveClaudeBin(config: Config): string {\n const candidate = config.claudeBin || which('claude') || path.join(HOME, '.local/bin/claude');\n if (!candidate || !fs.existsSync(candidate)) {\n throw new Error(`claude not found at '${candidate}'`);\n }\n return candidate;\n}\n\n/** Generate the profile file for the current project, return its path. */\nexport function generateProfile(\n config: Config,\n projectDir: string = resolveProjectDir(config),\n): string {\n requireSandboxExec();\n const file = profilePath(projectDir);\n const text = buildProfile(config, { projectDir, detectedPaths: detectPackagePaths() });\n fs.writeFileSync(file, text);\n return file;\n}\n\n/** Build the `env KEY=VALUE …` argument list forced onto the sandboxed claude. */\nexport function buildEnvArgs(config: Config): string[] {\n const sshDir = expandHome(config.bot.sshDir);\n const botKey = path.join(sshDir, 'id_ed25519');\n const botCfg = path.join(sshDir, 'config');\n const args = [\n `PATH=${path.join(HOME, '.local/bin')}:${process.env.PATH || ''}`,\n `CLAUDE_CONFIG_DIR=${expandHome(config.configDir)}`,\n `GIT_AUTHOR_NAME=${config.bot.name}`,\n `GIT_AUTHOR_EMAIL=${config.bot.email}`,\n `GIT_COMMITTER_NAME=${config.bot.name}`,\n `GIT_COMMITTER_EMAIL=${config.bot.email}`,\n 'GIT_CONFIG_COUNT=2',\n 'GIT_CONFIG_KEY_0=commit.gpgsign',\n 'GIT_CONFIG_VALUE_0=false',\n 'GIT_CONFIG_KEY_1=tag.gpgsign',\n 'GIT_CONFIG_VALUE_1=false',\n ];\n // Pin git ssh to the bot key only when it actually exists, so the sandbox\n // stays usable without a dedicated bot key configured.\n if (fs.existsSync(botKey)) {\n args.push(\n `GIT_SSH_COMMAND=ssh -F ${botCfg} -i ${botKey} -o IdentitiesOnly=yes -o IdentityAgent=none`,\n );\n }\n // User-declared extras go last so they win over the built-in vars above\n // (duplicate keys: `env` keeps the last assignment).\n for (const [key, value] of Object.entries(config.env ?? {})) {\n args.push(`${key}=${value}`);\n }\n return args;\n}\n\n/**\n * Write the per-box extra files (mkdir -p their dirs) `0600`. They live under\n * configDir and can carry secrets (e.g. an MCP auth token in a URL/header), so\n * they're kept out of argv (vs. an inline `--mcp-config '<json>'`) AND off the\n * world-readable bit on disk. Returns the paths.\n */\nexport function writeExtraFiles(files: ExtraFile[]): string[] {\n for (const f of files) {\n fs.mkdirSync(path.dirname(f.path), { recursive: true });\n fs.writeFileSync(f.path, f.content, { mode: 0o600 });\n fs.chmodSync(f.path, 0o600); // enforce even if the file pre-existed\n }\n return files.map((f) => f.path);\n}\n\n/** Options accepted by {@link runClaude}. */\nexport interface RunOptions {\n configFile?: string | null;\n}\n\n/** Generate the profile and exec claude under sandbox-exec. Returns exit code. */\nexport function runClaude(\n config: Config,\n claudeArgs: string[],\n { configFile }: RunOptions = {},\n): number {\n const projectDir = resolveProjectDir(config);\n const claudeBin = resolveClaudeBin(config);\n const profileFile = generateProfile(config, projectDir);\n\n // Compile the box's declarative mcp / systemPrompt into claude args, and\n // materialize the files they reference (under configDir, which is sandbox-RW).\n const extras = buildBoxExtras(config, boxSlug(configFile, projectDir));\n const extraFiles = writeExtraFiles(extras.files);\n\n if (process.env.CLABOX_DEBUG) {\n console.error(`→ Running Claude Code sandboxed in: ${projectDir}`);\n console.error(`→ Profile: ${profileFile}`);\n console.error(`→ Config: ${expandHome(config.configDir)}`);\n if (configFile) console.error(`→ Config file: ${configFile}`);\n for (const f of extraFiles) console.error(`→ MCP: ${f}`);\n }\n\n // Terminal title = cwd (with ~ for $HOME), matching the bash version.\n const title = projectDir.startsWith(HOME) ? `~${projectDir.slice(HOME.length)}` : projectDir;\n process.stdout.write(`\\x1b]0;${title}\\x07`);\n\n const envArgs = buildEnvArgs(config);\n const defaultArgs = Array.isArray(config.claudeArgs) ? config.claudeArgs : [];\n const inner = [\n 'sandbox-exec',\n '-f',\n profileFile,\n 'env',\n ...envArgs,\n claudeBin,\n ...defaultArgs,\n ...extras.claudeArgs,\n ...claudeArgs,\n ];\n\n // `ulimit` is a shell builtin; run the whole thing under sh so we can set it.\n // `exec \"$@\"` keeps argv intact without re-quoting (args start after $0=sh).\n const ulimit = config.ulimitProcs > 0 ? `ulimit -u ${config.ulimitProcs} 2>/dev/null; ` : '';\n const res = spawnSync('/bin/sh', ['-c', `${ulimit}exec \"$@\"`, 'sh', ...inner], {\n cwd: projectDir,\n stdio: 'inherit',\n });\n if (res.error) throw res.error;\n if (res.signal) return 1;\n return res.status ?? 0;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as runClaude, i as resolveProjectDir, n as generateProfile, o as writeExtraFiles, r as profilePath, t as buildEnvArgs } from "../run-GaW5zEbS.js";
1
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, o as writeExtraFiles, r as profilePath, t as buildEnvArgs } from "../run-yUsOaKFx.js";
2
2
  export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
@@ -1,11 +1,10 @@
1
1
  import { i as expandHome, l as resolveBox, o as listBoxes, s as loadConfig } from "./config-BQ44iVWT.js";
2
2
  import { n as buildBoxExtras } from "./extras-B2ss2Cgh.js";
3
- import { n as buildAliasFiles } from "./aliases-DKGcMHHe.js";
4
- import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-DcMEZ6Ey.js";
5
- import { n as canBuildApps, t as buildApp } from "./app-CQmESEdh.js";
3
+ import { n as buildAliasFiles } from "./aliases-KGjds7dK.js";
4
+ import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
5
+ import { n as canBuildApps, t as buildApp } from "./app-ChnKbgkD.js";
6
6
  import { t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
7
7
  import path from "node:path";
8
- import { execFileSync } from "node:child_process";
9
8
  import fs from "node:fs";
10
9
  //#region src/init/scaffold.ts
11
10
  /**
@@ -29,17 +28,29 @@ function pruneByExt(dir, ext) {
29
28
  if (!fs.existsSync(dir)) return;
30
29
  for (const f of fs.readdirSync(dir)) if (f.endsWith(ext)) fs.rmSync(path.join(dir, f), { force: true });
31
30
  }
32
- /** Locate the `clabox` binary to bake into the generated Ghostty `command`. */
31
+ /**
32
+ * The `clabox` command baked into the generated Ghostty `command`. Default is a
33
+ * bare `clabox` resolved from PATH at launch time by the `zsh -lic` login shell
34
+ * — this survives package-manager moves (e.g. bun → npm/homebrew) that change
35
+ * the binary's absolute path. Only an explicit `appBuilder.claboxBin` pins an
36
+ * absolute path.
37
+ */
33
38
  function resolveClaboxBin(configured) {
34
- if (configured) return expandHome(configured);
39
+ return configured ? expandHome(configured) : "clabox";
40
+ }
41
+ /**
42
+ * The `CLABOX_CONFIGS_DIR` value to bake into generated commands, or null to
43
+ * omit it. Omit when `<dir>` resolves (realpath, symlinks included) to the
44
+ * runtime default `~/.config/clabox/configs` — then `-b` finds the box via that
45
+ * default at launch time, so no path needs baking (and it stays correct if the
46
+ * dir later moves behind the same symlink). Otherwise bake the absolute path so
47
+ * `-b` resolves the box regardless of the launcher's cwd.
48
+ */
49
+ function bakeConfigsDir(dir) {
35
50
  try {
36
- const found = execFileSync("command", ["-v", "clabox"], {
37
- shell: "/bin/sh",
38
- encoding: "utf8"
39
- }).trim();
40
- if (found) return found;
51
+ if (fs.realpathSync(dir) === fs.realpathSync(expandHome("~/.config/clabox/configs"))) return null;
41
52
  } catch {}
42
- return "clabox";
53
+ return dir;
43
54
  }
44
55
  /**
45
56
  * Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the
@@ -90,7 +101,7 @@ async function buildAppArtifacts(base, configsDir, profiles, only, result) {
90
101
  app,
91
102
  boxName: name,
92
103
  projectDir,
93
- configsDir,
104
+ configsDir: bakeConfigsDir(configsDir),
94
105
  claboxBin: resolveClaboxBin(config.appBuilder.claboxBin),
95
106
  baseGhosttyConfig: baseGhostty
96
107
  }));
@@ -134,7 +145,7 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
134
145
  fs.mkdirSync(scriptsDir, { recursive: true });
135
146
  pruneGenerated(scriptsDir);
136
147
  const files = buildAliasFiles(profiles, {
137
- configsDir,
148
+ configsDir: bakeConfigsDir(configsDir),
138
149
  scriptsDir
139
150
  });
140
151
  for (const f of files) {
@@ -159,4 +170,4 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
159
170
  //#endregion
160
171
  export { runInit as n, discoverProfiles as t };
161
172
 
162
- //# sourceMappingURL=scaffold-CDKyY8gn.js.map
173
+ //# sourceMappingURL=scaffold-C1tqCL_8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-C1tqCL_8.js","names":[],"sources":["../src/init/scaffold.ts"],"sourcesContent":["// I/O for `clabox init`: discover the box configs in `<base>/configs/`,\n// (re)write the shell aliases into `<base>/scripts/`, and — for boxes that opt\n// in via `app` — generate a Ghostty config in `<base>/ghostty/` and build a\n// standalone `<appsDir>/<name>.app` (see init/app.ts).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { buildBoxExtras } from '../sandbox/extras.js';\nimport { type Config, expandHome, listBoxes, loadConfig, resolveBox } from '../utils/config.js';\nimport { buildAliasFiles } from './aliases.js';\nimport { buildApp, canBuildApps } from './app.js';\nimport { appBundlePath, buildGhosttyConfig } from './ghostty.js';\nimport { buildRaycastCommand } from './raycast.js';\n\n/**\n * Box names (sorted, de-duplicated) discovered in `<configsDir>` via the same\n * rules as `-b`: both `<name>.mjs` and `<name>.config.mjs`, `_`-prefixed\n * shared partials (e.g. `_presets.mjs`) skipped.\n */\nexport function discoverProfiles(configsDir: string): string[] {\n if (!fs.existsSync(configsDir)) {\n throw new Error(`clabox init: configs dir not found: ${configsDir}`);\n }\n const names = listBoxes(configsDir);\n if (names.length === 0) {\n throw new Error(`clabox init: no box configs (*.mjs) in ${configsDir}`);\n }\n return names;\n}\n\n/** Remove previously generated artifacts (`index.sh`, `clabox-*.sh`). */\nfunction pruneGenerated(scriptsDir: string): void {\n if (!fs.existsSync(scriptsDir)) return;\n for (const f of fs.readdirSync(scriptsDir)) {\n if (f === 'index.sh' || (f.startsWith('clabox-') && f.endsWith('.sh'))) {\n fs.rmSync(path.join(scriptsDir, f), { force: true });\n }\n }\n}\n\n/** Remove files in `dir` whose name ends with `ext` (a no-op if dir is absent). */\nfunction pruneByExt(dir: string, ext: string): void {\n if (!fs.existsSync(dir)) return;\n for (const f of fs.readdirSync(dir)) {\n if (f.endsWith(ext)) fs.rmSync(path.join(dir, f), { force: true });\n }\n}\n\n/**\n * The `clabox` command baked into the generated Ghostty `command`. Default is a\n * bare `clabox` resolved from PATH at launch time by the `zsh -lic` login shell\n * — this survives package-manager moves (e.g. bun → npm/homebrew) that change\n * the binary's absolute path. Only an explicit `appBuilder.claboxBin` pins an\n * absolute path.\n */\nfunction resolveClaboxBin(configured: string | null): string {\n return configured ? expandHome(configured) : 'clabox';\n}\n\n/**\n * The `CLABOX_CONFIGS_DIR` value to bake into generated commands, or null to\n * omit it. Omit when `<dir>` resolves (realpath, symlinks included) to the\n * runtime default `~/.config/clabox/configs` — then `-b` finds the box via that\n * default at launch time, so no path needs baking (and it stays correct if the\n * dir later moves behind the same symlink). Otherwise bake the absolute path so\n * `-b` resolves the box regardless of the launcher's cwd.\n */\nfunction bakeConfigsDir(dir: string): string | null {\n try {\n // The launched process has no CLABOX_CONFIGS_DIR set (we omit it), so its\n // runtime default is always ~/.config/clabox/configs — compare against that.\n if (fs.realpathSync(dir) === fs.realpathSync(expandHome('~/.config/clabox/configs'))) {\n return null;\n }\n } catch {\n // default dir missing/unresolvable → bake the explicit path.\n }\n return dir;\n}\n\n/** Options for {@link runInit}. */\nexport interface InitOptions {\n /** Base dir holding `configs/` and `scripts/`. Default: `<cwd>/__`. */\n baseDir?: string;\n /** Build the Ghostty apps for `app` boxes. Default: true. */\n buildApps?: boolean;\n /** Limit app building to a single box (by box name or app display name). */\n only?: string | null;\n}\n\n/** A standalone Ghostty app built by `clabox init`. */\nexport interface BuiltApp {\n box: string;\n appPath: string;\n signed: 'identity' | 'adhoc';\n}\n\n/** Result of {@link runInit}. */\nexport interface InitResult {\n profiles: string[];\n scriptsDir: string;\n indexFile: string;\n written: string[];\n /** Apps successfully built. */\n apps: BuiltApp[];\n /** Generated Ghostty config files. */\n ghosttyConfigs: string[];\n /** Generated Raycast command scripts. */\n raycastCommands: string[];\n /** Compiled per-box MCP json files (from `config.mcp`). */\n extraFiles: string[];\n /** Non-fatal issues (e.g. app build skipped/failed). */\n warnings: string[];\n}\n\n/**\n * Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the\n * files exist ahead of the first run (the same files `run` writes; slug = box\n * name). Best-effort per box: a config that fails to load becomes a warning.\n */\nasync function materializeExtras(\n configsDir: string,\n profiles: string[],\n result: InitResult,\n): Promise<void> {\n for (const name of profiles) {\n try {\n const { config } = await loadConfig(resolveBox(name, configsDir));\n for (const f of buildBoxExtras(config, name).files) {\n fs.mkdirSync(path.dirname(f.path), { recursive: true });\n // 0600 — the MCP json can carry an auth token (see run.ts#writeExtraFiles).\n fs.writeFileSync(f.path, f.content, { mode: 0o600 });\n fs.chmodSync(f.path, 0o600);\n result.extraFiles.push(f.path);\n }\n } catch (e) {\n result.warnings.push(`${name}: extras not materialized — ${(e as Error).message}`);\n }\n }\n}\n\n/** Generate the Ghostty configs and build the apps for every `app` box. */\nasync function buildAppArtifacts(\n base: string,\n configsDir: string,\n profiles: string[],\n only: string | null,\n result: InitResult,\n): Promise<void> {\n // Load each box config; keep the ones that opt into an app (and match `only`).\n const appBoxes: { name: string; config: Config }[] = [];\n for (const name of profiles) {\n const { config } = await loadConfig(resolveBox(name, configsDir));\n if (!config.app) continue;\n if (only && name !== only && config.app.name !== only) continue;\n appBoxes.push({ name, config });\n }\n if (appBoxes.length === 0) return;\n\n const ghosttyDir = path.join(base, 'ghostty');\n const raycastDir = path.join(base, 'raycast');\n fs.mkdirSync(ghosttyDir, { recursive: true });\n fs.mkdirSync(raycastDir, { recursive: true });\n // Only prune on a full run — with `only` we'd orphan other apps' artifacts.\n if (!only) {\n pruneByExt(ghosttyDir, '.config');\n pruneByExt(raycastDir, '.sh');\n }\n\n for (const { name, config } of appBoxes) {\n const app = config.app;\n if (!app) continue; // narrowed above; keeps the type checker happy\n const configPath = path.join(ghosttyDir, `${name}.config`);\n const projectDir = config.cwd ? path.resolve(expandHome(config.cwd)) : null;\n const baseGhostty = config.appBuilder.baseGhosttyConfig\n ? expandHome(config.appBuilder.baseGhosttyConfig)\n : null;\n fs.writeFileSync(\n configPath,\n buildGhosttyConfig({\n app,\n boxName: name,\n projectDir,\n configsDir: bakeConfigsDir(configsDir),\n claboxBin: resolveClaboxBin(config.appBuilder.claboxBin),\n baseGhosttyConfig: baseGhostty,\n }),\n );\n result.ghosttyConfigs.push(configPath);\n\n // Raycast command that opens the (to be) built bundle.\n const appPath = appBundlePath(expandHome(config.appBuilder.appsDir), app);\n const raycastPath = path.join(raycastDir, `${name}.sh`);\n fs.writeFileSync(raycastPath, buildRaycastCommand({ app, appPath }));\n fs.chmodSync(raycastPath, 0o755);\n result.raycastCommands.push(raycastPath);\n\n const check = canBuildApps(config.appBuilder);\n if (!check.ok) {\n result.warnings.push(`${name}: app not built (${check.reason})`);\n continue;\n }\n try {\n const built = buildApp({ boxName: name, app, builder: config.appBuilder, configPath });\n result.apps.push({ box: name, appPath: built.appPath, signed: built.signed });\n } catch (e) {\n result.warnings.push(`${name}: app build failed — ${(e as Error).message}`);\n }\n }\n}\n\n/** Scan the configs dir, (re)write the alias scripts, and build `app` apps. */\nexport async function runInit({\n baseDir,\n buildApps = true,\n only = null,\n}: InitOptions = {}): Promise<InitResult> {\n const base = path.resolve(baseDir ?? path.join(process.cwd(), '__'));\n const configsDir = path.join(base, 'configs');\n const scriptsDir = path.join(base, 'scripts');\n const profiles = discoverProfiles(configsDir);\n\n fs.mkdirSync(scriptsDir, { recursive: true });\n pruneGenerated(scriptsDir);\n\n const files = buildAliasFiles(profiles, { configsDir: bakeConfigsDir(configsDir), scriptsDir });\n for (const f of files) {\n fs.writeFileSync(f.path, f.content);\n if (f.executable) fs.chmodSync(f.path, 0o755);\n }\n\n const result: InitResult = {\n profiles,\n scriptsDir,\n indexFile: path.join(scriptsDir, 'index.sh'),\n written: files.map((f) => f.path),\n apps: [],\n ghosttyConfigs: [],\n raycastCommands: [],\n extraFiles: [],\n warnings: [],\n };\n\n await materializeExtras(configsDir, profiles, result);\n\n if (buildApps) {\n await buildAppArtifacts(base, configsDir, profiles, only, result);\n }\n return result;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clabox",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Run Claude Code in a sandbox for super-safe YOLO mode",
5
5
  "type": "module",
6
6
  "author": "Igor Suvorov",
@@ -1 +0,0 @@
1
- {"version":3,"file":"aliases-DKGcMHHe.js","names":[],"sources":["../src/init/aliases.ts"],"sourcesContent":["// Pure builder for the shell aliases emitted by `clabox init`.\n//\n// For each box config (`<name>.mjs` / `<name>.config.mjs`) it produces one\n// command `clabox-<name>` that runs that box. Whether it runs yolo or asks for\n// confirmation is decided by the box's own preset (its `claudeArgs`), not here.\n// Plus an `index.sh` defining them all as shell functions to `source`.\n\nimport path from 'node:path';\n\n/** A file to be written by `clabox init`. */\nexport interface InitFile {\n /** Absolute path to write. */\n path: string;\n /** File contents. */\n content: string;\n /** Whether to `chmod +x` after writing. */\n executable: boolean;\n}\n\n/** Absolute locations for the generated artifacts. */\nexport interface AliasPaths {\n /** Dir holding the box config files. */\n configsDir: string;\n /** Dir to emit `index.sh` and the per-box wrappers into. */\n scriptsDir: string;\n}\n\n/** The `clabox-<name>` command name for a box. */\nexport function aliasName(profile: string): string {\n return `clabox-${profile}`;\n}\n\n/** Build the source-able `index.sh` defining one function per box. */\nexport function buildIndex(profiles: string[], { configsDir, scriptsDir }: AliasPaths): string {\n const indexPath = path.join(scriptsDir, 'index.sh');\n const usage = profiles.map((p) => `# ${aliasName(p)}`);\n const defs = profiles.map((p) => `${aliasName(p)}() { _clabox_run ${p} \"$@\"; }`);\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init` — do not edit; rerun it after changing the configs dir.',\n '#',\n '# Source this from ~/.zshrc or ~/.bashrc:',\n `# source ${indexPath}`,\n '#',\n \"# Commands (run from any cwd). yolo vs. safe is set by each box's preset:\",\n ...usage,\n '',\n '_clabox_run() {',\n // Point `-b` at this dir so resolveBox picks the right .mjs/.config.mjs file.\n ` CLABOX_CONFIGS_DIR=\"${configsDir}\" clabox -b \"$1\" \"\\${@:2}\"`,\n '}',\n '',\n ...defs,\n ].join('\\n')}\\n`;\n}\n\n/** Build a standalone wrapper script that sources `index.sh` and calls one fn. */\nexport function buildWrapper(fnName: string, indexPath: string): string {\n return `${[\n '#!/usr/bin/env bash',\n '# Generated by `clabox init`.',\n 'set -euo pipefail',\n `source \"${indexPath}\"`,\n `${fnName} \"$@\"`,\n ].join('\\n')}\\n`;\n}\n\n/** All files `clabox init` writes: the `index.sh` plus a wrapper per box. */\nexport function buildAliasFiles(profiles: string[], paths: AliasPaths): InitFile[] {\n const indexPath = path.join(paths.scriptsDir, 'index.sh');\n const files: InitFile[] = [\n { path: indexPath, content: buildIndex(profiles, paths), executable: true },\n ];\n for (const p of profiles) {\n const fn = aliasName(p);\n files.push({\n path: path.join(paths.scriptsDir, `${fn}.sh`),\n content: buildWrapper(fn, indexPath),\n executable: true,\n });\n }\n return files;\n}\n"],"mappings":";;;AA4BA,SAAgB,UAAU,SAAyB;CACjD,OAAO,UAAU;AACnB;;AAGA,SAAgB,WAAW,UAAoB,EAAE,YAAY,cAAkC;CAC7F,MAAM,YAAY,KAAK,KAAK,YAAY,UAAU;CAClD,MAAM,QAAQ,SAAS,KAAK,MAAM,OAAO,UAAU,CAAC,GAAG;CACvD,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,UAAU,CAAC,EAAE,mBAAmB,EAAE,SAAS;CAC/E,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACA,GAAG;EACH;EACA;EAEA,yBAAyB,WAAW;EACpC;EACA;EACA,GAAG;CACL,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,aAAa,QAAgB,WAA2B;CACtE,OAAO,GAAG;EACR;EACA;EACA;EACA,WAAW,UAAU;EACrB,GAAG,OAAO;CACZ,CAAC,CAAC,KAAK,IAAI,EAAE;AACf;;AAGA,SAAgB,gBAAgB,UAAoB,OAA+B;CACjF,MAAM,YAAY,KAAK,KAAK,MAAM,YAAY,UAAU;CACxD,MAAM,QAAoB,CACxB;EAAE,MAAM;EAAW,SAAS,WAAW,UAAU,KAAK;EAAG,YAAY;CAAK,CAC5E;CACA,KAAK,MAAM,KAAK,UAAU;EACxB,MAAM,KAAK,UAAU,CAAC;EACtB,MAAM,KAAK;GACT,MAAM,KAAK,KAAK,MAAM,YAAY,GAAG,GAAG,IAAI;GAC5C,SAAS,aAAa,IAAI,SAAS;GACnC,YAAY;EACd,CAAC;CACH;CACA,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ghostty-DcMEZ6Ey.js","names":[],"sources":["../src/init/ghostty.ts"],"sourcesContent":["// Pure builders for the Ghostty-app artifacts emitted by `clabox init`.\n//\n// For a box that opts in via `app` (see AppConfig), `init` generates a Ghostty\n// config whose `command` launches `clabox -b <box>`, then clones Ghostty.app\n// with a tiny C launcher that bakes in `--config-file=<that config>`. Everything\n// here is text-only (no I/O) so it can be unit-tested without macOS; the actual\n// build lives in init/app.ts.\n\nimport path from 'node:path';\nimport type { AppConfig } from '../utils/config.js';\n\n/** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */\nexport interface GhosttyConfigOptions {\n app: AppConfig;\n /** The `-b` box name this config launches. */\n boxName: string;\n /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */\n projectDir: string | null;\n /** Absolute `CLABOX_CONFIGS_DIR` so `-b` resolves the box from any cwd. */\n configsDir: string;\n /** Absolute path to the `clabox` binary. */\n claboxBin: string;\n /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */\n baseGhosttyConfig?: string | null;\n}\n\n/** The `command = zsh -lic '…'` line that boots clabox for the box. */\nexport function buildCommand(opts: GhosttyConfigOptions): string {\n const cd = opts.projectDir ? `cd ${opts.projectDir} && ` : '';\n const inner = `${cd}CLABOX_CONFIGS_DIR=${opts.configsDir} ${opts.claboxBin} -b ${opts.boxName}; exec zsh`;\n // Login + interactive zsh so the GUI-launched app inherits the user's PATH\n // (/etc/zprofile→path_helper for Homebrew, ~/.zshrc for fnm/nvm/volta). A bare\n // `bash -c` gets only launchd's minimal PATH and can't find `node` (clabox's\n // shebang is `#!/usr/bin/env node`).\n return `command = zsh -lic '${inner}'`;\n}\n\n/** Build the Ghostty config text for an app box. */\nexport function buildGhosttyConfig(opts: GhosttyConfigOptions): string {\n const { app } = opts;\n const lines: string[] = [\n '# Generated by `clabox init` — do not edit; rerun it after changing the box config.',\n ];\n if (opts.baseGhosttyConfig) lines.push(`config-file = ${opts.baseGhosttyConfig}`);\n lines.push('', `title = \"${app.title ?? app.name}\"`);\n if (app.macosIcon) lines.push(`macos-icon = ${app.macosIcon}`);\n if (app.ghostty && Object.keys(app.ghostty).length > 0) {\n lines.push('');\n for (const [key, value] of Object.entries(app.ghostty)) lines.push(`${key} = ${value}`);\n }\n lines.push('', buildCommand(opts));\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Escape a string for embedding as a C double-quoted literal. */\nfunction cEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Build the C launcher source. It finds itself, locates `ghostty.real` next to\n * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone\n * always boots with its own config regardless of how it's launched.\n */\nexport function buildLauncherSource(configPath: string): string {\n return `// Generated by clabox init. Launches ghostty.real with a baked config.\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <libgen.h>\n#include <mach-o/dyld.h>\n\nstatic const char *CONFIG_PATH = \"${cEscape(configPath)}\";\n\nint main(int argc, char *argv[]) {\n char path[4096];\n uint32_t size = sizeof(path);\n _NSGetExecutablePath(path, &size);\n\n char *dir = dirname(path);\n char real_path[4096];\n snprintf(real_path, sizeof(real_path), \"%s/ghostty.real\", dir);\n\n char config_arg[4096];\n snprintf(config_arg, sizeof(config_arg), \"--config-file=%s\", CONFIG_PATH);\n\n char **new_argv = malloc(sizeof(char *) * (argc + 2));\n new_argv[0] = real_path;\n new_argv[1] = config_arg;\n for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i];\n new_argv[argc + 1] = NULL;\n\n execv(real_path, new_argv);\n return 1;\n}\n`;\n}\n\n/** Absolute path to the built `.app` bundle. */\nexport function appBundlePath(appsDir: string, app: AppConfig): string {\n return path.join(appsDir, `${app.name}.app`);\n}\n\n/** Bundle identifier for the clone (explicit, or derived from the box name). */\nexport function bundleId(boxName: string, app: AppConfig): string {\n return app.bundleId ?? `com.ghostty.custom.${boxName.replace(/-/g, '.')}`;\n}\n"],"mappings":";;;AA2BA,SAAgB,aAAa,MAAoC;CAO/D,OAAO,uBAAuB,GANnB,KAAK,aAAa,MAAM,KAAK,WAAW,QAAQ,GACvC,qBAAqB,KAAK,WAAW,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,YAK1D;AACtC;;AAGA,SAAgB,mBAAmB,MAAoC;CACrE,MAAM,EAAE,QAAQ;CAChB,MAAM,QAAkB,CACtB,qFACF;CACA,IAAI,KAAK,mBAAmB,MAAM,KAAK,iBAAiB,KAAK,mBAAmB;CAChF,MAAM,KAAK,IAAI,YAAY,IAAI,SAAS,IAAI,KAAK,EAAE;CACnD,IAAI,IAAI,WAAW,MAAM,KAAK,gBAAgB,IAAI,WAAW;CAC7D,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC,SAAS,GAAG;EACtD,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO;CACxF;CACA,MAAM,KAAK,IAAI,aAAa,IAAI,CAAC;CACjC,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC7B;;AAGA,SAAS,QAAQ,GAAmB;CAClC,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAK;AACrD;;;;;;AAOA,SAAgB,oBAAoB,YAA4B;CAC9D,OAAO;;;;;;;oCAO2B,QAAQ,UAAU,EAAE;;;;;;;;;;;;;;;;;;;;;;;;AAwBxD;;AAGA,SAAgB,cAAc,SAAiB,KAAwB;CACrE,OAAO,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,KAAK;AAC7C;;AAGA,SAAgB,SAAS,SAAiB,KAAwB;CAChE,OAAO,IAAI,YAAY,sBAAsB,QAAQ,QAAQ,MAAM,GAAG;AACxE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"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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"}