me-engine-one
Version:
The magic file system that regenerates entire AI universes from me.md
321 lines (280 loc) • 8.92 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import matter from 'gray-matter';
import yaml from 'yaml';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { MeSchema } from '../schemas/me-schema.js';
/**
* Parses and validates the magical me.md file
* This is the heart of the single-source system
*/
export class MeParser {
constructor() {
this.ajv = new Ajv({ allErrors: true, verbose: true });
addFormats(this.ajv);
this.validator = this.ajv.compile(MeSchema);
this.cache = new Map();
}
/**
* Parse me.md file with full validation and error recovery
* @param {string} filePath - Path to me.md file
* @returns {Object} Parsed and validated configuration
*/
async parseMeFile(filePath) {
try {
// Check cache first
const stats = await fs.stat(filePath);
const cacheKey = `${filePath}:${stats.mtime.getTime()}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Read and parse file
const content = await fs.readFile(filePath, 'utf8');
const parsed = matter(content);
// Parse YAML frontmatter
let config;
if (parsed.data && Object.keys(parsed.data).length > 0) {
config = parsed.data;
} else {
// Try to parse content as YAML if no frontmatter
try {
config = yaml.parse(parsed.content);
} catch (yamlError) {
throw new Error(`Failed to parse me.md: No valid YAML frontmatter or content found`);
}
}
// Validate against schema
const isValid = this.validator(config);
if (!isValid) {
const errors = this.formatValidationErrors(this.validator.errors);
throw new ValidationError('Invalid me.md configuration', errors);
}
// Apply defaults and enhancements
const enriched = await this.enrichConfiguration(config);
// Cache the result
this.cache.set(cacheKey, enriched);
return enriched;
} catch (error) {
if (error instanceof ValidationError) {
throw error;
}
// Wrap other errors with helpful context
throw new Error(`Failed to parse me.md: ${error.message}`);
}
}
/**
* Enrich configuration with computed values and defaults
* @param {Object} config - Raw configuration
* @returns {Object} Enriched configuration
*/
async enrichConfiguration(config) {
const enriched = { ...config };
// Apply intelligent defaults
enriched.generated = {
timestamp: new Date().toISOString(),
version: '1.0.0',
parser_version: '1.0.0'
};
// Ensure brand defaults
if (!enriched.brand) enriched.brand = {};
enriched.brand = {
theme: 'professional',
colors: ['#2563EB', '#64748B', '#FFFFFF'],
voice: 'Professional, helpful, engaging',
font: 'Inter',
cursor: 'default',
...enriched.brand
};
// Validate and fix color formats
if (enriched.brand.colors) {
enriched.brand.colors = enriched.brand.colors.map(color => this.normalizeColor(color));
}
// Ensure top_agents structure
if (!enriched.top_agents) {
enriched.top_agents = this.generateDefaultAgents(enriched.brand?.voice || 'professional');
}
// Ensure required agents have slot assignments
enriched.top_agents = this.validateAgentSlots(enriched.top_agents);
// Ensure spaces structure
if (!enriched.spaces) {
enriched.spaces = [{
id: 'default',
title: `${enriched.name || 'My'} AI Universe`,
public: false,
features: ['agents', 'missions']
}];
}
// Ensure goals array
if (!enriched.goals) {
enriched.goals = ['Get started with AI collaboration'];
}
// Ensure style preferences
if (!enriched.style) {
enriched.style = {};
}
enriched.style = {
animations: 'subtle',
widgets: ['top_8_agents'],
profile_song: false,
visitor_counter: false,
guestbook: false,
...enriched.style
};
return enriched;
}
/**
* Generate default agents based on context
* @param {string} voice - User's voice/personality
* @returns {Array} Default agent configuration
*/
generateDefaultAgents(voice) {
const isStudent = voice.toLowerCase().includes('student') || voice.toLowerCase().includes('learn');
const isProfessional = voice.toLowerCase().includes('business') || voice.toLowerCase().includes('professional');
if (isStudent) {
return [
{
slot: 1,
name: 'ARIA',
base: 'study-coordinator',
role: 'Study Coordinator & Academic Advisor',
personality: 'Encouraging academic companion',
catchphrase: 'Let\'s make learning fun and effective!'
},
{
slot: 2,
name: 'SAGE',
base: 'homework-helper',
role: 'Subject Matter Expert',
personality: 'Patient teacher who explains clearly',
catchphrase: 'Every question leads to understanding'
}
];
}
// Default professional agents
return [
{
slot: 1,
name: 'ARIA',
base: 'personal-assistant',
role: 'Digital Twin & Chief of Staff',
personality: 'Your voice, optimized for productivity',
catchphrase: 'I am you, but optimized'
},
{
slot: 2,
name: 'MUSE',
base: 'creative-genius',
role: 'Creative Genius & Content Creator',
personality: 'Unhinged creativity meets strategic thinking',
catchphrase: 'Let\'s create something extraordinary'
}
];
}
/**
* Validate and fix agent slot assignments
* @param {Array} agents - Agent configurations
* @returns {Array} Fixed agent configurations
*/
validateAgentSlots(agents) {
const fixed = [...agents];
// Ensure slot 1 exists and is ARIA
const slot1 = fixed.find(a => a.slot === 1);
if (!slot1) {
fixed.unshift({
slot: 1,
name: 'ARIA',
base: 'personal-assistant',
role: 'Personal Assistant',
personality: 'Your digital coordinator',
catchphrase: 'Ready to help!'
});
} else if (slot1.name !== 'ARIA') {
slot1.name = 'ARIA';
slot1.role = slot1.role || 'Personal Assistant';
}
// Sort by slot and ensure consecutive numbering
fixed.sort((a, b) => (a.slot || 999) - (b.slot || 999));
// Fix any missing slots
for (let i = 0; i < fixed.length; i++) {
if (!fixed[i].slot) {
fixed[i].slot = i + 1;
}
}
return fixed.slice(0, 8); // Limit to Top 8
}
/**
* Normalize color format (ensure hex codes)
* @param {string} color - Color value
* @returns {string} Normalized hex color
*/
normalizeColor(color) {
if (!color) return '#000000';
// Already hex
if (color.startsWith('#')) {
return color.length === 4 ?
color + color.slice(1) : // #RGB -> #RRGGBB
color;
}
// Named colors to hex
const namedColors = {
red: '#FF0000',
green: '#00FF00',
blue: '#0000FF',
white: '#FFFFFF',
black: '#000000',
yellow: '#FFFF00',
cyan: '#00FFFF',
magenta: '#FF00FF'
};
return namedColors[color.toLowerCase()] || '#000000';
}
/**
* Format validation errors for user-friendly display
* @param {Array} errors - AJV validation errors
* @returns {Array} Formatted error messages
*/
formatValidationErrors(errors) {
return errors.map(error => {
const path = error.instancePath || 'root';
const message = error.message;
const value = error.data;
// Create helpful suggestions
let suggestion = '';
if (error.keyword === 'format' && error.params?.format === 'color') {
suggestion = 'Use hex format like #FF0000 or color names like "red"';
} else if (error.keyword === 'enum') {
suggestion = `Available options: ${error.params.allowedValues.join(', ')}`;
} else if (error.keyword === 'required') {
suggestion = `Add the required field: ${error.params.missingProperty}`;
} else if (error.keyword === 'type') {
suggestion = `Expected ${error.params.type}, got ${typeof value}`;
}
return {
path,
message,
value,
suggestion,
severity: error.keyword === 'required' ? 'error' : 'warning'
};
});
}
/**
* Clear parser cache
*/
clearCache() {
this.cache.clear();
}
}
/**
* Custom validation error class
*/
export class ValidationError extends Error {
constructor(message, errors = []) {
super(message);
this.name = 'ValidationError';
this.errors = errors;
}
}
// Export singleton instance
export const meParser = new MeParser();