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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/clabox.config.example.mjs +90 -0
- package/docs/guideline.md +196 -0
- package/docs/logo.png +0 -0
- package/lib/aliases-DKGcMHHe.js +60 -0
- package/lib/aliases-DKGcMHHe.js.map +1 -0
- package/lib/aliases-DXyz-ufw.d.ts +31 -0
- package/lib/app-CieBa29D.js +246 -0
- package/lib/app-CieBa29D.js.map +1 -0
- package/lib/app-CpuMtOoj.d.ts +30 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +71 -0
- package/lib/cli.js.map +1 -0
- package/lib/config-BQ44iVWT.js +155 -0
- package/lib/config-BQ44iVWT.js.map +1 -0
- package/lib/config-DQWueb4a.d.ts +134 -0
- package/lib/ghostty-Ca0g9P9P.js +74 -0
- package/lib/ghostty-Ca0g9P9P.js.map +1 -0
- package/lib/ghostty-DemKkfqf.d.ts +34 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +9 -0
- package/lib/init/aliases.d.ts +2 -0
- package/lib/init/aliases.js +2 -0
- package/lib/init/app.d.ts +2 -0
- package/lib/init/app.js +2 -0
- package/lib/init/ghostty.d.ts +2 -0
- package/lib/init/ghostty.js +2 -0
- package/lib/init/raycast.d.ts +2 -0
- package/lib/init/raycast.js +2 -0
- package/lib/init/scaffold.d.ts +2 -0
- package/lib/init/scaffold.js +2 -0
- package/lib/profile-BeM41NXc.d.ts +29 -0
- package/lib/profile-DM6NAgb-.js +100 -0
- package/lib/profile-DM6NAgb-.js.map +1 -0
- package/lib/raycast-BCdO2Se1.js +35 -0
- package/lib/raycast-BCdO2Se1.js.map +1 -0
- package/lib/raycast-DM7c559f.d.ts +22 -0
- package/lib/run-CNehSQ-S.js +113 -0
- package/lib/run-CNehSQ-S.js.map +1 -0
- package/lib/run-Cx8cuTh5.d.ts +25 -0
- package/lib/sandbox/profile.d.ts +2 -0
- package/lib/sandbox/profile.js +2 -0
- package/lib/sandbox/run.d.ts +2 -0
- package/lib/sandbox/run.js +2 -0
- package/lib/scaffold-ByIbYAeS.d.ts +46 -0
- package/lib/scaffold-CRzC5KYe.js +141 -0
- package/lib/scaffold-CRzC5KYe.js.map +1 -0
- package/lib/utils/config.d.ts +2 -0
- package/lib/utils/config.js +2 -0
- package/package.json +141 -7
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { i as expandHome } from "./config-BQ44iVWT.js";
|
|
2
|
+
import { a as bundleId, i as buildLauncherSource, t as appBundlePath } from "./ghostty-Ca0g9P9P.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
//#region src/init/app.ts
|
|
8
|
+
function run(cmd, args) {
|
|
9
|
+
execFileSync(cmd, args, { stdio: [
|
|
10
|
+
"ignore",
|
|
11
|
+
"ignore",
|
|
12
|
+
"pipe"
|
|
13
|
+
] });
|
|
14
|
+
}
|
|
15
|
+
function has(bin) {
|
|
16
|
+
try {
|
|
17
|
+
execFileSync("command", ["-v", bin], {
|
|
18
|
+
shell: "/bin/sh",
|
|
19
|
+
stdio: "ignore"
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** True when the host can build apps (macOS with the donor app + a C compiler). */
|
|
27
|
+
function canBuildApps(builder) {
|
|
28
|
+
if (process.platform !== "darwin") return {
|
|
29
|
+
ok: false,
|
|
30
|
+
reason: "not macOS"
|
|
31
|
+
};
|
|
32
|
+
if (!fs.existsSync(expandHome(builder.ghosttyApp))) return {
|
|
33
|
+
ok: false,
|
|
34
|
+
reason: `Ghostty not found at ${builder.ghosttyApp}`
|
|
35
|
+
};
|
|
36
|
+
if (!has("cc")) return {
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: "no C compiler (cc) — install Xcode CLT"
|
|
39
|
+
};
|
|
40
|
+
return { ok: true };
|
|
41
|
+
}
|
|
42
|
+
/** Extract the donor app's entitlements to a tmp file, or null if it has none. */
|
|
43
|
+
function extractEntitlements(ghosttyApp, tmpDir) {
|
|
44
|
+
let xml;
|
|
45
|
+
try {
|
|
46
|
+
xml = execFileSync("codesign", [
|
|
47
|
+
"-d",
|
|
48
|
+
"--entitlements",
|
|
49
|
+
"-",
|
|
50
|
+
"--xml",
|
|
51
|
+
ghosttyApp
|
|
52
|
+
], {
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
stdio: [
|
|
55
|
+
"ignore",
|
|
56
|
+
"pipe",
|
|
57
|
+
"ignore"
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const start = xml.indexOf("<?xml");
|
|
64
|
+
if (start < 0) return null;
|
|
65
|
+
const file = path.join(tmpDir, "entitlements.xml");
|
|
66
|
+
fs.writeFileSync(file, xml.slice(start));
|
|
67
|
+
return file;
|
|
68
|
+
}
|
|
69
|
+
/** Name of the icon resource referenced by the bundle (default Ghostty.icns). */
|
|
70
|
+
function iconResourceName(plist) {
|
|
71
|
+
try {
|
|
72
|
+
const name = execFileSync("plutil", [
|
|
73
|
+
"-extract",
|
|
74
|
+
"CFBundleIconFile",
|
|
75
|
+
"raw",
|
|
76
|
+
plist
|
|
77
|
+
], {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
stdio: [
|
|
80
|
+
"ignore",
|
|
81
|
+
"pipe",
|
|
82
|
+
"ignore"
|
|
83
|
+
]
|
|
84
|
+
}).trim();
|
|
85
|
+
return name.endsWith(".icns") ? name : `${name}.icns`;
|
|
86
|
+
} catch {
|
|
87
|
+
return "Ghostty.icns";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Convert a PNG into a multi-resolution .icns at `out`. */
|
|
91
|
+
function pngToIcns(png, out, tmpDir) {
|
|
92
|
+
const iconset = path.join(tmpDir, "icon.iconset");
|
|
93
|
+
fs.mkdirSync(iconset, { recursive: true });
|
|
94
|
+
for (const size of [
|
|
95
|
+
16,
|
|
96
|
+
32,
|
|
97
|
+
128,
|
|
98
|
+
256,
|
|
99
|
+
512
|
|
100
|
+
]) {
|
|
101
|
+
run("sips", [
|
|
102
|
+
"-z",
|
|
103
|
+
`${size}`,
|
|
104
|
+
`${size}`,
|
|
105
|
+
png,
|
|
106
|
+
"--out",
|
|
107
|
+
path.join(iconset, `icon_${size}x${size}.png`)
|
|
108
|
+
]);
|
|
109
|
+
const d = size * 2;
|
|
110
|
+
run("sips", [
|
|
111
|
+
"-z",
|
|
112
|
+
`${d}`,
|
|
113
|
+
`${d}`,
|
|
114
|
+
png,
|
|
115
|
+
"--out",
|
|
116
|
+
path.join(iconset, `icon_${size}x${size}@2x.png`)
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
run("iconutil", [
|
|
120
|
+
"-c",
|
|
121
|
+
"icns",
|
|
122
|
+
iconset,
|
|
123
|
+
"-o",
|
|
124
|
+
out
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
/** Install the box icon into the cloned bundle, if `app.icon` is set. */
|
|
128
|
+
function installIcon(app, appPath, tmpDir) {
|
|
129
|
+
if (!app.icon) return;
|
|
130
|
+
const icon = expandHome(app.icon);
|
|
131
|
+
if (!fs.existsSync(icon)) throw new Error(`icon not found: ${app.icon}`);
|
|
132
|
+
const dest = path.join(appPath, "Contents", "Resources", iconResourceName(path.join(appPath, "Contents", "Info.plist")));
|
|
133
|
+
if (icon.endsWith(".icns")) fs.copyFileSync(icon, dest);
|
|
134
|
+
else if (icon.endsWith(".png")) pngToIcns(icon, dest, tmpDir);
|
|
135
|
+
else throw new Error(`unsupported icon type (need .icns/.png): ${app.icon}`);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build the standalone Ghostty app for a box. Throws on any failure (the caller
|
|
139
|
+
* decides whether to abort or carry on with the other boxes).
|
|
140
|
+
*/
|
|
141
|
+
function buildApp(opts) {
|
|
142
|
+
const { app, builder, boxName, configPath } = opts;
|
|
143
|
+
const check = canBuildApps(builder);
|
|
144
|
+
if (!check.ok) throw new Error(`cannot build app: ${check.reason}`);
|
|
145
|
+
const ghosttyApp = expandHome(builder.ghosttyApp);
|
|
146
|
+
const appsDir = expandHome(builder.appsDir);
|
|
147
|
+
const appPath = appBundlePath(appsDir, app);
|
|
148
|
+
const plist = path.join(appPath, "Contents", "Info.plist");
|
|
149
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clabox-app-"));
|
|
150
|
+
try {
|
|
151
|
+
const entitlements = extractEntitlements(ghosttyApp, tmpDir);
|
|
152
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
153
|
+
fs.rmSync(appPath, {
|
|
154
|
+
recursive: true,
|
|
155
|
+
force: true
|
|
156
|
+
});
|
|
157
|
+
run("cp", [
|
|
158
|
+
"-R",
|
|
159
|
+
ghosttyApp,
|
|
160
|
+
appPath
|
|
161
|
+
]);
|
|
162
|
+
run("plutil", [
|
|
163
|
+
"-replace",
|
|
164
|
+
"CFBundleIdentifier",
|
|
165
|
+
"-string",
|
|
166
|
+
bundleId(boxName, app),
|
|
167
|
+
plist
|
|
168
|
+
]);
|
|
169
|
+
run("plutil", [
|
|
170
|
+
"-replace",
|
|
171
|
+
"CFBundleName",
|
|
172
|
+
"-string",
|
|
173
|
+
app.name,
|
|
174
|
+
plist
|
|
175
|
+
]);
|
|
176
|
+
run("plutil", [
|
|
177
|
+
"-replace",
|
|
178
|
+
"CFBundleDisplayName",
|
|
179
|
+
"-string",
|
|
180
|
+
app.name,
|
|
181
|
+
plist
|
|
182
|
+
]);
|
|
183
|
+
run("plutil", [
|
|
184
|
+
"-replace",
|
|
185
|
+
"CFBundleExecutable",
|
|
186
|
+
"-string",
|
|
187
|
+
"ghostty",
|
|
188
|
+
plist
|
|
189
|
+
]);
|
|
190
|
+
run("plutil", [
|
|
191
|
+
"-replace",
|
|
192
|
+
"SUEnableAutomaticChecks",
|
|
193
|
+
"-bool",
|
|
194
|
+
"NO",
|
|
195
|
+
plist
|
|
196
|
+
]);
|
|
197
|
+
try {
|
|
198
|
+
run("plutil", [
|
|
199
|
+
"-replace",
|
|
200
|
+
"SUFeedURL",
|
|
201
|
+
"-string",
|
|
202
|
+
"",
|
|
203
|
+
plist
|
|
204
|
+
]);
|
|
205
|
+
} catch {}
|
|
206
|
+
const bin = path.join(appPath, "Contents", "MacOS", "ghostty");
|
|
207
|
+
fs.renameSync(bin, `${bin}.real`);
|
|
208
|
+
const src = path.join(tmpDir, "launcher.c");
|
|
209
|
+
fs.writeFileSync(src, buildLauncherSource(configPath));
|
|
210
|
+
run("cc", [
|
|
211
|
+
"-o",
|
|
212
|
+
bin,
|
|
213
|
+
src
|
|
214
|
+
]);
|
|
215
|
+
installIcon(app, appPath, tmpDir);
|
|
216
|
+
const signId = builder.signId;
|
|
217
|
+
const entArgs = entitlements ? ["--entitlements", entitlements] : [];
|
|
218
|
+
const idArgs = signId ? ["--sign", signId] : ["--sign", "-"];
|
|
219
|
+
run("codesign", [
|
|
220
|
+
"--force",
|
|
221
|
+
...idArgs,
|
|
222
|
+
...entArgs,
|
|
223
|
+
`${bin}.real`
|
|
224
|
+
]);
|
|
225
|
+
run("codesign", [
|
|
226
|
+
"--force",
|
|
227
|
+
"--deep",
|
|
228
|
+
...idArgs,
|
|
229
|
+
...entArgs,
|
|
230
|
+
appPath
|
|
231
|
+
]);
|
|
232
|
+
return {
|
|
233
|
+
appPath,
|
|
234
|
+
signed: signId ? "identity" : "adhoc"
|
|
235
|
+
};
|
|
236
|
+
} finally {
|
|
237
|
+
fs.rmSync(tmpDir, {
|
|
238
|
+
recursive: true,
|
|
239
|
+
force: true
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
export { canBuildApps as n, buildApp as t };
|
|
245
|
+
|
|
246
|
+
//# sourceMappingURL=app-CieBa29D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-CieBa29D.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. */\nfunction 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 dest = path.join(\n appPath,\n 'Contents',\n 'Resources',\n iconResourceName(path.join(appPath, 'Contents', 'Info.plist')),\n );\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\n/**\n * Build the standalone Ghostty app for a box. Throws on any failure (the caller\n * decides whether to abort or carry on with the other boxes).\n */\nexport function buildApp(opts: BuildAppOptions): BuildAppResult {\n const { app, builder, boxName, configPath } = opts;\n const check = canBuildApps(builder);\n if (!check.ok) throw new Error(`cannot build app: ${check.reason}`);\n\n const ghosttyApp = expandHome(builder.ghosttyApp);\n const appsDir = expandHome(builder.appsDir);\n const appPath = appBundlePath(appsDir, app);\n const plist = path.join(appPath, 'Contents', 'Info.plist');\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'clabox-app-'));\n\n try {\n const entitlements = extractEntitlements(ghosttyApp, tmpDir);\n\n // Full clone (cp -R keeps bundle symlinks/frameworks intact).\n fs.mkdirSync(appsDir, { recursive: true });\n fs.rmSync(appPath, { recursive: true, force: true });\n run('cp', ['-R', ghosttyApp, appPath]);\n\n // Identity.\n run('plutil', ['-replace', 'CFBundleIdentifier', '-string', bundleId(boxName, app), plist]);\n run('plutil', ['-replace', 'CFBundleName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleDisplayName', '-string', app.name, plist]);\n run('plutil', ['-replace', 'CFBundleExecutable', '-string', 'ghostty', plist]);\n\n // Disable Sparkle auto-update (would clobber the clone).\n run('plutil', ['-replace', 'SUEnableAutomaticChecks', '-bool', 'NO', plist]);\n try {\n run('plutil', ['-replace', 'SUFeedURL', '-string', '', plist]);\n } catch {\n // donor app may not define SUFeedURL — ignore\n }\n\n // Swap the binary for a launcher that prepends --config-file.\n const bin = path.join(appPath, 'Contents', 'MacOS', 'ghostty');\n fs.renameSync(bin, `${bin}.real`);\n const src = path.join(tmpDir, 'launcher.c');\n fs.writeFileSync(src, buildLauncherSource(configPath));\n run('cc', ['-o', bin, src]);\n\n installIcon(app, appPath, tmpDir);\n\n // Re-sign: inner real binary first, then the whole bundle.\n const signId = builder.signId;\n const entArgs = entitlements ? ['--entitlements', entitlements] : [];\n const idArgs = signId ? ['--sign', signId] : ['--sign', '-'];\n run('codesign', ['--force', ...idArgs, ...entArgs, `${bin}.real`]);\n run('codesign', ['--force', '--deep', ...idArgs, ...entArgs, appPath]);\n\n return { appPath, signed: signId ? 'identity' : 'adhoc' };\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { n as AppConfig, t as AppBuilderConfig } from "./config-DQWueb4a.js";
|
|
2
|
+
|
|
3
|
+
//#region src/init/app.d.ts
|
|
4
|
+
/** Inputs for {@link buildApp}. */
|
|
5
|
+
interface BuildAppOptions {
|
|
6
|
+
/** The `-b` box name (drives the default bundle id). */
|
|
7
|
+
boxName: string;
|
|
8
|
+
app: AppConfig;
|
|
9
|
+
builder: AppBuilderConfig;
|
|
10
|
+
/** Absolute path to the already-written Ghostty config to bake in. */
|
|
11
|
+
configPath: string;
|
|
12
|
+
}
|
|
13
|
+
/** Result of a successful {@link buildApp}. */
|
|
14
|
+
interface BuildAppResult {
|
|
15
|
+
appPath: string;
|
|
16
|
+
signed: 'identity' | 'adhoc';
|
|
17
|
+
}
|
|
18
|
+
/** True when the host can build apps (macOS with the donor app + a C compiler). */
|
|
19
|
+
declare function canBuildApps(builder: AppBuilderConfig): {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
reason?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Build the standalone Ghostty app for a box. Throws on any failure (the caller
|
|
25
|
+
* decides whether to abort or carry on with the other boxes).
|
|
26
|
+
*/
|
|
27
|
+
declare function buildApp(opts: BuildAppOptions): BuildAppResult;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { canBuildApps as i, BuildAppResult as n, buildApp as r, BuildAppOptions as t };
|
|
30
|
+
//# sourceMappingURL=app-CpuMtOoj.d.ts.map
|
package/lib/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { l as resolveBox, s as loadConfig } from "./config-BQ44iVWT.js";
|
|
3
|
+
import { n as runInit } from "./scaffold-CRzC5KYe.js";
|
|
4
|
+
import { a as runClaude, i as resolveProjectDir, n as generateProfile, r as profilePath } from "./run-CNehSQ-S.js";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import yargs from "yargs";
|
|
7
|
+
import { hideBin } from "yargs/helpers";
|
|
8
|
+
//#region src/cli.ts
|
|
9
|
+
/** Pick the explicit config path: a `--box <name>` wins over `--config <path>`. */
|
|
10
|
+
function explicitConfig(argv) {
|
|
11
|
+
if (argv.box) return resolveBox(argv.box);
|
|
12
|
+
return argv.config ?? void 0;
|
|
13
|
+
}
|
|
14
|
+
await yargs(hideBin(process.argv)).scriptName("clabox").parserConfiguration({ "unknown-options-as-args": true }).option("config", {
|
|
15
|
+
type: "string",
|
|
16
|
+
describe: "Path to a JS config file (overrides CLABOX_CONFIG)"
|
|
17
|
+
}).option("box", {
|
|
18
|
+
alias: "b",
|
|
19
|
+
type: "string",
|
|
20
|
+
describe: "Run a named config from ~/.config/clabox/configs/<name>.config.mjs"
|
|
21
|
+
}).command(["run [claudeArgs..]", "$0 [claudeArgs..]"], "Generate the profile and run claude inside the sandbox (default)", (y) => y.positional("claudeArgs", {
|
|
22
|
+
describe: "Arguments passed through to claude",
|
|
23
|
+
array: true,
|
|
24
|
+
default: []
|
|
25
|
+
}), async (argv) => {
|
|
26
|
+
const { config, configFile } = await loadConfig(explicitConfig(argv));
|
|
27
|
+
const code = runClaude(config, argv.claudeArgs ?? [], { configFile });
|
|
28
|
+
process.exit(code);
|
|
29
|
+
}).command("generate", "Build the sandbox profile only and print its path", (y) => y, async (argv) => {
|
|
30
|
+
const { config } = await loadConfig(explicitConfig(argv));
|
|
31
|
+
console.log(generateProfile(config));
|
|
32
|
+
}).command("profile", "Print the sandbox profile path (no build)", (y) => y, async (argv) => {
|
|
33
|
+
const { config } = await loadConfig(explicitConfig(argv));
|
|
34
|
+
console.log(profilePath(resolveProjectDir(config)));
|
|
35
|
+
}).command("init", "Generate clabox-<name> shell aliases and build Ghostty apps for `app` boxes", (y) => y.option("dir", {
|
|
36
|
+
type: "string",
|
|
37
|
+
describe: "Base dir holding configs/ and scripts/ (default: ./__)"
|
|
38
|
+
}).option("apps", {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
default: true,
|
|
41
|
+
describe: "Build Ghostty apps for `app` boxes (use --no-apps to skip)"
|
|
42
|
+
}).option("app", {
|
|
43
|
+
type: "string",
|
|
44
|
+
describe: "Build only this app box (by box name or app display name)"
|
|
45
|
+
}), async (argv) => {
|
|
46
|
+
const { profiles, indexFile, written, apps, raycastCommands, warnings } = await runInit({
|
|
47
|
+
baseDir: argv.dir,
|
|
48
|
+
buildApps: argv.apps,
|
|
49
|
+
only: argv.app ?? null
|
|
50
|
+
});
|
|
51
|
+
console.log(`clabox init: ${profiles.length} profile(s) → ${profiles.join(", ")}`);
|
|
52
|
+
for (const f of written) console.log(` ${path.basename(f)}`);
|
|
53
|
+
for (const a of apps) console.log(` 📦 ${a.appPath} (${a.signed})`);
|
|
54
|
+
for (const r of raycastCommands) console.log(` 🚀 ${r}`);
|
|
55
|
+
for (const w of warnings) console.warn(` ⚠️ ${w}`);
|
|
56
|
+
console.log(`\nAdd to ~/.zshrc: source ${indexFile}`);
|
|
57
|
+
if (raycastCommands.length > 0) console.log(`Add to Raycast (Script Commands dir): ${path.dirname(raycastCommands[0])}`);
|
|
58
|
+
}).example("$0 run --dangerously-skip-permissions", "YOLO mode inside the sandbox").example("$0 -b ax-root", "Run the ~/.config/clabox/configs/ax-root.config.mjs box").example("$0 init", "Generate shell aliases from __/configs/*.config.mjs").example("$0 --config ./my.clabox.mjs run", "Use a specific JS config file").example("CLAUDE_CONFIG_DIR=~/.claude_work $0 run", "Use a different Claude profile").epilogue([
|
|
59
|
+
"Config (later wins): defaults -> env vars -> JS config file.",
|
|
60
|
+
"File: ./clabox.config.mjs or ~/.config/clabox/config.mjs",
|
|
61
|
+
"(or --config /path, or CLABOX_CONFIG=/path).",
|
|
62
|
+
"Named boxes: -b <name> -> ~/.config/clabox/configs/<name>.config.mjs",
|
|
63
|
+
"(dir overridable via CLABOX_CONFIGS_DIR)."
|
|
64
|
+
].join("\n")).version(false).help().alias("h", "help").fail((msg, err) => {
|
|
65
|
+
console.error(`Error: ${err?.message ?? msg}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}).parseAsync();
|
|
68
|
+
//#endregion
|
|
69
|
+
export {};
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=cli.js.map
|
package/lib/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// clabox — run Claude Code in a sandbox for super-safe YOLO mode.\n// SPDX-License-Identifier: MIT\n//\n// Configure in plain JS: clabox.config.mjs (CWD) or\n// ~/.config/clabox/config.mjs. See clabox.config.example.mjs.\n\nimport path from 'node:path';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { runInit } from './init/scaffold.js';\nimport { generateProfile, profilePath, resolveProjectDir, runClaude } from './sandbox/run.js';\nimport { loadConfig, resolveBox } from './utils/config.js';\n\n/** Pick the explicit config path: a `--box <name>` wins over `--config <path>`. */\n// Index signature so any yargs argv (incl. commands with an empty builder)\n// is assignable — `box`/`config` are global options, present on every command.\nfunction explicitConfig(argv: {\n box?: unknown;\n config?: unknown;\n [k: string]: unknown;\n}): string | undefined {\n if (argv.box) return resolveBox(argv.box as string);\n return (argv.config as string | undefined) ?? undefined;\n}\n\nawait yargs(hideBin(process.argv))\n .scriptName('clabox')\n // Keep unknown flags (e.g. --dangerously-skip-permissions) as positionals so\n // they pass straight through to claude instead of erroring out.\n .parserConfiguration({ 'unknown-options-as-args': true })\n // clabox-owned flag: a config-file path that wins over CLABOX_CONFIG.\n .option('config', {\n type: 'string',\n describe: 'Path to a JS config file (overrides CLABOX_CONFIG)',\n })\n // clabox-owned flag: run a named config from the global configs dir.\n // (`-p` is left for claude's --print; `-c/-r/-d/-v` are claude flags too.)\n .option('box', {\n alias: 'b',\n type: 'string',\n describe: 'Run a named config from ~/.config/clabox/configs/<name>.config.mjs',\n })\n .command(\n ['run [claudeArgs..]', '$0 [claudeArgs..]'],\n 'Generate the profile and run claude inside the sandbox (default)',\n (y) =>\n y.positional('claudeArgs', {\n describe: 'Arguments passed through to claude',\n array: true,\n default: [] as string[],\n }),\n async (argv) => {\n const { config, configFile } = await loadConfig(explicitConfig(argv));\n const claudeArgs = (argv.claudeArgs ?? []) as string[];\n const code = runClaude(config, claudeArgs, { configFile });\n process.exit(code);\n },\n )\n .command(\n 'generate',\n 'Build the sandbox profile only and print its path',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(generateProfile(config));\n },\n )\n .command(\n 'profile',\n 'Print the sandbox profile path (no build)',\n (y) => y,\n async (argv) => {\n const { config } = await loadConfig(explicitConfig(argv));\n console.log(profilePath(resolveProjectDir(config)));\n },\n )\n .command(\n 'init',\n 'Generate clabox-<name> shell aliases and build Ghostty apps for `app` boxes',\n (y) =>\n y\n .option('dir', {\n type: 'string',\n describe: 'Base dir holding configs/ and scripts/ (default: ./__)',\n })\n .option('apps', {\n type: 'boolean',\n default: true,\n describe: 'Build Ghostty apps for `app` boxes (use --no-apps to skip)',\n })\n .option('app', {\n type: 'string',\n describe: 'Build only this app box (by box name or app display name)',\n }),\n async (argv) => {\n const { profiles, indexFile, written, apps, raycastCommands, warnings } = await runInit({\n baseDir: argv.dir as string | undefined,\n buildApps: argv.apps as boolean,\n only: (argv.app as string | undefined) ?? null,\n });\n console.log(`clabox init: ${profiles.length} profile(s) → ${profiles.join(', ')}`);\n for (const f of written) console.log(` ${path.basename(f)}`);\n for (const a of apps) console.log(` 📦 ${a.appPath} (${a.signed})`);\n for (const r of raycastCommands) console.log(` 🚀 ${r}`);\n for (const w of warnings) console.warn(` ⚠️ ${w}`);\n console.log(`\\nAdd to ~/.zshrc: source ${indexFile}`);\n if (raycastCommands.length > 0) {\n console.log(`Add to Raycast (Script Commands dir): ${path.dirname(raycastCommands[0])}`);\n }\n },\n )\n .example('$0 run --dangerously-skip-permissions', 'YOLO mode inside the sandbox')\n .example('$0 -b ax-root', 'Run the ~/.config/clabox/configs/ax-root.config.mjs box')\n .example('$0 init', 'Generate shell aliases from __/configs/*.config.mjs')\n .example('$0 --config ./my.clabox.mjs run', 'Use a specific JS config file')\n .example('CLAUDE_CONFIG_DIR=~/.claude_work $0 run', 'Use a different Claude profile')\n .epilogue(\n [\n 'Config (later wins): defaults -> env vars -> JS config file.',\n 'File: ./clabox.config.mjs or ~/.config/clabox/config.mjs',\n '(or --config /path, or CLABOX_CONFIG=/path).',\n 'Named boxes: -b <name> -> ~/.config/clabox/configs/<name>.config.mjs',\n '(dir overridable via CLABOX_CONFIGS_DIR).',\n ].join('\\n'),\n )\n .version(false)\n .help()\n .alias('h', 'help')\n .fail((msg, err) => {\n console.error(`Error: ${err?.message ?? msg}`);\n process.exit(1);\n })\n .parseAsync();\n"],"mappings":";;;;;;;;;AAiBA,SAAS,eAAe,MAID;CACrB,IAAI,KAAK,KAAK,OAAO,WAAW,KAAK,GAAa;CAClD,OAAQ,KAAK,UAAiC,KAAA;AAChD;AAEA,MAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,CAC/B,WAAW,QAAQ,CAAC,CAGpB,oBAAoB,EAAE,2BAA2B,KAAK,CAAC,CAAC,CAExD,OAAO,UAAU;CAChB,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CAGD,OAAO,OAAO;CACb,OAAO;CACP,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CACD,QACC,CAAC,sBAAsB,mBAAmB,GAC1C,qEACC,MACC,EAAE,WAAW,cAAc;CACzB,UAAU;CACV,OAAO;CACP,SAAS,CAAC;AACZ,CAAC,GACH,OAAO,SAAS;CACd,MAAM,EAAE,QAAQ,eAAe,MAAM,WAAW,eAAe,IAAI,CAAC;CAEpE,MAAM,OAAO,UAAU,QADH,KAAK,cAAc,CAAC,GACG,EAAE,WAAW,CAAC;CACzD,QAAQ,KAAK,IAAI;AACnB,CACF,CAAC,CACA,QACC,YACA,sDACC,MAAM,GACP,OAAO,SAAS;CACd,MAAM,EAAE,WAAW,MAAM,WAAW,eAAe,IAAI,CAAC;CACxD,QAAQ,IAAI,gBAAgB,MAAM,CAAC;AACrC,CACF,CAAC,CACA,QACC,WACA,8CACC,MAAM,GACP,OAAO,SAAS;CACd,MAAM,EAAE,WAAW,MAAM,WAAW,eAAe,IAAI,CAAC;CACxD,QAAQ,IAAI,YAAY,kBAAkB,MAAM,CAAC,CAAC;AACpD,CACF,CAAC,CACA,QACC,QACA,gFACC,MACC,EACG,OAAO,OAAO;CACb,MAAM;CACN,UAAU;AACZ,CAAC,CAAC,CACD,OAAO,QAAQ;CACd,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC,CAAC,CACD,OAAO,OAAO;CACb,MAAM;CACN,UAAU;AACZ,CAAC,GACL,OAAO,SAAS;CACd,MAAM,EAAE,UAAU,WAAW,SAAS,MAAM,iBAAiB,aAAa,MAAM,QAAQ;EACtF,SAAS,KAAK;EACd,WAAW,KAAK;EAChB,MAAO,KAAK,OAA8B;CAC5C,CAAC;CACD,QAAQ,IAAI,gBAAgB,SAAS,OAAO,gBAAgB,SAAS,KAAK,IAAI,GAAG;CACjF,KAAK,MAAM,KAAK,SAAS,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,GAAG;CAC5D,KAAK,MAAM,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,QAAQ,IAAI,EAAE,OAAO,EAAE;CACnE,KAAK,MAAM,KAAK,iBAAiB,QAAQ,IAAI,QAAQ,GAAG;CACxD,KAAK,MAAM,KAAK,UAAU,QAAQ,KAAK,SAAS,GAAG;CACnD,QAAQ,IAAI,8BAA8B,WAAW;CACrD,IAAI,gBAAgB,SAAS,GAC3B,QAAQ,IAAI,0CAA0C,KAAK,QAAQ,gBAAgB,EAAE,GAAG;AAE5F,CACF,CAAC,CACA,QAAQ,yCAAyC,8BAA8B,CAAC,CAChF,QAAQ,iBAAiB,yDAAyD,CAAC,CACnF,QAAQ,WAAW,qDAAqD,CAAC,CACzE,QAAQ,mCAAmC,+BAA+B,CAAC,CAC3E,QAAQ,2CAA2C,gCAAgC,CAAC,CACpF,SACC;CACE;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI,CACb,CAAC,CACA,QAAQ,KAAK,CAAC,CACd,KAAK,CAAC,CACN,MAAM,KAAK,MAAM,CAAC,CAClB,MAAM,KAAK,QAAQ;CAClB,QAAQ,MAAM,UAAU,KAAK,WAAW,KAAK;CAC7C,QAAQ,KAAK,CAAC;AAChB,CAAC,CAAC,CACD,WAAW"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
//#region src/utils/config.ts
|
|
6
|
+
const HOME = os.homedir();
|
|
7
|
+
/** Expand a leading `~` / `~/` to the user's home directory. */
|
|
8
|
+
function expandHome(p) {
|
|
9
|
+
if (!p) return p;
|
|
10
|
+
if (p === "~") return HOME;
|
|
11
|
+
if (p.startsWith("~/")) return path.join(HOME, p.slice(2));
|
|
12
|
+
return p;
|
|
13
|
+
}
|
|
14
|
+
const env = process.env;
|
|
15
|
+
/** Built-in defaults. Everything here is meant to be overridable. */
|
|
16
|
+
const defaultConfig = {
|
|
17
|
+
cwd: env.CLABOX_CWD ?? null,
|
|
18
|
+
claudeBin: env.CLABOX_CLAUDE_BIN ?? null,
|
|
19
|
+
configDir: env.CLAUDE_CONFIG_DIR ?? "~/.claude",
|
|
20
|
+
claudeArgs: ["--settings", "{\"includeCoAuthoredBy\": false}"],
|
|
21
|
+
bot: {
|
|
22
|
+
name: env.CLABOX_BOT_NAME ?? "claudeBOT",
|
|
23
|
+
email: env.CLABOX_BOT_EMAIL ?? "bot@example.com",
|
|
24
|
+
sshDir: env.CLABOX_BOT_SSH_DIR ?? "~/.ssh/claudebot"
|
|
25
|
+
},
|
|
26
|
+
env: {},
|
|
27
|
+
network: true,
|
|
28
|
+
ulimitProcs: 1024,
|
|
29
|
+
hooksDir: env.CLABOX_HOOKS_DIR ?? null,
|
|
30
|
+
paths: {
|
|
31
|
+
readWrite: [],
|
|
32
|
+
readOnly: [],
|
|
33
|
+
exec: [],
|
|
34
|
+
deny: []
|
|
35
|
+
},
|
|
36
|
+
denyHome: [
|
|
37
|
+
"Documents",
|
|
38
|
+
"Desktop",
|
|
39
|
+
"Downloads",
|
|
40
|
+
"Pictures",
|
|
41
|
+
"Movies",
|
|
42
|
+
"Music"
|
|
43
|
+
],
|
|
44
|
+
denyDotConfigs: [
|
|
45
|
+
"aws",
|
|
46
|
+
"gnupg",
|
|
47
|
+
"kube",
|
|
48
|
+
"docker",
|
|
49
|
+
"config"
|
|
50
|
+
],
|
|
51
|
+
appBuilder: {
|
|
52
|
+
ghosttyApp: env.CLABOX_GHOSTTY_APP ?? "/Applications/Ghostty.app",
|
|
53
|
+
appsDir: env.CLABOX_APPS_DIR ?? "~/Applications",
|
|
54
|
+
signId: env.CLABOX_SIGN_ID ?? null,
|
|
55
|
+
baseGhosttyConfig: env.CLABOX_GHOSTTY_BASE_CONFIG ?? null,
|
|
56
|
+
claboxBin: env.CLABOX_CLABOX_BIN ?? null
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function isPlainObject(v) {
|
|
60
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
61
|
+
}
|
|
62
|
+
/** Shallow-deep merge: nested plain objects merge, everything else replaces. */
|
|
63
|
+
function deepMerge(base, override) {
|
|
64
|
+
const out = { ...base };
|
|
65
|
+
for (const [key, val] of Object.entries(override)) {
|
|
66
|
+
if (val === void 0) continue;
|
|
67
|
+
const baseVal = base[key];
|
|
68
|
+
out[key] = isPlainObject(val) && isPlainObject(baseVal) ? deepMerge(baseVal, val) : val;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/** Merge a (partial) override over a full config, returning a new config. */
|
|
73
|
+
function mergeConfig(base, override) {
|
|
74
|
+
if (!isPlainObject(override)) return base;
|
|
75
|
+
return deepMerge(base, override);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),
|
|
79
|
+
* then CWD, then ~/.config. The CLI arg wins over the env var.
|
|
80
|
+
*/
|
|
81
|
+
function findConfigFile(explicit) {
|
|
82
|
+
const chosen = explicit ?? env.CLABOX_CONFIG;
|
|
83
|
+
if (chosen) return expandHome(chosen);
|
|
84
|
+
return [
|
|
85
|
+
path.join(process.cwd(), "clabox.config.mjs"),
|
|
86
|
+
path.join(process.cwd(), "clabox.config.js"),
|
|
87
|
+
path.join(HOME, ".config", "clabox", "config.mjs")
|
|
88
|
+
].find((c) => fs.existsSync(c)) ?? null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Global directory holding named "box" configs (`<name>.config.mjs`), used by
|
|
92
|
+
* the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.
|
|
93
|
+
*/
|
|
94
|
+
function configsDir() {
|
|
95
|
+
return expandHome(env.CLABOX_CONFIGS_DIR ?? "~/.config/clabox/configs");
|
|
96
|
+
}
|
|
97
|
+
const BOX_SUFFIXES = [".config.mjs", ".mjs"];
|
|
98
|
+
/** Candidate file paths for a box name, in resolution order. */
|
|
99
|
+
function boxCandidates(name, dir) {
|
|
100
|
+
return BOX_SUFFIXES.map((s) => path.join(dir, `${name}${s}`));
|
|
101
|
+
}
|
|
102
|
+
/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */
|
|
103
|
+
function listBoxes(dir = configsDir()) {
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = fs.readdirSync(dir);
|
|
107
|
+
} catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const names = /* @__PURE__ */ new Set();
|
|
111
|
+
for (const f of entries) {
|
|
112
|
+
if (f.startsWith("_")) continue;
|
|
113
|
+
const suffix = BOX_SUFFIXES.find((s) => f.endsWith(s));
|
|
114
|
+
if (suffix) names.add(f.slice(0, -suffix.length));
|
|
115
|
+
}
|
|
116
|
+
return [...names].sort();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a box name to its config file in {@link configsDir}, preferring
|
|
120
|
+
* `<name>.config.mjs` over a bare `<name>.mjs`.
|
|
121
|
+
*
|
|
122
|
+
* @throws if no matching file exists (the message lists the available boxes).
|
|
123
|
+
*/
|
|
124
|
+
function resolveBox(name, dir = configsDir()) {
|
|
125
|
+
if (!name.startsWith("_")) {
|
|
126
|
+
const found = boxCandidates(name, dir).find((c) => fs.existsSync(c));
|
|
127
|
+
if (found) return found;
|
|
128
|
+
}
|
|
129
|
+
const available = listBoxes(dir);
|
|
130
|
+
const hint = available.length ? `available: ${available.join(", ")}` : `none found in ${dir}`;
|
|
131
|
+
throw new Error(`clabox: box '${name}' not found in ${dir} (${hint})`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build the effective config: defaults ⊕ env ⊕ config file.
|
|
135
|
+
*
|
|
136
|
+
* @param explicitConfig optional config-file path (e.g. from `--config`);
|
|
137
|
+
* takes precedence over `CLABOX_CONFIG` and the default lookup locations.
|
|
138
|
+
*/
|
|
139
|
+
async function loadConfig(explicitConfig) {
|
|
140
|
+
let cfg = defaultConfig;
|
|
141
|
+
const file = findConfigFile(explicitConfig);
|
|
142
|
+
if (file) {
|
|
143
|
+
const mod = await import(pathToFileURL(file).href);
|
|
144
|
+
const exported = mod.default ?? mod.config ?? mod;
|
|
145
|
+
cfg = mergeConfig(defaultConfig, typeof exported === "function" ? await exported(defaultConfig) : exported);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
config: cfg,
|
|
149
|
+
configFile: file
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { findConfigFile as a, mergeConfig as c, expandHome as i, resolveBox as l, configsDir as n, listBoxes as o, defaultConfig as r, loadConfig as s, HOME as t };
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=config-BQ44iVWT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-BQ44iVWT.js","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["// Configuration: sane defaults, env overrides, and an optional JS config file.\n//\n// Resolution order (later wins):\n// 1. defaultConfig (below)\n// 2. env vars (CLAUDE_CONFIG_DIR, CLABOX_*, …)\n// 3. a JS config file (see loadConfig)\n//\n// A config file default-exports either a plain object (merged over the defaults)\n// or a function `(defaults) => config` for full programmatic control.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport const HOME = os.homedir();\n\n/** Expand a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (!p) return p;\n if (p === '~') return HOME;\n if (p.startsWith('~/')) return path.join(HOME, p.slice(2));\n return p;\n}\n\nconst env = process.env;\n\n/** Dedicated git/ssh identity for commits made from inside the sandbox. */\nexport interface BotConfig {\n name: string;\n email: string;\n /** If `${sshDir}/id_ed25519` exists, git ssh is pinned to it. */\n sshDir: string;\n}\n\n/**\n * Opt-in marker that turns a box into a standalone Ghostty app. When a box\n * config carries an `app`, `clabox init` generates a Ghostty config for it and\n * builds a cloned `<appsDir>/<name>.app` that launches `clabox -b <box>`.\n */\nexport interface AppConfig {\n /** App display name → `<appsDir>/<name>.app` + CFBundleName. */\n name: string;\n /** Ghostty window title. Defaults to {@link AppConfig.name}. */\n title?: string;\n /** Emoji for the generated Raycast command. Default: the title's leading emoji. */\n emoji?: string;\n /** Path to a `.icns`/`.png` icon for the .app. `.png` is converted. `~` ok. */\n icon?: string;\n /** Ghostty built-in `macos-icon` (e.g. `retro`, `holographic`). */\n macosIcon?: string;\n /** Extra raw `key = value` lines appended to the generated Ghostty config. */\n ghostty?: Record<string, string>;\n /** Bundle id. Default: `com.ghostty.custom.<name dot-joined>`. */\n bundleId?: string;\n}\n\n/** Machine-wide settings for the `clabox init` Ghostty-app builder. */\nexport interface AppBuilderConfig {\n /** Donor app to clone. `~` is expanded. */\n ghosttyApp: string;\n /** Where built apps land. `~` is expanded. */\n appsDir: string;\n /** codesign identity. null → ad-hoc (`codesign -s -`). */\n signId: string | null;\n /** Optional base Ghostty config emitted as a leading `config-file = …`. */\n baseGhosttyConfig: string | null;\n /** `clabox` binary baked into the generated `command`. null → autodetect. */\n claboxBin: string | null;\n}\n\n/** Extra rules layered on top of the built-in base profile. */\nexport interface PathRules {\n /** RW subpaths (beyond project dir + configDir + /tmp). */\n readWrite: string[];\n /** RO subpaths. */\n readOnly: string[];\n /** process-exec subpaths. */\n exec: string[];\n /** explicit deny subpaths (read + write). */\n deny: string[];\n}\n\n/** Effective clabox configuration. */\nexport interface Config {\n /**\n * Working directory to run `claude` in (and grant RW as the project dir).\n * null → the shell's CWD. Handy for named boxes that always target one\n * project regardless of where `clabox` is invoked from. `~` is expanded.\n */\n cwd: string | null;\n /** Path to the `claude` binary. null → autodetect (PATH, then ~/.local/bin). */\n claudeBin: string | null;\n /** Claude config/profile directory — supports multiple accounts. */\n configDir: string;\n /** Extra args always passed to `claude`, before any args from the CLI. */\n claudeArgs: string[];\n bot: BotConfig;\n /**\n * Extra environment variables forced onto the sandboxed `claude` process,\n * layered over the inherited shell env and after the built-in hardening vars\n * (so a key set here wins). Use it to pass secrets like `GITHUB_TOKEN`.\n */\n env: Record<string, string>;\n /** Allow outbound network. `false` → no `(allow network*)` line. */\n network: boolean;\n /** Cap the process table inside the sandbox (fork-bomb guard). 0 → skip. */\n ulimitProcs: number;\n /** Extra directory granted read + execute inside the sandbox. null → disabled. */\n hooksDir: string | null;\n paths: PathRules;\n /** Home subdirectories denied entirely (read + write). */\n denyHome: string[];\n /** Dotfile config dirs under $HOME denied entirely. */\n denyDotConfigs: string[];\n /**\n * Opt-in: build a standalone Ghostty app for this box during `clabox init`.\n * Absent → the box only gets a shell alias (the default).\n */\n app?: AppConfig;\n /** Machine-wide settings for the `clabox init` Ghostty-app builder. */\n appBuilder: AppBuilderConfig;\n}\n\n/** Built-in defaults. Everything here is meant to be overridable. */\nexport const defaultConfig: Config = {\n cwd: env.CLABOX_CWD ?? null,\n claudeBin: env.CLABOX_CLAUDE_BIN ?? null,\n configDir: env.CLAUDE_CONFIG_DIR ?? '~/.claude',\n claudeArgs: ['--settings', '{\"includeCoAuthoredBy\": false}'],\n bot: {\n name: env.CLABOX_BOT_NAME ?? 'claudeBOT',\n email: env.CLABOX_BOT_EMAIL ?? 'bot@example.com',\n sshDir: env.CLABOX_BOT_SSH_DIR ?? '~/.ssh/claudebot',\n },\n env: {},\n network: true,\n ulimitProcs: 1024,\n hooksDir: env.CLABOX_HOOKS_DIR ?? null,\n paths: {\n readWrite: [],\n readOnly: [],\n exec: [],\n deny: [],\n },\n denyHome: ['Documents', 'Desktop', 'Downloads', 'Pictures', 'Movies', 'Music'],\n // `.config/git` is always carved back out for git RO config in the profile.\n denyDotConfigs: ['aws', 'gnupg', 'kube', 'docker', 'config'],\n // `app` is opt-in per box, so there's no default — it stays undefined.\n appBuilder: {\n ghosttyApp: env.CLABOX_GHOSTTY_APP ?? '/Applications/Ghostty.app',\n appsDir: env.CLABOX_APPS_DIR ?? '~/Applications',\n signId: env.CLABOX_SIGN_ID ?? null,\n baseGhosttyConfig: env.CLABOX_GHOSTTY_BASE_CONFIG ?? null,\n claboxBin: env.CLABOX_CLABOX_BIN ?? null,\n },\n};\n\ntype Plain = Record<string, unknown>;\n\nfunction isPlainObject(v: unknown): v is Plain {\n return v != null && typeof v === 'object' && !Array.isArray(v);\n}\n\n/** Shallow-deep merge: nested plain objects merge, everything else replaces. */\nfunction deepMerge(base: Plain, override: Plain): Plain {\n const out: Plain = { ...base };\n for (const [key, val] of Object.entries(override)) {\n if (val === undefined) continue;\n const baseVal = base[key];\n out[key] = isPlainObject(val) && isPlainObject(baseVal) ? deepMerge(baseVal, val) : val;\n }\n return out;\n}\n\n/** Merge a (partial) override over a full config, returning a new config. */\nexport function mergeConfig(base: Config, override: unknown): Config {\n if (!isPlainObject(override)) return base;\n return deepMerge(base as unknown as Plain, override) as unknown as Config;\n}\n\n/**\n * Locate a config file: explicit (CLI arg, then `CLABOX_CONFIG` env),\n * then CWD, then ~/.config. The CLI arg wins over the env var.\n */\nexport function findConfigFile(explicit?: string | null): string | null {\n const chosen = explicit ?? env.CLABOX_CONFIG;\n if (chosen) return expandHome(chosen);\n const candidates = [\n path.join(process.cwd(), 'clabox.config.mjs'),\n path.join(process.cwd(), 'clabox.config.js'),\n path.join(HOME, '.config', 'clabox', 'config.mjs'),\n ];\n return candidates.find((c) => fs.existsSync(c)) ?? null;\n}\n\n/**\n * Global directory holding named \"box\" configs (`<name>.config.mjs`), used by\n * the `clabox --box <name>` / `-b` flag. Override with `CLABOX_CONFIGS_DIR`.\n */\nexport function configsDir(): string {\n return expandHome(env.CLABOX_CONFIGS_DIR ?? '~/.config/clabox/configs');\n}\n\nconst BOX_SUFFIXES = ['.config.mjs', '.mjs'];\n\n/** Candidate file paths for a box name, in resolution order. */\nfunction boxCandidates(name: string, dir: string): string[] {\n return BOX_SUFFIXES.map((s) => path.join(dir, `${name}${s}`));\n}\n\n/** Named box configs (sorted, de-duplicated) available in {@link configsDir}. */\nexport function listBoxes(dir: string = configsDir()): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n const names = new Set<string>();\n for (const f of entries) {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes.\n if (f.startsWith('_')) continue;\n const suffix = BOX_SUFFIXES.find((s) => f.endsWith(s));\n if (suffix) names.add(f.slice(0, -suffix.length));\n }\n return [...names].sort();\n}\n\n/**\n * Resolve a box name to its config file in {@link configsDir}, preferring\n * `<name>.config.mjs` over a bare `<name>.mjs`.\n *\n * @throws if no matching file exists (the message lists the available boxes).\n */\nexport function resolveBox(name: string, dir: string = configsDir()): string {\n // `_`-prefixed files are shared partials (e.g. `_presets.mjs`), not boxes —\n // keep them un-resolvable so `-b` matches what `listBoxes` advertises.\n if (!name.startsWith('_')) {\n const found = boxCandidates(name, dir).find((c) => fs.existsSync(c));\n if (found) return found;\n }\n const available = listBoxes(dir);\n const hint = available.length ? `available: ${available.join(', ')}` : `none found in ${dir}`;\n throw new Error(`clabox: box '${name}' not found in ${dir} (${hint})`);\n}\n\n/** Result of {@link loadConfig}: the effective config and the file it came from. */\nexport interface LoadedConfig {\n config: Config;\n configFile: string | null;\n}\n\n/**\n * Build the effective config: defaults ⊕ env ⊕ config file.\n *\n * @param explicitConfig optional config-file path (e.g. from `--config`);\n * takes precedence over `CLABOX_CONFIG` and the default lookup locations.\n */\nexport async function loadConfig(explicitConfig?: string | null): Promise<LoadedConfig> {\n let cfg: Config = defaultConfig;\n const file = findConfigFile(explicitConfig);\n if (file) {\n const mod = await import(pathToFileURL(file).href);\n const exported = mod.default ?? mod.config ?? mod;\n const resolved = typeof exported === 'function' ? await exported(defaultConfig) : exported;\n cfg = mergeConfig(defaultConfig, resolved);\n }\n return { config: cfg, configFile: file };\n}\n"],"mappings":"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"}
|