@zeyue0329/xiaoma-cli 1.0.0

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.
Files changed (123) hide show
  1. package/.releaserc.json +18 -0
  2. package/.vscode/settings.json +44 -0
  3. package/CONTRIBUTING.md +209 -0
  4. package/LICENSE +21 -0
  5. package/QUICK-START.md +173 -0
  6. package/README.md +532 -0
  7. package/common/tasks/create-doc.md +101 -0
  8. package/common/tasks/execute-checklist.md +93 -0
  9. package/common/utils/bmad-doc-template.md +325 -0
  10. package/common/utils/workflow-management.md +69 -0
  11. package/dist/agents/analyst.txt +2882 -0
  12. package/dist/agents/architect.txt +3543 -0
  13. package/dist/agents/dev.txt +428 -0
  14. package/dist/agents/pm.txt +2229 -0
  15. package/dist/agents/po.txt +1364 -0
  16. package/dist/agents/qa.txt +386 -0
  17. package/dist/agents/sm.txt +668 -0
  18. package/dist/agents/ux-expert.txt +701 -0
  19. package/dist/agents/xiaoma-master.txt +8756 -0
  20. package/dist/agents/xiaoma-orchestrator.txt +1490 -0
  21. package/dist/teams/team-all.txt +11062 -0
  22. package/dist/teams/team-fullstack.txt +10392 -0
  23. package/dist/teams/team-ide-minimal.txt +3507 -0
  24. package/dist/teams/team-no-ui.txt +8951 -0
  25. package/docs/GUIDING-PRINCIPLES.md +91 -0
  26. package/docs/core-architecture.md +219 -0
  27. package/docs/expansion-packs.md +280 -0
  28. package/docs/versioning-and-releases.md +77 -0
  29. package/docs/versions.md +48 -0
  30. package/expansion-packs/README.md +3 -0
  31. package/package.json +80 -0
  32. package/tools/bmad-npx-wrapper.js +39 -0
  33. package/tools/builders/web-builder.js +681 -0
  34. package/tools/bump-all-versions.js +106 -0
  35. package/tools/bump-expansion-version.js +83 -0
  36. package/tools/cli.js +152 -0
  37. package/tools/flattener/main.js +570 -0
  38. package/tools/installer/README.md +8 -0
  39. package/tools/installer/bin/xiaoma.js +326 -0
  40. package/tools/installer/config/ide-agent-config.yaml +58 -0
  41. package/tools/installer/config/install.config.yaml +113 -0
  42. package/tools/installer/lib/config-loader.js +253 -0
  43. package/tools/installer/lib/file-manager.js +411 -0
  44. package/tools/installer/lib/ide-base-setup.js +227 -0
  45. package/tools/installer/lib/ide-setup.js +1302 -0
  46. package/tools/installer/lib/installer.js +1772 -0
  47. package/tools/installer/lib/memory-profiler.js +224 -0
  48. package/tools/installer/lib/module-manager.js +110 -0
  49. package/tools/installer/lib/resource-locator.js +310 -0
  50. package/tools/installer/package-lock.json +906 -0
  51. package/tools/installer/package.json +43 -0
  52. package/tools/lib/dependency-resolver.js +179 -0
  53. package/tools/lib/yaml-utils.js +29 -0
  54. package/tools/md-assets/web-agent-startup-instructions.md +39 -0
  55. package/tools/semantic-release-sync-installer.js +30 -0
  56. package/tools/sync-installer-version.js +34 -0
  57. package/tools/update-expansion-version.js +54 -0
  58. package/tools/upgraders/v3-to-v4-upgrader.js +763 -0
  59. package/tools/version-bump.js +79 -0
  60. package/tools/xiaoma-npx-wrapper.js +39 -0
  61. package/tools/yaml-format.js +240 -0
  62. package/xiaoma-core/agent-teams/team-all.yaml +14 -0
  63. package/xiaoma-core/agent-teams/team-fullstack.yaml +18 -0
  64. package/xiaoma-core/agent-teams/team-ide-minimal.yaml +10 -0
  65. package/xiaoma-core/agent-teams/team-no-ui.yaml +13 -0
  66. package/xiaoma-core/agents/analyst.md +81 -0
  67. package/xiaoma-core/agents/architect.md +84 -0
  68. package/xiaoma-core/agents/dev.md +76 -0
  69. package/xiaoma-core/agents/pm.md +81 -0
  70. package/xiaoma-core/agents/po.md +76 -0
  71. package/xiaoma-core/agents/qa.md +69 -0
  72. package/xiaoma-core/agents/sm.md +62 -0
  73. package/xiaoma-core/agents/ux-expert.md +66 -0
  74. package/xiaoma-core/agents/xiaoma-master.md +108 -0
  75. package/xiaoma-core/agents/xiaoma-orchestrator.md +150 -0
  76. package/xiaoma-core/bmad-core/user-guide.md +0 -0
  77. package/xiaoma-core/checklists/architect-checklist.md +443 -0
  78. package/xiaoma-core/checklists/change-checklist.md +182 -0
  79. package/xiaoma-core/checklists/pm-checklist.md +375 -0
  80. package/xiaoma-core/checklists/po-master-checklist.md +441 -0
  81. package/xiaoma-core/checklists/story-dod-checklist.md +101 -0
  82. package/xiaoma-core/checklists/story-draft-checklist.md +156 -0
  83. package/xiaoma-core/core-config.yaml +20 -0
  84. package/xiaoma-core/data/brainstorming-techniques.md +36 -0
  85. package/xiaoma-core/data/elicitation-methods.md +134 -0
  86. package/xiaoma-core/data/technical-preferences.md +3 -0
  87. package/xiaoma-core/data/xiaoma-kb.md +803 -0
  88. package/xiaoma-core/enhanced-ide-development-workflow.md +43 -0
  89. package/xiaoma-core/tasks/advanced-elicitation.md +117 -0
  90. package/xiaoma-core/tasks/brownfield-create-epic.md +160 -0
  91. package/xiaoma-core/tasks/brownfield-create-story.md +147 -0
  92. package/xiaoma-core/tasks/correct-course.md +70 -0
  93. package/xiaoma-core/tasks/create-brownfield-story.md +304 -0
  94. package/xiaoma-core/tasks/create-deep-research-prompt.md +289 -0
  95. package/xiaoma-core/tasks/create-next-story.md +112 -0
  96. package/xiaoma-core/tasks/document-project.md +341 -0
  97. package/xiaoma-core/tasks/facilitate-brainstorming-session.md +136 -0
  98. package/xiaoma-core/tasks/generate-ai-frontend-prompt.md +51 -0
  99. package/xiaoma-core/tasks/index-docs.md +179 -0
  100. package/xiaoma-core/tasks/kb-mode-interaction.md +75 -0
  101. package/xiaoma-core/tasks/review-story.md +145 -0
  102. package/xiaoma-core/tasks/shard-doc.md +187 -0
  103. package/xiaoma-core/tasks/validate-next-story.md +134 -0
  104. package/xiaoma-core/templates/architecture-tmpl.yaml +650 -0
  105. package/xiaoma-core/templates/brainstorming-output-tmpl.yaml +156 -0
  106. package/xiaoma-core/templates/brownfield-architecture-tmpl.yaml +476 -0
  107. package/xiaoma-core/templates/brownfield-prd-tmpl.yaml +280 -0
  108. package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +293 -0
  109. package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +206 -0
  110. package/xiaoma-core/templates/front-end-spec-tmpl.yaml +349 -0
  111. package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +805 -0
  112. package/xiaoma-core/templates/market-research-tmpl.yaml +252 -0
  113. package/xiaoma-core/templates/prd-tmpl.yaml +202 -0
  114. package/xiaoma-core/templates/project-brief-tmpl.yaml +221 -0
  115. package/xiaoma-core/templates/story-tmpl.yaml +137 -0
  116. package/xiaoma-core/user-guide.md +251 -0
  117. package/xiaoma-core/workflows/brownfield-fullstack.yaml +297 -0
  118. package/xiaoma-core/workflows/brownfield-service.yaml +187 -0
  119. package/xiaoma-core/workflows/brownfield-ui.yaml +197 -0
  120. package/xiaoma-core/workflows/greenfield-fullstack.yaml +240 -0
  121. package/xiaoma-core/workflows/greenfield-service.yaml +206 -0
  122. package/xiaoma-core/workflows/greenfield-ui.yaml +235 -0
  123. package/xiaoma-core/working-in-the-brownfield.md +364 -0
@@ -0,0 +1,1302 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const yaml = require("js-yaml");
4
+ const chalk = require("chalk");
5
+ const inquirer = require("inquirer");
6
+ const fileManager = require("./file-manager");
7
+ const configLoader = require("./config-loader");
8
+ const { extractYamlFromAgent } = require("../../lib/yaml-utils");
9
+ const BaseIdeSetup = require("./ide-base-setup");
10
+ const resourceLocator = require("./resource-locator");
11
+
12
+ class IdeSetup extends BaseIdeSetup {
13
+ constructor() {
14
+ super();
15
+ this.ideAgentConfig = null;
16
+ }
17
+
18
+ async loadIdeAgentConfig() {
19
+ if (this.ideAgentConfig) return this.ideAgentConfig;
20
+
21
+ try {
22
+ const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yaml');
23
+ const configContent = await fs.readFile(configPath, 'utf8');
24
+ this.ideAgentConfig = yaml.load(configContent);
25
+ return this.ideAgentConfig;
26
+ } catch (error) {
27
+ console.warn('Failed to load IDE agent configuration, using defaults');
28
+ return {
29
+ 'roo-permissions': {},
30
+ 'cline-order': {}
31
+ };
32
+ }
33
+ }
34
+
35
+ async setup(ide, installDir, selectedAgent = null, spinner = null, preConfiguredSettings = null) {
36
+ const ideConfig = await configLoader.getIdeConfiguration(ide);
37
+
38
+ if (!ideConfig) {
39
+ console.log(chalk.yellow(`\nNo configuration available for ${ide}`));
40
+ return false;
41
+ }
42
+
43
+ switch (ide) {
44
+ case "cursor":
45
+ return this.setupCursor(installDir, selectedAgent);
46
+ case "claude-code":
47
+ return this.setupClaudeCode(installDir, selectedAgent);
48
+ case "windsurf":
49
+ return this.setupWindsurf(installDir, selectedAgent);
50
+ case "trae":
51
+ return this.setupTrae(installDir, selectedAgent);
52
+ case "roo":
53
+ return this.setupRoo(installDir, selectedAgent);
54
+ case "cline":
55
+ return this.setupCline(installDir, selectedAgent);
56
+ case "kilo":
57
+ return this.setupKilocode(installDir, selectedAgent);
58
+ case "gemini":
59
+ return this.setupGeminiCli(installDir, selectedAgent);
60
+ case "github-copilot":
61
+ return this.setupGitHubCopilot(installDir, selectedAgent, spinner, preConfiguredSettings);
62
+ case "qwen-code":
63
+ return this.setupQwenCode(installDir, selectedAgent);
64
+ default:
65
+ console.log(chalk.yellow(`\nIDE ${ide} not yet supported`));
66
+ return false;
67
+ }
68
+ }
69
+
70
+ async setupCursor(installDir, selectedAgent) {
71
+ const cursorRulesDir = path.join(installDir, ".cursor", "rules");
72
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
73
+
74
+ await fileManager.ensureDirectory(cursorRulesDir);
75
+
76
+ for (const agentId of agents) {
77
+ const agentPath = await this.findAgentPath(agentId, installDir);
78
+
79
+ if (agentPath) {
80
+ const mdcContent = await this.createAgentRuleContent(agentId, agentPath, installDir, 'mdc');
81
+ const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`);
82
+ await fileManager.writeFile(mdcPath, mdcContent);
83
+ console.log(chalk.green(`āœ“ Created rule: ${agentId}.mdc`));
84
+ }
85
+ }
86
+
87
+ console.log(chalk.green(`\nāœ“ Created Cursor rules in ${cursorRulesDir}`));
88
+ return true;
89
+ }
90
+
91
+ async setupClaudeCode(installDir, selectedAgent) {
92
+ // Clean up old BMad commands directory if it exists
93
+ const oldBmadCommandsDir = path.join(installDir, ".claude", "commands", "BMad");
94
+ if (await fileManager.pathExists(oldBmadCommandsDir)) {
95
+ await fileManager.removeDirectory(oldBmadCommandsDir);
96
+ console.log(chalk.dim(" Removed old BMad commands directory"));
97
+ }
98
+
99
+ // Setup xiaoma-core commands
100
+ const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
101
+ const coreAgents = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
102
+ const coreTasks = await this.getCoreTaskIds(installDir);
103
+ await this.setupClaudeCodeForPackage(installDir, "core", coreSlashPrefix, coreAgents, coreTasks, ".xiaoma-core");
104
+
105
+ // Setup expansion pack commands
106
+ const expansionPacks = await this.getInstalledExpansionPacks(installDir);
107
+ for (const packInfo of expansionPacks) {
108
+ const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
109
+ const packAgents = await this.getExpansionPackAgents(packInfo.path);
110
+ const packTasks = await this.getExpansionPackTasks(packInfo.path);
111
+
112
+ if (packAgents.length > 0 || packTasks.length > 0) {
113
+ // Use the actual directory name where the expansion pack is installed
114
+ const rootPath = path.relative(installDir, packInfo.path);
115
+ await this.setupClaudeCodeForPackage(installDir, packInfo.name, packSlashPrefix, packAgents, packTasks, rootPath);
116
+ }
117
+ }
118
+
119
+ return true;
120
+ }
121
+
122
+ async setupClaudeCodeForPackage(installDir, packageName, slashPrefix, agentIds, taskIds, rootPath) {
123
+ const commandsBaseDir = path.join(installDir, ".claude", "commands", slashPrefix);
124
+ const agentsDir = path.join(commandsBaseDir, "agents");
125
+ const tasksDir = path.join(commandsBaseDir, "tasks");
126
+
127
+ // Ensure directories exist
128
+ await fileManager.ensureDirectory(agentsDir);
129
+ await fileManager.ensureDirectory(tasksDir);
130
+
131
+ // Setup agents
132
+ for (const agentId of agentIds) {
133
+ // Find the agent file - for expansion packs, prefer the expansion pack version
134
+ let agentPath;
135
+ if (packageName !== "core") {
136
+ // For expansion packs, first try to find the agent in the expansion pack directory
137
+ const expansionPackPath = path.join(installDir, rootPath, "agents", `${agentId}.md`);
138
+ if (await fileManager.pathExists(expansionPackPath)) {
139
+ agentPath = expansionPackPath;
140
+ } else {
141
+ // Fall back to core if not found in expansion pack
142
+ agentPath = await this.findAgentPath(agentId, installDir);
143
+ }
144
+ } else {
145
+ // For core, use the normal search
146
+ agentPath = await this.findAgentPath(agentId, installDir);
147
+ }
148
+
149
+ const commandPath = path.join(agentsDir, `${agentId}.md`);
150
+
151
+ if (agentPath) {
152
+ // Create command file with agent content
153
+ let agentContent = await fileManager.readFile(agentPath);
154
+
155
+ // Replace {root} placeholder with the appropriate root path for this context
156
+ agentContent = agentContent.replace(/{root}/g, rootPath);
157
+
158
+ // Add command header
159
+ let commandContent = `# /${agentId} Command\n\n`;
160
+ commandContent += `When this command is used, adopt the following agent persona:\n\n`;
161
+ commandContent += agentContent;
162
+
163
+ await fileManager.writeFile(commandPath, commandContent);
164
+ console.log(chalk.green(`āœ“ Created agent command: /${agentId}`));
165
+ }
166
+ }
167
+
168
+ // Setup tasks
169
+ for (const taskId of taskIds) {
170
+ // Find the task file - for expansion packs, prefer the expansion pack version
171
+ let taskPath;
172
+ if (packageName !== "core") {
173
+ // For expansion packs, first try to find the task in the expansion pack directory
174
+ const expansionPackPath = path.join(installDir, rootPath, "tasks", `${taskId}.md`);
175
+ if (await fileManager.pathExists(expansionPackPath)) {
176
+ taskPath = expansionPackPath;
177
+ } else {
178
+ // Fall back to core if not found in expansion pack
179
+ taskPath = await this.findTaskPath(taskId, installDir);
180
+ }
181
+ } else {
182
+ // For core, use the normal search
183
+ taskPath = await this.findTaskPath(taskId, installDir);
184
+ }
185
+
186
+ const commandPath = path.join(tasksDir, `${taskId}.md`);
187
+
188
+ if (taskPath) {
189
+ // Create command file with task content
190
+ let taskContent = await fileManager.readFile(taskPath);
191
+
192
+ // Replace {root} placeholder with the appropriate root path for this context
193
+ taskContent = taskContent.replace(/{root}/g, rootPath);
194
+
195
+ // Add command header
196
+ let commandContent = `# /${taskId} Task\n\n`;
197
+ commandContent += `When this command is used, execute the following task:\n\n`;
198
+ commandContent += taskContent;
199
+
200
+ await fileManager.writeFile(commandPath, commandContent);
201
+ console.log(chalk.green(`āœ“ Created task command: /${taskId}`));
202
+ }
203
+ }
204
+
205
+ console.log(chalk.green(`\nāœ“ Created Claude Code commands for ${packageName} in ${commandsBaseDir}`));
206
+ console.log(chalk.dim(` - Agents in: ${agentsDir}`));
207
+ console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
208
+ }
209
+
210
+ async setupWindsurf(installDir, selectedAgent) {
211
+ const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
212
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
213
+
214
+ await fileManager.ensureDirectory(windsurfRulesDir);
215
+
216
+ for (const agentId of agents) {
217
+ // Find the agent file
218
+ const agentPath = await this.findAgentPath(agentId, installDir);
219
+
220
+ if (agentPath) {
221
+ const agentContent = await fileManager.readFile(agentPath);
222
+ const mdPath = path.join(windsurfRulesDir, `${agentId}.md`);
223
+
224
+ // Create MD content (similar to Cursor but without frontmatter)
225
+ let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
226
+ mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
227
+ agentId,
228
+ installDir
229
+ )} agent persona.\n\n`;
230
+ mdContent += "## Agent Activation\n\n";
231
+ mdContent +=
232
+ "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
233
+ mdContent += "```yaml\n";
234
+ // Extract just the YAML content from the agent file
235
+ const yamlContent = extractYamlFromAgent(agentContent);
236
+ if (yamlContent) {
237
+ mdContent += yamlContent;
238
+ } else {
239
+ // If no YAML found, include the whole content minus the header
240
+ mdContent += agentContent.replace(/^#.*$/m, "").trim();
241
+ }
242
+ mdContent += "\n```\n\n";
243
+ mdContent += "## File Reference\n\n";
244
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
245
+ mdContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
246
+ mdContent += "## Usage\n\n";
247
+ mdContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
248
+ agentId,
249
+ installDir
250
+ )} persona and follow all instructions defined in the YAML configuration above.\n`;
251
+
252
+ await fileManager.writeFile(mdPath, mdContent);
253
+ console.log(chalk.green(`āœ“ Created rule: ${agentId}.md`));
254
+ }
255
+ }
256
+
257
+ console.log(chalk.green(`\nāœ“ Created Windsurf rules in ${windsurfRulesDir}`));
258
+
259
+ return true;
260
+ }
261
+
262
+ async setupTrae(installDir, selectedAgent) {
263
+ const traeRulesDir = path.join(installDir, ".trae", "rules");
264
+ const agents = selectedAgent? [selectedAgent] : await this.getAllAgentIds(installDir);
265
+
266
+ await fileManager.ensureDirectory(traeRulesDir);
267
+
268
+ for (const agentId of agents) {
269
+ // Find the agent file
270
+ const agentPath = await this.findAgentPath(agentId, installDir);
271
+
272
+ if (agentPath) {
273
+ const agentContent = await fileManager.readFile(agentPath);
274
+ const mdPath = path.join(traeRulesDir, `${agentId}.md`);
275
+
276
+ // Create MD content (similar to Cursor but without frontmatter)
277
+ let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
278
+ mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
279
+ agentId,
280
+ installDir
281
+ )} agent persona.\n\n`;
282
+ mdContent += "## Agent Activation\n\n";
283
+ mdContent +=
284
+ "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
285
+ mdContent += "```yaml\n";
286
+ // Extract just the YAML content from the agent file
287
+ const yamlContent = extractYamlFromAgent(agentContent);
288
+ if (yamlContent) {
289
+ mdContent += yamlContent;
290
+ }
291
+ else {
292
+ // If no YAML found, include the whole content minus the header
293
+ mdContent += agentContent.replace(/^#.*$/m, "").trim();
294
+ }
295
+ mdContent += "\n```\n\n";
296
+ mdContent += "## File Reference\n\n";
297
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
298
+ mdContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
299
+ mdContent += "## Usage\n\n";
300
+ mdContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
301
+ agentId,
302
+ installDir
303
+ )} persona and follow all instructions defined in the YAML configuration above.\n`;
304
+
305
+ await fileManager.writeFile(mdPath, mdContent);
306
+ console.log(chalk.green(`āœ“ Created rule: ${agentId}.md`));
307
+ }
308
+ }
309
+ }
310
+
311
+ async findAgentPath(agentId, installDir) {
312
+ // Try to find the agent file in various locations
313
+ const possiblePaths = [
314
+ path.join(installDir, ".xiaoma-core", "agents", `${agentId}.md`),
315
+ path.join(installDir, "agents", `${agentId}.md`)
316
+ ];
317
+
318
+ // Also check expansion pack directories
319
+ const glob = require("glob");
320
+ const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
321
+ for (const expDir of expansionDirs) {
322
+ possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
323
+ }
324
+
325
+ for (const agentPath of possiblePaths) {
326
+ if (await fileManager.pathExists(agentPath)) {
327
+ return agentPath;
328
+ }
329
+ }
330
+
331
+ return null;
332
+ }
333
+
334
+ async getAllAgentIds(installDir) {
335
+ const glob = require("glob");
336
+ const allAgentIds = [];
337
+
338
+ // Check core agents in .xiaoma-core or root
339
+ let agentsDir = path.join(installDir, ".xiaoma-core", "agents");
340
+ if (!(await fileManager.pathExists(agentsDir))) {
341
+ agentsDir = path.join(installDir, "agents");
342
+ }
343
+
344
+ if (await fileManager.pathExists(agentsDir)) {
345
+ const agentFiles = glob.sync("*.md", { cwd: agentsDir });
346
+ allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
347
+ }
348
+
349
+ // Also check for expansion pack agents in dot folders
350
+ const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
351
+ for (const expDir of expansionDirs) {
352
+ const fullExpDir = path.join(installDir, expDir);
353
+ const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir });
354
+ allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md")));
355
+ }
356
+
357
+ // Remove duplicates
358
+ return [...new Set(allAgentIds)];
359
+ }
360
+
361
+ async getCoreAgentIds(installDir) {
362
+ const allAgentIds = [];
363
+
364
+ // Check core agents in .xiaoma-core or root only
365
+ let agentsDir = path.join(installDir, ".xiaoma-core", "agents");
366
+ if (!(await fileManager.pathExists(agentsDir))) {
367
+ agentsDir = path.join(installDir, "xiaoma-core", "agents");
368
+ }
369
+
370
+ if (await fileManager.pathExists(agentsDir)) {
371
+ const glob = require("glob");
372
+ const agentFiles = glob.sync("*.md", { cwd: agentsDir });
373
+ allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
374
+ }
375
+
376
+ return [...new Set(allAgentIds)];
377
+ }
378
+
379
+ async getCoreTaskIds(installDir) {
380
+ const allTaskIds = [];
381
+
382
+ // Check core tasks in .xiaoma-core or root only
383
+ let tasksDir = path.join(installDir, ".xiaoma-core", "tasks");
384
+ if (!(await fileManager.pathExists(tasksDir))) {
385
+ tasksDir = path.join(installDir, "xiaoma-core", "tasks");
386
+ }
387
+
388
+ if (await fileManager.pathExists(tasksDir)) {
389
+ const glob = require("glob");
390
+ const taskFiles = glob.sync("*.md", { cwd: tasksDir });
391
+ allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
392
+ }
393
+
394
+ // Check common tasks
395
+ const commonTasksDir = path.join(installDir, "common", "tasks");
396
+ if (await fileManager.pathExists(commonTasksDir)) {
397
+ const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
398
+ allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
399
+ }
400
+
401
+ return [...new Set(allTaskIds)];
402
+ }
403
+
404
+ async getAgentTitle(agentId, installDir) {
405
+ // Try to find the agent file in various locations
406
+ const possiblePaths = [
407
+ path.join(installDir, ".xiaoma-core", "agents", `${agentId}.md`),
408
+ path.join(installDir, "agents", `${agentId}.md`)
409
+ ];
410
+
411
+ // Also check expansion pack directories
412
+ const glob = require("glob");
413
+ const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
414
+ for (const expDir of expansionDirs) {
415
+ possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
416
+ }
417
+
418
+ for (const agentPath of possiblePaths) {
419
+ if (await fileManager.pathExists(agentPath)) {
420
+ try {
421
+ const agentContent = await fileManager.readFile(agentPath);
422
+ const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
423
+
424
+ if (yamlMatch) {
425
+ const yaml = yamlMatch[1];
426
+ const titleMatch = yaml.match(/title:\s*(.+)/);
427
+ if (titleMatch) {
428
+ return titleMatch[1].trim();
429
+ }
430
+ }
431
+ } catch (error) {
432
+ console.warn(`Failed to read agent title for ${agentId}: ${error.message}`);
433
+ }
434
+ }
435
+ }
436
+
437
+ // Fallback to formatted agent ID
438
+ return agentId.split('-').map(word =>
439
+ word.charAt(0).toUpperCase() + word.slice(1)
440
+ ).join(' ');
441
+ }
442
+
443
+ async getAllTaskIds(installDir) {
444
+ const glob = require("glob");
445
+ const allTaskIds = [];
446
+
447
+ // Check core tasks in .xiaoma-core or root
448
+ let tasksDir = path.join(installDir, ".xiaoma-core", "tasks");
449
+ if (!(await fileManager.pathExists(tasksDir))) {
450
+ tasksDir = path.join(installDir, "xiaoma-core", "tasks");
451
+ }
452
+
453
+ if (await fileManager.pathExists(tasksDir)) {
454
+ const taskFiles = glob.sync("*.md", { cwd: tasksDir });
455
+ allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
456
+ }
457
+
458
+ // Check common tasks
459
+ const commonTasksDir = path.join(installDir, "common", "tasks");
460
+ if (await fileManager.pathExists(commonTasksDir)) {
461
+ const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
462
+ allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
463
+ }
464
+
465
+ // Also check for expansion pack tasks in dot folders
466
+ const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
467
+ for (const expDir of expansionDirs) {
468
+ const fullExpDir = path.join(installDir, expDir);
469
+ const expTaskFiles = glob.sync("*.md", { cwd: fullExpDir });
470
+ allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
471
+ }
472
+
473
+ // Check expansion-packs folder tasks
474
+ const expansionPacksDir = path.join(installDir, "expansion-packs");
475
+ if (await fileManager.pathExists(expansionPacksDir)) {
476
+ const expPackDirs = glob.sync("*/tasks", { cwd: expansionPacksDir });
477
+ for (const expDir of expPackDirs) {
478
+ const fullExpDir = path.join(expansionPacksDir, expDir);
479
+ const expTaskFiles = glob.sync("*.md", { cwd: fullExpDir });
480
+ allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
481
+ }
482
+ }
483
+
484
+ // Remove duplicates
485
+ return [...new Set(allTaskIds)];
486
+ }
487
+
488
+ async findTaskPath(taskId, installDir) {
489
+ // Try to find the task file in various locations
490
+ const possiblePaths = [
491
+ path.join(installDir, ".xiaoma-core", "tasks", `${taskId}.md`),
492
+ path.join(installDir, "xiaoma-core", "tasks", `${taskId}.md`),
493
+ path.join(installDir, "common", "tasks", `${taskId}.md`)
494
+ ];
495
+
496
+ // Also check expansion pack directories
497
+ const glob = require("glob");
498
+
499
+ // Check dot folder expansion packs
500
+ const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
501
+ for (const expDir of expansionDirs) {
502
+ possiblePaths.push(path.join(installDir, expDir, `${taskId}.md`));
503
+ }
504
+
505
+ // Check expansion-packs folder
506
+ const expansionPacksDir = path.join(installDir, "expansion-packs");
507
+ if (await fileManager.pathExists(expansionPacksDir)) {
508
+ const expPackDirs = glob.sync("*/tasks", { cwd: expansionPacksDir });
509
+ for (const expDir of expPackDirs) {
510
+ possiblePaths.push(path.join(expansionPacksDir, expDir, `${taskId}.md`));
511
+ }
512
+ }
513
+
514
+ for (const taskPath of possiblePaths) {
515
+ if (await fileManager.pathExists(taskPath)) {
516
+ return taskPath;
517
+ }
518
+ }
519
+
520
+ return null;
521
+ }
522
+
523
+ async getCoreSlashPrefix(installDir) {
524
+ try {
525
+ const coreConfigPath = path.join(installDir, ".xiaoma-core", "core-config.yaml");
526
+ if (!(await fileManager.pathExists(coreConfigPath))) {
527
+ // Try xiaoma-core directory
528
+ const altConfigPath = path.join(installDir, "xiaoma-core", "core-config.yaml");
529
+ if (await fileManager.pathExists(altConfigPath)) {
530
+ const configContent = await fileManager.readFile(altConfigPath);
531
+ const config = yaml.load(configContent);
532
+ return config.slashPrefix || "XiaoMa";
533
+ }
534
+ return "XiaoMa"; // fallback
535
+ }
536
+
537
+ const configContent = await fileManager.readFile(coreConfigPath);
538
+ const config = yaml.load(configContent);
539
+ return config.slashPrefix || "XiaoMa";
540
+ } catch (error) {
541
+ console.warn(`Failed to read core slashPrefix, using default 'XiaoMa': ${error.message}`);
542
+ return "XiaoMa";
543
+ }
544
+ }
545
+
546
+ async getInstalledExpansionPacks(installDir) {
547
+ const expansionPacks = [];
548
+
549
+ // Check for dot-prefixed expansion packs in install directory
550
+ const glob = require("glob");
551
+ const dotExpansions = glob.sync(".xiaoma-*", { cwd: installDir });
552
+
553
+ for (const dotExpansion of dotExpansions) {
554
+ if (dotExpansion !== ".xiaoma-core") {
555
+ const packPath = path.join(installDir, dotExpansion);
556
+ const packName = dotExpansion.substring(1); // remove the dot
557
+ expansionPacks.push({
558
+ name: packName,
559
+ path: packPath
560
+ });
561
+ }
562
+ }
563
+
564
+ // Check for expansion-packs directory style
565
+ const expansionPacksDir = path.join(installDir, "expansion-packs");
566
+ if (await fileManager.pathExists(expansionPacksDir)) {
567
+ const packDirs = glob.sync("*", { cwd: expansionPacksDir });
568
+
569
+ for (const packDir of packDirs) {
570
+ const packPath = path.join(expansionPacksDir, packDir);
571
+ if ((await fileManager.pathExists(packPath)) &&
572
+ (await fileManager.pathExists(path.join(packPath, "config.yaml")))) {
573
+ expansionPacks.push({
574
+ name: packDir,
575
+ path: packPath
576
+ });
577
+ }
578
+ }
579
+ }
580
+
581
+ return expansionPacks;
582
+ }
583
+
584
+ async getExpansionPackSlashPrefix(packPath) {
585
+ try {
586
+ const configPath = path.join(packPath, "config.yaml");
587
+ if (await fileManager.pathExists(configPath)) {
588
+ const configContent = await fileManager.readFile(configPath);
589
+ const config = yaml.load(configContent);
590
+ return config.slashPrefix || path.basename(packPath);
591
+ }
592
+ } catch (error) {
593
+ console.warn(`Failed to read expansion pack slashPrefix from ${packPath}: ${error.message}`);
594
+ }
595
+
596
+ return path.basename(packPath); // fallback to directory name
597
+ }
598
+
599
+ async getExpansionPackAgents(packPath) {
600
+ const agentsDir = path.join(packPath, "agents");
601
+ if (!(await fileManager.pathExists(agentsDir))) {
602
+ return [];
603
+ }
604
+
605
+ try {
606
+ const glob = require("glob");
607
+ const agentFiles = glob.sync("*.md", { cwd: agentsDir });
608
+ return agentFiles.map(file => path.basename(file, ".md"));
609
+ } catch (error) {
610
+ console.warn(`Failed to read expansion pack agents from ${packPath}: ${error.message}`);
611
+ return [];
612
+ }
613
+ }
614
+
615
+ async getExpansionPackTasks(packPath) {
616
+ const tasksDir = path.join(packPath, "tasks");
617
+ if (!(await fileManager.pathExists(tasksDir))) {
618
+ return [];
619
+ }
620
+
621
+ try {
622
+ const glob = require("glob");
623
+ const taskFiles = glob.sync("*.md", { cwd: tasksDir });
624
+ return taskFiles.map(file => path.basename(file, ".md"));
625
+ } catch (error) {
626
+ console.warn(`Failed to read expansion pack tasks from ${packPath}: ${error.message}`);
627
+ return [];
628
+ }
629
+ }
630
+
631
+ async setupRoo(installDir, selectedAgent) {
632
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
633
+
634
+ // Check for existing .roomodes file in project root
635
+ const roomodesPath = path.join(installDir, ".roomodes");
636
+ let existingModes = [];
637
+ let existingContent = "";
638
+
639
+ if (await fileManager.pathExists(roomodesPath)) {
640
+ existingContent = await fileManager.readFile(roomodesPath);
641
+ // Parse existing modes to avoid duplicates
642
+ const modeMatches = existingContent.matchAll(/- slug: ([\w-]+)/g);
643
+ for (const match of modeMatches) {
644
+ existingModes.push(match[1]);
645
+ }
646
+ console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`));
647
+ }
648
+
649
+ // Create new modes content
650
+ let newModesContent = "";
651
+
652
+ // Load dynamic agent permissions from configuration
653
+ const config = await this.loadIdeAgentConfig();
654
+ const agentPermissions = config['roo-permissions'] || {};
655
+
656
+ for (const agentId of agents) {
657
+ // Skip if already exists
658
+ // Check both with and without bmad- prefix to handle both cases
659
+ const checkSlug = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
660
+ if (existingModes.includes(checkSlug)) {
661
+ console.log(chalk.dim(`Skipping ${agentId} - already exists in .roomodes`));
662
+ continue;
663
+ }
664
+
665
+ // Read agent file to extract all information
666
+ const agentPath = await this.findAgentPath(agentId, installDir);
667
+
668
+ if (agentPath) {
669
+ const agentContent = await fileManager.readFile(agentPath);
670
+
671
+ // Extract YAML content
672
+ const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
673
+ if (yamlMatch) {
674
+ const yaml = yamlMatch[1];
675
+
676
+ // Extract agent info from YAML
677
+ const titleMatch = yaml.match(/title:\s*(.+)/);
678
+ const iconMatch = yaml.match(/icon:\s*(.+)/);
679
+ const whenToUseMatch = yaml.match(/whenToUse:\s*"(.+)"/);
680
+ const roleDefinitionMatch = yaml.match(/roleDefinition:\s*"(.+)"/);
681
+
682
+ const title = titleMatch ? titleMatch[1].trim() : await this.getAgentTitle(agentId, installDir);
683
+ const icon = iconMatch ? iconMatch[1].trim() : "šŸ¤–";
684
+ const whenToUse = whenToUseMatch ? whenToUseMatch[1].trim() : `Use for ${title} tasks`;
685
+ const roleDefinition = roleDefinitionMatch
686
+ ? roleDefinitionMatch[1].trim()
687
+ : `You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`;
688
+
689
+
690
+ // Add permissions based on agent type
691
+ const permissions = agentPermissions[agentId];
692
+ // Build mode entry with proper formatting (matching exact indentation)
693
+ // Avoid double "bmad-" prefix for agents that already have it
694
+ const slug = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
695
+ newModesContent += ` - slug: ${slug}\n`;
696
+ newModesContent += ` name: '${icon} ${title}'\n`;
697
+ if (permissions) {
698
+ newModesContent += ` description: '${permissions.description}'\n`;
699
+ }
700
+ newModesContent += ` roleDefinition: ${roleDefinition}\n`;
701
+ newModesContent += ` whenToUse: ${whenToUse}\n`;
702
+ // Get relative path from installDir to agent file
703
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
704
+ newModesContent += ` customInstructions: CRITICAL Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
705
+ newModesContent += ` groups:\n`;
706
+ newModesContent += ` - read\n`;
707
+
708
+ if (permissions) {
709
+ newModesContent += ` - - edit\n`;
710
+ newModesContent += ` - fileRegex: ${permissions.fileRegex}\n`;
711
+ newModesContent += ` description: ${permissions.description}\n`;
712
+ } else {
713
+ newModesContent += ` - edit\n`;
714
+ }
715
+
716
+ console.log(chalk.green(`āœ“ Added mode: bmad-${agentId} (${icon} ${title})`));
717
+ }
718
+ }
719
+ }
720
+
721
+ // Build final roomodes content
722
+ let roomodesContent = "";
723
+ if (existingContent) {
724
+ // If there's existing content, append new modes to it
725
+ roomodesContent = existingContent.trim() + "\n" + newModesContent;
726
+ } else {
727
+ // Create new .roomodes file with proper YAML structure
728
+ roomodesContent = "customModes:\n" + newModesContent;
729
+ }
730
+
731
+ // Write .roomodes file
732
+ await fileManager.writeFile(roomodesPath, roomodesContent);
733
+ console.log(chalk.green("āœ“ Created .roomodes file in project root"));
734
+
735
+ console.log(chalk.green(`\nāœ“ Roo Code setup complete!`));
736
+ console.log(chalk.dim("Custom modes will be available when you open this project in Roo Code"));
737
+
738
+ return true;
739
+ }
740
+
741
+ async setupKilocode(installDir, selectedAgent) {
742
+ const filePath = path.join(installDir, ".kilocodemodes");
743
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
744
+
745
+ let existingModes = [], existingContent = "";
746
+ if (await fileManager.pathExists(filePath)) {
747
+ existingContent = await fileManager.readFile(filePath);
748
+ for (const match of existingContent.matchAll(/- slug: ([\w-]+)/g)) {
749
+ existingModes.push(match[1]);
750
+ }
751
+ console.log(chalk.yellow(`Found existing .kilocodemodes file with ${existingModes.length} modes`));
752
+ }
753
+
754
+ const config = await this.loadIdeAgentConfig();
755
+ const permissions = config['roo-permissions'] || {}; // reuse same roo permissions block (Kilo Code understands same mode schema)
756
+
757
+ let newContent = "";
758
+
759
+ for (const agentId of agents) {
760
+ const slug = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
761
+ if (existingModes.includes(slug)) {
762
+ console.log(chalk.dim(`Skipping ${agentId} - already exists in .kilocodemodes`));
763
+ continue;
764
+ }
765
+
766
+ const agentPath = await this.findAgentPath(agentId, installDir);
767
+ if (!agentPath) {
768
+ console.log(chalk.red(`āœ— Could not find agent file for ${agentId}`));
769
+ continue;
770
+ }
771
+
772
+ const agentContent = await fileManager.readFile(agentPath);
773
+ const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
774
+ if (!yamlMatch) {
775
+ console.log(chalk.red(`āœ— Could not extract YAML block for ${agentId}`));
776
+ continue;
777
+ }
778
+
779
+ const yaml = yamlMatch[1];
780
+
781
+ // Robust fallback for title and icon
782
+ const title = (yaml.match(/title:\s*(.+)/)?.[1]?.trim()) || await this.getAgentTitle(agentId, installDir);
783
+ const icon = (yaml.match(/icon:\s*(.+)/)?.[1]?.trim()) || 'šŸ¤–';
784
+ const whenToUse = (yaml.match(/whenToUse:\s*"(.+)"/)?.[1]?.trim()) || `Use for ${title} tasks`;
785
+ const roleDefinition = (yaml.match(/roleDefinition:\s*"(.+)"/)?.[1]?.trim()) ||
786
+ `You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`;
787
+
788
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
789
+ const customInstructions = `CRITICAL Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode`;
790
+
791
+ // Add permissions from config if they exist
792
+ const agentPermission = permissions[agentId];
793
+
794
+ // Begin .kilocodemodes block
795
+ newContent += ` - slug: ${slug}\n`;
796
+ newContent += ` name: '${icon} ${title}'\n`;
797
+ if (agentPermission) {
798
+ newContent += ` description: '${agentPermission.description}'\n`;
799
+ }
800
+
801
+ newContent += ` roleDefinition: ${roleDefinition}\n`;
802
+ newContent += ` whenToUse: ${whenToUse}\n`;
803
+ newContent += ` customInstructions: ${customInstructions}\n`;
804
+ newContent += ` groups:\n`;
805
+ newContent += ` - read\n`;
806
+
807
+
808
+ if (agentPermission) {
809
+ newContent += ` - - edit\n`;
810
+ newContent += ` - fileRegex: ${agentPermission.fileRegex}\n`;
811
+ newContent += ` description: ${agentPermission.description}\n`;
812
+ } else {
813
+ // Fallback to generic edit
814
+ newContent += ` - edit\n`;
815
+ }
816
+
817
+ console.log(chalk.green(`āœ“ Added Kilo mode: ${slug} (${icon} ${title})`));
818
+ }
819
+
820
+ const finalContent = existingContent
821
+ ? existingContent.trim() + "\n" + newContent
822
+ : "customModes:\n" + newContent;
823
+
824
+ await fileManager.writeFile(filePath, finalContent);
825
+ console.log(chalk.green("āœ“ Created .kilocodemodes file in project root"));
826
+ console.log(chalk.green(`āœ“ KiloCode setup complete!`));
827
+ console.log(chalk.dim("Custom modes will be available when you open this project in KiloCode"));
828
+
829
+ return true;
830
+ }
831
+
832
+ async setupCline(installDir, selectedAgent) {
833
+ const clineRulesDir = path.join(installDir, ".clinerules");
834
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
835
+
836
+ await fileManager.ensureDirectory(clineRulesDir);
837
+
838
+ // Load dynamic agent ordering from configuration
839
+ const config = await this.loadIdeAgentConfig();
840
+ const agentOrder = config['cline-order'] || {};
841
+
842
+ for (const agentId of agents) {
843
+ // Find the agent file
844
+ const agentPath = await this.findAgentPath(agentId, installDir);
845
+
846
+ if (agentPath) {
847
+ const agentContent = await fileManager.readFile(agentPath);
848
+
849
+ // Get numeric prefix for ordering
850
+ const order = agentOrder[agentId] || 99;
851
+ const prefix = order.toString().padStart(2, '0');
852
+ const mdPath = path.join(clineRulesDir, `${prefix}-${agentId}.md`);
853
+
854
+ // Create MD content for Cline (focused on project standards and role)
855
+ let mdContent = `# ${await this.getAgentTitle(agentId, installDir)} Agent\n\n`;
856
+ mdContent += `This rule defines the ${await this.getAgentTitle(agentId, installDir)} persona and project standards.\n\n`;
857
+ mdContent += "## Role Definition\n\n";
858
+ mdContent +=
859
+ "When the user types `@" + agentId + "`, adopt this persona and follow these guidelines:\n\n";
860
+ mdContent += "```yaml\n";
861
+ // Extract just the YAML content from the agent file
862
+ const yamlContent = extractYamlFromAgent(agentContent);
863
+ if (yamlContent) {
864
+ mdContent += yamlContent;
865
+ } else {
866
+ // If no YAML found, include the whole content minus the header
867
+ mdContent += agentContent.replace(/^#.*$/m, "").trim();
868
+ }
869
+ mdContent += "\n```\n\n";
870
+ mdContent += "## Project Standards\n\n";
871
+ mdContent += `- Always maintain consistency with project documentation in .xiaoma-core/\n`;
872
+ mdContent += `- Follow the agent's specific guidelines and constraints\n`;
873
+ mdContent += `- Update relevant project files when making changes\n`;
874
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
875
+ mdContent += `- Reference the complete agent definition in [${relativePath}](${relativePath})\n\n`;
876
+ mdContent += "## Usage\n\n";
877
+ mdContent += `Type \`@${agentId}\` to activate this ${await this.getAgentTitle(agentId, installDir)} persona.\n`;
878
+
879
+ await fileManager.writeFile(mdPath, mdContent);
880
+ console.log(chalk.green(`āœ“ Created rule: ${prefix}-${agentId}.md`));
881
+ }
882
+ }
883
+
884
+ console.log(chalk.green(`\nāœ“ Created Cline rules in ${clineRulesDir}`));
885
+
886
+ return true;
887
+ }
888
+
889
+ async setupGeminiCli(installDir) {
890
+ const geminiDir = path.join(installDir, ".gemini");
891
+ const bmadMethodDir = path.join(geminiDir, "xiaoma-web");
892
+ await fileManager.ensureDirectory(bmadMethodDir);
893
+
894
+ // Update logic for existing settings.json
895
+ const settingsPath = path.join(geminiDir, "settings.json");
896
+ if (await fileManager.pathExists(settingsPath)) {
897
+ try {
898
+ const settingsContent = await fileManager.readFile(settingsPath);
899
+ const settings = JSON.parse(settingsContent);
900
+ let updated = false;
901
+
902
+ // Handle contextFileName property
903
+ if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
904
+ const originalLength = settings.contextFileName.length;
905
+ settings.contextFileName = settings.contextFileName.filter(
906
+ (fileName) => !fileName.startsWith("agents/")
907
+ );
908
+ if (settings.contextFileName.length !== originalLength) {
909
+ updated = true;
910
+ }
911
+ }
912
+
913
+ if (updated) {
914
+ await fileManager.writeFile(
915
+ settingsPath,
916
+ JSON.stringify(settings, null, 2)
917
+ );
918
+ console.log(chalk.green("āœ“ Updated .gemini/settings.json - removed agent file references"));
919
+ }
920
+ } catch (error) {
921
+ console.warn(
922
+ chalk.yellow("Could not update .gemini/settings.json"),
923
+ error
924
+ );
925
+ }
926
+ }
927
+
928
+ // Remove old agents directory
929
+ const agentsDir = path.join(geminiDir, "agents");
930
+ if (await fileManager.pathExists(agentsDir)) {
931
+ await fileManager.removeDirectory(agentsDir);
932
+ console.log(chalk.green("āœ“ Removed old .gemini/agents directory"));
933
+ }
934
+
935
+ // Get all available agents
936
+ const agents = await this.getAllAgentIds(installDir);
937
+ let concatenatedContent = "";
938
+
939
+ for (const agentId of agents) {
940
+ // Find the source agent file
941
+ const agentPath = await this.findAgentPath(agentId, installDir);
942
+
943
+ if (agentPath) {
944
+ const agentContent = await fileManager.readFile(agentPath);
945
+
946
+ // Create properly formatted agent rule content (similar to trae)
947
+ let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
948
+ agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
949
+ agentId,
950
+ installDir
951
+ )} agent persona.\n\n`;
952
+ agentRuleContent += "## Agent Activation\n\n";
953
+ agentRuleContent +=
954
+ "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
955
+ agentRuleContent += "```yaml\n";
956
+ // Extract just the YAML content from the agent file
957
+ const yamlContent = extractYamlFromAgent(agentContent);
958
+ if (yamlContent) {
959
+ agentRuleContent += yamlContent;
960
+ }
961
+ else {
962
+ // If no YAML found, include the whole content minus the header
963
+ agentRuleContent += agentContent.replace(/^#.*$/m, "").trim();
964
+ }
965
+ agentRuleContent += "\n```\n\n";
966
+ agentRuleContent += "## File Reference\n\n";
967
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
968
+ agentRuleContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
969
+ agentRuleContent += "## Usage\n\n";
970
+ agentRuleContent += `When the user types \`*${agentId}\`, activate this ${await this.getAgentTitle(
971
+ agentId,
972
+ installDir
973
+ )} persona and follow all instructions defined in the YAML configuration above.\n`;
974
+
975
+ // Add to concatenated content with separator
976
+ concatenatedContent += agentRuleContent + "\n\n---\n\n";
977
+ console.log(chalk.green(`āœ“ Added context for @${agentId}`));
978
+ }
979
+ }
980
+
981
+ // Write the concatenated content to GEMINI.md
982
+ const geminiMdPath = path.join(bmadMethodDir, "GEMINI.md");
983
+ await fileManager.writeFile(geminiMdPath, concatenatedContent);
984
+ console.log(chalk.green(`\nāœ“ Created GEMINI.md in ${bmadMethodDir}`));
985
+
986
+ return true;
987
+ }
988
+
989
+ async setupQwenCode(installDir, selectedAgent) {
990
+ const qwenDir = path.join(installDir, ".qwen");
991
+ const bmadMethodDir = path.join(qwenDir, "xiaoma-web");
992
+ await fileManager.ensureDirectory(bmadMethodDir);
993
+
994
+ // Update logic for existing settings.json
995
+ const settingsPath = path.join(qwenDir, "settings.json");
996
+ if (await fileManager.pathExists(settingsPath)) {
997
+ try {
998
+ const settingsContent = await fileManager.readFile(settingsPath);
999
+ const settings = JSON.parse(settingsContent);
1000
+ let updated = false;
1001
+
1002
+ // Handle contextFileName property
1003
+ if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
1004
+ const originalLength = settings.contextFileName.length;
1005
+ settings.contextFileName = settings.contextFileName.filter(
1006
+ (fileName) => !fileName.startsWith("agents/")
1007
+ );
1008
+ if (settings.contextFileName.length !== originalLength) {
1009
+ updated = true;
1010
+ }
1011
+ }
1012
+
1013
+ if (updated) {
1014
+ await fileManager.writeFile(
1015
+ settingsPath,
1016
+ JSON.stringify(settings, null, 2)
1017
+ );
1018
+ console.log(chalk.green("āœ“ Updated .qwen/settings.json - removed agent file references"));
1019
+ }
1020
+ } catch (error) {
1021
+ console.warn(
1022
+ chalk.yellow("Could not update .qwen/settings.json"),
1023
+ error
1024
+ );
1025
+ }
1026
+ }
1027
+
1028
+ // Remove old agents directory
1029
+ const agentsDir = path.join(qwenDir, "agents");
1030
+ if (await fileManager.pathExists(agentsDir)) {
1031
+ await fileManager.removeDirectory(agentsDir);
1032
+ console.log(chalk.green("āœ“ Removed old .qwen/agents directory"));
1033
+ }
1034
+
1035
+ // Get all available agents
1036
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
1037
+ let concatenatedContent = "";
1038
+
1039
+ for (const agentId of agents) {
1040
+ // Find the source agent file
1041
+ const agentPath = await this.findAgentPath(agentId, installDir);
1042
+
1043
+ if (agentPath) {
1044
+ const agentContent = await fileManager.readFile(agentPath);
1045
+
1046
+ // Create properly formatted agent rule content (similar to gemini)
1047
+ let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
1048
+ agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
1049
+ agentId,
1050
+ installDir
1051
+ )} agent persona.\n\n`;
1052
+ agentRuleContent += "## Agent Activation\n\n";
1053
+ agentRuleContent +=
1054
+ "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
1055
+ agentRuleContent += "```yaml\n";
1056
+ // Extract just the YAML content from the agent file
1057
+ const yamlContent = extractYamlFromAgent(agentContent);
1058
+ if (yamlContent) {
1059
+ agentRuleContent += yamlContent;
1060
+ }
1061
+ else {
1062
+ // If no YAML found, include the whole content minus the header
1063
+ agentRuleContent += agentContent.replace(/^#.*$/m, "").trim();
1064
+ }
1065
+ agentRuleContent += "\n```\n\n";
1066
+ agentRuleContent += "## File Reference\n\n";
1067
+ const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
1068
+ agentRuleContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
1069
+ agentRuleContent += "## Usage\n\n";
1070
+ agentRuleContent += `When the user types \`*${agentId}\`, activate this ${await this.getAgentTitle(
1071
+ agentId,
1072
+ installDir
1073
+ )} persona and follow all instructions defined in the YAML configuration above.\n`;
1074
+
1075
+ // Add to concatenated content with separator
1076
+ concatenatedContent += agentRuleContent + "\n\n---\n\n";
1077
+ console.log(chalk.green(`āœ“ Added context for *${agentId}`));
1078
+ }
1079
+ }
1080
+
1081
+ // Write the concatenated content to QWEN.md
1082
+ const qwenMdPath = path.join(bmadMethodDir, "QWEN.md");
1083
+ await fileManager.writeFile(qwenMdPath, concatenatedContent);
1084
+ console.log(chalk.green(`\nāœ“ Created QWEN.md in ${bmadMethodDir}`));
1085
+
1086
+ return true;
1087
+ }
1088
+
1089
+ async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
1090
+ // Configure VS Code workspace settings first to avoid UI conflicts with loading spinners
1091
+ await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings);
1092
+
1093
+ const chatmodesDir = path.join(installDir, ".github", "chatmodes");
1094
+ const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
1095
+
1096
+ await fileManager.ensureDirectory(chatmodesDir);
1097
+
1098
+ for (const agentId of agents) {
1099
+ // Find the agent file
1100
+ const agentPath = await this.findAgentPath(agentId, installDir);
1101
+ const chatmodePath = path.join(chatmodesDir, `${agentId}.chatmode.md`);
1102
+
1103
+ if (agentPath) {
1104
+ // Create chat mode file with agent content
1105
+ const agentContent = await fileManager.readFile(agentPath);
1106
+ const agentTitle = await this.getAgentTitle(agentId, installDir);
1107
+
1108
+ // Extract whenToUse for the description
1109
+ const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
1110
+ let description = `Activates the ${agentTitle} agent persona.`;
1111
+ if (yamlMatch) {
1112
+ const whenToUseMatch = yamlMatch[1].match(/whenToUse:\s*"(.*?)"/);
1113
+ if (whenToUseMatch && whenToUseMatch[1]) {
1114
+ description = whenToUseMatch[1];
1115
+ }
1116
+ }
1117
+
1118
+ let chatmodeContent = `---
1119
+ description: "${description.replace(/"/g, '\\"')}"
1120
+ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure']
1121
+ ---
1122
+
1123
+ `;
1124
+ chatmodeContent += agentContent;
1125
+
1126
+ await fileManager.writeFile(chatmodePath, chatmodeContent);
1127
+ console.log(chalk.green(`āœ“ Created chat mode: ${agentId}.chatmode.md`));
1128
+ }
1129
+ }
1130
+
1131
+ console.log(chalk.green(`\nāœ“ Github Copilot setup complete!`));
1132
+ console.log(chalk.dim(`You can now find the XiaoMa agents in the Chat view's mode selector.`));
1133
+
1134
+ return true;
1135
+ }
1136
+
1137
+ async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) {
1138
+ const vscodeDir = path.join(installDir, ".vscode");
1139
+ const settingsPath = path.join(vscodeDir, "settings.json");
1140
+
1141
+ await fileManager.ensureDirectory(vscodeDir);
1142
+
1143
+ // Read existing settings if they exist
1144
+ let existingSettings = {};
1145
+ if (await fileManager.pathExists(settingsPath)) {
1146
+ try {
1147
+ const existingContent = await fileManager.readFile(settingsPath);
1148
+ existingSettings = JSON.parse(existingContent);
1149
+ console.log(chalk.yellow("Found existing .vscode/settings.json. Merging XiaoMa settings..."));
1150
+ } catch (error) {
1151
+ console.warn(chalk.yellow("Could not parse existing settings.json. Creating new one."));
1152
+ existingSettings = {};
1153
+ }
1154
+ }
1155
+
1156
+ // Use pre-configured settings if provided, otherwise prompt
1157
+ let configChoice;
1158
+ if (preConfiguredSettings && preConfiguredSettings.configChoice) {
1159
+ configChoice = preConfiguredSettings.configChoice;
1160
+ console.log(chalk.dim(`Using pre-configured GitHub Copilot settings: ${configChoice}`));
1161
+ } else {
1162
+ // Clear any previous output and add spacing to avoid conflicts with loaders
1163
+ console.log('\n'.repeat(2));
1164
+ console.log(chalk.blue("šŸ”§ Github Copilot Agent Settings Configuration"));
1165
+ console.log(chalk.dim("XiaoMa works best with specific VS Code settings for optimal agent experience."));
1166
+ console.log(''); // Add extra spacing
1167
+
1168
+ const response = await inquirer.prompt([
1169
+ {
1170
+ type: 'list',
1171
+ name: 'configChoice',
1172
+ message: chalk.yellow('How would you like to configure GitHub Copilot settings?'),
1173
+ choices: [
1174
+ {
1175
+ name: 'Use recommended defaults (fastest setup)',
1176
+ value: 'defaults'
1177
+ },
1178
+ {
1179
+ name: 'Configure each setting manually (customize to your preferences)',
1180
+ value: 'manual'
1181
+ },
1182
+ {
1183
+ name: 'Skip settings configuration (I\'ll configure manually later)',
1184
+ value: 'skip'
1185
+ }
1186
+ ],
1187
+ default: 'defaults'
1188
+ }
1189
+ ]);
1190
+ configChoice = response.configChoice;
1191
+ }
1192
+
1193
+ let bmadSettings = {};
1194
+
1195
+ if (configChoice === 'skip') {
1196
+ console.log(chalk.yellow("āš ļø Skipping VS Code settings configuration."));
1197
+ console.log(chalk.dim("You can manually configure these settings in .vscode/settings.json:"));
1198
+ console.log(chalk.dim(" • chat.agent.enabled: true"));
1199
+ console.log(chalk.dim(" • chat.agent.maxRequests: 15"));
1200
+ console.log(chalk.dim(" • github.copilot.chat.agent.runTasks: true"));
1201
+ console.log(chalk.dim(" • chat.mcp.discovery.enabled: true"));
1202
+ console.log(chalk.dim(" • github.copilot.chat.agent.autoFix: true"));
1203
+ console.log(chalk.dim(" • chat.tools.autoApprove: false"));
1204
+ return true;
1205
+ }
1206
+
1207
+ if (configChoice === 'defaults') {
1208
+ // Use recommended defaults
1209
+ bmadSettings = {
1210
+ "chat.agent.enabled": true,
1211
+ "chat.agent.maxRequests": 15,
1212
+ "github.copilot.chat.agent.runTasks": true,
1213
+ "chat.mcp.discovery.enabled": true,
1214
+ "github.copilot.chat.agent.autoFix": true,
1215
+ "chat.tools.autoApprove": false
1216
+ };
1217
+ console.log(chalk.green("āœ“ Using recommended XiaoMa defaults for Github Copilot settings"));
1218
+ } else {
1219
+ // Manual configuration
1220
+ console.log(chalk.blue("\nšŸ“‹ Let's configure each setting for your preferences:"));
1221
+
1222
+ // Pause spinner during manual configuration prompts
1223
+ let spinnerWasActive = false;
1224
+ if (spinner && spinner.isSpinning) {
1225
+ spinner.stop();
1226
+ spinnerWasActive = true;
1227
+ }
1228
+
1229
+ const manualSettings = await inquirer.prompt([
1230
+ {
1231
+ type: 'input',
1232
+ name: 'maxRequests',
1233
+ message: 'Maximum requests per agent session (recommended: 15)?',
1234
+ default: '15',
1235
+ validate: (input) => {
1236
+ const num = parseInt(input);
1237
+ if (isNaN(num) || num < 1 || num > 50) {
1238
+ return 'Please enter a number between 1 and 50';
1239
+ }
1240
+ return true;
1241
+ }
1242
+ },
1243
+ {
1244
+ type: 'confirm',
1245
+ name: 'runTasks',
1246
+ message: 'Allow agents to run workspace tasks (package.json scripts, etc.)?',
1247
+ default: true
1248
+ },
1249
+ {
1250
+ type: 'confirm',
1251
+ name: 'mcpDiscovery',
1252
+ message: 'Enable MCP (Model Context Protocol) server discovery?',
1253
+ default: true
1254
+ },
1255
+ {
1256
+ type: 'confirm',
1257
+ name: 'autoFix',
1258
+ message: 'Enable automatic error detection and fixing in generated code?',
1259
+ default: true
1260
+ },
1261
+ {
1262
+ type: 'confirm',
1263
+ name: 'autoApprove',
1264
+ message: 'Auto-approve ALL tools without confirmation? (āš ļø EXPERIMENTAL - less secure)',
1265
+ default: false
1266
+ }
1267
+ ]);
1268
+
1269
+ // Restart spinner if it was active before prompts
1270
+ if (spinner && spinnerWasActive) {
1271
+ spinner.start();
1272
+ }
1273
+
1274
+ bmadSettings = {
1275
+ "chat.agent.enabled": true, // Always enabled - required for XiaoMa agents
1276
+ "chat.agent.maxRequests": parseInt(manualSettings.maxRequests),
1277
+ "github.copilot.chat.agent.runTasks": manualSettings.runTasks,
1278
+ "chat.mcp.discovery.enabled": manualSettings.mcpDiscovery,
1279
+ "github.copilot.chat.agent.autoFix": manualSettings.autoFix,
1280
+ "chat.tools.autoApprove": manualSettings.autoApprove
1281
+ };
1282
+
1283
+ console.log(chalk.green("āœ“ Custom settings configured"));
1284
+ }
1285
+
1286
+ // Merge settings (existing settings take precedence to avoid overriding user preferences)
1287
+ const mergedSettings = { ...bmadSettings, ...existingSettings };
1288
+
1289
+ // Write the updated settings
1290
+ await fileManager.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
1291
+
1292
+ console.log(chalk.green("āœ“ VS Code workspace settings configured successfully"));
1293
+ console.log(chalk.dim(" Settings written to .vscode/settings.json:"));
1294
+ Object.entries(bmadSettings).forEach(([key, value]) => {
1295
+ console.log(chalk.dim(` • ${key}: ${value}`));
1296
+ });
1297
+ console.log(chalk.dim(""));
1298
+ console.log(chalk.dim("You can modify these settings anytime in .vscode/settings.json"));
1299
+ }
1300
+ }
1301
+
1302
+ module.exports = new IdeSetup();