mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-01-30 04:32:02 +00:00
fix(cli): replace inquirer with @clack/prompts for Windows compatibility (#1316)
* fix(cli): replace inquirer with @clack/prompts for Windows compatibility - Add new prompts.js wrapper around @clack/prompts to fix Windows arrow key navigation issues (libuv #852) - Fix validation logic in github-copilot.js that always returned true - Add support for primitive choice values (string/number) in select/multiselect - Add 'when' property support for conditional questions in prompt() - Update all IDE installers to use new prompts module Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(cli): address code review feedback for prompts migration - Move @clack/prompts from devDependencies to dependencies (critical) - Remove unused inquirer dependency - Fix potential crash in multiselect when initialValues is undefined - Add async validator detection with explicit error message - Extract validateCustomContentPathSync method in ui.js - Extract promptInstallLocation methods in claude-code.js and antigravity.js - Fix moduleId -> missing.id in installer.js remove flow - Update multiselect to support native clack API (options/initialValues) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update comments to reference @clack/prompts instead of inquirer - Update bmad-cli.js comment about CLI prompts - Update config-collector.js JSDoc comments - Rename inquirer variable to choiceUtils in ui.js - Update JSDoc returns and calls documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(cli): add spacing between prompts and installation progress Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(cli): add multiselect usage hints for inexperienced users Add inline navigation hints to all multiselect prompts showing (↑/↓ navigate, SPACE select, ENTER confirm) to help users unfamiliar with terminal multiselect controls. Also restore detailed warning when no tools are selected, explaining that SPACE must be pressed to select items. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): restore IDE grouping using groupMultiselect Replace flat multiselect with native @clack/prompts groupMultiselect component to restore visual grouping of IDE/tool options: - "Previously Configured" - pre-selected IDEs from existing install - "Recommended Tools" - starred preferred options - "Additional Tools" - other available options This restores the grouped UX that was lost during the Inquirer.js to @clack/prompts migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
188
package-lock.json
generated
188
package-lock.json
generated
@@ -19,7 +19,6 @@
|
|||||||
"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",
|
||||||
"inquirer": "^9.3.8",
|
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
"ora": "^5.4.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
@@ -34,6 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.6.0",
|
||||||
"@astrojs/starlight": "^0.37.0",
|
"@astrojs/starlight": "^0.37.0",
|
||||||
|
"@clack/prompts": "^0.11.0",
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"astro": "^5.16.0",
|
"astro": "^5.16.0",
|
||||||
@@ -244,7 +244,6 @@
|
|||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -756,6 +755,29 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@clack/core": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@clack/prompts": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@clack/core": "0.5.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@colors/colors": {
|
"node_modules/@colors/colors": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
@@ -1998,36 +2020,6 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@inquirer/external-editor": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"chardet": "^2.1.1",
|
|
||||||
"iconv-lite": "^0.7.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/node": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@inquirer/figures": {
|
|
||||||
"version": "1.0.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz",
|
|
||||||
"integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@isaacs/balanced-match": {
|
"node_modules/@isaacs/balanced-match": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
@@ -3641,9 +3633,8 @@
|
|||||||
"version": "25.0.3",
|
"version": "25.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
||||||
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -3983,7 +3974,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -4031,6 +4021,7 @@
|
|||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||||
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"type-fest": "^0.21.3"
|
"type-fest": "^0.21.3"
|
||||||
@@ -4046,6 +4037,7 @@
|
|||||||
"version": "0.21.3",
|
"version": "0.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||||
|
"dev": true,
|
||||||
"license": "(MIT OR CC0-1.0)",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -4290,7 +4282,6 @@
|
|||||||
"integrity": "sha512-6mF/YrvwwRxLTu+aMEa5pwzKUNl5ZetWbTyZCs9Um0F12HUmxUiF5UHiZPy4rifzU3gtpM3xP2DfdmkNX9eZRg==",
|
"integrity": "sha512-6mF/YrvwwRxLTu+aMEa5pwzKUNl5ZetWbTyZCs9Um0F12HUmxUiF5UHiZPy4rifzU3gtpM3xP2DfdmkNX9eZRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^2.13.0",
|
"@astrojs/compiler": "^2.13.0",
|
||||||
"@astrojs/internal-helpers": "0.7.5",
|
"@astrojs/internal-helpers": "0.7.5",
|
||||||
@@ -5358,7 +5349,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -5601,12 +5591,6 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chardet": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -5787,15 +5771,6 @@
|
|||||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-width": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
@@ -6689,7 +6664,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -8269,22 +8243,6 @@
|
|||||||
"@babel/runtime": "^7.23.2"
|
"@babel/runtime": "^7.23.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
|
||||||
"version": "0.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
|
|
||||||
"integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/express"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -8420,43 +8378,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/inquirer": {
|
|
||||||
"version": "9.3.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz",
|
|
||||||
"integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@inquirer/external-editor": "^1.0.2",
|
|
||||||
"@inquirer/figures": "^1.0.3",
|
|
||||||
"ansi-escapes": "^4.3.2",
|
|
||||||
"cli-width": "^4.1.0",
|
|
||||||
"mute-stream": "1.0.0",
|
|
||||||
"ora": "^5.4.1",
|
|
||||||
"run-async": "^3.0.0",
|
|
||||||
"rxjs": "^7.8.1",
|
|
||||||
"string-width": "^4.2.3",
|
|
||||||
"strip-ansi": "^6.0.1",
|
|
||||||
"wrap-ansi": "^6.2.0",
|
|
||||||
"yoctocolors-cjs": "^2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inquirer/node_modules/wrap-ansi": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": "^4.0.0",
|
|
||||||
"string-width": "^4.1.0",
|
|
||||||
"strip-ansi": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/iron-webcrypto": {
|
"node_modules/iron-webcrypto": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
|
||||||
@@ -10304,7 +10225,6 @@
|
|||||||
"integrity": "sha512-p3JTemJJbkiMjXEMiFwgm0v6ym5g8K+b2oDny+6xdl300tUKySxvilJQLSea48C6OaYNmO30kH9KxpiAg5bWJw==",
|
"integrity": "sha512-p3JTemJJbkiMjXEMiFwgm0v6ym5g8K+b2oDny+6xdl300tUKySxvilJQLSea48C6OaYNmO30kH9KxpiAg5bWJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"globby": "15.0.0",
|
"globby": "15.0.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
@@ -11576,15 +11496,6 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mute-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/nano-spawn": {
|
"node_modules/nano-spawn": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
||||||
@@ -12378,7 +12289,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -12444,7 +12354,6 @@
|
|||||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -13273,7 +13182,6 @@
|
|||||||
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -13310,15 +13218,6 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/run-async": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -13343,15 +13242,6 @@
|
|||||||
"queue-microtask": "^1.2.2"
|
"queue-microtask": "^1.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rxjs": {
|
|
||||||
"version": "7.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
|
||||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -13372,12 +13262,6 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safer-buffer": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/sax": {
|
"node_modules/sax": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
|
||||||
@@ -14251,6 +14135,7 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
@@ -14335,7 +14220,7 @@
|
|||||||
"version": "7.16.0",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unicode-properties": {
|
"node_modules/unicode-properties": {
|
||||||
@@ -14837,7 +14722,6 @@
|
|||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -15111,7 +14995,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
},
|
},
|
||||||
@@ -15270,18 +15153,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yoctocolors-cjs": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/zip-stream": {
|
"node_modules/zip-stream": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
||||||
@@ -15303,7 +15174,6 @@
|
|||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@clack/prompts": "^0.11.0",
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
"boxen": "^5.1.2",
|
"boxen": "^5.1.2",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
@@ -77,7 +78,6 @@
|
|||||||
"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",
|
||||||
"inquirer": "^9.3.8",
|
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
"ora": "^5.4.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const path = require('node:path');
|
|||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
|
||||||
// Fix for stdin issues when running through npm on Windows
|
// Fix for stdin issues when running through npm on Windows
|
||||||
// Ensures keyboard interaction works properly with inquirer prompts
|
// Ensures keyboard interaction works properly with CLI prompts
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
try {
|
try {
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
|
|||||||
@@ -71,14 +71,10 @@ module.exports = {
|
|||||||
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
||||||
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
||||||
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
const prompts = require('../lib/prompts');
|
||||||
await inquirer.prompt([
|
await prompts.text({
|
||||||
{
|
message: chalk.green('Press Enter to start AgentVibes installer...'),
|
||||||
type: 'input',
|
});
|
||||||
name: 'continue',
|
|
||||||
message: chalk.green('Press Enter to start AgentVibes installer...'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,7 @@ const yaml = require('yaml');
|
|||||||
const chalk = require('chalk');
|
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');
|
||||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
|
||||||
let _inquirer = null;
|
|
||||||
async function getInquirer() {
|
|
||||||
if (!_inquirer) {
|
|
||||||
_inquirer = (await import('inquirer')).default;
|
|
||||||
}
|
|
||||||
return _inquirer;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConfigCollector {
|
class ConfigCollector {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -183,7 +175,6 @@ class ConfigCollector {
|
|||||||
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
||||||
*/
|
*/
|
||||||
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
this.currentProjectDir = projectDir;
|
this.currentProjectDir = projectDir;
|
||||||
|
|
||||||
// Load existing config if not already loaded
|
// Load existing config if not already loaded
|
||||||
@@ -359,7 +350,7 @@ class ConfigCollector {
|
|||||||
// Only show header if we actually have questions
|
// Only show header if we actually have questions
|
||||||
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
console.log(); // Line break before questions
|
console.log(); // Line break before questions
|
||||||
const promptedAnswers = await inquirer.prompt(questions);
|
const promptedAnswers = await prompts.prompt(questions);
|
||||||
|
|
||||||
// Merge prompted answers with static answers
|
// Merge prompted answers with static answers
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
@@ -502,7 +493,6 @@ class ConfigCollector {
|
|||||||
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
||||||
*/
|
*/
|
||||||
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
this.currentProjectDir = projectDir;
|
this.currentProjectDir = projectDir;
|
||||||
// Load existing config if needed and not already loaded
|
// Load existing config if needed and not already loaded
|
||||||
if (!skipLoadExisting && !this.existingConfig) {
|
if (!skipLoadExisting && !this.existingConfig) {
|
||||||
@@ -597,7 +587,7 @@ class ConfigCollector {
|
|||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
||||||
let customize = true;
|
let customize = true;
|
||||||
if (moduleName !== 'core') {
|
if (moduleName !== 'core') {
|
||||||
const customizeAnswer = await inquirer.prompt([
|
const customizeAnswer = await prompts.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
name: 'customize',
|
name: 'customize',
|
||||||
@@ -614,7 +604,7 @@ class ConfigCollector {
|
|||||||
|
|
||||||
if (questionsWithoutDefaults.length > 0) {
|
if (questionsWithoutDefaults.length > 0) {
|
||||||
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
||||||
const promptedAnswers = await inquirer.prompt(questionsWithoutDefaults);
|
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +618,7 @@ class ConfigCollector {
|
|||||||
allAnswers[question.name] = question.default;
|
allAnswers[question.name] = question.default;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const promptedAnswers = await inquirer.prompt(questions);
|
const promptedAnswers = await prompts.prompt(questions);
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -750,7 +740,7 @@ class ConfigCollector {
|
|||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
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 inquirer.prompt([
|
const { customize } = await prompts.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
name: 'customize',
|
name: 'customize',
|
||||||
@@ -845,7 +835,7 @@ class ConfigCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an inquirer question from a config item
|
* Build a prompt question from a config item
|
||||||
* @param {string} moduleName - Module name
|
* @param {string} moduleName - Module name
|
||||||
* @param {string} key - Config key
|
* @param {string} key - Config key
|
||||||
* @param {Object} item - Config item definition
|
* @param {Object} item - Config item definition
|
||||||
@@ -1007,7 +997,7 @@ class ConfigCollector {
|
|||||||
message: message,
|
message: message,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set default - if it's dynamic, use a function that inquirer will evaluate with current answers
|
// Set default - if it's dynamic, use a function that the prompt will evaluate with current answers
|
||||||
// But if we have an existing value, always use that instead
|
// But if we have an existing value, always use that instead
|
||||||
if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
|
if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
|
||||||
question.default = existingValue;
|
question.default = existingValue;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const { CLIUtils } = require('../../../lib/cli-utils');
|
|||||||
const { ManifestGenerator } = require('./manifest-generator');
|
const { ManifestGenerator } = require('./manifest-generator');
|
||||||
const { IdeConfigManager } = require('./ide-config-manager');
|
const { IdeConfigManager } = require('./ide-config-manager');
|
||||||
const { CustomHandler } = require('../custom/handler');
|
const { CustomHandler } = require('../custom/handler');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
// BMAD installation folder name - this is constant and should never change
|
// BMAD installation folder name - this is constant and should never change
|
||||||
const BMAD_FOLDER_NAME = '_bmad';
|
const BMAD_FOLDER_NAME = '_bmad';
|
||||||
@@ -758,6 +759,9 @@ class Installer {
|
|||||||
config.skipIde = toolSelection.skipIde;
|
config.skipIde = toolSelection.skipIde;
|
||||||
const ideConfigurations = toolSelection.configurations;
|
const ideConfigurations = toolSelection.configurations;
|
||||||
|
|
||||||
|
// Add spacing after prompts before installation progress
|
||||||
|
console.log('');
|
||||||
|
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.text = 'Continuing installation...';
|
spinner.text = 'Continuing installation...';
|
||||||
} else {
|
} else {
|
||||||
@@ -2139,15 +2143,11 @@ class Installer {
|
|||||||
* Private: Prompt for update action
|
* Private: Prompt for update action
|
||||||
*/
|
*/
|
||||||
async promptUpdateAction() {
|
async promptUpdateAction() {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const action = await prompts.select({
|
||||||
return await inquirer.prompt([
|
message: 'What would you like to do?',
|
||||||
{
|
choices: [{ name: 'Update existing installation', value: 'update' }],
|
||||||
type: 'list',
|
});
|
||||||
name: 'action',
|
return { action };
|
||||||
message: 'What would you like to do?',
|
|
||||||
choices: [{ name: 'Update existing installation', value: 'update' }],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2156,8 +2156,6 @@ 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) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
||||||
console.log(chalk.yellow('─'.repeat(80)));
|
console.log(chalk.yellow('─'.repeat(80)));
|
||||||
@@ -2172,26 +2170,22 @@ class Installer {
|
|||||||
console.log(chalk.dim('If your v4 installation set up rules or commands, you should remove those as well.'));
|
console.log(chalk.dim('If your v4 installation set up rules or commands, you should remove those as well.'));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const { proceed } = await inquirer.prompt([
|
const proceed = await prompts.select({
|
||||||
{
|
message: 'What would you like to do?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'proceed',
|
{
|
||||||
message: 'What would you like to do?',
|
name: 'Exit and clean up manually (recommended)',
|
||||||
choices: [
|
value: 'exit',
|
||||||
{
|
hint: 'Exit installation',
|
||||||
name: 'Exit and clean up manually (recommended)',
|
},
|
||||||
value: 'exit',
|
{
|
||||||
short: 'Exit installation',
|
name: 'Continue with installation anyway',
|
||||||
},
|
value: 'continue',
|
||||||
{
|
hint: 'Continue',
|
||||||
name: 'Continue with installation anyway',
|
},
|
||||||
value: 'continue',
|
],
|
||||||
short: 'Continue',
|
default: 'exit',
|
||||||
},
|
});
|
||||||
],
|
|
||||||
default: 'exit',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (proceed === 'exit') {
|
if (proceed === 'exit') {
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -2437,7 +2431,6 @@ class Installer {
|
|||||||
|
|
||||||
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
||||||
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
let keptCount = 0;
|
let keptCount = 0;
|
||||||
let updatedCount = 0;
|
let updatedCount = 0;
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
@@ -2451,12 +2444,12 @@ class Installer {
|
|||||||
{
|
{
|
||||||
name: 'Keep installed (will not be processed)',
|
name: 'Keep installed (will not be processed)',
|
||||||
value: 'keep',
|
value: 'keep',
|
||||||
short: 'Keep',
|
hint: 'Keep',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Specify new source location',
|
name: 'Specify new source location',
|
||||||
value: 'update',
|
value: 'update',
|
||||||
short: 'Update',
|
hint: 'Update',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -2465,47 +2458,40 @@ class Installer {
|
|||||||
choices.push({
|
choices.push({
|
||||||
name: '⚠️ REMOVE module completely (destructive!)',
|
name: '⚠️ REMOVE module completely (destructive!)',
|
||||||
value: 'remove',
|
value: 'remove',
|
||||||
short: 'Remove',
|
hint: 'Remove',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { action } = await inquirer.prompt([
|
const action = await prompts.select({
|
||||||
{
|
message: `How would you like to handle "${missing.name}"?`,
|
||||||
type: 'list',
|
choices,
|
||||||
name: 'action',
|
});
|
||||||
message: `How would you like to handle "${missing.name}"?`,
|
|
||||||
choices,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'update': {
|
case 'update': {
|
||||||
const { newSourcePath } = await inquirer.prompt([
|
// Use sync validation because @clack/prompts doesn't support async validate
|
||||||
{
|
const newSourcePath = await prompts.text({
|
||||||
type: 'input',
|
message: 'Enter the new path to the custom module:',
|
||||||
name: 'newSourcePath',
|
default: missing.sourcePath,
|
||||||
message: 'Enter the new path to the custom module:',
|
validate: (input) => {
|
||||||
default: missing.sourcePath,
|
if (!input || input.trim() === '') {
|
||||||
validate: async (input) => {
|
return 'Please enter a path';
|
||||||
if (!input || input.trim() === '') {
|
}
|
||||||
return 'Please enter a path';
|
const expandedPath = path.resolve(input.trim());
|
||||||
}
|
if (!fs.pathExistsSync(expandedPath)) {
|
||||||
const expandedPath = path.resolve(input.trim());
|
return 'Path does not exist';
|
||||||
if (!(await fs.pathExists(expandedPath))) {
|
}
|
||||||
return 'Path does not exist';
|
// Check if it looks like a valid module
|
||||||
}
|
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||||
// Check if it looks like a valid module
|
const agentsPath = path.join(expandedPath, 'agents');
|
||||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
const workflowsPath = path.join(expandedPath, 'workflows');
|
||||||
const agentsPath = path.join(expandedPath, 'agents');
|
|
||||||
const workflowsPath = path.join(expandedPath, 'workflows');
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(moduleYamlPath)) && !(await fs.pathExists(agentsPath)) && !(await fs.pathExists(workflowsPath))) {
|
if (!fs.pathExistsSync(moduleYamlPath) && !fs.pathExistsSync(agentsPath) && !fs.pathExistsSync(workflowsPath)) {
|
||||||
return 'Path does not appear to contain a valid custom module';
|
return 'Path does not appear to contain a valid custom module';
|
||||||
}
|
}
|
||||||
return true;
|
return; // clack expects undefined for valid input
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
});
|
||||||
|
|
||||||
// Update the source in manifest
|
// Update the source in manifest
|
||||||
const resolvedPath = path.resolve(newSourcePath.trim());
|
const resolvedPath = path.resolve(newSourcePath.trim());
|
||||||
@@ -2531,46 +2517,38 @@ class Installer {
|
|||||||
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
||||||
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
|
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
|
||||||
|
|
||||||
const { confirm } = await inquirer.prompt([
|
const confirmDelete = await prompts.confirm({
|
||||||
{
|
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'confirm',
|
});
|
||||||
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (confirm) {
|
if (confirmDelete) {
|
||||||
const { typedConfirm } = await inquirer.prompt([
|
const typedConfirm = await prompts.text({
|
||||||
{
|
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
||||||
type: 'input',
|
validate: (input) => {
|
||||||
name: 'typedConfirm',
|
if (input !== 'DELETE') {
|
||||||
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
return chalk.red('You must type "DELETE" exactly to proceed');
|
||||||
validate: (input) => {
|
}
|
||||||
if (input !== 'DELETE') {
|
return; // clack expects undefined for valid input
|
||||||
return chalk.red('You must type "DELETE" exactly to proceed');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
});
|
||||||
|
|
||||||
if (typedConfirm === 'DELETE') {
|
if (typedConfirm === 'DELETE') {
|
||||||
// Remove the module from filesystem and manifest
|
// Remove the module from filesystem and manifest
|
||||||
const modulePath = path.join(bmadDir, moduleId);
|
const modulePath = path.join(bmadDir, missing.id);
|
||||||
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)}`));
|
console.log(chalk.yellow(` ✓ Deleted module directory: ${path.relative(projectRoot, modulePath)}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.manifest.removeModule(bmadDir, moduleId);
|
await this.manifest.removeModule(bmadDir, missing.id);
|
||||||
await this.manifest.removeCustomModule(bmadDir, moduleId);
|
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
||||||
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
||||||
|
|
||||||
// Also remove from installedModules list
|
// Also remove from installedModules list
|
||||||
if (installedModules && installedModules.includes(moduleId)) {
|
if (installedModules && installedModules.includes(missing.id)) {
|
||||||
const index = installedModules.indexOf(moduleId);
|
const index = installedModules.indexOf(missing.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
installedModules.splice(index, 1);
|
installedModules.splice(index, 1);
|
||||||
}
|
}
|
||||||
@@ -2591,7 +2569,7 @@ class Installer {
|
|||||||
}
|
}
|
||||||
case 'keep': {
|
case 'keep': {
|
||||||
keptCount++;
|
keptCount++;
|
||||||
keptModulesWithoutSources.push(moduleId);
|
keptModulesWithoutSources.push(missing.id);
|
||||||
console.log(chalk.dim(` Module will be kept as-is`));
|
console.log(chalk.dim(` Module will be kept as-is`));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const {
|
|||||||
resolveSubagentFiles,
|
resolveSubagentFiles,
|
||||||
} = require('./shared/module-injections');
|
} = require('./shared/module-injections');
|
||||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Google Antigravity IDE setup handler
|
* Google Antigravity IDE setup handler
|
||||||
@@ -26,6 +27,21 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
this.workflowsDir = 'workflows';
|
this.workflowsDir = 'workflows';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt for subagent installation location
|
||||||
|
* @returns {Promise<string>} Selected location ('project' or 'user')
|
||||||
|
*/
|
||||||
|
async _promptInstallLocation() {
|
||||||
|
return prompts.select({
|
||||||
|
message: 'Where would you like to install Antigravity subagents?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
||||||
|
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
||||||
|
],
|
||||||
|
default: 'project',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect configuration choices before installation
|
* Collect configuration choices before installation
|
||||||
* @param {Object} options - Configuration options
|
* @param {Object} options - Configuration options
|
||||||
@@ -57,21 +73,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
||||||
|
|
||||||
if (config.subagentChoices.install !== 'none') {
|
if (config.subagentChoices.install !== 'none') {
|
||||||
// Ask for installation location
|
config.installLocation = await this._promptInstallLocation();
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
const locationAnswer = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'location',
|
|
||||||
message: 'Where would you like to install Antigravity subagents?',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
|
||||||
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
|
||||||
],
|
|
||||||
default: 'project',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
config.installLocation = locationAnswer.location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -297,20 +299,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
choices = await this.promptSubagentInstallation(config.subagents);
|
choices = await this.promptSubagentInstallation(config.subagents);
|
||||||
|
|
||||||
if (choices.install !== 'none') {
|
if (choices.install !== 'none') {
|
||||||
const { default: inquirer } = await import('inquirer');
|
location = await this._promptInstallLocation();
|
||||||
const locationAnswer = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'location',
|
|
||||||
message: 'Where would you like to install Antigravity subagents?',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
|
||||||
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
|
||||||
],
|
|
||||||
default: 'project',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
location = locationAnswer.location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,22 +323,16 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
* Prompt user for subagent installation preferences
|
* Prompt user for subagent installation preferences
|
||||||
*/
|
*/
|
||||||
async promptSubagentInstallation(subagentConfig) {
|
async promptSubagentInstallation(subagentConfig) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
// First ask if they want to install subagents
|
// First ask if they want to install subagents
|
||||||
const { install } = await inquirer.prompt([
|
const install = await prompts.select({
|
||||||
{
|
message: 'Would you like to install Antigravity subagents for enhanced functionality?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'install',
|
{ name: 'Yes, install all subagents', value: 'all' },
|
||||||
message: 'Would you like to install Antigravity subagents for enhanced functionality?',
|
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
|
||||||
choices: [
|
{ name: 'No, skip subagent installation', value: 'none' },
|
||||||
{ name: 'Yes, install all subagents', value: 'all' },
|
],
|
||||||
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
|
default: 'all',
|
||||||
{ name: 'No, skip subagent installation', value: 'none' },
|
});
|
||||||
],
|
|
||||||
default: 'all',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (install === 'selective') {
|
if (install === 'selective') {
|
||||||
// Show list of available subagents with descriptions
|
// Show list of available subagents with descriptions
|
||||||
@@ -361,18 +344,14 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
'document-reviewer.md': 'Document quality review',
|
'document-reviewer.md': 'Document quality review',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { selected } = await inquirer.prompt([
|
const selected = await prompts.multiselect({
|
||||||
{
|
message: `Select subagents to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
type: 'checkbox',
|
choices: subagentConfig.files.map((file) => ({
|
||||||
name: 'selected',
|
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
||||||
message: 'Select subagents to install:',
|
value: file,
|
||||||
choices: subagentConfig.files.map((file) => ({
|
checked: true,
|
||||||
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
})),
|
||||||
value: file,
|
});
|
||||||
checked: true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { install: 'selective', selected };
|
return { install: 'selective', selected };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const {
|
|||||||
resolveSubagentFiles,
|
resolveSubagentFiles,
|
||||||
} = require('./shared/module-injections');
|
} = require('./shared/module-injections');
|
||||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Claude Code IDE setup handler
|
* Claude Code IDE setup handler
|
||||||
@@ -25,6 +26,21 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
this.agentsDir = 'agents';
|
this.agentsDir = 'agents';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt for subagent installation location
|
||||||
|
* @returns {Promise<string>} Selected location ('project' or 'user')
|
||||||
|
*/
|
||||||
|
async promptInstallLocation() {
|
||||||
|
return prompts.select({
|
||||||
|
message: 'Where would you like to install Claude Code subagents?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
||||||
|
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
||||||
|
],
|
||||||
|
default: 'project',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect configuration choices before installation
|
* Collect configuration choices before installation
|
||||||
* @param {Object} options - Configuration options
|
* @param {Object} options - Configuration options
|
||||||
@@ -56,21 +72,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
||||||
|
|
||||||
if (config.subagentChoices.install !== 'none') {
|
if (config.subagentChoices.install !== 'none') {
|
||||||
// Ask for installation location
|
config.installLocation = await this.promptInstallLocation();
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
const locationAnswer = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'location',
|
|
||||||
message: 'Where would you like to install Claude Code subagents?',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
|
||||||
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
|
||||||
],
|
|
||||||
default: 'project',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
config.installLocation = locationAnswer.location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -305,20 +307,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
choices = await this.promptSubagentInstallation(config.subagents);
|
choices = await this.promptSubagentInstallation(config.subagents);
|
||||||
|
|
||||||
if (choices.install !== 'none') {
|
if (choices.install !== 'none') {
|
||||||
const { default: inquirer } = await import('inquirer');
|
location = await this.promptInstallLocation();
|
||||||
const locationAnswer = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'location',
|
|
||||||
message: 'Where would you like to install Claude Code subagents?',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
|
||||||
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
|
||||||
],
|
|
||||||
default: 'project',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
location = locationAnswer.location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,22 +331,16 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
* Prompt user for subagent installation preferences
|
* Prompt user for subagent installation preferences
|
||||||
*/
|
*/
|
||||||
async promptSubagentInstallation(subagentConfig) {
|
async promptSubagentInstallation(subagentConfig) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
// First ask if they want to install subagents
|
// First ask if they want to install subagents
|
||||||
const { install } = await inquirer.prompt([
|
const install = await prompts.select({
|
||||||
{
|
message: 'Would you like to install Claude Code subagents for enhanced functionality?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'install',
|
{ name: 'Yes, install all subagents', value: 'all' },
|
||||||
message: 'Would you like to install Claude Code subagents for enhanced functionality?',
|
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
|
||||||
choices: [
|
{ name: 'No, skip subagent installation', value: 'none' },
|
||||||
{ name: 'Yes, install all subagents', value: 'all' },
|
],
|
||||||
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
|
default: 'all',
|
||||||
{ name: 'No, skip subagent installation', value: 'none' },
|
});
|
||||||
],
|
|
||||||
default: 'all',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (install === 'selective') {
|
if (install === 'selective') {
|
||||||
// Show list of available subagents with descriptions
|
// Show list of available subagents with descriptions
|
||||||
@@ -369,18 +352,14 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
'document-reviewer.md': 'Document quality review',
|
'document-reviewer.md': 'Document quality review',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { selected } = await inquirer.prompt([
|
const selected = await prompts.multiselect({
|
||||||
{
|
message: `Select subagents to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
type: 'checkbox',
|
options: subagentConfig.files.map((file) => ({
|
||||||
name: 'selected',
|
label: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
||||||
message: 'Select subagents to install:',
|
value: file,
|
||||||
choices: subagentConfig.files.map((file) => ({
|
})),
|
||||||
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
initialValues: subagentConfig.files,
|
||||||
value: file,
|
});
|
||||||
checked: true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { install: 'selective', selected };
|
return { install: 'selective', selected };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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');
|
||||||
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Codex setup handler (CLI mode)
|
* Codex setup handler (CLI mode)
|
||||||
@@ -21,32 +22,24 @@ class CodexSetup extends BaseIdeSetup {
|
|||||||
* @returns {Object} Collected configuration
|
* @returns {Object} Collected configuration
|
||||||
*/
|
*/
|
||||||
async collectConfiguration(options = {}) {
|
async collectConfiguration(options = {}) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
let confirmed = false;
|
let confirmed = false;
|
||||||
let installLocation = 'global';
|
let installLocation = 'global';
|
||||||
|
|
||||||
while (!confirmed) {
|
while (!confirmed) {
|
||||||
const { location } = await inquirer.prompt([
|
installLocation = await prompts.select({
|
||||||
{
|
message: 'Where would you like to install Codex CLI prompts?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'location',
|
{
|
||||||
message: 'Where would you like to install Codex CLI prompts?',
|
name: 'Global - Simple for single project ' + '(~/.codex/prompts, but references THIS project only)',
|
||||||
choices: [
|
value: 'global',
|
||||||
{
|
},
|
||||||
name: 'Global - Simple for single project ' + '(~/.codex/prompts, but references THIS project only)',
|
{
|
||||||
value: 'global',
|
name: `Project-specific - Recommended for real work (requires CODEX_HOME=<project-dir>${path.sep}.codex)`,
|
||||||
},
|
value: 'project',
|
||||||
{
|
},
|
||||||
name: `Project-specific - Recommended for real work (requires CODEX_HOME=<project-dir>${path.sep}.codex)`,
|
],
|
||||||
value: 'project',
|
default: 'global',
|
||||||
},
|
});
|
||||||
],
|
|
||||||
default: 'global',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
installLocation = location;
|
|
||||||
|
|
||||||
// Display detailed instructions for the chosen option
|
// Display detailed instructions for the chosen option
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -57,16 +50,10 @@ class CodexSetup extends BaseIdeSetup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the choice
|
// Confirm the choice
|
||||||
const { proceed } = await inquirer.prompt([
|
confirmed = await prompts.confirm({
|
||||||
{
|
message: 'Proceed with this installation option?',
|
||||||
type: 'confirm',
|
default: true,
|
||||||
name: 'proceed',
|
});
|
||||||
message: 'Proceed with this installation option?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
confirmed = proceed;
|
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
|
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const path = require('node:path');
|
|||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GitHub Copilot setup handler
|
* GitHub Copilot setup handler
|
||||||
@@ -21,29 +22,23 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||||||
* @returns {Object} Collected configuration
|
* @returns {Object} Collected configuration
|
||||||
*/
|
*/
|
||||||
async collectConfiguration(options = {}) {
|
async collectConfiguration(options = {}) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
const config = {};
|
const config = {};
|
||||||
|
|
||||||
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
|
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
|
||||||
console.log(chalk.dim(' GitHub Copilot works best with specific settings\n'));
|
console.log(chalk.dim(' GitHub Copilot works best with specific settings\n'));
|
||||||
|
|
||||||
const response = await inquirer.prompt([
|
config.vsCodeConfig = await prompts.select({
|
||||||
{
|
message: 'How would you like to configure VS Code settings?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'configChoice',
|
{ name: 'Use recommended defaults (fastest)', value: 'defaults' },
|
||||||
message: 'How would you like to configure VS Code settings?',
|
{ name: 'Configure each setting manually', value: 'manual' },
|
||||||
choices: [
|
{ name: 'Skip settings configuration', value: 'skip' },
|
||||||
{ name: 'Use recommended defaults (fastest)', value: 'defaults' },
|
],
|
||||||
{ name: 'Configure each setting manually', value: 'manual' },
|
default: 'defaults',
|
||||||
{ name: 'Skip settings configuration', value: 'skip' },
|
});
|
||||||
],
|
|
||||||
default: 'defaults',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
config.vsCodeConfig = response.configChoice;
|
|
||||||
|
|
||||||
if (response.configChoice === 'manual') {
|
if (config.vsCodeConfig === 'manual') {
|
||||||
config.manualSettings = await inquirer.prompt([
|
config.manualSettings = await prompts.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'maxRequests',
|
name: 'maxRequests',
|
||||||
@@ -52,7 +47,8 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
const num = parseInt(input, 10);
|
const num = parseInt(input, 10);
|
||||||
if (isNaN(num)) return 'Enter a valid number 1-50';
|
if (isNaN(num)) return 'Enter a valid number 1-50';
|
||||||
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
if (num < 1 || num > 50) return 'Enter a number between 1-50';
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
432
tools/cli/lib/prompts.js
Normal file
432
tools/cli/lib/prompts.js
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/**
|
||||||
|
* @clack/prompts wrapper for BMAD CLI
|
||||||
|
*
|
||||||
|
* This module provides a unified interface for CLI prompts using @clack/prompts.
|
||||||
|
* It replaces Inquirer.js to fix Windows arrow key navigation issues (libuv #852).
|
||||||
|
*
|
||||||
|
* @module prompts
|
||||||
|
*/
|
||||||
|
|
||||||
|
let _clack = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-load @clack/prompts (ESM module)
|
||||||
|
* @returns {Promise<Object>} The clack prompts module
|
||||||
|
*/
|
||||||
|
async function getClack() {
|
||||||
|
if (!_clack) {
|
||||||
|
_clack = await import('@clack/prompts');
|
||||||
|
}
|
||||||
|
return _clack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle user cancellation gracefully
|
||||||
|
* @param {any} value - The value to check
|
||||||
|
* @param {string} [message='Operation cancelled'] - Message to display
|
||||||
|
* @returns {boolean} True if cancelled
|
||||||
|
*/
|
||||||
|
async function handleCancel(value, message = 'Operation cancelled') {
|
||||||
|
const clack = await getClack();
|
||||||
|
if (clack.isCancel(value)) {
|
||||||
|
clack.cancel(message);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display intro message
|
||||||
|
* @param {string} message - The intro message
|
||||||
|
*/
|
||||||
|
async function intro(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.intro(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display outro message
|
||||||
|
* @param {string} message - The outro message
|
||||||
|
*/
|
||||||
|
async function outro(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.outro(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a note/info box
|
||||||
|
* @param {string} message - The note content
|
||||||
|
* @param {string} [title] - Optional title
|
||||||
|
*/
|
||||||
|
async function note(message, title) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.note(message, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a spinner for async operations
|
||||||
|
* @returns {Object} Spinner controller with start, stop, message methods
|
||||||
|
*/
|
||||||
|
async function spinner() {
|
||||||
|
const clack = await getClack();
|
||||||
|
return clack.spinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-select prompt (replaces Inquirer 'list' type)
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {Array} options.choices - Array of choices [{name, value, hint?}]
|
||||||
|
* @param {any} [options.default] - Default selected value
|
||||||
|
* @returns {Promise<any>} Selected value
|
||||||
|
*/
|
||||||
|
async function select(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
// Convert Inquirer-style choices to clack format
|
||||||
|
// Handle both object choices {name, value, hint} and primitive choices (string/number)
|
||||||
|
const clackOptions = options.choices
|
||||||
|
.filter((c) => c.type !== 'separator') // Skip separators for now
|
||||||
|
.map((choice) => {
|
||||||
|
if (typeof choice === 'string' || typeof choice === 'number') {
|
||||||
|
return { value: choice, label: String(choice) };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: choice.value === undefined ? choice.name : choice.value,
|
||||||
|
label: choice.name || choice.label || String(choice.value),
|
||||||
|
hint: choice.hint || choice.description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find initial value
|
||||||
|
let initialValue;
|
||||||
|
if (options.default !== undefined) {
|
||||||
|
initialValue = options.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await clack.select({
|
||||||
|
message: options.message,
|
||||||
|
options: clackOptions,
|
||||||
|
initialValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-select prompt (replaces Inquirer 'checkbox' type)
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {Array} options.choices - Array of choices [{name, value, checked?, hint?}]
|
||||||
|
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||||
|
* @returns {Promise<Array>} Array of selected values
|
||||||
|
*/
|
||||||
|
async function multiselect(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
// Support both clack-native (options) and Inquirer-style (choices) APIs
|
||||||
|
let clackOptions;
|
||||||
|
let initialValues;
|
||||||
|
|
||||||
|
if (options.options) {
|
||||||
|
// Native clack format: options with label/value
|
||||||
|
clackOptions = options.options;
|
||||||
|
initialValues = options.initialValues || [];
|
||||||
|
} else {
|
||||||
|
// Convert Inquirer-style choices to clack format
|
||||||
|
// Handle both object choices {name, value, hint} and primitive choices (string/number)
|
||||||
|
clackOptions = options.choices
|
||||||
|
.filter((c) => c.type !== 'separator') // Skip separators
|
||||||
|
.map((choice) => {
|
||||||
|
if (typeof choice === 'string' || typeof choice === 'number') {
|
||||||
|
return { value: choice, label: String(choice) };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: choice.value === undefined ? choice.name : choice.value,
|
||||||
|
label: choice.name || choice.label || String(choice.value),
|
||||||
|
hint: choice.hint || choice.description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find initial values (pre-checked items)
|
||||||
|
initialValues = options.choices
|
||||||
|
.filter((c) => c.checked && c.type !== 'separator')
|
||||||
|
.map((c) => (c.value === undefined ? c.name : c.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await clack.multiselect({
|
||||||
|
message: options.message,
|
||||||
|
options: clackOptions,
|
||||||
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||||
|
required: options.required || false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(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,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm prompt (replaces Inquirer 'confirm' type)
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {boolean} [options.default=true] - Default value
|
||||||
|
* @returns {Promise<boolean>} User's answer
|
||||||
|
*/
|
||||||
|
async function confirm(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
const result = await clack.confirm({
|
||||||
|
message: options.message,
|
||||||
|
initialValue: options.default === undefined ? true : options.default,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text input prompt (replaces Inquirer 'input' type)
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {string} [options.default] - Default value
|
||||||
|
* @param {string} [options.placeholder] - Placeholder text (defaults to options.default if not provided)
|
||||||
|
* @param {Function} [options.validate] - Validation function
|
||||||
|
* @returns {Promise<string>} User's input
|
||||||
|
*/
|
||||||
|
async function text(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
// Use default as placeholder if placeholder not explicitly provided
|
||||||
|
// This shows the default value as grayed-out hint text
|
||||||
|
const placeholder = options.placeholder === undefined ? options.default : options.placeholder;
|
||||||
|
|
||||||
|
const result = await clack.text({
|
||||||
|
message: options.message,
|
||||||
|
defaultValue: options.default,
|
||||||
|
placeholder: typeof placeholder === 'string' ? placeholder : undefined,
|
||||||
|
validate: options.validate,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password input prompt (replaces Inquirer 'password' type)
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {Function} [options.validate] - Validation function
|
||||||
|
* @returns {Promise<string>} User's input
|
||||||
|
*/
|
||||||
|
async function password(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
const result = await clack.password({
|
||||||
|
message: options.message,
|
||||||
|
validate: options.validate,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group multiple prompts together
|
||||||
|
* @param {Object} prompts - Object of prompt functions
|
||||||
|
* @param {Object} [options] - Group options
|
||||||
|
* @returns {Promise<Object>} Object with all answers
|
||||||
|
*/
|
||||||
|
async function group(prompts, options = {}) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
const result = await clack.group(prompts, {
|
||||||
|
onCancel: () => {
|
||||||
|
clack.cancel('Operation cancelled');
|
||||||
|
process.exit(0);
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks with spinner feedback
|
||||||
|
* @param {Array} tasks - Array of task objects [{title, task, enabled?}]
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function tasks(taskList) {
|
||||||
|
const clack = await getClack();
|
||||||
|
await clack.tasks(taskList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log messages with styling
|
||||||
|
*/
|
||||||
|
const log = {
|
||||||
|
async info(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.info(message);
|
||||||
|
},
|
||||||
|
async success(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.success(message);
|
||||||
|
},
|
||||||
|
async warn(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.warn(message);
|
||||||
|
},
|
||||||
|
async error(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.error(message);
|
||||||
|
},
|
||||||
|
async message(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.message(message);
|
||||||
|
},
|
||||||
|
async step(message) {
|
||||||
|
const clack = await getClack();
|
||||||
|
clack.log.step(message);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an array of Inquirer-style questions using @clack/prompts
|
||||||
|
* This provides compatibility with dynamic question arrays
|
||||||
|
* @param {Array} questions - Array of Inquirer-style question objects
|
||||||
|
* @returns {Promise<Object>} Object with answers keyed by question name
|
||||||
|
*/
|
||||||
|
async function prompt(questions) {
|
||||||
|
const answers = {};
|
||||||
|
|
||||||
|
for (const question of questions) {
|
||||||
|
const { type, name, message, choices, default: defaultValue, validate, when } = question;
|
||||||
|
|
||||||
|
// Handle conditional questions via 'when' property
|
||||||
|
if (when !== undefined) {
|
||||||
|
const shouldAsk = typeof when === 'function' ? await when(answers) : when;
|
||||||
|
if (!shouldAsk) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let answer;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'input': {
|
||||||
|
// Note: @clack/prompts doesn't support async validation, so validate must be sync
|
||||||
|
answer = await text({
|
||||||
|
message,
|
||||||
|
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||||
|
validate: validate
|
||||||
|
? (val) => {
|
||||||
|
const result = validate(val, answers);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
throw new TypeError('Async validation is not supported by @clack/prompts. Please use synchronous validation.');
|
||||||
|
}
|
||||||
|
return result === true ? undefined : result;
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'confirm': {
|
||||||
|
answer = await confirm({
|
||||||
|
message,
|
||||||
|
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'list': {
|
||||||
|
answer = await select({
|
||||||
|
message,
|
||||||
|
choices: choices || [],
|
||||||
|
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'checkbox': {
|
||||||
|
answer = await multiselect({
|
||||||
|
message,
|
||||||
|
choices: choices || [],
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'password': {
|
||||||
|
// Note: @clack/prompts doesn't support async validation, so validate must be sync
|
||||||
|
answer = await password({
|
||||||
|
message,
|
||||||
|
validate: validate
|
||||||
|
? (val) => {
|
||||||
|
const result = validate(val, answers);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
throw new TypeError('Async validation is not supported by @clack/prompts. Please use synchronous validation.');
|
||||||
|
}
|
||||||
|
return result === true ? undefined : result;
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// Default to text input for unknown types
|
||||||
|
answer = await text({
|
||||||
|
message,
|
||||||
|
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
answers[name] = answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getClack,
|
||||||
|
handleCancel,
|
||||||
|
intro,
|
||||||
|
outro,
|
||||||
|
note,
|
||||||
|
spinner,
|
||||||
|
select,
|
||||||
|
multiselect,
|
||||||
|
groupMultiselect,
|
||||||
|
confirm,
|
||||||
|
text,
|
||||||
|
password,
|
||||||
|
group,
|
||||||
|
tasks,
|
||||||
|
log,
|
||||||
|
prompt,
|
||||||
|
};
|
||||||
@@ -4,16 +4,21 @@ const os = require('node:os');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { CLIUtils } = require('./cli-utils');
|
const { CLIUtils } = require('./cli-utils');
|
||||||
const { CustomHandler } = require('../installers/lib/custom/handler');
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
|
const prompts = require('./prompts');
|
||||||
|
|
||||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
// Separator class for visual grouping in select/multiselect prompts
|
||||||
let _inquirer = null;
|
// Note: @clack/prompts doesn't support separators natively, they are filtered out
|
||||||
async function getInquirer() {
|
class Separator {
|
||||||
if (!_inquirer) {
|
constructor(text = '────────') {
|
||||||
_inquirer = (await import('inquirer')).default;
|
this.line = text;
|
||||||
|
this.name = text;
|
||||||
}
|
}
|
||||||
return _inquirer;
|
type = 'separator';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Separator for choice lists (compatible interface)
|
||||||
|
const choiceUtils = { Separator };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI utilities for the installer
|
* UI utilities for the installer
|
||||||
*/
|
*/
|
||||||
@@ -23,7 +28,6 @@ class UI {
|
|||||||
* @returns {Object} Installation configuration
|
* @returns {Object} Installation configuration
|
||||||
*/
|
*/
|
||||||
async promptInstall() {
|
async promptInstall() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
CLIUtils.displayLogo();
|
CLIUtils.displayLogo();
|
||||||
|
|
||||||
// Display version-specific start message from install-messages.yaml
|
// Display version-specific start message from install-messages.yaml
|
||||||
@@ -113,26 +117,20 @@ class UI {
|
|||||||
console.log(chalk.yellow('─'.repeat(80)));
|
console.log(chalk.yellow('─'.repeat(80)));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const { proceed } = await inquirer.prompt([
|
const proceed = await prompts.select({
|
||||||
{
|
message: 'What would you like to do?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'proceed',
|
{
|
||||||
message: 'What would you like to do?',
|
name: 'Cancel and do a fresh install (recommended)',
|
||||||
choices: [
|
value: 'cancel',
|
||||||
{
|
},
|
||||||
name: 'Cancel and do a fresh install (recommended)',
|
{
|
||||||
value: 'cancel',
|
name: 'Proceed anyway (will attempt update, potentially may fail or have unstable behavior)',
|
||||||
short: 'Cancel installation',
|
value: 'proceed',
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
name: 'Proceed anyway (will attempt update, potentially may fail or have unstable behavior)',
|
default: 'cancel',
|
||||||
value: 'proceed',
|
});
|
||||||
short: 'Proceed with update',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
default: 'cancel',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (proceed === 'cancel') {
|
if (proceed === 'cancel') {
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -188,14 +186,10 @@ class UI {
|
|||||||
|
|
||||||
// If Claude Code was selected, ask about TTS
|
// If Claude Code was selected, ask about TTS
|
||||||
if (claudeCodeSelected) {
|
if (claudeCodeSelected) {
|
||||||
const { enableTts } = await inquirer.prompt([
|
const enableTts = await prompts.confirm({
|
||||||
{
|
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'enableTts',
|
});
|
||||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (enableTts) {
|
if (enableTts) {
|
||||||
agentVibesConfig = { enabled: true, alreadyInstalled: false };
|
agentVibesConfig = { enabled: true, alreadyInstalled: false };
|
||||||
@@ -250,18 +244,11 @@ class UI {
|
|||||||
// Common actions
|
// Common actions
|
||||||
choices.push({ name: 'Modify BMAD Installation', value: 'update' });
|
choices.push({ name: 'Modify BMAD Installation', value: 'update' });
|
||||||
|
|
||||||
const promptResult = await inquirer.prompt([
|
actionType = await prompts.select({
|
||||||
{
|
message: 'What would you like to do?',
|
||||||
type: 'list',
|
choices: choices,
|
||||||
name: 'actionType',
|
default: choices[0].value,
|
||||||
message: 'What would you like to do?',
|
});
|
||||||
choices: choices,
|
|
||||||
default: choices[0].value, // Use the first option as default
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Extract actionType from prompt result
|
|
||||||
actionType = promptResult.actionType;
|
|
||||||
|
|
||||||
// Handle quick update separately
|
// Handle quick update separately
|
||||||
if (actionType === 'quick-update') {
|
if (actionType === 'quick-update') {
|
||||||
@@ -290,14 +277,10 @@ class UI {
|
|||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||||
|
|
||||||
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
||||||
const { changeModuleSelection } = await inquirer.prompt([
|
const changeModuleSelection = await prompts.confirm({
|
||||||
{
|
message: 'Modify official module selection (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'changeModuleSelection',
|
});
|
||||||
message: 'Modify official module selection (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let selectedModules = [];
|
let selectedModules = [];
|
||||||
if (changeModuleSelection) {
|
if (changeModuleSelection) {
|
||||||
@@ -310,14 +293,10 @@ class UI {
|
|||||||
|
|
||||||
// After module selection, ask about custom modules
|
// After module selection, ask about custom modules
|
||||||
console.log('');
|
console.log('');
|
||||||
const { changeCustomModules } = await inquirer.prompt([
|
const changeCustomModules = await prompts.confirm({
|
||||||
{
|
message: 'Modify custom module selection (add, update, or remove custom modules/agents/workflows)?',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'changeCustomModules',
|
});
|
||||||
message: 'Modify custom module selection (add, update, or remove custom modules/agents/workflows)?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||||
if (changeCustomModules) {
|
if (changeCustomModules) {
|
||||||
@@ -352,15 +331,10 @@ class UI {
|
|||||||
let enableTts = false;
|
let enableTts = false;
|
||||||
|
|
||||||
if (hasClaudeCode) {
|
if (hasClaudeCode) {
|
||||||
const { enableTts: enable } = await inquirer.prompt([
|
enableTts = await prompts.confirm({
|
||||||
{
|
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'enableTts',
|
});
|
||||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
enableTts = enable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core config with existing defaults (ask after TTS)
|
// Core config with existing defaults (ask after TTS)
|
||||||
@@ -385,14 +359,10 @@ class UI {
|
|||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||||
|
|
||||||
// Ask about official modules for new installations
|
// Ask about official modules for new installations
|
||||||
const { wantsOfficialModules } = await inquirer.prompt([
|
const wantsOfficialModules = await prompts.confirm({
|
||||||
{
|
message: 'Will you be installing any official BMad modules (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
||||||
type: 'confirm',
|
default: true,
|
||||||
name: 'wantsOfficialModules',
|
});
|
||||||
message: 'Will you be installing any official BMad modules (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let selectedOfficialModules = [];
|
let selectedOfficialModules = [];
|
||||||
if (wantsOfficialModules) {
|
if (wantsOfficialModules) {
|
||||||
@@ -401,14 +371,10 @@ class UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ask about custom content
|
// Ask about custom content
|
||||||
const { wantsCustomContent } = await inquirer.prompt([
|
const wantsCustomContent = await prompts.confirm({
|
||||||
{
|
message: 'Would you like to install a local custom module (this includes custom agents and workflows also)?',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'wantsCustomContent',
|
});
|
||||||
message: 'Would you like to install a local custom module (this includes custom agents and workflows also)?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (wantsCustomContent) {
|
if (wantsCustomContent) {
|
||||||
customContentConfig = await this.promptCustomContentSource();
|
customContentConfig = await this.promptCustomContentSource();
|
||||||
@@ -459,7 +425,6 @@ class UI {
|
|||||||
* @returns {Object} Tool configuration
|
* @returns {Object} Tool configuration
|
||||||
*/
|
*/
|
||||||
async promptToolSelection(projectDir, selectedModules) {
|
async promptToolSelection(projectDir, selectedModules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
||||||
const { Detector } = require('../installers/lib/core/detector');
|
const { Detector } = require('../installers/lib/core/detector');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
@@ -477,13 +442,14 @@ class UI {
|
|||||||
const preferredIdes = ideManager.getPreferredIdes();
|
const preferredIdes = ideManager.getPreferredIdes();
|
||||||
const otherIdes = ideManager.getOtherIdes();
|
const otherIdes = ideManager.getOtherIdes();
|
||||||
|
|
||||||
// Build IDE choices array with separators
|
// Build grouped options object for groupMultiselect
|
||||||
const ideChoices = [];
|
const groupedOptions = {};
|
||||||
const processedIdes = new Set();
|
const processedIdes = new Set();
|
||||||
|
const initialValues = [];
|
||||||
|
|
||||||
// First, add previously configured IDEs at the top, marked with ✅
|
// First, add previously configured IDEs at the top, marked with ✅
|
||||||
if (configuredIdes.length > 0) {
|
if (configuredIdes.length > 0) {
|
||||||
ideChoices.push(new inquirer.Separator('── Previously Configured ──'));
|
const configuredGroup = [];
|
||||||
for (const ideValue of configuredIdes) {
|
for (const ideValue of configuredIdes) {
|
||||||
// Skip empty or invalid IDE values
|
// Skip empty or invalid IDE values
|
||||||
if (!ideValue || typeof ideValue !== 'string') {
|
if (!ideValue || typeof ideValue !== 'string') {
|
||||||
@@ -496,81 +462,71 @@ class UI {
|
|||||||
const ide = preferredIde || otherIde;
|
const ide = preferredIde || otherIde;
|
||||||
|
|
||||||
if (ide) {
|
if (ide) {
|
||||||
ideChoices.push({
|
configuredGroup.push({
|
||||||
name: `${ide.name} ✅`,
|
label: `${ide.name} ✅`,
|
||||||
value: ide.value,
|
value: ide.value,
|
||||||
checked: true, // Previously configured IDEs are checked by default
|
|
||||||
});
|
});
|
||||||
processedIdes.add(ide.value);
|
processedIdes.add(ide.value);
|
||||||
|
initialValues.push(ide.value); // Pre-select configured IDEs
|
||||||
} else {
|
} else {
|
||||||
// Warn about unrecognized IDE (but don't fail)
|
// Warn about unrecognized IDE (but don't fail)
|
||||||
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (configuredGroup.length > 0) {
|
||||||
|
groupedOptions['Previously Configured'] = configuredGroup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add preferred tools (excluding already processed)
|
// Add preferred tools (excluding already processed)
|
||||||
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||||
if (remainingPreferred.length > 0) {
|
if (remainingPreferred.length > 0) {
|
||||||
ideChoices.push(new inquirer.Separator('── Recommended Tools ──'));
|
groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
|
||||||
for (const ide of remainingPreferred) {
|
|
||||||
ideChoices.push({
|
|
||||||
name: `${ide.name} ⭐`,
|
|
||||||
value: ide.value,
|
|
||||||
checked: false,
|
|
||||||
});
|
|
||||||
processedIdes.add(ide.value);
|
processedIdes.add(ide.value);
|
||||||
}
|
return {
|
||||||
|
label: `${ide.name} ⭐`,
|
||||||
|
value: ide.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add other tools (excluding already processed)
|
// Add other tools (excluding already processed)
|
||||||
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||||
if (remainingOther.length > 0) {
|
if (remainingOther.length > 0) {
|
||||||
ideChoices.push(new inquirer.Separator('── Additional Tools ──'));
|
groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
|
||||||
for (const ide of remainingOther) {
|
label: ide.name,
|
||||||
ideChoices.push({
|
value: ide.value,
|
||||||
name: ide.name,
|
}));
|
||||||
value: ide.value,
|
|
||||||
checked: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let answers;
|
let selectedIdes = [];
|
||||||
let userConfirmedNoTools = false;
|
let userConfirmedNoTools = false;
|
||||||
|
|
||||||
// Loop until user selects at least one tool OR explicitly confirms no tools
|
// Loop until user selects at least one tool OR explicitly confirms no tools
|
||||||
while (!userConfirmedNoTools) {
|
while (!userConfirmedNoTools) {
|
||||||
answers = await inquirer.prompt([
|
selectedIdes = await prompts.groupMultiselect({
|
||||||
{
|
message: `Select tools to configure ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
type: 'checkbox',
|
options: groupedOptions,
|
||||||
name: 'ides',
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||||
message: 'Select tools to configure:',
|
required: false,
|
||||||
choices: ideChoices,
|
});
|
||||||
pageSize: 30,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// If tools were selected, we're done
|
// If tools were selected, we're done
|
||||||
if (answers.ides && answers.ides.length > 0) {
|
if (selectedIdes && selectedIdes.length > 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn that no tools were selected - users often miss the spacebar requirement
|
// Warn that no tools were selected - users often miss the spacebar requirement
|
||||||
console.log();
|
console.log();
|
||||||
console.log(chalk.red.bold('⚠️ WARNING: No tools were selected!'));
|
console.log(chalk.red.bold('⚠️ WARNING: No tools were selected!'));
|
||||||
console.log(chalk.red(' You must press SPACEBAR to select items, then ENTER to confirm.'));
|
console.log(chalk.red(' You must press SPACE to select items, then ENTER to confirm.'));
|
||||||
console.log(chalk.red(' Simply highlighting an item does NOT select it.'));
|
console.log(chalk.red(' Simply highlighting an item does NOT select it.'));
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
const { goBack } = await inquirer.prompt([
|
const goBack = await prompts.confirm({
|
||||||
{
|
message: chalk.yellow('Would you like to go back and select at least one tool?'),
|
||||||
type: 'confirm',
|
default: true,
|
||||||
name: 'goBack',
|
});
|
||||||
message: chalk.yellow('Would you like to go back and select at least one tool?'),
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (goBack) {
|
if (goBack) {
|
||||||
// Re-display a message before looping back
|
// Re-display a message before looping back
|
||||||
@@ -582,8 +538,8 @@ class UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ides: answers.ides || [],
|
ides: selectedIdes || [],
|
||||||
skipIde: !answers.ides || answers.ides.length === 0,
|
skipIde: !selectedIdes || selectedIdes.length === 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,23 +548,17 @@ class UI {
|
|||||||
* @returns {Object} Update configuration
|
* @returns {Object} Update configuration
|
||||||
*/
|
*/
|
||||||
async promptUpdate() {
|
async promptUpdate() {
|
||||||
const inquirer = await getInquirer();
|
const backupFirst = await prompts.confirm({
|
||||||
const answers = await inquirer.prompt([
|
message: 'Create backup before updating?',
|
||||||
{
|
default: true,
|
||||||
type: 'confirm',
|
});
|
||||||
name: 'backupFirst',
|
|
||||||
message: 'Create backup before updating?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'preserveCustomizations',
|
|
||||||
message: 'Preserve local customizations?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return answers;
|
const preserveCustomizations = await prompts.confirm({
|
||||||
|
message: 'Preserve local customizations?',
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { backupFirst, preserveCustomizations };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -617,27 +567,17 @@ class UI {
|
|||||||
* @returns {Array} Selected modules
|
* @returns {Array} Selected modules
|
||||||
*/
|
*/
|
||||||
async promptModules(modules) {
|
async promptModules(modules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const choices = modules.map((mod) => ({
|
const choices = modules.map((mod) => ({
|
||||||
name: `${mod.name} - ${mod.description}`,
|
name: `${mod.name} - ${mod.description}`,
|
||||||
value: mod.id,
|
value: mod.id,
|
||||||
checked: false,
|
checked: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { selectedModules } = await inquirer.prompt([
|
const selectedModules = await prompts.multiselect({
|
||||||
{
|
message: `Select modules to add ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
type: 'checkbox',
|
choices,
|
||||||
name: 'selectedModules',
|
required: true,
|
||||||
message: 'Select modules to add:',
|
});
|
||||||
choices,
|
|
||||||
validate: (answer) => {
|
|
||||||
if (answer.length === 0) {
|
|
||||||
return 'You must choose at least one module.';
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return selectedModules;
|
return selectedModules;
|
||||||
}
|
}
|
||||||
@@ -649,17 +589,10 @@ class UI {
|
|||||||
* @returns {boolean} User confirmation
|
* @returns {boolean} User confirmation
|
||||||
*/
|
*/
|
||||||
async confirm(message, defaultValue = false) {
|
async confirm(message, defaultValue = false) {
|
||||||
const inquirer = await getInquirer();
|
return await prompts.confirm({
|
||||||
const { confirmed } = await inquirer.prompt([
|
message,
|
||||||
{
|
default: defaultValue,
|
||||||
type: 'confirm',
|
});
|
||||||
name: 'confirmed',
|
|
||||||
message,
|
|
||||||
default: defaultValue,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return confirmed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -753,10 +686,9 @@ class UI {
|
|||||||
* Get module choices for selection
|
* Get module choices for selection
|
||||||
* @param {Set} installedModuleIds - Currently installed module IDs
|
* @param {Set} installedModuleIds - Currently installed module IDs
|
||||||
* @param {Object} customContentConfig - Custom content configuration
|
* @param {Object} customContentConfig - Custom content configuration
|
||||||
* @returns {Array} Module choices for inquirer
|
* @returns {Array} Module choices for prompt
|
||||||
*/
|
*/
|
||||||
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const moduleChoices = [];
|
const moduleChoices = [];
|
||||||
const isNewInstallation = installedModuleIds.size === 0;
|
const isNewInstallation = installedModuleIds.size === 0;
|
||||||
|
|
||||||
@@ -811,9 +743,9 @@ class UI {
|
|||||||
if (allCustomModules.length > 0) {
|
if (allCustomModules.length > 0) {
|
||||||
// Add separator for custom content, all custom modules, and official content separator
|
// Add separator for custom content, all custom modules, and official content separator
|
||||||
moduleChoices.push(
|
moduleChoices.push(
|
||||||
new inquirer.Separator('── Custom Content ──'),
|
new choiceUtils.Separator('── Custom Content ──'),
|
||||||
...allCustomModules,
|
...allCustomModules,
|
||||||
new inquirer.Separator('── Official Content ──'),
|
new choiceUtils.Separator('── Official Content ──'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,44 +769,43 @@ class UI {
|
|||||||
* @returns {Array} Selected module IDs
|
* @returns {Array} Selected module IDs
|
||||||
*/
|
*/
|
||||||
async selectModules(moduleChoices, defaultSelections = []) {
|
async selectModules(moduleChoices, defaultSelections = []) {
|
||||||
const inquirer = await getInquirer();
|
// Mark choices as checked based on defaultSelections
|
||||||
const moduleAnswer = await inquirer.prompt([
|
const choicesWithDefaults = moduleChoices.map((choice) => ({
|
||||||
{
|
...choice,
|
||||||
type: 'checkbox',
|
checked: defaultSelections.includes(choice.value),
|
||||||
name: 'modules',
|
}));
|
||||||
message: 'Select modules to install:',
|
|
||||||
choices: moduleChoices,
|
|
||||||
default: defaultSelections,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selected = moduleAnswer.modules || [];
|
const selected = await prompts.multiselect({
|
||||||
|
message: `Select modules to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
|
choices: choicesWithDefaults,
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
|
||||||
return selected;
|
return selected || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt for directory selection
|
* Prompt for directory selection
|
||||||
* @returns {Object} Directory answer from inquirer
|
* @returns {Object} Directory answer from prompt
|
||||||
*/
|
*/
|
||||||
async promptForDirectory() {
|
async promptForDirectory() {
|
||||||
const inquirer = await getInquirer();
|
// Use sync validation because @clack/prompts doesn't support async validate
|
||||||
return await inquirer.prompt([
|
const directory = await prompts.text({
|
||||||
{
|
message: 'Installation directory:',
|
||||||
type: 'input',
|
default: process.cwd(),
|
||||||
name: 'directory',
|
placeholder: process.cwd(),
|
||||||
message: `Installation directory:`,
|
validate: (input) => this.validateDirectorySync(input),
|
||||||
default: process.cwd(),
|
});
|
||||||
validate: async (input) => this.validateDirectory(input),
|
|
||||||
filter: (input) => {
|
// Apply filter logic
|
||||||
// If empty, use the default
|
let filteredDir = directory;
|
||||||
if (!input || input.trim() === '') {
|
if (!filteredDir || filteredDir.trim() === '') {
|
||||||
return process.cwd();
|
filteredDir = process.cwd();
|
||||||
}
|
} else {
|
||||||
return this.expandUserPath(input);
|
filteredDir = this.expandUserPath(filteredDir);
|
||||||
},
|
}
|
||||||
},
|
|
||||||
]);
|
return { directory: filteredDir };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -915,45 +846,92 @@ class UI {
|
|||||||
* @returns {boolean} Whether user confirmed
|
* @returns {boolean} Whether user confirmed
|
||||||
*/
|
*/
|
||||||
async confirmDirectory(directory) {
|
async confirmDirectory(directory) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const dirExists = await fs.pathExists(directory);
|
const dirExists = await fs.pathExists(directory);
|
||||||
|
|
||||||
if (dirExists) {
|
if (dirExists) {
|
||||||
const confirmAnswer = await inquirer.prompt([
|
const proceed = await prompts.confirm({
|
||||||
{
|
message: 'Install to this directory?',
|
||||||
type: 'confirm',
|
default: true,
|
||||||
name: 'proceed',
|
});
|
||||||
message: `Install to this directory?`,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!confirmAnswer.proceed) {
|
if (!proceed) {
|
||||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return confirmAnswer.proceed;
|
return proceed;
|
||||||
} else {
|
} else {
|
||||||
// Ask for confirmation to create the directory
|
// Ask for confirmation to create the directory
|
||||||
const createConfirm = await inquirer.prompt([
|
const create = await prompts.confirm({
|
||||||
{
|
message: `The directory '${directory}' doesn't exist. Would you like to create it?`,
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'create',
|
});
|
||||||
message: `The directory '${directory}' doesn't exist. Would you like to create it?`,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!createConfirm.create) {
|
if (!create) {
|
||||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return createConfirm.create;
|
return create;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate directory path for installation
|
* Validate directory path for installation (sync version for clack prompts)
|
||||||
|
* @param {string} input - User input path
|
||||||
|
* @returns {string|undefined} Error message or undefined if valid
|
||||||
|
*/
|
||||||
|
validateDirectorySync(input) {
|
||||||
|
// Allow empty input to use the default
|
||||||
|
if (!input || input.trim() === '') {
|
||||||
|
return; // Empty means use default, undefined = valid for clack
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandedPath;
|
||||||
|
try {
|
||||||
|
expandedPath = this.expandUserPath(input.trim());
|
||||||
|
} catch (error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the path exists
|
||||||
|
const pathExists = fs.pathExistsSync(expandedPath);
|
||||||
|
|
||||||
|
if (!pathExists) {
|
||||||
|
// Find the first existing parent directory
|
||||||
|
const existingParent = this.findExistingParentSync(expandedPath);
|
||||||
|
|
||||||
|
if (!existingParent) {
|
||||||
|
return 'Cannot create directory: no existing parent directory found';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the existing parent is writable
|
||||||
|
try {
|
||||||
|
fs.accessSync(existingParent, fs.constants.W_OK);
|
||||||
|
// Path doesn't exist but can be created - will prompt for confirmation later
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// Provide a detailed error message explaining both issues
|
||||||
|
return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it exists, validate it's a directory and writable
|
||||||
|
const stat = fs.statSync(expandedPath);
|
||||||
|
if (!stat.isDirectory()) {
|
||||||
|
return `Path exists but is not a directory: ${expandedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check write permissions
|
||||||
|
try {
|
||||||
|
fs.accessSync(expandedPath, fs.constants.W_OK);
|
||||||
|
} catch {
|
||||||
|
return `Directory is not writable: ${expandedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate directory path for installation (async version)
|
||||||
* @param {string} input - User input path
|
* @param {string} input - User input path
|
||||||
* @returns {string|true} Error message or true if valid
|
* @returns {string|true} Error message or true if valid
|
||||||
*/
|
*/
|
||||||
@@ -1009,7 +987,28 @@ class UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the first existing parent directory
|
* Find the first existing parent directory (sync version)
|
||||||
|
* @param {string} targetPath - The path to check
|
||||||
|
* @returns {string|null} The first existing parent directory, or null if none found
|
||||||
|
*/
|
||||||
|
findExistingParentSync(targetPath) {
|
||||||
|
let currentPath = path.resolve(targetPath);
|
||||||
|
|
||||||
|
// Walk up the directory tree until we find an existing directory
|
||||||
|
while (currentPath !== path.dirname(currentPath)) {
|
||||||
|
// Stop at root
|
||||||
|
const parent = path.dirname(currentPath);
|
||||||
|
if (fs.pathExistsSync(parent)) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
currentPath = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // No existing parent found (shouldn't happen in practice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first existing parent directory (async version)
|
||||||
* @param {string} targetPath - The path to check
|
* @param {string} targetPath - The path to check
|
||||||
* @returns {string|null} The first existing parent directory, or null if none found
|
* @returns {string|null} The first existing parent directory, or null if none found
|
||||||
*/
|
*/
|
||||||
@@ -1071,7 +1070,7 @@ class UI {
|
|||||||
* @sideeffects None - pure user input collection, no files written
|
* @sideeffects None - pure user input collection, no files written
|
||||||
* @edgecases Shows warning if user enables TTS but AgentVibes not detected
|
* @edgecases Shows warning if user enables TTS but AgentVibes not detected
|
||||||
* @calledby promptInstall() during installation flow, after core config, before IDE selection
|
* @calledby promptInstall() during installation flow, after core config, before IDE selection
|
||||||
* @calls checkAgentVibesInstalled(), inquirer.prompt(), chalk.green/yellow/dim()
|
* @calls checkAgentVibesInstalled(), prompts.select(), chalk.green/yellow/dim()
|
||||||
*
|
*
|
||||||
* AI NOTE: This prompt is strategically positioned in installation flow:
|
* AI NOTE: This prompt is strategically positioned in installation flow:
|
||||||
* - AFTER core config (user_name, etc)
|
* - AFTER core config (user_name, etc)
|
||||||
@@ -1102,7 +1101,6 @@ class UI {
|
|||||||
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
||||||
*/
|
*/
|
||||||
async promptAgentVibes(projectDir) {
|
async promptAgentVibes(projectDir) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
||||||
|
|
||||||
// Check if AgentVibes is already installed
|
// Check if AgentVibes is already installed
|
||||||
@@ -1114,23 +1112,19 @@ class UI {
|
|||||||
console.log(chalk.dim(' AgentVibes not detected'));
|
console.log(chalk.dim(' AgentVibes not detected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const answers = await inquirer.prompt([
|
const enableTts = await prompts.confirm({
|
||||||
{
|
message: 'Enable Agents to Speak Out loud (powered by Agent Vibes? Claude Code only currently)',
|
||||||
type: 'confirm',
|
default: false,
|
||||||
name: 'enableTts',
|
});
|
||||||
message: 'Enable Agents to Speak Out loud (powered by Agent Vibes? Claude Code only currently)',
|
|
||||||
default: false, // Default to yes - recommended for best experience
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (answers.enableTts && !agentVibesInstalled) {
|
if (enableTts && !agentVibesInstalled) {
|
||||||
console.log(chalk.yellow('\n ⚠️ AgentVibes not installed'));
|
console.log(chalk.yellow('\n ⚠️ AgentVibes not installed'));
|
||||||
console.log(chalk.dim(' Install AgentVibes separately to enable TTS:'));
|
console.log(chalk.dim(' Install AgentVibes separately to enable TTS:'));
|
||||||
console.log(chalk.dim(' https://github.com/paulpreibisch/AgentVibes\n'));
|
console.log(chalk.dim(' https://github.com/paulpreibisch/AgentVibes\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: answers.enableTts,
|
enabled: enableTts,
|
||||||
alreadyInstalled: agentVibesInstalled,
|
alreadyInstalled: agentVibesInstalled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1248,30 +1242,75 @@ class UI {
|
|||||||
return existingInstall.ides || [];
|
return existingInstall.ides || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate custom content path synchronously
|
||||||
|
* @param {string} input - User input path
|
||||||
|
* @returns {string|undefined} Error message or undefined if valid
|
||||||
|
*/
|
||||||
|
validateCustomContentPathSync(input) {
|
||||||
|
// Allow empty input to cancel
|
||||||
|
if (!input || input.trim() === '') {
|
||||||
|
return; // Allow empty to exit
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Expand the path
|
||||||
|
const expandedPath = this.expandUserPath(input.trim());
|
||||||
|
|
||||||
|
// Check if path exists
|
||||||
|
if (!fs.pathExistsSync(expandedPath)) {
|
||||||
|
return 'Path does not exist';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a directory
|
||||||
|
const stat = fs.statSync(expandedPath);
|
||||||
|
if (!stat.isDirectory()) {
|
||||||
|
return 'Path must be a directory';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for module.yaml in the root
|
||||||
|
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||||
|
if (!fs.pathExistsSync(moduleYamlPath)) {
|
||||||
|
return 'Directory must contain a module.yaml file in the root';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse the module.yaml to get the module ID
|
||||||
|
try {
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const content = fs.readFileSync(moduleYamlPath, 'utf8');
|
||||||
|
const moduleData = yaml.parse(content);
|
||||||
|
if (!moduleData.code) {
|
||||||
|
return 'module.yaml must contain a "code" field for the module ID';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return 'Invalid module.yaml file: ' + error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return; // Valid
|
||||||
|
} catch (error) {
|
||||||
|
return 'Error validating path: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt user for custom content source location
|
* Prompt user for custom content source location
|
||||||
* @returns {Object} Custom content configuration
|
* @returns {Object} Custom content configuration
|
||||||
*/
|
*/
|
||||||
async promptCustomContentSource() {
|
async promptCustomContentSource() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const customContentConfig = { hasCustomContent: true, sources: [] };
|
const customContentConfig = { hasCustomContent: true, sources: [] };
|
||||||
|
|
||||||
// Keep asking for more sources until user is done
|
// Keep asking for more sources until user is done
|
||||||
while (true) {
|
while (true) {
|
||||||
// First ask if user wants to add another module or continue
|
// First ask if user wants to add another module or continue
|
||||||
if (customContentConfig.sources.length > 0) {
|
if (customContentConfig.sources.length > 0) {
|
||||||
const { action } = await inquirer.prompt([
|
const action = await prompts.select({
|
||||||
{
|
message: 'Would you like to:',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'action',
|
{ name: 'Add another custom module', value: 'add' },
|
||||||
message: 'Would you like to:',
|
{ name: 'Continue with installation', value: 'continue' },
|
||||||
choices: [
|
],
|
||||||
{ name: 'Add another custom module', value: 'add' },
|
default: 'continue',
|
||||||
{ name: 'Continue with installation', value: 'continue' },
|
});
|
||||||
],
|
|
||||||
default: 'continue',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (action === 'continue') {
|
if (action === 'continue') {
|
||||||
break;
|
break;
|
||||||
@@ -1282,57 +1321,11 @@ class UI {
|
|||||||
let isValid = false;
|
let isValid = false;
|
||||||
|
|
||||||
while (!isValid) {
|
while (!isValid) {
|
||||||
const { path: inputPath } = await inquirer.prompt([
|
// Use sync validation because @clack/prompts doesn't support async validate
|
||||||
{
|
const inputPath = await prompts.text({
|
||||||
type: 'input',
|
message: 'Enter the path to your custom content folder (or press Enter to cancel):',
|
||||||
name: 'path',
|
validate: (input) => this.validateCustomContentPathSync(input),
|
||||||
message: 'Enter the path to your custom content folder (or press Enter to cancel):',
|
});
|
||||||
validate: async (input) => {
|
|
||||||
// Allow empty input to cancel
|
|
||||||
if (!input || input.trim() === '') {
|
|
||||||
return true; // Allow empty to exit
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Expand the path
|
|
||||||
const expandedPath = this.expandUserPath(input.trim());
|
|
||||||
|
|
||||||
// Check if path exists
|
|
||||||
if (!(await fs.pathExists(expandedPath))) {
|
|
||||||
return 'Path does not exist';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a directory
|
|
||||||
const stat = await fs.stat(expandedPath);
|
|
||||||
if (!stat.isDirectory()) {
|
|
||||||
return 'Path must be a directory';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for module.yaml in the root
|
|
||||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
||||||
if (!(await fs.pathExists(moduleYamlPath))) {
|
|
||||||
return 'Directory must contain a module.yaml file in the root';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse the module.yaml to get the module ID
|
|
||||||
try {
|
|
||||||
const yaml = require('yaml');
|
|
||||||
const content = await fs.readFile(moduleYamlPath, 'utf8');
|
|
||||||
const moduleData = yaml.parse(content);
|
|
||||||
if (!moduleData.code) {
|
|
||||||
return 'module.yaml must contain a "code" field for the module ID';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return 'Invalid module.yaml file: ' + error.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return 'Error validating path: ' + error.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// If user pressed Enter without typing anything, exit the loop
|
// If user pressed Enter without typing anything, exit the loop
|
||||||
if (!inputPath || inputPath.trim() === '') {
|
if (!inputPath || inputPath.trim() === '') {
|
||||||
@@ -1364,14 +1357,10 @@ class UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ask if user wants to add these to the installation
|
// Ask if user wants to add these to the installation
|
||||||
const { shouldInstall } = await inquirer.prompt([
|
const shouldInstall = await prompts.confirm({
|
||||||
{
|
message: `Install ${customContentConfig.sources.length} custom module(s) now?`,
|
||||||
type: 'confirm',
|
default: true,
|
||||||
name: 'shouldInstall',
|
});
|
||||||
message: `Install ${customContentConfig.sources.length} custom module(s) now?`,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (shouldInstall) {
|
if (shouldInstall) {
|
||||||
customContentConfig.selected = true;
|
customContentConfig.selected = true;
|
||||||
@@ -1391,7 +1380,6 @@ class UI {
|
|||||||
* @returns {Object} Result with selected custom modules and custom content config
|
* @returns {Object} Result with selected custom modules and custom content config
|
||||||
*/
|
*/
|
||||||
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
// Get existing installation to find custom modules
|
// Get existing installation to find custom modules
|
||||||
const { existingInstall } = await this.getExistingInstallation(directory);
|
const { existingInstall } = await this.getExistingInstallation(directory);
|
||||||
|
|
||||||
@@ -1451,16 +1439,11 @@ class UI {
|
|||||||
choices.push({ name: 'Add new custom modules', value: 'add' }, { name: 'Cancel (no custom modules)', value: 'cancel' });
|
choices.push({ name: 'Add new custom modules', value: 'add' }, { name: 'Cancel (no custom modules)', value: 'cancel' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customAction } = await inquirer.prompt([
|
const customAction = await prompts.select({
|
||||||
{
|
message: cachedCustomModules.length > 0 ? 'What would you like to do with custom modules?' : 'Would you like to add custom modules?',
|
||||||
type: 'list',
|
choices: choices,
|
||||||
name: 'customAction',
|
default: cachedCustomModules.length > 0 ? 'keep' : 'add',
|
||||||
message:
|
});
|
||||||
cachedCustomModules.length > 0 ? 'What would you like to do with custom modules?' : 'Would you like to add custom modules?',
|
|
||||||
choices: choices,
|
|
||||||
default: cachedCustomModules.length > 0 ? 'keep' : 'add',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
switch (customAction) {
|
switch (customAction) {
|
||||||
case 'keep': {
|
case 'keep': {
|
||||||
@@ -1472,21 +1455,18 @@ class UI {
|
|||||||
|
|
||||||
case 'select': {
|
case 'select': {
|
||||||
// Let user choose which to keep
|
// Let user choose which to keep
|
||||||
const choices = cachedCustomModules.map((m) => ({
|
const selectChoices = cachedCustomModules.map((m) => ({
|
||||||
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
|
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
|
||||||
value: m.id,
|
value: m.id,
|
||||||
|
checked: m.checked,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { keepModules } = await inquirer.prompt([
|
const keepModules = await prompts.multiselect({
|
||||||
{
|
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
type: 'checkbox',
|
choices: selectChoices,
|
||||||
name: 'keepModules',
|
required: false,
|
||||||
message: 'Select custom modules to keep:',
|
});
|
||||||
choices: choices,
|
result.selectedCustomModules = keepModules || [];
|
||||||
default: cachedCustomModules.filter((m) => m.checked).map((m) => m.id),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
result.selectedCustomModules = keepModules;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1586,7 +1566,6 @@ class UI {
|
|||||||
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
||||||
*/
|
*/
|
||||||
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
||||||
|
|
||||||
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
||||||
@@ -1627,26 +1606,20 @@ class UI {
|
|||||||
console.log(chalk.yellow('─'.repeat(80)));
|
console.log(chalk.yellow('─'.repeat(80)));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const { proceed } = await inquirer.prompt([
|
const proceed = await prompts.select({
|
||||||
{
|
message: 'What would you like to do?',
|
||||||
type: 'list',
|
choices: [
|
||||||
name: 'proceed',
|
{
|
||||||
message: 'What would you like to do?',
|
name: 'Proceed with update anyway (may have issues)',
|
||||||
choices: [
|
value: 'proceed',
|
||||||
{
|
},
|
||||||
name: 'Proceed with update anyway (may have issues)',
|
{
|
||||||
value: 'proceed',
|
name: 'Cancel (recommended - do a fresh install instead)',
|
||||||
short: 'Proceed with update',
|
value: 'cancel',
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
name: 'Cancel (recommended - do a fresh install instead)',
|
default: 'cancel',
|
||||||
value: 'cancel',
|
});
|
||||||
short: 'Cancel installation',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
default: 'cancel',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (proceed === 'cancel') {
|
if (proceed === 'cancel') {
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
Reference in New Issue
Block a user