clabox 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +0 -1
  2. package/clabox.config.example.mjs +10 -4
  3. package/docs/guideline.md +25 -16
  4. package/lib/{app-ChnKbgkD.js → app-CuEM7S0i.js} +18 -8
  5. package/lib/app-CuEM7S0i.js.map +1 -0
  6. package/lib/{app-B1djcEnN.d.ts → app-MIaByu-I.d.ts} +2 -2
  7. package/lib/cli.js +17 -5
  8. package/lib/cli.js.map +1 -1
  9. package/lib/{config-jZAB2Zg8.d.ts → config-1vfbPBRi.d.ts} +46 -7
  10. package/lib/{config-BQ44iVWT.js → config-CY_Cf87P.js} +21 -4
  11. package/lib/config-CY_Cf87P.js.map +1 -0
  12. package/lib/extras-Bp4DpyZP.js +87 -0
  13. package/lib/extras-Bp4DpyZP.js.map +1 -0
  14. package/lib/{extras-B_dzBrCF.d.ts → extras-CITv2nRg.d.ts} +15 -6
  15. package/lib/{ghostty-DFwFh4aL.d.ts → ghostty-D8KUXOw7.d.ts} +2 -2
  16. package/lib/index.d.ts +10 -9
  17. package/lib/index.js +8 -7
  18. package/lib/info/info.d.ts +2 -0
  19. package/lib/info/info.js +2 -0
  20. package/lib/info-BRlaIckk.js +176 -0
  21. package/lib/info-BRlaIckk.js.map +1 -0
  22. package/lib/info-C0WvNVNS.d.ts +98 -0
  23. package/lib/init/app.d.ts +1 -1
  24. package/lib/init/app.js +1 -1
  25. package/lib/init/ghostty.d.ts +1 -1
  26. package/lib/init/raycast.d.ts +1 -1
  27. package/lib/init/scaffold.d.ts +2 -2
  28. package/lib/init/scaffold.js +2 -2
  29. package/lib/{profile-DM6NAgb-.js → profile-1oytMVlE.js} +25 -5
  30. package/lib/profile-1oytMVlE.js.map +1 -0
  31. package/lib/{profile-7CiMUPu9.d.ts → profile-Bzq0Y4PI.d.ts} +13 -3
  32. package/lib/{raycast-fc5U1x7E.d.ts → raycast-DuHEu0Vz.d.ts} +2 -2
  33. package/lib/{run-yUsOaKFx.js → run-9vQC2fCs.js} +10 -8
  34. package/lib/run-9vQC2fCs.js.map +1 -0
  35. package/lib/{run-BWGDNJiY.d.ts → run-gag6YjI9.d.ts} +10 -7
  36. package/lib/sandbox/extras.d.ts +1 -1
  37. package/lib/sandbox/extras.js +1 -1
  38. package/lib/sandbox/profile.d.ts +2 -2
  39. package/lib/sandbox/profile.js +2 -2
  40. package/lib/sandbox/run.d.ts +2 -2
  41. package/lib/sandbox/run.js +2 -2
  42. package/lib/{scaffold-CbsKxlL0.d.ts → scaffold-BiCJzELQ.d.ts} +10 -3
  43. package/lib/{scaffold-C1tqCL_8.js → scaffold-DiG5q28w.js} +18 -9
  44. package/lib/scaffold-DiG5q28w.js.map +1 -0
  45. package/lib/utils/config.d.ts +2 -2
  46. package/lib/utils/config.js +2 -2
  47. package/package.json +2 -1
  48. package/lib/app-ChnKbgkD.js.map +0 -1
  49. package/lib/config-BQ44iVWT.js.map +0 -1
  50. package/lib/extras-B2ss2Cgh.js +0 -46
  51. package/lib/extras-B2ss2Cgh.js.map +0 -1
  52. package/lib/profile-DM6NAgb-.js.map +0 -1
  53. package/lib/run-yUsOaKFx.js.map +0 -1
  54. package/lib/scaffold-C1tqCL_8.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { i as expandHome, t as HOME } from "./config-BQ44iVWT.js";
1
+ import { n as claboxHomeDir, s as expandHome, t as HOME } from "./config-CY_Cf87P.js";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  //#region src/sandbox/profile.ts
@@ -30,6 +30,25 @@ function detectPackagePaths() {
30
30
  return paths;
31
31
  }
32
32
  /**
33
+ * The clabox home as it physically resolves on disk, but ONLY when
34
+ * {@link claboxHomeDir} is a symlink (so the real path differs from the nominal
35
+ * `~/.config/clabox` one). Used to grant the resolved location in the profile —
36
+ * the macOS sandbox matches the symlink-resolved path, so a relocated clabox
37
+ * home (e.g. symlinked into a project repo) would otherwise have its box configs
38
+ * + compiled `--mcp-config` / `--settings` denied. Best-effort: returns `[]`
39
+ * when the home is absent or already canonical.
40
+ */
41
+ function resolvedClaboxHome() {
42
+ const home = claboxHomeDir();
43
+ let realHome;
44
+ try {
45
+ realHome = fs.realpathSync(home);
46
+ } catch {
47
+ return [];
48
+ }
49
+ return realHome === home ? [] : [realHome];
50
+ }
51
+ /**
33
52
  * Build the full SBPL profile text.
34
53
  * @param config effective config (see config.ts)
35
54
  * @param ctx { projectDir, detectedPaths }
@@ -37,7 +56,6 @@ function detectPackagePaths() {
37
56
  function buildProfile(config, { projectDir, detectedPaths = detectPackagePaths() }) {
38
57
  const configDir = expandHome(config.configDir);
39
58
  const sshDir = expandHome(config.bot.sshDir);
40
- const hooksDir = config.hooksDir ? expandHome(config.hooksDir) : null;
41
59
  const homeRe = reEscape(HOME);
42
60
  const sections = [];
43
61
  const add = (comment, body) => sections.push(`;; ---------- ${comment}\n${body}`);
@@ -73,13 +91,13 @@ function buildProfile(config, { projectDir, detectedPaths = detectPackagePaths()
73
91
  add("Launch Services needed by /usr/bin/open", allow("mach-lookup", regex("^com\\.apple\\.lsd(\\..*)?$")));
74
92
  add("Developer Tools (xcrun / libxcrun)", allow("mach-lookup", globalName("com.apple.dt.xcsecurity"), regex("^com\\.apple\\.dt\\..*$")));
75
93
  add("Audio (afplay)", allow("mach-lookup", globalName("com.apple.audio.audiohald"), globalName("com.apple.audio.AudioComponentRegistrar")));
94
+ add("Notifications (terminal-notifier / osascript banners)", allow("mach-lookup", globalName("com.apple.hiservices-xpcservice")));
76
95
  add("Notification Center shared-memory (RO)", allow("ipc-posix-shm-read-data", ipcName("apple.shm.notification_center")));
77
96
  add("User-level preference reads (RO)", allow("file-read*", subpath(path.join(HOME, "Library/Preferences"))));
78
97
  add("Keychain access (for OAuth)", [allow("file-read* file-write*", subpath(path.join(HOME, "Library/Keychains"))), allow("mach-lookup", globalName("com.apple.SecurityServer"), globalName("com.apple.security.agent"), globalName("com.apple.securityd"), globalName("com.apple.secd"), globalName("com.apple.trustd"), globalName("com.apple.trustd.agent"), globalName("com.apple.CoreAuthentication.daemon"))].join("\n"));
79
98
  add("git config (RO)", allow("file-read*", literal(path.join(HOME, ".gitconfig")), literal(path.join(HOME, ".gitignore_global")), subpath(path.join(HOME, ".config/git"))));
80
99
  add("soft privacy DENY list (overridable by explicit grants)", deny("file-read* file-write*", ...[...config.denyHome.map((d) => subpath(path.join(HOME, d))), ...config.paths.deny.map((p) => subpath(expandHome(p)))]));
81
100
  add("SSH: bot key + known_hosts (personal keys hard-denied at the very end)", [allow("file-read*", literal(path.join(HOME, ".ssh")), literal(path.join(HOME, ".ssh/known_hosts")), literal(path.join(HOME, ".ssh/known_hosts2")), literal(path.join(HOME, ".ssh/config")), subpath(sshDir)), allow("file-write*", literal(path.join(HOME, ".ssh/known_hosts")), literal(path.join(HOME, ".ssh/known_hosts2")))].join("\n"));
82
- add("claude hooks (RO + exec)", hooksDir && fs.existsSync(hooksDir) ? [allow("file-read* file-map-executable", subpath(hooksDir)), allow("process-exec", subpath(hooksDir))].join("\n") : ";; (no hooks dir; set config.hooksDir / CLABOX_HOOKS_DIR to enable)");
83
101
  if (config.paths.readOnly.length) add("extra read-only paths", allow("file-read*", ...config.paths.readOnly.map((p) => subpath(expandHome(p)))));
84
102
  if (config.paths.readWrite.length) add("extra read-write paths", allow("file-read* file-write*", ...config.paths.readWrite.map((p) => subpath(expandHome(p)))));
85
103
  if (config.paths.exec.length) add("extra exec paths", allow("process-exec", ...config.paths.exec.map((p) => subpath(expandHome(p)))));
@@ -88,6 +106,8 @@ function buildProfile(config, { projectDir, detectedPaths = detectPackagePaths()
88
106
  if (config.denyDotConfigs.length) hardDeny.push(regex(`^${homeRe}/\\.(${config.denyDotConfigs.join("|")})($|/)`));
89
107
  hardDeny.push(regex(`^${homeRe}/\\.ssh/id_`), regex(`^${homeRe}/\\.ssh/.*\\.pem$`), regex(`^${homeRe}/\\.ssh/.*\\.key$`));
90
108
  add("hard secret DENY (always wins — credentials & private keys)", deny("file-read* file-write*", ...hardDeny));
109
+ const claboxDirs = [claboxHomeDir(), ...resolvedClaboxHome()];
110
+ add("clabox home (box configs + compiled mcp/settings) RW — re-granted after the hard deny", [allow("file-read* file-write*", ...claboxDirs.map(subpath)), allow("process-exec", ...claboxDirs.map(subpath))].join("\n"));
91
111
  if (config.network) add("networking", "(allow network*)");
92
112
  sections.push("(allow process-fork)\n(allow lsopen)");
93
113
  const text = `${sections.join("\n\n")}\n`;
@@ -95,6 +115,6 @@ function buildProfile(config, { projectDir, detectedPaths = detectPackagePaths()
95
115
  return text;
96
116
  }
97
117
  //#endregion
98
- export { literal as a, subpath as c, ipcName as i, detectPackagePaths as n, reEscape as o, globalName as r, regex as s, buildProfile as t };
118
+ export { literal as a, resolvedClaboxHome as c, ipcName as i, subpath as l, detectPackagePaths as n, reEscape as o, globalName as r, regex as s, buildProfile as t };
99
119
 
100
- //# sourceMappingURL=profile-DM6NAgb-.js.map
120
+ //# sourceMappingURL=profile-1oytMVlE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-1oytMVlE.js","names":[],"sources":["../src/sandbox/profile.ts"],"sourcesContent":["// Seatbelt (SBPL) profile generator.\n//\n// The old bash version baked the profile into a heredoc and patched it with\n// sed. Here the profile is assembled from small typed helpers, so the parts\n// you actually want to tweak live in config.ts as plain data.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { type Config, claboxHomeDir, expandHome, HOME } from '../utils/config.js';\n\n// ---- SBPL helpers ----------------------------------------------------------\n\n// Quote a literal string for SBPL. Only `\"` needs escaping; backslashes are\n// left as-is so regex patterns survive verbatim.\nconst q = (s: string): string => `\"${String(s).replace(/\"/g, '\\\\\"')}\"`;\n\nexport const subpath = (p: string): string => `(subpath ${q(p)})`;\nexport const literal = (p: string): string => `(literal ${q(p)})`;\nexport const regex = (p: string): string => `(regex ${q(p)})`;\nexport const globalName = (n: string): string => `(global-name ${q(n)})`;\nexport const ipcName = (n: string): string => `(ipc-posix-name ${q(n)})`;\n\n/** Escape a path for safe embedding inside an SBPL regex. */\nexport const reEscape = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nfunction block(op: string, rules: string[]): string {\n return [`(${op}`, ...rules.map((r) => ` ${r}`), ')'].join('\\n');\n}\nconst allow = (op: string, ...rules: string[]): string => block(`allow ${op}`, rules);\nconst deny = (op: string, ...rules: string[]): string => block(`deny ${op}`, rules);\n\n// ---- package-manager autodetection ----------------------------------------\n\n/** Detect installed package managers whose paths must be readable/executable. */\nexport function detectPackagePaths(): string[] {\n const paths: string[] = [];\n if (fs.existsSync('/opt/homebrew')) paths.push('/opt/homebrew');\n else if (fs.existsSync('/usr/local/Homebrew')) paths.push('/usr/local/Homebrew');\n const local = path.join(HOME, '.local');\n if (fs.existsSync(local)) paths.push(local);\n if (fs.existsSync('/nix/store')) paths.push('/nix/store');\n return paths;\n}\n\n/**\n * The clabox home as it physically resolves on disk, but ONLY when\n * {@link claboxHomeDir} is a symlink (so the real path differs from the nominal\n * `~/.config/clabox` one). Used to grant the resolved location in the profile —\n * the macOS sandbox matches the symlink-resolved path, so a relocated clabox\n * home (e.g. symlinked into a project repo) would otherwise have its box configs\n * + compiled `--mcp-config` / `--settings` denied. Best-effort: returns `[]`\n * when the home is absent or already canonical.\n */\nexport function resolvedClaboxHome(): string[] {\n const home = claboxHomeDir();\n let realHome: string;\n try {\n realHome = fs.realpathSync(home);\n } catch {\n return [];\n }\n return realHome === home ? [] : [realHome];\n}\n\n/** Context needed to assemble a profile for a specific project. */\nexport interface ProfileContext {\n projectDir: string;\n detectedPaths?: string[];\n}\n\n/**\n * Build the full SBPL profile text.\n * @param config effective config (see config.ts)\n * @param ctx { projectDir, detectedPaths }\n */\nexport function buildProfile(\n config: Config,\n { projectDir, detectedPaths = detectPackagePaths() }: ProfileContext,\n): string {\n const configDir = expandHome(config.configDir);\n const sshDir = expandHome(config.bot.sshDir);\n const homeRe = reEscape(HOME);\n\n const sections: string[] = [];\n const add = (comment: string, body: string) => sections.push(`;; ---------- ${comment}\\n${body}`);\n\n sections.push(\n [\n ';; ------------------------------------------------------------------',\n ';; Claude Code macOS sandbox profile (autogenerated)',\n ';; ------------------------------------------------------------------',\n '(version 1)',\n '(deny default)',\n ].join('\\n'),\n );\n\n add('introspection & sysctl', '(allow file-read-metadata)\\n(allow sysctl-read)');\n\n add(\n 'basic dir traversal',\n [\n allow('file-read*', literal('/')),\n allow('file-read*', literal('/private')),\n allow('file-read-data', literal('/Users')),\n allow('file-read-data', literal(HOME)),\n ].join('\\n'),\n );\n\n add(\n 'system runtime (read-only)',\n allow(\n 'file-read* file-map-executable',\n subpath('/System'),\n subpath('/usr'),\n subpath('/bin'),\n subpath('/sbin'),\n subpath('/Library/Frameworks'),\n subpath('/private/etc'),\n subpath('/var/db/dyld'),\n ...detectedPaths.map(subpath),\n ),\n );\n\n add(\n 'Xcode / Command Line Tools (xcrun, git, etc.)',\n [\n allow(\n 'file-read* file-map-executable',\n subpath('/Library/Developer/CommandLineTools'),\n subpath('/Applications/Xcode.app'),\n ),\n allow(\n 'process-exec',\n subpath('/Library/Developer/CommandLineTools'),\n subpath('/Applications/Xcode.app'),\n ),\n ].join('\\n'),\n );\n\n // global npm/pipx/cargo bins (user-installed)\n const userPaths = detectedPaths.filter((p) => p.endsWith('/.local'));\n add(\n 'global npm/pipx/cargo bins',\n userPaths.length\n ? userPaths.map((p) => allow('file-read*', subpath(p))).join('\\n')\n : ';; No user package paths detected',\n );\n\n add(\n 'executable paths',\n allow(\n 'process-exec',\n subpath('/usr'),\n subpath('/System'),\n subpath('/bin'),\n subpath('/sbin'),\n literal('/usr/bin/env'),\n ...detectedPaths.map(subpath),\n ),\n );\n\n add(\n 'temp dirs',\n allow(\n 'file-read* file-write*',\n subpath('/tmp'),\n subpath('/private/tmp'),\n regex('^/private/var/folders/'),\n ),\n );\n\n add('Claude config & token files', allow('file-read* file-write*', subpath(configDir)));\n\n add(\n 'Claude auto-update (RO) -- suppress warnings',\n allow(\n 'file-read*',\n subpath(path.join(HOME, '.local/state/claude')),\n subpath(path.join(HOME, '.cache/claude')),\n ),\n );\n\n add(\n 'time-zone & prefs (RO)',\n allow('file-read*', subpath('/private/var/db/timezone'), subpath('/Library/Preferences')),\n );\n\n add(\n '/dev access (RO) + ioctl',\n [\n allow('file-read*', literal('/dev')),\n allow('file-read* file-write*', regex('^/dev/(tty.*|null|zero|dtracehelper)')),\n allow('file-ioctl', literal('/dev/dtracehelper'), regex('^/dev/tty.*')),\n ].join('\\n'),\n );\n\n add(\n 'mach-lookup services',\n allow(\n 'mach-lookup',\n globalName('com.apple.system.opendirectoryd.libinfo'),\n globalName('com.apple.SystemConfiguration.DNSConfiguration'),\n globalName('com.apple.coreservices.launchservicesd'),\n globalName('com.apple.CoreServices.coreservicesd'),\n globalName('com.apple.system.notification_center'),\n globalName('com.apple.logd'),\n globalName('com.apple.diagnosticd'),\n globalName('com.apple.lsd.mapdb'),\n globalName('com.apple.lsd.modifydb'),\n globalName('com.apple.coreservices.quarantine-resolver'),\n globalName('com.apple.pasteboard.pboard'),\n globalName('com.apple.pasteboard.1'),\n ),\n );\n\n add(\n 'Launch Services needed by /usr/bin/open',\n allow('mach-lookup', regex('^com\\\\.apple\\\\.lsd(\\\\..*)?$')),\n );\n\n add(\n 'Developer Tools (xcrun / libxcrun)',\n allow('mach-lookup', globalName('com.apple.dt.xcsecurity'), regex('^com\\\\.apple\\\\.dt\\\\..*$')),\n );\n\n add(\n 'Audio (afplay)',\n allow(\n 'mach-lookup',\n globalName('com.apple.audio.audiohald'),\n globalName('com.apple.audio.AudioComponentRegistrar'),\n ),\n );\n\n // terminal-notifier / osascript banners post via the high-level-services XPC\n // (NSUserNotification). Without it they die with:\n // \"Connection Invalid error for service com.apple.hiservices-xpcservice\".\n // afplay sound is already granted above, so this is what enables sound+banner\n // for Stop/Notification hooks inside the sandbox. Banner-click→focus is NOT\n // enabled: that needs appleevent-send to the terminal, which would let a\n // sandboxed claude script your terminal (a real escape hatch).\n add(\n 'Notifications (terminal-notifier / osascript banners)',\n allow('mach-lookup', globalName('com.apple.hiservices-xpcservice')),\n );\n\n add(\n 'Notification Center shared-memory (RO)',\n allow('ipc-posix-shm-read-data', ipcName('apple.shm.notification_center')),\n );\n\n add(\n 'User-level preference reads (RO)',\n allow('file-read*', subpath(path.join(HOME, 'Library/Preferences'))),\n );\n\n // Keychain RW so Claude can persist refreshed OAuth tokens (else ~24h → 401).\n add(\n 'Keychain access (for OAuth)',\n [\n allow('file-read* file-write*', subpath(path.join(HOME, 'Library/Keychains'))),\n allow(\n 'mach-lookup',\n globalName('com.apple.SecurityServer'),\n globalName('com.apple.security.agent'),\n globalName('com.apple.securityd'),\n globalName('com.apple.secd'),\n globalName('com.apple.trustd'),\n globalName('com.apple.trustd.agent'),\n globalName('com.apple.CoreAuthentication.daemon'),\n ),\n ].join('\\n'),\n );\n\n add(\n 'git config (RO)',\n allow(\n 'file-read*',\n literal(path.join(HOME, '.gitconfig')),\n literal(path.join(HOME, '.gitignore_global')),\n subpath(path.join(HOME, '.config/git')),\n ),\n );\n\n // Soft privacy DENY list — placed BEFORE the extra readOnly/readWrite and the\n // project dir, so an explicit grant may override it (e.g. running on a project\n // that lives under ~/Documents). The hard secret deny below is what's binding.\n const softDeny = [\n ...config.denyHome.map((d) => subpath(path.join(HOME, d))),\n ...config.paths.deny.map((p) => subpath(expandHome(p))),\n ];\n add(\n 'soft privacy DENY list (overridable by explicit grants)',\n deny('file-read* file-write*', ...softDeny),\n );\n\n add(\n 'SSH: bot key + known_hosts (personal keys hard-denied at the very end)',\n [\n allow(\n 'file-read*',\n literal(path.join(HOME, '.ssh')),\n literal(path.join(HOME, '.ssh/known_hosts')),\n literal(path.join(HOME, '.ssh/known_hosts2')),\n literal(path.join(HOME, '.ssh/config')),\n subpath(sshDir),\n ),\n allow(\n 'file-write*',\n literal(path.join(HOME, '.ssh/known_hosts')),\n literal(path.join(HOME, '.ssh/known_hosts2')),\n ),\n ].join('\\n'),\n );\n\n // Extra user-supplied RO / RW / exec rules. Hook scripts that claude must run\n // inside the sandbox are granted via `paths.exec` (read via `paths.readOnly`).\n // Compiled hooks (config.hooks) only register the script with claude — the\n // sandbox still needs an exec grant for the script's path to actually run.\n if (config.paths.readOnly.length)\n add(\n 'extra read-only paths',\n allow('file-read*', ...config.paths.readOnly.map((p) => subpath(expandHome(p)))),\n );\n if (config.paths.readWrite.length)\n add(\n 'extra read-write paths',\n allow('file-read* file-write*', ...config.paths.readWrite.map((p) => subpath(expandHome(p)))),\n );\n if (config.paths.exec.length)\n add(\n 'extra exec paths',\n allow('process-exec', ...config.paths.exec.map((p) => subpath(expandHome(p)))),\n );\n\n add(\n 'project workspace (RW)',\n [\n allow('file-read* file-write* file-map-executable', subpath(projectDir)),\n allow('process-exec', subpath(projectDir)),\n ].join('\\n'),\n );\n\n // Hard secret DENY — emitted LAST of all file rules so it wins even over a\n // broad readOnly/readWrite or the project dir (SBPL = last matching rule\n // wins). This is the binding invariant: personal credentials and private keys\n // are never readable, however wide the grants above. Only the bot key subdir\n // (allowed earlier, and not matched by these patterns) stays readable.\n const hardDeny: string[] = [];\n if (config.denyDotConfigs.length) {\n hardDeny.push(regex(`^${homeRe}/\\\\.(${config.denyDotConfigs.join('|')})($|/)`));\n }\n hardDeny.push(\n regex(`^${homeRe}/\\\\.ssh/id_`),\n regex(`^${homeRe}/\\\\.ssh/.*\\\\.pem$`),\n regex(`^${homeRe}/\\\\.ssh/.*\\\\.key$`),\n );\n add(\n 'hard secret DENY (always wins — credentials & private keys)',\n deny('file-read* file-write*', ...hardDeny),\n );\n\n // clabox's own home (~/.config/clabox) holds the box configs AND clabox's\n // compiled extras (mcp/settings json). The hard `.config` deny above blocks\n // the whole tree, and a user `paths.readWrite` CANNOT help: the hard deny is\n // emitted LAST and, SBPL being last-match-wins, always beats an earlier grant.\n // Re-grant READ+WRITE to the clabox home AFTER the hard deny so a box can read\n // its own `--mcp-config` / `--settings` and edit its own configs in-box. This\n // is scoped to the `clabox` subdir only — the rest of ~/.config (aws, gnupg,\n // docker, …) stays denied — and nothing credential-shaped lives here (secrets\n // come from env, not files), so the hard-deny invariant still holds.\n //\n // Seatbelt matches rules against the *real* (symlink-resolved) path of a vnode\n // — that's why this profile grants both `/tmp` and `/private/tmp`. When\n // ~/.config/clabox is itself a symlink (e.g. relocated INTO a project so the\n // box configs + compiled extras live in the repo), the files physically sit at\n // the symlink target, so the nominal grant never matches and the in-box access\n // fails with EPERM. Grant the resolved target too.\n const claboxDirs = [claboxHomeDir(), ...resolvedClaboxHome()];\n add(\n 'clabox home (box configs + compiled mcp/settings) RW — re-granted after the hard deny',\n [\n allow('file-read* file-write*', ...claboxDirs.map(subpath)),\n // …plus exec, so a box's hook scripts can live in ~/.config/clabox (e.g.\n // a notify.sh) and actually run in-box without a separate `paths.exec`.\n allow('process-exec', ...claboxDirs.map(subpath)),\n ].join('\\n'),\n );\n\n if (config.network) add('networking', '(allow network*)');\n\n sections.push('(allow process-fork)\\n(allow lsopen)');\n\n const text = `${sections.join('\\n\\n')}\\n`;\n\n // Sanity-check before anyone feeds it to sandbox-exec.\n if (!/^\\(version 1\\)/m.test(text)) {\n throw new Error('generated sandbox profile is missing \"(version 1)\"');\n }\n return text;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as Config } from "./config-jZAB2Zg8.js";
1
+ import { i as Config } from "./config-1vfbPBRi.js";
2
2
 
3
3
  //#region src/sandbox/profile.d.ts
4
4
  declare const subpath: (p: string) => string;
@@ -10,6 +10,16 @@ declare const ipcName: (n: string) => string;
10
10
  declare const reEscape: (s: string) => string;
11
11
  /** Detect installed package managers whose paths must be readable/executable. */
12
12
  declare function detectPackagePaths(): string[];
13
+ /**
14
+ * The clabox home as it physically resolves on disk, but ONLY when
15
+ * {@link claboxHomeDir} is a symlink (so the real path differs from the nominal
16
+ * `~/.config/clabox` one). Used to grant the resolved location in the profile —
17
+ * the macOS sandbox matches the symlink-resolved path, so a relocated clabox
18
+ * home (e.g. symlinked into a project repo) would otherwise have its box configs
19
+ * + compiled `--mcp-config` / `--settings` denied. Best-effort: returns `[]`
20
+ * when the home is absent or already canonical.
21
+ */
22
+ declare function resolvedClaboxHome(): string[];
13
23
  /** Context needed to assemble a profile for a specific project. */
14
24
  interface ProfileContext {
15
25
  projectDir: string;
@@ -25,5 +35,5 @@ declare function buildProfile(config: Config, {
25
35
  detectedPaths
26
36
  }: ProfileContext): string;
27
37
  //#endregion
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-7CiMUPu9.d.ts.map
38
+ export { ipcName as a, regex as c, globalName as i, resolvedClaboxHome as l, buildProfile as n, literal as o, detectPackagePaths as r, reEscape as s, ProfileContext as t, subpath as u };
39
+ //# sourceMappingURL=profile-Bzq0Y4PI.d.ts.map
@@ -1,4 +1,4 @@
1
- import { n as AppConfig } from "./config-jZAB2Zg8.js";
1
+ import { n as AppConfig } from "./config-1vfbPBRi.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-fc5U1x7E.d.ts.map
22
+ //# sourceMappingURL=raycast-DuHEu0Vz.d.ts.map
@@ -1,6 +1,6 @@
1
- import { i as expandHome, t as HOME } from "./config-BQ44iVWT.js";
2
- import { n as buildBoxExtras, t as boxSlug } from "./extras-B2ss2Cgh.js";
3
- import { n as detectPackagePaths, t as buildProfile } from "./profile-DM6NAgb-.js";
1
+ import { s as expandHome, t as HOME } from "./config-CY_Cf87P.js";
2
+ import { n as buildBoxExtras, t as boxSlug } from "./extras-Bp4DpyZP.js";
3
+ import { n as detectPackagePaths, t as buildProfile } from "./profile-1oytMVlE.js";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
6
6
  import { execFileSync, spawnSync } from "node:child_process";
@@ -19,6 +19,7 @@ function profilePath(projectDir = process.cwd()) {
19
19
  function resolveProjectDir(config) {
20
20
  return config.cwd ? path.resolve(expandHome(config.cwd)) : process.cwd();
21
21
  }
22
+ /** Resolve a binary via the shell's `command -v`; null if not on PATH. */
22
23
  function which(bin) {
23
24
  try {
24
25
  return execFileSync("command", ["-v", bin], {
@@ -72,9 +73,10 @@ function buildEnvArgs(config) {
72
73
  }
73
74
  /**
74
75
  * 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.
76
+ * clabox's home (`~/.config/clabox`) and can carry secrets (e.g. an MCP auth
77
+ * token in a URL/header), so they're kept out of argv (vs. an inline
78
+ * `--mcp-config '<json>'`) AND off the world-readable bit on disk. Returns the
79
+ * paths.
78
80
  */
79
81
  function writeExtraFiles(files) {
80
82
  for (const f of files) {
@@ -127,6 +129,6 @@ function runClaude(config, claudeArgs, { configFile } = {}) {
127
129
  return res.status ?? 0;
128
130
  }
129
131
  //#endregion
130
- export { runClaude as a, resolveProjectDir as i, generateProfile as n, writeExtraFiles as o, profilePath as r, buildEnvArgs as t };
132
+ export { runClaude as a, resolveProjectDir as i, generateProfile as n, which as o, profilePath as r, writeExtraFiles as s, buildEnvArgs as t };
131
133
 
132
- //# sourceMappingURL=run-yUsOaKFx.js.map
134
+ //# sourceMappingURL=run-9vQC2fCs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-9vQC2fCs.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\n/** Resolve a binary via the shell's `command -v`; null if not on PATH. */\nexport function 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 * clabox's home (`~/.config/clabox`) and can carry secrets (e.g. an MCP auth\n * token in a URL/header), so they're kept out of argv (vs. an inline\n * `--mcp-config '<json>'`) AND off the world-readable bit on disk. Returns the\n * 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 ~/.config/clabox, granted RO\n // in-box).\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,5 +1,5 @@
1
- import { i as Config } from "./config-jZAB2Zg8.js";
2
- import { n as ExtraFile } from "./extras-B_dzBrCF.js";
1
+ import { i as Config } from "./config-1vfbPBRi.js";
2
+ import { n as ExtraFile } from "./extras-CITv2nRg.js";
3
3
 
4
4
  //#region src/sandbox/run.d.ts
5
5
  /** Deterministic per-project profile path under TMPDIR. */
@@ -9,15 +9,18 @@ declare function profilePath(projectDir?: string): string;
9
9
  * absolute path so SBPL `subpath` rules stay valid) if set, else the shell CWD.
10
10
  */
11
11
  declare function resolveProjectDir(config: Config): string;
12
+ /** Resolve a binary via the shell's `command -v`; null if not on PATH. */
13
+ declare function which(bin: string): string | null;
12
14
  /** Generate the profile file for the current project, return its path. */
13
15
  declare function generateProfile(config: Config, projectDir?: string): string;
14
16
  /** Build the `env KEY=VALUE …` argument list forced onto the sandboxed claude. */
15
17
  declare function buildEnvArgs(config: Config): string[];
16
18
  /**
17
19
  * 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.
20
+ * clabox's home (`~/.config/clabox`) and can carry secrets (e.g. an MCP auth
21
+ * token in a URL/header), so they're kept out of argv (vs. an inline
22
+ * `--mcp-config '<json>'`) AND off the world-readable bit on disk. Returns the
23
+ * paths.
21
24
  */
22
25
  declare function writeExtraFiles(files: ExtraFile[]): string[];
23
26
  /** Options accepted by {@link runClaude}. */
@@ -29,5 +32,5 @@ declare function runClaude(config: Config, claudeArgs: string[], {
29
32
  configFile
30
33
  }?: RunOptions): number;
31
34
  //#endregion
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
35
+ export { resolveProjectDir as a, writeExtraFiles as c, profilePath as i, buildEnvArgs as n, runClaude as o, generateProfile as r, which as s, RunOptions as t };
36
+ //# sourceMappingURL=run-gag6YjI9.d.ts.map
@@ -1,2 +1,2 @@
1
- import { i as buildBoxExtras, n as ExtraFile, r as boxSlug, t as BoxExtras } from "../extras-B_dzBrCF.js";
1
+ import { i as buildBoxExtras, n as ExtraFile, r as boxSlug, t as BoxExtras } from "../extras-CITv2nRg.js";
2
2
  export { BoxExtras, ExtraFile, boxSlug, buildBoxExtras };
@@ -1,2 +1,2 @@
1
- import { n as buildBoxExtras, t as boxSlug } from "../extras-B2ss2Cgh.js";
1
+ import { n as buildBoxExtras, t as boxSlug } from "../extras-Bp4DpyZP.js";
2
2
  export { boxSlug, buildBoxExtras };
@@ -1,2 +1,2 @@
1
- import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "../profile-7CiMUPu9.js";
2
- export { ProfileContext, buildProfile, detectPackagePaths, globalName, ipcName, literal, reEscape, regex, subpath };
1
+ import { a as ipcName, c as regex, i as globalName, l as resolvedClaboxHome, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext, u as subpath } from "../profile-Bzq0Y4PI.js";
2
+ export { ProfileContext, buildProfile, detectPackagePaths, globalName, ipcName, literal, reEscape, regex, resolvedClaboxHome, subpath };
@@ -1,2 +1,2 @@
1
- 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";
2
- export { buildProfile, detectPackagePaths, globalName, ipcName, literal, reEscape, regex, subpath };
1
+ import { a as literal, c as resolvedClaboxHome, i as ipcName, l as subpath, n as detectPackagePaths, o as reEscape, r as globalName, s as regex, t as buildProfile } from "../profile-1oytMVlE.js";
2
+ export { buildProfile, detectPackagePaths, globalName, ipcName, literal, reEscape, regex, resolvedClaboxHome, subpath };
@@ -1,2 +1,2 @@
1
- import { a as resolveProjectDir, i as profilePath, n as buildEnvArgs, o as runClaude, r as generateProfile, s as writeExtraFiles, t as RunOptions } from "../run-BWGDNJiY.js";
2
- export { RunOptions, buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
1
+ import { a as resolveProjectDir, c as writeExtraFiles, i as profilePath, n as buildEnvArgs, o as runClaude, r as generateProfile, s as which, t as RunOptions } from "../run-gag6YjI9.js";
2
+ export { RunOptions, buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, which, writeExtraFiles };
@@ -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-yUsOaKFx.js";
2
- export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, writeExtraFiles };
1
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, o as which, r as profilePath, s as writeExtraFiles, t as buildEnvArgs } from "../run-9vQC2fCs.js";
2
+ export { buildEnvArgs, generateProfile, profilePath, resolveProjectDir, runClaude, which, writeExtraFiles };
@@ -5,9 +5,16 @@
5
5
  * shared partials (e.g. `_presets.mjs`) skipped.
6
6
  */
7
7
  declare function discoverProfiles(configsDir: string): string[];
8
+ /**
9
+ * Default base dir for `init`: the parent of the global configs dir — i.e.
10
+ * `~/.config/clabox` (or the parent of `CLABOX_CONFIGS_DIR`). This keeps `init`
11
+ * in sync with the dir `-b` resolves boxes from, so `clabox init` works from any
12
+ * cwd. Override with `--dir` for a project-local `<dir>/configs` layout.
13
+ */
14
+ declare function defaultBaseDir(): string;
8
15
  /** Options for {@link runInit}. */
9
16
  interface InitOptions {
10
- /** Base dir holding `configs/` and `scripts/`. Default: `<cwd>/__`. */
17
+ /** Base dir holding `configs/` and `scripts/`. Default: {@link defaultBaseDir}. */
11
18
  baseDir?: string;
12
19
  /** Build the Ghostty apps for `app` boxes. Default: true. */
13
20
  buildApps?: boolean;
@@ -44,5 +51,5 @@ declare function runInit({
44
51
  only
45
52
  }?: InitOptions): Promise<InitResult>;
46
53
  //#endregion
47
- export { runInit as a, discoverProfiles as i, InitOptions as n, InitResult as r, BuiltApp as t };
48
- //# sourceMappingURL=scaffold-CbsKxlL0.d.ts.map
54
+ export { discoverProfiles as a, defaultBaseDir as i, InitOptions as n, runInit as o, InitResult as r, BuiltApp as t };
55
+ //# sourceMappingURL=scaffold-BiCJzELQ.d.ts.map
@@ -1,8 +1,8 @@
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";
1
+ import { a as configsDir, f as resolveBox, l as listBoxes, s as expandHome, u as loadConfig } from "./config-CY_Cf87P.js";
2
+ import { n as buildBoxExtras } from "./extras-Bp4DpyZP.js";
3
3
  import { n as buildAliasFiles } from "./aliases-KGjds7dK.js";
4
4
  import { r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Dw4QLyl-.js";
5
- import { n as canBuildApps, t as buildApp } from "./app-ChnKbgkD.js";
5
+ import { n as canBuildApps, t as buildApp } from "./app-CuEM7S0i.js";
6
6
  import { t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
7
7
  import path from "node:path";
8
8
  import fs from "node:fs";
@@ -53,9 +53,18 @@ function bakeConfigsDir(dir) {
53
53
  return dir;
54
54
  }
55
55
  /**
56
- * Compile each box's declarative `mcp` (→ `<configDir>/mcp/<box>.json`) so the
57
- * files exist ahead of the first run (the same files `run` writes; slug = box
58
- * name). Best-effort per box: a config that fails to load becomes a warning.
56
+ * Default base dir for `init`: the parent of the global configs dir — i.e.
57
+ * `~/.config/clabox` (or the parent of `CLABOX_CONFIGS_DIR`). This keeps `init`
58
+ * in sync with the dir `-b` resolves boxes from, so `clabox init` works from any
59
+ * cwd. Override with `--dir` for a project-local `<dir>/configs` layout.
60
+ */
61
+ function defaultBaseDir() {
62
+ return path.dirname(configsDir());
63
+ }
64
+ /**
65
+ * Compile each box's declarative `mcp` (→ `~/.config/clabox/mcp/<box>.json`) so
66
+ * the files exist ahead of the first run (the same files `run` writes; slug =
67
+ * box name). Best-effort per box: a config that fails to load becomes a warning.
59
68
  */
60
69
  async function materializeExtras(configsDir, profiles, result) {
61
70
  for (const name of profiles) try {
@@ -138,7 +147,7 @@ async function buildAppArtifacts(base, configsDir, profiles, only, result) {
138
147
  }
139
148
  /** Scan the configs dir, (re)write the alias scripts, and build `app` apps. */
140
149
  async function runInit({ baseDir, buildApps = true, only = null } = {}) {
141
- const base = path.resolve(baseDir ?? path.join(process.cwd(), "__"));
150
+ const base = path.resolve(baseDir ?? defaultBaseDir());
142
151
  const configsDir = path.join(base, "configs");
143
152
  const scriptsDir = path.join(base, "scripts");
144
153
  const profiles = discoverProfiles(configsDir);
@@ -168,6 +177,6 @@ async function runInit({ baseDir, buildApps = true, only = null } = {}) {
168
177
  return result;
169
178
  }
170
179
  //#endregion
171
- export { runInit as n, discoverProfiles as t };
180
+ export { discoverProfiles as n, runInit as r, defaultBaseDir as t };
172
181
 
173
- //# sourceMappingURL=scaffold-C1tqCL_8.js.map
182
+ //# sourceMappingURL=scaffold-DiG5q28w.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-DiG5q28w.js","names":["globalConfigsDir"],"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 {\n type Config,\n expandHome,\n configsDir as globalConfigsDir,\n listBoxes,\n loadConfig,\n resolveBox,\n} 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/**\n * Default base dir for `init`: the parent of the global configs dir — i.e.\n * `~/.config/clabox` (or the parent of `CLABOX_CONFIGS_DIR`). This keeps `init`\n * in sync with the dir `-b` resolves boxes from, so `clabox init` works from any\n * cwd. Override with `--dir` for a project-local `<dir>/configs` layout.\n */\nexport function defaultBaseDir(): string {\n return path.dirname(globalConfigsDir());\n}\n\n/** Options for {@link runInit}. */\nexport interface InitOptions {\n /** Base dir holding `configs/` and `scripts/`. Default: {@link defaultBaseDir}. */\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` (→ `~/.config/clabox/mcp/<box>.json`) so\n * the files exist ahead of the first run (the same files `run` writes; slug =\n * box 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 ?? defaultBaseDir());\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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"}
@@ -1,2 +1,2 @@
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 };
1
+ import { S as resolveBox, _ as expandHome, a as HOME, b as loadConfig, c as HooksConfig, d as PathRules, f as claboxHomeDir, g as defaultConfig, h as configsDir, i as Config, l as LoadedConfig, m as claboxSettingsDir, n as AppConfig, o as HookCommand, p as claboxMcpDir, r as BotConfig, s as HookMatcher, t as AppBuilderConfig, u as McpServer, v as findConfigFile, x as mergeConfig, y as listBoxes } from "../config-1vfbPBRi.js";
2
+ export { AppBuilderConfig, AppConfig, BotConfig, Config, HOME, HookCommand, HookMatcher, HooksConfig, LoadedConfig, McpServer, PathRules, claboxHomeDir, claboxMcpDir, claboxSettingsDir, configsDir, defaultConfig, expandHome, findConfigFile, listBoxes, loadConfig, mergeConfig, resolveBox };
@@ -1,2 +1,2 @@
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
- export { HOME, configsDir, defaultConfig, expandHome, findConfigFile, listBoxes, loadConfig, mergeConfig, resolveBox };
1
+ import { a as configsDir, c as findConfigFile, d as mergeConfig, f as resolveBox, i as claboxSettingsDir, l as listBoxes, n as claboxHomeDir, o as defaultConfig, r as claboxMcpDir, s as expandHome, t as HOME, u as loadConfig } from "../config-CY_Cf87P.js";
2
+ export { HOME, claboxHomeDir, claboxMcpDir, claboxSettingsDir, configsDir, defaultConfig, expandHome, findConfigFile, listBoxes, loadConfig, mergeConfig, resolveBox };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clabox",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Run Claude Code in a sandbox for super-safe YOLO mode",
5
5
  "type": "module",
6
6
  "author": "Igor Suvorov",
@@ -78,6 +78,7 @@
78
78
  }
79
79
  ],
80
80
  "dependencies": {
81
+ "@lsk4/log": "^4.28.0",
81
82
  "yargs": "^17.7.2"
82
83
  },
83
84
  "release": {
@@ -1 +0,0 @@
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":";;;;;;;AA8BA,SAAS,IAAI,KAAa,MAAsB;CAC9C,aAAa,KAAK,MAAM,EAAE,OAAO;EAAC;EAAU;EAAU;CAAM,EAAE,CAAC;AACjE;AAEA,SAAS,IAAI,KAAsB;CACjC,IAAI;EACF,aAAa,WAAW,CAAC,MAAM,GAAG,GAAG;GAAE,OAAO;GAAW,OAAO;EAAS,CAAC;EAC1E,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,aAAa,SAA6D;CACxF,IAAI,QAAQ,aAAa,UAAU,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAY;CAC3E,IAAI,CAAC,GAAG,WAAW,WAAW,QAAQ,UAAU,CAAC,GAC/C,OAAO;EAAE,IAAI;EAAO,QAAQ,wBAAwB,QAAQ;CAAa;CAE3E,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAyC;CACrF,OAAO,EAAE,IAAI,KAAK;AACpB;;AAGA,SAAS,oBAAoB,YAAoB,QAA+B;CAC9E,IAAI;CACJ,IAAI;EACF,MAAM,aAAa,YAAY;GAAC;GAAM;GAAkB;GAAK;GAAS;EAAU,GAAG;GACjF,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACpC,CAAC;CACH,QAAQ;EACN,OAAO;CACT;CACA,MAAM,QAAQ,IAAI,QAAQ,OAAO;CACjC,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,OAAO,KAAK,KAAK,QAAQ,kBAAkB;CACjD,GAAG,cAAc,MAAM,IAAI,MAAM,KAAK,CAAC;CACvC,OAAO;AACT;;AAGA,SAAS,iBAAiB,OAAuB;CAC/C,IAAI;EACF,MAAM,OAAO,aAAa,UAAU;GAAC;GAAY;GAAoB;GAAO;EAAK,GAAG;GAClF,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACpC,CAAC,CAAC,CAAC,KAAK;EACR,OAAO,KAAK,SAAS,OAAO,IAAI,OAAO,GAAG,KAAK;CACjD,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAS,UAAU,KAAa,KAAa,QAAsB;CACjE,MAAM,UAAU,KAAK,KAAK,QAAQ,cAAc;CAChD,GAAG,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;CACzC,KAAK,MAAM,QAAQ;EAAC;EAAI;EAAI;EAAK;EAAK;CAAG,GAAG;EAC1C,IAAI,QAAQ;GACV;GACA,GAAG;GACH,GAAG;GACH;GACA;GACA,KAAK,KAAK,SAAS,QAAQ,KAAK,GAAG,KAAK,KAAK;EAC/C,CAAC;EACD,MAAM,IAAI,OAAO;EACjB,IAAI,QAAQ;GACV;GACA,GAAG;GACH,GAAG;GACH;GACA;GACA,KAAK,KAAK,SAAS,QAAQ,KAAK,GAAG,KAAK,QAAQ;EAClD,CAAC;CACH;CACA,IAAI,YAAY;EAAC;EAAM;EAAQ;EAAS;EAAM;CAAG,CAAC;AACpD;;AAGA,SAAgB,YAAY,KAAgB,SAAiB,QAAsB;CACjF,IAAI,CAAC,IAAI,MAAM;CACf,MAAM,OAAO,WAAW,IAAI,IAAI;CAChC,IAAI,CAAC,GAAG,WAAW,IAAI,GAAG,MAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM;CACvE,MAAM,QAAQ,KAAK,KAAK,SAAS,YAAY,YAAY;CACzD,MAAM,OAAO,KAAK,KAAK,SAAS,YAAY,aAAa,iBAAiB,KAAK,CAAC;CAChF,IAAI,KAAK,SAAS,OAAO,GAAG,GAAG,aAAa,MAAM,IAAI;MACjD,IAAI,KAAK,SAAS,MAAM,GAAG,UAAU,MAAM,MAAM,MAAM;MACvD,MAAM,IAAI,MAAM,4CAA4C,IAAI,MAAM;CAM3E,IAAI;EACF,IAAI,UAAU;GAAC;GAAW;GAAoB;EAAK,CAAC;CACtD,QAAQ,CAER;AACF;;;;;AAMA,SAAgB,SAAS,MAAuC;CAC9D,MAAM,EAAE,KAAK,SAAS,SAAS,eAAe;CAC9C,MAAM,QAAQ,aAAa,OAAO;CAClC,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,qBAAqB,MAAM,QAAQ;CAElE,MAAM,aAAa,WAAW,QAAQ,UAAU;CAChD,MAAM,UAAU,WAAW,QAAQ,OAAO;CAC1C,MAAM,UAAU,cAAc,SAAS,GAAG;CAC1C,MAAM,QAAQ,KAAK,KAAK,SAAS,YAAY,YAAY;CACzD,MAAM,SAAS,GAAG,YAAY,KAAK,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC;CAEnE,IAAI;EACF,MAAM,eAAe,oBAAoB,YAAY,MAAM;EAG3D,GAAG,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;EACzC,GAAG,OAAO,SAAS;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACnD,IAAI,MAAM;GAAC;GAAM;GAAY;EAAO,CAAC;EAGrC,IAAI,UAAU;GAAC;GAAY;GAAsB;GAAW,SAAS,SAAS,GAAG;GAAG;EAAK,CAAC;EAC1F,IAAI,UAAU;GAAC;GAAY;GAAgB;GAAW,IAAI;GAAM;EAAK,CAAC;EACtE,IAAI,UAAU;GAAC;GAAY;GAAuB;GAAW,IAAI;GAAM;EAAK,CAAC;EAC7E,IAAI,UAAU;GAAC;GAAY;GAAsB;GAAW;GAAW;EAAK,CAAC;EAG7E,IAAI,UAAU;GAAC;GAAY;GAA2B;GAAS;GAAM;EAAK,CAAC;EAC3E,IAAI;GACF,IAAI,UAAU;IAAC;IAAY;IAAa;IAAW;IAAI;GAAK,CAAC;EAC/D,QAAQ,CAER;EAGA,MAAM,MAAM,KAAK,KAAK,SAAS,YAAY,SAAS,SAAS;EAC7D,GAAG,WAAW,KAAK,GAAG,IAAI,MAAM;EAChC,MAAM,MAAM,KAAK,KAAK,QAAQ,YAAY;EAC1C,GAAG,cAAc,KAAK,oBAAoB,UAAU,CAAC;EACrD,IAAI,MAAM;GAAC;GAAM;GAAK;EAAG,CAAC;EAE1B,YAAY,KAAK,SAAS,MAAM;EAGhC,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,eAAe,CAAC,kBAAkB,YAAY,IAAI,CAAC;EACnE,MAAM,SAAS,SAAS,CAAC,UAAU,MAAM,IAAI,CAAC,UAAU,GAAG;EAC3D,IAAI,YAAY;GAAC;GAAW,GAAG;GAAQ,GAAG;GAAS,GAAG,IAAI;EAAM,CAAC;EACjE,IAAI,YAAY;GAAC;GAAW;GAAU,GAAG;GAAQ,GAAG;GAAS;EAAO,CAAC;EAErE,OAAO;GAAE;GAAS,QAAQ,SAAS,aAAa;EAAQ;CAC1D,UAAU;EACR,GAAG,OAAO,QAAQ;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACpD;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-BQ44iVWT.js","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["// Configuration: sane defaults, env overrides, and an optional JS config file.\n//\n// Resolution order (later wins):\n// 1. defaultConfig (below)\n// 2. env vars (CLAUDE_CONFIG_DIR, CLABOX_*, …)\n// 3. a JS config file (see loadConfig)\n//\n// A config file default-exports either a plain object (merged over the defaults)\n// or a function `(defaults) => config` for full programmatic control.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport const HOME = os.homedir();\n\n/** Expand a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (!p) return p;\n if (p === '~') return HOME;\n if (p.startsWith('~/')) return path.join(HOME, p.slice(2));\n return p;\n}\n\nconst env = process.env;\n\n/** Dedicated git/ssh identity for commits made from inside the sandbox. */\nexport interface BotConfig {\n name: string;\n email: string;\n /** If `${sshDir}/id_ed25519` exists, git ssh is pinned to it. */\n sshDir: string;\n}\n\n/**\n * Opt-in marker that turns a box into a standalone Ghostty app. When a box\n * config carries an `app`, `clabox init` generates a Ghostty config for it and\n * builds a cloned `<appsDir>/<name>.app` that launches `clabox -b <box>`.\n */\nexport interface AppConfig {\n /** App display name → `<appsDir>/<name>.app` + CFBundleName. */\n name: string;\n /** Ghostty window title. Defaults to {@link AppConfig.name}. */\n title?: string;\n /** Emoji for the generated Raycast command. Default: the title's leading emoji. */\n emoji?: string;\n /** Path to a `.icns`/`.png` icon for the .app. `.png` is converted. `~` ok. */\n icon?: string;\n /** Ghostty built-in `macos-icon` (e.g. `retro`, `holographic`). */\n macosIcon?: string;\n /** Extra raw `key = value` lines appended to the generated Ghostty config. */\n ghostty?: Record<string, string>;\n /** Bundle id. Default: `com.ghostty.custom.<name dot-joined>`. */\n bundleId?: string;\n}\n\n/** Machine-wide settings for the `clabox init` Ghostty-app builder. */\nexport interface AppBuilderConfig {\n /** Donor app to clone. `~` is expanded. */\n ghosttyApp: string;\n /** Where built apps land. `~` is expanded. */\n appsDir: string;\n /** codesign identity. null → ad-hoc (`codesign -s -`). */\n signId: string | null;\n /** Optional base Ghostty config emitted as a leading `config-file = …`. */\n baseGhosttyConfig: string | null;\n /** `clabox` binary baked into the generated `command`. null → autodetect. */\n claboxBin: string | null;\n}\n\n/**\n * A single MCP server entry — the value under a key in claude's `mcpServers`\n * map. Loose on purpose (mirrors claude's mcp.json schema): `stdio` servers use\n * `command`/`args`/`env`, remote `http`/`sse` servers use `url`/`headers`.\n */\nexport interface McpServer {\n type?: 'stdio' | 'http' | 'sse';\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n url?: string;\n headers?: Record<string, string>;\n}\n\n/** Extra rules layered on top of the built-in base profile. */\nexport interface PathRules {\n /** RW subpaths (beyond project dir + configDir + /tmp). */\n readWrite: string[];\n /** RO subpaths. */\n readOnly: string[];\n /** process-exec subpaths. */\n exec: string[];\n /** explicit deny subpaths (read + write). */\n deny: string[];\n}\n\n/** Effective clabox configuration. */\nexport interface Config {\n /**\n * Working directory to run `claude` in (and grant RW as the project dir).\n * null → the shell's CWD. Handy for named boxes that always target one\n * project regardless of where `clabox` is invoked from. `~` is expanded.\n */\n cwd: string | null;\n /** Path to the `claude` binary. null → autodetect (PATH, then ~/.local/bin). */\n claudeBin: string | null;\n /** Claude config/profile directory — supports multiple accounts. */\n configDir: string;\n /** Extra args always passed to `claude`, before any args from the CLI. */\n claudeArgs: string[];\n /**\n * Per-box MCP servers (the `mcpServers` map). clabox compiles them to\n * `<configDir>/mcp/<slug>.json` and launches claude with\n * `--strict-mcp-config --mcp-config <file>`, so a shared configDir's global /\n * plugin MCP servers are ignored — each box gets exactly these and no more.\n * Materialized on every `run` and during `init`. Absent → no MCP flags.\n */\n mcp?: Record<string, McpServer>;\n /**\n * Text appended to claude's system prompt via `--append-system-prompt`.\n * `string[]` is joined with blank lines. Use it for per-box pre-prompts while\n * sharing one configDir (the user-level CLAUDE.md is shared; this is not).\n */\n systemPrompt?: string | string[];\n bot: BotConfig;\n /**\n * Extra environment variables forced onto the sandboxed `claude` process,\n * layered over the inherited shell env and after the built-in hardening vars\n * (so a key set here wins). Use it to pass secrets like `GITHUB_TOKEN`.\n */\n env: Record<string, string>;\n /** Allow outbound network. `false` → no `(allow network*)` line. */\n network: boolean;\n /** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */\n ulimitProcs: number;\n /** Extra directory granted read + execute inside the sandbox. null → disabled. */\n hooksDir: string | null;\n paths: PathRules;\n /** Home subdirectories denied entirely (read + write). */\n denyHome: string[];\n /** Dotfile config dirs under $HOME denied entirely. */\n denyDotConfigs: string[];\n /**\n * Opt-in: build a standalone Ghostty app for this box during `clabox init`.\n * Absent → the box only gets a shell alias (the default).\n */\n app?: AppConfig;\n /** Machine-wide settings for the `clabox init` Ghostty-app builder. */\n appBuilder: AppBuilderConfig;\n}\n\n/** Built-in defaults. Everything here is meant to be overridable. */\nexport const defaultConfig: Config = {\n cwd: env.CLABOX_CWD ?? null,\n claudeBin: env.CLABOX_CLAUDE_BIN ?? null,\n configDir: env.CLAUDE_CONFIG_DIR ?? '~/.claude',\n claudeArgs: ['--settings', '{\"includeCoAuthoredBy\": false}'],\n bot: {\n name: env.CLABOX_BOT_NAME ?? 'claudeBOT',\n email: env.CLABOX_BOT_EMAIL ?? 'bot@example.com',\n sshDir: env.CLABOX_BOT_SSH_DIR ?? '~/.ssh/claudebot',\n },\n env: {},\n network: true,\n ulimitProcs: 1024,\n hooksDir: env.CLABOX_HOOKS_DIR ?? null,\n paths: {\n readWrite: [],\n readOnly: [],\n exec: [],\n deny: [],\n },\n denyHome: ['Documents', 'Desktop', 'Downloads', 'Pictures', 'Movies', 'Music'],\n // `.config/git` is always carved back out for git RO config in the profile.\n denyDotConfigs: ['aws', 'gnupg', 'kube', 'docker', 'config'],\n // `app` is opt-in per box, so there's no default — it stays undefined.\n appBuilder: {\n ghosttyApp: env.CLABOX_GHOSTTY_APP ?? '/Applications/Ghostty.app',\n appsDir: env.CLABOX_APPS_DIR ?? '~/Applications',\n signId: env.CLABOX_SIGN_ID ?? null,\n baseGhosttyConfig: env.CLABOX_GHOSTTY_BASE_CONFIG ?? null,\n claboxBin: env.CLABOX_CLABOX_BIN ?? null,\n },\n};\n\ntype Plain = Record<string, unknown>;\n\nfunction isPlainObject(v: unknown): v is Plain {\n return v != null && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** Shallow-deep merge: nested plain objects merge, everything else replaces. */\nfunction deepMerge(base: Plain, override: Plain): Plain {\n const out: Plain = { ...base };\n for (const [key, val] of Object.entries(override)) {\n if (val === undefined) continue;\n const baseVal = base[key];\n out[key] = isPlainObject(val) && isPlainObject(baseVal) ? deepMerge(baseVal, val) : val;\n }\n return out;\n}\n\n/** Merge a (partial) override over a full config, returning a new config. */\nexport function mergeConfig(base: Config, override: unknown): Config {\n if (!isPlainObject(override)) return base;\n return deepMerge(base as unknown as Plain, override) as unknown as Config;\n}\n\n/**\n * Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),\n * then CWD, then ~/.config. The CLI arg wins over the env var.\n */\nexport function findConfigFile(explicit?: string | null): string | null {\n const chosen = explicit ?? env.CLABOX_CONFIG;\n if (chosen) return expandHome(chosen);\n const candidates = [\n path.join(process.cwd(), 'clabox.config.mjs'),\n path.join(process.cwd(), 'clabox.config.js'),\n path.join(HOME, '.config', 'clabox', 'config.mjs'),\n ];\n return candidates.find((c) => fs.existsSync(c)) ?? null;\n}\n\n/**\n * Global directory holding named \"box\" configs (`<name>.config.mjs`), used by\n * the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.\n */\nexport function configsDir(): string {\n return expandHome(env.CLABOX_CONFIGS_DIR ?? '~/.config/clabox/configs');\n}\n\nconst BOX_SUFFIXES = ['.config.mjs', '.mjs'];\n\n/** Candidate file paths for a box name, in resolution order. */\nfunction boxCandidates(name: string, dir: string): string[] {\n return BOX_SUFFIXES.map((s) => path.join(dir, `${name}${s}`));\n}\n\n/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */\nexport function listBoxes(dir: string = configsDir()): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n const names = new Set<string>();\n for (const f of entries) {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes.\n if (f.startsWith('_')) continue;\n const suffix = BOX_SUFFIXES.find((s) => f.endsWith(s));\n if (suffix) names.add(f.slice(0, -suffix.length));\n }\n return [...names].sort();\n}\n\n/**\n * Resolve a box name to its config file in {@link configsDir}, preferring\n * `<name>.config.mjs` over a bare `<name>.mjs`.\n *\n * @throws if no matching file exists (the message lists the available boxes).\n */\nexport function resolveBox(name: string, dir: string = configsDir()): string {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes —\n // keep them un-resolvable so `-b` matches what `listBoxes` advertises.\n if (!name.startsWith('_')) {\n const found = boxCandidates(name, dir).find((c) => fs.existsSync(c));\n if (found) return found;\n }\n const available = listBoxes(dir);\n const hint = available.length ? `available: ${available.join(', ')}` : `none found in ${dir}`;\n throw new Error(`clabox: box '${name}' not found in ${dir} (${hint})`);\n}\n\n/** Result of {@link loadConfig}: the effective config and the file it came from. */\nexport interface LoadedConfig {\n config: Config;\n configFile: string | null;\n}\n\n/**\n * Build the effective config: defaults ⊕ env ⊕ config file.\n *\n * @param explicitConfig optional config-file path (e.g. from `--config`);\n * takes precedence over `CLABOX_CONFIG` and the default lookup locations.\n */\nexport async function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig> {\n let cfg: Config = defaultConfig;\n const file = findConfigFile(explicitConfig);\n if (file) {\n const mod = await import(pathToFileURL(file).href);\n const exported = mod.default ?? mod.config ?? mod;\n const resolved = typeof exported === 'function' ? await exported(defaultConfig) : exported;\n cfg = mergeConfig(defaultConfig, resolved);\n }\n return { config: cfg, configFile: file };\n}\n"],"mappings":"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"}
@@ -1,46 +0,0 @@
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
@@ -1 +0,0 @@
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"}