@boopkit/cli 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -17,9 +17,29 @@ npx @boopkit/cli@beta init myapp
17
17
  ## Quick Start
18
18
 
19
19
  ```bash
20
+ # go to your projects directory
21
+ cd ~/projects
22
+
23
+ # install the boop CLI globally
24
+ npm install -g @boopkit/cli@beta
25
+
26
+ # scaffold a new app from the default profile (web-app)
20
27
  boop init my-app
21
28
  cd my-app
22
- npm run dev
29
+ boop status # have your agent follow instructions to finish setup
30
+
31
+ # start your web server
32
+ npm run dev
33
+
34
+ # stop the server...
35
+
36
+ # see available capabilities to add new features
37
+ npm caps
38
+
39
+ # add user authentication and account management features
40
+ # have your agent follow instructions to finish setup
41
+ boop add users-and-accounts
42
+
23
43
  ```
24
44
 
25
45
  ## Commands
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "@boopkit/cap-api": "0.1.0",
3
+ "@boopkit/cap-background-jobs": "0.2.0",
3
4
  "@boopkit/cap-cloud-storage": "0.1.0",
4
5
  "@boopkit/cap-coding": "0.1.0",
5
- "@boopkit/cap-database": "0.1.2",
6
+ "@boopkit/cap-database": "0.1.3",
6
7
  "@boopkit/cap-email": "0.1.0",
7
8
  "@boopkit/cap-hello": "0.1.0",
8
9
  "@boopkit/cap-noticeboard": "0.1.1",
package/dist/cli.js CHANGED
@@ -52,6 +52,7 @@ program
52
52
  .option('--force', 'Overwrite locally modified files (creates .bak backup)')
53
53
  .option('--restore', 'Restore locally deleted scaffold files')
54
54
  .option('--prune', 'Remove deprecated scaffold files from disk (creates .bak backup)')
55
+ .option('--skip-npm', 'Skip npm update (only re-scaffold from installed versions)')
55
56
  .option('--json', 'Output structured JSON')
56
57
  .action(update_1.runUpdate);
57
58
  program
@@ -10,7 +10,7 @@ export interface LoopOptions {
10
10
  pollInterval?: string;
11
11
  model?: string;
12
12
  perIssueBranch?: boolean;
13
- noBranch?: boolean;
13
+ branch?: boolean;
14
14
  features?: boolean;
15
15
  skipPerms?: boolean;
16
16
  bare?: boolean;
@@ -577,7 +577,7 @@ async function runLoop(options) {
577
577
  const pollInterval = parseInt(options.pollInterval || '60', 10);
578
578
  const model = options.model || 'claude-opus-4-6';
579
579
  const perIssueBranch = options.perIssueBranch || false;
580
- const noBranch = options.noBranch || false;
580
+ const noBranch = options.branch === false;
581
581
  let includeFeatures = options.features || false;
582
582
  const skipPerms = options.skipPerms !== false; // default true
583
583
  const bare = options.bare || false;
@@ -68,7 +68,7 @@ function getStatusData(manifest, dir) {
68
68
  const capabilities = {};
69
69
  for (const capName of manifest.capabilities) {
70
70
  const capConfig = manifest.capabilityConfig?.[capName];
71
- const capMeta = (0, capability_1.getCapability)(capName);
71
+ const capMeta = (0, capability_1.getCapability)(capName, [dir]);
72
72
  const capType = capMeta?.type;
73
73
  // Resolve env declarations: prefer manifest capabilityConfig, fall back to capability definition
74
74
  let envEntries;
@@ -118,7 +118,7 @@ function getStatusData(manifest, dir) {
118
118
  }
119
119
  if (allRequiredSet) {
120
120
  // Env vars are set — check if capability has setup checks or postInstall steps
121
- const capDef = (0, capability_1.getCapability)(capName);
121
+ const capDef = (0, capability_1.getCapability)(capName, [dir]);
122
122
  const hasSetupChecks = capDef?.postInstall?.setupChecks?.length;
123
123
  const hasPostInstall = capDef?.postInstall && (capDef.postInstall.steps?.length || capDef.postInstall.verify);
124
124
  if (hasSetupChecks || hasPostInstall) {
@@ -131,7 +131,7 @@ function getStatusData(manifest, dir) {
131
131
  }
132
132
  else {
133
133
  // Enrich next steps with postInstall guidance from capability definition
134
- const capDef = (0, capability_1.getCapability)(capName);
134
+ const capDef = (0, capability_1.getCapability)(capName, [dir]);
135
135
  if (capDef?.postInstall) {
136
136
  const vars = { projectName: manifest.name, capabilityName: capName };
137
137
  const pi = capDef.postInstall;
@@ -171,7 +171,7 @@ async function enrichWithSetupChecks(data, envValues, projectDir, projectName) {
171
171
  for (const [capName, cap] of Object.entries(data.capabilities)) {
172
172
  if (cap.status !== 'setup')
173
173
  continue;
174
- const capDef = (0, capability_1.getCapability)(capName);
174
+ const capDef = (0, capability_1.getCapability)(capName, [projectDir]);
175
175
  const checks = capDef?.postInstall?.setupChecks;
176
176
  if (!checks?.length)
177
177
  continue;
@@ -4,5 +4,6 @@ export interface UpdateOptions {
4
4
  restore?: boolean;
5
5
  prune?: boolean;
6
6
  json?: boolean;
7
+ skipNpm?: boolean;
7
8
  }
8
9
  export declare function runUpdate(capabilityName: string | undefined, options: UpdateOptions): void;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.runUpdate = runUpdate;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
39
40
  const project_1 = require("../lib/project");
40
41
  const capability_1 = require("../lib/capability");
41
42
  const update_1 = require("../lib/update");
@@ -58,6 +59,21 @@ function runUpdate(capabilityName, options) {
58
59
  process.exitCode = 1;
59
60
  return;
60
61
  }
62
+ // Step 1: npm install capability packages at latest within current major (unless --skip-npm or --dry-run)
63
+ if (!options.skipNpm && !options.dryRun) {
64
+ const packageSpecs = resolveNpmPackageSpecs(capsToUpdate, targetDir);
65
+ if (packageSpecs.length > 0) {
66
+ if (!options.json) {
67
+ console.log(`Updating npm packages: ${packageSpecs.join(', ')}`);
68
+ }
69
+ try {
70
+ (0, child_process_1.execSync)(`npm install ${packageSpecs.join(' ')}`, { cwd: targetDir, stdio: options.json ? 'pipe' : 'inherit' });
71
+ }
72
+ catch (err) {
73
+ console.error(`\x1b[33mWARN\x1b[0m npm install failed — continuing with scaffold update using installed versions`);
74
+ }
75
+ }
76
+ }
61
77
  // Plan all updates
62
78
  const plans = [];
63
79
  for (const cap of capsToUpdate) {
@@ -67,7 +83,7 @@ function runUpdate(capabilityName, options) {
67
83
  }
68
84
  }
69
85
  if (plans.length === 0) {
70
- const diagnosticMessage = diagnoseMissingPlans(capsToUpdate, capabilityName);
86
+ const diagnosticMessage = diagnoseMissingPlans(capsToUpdate, capabilityName, targetDir);
71
87
  if (options.json) {
72
88
  console.log(JSON.stringify({ capabilities: [], message: diagnosticMessage }, null, 2));
73
89
  }
@@ -160,7 +176,7 @@ function runUpdate(capabilityName, options) {
160
176
  /**
161
177
  * Diagnose why no update plans were generated and return a specific message.
162
178
  */
163
- function diagnoseMissingPlans(capsToUpdate, requestedCap) {
179
+ function diagnoseMissingPlans(capsToUpdate, requestedCap, targetDir) {
164
180
  if (capsToUpdate.length === 0) {
165
181
  return 'No capabilities installed. Run `boop add <capability>` to install one.';
166
182
  }
@@ -168,7 +184,7 @@ function diagnoseMissingPlans(capsToUpdate, requestedCap) {
168
184
  const noPackage = [];
169
185
  const noBoopkit = [];
170
186
  for (const cap of capsToUpdate) {
171
- const pkgDir = (0, capability_1.getPackageDir)(cap);
187
+ const pkgDir = (0, capability_1.getPackageDir)(cap, [targetDir]);
172
188
  if (!pkgDir) {
173
189
  noPackage.push(cap);
174
190
  continue;
@@ -202,3 +218,26 @@ function diagnoseMissingPlans(capsToUpdate, requestedCap) {
202
218
  }
203
219
  return 'No capabilities to update.';
204
220
  }
221
+ /**
222
+ * Resolve capability names to npm install specs pinned to the current major version.
223
+ * E.g. @boopkit/cap-background-jobs@0.2.0 → "@boopkit/cap-background-jobs@0"
224
+ * This allows minor+patch updates within the same major (including 0.x → 0.y)
225
+ * without crossing a major version boundary.
226
+ */
227
+ function resolveNpmPackageSpecs(capabilities, targetDir) {
228
+ const specs = [];
229
+ for (const cap of capabilities) {
230
+ const pkgDir = (0, capability_1.getPackageDir)(cap, [targetDir]);
231
+ if (!pkgDir)
232
+ continue;
233
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
234
+ if (!fs.existsSync(pkgJsonPath))
235
+ continue;
236
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
237
+ if (pkgJson.name && pkgJson.boopkit?.capability && pkgJson.version) {
238
+ const major = pkgJson.version.split('.')[0];
239
+ specs.push(`${pkgJson.name}@${major}`);
240
+ }
241
+ }
242
+ return specs;
243
+ }
@@ -73,8 +73,8 @@ function planAdd(targetDir, capabilityName) {
73
73
  throw new PreflightError('Pre-flight failed', checks);
74
74
  }
75
75
  checks.push({ name: 'project-exists', passed: true });
76
- // 2. Resolve capability
77
- const cap = (0, capability_1.getCapability)(capabilityName);
76
+ // 2. Resolve capability (search project's node_modules first, then CLI root)
77
+ const cap = (0, capability_1.getCapability)(capabilityName, [targetDir]);
78
78
  if (!cap) {
79
79
  checks.push({ name: 'capability-exists', passed: false, message: `Capability "${capabilityName}" not found` });
80
80
  throw new PreflightError('Pre-flight failed', checks);
@@ -101,7 +101,7 @@ function planAdd(targetDir, capabilityName) {
101
101
  }
102
102
  // 5. Enumerate files that would be copied — check for conflicts
103
103
  // Resolve from package (scaffold/ and skills/) or source tree (files/ and skills/)
104
- const pkgDir = (0, capability_1.getPackageDir)(capabilityName);
104
+ const pkgDir = (0, capability_1.getPackageDir)(capabilityName, [targetDir]);
105
105
  const pkgJsonPath = pkgDir ? path.join(pkgDir, 'package.json') : null;
106
106
  const pkgJson = pkgJsonPath && fs.existsSync(pkgJsonPath)
107
107
  ? JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))
@@ -69,7 +69,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
69
69
  return null;
70
70
  }
71
71
  // Must have a package
72
- const pkgDir = (0, capability_1.getPackageDir)(capabilityName);
72
+ const pkgDir = (0, capability_1.getPackageDir)(capabilityName, [targetDir]);
73
73
  if (!pkgDir)
74
74
  return null;
75
75
  const pkgJsonPath = path.join(pkgDir, 'package.json');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "CLI tool to scaffold new projects from boop profiles and capabilities",
5
5
  "main": "dist/cli.js",
6
6
  "workspaces": [
@@ -26,8 +26,8 @@ As the application grows, the architecture supports additional Heroku process ty
26
26
 
27
27
  ## Data Layer
28
28
  ### **PostgreSQL** (Prisma ORM) - Primary data store
29
- - Schema: `src/db/prisma/schema.prisma`
30
- - Models in `src/db/models/`, Generated client at `@prisma/*`
29
+ - Schema directory: `prisma/schema/` (multi-file — Prisma auto-discovers all `.prisma` files)
30
+ - Generated client at `@prisma/client`
31
31
 
32
32
  > **Context:** This project uses a hybrid relational + document model: key entities are tables with FK relationships; most attributes live in an `info` JSON column with `info_schema` and `revision` for change tracking.
33
33
 
@@ -60,6 +60,6 @@ Some services are low-level and more 'data' oriented or third-party API wrappers
60
60
 
61
61
 
62
62
  ### Data Access
63
- - **Prisma ORM** (`src/db/prisma/`) — PostgreSQL models, migrations, and generated client
63
+ - **Prisma ORM** (`prisma/schema/`) — PostgreSQL models, migrations, and generated client
64
64
  - **ClickHouse** (`src/ch/`) — Analytics queries via `ClickHouseService`
65
65
  - **S3** — Object storage via `StorageService` with per-feature clients