feat(installer): add AICockpit IDE integration
This commit introduces support for AICockpit as a target IDE in the installer. When selected, the installer will generate or update a `.aicockpitmodes` file in the project's root directory. Each agent is configured as a custom mode within AICockpit, using metadata extracted from the agent's YAML definition. This allows users to easily use their agents directly within the AICockpit environment. The integration reuses the existing `roo-permissions` configuration for setting file access permissions.
This commit is contained in:
parent
f09e282d72
commit
cffd7c8cd8
|
|
@ -49,7 +49,7 @@ program
|
||||||
.option('-d, --directory <path>', 'Installation directory')
|
.option('-d, --directory <path>', 'Installation directory')
|
||||||
.option(
|
.option(
|
||||||
'-i, --ide <ide...>',
|
'-i, --ide <ide...>',
|
||||||
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, opencode, other)',
|
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, aicockpit, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, opencode, other)',
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
'-e, --expansion-packs <packs...>',
|
'-e, --expansion-packs <packs...>',
|
||||||
|
|
@ -402,6 +402,7 @@ async function promptInstallation() {
|
||||||
{ name: 'Trae', value: 'trae' }, // { name: 'Trae', value: 'trae'}
|
{ name: 'Trae', value: 'trae' }, // { name: 'Trae', value: 'trae'}
|
||||||
{ name: 'Roo Code', value: 'roo' },
|
{ name: 'Roo Code', value: 'roo' },
|
||||||
{ name: 'Kilo Code', value: 'kilo' },
|
{ name: 'Kilo Code', value: 'kilo' },
|
||||||
|
{ name: 'AICockpit', value: 'aicockpit' },
|
||||||
{ name: 'Cline', value: 'cline' },
|
{ name: 'Cline', value: 'cline' },
|
||||||
{ name: 'Gemini CLI', value: 'gemini' },
|
{ name: 'Gemini CLI', value: 'gemini' },
|
||||||
{ name: 'Qwen Code', value: 'qwen-code' },
|
{ name: 'Qwen Code', value: 'qwen-code' },
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,9 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
case 'kilo': {
|
case 'kilo': {
|
||||||
return this.setupKilocode(installDir, selectedAgent);
|
return this.setupKilocode(installDir, selectedAgent);
|
||||||
}
|
}
|
||||||
|
case 'aicockpit': {
|
||||||
|
return this.setupAICockpit(installDir, selectedAgent);
|
||||||
|
}
|
||||||
case 'gemini': {
|
case 'gemini': {
|
||||||
return this.setupGeminiCli(installDir, selectedAgent);
|
return this.setupGeminiCli(installDir, selectedAgent);
|
||||||
}
|
}
|
||||||
|
|
@ -1921,6 +1924,101 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setupAICockpit(installDir, selectedAgent) {
|
||||||
|
const filePath = path.join(installDir, '.aicockpitmodes');
|
||||||
|
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||||
|
|
||||||
|
let existingModes = [],
|
||||||
|
existingContent = '';
|
||||||
|
if (await fileManager.pathExists(filePath)) {
|
||||||
|
existingContent = await fileManager.readFile(filePath);
|
||||||
|
for (const match of existingContent.matchAll(/- slug: ([\w-]+)/g)) {
|
||||||
|
existingModes.push(match[1]);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(`Found existing .aicockpitmodes file with ${existingModes.length} modes`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await this.loadIdeAgentConfig();
|
||||||
|
const permissions = config['roo-permissions'] || {}; // reuse same roo permissions block (AICockpit understands same mode schema as Kilo Code)
|
||||||
|
|
||||||
|
let newContent = '';
|
||||||
|
|
||||||
|
for (const agentId of agents) {
|
||||||
|
const slug = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
|
||||||
|
if (existingModes.includes(slug)) {
|
||||||
|
console.log(chalk.dim(`Skipping ${agentId} - already exists in .aicockpitmodes`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentPath = await this.findAgentPath(agentId, installDir);
|
||||||
|
if (!agentPath) {
|
||||||
|
console.log(chalk.red(`✗ Could not find agent file for ${agentId}`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentContent = await fileManager.readFile(agentPath);
|
||||||
|
const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
|
||||||
|
if (!yamlMatch) {
|
||||||
|
console.log(chalk.red(`✗ Could not extract YAML block for ${agentId}`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const yaml = yamlMatch[1];
|
||||||
|
|
||||||
|
// Robust fallback for title and icon
|
||||||
|
const title =
|
||||||
|
yaml.match(/title:\s*(.+)/)?.[1]?.trim() || (await this.getAgentTitle(agentId, installDir));
|
||||||
|
const icon = yaml.match(/icon:\s*(.+)/)?.[1]?.trim() || '🤖';
|
||||||
|
const whenToUse = yaml.match(/whenToUse:\s*"(.+)"/)?.[1]?.trim() || `Use for ${title} tasks`;
|
||||||
|
const roleDefinition =
|
||||||
|
yaml.match(/roleDefinition:\s*"(.+)"/)?.[1]?.trim() ||
|
||||||
|
`You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`;
|
||||||
|
|
||||||
|
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
|
||||||
|
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`;
|
||||||
|
|
||||||
|
// Add permissions from config if they exist
|
||||||
|
const agentPermission = permissions[agentId];
|
||||||
|
|
||||||
|
// Begin .aicockpitmodes block
|
||||||
|
newContent += ` - slug: ${slug}\n`;
|
||||||
|
newContent += ` name: '${icon} ${title}'\n`;
|
||||||
|
if (agentPermission) {
|
||||||
|
newContent += ` description: '${agentPermission.description}'\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
newContent += ` roleDefinition: ${roleDefinition}\n`;
|
||||||
|
newContent += ` whenToUse: ${whenToUse}\n`;
|
||||||
|
newContent += ` customInstructions: ${customInstructions}\n`;
|
||||||
|
newContent += ` groups:\n`;
|
||||||
|
newContent += ` - read\n`;
|
||||||
|
|
||||||
|
if (agentPermission) {
|
||||||
|
newContent += ` - - edit\n`;
|
||||||
|
newContent += ` - fileRegex: ${agentPermission.fileRegex}\n`;
|
||||||
|
newContent += ` description: ${agentPermission.description}\n`;
|
||||||
|
} else {
|
||||||
|
// Fallback to generic edit
|
||||||
|
newContent += ` - edit\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green(`✓ Added AICockpit mode: ${slug} (${icon} ${title})`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalContent = existingContent
|
||||||
|
? existingContent.trim() + '\n' + newContent
|
||||||
|
: 'customModes:\n' + newContent;
|
||||||
|
|
||||||
|
await fileManager.writeFile(filePath, finalContent);
|
||||||
|
console.log(chalk.green('✓ Created .aicockpitmodes file in project root'));
|
||||||
|
console.log(chalk.green(`✓ AICockpit setup complete!`));
|
||||||
|
console.log(chalk.dim('Custom modes will be available when you open this project in AICockpit'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async setupCline(installDir, selectedAgent) {
|
async setupCline(installDir, selectedAgent) {
|
||||||
const clineRulesDir = path.join(installDir, '.clinerules');
|
const clineRulesDir = path.join(installDir, '.clinerules');
|
||||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue