From 2165c56e914d6d44e705ad1749e11f5ef3e10169 Mon Sep 17 00:00:00 2001 From: murat Date: Sat, 14 Feb 2026 10:24:50 -0600 Subject: [PATCH] addressed PR request from Brian --- tools/cli/README.md | 59 ----------------- .../installers/lib/core/config-collector.js | 64 ++++++++++++------- 2 files changed, 41 insertions(+), 82 deletions(-) diff --git a/tools/cli/README.md b/tools/cli/README.md index dbc7ee07c..6d698580f 100644 --- a/tools/cli/README.md +++ b/tools/cli/README.md @@ -5,62 +5,3 @@ For external official modules to be discoverable during install, ensure an entry for the external repo is added to external-official-modules.yaml. For community modules - this will be handled in a different way. This file is only for registration of modules under the bmad-code-org. - -## Post-Install Configuration Notes for Module Authors - -The installer can display setup guidance to users after a module's configuration is collected. This is handled by the `displayModulePostConfigNotes(moduleName)` method in `installers/lib/core/config-collector.js`. - -### When It Runs - -The method is called in two places: - -- After `collectModuleConfig()` completes (full interactive configuration) -- After `collectModuleConfigQuick()` completes (quick mode with existing config) - -This ensures users see relevant setup instructions regardless of installation path. - -### Guards - -Output is suppressed when: - -- **Silent mode** (`this._silentConfig`) — non-interactive installations skip all output -- **Feature disabled** — e.g., if the config value is `'none'`, no guidance is needed - -### Adding Support for a New Module - -To add post-config notes for your module, add a conditional block in `displayModulePostConfigNotes()`: - -```javascript -async displayModulePostConfigNotes(moduleName) { - if (this._silentConfig) return; - - // Existing: TEA module handler - if (moduleName !== 'tea') return; - // ... - - // To add your module, replace the early return above with: - if (moduleName === 'your-module') { - const config = this.collectedConfig[moduleName]; - if (!config || !config.your_config_key) return; - - const value = config.your_config_key; - if (value === 'none') return; - - const color = await prompts.getColor(); - await prompts.log.message(''); - await prompts.log.info(color.bold('Your Setup Instructions:')); - await prompts.log.message(color.dim(' Instructions based on selected value...')); - } -} -``` - -### Key Details - -- Read config values from `this.collectedConfig[moduleName]` -- Use `prompts.log.info()` for headers and `prompts.log.message()` for details -- Use `color.bold()` and `color.dim()` for visual hierarchy -- The config question that drives the output is defined in the module's `module.yaml` - -### Working Example: TEA Module - -The TEA module defines a `tea_browser_automation` config question with options: `auto`, `cli`, `mcp`, `none`. After configuration, the handler at lines 1207-1235 displays Playwright CLI install commands and/or MCP setup links based on the user's selection. diff --git a/tools/cli/installers/lib/core/config-collector.js b/tools/cli/installers/lib/core/config-collector.js index 895cf818b..b01098318 100644 --- a/tools/cli/installers/lib/core/config-collector.js +++ b/tools/cli/installers/lib/core/config-collector.js @@ -550,7 +550,7 @@ class ConfigCollector { } } - await this.displayModulePostConfigNotes(moduleName); + await this.displayModulePostConfigNotes(moduleName, moduleConfig); return newKeys.length > 0 || newStaticKeys.length > 0; // Return true if we had any new fields (interactive or static) } @@ -926,7 +926,7 @@ class ConfigCollector { } } - await this.displayModulePostConfigNotes(moduleName); + await this.displayModulePostConfigNotes(moduleName, moduleConfig); } /** @@ -1202,35 +1202,53 @@ class ConfigCollector { /** * Display post-configuration notes for a module * Shows prerequisite guidance based on collected config values + * Reads notes from the module's `post-install-notes` section in module.yaml + * Supports two formats: + * - Simple string: always displayed + * - Object keyed by config field name, with value-specific messages * @param {string} moduleName - Module name + * @param {Object} moduleConfig - Parsed module.yaml content */ - async displayModulePostConfigNotes(moduleName) { + async displayModulePostConfigNotes(moduleName, moduleConfig) { if (this._silentConfig) return; - if (moduleName !== 'tea') return; - - const teaConfig = this.collectedConfig[moduleName]; - if (!teaConfig || !teaConfig.tea_browser_automation) return; - - const mode = teaConfig.tea_browser_automation; - - if (mode === 'none') return; + if (!moduleConfig || !moduleConfig['post-install-notes']) return; + const notes = moduleConfig['post-install-notes']; const color = await prompts.getColor(); - await prompts.log.message(''); - - if (mode === 'cli' || mode === 'auto') { - await prompts.log.info(color.bold('Playwright CLI Setup:')); - await prompts.log.message(color.dim(' npm install -g @playwright/cli@latest')); - await prompts.log.message(color.dim(' playwright-cli install --skills # Run from project root')); - await prompts.log.message(color.dim(' Node.js 18+ required.')); + // Format 1: Simple string - always display + if (typeof notes === 'string') { + await prompts.log.message(''); + for (const line of notes.trim().split('\n')) { + await prompts.log.message(color.dim(line)); + } + return; } - if (mode === 'mcp' || mode === 'auto') { - if (mode === 'auto') await prompts.log.message(''); - await prompts.log.info(color.bold('Playwright MCP Setup:')); - await prompts.log.message(color.dim(' Configure MCP servers in your IDE')); - await prompts.log.message(color.dim(' See: https://github.com/microsoft/playwright-mcp')); + // Format 2: Conditional on config values + if (typeof notes === 'object') { + const config = this.collectedConfig[moduleName]; + if (!config) return; + + let hasOutput = false; + for (const [configKey, valueMessages] of Object.entries(notes)) { + const selectedValue = config[configKey]; + if (!selectedValue || !valueMessages[selectedValue]) continue; + + if (hasOutput) await prompts.log.message(''); + hasOutput = true; + + const message = valueMessages[selectedValue]; + await prompts.log.message(''); + for (const line of message.trim().split('\n')) { + const trimmedLine = line.trim(); + if (trimmedLine.endsWith(':') && !trimmedLine.startsWith(' ')) { + await prompts.log.info(color.bold(trimmedLine)); + } else { + await prompts.log.message(color.dim(' ' + trimmedLine)); + } + } + } } }