This guide explains how to add support for a new programming language to the everything-opencode project. Follow this pattern to ensure consistency and maintainability.
Each language in everything-opencode follows a consistent structure with these components:
- Language Detection - Detect if a project uses the language
- Tool Detection - Detect language-specific tools and versions
- Configuration Wizard - Interactive setup for the language
- Command Runner - Execute language-specific commands
- Command Files - Individual command implementations
- Documentation - User-facing documentation
languages/
├── {language}/ # Language-specific code
│ ├── config-wizard.js # Interactive configuration
│ └── tool-detector.js # Tool detection
│
scripts/
├── {language}/ # Language command runner
│ └── command-runner.js # Base command execution
│
scripts/commands/
├── {language}-setup.js # Setup command
├── {language}-test.js # Test command
├── {language}-build.js # Build command
└── ... # Other commands
commands/
├── {language}-setup.md # Setup documentation
├── {language}-test.md # Test documentation
└── ... # Other documentation
mkdir -p languages/{language}The tool detector detects language-specific tools and versions.
File: languages/{language}/tool-detector.js
/**
* {Language} Tool Detector
*
* Detect {Language} development tools with cross-platform support
*/
const { commandExists, runCommand } = require('../../scripts/lib/utils');
const PlatformDetector = require('../../scripts/lib/platform-detector');
class {Language}ToolDetector {
constructor() {
this.platformDetector = new PlatformDetector();
this.tools = [
// List of tools to detect
'{language}',
'build-tool',
'formatter',
'linter',
'package-manager',
];
}
/**
* Detect all {Language} tools
*/
async detectTools() {
const detectedTools = {};
for (const tool of this.tools) {
detectedTools[tool] = await this.detectTool(tool);
}
return detectedTools;
}
/**
* Detect a specific tool
*/
async detectTool(toolName) {
const toolInfo = {
name: toolName,
installed: false,
version: null,
path: null,
};
try {
// Special handling for main language
if (toolName === '{language}') {
return await this.detect{Language}();
}
// Check if tool exists
const exists = commandExists(toolName);
if (!exists) {
return toolInfo;
}
// Get version
const version = await this.getToolVersion(toolName);
toolInfo.installed = true;
toolInfo.version = version;
toolInfo.path = this.platformDetector.getToolPath(toolName);
} catch (error) {
// Tool not found
}
return toolInfo;
}
/**
* Detect {Language} installation
*/
async detect{Language}() {
const toolInfo = {
name: '{language}',
installed: false,
version: null,
path: null,
};
try {
const result = await runCommand('{language} --version');
if (result.success) {
toolInfo.installed = true;
toolInfo.version = result.output.trim();
toolInfo.path = this.platformDetector.getToolPath('{language}');
}
} catch (error) {
// {Language} not installed
}
return toolInfo;
}
/**
* Get tool version
*/
async getToolVersion(toolName) {
try {
const result = await runCommand(`${toolName} --version`);
if (result.success) {
return result.output.trim();
}
} catch (error) {
// Could not get version
}
return null;
}
/**
* Generate environment report
*/
generateEnvironmentReport(detectedTools) {
return {
summary: {
{language}Installed: detectedTools.{language}?.installed || false,
// Add other summary fields
},
{language}: detectedTools.{language},
// Include other tools
};
}
}
module.exports = {Language}ToolDetector;The configuration wizard provides interactive setup.
File: languages/{language}/config-wizard.js
/**
* {Language} Configuration Wizard
*
* Interactive configuration for {Language} projects
*/
const fs = require('fs');
const path = require('path');
const {Language}ToolDetector = require('./tool-detector');
class {Language}ConfigWizard {
constructor(projectPath = process.cwd()) {
this.projectPath = projectPath;
this.toolDetector = new {Language}ToolDetector();
this.detectedTools = null;
}
/**
* Run interactive configuration wizard
*/
async runWizard(options = {}) {
console.log(`🚀 {Language} Project Configuration Wizard\n`);
// Detect tools
this.detectedTools = await this.toolDetector.detectTools();
const report = this.toolDetector.generateEnvironmentReport(
this.detectedTools,
);
// Show environment report
this.showEnvironmentReport(report);
// Check if {Language} is installed
if (!report.summary.{language}Installed) {
console.log(`❌ {Language} is not installed. Please install {Language} first.`);
this.showInstallationGuide('{language}');
return null;
}
// Detect project
const projectType = await this.detectProjectType(options);
// Configure project
const config = await this.configureProject(projectType, options);
// Generate configuration
const fullConfig = this.generateConfiguration(config, report);
// Save configuration
const saved = await this.saveConfiguration(fullConfig);
if (saved) {
this.showSuccessMessage(fullConfig);
}
return fullConfig;
}
/**
* Show environment report
*/
showEnvironmentReport(report) {
console.log('📊 Environment Report:');
console.log('='.repeat(40));
if (report.{language}) {
console.log(`✅ {Language}: ${report.{language}.version}`);
} else {
console.log(`❌ {Language}: Not installed`);
}
// Add other tools
console.log('='.repeat(40) + '\n');
}
/**
* Detect project type
*/
async detectProjectType(options = {}) {
// Check for language-specific project files
const projectInfo = {
type: '{language}',
// Add project-specific information
};
return projectInfo;
}
/**
* Configure project
*/
async configureProject(projectType, options = {}) {
const config = {
name: path.basename(this.projectPath),
type: projectType.type,
version: '1.0.0',
tools: {},
};
// Add detected tools
if (this.detectedTools) {
for (const [toolName, toolInfo] of Object.entries(this.detectedTools)) {
if (toolInfo.installed) {
config.tools[toolName] = {
installed: true,
version: toolInfo.version,
path: toolInfo.path,
};
}
}
}
return config;
}
/**
* Generate full configuration
*/
generateConfiguration(config, report) {
return {
{language}: {
...config,
environment: {
{language}Version: report.{language}?.version || 'unknown',
os: process.platform,
detectedAt: new Date().toISOString(),
},
recommendations: this.generateRecommendations(config, report),
},
};
}
/**
* Generate tool recommendations
*/
generateRecommendations(config, report) {
const recommendations = [];
// Add language-specific recommendations
// Example: recommend formatter if not installed
return recommendations;
}
/**
* Save configuration
*/
async saveConfiguration(config) {
try {
const configManager = require('../../scripts/interactive/config-manager');
const manager = new configManager(this.projectPath);
const existingConfig = manager.loadConfig() || {};
const mergedConfig = {
...existingConfig,
...config,
};
const saved = manager.saveConfig(mergedConfig);
if (saved) {
console.log('✅ Configuration saved successfully!');
return true;
} else {
console.log('❌ Failed to save configuration');
return false;
}
} catch (error) {
console.log('❌ Error saving configuration:', error.message);
return false;
}
}
/**
* Show success message
*/
showSuccessMessage(config) {
console.log(`\n🎉 {Language} Configuration Complete!`);
console.log('='.repeat(50));
console.log(`Project: ${config.{language}.name}`);
console.log(`Type: ${config.{language}.type}`);
console.log('='.repeat(50));
console.log('\n🚀 Available Commands:');
console.log(` /{language}-test - Run tests`);
console.log(` /{language}-build - Build project`);
// Add other commands
console.log('\n💡 Next Steps:');
console.log(` 1. Run /{language}-setup to install recommended tools`);
console.log(` 2. Run /{language}-test to run your tests`);
console.log(` 3. Run /{language}-build to build your project`);
}
/**
* Show installation guide
*/
showInstallationGuide(tool) {
console.log('\n📖 Installation Guide:');
switch (tool) {
case '{language}':
console.log(` macOS: brew install {language}`);
console.log(` Ubuntu: sudo apt install {language}`);
console.log(` Windows: Download from {language-url}`);
break;
default:
console.log(` Install ${tool}: {installation-command}`);
}
console.log('');
}
}
module.exports = {Language}ConfigWizard;The command runner executes language-specific commands.
File: scripts/{language}/command-runner.js
/**
* {Language} Command Runner
*
* Execute {Language} commands with project-specific improvements
*/
const path = require('path');
const { spawn } = require('child_process');
const ConfigManager = require('../interactive/config-manager');
const {Language}ToolDetector = require('../../languages/{language}/tool-detector');
const PlatformDetector = require('../lib/platform-detector');
const { defaultErrorHandler } = require('../lib/error-handler');
// Import shared utilities
const {
ConfigUtils,
FileUtils,
ProjectUtils,
LoggingUtils,
} = require('../lib');
class {Language}CommandRunner {
constructor(projectPath = process.cwd()) {
this.projectPath = projectPath;
this.configManager = new ConfigManager(projectPath);
this.toolDetector = new {Language}ToolDetector();
this.platformDetector = new PlatformDetector();
this.config = null;
this.{language}Config = null;
this.detectedTools = null;
}
/**
* Initialize command runner
*/
async initialize() {
// Validate project type
try {
const projectInfo = ProjectUtils.detectProjectType(this.projectPath);
if (projectInfo.type !== '{language}' && projectInfo.confidence < 0.7) {
LoggingUtils.warn(
`Project detection: ${projectInfo.type} (confidence: ${projectInfo.confidence})`,
);
LoggingUtils.warn(
'This may not be a {Language} project. Some features may not work correctly.',
);
} else if (projectInfo.type === '{language}') {
LoggingUtils.debug(
`Detected {Language} project: ${projectInfo.framework || 'standard {Language}'}`,
);
}
} catch (error) {
LoggingUtils.debug('Project detection failed:', error.message);
}
// Load configuration
try {
this.config = ConfigUtils.loadConfig(this.projectPath);
if (!this.config) {
throw new Error(`Project not configured. Run /{language}-setup first.`);
}
this.{language}Config = this.config.{language};
if (!this.{language}Config) {
throw new Error(
`{Language} configuration not found. Run /{language}-setup first.`,
);
}
ConfigUtils.validateConfig(this.{language}Config, '{language}');
this.detectedTools = await this.toolDetector.detectTools();
return true;
} catch (error) {
LoggingUtils.error(
'Failed to initialize {Language} command runner:',
error.message,
);
LoggingUtils.info(`Run /{language}-setup to configure your {Language} project`);
throw error;
}
}
/**
* Check if tool is available
*/
checkTool(toolName, required = true) {
try {
const isInstalled = ConfigUtils.checkToolInstalled(
this.{language}Config,
toolName,
required,
);
if (!isInstalled && required) {
throw new Error(
`Required {Language} tool '${toolName}' is not installed. Run /{language}-setup to install it.`,
);
}
return isInstalled;
} catch (error) {
if (required) {
LoggingUtils.error(
`{Language} tool '${toolName}' check failed:`,
error.message,
);
LoggingUtils.info(`Run /{language}-setup to install '${toolName}'`);
}
throw error;
}
}
/**
* Execute {Language} command
*/
async execute{Language}Command(command, args = [], options = {}) {
try {
await this.initialize();
this.checkTool('{language}', true);
const defaultOptions = {
cwd: this.projectPath,
stdio: 'inherit',
shell: true,
env: process.env,
};
const finalOptions = {
...defaultOptions,
...options,
env: options.env
? { ...defaultOptions.env, ...options.env }
: defaultOptions.env,
};
LoggingUtils.info(`🚀 Executing: {language} ${command} ${args.join(' ')}`);
return await new Promise((resolve, reject) => {
const {language}Path = this.platformDetector.getToolPath('{language}', {
required: true,
});
const child = spawn({language}Path, [command, ...args], finalOptions);
child.on('close', (code) => {
if (code === 0) {
resolve({ success: true, code: 0 });
} else {
reject(new Error(`{language} ${command} failed with exit code ${code}`));
}
});
child.on('error', (error) => {
LoggingUtils.debug(`🔍 Exec error: ${error.message}`);
reject(new Error(`Failed to execute {language} ${command}: ${error.message}`));
});
});
} catch (error) {
return this._handle{Language}Error(error, { command, args, options });
}
}
/**
* Handle errors
*/
_handle{Language}Error(error, context = {}) {
const errorInfo = defaultErrorHandler.handleError(error, context);
LoggingUtils.error(errorInfo.userMessage);
if (errorInfo.recoverySteps && errorInfo.recoverySteps.length > 0) {
LoggingUtils.info('💡 Recovery steps:');
errorInfo.recoverySteps.forEach((step, i) => {
LoggingUtils.info(` ${i + 1}. ${step}`);
});
}
this._suggest{Language}Fix(error.message, context.command);
const enhancedError = new Error(errorInfo.userMessage);
enhancedError.recoverySteps = errorInfo.recoverySteps;
enhancedError.originalError = error;
throw enhancedError;
}
/**
* Suggest fixes
*/
_suggest{Language}Fix(errorMessage, command) {
LoggingUtils.info('\n💡 {Language} Error Suggestions:');
// Add language-specific suggestions
if (errorMessage.includes('not found')) {
LoggingUtils.info(` • Install {language}: {installation-command}`);
LoggingUtils.info(` • Check PATH environment variable`);
}
}
/**
* Run tests
*/
async test(args = [], options = {}) {
await this.initialize();
// Implement test execution
}
/**
* Build project
*/
async build(args = [], options = {}) {
await this.initialize();
// Implement build execution
}
}
module.exports = {Language}CommandRunner;Create individual command files in scripts/commands/.
Example: scripts/commands/{language}-setup.js
#!/usr/bin/env node
/**
* {Language} Setup Command
*
* Interactive setup for {Language} projects
*/
const {Language}ConfigWizard = require('../../languages/{language}/config-wizard');
async function main() {
try {
const projectPath = process.cwd();
const wizard = new {Language}ConfigWizard(projectPath);
console.log(`🚀 {Language} Project Setup\n`);
const config = await wizard.runWizard();
if (config) {
console.log(`\n✅ Setup completed successfully!`);
console.log(`\n💡 Next steps:`);
console.log(` 1. Run /{language}-test to test your project`);
console.log(` 2. Run /{language}-build to build your project`);
} else {
console.log(`\n❌ Setup failed. Please check the errors above.`);
process.exit(1);
}
} catch (error) {
console.error(`\n❌ Setup failed:`, error.message);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = main;Create user documentation in commands/.
Example: commands/{language}-setup.md
# `/{language}-setup` - {Language} Project Setup
Interactive setup wizard for configuring {Language} projects in opencode.
## Overview
The `/{language}-setup` command guides you through configuring your {Language} project with opencode.
## Usage
```bash
/{language}-setup
```- Automatic Detection: Detects {Language} and related tools
- Environment Report: Shows detailed information about your setup
- Smart Configuration: Creates optimized configuration
- Tool Recommendations: Suggests useful tools to install
- Checks for {Language} installation
- Detects project type and tools
- Guides through configuration options
- Saves configuration for opencode
After successful setup, you'll see:
- Environment Report: Summary of detected tools
- Project Configuration: Details about your project
- Available Commands: List of {Language} commands
- Next Steps: Recommended actions
/{language}-test- Run tests/{language}-build- Build project/{language}-run- Run project
# In a {Language} project directory
/{language}-setup# Install {Language} first:
# macOS: brew install {language}
# Ubuntu: sudo apt install {language}
# Windows: Download from {language-url}- Ensure {Language} project files exist
- Check for language-specific configuration files
- Run setup from the correct directory
/{language}-test- Run tests after setup/{language}-build- Build your project/{language}-run- Run your project
## Testing Your Implementation
### 1. Syntax Check
```bash
node -c languages/{language}/config-wizard.js
node -c languages/{language}/tool-detector.js
node -c scripts/{language}/command-runner.js
node -c scripts/commands/{language}-*.js
npm test# Test setup command
node scripts/commands/{language}-setup.js
# Test other commands
node scripts/commands/{language}-test.js --help- Use the same code structure as other languages
- Follow naming conventions (camelCase for functions, PascalCase for classes)
- Use shared utilities (ConfigUtils, FileUtils, LoggingUtils)
- Use try-catch blocks for async operations
- Provide helpful error messages
- Include recovery suggestions
- Use PlatformDetector for tool paths
- Handle different path separators (/, )
- Consider Windows/macOS/Linux differences
- Document all public methods with JSDoc
- Create user documentation for each command
- Include examples and troubleshooting
- Run ESLint:
npm run lint - Format code:
npm run format - Follow the project's coding standards
Study these existing implementations for reference:
- JavaScript/TypeScript:
languages/javascript/,scripts/javascript/ - Go:
languages/golang/,scripts/golang/ - Python:
languages/python/,scripts/commands/python-*.js - Elixir:
languages/elixir/,scripts/elixir/ - PineScript:
languages/pinescript/,scripts/pinescript/
async detectTool(toolName) {
const toolInfo = {
name: toolName,
installed: false,
version: null,
path: null,
};
try {
const exists = commandExists(toolName);
if (!exists) return toolInfo;
const version = await this.getToolVersion(toolName);
toolInfo.installed = true;
toolInfo.version = version;
toolInfo.path = this.platformDetector.getToolPath(toolName);
} catch (error) {
// Tool not found
}
return toolInfo;
}async executeCommand(command, args = [], options = {}) {
await this.initialize();
this.checkTool('tool-name', true);
LoggingUtils.info(`🚀 Executing: ${command} ${args.join(' ')}`);
return await new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd: this.projectPath,
stdio: 'inherit',
shell: true,
env: { ...process.env, ...options.env },
});
child.on('close', (code) => {
if (code === 0) {
resolve({ success: true, code: 0 });
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
child.on('error', (error) => {
reject(new Error(`Failed to execute command: ${error.message}`));
});
});
}_handleError(error, context = {}) {
const errorInfo = defaultErrorHandler.handleError(error, context);
LoggingUtils.error(errorInfo.userMessage);
if (errorInfo.recoverySteps && errorInfo.recoverySteps.length > 0) {
LoggingUtils.info('💡 Recovery steps:');
errorInfo.recoverySteps.forEach((step, i) => {
LoggingUtils.info(` ${i + 1}. ${step}`);
});
}
// Language-specific suggestions
this._suggestFix(error.message, context.command);
const enhancedError = new Error(errorInfo.userMessage);
enhancedError.recoverySteps = errorInfo.recoverySteps;
enhancedError.originalError = error;
throw enhancedError;
}Once your implementation is complete, consider adding tests:
- Unit Tests: Test individual components
- Integration Tests: Test command execution
- Cross-Platform Tests: Test on different operating systems
- Code Review: Ensure code follows project standards
- Testing: Run all existing tests
- Documentation: Verify documentation is complete
- Examples: Provide usage examples
- Pull Request: Submit for review
If you need help:
- Check existing language implementations
- Review this guide
- Ask for help in project discussions
- Submit issues for bugs or questions
Thank you for contributing to everything-opencode! Your language implementation will help other developers work more efficiently with their preferred programming languages.