@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,295 @@
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.runInit = runInit;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const config_1 = require("../lib/config");
41
+ const profile_1 = require("../lib/profile");
42
+ const capability_1 = require("../lib/capability");
43
+ const scaffold_1 = require("../lib/scaffold");
44
+ const project_1 = require("../lib/project");
45
+ const env_1 = require("../lib/env");
46
+ const safe_io_1 = require("../lib/safe-io");
47
+ function runInit(name, options) {
48
+ const profileName = options.profile ?? 'web-app';
49
+ const shouldInstall = options.install !== false;
50
+ // --- Validate ---
51
+ // Guard: project name must not be empty
52
+ if (!name || !name.trim()) {
53
+ console.error('Error: Project name is required and cannot be empty.');
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+ const targetDir = path.resolve(process.cwd(), name);
58
+ // Guard: target directory must not exist or must be empty
59
+ if (fs.existsSync(targetDir)) {
60
+ const entries = fs.readdirSync(targetDir);
61
+ if (entries.length > 0) {
62
+ console.error(`Error: Directory "${name}" already exists and is not empty.`);
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ }
67
+ // --- Resolve profile ---
68
+ const profile = (0, profile_1.getProfile)(profileName);
69
+ if (!profile) {
70
+ console.error(`Error: Profile "${profileName}" not found.`);
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ // --- Node version check ---
75
+ const requiredNodeVersion = profile.kernel.nodeVersion;
76
+ if (requiredNodeVersion && !process.env.BOOP_SKIP_VERSION_CHECK) {
77
+ const minVersion = requiredNodeVersion.replace(/^[>=^~]+/, '');
78
+ const currentMajor = parseInt(process.versions.node.split('.')[0], 10);
79
+ const requiredMajor = parseInt(minVersion.split('.')[0], 10);
80
+ if (currentMajor < requiredMajor) {
81
+ console.error(`Error: Node.js v${requiredMajor}+ required. You're running v${process.versions.node}.`);
82
+ console.error(`Run \`nvm install ${requiredMajor}\` to upgrade.`);
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+ }
87
+ // --- Pre-flight: detect script conflicts before writing anything ---
88
+ const capabilityNames = profile.defaultCapabilities;
89
+ const preflightScripts = {};
90
+ for (const capName of capabilityNames) {
91
+ const cap = (0, capability_1.getCapability)(capName);
92
+ if (!cap?.scripts)
93
+ continue;
94
+ for (const scriptName of Object.keys(cap.scripts)) {
95
+ if (scriptName in preflightScripts) {
96
+ console.error(`Error: Script "${scriptName}" conflicts between capabilities "${preflightScripts[scriptName]}" and "${capName}".`);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+ preflightScripts[scriptName] = capName;
101
+ }
102
+ }
103
+ // --- Create directory ---
104
+ fs.mkdirSync(targetDir, { recursive: true });
105
+ // --- Collect hooks from capabilities ---
106
+ const hookEntries = [];
107
+ for (const capName of capabilityNames) {
108
+ const cap = (0, capability_1.getCapability)(capName);
109
+ if (cap?.hooks) {
110
+ hookEntries.push({ name: capName, hooks: cap.hooks });
111
+ }
112
+ }
113
+ const hookVars = (0, scaffold_1.collectHooks)(hookEntries);
114
+ // --- Scaffold kernel ---
115
+ const variables = {
116
+ projectName: name,
117
+ port: profile.kernel.port,
118
+ scripts: profile.kernel.scripts,
119
+ nodeVersion: profile.kernel.nodeVersion ?? '>=18.0.0',
120
+ ...hookVars,
121
+ };
122
+ const kernelFiles = (0, scaffold_1.scaffoldKernel)(targetDir, variables);
123
+ // --- Merge kernel dependencies ---
124
+ (0, scaffold_1.mergePackageJson)(targetDir, profile.kernel.dependencies, profile.kernel.devDependencies, undefined, { skipBackup: true });
125
+ // --- Create minimal manifest so capability hash storage can find config.json ---
126
+ (0, project_1.writeProjectManifest)(targetDir, {
127
+ name,
128
+ profile: profileName,
129
+ boopVersion: '0.1.0',
130
+ capabilities: [],
131
+ createdAt: new Date().toISOString(),
132
+ });
133
+ // --- Resolve and install capabilities ---
134
+ const installedCapabilities = [];
135
+ const allScripts = {};
136
+ const scriptSources = {};
137
+ const capabilityFiles = [];
138
+ const cssCapMap = {};
139
+ for (const capName of capabilityNames) {
140
+ const cap = (0, capability_1.getCapability)(capName);
141
+ if (!cap) {
142
+ console.error(`Warning: Capability "${capName}" not found, skipping.`);
143
+ continue;
144
+ }
145
+ // Copy capability files (skills, scaffolded files, etc.)
146
+ const copied = (0, scaffold_1.copyCapabilityFiles)(targetDir, capName);
147
+ capabilityFiles.push(...copied);
148
+ // Track CSS partials for annotation
149
+ for (const f of copied) {
150
+ if (f.startsWith('styles/') && f.endsWith('.css') && f !== 'styles/styles.css') {
151
+ cssCapMap[path.basename(f)] = capName;
152
+ }
153
+ }
154
+ // Merge dependencies
155
+ (0, scaffold_1.mergePackageJson)(targetDir, cap.dependencies, cap.devDependencies, undefined, { skipBackup: true });
156
+ // Add capability package as semver dependency if specified
157
+ if (cap.package) {
158
+ (0, scaffold_1.mergePackageJson)(targetDir, undefined, undefined, {
159
+ [cap.package]: (0, scaffold_1.getBoopkitVersion)(cap.package),
160
+ }, { skipBackup: true });
161
+ }
162
+ // Collect scripts (conflicts already checked in pre-flight)
163
+ if (cap.scripts) {
164
+ for (const [scriptName, scriptDef] of Object.entries(cap.scripts)) {
165
+ allScripts[scriptName] = scriptDef.cmd;
166
+ scriptSources[scriptName] = capName;
167
+ }
168
+ }
169
+ installedCapabilities.push(capName);
170
+ }
171
+ // --- Inject CSS imports for capability partials ---
172
+ (0, scaffold_1.injectCssImports)(targetDir, cssCapMap, { skipBackup: true });
173
+ // --- Merge capability scripts into package.json ---
174
+ if (Object.keys(allScripts).length > 0) {
175
+ const pkgPath = path.join(targetDir, 'package.json');
176
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
177
+ if (!pkg.scripts)
178
+ pkg.scripts = {};
179
+ for (const [scriptName, cmd] of Object.entries(allScripts)) {
180
+ pkg.scripts[scriptName] = cmd;
181
+ }
182
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
183
+ }
184
+ // --- Copy reference docs from profile ---
185
+ const boopRoot = (0, config_1.getBoopRoot)();
186
+ const profileDir = path.join(boopRoot, 'profiles', profileName);
187
+ const profileRefDir = path.join(profileDir, 'reference');
188
+ if (fs.existsSync(profileRefDir)) {
189
+ (0, safe_io_1.safeCopyDir)(profileRefDir, path.join(targetDir, 'reference'), { onConflict: 'skip' });
190
+ }
191
+ // --- Generate skills index from installed skill files ---
192
+ (0, scaffold_1.generateSkillsIndex)(targetDir);
193
+ // --- Build capabilityConfig for capabilities with env vars ---
194
+ const capabilityConfig = {};
195
+ const envEntries = [];
196
+ for (const capName of installedCapabilities) {
197
+ const cap = (0, capability_1.getCapability)(capName);
198
+ if (cap?.env && cap.env.length > 0) {
199
+ const envConfig = {};
200
+ for (const e of cap.env) {
201
+ envConfig[e.name] = { required: e.required, set: false };
202
+ envEntries.push({ name: e.name, description: e.description, default: e.default, capability: capName });
203
+ }
204
+ capabilityConfig[capName] = { env: envConfig };
205
+ }
206
+ }
207
+ // --- Generate .env and .env.example ---
208
+ if (envEntries.length > 0) {
209
+ (0, env_1.generateEnvFiles)(targetDir, envEntries, name);
210
+ }
211
+ // --- Write final manifest, preserving capabilityMeta from hash storage ---
212
+ const existingManifest = (0, project_1.readProjectManifest)(targetDir);
213
+ const manifest = {
214
+ name,
215
+ profile: profileName,
216
+ boopVersion: '0.1.0',
217
+ capabilities: installedCapabilities,
218
+ ...(Object.keys(capabilityConfig).length > 0 && { capabilityConfig }),
219
+ ...(existingManifest?.capabilityMeta && { capabilityMeta: existingManifest.capabilityMeta }),
220
+ createdAt: new Date().toISOString(),
221
+ };
222
+ (0, project_1.writeProjectManifest)(targetDir, manifest);
223
+ // --- Print summary ---
224
+ console.log(`\nCreated project "${name}" with profile "${profileName}"\n`);
225
+ const scriptCount = Object.keys(allScripts).length;
226
+ const parts = [
227
+ `${kernelFiles.length} kernel files`,
228
+ `${installedCapabilities.length} capabilities`,
229
+ `${capabilityFiles.length} capability files`,
230
+ ...(scriptCount > 0 ? [`${scriptCount} scripts`] : []),
231
+ ];
232
+ console.log(` ${parts.join(' · ')}`);
233
+ const typeLabels = {
234
+ application: 'Application',
235
+ development: 'Development',
236
+ operations: 'Operations',
237
+ demo: 'Demo',
238
+ };
239
+ const typeOrder = ['application', 'development', 'operations', 'demo'];
240
+ const grouped = new Map();
241
+ for (const capName of installedCapabilities) {
242
+ const cap = (0, capability_1.getCapability)(capName);
243
+ const key = cap?.type ?? 'other';
244
+ if (!grouped.has(key))
245
+ grouped.set(key, []);
246
+ grouped.get(key).push(capName);
247
+ }
248
+ for (const type of [...typeOrder, ...grouped.keys()]) {
249
+ const caps = grouped.get(type);
250
+ if (!caps)
251
+ continue;
252
+ grouped.delete(type);
253
+ const label = typeLabels[type] ?? type;
254
+ console.log(` ${label}: ${caps.join(', ')}`);
255
+ }
256
+ // --- npm install (after summary so output is clear) ---
257
+ if (shouldInstall) {
258
+ try {
259
+ console.log('\nInstalling dependencies...');
260
+ (0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
261
+ }
262
+ catch {
263
+ console.error('Warning: npm install failed. You can run it manually later.');
264
+ }
265
+ // Build CSS if tailwind:build script exists
266
+ const pkgPath = path.join(targetDir, 'package.json');
267
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
268
+ if (pkg.scripts?.['tailwind:build']) {
269
+ try {
270
+ console.log('\nBuilding CSS...');
271
+ (0, child_process_1.execSync)('npm run tailwind:build', { cwd: targetDir, stdio: 'inherit' });
272
+ }
273
+ catch {
274
+ console.error('Warning: CSS build failed. Run `npm run tailwind:build` manually.');
275
+ }
276
+ }
277
+ // Generate Prisma client if db:generate script exists
278
+ if (pkg.scripts?.['db:generate']) {
279
+ try {
280
+ console.log('\nGenerating Prisma client...');
281
+ (0, child_process_1.execSync)('npm run db:generate', { cwd: targetDir, stdio: 'inherit' });
282
+ }
283
+ catch {
284
+ console.error('Warning: Prisma generate failed. Run `npm run db:generate` manually.');
285
+ }
286
+ }
287
+ }
288
+ console.log(`\nNext steps:`);
289
+ console.log(` cd ${name}`);
290
+ if (!shouldInstall) {
291
+ console.log(' npm install');
292
+ }
293
+ console.log(' boop status ← check what needs configuration');
294
+ console.log(' npm run dev ← start development server');
295
+ }
@@ -0,0 +1,6 @@
1
+ export declare function runList(type: string): void;
2
+ /**
3
+ * Print capabilities grouped by type with install status markers.
4
+ * Reused by both `boop list capabilities` and `boop add` (no arg).
5
+ */
6
+ export declare function printGroupedCapabilities(): void;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runList = runList;
4
+ exports.printGroupedCapabilities = printGroupedCapabilities;
5
+ const capability_1 = require("../lib/capability");
6
+ const profile_1 = require("../lib/profile");
7
+ const project_1 = require("../lib/project");
8
+ function runList(type) {
9
+ switch (type) {
10
+ case 'profiles':
11
+ listProfilesCommand();
12
+ break;
13
+ case 'capabilities':
14
+ listCapabilitiesCommand();
15
+ break;
16
+ default:
17
+ console.error(`Unknown type: ${type}. Use 'profiles' or 'capabilities'.`);
18
+ process.exitCode = 1;
19
+ }
20
+ }
21
+ function listProfilesCommand() {
22
+ const profiles = (0, profile_1.listProfiles)();
23
+ if (profiles.length === 0) {
24
+ console.log('No profiles found.');
25
+ return;
26
+ }
27
+ console.log('Available profiles:\n');
28
+ const typeLabels = {
29
+ application: 'Application',
30
+ development: 'Development',
31
+ operations: 'Operations',
32
+ demo: 'Demo',
33
+ };
34
+ const typeOrder = ['application', 'development', 'operations', 'demo'];
35
+ for (const p of profiles) {
36
+ console.log(` ${p.name}`);
37
+ console.log(` ${p.description}`);
38
+ console.log(` runtime: ${p.kernel.runtime} | deploy: ${p.kernel.deploy} | port: ${p.kernel.port}`);
39
+ const grouped = new Map();
40
+ for (const capName of p.defaultCapabilities) {
41
+ const cap = (0, capability_1.getCapability)(capName);
42
+ const key = cap?.type ?? 'other';
43
+ if (!grouped.has(key))
44
+ grouped.set(key, []);
45
+ grouped.get(key).push(capName);
46
+ }
47
+ console.log(' capabilities:');
48
+ for (const type of [...typeOrder, ...grouped.keys()]) {
49
+ const caps = grouped.get(type);
50
+ if (!caps)
51
+ continue;
52
+ grouped.delete(type);
53
+ const label = typeLabels[type] ?? type;
54
+ console.log(` ${label}: ${caps.join(', ')}`);
55
+ }
56
+ console.log();
57
+ }
58
+ }
59
+ /**
60
+ * Print capabilities grouped by type with install status markers.
61
+ * Reused by both `boop list capabilities` and `boop add` (no arg).
62
+ */
63
+ function printGroupedCapabilities() {
64
+ const capabilities = (0, capability_1.listCapabilities)().filter(c => !c.name.startsWith('_'));
65
+ if (capabilities.length === 0) {
66
+ console.log('No capabilities found.');
67
+ return;
68
+ }
69
+ const typeLabels = {
70
+ application: 'Application',
71
+ development: 'Development',
72
+ operations: 'Operations',
73
+ demo: 'Demo',
74
+ };
75
+ const typeOrder = ['application', 'development', 'operations', 'demo'];
76
+ const grouped = new Map();
77
+ for (const c of capabilities) {
78
+ const key = c.type ?? 'other';
79
+ if (!grouped.has(key))
80
+ grouped.set(key, []);
81
+ grouped.get(key).push(c);
82
+ }
83
+ const manifest = (0, project_1.readProjectManifest)(process.cwd());
84
+ const installed = new Set(manifest?.capabilities ?? []);
85
+ console.log('Available capabilities:\n');
86
+ for (const type of [...typeOrder, ...grouped.keys()]) {
87
+ const caps = grouped.get(type);
88
+ if (!caps)
89
+ continue;
90
+ grouped.delete(type);
91
+ const label = typeLabels[type] ?? type;
92
+ console.log(` ${label}`);
93
+ for (const c of caps) {
94
+ const isInstalled = installed.has(c.name);
95
+ const mark = isInstalled ? '\x1b[2m\u2713\x1b[0m ' : '\x1b[32m\u25cb\x1b[0m ';
96
+ const name = isInstalled ? `\x1b[2m${c.name}\x1b[0m` : `\x1b[32m${c.name}\x1b[0m`;
97
+ const desc = isInstalled ? `\x1b[2m${c.description}\x1b[0m` : c.description;
98
+ console.log(` ${mark}${name} \u2014 ${desc}`);
99
+ }
100
+ console.log();
101
+ }
102
+ }
103
+ function listCapabilitiesCommand() {
104
+ printGroupedCapabilities();
105
+ }
@@ -0,0 +1,63 @@
1
+ export interface LoopOptions {
2
+ run?: boolean;
3
+ prefix?: string;
4
+ work?: string;
5
+ flow?: string;
6
+ max?: string;
7
+ budget?: string;
8
+ resumeLimit?: string;
9
+ poll?: boolean;
10
+ pollInterval?: string;
11
+ model?: string;
12
+ perIssueBranch?: boolean;
13
+ noBranch?: boolean;
14
+ features?: boolean;
15
+ skipPerms?: boolean;
16
+ bare?: boolean;
17
+ json?: boolean;
18
+ }
19
+ interface ClaudeRunResult {
20
+ exitCode: number;
21
+ limitHit: boolean;
22
+ }
23
+ export interface PlanResult {
24
+ featureId: string | null;
25
+ costUsd: number | null;
26
+ }
27
+ export declare function buildPlanningPrompt(workContent: string, flowContent: string, prefix?: string): string;
28
+ export type RunClaudeFn = (prompt: string, opts: {
29
+ budget: string;
30
+ model: string;
31
+ skipPerms: boolean;
32
+ sessionId: string;
33
+ resume?: boolean;
34
+ bare?: boolean;
35
+ }, logFile: string, readableLogFile?: string) => Promise<ClaudeRunResult>;
36
+ export declare function planWork(opts: {
37
+ workPath: string;
38
+ flowPath: string;
39
+ prefix?: string;
40
+ budget: string;
41
+ model: string;
42
+ skipPerms: boolean;
43
+ bare: boolean;
44
+ logDir: string;
45
+ runClaudeFn?: RunClaudeFn;
46
+ execSyncFn?: (cmd: string, opts: {
47
+ encoding: string;
48
+ }) => string;
49
+ }): Promise<PlanResult>;
50
+ export declare function findFallbackFeature(features: Array<{
51
+ id?: string;
52
+ created_at?: string;
53
+ title?: string;
54
+ }>, workContent: string, workPath: string): string | null;
55
+ export declare function extractFeatureId(text: string): string | null;
56
+ export declare function isUsageLimitText(text: string): boolean;
57
+ export declare function isStandaloneUsageLimitMessage(text: string): boolean;
58
+ export declare function isIssueInScope(issue: {
59
+ id: string;
60
+ parent?: string;
61
+ }, prefix: string, getParentById: (id: string) => string | null): boolean;
62
+ export declare function runLoop(options: LoopOptions): Promise<void>;
63
+ export {};