bmad-plus 0.4.1 → 0.4.3
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/CHANGELOG.md +38 -0
- package/README.md +4 -2
- package/package.json +5 -4
- package/readme-international/README.de.md +1 -1
- package/readme-international/README.es.md +1 -1
- package/readme-international/README.fr.md +1 -1
- package/src/bmad-plus/agents/pack-animated/animated-website-agent.md +325 -0
- package/src/bmad-plus/agents/pack-animated/templates/animated-website-workflow.md +55 -0
- package/src/bmad-plus/agents/pack-backup/backup-agent.md +71 -0
- package/src/bmad-plus/agents/pack-backup/templates/backup-workflow.md +51 -0
- package/src/bmad-plus/agents/pack-seo/SKILL.md +171 -0
- package/src/bmad-plus/agents/pack-seo/checklist.md +140 -0
- package/src/bmad-plus/agents/pack-seo/pagespeed-playbook.md +320 -0
- package/src/bmad-plus/agents/pack-seo/ref/audit-schema.json +187 -0
- package/src/bmad-plus/agents/pack-seo/ref/cwv-thresholds.md +87 -0
- package/src/bmad-plus/agents/pack-seo/ref/eeat-criteria.md +123 -0
- package/src/bmad-plus/agents/pack-seo/ref/geo-signals.md +167 -0
- package/src/bmad-plus/agents/pack-seo/ref/hreflang-rules.md +153 -0
- package/src/bmad-plus/agents/pack-seo/ref/quality-gates.md +133 -0
- package/src/bmad-plus/agents/pack-seo/ref/schema-catalog.md +91 -0
- package/src/bmad-plus/agents/pack-seo/ref/schema-templates.json +356 -0
- package/src/bmad-plus/agents/pack-seo/seo-chief.md +294 -0
- package/src/bmad-plus/agents/pack-seo/seo-judge.md +241 -0
- package/src/bmad-plus/agents/pack-seo/seo-scout.md +171 -0
- package/src/bmad-plus/agents/pack-seo/templates/seo-audit-workflow.md +241 -0
- package/src/bmad-plus/module.yaml +29 -0
- package/tools/cli/bmad-plus-cli.js +23 -0
- package/tools/cli/commands/doctor.js +175 -0
- package/tools/cli/commands/install.js +54 -16
- package/tools/cli/commands/uninstall.js +34 -8
- package/tools/cli/commands/update.js +172 -0
- package/tools/cli/i18n.js +425 -303
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* BMAD+ CLI — Main entry point
|
|
5
|
+
* Commands: install, uninstall, update, doctor
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const { program } = require('commander');
|
|
@@ -22,6 +23,8 @@ if (process.stdin.isTTY) {
|
|
|
22
23
|
// Register commands
|
|
23
24
|
const install = require('./commands/install');
|
|
24
25
|
const uninstall = require('./commands/uninstall');
|
|
26
|
+
const update = require('./commands/update');
|
|
27
|
+
const doctor = require('./commands/doctor');
|
|
25
28
|
|
|
26
29
|
program
|
|
27
30
|
.version(packageJson.version)
|
|
@@ -43,6 +46,26 @@ const uninstallCmd = program
|
|
|
43
46
|
.description('Remove BMAD+ from your project');
|
|
44
47
|
uninstallCmd.action(uninstall.action);
|
|
45
48
|
|
|
49
|
+
// Update command
|
|
50
|
+
const updateCmd = program
|
|
51
|
+
.command('update')
|
|
52
|
+
.description('Update BMAD+ agents and skills (preserves config)');
|
|
53
|
+
|
|
54
|
+
for (const option of update.options || []) {
|
|
55
|
+
updateCmd.option(...option);
|
|
56
|
+
}
|
|
57
|
+
updateCmd.action(update.action);
|
|
58
|
+
|
|
59
|
+
// Doctor command
|
|
60
|
+
const doctorCmd = program
|
|
61
|
+
.command('doctor')
|
|
62
|
+
.description('Check BMAD+ installation integrity');
|
|
63
|
+
|
|
64
|
+
for (const option of doctor.options || []) {
|
|
65
|
+
doctorCmd.option(...option);
|
|
66
|
+
}
|
|
67
|
+
doctorCmd.action(doctor.action);
|
|
68
|
+
|
|
46
69
|
program.parse(process.argv);
|
|
47
70
|
|
|
48
71
|
if (process.argv.slice(2).length === 0) {
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BMAD+ Doctor Command
|
|
3
|
+
* Checks installation integrity and reports issues
|
|
4
|
+
*
|
|
5
|
+
* Author: Laurent Rochetta
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const clack = require('@clack/prompts');
|
|
11
|
+
const pc = require('picocolors');
|
|
12
|
+
const { t } = require('../i18n');
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
command: 'doctor',
|
|
16
|
+
description: 'Check BMAD+ installation integrity',
|
|
17
|
+
options: [
|
|
18
|
+
['-d, --directory <path>', 'Project directory (default: current directory)'],
|
|
19
|
+
],
|
|
20
|
+
action: async (options) => {
|
|
21
|
+
const projectDir = path.resolve(options.directory || process.cwd());
|
|
22
|
+
const packageJson = require('../../../package.json');
|
|
23
|
+
|
|
24
|
+
clack.intro(pc.bgBlue(pc.white(` BMAD+ Doctor v${packageJson.version} `)));
|
|
25
|
+
|
|
26
|
+
let checks = 0;
|
|
27
|
+
let passed = 0;
|
|
28
|
+
let warnings = 0;
|
|
29
|
+
let errors = 0;
|
|
30
|
+
|
|
31
|
+
// ── Check 1: Installation manifest ──
|
|
32
|
+
checks++;
|
|
33
|
+
const manifestPath = path.join(projectDir, '_bmad', '.bmad-plus-install.json');
|
|
34
|
+
if (!fs.existsSync(manifestPath)) {
|
|
35
|
+
clack.log.error('❌ BMAD+ is not installed in this directory');
|
|
36
|
+
clack.outro(pc.red('Run `npx bmad-plus install` first.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
41
|
+
clack.log.success(`✅ Manifest found — v${manifest.version}`);
|
|
42
|
+
passed++;
|
|
43
|
+
|
|
44
|
+
// ── Check 2: Version comparison ──
|
|
45
|
+
checks++;
|
|
46
|
+
if (manifest.version === packageJson.version) {
|
|
47
|
+
clack.log.success(`✅ Version up to date (v${manifest.version})`);
|
|
48
|
+
passed++;
|
|
49
|
+
} else {
|
|
50
|
+
clack.log.warn(`⚠️ Version mismatch: installed v${manifest.version} → latest v${packageJson.version}`);
|
|
51
|
+
clack.log.info(' Run `npx bmad-plus update` to upgrade');
|
|
52
|
+
warnings++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Check 3: Agent files ──
|
|
56
|
+
checks++;
|
|
57
|
+
const agentsDir = path.join(projectDir, '.agents', 'skills');
|
|
58
|
+
if (fs.existsSync(agentsDir)) {
|
|
59
|
+
const agentDirs = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
60
|
+
.filter(d => d.isDirectory())
|
|
61
|
+
.map(d => d.name);
|
|
62
|
+
clack.log.success(`✅ ${agentDirs.length} agent/skill directories found`);
|
|
63
|
+
passed++;
|
|
64
|
+
|
|
65
|
+
// Check each expected agent from manifest packs
|
|
66
|
+
const expectedAgents = {
|
|
67
|
+
core: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
|
|
68
|
+
osint: ['agent-shadow'],
|
|
69
|
+
maker: ['agent-maker'],
|
|
70
|
+
seo: ['pack-seo'],
|
|
71
|
+
backup: ['pack-backup'],
|
|
72
|
+
animated: ['pack-animated'],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (const pack of (manifest.packs || ['core'])) {
|
|
76
|
+
const expected = expectedAgents[pack] || [];
|
|
77
|
+
for (const agent of expected) {
|
|
78
|
+
checks++;
|
|
79
|
+
const agentPath = path.join(agentsDir, agent);
|
|
80
|
+
if (fs.existsSync(agentPath)) {
|
|
81
|
+
passed++;
|
|
82
|
+
} else {
|
|
83
|
+
clack.log.warn(`⚠️ Missing agent: ${agent} (pack: ${pack})`);
|
|
84
|
+
warnings++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
clack.log.error('❌ No .agents/skills/ directory found');
|
|
90
|
+
errors++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Check 4: Config files ──
|
|
94
|
+
checks++;
|
|
95
|
+
const configPath = path.join(projectDir, '_bmad', 'config.yaml');
|
|
96
|
+
if (fs.existsSync(configPath)) {
|
|
97
|
+
clack.log.success('✅ config.yaml present');
|
|
98
|
+
passed++;
|
|
99
|
+
} else {
|
|
100
|
+
clack.log.error('❌ config.yaml missing');
|
|
101
|
+
errors++;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Check 5: Module config ──
|
|
105
|
+
checks++;
|
|
106
|
+
const moduleYaml = path.join(projectDir, '_bmad', 'module.yaml');
|
|
107
|
+
if (fs.existsSync(moduleYaml)) {
|
|
108
|
+
clack.log.success('✅ module.yaml present');
|
|
109
|
+
passed++;
|
|
110
|
+
} else {
|
|
111
|
+
clack.log.warn('⚠️ module.yaml missing');
|
|
112
|
+
warnings++;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Check 6: IDE configs ──
|
|
116
|
+
checks++;
|
|
117
|
+
const ideFiles = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md'].filter(f =>
|
|
118
|
+
fs.existsSync(path.join(projectDir, f))
|
|
119
|
+
);
|
|
120
|
+
if (ideFiles.length > 0) {
|
|
121
|
+
clack.log.success(`✅ ${ideFiles.length} IDE config(s): ${ideFiles.join(', ')}`);
|
|
122
|
+
passed++;
|
|
123
|
+
} else {
|
|
124
|
+
clack.log.warn('⚠️ No IDE config files found');
|
|
125
|
+
warnings++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Check 7: Output directories ──
|
|
129
|
+
checks++;
|
|
130
|
+
const outputDir = path.join(projectDir, '_bmad-output');
|
|
131
|
+
if (fs.existsSync(outputDir)) {
|
|
132
|
+
clack.log.success('✅ _bmad-output/ directory exists');
|
|
133
|
+
passed++;
|
|
134
|
+
} else {
|
|
135
|
+
clack.log.warn('⚠️ _bmad-output/ not found');
|
|
136
|
+
warnings++;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Check 8: Skills integrity (SKILL.md exists in pack dirs) ──
|
|
140
|
+
if (manifest.packs && manifest.packs.includes('seo')) {
|
|
141
|
+
checks++;
|
|
142
|
+
const seoSkill = path.join(agentsDir, 'pack-seo', 'SKILL.md');
|
|
143
|
+
if (fs.existsSync(seoSkill)) {
|
|
144
|
+
clack.log.success('✅ SEO Engine SKILL.md present');
|
|
145
|
+
passed++;
|
|
146
|
+
} else {
|
|
147
|
+
clack.log.error('❌ SEO Engine SKILL.md missing — /seo audit will not work');
|
|
148
|
+
errors++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Summary ──
|
|
153
|
+
const summaryColor = errors > 0 ? pc.red : warnings > 0 ? pc.yellow : pc.green;
|
|
154
|
+
const summaryIcon = errors > 0 ? '❌' : warnings > 0 ? '⚠️' : '✅';
|
|
155
|
+
|
|
156
|
+
clack.note([
|
|
157
|
+
`${summaryIcon} ${passed}/${checks} checks passed`,
|
|
158
|
+
errors > 0 ? `❌ ${errors} error(s)` : null,
|
|
159
|
+
warnings > 0 ? `⚠️ ${warnings} warning(s)` : null,
|
|
160
|
+
'',
|
|
161
|
+
`📦 Version: v${manifest.version}`,
|
|
162
|
+
`📅 Installed: ${manifest.installed.split('T')[0]}`,
|
|
163
|
+
`🗣️ Language: ${manifest.language || 'N/A'}`,
|
|
164
|
+
`📦 Packs: ${(manifest.packs || ['core']).join(', ')}`,
|
|
165
|
+
].filter(Boolean).join('\n'), '🩺 BMAD+ Health Report');
|
|
166
|
+
|
|
167
|
+
clack.outro(summaryColor(
|
|
168
|
+
errors > 0
|
|
169
|
+
? 'Issues found — run `npx bmad-plus install` to fix'
|
|
170
|
+
: warnings > 0
|
|
171
|
+
? 'Minor issues found — consider running `npx bmad-plus update`'
|
|
172
|
+
: 'Everything looks great! 🎉'
|
|
173
|
+
));
|
|
174
|
+
},
|
|
175
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BMAD+ Install Command
|
|
3
3
|
* Installs agents, skills, and IDE configs into the current project
|
|
4
|
-
* Supports
|
|
4
|
+
* Supports 10 languages: EN, FR, ES, DE, PT-BR, RU, ZH, HE, JA, IT
|
|
5
5
|
*
|
|
6
6
|
* Author: Laurent Rochetta
|
|
7
7
|
*/
|
|
@@ -54,29 +54,29 @@ const PACKS = {
|
|
|
54
54
|
seo: {
|
|
55
55
|
name: 'SEO Audit 360',
|
|
56
56
|
icon: '🔍',
|
|
57
|
-
description: '
|
|
57
|
+
description: '3 agents (Scout, Chief, Judge) + 6-phase audit + PageSpeed loop',
|
|
58
58
|
required: false,
|
|
59
59
|
agents: [],
|
|
60
60
|
skills: [],
|
|
61
|
-
|
|
61
|
+
packDir: 'pack-seo',
|
|
62
62
|
},
|
|
63
63
|
backup: {
|
|
64
64
|
name: 'Universal Backup',
|
|
65
65
|
icon: '🗂️',
|
|
66
|
-
description: 'Timestamped ZIP backup with smart exclusions
|
|
66
|
+
description: 'Timestamped ZIP backup with smart exclusions',
|
|
67
67
|
required: false,
|
|
68
68
|
agents: [],
|
|
69
69
|
skills: [],
|
|
70
|
-
|
|
70
|
+
packDir: 'pack-backup',
|
|
71
71
|
},
|
|
72
72
|
animated: {
|
|
73
73
|
name: 'Animated Website',
|
|
74
74
|
icon: '🎬',
|
|
75
|
-
description: 'Luxury scroll-driven website from video
|
|
75
|
+
description: 'Luxury scroll-driven website from video',
|
|
76
76
|
required: false,
|
|
77
77
|
agents: [],
|
|
78
78
|
skills: [],
|
|
79
|
-
|
|
79
|
+
packDir: 'pack-animated',
|
|
80
80
|
},
|
|
81
81
|
};
|
|
82
82
|
|
|
@@ -118,7 +118,9 @@ module.exports = {
|
|
|
118
118
|
const bmadSrc = path.join(__dirname, '..', '..', '..', 'src', 'bmad-plus');
|
|
119
119
|
|
|
120
120
|
// ── Step 0: Language Selection ──
|
|
121
|
-
|
|
121
|
+
const pkgJson = require('../../../package.json');
|
|
122
|
+
clack.intro(pc.bgCyan(pc.black(` BMAD+ Installer v${pkgJson.version} `)));
|
|
123
|
+
clack.log.info(pc.dim('✨ Created by Laurent Rochetta — github.com/lrochetta'));
|
|
122
124
|
|
|
123
125
|
let lang = 'en';
|
|
124
126
|
if (!options.yes) {
|
|
@@ -316,13 +318,14 @@ module.exports = {
|
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
|
|
319
|
-
// Copy
|
|
320
|
-
if (pack.
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
if (fs.existsSync(
|
|
324
|
-
fsExtra.copySync(
|
|
325
|
-
|
|
321
|
+
// Copy pack directory (SEO, Backup, Animated Website)
|
|
322
|
+
if (pack.packDir) {
|
|
323
|
+
const packSrc = path.join(bmadSrc, 'agents', pack.packDir);
|
|
324
|
+
const packDest = path.join(targetAgentsDir, pack.packDir);
|
|
325
|
+
if (fs.existsSync(packSrc)) {
|
|
326
|
+
fsExtra.copySync(packSrc, packDest, { overwrite: true });
|
|
327
|
+
copiedAgents++;
|
|
328
|
+
copiedFiles++;
|
|
326
329
|
}
|
|
327
330
|
}
|
|
328
331
|
}
|
|
@@ -372,8 +375,9 @@ module.exports = {
|
|
|
372
375
|
fsExtra.ensureDirSync(path.join(projectDir, 'docs'));
|
|
373
376
|
|
|
374
377
|
// ── Step 8: Write install manifest ──
|
|
378
|
+
const pkgVersion = require('../../../package.json').version;
|
|
375
379
|
const manifest = {
|
|
376
|
-
version:
|
|
380
|
+
version: pkgVersion,
|
|
377
381
|
uiLanguage: lang,
|
|
378
382
|
installed: new Date().toISOString(),
|
|
379
383
|
packs: selectedPacks,
|
|
@@ -430,6 +434,40 @@ module.exports = {
|
|
|
430
434
|
i.guide_or_auto,
|
|
431
435
|
'',
|
|
432
436
|
`${i.guide_output}: _bmad-output/discovery/ & _bmad-output/build/`,
|
|
437
|
+
'',
|
|
438
|
+
'─'.repeat(50),
|
|
439
|
+
'',
|
|
440
|
+
`📦 ${i.guide_cli_title || 'CLI Commands'}:`,
|
|
441
|
+
` npx bmad-plus install ${i.guide_cli_install || '— Install agents & skills'}`,
|
|
442
|
+
` npx bmad-plus update ${i.guide_cli_update || '— Update agents (keeps config)'}`,
|
|
443
|
+
` npx bmad-plus doctor ${i.guide_cli_doctor || '— Check installation health'}`,
|
|
444
|
+
` npx bmad-plus uninstall ${i.guide_cli_uninstall || '— Remove BMAD+ from project'}`,
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// Add pack-specific examples
|
|
448
|
+
const examples = [];
|
|
449
|
+
if (selectedPacks.includes('seo')) {
|
|
450
|
+
examples.push(` ${i.guide_example_seo || '🔍 SEO: "/seo audit https://example.com"'}`);
|
|
451
|
+
}
|
|
452
|
+
if (selectedPacks.includes('backup')) {
|
|
453
|
+
examples.push(` ${i.guide_example_backup || '🗂️ Backup: "/backup create" → ZIP timestamped'}`);
|
|
454
|
+
}
|
|
455
|
+
if (selectedPacks.includes('animated')) {
|
|
456
|
+
examples.push(` ${i.guide_example_animated || '🎬 Animated: "/animated build hero.mp4"'}`);
|
|
457
|
+
}
|
|
458
|
+
if (selectedPacks.includes('osint')) {
|
|
459
|
+
examples.push(` ${i.guide_example_osint || '🔍 OSINT: "Shadow, investigate John Doe"'}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (examples.length > 0) {
|
|
463
|
+
agentGuide.push(
|
|
464
|
+
'',
|
|
465
|
+
`💡 ${i.guide_examples_title || 'Quick Examples'}:`,
|
|
466
|
+
...examples
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
agentGuide.push(
|
|
433
471
|
'',
|
|
434
472
|
'---',
|
|
435
473
|
i.guide_credits
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BMAD+ Uninstall Command
|
|
3
3
|
* Removes BMAD+ agents, skills, and configs from the current project
|
|
4
|
+
* Internationalized — uses i18n system
|
|
5
|
+
*
|
|
6
|
+
* Author: Laurent Rochetta
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
const path = require('node:path');
|
|
@@ -8,6 +11,7 @@ const fs = require('node:fs');
|
|
|
8
11
|
const fsExtra = require('fs-extra');
|
|
9
12
|
const clack = require('@clack/prompts');
|
|
10
13
|
const pc = require('picocolors');
|
|
14
|
+
const { t, getLanguageOptions } = require('../i18n');
|
|
11
15
|
|
|
12
16
|
module.exports = {
|
|
13
17
|
command: 'uninstall',
|
|
@@ -26,45 +30,67 @@ module.exports = {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
29
|
-
|
|
33
|
+
|
|
34
|
+
// Use the language from install manifest, or ask
|
|
35
|
+
let lang = manifest.uiLanguage || 'en';
|
|
36
|
+
const i = t(lang);
|
|
37
|
+
|
|
38
|
+
clack.log.info(`BMAD+ v${manifest.version} (${i.installed_on || 'installed'} ${manifest.installed.split('T')[0]})`);
|
|
39
|
+
clack.log.info(`${i.selected_packs}: ${(manifest.packs || ['core']).join(', ')}`);
|
|
30
40
|
|
|
31
41
|
const confirm = await clack.confirm({
|
|
32
|
-
message: '
|
|
42
|
+
message: i.uninstall_confirm || 'Remove BMAD+ from this project?',
|
|
33
43
|
});
|
|
34
44
|
|
|
35
45
|
if (!confirm || clack.isCancel(confirm)) {
|
|
36
|
-
clack.cancel(
|
|
46
|
+
clack.cancel(i.cancelled);
|
|
37
47
|
return;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
const spinner = clack.spinner();
|
|
41
|
-
spinner.start('
|
|
51
|
+
spinner.start(i.uninstall_removing || 'Removing...');
|
|
52
|
+
|
|
53
|
+
let removed = 0;
|
|
42
54
|
|
|
43
55
|
// Remove .agents/skills/ (BMAD+ agents & skills)
|
|
44
56
|
const agentsDir = path.join(projectDir, '.agents');
|
|
45
57
|
if (fs.existsSync(agentsDir)) {
|
|
46
58
|
fsExtra.removeSync(agentsDir);
|
|
59
|
+
removed++;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
// Remove _bmad/ config
|
|
50
63
|
const bmadDir = path.join(projectDir, '_bmad');
|
|
51
64
|
if (fs.existsSync(bmadDir)) {
|
|
52
65
|
fsExtra.removeSync(bmadDir);
|
|
66
|
+
removed++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove _bmad-output/ (only if empty)
|
|
70
|
+
const outputDir = path.join(projectDir, '_bmad-output');
|
|
71
|
+
if (fs.existsSync(outputDir)) {
|
|
72
|
+
const contents = fs.readdirSync(outputDir, { recursive: true }).filter(f => !f.startsWith('.'));
|
|
73
|
+
if (contents.length === 0) {
|
|
74
|
+
fsExtra.removeSync(outputDir);
|
|
75
|
+
removed++;
|
|
76
|
+
} else {
|
|
77
|
+
clack.log.info(i.uninstall_output_kept || '📁 _bmad-output/ kept (contains files)');
|
|
78
|
+
}
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
// Remove IDE configs
|
|
81
|
+
// Remove IDE configs (only BMAD+-generated ones)
|
|
56
82
|
for (const configFile of ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md', 'OPENCODE.md']) {
|
|
57
83
|
const p = path.join(projectDir, configFile);
|
|
58
84
|
if (fs.existsSync(p)) {
|
|
59
|
-
// Only remove if it's a BMAD+ generated file
|
|
60
85
|
const content = fs.readFileSync(p, 'utf8');
|
|
61
86
|
if (content.includes('BMAD+')) {
|
|
62
87
|
fs.unlinkSync(p);
|
|
88
|
+
removed++;
|
|
63
89
|
}
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
spinner.stop(
|
|
68
|
-
clack.outro(pc.green('Done!'));
|
|
93
|
+
spinner.stop(i.uninstall_done ? i.uninstall_done(removed) : `✅ BMAD+ removed (${removed} items)`);
|
|
94
|
+
clack.outro(pc.green(i.guide_ready ? '👋 Done!' : 'Done!'));
|
|
69
95
|
},
|
|
70
96
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BMAD+ Update Command
|
|
3
|
+
* Updates agents and skills while preserving user config
|
|
4
|
+
*
|
|
5
|
+
* Author: Laurent Rochetta
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const fsExtra = require('fs-extra');
|
|
11
|
+
const clack = require('@clack/prompts');
|
|
12
|
+
const pc = require('picocolors');
|
|
13
|
+
const { t } = require('../i18n');
|
|
14
|
+
|
|
15
|
+
// Same pack definitions as install.js — keep in sync
|
|
16
|
+
const PACKS = {
|
|
17
|
+
core: {
|
|
18
|
+
agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
|
|
19
|
+
skills: ['bmad-plus-autopilot', 'bmad-plus-parallel', 'bmad-plus-sync'],
|
|
20
|
+
data: ['role-triggers.yaml'],
|
|
21
|
+
},
|
|
22
|
+
osint: {
|
|
23
|
+
agents: ['agent-shadow'],
|
|
24
|
+
skills: [],
|
|
25
|
+
externalPackage: 'osint-agent-package',
|
|
26
|
+
},
|
|
27
|
+
maker: {
|
|
28
|
+
agents: ['agent-maker'],
|
|
29
|
+
skills: [],
|
|
30
|
+
data: [],
|
|
31
|
+
},
|
|
32
|
+
seo: { agents: [], skills: [], packDir: 'pack-seo' },
|
|
33
|
+
backup: { agents: [], skills: [], packDir: 'pack-backup' },
|
|
34
|
+
animated: { agents: [], skills: [], packDir: 'pack-animated' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
command: 'update',
|
|
39
|
+
description: 'Update BMAD+ agents and skills (preserves config)',
|
|
40
|
+
options: [
|
|
41
|
+
['-d, --directory <path>', 'Project directory (default: current directory)'],
|
|
42
|
+
],
|
|
43
|
+
action: async (options) => {
|
|
44
|
+
const projectDir = path.resolve(options.directory || process.cwd());
|
|
45
|
+
const bmadSrc = path.join(__dirname, '..', '..', '..', 'src', 'bmad-plus');
|
|
46
|
+
const packageJson = require('../../../package.json');
|
|
47
|
+
|
|
48
|
+
clack.intro(pc.bgMagenta(pc.white(` BMAD+ Updater v${packageJson.version} `)));
|
|
49
|
+
|
|
50
|
+
// Check if installed
|
|
51
|
+
const manifestPath = path.join(projectDir, '_bmad', '.bmad-plus-install.json');
|
|
52
|
+
if (!fs.existsSync(manifestPath)) {
|
|
53
|
+
clack.log.error('BMAD+ is not installed in this directory.');
|
|
54
|
+
clack.log.info('Run `npx bmad-plus install` first.');
|
|
55
|
+
clack.outro(pc.red('Update aborted.'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
60
|
+
const lang = manifest.uiLanguage || 'en';
|
|
61
|
+
const i = t(lang);
|
|
62
|
+
|
|
63
|
+
clack.log.info(`📦 Installed: v${manifest.version} → Available: v${packageJson.version}`);
|
|
64
|
+
|
|
65
|
+
if (manifest.version === packageJson.version) {
|
|
66
|
+
clack.log.success(i.update_current || '✅ Already up to date!');
|
|
67
|
+
clack.outro(pc.green('Nothing to update.'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const selectedPacks = manifest.packs || ['core'];
|
|
72
|
+
clack.log.info(`${i.selected_packs}: ${selectedPacks.join(', ')}`);
|
|
73
|
+
|
|
74
|
+
const confirm = await clack.confirm({
|
|
75
|
+
message: i.update_confirm || `Update from v${manifest.version} to v${packageJson.version}?`,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!confirm || clack.isCancel(confirm)) {
|
|
79
|
+
clack.cancel(i.cancelled);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const spinner = clack.spinner();
|
|
84
|
+
spinner.start(i.update_updating || 'Updating agents and skills...');
|
|
85
|
+
|
|
86
|
+
const targetAgentsDir = path.join(projectDir, '.agents', 'skills');
|
|
87
|
+
const targetDataDir = path.join(projectDir, '.agents', 'data');
|
|
88
|
+
|
|
89
|
+
fsExtra.ensureDirSync(targetAgentsDir);
|
|
90
|
+
fsExtra.ensureDirSync(targetDataDir);
|
|
91
|
+
|
|
92
|
+
let updated = 0;
|
|
93
|
+
|
|
94
|
+
for (const packId of selectedPacks) {
|
|
95
|
+
const pack = PACKS[packId];
|
|
96
|
+
if (!pack) continue;
|
|
97
|
+
|
|
98
|
+
// Update agents
|
|
99
|
+
for (const agent of (pack.agents || [])) {
|
|
100
|
+
const src = path.join(bmadSrc, 'agents', agent);
|
|
101
|
+
const dest = path.join(targetAgentsDir, agent);
|
|
102
|
+
if (fs.existsSync(src)) {
|
|
103
|
+
fsExtra.copySync(src, dest, { overwrite: true });
|
|
104
|
+
updated++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Update skills
|
|
109
|
+
for (const skill of (pack.skills || [])) {
|
|
110
|
+
const src = path.join(bmadSrc, 'skills', skill);
|
|
111
|
+
const dest = path.join(targetAgentsDir, skill);
|
|
112
|
+
if (fs.existsSync(src)) {
|
|
113
|
+
fsExtra.copySync(src, dest, { overwrite: true });
|
|
114
|
+
updated++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Update data files
|
|
119
|
+
for (const dataFile of (pack.data || [])) {
|
|
120
|
+
const src = path.join(bmadSrc, 'data', dataFile);
|
|
121
|
+
const dest = path.join(targetDataDir, dataFile);
|
|
122
|
+
if (fs.existsSync(src)) {
|
|
123
|
+
fsExtra.copySync(src, dest, { overwrite: true });
|
|
124
|
+
updated++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Update external package (OSINT)
|
|
129
|
+
if (pack.externalPackage) {
|
|
130
|
+
const extSrc = path.join(__dirname, '..', '..', '..', pack.externalPackage, 'skills');
|
|
131
|
+
if (fs.existsSync(extSrc)) {
|
|
132
|
+
fsExtra.copySync(extSrc, targetAgentsDir, { overwrite: true });
|
|
133
|
+
updated++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Update pack directory (SEO, Backup, Animated)
|
|
138
|
+
if (pack.packDir) {
|
|
139
|
+
const packSrc = path.join(bmadSrc, 'agents', pack.packDir);
|
|
140
|
+
const packDest = path.join(targetAgentsDir, pack.packDir);
|
|
141
|
+
if (fs.existsSync(packSrc)) {
|
|
142
|
+
fsExtra.copySync(packSrc, packDest, { overwrite: true });
|
|
143
|
+
updated++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Update module config (always)
|
|
149
|
+
const moduleYaml = path.join(bmadSrc, 'module.yaml');
|
|
150
|
+
const targetBmadDir = path.join(projectDir, '_bmad');
|
|
151
|
+
if (fs.existsSync(moduleYaml)) {
|
|
152
|
+
fsExtra.copySync(moduleYaml, path.join(targetBmadDir, 'module.yaml'));
|
|
153
|
+
updated++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const helpCsv = path.join(bmadSrc, 'module-help.csv');
|
|
157
|
+
if (fs.existsSync(helpCsv)) {
|
|
158
|
+
fsExtra.copySync(helpCsv, path.join(targetBmadDir, 'module-help.csv'));
|
|
159
|
+
updated++;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Update manifest version (preserve everything else)
|
|
163
|
+
manifest.version = packageJson.version;
|
|
164
|
+
manifest.lastUpdated = new Date().toISOString();
|
|
165
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
166
|
+
|
|
167
|
+
spinner.stop(i.update_done ? i.update_done(updated) : `✅ ${updated} files updated to v${packageJson.version}`);
|
|
168
|
+
|
|
169
|
+
clack.log.info('📋 Config preserved: config.yaml, IDE configs, output directories');
|
|
170
|
+
clack.outro(pc.green(i.update_ready || `BMAD+ v${packageJson.version} is ready! 🚀`));
|
|
171
|
+
},
|
|
172
|
+
};
|