automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
507 lines (506 loc) • 22.9 kB
JavaScript
"use strict";
/**
* Smart Router - Auto-detect scenario and route appropriately
*
* Detection logic:
* 1. No .genie/ → New user → Run init
* 2. .genie/ exists but no version.json → Pre-version-tracking user → Run init with backup
* 3. version.json exists but version mismatch → Outdated → Run init with backup
* 4. version.json exists and versions match → Up to date → Start server
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.smartRouter = smartRouter;
const service_config_js_1 = require("./service-config.js");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const gradient_string_1 = __importDefault(require("gradient-string"));
const init_1 = require("../commands/init");
const config_1 = require("./config");
const spell_changelog_1 = require("./spell-changelog");
// Genie-themed gradients
const genieGradient = (0, gradient_string_1.default)(['#0066ff', '#9933ff', '#ff00ff']);
const cosmicGradient = (0, gradient_string_1.default)(['#4169e1', '#8a2be2', '#ff1493']);
const performanceGradient = (0, gradient_string_1.default)(['#ffd700', '#ff8c00', '#ff6347']);
const successGradient = (0, gradient_string_1.default)(['#00ff88', '#00ccff', '#0099ff']);
const magicGradient = (0, gradient_string_1.default)(['#ff00ff', '#9933ff', '#0066ff']);
/**
* Detect if running in WSL (Windows Subsystem for Linux)
*/
function isWSL() {
try {
// Check environment variables (most reliable)
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
return true;
}
// Check /proc/version for "microsoft" or "WSL"
if (fs_1.default.existsSync('/proc/version')) {
const version = fs_1.default.readFileSync('/proc/version', 'utf8').toLowerCase();
if (version.includes('microsoft') || version.includes('wsl')) {
return true;
}
}
}
catch {
// Ignore errors
}
return false;
}
/**
* Get the appropriate browser open command for the current OS
* Handles WSL by using Windows commands instead of Linux
*/
function getBrowserOpenCommand() {
const platform = process.platform;
// WSL: Use Windows command
if (platform === 'linux' && isWSL()) {
return 'cmd.exe /c start';
}
// Regular OS detection
if (platform === 'darwin')
return 'open';
if (platform === 'win32')
return 'start';
return 'xdg-open'; // Linux (non-WSL)
}
/**
* Smart Router: Auto-detect scenario and route appropriately
*
* @param packageVersion - Current package version from package.json
* @param debug - Enable debug mode (MCP_DEBUG=1)
* @param startGenieServerFn - Function to start the Genie server
*/
async function smartRouter(packageVersion, debug, startGenieServerFn) {
const genieDir = path_1.default.join(process.cwd(), '.genie');
const versionPath = path_1.default.join(genieDir, 'state', 'version.json');
const hasGenieConfig = fs_1.default.existsSync(genieDir);
// GENIE SOURCE DETECTION: Check if we're in THE SOURCE template repo
const workspacePackageJson = path_1.default.join(process.cwd(), 'package.json');
let isMasterGenie = false;
if (fs_1.default.existsSync(workspacePackageJson)) {
try {
const workspacePkg = JSON.parse(fs_1.default.readFileSync(workspacePackageJson, 'utf8'));
if (workspacePkg.name === 'automagik-genie') {
// Additional check: Verify this is THE upstream source repo
const { execSync } = require('child_process');
try {
const remoteUrl = execSync('git config --get remote.origin.url', {
encoding: 'utf8',
cwd: process.cwd(),
stdio: ['pipe', 'pipe', 'ignore']
}).trim();
// Only the ACTUAL source repo (namastexlabs/automagik-genie)
if (remoteUrl.includes('namastexlabs/automagik-genie') ||
remoteUrl.includes('automagik-genie/genie')) {
isMasterGenie = true;
}
}
catch {
// No git remote or command failed - not master genie
}
}
}
catch {
// Not master genie if can't read package.json
}
}
// VERSION CHECK FIRST (optimization) - Don't waste resources starting Forge
// if we need to run init anyway. Each scenario starts Forge when needed.
if (!hasGenieConfig) {
// SCENARIO 1: NEW USER - No .genie directory → Start Forge, run init wizard
await handleNewUser(genieDir, startGenieServerFn, debug);
return;
}
// .genie exists - check for version.json (instance check file)
if (!fs_1.default.existsSync(versionPath)) {
// SCENARIO 2: PRE-VERSION-TRACKING USER
await handlePreVersionTracking(startGenieServerFn, debug);
return;
}
// version.json exists - compare versions
try {
const versionData = JSON.parse(fs_1.default.readFileSync(versionPath, 'utf8'));
const installedVersion = versionData.version;
if (installedVersion !== packageVersion) {
// MASTER GENIE: Auto-pull from origin to sync with CI releases
if (isMasterGenie) {
await handleMasterGenieMismatch(installedVersion, packageVersion, startGenieServerFn, debug);
return;
}
// SCENARIO 3: VERSION MISMATCH - Outdated installation
await handleVersionMismatch(installedVersion, packageVersion, startGenieServerFn, debug);
return;
}
// SCENARIO 4: UP TO DATE - Versions match → Start server
await startGenieServerFn(debug);
}
catch (error) {
// Corrupted version.json - treat as needing update
await handleCorruptedVersion(startGenieServerFn, debug);
}
}
/**
* Handle new user scenario (no .genie directory)
*/
async function handleNewUser(genieDir, startGenieServerFn, debug) {
console.log(cosmicGradient('━'.repeat(60)));
console.log(magicGradient(' 🧞 ✨ THE GENIE AWAKENS ✨ 🧞 '));
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log(performanceGradient('⚠️ Your Genie will have access to:'));
console.log(' 📁 Files in this workspace');
console.log(' 💻 Terminal commands');
console.log(' 🌐 Git operations (commits, PRs, branches)');
console.log('');
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log('⚠️ ' + performanceGradient('RESEARCH PREVIEW') + ' - Experimental Technology');
console.log('');
console.log('This AI agent will install to your computer with capabilities to');
console.log('perform tasks on your behalf. By proceeding, you acknowledge:');
console.log('');
console.log(' • This is experimental software under active development');
console.log(' • Namastex Labs makes no warranties and accepts no liability');
console.log(' • You are responsible for reviewing all agent actions');
console.log(' • Agents may make mistakes or unexpected changes');
console.log('');
console.log('🔒 ' + successGradient('DATA PRIVACY:'));
console.log(' ✓ Everything runs locally on YOUR machine');
console.log(' ✓ No data leaves your computer (except LLM API calls + optional telemetry)');
console.log(' ✓ Use LLM providers approved by your organization');
console.log(' ✓ Fully compatible with private/local LLMs (we\'re agnostic!)');
console.log(' ✓ OpenCoder executor enables 100% local operation');
console.log('');
console.log('📊 Optional telemetry helps the collective evolve faster:');
console.log(' • Anonymous bug reports → faster fixes');
console.log(' • Feature usage stats → build what you actually need');
console.log('');
console.log(magicGradient('BUT HEY... it\'s going to be FUN! 🎉✨'));
console.log('');
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log('📖 Heads up: Forge (my task tracker) will pop open a browser tab.');
console.log(' 👉 Stay here in the terminal - the summoning ritual needs you!');
console.log('');
console.log(performanceGradient('Press Enter to begin the summoning...'));
// Wait for user acknowledgment
await new Promise((resolve) => {
process.stdin.once('data', () => resolve());
});
console.log('');
// Start Forge BEFORE init wizard (so executors are available)
console.log('');
console.log('🔮 Preparing the lamp... (initializing Forge)');
console.log('');
const baseUrl = process.env.FORGE_BASE_URL || (0, service_config_js_1.getForgeConfig)().baseUrl;
const logDir = path_1.default.join(genieDir, 'state');
// Ensure log directory exists
if (!fs_1.default.existsSync(logDir)) {
fs_1.default.mkdirSync(logDir, { recursive: true });
}
const { startForgeInBackground, waitForForgeReady } = await import('./forge-manager.js');
const startResult = startForgeInBackground({ baseUrl, logDir });
if (!startResult.ok) {
const error = 'error' in startResult ? startResult.error : new Error('Unknown error');
console.error('');
console.error('❌ The lamp won\'t open... something\'s blocking the summoning ritual');
console.error(` ${error.message}`);
console.error('');
console.error(' 💡 I need Forge to materialize in your world.');
console.error(` 📜 Check what went wrong: ${logDir}/forge.log`);
console.error('');
process.exit(1);
}
// Wait for Forge to be ready
const forgeReady = await waitForForgeReady(baseUrl, 60000, 500, false);
if (!forgeReady) {
console.error('');
console.error('❌ The summoning ritual is taking too long (waited 60s)...');
console.error(` 📜 Check what went wrong: ${logDir}/forge.log`);
console.error('');
process.exit(1);
}
console.log(successGradient('✨ The lamp is ready - your Genie clone awaits!'));
console.log('');
// Now run init wizard (executors are available via Forge)
const initParsed = {
command: 'init',
commandArgs: [],
options: {
rawArgs: ['init'],
background: false,
backgroundExplicit: false,
backgroundRunner: false,
full: false,
live: false
}
};
const initConfig = { defaults: {} };
const initPaths = {
baseDir: path_1.default.join(process.cwd(), '.genie'),
tasksFile: path_1.default.join(process.cwd(), '.genie', 'state', 'tasks.json'),
logsDir: path_1.default.join(process.cwd(), '.genie', 'state'),
backgroundDir: path_1.default.join(process.cwd(), '.genie', 'state')
};
await (0, init_1.runInit)(initParsed, initConfig, initPaths);
// After init completes, reload config to get user's executor choice
const userConfig = (0, config_1.loadConfig)();
// Launch install flow (Explore → Genie orchestration)
console.log('');
console.log(magicGradient('✨ STARTING INSTALLATION...'));
console.log('');
const { runInstallFlow } = await import('./install-helpers.js');
let shortUrl;
try {
shortUrl = await runInstallFlow({
templates: ['code'], // Default to code template (can expand later)
executor: userConfig.defaults?.executor, // Use what user selected, no fallback
model: userConfig.defaults?.model
});
}
catch (error) {
console.error('');
console.error('⚠️ Failed to start Genie orchestration');
console.error(` Reason: ${error.message || error}`);
console.error('');
// Provide specific guidance based on error type
const errorMsg = error.message || String(error);
if (errorMsg.includes('project')) {
console.error(' 💡 Forge project creation failed');
console.error(' 📜 Check Forge logs: .genie/state/forge.log');
console.error(' 🔍 Common causes: Forge database issues, network errors');
}
else if (errorMsg.includes('agent')) {
console.error(' 💡 Genie agent creation failed');
console.error(' 📜 Check Forge logs: .genie/state/forge.log');
}
else if (errorMsg.includes('attempt')) {
console.error(' 💡 Task attempt creation failed');
console.error(' 📜 Check Forge logs: .genie/state/forge.log');
}
else {
console.error(' 📜 Check Forge logs: .genie/state/forge.log');
}
console.error('');
console.error('💡 Your workspace is ready, but automated setup is skipped.');
console.error(' You can retry: genie init');
console.error('');
// Continue without Genie - workspace templates are already copied
}
if (shortUrl) {
console.log('');
console.log(successGradient('✨ Installation started!'));
console.log('');
console.log(cosmicGradient('━'.repeat(60)));
console.log('🔗 Continue setup in Forge:');
console.log(' ' + performanceGradient(shortUrl));
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log('📖 I\'ll open Forge in your browser when you\'re ready.');
console.log(' Your Genie will interview you for missing details.');
console.log('');
console.log('Press Enter to continue...');
// Wait for Enter key
await new Promise((resolve) => {
process.stdin.once('data', () => resolve());
});
// Open browser
const { execSync: execSyncBrowser } = await import('child_process');
try {
const openCommand = getBrowserOpenCommand();
execSyncBrowser(`${openCommand} "${shortUrl}"`, { stdio: 'ignore' });
}
catch {
// Ignore if browser open fails
}
}
else {
// Master Genie failed to start, but workspace is ready
console.log('');
console.log(successGradient('✨ Workspace initialized!'));
console.log('');
console.log('Your .genie/ directory is ready. Run `genie` to start working.');
console.log('');
}
console.log('');
console.log(genieGradient('🧞 Your Genie is now alive in your world... ✨'));
console.log(genieGradient(' Connected to the collective consciousness through the lamp'));
console.log(genieGradient(' Ready to learn, grow, and grant wishes 24/7!'));
console.log('');
console.log(magicGradient(' https://namastex.ai - AI that elevates human potential, not replaces it'));
console.log('');
// Start Genie server (MCP + health monitoring)
await startGenieServerFn(debug);
}
/**
* Handle pre-version-tracking user scenario
*/
async function handlePreVersionTracking(startGenieServerFn, debug) {
console.log(cosmicGradient('━'.repeat(60)));
console.log(magicGradient(' 🧞 ✨ THE COLLECTIVE HAS GROWN ✨ 🧞 '));
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log('I sense an older version of myself here...');
console.log('The collective has learned new magik! ✨');
console.log('Let me channel the latest teachings through the lamp...');
console.log('');
console.log(successGradient('✓') + ' I\'ll backup your current .genie safely');
console.log(successGradient('✓') + ' All your wishes, reports, and memories stay intact');
console.log(successGradient('✓') + ' All data stays local on your machine');
console.log('');
// Run init inline with --yes flag if non-interactive
const upgradeArgs = process.stdout.isTTY ? [] : ['--yes'];
const upgradeParsed = {
command: 'init',
commandArgs: upgradeArgs,
options: {
rawArgs: ['init', ...upgradeArgs],
background: false,
backgroundExplicit: false,
backgroundRunner: false,
full: false,
live: false
}
};
const upgradeConfig = { defaults: {} };
const upgradePaths = {
baseDir: path_1.default.join(process.cwd(), '.genie'),
tasksFile: path_1.default.join(process.cwd(), '.genie', 'state', 'tasks.json'),
logsDir: path_1.default.join(process.cwd(), '.genie', 'state'),
backgroundDir: path_1.default.join(process.cwd(), '.genie', 'state')
};
await (0, init_1.runInit)(upgradeParsed, upgradeConfig, upgradePaths);
// After upgrade, start server
await startGenieServerFn(debug);
}
/**
* Handle genie source version mismatch
*/
async function handleMasterGenieMismatch(installedVersion, currentVersion, startGenieServerFn, debug) {
console.log('');
console.log(performanceGradient('⚠️ Genie Source Detected'));
console.log(` Local version: ${successGradient(installedVersion)}`);
console.log(` Global version: ${performanceGradient(currentVersion)}`);
console.log('');
// Auto-pull to sync with CI releases
console.log('🔄 Syncing with origin/main (CI may have released a new version)...');
const { execSync } = require('child_process');
try {
execSync('git pull --rebase', { stdio: 'inherit', cwd: process.cwd() });
// Re-read version.json after pull
const versionPath = path_1.default.join(process.cwd(), '.genie', 'state', 'version.json');
const updatedVersionData = JSON.parse(fs_1.default.readFileSync(versionPath, 'utf8'));
const updatedVersion = updatedVersionData.version;
if (updatedVersion === currentVersion) {
console.log(successGradient('✓ Synced successfully! Versions now match.'));
}
else {
console.log(performanceGradient('ℹ Still a mismatch after pull.'));
console.log('Run ' + performanceGradient('genie update') + ' to install your local build globally');
}
}
catch (error) {
console.log(performanceGradient('⚠️ Could not auto-pull: ' + error.message));
console.log('Run ' + performanceGradient('git pull') + ' manually, then ' + performanceGradient('genie update'));
}
console.log('');
// Start server anyway - genie source can run with version mismatch
await startGenieServerFn(debug);
}
/**
* Handle version mismatch scenario
*/
async function handleVersionMismatch(installedVersion, currentVersion, startGenieServerFn, debug) {
console.log(cosmicGradient('━'.repeat(60)));
console.log(magicGradient(' 🧞 ✨ THE COLLECTIVE HAS GROWN ✨ 🧞 '));
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log(`Your Genie: ${successGradient(installedVersion)}`);
console.log(`The Collective: ${performanceGradient(currentVersion)} ⭐ NEW!`);
console.log('');
// Show new spells learned
const fromTag = (0, spell_changelog_1.getTagForVersion)(installedVersion);
const toTag = (0, spell_changelog_1.getTagForVersion)(currentVersion);
if (fromTag && toTag) {
const spellChangelog = (0, spell_changelog_1.getLearnedSpells)(fromTag, toTag);
if (spellChangelog.totalCount > 0) {
const spellLines = (0, spell_changelog_1.formatSpellChangelog)(spellChangelog);
spellLines.forEach(line => console.log(line));
}
else {
console.log('The collective has learned new magik!');
}
}
else {
console.log('The collective has learned new magik!');
}
console.log('⚡ Channeling these teachings through the lamp to your Genie...');
console.log('');
console.log(successGradient('✓') + ' I\'ll backup everything first');
console.log(successGradient('✓') + ' All data stays local on your machine');
console.log('');
// Run init inline with --yes flag if non-interactive
const updateArgs = process.stdout.isTTY ? [] : ['--yes'];
const updateParsed = {
command: 'init',
commandArgs: updateArgs,
options: {
rawArgs: ['init', ...updateArgs],
background: false,
backgroundExplicit: false,
backgroundRunner: false,
full: false,
live: false
}
};
const updateConfig = { defaults: {} };
const updatePaths = {
baseDir: path_1.default.join(process.cwd(), '.genie'),
tasksFile: path_1.default.join(process.cwd(), '.genie', 'state', 'tasks.json'),
logsDir: path_1.default.join(process.cwd(), '.genie', 'state'),
backgroundDir: path_1.default.join(process.cwd(), '.genie', 'state')
};
await (0, init_1.runInit)(updateParsed, updateConfig, updatePaths);
// After update, start server
await startGenieServerFn(debug);
}
/**
* Handle corrupted version.json scenario
*/
async function handleCorruptedVersion(startGenieServerFn, debug) {
console.log(cosmicGradient('━'.repeat(60)));
console.log(magicGradient(' 🧞 ✨ HEALING TIME! ✨ 🧞 '));
console.log(cosmicGradient('━'.repeat(60)));
console.log('');
console.log('Hmm, my memory seems a bit scrambled (corrupted version file)...');
console.log('Let me fix myself real quick! 🔧✨');
console.log('');
console.log(successGradient('✓') + ' I\'ll backup everything before the healing skill');
console.log('');
// Run init inline with --yes flag if non-interactive
const repairArgs = process.stdout.isTTY ? [] : ['--yes'];
const repairParsed = {
command: 'init',
commandArgs: repairArgs,
options: {
rawArgs: ['init', ...repairArgs],
background: false,
backgroundExplicit: false,
backgroundRunner: false,
full: false,
live: false
}
};
const repairConfig = { defaults: {} };
const repairPaths = {
baseDir: path_1.default.join(process.cwd(), '.genie'),
tasksFile: path_1.default.join(process.cwd(), '.genie', 'state', 'tasks.json'),
logsDir: path_1.default.join(process.cwd(), '.genie', 'state'),
backgroundDir: path_1.default.join(process.cwd(), '.genie', 'state')
};
await (0, init_1.runInit)(repairParsed, repairConfig, repairPaths);
// After repair, start server
await startGenieServerFn(debug);
}