@boopkit/cli 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 (53) hide show
  1. package/README.md +65 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +85 -0
  4. package/dist/commands/add.d.ts +6 -0
  5. package/dist/commands/add.js +173 -0
  6. package/dist/commands/adopt.d.ts +5 -0
  7. package/dist/commands/adopt.js +286 -0
  8. package/dist/commands/init.d.ts +5 -0
  9. package/dist/commands/init.js +295 -0
  10. package/dist/commands/list.d.ts +6 -0
  11. package/dist/commands/list.js +105 -0
  12. package/dist/commands/loop.d.ts +63 -0
  13. package/dist/commands/loop.js +765 -0
  14. package/dist/commands/status.d.ts +40 -0
  15. package/dist/commands/status.js +303 -0
  16. package/dist/commands/status.test.d.ts +1 -0
  17. package/dist/commands/status.test.js +118 -0
  18. package/dist/commands/update.d.ts +8 -0
  19. package/dist/commands/update.js +204 -0
  20. package/dist/lib/capability.d.ts +17 -0
  21. package/dist/lib/capability.js +172 -0
  22. package/dist/lib/config.d.ts +70 -0
  23. package/dist/lib/config.js +59 -0
  24. package/dist/lib/db-status.d.ts +64 -0
  25. package/dist/lib/db-status.js +257 -0
  26. package/dist/lib/env.d.ts +25 -0
  27. package/dist/lib/env.js +116 -0
  28. package/dist/lib/execute.d.ts +21 -0
  29. package/dist/lib/execute.js +228 -0
  30. package/dist/lib/interpolate.d.ts +4 -0
  31. package/dist/lib/interpolate.js +13 -0
  32. package/dist/lib/manifest.d.ts +54 -0
  33. package/dist/lib/manifest.js +11 -0
  34. package/dist/lib/preflight.d.ts +31 -0
  35. package/dist/lib/preflight.js +402 -0
  36. package/dist/lib/profile.d.ts +11 -0
  37. package/dist/lib/profile.js +73 -0
  38. package/dist/lib/project.d.ts +36 -0
  39. package/dist/lib/project.js +71 -0
  40. package/dist/lib/project.test.d.ts +1 -0
  41. package/dist/lib/project.test.js +118 -0
  42. package/dist/lib/rewire.d.ts +19 -0
  43. package/dist/lib/rewire.js +222 -0
  44. package/dist/lib/safe-io.d.ts +66 -0
  45. package/dist/lib/safe-io.js +167 -0
  46. package/dist/lib/scaffold.d.ts +87 -0
  47. package/dist/lib/scaffold.js +548 -0
  48. package/dist/lib/update.d.ts +73 -0
  49. package/dist/lib/update.js +379 -0
  50. package/package.json +46 -0
  51. package/profiles/web-app/profile.md +113 -0
  52. package/profiles/web-app/reference/architecture.ref.md +65 -0
  53. package/profiles/web-app/reference/message-queue-patterns.ref.md +247 -0
@@ -0,0 +1,402 @@
1
+ "use strict";
2
+ /**
3
+ * Pre-flight planning for `boop add`.
4
+ *
5
+ * Reads all inputs, validates everything, and produces a manifest of tasks
6
+ * to execute — without writing a single file.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.PreflightError = void 0;
43
+ exports.planAdd = planAdd;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const config_1 = require("./config");
47
+ const capability_1 = require("./capability");
48
+ const project_1 = require("./project");
49
+ class PreflightError extends Error {
50
+ constructor(message, checks) {
51
+ super(message);
52
+ this.name = 'PreflightError';
53
+ this.checks = checks;
54
+ }
55
+ }
56
+ exports.PreflightError = PreflightError;
57
+ /**
58
+ * Plan a `boop add` operation. Reads filesystem, validates everything,
59
+ * and produces a manifest. Writes nothing.
60
+ *
61
+ * Throws PreflightError if any check fails.
62
+ */
63
+ function planAdd(targetDir, capabilityName) {
64
+ const checks = [];
65
+ const tasks = [];
66
+ const warnings = [];
67
+ const boopRoot = (0, config_1.getBoopRoot)();
68
+ // --- Pre-flight checks ---
69
+ // 1. Validate project
70
+ const projectManifest = (0, project_1.readProjectManifest)(targetDir);
71
+ if (!projectManifest) {
72
+ checks.push({ name: 'project-exists', passed: false, message: 'Not a boop project (no .boop/config.json)' });
73
+ throw new PreflightError('Pre-flight failed', checks);
74
+ }
75
+ checks.push({ name: 'project-exists', passed: true });
76
+ // 2. Resolve capability
77
+ const cap = (0, capability_1.getCapability)(capabilityName);
78
+ if (!cap) {
79
+ checks.push({ name: 'capability-exists', passed: false, message: `Capability "${capabilityName}" not found` });
80
+ throw new PreflightError('Pre-flight failed', checks);
81
+ }
82
+ checks.push({ name: 'capability-exists', passed: true });
83
+ // 3. Not already installed
84
+ if (projectManifest.capabilities.includes(capabilityName)) {
85
+ checks.push({ name: 'not-already-installed', passed: false, message: `Capability "${capabilityName}" is already installed` });
86
+ throw new PreflightError('Pre-flight failed', checks);
87
+ }
88
+ checks.push({ name: 'not-already-installed', passed: true });
89
+ // 4. Verify required capabilities are installed
90
+ if (cap.requires && cap.requires.length > 0) {
91
+ const missing = cap.requires.filter(r => !projectManifest.capabilities.includes(r));
92
+ if (missing.length > 0) {
93
+ checks.push({
94
+ name: 'requires-satisfied',
95
+ passed: false,
96
+ message: `Missing required capabilities: ${missing.join(', ')}. Install them first with: ${missing.map(m => `boop add ${m}`).join(', then ')}`,
97
+ });
98
+ throw new PreflightError('Pre-flight failed', checks);
99
+ }
100
+ checks.push({ name: 'requires-satisfied', passed: true });
101
+ }
102
+ // 5. Enumerate files that would be copied — check for conflicts
103
+ // Resolve from package (scaffold/ and skills/) or source tree (files/ and skills/)
104
+ const pkgDir = (0, capability_1.getPackageDir)(capabilityName);
105
+ const pkgJsonPath = pkgDir ? path.join(pkgDir, 'package.json') : null;
106
+ const pkgJson = pkgJsonPath && fs.existsSync(pkgJsonPath)
107
+ ? JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))
108
+ : null;
109
+ const boopkit = pkgJson?.boopkit;
110
+ const capDir = boopkit
111
+ ? pkgDir
112
+ : path.join(boopRoot, 'capabilities', capabilityName);
113
+ const filesToCopy = [];
114
+ const fileConflicts = [];
115
+ // Skills files
116
+ const skillsDir = boopkit?.skillsDir
117
+ ? path.join(capDir, boopkit.skillsDir)
118
+ : path.join(capDir, 'skills');
119
+ if (fs.existsSync(skillsDir)) {
120
+ for (const entry of fs.readdirSync(skillsDir)) {
121
+ const destPath = path.join(targetDir, 'skills', entry);
122
+ const relPath = path.join('skills', entry);
123
+ filesToCopy.push(relPath);
124
+ if (fs.existsSync(destPath)) {
125
+ fileConflicts.push(relPath);
126
+ }
127
+ }
128
+ }
129
+ // Capability files from scaffold/ (package) or files/ (source tree)
130
+ if (cap.files) {
131
+ const filesDir = boopkit?.scaffoldDir
132
+ ? path.join(capDir, boopkit.scaffoldDir)
133
+ : path.join(capDir, 'files');
134
+ if (fs.existsSync(filesDir)) {
135
+ enumerateDir(filesDir, targetDir, filesToCopy, fileConflicts);
136
+ }
137
+ }
138
+ // File conflicts are warnings (skip behavior), not failures
139
+ if (fileConflicts.length > 0) {
140
+ checks.push({
141
+ name: 'no-file-conflicts',
142
+ passed: true,
143
+ message: `${fileConflicts.length} existing file(s) will be skipped: ${fileConflicts.join(', ')}`,
144
+ });
145
+ }
146
+ else {
147
+ checks.push({ name: 'no-file-conflicts', passed: true });
148
+ }
149
+ // 5. Validate hook markers exist in app.ts or server.ts (if capability has hooks)
150
+ if (cap.hooks && cap.hooks.length > 0) {
151
+ const appPath = path.join(targetDir, 'src', 'app.ts');
152
+ const serverPath = path.join(targetDir, 'src', 'server.ts');
153
+ // New structure uses app.ts; legacy uses server.ts
154
+ const hookFile = fs.existsSync(appPath) ? appPath : serverPath;
155
+ const hookFileName = fs.existsSync(appPath) ? 'app.ts' : 'server.ts';
156
+ if (fs.existsSync(hookFile)) {
157
+ const content = fs.readFileSync(hookFile, 'utf-8');
158
+ const phases = new Set(cap.hooks.map(h => h.phase));
159
+ const missingMarkers = [];
160
+ for (const phase of phases) {
161
+ const marker = `// @boopkit:${phase}`;
162
+ if (!content.includes(marker)) {
163
+ missingMarkers.push(phase);
164
+ }
165
+ }
166
+ if (missingMarkers.length > 0) {
167
+ checks.push({
168
+ name: 'has-hook-markers',
169
+ passed: false,
170
+ message: `Missing markers in ${hookFileName}: ${missingMarkers.join(', ')}`,
171
+ });
172
+ }
173
+ else {
174
+ checks.push({ name: 'has-hook-markers', passed: true });
175
+ }
176
+ // Check if already wired (idempotency)
177
+ const capAnnotation = `@boopkit/cap-${capabilityName}`;
178
+ if (content.includes(capAnnotation)) {
179
+ checks.push({
180
+ name: 'hooks-not-duplicate',
181
+ passed: true,
182
+ message: `Hooks for ${capabilityName} already present in ${hookFileName} — will skip injection`,
183
+ });
184
+ }
185
+ }
186
+ else {
187
+ checks.push({
188
+ name: 'has-hook-markers',
189
+ passed: false,
190
+ message: 'Neither app.ts nor server.ts found — hooks cannot be injected',
191
+ });
192
+ }
193
+ }
194
+ // 6. Check for script conflicts
195
+ if (cap.scripts) {
196
+ const pkgPath = path.join(targetDir, 'package.json');
197
+ if (fs.existsSync(pkgPath)) {
198
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
199
+ const existingScripts = pkg.scripts || {};
200
+ const conflicts = [];
201
+ for (const scriptName of Object.keys(cap.scripts)) {
202
+ if (scriptName in existingScripts) {
203
+ conflicts.push(scriptName);
204
+ }
205
+ }
206
+ if (conflicts.length > 0) {
207
+ checks.push({
208
+ name: 'script-conflicts',
209
+ passed: true,
210
+ message: `Existing scripts will be preserved: ${conflicts.join(', ')}`,
211
+ });
212
+ }
213
+ else {
214
+ checks.push({ name: 'script-conflicts', passed: true });
215
+ }
216
+ }
217
+ }
218
+ // --- Abort if any check failed ---
219
+ const failedChecks = checks.filter(c => !c.passed);
220
+ if (failedChecks.length > 0) {
221
+ throw new PreflightError(`Pre-flight failed: ${failedChecks.map(c => c.message).join('; ')}`, checks);
222
+ }
223
+ // --- Build task manifest ---
224
+ // Task: copy files
225
+ if (filesToCopy.length > 0) {
226
+ tasks.push({
227
+ op: 'copy-files',
228
+ source: capDir,
229
+ target: targetDir,
230
+ detail: `Copy ${filesToCopy.length} file(s) from ${capabilityName}`,
231
+ status: 'pending',
232
+ data: { files: filesToCopy, conflicts: fileConflicts },
233
+ });
234
+ }
235
+ // Task: merge dependencies
236
+ if (cap.dependencies?.length || cap.devDependencies?.length) {
237
+ tasks.push({
238
+ op: 'merge-deps',
239
+ source: capabilityName,
240
+ target: path.join(targetDir, 'package.json'),
241
+ detail: `Merge ${[...(cap.dependencies || []), ...(cap.devDependencies || [])].join(', ')}`,
242
+ status: 'pending',
243
+ data: { dependencies: cap.dependencies, devDependencies: cap.devDependencies },
244
+ });
245
+ }
246
+ // Task: merge scripts
247
+ if (cap.scripts && Object.keys(cap.scripts).length > 0) {
248
+ tasks.push({
249
+ op: 'merge-scripts',
250
+ source: capabilityName,
251
+ target: path.join(targetDir, 'package.json'),
252
+ detail: `Add scripts: ${Object.keys(cap.scripts).join(', ')}`,
253
+ status: 'pending',
254
+ data: { scripts: cap.scripts },
255
+ });
256
+ }
257
+ // Task: link package
258
+ if (cap.package) {
259
+ const pkgName = cap.package.replace(/^@boopkit\//, '');
260
+ const isAdopted = projectManifest.adopted?.includes(pkgName);
261
+ if (isAdopted) {
262
+ warnings.push(`Skipping package link for ${cap.package} — package is adopted (source already in project)`);
263
+ }
264
+ else {
265
+ tasks.push({
266
+ op: 'link-package',
267
+ source: path.join(boopRoot, 'packages', pkgName),
268
+ target: path.join(targetDir, 'package.json'),
269
+ detail: `Add ${cap.package} as semver dependency`,
270
+ status: 'pending',
271
+ data: { packageName: cap.package },
272
+ });
273
+ }
274
+ }
275
+ // Task: set env vars
276
+ const hasEnv = cap.env && cap.env.length > 0;
277
+ if (hasEnv) {
278
+ tasks.push({
279
+ op: 'set-env',
280
+ source: capabilityName,
281
+ target: path.join(targetDir, '.env'),
282
+ detail: `Set env vars: ${cap.env.map(e => e.name).join(', ')}`,
283
+ status: 'pending',
284
+ data: {
285
+ entries: cap.env.map(e => ({
286
+ name: e.name,
287
+ description: e.description,
288
+ default: e.default,
289
+ capability: capabilityName,
290
+ })),
291
+ projectName: projectManifest.name,
292
+ },
293
+ });
294
+ // Also need dotenv dependency
295
+ tasks.push({
296
+ op: 'merge-deps',
297
+ source: 'dotenv',
298
+ target: path.join(targetDir, 'package.json'),
299
+ detail: 'Add dotenv dependency (required for env vars)',
300
+ status: 'pending',
301
+ data: { dependencies: ['dotenv@^16'] },
302
+ });
303
+ }
304
+ // Task: inject hooks
305
+ if (cap.hooks && cap.hooks.length > 0) {
306
+ const hasAppTs = fs.existsSync(path.join(targetDir, 'src', 'app.ts'));
307
+ tasks.push({
308
+ op: 'inject-hook',
309
+ source: capabilityName,
310
+ target: path.join(targetDir, 'src', hasAppTs ? 'app.ts' : 'server.ts'),
311
+ detail: `Inject ${cap.hooks.length} hook(s) into ${hasAppTs ? 'app.ts/server.ts' : 'server.ts'}`,
312
+ status: 'pending',
313
+ data: { hooks: cap.hooks, needsEnv: !!hasEnv },
314
+ });
315
+ }
316
+ // Task: inject CSS
317
+ const cssFiles = filesToCopy.filter(f => f.startsWith('styles/') && f.endsWith('.css') && f !== 'styles/styles.css');
318
+ if (cssFiles.length > 0) {
319
+ const cssCapMap = {};
320
+ for (const f of cssFiles) {
321
+ cssCapMap[path.basename(f)] = capabilityName;
322
+ }
323
+ tasks.push({
324
+ op: 'inject-css',
325
+ source: capabilityName,
326
+ target: path.join(targetDir, 'styles', 'styles.css'),
327
+ detail: `Inject CSS imports for ${cssFiles.join(', ')}`,
328
+ status: 'pending',
329
+ data: { cssCapMap },
330
+ });
331
+ }
332
+ // Task: regenerate skills index
333
+ tasks.push({
334
+ op: 'generate-skills-index',
335
+ source: capabilityName,
336
+ target: path.join(targetDir, 'skills', 'index.md'),
337
+ detail: 'Regenerate skills/index.md',
338
+ status: 'pending',
339
+ });
340
+ // Task: update manifest
341
+ tasks.push({
342
+ op: 'update-manifest',
343
+ source: capabilityName,
344
+ target: path.join(targetDir, '.boop', 'config.json'),
345
+ detail: `Add ${capabilityName} to project manifest`,
346
+ status: 'pending',
347
+ data: { hasEnv: !!hasEnv, envVars: cap.env },
348
+ });
349
+ // Task: npm install
350
+ tasks.push({
351
+ op: 'run-command',
352
+ target: targetDir,
353
+ detail: 'npm install',
354
+ status: 'pending',
355
+ data: { command: 'npm install' },
356
+ });
357
+ // Task: rebuild CSS if we injected styles and the project has tailwind:build
358
+ const hasInjectCss = tasks.some(t => t.op === 'inject-css');
359
+ if (hasInjectCss) {
360
+ const pkgJsonPath = path.join(targetDir, 'package.json');
361
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
362
+ if (pkgJson.scripts?.['tailwind:build']) {
363
+ tasks.push({
364
+ op: 'run-command',
365
+ target: targetDir,
366
+ detail: 'Rebuild CSS',
367
+ status: 'pending',
368
+ data: { command: 'npm run tailwind:build' },
369
+ });
370
+ }
371
+ }
372
+ const manifest = {
373
+ capability: capabilityName,
374
+ targetDir,
375
+ timestamp: new Date().toISOString(),
376
+ preflight: { passed: true, checks },
377
+ tasks,
378
+ };
379
+ return { manifest, cap, projectManifest, filesToCopy, warnings };
380
+ }
381
+ /**
382
+ * Recursively enumerate files in srcDir and check for conflicts in destDir.
383
+ * Pure read — does not write.
384
+ */
385
+ function enumerateDir(srcDir, destBase, filesToCopy, fileConflicts, baseDir) {
386
+ const root = baseDir ?? srcDir;
387
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
388
+ for (const entry of entries) {
389
+ const srcPath = path.join(srcDir, entry.name);
390
+ if (entry.isDirectory()) {
391
+ enumerateDir(srcPath, destBase, filesToCopy, fileConflicts, root);
392
+ }
393
+ else {
394
+ const rel = path.relative(root, srcPath);
395
+ const destPath = path.join(destBase, rel);
396
+ filesToCopy.push(rel);
397
+ if (fs.existsSync(destPath)) {
398
+ fileConflicts.push(rel);
399
+ }
400
+ }
401
+ }
402
+ }
@@ -0,0 +1,11 @@
1
+ import { ProfileMetadata } from './config';
2
+ /**
3
+ * List all profiles by scanning the profiles/ directory.
4
+ * Returns metadata for each profile that has a valid profile.md.
5
+ */
6
+ export declare function listProfiles(): ProfileMetadata[];
7
+ /**
8
+ * Get a single profile's metadata by name.
9
+ * Returns null if the profile directory or profile.md doesn't exist.
10
+ */
11
+ export declare function getProfile(name: string): ProfileMetadata | null;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.listProfiles = listProfiles;
37
+ exports.getProfile = getProfile;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const config_1 = require("./config");
41
+ /**
42
+ * List all profiles by scanning the profiles/ directory.
43
+ * Returns metadata for each profile that has a valid profile.md.
44
+ */
45
+ function listProfiles() {
46
+ const profilesDir = path.join((0, config_1.getBoopRoot)(), 'profiles');
47
+ if (!fs.existsSync(profilesDir)) {
48
+ return [];
49
+ }
50
+ const entries = fs.readdirSync(profilesDir, { withFileTypes: true });
51
+ const profiles = [];
52
+ for (const entry of entries) {
53
+ if (!entry.isDirectory())
54
+ continue;
55
+ const profile = getProfile(entry.name);
56
+ if (profile) {
57
+ profiles.push(profile);
58
+ }
59
+ }
60
+ return profiles;
61
+ }
62
+ /**
63
+ * Get a single profile's metadata by name.
64
+ * Returns null if the profile directory or profile.md doesn't exist.
65
+ */
66
+ function getProfile(name) {
67
+ const profileMd = path.join((0, config_1.getBoopRoot)(), 'profiles', name, 'profile.md');
68
+ const parsed = (0, config_1.parseFrontmatter)(profileMd);
69
+ if (!parsed) {
70
+ return null;
71
+ }
72
+ return parsed.data;
73
+ }
@@ -0,0 +1,36 @@
1
+ export interface CapabilityConfig {
2
+ env?: Record<string, {
3
+ required?: string;
4
+ set: boolean;
5
+ }>;
6
+ }
7
+ export interface CapabilityMeta {
8
+ installedAt: string;
9
+ version?: string;
10
+ fileHashes: Record<string, string>;
11
+ }
12
+ export interface BoopManifest {
13
+ name: string;
14
+ profile: string;
15
+ boopVersion: string;
16
+ capabilities: string[];
17
+ capabilityConfig?: Record<string, CapabilityConfig>;
18
+ capabilityMeta?: Record<string, CapabilityMeta>;
19
+ adopted?: string[];
20
+ createdAt: string;
21
+ }
22
+ /**
23
+ * Read and parse the .boop/config.json manifest from a directory.
24
+ * Returns null if the file does not exist.
25
+ * Throws if the file exists but contains invalid JSON.
26
+ */
27
+ export declare function readProjectManifest(dir: string): BoopManifest | null;
28
+ /**
29
+ * Write a .boop/config.json manifest to a directory.
30
+ * Creates the .boop/ directory if it does not exist.
31
+ */
32
+ export declare function writeProjectManifest(dir: string, manifest: BoopManifest): void;
33
+ /**
34
+ * Check if a directory contains a boop project (.boop/config.json exists).
35
+ */
36
+ export declare function isBoopProject(dir: string): boolean;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readProjectManifest = readProjectManifest;
37
+ exports.writeProjectManifest = writeProjectManifest;
38
+ exports.isBoopProject = isBoopProject;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ /**
42
+ * Read and parse the .boop/config.json manifest from a directory.
43
+ * Returns null if the file does not exist.
44
+ * Throws if the file exists but contains invalid JSON.
45
+ */
46
+ function readProjectManifest(dir) {
47
+ const manifestPath = path.join(dir, '.boop', 'config.json');
48
+ if (!fs.existsSync(manifestPath)) {
49
+ return null;
50
+ }
51
+ const content = fs.readFileSync(manifestPath, 'utf-8');
52
+ return JSON.parse(content);
53
+ }
54
+ /**
55
+ * Write a .boop/config.json manifest to a directory.
56
+ * Creates the .boop/ directory if it does not exist.
57
+ */
58
+ function writeProjectManifest(dir, manifest) {
59
+ const boopDir = path.join(dir, '.boop');
60
+ if (!fs.existsSync(boopDir)) {
61
+ fs.mkdirSync(boopDir, { recursive: true });
62
+ }
63
+ const manifestPath = path.join(boopDir, 'config.json');
64
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
65
+ }
66
+ /**
67
+ * Check if a directory contains a boop project (.boop/config.json exists).
68
+ */
69
+ function isBoopProject(dir) {
70
+ return fs.existsSync(path.join(dir, '.boop', 'config.json'));
71
+ }
@@ -0,0 +1 @@
1
+ export {};