feat(cli): complete @clack/prompts migration
Full migration of BMAD CLI installer from legacy terminal libraries
(chalk, ora, boxen, figlet, wrap-ansi, cli-table3, readline) to unified
@clack/prompts v1.0.0 visual system.
Foundation (prompts.js + cli-utils.js):
- Extended prompts.js wrapper with box, spinner, progress, taskLog,
path, autocomplete, selectKey, stream, color re-export
- Refactored cli-utils.js: displayLogo uses box(), sections use note(),
steps use log.step(), removed boxen/figlet/wrap-ansi/cli-table3
UI orchestration (ui.js):
- Replaced ~100 console.log+chalk calls with log.*, note(), box()
- Replaced ora spinner with @clack spinner
- Module selection: autocompleteMultiselect with locked core module,
bulleted post-selection display, maxItems for no-scroll
Spinner migration (installer.js):
- Replaced 40+ ora spinner calls with @clack spinner
- All spinner.stop() calls include meaningful messages
- Failure paths use spinner.error() (red cross) instead of stop()
Readline migration (agent/installer.js + config-collector.js):
- Migrated readline prompts to @clack text/confirm/select
- Fixed chalk.dim bug (chalk was never imported)
- Removed chalk from config-collector.js
IDE handlers + modules (7 files):
- Replaced chalk+ora across all IDE handlers and module manager
- Fixed options.installer undefined bug in manager.js update()
Cleanup:
- Removed ora, boxen, figlet, wrap-ansi, cli-table3 from dependencies
- chalk stays (used outside tools/cli/ scope)
- Replaced hand-drawn Unicode update box in bmad-cli.js with box()
- Added process.stdin.setMaxListeners(25) for sequential prompts
Spinner wrapper adds isSpinning state tracking (not native to @clack).
Removed dead groupMultiselect and sortKey sort calls.
Ref: tech-spec-installer-clack-migration-ui-enhancement.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ecf7fbcb2c
commit
fd09349683
|
|
@ -12,20 +12,15 @@
|
||||||
"@clack/core": "^1.0.0",
|
"@clack/core": "^1.0.0",
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
"boxen": "^5.1.2",
|
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"cli-table3": "^0.6.5",
|
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"figlet": "^1.8.0",
|
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"wrap-ansi": "^7.0.0",
|
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"yaml": "^2.7.0"
|
"yaml": "^2.7.0"
|
||||||
},
|
},
|
||||||
|
|
@ -777,16 +772,6 @@
|
||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@colors/colors": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.1.90"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ctrl/tinycolor": {
|
"node_modules/@ctrl/tinycolor": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
|
||||||
|
|
@ -2029,9 +2014,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@isaacs/brace-expansion": {
|
"node_modules/@isaacs/brace-expansion": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||||
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
|
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/balanced-match": "^4.0.1"
|
"@isaacs/balanced-match": "^4.0.1"
|
||||||
|
|
@ -3993,6 +3978,7 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||||
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
|
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^4.1.0"
|
"string-width": "^4.1.0"
|
||||||
|
|
@ -4985,26 +4971,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.9.19",
|
"version": "2.9.19",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||||
|
|
@ -5042,59 +5008,12 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bl": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer": "^5.5.0",
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"readable-stream": "^3.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bl/node_modules/readable-stream": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
|
||||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/boxen": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-align": "^3.0.0",
|
|
||||||
"camelcase": "^6.2.0",
|
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"cli-boxes": "^2.2.1",
|
|
||||||
"string-width": "^4.2.2",
|
|
||||||
"type-fest": "^0.20.2",
|
|
||||||
"widest-line": "^3.1.0",
|
|
||||||
"wrap-ansi": "^7.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
|
|
@ -5163,30 +5082,6 @@
|
||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer": {
|
|
||||||
"version": "5.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"base64-js": "^1.3.1",
|
|
||||||
"ieee754": "^1.1.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
|
@ -5255,6 +5150,7 @@
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
|
@ -5433,18 +5329,6 @@
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-boxes": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
|
|
||||||
"integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||||
|
|
@ -5461,33 +5345,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-spinners": {
|
|
||||||
"version": "2.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
|
||||||
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-table3": {
|
|
||||||
"version": "0.6.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
|
|
||||||
"integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"string-width": "^4.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "10.* || >= 12.*"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@colors/colors": "1.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-truncate": {
|
"node_modules/cli-truncate": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
|
||||||
|
|
@ -5560,15 +5417,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/clone": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
|
@ -5942,18 +5790,6 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/defaults": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"clone": "^1.0.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/defu": {
|
"node_modules/defu": {
|
||||||
"version": "6.1.4",
|
"version": "6.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
|
|
@ -7034,21 +6870,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/figlet": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-aktIwEZZ6Gp9AWdMXW4YCi0J2Ahuxo67fNJRUIWD81w8pQ0t9TS8FFpbl27ChlTLF06VkwjDesZSzEVzN75rzA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^14.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"figlet": "bin/index.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 17.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/file-entry-cache": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||||
|
|
@ -7906,26 +7727,6 @@
|
||||||
"@babel/runtime": "^7.23.2"
|
"@babel/runtime": "^7.23.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ieee754": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "BSD-3-Clause"
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "7.0.5",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||||
|
|
@ -8022,6 +7823,7 @@
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ini": {
|
"node_modules/ini": {
|
||||||
|
|
@ -8206,15 +8008,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-interactive": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
|
@ -8250,18 +8043,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-unicode-supported": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
|
|
||||||
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-wsl": {
|
"node_modules/is-wsl": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
|
||||||
|
|
@ -9523,22 +9304,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/log-symbols": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"is-unicode-supported": "^0.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/log-update": {
|
"node_modules/log-update": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||||
|
|
@ -10985,6 +10750,7 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
|
@ -11296,6 +11062,7 @@
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mimic-fn": "^2.1.0"
|
"mimic-fn": "^2.1.0"
|
||||||
|
|
@ -11344,81 +11111,6 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ora": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"bl": "^4.1.0",
|
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"cli-cursor": "^3.1.0",
|
|
||||||
"cli-spinners": "^2.5.0",
|
|
||||||
"is-interactive": "^1.0.0",
|
|
||||||
"is-unicode-supported": "^0.1.0",
|
|
||||||
"log-symbols": "^4.1.0",
|
|
||||||
"strip-ansi": "^6.0.0",
|
|
||||||
"wcwidth": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ora/node_modules/ansi-regex": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ora/node_modules/cli-cursor": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"restore-cursor": "^3.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ora/node_modules/restore-cursor": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"onetime": "^5.1.0",
|
|
||||||
"signal-exit": "^3.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ora/node_modules/signal-exit": {
|
|
||||||
"version": "3.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/ora/node_modules/strip-ansi": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
|
||||||
|
|
@ -12726,26 +12418,6 @@
|
||||||
"queue-microtask": "^1.2.2"
|
"queue-microtask": "^1.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/safe-buffer": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/sax": {
|
"node_modules/sax": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||||
|
|
@ -13098,15 +12770,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string-argv": {
|
"node_modules/string-argv": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
||||||
|
|
@ -13691,18 +13354,6 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/type-fest": {
|
|
||||||
"version": "0.20.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
|
||||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
|
||||||
"license": "(MIT OR CC0-1.0)",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/uc.micro": {
|
"node_modules/uc.micro": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
|
|
@ -14152,6 +13803,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
|
|
@ -14317,15 +13969,6 @@
|
||||||
"makeerror": "1.0.12"
|
"makeerror": "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wcwidth": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"defaults": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/web-namespaces": {
|
"node_modules/web-namespaces": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||||
|
|
@ -14362,18 +14005,6 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/widest-line": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"string-width": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|
@ -14388,6 +14019,7 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
|
|
@ -14444,6 +14076,7 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -14453,6 +14086,7 @@
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
|
|
|
||||||
|
|
@ -68,20 +68,15 @@
|
||||||
"@clack/core": "^1.0.0",
|
"@clack/core": "^1.0.0",
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
"boxen": "^5.1.2",
|
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"cli-table3": "^0.6.5",
|
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"figlet": "^1.8.0",
|
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"wrap-ansi": "^7.0.0",
|
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"yaml": "^2.7.0"
|
"yaml": "^2.7.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ const { program } = require('commander');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const { execSync } = require('node:child_process');
|
const { execSync } = require('node:child_process');
|
||||||
|
const prompts = require('./lib/prompts');
|
||||||
|
|
||||||
|
// The installer flow uses many sequential @clack/prompts, each adding keypress
|
||||||
|
// listeners to stdin. Raise the limit to avoid spurious EventEmitter warnings.
|
||||||
|
if (process.stdin?.setMaxListeners) {
|
||||||
|
process.stdin.setMaxListeners(25);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for updates - do this asynchronously so it doesn't block startup
|
// Check for updates - do this asynchronously so it doesn't block startup
|
||||||
const packageJson = require('../../package.json');
|
const packageJson = require('../../package.json');
|
||||||
|
|
@ -27,17 +34,17 @@ async function checkForUpdate() {
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
if (result && result !== packageJson.version) {
|
if (result && result !== packageJson.version) {
|
||||||
console.warn('');
|
const color = await prompts.getColor();
|
||||||
console.warn(' ╔═══════════════════════════════════════════════════════════════════════════════╗');
|
const updateMsg = [
|
||||||
console.warn(' ║ UPDATE AVAILABLE ║');
|
`You are using version ${packageJson.version} but ${result} is available.`,
|
||||||
console.warn(' ║ ║');
|
'',
|
||||||
console.warn(` ║ You are using version ${packageJson.version} but ${result} is available. ║`);
|
'To update, exit and first run:',
|
||||||
console.warn(' ║ ║');
|
` npm cache clean --force && npx bmad-method@${tag} install`,
|
||||||
console.warn(' ║ To update,exir and first run: ║');
|
].join('\n');
|
||||||
console.warn(` ║ npm cache clean --force && npx bmad-method@${tag} install ║`);
|
await prompts.box(updateMsg, 'Update Available', {
|
||||||
console.warn(' ║ ║');
|
rounded: true,
|
||||||
console.warn(' ╚═══════════════════════════════════════════════════════════════════════════════╝');
|
formatBorder: color.yellow,
|
||||||
console.warn('');
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Silently fail - network issues or npm not available
|
// Silently fail - network issues or npm not available
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const chalk = require('chalk');
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
const prompts = require('../lib/prompts');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
const { UI } = require('../lib/ui');
|
const { UI } = require('../lib/ui');
|
||||||
|
|
||||||
|
|
@ -30,14 +30,14 @@ module.exports = {
|
||||||
// Set debug flag as environment variable for all components
|
// Set debug flag as environment variable for all components
|
||||||
if (options.debug) {
|
if (options.debug) {
|
||||||
process.env.BMAD_DEBUG_MANIFEST = 'true';
|
process.env.BMAD_DEBUG_MANIFEST = 'true';
|
||||||
console.log(chalk.cyan('Debug mode enabled\n'));
|
await prompts.log.info('Debug mode enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await ui.promptInstall(options);
|
const config = await ui.promptInstall(options);
|
||||||
|
|
||||||
// Handle cancel
|
// Handle cancel
|
||||||
if (config.actionType === 'cancel') {
|
if (config.actionType === 'cancel') {
|
||||||
console.log(chalk.yellow('Installation cancelled.'));
|
await prompts.log.warn('Installation cancelled.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -45,13 +45,13 @@ module.exports = {
|
||||||
// Handle quick update separately
|
// Handle quick update separately
|
||||||
if (config.actionType === 'quick-update') {
|
if (config.actionType === 'quick-update') {
|
||||||
const result = await installer.quickUpdate(config);
|
const result = await installer.quickUpdate(config);
|
||||||
console.log(chalk.green('\n✨ Quick update complete!'));
|
await prompts.log.success('Quick update complete!');
|
||||||
console.log(chalk.cyan(`Updated ${result.moduleCount} modules with preserved settings (${result.modules.join(', ')})`));
|
await prompts.log.info(`Updated ${result.moduleCount} modules with preserved settings (${result.modules.join(', ')})`);
|
||||||
|
|
||||||
// Display version-specific end message
|
// Display version-specific end message
|
||||||
const { MessageLoader } = require('../installers/lib/message-loader');
|
const { MessageLoader } = require('../installers/lib/message-loader');
|
||||||
const messageLoader = new MessageLoader();
|
const messageLoader = new MessageLoader();
|
||||||
messageLoader.displayEndMessage();
|
await messageLoader.displayEndMessage();
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -60,8 +60,8 @@ module.exports = {
|
||||||
// Handle compile agents separately
|
// Handle compile agents separately
|
||||||
if (config.actionType === 'compile-agents') {
|
if (config.actionType === 'compile-agents') {
|
||||||
const result = await installer.compileAgents(config);
|
const result = await installer.compileAgents(config);
|
||||||
console.log(chalk.green('\n✨ Agent recompilation complete!'));
|
await prompts.log.success('Agent recompilation complete!');
|
||||||
console.log(chalk.cyan(`Recompiled ${result.agentCount} agents with customizations applied`));
|
await prompts.log.info(`Recompiled ${result.agentCount} agents with customizations applied`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ module.exports = {
|
||||||
// Display version-specific end message from install-messages.yaml
|
// Display version-specific end message from install-messages.yaml
|
||||||
const { MessageLoader } = require('../installers/lib/message-loader');
|
const { MessageLoader } = require('../installers/lib/message-loader');
|
||||||
const messageLoader = new MessageLoader();
|
const messageLoader = new MessageLoader();
|
||||||
messageLoader.displayEndMessage();
|
await messageLoader.displayEndMessage();
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
@ -89,12 +89,12 @@ module.exports = {
|
||||||
if (error.fullMessage) {
|
if (error.fullMessage) {
|
||||||
console.error(error.fullMessage);
|
console.error(error.fullMessage);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
console.error('\n' + chalk.dim(error.stack));
|
await prompts.log.message(error.stack);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Generic error handling for all other errors
|
// Generic error handling for all other errors
|
||||||
console.error(chalk.red('Installation failed:'), error.message);
|
await prompts.log.error(`Installation failed: ${error.message}`);
|
||||||
console.error(chalk.dim(error.stack));
|
await prompts.log.message(error.stack);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const chalk = require('chalk');
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
const prompts = require('../lib/prompts');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
const { Manifest } = require('../installers/lib/core/manifest');
|
const { Manifest } = require('../installers/lib/core/manifest');
|
||||||
const { UI } = require('../lib/ui');
|
const { UI } = require('../lib/ui');
|
||||||
|
|
@ -21,9 +21,9 @@ module.exports = {
|
||||||
// Check if bmad directory exists
|
// Check if bmad directory exists
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
if (!(await fs.pathExists(bmadDir))) {
|
if (!(await fs.pathExists(bmadDir))) {
|
||||||
console.log(chalk.yellow('No BMAD installation found in the current directory.'));
|
await prompts.log.warn('No BMAD installation found in the current directory.');
|
||||||
console.log(chalk.dim(`Expected location: ${bmadDir}`));
|
await prompts.log.message(`Expected location: ${bmadDir}`);
|
||||||
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
|
await prompts.log.message('Run "bmad install" to set up a new installation.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -32,8 +32,8 @@ module.exports = {
|
||||||
const manifestData = await manifest._readRaw(bmadDir);
|
const manifestData = await manifest._readRaw(bmadDir);
|
||||||
|
|
||||||
if (!manifestData) {
|
if (!manifestData) {
|
||||||
console.log(chalk.yellow('No BMAD installation manifest found.'));
|
await prompts.log.warn('No BMAD installation manifest found.');
|
||||||
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
|
await prompts.log.message('Run "bmad install" to set up a new installation.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ module.exports = {
|
||||||
const availableUpdates = await manifest.checkForUpdates(bmadDir);
|
const availableUpdates = await manifest.checkForUpdates(bmadDir);
|
||||||
|
|
||||||
// Display status
|
// Display status
|
||||||
ui.displayStatus({
|
await ui.displayStatus({
|
||||||
installation,
|
installation,
|
||||||
modules,
|
modules,
|
||||||
availableUpdates,
|
availableUpdates,
|
||||||
|
|
@ -55,9 +55,9 @@ module.exports = {
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(chalk.red('Status check failed:'), error.message);
|
await prompts.log.error(`Status check failed: ${error.message}`);
|
||||||
if (process.env.BMAD_DEBUG) {
|
if (process.env.BMAD_DEBUG) {
|
||||||
console.error(chalk.dim(error.stack));
|
await prompts.log.message(error.stack);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
||||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
@ -260,15 +259,9 @@ class ConfigCollector {
|
||||||
|
|
||||||
// If module has no config keys at all, handle it specially
|
// If module has no config keys at all, handle it specially
|
||||||
if (hasNoConfig && moduleConfig.subheader) {
|
if (hasNoConfig && moduleConfig.subheader) {
|
||||||
// Add blank line for better readability (matches other modules)
|
|
||||||
console.log();
|
|
||||||
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
||||||
|
await prompts.log.step(moduleDisplayName);
|
||||||
// Display the module name in color first (matches other modules)
|
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
|
|
||||||
// Show the subheader since there's no configuration to ask about
|
|
||||||
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
|
||||||
return false; // No new fields
|
return false; // No new fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,7 +315,7 @@ class ConfigCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show "no config" message for modules with no new questions (that have config keys)
|
// Show "no config" message for modules with no new questions (that have config keys)
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module already up to date`));
|
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module already up to date`);
|
||||||
return false; // No new fields
|
return false; // No new fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,7 +343,7 @@ class ConfigCollector {
|
||||||
|
|
||||||
if (questions.length > 0) {
|
if (questions.length > 0) {
|
||||||
// Only show header if we actually have questions
|
// Only show header if we actually have questions
|
||||||
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
console.log(); // Line break before questions
|
console.log(); // Line break before questions
|
||||||
const promptedAnswers = await prompts.prompt(questions);
|
const promptedAnswers = await prompts.prompt(questions);
|
||||||
|
|
||||||
|
|
@ -358,7 +351,7 @@ class ConfigCollector {
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
} else if (newStaticKeys.length > 0) {
|
} else if (newStaticKeys.length > 0) {
|
||||||
// Only static fields, no questions - show no config message
|
// Only static fields, no questions - show no config message
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configuration updated`));
|
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configuration updated`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store all answers for cross-referencing
|
// Store all answers for cross-referencing
|
||||||
|
|
@ -588,7 +581,7 @@ class ConfigCollector {
|
||||||
|
|
||||||
// Skip prompts mode: use all defaults without asking
|
// Skip prompts mode: use all defaults without asking
|
||||||
if (this.skipPrompts) {
|
if (this.skipPrompts) {
|
||||||
console.log(chalk.cyan('Using default configuration for'), chalk.magenta(moduleDisplayName));
|
await prompts.log.info(`Using default configuration for ${moduleDisplayName}`);
|
||||||
// Use defaults for all questions
|
// Use defaults for all questions
|
||||||
for (const question of questions) {
|
for (const question of questions) {
|
||||||
const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
|
const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
|
||||||
|
|
@ -597,12 +590,10 @@ class ConfigCollector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log();
|
await prompts.log.step(moduleDisplayName);
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
let customize = true;
|
let customize = true;
|
||||||
if (moduleName === 'core') {
|
if (moduleName === 'core') {
|
||||||
// Core module: no confirm prompt, so add spacing manually to match visual style
|
// Core module: no confirm prompt, continues directly
|
||||||
console.log(chalk.gray('│'));
|
|
||||||
} else {
|
} else {
|
||||||
// Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing)
|
// Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing)
|
||||||
const customizeAnswer = await prompts.prompt([
|
const customizeAnswer = await prompts.prompt([
|
||||||
|
|
@ -621,7 +612,7 @@ class ConfigCollector {
|
||||||
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
|
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
|
||||||
|
|
||||||
if (questionsWithoutDefaults.length > 0) {
|
if (questionsWithoutDefaults.length > 0) {
|
||||||
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
await prompts.log.message(` Asking required questions for ${moduleName.toUpperCase()}...`);
|
||||||
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
|
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
}
|
}
|
||||||
|
|
@ -748,11 +739,7 @@ class ConfigCollector {
|
||||||
|
|
||||||
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
|
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
|
||||||
// Module explicitly has no configuration - show with special styling
|
// Module explicitly has no configuration - show with special styling
|
||||||
// Add blank line for better readability (matches other modules)
|
await prompts.log.step(moduleDisplayName);
|
||||||
console.log();
|
|
||||||
|
|
||||||
// Display the module name in color first (matches other modules)
|
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
|
|
||||||
// Ask user if they want to accept defaults or customize on the next line
|
// Ask user if they want to accept defaults or customize on the next line
|
||||||
const { customize } = await prompts.prompt([
|
const { customize } = await prompts.prompt([
|
||||||
|
|
@ -766,13 +753,13 @@ class ConfigCollector {
|
||||||
|
|
||||||
// Show the subheader if available, otherwise show a default message
|
// Show the subheader if available, otherwise show a default message
|
||||||
if (moduleConfig.subheader) {
|
if (moduleConfig.subheader) {
|
||||||
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim(` ✓ No custom configuration required`));
|
await prompts.log.message(` \u2713 No custom configuration required`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Module has config but just no questions to ask
|
// Module has config but just no questions to ask
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configured`));
|
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configured`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -981,14 +968,15 @@ class ConfigCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add current value indicator for existing configs
|
// Add current value indicator for existing configs
|
||||||
|
const color = await prompts.getColor();
|
||||||
if (existingValue !== null && existingValue !== undefined) {
|
if (existingValue !== null && existingValue !== undefined) {
|
||||||
if (typeof existingValue === 'boolean') {
|
if (typeof existingValue === 'boolean') {
|
||||||
message += chalk.dim(` (current: ${existingValue ? 'true' : 'false'})`);
|
message += color.dim(` (current: ${existingValue ? 'true' : 'false'})`);
|
||||||
} else if (Array.isArray(existingValue)) {
|
} else if (Array.isArray(existingValue)) {
|
||||||
message += chalk.dim(` (current: ${existingValue.join(', ')})`);
|
message += color.dim(` (current: ${existingValue.join(', ')})`);
|
||||||
} else if (questionType !== 'list') {
|
} else if (questionType !== 'list') {
|
||||||
// Show the cleaned value (without {project-root}/) for display
|
// Show the cleaned value (without {project-root}/) for display
|
||||||
message += chalk.dim(` (current: ${existingValue})`);
|
message += color.dim(` (current: ${existingValue})`);
|
||||||
}
|
}
|
||||||
} else if (item.example && questionType === 'input') {
|
} else if (item.example && questionType === 'input') {
|
||||||
// Show example for input fields
|
// Show example for input fields
|
||||||
|
|
@ -998,7 +986,7 @@ class ConfigCollector {
|
||||||
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
|
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
|
||||||
exampleText = exampleText.replace('{project-root}/', '');
|
exampleText = exampleText.replace('{project-root}/', '');
|
||||||
}
|
}
|
||||||
message += chalk.dim(` (e.g., ${exampleText})`);
|
message += color.dim(` (e.g., ${exampleText})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the question object
|
// Build the question object
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
const chalk = require('chalk');
|
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dependency Resolver for BMAD modules
|
* Dependency Resolver for BMAD modules
|
||||||
|
|
@ -24,7 +24,7 @@ class DependencyResolver {
|
||||||
*/
|
*/
|
||||||
async resolve(bmadDir, selectedModules = [], options = {}) {
|
async resolve(bmadDir, selectedModules = [], options = {}) {
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
console.log(chalk.cyan('Resolving module dependencies...'));
|
await prompts.log.info('Resolving module dependencies...');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always include core as base
|
// Always include core as base
|
||||||
|
|
@ -50,7 +50,7 @@ class DependencyResolver {
|
||||||
|
|
||||||
// Report results (only in verbose mode)
|
// Report results (only in verbose mode)
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
this.reportResults(organizedFiles, selectedModules);
|
await this.reportResults(organizedFiles, selectedModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -91,7 +91,7 @@ class DependencyResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await fs.pathExists(moduleDir))) {
|
if (!(await fs.pathExists(moduleDir))) {
|
||||||
console.warn(chalk.yellow(`Module directory not found: ${moduleDir}`));
|
await prompts.log.warn('Module directory not found: ' + moduleDir);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ class DependencyResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`Failed to parse frontmatter in ${file.name}: ${error.message}`));
|
await prompts.log.warn('Failed to parse frontmatter in ' + file.name + ': ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -658,8 +658,8 @@ class DependencyResolver {
|
||||||
/**
|
/**
|
||||||
* Report resolution results
|
* Report resolution results
|
||||||
*/
|
*/
|
||||||
reportResults(organized, selectedModules) {
|
async reportResults(organized, selectedModules) {
|
||||||
console.log(chalk.green('\n✓ Dependency resolution complete'));
|
await prompts.log.success('Dependency resolution complete');
|
||||||
|
|
||||||
for (const [module, files] of Object.entries(organized)) {
|
for (const [module, files] of Object.entries(organized)) {
|
||||||
const isSelected = selectedModules.includes(module) || module === 'core';
|
const isSelected = selectedModules.includes(module) || module === 'core';
|
||||||
|
|
@ -667,31 +667,31 @@ class DependencyResolver {
|
||||||
files.agents.length + files.tasks.length + files.tools.length + files.templates.length + files.data.length + files.other.length;
|
files.agents.length + files.tasks.length + files.tools.length + files.templates.length + files.data.length + files.other.length;
|
||||||
|
|
||||||
if (totalFiles > 0) {
|
if (totalFiles > 0) {
|
||||||
console.log(chalk.cyan(`\n ${module.toUpperCase()} module:`));
|
await prompts.log.info(` ${module.toUpperCase()} module:`);
|
||||||
console.log(chalk.dim(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`));
|
await prompts.log.message(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`);
|
||||||
|
|
||||||
if (files.agents.length > 0) {
|
if (files.agents.length > 0) {
|
||||||
console.log(chalk.dim(` Agents: ${files.agents.length}`));
|
await prompts.log.message(` Agents: ${files.agents.length}`);
|
||||||
}
|
}
|
||||||
if (files.tasks.length > 0) {
|
if (files.tasks.length > 0) {
|
||||||
console.log(chalk.dim(` Tasks: ${files.tasks.length}`));
|
await prompts.log.message(` Tasks: ${files.tasks.length}`);
|
||||||
}
|
}
|
||||||
if (files.templates.length > 0) {
|
if (files.templates.length > 0) {
|
||||||
console.log(chalk.dim(` Templates: ${files.templates.length}`));
|
await prompts.log.message(` Templates: ${files.templates.length}`);
|
||||||
}
|
}
|
||||||
if (files.data.length > 0) {
|
if (files.data.length > 0) {
|
||||||
console.log(chalk.dim(` Data files: ${files.data.length}`));
|
await prompts.log.message(` Data files: ${files.data.length}`);
|
||||||
}
|
}
|
||||||
if (files.other.length > 0) {
|
if (files.other.length > 0) {
|
||||||
console.log(chalk.dim(` Other files: ${files.other.length}`));
|
await prompts.log.message(` Other files: ${files.other.length}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.missingDependencies.size > 0) {
|
if (this.missingDependencies.size > 0) {
|
||||||
console.log(chalk.yellow('\n ⚠ Missing dependencies:'));
|
await prompts.log.warn('Missing dependencies:');
|
||||||
for (const missing of this.missingDependencies) {
|
for (const missing of this.missingDependencies) {
|
||||||
console.log(chalk.yellow(` - ${missing}`));
|
await prompts.log.warn(` - ${missing}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const ora = require('ora');
|
|
||||||
const { Detector } = require('./detector');
|
const { Detector } = require('./detector');
|
||||||
const { Manifest } = require('./manifest');
|
const { Manifest } = require('./manifest');
|
||||||
const { ModuleManager } = require('../modules/manager');
|
const { ModuleManager } = require('../modules/manager');
|
||||||
|
|
@ -166,7 +164,7 @@ class Installer {
|
||||||
const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
|
const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
|
||||||
|
|
||||||
if (newlySelectedIdes.length > 0) {
|
if (newlySelectedIdes.length > 0) {
|
||||||
console.log('\n'); // Add spacing before IDE questions
|
// @clack handles spacing
|
||||||
|
|
||||||
// Collect configuration for IDEs that support it
|
// Collect configuration for IDEs that support it
|
||||||
for (const ide of newlySelectedIdes) {
|
for (const ide of newlySelectedIdes) {
|
||||||
|
|
@ -174,14 +172,14 @@ class Installer {
|
||||||
const handler = this.ideManager.handlers.get(ide);
|
const handler = this.ideManager.handlers.get(ide);
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.warn(chalk.yellow(`Warning: IDE '${ide}' handler not found`));
|
await prompts.log.warn(`Warning: IDE '${ide}' handler not found`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this IDE handler has a collectConfiguration method
|
// Check if this IDE handler has a collectConfiguration method
|
||||||
// (custom installers like Codex, Kilo, Kiro-cli may have this)
|
// (custom installers like Codex, Kilo, Kiro-cli may have this)
|
||||||
if (typeof handler.collectConfiguration === 'function') {
|
if (typeof handler.collectConfiguration === 'function') {
|
||||||
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
|
await prompts.log.info(`Configuring ${ide}...`);
|
||||||
ideConfigurations[ide] = await handler.collectConfiguration({
|
ideConfigurations[ide] = await handler.collectConfiguration({
|
||||||
selectedModules: selectedModules || [],
|
selectedModules: selectedModules || [],
|
||||||
projectDir,
|
projectDir,
|
||||||
|
|
@ -191,7 +189,7 @@ class Installer {
|
||||||
// Most config-driven IDEs don't need configuration - silently skip
|
// Most config-driven IDEs don't need configuration - silently skip
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// IDE doesn't support configuration or has an error
|
// IDE doesn't support configuration or has an error
|
||||||
console.warn(chalk.yellow(`Warning: Could not load configuration for ${ide}: ${error.message}`));
|
await prompts.log.warn(`Warning: Could not load configuration for ${ide}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +197,7 @@ class Installer {
|
||||||
// Log which IDEs are already configured and being kept
|
// Log which IDEs are already configured and being kept
|
||||||
const keptIdes = toolConfig.ides.filter((ide) => previouslyConfiguredIdes.includes(ide));
|
const keptIdes = toolConfig.ides.filter((ide) => previouslyConfiguredIdes.includes(ide));
|
||||||
if (keptIdes.length > 0) {
|
if (keptIdes.length > 0) {
|
||||||
console.log(chalk.dim(`\nKeeping existing configuration for: ${keptIdes.join(', ')}`));
|
await prompts.log.message(`Keeping existing configuration for: ${keptIdes.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,10 +227,10 @@ class Installer {
|
||||||
// Only display logo if core config wasn't already collected (meaning we're not continuing from UI)
|
// Only display logo if core config wasn't already collected (meaning we're not continuing from UI)
|
||||||
if (!hasCoreConfig) {
|
if (!hasCoreConfig) {
|
||||||
// Display BMAD logo
|
// Display BMAD logo
|
||||||
CLIUtils.displayLogo();
|
await CLIUtils.displayLogo();
|
||||||
|
|
||||||
// Display welcome message
|
// Display welcome message
|
||||||
CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
|
await CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Legacy V4 detection now happens earlier in UI.promptInstall()
|
// Note: Legacy V4 detection now happens earlier in UI.promptInstall()
|
||||||
|
|
@ -372,7 +370,8 @@ class Installer {
|
||||||
|
|
||||||
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
||||||
|
|
||||||
const spinner = ora('Preparing installation...').start();
|
const spinner = await prompts.spinner();
|
||||||
|
spinner.start('Preparing installation...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Resolve target directory (path.resolve handles platform differences)
|
// Resolve target directory (path.resolve handles platform differences)
|
||||||
|
|
@ -383,30 +382,30 @@ class Installer {
|
||||||
|
|
||||||
// Create a project directory if it doesn't exist (user already confirmed)
|
// Create a project directory if it doesn't exist (user already confirmed)
|
||||||
if (!(await fs.pathExists(projectDir))) {
|
if (!(await fs.pathExists(projectDir))) {
|
||||||
spinner.text = 'Creating installation directory...';
|
spinner.message('Creating installation directory...');
|
||||||
try {
|
try {
|
||||||
// fs.ensureDir handles platform-specific directory creation
|
// fs.ensureDir handles platform-specific directory creation
|
||||||
// It will recursively create all necessary parent directories
|
// It will recursively create all necessary parent directories
|
||||||
await fs.ensureDir(projectDir);
|
await fs.ensureDir(projectDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Failed to create installation directory');
|
spinner.error('Failed to create installation directory');
|
||||||
console.error(chalk.red(`Error: ${error.message}`));
|
await prompts.log.error(`Error: ${error.message}`);
|
||||||
// More detailed error for common issues
|
// More detailed error for common issues
|
||||||
if (error.code === 'EACCES') {
|
if (error.code === 'EACCES') {
|
||||||
console.error(chalk.red('Permission denied. Check parent directory permissions.'));
|
await prompts.log.error('Permission denied. Check parent directory permissions.');
|
||||||
} else if (error.code === 'ENOSPC') {
|
} else if (error.code === 'ENOSPC') {
|
||||||
console.error(chalk.red('No space left on device.'));
|
await prompts.log.error('No space left on device.');
|
||||||
}
|
}
|
||||||
throw new Error(`Cannot create directory: ${projectDir}`);
|
throw new Error(`Cannot create directory: ${projectDir}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check existing installation
|
// Check existing installation
|
||||||
spinner.text = 'Checking for existing installation...';
|
spinner.message('Checking for existing installation...');
|
||||||
const existingInstall = await this.detector.detect(bmadDir);
|
const existingInstall = await this.detector.detect(bmadDir);
|
||||||
|
|
||||||
if (existingInstall.installed && !config.force && !config._quickUpdate) {
|
if (existingInstall.installed && !config.force && !config._quickUpdate) {
|
||||||
spinner.stop();
|
spinner.stop('Existing installation detected');
|
||||||
|
|
||||||
// Check if user already decided what to do (from early menu in ui.js)
|
// Check if user already decided what to do (from early menu in ui.js)
|
||||||
let action = null;
|
let action = null;
|
||||||
|
|
@ -414,9 +413,9 @@ class Installer {
|
||||||
action = 'update';
|
action = 'update';
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Ask the user (backwards compatibility for other code paths)
|
// Fallback: Ask the user (backwards compatibility for other code paths)
|
||||||
console.log(chalk.yellow('\n⚠️ Existing BMAD installation detected'));
|
await prompts.log.warn('Existing BMAD installation detected');
|
||||||
console.log(chalk.dim(` Location: ${bmadDir}`));
|
await prompts.log.message(` Location: ${bmadDir}`);
|
||||||
console.log(chalk.dim(` Version: ${existingInstall.version}`));
|
await prompts.log.message(` Version: ${existingInstall.version}`);
|
||||||
|
|
||||||
const promptResult = await this.promptUpdateAction();
|
const promptResult = await this.promptUpdateAction();
|
||||||
action = promptResult.action;
|
action = promptResult.action;
|
||||||
|
|
@ -438,17 +437,17 @@ class Installer {
|
||||||
// If there are modules to remove, ask for confirmation
|
// If there are modules to remove, ask for confirmation
|
||||||
if (modulesToRemove.length > 0) {
|
if (modulesToRemove.length > 0) {
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
spinner.stop();
|
if (spinner.isSpinning) {
|
||||||
|
spinner.stop('Reviewing module changes');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('');
|
await prompts.log.warn('Modules to be removed:');
|
||||||
console.log(chalk.yellow.bold('⚠️ Modules to be removed:'));
|
|
||||||
for (const moduleId of modulesToRemove) {
|
for (const moduleId of modulesToRemove) {
|
||||||
const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId);
|
const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId);
|
||||||
const displayName = moduleInfo?.name || moduleId;
|
const displayName = moduleInfo?.name || moduleId;
|
||||||
const modulePath = path.join(bmadDir, moduleId);
|
const modulePath = path.join(bmadDir, moduleId);
|
||||||
console.log(chalk.red(` - ${displayName} (${modulePath})`));
|
await prompts.log.error(` - ${displayName} (${modulePath})`);
|
||||||
}
|
}
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const confirmRemoval = await prompts.confirm({
|
const confirmRemoval = await prompts.confirm({
|
||||||
message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`,
|
message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`,
|
||||||
|
|
@ -462,15 +461,15 @@ class Installer {
|
||||||
try {
|
try {
|
||||||
if (await fs.pathExists(modulePath)) {
|
if (await fs.pathExists(modulePath)) {
|
||||||
await fs.remove(modulePath);
|
await fs.remove(modulePath);
|
||||||
console.log(chalk.dim(` ✓ Removed: ${moduleId}`));
|
await prompts.log.message(` Removed: ${moduleId}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Warning: Failed to remove ${moduleId}: ${error.message}`));
|
await prompts.log.warn(` Warning: Failed to remove ${moduleId}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(chalk.green(` ✓ Removed ${modulesToRemove.length} module(s)`));
|
await prompts.log.success(` Removed ${modulesToRemove.length} module(s)`);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim(' → Module removal cancelled'));
|
await prompts.log.message(' Module removal cancelled');
|
||||||
// Add the modules back to the selection since user cancelled removal
|
// Add the modules back to the selection since user cancelled removal
|
||||||
for (const moduleId of modulesToRemove) {
|
for (const moduleId of modulesToRemove) {
|
||||||
if (!config.modules) config.modules = [];
|
if (!config.modules) config.modules = [];
|
||||||
|
|
@ -503,7 +502,7 @@ class Installer {
|
||||||
// Also store in configCollector for use during config collection
|
// Also store in configCollector for use during config collection
|
||||||
this.configCollector.collectedConfig.core = existingCoreConfig;
|
this.configCollector.collectedConfig.core = existingCoreConfig;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`Warning: Could not read existing core config: ${error.message}`));
|
await prompts.log.warn(`Warning: Could not read existing core config: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,7 +553,7 @@ class Installer {
|
||||||
await fs.ensureDir(path.dirname(backupPath));
|
await fs.ensureDir(path.dirname(backupPath));
|
||||||
await fs.copy(customFile, backupPath);
|
await fs.copy(customFile, backupPath);
|
||||||
}
|
}
|
||||||
spinner.succeed(`Backed up ${customFiles.length} custom files`);
|
spinner.stop(`Backed up ${customFiles.length} custom files`);
|
||||||
|
|
||||||
config._tempBackupDir = tempBackupDir;
|
config._tempBackupDir = tempBackupDir;
|
||||||
}
|
}
|
||||||
|
|
@ -571,14 +570,14 @@ class Installer {
|
||||||
await fs.ensureDir(path.dirname(tempBackupPath));
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
||||||
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
||||||
}
|
}
|
||||||
spinner.succeed(`Backed up ${modifiedFiles.length} modified files`);
|
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
||||||
|
|
||||||
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (existingInstall.installed && config._quickUpdate) {
|
} else if (existingInstall.installed && config._quickUpdate) {
|
||||||
// Quick update mode - automatically treat as update without prompting
|
// Quick update mode - automatically treat as update without prompting
|
||||||
spinner.text = 'Preparing quick update...';
|
spinner.message('Preparing quick update...');
|
||||||
config._isUpdate = true;
|
config._isUpdate = true;
|
||||||
config._existingInstall = existingInstall;
|
config._existingInstall = existingInstall;
|
||||||
|
|
||||||
|
|
@ -636,7 +635,7 @@ class Installer {
|
||||||
await fs.ensureDir(path.dirname(backupPath));
|
await fs.ensureDir(path.dirname(backupPath));
|
||||||
await fs.copy(customFile, backupPath);
|
await fs.copy(customFile, backupPath);
|
||||||
}
|
}
|
||||||
spinner.succeed(`Backed up ${customFiles.length} custom files`);
|
spinner.stop(`Backed up ${customFiles.length} custom files`);
|
||||||
config._tempBackupDir = tempBackupDir;
|
config._tempBackupDir = tempBackupDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,14 +651,14 @@ class Installer {
|
||||||
await fs.ensureDir(path.dirname(tempBackupPath));
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
||||||
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
||||||
}
|
}
|
||||||
spinner.succeed(`Backed up ${modifiedFiles.length} modified files`);
|
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
||||||
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now collect tool configurations after we know if it's a reinstall
|
// Now collect tool configurations after we know if it's a reinstall
|
||||||
// Skip for quick update since we already have the IDE list
|
// Skip for quick update since we already have the IDE list
|
||||||
spinner.stop();
|
spinner.stop('Pre-checks complete');
|
||||||
let toolSelection;
|
let toolSelection;
|
||||||
if (config._quickUpdate) {
|
if (config._quickUpdate) {
|
||||||
// Quick update already has IDEs configured, use saved configurations
|
// Quick update already has IDEs configured, use saved configurations
|
||||||
|
|
@ -699,18 +698,18 @@ class Installer {
|
||||||
const ideConfigurations = toolSelection.configurations;
|
const ideConfigurations = toolSelection.configurations;
|
||||||
|
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.text = 'Continuing installation...';
|
spinner.message('Continuing installation...');
|
||||||
} else {
|
} else {
|
||||||
spinner.start('Continuing installation...');
|
spinner.start('Continuing installation...');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create bmad directory structure
|
// Create bmad directory structure
|
||||||
spinner.text = 'Creating directory structure...';
|
spinner.message('Creating directory structure...');
|
||||||
await this.createDirectoryStructure(bmadDir);
|
await this.createDirectoryStructure(bmadDir);
|
||||||
|
|
||||||
// Cache custom modules if any
|
// Cache custom modules if any
|
||||||
if (customModulePaths && customModulePaths.size > 0) {
|
if (customModulePaths && customModulePaths.size > 0) {
|
||||||
spinner.text = 'Caching custom modules...';
|
spinner.message('Caching custom modules...');
|
||||||
const { CustomModuleCache } = require('./custom-module-cache');
|
const { CustomModuleCache } = require('./custom-module-cache');
|
||||||
const customCache = new CustomModuleCache(bmadDir);
|
const customCache = new CustomModuleCache(bmadDir);
|
||||||
|
|
||||||
|
|
@ -725,7 +724,7 @@ class Installer {
|
||||||
|
|
||||||
// Update module manager with the cached paths
|
// Update module manager with the cached paths
|
||||||
this.moduleManager.setCustomModulePaths(customModulePaths);
|
this.moduleManager.setCustomModulePaths(customModulePaths);
|
||||||
spinner.succeed('Custom modules cached');
|
spinner.stop('Custom modules cached');
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectRoot = getProjectRoot();
|
const projectRoot = getProjectRoot();
|
||||||
|
|
@ -734,7 +733,7 @@ class Installer {
|
||||||
if (config.installCore) {
|
if (config.installCore) {
|
||||||
spinner.start('Installing BMAD core...');
|
spinner.start('Installing BMAD core...');
|
||||||
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
||||||
spinner.succeed('Core installed');
|
spinner.stop('Core installed');
|
||||||
|
|
||||||
// Generate core config file
|
// Generate core config file
|
||||||
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
||||||
|
|
@ -809,7 +808,7 @@ class Installer {
|
||||||
moduleManager: tempModuleManager,
|
moduleManager: tempModuleManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
spinner.succeed('Dependencies resolved');
|
spinner.stop('Dependencies resolved');
|
||||||
|
|
||||||
// Install modules with their dependencies
|
// Install modules with their dependencies
|
||||||
if (allModules && allModules.length > 0) {
|
if (allModules && allModules.length > 0) {
|
||||||
|
|
@ -915,7 +914,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed(`Module ${isQuickUpdate ? 'updated' : 'installed'}: ${moduleName}`);
|
spinner.stop(`Module ${isQuickUpdate ? 'updated' : 'installed'}: ${moduleName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install partial modules (only dependencies)
|
// Install partial modules (only dependencies)
|
||||||
|
|
@ -931,7 +930,7 @@ class Installer {
|
||||||
if (totalFiles > 0) {
|
if (totalFiles > 0) {
|
||||||
spinner.start(`Installing ${module} dependencies...`);
|
spinner.start(`Installing ${module} dependencies...`);
|
||||||
await this.installPartialModule(module, bmadDir, files);
|
await this.installPartialModule(module, bmadDir, files);
|
||||||
spinner.succeed(`${module} dependencies installed`);
|
spinner.stop(`${module} dependencies installed`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -942,7 +941,7 @@ class Installer {
|
||||||
// Generate clean config.yaml files for each installed module
|
// Generate clean config.yaml files for each installed module
|
||||||
spinner.start('Generating module configurations...');
|
spinner.start('Generating module configurations...');
|
||||||
await this.generateModuleConfigs(bmadDir, moduleConfigs);
|
await this.generateModuleConfigs(bmadDir, moduleConfigs);
|
||||||
spinner.succeed('Module configurations generated');
|
spinner.stop('Module configurations generated');
|
||||||
|
|
||||||
// Create agent configuration files
|
// Create agent configuration files
|
||||||
// Note: Legacy createAgentConfigs removed - using YAML customize system instead
|
// Note: Legacy createAgentConfigs removed - using YAML customize system instead
|
||||||
|
|
@ -985,7 +984,7 @@ class Installer {
|
||||||
|
|
||||||
// Custom modules are now included in the main modules list - no separate tracking needed
|
// Custom modules are now included in the main modules list - no separate tracking needed
|
||||||
|
|
||||||
spinner.succeed(
|
spinner.stop(
|
||||||
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -993,7 +992,7 @@ class Installer {
|
||||||
// This must happen AFTER generateManifests because it depends on agent-manifest.csv
|
// This must happen AFTER generateManifests because it depends on agent-manifest.csv
|
||||||
spinner.start('Generating workflow help catalog...');
|
spinner.start('Generating workflow help catalog...');
|
||||||
await this.mergeModuleHelpCatalogs(bmadDir);
|
await this.mergeModuleHelpCatalogs(bmadDir);
|
||||||
spinner.succeed('Workflow help catalog generated');
|
spinner.stop('Workflow help catalog generated');
|
||||||
|
|
||||||
// Configure IDEs and copy documentation
|
// Configure IDEs and copy documentation
|
||||||
if (!config.skipIde && config.ides && config.ides.length > 0) {
|
if (!config.skipIde && config.ides && config.ides.length > 0) {
|
||||||
|
|
@ -1004,7 +1003,7 @@ class Installer {
|
||||||
const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
|
const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
|
||||||
|
|
||||||
if (validIdes.length === 0) {
|
if (validIdes.length === 0) {
|
||||||
console.log(chalk.yellow('⚠️ No valid IDEs selected. Skipping IDE configuration.'));
|
await prompts.log.warn('No valid IDEs selected. Skipping IDE configuration.');
|
||||||
} else {
|
} else {
|
||||||
// Check if any IDE might need prompting (no pre-collected config)
|
// Check if any IDE might need prompting (no pre-collected config)
|
||||||
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
||||||
|
|
@ -1022,13 +1021,13 @@ class Installer {
|
||||||
for (const ide of validIdes) {
|
for (const ide of validIdes) {
|
||||||
// Only show spinner if we have pre-collected config (no prompts expected)
|
// Only show spinner if we have pre-collected config (no prompts expected)
|
||||||
if (ideConfigurations[ide] && !needsPrompting) {
|
if (ideConfigurations[ide] && !needsPrompting) {
|
||||||
spinner.text = `Configuring ${ide}...`;
|
spinner.message(`Configuring ${ide}...`);
|
||||||
} else if (!ideConfigurations[ide]) {
|
} else if (!ideConfigurations[ide]) {
|
||||||
// Stop spinner before prompting
|
// Stop spinner before prompting
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.stop();
|
spinner.stop('Ready for IDE configuration');
|
||||||
}
|
}
|
||||||
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
|
await prompts.log.info(`Configuring ${ide}...`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass pre-collected configuration to avoid re-prompting
|
// Pass pre-collected configuration to avoid re-prompting
|
||||||
|
|
@ -1053,9 +1052,9 @@ class Installer {
|
||||||
console.log = originalLog;
|
console.log = originalLog;
|
||||||
|
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.succeed(`Configured: ${validIdes.join(', ')}`);
|
spinner.stop(`Configured: ${validIdes.join(', ')}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.green(`✓ Configured: ${validIdes.join(', ')}`));
|
await prompts.log.success(`Configured: ${validIdes.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1073,7 +1072,7 @@ class Installer {
|
||||||
|
|
||||||
// Run core module installer if core was installed
|
// Run core module installer if core was installed
|
||||||
if (config.installCore || resolution.byModule.core) {
|
if (config.installCore || resolution.byModule.core) {
|
||||||
spinner.text = 'Running core module installer...';
|
spinner.message('Running core module installer...');
|
||||||
|
|
||||||
await this.moduleManager.runModuleInstaller('core', bmadDir, {
|
await this.moduleManager.runModuleInstaller('core', bmadDir, {
|
||||||
installedIDEs: config.ides || [],
|
installedIDEs: config.ides || [],
|
||||||
|
|
@ -1086,7 +1085,7 @@ class Installer {
|
||||||
// Run installers for user-selected modules
|
// Run installers for user-selected modules
|
||||||
if (config.modules && config.modules.length > 0) {
|
if (config.modules && config.modules.length > 0) {
|
||||||
for (const moduleName of config.modules) {
|
for (const moduleName of config.modules) {
|
||||||
spinner.text = `Running ${moduleName} module installer...`;
|
spinner.message(`Running ${moduleName} module installer...`);
|
||||||
|
|
||||||
// Pass installed IDEs and module config to module installer
|
// Pass installed IDEs and module config to module installer
|
||||||
await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
|
await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
|
||||||
|
|
@ -1098,7 +1097,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed('Module-specific installers completed');
|
spinner.stop('Module-specific installers completed');
|
||||||
|
|
||||||
// Note: Manifest files are already created by ManifestGenerator above
|
// Note: Manifest files are already created by ManifestGenerator above
|
||||||
// No need to create legacy manifest.csv anymore
|
// No need to create legacy manifest.csv anymore
|
||||||
|
|
@ -1125,7 +1124,7 @@ class Installer {
|
||||||
await fs.remove(config._tempBackupDir);
|
await fs.remove(config._tempBackupDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed(`Restored ${config._customFiles.length} custom files`);
|
spinner.stop(`Restored ${config._customFiles.length} custom files`);
|
||||||
customFiles = config._customFiles;
|
customFiles = config._customFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1150,32 +1149,32 @@ class Installer {
|
||||||
// Clean up temp backup
|
// Clean up temp backup
|
||||||
await fs.remove(config._tempModifiedBackupDir);
|
await fs.remove(config._tempModifiedBackupDir);
|
||||||
|
|
||||||
spinner.succeed(`Restored ${modifiedFiles.length} modified files as .bak`);
|
spinner.stop(`Restored ${modifiedFiles.length} modified files as .bak`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.stop();
|
if (spinner.isSpinning) {
|
||||||
|
spinner.stop('Installation finalized');
|
||||||
|
}
|
||||||
|
|
||||||
// Report custom and modified files if any were found
|
// Report custom and modified files if any were found
|
||||||
if (customFiles.length > 0) {
|
if (customFiles.length > 0) {
|
||||||
console.log(chalk.cyan(`\n📁 Custom files preserved: ${customFiles.length}`));
|
await prompts.log.info(`Custom files preserved: ${customFiles.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifiedFiles.length > 0) {
|
if (modifiedFiles.length > 0) {
|
||||||
console.log(chalk.yellow(`\n⚠️ User modified files detected: ${modifiedFiles.length}`));
|
await prompts.log.warn(`User modified files detected: ${modifiedFiles.length}`);
|
||||||
console.log(
|
await prompts.log.message(
|
||||||
chalk.dim(
|
'These user modified files have been updated with the new version, search the project for .bak files that had your customizations.',
|
||||||
'\nThese user modified files have been updated with the new version, search the project for .bak files that had your customizations.',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
console.log(chalk.dim('Remove these .bak files it no longer needed\n'));
|
await prompts.log.message('Remove these .bak files if no longer needed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display completion message
|
// Display completion message
|
||||||
const { UI } = require('../../../lib/ui');
|
const { UI } = require('../../../lib/ui');
|
||||||
const ui = new UI();
|
const ui = new UI();
|
||||||
ui.showInstallSummary({
|
await ui.showInstallSummary({
|
||||||
path: bmadDir,
|
path: bmadDir,
|
||||||
modules: config.modules,
|
modules: config.modules,
|
||||||
ides: config.ides,
|
ides: config.ides,
|
||||||
|
|
@ -1190,7 +1189,7 @@ class Installer {
|
||||||
projectDir: projectDir,
|
projectDir: projectDir,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Installation failed');
|
spinner.error('Installation failed');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1199,7 +1198,8 @@ class Installer {
|
||||||
* Update existing installation
|
* Update existing installation
|
||||||
*/
|
*/
|
||||||
async update(config) {
|
async update(config) {
|
||||||
const spinner = ora('Checking installation...').start();
|
const spinner = await prompts.spinner();
|
||||||
|
spinner.start('Checking installation...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const projectDir = path.resolve(config.directory);
|
const projectDir = path.resolve(config.directory);
|
||||||
|
|
@ -1207,11 +1207,11 @@ class Installer {
|
||||||
const existingInstall = await this.detector.detect(bmadDir);
|
const existingInstall = await this.detector.detect(bmadDir);
|
||||||
|
|
||||||
if (!existingInstall.installed) {
|
if (!existingInstall.installed) {
|
||||||
spinner.fail('No BMAD installation found');
|
spinner.stop('No BMAD installation found');
|
||||||
throw new Error(`No BMAD installation found at ${bmadDir}`);
|
throw new Error(`No BMAD installation found at ${bmadDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.text = 'Analyzing update requirements...';
|
spinner.message('Analyzing update requirements...');
|
||||||
|
|
||||||
// Compare versions and determine what needs updating
|
// Compare versions and determine what needs updating
|
||||||
const currentVersion = existingInstall.version;
|
const currentVersion = existingInstall.version;
|
||||||
|
|
@ -1265,8 +1265,8 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customModuleSources.size > 0) {
|
if (customModuleSources.size > 0) {
|
||||||
spinner.stop();
|
spinner.stop('Update analysis complete');
|
||||||
console.log(chalk.yellow('\nChecking custom module sources before update...'));
|
await prompts.log.warn('Checking custom module sources before update...');
|
||||||
|
|
||||||
const projectRoot = getProjectRoot();
|
const projectRoot = getProjectRoot();
|
||||||
await this.handleMissingCustomSources(
|
await this.handleMissingCustomSources(
|
||||||
|
|
@ -1281,43 +1281,43 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.dryRun) {
|
if (config.dryRun) {
|
||||||
spinner.stop();
|
spinner.stop('Dry run analysis complete');
|
||||||
console.log(chalk.cyan('\n🔍 Update Preview (Dry Run)\n'));
|
let dryRunContent = `Current version: ${currentVersion}\n`;
|
||||||
console.log(chalk.bold('Current version:'), currentVersion);
|
dryRunContent += `New version: ${newVersion}\n`;
|
||||||
console.log(chalk.bold('New version:'), newVersion);
|
dryRunContent += `Core: ${existingInstall.hasCore ? 'Will be updated' : 'Not installed'}`;
|
||||||
console.log(chalk.bold('Core:'), existingInstall.hasCore ? 'Will be updated' : 'Not installed');
|
|
||||||
|
|
||||||
if (existingInstall.modules.length > 0) {
|
if (existingInstall.modules.length > 0) {
|
||||||
console.log(chalk.bold('\nModules to update:'));
|
dryRunContent += '\n\nModules to update:';
|
||||||
for (const mod of existingInstall.modules) {
|
for (const mod of existingInstall.modules) {
|
||||||
console.log(` - ${mod.id}`);
|
dryRunContent += `\n - ${mod.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await prompts.note(dryRunContent, 'Update Preview (Dry Run)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform actual update
|
// Perform actual update
|
||||||
if (existingInstall.hasCore) {
|
if (existingInstall.hasCore) {
|
||||||
spinner.text = 'Updating core...';
|
spinner.message('Updating core...');
|
||||||
await this.updateCore(bmadDir, config.force);
|
await this.updateCore(bmadDir, config.force);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const module of existingInstall.modules) {
|
for (const module of existingInstall.modules) {
|
||||||
spinner.text = `Updating module: ${module.id}...`;
|
spinner.message(`Updating module: ${module.id}...`);
|
||||||
await this.moduleManager.update(module.id, bmadDir, config.force);
|
await this.moduleManager.update(module.id, bmadDir, config.force);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update manifest
|
// Update manifest
|
||||||
spinner.text = 'Updating manifest...';
|
spinner.message('Updating manifest...');
|
||||||
await this.manifest.update(bmadDir, {
|
await this.manifest.update(bmadDir, {
|
||||||
version: newVersion,
|
version: newVersion,
|
||||||
updateDate: new Date().toISOString(),
|
updateDate: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
spinner.succeed('Update complete');
|
spinner.stop('Update complete');
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Update failed');
|
spinner.error('Update failed');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1492,10 +1492,10 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Merged module-help from: ${moduleName}`));
|
await prompts.log.message(` Merged module-help from: ${moduleName}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Warning: Failed to read module-help.csv from ${moduleName}:`, error.message));
|
await prompts.log.warn(` Warning: Failed to read module-help.csv from ${moduleName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1537,7 +1537,7 @@ class Installer {
|
||||||
this.installedFiles.add(outputPath);
|
this.installedFiles.add(outputPath);
|
||||||
|
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Generated bmad-help.csv: ${allRows.length} workflows`));
|
await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1907,7 +1907,7 @@ class Installer {
|
||||||
// Check for localskip="true" in the agent tag
|
// Check for localskip="true" in the agent tag
|
||||||
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
||||||
if (agentMatch) {
|
if (agentMatch) {
|
||||||
console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
|
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
|
||||||
continue; // Skip this agent
|
continue; // Skip this agent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1994,7 +1994,7 @@ class Installer {
|
||||||
if (await fs.pathExists(genericTemplatePath)) {
|
if (await fs.pathExists(genericTemplatePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2029,8 +2029,8 @@ class Installer {
|
||||||
* @returns {Object} Update result
|
* @returns {Object} Update result
|
||||||
*/
|
*/
|
||||||
async quickUpdate(config) {
|
async quickUpdate(config) {
|
||||||
const ora = require('ora');
|
const spinner = await prompts.spinner();
|
||||||
const spinner = ora('Starting quick update...').start();
|
spinner.start('Starting quick update...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const projectDir = path.resolve(config.directory);
|
const projectDir = path.resolve(config.directory);
|
||||||
|
|
@ -2038,11 +2038,11 @@ class Installer {
|
||||||
|
|
||||||
// Check if bmad directory exists
|
// Check if bmad directory exists
|
||||||
if (!(await fs.pathExists(bmadDir))) {
|
if (!(await fs.pathExists(bmadDir))) {
|
||||||
spinner.fail('No BMAD installation found');
|
spinner.stop('No BMAD installation found');
|
||||||
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.text = 'Detecting installed modules and configuration...';
|
spinner.message('Detecting installed modules and configuration...');
|
||||||
|
|
||||||
// Detect existing installation
|
// Detect existing installation
|
||||||
const existingInstall = await this.detector.detect(bmadDir);
|
const existingInstall = await this.detector.detect(bmadDir);
|
||||||
|
|
@ -2169,14 +2169,14 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
spinner.stop(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
||||||
|
|
||||||
if (skippedModules.length > 0) {
|
if (skippedModules.length > 0) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`));
|
await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load existing configs and collect new fields (if any)
|
// Load existing configs and collect new fields (if any)
|
||||||
console.log(chalk.cyan('\n📋 Checking for new configuration options...'));
|
await prompts.log.info('Checking for new configuration options...');
|
||||||
await this.configCollector.loadExistingConfig(projectDir);
|
await this.configCollector.loadExistingConfig(projectDir);
|
||||||
|
|
||||||
let promptedForNewFields = false;
|
let promptedForNewFields = false;
|
||||||
|
|
@ -2196,7 +2196,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!promptedForNewFields) {
|
if (!promptedForNewFields) {
|
||||||
console.log(chalk.green('✓ All configuration is up to date, no new options to configure'));
|
await prompts.log.success('All configuration is up to date, no new options to configure');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add metadata
|
// Add metadata
|
||||||
|
|
@ -2228,7 +2228,7 @@ class Installer {
|
||||||
// Only succeed the spinner if it's still spinning
|
// Only succeed the spinner if it's still spinning
|
||||||
// (install method might have stopped it if folder name changed)
|
// (install method might have stopped it if folder name changed)
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.succeed('Quick update complete!');
|
spinner.stop('Quick update complete!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -2240,7 +2240,7 @@ class Installer {
|
||||||
ides: configuredIdes,
|
ides: configuredIdes,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Quick update failed');
|
spinner.error('Quick update failed');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2251,12 +2251,12 @@ class Installer {
|
||||||
* @returns {Object} Compilation result
|
* @returns {Object} Compilation result
|
||||||
*/
|
*/
|
||||||
async compileAgents(config) {
|
async compileAgents(config) {
|
||||||
const ora = require('ora');
|
// Using @clack prompts
|
||||||
const chalk = require('chalk');
|
|
||||||
const { ModuleManager } = require('../modules/manager');
|
const { ModuleManager } = require('../modules/manager');
|
||||||
const { getSourcePath } = require('../../../lib/project-root');
|
const { getSourcePath } = require('../../../lib/project-root');
|
||||||
|
|
||||||
const spinner = ora('Recompiling agents with customizations...').start();
|
const spinner = await prompts.spinner();
|
||||||
|
spinner.start('Recompiling agents with customizations...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const projectDir = path.resolve(config.directory);
|
const projectDir = path.resolve(config.directory);
|
||||||
|
|
@ -2264,7 +2264,7 @@ class Installer {
|
||||||
|
|
||||||
// Check if bmad directory exists
|
// Check if bmad directory exists
|
||||||
if (!(await fs.pathExists(bmadDir))) {
|
if (!(await fs.pathExists(bmadDir))) {
|
||||||
spinner.fail('No BMAD installation found');
|
spinner.stop('No BMAD installation found');
|
||||||
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2306,7 +2306,7 @@ class Installer {
|
||||||
|
|
||||||
// Process each installed module
|
// Process each installed module
|
||||||
for (const moduleId of installedModules) {
|
for (const moduleId of installedModules) {
|
||||||
spinner.text = `Recompiling agents in ${moduleId}...`;
|
spinner.message(`Recompiling agents in ${moduleId}...`);
|
||||||
|
|
||||||
// Get source path
|
// Get source path
|
||||||
let sourcePath;
|
let sourcePath;
|
||||||
|
|
@ -2322,7 +2322,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sourcePath) {
|
if (!sourcePath) {
|
||||||
console.log(chalk.yellow(` Warning: Source not found for module ${moduleId}, skipping...`));
|
await prompts.log.warn(`Source not found for module ${moduleId}, skipping...`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2340,7 +2340,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed('Agent recompilation complete!');
|
spinner.stop('Agent recompilation complete!');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -2348,7 +2348,7 @@ class Installer {
|
||||||
modules: installedModules,
|
modules: installedModules,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Agent recompilation failed');
|
spinner.error('Agent recompilation failed');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2370,19 +2370,14 @@ class Installer {
|
||||||
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
||||||
*/
|
*/
|
||||||
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
||||||
console.log('');
|
await prompts.note(
|
||||||
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
'Found .bmad-method folder from BMAD v4 installation.\n\n' +
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
'Before continuing with installation, we recommend:\n' +
|
||||||
console.log(chalk.yellow('Found .bmad-method folder from BMAD v4 installation.'));
|
' 1. Remove the .bmad-method folder, OR\n' +
|
||||||
console.log('');
|
' 2. Back it up by renaming it to another name (e.g., bmad-method-backup)\n\n' +
|
||||||
|
'If your v4 installation set up rules or commands, you should remove those as well.',
|
||||||
console.log(chalk.dim('Before continuing with installation, we recommend:'));
|
'Legacy BMAD v4 detected',
|
||||||
console.log(chalk.dim(' 1. Remove the .bmad-method folder, OR'));
|
);
|
||||||
console.log(chalk.dim(' 2. Back it up by renaming it to another name (e.g., bmad-method-backup)'));
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
console.log(chalk.dim('If your v4 installation set up rules or commands, you should remove those as well.'));
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const proceed = await prompts.select({
|
const proceed = await prompts.select({
|
||||||
message: 'What would you like to do?',
|
message: 'What would you like to do?',
|
||||||
|
|
@ -2402,16 +2397,11 @@ class Installer {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (proceed === 'exit') {
|
if (proceed === 'exit') {
|
||||||
console.log('');
|
await prompts.log.info('Please remove the .bmad-method folder and any v4 rules/commands, then run the installer again.');
|
||||||
console.log(chalk.cyan('Please remove the .bmad-method folder and any v4 rules/commands,'));
|
|
||||||
console.log(chalk.cyan('then run the installer again.'));
|
|
||||||
console.log('');
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
await prompts.log.warn('Proceeding with installation despite legacy v4 folder');
|
||||||
console.log(chalk.yellow('⚠️ Proceeding with installation despite legacy v4 folder'));
|
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2465,7 +2455,7 @@ class Installer {
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Warning: Could not read files-manifest.csv:', error.message);
|
await prompts.log.warn('Could not read files-manifest.csv: ' + error.message);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2637,22 +2627,16 @@ class Installer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any spinner for interactive prompts
|
await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
|
||||||
const currentSpinner = ora();
|
|
||||||
if (currentSpinner.isSpinning) {
|
|
||||||
currentSpinner.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
|
||||||
|
|
||||||
let keptCount = 0;
|
let keptCount = 0;
|
||||||
let updatedCount = 0;
|
let updatedCount = 0;
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
|
|
||||||
for (const missing of customModulesWithMissingSources) {
|
for (const missing of customModulesWithMissingSources) {
|
||||||
console.log(chalk.dim(` • ${missing.name} (${missing.id})`));
|
await prompts.log.message(
|
||||||
console.log(chalk.dim(` Original source: ${missing.relativePath}`));
|
`${missing.name} (${missing.id})\n Original source: ${missing.relativePath}\n Full path: ${missing.sourcePath}`,
|
||||||
console.log(chalk.dim(` Full path: ${missing.sourcePath}`));
|
);
|
||||||
|
|
||||||
const choices = [
|
const choices = [
|
||||||
{
|
{
|
||||||
|
|
@ -2722,26 +2706,27 @@ class Installer {
|
||||||
});
|
});
|
||||||
|
|
||||||
updatedCount++;
|
updatedCount++;
|
||||||
console.log(chalk.green(`✓ Updated source location`));
|
await prompts.log.success('Updated source location');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
// Extra confirmation for destructive remove
|
// Extra confirmation for destructive remove
|
||||||
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
await prompts.log.error(
|
||||||
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
|
`WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!\n Module location: ${path.join(bmadDir, missing.id)}`,
|
||||||
|
);
|
||||||
|
|
||||||
const confirmDelete = await prompts.confirm({
|
const confirmDelete = await prompts.confirm({
|
||||||
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
message: 'Are you absolutely sure you want to delete this module?',
|
||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
const typedConfirm = await prompts.text({
|
const typedConfirm = await prompts.text({
|
||||||
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
message: 'Type "DELETE" to confirm permanent deletion:',
|
||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
if (input !== 'DELETE') {
|
if (input !== 'DELETE') {
|
||||||
return chalk.red('You must type "DELETE" exactly to proceed');
|
return 'You must type "DELETE" exactly to proceed';
|
||||||
}
|
}
|
||||||
return; // clack expects undefined for valid input
|
return; // clack expects undefined for valid input
|
||||||
},
|
},
|
||||||
|
|
@ -2753,12 +2738,12 @@ class Installer {
|
||||||
if (await fs.pathExists(modulePath)) {
|
if (await fs.pathExists(modulePath)) {
|
||||||
const fsExtra = require('fs-extra');
|
const fsExtra = require('fs-extra');
|
||||||
await fsExtra.remove(modulePath);
|
await fsExtra.remove(modulePath);
|
||||||
console.log(chalk.yellow(` ✓ Deleted module directory: ${path.relative(projectRoot, modulePath)}`));
|
await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.manifest.removeModule(bmadDir, missing.id);
|
await this.manifest.removeModule(bmadDir, missing.id);
|
||||||
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
||||||
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
await prompts.log.warn('Removed from manifest');
|
||||||
|
|
||||||
// Also remove from installedModules list
|
// Also remove from installedModules list
|
||||||
if (installedModules && installedModules.includes(missing.id)) {
|
if (installedModules && installedModules.includes(missing.id)) {
|
||||||
|
|
@ -2769,13 +2754,13 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
removedCount++;
|
removedCount++;
|
||||||
console.log(chalk.red.bold(`✓ "${missing.name}" has been permanently removed`));
|
await prompts.log.error(`"${missing.name}" has been permanently removed`);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
await prompts.log.message('Removal cancelled - module will be kept');
|
||||||
keptCount++;
|
keptCount++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
await prompts.log.message('Removal cancelled - module will be kept');
|
||||||
keptCount++;
|
keptCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2784,7 +2769,7 @@ class Installer {
|
||||||
case 'keep': {
|
case 'keep': {
|
||||||
keptCount++;
|
keptCount++;
|
||||||
keptModulesWithoutSources.push(missing.id);
|
keptModulesWithoutSources.push(missing.id);
|
||||||
console.log(chalk.dim(` Module will be kept as-is`));
|
await prompts.log.message('Module will be kept as-is');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -2794,10 +2779,11 @@ class Installer {
|
||||||
|
|
||||||
// Show summary
|
// Show summary
|
||||||
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
||||||
console.log(chalk.dim(`\nSummary for custom modules with missing sources:`));
|
let summary = 'Summary for custom modules with missing sources:';
|
||||||
if (keptCount > 0) console.log(chalk.dim(` • ${keptCount} module(s) kept as-is`));
|
if (keptCount > 0) summary += `\n • ${keptCount} module(s) kept as-is`;
|
||||||
if (updatedCount > 0) console.log(chalk.dim(` • ${updatedCount} module(s) updated with new sources`));
|
if (updatedCount > 0) summary += `\n • ${updatedCount} module(s) updated with new sources`;
|
||||||
if (removedCount > 0) console.log(chalk.red(` • ${removedCount} module(s) permanently deleted`));
|
if (removedCount > 0) summary += `\n • ${removedCount} module(s) permanently deleted`;
|
||||||
|
await prompts.log.message(summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
const { FileOps } = require('../../../lib/file-ops');
|
const { FileOps } = require('../../../lib/file-ops');
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ class CustomHandler {
|
||||||
try {
|
try {
|
||||||
config = yaml.parse(configContent);
|
config = yaml.parse(configContent);
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message));
|
await prompts.log.warn('YAML parse error in ' + configPath + ': ' + parseError.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ class CustomHandler {
|
||||||
isInstallConfig: isInstallConfig, // Track which type this is
|
isInstallConfig: isInstallConfig, // Track which type this is
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`Warning: Failed to read ${configPath}:`, error.message));
|
await prompts.log.warn('Failed to read ' + configPath + ': ' + error.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -322,7 +322,7 @@ class CustomHandler {
|
||||||
await fs.writeFile(customizePath, templateContent, 'utf8');
|
await fs.writeFile(customizePath, templateContent, 'utf8');
|
||||||
// Only show customize creation in verbose mode
|
// Only show customize creation in verbose mode
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
|
await prompts.log.message(' Created customize: custom-' + agentName + '.customize.yaml');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,14 +346,10 @@ class CustomHandler {
|
||||||
|
|
||||||
// Only show compilation details in verbose mode
|
// Only show compilation details in verbose mode
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(
|
await prompts.log.message(' Compiled agent: ' + agentName + ' -> ' + path.relative(targetAgentsPath, targetMdPath));
|
||||||
chalk.dim(
|
|
||||||
` Compiled agent: ${agentName} -> ${path.relative(targetAgentsPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
await prompts.log.warn(' Failed to compile agent ' + agentName + ': ' + error.message);
|
||||||
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
|
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
const { getSourcePath } = require('../../../lib/project-root');
|
const { getSourcePath } = require('../../../lib/project-root');
|
||||||
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ class BaseIdeSetup {
|
||||||
const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME);
|
const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME);
|
||||||
if (await fs.pathExists(bmadRulesPath)) {
|
if (await fs.pathExists(bmadRulesPath)) {
|
||||||
await fs.remove(bmadRulesPath);
|
await fs.remove(bmadRulesPath);
|
||||||
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
|
await prompts.log.message(`Removed ${this.name} BMAD configuration`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||||
|
|
@ -34,7 +34,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
* @returns {Promise<Object>} Setup result
|
* @returns {Promise<Object>} Setup result
|
||||||
*/
|
*/
|
||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
await prompts.log.info(`Setting up ${this.name}...`);
|
||||||
|
|
||||||
// Clean up any old BMAD installation first
|
// Clean up any old BMAD installation first
|
||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
@ -102,7 +102,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
results.tools = taskToolResult.tools || 0;
|
results.tools = taskToolResult.tools || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.printSummary(results, target_dir);
|
await this.printSummary(results, target_dir);
|
||||||
return { success: true, results };
|
return { success: true, results };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,18 +439,13 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
* @param {Object} results - Installation results
|
* @param {Object} results - Installation results
|
||||||
* @param {string} targetDir - Target directory (relative)
|
* @param {string} targetDir - Target directory (relative)
|
||||||
*/
|
*/
|
||||||
printSummary(results, targetDir) {
|
async printSummary(results, targetDir) {
|
||||||
console.log(chalk.green(`\n✓ ${this.name} configured:`));
|
const parts = [];
|
||||||
if (results.agents > 0) {
|
if (results.agents > 0) parts.push(`${results.agents} agents`);
|
||||||
console.log(chalk.dim(` - ${results.agents} agents installed`));
|
if (results.workflows > 0) parts.push(`${results.workflows} workflows`);
|
||||||
}
|
if (results.tasks > 0) parts.push(`${results.tasks} tasks`);
|
||||||
if (results.workflows > 0) {
|
if (results.tools > 0) parts.push(`${results.tools} tools`);
|
||||||
console.log(chalk.dim(` - ${results.workflows} workflow commands generated`));
|
await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
|
||||||
}
|
|
||||||
if (results.tasks > 0 || results.tools > 0) {
|
|
||||||
console.log(chalk.dim(` - ${results.tasks + results.tools} task/tool commands generated`));
|
|
||||||
}
|
|
||||||
console.log(chalk.dim(` - Destination: ${targetDir}`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -514,7 +509,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removedCount > 0) {
|
if (removedCount > 0) {
|
||||||
console.log(chalk.dim(` Cleaned ${removedCount} BMAD files from ${targetDir}`));
|
await prompts.log.message(` Cleaned ${removedCount} BMAD files from ${targetDir}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
|
|
@ -44,11 +43,10 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display detailed instructions for the chosen option
|
// Display detailed instructions for the chosen option
|
||||||
console.log('');
|
|
||||||
if (installLocation === 'project') {
|
if (installLocation === 'project') {
|
||||||
console.log(this.getProjectSpecificInstructions());
|
await prompts.note(this.getProjectSpecificInstructions(), 'Codex Project Installation');
|
||||||
} else {
|
} else {
|
||||||
console.log(this.getGlobalInstructions());
|
await prompts.note(this.getGlobalInstructions(), 'Codex Global Installation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the choice
|
// Confirm the choice
|
||||||
|
|
@ -58,7 +56,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
|
await prompts.log.warn("Let's choose a different installation option.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +70,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
* @param {Object} options - Setup options
|
* @param {Object} options - Setup options
|
||||||
*/
|
*/
|
||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
await prompts.log.info(`Setting up ${this.name}...`);
|
||||||
|
|
||||||
// Always use CLI mode
|
// Always use CLI mode
|
||||||
const mode = 'cli';
|
const mode = 'cli';
|
||||||
|
|
@ -124,16 +122,9 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
const written = agentCount + workflowCount + tasksWritten;
|
const written = agentCount + workflowCount + tasksWritten;
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
await prompts.log.success(
|
||||||
console.log(chalk.dim(` - Mode: CLI`));
|
`${this.name} configured: ${counts.agents} agents, ${counts.workflows} workflows, ${counts.tasks} tasks, ${written} files → ${destDir}`,
|
||||||
console.log(chalk.dim(` - ${counts.agents} agents exported`));
|
);
|
||||||
console.log(chalk.dim(` - ${counts.tasks} tasks exported`));
|
|
||||||
console.log(chalk.dim(` - ${counts.workflows} workflow commands exported`));
|
|
||||||
if (counts.workflowLaunchers > 0) {
|
|
||||||
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers exported`));
|
|
||||||
}
|
|
||||||
console.log(chalk.dim(` - ${written} Codex prompt files written`));
|
|
||||||
console.log(chalk.dim(` - Destination: ${destDir}`));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -272,7 +263,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
entries = await fs.readdir(destDir);
|
entries = await fs.readdir(destDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Directory exists but can't be read - skip cleanup
|
// Directory exists but can't be read - skip cleanup
|
||||||
console.warn(chalk.yellow(`Warning: Could not read directory ${destDir}: ${error.message}`));
|
await prompts.log.warn(`Warning: Could not read directory ${destDir}: ${error.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,7 +290,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Skip files that can't be processed
|
// Skip files that can't be processed
|
||||||
console.warn(chalk.dim(` Skipping ${entry}: ${error.message}`));
|
await prompts.log.message(` Skipping ${entry}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,22 +306,16 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
*/
|
*/
|
||||||
getGlobalInstructions(destDir) {
|
getGlobalInstructions(destDir) {
|
||||||
const lines = [
|
const lines = [
|
||||||
|
'IMPORTANT: Codex Configuration',
|
||||||
'',
|
'',
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
'/prompts installed globally to your HOME DIRECTORY.',
|
||||||
chalk.bold.yellow(' IMPORTANT: Codex Configuration'),
|
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
|
||||||
'',
|
'',
|
||||||
chalk.white(' /prompts installed globally to your HOME DIRECTORY.'),
|
'These prompts reference a specific _bmad path.',
|
||||||
'',
|
"To use with other projects, you'd need to copy the _bmad dir.",
|
||||||
chalk.yellow(' ⚠️ These prompts reference a specific _bmad path'),
|
|
||||||
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
|
|
||||||
'',
|
|
||||||
chalk.green(' ✓ You can now use /commands in Codex CLI'),
|
|
||||||
chalk.dim(' Example: /bmad_bmm_pm'),
|
|
||||||
chalk.dim(' Type / to see all available commands'),
|
|
||||||
'',
|
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
|
||||||
'',
|
'',
|
||||||
|
'You can now use /commands in Codex CLI',
|
||||||
|
' Example: /bmad_bmm_pm',
|
||||||
|
' Type / to see all available commands',
|
||||||
];
|
];
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
@ -345,43 +330,34 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
const isWindows = os.platform() === 'win32';
|
const isWindows = os.platform() === 'win32';
|
||||||
|
|
||||||
const commonLines = [
|
const commonLines = [
|
||||||
|
'Project-Specific Codex Configuration',
|
||||||
'',
|
'',
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
`Prompts will be installed to: ${destDir || '<project>/.codex/prompts'}`,
|
||||||
chalk.bold.yellow(' Project-Specific Codex Configuration'),
|
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
|
||||||
'',
|
'',
|
||||||
chalk.white(' Prompts will be installed to: ') + chalk.cyan(destDir || '<project>/.codex/prompts'),
|
'REQUIRED: You must set CODEX_HOME to use these prompts',
|
||||||
'',
|
|
||||||
chalk.bold.yellow(' ⚠️ REQUIRED: You must set CODEX_HOME to use these prompts'),
|
|
||||||
'',
|
'',
|
||||||
];
|
];
|
||||||
|
|
||||||
const windowsLines = [
|
const windowsLines = [
|
||||||
chalk.bold(' Create a codex.cmd file in your project root:'),
|
'Create a codex.cmd file in your project root:',
|
||||||
'',
|
'',
|
||||||
chalk.green(' @echo off'),
|
' @echo off',
|
||||||
chalk.green(' set CODEX_HOME=%~dp0.codex'),
|
' set CODEX_HOME=%~dp0.codex',
|
||||||
chalk.green(' codex %*'),
|
' codex %*',
|
||||||
'',
|
'',
|
||||||
chalk.dim(String.raw` Then run: .\codex instead of codex`),
|
String.raw`Then run: .\codex instead of codex`,
|
||||||
chalk.dim(' (The %~dp0 gets the directory of the .cmd file)'),
|
'(The %~dp0 gets the directory of the .cmd file)',
|
||||||
];
|
];
|
||||||
|
|
||||||
const unixLines = [
|
const unixLines = [
|
||||||
chalk.bold(' Add this alias to your ~/.bashrc or ~/.zshrc:'),
|
'Add this alias to your ~/.bashrc or ~/.zshrc:',
|
||||||
'',
|
'',
|
||||||
chalk.green(' alias codex=\'CODEX_HOME="$PWD/.codex" codex\''),
|
' alias codex=\'CODEX_HOME="$PWD/.codex" codex\'',
|
||||||
'',
|
|
||||||
chalk.dim(' After adding, run: source ~/.bashrc (or source ~/.zshrc)'),
|
|
||||||
chalk.dim(' (The $PWD uses your current working directory)'),
|
|
||||||
];
|
|
||||||
const closingLines = [
|
|
||||||
'',
|
|
||||||
chalk.dim(' This tells Codex CLI to use prompts from this project instead of ~/.codex'),
|
|
||||||
'',
|
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
|
||||||
'',
|
'',
|
||||||
|
'After adding, run: source ~/.bashrc (or source ~/.zshrc)',
|
||||||
|
'(The $PWD uses your current working directory)',
|
||||||
];
|
];
|
||||||
|
const closingLines = ['', 'This tells Codex CLI to use prompts from this project instead of ~/.codex'];
|
||||||
|
|
||||||
const lines = [...commonLines, ...(isWindows ? windowsLines : unixLines), ...closingLines];
|
const lines = [...commonLines, ...(isWindows ? windowsLines : unixLines), ...closingLines];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||||
|
|
@ -23,7 +23,7 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
* @param {Object} options - Setup options
|
* @param {Object} options - Setup options
|
||||||
*/
|
*/
|
||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
await prompts.log.info(`Setting up ${this.name}...`);
|
||||||
|
|
||||||
// Clean up any old BMAD installation first
|
// Clean up any old BMAD installation first
|
||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
@ -38,7 +38,7 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
config = yaml.parse(existingContent) || {};
|
config = yaml.parse(existingContent) || {};
|
||||||
} catch {
|
} catch {
|
||||||
// If parsing fails, start fresh but warn user
|
// If parsing fails, start fresh but warn user
|
||||||
console.log(chalk.yellow('Warning: Could not parse existing .kilocodemodes, starting fresh'));
|
await prompts.log.warn('Warning: Could not parse existing .kilocodemodes, starting fresh');
|
||||||
config = {};
|
config = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,14 +88,9 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
const taskCount = taskToolCounts.tasks || 0;
|
const taskCount = taskToolCounts.tasks || 0;
|
||||||
const toolCount = taskToolCounts.tools || 0;
|
const toolCount = taskToolCounts.tools || 0;
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
await prompts.log.success(
|
||||||
console.log(chalk.dim(` - ${addedCount} modes added`));
|
`${this.name} configured: ${addedCount} modes, ${workflowCount} workflows, ${taskCount} tasks, ${toolCount} tools → ${this.configFile}`,
|
||||||
console.log(chalk.dim(` - ${workflowCount} workflows exported`));
|
);
|
||||||
console.log(chalk.dim(` - ${taskCount} tasks exported`));
|
|
||||||
console.log(chalk.dim(` - ${toolCount} tools exported`));
|
|
||||||
console.log(chalk.dim(` - Configuration file: ${this.configFile}`));
|
|
||||||
console.log(chalk.dim(` - Workflows directory: .kilocode/workflows/`));
|
|
||||||
console.log(chalk.dim('\n Modes will be available when you open this project in KiloCode'));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -192,12 +187,12 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
if (removedCount > 0) {
|
if (removedCount > 0) {
|
||||||
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
|
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
|
||||||
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .kilocodemodes`));
|
await prompts.log.message(`Removed ${removedCount} BMAD modes from .kilocodemodes`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// If parsing fails, leave file as-is
|
// If parsing fails, leave file as-is
|
||||||
console.log(chalk.yellow('Warning: Could not parse .kilocodemodes for cleanup'));
|
await prompts.log.warn('Warning: Could not parse .kilocodemodes for cleanup');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,7 +29,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
||||||
await fs.remove(path.join(bmadAgentsDir, file));
|
await fs.remove(path.join(bmadAgentsDir, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(chalk.dim(` Cleaned old BMAD agents from ${this.name}`));
|
await prompts.log.message(` Cleaned old BMAD agents from ${this.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
||||||
* @param {Object} options - Setup options
|
* @param {Object} options - Setup options
|
||||||
*/
|
*/
|
||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
await prompts.log.info(`Setting up ${this.name}...`);
|
||||||
|
|
||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
||||||
// Create BMad agents from source YAML files
|
// Create BMad agents from source YAML files
|
||||||
await this.createBmadAgentsFromSource(agentsDir, projectDir);
|
await this.createBmadAgentsFromSource(agentsDir, projectDir);
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured with BMad agents`));
|
await prompts.log.success(`${this.name} configured with BMad agents`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,7 +70,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
||||||
try {
|
try {
|
||||||
await this.processAgentFile(agentFile, agentsDir, projectDir);
|
await this.processAgentFile(agentFile, agentsDir, projectDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`⚠️ Failed to process ${agentFile}: ${error.message}`));
|
await prompts.log.warn(`Failed to process ${agentFile}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IDE Manager - handles IDE-specific setup
|
* IDE Manager - handles IDE-specific setup
|
||||||
|
|
@ -49,7 +49,7 @@ class IdeManager {
|
||||||
*/
|
*/
|
||||||
async loadHandlers() {
|
async loadHandlers() {
|
||||||
// Load custom installer files
|
// Load custom installer files
|
||||||
this.loadCustomInstallerFiles();
|
await this.loadCustomInstallerFiles();
|
||||||
|
|
||||||
// Load config-driven handlers from platform-codes.yaml
|
// Load config-driven handlers from platform-codes.yaml
|
||||||
await this.loadConfigDrivenHandlers();
|
await this.loadConfigDrivenHandlers();
|
||||||
|
|
@ -59,7 +59,7 @@ class IdeManager {
|
||||||
* Load custom installer files (unique installation logic)
|
* Load custom installer files (unique installation logic)
|
||||||
* These files have special installation patterns that don't fit the config-driven model
|
* These files have special installation patterns that don't fit the config-driven model
|
||||||
*/
|
*/
|
||||||
loadCustomInstallerFiles() {
|
async loadCustomInstallerFiles() {
|
||||||
const ideDir = __dirname;
|
const ideDir = __dirname;
|
||||||
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
|
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ class IdeManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.yellow(` Warning: Could not load ${file}: ${error.message}`));
|
await prompts.log.warn(`Warning: Could not load ${file}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,8 +171,8 @@ class IdeManager {
|
||||||
const handler = this.handlers.get(ideName.toLowerCase());
|
const handler = this.handlers.get(ideName.toLowerCase());
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.warn(chalk.yellow(`⚠️ IDE '${ideName}' is not yet supported`));
|
await prompts.log.warn(`IDE '${ideName}' is not yet supported`);
|
||||||
console.log(chalk.dim('Supported IDEs:', [...this.handlers.keys()].join(', ')));
|
await prompts.log.message(`Supported IDEs: ${[...this.handlers.keys()].join(', ')}`);
|
||||||
return { success: false, reason: 'unsupported' };
|
return { success: false, reason: 'unsupported' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ class IdeManager {
|
||||||
await handler.setup(projectDir, bmadDir, options);
|
await handler.setup(projectDir, bmadDir, options);
|
||||||
return { success: true, ide: ideName };
|
return { success: true, ide: ideName };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(chalk.red(`Failed to setup ${ideName}:`), error.message);
|
await prompts.log.error(`Failed to setup ${ideName}: ${error.message}`);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +254,7 @@ class IdeManager {
|
||||||
const handler = this.handlers.get(ideName.toLowerCase());
|
const handler = this.handlers.get(ideName.toLowerCase());
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.warn(chalk.yellow(`⚠️ IDE '${ideName}' is not yet supported for custom agent installation`));
|
await prompts.log.warn(`IDE '${ideName}' is not yet supported for custom agent installation`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ class IdeManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`⚠️ Failed to install ${ideName} launcher: ${error.message}`));
|
await prompts.log.warn(`Failed to install ${ideName} launcher: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const chalk = require('chalk');
|
const prompts = require('../../../../lib/prompts');
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,7 +22,7 @@ class WorkflowCommandGenerator {
|
||||||
const workflows = await this.loadWorkflowManifest(bmadDir);
|
const workflows = await this.loadWorkflowManifest(bmadDir);
|
||||||
|
|
||||||
if (!workflows) {
|
if (!workflows) {
|
||||||
console.log(chalk.yellow('Workflow manifest not found. Skipping command generation.'));
|
await prompts.log.warn('Workflow manifest not found. Skipping command generation.');
|
||||||
return { generated: 0 };
|
return { generated: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const chalk = require('chalk');
|
const prompts = require('../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and display installer messages from messages.yaml
|
* Load and display installer messages from messages.yaml
|
||||||
|
|
@ -51,22 +51,20 @@ class MessageLoader {
|
||||||
/**
|
/**
|
||||||
* Display the start message (after logo, before prompts)
|
* Display the start message (after logo, before prompts)
|
||||||
*/
|
*/
|
||||||
displayStartMessage() {
|
async displayStartMessage() {
|
||||||
const message = this.getStartMessage();
|
const message = this.getStartMessage();
|
||||||
if (message) {
|
if (message) {
|
||||||
console.log(chalk.cyan(message));
|
await prompts.log.info(message);
|
||||||
console.log();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the end message (after installation completes)
|
* Display the end message (after installation completes)
|
||||||
*/
|
*/
|
||||||
displayEndMessage() {
|
async displayEndMessage() {
|
||||||
const message = this.getEndMessage();
|
const message = this.getEndMessage();
|
||||||
if (message) {
|
if (message) {
|
||||||
console.log();
|
await prompts.log.info(message);
|
||||||
console.log(chalk.cyan(message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const chalk = require('chalk');
|
const prompts = require('../../../lib/prompts');
|
||||||
const ora = require('ora');
|
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||||
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
||||||
|
|
@ -17,7 +16,7 @@ const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
* @class ModuleManager
|
* @class ModuleManager
|
||||||
* @requires fs-extra
|
* @requires fs-extra
|
||||||
* @requires yaml
|
* @requires yaml
|
||||||
* @requires chalk
|
* @requires prompts
|
||||||
* @requires XmlHandler
|
* @requires XmlHandler
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -152,26 +151,26 @@ class ModuleManager {
|
||||||
// File hasn't been modified by user, safe to update
|
// File hasn't been modified by user, safe to update
|
||||||
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Updated sidecar file: ${relativeToBmad}`));
|
await prompts.log.message(` Updated sidecar file: ${relativeToBmad}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// User has modified the file, preserve it
|
// User has modified the file, preserve it
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Preserving user-modified file: ${relativeToBmad}`));
|
await prompts.log.message(` Preserving user-modified file: ${relativeToBmad}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// First time seeing this file in manifest, copy it
|
// First time seeing this file in manifest, copy it
|
||||||
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Added new sidecar file: ${relativeToBmad}`));
|
await prompts.log.message(` Added new sidecar file: ${relativeToBmad}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// New installation
|
// New installation
|
||||||
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Copied sidecar file: ${relativeToBmad}`));
|
await prompts.log.message(` Copied sidecar file: ${relativeToBmad}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,7 +368,8 @@ class ModuleManager {
|
||||||
// Check if already cloned
|
// Check if already cloned
|
||||||
if (await fs.pathExists(moduleCacheDir)) {
|
if (await fs.pathExists(moduleCacheDir)) {
|
||||||
// Try to update if it's a git repo
|
// Try to update if it's a git repo
|
||||||
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
const fetchSpinner = await prompts.spinner();
|
||||||
|
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
||||||
try {
|
try {
|
||||||
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
// Fetch and reset to remote - works better with shallow clones than pull
|
// Fetch and reset to remote - works better with shallow clones than pull
|
||||||
|
|
@ -377,13 +377,13 @@ class ModuleManager {
|
||||||
execSync('git reset --hard origin/HEAD', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git reset --hard origin/HEAD', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
|
|
||||||
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
||||||
// Force dependency install if we got new code
|
// Force dependency install if we got new code
|
||||||
if (currentRef !== newRef) {
|
if (currentRef !== newRef) {
|
||||||
needsDependencyInstall = true;
|
needsDependencyInstall = true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
fetchSpinner.warn(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
||||||
// If update fails, remove and re-clone
|
// If update fails, remove and re-clone
|
||||||
await fs.remove(moduleCacheDir);
|
await fs.remove(moduleCacheDir);
|
||||||
wasNewClone = true;
|
wasNewClone = true;
|
||||||
|
|
@ -394,14 +394,15 @@ class ModuleManager {
|
||||||
|
|
||||||
// Clone if not exists or was removed
|
// Clone if not exists or was removed
|
||||||
if (wasNewClone) {
|
if (wasNewClone) {
|
||||||
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
const fetchSpinner = await prompts.spinner();
|
||||||
|
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
||||||
try {
|
try {
|
||||||
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fetchSpinner.fail(`Failed to fetch ${moduleInfo.name}`);
|
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
|
||||||
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -415,17 +416,18 @@ class ModuleManager {
|
||||||
|
|
||||||
// Force install if we updated or cloned new
|
// Force install if we updated or cloned new
|
||||||
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
||||||
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
const installSpinner = await prompts.spinner();
|
||||||
|
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
||||||
try {
|
try {
|
||||||
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
||||||
cwd: moduleCacheDir,
|
cwd: moduleCacheDir,
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
timeout: 120_000, // 2 minute timeout
|
timeout: 120_000, // 2 minute timeout
|
||||||
});
|
});
|
||||||
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
await prompts.log.warn(` Warning: ${error.message}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check if package.json is newer than node_modules
|
// Check if package.json is newer than node_modules
|
||||||
|
|
@ -440,17 +442,18 @@ class ModuleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packageJsonNewer) {
|
if (packageJsonNewer) {
|
||||||
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
const installSpinner = await prompts.spinner();
|
||||||
|
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
||||||
try {
|
try {
|
||||||
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
||||||
cwd: moduleCacheDir,
|
cwd: moduleCacheDir,
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
timeout: 120_000, // 2 minute timeout
|
timeout: 120_000, // 2 minute timeout
|
||||||
});
|
});
|
||||||
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
await prompts.log.warn(` Warning: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -514,14 +517,14 @@ class ModuleManager {
|
||||||
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
|
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
|
||||||
customConfig = yaml.parse(customContent);
|
customConfig = yaml.parse(customContent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
|
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
|
||||||
try {
|
try {
|
||||||
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
|
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
|
||||||
customConfig = yaml.parse(customContent);
|
customConfig = yaml.parse(customContent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -529,7 +532,7 @@ class ModuleManager {
|
||||||
if (customConfig) {
|
if (customConfig) {
|
||||||
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
|
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
|
||||||
if (options.logger) {
|
if (options.logger) {
|
||||||
options.logger.log(chalk.cyan(` Merged custom configuration for ${moduleName}`));
|
options.logger.log(` Merged custom configuration for ${moduleName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,7 +608,7 @@ class ModuleManager {
|
||||||
await this.syncModule(sourcePath, targetPath);
|
await this.syncModule(sourcePath, targetPath);
|
||||||
|
|
||||||
// Recompile agents (#1133)
|
// Recompile agents (#1133)
|
||||||
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
|
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir);
|
||||||
await this.processAgentFiles(targetPath, moduleName);
|
await this.processAgentFiles(targetPath, moduleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -735,7 +738,7 @@ class ModuleManager {
|
||||||
// Check for localskip="true" in the agent tag
|
// Check for localskip="true" in the agent tag
|
||||||
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
||||||
if (agentMatch) {
|
if (agentMatch) {
|
||||||
console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
|
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
|
||||||
continue; // Skip this agent
|
continue; // Skip this agent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -838,7 +841,7 @@ class ModuleManager {
|
||||||
await fs.writeFile(targetFile, strippedYaml, 'utf8');
|
await fs.writeFile(targetFile, strippedYaml, 'utf8');
|
||||||
} catch {
|
} catch {
|
||||||
// If anything fails, just copy the file as-is
|
// If anything fails, just copy the file as-is
|
||||||
console.warn(chalk.yellow(` Warning: Could not process ${path.basename(sourceFile)}, copying as-is`));
|
await prompts.log.warn(` Warning: Could not process ${path.basename(sourceFile)}, copying as-is`);
|
||||||
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -890,7 +893,7 @@ class ModuleManager {
|
||||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
||||||
// Only show customize creation in verbose mode
|
// Only show customize creation in verbose mode
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original hash for modification detection
|
// Store original hash for modification detection
|
||||||
|
|
@ -990,10 +993,10 @@ class ModuleManager {
|
||||||
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
|
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
|
||||||
|
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
|
||||||
console.log(chalk.dim(` Sidecar files processed: ${copiedFiles.length} files`));
|
await prompts.log.message(` Sidecar files processed: ${copiedFiles.length} files`);
|
||||||
}
|
}
|
||||||
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(chalk.yellow(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`));
|
await prompts.log.warn(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1012,14 +1015,12 @@ class ModuleManager {
|
||||||
|
|
||||||
// Only show compilation details in verbose mode
|
// Only show compilation details in verbose mode
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
console.log(
|
await prompts.log.message(
|
||||||
chalk.dim(
|
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
||||||
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
await prompts.log.warn(` Failed to compile agent ${agentName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1139,11 +1140,11 @@ class ModuleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workflowsVendored) {
|
if (!workflowsVendored) {
|
||||||
console.log(chalk.cyan(`\n Vendoring cross-module workflows for ${moduleName}...`));
|
await prompts.log.info(`\n Vendoring cross-module workflows for ${moduleName}...`);
|
||||||
workflowsVendored = true;
|
workflowsVendored = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.dim(` Processing: ${agentFile}`));
|
await prompts.log.message(` Processing: ${agentFile}`);
|
||||||
|
|
||||||
for (const item of workflowInstallItems) {
|
for (const item of workflowInstallItems) {
|
||||||
const sourceWorkflowPath = item.workflow; // Where to copy FROM
|
const sourceWorkflowPath = item.workflow; // Where to copy FROM
|
||||||
|
|
@ -1155,7 +1156,7 @@ class ModuleManager {
|
||||||
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||||
if (!sourceMatch) {
|
if (!sourceMatch) {
|
||||||
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
|
await prompts.log.warn(` Could not parse workflow path: ${sourceWorkflowPath}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1166,7 +1167,7 @@ class ModuleManager {
|
||||||
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
||||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||||
if (!installMatch) {
|
if (!installMatch) {
|
||||||
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
|
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1179,15 +1180,13 @@ class ModuleManager {
|
||||||
|
|
||||||
// Check if source workflow exists
|
// Check if source workflow exists
|
||||||
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
||||||
console.warn(chalk.yellow(` Source workflow not found: ${actualSourceWorkflowPath}`));
|
await prompts.log.warn(` Source workflow not found: ${actualSourceWorkflowPath}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the entire workflow folder
|
// Copy the entire workflow folder
|
||||||
console.log(
|
await prompts.log.message(
|
||||||
chalk.dim(
|
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
|
||||||
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
|
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
|
||||||
|
|
@ -1203,7 +1202,7 @@ class ModuleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflowsVendored) {
|
if (workflowsVendored) {
|
||||||
console.log(chalk.green(` ✓ Workflow vendoring complete\n`));
|
await prompts.log.success(` Workflow vendoring complete\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1225,7 +1224,7 @@ class ModuleManager {
|
||||||
|
|
||||||
if (updatedYaml !== yamlContent) {
|
if (updatedYaml !== yamlContent) {
|
||||||
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
||||||
console.log(chalk.dim(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`));
|
await prompts.log.message(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1280,11 +1279,11 @@ class ModuleManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
console.warn(chalk.yellow(`Module installer for ${moduleName} returned false`));
|
await prompts.log.warn(`Module installer for ${moduleName} returned false`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(chalk.red(`Error running module installer for ${moduleName}: ${error.message}`));
|
await prompts.log.error(`Error running module installer for ${moduleName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const readline = require('node:readline');
|
const prompts = require('../prompts');
|
||||||
const { compileAgent, compileAgentFile } = require('./compiler');
|
const { compileAgent, compileAgentFile } = require('./compiler');
|
||||||
const { extractInstallConfig, getDefaultValues } = require('./template-engine');
|
const { extractInstallConfig, getDefaultValues } = require('./template-engine');
|
||||||
|
|
||||||
|
|
@ -149,83 +149,47 @@ async function promptInstallQuestions(installConfig, defaults, presetAnswers = {
|
||||||
return { ...defaults, ...presetAnswers };
|
return { ...defaults, ...presetAnswers };
|
||||||
}
|
}
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout,
|
|
||||||
});
|
|
||||||
|
|
||||||
const question = (prompt) =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
rl.question(prompt, resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
const answers = { ...defaults, ...presetAnswers };
|
const answers = { ...defaults, ...presetAnswers };
|
||||||
|
|
||||||
console.log('\n📝 Agent Configuration\n');
|
await prompts.note(installConfig.description || '', 'Agent Configuration');
|
||||||
if (installConfig.description) {
|
|
||||||
console.log(` ${installConfig.description}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const q of installConfig.questions) {
|
for (const q of installConfig.questions) {
|
||||||
// Skip questions for variables that are already set (e.g., custom_name set upfront)
|
// Skip questions for variables that are already set (e.g., custom_name set upfront)
|
||||||
if (answers[q.var] !== undefined && answers[q.var] !== defaults[q.var]) {
|
if (answers[q.var] !== undefined && answers[q.var] !== defaults[q.var]) {
|
||||||
console.log(chalk.dim(` ${q.var}: ${answers[q.var]} (already set)`));
|
await prompts.log.message(` ${q.var}: ${answers[q.var]} (already set)`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let response;
|
|
||||||
|
|
||||||
switch (q.type) {
|
switch (q.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
const defaultHint = q.default ? ` (default: ${q.default})` : '';
|
const response = await prompts.text({
|
||||||
response = await question(` ${q.prompt}${defaultHint}: `);
|
message: q.prompt,
|
||||||
|
defaultValue: q.default || '',
|
||||||
|
});
|
||||||
answers[q.var] = response || q.default || '';
|
answers[q.var] = response || q.default || '';
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'boolean': {
|
case 'boolean': {
|
||||||
const defaultHint = q.default ? ' [Y/n]' : ' [y/N]';
|
const response = await prompts.confirm({
|
||||||
response = await question(` ${q.prompt}${defaultHint}: `);
|
message: q.prompt,
|
||||||
if (response === '') {
|
initialValue: q.default,
|
||||||
answers[q.var] = q.default;
|
});
|
||||||
} else {
|
answers[q.var] = response;
|
||||||
answers[q.var] = response.toLowerCase().startsWith('y');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'choice': {
|
case 'choice': {
|
||||||
console.log(` ${q.prompt}`);
|
const response = await prompts.select({
|
||||||
for (const [idx, opt] of q.options.entries()) {
|
message: q.prompt,
|
||||||
const marker = opt.value === q.default ? '* ' : ' ';
|
options: q.options.map((o) => ({ value: o.value, label: o.label })),
|
||||||
console.log(` ${marker}${idx + 1}. ${opt.label}`);
|
initialValue: q.default,
|
||||||
}
|
});
|
||||||
const defaultIdx = q.options.findIndex((o) => o.value === q.default) + 1;
|
answers[q.var] = response;
|
||||||
let validChoice = false;
|
|
||||||
let choiceIdx;
|
|
||||||
while (!validChoice) {
|
|
||||||
response = await question(` Choice (default: ${defaultIdx}): `);
|
|
||||||
if (response) {
|
|
||||||
choiceIdx = parseInt(response, 10) - 1;
|
|
||||||
if (isNaN(choiceIdx) || choiceIdx < 0 || choiceIdx >= q.options.length) {
|
|
||||||
console.log(` Invalid choice. Please enter 1-${q.options.length}`);
|
|
||||||
} else {
|
|
||||||
validChoice = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
choiceIdx = defaultIdx - 1;
|
|
||||||
validChoice = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
answers[q.var] = q.options[choiceIdx].value;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// No default
|
// No default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.close();
|
|
||||||
return answers;
|
return answers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
const chalk = require('chalk');
|
|
||||||
const boxen = require('boxen');
|
|
||||||
const wrapAnsi = require('wrap-ansi');
|
|
||||||
const figlet = require('figlet');
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
|
const prompts = require('./prompts');
|
||||||
|
|
||||||
const CLIUtils = {
|
const CLIUtils = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,27 +16,32 @@ const CLIUtils = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display BMAD logo
|
* Display BMAD logo using @clack intro + box
|
||||||
* @param {boolean} clearScreen - Whether to clear the screen first (default: true for initial display only)
|
* @param {boolean} _clearScreen - Deprecated, ignored (no longer clears screen)
|
||||||
*/
|
*/
|
||||||
displayLogo(clearScreen = true) {
|
async displayLogo(_clearScreen = true) {
|
||||||
if (clearScreen) {
|
|
||||||
console.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = this.getVersion();
|
const version = this.getVersion();
|
||||||
|
const color = await prompts.getColor();
|
||||||
|
|
||||||
// ASCII art logo
|
// ASCII art logo
|
||||||
const logo = `
|
const logo = [
|
||||||
██████╗ ███╗ ███╗ █████╗ ██████╗ ™
|
' ██████╗ ███╗ ███╗ █████╗ ██████╗ ™',
|
||||||
██╔══██╗████╗ ████║██╔══██╗██╔══██╗
|
' ██╔══██╗████╗ ████║██╔══██╗██╔══██╗',
|
||||||
██████╔╝██╔████╔██║███████║██║ ██║
|
' ██████╔╝██╔████╔██║███████║██║ ██║',
|
||||||
██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║
|
' ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║',
|
||||||
██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝
|
' ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝',
|
||||||
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝`;
|
' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝',
|
||||||
|
]
|
||||||
|
.map((line) => color.yellow(line))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
console.log(chalk.cyan(logo));
|
const tagline = ' Build More, Architect Dreams';
|
||||||
console.log(chalk.dim(` Build More, Architect Dreams`) + chalk.cyan.bold(` v${version}`) + '\n');
|
|
||||||
|
await prompts.box(`${logo}\n${tagline}`, `v${version}`, {
|
||||||
|
contentAlign: 'center',
|
||||||
|
rounded: true,
|
||||||
|
formatBorder: color.blue,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,13 +49,8 @@ const CLIUtils = {
|
||||||
* @param {string} title - Section title
|
* @param {string} title - Section title
|
||||||
* @param {string} subtitle - Optional subtitle
|
* @param {string} subtitle - Optional subtitle
|
||||||
*/
|
*/
|
||||||
displaySection(title, subtitle = null) {
|
async displaySection(title, subtitle = null) {
|
||||||
console.log('\n' + chalk.cyan('═'.repeat(80)));
|
await prompts.note(subtitle || '', title);
|
||||||
console.log(chalk.cyan.bold(` ${title}`));
|
|
||||||
if (subtitle) {
|
|
||||||
console.log(chalk.dim(` ${subtitle}`));
|
|
||||||
}
|
|
||||||
console.log(chalk.cyan('═'.repeat(80)) + '\n');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,25 +58,21 @@ const CLIUtils = {
|
||||||
* @param {string|Array} content - Content to display
|
* @param {string|Array} content - Content to display
|
||||||
* @param {Object} options - Box options
|
* @param {Object} options - Box options
|
||||||
*/
|
*/
|
||||||
displayBox(content, options = {}) {
|
async displayBox(content, options = {}) {
|
||||||
const defaultOptions = {
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'cyan',
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle array content
|
|
||||||
let text = content;
|
let text = content;
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
text = content.join('\n\n');
|
text = content.join('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap text to prevent overflow
|
const color = await prompts.getColor();
|
||||||
const wrapped = wrapAnsi(text, 76, { hard: true, wordWrap: true });
|
const borderColor = options.borderColor || 'cyan';
|
||||||
|
const formatBorder =
|
||||||
|
borderColor === 'green' ? color.green : borderColor === 'red' ? color.red : borderColor === 'yellow' ? color.yellow : color.cyan;
|
||||||
|
|
||||||
console.log(boxen(wrapped, defaultOptions));
|
await prompts.box(text, options.title, {
|
||||||
|
rounded: options.borderStyle === 'round' || options.borderStyle === undefined,
|
||||||
|
formatBorder,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,14 +81,9 @@ const CLIUtils = {
|
||||||
* @param {string} header - Custom header from module.yaml
|
* @param {string} header - Custom header from module.yaml
|
||||||
* @param {string} subheader - Custom subheader from module.yaml
|
* @param {string} subheader - Custom subheader from module.yaml
|
||||||
*/
|
*/
|
||||||
displayModuleConfigHeader(moduleName, header = null, subheader = null) {
|
async displayModuleConfigHeader(moduleName, header = null, subheader = null) {
|
||||||
// Simple blue banner with custom header/subheader if provided
|
const title = header || `Configuring ${moduleName.toUpperCase()} Module`;
|
||||||
console.log('\n' + chalk.cyan('─'.repeat(80)));
|
await prompts.note(subheader || '', title);
|
||||||
console.log(chalk.cyan(header || `Configuring ${moduleName.toUpperCase()} Module`));
|
|
||||||
if (subheader) {
|
|
||||||
console.log(chalk.dim(`${subheader}`));
|
|
||||||
}
|
|
||||||
console.log(chalk.cyan('─'.repeat(80)) + '\n');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -104,14 +92,9 @@ const CLIUtils = {
|
||||||
* @param {string} header - Custom header from module.yaml
|
* @param {string} header - Custom header from module.yaml
|
||||||
* @param {string} subheader - Custom subheader from module.yaml
|
* @param {string} subheader - Custom subheader from module.yaml
|
||||||
*/
|
*/
|
||||||
displayModuleNoConfig(moduleName, header = null, subheader = null) {
|
async displayModuleNoConfig(moduleName, header = null, subheader = null) {
|
||||||
// Show full banner with header/subheader, just like modules with config
|
const title = header || `${moduleName.toUpperCase()} Module - No Custom Configuration`;
|
||||||
console.log('\n' + chalk.cyan('─'.repeat(80)));
|
await prompts.note(subheader || '', title);
|
||||||
console.log(chalk.cyan(header || `${moduleName.toUpperCase()} Module - No Custom Configuration`));
|
|
||||||
if (subheader) {
|
|
||||||
console.log(chalk.dim(`${subheader}`));
|
|
||||||
}
|
|
||||||
console.log(chalk.cyan('─'.repeat(80)) + '\n');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -120,42 +103,33 @@ const CLIUtils = {
|
||||||
* @param {number} total - Total steps
|
* @param {number} total - Total steps
|
||||||
* @param {string} description - Step description
|
* @param {string} description - Step description
|
||||||
*/
|
*/
|
||||||
displayStep(current, total, description) {
|
async displayStep(current, total, description) {
|
||||||
const progress = `[${current}/${total}]`;
|
const progress = `[${current}/${total}]`;
|
||||||
console.log('\n' + chalk.cyan(progress) + ' ' + chalk.bold(description));
|
await prompts.log.step(`${progress} ${description}`);
|
||||||
console.log(chalk.dim('─'.repeat(80 - progress.length - 1)) + '\n');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display completion message
|
* Display completion message
|
||||||
* @param {string} message - Completion message
|
* @param {string} message - Completion message
|
||||||
*/
|
*/
|
||||||
displayComplete(message) {
|
async displayComplete(message) {
|
||||||
console.log(
|
const color = await prompts.getColor();
|
||||||
'\n' +
|
await prompts.box(`\u2728 ${message}`, 'Complete', {
|
||||||
boxen(chalk.green('✨ ' + message), {
|
rounded: true,
|
||||||
padding: 1,
|
formatBorder: color.green,
|
||||||
margin: 1,
|
});
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'green',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display error message
|
* Display error message
|
||||||
* @param {string} message - Error message
|
* @param {string} message - Error message
|
||||||
*/
|
*/
|
||||||
displayError(message) {
|
async displayError(message) {
|
||||||
console.log(
|
const color = await prompts.getColor();
|
||||||
'\n' +
|
await prompts.box(`\u2717 ${message}`, 'Error', {
|
||||||
boxen(chalk.red('✗ ' + message), {
|
rounded: true,
|
||||||
padding: 1,
|
formatBorder: color.red,
|
||||||
margin: 1,
|
});
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'red',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,7 +137,7 @@ const CLIUtils = {
|
||||||
* @param {Array} items - Items to display
|
* @param {Array} items - Items to display
|
||||||
* @param {string} prefix - Item prefix
|
* @param {string} prefix - Item prefix
|
||||||
*/
|
*/
|
||||||
formatList(items, prefix = '•') {
|
formatList(items, prefix = '\u2022') {
|
||||||
return items.map((item) => ` ${prefix} ${item}`).join('\n');
|
return items.map((item) => ` ${prefix} ${item}`).join('\n');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -178,25 +152,6 @@ const CLIUtils = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Display table
|
|
||||||
* @param {Array} data - Table data
|
|
||||||
* @param {Object} options - Table options
|
|
||||||
*/
|
|
||||||
displayTable(data, options = {}) {
|
|
||||||
const Table = require('cli-table3');
|
|
||||||
const table = new Table({
|
|
||||||
style: {
|
|
||||||
head: ['cyan'],
|
|
||||||
border: ['dim'],
|
|
||||||
},
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const row of data) table.push(row);
|
|
||||||
console.log(table.toString());
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display module completion message
|
* Display module completion message
|
||||||
* @param {string} moduleName - Name of the completed module
|
* @param {string} moduleName - Name of the completed module
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,43 @@ async function note(message, title) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a spinner for async operations
|
* Display a spinner for async operations
|
||||||
* @returns {Object} Spinner controller with start, stop, message methods
|
* Wraps @clack/prompts spinner with isSpinning state tracking
|
||||||
|
* @returns {Object} Spinner controller with start, stop, message, error, cancel, clear, isSpinning
|
||||||
*/
|
*/
|
||||||
async function spinner() {
|
async function spinner() {
|
||||||
const clack = await getClack();
|
const clack = await getClack();
|
||||||
return clack.spinner();
|
const s = clack.spinner();
|
||||||
|
let spinning = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: (msg) => {
|
||||||
|
spinning = true;
|
||||||
|
s.start(msg);
|
||||||
|
},
|
||||||
|
stop: (msg) => {
|
||||||
|
spinning = false;
|
||||||
|
s.stop(msg);
|
||||||
|
},
|
||||||
|
message: (msg) => s.message(msg),
|
||||||
|
error: (msg) => {
|
||||||
|
spinning = false;
|
||||||
|
s.error(msg);
|
||||||
|
},
|
||||||
|
cancel: (msg) => {
|
||||||
|
spinning = false;
|
||||||
|
s.cancel(msg);
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
spinning = false;
|
||||||
|
s.clear();
|
||||||
|
},
|
||||||
|
get isSpinning() {
|
||||||
|
return spinning;
|
||||||
|
},
|
||||||
|
get isCancelled() {
|
||||||
|
return s.isCancelled;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -190,31 +222,6 @@ async function multiselect(options) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Grouped multi-select prompt for categorized options
|
|
||||||
* @param {Object} options - Prompt options
|
|
||||||
* @param {string} options.message - The question to ask
|
|
||||||
* @param {Object} options.options - Object mapping group names to arrays of choices
|
|
||||||
* @param {Array} [options.initialValues] - Array of initially selected values
|
|
||||||
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
|
||||||
* @param {boolean} [options.selectableGroups=false] - Whether groups can be selected as a whole
|
|
||||||
* @returns {Promise<Array>} Array of selected values
|
|
||||||
*/
|
|
||||||
async function groupMultiselect(options) {
|
|
||||||
const clack = await getClack();
|
|
||||||
|
|
||||||
const result = await clack.groupMultiselect({
|
|
||||||
message: options.message,
|
|
||||||
options: options.options,
|
|
||||||
initialValues: options.initialValues,
|
|
||||||
required: options.required || false,
|
|
||||||
selectableGroups: options.selectableGroups || false,
|
|
||||||
});
|
|
||||||
|
|
||||||
await handleCancel(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default filter function for autocomplete - case-insensitive label matching
|
* Default filter function for autocomplete - case-insensitive label matching
|
||||||
* @param {string} search - Search string
|
* @param {string} search - Search string
|
||||||
|
|
@ -237,6 +244,7 @@ function defaultAutocompleteFilter(search, option) {
|
||||||
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||||
* @param {number} [options.maxItems=5] - Maximum visible items in scrollable list
|
* @param {number} [options.maxItems=5] - Maximum visible items in scrollable list
|
||||||
* @param {Function} [options.filter] - Custom filter function (search, option) => boolean
|
* @param {Function} [options.filter] - Custom filter function (search, option) => boolean
|
||||||
|
* @param {Array} [options.lockedValues] - Values that are always selected and cannot be toggled off
|
||||||
* @returns {Promise<Array>} Array of selected values
|
* @returns {Promise<Array>} Array of selected values
|
||||||
*/
|
*/
|
||||||
async function autocompleteMultiselect(options) {
|
async function autocompleteMultiselect(options) {
|
||||||
|
|
@ -245,6 +253,7 @@ async function autocompleteMultiselect(options) {
|
||||||
const color = await getPicocolors();
|
const color = await getPicocolors();
|
||||||
|
|
||||||
const filterFn = options.filter ?? defaultAutocompleteFilter;
|
const filterFn = options.filter ?? defaultAutocompleteFilter;
|
||||||
|
const lockedSet = new Set(options.lockedValues || []);
|
||||||
|
|
||||||
const prompt = new core.AutocompletePrompt({
|
const prompt = new core.AutocompletePrompt({
|
||||||
options: options.options,
|
options: options.options,
|
||||||
|
|
@ -280,9 +289,17 @@ async function autocompleteMultiselect(options) {
|
||||||
// Render option with checkbox
|
// Render option with checkbox
|
||||||
const renderOption = (opt, isHighlighted) => {
|
const renderOption = (opt, isHighlighted) => {
|
||||||
const isSelected = this.selectedValues.includes(opt.value);
|
const isSelected = this.selectedValues.includes(opt.value);
|
||||||
|
const isLocked = lockedSet.has(opt.value);
|
||||||
const label = opt.label ?? String(opt.value ?? '');
|
const label = opt.label ?? String(opt.value ?? '');
|
||||||
const hintText = opt.hint && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : '';
|
const hintText = opt.hint && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : '';
|
||||||
const checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
|
|
||||||
|
let checkbox;
|
||||||
|
if (isLocked) {
|
||||||
|
checkbox = color.green(clack.S_CHECKBOX_SELECTED);
|
||||||
|
const lockHint = color.dim(' (always installed)');
|
||||||
|
return isHighlighted ? `${checkbox} ${label}${lockHint}` : `${checkbox} ${color.dim(label)}${lockHint}`;
|
||||||
|
}
|
||||||
|
checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
|
||||||
return isHighlighted ? `${checkbox} ${label}${hintText}` : `${checkbox} ${color.dim(label)}`;
|
return isHighlighted ? `${checkbox} ${label}${hintText}` : `${checkbox} ${color.dim(label)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -322,6 +339,18 @@ async function autocompleteMultiselect(options) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Prevent locked values from being toggled off
|
||||||
|
if (lockedSet.size > 0) {
|
||||||
|
const originalToggle = prompt.toggleSelected.bind(prompt);
|
||||||
|
prompt.toggleSelected = function (value) {
|
||||||
|
// If locked and already selected, skip the toggle (would deselect)
|
||||||
|
if (lockedSet.has(value) && this.selectedValues.includes(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalToggle(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// === FIX: Make SPACE always act as selection key (not search input) ===
|
// === FIX: Make SPACE always act as selection key (not search input) ===
|
||||||
// Override _isActionKey to treat SPACE like TAB - always an action key
|
// Override _isActionKey to treat SPACE like TAB - always an action key
|
||||||
// This prevents SPACE from being added to the search input
|
// This prevents SPACE from being added to the search input
|
||||||
|
|
@ -520,6 +549,131 @@ const log = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display cancellation message
|
||||||
|
* @param {string} [message='Operation cancelled'] - The cancellation message
|
||||||
|
*/
|
||||||
|
async function cancel(message = 'Operation cancelled') {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.cancel(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display content in a styled box
|
||||||
|
* @param {string} content - The box content
|
||||||
|
* @param {string} [title] - Optional title
|
||||||
|
* @param {Object} [options] - Box options (contentAlign, titleAlign, width, rounded, formatBorder, etc.)
|
||||||
|
*/
|
||||||
|
async function box(content, title, options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.box(content, title, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a progress bar for visualizing task completion
|
||||||
|
* @param {Object} [options] - Progress options (max, style, etc.)
|
||||||
|
* @returns {Promise<Object>} Progress controller with start, advance, stop methods
|
||||||
|
*/
|
||||||
|
async function progress(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
return clack.progress(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a task log for displaying scrolling subprocess output
|
||||||
|
* @param {Object} options - TaskLog options (title, limit, retainLog)
|
||||||
|
* @returns {Promise<Object>} TaskLog controller with message, success, error methods
|
||||||
|
*/
|
||||||
|
async function taskLog(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
return clack.taskLog(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File system path prompt with autocomplete
|
||||||
|
* @param {Object} options - Path options
|
||||||
|
* @param {string} options.message - The prompt message
|
||||||
|
* @param {string} [options.initialValue] - Initial path value
|
||||||
|
* @param {boolean} [options.directory=false] - Only allow directories
|
||||||
|
* @param {Function} [options.validate] - Validation function
|
||||||
|
* @returns {Promise<string>} Selected path
|
||||||
|
*/
|
||||||
|
async function pathPrompt(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
const result = await clack.path(options);
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocomplete single-select prompt with type-ahead filtering
|
||||||
|
* @param {Object} options - Autocomplete options
|
||||||
|
* @param {string} options.message - The prompt message
|
||||||
|
* @param {Array} options.options - Array of choices [{value, label, hint?}]
|
||||||
|
* @param {string} [options.placeholder] - Placeholder text
|
||||||
|
* @param {number} [options.maxItems] - Maximum visible items
|
||||||
|
* @param {Function} [options.filter] - Custom filter function
|
||||||
|
* @returns {Promise<any>} Selected value
|
||||||
|
*/
|
||||||
|
async function autocomplete(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
const result = await clack.autocomplete(options);
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key-based instant selection prompt
|
||||||
|
* @param {Object} options - SelectKey options
|
||||||
|
* @param {string} options.message - The prompt message
|
||||||
|
* @param {Array} options.options - Array of choices [{value, label, hint?}]
|
||||||
|
* @returns {Promise<any>} Selected value
|
||||||
|
*/
|
||||||
|
async function selectKey(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
const result = await clack.selectKey(options);
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream messages with dynamic content (for LLMs, generators, etc.)
|
||||||
|
*/
|
||||||
|
const stream = {
|
||||||
|
async info(generator) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.info(generator);
|
||||||
|
},
|
||||||
|
async success(generator) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.success(generator);
|
||||||
|
},
|
||||||
|
async step(generator) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.step(generator);
|
||||||
|
},
|
||||||
|
async warn(generator) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.warn(generator);
|
||||||
|
},
|
||||||
|
async error(generator) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.error(generator);
|
||||||
|
},
|
||||||
|
async message(generator, options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.stream.message(generator, options);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color utility (picocolors instance from @clack/prompts)
|
||||||
|
* @returns {Promise<Object>} The color utility (picocolors)
|
||||||
|
*/
|
||||||
|
async function getColor() {
|
||||||
|
return await getPicocolors();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute an array of Inquirer-style questions using @clack/prompts
|
* Execute an array of Inquirer-style questions using @clack/prompts
|
||||||
* This provides compatibility with dynamic question arrays
|
* This provides compatibility with dynamic question arrays
|
||||||
|
|
@ -619,20 +773,28 @@ async function prompt(questions) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getClack,
|
getClack,
|
||||||
|
getColor,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
intro,
|
intro,
|
||||||
outro,
|
outro,
|
||||||
|
cancel,
|
||||||
note,
|
note,
|
||||||
|
box,
|
||||||
spinner,
|
spinner,
|
||||||
|
progress,
|
||||||
|
taskLog,
|
||||||
select,
|
select,
|
||||||
multiselect,
|
multiselect,
|
||||||
groupMultiselect,
|
|
||||||
autocompleteMultiselect,
|
autocompleteMultiselect,
|
||||||
|
autocomplete,
|
||||||
|
selectKey,
|
||||||
confirm,
|
confirm,
|
||||||
text,
|
text,
|
||||||
|
path: pathPrompt,
|
||||||
password,
|
password,
|
||||||
group,
|
group,
|
||||||
tasks,
|
tasks,
|
||||||
log,
|
log,
|
||||||
|
stream,
|
||||||
prompt,
|
prompt,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const chalk = require('chalk');
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
@ -30,12 +29,12 @@ class UI {
|
||||||
* @returns {Object} Installation configuration
|
* @returns {Object} Installation configuration
|
||||||
*/
|
*/
|
||||||
async promptInstall(options = {}) {
|
async promptInstall(options = {}) {
|
||||||
CLIUtils.displayLogo();
|
await CLIUtils.displayLogo();
|
||||||
|
|
||||||
// Display version-specific start message from install-messages.yaml
|
// Display version-specific start message from install-messages.yaml
|
||||||
const { MessageLoader } = require('../installers/lib/message-loader');
|
const { MessageLoader } = require('../installers/lib/message-loader');
|
||||||
const messageLoader = new MessageLoader();
|
const messageLoader = new MessageLoader();
|
||||||
messageLoader.displayStartMessage();
|
await messageLoader.displayStartMessage();
|
||||||
|
|
||||||
// Get directory from options or prompt
|
// Get directory from options or prompt
|
||||||
let confirmedDirectory;
|
let confirmedDirectory;
|
||||||
|
|
@ -47,7 +46,7 @@ class UI {
|
||||||
throw new Error(`Invalid directory: ${validation}`);
|
throw new Error(`Invalid directory: ${validation}`);
|
||||||
}
|
}
|
||||||
confirmedDirectory = expandedDir;
|
confirmedDirectory = expandedDir;
|
||||||
console.log(chalk.cyan('Using directory from command-line:'), chalk.bold(confirmedDirectory));
|
await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
|
||||||
} else {
|
} else {
|
||||||
confirmedDirectory = await this.getConfirmedDirectory();
|
confirmedDirectory = await this.getConfirmedDirectory();
|
||||||
}
|
}
|
||||||
|
|
@ -98,38 +97,30 @@ class UI {
|
||||||
// Handle legacy .bmad or _cfg folder - these are very old (v4 or alpha)
|
// Handle legacy .bmad or _cfg folder - these are very old (v4 or alpha)
|
||||||
// Show version warning instead of offering conversion
|
// Show version warning instead of offering conversion
|
||||||
if (hasLegacyBmadFolder || hasLegacyCfg) {
|
if (hasLegacyBmadFolder || hasLegacyCfg) {
|
||||||
console.log('');
|
await prompts.log.warn('LEGACY INSTALLATION DETECTED');
|
||||||
console.log(chalk.yellow.bold('⚠️ LEGACY INSTALLATION DETECTED'));
|
await prompts.note(
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
'Found a ".bmad"/"bmad" folder, or a legacy "_cfg" folder under the bmad folder -\n' +
|
||||||
console.log(
|
'this is from an old BMAD version that is out of date for automatic upgrade,\n' +
|
||||||
chalk.yellow(
|
'manual intervention required.\n\n' +
|
||||||
'Found a ".bmad"/"bmad" folder, or a legacy "_cfg" folder under the bmad folder - this is from a old BMAD version that is out of date for automatic upgrade, manual intervention required.',
|
'You have a legacy version installed (v4 or alpha).\n' +
|
||||||
),
|
'Legacy installations may have compatibility issues.\n\n' +
|
||||||
|
'For the best experience, we strongly recommend:\n' +
|
||||||
|
' 1. Delete your current BMAD installation folder (.bmad or bmad)\n' +
|
||||||
|
' 2. Run a fresh installation\n\n' +
|
||||||
|
'If you do not want to start fresh, you can attempt to proceed beyond this\n' +
|
||||||
|
'point IF you have ensured the bmad folder is named _bmad, and under it there\n' +
|
||||||
|
'is a _config folder. If you have a folder under your bmad folder named _cfg,\n' +
|
||||||
|
'you would need to rename it _config, and then restart the installer.\n\n' +
|
||||||
|
'Benefits of a fresh install:\n' +
|
||||||
|
' \u2022 Cleaner configuration without legacy artifacts\n' +
|
||||||
|
' \u2022 All new features properly configured\n' +
|
||||||
|
' \u2022 Fewer potential conflicts\n\n' +
|
||||||
|
'If you have already produced output from an earlier alpha version, you can\n' +
|
||||||
|
'still retain those artifacts. After installation, ensure you configured during\n' +
|
||||||
|
'install the proper file locations for artifacts depending on the module you\n' +
|
||||||
|
'are using, or move the files to the proper locations.',
|
||||||
|
'Legacy Installation Detected',
|
||||||
);
|
);
|
||||||
console.log(chalk.yellow('You have a legacy version installed (v4 or alpha).'));
|
|
||||||
console.log('');
|
|
||||||
console.log(chalk.dim('Legacy installations may have compatibility issues.'));
|
|
||||||
console.log('');
|
|
||||||
console.log(chalk.dim('For the best experience, we strongly recommend:'));
|
|
||||||
console.log(chalk.dim(' 1. Delete your current BMAD installation folder (.bmad or bmad)'));
|
|
||||||
console.log(
|
|
||||||
chalk.dim(
|
|
||||||
' 2. Run a fresh installation\n\nIf you do not want to start fresh, you can attempt to proceed beyond this point IF you have ensured the bmad folder is named _bmad, and under it there is a _config folder. If you have a folder under your bmad folder named _cfg, you would need to rename it _config, and then restart the installer.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log('');
|
|
||||||
console.log(chalk.dim('Benefits of a fresh install:'));
|
|
||||||
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
|
|
||||||
console.log(chalk.dim(' • All new features properly configured'));
|
|
||||||
console.log(chalk.dim(' • Fewer potential conflicts'));
|
|
||||||
console.log(chalk.dim(''));
|
|
||||||
console.log(
|
|
||||||
chalk.dim(
|
|
||||||
'If you have already produced output from an earlier alpha version, you can still retain those artifacts. After installation, ensure you configured during install the proper file locations for artifacts depending on the module you are using, or move the files to the proper locations.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const proceed = await prompts.select({
|
const proceed = await prompts.select({
|
||||||
message: 'How would you like to proceed?',
|
message: 'How would you like to proceed?',
|
||||||
|
|
@ -147,37 +138,33 @@ class UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (proceed === 'cancel') {
|
if (proceed === 'cancel') {
|
||||||
console.log('');
|
await prompts.note('1. Delete the existing bmad folder in your project\n' + "2. Run 'bmad install' again", 'To do a fresh install');
|
||||||
console.log(chalk.cyan('To do a fresh install:'));
|
|
||||||
console.log(chalk.dim(' 1. Delete the existing bmad folder in your project'));
|
|
||||||
console.log(chalk.dim(" 2. Run 'bmad install' again"));
|
|
||||||
console.log('');
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ora = require('ora');
|
const s = await prompts.spinner();
|
||||||
const spinner = ora('Updating folder structure...').start();
|
s.start('Updating folder structure...');
|
||||||
try {
|
try {
|
||||||
// Handle .bmad folder
|
// Handle .bmad folder
|
||||||
if (hasLegacyBmadFolder) {
|
if (hasLegacyBmadFolder) {
|
||||||
const newBmadPath = path.join(confirmedDirectory, '_bmad');
|
const newBmadPath = path.join(confirmedDirectory, '_bmad');
|
||||||
await fs.move(legacyBmadPath, newBmadPath);
|
await fs.move(legacyBmadPath, newBmadPath);
|
||||||
bmadDir = newBmadPath;
|
bmadDir = newBmadPath;
|
||||||
spinner.succeed('Renamed ".bmad" to "_bmad"');
|
s.stop('Renamed ".bmad" to "_bmad"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle _cfg folder (either from .bmad or standalone)
|
// Handle _cfg folder (either from .bmad or standalone)
|
||||||
const cfgPath = path.join(bmadDir, '_cfg');
|
const cfgPath = path.join(bmadDir, '_cfg');
|
||||||
if (await fs.pathExists(cfgPath)) {
|
if (await fs.pathExists(cfgPath)) {
|
||||||
spinner.start('Renaming configuration folder...');
|
s.start('Renaming configuration folder...');
|
||||||
const newCfgPath = path.join(bmadDir, '_config');
|
const newCfgPath = path.join(bmadDir, '_config');
|
||||||
await fs.move(cfgPath, newCfgPath);
|
await fs.move(cfgPath, newCfgPath);
|
||||||
spinner.succeed('Renamed "_cfg" to "_config"');
|
s.stop('Renamed "_cfg" to "_config"');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Failed to update folder structure');
|
s.stop('Failed to update folder structure');
|
||||||
console.error(chalk.red(`Error: ${error.message}`));
|
await prompts.log.error(`Error: ${error.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +226,7 @@ class UI {
|
||||||
throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
|
throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
|
||||||
}
|
}
|
||||||
actionType = options.action;
|
actionType = options.action;
|
||||||
console.log(chalk.cyan('Using action from command-line:'), chalk.bold(actionType));
|
await prompts.log.info(`Using action from command-line: ${actionType}`);
|
||||||
} else {
|
} else {
|
||||||
actionType = await prompts.select({
|
actionType = await prompts.select({
|
||||||
message: 'How would you like to proceed?',
|
message: 'How would you like to proceed?',
|
||||||
|
|
@ -274,7 +261,7 @@ class UI {
|
||||||
// Get existing installation info
|
// Get existing installation info
|
||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||||
|
|
||||||
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
await prompts.log.message(`Found existing modules: ${[...installedModuleIds].join(', ')}`);
|
||||||
|
|
||||||
// Unified module selection - all modules in one grouped multiselect
|
// Unified module selection - all modules in one grouped multiselect
|
||||||
let selectedModules;
|
let selectedModules;
|
||||||
|
|
@ -284,13 +271,12 @@ class UI {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((m) => m.trim())
|
.map((m) => m.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
|
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
|
||||||
} else {
|
} else {
|
||||||
selectedModules = await this.selectAllModules(installedModuleIds);
|
selectedModules = await this.selectAllModules(installedModuleIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// After module selection, ask about custom modules
|
// After module selection, ask about custom modules
|
||||||
console.log('');
|
|
||||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||||
|
|
||||||
if (options.customContent) {
|
if (options.customContent) {
|
||||||
|
|
@ -299,7 +285,7 @@ class UI {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((p) => p.trim())
|
.map((p) => p.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
|
await prompts.log.info(`Using custom content from command-line: ${paths.join(', ')}`);
|
||||||
|
|
||||||
// Build custom content config similar to promptCustomContentSource
|
// Build custom content config similar to promptCustomContentSource
|
||||||
const customPaths = [];
|
const customPaths = [];
|
||||||
|
|
@ -309,7 +295,7 @@ class UI {
|
||||||
const expandedPath = this.expandUserPath(customPath);
|
const expandedPath = this.expandUserPath(customPath);
|
||||||
const validation = this.validateCustomContentPathSync(expandedPath);
|
const validation = this.validateCustomContentPathSync(expandedPath);
|
||||||
if (validation) {
|
if (validation) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
|
await prompts.log.warn(`Skipping invalid custom content path: ${customPath} - ${validation}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,12 +307,12 @@ class UI {
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
moduleMeta = yaml.parse(moduleYaml);
|
moduleMeta = yaml.parse(moduleYaml);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
|
await prompts.log.warn(`Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moduleMeta.code) {
|
if (!moduleMeta.code) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
|
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,11 +390,11 @@ class UI {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((m) => m.trim())
|
.map((m) => m.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
|
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
|
||||||
} else if (options.yes) {
|
} else if (options.yes) {
|
||||||
// Use default modules when --yes flag is set
|
// Use default modules when --yes flag is set
|
||||||
selectedModules = await this.getDefaultModules(installedModuleIds);
|
selectedModules = await this.getDefaultModules(installedModuleIds);
|
||||||
console.log(chalk.cyan('Using default modules (--yes flag):'), chalk.bold(selectedModules.join(', ')));
|
await prompts.log.info(`Using default modules (--yes flag): ${selectedModules.join(', ')}`);
|
||||||
} else {
|
} else {
|
||||||
selectedModules = await this.selectAllModules(installedModuleIds);
|
selectedModules = await this.selectAllModules(installedModuleIds);
|
||||||
}
|
}
|
||||||
|
|
@ -420,7 +406,7 @@ class UI {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((p) => p.trim())
|
.map((p) => p.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
|
await prompts.log.info(`Using custom content from command-line: ${paths.join(', ')}`);
|
||||||
|
|
||||||
// Build custom content config similar to promptCustomContentSource
|
// Build custom content config similar to promptCustomContentSource
|
||||||
const customPaths = [];
|
const customPaths = [];
|
||||||
|
|
@ -430,7 +416,7 @@ class UI {
|
||||||
const expandedPath = this.expandUserPath(customPath);
|
const expandedPath = this.expandUserPath(customPath);
|
||||||
const validation = this.validateCustomContentPathSync(expandedPath);
|
const validation = this.validateCustomContentPathSync(expandedPath);
|
||||||
if (validation) {
|
if (validation) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
|
await prompts.log.warn(`Skipping invalid custom content path: ${customPath} - ${validation}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,12 +428,12 @@ class UI {
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
moduleMeta = yaml.parse(moduleYaml);
|
moduleMeta = yaml.parse(moduleYaml);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
|
await prompts.log.warn(`Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moduleMeta.code) {
|
if (!moduleMeta.code) {
|
||||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
|
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,7 +517,7 @@ class UI {
|
||||||
const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
|
const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
|
||||||
const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
|
const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
|
||||||
if (unknownTools.length > 0) {
|
if (unknownTools.length > 0) {
|
||||||
console.log(chalk.yellow(`⚠️ Previously configured tools are no longer available: ${unknownTools.join(', ')}`));
|
await prompts.log.warn(`Previously configured tools are no longer available: ${unknownTools.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -569,7 +555,6 @@ class UI {
|
||||||
const selectedIdes = upgradeSelected || [];
|
const selectedIdes = upgradeSelected || [];
|
||||||
|
|
||||||
if (selectedIdes.length === 0) {
|
if (selectedIdes.length === 0) {
|
||||||
console.log('');
|
|
||||||
const confirmNoTools = await prompts.confirm({
|
const confirmNoTools = await prompts.confirm({
|
||||||
message: 'No tools selected. Continue without installing any tools?',
|
message: 'No tools selected. Continue without installing any tools?',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
@ -583,7 +568,7 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display selected tools
|
// Display selected tools
|
||||||
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
||||||
|
|
||||||
return { ides: selectedIdes, skipIde: false };
|
return { ides: selectedIdes, skipIde: false };
|
||||||
}
|
}
|
||||||
|
|
@ -609,25 +594,25 @@ class UI {
|
||||||
if (options.tools) {
|
if (options.tools) {
|
||||||
// Check for explicit "none" value to skip tool installation
|
// Check for explicit "none" value to skip tool installation
|
||||||
if (options.tools.toLowerCase() === 'none') {
|
if (options.tools.toLowerCase() === 'none') {
|
||||||
console.log(chalk.cyan('Skipping tool configuration (--tools none)'));
|
await prompts.log.info('Skipping tool configuration (--tools none)');
|
||||||
return { ides: [], skipIde: true };
|
return { ides: [], skipIde: true };
|
||||||
} else {
|
} else {
|
||||||
selectedIdes = options.tools
|
selectedIdes = options.tools
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((t) => t.trim())
|
.map((t) => t.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
console.log(chalk.cyan('Using tools from command-line:'), chalk.bold(selectedIdes.join(', ')));
|
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
|
||||||
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
||||||
return { ides: selectedIdes, skipIde: false };
|
return { ides: selectedIdes, skipIde: false };
|
||||||
}
|
}
|
||||||
} else if (options.yes) {
|
} else if (options.yes) {
|
||||||
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
|
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
|
||||||
if (configuredIdes.length > 0) {
|
if (configuredIdes.length > 0) {
|
||||||
console.log(chalk.cyan('Using previously configured tools (--yes flag):'), chalk.bold(configuredIdes.join(', ')));
|
await prompts.log.info(`Using previously configured tools (--yes flag): ${configuredIdes.join(', ')}`);
|
||||||
this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
|
||||||
return { ides: configuredIdes, skipIde: false };
|
return { ides: configuredIdes, skipIde: false };
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.cyan('Skipping tool configuration (--yes flag, no previous tools)'));
|
await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)');
|
||||||
return { ides: [], skipIde: true };
|
return { ides: [], skipIde: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +632,6 @@ class UI {
|
||||||
// STEP 3: Confirm if no tools selected
|
// STEP 3: Confirm if no tools selected
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
if (selectedIdes.length === 0) {
|
if (selectedIdes.length === 0) {
|
||||||
console.log('');
|
|
||||||
const confirmNoTools = await prompts.confirm({
|
const confirmNoTools = await prompts.confirm({
|
||||||
message: 'No tools selected. Continue without installing any tools?',
|
message: 'No tools selected. Continue without installing any tools?',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
@ -665,7 +649,7 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display selected tools
|
// Display selected tools
|
||||||
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ides: selectedIdes,
|
ides: selectedIdes,
|
||||||
|
|
@ -708,15 +692,12 @@ class UI {
|
||||||
* Display installation summary
|
* Display installation summary
|
||||||
* @param {Object} result - Installation result
|
* @param {Object} result - Installation result
|
||||||
*/
|
*/
|
||||||
showInstallSummary(result) {
|
async showInstallSummary(result) {
|
||||||
// Clean, simple completion message
|
let summary = `Installed to: ${result.path}`;
|
||||||
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
|
||||||
|
|
||||||
// Show installation summary in a simple format
|
|
||||||
console.log(chalk.dim(`Installed to: ${result.path}`));
|
|
||||||
if (result.modules && result.modules.length > 0) {
|
if (result.modules && result.modules.length > 0) {
|
||||||
console.log(chalk.dim(`Modules: ${result.modules.join(', ')}`));
|
summary += `\nModules: ${result.modules.join(', ')}`;
|
||||||
}
|
}
|
||||||
|
await prompts.note(summary, 'BMAD is ready to use!');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -769,19 +750,19 @@ class UI {
|
||||||
const coreConfig = {};
|
const coreConfig = {};
|
||||||
if (options.userName) {
|
if (options.userName) {
|
||||||
coreConfig.user_name = options.userName;
|
coreConfig.user_name = options.userName;
|
||||||
console.log(chalk.cyan('Using user name from command-line:'), chalk.bold(options.userName));
|
await prompts.log.info(`Using user name from command-line: ${options.userName}`);
|
||||||
}
|
}
|
||||||
if (options.communicationLanguage) {
|
if (options.communicationLanguage) {
|
||||||
coreConfig.communication_language = options.communicationLanguage;
|
coreConfig.communication_language = options.communicationLanguage;
|
||||||
console.log(chalk.cyan('Using communication language from command-line:'), chalk.bold(options.communicationLanguage));
|
await prompts.log.info(`Using communication language from command-line: ${options.communicationLanguage}`);
|
||||||
}
|
}
|
||||||
if (options.documentOutputLanguage) {
|
if (options.documentOutputLanguage) {
|
||||||
coreConfig.document_output_language = options.documentOutputLanguage;
|
coreConfig.document_output_language = options.documentOutputLanguage;
|
||||||
console.log(chalk.cyan('Using document output language from command-line:'), chalk.bold(options.documentOutputLanguage));
|
await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
|
||||||
}
|
}
|
||||||
if (options.outputFolder) {
|
if (options.outputFolder) {
|
||||||
coreConfig.output_folder = options.outputFolder;
|
coreConfig.output_folder = options.outputFolder;
|
||||||
console.log(chalk.cyan('Using output folder from command-line:'), chalk.bold(options.outputFolder));
|
await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load existing config to merge with provided options
|
// Load existing config to merge with provided options
|
||||||
|
|
@ -818,7 +799,7 @@ class UI {
|
||||||
document_output_language: 'English',
|
document_output_language: 'English',
|
||||||
output_folder: '_bmad-output',
|
output_folder: '_bmad-output',
|
||||||
};
|
};
|
||||||
console.log(chalk.cyan('Using default configuration (--yes flag)'));
|
await prompts.log.info('Using default configuration (--yes flag)');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load existing configs first if they exist
|
// Load existing configs first if they exist
|
||||||
|
|
@ -839,6 +820,7 @@ class UI {
|
||||||
* @returns {Array} Module choices for prompt
|
* @returns {Array} Module choices for prompt
|
||||||
*/
|
*/
|
||||||
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
||||||
|
const color = await prompts.getColor();
|
||||||
const moduleChoices = [];
|
const moduleChoices = [];
|
||||||
const isNewInstallation = installedModuleIds.size === 0;
|
const isNewInstallation = installedModuleIds.size === 0;
|
||||||
|
|
||||||
|
|
@ -855,7 +837,7 @@ class UI {
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile);
|
const customInfo = await customHandler.getCustomInfo(customFile);
|
||||||
if (customInfo) {
|
if (customInfo) {
|
||||||
customContentItems.push({
|
customContentItems.push({
|
||||||
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
|
name: `${color.cyan('\u2713')} ${customInfo.name} ${color.dim(`(${customInfo.relativePath})`)}`,
|
||||||
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
|
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
|
||||||
checked: true, // Default to selected since user chose to provide custom content
|
checked: true, // Default to selected since user chose to provide custom content
|
||||||
path: customInfo.path, // Track path to avoid duplicates
|
path: customInfo.path, // Track path to avoid duplicates
|
||||||
|
|
@ -883,7 +865,7 @@ class UI {
|
||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
allCustomModules.push({
|
allCustomModules.push({
|
||||||
name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(cached)`)}`,
|
name: `${color.cyan('\u2713')} ${mod.name} ${color.dim('(cached)')}`,
|
||||||
value: mod.id,
|
value: mod.id,
|
||||||
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
|
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
|
||||||
hint: mod.description || undefined,
|
hint: mod.description || undefined,
|
||||||
|
|
@ -934,22 +916,20 @@ class UI {
|
||||||
...choicesWithDefaults,
|
...choicesWithDefaults,
|
||||||
{
|
{
|
||||||
value: '__NONE__',
|
value: '__NONE__',
|
||||||
label: '⚠ None / I changed my mind - skip module installation',
|
label: '\u26A0 None / I changed my mind - skip module installation',
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const selected = await prompts.multiselect({
|
const selected = await prompts.multiselect({
|
||||||
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
message: 'Select modules to install (use arrow keys, space to toggle):',
|
||||||
choices: choicesWithSkipOption,
|
choices: choicesWithSkipOption,
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||||
console.log();
|
await prompts.log.warn('"None / I changed my mind" was selected, so no modules will be installed.');
|
||||||
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no modules will be installed.'));
|
|
||||||
console.log();
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -983,7 +963,7 @@ class UI {
|
||||||
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
|
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
|
||||||
// Build a message showing available modules
|
// Build a message showing available modules
|
||||||
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
|
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
|
||||||
const message = `Select official BMad modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`;
|
const message = 'Select official BMad modules to install (use arrow keys, space to toggle):';
|
||||||
|
|
||||||
// Mark choices as checked based on defaultSelections
|
// Mark choices as checked based on defaultSelections
|
||||||
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
|
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
|
||||||
|
|
@ -1009,9 +989,7 @@ class UI {
|
||||||
|
|
||||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||||
console.log();
|
await prompts.log.warn('"None / I changed my mind" was selected, so no external modules will be installed.');
|
||||||
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no external modules will be installed.'));
|
|
||||||
console.log();
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1033,100 +1011,98 @@ class UI {
|
||||||
const externalManager = new ExternalModuleManager();
|
const externalManager = new ExternalModuleManager();
|
||||||
const externalModules = await externalManager.listAvailable();
|
const externalModules = await externalManager.listAvailable();
|
||||||
|
|
||||||
// Build grouped options
|
// Build flat options list with group hints for autocompleteMultiselect
|
||||||
const groupedOptions = {};
|
const allOptions = [];
|
||||||
const initialValues = [];
|
const initialValues = [];
|
||||||
|
const lockedValues = ['core'];
|
||||||
|
|
||||||
|
// Core module is always installed — show it locked at the top
|
||||||
|
allOptions.push({ label: 'BMad Core Module', value: 'core', hint: 'Core configuration and shared resources' });
|
||||||
|
initialValues.push('core');
|
||||||
|
|
||||||
// Helper to build module entry with proper sorting and selection
|
// Helper to build module entry with proper sorting and selection
|
||||||
const buildModuleEntry = (mod, value) => {
|
const buildModuleEntry = (mod, value, group) => {
|
||||||
const isInstalled = installedModuleIds.has(value);
|
const isInstalled = installedModuleIds.has(value);
|
||||||
const isDefault = mod.defaultSelected === true;
|
|
||||||
return {
|
return {
|
||||||
label: mod.description ? `${mod.name} — ${mod.description}` : mod.name,
|
label: mod.name,
|
||||||
value,
|
value,
|
||||||
// For sorting: defaultSelected=0, others=1
|
hint: mod.description || group,
|
||||||
sortKey: isDefault ? 0 : 1,
|
// Pre-select only if already installed (not on fresh install)
|
||||||
// Pre-select if default selected OR already installed
|
selected: isInstalled,
|
||||||
selected: isDefault || isInstalled,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Group 1: BMad Core (BMM, BMB)
|
// Local modules (BMM, BMB, etc.)
|
||||||
const coreModules = [];
|
const localEntries = [];
|
||||||
for (const mod of localModules) {
|
for (const mod of localModules) {
|
||||||
if (!mod.isCustom && (mod.id === 'bmm' || mod.id === 'bmb')) {
|
if (!mod.isCustom && mod.id !== 'core') {
|
||||||
const entry = buildModuleEntry(mod, mod.id);
|
const entry = buildModuleEntry(mod, mod.id, 'Local');
|
||||||
coreModules.push(entry);
|
localEntries.push(entry);
|
||||||
if (entry.selected) {
|
if (entry.selected) {
|
||||||
initialValues.push(mod.id);
|
initialValues.push(mod.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sort: defaultSelected first, then others
|
allOptions.push(...localEntries.map(({ label, value, hint }) => ({ label, value, hint })));
|
||||||
coreModules.sort((a, b) => a.sortKey - b.sortKey);
|
|
||||||
// Remove sortKey from final entries
|
|
||||||
if (coreModules.length > 0) {
|
|
||||||
groupedOptions['BMad Core'] = coreModules.map(({ label, value }) => ({ label, value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group 2: BMad Official Modules (type: bmad-org)
|
// Group 2: BMad Official Modules (type: bmad-org)
|
||||||
const officialModules = [];
|
const officialModules = [];
|
||||||
for (const mod of externalModules) {
|
for (const mod of externalModules) {
|
||||||
if (mod.type === 'bmad-org') {
|
if (mod.type === 'bmad-org') {
|
||||||
const entry = buildModuleEntry(mod, mod.code);
|
const entry = buildModuleEntry(mod, mod.code, 'Official');
|
||||||
officialModules.push(entry);
|
officialModules.push(entry);
|
||||||
if (entry.selected) {
|
if (entry.selected) {
|
||||||
initialValues.push(mod.code);
|
initialValues.push(mod.code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
officialModules.sort((a, b) => a.sortKey - b.sortKey);
|
allOptions.push(...officialModules.map(({ label, value, hint }) => ({ label, value, hint })));
|
||||||
if (officialModules.length > 0) {
|
|
||||||
groupedOptions['BMad Official Modules'] = officialModules.map(({ label, value }) => ({ label, value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group 3: Community Modules (type: community)
|
// Group 3: Community Modules (type: community)
|
||||||
const communityModules = [];
|
const communityModules = [];
|
||||||
for (const mod of externalModules) {
|
for (const mod of externalModules) {
|
||||||
if (mod.type === 'community') {
|
if (mod.type === 'community') {
|
||||||
const entry = buildModuleEntry(mod, mod.code);
|
const entry = buildModuleEntry(mod, mod.code, 'Community');
|
||||||
communityModules.push(entry);
|
communityModules.push(entry);
|
||||||
if (entry.selected) {
|
if (entry.selected) {
|
||||||
initialValues.push(mod.code);
|
initialValues.push(mod.code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
communityModules.sort((a, b) => a.sortKey - b.sortKey);
|
allOptions.push(...communityModules.map(({ label, value, hint }) => ({ label, value, hint })), {
|
||||||
if (communityModules.length > 0) {
|
// "None" option at the end
|
||||||
groupedOptions['Community Modules'] = communityModules.map(({ label, value }) => ({ label, value }));
|
label: '\u26A0 None - Skip module installation',
|
||||||
}
|
value: '__NONE__',
|
||||||
|
});
|
||||||
|
|
||||||
// Add "None" option at the end
|
const selected = await prompts.autocompleteMultiselect({
|
||||||
groupedOptions[' '] = [
|
message: 'Select modules to install:',
|
||||||
{
|
options: allOptions,
|
||||||
label: '⚠ None - Skip module installation',
|
|
||||||
value: '__NONE__',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const selected = await prompts.groupMultiselect({
|
|
||||||
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
||||||
options: groupedOptions,
|
|
||||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||||
|
lockedValues,
|
||||||
required: true,
|
required: true,
|
||||||
selectableGroups: false,
|
maxItems: allOptions.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||||
console.log();
|
await prompts.log.warn('"None" was selected, so no modules will be installed.');
|
||||||
console.log(chalk.yellow('⚠️ "None" was selected, so no modules will be installed.'));
|
|
||||||
console.log();
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out the special '__NONE__' value
|
// Filter out the special '__NONE__' value
|
||||||
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
const result = selected ? selected.filter((m) => m !== '__NONE__') : [];
|
||||||
|
|
||||||
|
// Display selected modules as bulleted list
|
||||||
|
if (result.length > 0) {
|
||||||
|
const moduleLines = result.map((moduleId) => {
|
||||||
|
const opt = allOptions.find((o) => o.value === moduleId);
|
||||||
|
return ` \u2022 ${opt?.label || moduleId}`;
|
||||||
|
});
|
||||||
|
await prompts.log.message('Selected modules:\n' + moduleLines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1185,7 +1161,7 @@ class UI {
|
||||||
* @param {string} directory - The directory path
|
* @param {string} directory - The directory path
|
||||||
*/
|
*/
|
||||||
async displayDirectoryInfo(directory) {
|
async displayDirectoryInfo(directory) {
|
||||||
console.log(chalk.cyan('\nResolved installation path:'), chalk.bold(directory));
|
await prompts.log.info(`Resolved installation path: ${directory}`);
|
||||||
|
|
||||||
const dirExists = await fs.pathExists(directory);
|
const dirExists = await fs.pathExists(directory);
|
||||||
if (dirExists) {
|
if (dirExists) {
|
||||||
|
|
@ -1201,12 +1177,10 @@ class UI {
|
||||||
const hasBmadInstall =
|
const hasBmadInstall =
|
||||||
(await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
|
(await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
|
||||||
|
|
||||||
console.log(
|
const bmadNote = hasBmadInstall ? ` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})` : '';
|
||||||
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
|
await prompts.log.message(`Directory exists and contains ${files.length} item(s)${bmadNote}`);
|
||||||
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})`) : ''),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.gray('Directory exists and is empty'));
|
await prompts.log.message('Directory exists and is empty');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1227,7 +1201,7 @@ class UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!proceed) {
|
if (!proceed) {
|
||||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
await prompts.log.warn("Let's try again with a different path.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return proceed;
|
return proceed;
|
||||||
|
|
@ -1239,7 +1213,7 @@ class UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!create) {
|
if (!create) {
|
||||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
await prompts.log.warn("Let's try again with a different path.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return create;
|
return create;
|
||||||
|
|
@ -1459,7 +1433,7 @@ class UI {
|
||||||
return configs;
|
return configs;
|
||||||
} catch {
|
} catch {
|
||||||
// If loading fails, return empty configs
|
// If loading fails, return empty configs
|
||||||
console.warn('Warning: Could not load existing configurations');
|
await prompts.log.warn('Could not load existing configurations');
|
||||||
return configs;
|
return configs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1590,7 +1564,7 @@ class UI {
|
||||||
name: moduleData.name || moduleData.code,
|
name: moduleData.name || moduleData.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(chalk.green(`✓ Confirmed local custom module: ${moduleData.name || moduleData.code}`));
|
await prompts.log.success(`Confirmed local custom module: ${moduleData.name || moduleData.code}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask if user wants to add these to the installation
|
// Ask if user wants to add these to the installation
|
||||||
|
|
@ -1656,11 +1630,11 @@ class UI {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ask user about custom modules
|
// Ask user about custom modules
|
||||||
console.log(chalk.cyan('\n⚙️ Custom Modules'));
|
await prompts.log.info('Custom Modules');
|
||||||
if (cachedCustomModules.length > 0) {
|
if (cachedCustomModules.length > 0) {
|
||||||
console.log(chalk.dim('Found custom modules in your installation:'));
|
await prompts.log.message('Found custom modules in your installation:');
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim('No custom modules currently installed.'));
|
await prompts.log.message('No custom modules currently installed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build choices dynamically based on whether we have existing modules
|
// Build choices dynamically based on whether we have existing modules
|
||||||
|
|
@ -1686,14 +1660,14 @@ class UI {
|
||||||
case 'keep': {
|
case 'keep': {
|
||||||
// Keep all existing custom modules
|
// Keep all existing custom modules
|
||||||
result.selectedCustomModules = cachedCustomModules.map((m) => m.id);
|
result.selectedCustomModules = cachedCustomModules.map((m) => m.id);
|
||||||
console.log(chalk.dim(`Keeping ${result.selectedCustomModules.length} custom module(s)`));
|
await prompts.log.message(`Keeping ${result.selectedCustomModules.length} custom module(s)`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'select': {
|
case 'select': {
|
||||||
// Let user choose which to keep
|
// Let user choose which to keep
|
||||||
const selectChoices = cachedCustomModules.map((m) => ({
|
const selectChoices = cachedCustomModules.map((m) => ({
|
||||||
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
|
name: `${m.name} (${m.id})`,
|
||||||
value: m.id,
|
value: m.id,
|
||||||
checked: m.checked,
|
checked: m.checked,
|
||||||
}));
|
}));
|
||||||
|
|
@ -1709,16 +1683,14 @@ class UI {
|
||||||
];
|
];
|
||||||
|
|
||||||
const keepModules = await prompts.multiselect({
|
const keepModules = await prompts.multiselect({
|
||||||
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
message: 'Select custom modules to keep (use arrow keys, space to toggle):',
|
||||||
choices: choicesWithSkip,
|
choices: choicesWithSkip,
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If user selected both "__NONE__" and other modules, honor the "None" choice
|
// If user selected both "__NONE__" and other modules, honor the "None" choice
|
||||||
if (keepModules && keepModules.includes('__NONE__') && keepModules.length > 1) {
|
if (keepModules && keepModules.includes('__NONE__') && keepModules.length > 1) {
|
||||||
console.log();
|
await prompts.log.warn('"None / I changed my mind" was selected, so no custom modules will be kept.');
|
||||||
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no custom modules will be kept.'));
|
|
||||||
console.log();
|
|
||||||
result.selectedCustomModules = [];
|
result.selectedCustomModules = [];
|
||||||
} else {
|
} else {
|
||||||
// Filter out the special '__NONE__' value
|
// Filter out the special '__NONE__' value
|
||||||
|
|
@ -1743,13 +1715,13 @@ class UI {
|
||||||
|
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
// Remove all custom modules
|
// Remove all custom modules
|
||||||
console.log(chalk.yellow('All custom modules will be removed from the installation'));
|
await prompts.log.warn('All custom modules will be removed from the installation');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'cancel': {
|
case 'cancel': {
|
||||||
// User cancelled - no custom modules
|
// User cancelled - no custom modules
|
||||||
console.log(chalk.dim('No custom modules will be added'));
|
await prompts.log.message('No custom modules will be added');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1782,30 +1754,26 @@ class UI {
|
||||||
return true; // Not legacy, proceed
|
return true; // Not legacy, proceed
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
let warningContent;
|
||||||
console.log(chalk.yellow.bold('⚠️ VERSION WARNING'));
|
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
|
||||||
|
|
||||||
if (installedVersion === 'unknown') {
|
if (installedVersion === 'unknown') {
|
||||||
console.log(chalk.yellow('Unable to detect your installed BMAD version.'));
|
warningContent = 'Unable to detect your installed BMAD version.\n' + 'This appears to be a legacy or unsupported installation.';
|
||||||
console.log(chalk.yellow('This appears to be a legacy or unsupported installation.'));
|
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.yellow(`You are updating from ${installedVersion} to ${currentVersion}.`));
|
warningContent =
|
||||||
console.log(chalk.yellow('You have a legacy version installed (v4 or alpha).'));
|
`You are updating from ${installedVersion} to ${currentVersion}.\n` + 'You have a legacy version installed (v4 or alpha).';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
warningContent +=
|
||||||
console.log(chalk.dim('For the best experience, we recommend:'));
|
'\n\nFor the best experience, we recommend:\n' +
|
||||||
console.log(chalk.dim(' 1. Delete your current BMAD installation folder'));
|
' 1. Delete your current BMAD installation folder\n' +
|
||||||
console.log(chalk.dim(` (the "${bmadFolderName}/" folder in your project)`));
|
` (the "${bmadFolderName}/" folder in your project)\n` +
|
||||||
console.log(chalk.dim(' 2. Run a fresh installation'));
|
' 2. Run a fresh installation\n\n' +
|
||||||
console.log('');
|
'Benefits of a fresh install:\n' +
|
||||||
console.log(chalk.dim('Benefits of a fresh install:'));
|
' \u2022 Cleaner configuration without legacy artifacts\n' +
|
||||||
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
|
' \u2022 All new features properly configured\n' +
|
||||||
console.log(chalk.dim(' • All new features properly configured'));
|
' \u2022 Fewer potential conflicts';
|
||||||
console.log(chalk.dim(' • Fewer potential conflicts'));
|
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
await prompts.log.warn('VERSION WARNING');
|
||||||
console.log('');
|
await prompts.note(warningContent, 'Version Warning');
|
||||||
|
|
||||||
const proceed = await prompts.select({
|
const proceed = await prompts.select({
|
||||||
message: 'How would you like to proceed?',
|
message: 'How would you like to proceed?',
|
||||||
|
|
@ -1823,11 +1791,10 @@ class UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (proceed === 'cancel') {
|
if (proceed === 'cancel') {
|
||||||
console.log('');
|
await prompts.note(
|
||||||
console.log(chalk.cyan('To do a fresh install:'));
|
`1. Delete the "${bmadFolderName}/" folder in your project\n` + "2. Run 'bmad install' again",
|
||||||
console.log(chalk.dim(` 1. Delete the "${bmadFolderName}/" folder in your project`));
|
'To do a fresh install',
|
||||||
console.log(chalk.dim(" 2. Run 'bmad install' again"));
|
);
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return proceed === 'proceed';
|
return proceed === 'proceed';
|
||||||
|
|
@ -1838,41 +1805,34 @@ class UI {
|
||||||
* @param {Array} modules - Array of module info objects with version info
|
* @param {Array} modules - Array of module info objects with version info
|
||||||
* @param {Array} availableUpdates - Array of available updates
|
* @param {Array} availableUpdates - Array of available updates
|
||||||
*/
|
*/
|
||||||
displayModuleVersions(modules, availableUpdates = []) {
|
async displayModuleVersions(modules, availableUpdates = []) {
|
||||||
console.log('');
|
|
||||||
console.log(chalk.cyan.bold('📦 Module Versions'));
|
|
||||||
console.log(chalk.gray('─'.repeat(80)));
|
|
||||||
|
|
||||||
// Group modules by source
|
// Group modules by source
|
||||||
const builtIn = modules.filter((m) => m.source === 'built-in');
|
const builtIn = modules.filter((m) => m.source === 'built-in');
|
||||||
const external = modules.filter((m) => m.source === 'external');
|
const external = modules.filter((m) => m.source === 'external');
|
||||||
const custom = modules.filter((m) => m.source === 'custom');
|
const custom = modules.filter((m) => m.source === 'custom');
|
||||||
const unknown = modules.filter((m) => m.source === 'unknown');
|
const unknown = modules.filter((m) => m.source === 'unknown');
|
||||||
|
|
||||||
const displayGroup = (group, title) => {
|
const lines = [];
|
||||||
|
const formatGroup = (group, title) => {
|
||||||
if (group.length === 0) return;
|
if (group.length === 0) return;
|
||||||
|
lines.push(title);
|
||||||
console.log(chalk.yellow(`\n${title}`));
|
for (const mod of group) {
|
||||||
for (const module of group) {
|
const updateInfo = availableUpdates.find((u) => u.name === mod.name);
|
||||||
const updateInfo = availableUpdates.find((u) => u.name === module.name);
|
const versionDisplay = mod.version || 'unknown';
|
||||||
const versionDisplay = module.version || chalk.gray('unknown');
|
|
||||||
|
|
||||||
if (updateInfo) {
|
if (updateInfo) {
|
||||||
console.log(
|
lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2192 ${updateInfo.latestVersion} \u2191`);
|
||||||
` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay} → ${chalk.green(updateInfo.latestVersion)} ${chalk.green('↑')}`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay} ${chalk.gray('✓')}`);
|
lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2713`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
displayGroup(builtIn, 'Built-in Modules');
|
formatGroup(builtIn, 'Built-in Modules');
|
||||||
displayGroup(external, 'External Modules (Official)');
|
formatGroup(external, 'External Modules (Official)');
|
||||||
displayGroup(custom, 'Custom Modules');
|
formatGroup(custom, 'Custom Modules');
|
||||||
displayGroup(unknown, 'Other Modules');
|
formatGroup(unknown, 'Other Modules');
|
||||||
|
|
||||||
console.log('');
|
await prompts.note(lines.join('\n'), 'Module Versions');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1885,12 +1845,10 @@ class UI {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
await prompts.log.info('Available Updates');
|
||||||
console.log(chalk.cyan.bold('🔄 Available Updates'));
|
|
||||||
console.log(chalk.gray('─'.repeat(80)));
|
|
||||||
|
|
||||||
const choices = availableUpdates.map((update) => ({
|
const choices = availableUpdates.map((update) => ({
|
||||||
name: `${update.name} ${chalk.dim(`(v${update.installedVersion} → v${update.latestVersion})`)}`,
|
name: `${update.name} (v${update.installedVersion} \u2192 v${update.latestVersion})`,
|
||||||
value: update.name,
|
value: update.name,
|
||||||
checked: true, // Default to selecting all updates
|
checked: true, // Default to selecting all updates
|
||||||
}));
|
}));
|
||||||
|
|
@ -1916,7 +1874,7 @@ class UI {
|
||||||
|
|
||||||
// Allow specific selection
|
// Allow specific selection
|
||||||
const selected = await prompts.multiselect({
|
const selected = await prompts.multiselect({
|
||||||
message: `Select modules to update ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
message: 'Select modules to update (use arrow keys, space to toggle):',
|
||||||
choices: choices,
|
choices: choices,
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
@ -1928,34 +1886,29 @@ class UI {
|
||||||
* Display status of all installed modules
|
* Display status of all installed modules
|
||||||
* @param {Object} statusData - Status data with modules, installation info, and available updates
|
* @param {Object} statusData - Status data with modules, installation info, and available updates
|
||||||
*/
|
*/
|
||||||
displayStatus(statusData) {
|
async displayStatus(statusData) {
|
||||||
const { installation, modules, availableUpdates, bmadDir } = statusData;
|
const { installation, modules, availableUpdates, bmadDir } = statusData;
|
||||||
|
|
||||||
console.log('');
|
|
||||||
console.log(chalk.cyan.bold('📋 BMAD Status'));
|
|
||||||
console.log(chalk.gray('─'.repeat(80)));
|
|
||||||
|
|
||||||
// Installation info
|
// Installation info
|
||||||
console.log(chalk.yellow('\nInstallation'));
|
const infoLines = [
|
||||||
console.log(` ${chalk.gray('Version:'.padEnd(20))} ${installation.version || chalk.gray('unknown')}`);
|
`Version: ${installation.version || 'unknown'}`,
|
||||||
console.log(` ${chalk.gray('Location:'.padEnd(20))} ${bmadDir}`);
|
`Location: ${bmadDir}`,
|
||||||
console.log(` ${chalk.gray('Installed:'.padEnd(20))} ${new Date(installation.installDate).toLocaleDateString()}`);
|
`Installed: ${new Date(installation.installDate).toLocaleDateString()}`,
|
||||||
console.log(
|
`Last Updated: ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : 'unknown'}`,
|
||||||
` ${chalk.gray('Last Updated:'.padEnd(20))} ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : chalk.gray('unknown')}`,
|
];
|
||||||
);
|
|
||||||
|
await prompts.note(infoLines.join('\n'), 'BMAD Status');
|
||||||
|
|
||||||
// Module versions
|
// Module versions
|
||||||
this.displayModuleVersions(modules, availableUpdates);
|
await this.displayModuleVersions(modules, availableUpdates);
|
||||||
|
|
||||||
// Update summary
|
// Update summary
|
||||||
if (availableUpdates.length > 0) {
|
if (availableUpdates.length > 0) {
|
||||||
console.log(chalk.yellow.bold(`\n⚠️ ${availableUpdates.length} update(s) available`));
|
await prompts.log.warn(`${availableUpdates.length} update(s) available`);
|
||||||
console.log(chalk.dim(` Run 'bmad install' and select "Quick Update" to update`));
|
await prompts.log.message('Run \'bmad install\' and select "Quick Update" to update');
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.green.bold('\n✓ All modules are up to date'));
|
await prompts.log.success('All modules are up to date');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1964,19 +1917,17 @@ class UI {
|
||||||
* @param {Array} preferredIdes - Array of preferred IDE objects
|
* @param {Array} preferredIdes - Array of preferred IDE objects
|
||||||
* @param {Array} allTools - Array of all tool objects
|
* @param {Array} allTools - Array of all tool objects
|
||||||
*/
|
*/
|
||||||
displaySelectedTools(selectedIdes, preferredIdes, allTools) {
|
async displaySelectedTools(selectedIdes, preferredIdes, allTools) {
|
||||||
if (selectedIdes.length === 0) return;
|
if (selectedIdes.length === 0) return;
|
||||||
|
|
||||||
const preferredValues = new Set(preferredIdes.map((ide) => ide.value));
|
const preferredValues = new Set(preferredIdes.map((ide) => ide.value));
|
||||||
|
const toolLines = selectedIdes.map((ideValue) => {
|
||||||
console.log('');
|
|
||||||
console.log(chalk.dim(' Selected tools:'));
|
|
||||||
for (const ideValue of selectedIdes) {
|
|
||||||
const tool = allTools.find((t) => t.value === ideValue);
|
const tool = allTools.find((t) => t.value === ideValue);
|
||||||
const name = tool?.name || ideValue;
|
const name = tool?.name || ideValue;
|
||||||
const marker = preferredValues.has(ideValue) ? ' ⭐' : '';
|
const marker = preferredValues.has(ideValue) ? ' \u2B50' : '';
|
||||||
console.log(chalk.dim(` • ${name}${marker}`));
|
return ` \u2022 ${name}${marker}`;
|
||||||
}
|
});
|
||||||
|
await prompts.log.message('Selected tools:\n' + toolLines.join('\n'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue