feat(ide): add Bob IDE installer support
- bob.js: fix installCustomAgentLauncher using wrong mode schema (description/prompt/always/permissions → roleDefinition/whenToUse/ customInstructions/groups) - bob.js: add detectionPaths for .bob directory - manager.js: update comments to include bob.js in custom installer list - platform-codes.yaml: move bob entry to correct alphabetical position - eslint.config.mjs: add .bob/** to eslint ignores (like .claude, .roo, etc.)
This commit is contained in:
parent
3bbfc89c27
commit
d21ed92ae8
|
|
@ -20,6 +20,7 @@ export default [
|
||||||
'website/**',
|
'website/**',
|
||||||
// Gitignored patterns
|
// Gitignored patterns
|
||||||
'z*/**', // z-samples, z1, z2, etc.
|
'z*/**', // z-samples, z1, z2, etc.
|
||||||
|
'.bob/**',
|
||||||
'.claude/**',
|
'.claude/**',
|
||||||
'.codex/**',
|
'.codex/**',
|
||||||
'.github/chatmodes/**',
|
'.github/chatmodes/**',
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IBM Bob IDE setup handler
|
* IBM Bob IDE setup handler
|
||||||
* Creates custom modes in .bobmodes file (similar to Kilo)
|
* Creates custom modes in .bob/custom_modes.yaml file
|
||||||
*/
|
*/
|
||||||
class BobSetup extends BaseIdeSetup {
|
class BobSetup extends BaseIdeSetup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('bob', 'IBM Bob');
|
super('bob', 'IBM Bob');
|
||||||
this.configFile = '.bobmodes';
|
this.configFile = '.bob/custom_modes.yaml';
|
||||||
|
this.detectionPaths = ['.bob'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -38,7 +39,7 @@ class BobSetup extends BaseIdeSetup {
|
||||||
config = yaml.parse(existingContent) || {};
|
config = yaml.parse(existingContent) || {};
|
||||||
} catch {
|
} catch {
|
||||||
// If parsing fails, start fresh but warn user
|
// If parsing fails, start fresh but warn user
|
||||||
await prompts.log.warn('Warning: Could not parse existing .bobmodes, starting fresh');
|
await prompts.log.warn('Warning: Could not parse existing .bob/custom_modes.yaml, starting fresh');
|
||||||
config = {};
|
config = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +62,7 @@ class BobSetup extends BaseIdeSetup {
|
||||||
addedCount++;
|
addedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write .bobmodes file with proper YAML structure
|
// Write .bob/custom_modes.yaml file with proper YAML structure
|
||||||
const finalContent = yaml.stringify(config, { lineWidth: 0 });
|
const finalContent = yaml.stringify(config, { lineWidth: 0 });
|
||||||
await this.writeFile(bobModesPath, finalContent);
|
await this.writeFile(bobModesPath, finalContent);
|
||||||
|
|
||||||
|
|
@ -110,23 +111,25 @@ class BobSetup extends BaseIdeSetup {
|
||||||
* @returns {Object} Mode object for YAML serialization
|
* @returns {Object} Mode object for YAML serialization
|
||||||
*/
|
*/
|
||||||
async createModeObject(artifact, projectDir) {
|
async createModeObject(artifact, projectDir) {
|
||||||
// Extract metadata from launcher content
|
// Extract title and icon from the compiled agent file's <agent> XML tag
|
||||||
const titleMatch = artifact.content.match(/title="([^"]+)"/);
|
// artifact.content is the launcher template which does NOT contain these attributes
|
||||||
const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name);
|
let title = this.formatTitle(artifact.name);
|
||||||
|
let icon = '🤖';
|
||||||
|
|
||||||
const iconMatch = artifact.content.match(/icon="([^"]+)"/);
|
if (artifact.sourcePath && (await this.pathExists(artifact.sourcePath))) {
|
||||||
const icon = iconMatch ? iconMatch[1] : '🤖';
|
const agentContent = await this.readFile(artifact.sourcePath);
|
||||||
|
const titleMatch = agentContent.match(/<agent[^>]*\stitle="([^"]+)"/);
|
||||||
|
if (titleMatch) title = titleMatch[1];
|
||||||
|
const iconMatch = agentContent.match(/<agent[^>]*\sicon="([^"]+)"/);
|
||||||
|
if (iconMatch) icon = iconMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
const whenToUseMatch = artifact.content.match(/whenToUse="([^"]+)"/);
|
const whenToUse = `Use for ${title} tasks`;
|
||||||
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
|
|
||||||
|
|
||||||
// Get the activation header from central template (trim to avoid YAML formatting issues)
|
// Get the activation header from central template (trim to avoid YAML formatting issues)
|
||||||
const activationHeader = (await this.getAgentCommandHeader()).trim();
|
const activationHeader = (await this.getAgentCommandHeader()).trim();
|
||||||
|
|
||||||
const roleDefinitionMatch = artifact.content.match(/roleDefinition="([^"]+)"/);
|
const roleDefinition = `You are a ${title} specializing in ${title.toLowerCase()} tasks.`;
|
||||||
const roleDefinition = roleDefinitionMatch
|
|
||||||
? roleDefinitionMatch[1]
|
|
||||||
: `You are a ${title} specializing in ${title.toLowerCase()} tasks.`;
|
|
||||||
|
|
||||||
// Get relative path
|
// Get relative path
|
||||||
const relativePath = path.relative(projectDir, artifact.sourcePath).replaceAll('\\', '/');
|
const relativePath = path.relative(projectDir, artifact.sourcePath).replaceAll('\\', '/');
|
||||||
|
|
@ -189,12 +192,12 @@ class BobSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
if (removedCount > 0) {
|
if (removedCount > 0) {
|
||||||
await fs.writeFile(bobModesPath, yaml.stringify(config, { lineWidth: 0 }));
|
await fs.writeFile(bobModesPath, yaml.stringify(config, { lineWidth: 0 }));
|
||||||
if (!options.silent) await prompts.log.message(`Removed ${removedCount} BMAD modes from .bobmodes`);
|
if (!options.silent) await prompts.log.message(`Removed ${removedCount} BMAD modes from .bob/custom_modes.yaml`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// If parsing fails, leave file as-is
|
// If parsing fails, leave file as-is
|
||||||
if (!options.silent) await prompts.log.warn('Warning: Could not parse .bobmodes for cleanup');
|
if (!options.silent) await prompts.log.warn('Warning: Could not parse .bob/custom_modes.yaml for cleanup');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +218,7 @@ class BobSetup extends BaseIdeSetup {
|
||||||
const bobmodesPath = path.join(projectDir, this.configFile);
|
const bobmodesPath = path.join(projectDir, this.configFile);
|
||||||
let config = {};
|
let config = {};
|
||||||
|
|
||||||
// Read existing .bobmodes file
|
// Read existing .bob/custom_modes.yaml file
|
||||||
if (await this.pathExists(bobmodesPath)) {
|
if (await this.pathExists(bobmodesPath)) {
|
||||||
const existingContent = await this.readFile(bobmodesPath);
|
const existingContent = await this.readFile(bobmodesPath);
|
||||||
try {
|
try {
|
||||||
|
|
@ -245,16 +248,18 @@ class BobSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom mode object
|
// Add custom mode object
|
||||||
|
const title = `BMAD Custom: ${agentName}`;
|
||||||
|
const activationHeader = (await this.getAgentCommandHeader()).trim();
|
||||||
config.customModes.push({
|
config.customModes.push({
|
||||||
slug: slug,
|
slug: slug,
|
||||||
name: `BMAD Custom: ${agentName}`,
|
name: title,
|
||||||
description: `Custom BMAD agent: ${agentName}\n\n**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent!\n\nThis is a launcher for the custom BMAD agent "${agentName}". The agent will follow the persona and instructions from the main agent file.\n`,
|
roleDefinition: `You are a custom BMAD agent "${agentName}". Follow the persona and instructions from the agent file.`,
|
||||||
prompt: `@${agentPath}\n`,
|
whenToUse: `Use for custom BMAD agent "${agentName}" tasks`,
|
||||||
always: false,
|
customInstructions: `${activationHeader} Read the full agent from ${agentPath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`,
|
||||||
permissions: 'all',
|
groups: ['read', 'edit', 'browser', 'command', 'mcp'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write .bobmodes file with proper YAML structure
|
// Write .bob/custom_modes.yaml file with proper YAML structure
|
||||||
await this.writeFile(bobmodesPath, yaml.stringify(config, { lineWidth: 0 }));
|
await this.writeFile(bobmodesPath, yaml.stringify(config, { lineWidth: 0 }));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const prompts = require('../../../lib/prompts');
|
||||||
* Dynamically discovers and loads IDE handlers
|
* Dynamically discovers and loads IDE handlers
|
||||||
*
|
*
|
||||||
* Loading strategy:
|
* Loading strategy:
|
||||||
* 1. Custom installer files (codex.js, github-copilot.js, kilo.js, rovodev.js) - for platforms with unique installation logic
|
* 1. Custom installer files (bob.js, codex.js, github-copilot.js, kilo.js, rovodev.js) - for platforms with unique installation logic
|
||||||
* 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns
|
* 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns
|
||||||
*/
|
*/
|
||||||
class IdeManager {
|
class IdeManager {
|
||||||
|
|
@ -44,7 +44,7 @@ class IdeManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically load all IDE handlers
|
* Dynamically load all IDE handlers
|
||||||
* 1. Load custom installer files first (codex.js, github-copilot.js, kilo.js, rovodev.js)
|
* 1. Load custom installer files first (bob.js, codex.js, github-copilot.js, kilo.js, rovodev.js)
|
||||||
* 2. Load config-driven handlers from platform-codes.yaml
|
* 2. Load config-driven handlers from platform-codes.yaml
|
||||||
*/
|
*/
|
||||||
async loadHandlers() {
|
async loadHandlers() {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@ platforms:
|
||||||
target_dir: .augment/commands
|
target_dir: .augment/commands
|
||||||
template_type: default
|
template_type: default
|
||||||
|
|
||||||
|
bob:
|
||||||
|
name: "IBM Bob"
|
||||||
|
preferred: false
|
||||||
|
category: ide
|
||||||
|
description: "IBM's AI development environment"
|
||||||
|
# No installer config - uses custom bob.js (creates .bob/custom_modes.yaml)
|
||||||
|
|
||||||
claude-code:
|
claude-code:
|
||||||
name: "Claude Code"
|
name: "Claude Code"
|
||||||
preferred: true
|
preferred: true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue