bmad-cybersec 2.0.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.
@@ -0,0 +1,480 @@
1
+ import { existsSync, promises as fs } from 'fs';
2
+ import { join } from 'path';
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+ import { logger } from './logger.js';
6
+
7
+ // Security: Keys that could enable prototype pollution attacks
8
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
9
+
10
+ // Security: Pattern to detect shell metacharacters in scripts (for script injection prevention)
11
+ const SHELL_METACHAR_PATTERN = /[`$|;&<>(){}[\]\n\r\\]/;
12
+
13
+ // Security: Pattern to detect path traversal in package names
14
+ const PATH_TRAVERSAL_PATTERN = /\.\.|^\/|^\\/;
15
+
16
+ // Security: Pattern to detect typosquatting (common package name variations)
17
+ const TYPOSQUATTING_PATTERNS = [
18
+ /^(@.*\/)?lodash[^a-z]/, // lodash-es is fine, lodash. is suspicious
19
+ /^(@.*\/)?react[^a-z-](?!native|dom|router)/i, // react with unusual suffix
20
+ /^(@.*\/)?express[^a-z-]/i, // express with unusual suffix
21
+ ];
22
+
23
+ /**
24
+ * Sanitizes an object by removing dangerous prototype pollution keys
25
+ * @param {Object} obj - Object to sanitize
26
+ * @returns {Object} Sanitized object without dangerous keys
27
+ */
28
+ function sanitizeObject(obj) {
29
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
30
+ return obj;
31
+ }
32
+
33
+ const sanitized = {};
34
+ // Use Object.getOwnPropertyNames to catch ALL own properties including __proto__
35
+ // JSON.parse can create objects with __proto__ as an own property
36
+ for (const key of Object.getOwnPropertyNames(obj)) {
37
+ // Skip dangerous keys that could enable prototype pollution
38
+ if (DANGEROUS_KEYS.has(key)) {
39
+ logger.warn(`Blocked dangerous key "${key}" in package.json (prototype pollution prevention)`);
40
+ continue;
41
+ }
42
+ sanitized[key] = obj[key];
43
+ }
44
+ return sanitized;
45
+ }
46
+
47
+ /**
48
+ * Validates a package name for path traversal attacks
49
+ * @param {string} name - Package name to validate
50
+ * @returns {{ valid: boolean, error?: string }}
51
+ */
52
+ function validatePackageName(name) {
53
+ if (!name || typeof name !== 'string') {
54
+ return { valid: false, error: 'Package name must be a non-empty string' };
55
+ }
56
+
57
+ // Check for path traversal patterns
58
+ if (PATH_TRAVERSAL_PATTERN.test(name)) {
59
+ return {
60
+ valid: false,
61
+ error: `Package name "${name}" contains path traversal characters`
62
+ };
63
+ }
64
+
65
+ // Check for absolute paths (Windows style)
66
+ if (/^[a-zA-Z]:/.test(name)) {
67
+ return {
68
+ valid: false,
69
+ error: `Package name "${name}" appears to be an absolute Windows path`
70
+ };
71
+ }
72
+
73
+ // Check for null bytes (could be used for injection)
74
+ if (name.includes('\0')) {
75
+ return {
76
+ valid: false,
77
+ error: `Package name "${name}" contains null bytes`
78
+ };
79
+ }
80
+
81
+ return { valid: true };
82
+ }
83
+
84
+ /**
85
+ * Validates dependency names for potential typosquatting
86
+ * @param {Object} dependencies - Dependencies object
87
+ * @returns {string[]} Array of suspicious package names
88
+ */
89
+ function detectSuspiciousDependencies(dependencies) {
90
+ if (!dependencies || typeof dependencies !== 'object') {
91
+ return [];
92
+ }
93
+
94
+ const suspicious = [];
95
+ for (const name of Object.keys(dependencies)) {
96
+ // Skip dangerous keys
97
+ if (DANGEROUS_KEYS.has(name)) {
98
+ continue;
99
+ }
100
+
101
+ // Check for typosquatting patterns
102
+ for (const pattern of TYPOSQUATTING_PATTERNS) {
103
+ if (pattern.test(name)) {
104
+ suspicious.push(name);
105
+ break;
106
+ }
107
+ }
108
+ }
109
+ return suspicious;
110
+ }
111
+
112
+ /**
113
+ * Validates scripts for potential shell injection
114
+ * @param {Object} scripts - Scripts object from package.json
115
+ * @returns {string[]} Array of script names with suspicious content
116
+ */
117
+ function detectSuspiciousScripts(scripts) {
118
+ if (!scripts || typeof scripts !== 'object') {
119
+ return [];
120
+ }
121
+
122
+ const suspicious = [];
123
+ for (const [name, command] of Object.entries(scripts)) {
124
+ // Skip dangerous keys
125
+ if (DANGEROUS_KEYS.has(name)) {
126
+ continue;
127
+ }
128
+
129
+ // Check for shell metacharacters that could enable injection
130
+ if (typeof command === 'string' && SHELL_METACHAR_PATTERN.test(command)) {
131
+ // Allow common safe patterns
132
+ const safePatterns = [
133
+ /^node\s+[\w./-]+\.js$/, // Simple node commands
134
+ /^npm\s+(run|test|start|build)/, // npm commands
135
+ /^tsc(\s|$)/, // TypeScript compiler
136
+ /^vitest(\s|$)/, // Vitest
137
+ /^jest(\s|$)/, // Jest
138
+ /^eslint(\s|$)/, // ESLint
139
+ /&&/, // Command chaining is common
140
+ ];
141
+
142
+ // If it contains metacharacters but doesn't match safe patterns, flag it
143
+ const isSafe = safePatterns.some(p => p.test(command));
144
+ if (!isSafe && /[`$|;&<>(){}[\]]/.test(command)) {
145
+ suspicious.push(name);
146
+ }
147
+ }
148
+ }
149
+ return suspicious;
150
+ }
151
+
152
+ /**
153
+ * Detects path traversal attempts in dependency names
154
+ * @param {Object} dependencies - Dependencies object
155
+ * @returns {string[]} Array of package names with path traversal
156
+ */
157
+ function detectPathTraversalInDependencies(dependencies) {
158
+ if (!dependencies || typeof dependencies !== 'object') {
159
+ return [];
160
+ }
161
+
162
+ const malicious = [];
163
+ for (const name of Object.keys(dependencies)) {
164
+ // Skip dangerous keys
165
+ if (DANGEROUS_KEYS.has(name)) {
166
+ continue;
167
+ }
168
+
169
+ const validation = validatePackageName(name);
170
+ if (!validation.valid) {
171
+ malicious.push(name);
172
+ }
173
+ }
174
+ return malicious;
175
+ }
176
+
177
+ // BMAD scripts to add (with bmad: prefix)
178
+ const BMAD_SCRIPTS = {
179
+ 'bmad:modules': 'node src/utility/tools/module-selector/index.js',
180
+ 'bmad:security': 'node src/utility/tools/security-config/index.js',
181
+ 'bmad:llm': 'node src/utility/tools/llm-setup/index.js',
182
+ 'bmad:health': 'node src/utility/tools/health-check/index.js',
183
+ 'bmad:setup': 'node src/utility/tools/setup-wizard/index.js'
184
+ };
185
+
186
+ // BMAD dependencies to add
187
+ const BMAD_DEPENDENCIES = {
188
+ 'chalk': '^5.3.0',
189
+ 'inquirer': '^9.2.0',
190
+ 'zod': '^3.22.0',
191
+ 'commander': '^12.0.0',
192
+ 'ora': '^8.0.0'
193
+ };
194
+
195
+ const BMAD_DEV_DEPENDENCIES = {
196
+ 'typescript': '^5.3.0',
197
+ '@types/node': '^20.0.0',
198
+ 'vitest': '^1.0.0'
199
+ };
200
+
201
+ /**
202
+ * Merges BMAD framework dependencies and scripts into existing package.json
203
+ * @description Creates a new package.json if none exists, or merges BMAD-specific
204
+ * scripts (prefixed with 'bmad:'), dependencies, and devDependencies into an existing one.
205
+ * Creates a backup before modifying existing files.
206
+ * @param {string} targetDir - Target directory containing or to contain package.json
207
+ * @param {Object} [options={}] - Merge options
208
+ * @param {boolean} [options.yes=false] - Skip confirmation prompts and apply changes automatically
209
+ * @param {boolean} [options.dryRun=false] - Preview changes without writing to disk
210
+ * @returns {Promise<Object>} Merge result object
211
+ * @returns {boolean} [returns.success] - True if merge completed successfully
212
+ * @returns {boolean} [returns.cancelled] - True if user cancelled the operation
213
+ * @returns {boolean} [returns.created] - True if a new package.json was created
214
+ * @returns {boolean} [returns.noChanges] - True if no changes were needed
215
+ * @returns {boolean} [returns.dryRun] - True if this was a dry run
216
+ * @returns {Object} [returns.diff] - Object containing added and modified entries
217
+ * @returns {string} [returns.backupPath] - Path to backup file (if existing file was modified)
218
+ * @throws {Error} If file operations fail
219
+ * @example
220
+ * // Interactive merge
221
+ * const result = await mergePackageJson('./my-project');
222
+ *
223
+ * @example
224
+ * // Non-interactive merge
225
+ * const result = await mergePackageJson('./my-project', { yes: true });
226
+ *
227
+ * @example
228
+ * // Preview changes
229
+ * const result = await mergePackageJson('./my-project', { dryRun: true });
230
+ */
231
+ export async function mergePackageJson(targetDir, options = {}) {
232
+ const { yes = false, dryRun = false } = options;
233
+
234
+ const targetPath = join(targetDir, 'package.json');
235
+ const hasExisting = existsSync(targetPath);
236
+
237
+ if (!hasExisting) {
238
+ // No existing package.json - create new one
239
+ logger.info('No existing package.json found. Creating new one...');
240
+
241
+ const newPackage = createNewPackageJson(targetDir);
242
+
243
+ if (dryRun) {
244
+ logger.info('\nWould create package.json:');
245
+ console.log(JSON.stringify(newPackage, null, 2));
246
+ return { dryRun: true, created: true };
247
+ }
248
+
249
+ await fs.writeFile(targetPath, JSON.stringify(newPackage, null, 2) + '\n');
250
+ logger.success('Created package.json');
251
+
252
+ return { success: true, created: true };
253
+ }
254
+
255
+ // Merge with existing package.json
256
+ logger.info('Merging with existing package.json...');
257
+
258
+ const existing = JSON.parse(await fs.readFile(targetPath, 'utf-8'));
259
+
260
+ // Security: Check for suspicious patterns in existing package.json
261
+ const suspiciousDeps = detectSuspiciousDependencies(existing.dependencies);
262
+ const suspiciousDevDeps = detectSuspiciousDependencies(existing.devDependencies);
263
+ const suspiciousScripts = detectSuspiciousScripts(existing.scripts);
264
+
265
+ // Security: Check for path traversal in package names
266
+ const pathTraversalDeps = detectPathTraversalInDependencies(existing.dependencies);
267
+ const pathTraversalDevDeps = detectPathTraversalInDependencies(existing.devDependencies);
268
+
269
+ if (pathTraversalDeps.length > 0) {
270
+ logger.error(`SECURITY: Path traversal detected in dependencies: ${pathTraversalDeps.join(', ')}`);
271
+ throw new Error(`Security: Refusing to process package.json with path traversal in dependency names: ${pathTraversalDeps.join(', ')}`);
272
+ }
273
+
274
+ if (pathTraversalDevDeps.length > 0) {
275
+ logger.error(`SECURITY: Path traversal detected in devDependencies: ${pathTraversalDevDeps.join(', ')}`);
276
+ throw new Error(`Security: Refusing to process package.json with path traversal in devDependency names: ${pathTraversalDevDeps.join(', ')}`);
277
+ }
278
+
279
+ if (suspiciousDeps.length > 0) {
280
+ logger.warn(`Potentially suspicious dependencies detected: ${suspiciousDeps.join(', ')}`);
281
+ logger.warn('Please verify these packages are legitimate before proceeding.');
282
+ }
283
+
284
+ if (suspiciousDevDeps.length > 0) {
285
+ logger.warn(`Potentially suspicious devDependencies detected: ${suspiciousDevDeps.join(', ')}`);
286
+ }
287
+
288
+ if (suspiciousScripts.length > 0) {
289
+ logger.warn(`Scripts with potentially dangerous shell commands: ${suspiciousScripts.join(', ')}`);
290
+ }
291
+
292
+ const merged = mergePackages(existing);
293
+
294
+ // Calculate diff
295
+ const diff = calculateDiff(existing, merged);
296
+
297
+ if (Object.keys(diff.added).length === 0 &&
298
+ Object.keys(diff.modified).length === 0) {
299
+ logger.info('No changes needed to package.json');
300
+ return { success: true, noChanges: true };
301
+ }
302
+
303
+ // Show diff
304
+ if (!yes) {
305
+ showDiff(diff);
306
+
307
+ const { proceed } = await inquirer.prompt([
308
+ {
309
+ type: 'confirm',
310
+ name: 'proceed',
311
+ message: 'Apply these changes?',
312
+ default: true
313
+ }
314
+ ]);
315
+
316
+ if (!proceed) {
317
+ return { cancelled: true };
318
+ }
319
+ }
320
+
321
+ if (dryRun) {
322
+ logger.info('\nDry run - no changes made');
323
+ return { dryRun: true, diff };
324
+ }
325
+
326
+ // Create backup
327
+ const backupPath = `${targetPath}.backup.${Date.now()}`;
328
+ await fs.copyFile(targetPath, backupPath);
329
+ logger.info(`Backup created: ${backupPath}`);
330
+
331
+ // Write merged package.json
332
+ await fs.writeFile(targetPath, JSON.stringify(merged, null, 2) + '\n');
333
+ logger.success('Package.json updated');
334
+
335
+ return { success: true, diff, backupPath };
336
+ }
337
+
338
+ function createNewPackageJson(targetDir) {
339
+ const dirName = targetDir.split('/').pop() || 'my-project';
340
+
341
+ return {
342
+ name: dirName,
343
+ version: '1.0.0',
344
+ type: 'module',
345
+ scripts: {
346
+ ...BMAD_SCRIPTS,
347
+ 'start': 'node index.js',
348
+ 'build': 'tsc',
349
+ 'test': 'vitest'
350
+ },
351
+ dependencies: {
352
+ ...BMAD_DEPENDENCIES
353
+ },
354
+ devDependencies: {
355
+ ...BMAD_DEV_DEPENDENCIES
356
+ },
357
+ engines: {
358
+ node: '>=18.0.0'
359
+ }
360
+ };
361
+ }
362
+
363
+ function mergePackages(existing) {
364
+ // Security: Sanitize the existing package.json to remove prototype pollution vectors
365
+ const sanitizedExisting = sanitizeObject(existing);
366
+
367
+ const merged = { ...sanitizedExisting };
368
+
369
+ // Security: Sanitize dependencies before merging
370
+ const sanitizedDeps = sanitizeObject(existing.dependencies);
371
+ const sanitizedDevDeps = sanitizeObject(existing.devDependencies);
372
+ const sanitizedScripts = sanitizeObject(existing.scripts);
373
+
374
+ // Merge dependencies (don't override existing)
375
+ merged.dependencies = {
376
+ ...BMAD_DEPENDENCIES,
377
+ ...sanitizedDeps
378
+ };
379
+
380
+ // Merge devDependencies (don't override existing)
381
+ merged.devDependencies = {
382
+ ...BMAD_DEV_DEPENDENCIES,
383
+ ...sanitizedDevDeps
384
+ };
385
+
386
+ // Merge scripts (add bmad: prefixed scripts)
387
+ merged.scripts = {
388
+ ...sanitizedScripts,
389
+ ...BMAD_SCRIPTS
390
+ };
391
+
392
+ // Update engines if needed
393
+ if (!merged.engines) {
394
+ merged.engines = {};
395
+ }
396
+ if (!merged.engines.node || !meetsMinVersion(merged.engines.node, '18.0.0')) {
397
+ merged.engines.node = '>=18.0.0';
398
+ }
399
+
400
+ // Ensure type is module if not set
401
+ if (!merged.type) {
402
+ merged.type = 'module';
403
+ }
404
+
405
+ return merged;
406
+ }
407
+
408
+ function meetsMinVersion(versionSpec, minVersion) {
409
+ // Simple check - extract version number
410
+ const match = versionSpec.match(/(\d+)/);
411
+ if (!match) return false;
412
+ const majorVersion = parseInt(match[1], 10);
413
+ const minMajor = parseInt(minVersion.split('.')[0], 10);
414
+ return majorVersion >= minMajor;
415
+ }
416
+
417
+ function calculateDiff(original, merged) {
418
+ const diff = {
419
+ added: {},
420
+ modified: {},
421
+ unchanged: {}
422
+ };
423
+
424
+ // Compare scripts
425
+ for (const [key, value] of Object.entries(merged.scripts || {})) {
426
+ if (!original.scripts?.[key]) {
427
+ diff.added[`scripts.${key}`] = value;
428
+ } else if (original.scripts[key] !== value) {
429
+ diff.modified[`scripts.${key}`] = { from: original.scripts[key], to: value };
430
+ }
431
+ }
432
+
433
+ // Compare dependencies
434
+ for (const [key, value] of Object.entries(merged.dependencies || {})) {
435
+ if (!original.dependencies?.[key]) {
436
+ diff.added[`dependencies.${key}`] = value;
437
+ }
438
+ }
439
+
440
+ // Compare devDependencies
441
+ for (const [key, value] of Object.entries(merged.devDependencies || {})) {
442
+ if (!original.devDependencies?.[key]) {
443
+ diff.added[`devDependencies.${key}`] = value;
444
+ }
445
+ }
446
+
447
+ // Check engines
448
+ if (merged.engines?.node !== original.engines?.node) {
449
+ diff.modified['engines.node'] = {
450
+ from: original.engines?.node || 'not set',
451
+ to: merged.engines.node
452
+ };
453
+ }
454
+
455
+ return diff;
456
+ }
457
+
458
+ function showDiff(diff) {
459
+ console.log('\n');
460
+ logger.info('Changes to package.json:');
461
+ console.log('');
462
+
463
+ if (Object.keys(diff.added).length > 0) {
464
+ console.log(chalk.green('+ Added:'));
465
+ for (const [key, value] of Object.entries(diff.added)) {
466
+ console.log(chalk.green(` + ${key}: ${JSON.stringify(value)}`));
467
+ }
468
+ }
469
+
470
+ if (Object.keys(diff.modified).length > 0) {
471
+ console.log(chalk.yellow('~ Modified:'));
472
+ for (const [key, change] of Object.entries(diff.modified)) {
473
+ console.log(chalk.yellow(` ~ ${key}:`));
474
+ console.log(chalk.red(` - ${change.from}`));
475
+ console.log(chalk.green(` + ${change.to}`));
476
+ }
477
+ }
478
+
479
+ console.log('');
480
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * URL Validator for Git Repository URLs
3
+ *
4
+ * Security module to prevent command injection via malicious repository URLs.
5
+ * Only allows HTTPS URLs from trusted Git hosting providers.
6
+ *
7
+ * @module url-validator
8
+ */
9
+
10
+ // Trusted domains for git repositories
11
+ const TRUSTED_DOMAINS = [
12
+ 'github.com',
13
+ 'gitlab.com',
14
+ 'bitbucket.org'
15
+ ];
16
+
17
+ // Shell metacharacters that could enable command injection
18
+ const DANGEROUS_CHARS = /[$`|;&(){}<>\n\r\\]/;
19
+
20
+ /**
21
+ * Validates a git repository URL for security
22
+ *
23
+ * @param {string} repoUrl - The repository URL to validate
24
+ * @returns {{ valid: boolean, error?: string }} Validation result
25
+ *
26
+ * @example
27
+ * const result = validateRepoUrl('https://github.com/user/repo.git');
28
+ * if (!result.valid) {
29
+ * throw new Error(result.error);
30
+ * }
31
+ */
32
+ export function validateRepoUrl(repoUrl) {
33
+ // Must be a non-empty string
34
+ if (!repoUrl || typeof repoUrl !== 'string') {
35
+ return {
36
+ valid: false,
37
+ error: 'Repository URL must be a non-empty string'
38
+ };
39
+ }
40
+
41
+ // Check for shell metacharacters BEFORE any other processing
42
+ if (DANGEROUS_CHARS.test(repoUrl)) {
43
+ return {
44
+ valid: false,
45
+ error: 'Repository URL contains invalid characters. URLs must not contain shell metacharacters.'
46
+ };
47
+ }
48
+
49
+ // Parse the URL to validate format
50
+ let url;
51
+ try {
52
+ url = new URL(repoUrl);
53
+ } catch {
54
+ return {
55
+ valid: false,
56
+ error: 'Invalid URL format. Please provide a valid HTTPS URL.'
57
+ };
58
+ }
59
+
60
+ // Only allow HTTPS protocol
61
+ if (url.protocol !== 'https:') {
62
+ return {
63
+ valid: false,
64
+ error: `Only HTTPS URLs are allowed. Received protocol: ${url.protocol}`
65
+ };
66
+ }
67
+
68
+ // Check if hostname is in trusted domains
69
+ const hostname = url.hostname.toLowerCase();
70
+ const isTrusted = TRUSTED_DOMAINS.some(domain =>
71
+ hostname === domain || hostname.endsWith('.' + domain)
72
+ );
73
+
74
+ if (!isTrusted) {
75
+ return {
76
+ valid: false,
77
+ error: `Repository URL must be from a trusted domain: ${TRUSTED_DOMAINS.join(', ')}`
78
+ };
79
+ }
80
+
81
+ // Additional path validation - must look like a valid repo path
82
+ // e.g., /owner/repo or /owner/repo.git
83
+ const pathPattern = /^\/[\w.-]+\/[\w.-]+(\.git)?$/;
84
+ if (!pathPattern.test(url.pathname)) {
85
+ return {
86
+ valid: false,
87
+ error: 'Invalid repository path format. Expected format: https://github.com/owner/repo'
88
+ };
89
+ }
90
+
91
+ return { valid: true };
92
+ }
93
+
94
+ /**
95
+ * Validates a git repository URL and throws if invalid
96
+ *
97
+ * @param {string} repoUrl - The repository URL to validate
98
+ * @throws {Error} If the URL is invalid or from an untrusted source
99
+ *
100
+ * @example
101
+ * assertValidRepoUrl('https://github.com/user/repo.git'); // OK
102
+ * assertValidRepoUrl('$(malicious)'); // throws Error
103
+ */
104
+ export function assertValidRepoUrl(repoUrl) {
105
+ const result = validateRepoUrl(repoUrl);
106
+ if (!result.valid) {
107
+ throw new Error(result.error);
108
+ }
109
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,44 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Checks if BMAD framework is installed in the target directory
6
+ * @description Determines if BMAD is installed by checking for the presence
7
+ * of the _bmad directory in the target location.
8
+ * @param {string} [targetDir=process.cwd()] - Directory to check for BMAD installation
9
+ * @returns {boolean} True if _bmad directory exists, false otherwise
10
+ * @example
11
+ * if (isBmadInstalled()) {
12
+ * console.log('BMAD is already installed');
13
+ * }
14
+ *
15
+ * @example
16
+ * // Check specific directory
17
+ * const isInstalled = isBmadInstalled('/path/to/project');
18
+ */
19
+ export function isBmadInstalled(targetDir = process.cwd()) {
20
+ return existsSync(join(targetDir, '_bmad'));
21
+ }
22
+
23
+ /**
24
+ * Gets the installed version of BMAD framework
25
+ * @description Attempts to read the version from the BMAD config file.
26
+ * Note: Version parsing is not yet implemented and currently returns null.
27
+ * @param {string} [targetDir=process.cwd()] - Directory to check for BMAD installation
28
+ * @returns {string|null} Version string if found, null otherwise (currently always null)
29
+ * @example
30
+ * const version = getInstalledVersion();
31
+ * if (version) {
32
+ * console.log(`Installed version: ${version}`);
33
+ * } else {
34
+ * console.log('Version not found or BMAD not installed');
35
+ * }
36
+ */
37
+ export function getInstalledVersion(targetDir = process.cwd()) {
38
+ const configPath = join(targetDir, '_bmad', 'core', 'config.yaml');
39
+ if (!existsSync(configPath)) {
40
+ return null;
41
+ }
42
+ // Return null for now - version parsing to be implemented
43
+ return null;
44
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "bmad-cybersec",
3
+ "version": "2.0.0",
4
+ "description": "Install BMAD-CYBERSEC operations framework",
5
+ "type": "module",
6
+ "bin": {
7
+ "bmad-cybersec": "cli.js"
8
+ },
9
+ "files": [
10
+ "cli.js",
11
+ "index.js",
12
+ "commands/",
13
+ "lib/",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "test:coverage": "vitest run --coverage"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^12.0.0",
23
+ "chalk": "^5.3.0",
24
+ "ora": "^8.0.0",
25
+ "inquirer": "^9.2.0",
26
+ "tar": "^6.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "vitest": "^2.1.0",
30
+ "@vitest/coverage-v8": "^2.1.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "keywords": [
36
+ "bmad",
37
+ "cyber",
38
+ "security",
39
+ "ai",
40
+ "agents",
41
+ "claude"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/SchenLong/BMAD-CYBERSEC.git"
46
+ },
47
+ "homepage": "https://github.com/SchenLong/BMAD-CYBERSEC#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/SchenLong/BMAD-CYBERSEC/issues"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "license": "MIT"
55
+ }