feat: add Mistral Vibe CLI support
This commit is contained in:
parent
82ce240234
commit
c7835413ad
|
|
@ -60,7 +60,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,379 @@
|
||||||
|
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.name === 'bmad-master' ? 'agent-bmad-master' : `agent-bmm-${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: ${skill.name}
|
||||||
|
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/_bmad/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(skill, bmadDir) {
|
||||||
|
// Generate agent skill content in the exact format
|
||||||
|
const agentName = skill.name.replace('agent-', '');
|
||||||
|
const isBmmAgent = skill.name.startsWith('agent-bmm-');
|
||||||
|
|
||||||
|
// Read the actual agent file to get the path
|
||||||
|
let agentPath;
|
||||||
|
if (isBmmAgent) {
|
||||||
|
const realAgentName = agentName.replace('bmm-', '');
|
||||||
|
agentPath = `_bmad/bmm/agents/${realAgentName}.md`;
|
||||||
|
} else {
|
||||||
|
agentPath = `_bmad/core/agents/${agentName}.md`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `---
|
||||||
|
name: ${skill.name}
|
||||||
|
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(skill, bmadDir) {
|
||||||
|
// Generate workflow skill content in the exact format
|
||||||
|
const workflowName = skill.name.replace('bmm-', '');
|
||||||
|
|
||||||
|
// Determine the workflow path based on the workflow name
|
||||||
|
let workflowPath;
|
||||||
|
switch (workflowName) {
|
||||||
|
case 'brainstorming':
|
||||||
|
case 'party-mode': {
|
||||||
|
workflowPath = `_bmad/core/workflows/${workflowName}/workflow.md`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'help': {
|
||||||
|
workflowPath = `_bmad/core/tasks/help.md`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// BMM workflows are in various subdirectories
|
||||||
|
workflowPath = `_bmad/bmm/workflows/${workflowName}/workflow.md`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `---
|
||||||
|
name: ${skill.name}
|
||||||
|
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/_bmad/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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue