@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,116 @@
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.generateEnvFiles = generateEnvFiles;
37
+ exports.readEnvKeys = readEnvKeys;
38
+ exports.readEnvValues = readEnvValues;
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const interpolate_1 = require("./interpolate");
43
+ /**
44
+ * Generate or append to .env and .env.example files from capability env declarations.
45
+ * - Creates files if they don't exist
46
+ * - Appends new entries without duplicating existing ones
47
+ * - .env gets empty values (so the var is unset and capabilities degrade gracefully)
48
+ * - .env.example gets interpolated defaults (as documentation for the developer)
49
+ */
50
+ function generateEnvFiles(dir, entries, projectName) {
51
+ const envPath = path.join(dir, '.env');
52
+ const examplePath = path.join(dir, '.env.example');
53
+ const existingEnv = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
54
+ const existingExample = fs.existsSync(examplePath) ? fs.readFileSync(examplePath, 'utf-8') : '';
55
+ const newEnvLines = buildNewEntries(existingEnv, entries, projectName, false);
56
+ const newExampleLines = buildNewEntries(existingExample, entries, projectName, true);
57
+ fs.writeFileSync(envPath, existingEnv + newEnvLines, 'utf-8');
58
+ fs.writeFileSync(examplePath, existingExample + newExampleLines, 'utf-8');
59
+ }
60
+ function buildNewEntries(existingContent, entries, projectName, includeDefaults) {
61
+ const lines = [];
62
+ for (const entry of entries) {
63
+ // Skip if this env var already exists in the file
64
+ const pattern = new RegExp(`^${escapeRegex(entry.name)}=`, 'm');
65
+ if (pattern.test(existingContent)) {
66
+ continue;
67
+ }
68
+ const value = includeDefaults && entry.default
69
+ ? (0, interpolate_1.interpolate)(entry.default, { projectName, username: os.userInfo().username })
70
+ : '';
71
+ lines.push(`# ${entry.name} — ${entry.description} (${entry.capability} capability)`);
72
+ lines.push(`${entry.name}=${value}`);
73
+ }
74
+ return lines.length > 0 ? lines.join('\n') + '\n' : '';
75
+ }
76
+ function escapeRegex(str) {
77
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
78
+ }
79
+ /**
80
+ * Read a .env file and return the set of variable names that have non-empty values.
81
+ * Returns an empty set if the file does not exist.
82
+ */
83
+ function readEnvKeys(filePath) {
84
+ const values = readEnvValues(filePath);
85
+ return new Set(values.keys());
86
+ }
87
+ /**
88
+ * Read a .env file and return a map of variable names to their values.
89
+ * Only includes variables with non-empty values.
90
+ * Returns an empty map if the file does not exist.
91
+ */
92
+ function readEnvValues(filePath) {
93
+ if (!fs.existsSync(filePath)) {
94
+ return new Map();
95
+ }
96
+ const content = fs.readFileSync(filePath, 'utf-8');
97
+ const entries = new Map();
98
+ for (const line of content.split('\n')) {
99
+ const trimmed = line.trim();
100
+ if (!trimmed || trimmed.startsWith('#'))
101
+ continue;
102
+ const eqIndex = trimmed.indexOf('=');
103
+ if (eqIndex === -1)
104
+ continue;
105
+ const key = trimmed.slice(0, eqIndex).trim();
106
+ let value = trimmed.slice(eqIndex + 1).trim();
107
+ // Strip surrounding quotes (single or double)
108
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
109
+ value = value.slice(1, -1);
110
+ }
111
+ if (key && value) {
112
+ entries.set(key, value);
113
+ }
114
+ }
115
+ return entries;
116
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Execute phase for `boop add`.
3
+ *
4
+ * Walks a pre-flight manifest and performs each operation using the
5
+ * safe-io utilities. Each task is marked done/failed/skipped as it executes.
6
+ */
7
+ import { AddManifest, ExecuteResult } from './manifest';
8
+ /**
9
+ * Execute all tasks in a manifest. Returns the result with per-task status.
10
+ */
11
+ export declare function executeManifest(manifest: AddManifest, options?: {
12
+ install?: boolean;
13
+ }): ExecuteResult;
14
+ /**
15
+ * Save a completed manifest to .boop/logs/ for audit trail.
16
+ */
17
+ export declare function saveManifestLog(manifest: AddManifest): void;
18
+ /**
19
+ * Format a manifest as a human-readable summary (for --dry-run output).
20
+ */
21
+ export declare function formatManifest(manifest: AddManifest): string;
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * Execute phase for `boop add`.
4
+ *
5
+ * Walks a pre-flight manifest and performs each operation using the
6
+ * safe-io utilities. Each task is marked done/failed/skipped as it executes.
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.executeManifest = executeManifest;
43
+ exports.saveManifestLog = saveManifestLog;
44
+ exports.formatManifest = formatManifest;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const child_process_1 = require("child_process");
48
+ const scaffold_1 = require("./scaffold");
49
+ const project_1 = require("./project");
50
+ const env_1 = require("./env");
51
+ const rewire_1 = require("./rewire");
52
+ const safe_io_1 = require("./safe-io");
53
+ /**
54
+ * Execute all tasks in a manifest. Returns the result with per-task status.
55
+ */
56
+ function executeManifest(manifest, options = {}) {
57
+ const shouldInstall = options.install !== false;
58
+ const completed = [];
59
+ const failed = [];
60
+ const skipped = [];
61
+ // Back up each mutable target file once before executing any tasks
62
+ const backedUp = new Set();
63
+ for (const task of manifest.tasks) {
64
+ if (task.op === 'copy-files' || task.op === 'run-command')
65
+ continue;
66
+ if (task.target && fs.existsSync(task.target) && !backedUp.has(task.target)) {
67
+ (0, safe_io_1.createBackup)(task.target);
68
+ backedUp.add(task.target);
69
+ }
70
+ }
71
+ for (const task of manifest.tasks) {
72
+ // Skip npm install if install=false
73
+ if (task.op === 'run-command' && !shouldInstall) {
74
+ task.status = 'skipped';
75
+ skipped.push(task);
76
+ continue;
77
+ }
78
+ try {
79
+ executeTask(task, manifest);
80
+ task.status = 'done';
81
+ completed.push(task);
82
+ }
83
+ catch (err) {
84
+ task.status = 'failed';
85
+ failed.push(task);
86
+ // Log but continue — best effort for remaining tasks
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ console.error(`Warning: Task "${task.detail}" failed: ${message}`);
89
+ }
90
+ }
91
+ return {
92
+ success: failed.length === 0,
93
+ completed,
94
+ failed,
95
+ skipped,
96
+ };
97
+ }
98
+ function executeTask(task, manifest) {
99
+ const { targetDir, capability: capabilityName } = manifest;
100
+ switch (task.op) {
101
+ case 'copy-files': {
102
+ (0, scaffold_1.copyCapabilityFiles)(targetDir, capabilityName);
103
+ break;
104
+ }
105
+ case 'merge-deps': {
106
+ const data = task.data;
107
+ (0, scaffold_1.mergePackageJson)(targetDir, data.dependencies, data.devDependencies, undefined, { skipBackup: true });
108
+ break;
109
+ }
110
+ case 'merge-scripts': {
111
+ const data = task.data;
112
+ const pkgPath = path.join(targetDir, 'package.json');
113
+ if (fs.existsSync(pkgPath)) {
114
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
115
+ if (!pkg.scripts)
116
+ pkg.scripts = {};
117
+ for (const [scriptName, scriptDef] of Object.entries(data.scripts)) {
118
+ if (!(scriptName in pkg.scripts)) {
119
+ pkg.scripts[scriptName] = scriptDef.cmd;
120
+ }
121
+ }
122
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
123
+ }
124
+ break;
125
+ }
126
+ case 'link-package': {
127
+ const data = task.data;
128
+ (0, scaffold_1.mergePackageJson)(targetDir, undefined, undefined, {
129
+ [data.packageName]: (0, scaffold_1.getBoopkitVersion)(data.packageName),
130
+ }, { skipBackup: true });
131
+ break;
132
+ }
133
+ case 'set-env': {
134
+ const data = task.data;
135
+ (0, env_1.generateEnvFiles)(targetDir, data.entries, data.projectName);
136
+ break;
137
+ }
138
+ case 'inject-hook': {
139
+ const data = task.data;
140
+ const appPath = path.join(targetDir, 'src', 'app.ts');
141
+ const serverPath = path.join(targetDir, 'src', 'server.ts');
142
+ // New structure: all hooks in app.ts; legacy: all in server.ts
143
+ const targetFile = fs.existsSync(appPath) ? appPath : serverPath;
144
+ if (fs.existsSync(targetFile)) {
145
+ (0, rewire_1.rewireServerFile)(targetFile, capabilityName, data.hooks, {
146
+ needsEnv: data.needsEnv,
147
+ skipBackup: true,
148
+ });
149
+ }
150
+ break;
151
+ }
152
+ case 'inject-css': {
153
+ const data = task.data;
154
+ (0, scaffold_1.injectCssImports)(targetDir, data.cssCapMap, { skipBackup: true });
155
+ break;
156
+ }
157
+ case 'generate-skills-index': {
158
+ (0, scaffold_1.generateSkillsIndex)(targetDir);
159
+ break;
160
+ }
161
+ case 'update-manifest': {
162
+ const data = task.data;
163
+ const projectManifest = (0, project_1.readProjectManifest)(targetDir);
164
+ if (projectManifest) {
165
+ projectManifest.capabilities.push(capabilityName);
166
+ if (data.hasEnv && data.envVars) {
167
+ if (!projectManifest.capabilityConfig)
168
+ projectManifest.capabilityConfig = {};
169
+ const envConfig = {};
170
+ for (const e of data.envVars) {
171
+ envConfig[e.name] = { required: e.required, set: false };
172
+ }
173
+ const capConfig = { env: envConfig };
174
+ projectManifest.capabilityConfig[capabilityName] = capConfig;
175
+ }
176
+ (0, project_1.writeProjectManifest)(targetDir, projectManifest);
177
+ }
178
+ break;
179
+ }
180
+ case 'run-command': {
181
+ const data = task.data;
182
+ console.log(`\nRunning ${data.command}...`);
183
+ (0, child_process_1.execSync)(data.command, { cwd: targetDir, stdio: 'inherit' });
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * Save a completed manifest to .boop/logs/ for audit trail.
190
+ */
191
+ function saveManifestLog(manifest) {
192
+ const logsDir = path.join(manifest.targetDir, '.boop', 'logs');
193
+ if (!fs.existsSync(logsDir)) {
194
+ fs.mkdirSync(logsDir, { recursive: true });
195
+ }
196
+ const timestamp = manifest.timestamp.replace(/[:.]/g, '-');
197
+ const logPath = path.join(logsDir, `add-${manifest.capability}-${timestamp}.json`);
198
+ fs.writeFileSync(logPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
199
+ }
200
+ /**
201
+ * Format a manifest as a human-readable summary (for --dry-run output).
202
+ */
203
+ function formatManifest(manifest) {
204
+ const lines = [];
205
+ lines.push(`Plan: boop add ${manifest.capability}`);
206
+ lines.push(`Target: ${manifest.targetDir}`);
207
+ lines.push('');
208
+ // Pre-flight checks
209
+ lines.push('Pre-flight checks:');
210
+ for (const check of manifest.preflight.checks) {
211
+ const hasWarning = check.passed && check.message;
212
+ const icon = !check.passed ? '✗' : hasWarning ? '◐' : '✓';
213
+ const msg = check.message ? ` — ${check.message}` : '';
214
+ lines.push(` ${icon} ${check.name}${msg}`);
215
+ }
216
+ lines.push('');
217
+ // Tasks
218
+ lines.push(`Tasks (${manifest.tasks.length}):`);
219
+ for (let i = 0; i < manifest.tasks.length; i++) {
220
+ const task = manifest.tasks[i];
221
+ lines.push(` ${i + 1}. [${task.op}] ${task.detail}`);
222
+ if (task.source) {
223
+ lines.push(` from: ${task.source}`);
224
+ }
225
+ lines.push(` to: ${task.target}`);
226
+ }
227
+ return lines.join('\n');
228
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Replace ${name} placeholders in a string with values from a vars map.
3
+ */
4
+ export declare function interpolate(str: string, vars: Record<string, string>): string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interpolate = interpolate;
4
+ /**
5
+ * Replace ${name} placeholders in a string with values from a vars map.
6
+ */
7
+ function interpolate(str, vars) {
8
+ let result = str;
9
+ for (const [name, value] of Object.entries(vars)) {
10
+ result = result.split(`\${${name}}`).join(value);
11
+ }
12
+ return result;
13
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Types for the two-phase pre-flight → execute architecture.
3
+ *
4
+ * Phase 1 (pre-flight) builds an AddManifest — a complete, ordered list of
5
+ * every mutation that `boop add` will perform.
6
+ *
7
+ * Phase 2 (execute) walks the manifest, performing each operation and
8
+ * updating task status.
9
+ */
10
+ export type ManifestOp = 'copy-files' | 'merge-deps' | 'merge-scripts' | 'link-package' | 'inject-hook' | 'inject-css' | 'set-env' | 'generate-skills-index' | 'update-manifest' | 'run-command';
11
+ export type TaskStatus = 'pending' | 'done' | 'failed' | 'skipped';
12
+ export interface ManifestTask {
13
+ op: ManifestOp;
14
+ /** Where data comes from (capability dir, package dir, etc.) */
15
+ source?: string;
16
+ /** What gets modified in the developer's project */
17
+ target: string;
18
+ /** Human-readable description of what this task does */
19
+ detail: string;
20
+ /** Execution status — starts as 'pending', updated during execute phase */
21
+ status: TaskStatus;
22
+ /** Additional data needed for execution (op-specific) */
23
+ data?: Record<string, unknown>;
24
+ }
25
+ export interface PreflightCheck {
26
+ name: string;
27
+ passed: boolean;
28
+ message?: string;
29
+ }
30
+ export interface PreflightResult {
31
+ passed: boolean;
32
+ checks: PreflightCheck[];
33
+ }
34
+ export interface AddManifest {
35
+ /** Capability being added */
36
+ capability: string;
37
+ /** Target project directory */
38
+ targetDir: string;
39
+ /** ISO 8601 timestamp when the plan was created */
40
+ timestamp: string;
41
+ /** Results of all pre-flight checks */
42
+ preflight: PreflightResult;
43
+ /** Ordered list of operations to perform */
44
+ tasks: ManifestTask[];
45
+ }
46
+ export interface ExecuteResult {
47
+ success: boolean;
48
+ /** Tasks that were executed */
49
+ completed: ManifestTask[];
50
+ /** Tasks that failed */
51
+ failed: ManifestTask[];
52
+ /** Tasks that were skipped */
53
+ skipped: ManifestTask[];
54
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * Types for the two-phase pre-flight → execute architecture.
4
+ *
5
+ * Phase 1 (pre-flight) builds an AddManifest — a complete, ordered list of
6
+ * every mutation that `boop add` will perform.
7
+ *
8
+ * Phase 2 (execute) walks the manifest, performing each operation and
9
+ * updating task status.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pre-flight planning for `boop add`.
3
+ *
4
+ * Reads all inputs, validates everything, and produces a manifest of tasks
5
+ * to execute — without writing a single file.
6
+ */
7
+ import { CapabilityMetadata } from './config';
8
+ import { BoopManifest } from './project';
9
+ import { AddManifest, PreflightCheck } from './manifest';
10
+ export declare class PreflightError extends Error {
11
+ readonly checks: PreflightCheck[];
12
+ constructor(message: string, checks: PreflightCheck[]);
13
+ }
14
+ export interface PlanAddResult {
15
+ manifest: AddManifest;
16
+ /** Resolved capability metadata (for use in output formatting) */
17
+ cap: CapabilityMetadata;
18
+ /** Current project manifest (for use in output formatting) */
19
+ projectManifest: BoopManifest;
20
+ /** Files that would be copied (relative paths) */
21
+ filesToCopy: string[];
22
+ /** Warnings generated during pre-flight (non-fatal) */
23
+ warnings: string[];
24
+ }
25
+ /**
26
+ * Plan a `boop add` operation. Reads filesystem, validates everything,
27
+ * and produces a manifest. Writes nothing.
28
+ *
29
+ * Throws PreflightError if any check fails.
30
+ */
31
+ export declare function planAdd(targetDir: string, capabilityName: string): PlanAddResult;