clabox 0.0.1 → 0.1.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/clabox.config.example.mjs +90 -0
  4. package/docs/guideline.md +196 -0
  5. package/docs/logo.png +0 -0
  6. package/lib/aliases-DKGcMHHe.js +60 -0
  7. package/lib/aliases-DKGcMHHe.js.map +1 -0
  8. package/lib/aliases-DXyz-ufw.d.ts +31 -0
  9. package/lib/app-CieBa29D.js +246 -0
  10. package/lib/app-CieBa29D.js.map +1 -0
  11. package/lib/app-CpuMtOoj.d.ts +30 -0
  12. package/lib/cli.d.ts +1 -0
  13. package/lib/cli.js +71 -0
  14. package/lib/cli.js.map +1 -0
  15. package/lib/config-BQ44iVWT.js +155 -0
  16. package/lib/config-BQ44iVWT.js.map +1 -0
  17. package/lib/config-DQWueb4a.d.ts +134 -0
  18. package/lib/ghostty-Ca0g9P9P.js +74 -0
  19. package/lib/ghostty-Ca0g9P9P.js.map +1 -0
  20. package/lib/ghostty-DemKkfqf.d.ts +34 -0
  21. package/lib/index.d.ts +9 -0
  22. package/lib/index.js +9 -0
  23. package/lib/init/aliases.d.ts +2 -0
  24. package/lib/init/aliases.js +2 -0
  25. package/lib/init/app.d.ts +2 -0
  26. package/lib/init/app.js +2 -0
  27. package/lib/init/ghostty.d.ts +2 -0
  28. package/lib/init/ghostty.js +2 -0
  29. package/lib/init/raycast.d.ts +2 -0
  30. package/lib/init/raycast.js +2 -0
  31. package/lib/init/scaffold.d.ts +2 -0
  32. package/lib/init/scaffold.js +2 -0
  33. package/lib/profile-BeM41NXc.d.ts +29 -0
  34. package/lib/profile-DM6NAgb-.js +100 -0
  35. package/lib/profile-DM6NAgb-.js.map +1 -0
  36. package/lib/raycast-BCdO2Se1.js +35 -0
  37. package/lib/raycast-BCdO2Se1.js.map +1 -0
  38. package/lib/raycast-DM7c559f.d.ts +22 -0
  39. package/lib/run-CNehSQ-S.js +113 -0
  40. package/lib/run-CNehSQ-S.js.map +1 -0
  41. package/lib/run-Cx8cuTh5.d.ts +25 -0
  42. package/lib/sandbox/profile.d.ts +2 -0
  43. package/lib/sandbox/profile.js +2 -0
  44. package/lib/sandbox/run.d.ts +2 -0
  45. package/lib/sandbox/run.js +2 -0
  46. package/lib/scaffold-ByIbYAeS.d.ts +46 -0
  47. package/lib/scaffold-CRzC5KYe.js +141 -0
  48. package/lib/scaffold-CRzC5KYe.js.map +1 -0
  49. package/lib/utils/config.d.ts +2 -0
  50. package/lib/utils/config.js +2 -0
  51. package/package.json +141 -7
@@ -0,0 +1,134 @@
1
+ //#region src/utils/config.d.ts
2
+ declare const HOME: string;
3
+ /** Expand a leading `~` / `~/` to the user's home directory. */
4
+ declare function expandHome(p: string): string;
5
+ /** Dedicated git/ssh identity for commits made from inside the sandbox. */
6
+ interface BotConfig {
7
+ name: string;
8
+ email: string;
9
+ /** If `${sshDir}/id_ed25519` exists, git ssh is pinned to it. */
10
+ sshDir: string;
11
+ }
12
+ /**
13
+ * Opt-in marker that turns a box into a standalone Ghostty app. When a box
14
+ * config carries an `app`, `clabox init` generates a Ghostty config for it and
15
+ * builds a cloned `<appsDir>/<name>.app` that launches `clabox -b <box>`.
16
+ */
17
+ interface AppConfig {
18
+ /** App display name → `<appsDir>/<name>.app` + CFBundleName. */
19
+ name: string;
20
+ /** Ghostty window title. Defaults to {@link AppConfig.name}. */
21
+ title?: string;
22
+ /** Emoji for the generated Raycast command. Default: the title's leading emoji. */
23
+ emoji?: string;
24
+ /** Path to a `.icns`/`.png` icon for the .app. `.png` is converted. `~` ok. */
25
+ icon?: string;
26
+ /** Ghostty built-in `macos-icon` (e.g. `retro`, `holographic`). */
27
+ macosIcon?: string;
28
+ /** Extra raw `key = value` lines appended to the generated Ghostty config. */
29
+ ghostty?: Record<string, string>;
30
+ /** Bundle id. Default: `com.ghostty.custom.<name dot-joined>`. */
31
+ bundleId?: string;
32
+ }
33
+ /** Machine-wide settings for the `clabox init` Ghostty-app builder. */
34
+ interface AppBuilderConfig {
35
+ /** Donor app to clone. `~` is expanded. */
36
+ ghosttyApp: string;
37
+ /** Where built apps land. `~` is expanded. */
38
+ appsDir: string;
39
+ /** codesign identity. null → ad-hoc (`codesign -s -`). */
40
+ signId: string | null;
41
+ /** Optional base Ghostty config emitted as a leading `config-file = …`. */
42
+ baseGhosttyConfig: string | null;
43
+ /** `clabox` binary baked into the generated `command`. null → autodetect. */
44
+ claboxBin: string | null;
45
+ }
46
+ /** Extra rules layered on top of the built-in base profile. */
47
+ interface PathRules {
48
+ /** RW subpaths (beyond project dir + configDir + /tmp). */
49
+ readWrite: string[];
50
+ /** RO subpaths. */
51
+ readOnly: string[];
52
+ /** process-exec subpaths. */
53
+ exec: string[];
54
+ /** explicit deny subpaths (read + write). */
55
+ deny: string[];
56
+ }
57
+ /** Effective clabox configuration. */
58
+ interface Config {
59
+ /**
60
+ * Working directory to run `claude` in (and grant RW as the project dir).
61
+ * null → the shell's CWD. Handy for named boxes that always target one
62
+ * project regardless of where `clabox` is invoked from. `~` is expanded.
63
+ */
64
+ cwd: string | null;
65
+ /** Path to the `claude` binary. null → autodetect (PATH, then ~/.local/bin). */
66
+ claudeBin: string | null;
67
+ /** Claude config/profile directory — supports multiple accounts. */
68
+ configDir: string;
69
+ /** Extra args always passed to `claude`, before any args from the CLI. */
70
+ claudeArgs: string[];
71
+ bot: BotConfig;
72
+ /**
73
+ * Extra environment variables forced onto the sandboxed `claude` process,
74
+ * layered over the inherited shell env and after the built-in hardening vars
75
+ * (so a key set here wins). Use it to pass secrets like `GITHUB_TOKEN`.
76
+ */
77
+ env: Record<string, string>;
78
+ /** Allow outbound network. `false` → no `(allow network*)` line. */
79
+ network: boolean;
80
+ /** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */
81
+ ulimitProcs: number;
82
+ /** Extra directory granted read + execute inside the sandbox. null → disabled. */
83
+ hooksDir: string | null;
84
+ paths: PathRules;
85
+ /** Home subdirectories denied entirely (read + write). */
86
+ denyHome: string[];
87
+ /** Dotfile config dirs under $HOME denied entirely. */
88
+ denyDotConfigs: string[];
89
+ /**
90
+ * Opt-in: build a standalone Ghostty app for this box during `clabox init`.
91
+ * Absent → the box only gets a shell alias (the default).
92
+ */
93
+ app?: AppConfig;
94
+ /** Machine-wide settings for the `clabox init` Ghostty-app builder. */
95
+ appBuilder: AppBuilderConfig;
96
+ }
97
+ /** Built-in defaults. Everything here is meant to be overridable. */
98
+ declare const defaultConfig: Config;
99
+ /** Merge a (partial) override over a full config, returning a new config. */
100
+ declare function mergeConfig(base: Config, override: unknown): Config;
101
+ /**
102
+ * Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),
103
+ * then CWD, then ~/.config. The CLI arg wins over the env var.
104
+ */
105
+ declare function findConfigFile(explicit?: string | null): string | null;
106
+ /**
107
+ * Global directory holding named "box" configs (`<name>.config.mjs`), used by
108
+ * the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.
109
+ */
110
+ declare function configsDir(): string;
111
+ /** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */
112
+ declare function listBoxes(dir?: string): string[];
113
+ /**
114
+ * Resolve a box name to its config file in {@link configsDir}, preferring
115
+ * `<name>.config.mjs` over a bare `<name>.mjs`.
116
+ *
117
+ * @throws if no matching file exists (the message lists the available boxes).
118
+ */
119
+ declare function resolveBox(name: string, dir?: string): string;
120
+ /** Result of {@link loadConfig}: the effective config and the file it came from. */
121
+ interface LoadedConfig {
122
+ config: Config;
123
+ configFile: string | null;
124
+ }
125
+ /**
126
+ * Build the effective config: defaults ⊕ env ⊕ config file.
127
+ *
128
+ * @param explicitConfig optional config-file path (e.g. from `--config`);
129
+ * takes precedence over `CLABOX_CONFIG` and the default lookup locations.
130
+ */
131
+ declare function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig>;
132
+ //#endregion
133
+ export { HOME as a, configsDir as c, findConfigFile as d, listBoxes as f, resolveBox as h, Config as i, defaultConfig as l, mergeConfig as m, AppConfig as n, LoadedConfig as o, loadConfig as p, BotConfig as r, PathRules as s, AppBuilderConfig as t, expandHome as u };
134
+ //# sourceMappingURL=config-DQWueb4a.d.ts.map
@@ -0,0 +1,74 @@
1
+ import path from "node:path";
2
+ //#region src/init/ghostty.ts
3
+ /** The `command = bash -c '…'` line that boots clabox for the box. */
4
+ function buildCommand(opts) {
5
+ return `command = bash -c '${`${opts.projectDir ? `cd ${opts.projectDir} && ` : ""}CLABOX_CONFIGS_DIR=${opts.configsDir} ${opts.claboxBin} -b ${opts.boxName}; exec zsh`}'`;
6
+ }
7
+ /** Build the Ghostty config text for an app box. */
8
+ function buildGhosttyConfig(opts) {
9
+ const { app } = opts;
10
+ const lines = ["# Generated by `clabox init` — do not edit; rerun it after changing the box config."];
11
+ if (opts.baseGhosttyConfig) lines.push(`config-file = ${opts.baseGhosttyConfig}`);
12
+ lines.push("", `title = "${app.title ?? app.name}"`);
13
+ if (app.macosIcon) lines.push(`macos-icon = ${app.macosIcon}`);
14
+ if (app.ghostty && Object.keys(app.ghostty).length > 0) {
15
+ lines.push("");
16
+ for (const [key, value] of Object.entries(app.ghostty)) lines.push(`${key} = ${value}`);
17
+ }
18
+ lines.push("", buildCommand(opts));
19
+ return `${lines.join("\n")}\n`;
20
+ }
21
+ /** Escape a string for embedding as a C double-quoted literal. */
22
+ function cEscape(s) {
23
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
24
+ }
25
+ /**
26
+ * Build the C launcher source. It finds itself, locates `ghostty.real` next to
27
+ * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone
28
+ * always boots with its own config regardless of how it's launched.
29
+ */
30
+ function buildLauncherSource(configPath) {
31
+ return `// Generated by clabox init. Launches ghostty.real with a baked config.
32
+ #include <stdio.h>
33
+ #include <unistd.h>
34
+ #include <stdlib.h>
35
+ #include <libgen.h>
36
+ #include <mach-o/dyld.h>
37
+
38
+ static const char *CONFIG_PATH = "${cEscape(configPath)}";
39
+
40
+ int main(int argc, char *argv[]) {
41
+ char path[4096];
42
+ uint32_t size = sizeof(path);
43
+ _NSGetExecutablePath(path, &size);
44
+
45
+ char *dir = dirname(path);
46
+ char real_path[4096];
47
+ snprintf(real_path, sizeof(real_path), "%s/ghostty.real", dir);
48
+
49
+ char config_arg[4096];
50
+ snprintf(config_arg, sizeof(config_arg), "--config-file=%s", CONFIG_PATH);
51
+
52
+ char **new_argv = malloc(sizeof(char *) * (argc + 2));
53
+ new_argv[0] = real_path;
54
+ new_argv[1] = config_arg;
55
+ for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i];
56
+ new_argv[argc + 1] = NULL;
57
+
58
+ execv(real_path, new_argv);
59
+ return 1;
60
+ }
61
+ `;
62
+ }
63
+ /** Absolute path to the built `.app` bundle. */
64
+ function appBundlePath(appsDir, app) {
65
+ return path.join(appsDir, `${app.name}.app`);
66
+ }
67
+ /** Bundle identifier for the clone (explicit, or derived from the box name). */
68
+ function bundleId(boxName, app) {
69
+ return app.bundleId ?? `com.ghostty.custom.${boxName.replace(/-/g, ".")}`;
70
+ }
71
+ //#endregion
72
+ export { bundleId as a, buildLauncherSource as i, buildCommand as n, buildGhosttyConfig as r, appBundlePath as t };
73
+
74
+ //# sourceMappingURL=ghostty-Ca0g9P9P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ghostty-Ca0g9P9P.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 = bash -c '…'` 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 return `command = bash -c '${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;CAG/D,OAAO,sBAAsB,GAFlB,KAAK,aAAa,MAAM,KAAK,WAAW,QAAQ,GACvC,qBAAqB,KAAK,WAAW,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,YAC3D;AACrC;;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"}
@@ -0,0 +1,34 @@
1
+ import { n as AppConfig } from "./config-DQWueb4a.js";
2
+
3
+ //#region src/init/ghostty.d.ts
4
+ /** Inputs for {@link buildGhosttyConfig} (all paths already absolute). */
5
+ interface GhosttyConfigOptions {
6
+ app: AppConfig;
7
+ /** The `-b` box name this config launches. */
8
+ boxName: string;
9
+ /** Absolute project dir to `cd` into. null → don't `cd` (run in launch cwd). */
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. */
14
+ claboxBin: string;
15
+ /** Absolute path to a base Ghostty config, emitted as a leading `config-file`. */
16
+ baseGhosttyConfig?: string | null;
17
+ }
18
+ /** The `command = bash -c '…'` line that boots clabox for the box. */
19
+ declare function buildCommand(opts: GhosttyConfigOptions): string;
20
+ /** Build the Ghostty config text for an app box. */
21
+ declare function buildGhosttyConfig(opts: GhosttyConfigOptions): string;
22
+ /**
23
+ * Build the C launcher source. It finds itself, locates `ghostty.real` next to
24
+ * it, and re-execs it with `--config-file=<configPath>` prepended — so the clone
25
+ * always boots with its own config regardless of how it's launched.
26
+ */
27
+ declare function buildLauncherSource(configPath: string): string;
28
+ /** Absolute path to the built `.app` bundle. */
29
+ declare function appBundlePath(appsDir: string, app: AppConfig): string;
30
+ /** Bundle identifier for the clone (explicit, or derived from the box name). */
31
+ declare function bundleId(boxName: string, app: AppConfig): string;
32
+ //#endregion
33
+ export { buildLauncherSource as a, buildGhosttyConfig as i, appBundlePath as n, bundleId as o, buildCommand as r, GhosttyConfigOptions as t };
34
+ //# sourceMappingURL=ghostty-DemKkfqf.d.ts.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { a as buildIndex, i as buildAliasFiles, n as InitFile, o as buildWrapper, r as aliasName, t as AliasPaths } from "./aliases-DXyz-ufw.js";
2
+ import { a as HOME, c as configsDir, d as findConfigFile, f as listBoxes, h as resolveBox, i as Config, l as defaultConfig, m as mergeConfig, n as AppConfig, o as LoadedConfig, p as loadConfig, r as BotConfig, s as PathRules, t as AppBuilderConfig, u as expandHome } from "./config-DQWueb4a.js";
3
+ import { i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "./app-CpuMtOoj.js";
4
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "./ghostty-DemKkfqf.js";
5
+ import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "./raycast-DM7c559f.js";
6
+ import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "./scaffold-ByIbYAeS.js";
7
+ import { a as ipcName, c as regex, i as globalName, l as subpath, n as buildProfile, o as literal, r as detectPackagePaths, s as reEscape, t as ProfileContext } from "./profile-BeM41NXc.js";
8
+ import { a as resolveProjectDir, i as profilePath, o as runClaude, r as generateProfile, t as RunOptions } from "./run-Cx8cuTh5.js";
9
+ export { type AliasPaths, type AppBuilderConfig, type AppConfig, type BotConfig, type BuildAppOptions, type BuildAppResult, type BuiltApp, type Config, type GhosttyConfigOptions, HOME, type InitFile, type InitOptions, type InitResult, type LoadedConfig, type PathRules, type ProfileContext, type RaycastCommandOptions, type RunOptions, aliasName, appBundlePath, buildAliasFiles, buildApp, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
package/lib/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { a as findConfigFile, c as mergeConfig, i as expandHome, l as resolveBox, n as configsDir, o as listBoxes, r as defaultConfig, s as loadConfig, t as HOME } from "./config-BQ44iVWT.js";
2
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "./aliases-DKGcMHHe.js";
3
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "./ghostty-Ca0g9P9P.js";
4
+ import { n as canBuildApps, t as buildApp } from "./app-CieBa29D.js";
5
+ import { n as raycastIcon, t as buildRaycastCommand } from "./raycast-BCdO2Se1.js";
6
+ import { n as runInit, t as discoverProfiles } from "./scaffold-CRzC5KYe.js";
7
+ import { a as literal, c as subpath, i as ipcName, n as detectPackagePaths, o as reEscape, r as globalName, s as regex, t as buildProfile } from "./profile-DM6NAgb-.js";
8
+ import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-CNehSQ-S.js";
9
+ export { HOME, aliasName, appBundlePath, buildAliasFiles, buildApp, buildCommand, buildGhosttyConfig, buildIndex, buildLauncherSource, buildProfile, buildRaycastCommand, buildWrapper, bundleId, canBuildApps, configsDir, defaultConfig, detectPackagePaths, discoverProfiles, expandHome, findConfigFile, generateProfile, globalName, ipcName, listBoxes, literal, loadConfig, mergeConfig, profilePath, raycastIcon, reEscape, regex, resolveBox, resolveProjectDir, runClaude, runInit, subpath };
@@ -0,0 +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";
2
+ export { AliasPaths, InitFile, aliasName, buildAliasFiles, buildIndex, buildWrapper };
@@ -0,0 +1,2 @@
1
+ import { i as buildWrapper, n as buildAliasFiles, r as buildIndex, t as aliasName } from "../aliases-DKGcMHHe.js";
2
+ export { aliasName, buildAliasFiles, buildIndex, buildWrapper };
@@ -0,0 +1,2 @@
1
+ import { i as canBuildApps, n as BuildAppResult, r as buildApp, t as BuildAppOptions } from "../app-CpuMtOoj.js";
2
+ export { BuildAppOptions, BuildAppResult, buildApp, canBuildApps };
@@ -0,0 +1,2 @@
1
+ import { n as canBuildApps, t as buildApp } from "../app-CieBa29D.js";
2
+ export { buildApp, canBuildApps };
@@ -0,0 +1,2 @@
1
+ import { a as buildLauncherSource, i as buildGhosttyConfig, n as appBundlePath, o as bundleId, r as buildCommand, t as GhosttyConfigOptions } from "../ghostty-DemKkfqf.js";
2
+ export { GhosttyConfigOptions, appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -0,0 +1,2 @@
1
+ import { a as bundleId, i as buildLauncherSource, n as buildCommand, r as buildGhosttyConfig, t as appBundlePath } from "../ghostty-Ca0g9P9P.js";
2
+ export { appBundlePath, buildCommand, buildGhosttyConfig, buildLauncherSource, bundleId };
@@ -0,0 +1,2 @@
1
+ import { n as buildRaycastCommand, r as raycastIcon, t as RaycastCommandOptions } from "../raycast-DM7c559f.js";
2
+ export { RaycastCommandOptions, buildRaycastCommand, raycastIcon };
@@ -0,0 +1,2 @@
1
+ import { n as raycastIcon, t as buildRaycastCommand } from "../raycast-BCdO2Se1.js";
2
+ export { buildRaycastCommand, raycastIcon };
@@ -0,0 +1,2 @@
1
+ import { a as runInit, i as discoverProfiles, n as InitOptions, r as InitResult, t as BuiltApp } from "../scaffold-ByIbYAeS.js";
2
+ export { BuiltApp, InitOptions, InitResult, discoverProfiles, runInit };
@@ -0,0 +1,2 @@
1
+ import { n as runInit, t as discoverProfiles } from "../scaffold-CRzC5KYe.js";
2
+ export { discoverProfiles, runInit };
@@ -0,0 +1,29 @@
1
+ import { i as Config } from "./config-DQWueb4a.js";
2
+
3
+ //#region src/sandbox/profile.d.ts
4
+ declare const subpath: (p: string) => string;
5
+ declare const literal: (p: string) => string;
6
+ declare const regex: (p: string) => string;
7
+ declare const globalName: (n: string) => string;
8
+ declare const ipcName: (n: string) => string;
9
+ /** Escape a path for safe embedding inside an SBPL regex. */
10
+ declare const reEscape: (s: string) => string;
11
+ /** Detect installed package managers whose paths must be readable/executable. */
12
+ declare function detectPackagePaths(): string[];
13
+ /** Context needed to assemble a profile for a specific project. */
14
+ interface ProfileContext {
15
+ projectDir: string;
16
+ detectedPaths?: string[];
17
+ }
18
+ /**
19
+ * Build the full SBPL profile text.
20
+ * @param config effective config (see config.ts)
21
+ * @param ctx { projectDir, detectedPaths }
22
+ */
23
+ declare function buildProfile(config: Config, {
24
+ projectDir,
25
+ detectedPaths
26
+ }: ProfileContext): string;
27
+ //#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-BeM41NXc.d.ts.map
@@ -0,0 +1,100 @@
1
+ import { i as expandHome, t as HOME } from "./config-BQ44iVWT.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ //#region src/sandbox/profile.ts
5
+ const q = (s) => `"${String(s).replace(/"/g, "\\\"")}"`;
6
+ const subpath = (p) => `(subpath ${q(p)})`;
7
+ const literal = (p) => `(literal ${q(p)})`;
8
+ const regex = (p) => `(regex ${q(p)})`;
9
+ const globalName = (n) => `(global-name ${q(n)})`;
10
+ const ipcName = (n) => `(ipc-posix-name ${q(n)})`;
11
+ /** Escape a path for safe embedding inside an SBPL regex. */
12
+ const reEscape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
+ function block(op, rules) {
14
+ return [
15
+ `(${op}`,
16
+ ...rules.map((r) => ` ${r}`),
17
+ ")"
18
+ ].join("\n");
19
+ }
20
+ const allow = (op, ...rules) => block(`allow ${op}`, rules);
21
+ const deny = (op, ...rules) => block(`deny ${op}`, rules);
22
+ /** Detect installed package managers whose paths must be readable/executable. */
23
+ function detectPackagePaths() {
24
+ const paths = [];
25
+ if (fs.existsSync("/opt/homebrew")) paths.push("/opt/homebrew");
26
+ else if (fs.existsSync("/usr/local/Homebrew")) paths.push("/usr/local/Homebrew");
27
+ const local = path.join(HOME, ".local");
28
+ if (fs.existsSync(local)) paths.push(local);
29
+ if (fs.existsSync("/nix/store")) paths.push("/nix/store");
30
+ return paths;
31
+ }
32
+ /**
33
+ * Build the full SBPL profile text.
34
+ * @param config effective config (see config.ts)
35
+ * @param ctx { projectDir, detectedPaths }
36
+ */
37
+ function buildProfile(config, { projectDir, detectedPaths = detectPackagePaths() }) {
38
+ const configDir = expandHome(config.configDir);
39
+ const sshDir = expandHome(config.bot.sshDir);
40
+ const hooksDir = config.hooksDir ? expandHome(config.hooksDir) : null;
41
+ const homeRe = reEscape(HOME);
42
+ const sections = [];
43
+ const add = (comment, body) => sections.push(`;; ---------- ${comment}\n${body}`);
44
+ sections.push([
45
+ ";; ------------------------------------------------------------------",
46
+ ";; Claude Code macOS sandbox profile (autogenerated)",
47
+ ";; ------------------------------------------------------------------",
48
+ "(version 1)",
49
+ "(deny default)"
50
+ ].join("\n"));
51
+ add("introspection & sysctl", "(allow file-read-metadata)\n(allow sysctl-read)");
52
+ add("basic dir traversal", [
53
+ allow("file-read*", literal("/")),
54
+ allow("file-read*", literal("/private")),
55
+ allow("file-read-data", literal("/Users")),
56
+ allow("file-read-data", literal(HOME))
57
+ ].join("\n"));
58
+ add("system runtime (read-only)", allow("file-read* file-map-executable", subpath("/System"), subpath("/usr"), subpath("/bin"), subpath("/sbin"), subpath("/Library/Frameworks"), subpath("/private/etc"), subpath("/var/db/dyld"), ...detectedPaths.map(subpath)));
59
+ add("Xcode / Command Line Tools (xcrun, git, etc.)", [allow("file-read* file-map-executable", subpath("/Library/Developer/CommandLineTools"), subpath("/Applications/Xcode.app")), allow("process-exec", subpath("/Library/Developer/CommandLineTools"), subpath("/Applications/Xcode.app"))].join("\n"));
60
+ const userPaths = detectedPaths.filter((p) => p.endsWith("/.local"));
61
+ add("global npm/pipx/cargo bins", userPaths.length ? userPaths.map((p) => allow("file-read*", subpath(p))).join("\n") : ";; No user package paths detected");
62
+ add("executable paths", allow("process-exec", subpath("/usr"), subpath("/System"), subpath("/bin"), subpath("/sbin"), literal("/usr/bin/env"), ...detectedPaths.map(subpath)));
63
+ add("temp dirs", allow("file-read* file-write*", subpath("/tmp"), subpath("/private/tmp"), regex("^/private/var/folders/")));
64
+ add("Claude config & token files", allow("file-read* file-write*", subpath(configDir)));
65
+ add("Claude auto-update (RO) -- suppress warnings", allow("file-read*", subpath(path.join(HOME, ".local/state/claude")), subpath(path.join(HOME, ".cache/claude"))));
66
+ add("time-zone & prefs (RO)", allow("file-read*", subpath("/private/var/db/timezone"), subpath("/Library/Preferences")));
67
+ add("/dev access (RO) + ioctl", [
68
+ allow("file-read*", literal("/dev")),
69
+ allow("file-read* file-write*", regex("^/dev/(tty.*|null|zero|dtracehelper)")),
70
+ allow("file-ioctl", literal("/dev/dtracehelper"), regex("^/dev/tty.*"))
71
+ ].join("\n"));
72
+ add("mach-lookup services", allow("mach-lookup", globalName("com.apple.system.opendirectoryd.libinfo"), globalName("com.apple.SystemConfiguration.DNSConfiguration"), globalName("com.apple.coreservices.launchservicesd"), globalName("com.apple.CoreServices.coreservicesd"), globalName("com.apple.system.notification_center"), globalName("com.apple.logd"), globalName("com.apple.diagnosticd"), globalName("com.apple.lsd.mapdb"), globalName("com.apple.lsd.modifydb"), globalName("com.apple.coreservices.quarantine-resolver"), globalName("com.apple.pasteboard.pboard"), globalName("com.apple.pasteboard.1")));
73
+ add("Launch Services needed by /usr/bin/open", allow("mach-lookup", regex("^com\\.apple\\.lsd(\\..*)?$")));
74
+ add("Developer Tools (xcrun / libxcrun)", allow("mach-lookup", globalName("com.apple.dt.xcsecurity"), regex("^com\\.apple\\.dt\\..*$")));
75
+ add("Audio (afplay)", allow("mach-lookup", globalName("com.apple.audio.audiohald"), globalName("com.apple.audio.AudioComponentRegistrar")));
76
+ add("Notification Center shared-memory (RO)", allow("ipc-posix-shm-read-data", ipcName("apple.shm.notification_center")));
77
+ add("User-level preference reads (RO)", allow("file-read*", subpath(path.join(HOME, "Library/Preferences"))));
78
+ 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
+ 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
+ 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
+ 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
+ if (config.paths.readOnly.length) add("extra read-only paths", allow("file-read*", ...config.paths.readOnly.map((p) => subpath(expandHome(p)))));
84
+ if (config.paths.readWrite.length) add("extra read-write paths", allow("file-read* file-write*", ...config.paths.readWrite.map((p) => subpath(expandHome(p)))));
85
+ if (config.paths.exec.length) add("extra exec paths", allow("process-exec", ...config.paths.exec.map((p) => subpath(expandHome(p)))));
86
+ add("project workspace (RW)", [allow("file-read* file-write* file-map-executable", subpath(projectDir)), allow("process-exec", subpath(projectDir))].join("\n"));
87
+ const hardDeny = [];
88
+ if (config.denyDotConfigs.length) hardDeny.push(regex(`^${homeRe}/\\.(${config.denyDotConfigs.join("|")})($|/)`));
89
+ hardDeny.push(regex(`^${homeRe}/\\.ssh/id_`), regex(`^${homeRe}/\\.ssh/.*\\.pem$`), regex(`^${homeRe}/\\.ssh/.*\\.key$`));
90
+ add("hard secret DENY (always wins — credentials & private keys)", deny("file-read* file-write*", ...hardDeny));
91
+ if (config.network) add("networking", "(allow network*)");
92
+ sections.push("(allow process-fork)\n(allow lsopen)");
93
+ const text = `${sections.join("\n\n")}\n`;
94
+ if (!/^\(version 1\)/m.test(text)) throw new Error("generated sandbox profile is missing \"(version 1)\"");
95
+ return text;
96
+ }
97
+ //#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 };
99
+
100
+ //# sourceMappingURL=profile-DM6NAgb-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-DM6NAgb-.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, 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/** 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 hooksDir = config.hooksDir ? expandHome(config.hooksDir) : null;\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 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 add(\n 'claude hooks (RO + exec)',\n hooksDir && fs.existsSync(hooksDir)\n ? [\n allow('file-read* file-map-executable', subpath(hooksDir)),\n allow('process-exec', subpath(hooksDir)),\n ].join('\\n')\n : ';; (no hooks dir; set config.hooksDir / CLABOX_HOOKS_DIR to enable)',\n );\n\n // Extra user-supplied RO / RW / exec rules.\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 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":";;;;AAcA,MAAM,KAAK,MAAsB,IAAI,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAK,EAAE;AAEpE,MAAa,WAAW,MAAsB,YAAY,EAAE,CAAC,EAAE;AAC/D,MAAa,WAAW,MAAsB,YAAY,EAAE,CAAC,EAAE;AAC/D,MAAa,SAAS,MAAsB,UAAU,EAAE,CAAC,EAAE;AAC3D,MAAa,cAAc,MAAsB,gBAAgB,EAAE,CAAC,EAAE;AACtE,MAAa,WAAW,MAAsB,mBAAmB,EAAE,CAAC,EAAE;;AAGtE,MAAa,YAAY,MAAsB,EAAE,QAAQ,uBAAuB,MAAM;AAEtF,SAAS,MAAM,IAAY,OAAyB;CAClD,OAAO;EAAC,IAAI;EAAM,GAAG,MAAM,KAAK,MAAM,KAAK,GAAG;EAAG;CAAG,CAAC,CAAC,KAAK,IAAI;AACjE;AACA,MAAM,SAAS,IAAY,GAAG,UAA4B,MAAM,SAAS,MAAM,KAAK;AACpF,MAAM,QAAQ,IAAY,GAAG,UAA4B,MAAM,QAAQ,MAAM,KAAK;;AAKlF,SAAgB,qBAA+B;CAC7C,MAAM,QAAkB,CAAC;CACzB,IAAI,GAAG,WAAW,eAAe,GAAG,MAAM,KAAK,eAAe;MACzD,IAAI,GAAG,WAAW,qBAAqB,GAAG,MAAM,KAAK,qBAAqB;CAC/E,MAAM,QAAQ,KAAK,KAAK,MAAM,QAAQ;CACtC,IAAI,GAAG,WAAW,KAAK,GAAG,MAAM,KAAK,KAAK;CAC1C,IAAI,GAAG,WAAW,YAAY,GAAG,MAAM,KAAK,YAAY;CACxD,OAAO;AACT;;;;;;AAaA,SAAgB,aACd,QACA,EAAE,YAAY,gBAAgB,mBAAmB,KACzC;CACR,MAAM,YAAY,WAAW,OAAO,SAAS;CAC7C,MAAM,SAAS,WAAW,OAAO,IAAI,MAAM;CAC3C,MAAM,WAAW,OAAO,WAAW,WAAW,OAAO,QAAQ,IAAI;CACjE,MAAM,SAAS,SAAS,IAAI;CAE5B,MAAM,WAAqB,CAAC;CAC5B,MAAM,OAAO,SAAiB,SAAiB,SAAS,KAAK,iBAAiB,QAAQ,IAAI,MAAM;CAEhG,SAAS,KACP;EACE;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,IAAI,0BAA0B,iDAAiD;CAE/E,IACE,uBACA;EACE,MAAM,cAAc,QAAQ,GAAG,CAAC;EAChC,MAAM,cAAc,QAAQ,UAAU,CAAC;EACvC,MAAM,kBAAkB,QAAQ,QAAQ,CAAC;EACzC,MAAM,kBAAkB,QAAQ,IAAI,CAAC;CACvC,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,IACE,8BACA,MACE,kCACA,QAAQ,SAAS,GACjB,QAAQ,MAAM,GACd,QAAQ,MAAM,GACd,QAAQ,OAAO,GACf,QAAQ,qBAAqB,GAC7B,QAAQ,cAAc,GACtB,QAAQ,cAAc,GACtB,GAAG,cAAc,IAAI,OAAO,CAC9B,CACF;CAEA,IACE,iDACA,CACE,MACE,kCACA,QAAQ,qCAAqC,GAC7C,QAAQ,yBAAyB,CACnC,GACA,MACE,gBACA,QAAQ,qCAAqC,GAC7C,QAAQ,yBAAyB,CACnC,CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAGA,MAAM,YAAY,cAAc,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC;CACnE,IACE,8BACA,UAAU,SACN,UAAU,KAAK,MAAM,MAAM,cAAc,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAC/D,mCACN;CAEA,IACE,oBACA,MACE,gBACA,QAAQ,MAAM,GACd,QAAQ,SAAS,GACjB,QAAQ,MAAM,GACd,QAAQ,OAAO,GACf,QAAQ,cAAc,GACtB,GAAG,cAAc,IAAI,OAAO,CAC9B,CACF;CAEA,IACE,aACA,MACE,0BACA,QAAQ,MAAM,GACd,QAAQ,cAAc,GACtB,MAAM,wBAAwB,CAChC,CACF;CAEA,IAAI,+BAA+B,MAAM,0BAA0B,QAAQ,SAAS,CAAC,CAAC;CAEtF,IACE,gDACA,MACE,cACA,QAAQ,KAAK,KAAK,MAAM,qBAAqB,CAAC,GAC9C,QAAQ,KAAK,KAAK,MAAM,eAAe,CAAC,CAC1C,CACF;CAEA,IACE,0BACA,MAAM,cAAc,QAAQ,0BAA0B,GAAG,QAAQ,sBAAsB,CAAC,CAC1F;CAEA,IACE,4BACA;EACE,MAAM,cAAc,QAAQ,MAAM,CAAC;EACnC,MAAM,0BAA0B,MAAM,sCAAsC,CAAC;EAC7E,MAAM,cAAc,QAAQ,mBAAmB,GAAG,MAAM,aAAa,CAAC;CACxE,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,IACE,wBACA,MACE,eACA,WAAW,yCAAyC,GACpD,WAAW,gDAAgD,GAC3D,WAAW,wCAAwC,GACnD,WAAW,sCAAsC,GACjD,WAAW,sCAAsC,GACjD,WAAW,gBAAgB,GAC3B,WAAW,uBAAuB,GAClC,WAAW,qBAAqB,GAChC,WAAW,wBAAwB,GACnC,WAAW,4CAA4C,GACvD,WAAW,6BAA6B,GACxC,WAAW,wBAAwB,CACrC,CACF;CAEA,IACE,2CACA,MAAM,eAAe,MAAM,6BAA6B,CAAC,CAC3D;CAEA,IACE,sCACA,MAAM,eAAe,WAAW,yBAAyB,GAAG,MAAM,yBAAyB,CAAC,CAC9F;CAEA,IACE,kBACA,MACE,eACA,WAAW,2BAA2B,GACtC,WAAW,yCAAyC,CACtD,CACF;CAEA,IACE,0CACA,MAAM,2BAA2B,QAAQ,+BAA+B,CAAC,CAC3E;CAEA,IACE,oCACA,MAAM,cAAc,QAAQ,KAAK,KAAK,MAAM,qBAAqB,CAAC,CAAC,CACrE;CAGA,IACE,+BACA,CACE,MAAM,0BAA0B,QAAQ,KAAK,KAAK,MAAM,mBAAmB,CAAC,CAAC,GAC7E,MACE,eACA,WAAW,0BAA0B,GACrC,WAAW,0BAA0B,GACrC,WAAW,qBAAqB,GAChC,WAAW,gBAAgB,GAC3B,WAAW,kBAAkB,GAC7B,WAAW,wBAAwB,GACnC,WAAW,qCAAqC,CAClD,CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,IACE,mBACA,MACE,cACA,QAAQ,KAAK,KAAK,MAAM,YAAY,CAAC,GACrC,QAAQ,KAAK,KAAK,MAAM,mBAAmB,CAAC,GAC5C,QAAQ,KAAK,KAAK,MAAM,aAAa,CAAC,CACxC,CACF;CASA,IACE,2DACA,KAAK,0BAA0B,GAAG,CALlC,GAAG,OAAO,SAAS,KAAK,MAAM,QAAQ,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,GACzD,GAAG,OAAO,MAAM,KAAK,KAAK,MAAM,QAAQ,WAAW,CAAC,CAAC,CAAC,CAIb,CAAC,CAC5C;CAEA,IACE,0EACA,CACE,MACE,cACA,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,GAC/B,QAAQ,KAAK,KAAK,MAAM,kBAAkB,CAAC,GAC3C,QAAQ,KAAK,KAAK,MAAM,mBAAmB,CAAC,GAC5C,QAAQ,KAAK,KAAK,MAAM,aAAa,CAAC,GACtC,QAAQ,MAAM,CAChB,GACA,MACE,eACA,QAAQ,KAAK,KAAK,MAAM,kBAAkB,CAAC,GAC3C,QAAQ,KAAK,KAAK,MAAM,mBAAmB,CAAC,CAC9C,CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,IACE,4BACA,YAAY,GAAG,WAAW,QAAQ,IAC9B,CACE,MAAM,kCAAkC,QAAQ,QAAQ,CAAC,GACzD,MAAM,gBAAgB,QAAQ,QAAQ,CAAC,CACzC,CAAC,CAAC,KAAK,IAAI,IACX,qEACN;CAGA,IAAI,OAAO,MAAM,SAAS,QACxB,IACE,yBACA,MAAM,cAAc,GAAG,OAAO,MAAM,SAAS,KAAK,MAAM,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,CACjF;CACF,IAAI,OAAO,MAAM,UAAU,QACzB,IACE,0BACA,MAAM,0BAA0B,GAAG,OAAO,MAAM,UAAU,KAAK,MAAM,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,CAC9F;CACF,IAAI,OAAO,MAAM,KAAK,QACpB,IACE,oBACA,MAAM,gBAAgB,GAAG,OAAO,MAAM,KAAK,KAAK,MAAM,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,CAC/E;CAEF,IACE,0BACA,CACE,MAAM,8CAA8C,QAAQ,UAAU,CAAC,GACvE,MAAM,gBAAgB,QAAQ,UAAU,CAAC,CAC3C,CAAC,CAAC,KAAK,IAAI,CACb;CAOA,MAAM,WAAqB,CAAC;CAC5B,IAAI,OAAO,eAAe,QACxB,SAAS,KAAK,MAAM,IAAI,OAAO,OAAO,OAAO,eAAe,KAAK,GAAG,EAAE,OAAO,CAAC;CAEhF,SAAS,KACP,MAAM,IAAI,OAAO,YAAY,GAC7B,MAAM,IAAI,OAAO,kBAAkB,GACnC,MAAM,IAAI,OAAO,kBAAkB,CACrC;CACA,IACE,+DACA,KAAK,0BAA0B,GAAG,QAAQ,CAC5C;CAEA,IAAI,OAAO,SAAS,IAAI,cAAc,kBAAkB;CAExD,SAAS,KAAK,sCAAsC;CAEpD,MAAM,OAAO,GAAG,SAAS,KAAK,MAAM,EAAE;CAGtC,IAAI,CAAC,kBAAkB,KAAK,IAAI,GAC9B,MAAM,IAAI,MAAM,sDAAoD;CAEtE,OAAO;AACT"}
@@ -0,0 +1,35 @@
1
+ //#region src/init/raycast.ts
2
+ /** Single-quote a string for safe use in a POSIX shell. */
3
+ function shellQuote(s) {
4
+ return `'${s.replace(/'/g, `'\\''`)}'`;
5
+ }
6
+ /**
7
+ * The Raycast command icon: explicit `app.emoji`, else the title's leading
8
+ * token when it isn't plain ASCII (i.e. an emoji), else the Ghostty 👻.
9
+ */
10
+ function raycastIcon(app) {
11
+ if (app.emoji) return app.emoji;
12
+ const lead = (app.title ?? "").split(" ")[0];
13
+ if (lead && !/^[\x00-\x7F]*$/.test(lead)) return lead;
14
+ return "👻";
15
+ }
16
+ /** Build a Raycast script command that opens the box's built app. */
17
+ function buildRaycastCommand({ app, appPath }) {
18
+ return `${[
19
+ "#!/bin/bash",
20
+ "# Generated by `clabox init`.",
21
+ "",
22
+ "# @raycast.schemaVersion 1",
23
+ `# @raycast.title ${app.title ?? app.name}`,
24
+ "# @raycast.mode silent",
25
+ `# @raycast.icon ${raycastIcon(app)}`,
26
+ "# @raycast.packageName Ghostty",
27
+ `# @raycast.description Open ${app.name} with Claude Code in Ghostty (clabox)`,
28
+ "",
29
+ `open ${shellQuote(appPath)}`
30
+ ].join("\n")}\n`;
31
+ }
32
+ //#endregion
33
+ export { raycastIcon as n, buildRaycastCommand as t };
34
+
35
+ //# sourceMappingURL=raycast-BCdO2Se1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raycast-BCdO2Se1.js","names":[],"sources":["../src/init/raycast.ts"],"sourcesContent":["// Pure builder for the Raycast script commands emitted by `clabox init`.\n//\n// For each `app` box, `init` writes a Raycast script command (`@raycast.*`\n// metadata + `open <app>`) so the built bundle is launchable from Raycast —\n// modelled on the hand-written ghostty-*.sh examples. Text only; the fs I/O\n// lives in init/scaffold.ts.\n\nimport type { AppConfig } from '../utils/config.js';\n\n/** Inputs for {@link buildRaycastCommand}. */\nexport interface RaycastCommandOptions {\n app: AppConfig;\n /** Absolute path to the built `.app` to open. */\n appPath: string;\n}\n\n/** Single-quote a string for safe use in a POSIX shell. */\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\n/**\n * The Raycast command icon: explicit `app.emoji`, else the title's leading\n * token when it isn't plain ASCII (i.e. an emoji), else the Ghostty 👻.\n */\nexport function raycastIcon(app: AppConfig): string {\n if (app.emoji) return app.emoji;\n const lead = (app.title ?? '').split(' ')[0];\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ASCII-range guard\n if (lead && !/^[\\x00-\\x7F]*$/.test(lead)) return lead;\n return '👻';\n}\n\n/** Build a Raycast script command that opens the box's built app. */\nexport function buildRaycastCommand({ app, appPath }: RaycastCommandOptions): string {\n const title = app.title ?? app.name;\n return `${[\n '#!/bin/bash',\n '# Generated by `clabox init`.',\n '',\n '# @raycast.schemaVersion 1',\n `# @raycast.title ${title}`,\n '# @raycast.mode silent',\n `# @raycast.icon ${raycastIcon(app)}`,\n '# @raycast.packageName Ghostty',\n `# @raycast.description Open ${app.name} with Claude Code in Ghostty (clabox)`,\n '',\n `open ${shellQuote(appPath)}`,\n ].join('\\n')}\\n`;\n}\n"],"mappings":";;AAiBA,SAAS,WAAW,GAAmB;CACrC,OAAO,IAAI,EAAE,QAAQ,MAAM,OAAO,EAAE;AACtC;;;;;AAMA,SAAgB,YAAY,KAAwB;CAClD,IAAI,IAAI,OAAO,OAAO,IAAI;CAC1B,MAAM,QAAQ,IAAI,SAAS,GAAA,CAAI,MAAM,GAAG,CAAC,CAAC;CAE1C,IAAI,QAAQ,CAAC,iBAAiB,KAAK,IAAI,GAAG,OAAO;CACjD,OAAO;AACT;;AAGA,SAAgB,oBAAoB,EAAE,KAAK,WAA0C;CAEnF,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA,oBANY,IAAI,SAAS,IAAI;EAO7B;EACA,mBAAmB,YAAY,GAAG;EAClC;EACA,+BAA+B,IAAI,KAAK;EACxC;EACA,QAAQ,WAAW,OAAO;CAC5B,CAAC,CAAC,KAAK,IAAI,EAAE;AACf"}
@@ -0,0 +1,22 @@
1
+ import { n as AppConfig } from "./config-DQWueb4a.js";
2
+
3
+ //#region src/init/raycast.d.ts
4
+ /** Inputs for {@link buildRaycastCommand}. */
5
+ interface RaycastCommandOptions {
6
+ app: AppConfig;
7
+ /** Absolute path to the built `.app` to open. */
8
+ appPath: string;
9
+ }
10
+ /**
11
+ * The Raycast command icon: explicit `app.emoji`, else the title's leading
12
+ * token when it isn't plain ASCII (i.e. an emoji), else the Ghostty 👻.
13
+ */
14
+ declare function raycastIcon(app: AppConfig): string;
15
+ /** Build a Raycast script command that opens the box's built app. */
16
+ declare function buildRaycastCommand({
17
+ app,
18
+ appPath
19
+ }: RaycastCommandOptions): string;
20
+ //#endregion
21
+ export { buildRaycastCommand as n, raycastIcon as r, RaycastCommandOptions as t };
22
+ //# sourceMappingURL=raycast-DM7c559f.d.ts.map