ez-vibecoder
Version:
WORD-IN-PROGRESS: Vibe Coding Tools installer for VS Code AI-assisted development
843 lines (755 loc) • 31.6 kB
JavaScript
/**
* RooFlow Installer
* =====================================
*
* This script installs and configures RooFlow and other compatible AI assistant tools
* for VS Code development environments. It performs the following tasks:
*
* 1. Copies configuration files from local template folders
* 2. Creates the necessary directory structure for multiple AI tools
* 3. Validates YAML structure in system prompt files
* 4. Fixes common issues like duplicate capabilities sections
* 5. Sets up environment variables using insert-variables scripts
*
* The installer supports multiple AI tool directories (.roo, .cline, .windsurf, .cursor)
* and maintains compatibility with their specific configuration requirements.
*
* Author: Ardumotion
* License: MIT
*/
const https = require("https");
const AdmZip = require("adm-zip");
const fs = require("fs");
const path = require("path");
const os = require("os");
const { execSync } = require("child_process");
const sanitizerScript = require("./sanitizer"); // Import our YAML sanitizer module
// Terminal color constants for better readability in console output
const GREEN = "\x1b[32m"; // Success messages
const BLUE = "\x1b[34m"; // Informational highlights
const YELLOW = "\x1b[33m"; // Warnings
const RED = "\x1b[31m"; // Errors
const RESET = "\x1b[0m"; // Reset color
const projectRoot = findProjectRoot();
const isPostinstall = process.env.npm_lifecycle_event === "postinstall";
const AI_IDE_EXTENSIONS = {
".roo": {
url: "https://codeload.github.com/GreatScottyMac/RooFlow/zip/refs/heads/main",
templateDir: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo"),
filesToCopy: [
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo", "system-prompt-architect"), destFolder: path.join(".roo", "system-prompt-architect.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo", "system-prompt-ask"), destFolder: path.join(".roo", "system-prompt-ask.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo", "system-prompt-code"), destFolder: path.join(".roo", "system-prompt-code.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo", "system-prompt-debug"), destFolder: path.join(".roo", "system-prompt-debug.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roo", "system-prompt-test"), destFolder: path.join(".roo", "system-prompt-test.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "default-system-prompt.md"), destFolder: path.join(".roo", "system-prompt-default.md"), overwritePermission: false},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "default-mode", "cline_custom_modes.json"), destFolder: path.join(".roo", "cline_custom_modes.json"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "default-mode", "custom-instructions.yaml"), destFolder: path.join(".roo", "custom-instructions.yaml"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "default-mode", "README.md"), destFolder: path.join(".roo", "README.md"), overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "default-mode", "role-definition.txt"), destFolder: path.join(".roo", "role-definition.txt"), overwritePermission: true},
],
additionalFilesToCopy: [
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".rooignore"), dest: ".rooignore", overwritePermission: false},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", ".roomodes"), dest: ".roomodes", overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "insert-variables.cmd"), dest: "insert-variables.cmd", overwritePermission: true},
{src: path.join(projectRoot, "roo", "RooFlow-main", "config", "insert-variables.sh"), dest: "insert-variables.sh", overwritePermission: true},
],
},
".cline": {
url: "https://codeload.github.com/GreatScottyMac/roo-code-memory-bank/zip/refs/heads/main",
templateDir: path.join(projectRoot, "cline", "roo-code-memory-bank-main"),
filesToCopy: [
{src: path.join(projectRoot, "cline", "roo-code-memory-bank-main", ".clinerules-architect"), destFolder: path.join( ".clinerules-architect"), overwritePermission: true},
{src: path.join(projectRoot, "cline", "roo-code-memory-bank-main", ".clinerules-code"), destFolder: path.join( ".clinerules-code"), overwritePermission: true},
{src: path.join(projectRoot, "cline", "roo-code-memory-bank-main", ".clinerules-ask"), destFolder: path.join( ".clinerules-ask"), overwritePermission: true},
{src: path.join(projectRoot, "clione", "roo-code-memory-bank-main", ".clinerules-debug"), destFolder: path.join( ".clinerules-debug"), overwritePermission: true},
{src: path.join(projectRoot, "cline", "roo-code-memory-bank-main", ".clinerules-test"), destFolder: path.join( ".clinerules-test"), overwritePermission: true},
{src: path.join(projectRoot, "cline", "roo-code-memory-bank-main", ".roomodes"), destFolder: path.join( ".roomodes"), overwritePermission: true},
],
additionalFilesToCopy: [],
},
".windsurf": {
url: "https://codeload.github.com/GreatScottyMac/cascade-memory-bank/zip/refs/heads/main",
templateDir: path.join(projectRoot, "windsurf", "cascade-memory-bank-main"),
filesToCopy: [
{src: path.join(projectRoot, "windsurf", "cascade-memory-bank-main", ".windsurfrules"), destFolder: path.join( ".windsurfrules"), overwritePermission: true},
{src: path.join(projectRoot, "windsurf", "cascade-memory-bank-main", "global_rules.md"), dest: path.join(os.homedir(), ".codeium", "windsurf", "memories", "global_rules.md"), isAbsolutePath: true, overwritePermission: true},
],
additionalFilesToCopy: [],
},
".cursor": {
url: null,
configPath: null,
templateDir: null, // Placeholder for future template directory
filesToCopy: [],
additionalFilesToCopy: [],
},
".gitlab-copilot": {
url: null,
templateDir: null, // Placeholder for future template directory
filesToCopy: [],
additionalFilesToCopy: [],
},
};
// Consolidated global data for AI IDE extensions
const AI_TOOL_DIRS = Object.keys(AI_IDE_EXTENSIONS);
/**
* Generates the list of files to copy based on supported AI tools
* ---------------------------------------------------------------------
* Updated to handle the new structure of filesToCopy with destFolder.
*/
function getFilesToCopy() {
const files = [];
// Add additional files for each AI tool
AI_TOOL_DIRS.forEach((dir) => {
const {
url,
templateDir,
additionalFilesToCopy,
filesToCopy,
} = AI_IDE_EXTENSIONS[dir];
// Skip processing if the URL is null
if (!url) {
console.log(`${YELLOW}Info${RESET}: Skipping ${dir} as it has no URL.`);
return;
}
console.log(`${YELLOW}Info${RESET}: template directory: ${templateDir}`);
// Add additional files to the copy list
if (additionalFilesToCopy) {
files.push(...additionalFilesToCopy);
}
// Skip if no template directory exists for this tool
if (url !== null && (!templateDir || !fs.existsSync(templateDir))) {
console.log(
`${YELLOW}Warning${RESET}: No template directory found for ${dir}`
);
return;
}
// Add filesToCopy for each tool
filesToCopy.forEach(({ src, dest, destFolder, isAbsolutePath, overwritePermission }) => {
if (fs.existsSync(src)) {
if (dest) {
// If dest is directly provided, use it
files.push({ src, dest, isAbsolutePath, overwritePermission });
} else if (destFolder) {
console.log(`${YELLOW}Note${RESET}: Using deprecated 'destFolder' property for: ${src}`);
files.push({ src, dest: destFolder, isAbsolutePath, overwritePermission });
} else {
console.log(`${YELLOW}Warning${RESET}: Destination is undefined for source file: ${src}`);
}
} else {
console.log(`${YELLOW}Warning${RESET}: Source file does not exist: ${src} for tool: ${dir}`);
}
});
});
return files;
}
/**
* Finds the root directory of the project
* ----------------------------------------
* This function uses multiple strategies to determine the project root:
* 1. First checks npm environment variables (most reliable)
* 2. Then checks if running from within node_modules (common for npm scripts)
* 3. Falls back to current working directory (for direct executions)
*
* Havingthe correct project root is crucial for installing files in the right location.
*
* @returns {string} Path to the project root
*/
function findProjectRoot() {
// Most important: If we're being run as part of npm install in a directory,
// npm sets npm_config_local_prefix to that directory
if (process.env.npm_config_local_prefix) {
return process.env.npm_config_local_prefix;
}
// Check for test directory pattern in parent directories
let currentDir = process.cwd();
while (currentDir !== path.dirname(currentDir)) {
if (path.basename(currentDir).startsWith("vibecoder-tests-")) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
// Next check if we're being run from within node_modules
if (process.cwd().includes("node_modules")) {
// For any execution from within node_modules, use the parent project directory
return path.resolve(process.cwd(), "..", "..");
}
// For direct runs, use current working directory
return process.cwd();
}
/**
* Copies a file from template directory to the project
* ------------------------------------------------------
* This function handles copying a file from the local template directory
* to the appropriate location in the project. It creates any necessary
* directories and handles various error conditions.
*
* @param {string} srcPath - Source path in the template directory
* @param {string} destPath - Destination path in the project
* @returns {Promise} Promise that resolves to true if successful, false if file not found
*/
/**
* Copies a file from template directory to the project or an absolute path
* ------------------------------------------------------
* This function handles copying a file from the local template directory
* to the appropriate location in the project or to an absolute path.
* It creates any necessary directories and handles various error conditions.
* If the destination file already exists, it will not overwrite it.
*
* @param {string} srcPath - Source path in the template directory
* @param {string} destPath - Destination path (relative to projectRoot or absolute)
* @param {boolean} isAbsolutePath - Whether destPath is an absolute path
* @param {boolean} forceOverwrite - Whether to overwrite existing files (default: false)
* @returns {Promise} Promise that resolves to true if successful, false if file not found or skipped
*/
function copyFile(srcPath, destPath, isAbsolutePath = false, overwritePermission) {
return new Promise((resolve, reject) => {
// Check if source file exists
if (!fs.existsSync(srcPath)) {
console.log(
` ${YELLOW}Warning${RESET}: Source file not found at ${srcPath}`
);
resolve(false);
return;
}
// Determine the full destination path based on whether it's absolute or relative
const destFullPath = isAbsolutePath ? destPath : path.join(projectRoot, destPath);
// Check if destination file already exists
if (fs.existsSync(destFullPath) && !overwritePermission) {
console.log(` ${BLUE}Skipping${RESET}: File already exists at ${destFullPath} and overwritePermission: ${overwritePermission}`);
console.log(` ${YELLOW}Note${RESET}: Preserving user modifications`);
resolve(true); // Still return true as this is an expected condition
return;
}
console.log(` Copying ${BLUE}${srcPath}${RESET} to ${destPath} ...`);
// Create directories if they don't exist
const destDir = path.dirname(destFullPath);
if (!fs.existsSync(destDir)) {
try {
fs.mkdirSync(destDir, { recursive: true });
console.log(` Created directory: ${destDir}`);
} catch (dirError) {
console.error(` ${RED}Error${RESET}: Failed to create directory ${destDir}: ${dirError.message}`);
resolve(false);
return;
}
}
try {
// Perform the file copy
fs.copyFileSync(srcPath, destFullPath);
console.log(` Successfully copied to ${destFullPath}`);
resolve(true);
} catch (error) {
reject(
new Error(
`Failed to copy ${srcPath} to ${destFullPath}: ${error.message}`
)
);
}
});
}
/**
* Run insert-variables script to configure the environment
* --------------------------------------------------------
* This function executes the platform-appropriate script to set up
* environment variables. It handles both Windows and Unix-like platforms,
* with fallback mechanisms for Windows to try both bash and cmd options.
*
* The insert-variables scripts set up project-specific placeholders in
* the system prompt files for personalization.
*
* @returns {boolean} True if script ran successfully
*/
function runInsertVariablesScript() {
console.log(
"\nRunning insert-variables script to configure system variables ..."
);
const isWindows = process.platform === "win32";
const projectRoot = findProjectRoot();
try {
if (isWindows) {
// Windows systems - try Git Bash first, fall back to cmd script
try {
// Make sure shell script is executable
fs.chmodSync(path.join(projectRoot, "insert-variables.sh"), "755");
// Try to run with bash (for Git Bash or WSL)
execSync("bash ./insert-variables.sh", {
cwd: projectRoot,
stdio: "inherit",
});
} catch (err) {
// Fallback to cmd script if bash fails
console.log(
` ${YELLOW}Bash execution failed, trying Windows cmd script...${RESET}`
);
execSync("insert-variables.cmd", {
cwd: projectRoot,
stdio: "inherit",
});
}
} else {
// For Unix-like systems (Linux, macOS)
fs.chmodSync(path.join(projectRoot, "insert-variables.sh"), "755");
execSync("./insert-variables.sh", {
cwd: projectRoot,
stdio: "inherit",
});
}
return true;
} catch (error) {
console.error(`${RED}Failed to run insert-variables script${RESET}`);
console.error("You may need to run it manually:");
console.error(
isWindows
? " insert-variables.cmd or bash ./insert-variables.sh"
: " ./insert-variables.sh"
);
return false;
}
}
/**
* Cleanup temporary installation files
* ------------------------------------
* This function removes the insert-variables scripts after successful
* installation, as they're only needed during the setup process.
* These scripts contain sensitive information like placeholders,
* so they should be removed after setup is complete.
*
* @returns {void}
*/
function cleanupInsertVariablesScripts() {
console.log("\nCleaning up insert-variables scripts...");
const projectRoot = findProjectRoot();
try {
const cmdPath = path.join(projectRoot, "insert-variables.cmd");
const shPath = path.join(projectRoot, "insert-variables.sh");
// Remove Windows cmd script if it exists
if (fs.existsSync(cmdPath)) {
fs.unlinkSync(cmdPath);
console.log(` Removed ${BLUE}insert-variables.cmd${RESET}`);
}
// Remove bash shell script if it exists
if (fs.existsSync(shPath)) {
fs.unlinkSync(shPath);
console.log(` Removed ${BLUE}insert-variables.sh${RESET}`);
}
console.log(`${GREEN}Cleanup completed successfully${RESET}`);
} catch (error) {
console.error(
`${YELLOW}Warning: Failed to clean up insert-variables scripts${RESET}`
);
console.error(` Error: ${error.message}`);
console.error(
" You may want to delete these files manually for security reasons."
);
}
}
/**
* Cleanup downloaded zip files and extracted extension directories
* ------------------------------------------------------------
* This function removes temporary zip files and extracted directories
* for AI IDE extensions after successful installation.
*
* @returns {void}
*/
function cleanupDownloadedExtensions() {
console.log("\nCleaning up downloaded AI extension files...");
try {
AI_TOOL_DIRS.forEach((extension) => {
// Skip if no URL (no download occurred)
if (!AI_IDE_EXTENSIONS[extension].url) return;
const zipPath = path.join(projectRoot, `${extension.substring(1)}.zip`);
const extractPath = path.join(projectRoot, extension.substring(1));
// Remove zip file if it still exists
if (fs.existsSync(zipPath)) {
fs.unlinkSync(zipPath);
console.log(` Removed ${BLUE}${extension.substring(1)}.zip${RESET}`);
}
// Remove extracted directory if it exists
if (fs.existsSync(extractPath)) {
fs.rmSync(extractPath, { recursive: true, force: true });
console.log(` Removed ${BLUE}${extension.substring(1)} directory${RESET}`);
}
});
console.log(`${GREEN}Extension cleanup completed successfully${RESET}`);
} catch (error) {
console.error(
`${YELLOW}Warning: Failed to clean up downloaded extension files${RESET}`
);
console.error(` Error: ${error.message}`);
console.error(
" You may want to manually remove any remaining .zip files or extension directories."
);
}
}
/**
* Validates all system prompt files across all AI tools
* -----------------------------------------------------
* This function runs the YAML sanitizer on all system prompt files
* to ensure they have valid YAML structure and no duplicate sections.
* First, it attempts to fix any duplicate capabilities sections,
* then it validates the resulting YAML structure.
*
* @returns {boolean} True if all validations passed
*/
function validateAllToolConfigurations() {
let allValid = true;
// First attempt to fix any duplicate capabilities sections
// This is a common issue when updating from older versions
console.log("\nChecking and fixing system prompt files ...");
const fixResult = sanitizerScript.fixDuplicateCapabilities(findProjectRoot());
if (!fixResult) {
console.log(
`${YELLOW}Warning: Some issues could not be automatically fixed${RESET}`
);
allValid = false;
}
// Then validate YAML structure across all files
console.log("\nValidating YAML structure in all configuration files ...");
const validationSuccess = sanitizerScript.validateFilesWithYaml(
findProjectRoot()
);
if (!validationSuccess) {
console.log(`${YELLOW}Warning: YAML validation detected issues${RESET}`);
allValid = false;
}
return allValid;
}
/**
* Verifies that template directories exist
* ----------------------------------------
* This function checks if the necessary template directories exist
* before starting the installation process.
*
* @returns {boolean} True if template directories exist
*/
function verifyTemplateDirectories() {
console.log("Verifying template directories ...");
let valid = true;
// Check tool-specific templates
let foundAny = false;
AI_TOOL_DIRS.forEach((dir) => {
const ideDir = AI_IDE_EXTENSIONS[dir].templateDir;
if (!ideDir) {
console.log(
` ${YELLOW}Warning: No template directory found for ${ideDir}${RESET}`
);
return;
}
if (fs.existsSync(ideDir)) {
console.log(` ${GREEN}Found templates for ${ideDir}${RESET}`);
foundAny = true;
} else {
console.log(
` ${YELLOW}Warning: No templates found for ${ideDir}${RESET}`
);
}
});
if (!foundAny) {
console.error(
`${RED}Error: No tool-specific template directories found${RESET}`
);
valid = false;
}
return valid;
}
/**
* Downloads and unpacks the specified AI IDE extension
* -----------------------------------------------------
* This function downloads the zip file for a given AI IDE extension,
* extracts it to the appropriate directory, and verifies the config path.
*
* @param {string} extension - The AI IDE extension (e.g., ".roo", ".cline")
* @returns {Promise<void>}
*/
async function downloadAndUnpackAIExtension(extension) {
const { url } = AI_IDE_EXTENSIONS[extension];
// Skip processing if the URL is null
if (!url) {
console.log(
`${YELLOW}Info${RESET}: Skipping download and unpack for ${extension} as it has no URL.`
);
return;
}
const zipPath = path.join(projectRoot, `${extension.substring(1)}.zip`);
const extractPath = path.join(projectRoot, extension.substring(1));
console.log(`Downloading and unpacking ${extension} ...`);
try {
console.log(`Downloading from ${url} to ${zipPath}`);
await new Promise((resolve, reject) => {
https
.get(url, (res) => {
if (res.statusCode === 302) {
const redirectUrl = res.headers.location;
console.log(`Redirecting to ${redirectUrl}`);
https
.get(redirectUrl, (redirectRes) => {
if (redirectRes.statusCode !== 200) {
reject(
new Error(
`Failed to download ${extension}.zip after redirect: HTTP ${redirectRes.statusCode}`
)
);
return;
}
const file = fs.createWriteStream(zipPath);
redirectRes.pipe(file);
file.on("finish", () => {
file.close();
console.log(`Downloaded ${extension}.zip successfully`);
resolve();
});
})
.on("error", (redirectErr) => {
fs.unlinkSync(zipPath);
console.error(
`Error downloading ${extension}.zip after redirect:`,
redirectErr.message
);
reject(redirectErr);
});
} else if (res.statusCode !== 200) {
reject(
new Error(
`Failed to download ${extension}.zip: HTTP ${res.statusCode}`
)
);
return;
} else {
const file = fs.createWriteStream(zipPath);
res.pipe(file);
file.on("finish", () => {
file.close();
console.log(`Downloaded ${extension}.zip successfully`);
resolve();
});
}
})
.on("error", (err) => {
fs.unlinkSync(zipPath);
console.error(`Error downloading ${extension}.zip:`, err.message);
reject(err);
});
});
console.log(`Extracting ${extension}.zip to ${extractPath}`);
try {
const zip = new AdmZip(zipPath);
zip.extractAllTo(extractPath, true);
console.log(`Unpacked ${extension}.zip to ${extension.substring(1)} folder successfully`);
// Delete the zip file if it exists
if (fs.existsSync(zipPath)) {
fs.unlinkSync(zipPath);
console.log(`Deleted ${extension}.zip successfully`);
}
} catch (extractError) {
console.error(`Error extracting ${extension}.zip: ${extractError.message}`);
// Don't rethrow, let the outer catch handle it
}
} catch (error) {
console.error(
`Error downloading and unpacking ${extension}: ${error.message}`
);
}
}
/**
* Main installation function
* --------------------------
* Updated to handle multiple AI IDE extensions dynamically.
*
* @param {Object} options - Installation options
* @param {boolean} options.forceOverwrite - Whether to force overwrite existing files
* @returns {Promise<boolean>} - Whether installation was successful
*/
async function install() {
console.log(`${BLUE}RooFlow Installer${RESET}`);
// Print installation context information (useful for debugging)
console.log(`Installation details:`);
console.log(`- Script directory: ${__dirname}`);
console.log(`- Target directory: ${projectRoot}`);
console.log(`- Running as postinstall: ${isPostinstall ? "Yes" : "No"}`);
console.log(`- Supported AI tools: ${AI_TOOL_DIRS.join(", ")}\n`);
// Download and unpack each AI IDE extension
for (const extension of AI_TOOL_DIRS) {
await downloadAndUnpackAIExtension(extension);
}
// Verify template directories exist
if (!verifyTemplateDirectories()) {
console.error(
`${RED}Installation cannot proceed due to missing template directories${RESET}`
);
return false;
}
// Get list of files to copy based on supported tools
const FILES = getFilesToCopy();
// Copy all files from templates
console.log("Copying template files ...");
let copySuccess = true;
try {
// Process each file in the copy list
for (const file of FILES) {
const success = await copyFile(file.src, file.dest, file.isAbsolutePath, file.overwritePermission);
if (!success) {
console.log(` ${YELLOW}Warning: Failed to copy ${file.src}${RESET}`);
if (file.dest.includes("modes")) {
console.log(
` ${YELLOW}Note: Missing modes file for ${path.dirname(
file.dest
)}${RESET}`
);
}
copySuccess = false;
}
}
// Report copy results
if (copySuccess) {
console.log(`\n${GREEN}All required files copied successfully${RESET}`);
} else {
console.log(`\n${YELLOW}Warning: Some files could not be copied${RESET}`);
console.log("Installation will continue with available files.");
}
// Make shell script executable on Unix-like systems
if (process.platform !== "win32") {
fs.chmodSync(path.join(findProjectRoot(), "insert-variables.sh"), "755");
}
// Run setup scripts and validate configurations
const scriptSuccess = runInsertVariablesScript();
const validationSuccess = validateAllToolConfigurations();
if (scriptSuccess) {
// Clean up temporary files after successful setup
cleanupInsertVariablesScripts();
cleanupDownloadedExtensions();
if (validationSuccess) {
// Report successful installation
console.log(`\n${GREEN}Installation complete!${RESET}`);
console.log(
"Your project is now configured to use the following AI tools:"
);
AI_TOOL_DIRS.forEach((dir) => {
console.log(
` ${BLUE}${dir}${RESET} - Contains system prompt files for ${dir.substring(
1
)}`
);
});
console.log("\nDirectory structure created:");
AI_TOOL_DIRS.forEach((dir) => {
console.log(` ${dir}/ - System prompt files`);
if (dir === ".roo") {
console.log(" .roomodes - Mode configurations file for RooFlow");
console.log(" .rooignore - ignore file for RooFlow");
}
});
console.log(
"\nThe memory-bank directory will be created automatically when you first use the AI tools."
);
console.log("\nTo start using RooFlow (primary tool):");
console.log(" 1. Open your project in VS Code");
console.log(" 2. Ensure the Roo Code extension is installed");
console.log(
" 3. Follow the instructions in the ./.roo/README.md file to setup the defaults and the Memory Bank"
);
console.log(' 4. Start a new Roo Code chat and say "Hello"');
} else {
// Installation completed but with warnings
console.log(`\n${YELLOW}Installation completed with warnings${RESET}`);
console.log(
"Some system prompt files may have YAML issues that need manual attention."
);
console.log(
"Your project is configured, but you may encounter issues."
);
console.log("\nTo fix YAML issues, you can run the validation again:");
console.log(" node node_modules/vibecoder/sanitizer.js");
}
// Provide recovery options for any issues
console.log(
"\nIf you have any issues with automatic installation, you can always (NOTE that the files will be overwritten so be careful!):"
);
console.log('- Run "npm run setup" in your project');
console.log('- Run "node node_modules/vibecode/installer.js" directly');
} else {
// Script execution failed
console.error(`\n${RED}Installation encountered issues${RESET}`);
console.error("The insert-variables script failed to run.");
console.error("You may need to run it manually:");
console.error(
process.platform === "win32"
? " insert-variables.cmd or bash ./insert-variables.sh"
: " ./insert-variables.sh"
);
}
} catch (error) {
// Handle any unexpected errors
console.error(`${RED}Error during installation:${RESET}`, error.message);
process.exit(1);
}
}
/**
* Parse command line arguments
* ----------------------------
* Parses command line arguments to determine installation options.
*
* @returns {Object} Options object with parsed command line arguments
*/
function parseCommandLineArgs() {
const args = process.argv.slice(2);
const options = {
forceOverwrite: false,
help: false
};
for (const arg of args) {
if (arg === '--force' || arg === '-f') {
options.forceOverwrite = true;
} else if (arg === '--help' || arg === '-h') {
options.help = true;
}
}
return options;
}
/**
* Display help information
* -----------------------
* Shows usage information and available command line options.
*/
function showHelp() {
console.log(`
${BLUE}RooFlow Installer Help${RESET}
Usage: node installer.js [options]
Options:
-f, --force Force overwrite of existing files (by default, existing files are preserved)
-h, --help Display this help message
Examples:
node installer.js # Normal installation (preserves existing files)
node installer.js --force # Force overwrite all files
`);
process.exit(0);
}
// Add error handler for better diagnostic information
process.on("uncaughtException", (error) => {
console.error(`${RED}Installer encountered an error:${RESET}`);
console.error(error);
console.error("\nIf installation failed, you can run it manually:");
console.error("- npm run setup");
console.error("- node node_modules/vibecode/installer.js");
console.error("- Add --force to overwrite existing files: node node_modules/vibecode/installer.js --force");
process.exit(1);
});
// Parse command line arguments
const options = parseCommandLineArgs();
// Show help if requested
if (options.help) {
showHelp();
}
// Run the installation
console.log("Installer running ...");
if (options.forceOverwrite) {
console.log(`${YELLOW}Force overwrite mode enabled${RESET} - Existing files will be overwritten`);
} else {
console.log(`${BLUE}Preserve mode enabled${RESET} - Existing files will be kept`);
}
install(options).catch((error) => {
console.error(`${RED}Installation failed:${RESET}`, error);
process.exit(1);
});