Compare commits
10 Commits
3aec79d173
...
0bb07e50a3
| Author | SHA1 | Date |
|---|---|---|
|
|
0bb07e50a3 | |
|
|
df176d4206 | |
|
|
d8038d7848 | |
|
|
8043beef0c | |
|
|
cd541cfc00 | |
|
|
df103fc19b | |
|
|
2b27257110 | |
|
|
d83680838b | |
|
|
c7835413ad | |
|
|
82ce240234 |
|
|
@ -36,6 +36,7 @@ cursor
|
||||||
CLAUDE.local.md
|
CLAUDE.local.md
|
||||||
.serena/
|
.serena/
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
.vibe
|
||||||
|
|
||||||
z*/
|
z*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -536,6 +536,11 @@ class BaseIdeSetup {
|
||||||
content = content.replaceAll('_bmad', this.bmadFolderName);
|
content = content.replaceAll('_bmad', this.bmadFolderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace {bmad-folder} placeholder if present
|
||||||
|
if (typeof content === 'string' && content.includes('{bmad-folder}')) {
|
||||||
|
content = content.replaceAll('{bmad-folder}', this.bmadFolderName);
|
||||||
|
}
|
||||||
|
|
||||||
// Replace escape sequence _bmad with literal _bmad
|
// Replace escape sequence _bmad with literal _bmad
|
||||||
if (typeof content === 'string' && content.includes('_bmad')) {
|
if (typeof content === 'string' && content.includes('_bmad')) {
|
||||||
content = content.replaceAll('_bmad', '_bmad');
|
content = content.replaceAll('_bmad', '_bmad');
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class IdeManager {
|
||||||
*/
|
*/
|
||||||
loadCustomInstallerFiles() {
|
loadCustomInstallerFiles() {
|
||||||
const ideDir = __dirname;
|
const ideDir = __dirname;
|
||||||
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
|
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js', 'mistral-vibe.js'];
|
||||||
|
|
||||||
for (const file of customFiles) {
|
for (const file of customFiles) {
|
||||||
const filePath = path.join(ideDir, file);
|
const filePath = path.join(ideDir, file);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
const path = require('node:path');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const os = require('node:os');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||||
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
|
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mistral Vibe setup handler (CLI mode)
|
||||||
|
*/
|
||||||
|
class MistralVibeSetup extends BaseIdeSetup {
|
||||||
|
constructor() {
|
||||||
|
super('mistral-vibe', 'Mistral Vibe CLI', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup Mistral Vibe configuration
|
||||||
|
* @param {string} projectDir - Project directory
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @param {Object} options - Setup options
|
||||||
|
*/
|
||||||
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
|
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||||
|
|
||||||
|
// Always use CLI mode
|
||||||
|
const mode = 'cli';
|
||||||
|
|
||||||
|
// Get the skills directory
|
||||||
|
const skillsDir = path.join(projectDir, '.vibe', 'skills');
|
||||||
|
await fs.ensureDir(skillsDir);
|
||||||
|
await this.clearOldBmadFiles(skillsDir);
|
||||||
|
|
||||||
|
// Collect artifacts and write using the same pattern as Codex
|
||||||
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
|
|
||||||
|
// Collect core and BMM agents separately
|
||||||
|
const { artifacts: coreAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['core']);
|
||||||
|
const { artifacts: bmmAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['bmm']);
|
||||||
|
|
||||||
|
// Filter bmad-master from BMM agents (it appears in both)
|
||||||
|
const filteredBmmAgents = bmmAgents.filter((a) => a.name !== 'bmad-master');
|
||||||
|
|
||||||
|
// Combine: core bmad-master + BMM agents
|
||||||
|
const filteredAgentArtifacts = [...coreAgents, ...filteredBmmAgents];
|
||||||
|
|
||||||
|
// Write agent skills with "agent-" or "agent-bmm-" prefix
|
||||||
|
const agentCount = await this.writeMistralVibeArtifacts(skillsDir, filteredAgentArtifacts, 'agent');
|
||||||
|
|
||||||
|
// Collect workflows
|
||||||
|
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
|
const workflows = await workflowGen.loadWorkflowManifest(bmadDir);
|
||||||
|
|
||||||
|
// Filter to only include BMM workflows (matching existing .vibe/skills/)
|
||||||
|
const bmmWorkflows = workflows ? workflows.filter((wf) => wf.module === 'bmm') : [];
|
||||||
|
|
||||||
|
// Write workflow skills with "bmm-" prefix
|
||||||
|
const workflowCount = await this.writeMistralVibeWorkflows(skillsDir, bmmWorkflows);
|
||||||
|
|
||||||
|
// Write other skills (brainstorming, help, party-mode)
|
||||||
|
const otherCount = await this.writeOtherSkills(skillsDir);
|
||||||
|
|
||||||
|
const totalWritten = agentCount + workflowCount + otherCount;
|
||||||
|
|
||||||
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
|
console.log(chalk.dim(` - ${totalWritten} skills generated`));
|
||||||
|
console.log(chalk.dim(` - Destination: ${skillsDir}`));
|
||||||
|
|
||||||
|
return { success: true, written: totalWritten, destination: skillsDir };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear old BMAD files from the destination directory
|
||||||
|
*/
|
||||||
|
async clearOldBmadFiles(destDir) {
|
||||||
|
if (await fs.pathExists(destDir)) {
|
||||||
|
const existingFiles = await fs.readdir(destDir);
|
||||||
|
for (const file of existingFiles) {
|
||||||
|
if (file !== 'README.md') {
|
||||||
|
const filePath = path.join(destDir, file);
|
||||||
|
const stat = await fs.lstat(filePath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
await fs.remove(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Mistral Vibe agent artifacts
|
||||||
|
*/
|
||||||
|
async writeMistralVibeArtifacts(destDir, artifacts, prefix = '') {
|
||||||
|
let count = 0;
|
||||||
|
for (const artifact of artifacts) {
|
||||||
|
const skillName =
|
||||||
|
prefix === 'agent'
|
||||||
|
? (artifact.module === 'bmm' ? `agent-bmm-${artifact.name}` : `agent-${artifact.name}`)
|
||||||
|
: artifact.name;
|
||||||
|
|
||||||
|
const skillDir = path.join(destDir, skillName);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
const skillContent = await this.generateAgentSkillContent(artifact, skillName);
|
||||||
|
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Mistral Vibe workflow artifacts
|
||||||
|
*/
|
||||||
|
async writeMistralVibeWorkflows(destDir, workflows) {
|
||||||
|
let count = 0;
|
||||||
|
for (const workflow of workflows) {
|
||||||
|
const skillName = `bmm-${workflow.name}`;
|
||||||
|
const skillDir = path.join(destDir, skillName);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
const skillContent = await this.generateWorkflowSkillContent(workflow, skillName);
|
||||||
|
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write other skills (brainstorming, help, party-mode)
|
||||||
|
*/
|
||||||
|
async writeOtherSkills(destDir) {
|
||||||
|
const otherSkills = [
|
||||||
|
{ name: 'brainstorming', description: 'Brainstorm Project Ideas with guided facilitation of a brainstorming coach' },
|
||||||
|
{ name: 'help', description: 'Execute undefined' },
|
||||||
|
{
|
||||||
|
name: 'party-mode',
|
||||||
|
description: 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
for (const skill of otherSkills) {
|
||||||
|
const skillDir = path.join(destDir, skill.name);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
const skillContent = `---
|
||||||
|
name: ${skillName}
|
||||||
|
description: ${skill.description}
|
||||||
|
license: MIT
|
||||||
|
compatibility: Mistral Vibe CLI
|
||||||
|
user-invocable: True
|
||||||
|
---
|
||||||
|
|
||||||
|
# ${skill.name.toUpperCase()}
|
||||||
|
|
||||||
|
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @.vibe/${this.bmadFolderName}/core/workflows/${skill.name}/workflow.md, READ its entire contents and follow its directions exactly!`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAgentSkills(skillsDir, bmadDir, options) {
|
||||||
|
// Get agents from both core and bmm modules
|
||||||
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
|
const { artifacts: coreAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['core']);
|
||||||
|
const { artifacts: bmmAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['bmm']);
|
||||||
|
const allAgents = [...coreAgents, ...bmmAgents];
|
||||||
|
|
||||||
|
let written = 0;
|
||||||
|
for (const artifact of allAgents) {
|
||||||
|
// Use "agent-" prefix for core, "agent-bmm-" for BMM agents to match existing format
|
||||||
|
const skillName = artifact.module === 'bmm' ? `agent-bmm-${artifact.name}` : `agent-${artifact.name}`;
|
||||||
|
const skillDir = path.join(skillsDir, skillName);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
const skillContent = this.addUserInvocableToContent(artifact.content, {
|
||||||
|
...artifact,
|
||||||
|
name: skillName,
|
||||||
|
});
|
||||||
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
||||||
|
await fs.writeFile(skillPath, skillContent);
|
||||||
|
written++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateWorkflowSkills(skillsDir, bmadDir) {
|
||||||
|
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
|
const workflows = await workflowGen.loadWorkflowManifest(bmadDir);
|
||||||
|
|
||||||
|
if (!workflows || workflows.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let written = 0;
|
||||||
|
for (const workflow of workflows) {
|
||||||
|
// Add module prefix to workflow names (bmm- for BMM workflows)
|
||||||
|
const skillName = workflow.module === 'bmm' ? `bmm-${workflow.name}` : workflow.name;
|
||||||
|
const skillDir = path.join(skillsDir, skillName);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
// Generate workflow content in the same format as existing skills
|
||||||
|
const workflowContent = await this.generateSimpleWorkflowSkillContent(workflow, bmadDir);
|
||||||
|
const skillContent = this.addUserInvocableToContent(workflowContent, {
|
||||||
|
name: skillName,
|
||||||
|
description: workflow.description || `${workflow.name} workflow`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
||||||
|
await fs.writeFile(skillPath, skillContent);
|
||||||
|
written++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateSimpleWorkflowSkillContent(workflow, bmadDir) {
|
||||||
|
// Generate content in the same format as existing workflow skills
|
||||||
|
return `IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @${workflow.path}, READ its entire contents and follow its directions exactly!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateOtherSkills(skillsDir, bmadDir) {
|
||||||
|
// Generate additional skills that aren't agents or workflows
|
||||||
|
// These are core workflows that don't have the bmm- prefix
|
||||||
|
const otherSkills = [
|
||||||
|
{ name: 'help', description: 'BMAD help system' },
|
||||||
|
{ name: 'brainstorming', description: 'Brainstorming workflow' },
|
||||||
|
{ name: 'party-mode', description: 'Multi-agent collaboration mode' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let written = 0;
|
||||||
|
for (const skill of otherSkills) {
|
||||||
|
const skillDir = path.join(skillsDir, skill.name);
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
|
||||||
|
// Create basic skill content
|
||||||
|
const skillContent = `---
|
||||||
|
name: ${skill.name}
|
||||||
|
description: ${skill.description}
|
||||||
|
license: MIT
|
||||||
|
compatibility: Mistral Vibe CLI
|
||||||
|
user-invocable: True
|
||||||
|
---
|
||||||
|
|
||||||
|
# ${skill.name.toUpperCase()} Skill
|
||||||
|
|
||||||
|
This skill provides ${skill.description.toLowerCase()} functionality.`;
|
||||||
|
|
||||||
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
||||||
|
await fs.writeFile(skillPath, skillContent);
|
||||||
|
written++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAgentSkillContent(artifact, skillName) {
|
||||||
|
const agentName = artifact.name;
|
||||||
|
const isBmmAgent = artifact.module === 'bmm';
|
||||||
|
const agentPath = isBmmAgent ? `${this.bmadFolderName}/bmm/agents/${agentName}.md` : `${this.bmadFolderName}/core/agents/${agentName}.md`;
|
||||||
|
|
||||||
|
return `---
|
||||||
|
name: ${skillName}
|
||||||
|
description: ${agentName} agent
|
||||||
|
license: MIT
|
||||||
|
compatibility: Mistral Vibe CLI
|
||||||
|
user-invocable: True
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent ${agentName.toUpperCase()}
|
||||||
|
|
||||||
|
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||||
|
|
||||||
|
<agent-activation CRITICAL="TRUE">
|
||||||
|
1. LOAD the FULL agent file from @${agentPath}
|
||||||
|
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
||||||
|
3. Execute ALL activation steps exactly as written in the agent file
|
||||||
|
4. Follow the agent's persona and menu system precisely
|
||||||
|
5. Stay in character throughout the session
|
||||||
|
</agent-activation>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateWorkflowSkillContent(workflow, skillName) {
|
||||||
|
// Generate workflow skill content in the exact format
|
||||||
|
const workflowName = skillName.replace('bmm-', '');
|
||||||
|
|
||||||
|
// Determine the workflow path based on the workflow name
|
||||||
|
let workflowPath;
|
||||||
|
switch (workflowName) {
|
||||||
|
case 'brainstorming':
|
||||||
|
case 'party-mode': {
|
||||||
|
workflowPath = `${this.bmadFolderName}/core/workflows/${workflowName}/workflow.md`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'help': {
|
||||||
|
workflowPath = `${this.bmadFolderName}/core/tasks/help.md`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// BMM workflows are in various subdirectories
|
||||||
|
workflowPath = `${this.bmadFolderName}/bmm/workflows/${workflowName}/workflow.md`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `---
|
||||||
|
name: ${skillName}
|
||||||
|
description: ${this.getWorkflowDescription(workflowName)}
|
||||||
|
license: MIT
|
||||||
|
compatibility: Mistral Vibe CLI
|
||||||
|
user-invocable: True
|
||||||
|
---
|
||||||
|
|
||||||
|
# ${workflowName.toUpperCase()}
|
||||||
|
|
||||||
|
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @${workflowPath}, READ its entire contents and follow its directions exactly!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateOtherSkillContent(skill) {
|
||||||
|
// Generate other skill content
|
||||||
|
const descriptions = {
|
||||||
|
brainstorming: 'Brainstorm Project Ideas with guided facilitation of a brainstorming coach',
|
||||||
|
help: 'Execute undefined',
|
||||||
|
'party-mode': 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations',
|
||||||
|
};
|
||||||
|
|
||||||
|
return `---
|
||||||
|
name: ${skill.name}
|
||||||
|
description: ${descriptions[skill.name] || skill.name}
|
||||||
|
license: MIT
|
||||||
|
compatibility: Mistral Vibe CLI
|
||||||
|
user-invocable: True
|
||||||
|
---
|
||||||
|
|
||||||
|
# ${skill.name.toUpperCase()}
|
||||||
|
|
||||||
|
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @.vibe/${this.bmadFolderName}/core/workflows/${skill.name}/workflow.md, READ its entire contents and follow its directions exactly!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkflowDescription(workflowName) {
|
||||||
|
const descriptions = {
|
||||||
|
'check-implementation-readiness': 'Gate check before implementation',
|
||||||
|
'code-review': 'Validate implementation quality',
|
||||||
|
'correct-course': 'Handle significant mid-sprint changes',
|
||||||
|
'create-architecture': 'Make technical decisions explicit',
|
||||||
|
'create-epics-and-stories': 'Break requirements into implementable work',
|
||||||
|
'create-excalidraw-dataflow': 'Create data flow diagrams',
|
||||||
|
'create-excalidraw-diagram': 'Create diagrams',
|
||||||
|
'create-excalidraw-flowchart': 'Create flowcharts',
|
||||||
|
'create-excalidraw-wireframe': 'Create wireframes',
|
||||||
|
'create-prd': 'Define requirements (FRs/NFRs)',
|
||||||
|
'create-product-brief': 'Capture strategic vision',
|
||||||
|
'create-story': 'Prepare next story for implementation',
|
||||||
|
'create-ux-design': 'Design user experience (when UX matters)',
|
||||||
|
'dev-story': 'Implement the story',
|
||||||
|
'document-project': 'Document the project',
|
||||||
|
'qa-automate': 'Generate tests for existing features',
|
||||||
|
'quick-dev': 'Implement from spec or direct instructions',
|
||||||
|
'quick-spec': 'Define an ad-hoc change',
|
||||||
|
research: 'Validate market, technical, or domain assumptions',
|
||||||
|
retrospective: 'Review after epic completion',
|
||||||
|
'sprint-planning': 'Initialize tracking (once per project)',
|
||||||
|
'sprint-status': 'Track sprint status',
|
||||||
|
};
|
||||||
|
return descriptions[workflowName] || workflowName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { MistralVibeSetup };
|
||||||
|
|
@ -84,6 +84,13 @@ platforms:
|
||||||
target_dir: .gemini/commands
|
target_dir: .gemini/commands
|
||||||
template_type: gemini
|
template_type: gemini
|
||||||
|
|
||||||
|
mistral-vibe:
|
||||||
|
name: "Mistral Vibe CLI"
|
||||||
|
preferred: false
|
||||||
|
category: cli
|
||||||
|
description: "Mistral's vibe command-line interface"
|
||||||
|
# No installer config - uses custom mistral-vibe.js
|
||||||
|
|
||||||
github-copilot:
|
github-copilot:
|
||||||
name: "GitHub Copilot"
|
name: "GitHub Copilot"
|
||||||
preferred: false
|
preferred: false
|
||||||
|
|
|
||||||
|
|
@ -435,95 +435,29 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// NEW INSTALL: Show recommended tools first with "Browse All" option
|
// NEW INSTALL: Show all tools with search
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
const recommendedOptions = preferredIdes.map((ide) => {
|
const allTools = [...preferredIdes, ...otherIdes];
|
||||||
const isConfigured = configuredPreferred.includes(ide.value);
|
|
||||||
return {
|
|
||||||
label: isConfigured ? `${ide.name} ⭐ ✅` : `${ide.name} ⭐`,
|
|
||||||
value: ide.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add "browse all" option at the end if there are additional tools
|
const allToolOptions = allTools.map((ide) => {
|
||||||
if (otherIdes.length > 0) {
|
|
||||||
const totalTools = preferredIdes.length + otherIdes.length;
|
|
||||||
recommendedOptions.push({
|
|
||||||
label: `→ Browse all supported tools (${totalTools} total)...`,
|
|
||||||
value: '__BROWSE_ALL__',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-select previously configured preferred tools
|
|
||||||
const recommendedInitialValues = configuredPreferred.length > 0 ? configuredPreferred : undefined;
|
|
||||||
|
|
||||||
const recommendedSelected = await prompts.multiselect({
|
|
||||||
message: `Integrate with ${chalk.dim('(↑/↓ to navigate • SPACE: select • ENTER: confirm)')}:`,
|
|
||||||
options: recommendedOptions,
|
|
||||||
initialValues: recommendedInitialValues,
|
|
||||||
required: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedRecommended = recommendedSelected || [];
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// STEP 2: Handle "Browse All" selection - show additional tools if requested
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
const wantsBrowseAll = selectedRecommended.includes('__BROWSE_ALL__');
|
|
||||||
const filteredRecommended = selectedRecommended.filter((v) => v !== '__BROWSE_ALL__');
|
|
||||||
|
|
||||||
// Show additional tools if:
|
|
||||||
// 1. User explicitly chose "Browse All", OR
|
|
||||||
// 2. User has previously configured "other" tools, OR
|
|
||||||
// 3. User selected no recommended tools (allow them to pick from other tools)
|
|
||||||
const showAdditionalTools = wantsBrowseAll || configuredOther.length > 0 || filteredRecommended.length === 0;
|
|
||||||
|
|
||||||
let selectedAdditionalOrAll = [];
|
|
||||||
|
|
||||||
if (showAdditionalTools) {
|
|
||||||
// Show ALL tools if:
|
|
||||||
// - User explicitly chose "Browse All", OR
|
|
||||||
// - User selected nothing from recommended (so they can pick from everything)
|
|
||||||
// Otherwise, show only "other" tools as additional options
|
|
||||||
const showAllTools = wantsBrowseAll || filteredRecommended.length === 0;
|
|
||||||
const toolsToShow = showAllTools ? [...preferredIdes, ...otherIdes] : otherIdes;
|
|
||||||
|
|
||||||
if (toolsToShow.length > 0) {
|
|
||||||
const allToolOptions = toolsToShow.map((ide) => {
|
|
||||||
const isConfigured = configuredIdes.includes(ide.value);
|
|
||||||
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
|
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
|
||||||
let label = ide.name;
|
let label = ide.name;
|
||||||
if (isPreferred) label += ' ⭐';
|
if (isPreferred) label += ' ⭐';
|
||||||
if (isConfigured) label += ' ✅';
|
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
value: ide.value,
|
value: ide.value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pre-select: previously configured tools + any recommended tools already selected
|
const selectedIdes = await prompts.autocompleteMultiselect({
|
||||||
const initialValues = [...configuredIdes, ...filteredRecommended].filter((v, i, arr) => arr.indexOf(v) === i); // dedupe
|
message: 'Select tools:',
|
||||||
|
|
||||||
// Use "additional" only if user already selected some recommended tools
|
|
||||||
const isAdditional = !wantsBrowseAll && filteredRecommended.length > 0;
|
|
||||||
|
|
||||||
console.log('');
|
|
||||||
const selected = await prompts.autocompleteMultiselect({
|
|
||||||
message: isAdditional ? 'Select additional tools:' : 'Select tools:',
|
|
||||||
options: allToolOptions,
|
options: allToolOptions,
|
||||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
initialValues: configuredIdes.length > 0 ? configuredIdes : undefined,
|
||||||
required: false,
|
required: false,
|
||||||
maxItems: 8,
|
maxItems: 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
selectedAdditionalOrAll = selected || [];
|
const allSelectedIdes = selectedIdes || [];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine selections:
|
|
||||||
// - If "Browse All" was used, the second prompt contains ALL selections
|
|
||||||
// - Otherwise, combine recommended + additional
|
|
||||||
const allSelectedIdes = wantsBrowseAll ? selectedAdditionalOrAll : [...filteredRecommended, ...selectedAdditionalOrAll];
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// STEP 3: Confirm if no tools selected
|
// STEP 3: Confirm if no tools selected
|
||||||
|
|
@ -547,7 +481,6 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display selected tools
|
// Display selected tools
|
||||||
const allTools = [...preferredIdes, ...otherIdes];
|
|
||||||
this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
|
this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue