bmad-method 6.3.1-next.2 → 6.3.1-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tools/installer/cli-utils.js +0 -137
- package/tools/installer/core/manifest.js +0 -577
- package/tools/installer/ide/shared/path-utils.js +0 -145
- package/tools/installer/modules/custom-module-manager.js +0 -27
- package/tools/installer/modules/external-manager.js +0 -40
- package/tools/installer/modules/official-modules.js +2 -50
- package/tools/installer/modules/registry-client.js +0 -11
- package/tools/installer/prompts.js +0 -106
- package/tools/installer/ide/shared/agent-command-generator.js +0 -180
- package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
- package/tools/installer/ide/shared/module-injections.js +0 -136
- package/tools/installer/ide/templates/agent-command-template.md +0 -14
- package/tools/installer/ide/templates/combined/antigravity.md +0 -8
- package/tools/installer/ide/templates/combined/default-agent.md +0 -15
- package/tools/installer/ide/templates/combined/default-task.md +0 -10
- package/tools/installer/ide/templates/combined/default-tool.md +0 -10
- package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
- package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
- package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
- package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
- package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
- package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
- package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
- package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
- package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
- package/tools/installer/ide/templates/combined/rovodev.md +0 -9
- package/tools/installer/ide/templates/combined/trae.md +0 -9
- package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
- package/tools/installer/ide/templates/split/.gitkeep +0 -0
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
* - standalone/agents/fred.md → bmad-agent-standalone-fred.md
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
// Type segments - agents are included in naming, others are filtered out
|
|
19
|
-
const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
|
|
20
18
|
const AGENT_SEGMENT = 'agents';
|
|
21
19
|
|
|
22
20
|
// BMAD installation folder name - centralized constant for all installers
|
|
@@ -194,125 +192,6 @@ function parseDashName(filename) {
|
|
|
194
192
|
};
|
|
195
193
|
}
|
|
196
194
|
|
|
197
|
-
// ============================================================================
|
|
198
|
-
// LEGACY FUNCTIONS (underscore format) - kept for backward compatibility
|
|
199
|
-
// ============================================================================
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Convert hierarchical path to flat underscore-separated name (LEGACY)
|
|
203
|
-
* @deprecated Use toDashName instead
|
|
204
|
-
*/
|
|
205
|
-
function toUnderscoreName(module, type, name) {
|
|
206
|
-
const isAgent = type === AGENT_SEGMENT;
|
|
207
|
-
if (module === 'core') {
|
|
208
|
-
return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
|
|
209
|
-
}
|
|
210
|
-
if (module === 'standalone') {
|
|
211
|
-
return isAgent ? `bmad_agent_standalone_${name}.md` : `bmad_standalone_${name}.md`;
|
|
212
|
-
}
|
|
213
|
-
return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Convert relative path to flat underscore-separated name (LEGACY)
|
|
218
|
-
* @deprecated Use toDashPath instead
|
|
219
|
-
*/
|
|
220
|
-
function toUnderscorePath(relativePath) {
|
|
221
|
-
// Strip common file extensions (same as toDashPath for consistency)
|
|
222
|
-
const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
|
|
223
|
-
const parts = withoutExt.split(/[/\\]/);
|
|
224
|
-
|
|
225
|
-
const module = parts[0];
|
|
226
|
-
const type = parts[1];
|
|
227
|
-
const name = parts.slice(2).join('_');
|
|
228
|
-
|
|
229
|
-
return toUnderscoreName(module, type, name);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Create custom agent underscore name (LEGACY)
|
|
234
|
-
* @deprecated Use customAgentDashName instead
|
|
235
|
-
*/
|
|
236
|
-
function customAgentUnderscoreName(agentName) {
|
|
237
|
-
return `bmad_custom_${agentName}.md`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Check if a filename uses underscore format (LEGACY)
|
|
242
|
-
* @deprecated Use isDashFormat instead
|
|
243
|
-
*/
|
|
244
|
-
function isUnderscoreFormat(filename) {
|
|
245
|
-
return filename.startsWith('bmad_') && filename.includes('_');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Extract parts from an underscore-formatted filename (LEGACY)
|
|
250
|
-
* @deprecated Use parseDashName instead
|
|
251
|
-
*/
|
|
252
|
-
function parseUnderscoreName(filename) {
|
|
253
|
-
const withoutExt = filename.replace('.md', '');
|
|
254
|
-
const parts = withoutExt.split('_');
|
|
255
|
-
|
|
256
|
-
if (parts.length < 2 || parts[0] !== 'bmad') {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const agentIndex = parts.indexOf('agent');
|
|
261
|
-
|
|
262
|
-
if (agentIndex !== -1) {
|
|
263
|
-
if (agentIndex === 1) {
|
|
264
|
-
// bmad_agent_... - check for standalone
|
|
265
|
-
if (parts.length >= 4 && parts[2] === 'standalone') {
|
|
266
|
-
return {
|
|
267
|
-
prefix: parts[0],
|
|
268
|
-
module: 'standalone',
|
|
269
|
-
type: 'agents',
|
|
270
|
-
name: parts.slice(3).join('_'),
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
prefix: parts[0],
|
|
275
|
-
module: 'core',
|
|
276
|
-
type: 'agents',
|
|
277
|
-
name: parts.slice(agentIndex + 1).join('_'),
|
|
278
|
-
};
|
|
279
|
-
} else {
|
|
280
|
-
return {
|
|
281
|
-
prefix: parts[0],
|
|
282
|
-
module: parts[1],
|
|
283
|
-
type: 'agents',
|
|
284
|
-
name: parts.slice(agentIndex + 1).join('_'),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (parts.length === 2) {
|
|
290
|
-
return {
|
|
291
|
-
prefix: parts[0],
|
|
292
|
-
module: 'core',
|
|
293
|
-
type: 'workflows',
|
|
294
|
-
name: parts[1],
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check for standalone non-agent: bmad_standalone_name
|
|
299
|
-
if (parts[1] === 'standalone') {
|
|
300
|
-
return {
|
|
301
|
-
prefix: parts[0],
|
|
302
|
-
module: 'standalone',
|
|
303
|
-
type: 'workflows',
|
|
304
|
-
name: parts.slice(2).join('_'),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
prefix: parts[0],
|
|
310
|
-
module: parts[1],
|
|
311
|
-
type: 'workflows',
|
|
312
|
-
name: parts.slice(2).join('_'),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
195
|
/**
|
|
317
196
|
* Resolve the skill name for an artifact.
|
|
318
197
|
* Prefers canonicalId from a bmad-skill-manifest.yaml sidecar when available,
|
|
@@ -328,37 +207,13 @@ function resolveSkillName(artifact) {
|
|
|
328
207
|
return toDashPath(artifact.relativePath);
|
|
329
208
|
}
|
|
330
209
|
|
|
331
|
-
// Backward compatibility aliases (colon format was same as underscore)
|
|
332
|
-
const toColonName = toUnderscoreName;
|
|
333
|
-
const toColonPath = toUnderscorePath;
|
|
334
|
-
const customAgentColonName = customAgentUnderscoreName;
|
|
335
|
-
const isColonFormat = isUnderscoreFormat;
|
|
336
|
-
const parseColonName = parseUnderscoreName;
|
|
337
|
-
|
|
338
210
|
module.exports = {
|
|
339
|
-
// New standard (dash-based)
|
|
340
211
|
toDashName,
|
|
341
212
|
toDashPath,
|
|
342
213
|
resolveSkillName,
|
|
343
214
|
customAgentDashName,
|
|
344
215
|
isDashFormat,
|
|
345
216
|
parseDashName,
|
|
346
|
-
|
|
347
|
-
// Legacy (underscore-based) - kept for backward compatibility
|
|
348
|
-
toUnderscoreName,
|
|
349
|
-
toUnderscorePath,
|
|
350
|
-
customAgentUnderscoreName,
|
|
351
|
-
isUnderscoreFormat,
|
|
352
|
-
parseUnderscoreName,
|
|
353
|
-
|
|
354
|
-
// Backward compatibility aliases
|
|
355
|
-
toColonName,
|
|
356
|
-
toColonPath,
|
|
357
|
-
customAgentColonName,
|
|
358
|
-
isColonFormat,
|
|
359
|
-
parseColonName,
|
|
360
|
-
|
|
361
|
-
TYPE_SEGMENTS,
|
|
362
217
|
AGENT_SEGMENT,
|
|
363
218
|
BMAD_FOLDER_NAME,
|
|
364
219
|
};
|
|
@@ -155,33 +155,6 @@ class CustomModuleManager {
|
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/**
|
|
159
|
-
* @deprecated Use parseSource() instead. Kept for backward compatibility.
|
|
160
|
-
* Parse and validate a GitHub repository URL.
|
|
161
|
-
* @param {string} url - GitHub URL to validate
|
|
162
|
-
* @returns {Object} { owner, repo, isValid, error }
|
|
163
|
-
*/
|
|
164
|
-
validateGitHubUrl(url) {
|
|
165
|
-
if (!url || typeof url !== 'string') {
|
|
166
|
-
return { owner: null, repo: null, isValid: false, error: 'URL is required' };
|
|
167
|
-
}
|
|
168
|
-
const trimmed = url.trim();
|
|
169
|
-
|
|
170
|
-
// HTTPS format: https://github.com/owner/repo[.git] (strict, no trailing path)
|
|
171
|
-
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
172
|
-
if (httpsMatch) {
|
|
173
|
-
return { owner: httpsMatch[1], repo: httpsMatch[2], isValid: true, error: null };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// SSH format: git@github.com:owner/repo[.git]
|
|
177
|
-
const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
178
|
-
if (sshMatch) {
|
|
179
|
-
return { owner: sshMatch[1], repo: sshMatch[2], isValid: true, error: null };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { owner: null, repo: null, isValid: false, error: 'Not a valid GitHub URL (expected https://github.com/owner/repo)' };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
158
|
// ─── Marketplace JSON ─────────────────────────────────────────────────────
|
|
186
159
|
|
|
187
160
|
/**
|
|
@@ -109,46 +109,6 @@ class ExternalModuleManager {
|
|
|
109
109
|
return modules.find((m) => m.code === code) || null;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/**
|
|
113
|
-
* Get module info by key
|
|
114
|
-
* @param {string} key - The module key (e.g., 'bmad-creative-intelligence-suite')
|
|
115
|
-
* @returns {Object|null} Module info or null if not found
|
|
116
|
-
*/
|
|
117
|
-
async getModuleByKey(key) {
|
|
118
|
-
const modules = await this.listAvailable();
|
|
119
|
-
return modules.find((m) => m.key === key) || null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if a module code exists in external modules
|
|
124
|
-
* @param {string} code - The module code to check
|
|
125
|
-
* @returns {boolean} True if the module exists
|
|
126
|
-
*/
|
|
127
|
-
async hasModule(code) {
|
|
128
|
-
const module = await this.getModuleByCode(code);
|
|
129
|
-
return module !== null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get the URL for a module by code
|
|
134
|
-
* @param {string} code - The module code
|
|
135
|
-
* @returns {string|null} The URL or null if not found
|
|
136
|
-
*/
|
|
137
|
-
async getModuleUrl(code) {
|
|
138
|
-
const module = await this.getModuleByCode(code);
|
|
139
|
-
return module ? module.url : null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get the module definition path for a module by code
|
|
144
|
-
* @param {string} code - The module code
|
|
145
|
-
* @returns {string|null} The module definition path or null if not found
|
|
146
|
-
*/
|
|
147
|
-
async getModuleDefinition(code) {
|
|
148
|
-
const module = await this.getModuleByCode(code);
|
|
149
|
-
return module ? module.moduleDefinition : null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
112
|
/**
|
|
153
113
|
* Get the cache directory for external modules
|
|
154
114
|
* @returns {string} Path to the external modules cache directory
|
|
@@ -12,6 +12,8 @@ class OfficialModules {
|
|
|
12
12
|
// Config collection state (merged from ConfigCollector)
|
|
13
13
|
this.collectedConfig = {};
|
|
14
14
|
this._existingConfig = null;
|
|
15
|
+
// Tracked during interactive config collection so {directory_name}
|
|
16
|
+
// placeholder defaults can be resolved in buildQuestion().
|
|
15
17
|
this.currentProjectDir = null;
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -500,32 +502,6 @@ class OfficialModules {
|
|
|
500
502
|
}
|
|
501
503
|
}
|
|
502
504
|
|
|
503
|
-
/**
|
|
504
|
-
* Find all .md agent files recursively in a directory
|
|
505
|
-
* @param {string} dir - Directory to search
|
|
506
|
-
* @returns {Array} List of .md agent file paths
|
|
507
|
-
*/
|
|
508
|
-
async findAgentMdFiles(dir) {
|
|
509
|
-
const agentFiles = [];
|
|
510
|
-
|
|
511
|
-
async function searchDirectory(searchDir) {
|
|
512
|
-
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
513
|
-
|
|
514
|
-
for (const entry of entries) {
|
|
515
|
-
const fullPath = path.join(searchDir, entry.name);
|
|
516
|
-
|
|
517
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
518
|
-
agentFiles.push(fullPath);
|
|
519
|
-
} else if (entry.isDirectory()) {
|
|
520
|
-
await searchDirectory(fullPath);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
await searchDirectory(dir);
|
|
526
|
-
return agentFiles;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
505
|
/**
|
|
530
506
|
* Create directories declared in module.yaml's `directories` key
|
|
531
507
|
* This replaces the security-risky module installer pattern with declarative config
|
|
@@ -699,29 +675,6 @@ class OfficialModules {
|
|
|
699
675
|
return { createdDirs, movedDirs, createdWdsFolders };
|
|
700
676
|
}
|
|
701
677
|
|
|
702
|
-
/**
|
|
703
|
-
* Private: Process module configuration
|
|
704
|
-
* @param {string} modulePath - Path to installed module
|
|
705
|
-
* @param {string} moduleName - Module name
|
|
706
|
-
*/
|
|
707
|
-
async processModuleConfig(modulePath, moduleName) {
|
|
708
|
-
const configPath = path.join(modulePath, 'config.yaml');
|
|
709
|
-
|
|
710
|
-
if (await fs.pathExists(configPath)) {
|
|
711
|
-
try {
|
|
712
|
-
let configContent = await fs.readFile(configPath, 'utf8');
|
|
713
|
-
|
|
714
|
-
// Replace path placeholders
|
|
715
|
-
configContent = configContent.replaceAll('{project-root}', `bmad/${moduleName}`);
|
|
716
|
-
configContent = configContent.replaceAll('{module}', moduleName);
|
|
717
|
-
|
|
718
|
-
await fs.writeFile(configPath, configContent, 'utf8');
|
|
719
|
-
} catch (error) {
|
|
720
|
-
await prompts.log.warn(`Failed to process module config: ${error.message}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
678
|
/**
|
|
726
679
|
* Private: Sync module files (preserving user modifications)
|
|
727
680
|
* @param {string} sourcePath - Source module path
|
|
@@ -1091,7 +1044,6 @@ class OfficialModules {
|
|
|
1091
1044
|
*/
|
|
1092
1045
|
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
|
1093
1046
|
this.currentProjectDir = projectDir;
|
|
1094
|
-
|
|
1095
1047
|
// Load existing config if not already loaded
|
|
1096
1048
|
if (!this._existingConfig) {
|
|
1097
1049
|
await this.loadExistingConfig(projectDir);
|
|
@@ -50,17 +50,6 @@ class RegistryClient {
|
|
|
50
50
|
const content = await this.fetch(url, timeout);
|
|
51
51
|
return yaml.parse(content);
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fetch a URL and parse the response as JSON.
|
|
56
|
-
* @param {string} url - URL to fetch
|
|
57
|
-
* @param {number} [timeout] - Timeout in ms
|
|
58
|
-
* @returns {Promise<Object>} Parsed JSON content
|
|
59
|
-
*/
|
|
60
|
-
async fetchJson(url, timeout) {
|
|
61
|
-
const content = await this.fetch(url, timeout);
|
|
62
|
-
return JSON.parse(content);
|
|
63
|
-
}
|
|
64
53
|
}
|
|
65
54
|
|
|
66
55
|
module.exports = { RegistryClient };
|
|
@@ -498,26 +498,6 @@ async function password(options) {
|
|
|
498
498
|
return result;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
/**
|
|
502
|
-
* Group multiple prompts together
|
|
503
|
-
* @param {Object} prompts - Object of prompt functions
|
|
504
|
-
* @param {Object} [options] - Group options
|
|
505
|
-
* @returns {Promise<Object>} Object with all answers
|
|
506
|
-
*/
|
|
507
|
-
async function group(prompts, options = {}) {
|
|
508
|
-
const clack = await getClack();
|
|
509
|
-
|
|
510
|
-
const result = await clack.group(prompts, {
|
|
511
|
-
onCancel: () => {
|
|
512
|
-
clack.cancel('Operation cancelled');
|
|
513
|
-
process.exit(0);
|
|
514
|
-
},
|
|
515
|
-
...options,
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
return result;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
501
|
/**
|
|
522
502
|
* Run tasks with spinner feedback
|
|
523
503
|
* @param {Array} tasks - Array of task objects [{title, task, enabled?}]
|
|
@@ -578,42 +558,6 @@ async function box(content, title, options) {
|
|
|
578
558
|
clack.box(content, title, options);
|
|
579
559
|
}
|
|
580
560
|
|
|
581
|
-
/**
|
|
582
|
-
* Create a progress bar for visualizing task completion
|
|
583
|
-
* @param {Object} [options] - Progress options (max, style, etc.)
|
|
584
|
-
* @returns {Promise<Object>} Progress controller with start, advance, stop methods
|
|
585
|
-
*/
|
|
586
|
-
async function progress(options) {
|
|
587
|
-
const clack = await getClack();
|
|
588
|
-
return clack.progress(options);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Create a task log for displaying scrolling subprocess output
|
|
593
|
-
* @param {Object} options - TaskLog options (title, limit, retainLog)
|
|
594
|
-
* @returns {Promise<Object>} TaskLog controller with message, success, error methods
|
|
595
|
-
*/
|
|
596
|
-
async function taskLog(options) {
|
|
597
|
-
const clack = await getClack();
|
|
598
|
-
return clack.taskLog(options);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* File system path prompt with autocomplete
|
|
603
|
-
* @param {Object} options - Path options
|
|
604
|
-
* @param {string} options.message - The prompt message
|
|
605
|
-
* @param {string} [options.initialValue] - Initial path value
|
|
606
|
-
* @param {boolean} [options.directory=false] - Only allow directories
|
|
607
|
-
* @param {Function} [options.validate] - Validation function
|
|
608
|
-
* @returns {Promise<string>} Selected path
|
|
609
|
-
*/
|
|
610
|
-
async function pathPrompt(options) {
|
|
611
|
-
const clack = await getClack();
|
|
612
|
-
const result = await clack.path(options);
|
|
613
|
-
await handleCancel(result);
|
|
614
|
-
return result;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
561
|
/**
|
|
618
562
|
* Autocomplete single-select prompt with type-ahead filtering
|
|
619
563
|
* @param {Object} options - Autocomplete options
|
|
@@ -631,50 +575,6 @@ async function autocomplete(options) {
|
|
|
631
575
|
return result;
|
|
632
576
|
}
|
|
633
577
|
|
|
634
|
-
/**
|
|
635
|
-
* Key-based instant selection prompt
|
|
636
|
-
* @param {Object} options - SelectKey options
|
|
637
|
-
* @param {string} options.message - The prompt message
|
|
638
|
-
* @param {Array} options.options - Array of choices [{value, label, hint?}]
|
|
639
|
-
* @returns {Promise<any>} Selected value
|
|
640
|
-
*/
|
|
641
|
-
async function selectKey(options) {
|
|
642
|
-
const clack = await getClack();
|
|
643
|
-
const result = await clack.selectKey(options);
|
|
644
|
-
await handleCancel(result);
|
|
645
|
-
return result;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Stream messages with dynamic content (for LLMs, generators, etc.)
|
|
650
|
-
*/
|
|
651
|
-
const stream = {
|
|
652
|
-
async info(generator) {
|
|
653
|
-
const clack = await getClack();
|
|
654
|
-
return clack.stream.info(generator);
|
|
655
|
-
},
|
|
656
|
-
async success(generator) {
|
|
657
|
-
const clack = await getClack();
|
|
658
|
-
return clack.stream.success(generator);
|
|
659
|
-
},
|
|
660
|
-
async step(generator) {
|
|
661
|
-
const clack = await getClack();
|
|
662
|
-
return clack.stream.step(generator);
|
|
663
|
-
},
|
|
664
|
-
async warn(generator) {
|
|
665
|
-
const clack = await getClack();
|
|
666
|
-
return clack.stream.warn(generator);
|
|
667
|
-
},
|
|
668
|
-
async error(generator) {
|
|
669
|
-
const clack = await getClack();
|
|
670
|
-
return clack.stream.error(generator);
|
|
671
|
-
},
|
|
672
|
-
async message(generator, options) {
|
|
673
|
-
const clack = await getClack();
|
|
674
|
-
return clack.stream.message(generator, options);
|
|
675
|
-
},
|
|
676
|
-
};
|
|
677
|
-
|
|
678
578
|
/**
|
|
679
579
|
* Get the color utility (picocolors instance from @clack/prompts)
|
|
680
580
|
* @returns {Promise<Object>} The color utility (picocolors)
|
|
@@ -790,20 +690,14 @@ module.exports = {
|
|
|
790
690
|
note,
|
|
791
691
|
box,
|
|
792
692
|
spinner,
|
|
793
|
-
progress,
|
|
794
|
-
taskLog,
|
|
795
693
|
select,
|
|
796
694
|
multiselect,
|
|
797
695
|
autocompleteMultiselect,
|
|
798
696
|
autocomplete,
|
|
799
|
-
selectKey,
|
|
800
697
|
confirm,
|
|
801
698
|
text,
|
|
802
|
-
path: pathPrompt,
|
|
803
699
|
password,
|
|
804
|
-
group,
|
|
805
700
|
tasks,
|
|
806
701
|
log,
|
|
807
|
-
stream,
|
|
808
702
|
prompt,
|
|
809
703
|
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
const path = require('node:path');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generates launcher command files for each agent
|
|
7
|
-
*/
|
|
8
|
-
class AgentCommandGenerator {
|
|
9
|
-
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
|
10
|
-
this.templatePath = path.join(__dirname, '../templates/agent-command-template.md');
|
|
11
|
-
this.bmadFolderName = bmadFolderName;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Collect agent artifacts for IDE installation
|
|
16
|
-
* @param {string} bmadDir - BMAD installation directory
|
|
17
|
-
* @param {Array} selectedModules - Modules to include
|
|
18
|
-
* @returns {Object} Artifacts array with metadata
|
|
19
|
-
*/
|
|
20
|
-
async collectAgentArtifacts(bmadDir, selectedModules = []) {
|
|
21
|
-
const { getAgentsFromBmad } = require('./bmad-artifacts');
|
|
22
|
-
|
|
23
|
-
// Get agents from INSTALLED bmad/ directory
|
|
24
|
-
const agents = await getAgentsFromBmad(bmadDir, selectedModules);
|
|
25
|
-
|
|
26
|
-
const artifacts = [];
|
|
27
|
-
|
|
28
|
-
for (const agent of agents) {
|
|
29
|
-
const launcherContent = await this.generateLauncherContent(agent);
|
|
30
|
-
// Use relativePath if available (for nested agents), otherwise just name with .md
|
|
31
|
-
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
|
32
|
-
// Calculate the relative agent path (e.g., bmm/agents/pm.md)
|
|
33
|
-
let agentRelPath = agent.path || '';
|
|
34
|
-
// Normalize path separators for cross-platform compatibility
|
|
35
|
-
agentRelPath = agentRelPath.replaceAll('\\', '/');
|
|
36
|
-
// Remove _bmad/ prefix if present to get relative path from project root
|
|
37
|
-
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
|
|
38
|
-
if (agentRelPath.includes('_bmad/')) {
|
|
39
|
-
const parts = agentRelPath.split(/_bmad\//);
|
|
40
|
-
if (parts.length > 1) {
|
|
41
|
-
agentRelPath = parts.slice(1).join('/');
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
artifacts.push({
|
|
45
|
-
type: 'agent-launcher',
|
|
46
|
-
name: agent.name,
|
|
47
|
-
description: agent.description || `${agent.name} agent`,
|
|
48
|
-
module: agent.module,
|
|
49
|
-
canonicalId: agent.canonicalId || '',
|
|
50
|
-
relativePath: path.join(agent.module, 'agents', agentPathInModule), // For command filename
|
|
51
|
-
agentPath: agentRelPath, // Relative path to actual agent file
|
|
52
|
-
content: launcherContent,
|
|
53
|
-
sourcePath: agent.path,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
artifacts,
|
|
59
|
-
counts: {
|
|
60
|
-
agents: agents.length,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Generate launcher content for an agent
|
|
67
|
-
* @param {Object} agent - Agent metadata
|
|
68
|
-
* @returns {string} Launcher file content
|
|
69
|
-
*/
|
|
70
|
-
async generateLauncherContent(agent) {
|
|
71
|
-
// Load the template
|
|
72
|
-
const template = await fs.readFile(this.templatePath, 'utf8');
|
|
73
|
-
|
|
74
|
-
// Replace template variables
|
|
75
|
-
// Use relativePath if available (for nested agents), otherwise just name with .md
|
|
76
|
-
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
|
77
|
-
return template
|
|
78
|
-
.replaceAll('{{name}}', agent.name)
|
|
79
|
-
.replaceAll('{{module}}', agent.module)
|
|
80
|
-
.replaceAll('{{path}}', agentPathInModule)
|
|
81
|
-
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
|
82
|
-
.replaceAll('_bmad', this.bmadFolderName)
|
|
83
|
-
.replaceAll('_bmad', '_bmad');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Write agent launcher artifacts to IDE commands directory
|
|
88
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
89
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
90
|
-
* @returns {number} Count of launchers written
|
|
91
|
-
*/
|
|
92
|
-
async writeAgentLaunchers(baseCommandsDir, artifacts) {
|
|
93
|
-
let writtenCount = 0;
|
|
94
|
-
|
|
95
|
-
for (const artifact of artifacts) {
|
|
96
|
-
if (artifact.type === 'agent-launcher') {
|
|
97
|
-
const moduleAgentsDir = path.join(baseCommandsDir, artifact.module, 'agents');
|
|
98
|
-
await fs.ensureDir(moduleAgentsDir);
|
|
99
|
-
|
|
100
|
-
const launcherPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
|
|
101
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
102
|
-
writtenCount++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return writtenCount;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
|
111
|
-
* Creates flat files like: bmad_bmm_pm.md
|
|
112
|
-
*
|
|
113
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
114
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
115
|
-
* @returns {number} Count of launchers written
|
|
116
|
-
*/
|
|
117
|
-
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
|
118
|
-
let writtenCount = 0;
|
|
119
|
-
|
|
120
|
-
for (const artifact of artifacts) {
|
|
121
|
-
if (artifact.type === 'agent-launcher') {
|
|
122
|
-
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
|
123
|
-
const flatName = toColonPath(artifact.relativePath);
|
|
124
|
-
const launcherPath = path.join(baseCommandsDir, flatName);
|
|
125
|
-
await fs.ensureDir(path.dirname(launcherPath));
|
|
126
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
127
|
-
writtenCount++;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return writtenCount;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Write agent launcher artifacts using dash format (NEW STANDARD)
|
|
136
|
-
* Creates flat files like: bmad-agent-bmm-pm.md
|
|
137
|
-
*
|
|
138
|
-
* The bmad-agent- prefix distinguishes agents from workflows/tasks/tools.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
141
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
142
|
-
* @returns {number} Count of launchers written
|
|
143
|
-
*/
|
|
144
|
-
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
|
145
|
-
let writtenCount = 0;
|
|
146
|
-
|
|
147
|
-
for (const artifact of artifacts) {
|
|
148
|
-
if (artifact.type === 'agent-launcher') {
|
|
149
|
-
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-agent-bmm-pm.md
|
|
150
|
-
const flatName = toDashPath(artifact.relativePath);
|
|
151
|
-
const launcherPath = path.join(baseCommandsDir, flatName);
|
|
152
|
-
await fs.ensureDir(path.dirname(launcherPath));
|
|
153
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
154
|
-
writtenCount++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return writtenCount;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get the custom agent name in underscore format (Windows-compatible)
|
|
163
|
-
* @param {string} agentName - Custom agent name
|
|
164
|
-
* @returns {string} Underscore-formatted filename
|
|
165
|
-
*/
|
|
166
|
-
getCustomAgentColonName(agentName) {
|
|
167
|
-
return customAgentColonName(agentName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get the custom agent name in underscore format (Windows-compatible)
|
|
172
|
-
* @param {string} agentName - Custom agent name
|
|
173
|
-
* @returns {string} Underscore-formatted filename
|
|
174
|
-
*/
|
|
175
|
-
getCustomAgentDashName(agentName) {
|
|
176
|
-
return customAgentDashName(agentName);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
module.exports = { AgentCommandGenerator };
|