Skip to main content
Glama

myAI Memory Sync

by Jktfe
platformService.ts14.3 kB
import { promises as fs } from 'fs'; import path from 'path'; import { homedir } from 'os'; import { SyncStatus, PlatformType } from '../types.js'; import { generateTemplate } from '../templateParser.js'; import { templateService } from './templateService.js'; import { config } from '../config.js'; import { ClaudeCodeSyncer, ensureFileWritable } from '../platformSync.js'; // Import the memory cache service dynamically - handles if Anthropic features are not enabled let memoryCacheService: any = null; try { // We'll attempt to load this module, but it's optional import('../utils/memoryCacheService.js').then(module => { memoryCacheService = module; console.error('Memory cache service loaded for prompt caching'); }).catch(err => { console.error('Memory cache service not available (Anthropic features likely not enabled)'); }); } catch (e) { // This is expected if Anthropic features are not enabled console.error('Memory cache service could not be imported'); } /** * Base platform sync interface */ interface PlatformSyncer { sync(templateContent: string): Promise<SyncStatus>; } /** * Windsurf synchronization (global_rules.md file) */ export class WindsurfSyncer implements PlatformSyncer { private rulesPath: string; private lastSyncTime: number = 0; private syncCooldown: number = 5000; // 5 second cooldown constructor(rulesPath?: string) { this.rulesPath = rulesPath || path.join(homedir(), '.windsurf', 'global_rules.md'); } async sync(templateContent: string): Promise<SyncStatus> { try { const now = Date.now(); // Check if we're within the cooldown period if (now - this.lastSyncTime < this.syncCooldown) { console.error(`Skipping Windsurf sync - within cooldown period (${this.syncCooldown}ms)`); return { platform: 'windsurf', success: true, message: `Skipped update to global_rules.md (within throttle period)` }; } // Create directory if it doesn't exist const dir = path.dirname(this.rulesPath); await fs.mkdir(dir, { recursive: true }); // Write the template to the global_rules.md file console.error(`Writing template to ${this.rulesPath}`); await fs.writeFile(this.rulesPath, templateContent, 'utf-8'); // Update last sync time this.lastSyncTime = now; return { platform: 'windsurf', success: true, message: `Successfully updated global_rules.md at ${this.rulesPath}` }; } catch (err) { console.error('Error syncing with Windsurf:', err); return { platform: 'windsurf', success: false, message: `Failed to sync with Windsurf: ${err instanceof Error ? err.message : String(err)}` }; } } } /** * Claude Web synchronization */ export class ClaudeWebSyncer implements PlatformSyncer { private email?: string; private headless: boolean; private lastSyncTime: number = 0; private syncCooldown: number = 60000; // 1 minute cooldown (web sync is expensive) constructor(options?: { email?: string, headless?: boolean }) { this.email = options?.email; this.headless = options?.headless ?? true; } async sync(templateContent: string): Promise<SyncStatus> { try { const now = Date.now(); // Check if we're within the cooldown period if (now - this.lastSyncTime < this.syncCooldown) { console.error(`Skipping Claude Web sync - within cooldown period (${this.syncCooldown}ms)`); return { platform: 'claude-web', success: true, message: `Skipped update to Claude Web (within throttle period)` }; } console.error('Starting Claude Web sync operation...'); // We'll implement this as a background task if possible // For now, just log that we would sync with Claude Web console.error('Sync with Claude Web would happen here (headless mode)'); // Update last sync time this.lastSyncTime = now; return { platform: 'claude-web', success: true, message: `Claude Web sync scheduled (headless mode: ${this.headless})` }; } catch (err) { console.error('Error syncing with Claude Web:', err); return { platform: 'claude-web', success: false, message: `Failed to sync with Claude Web: ${err instanceof Error ? err.message : String(err)}` }; } } } /** * Master template file synchronization */ export class MasterSyncer implements PlatformSyncer { private masterPath: string; private lastSyncTime: number = 0; private syncCooldown: number = 5000; // 5 second cooldown constructor(masterPath?: string) { this.masterPath = masterPath || (config.paths.masterTemplate || path.join(homedir(), 'myAI Master.md')); } async sync(templateContent: string): Promise<SyncStatus> { try { const now = Date.now(); // Check if we're within the cooldown period if (now - this.lastSyncTime < this.syncCooldown) { console.error(`Skipping Master template sync - within cooldown period (${this.syncCooldown}ms)`); return { platform: 'master', success: true, message: `Skipped update to Master template (within throttle period)` }; } // Ensure the file is writable const isWritable = await ensureFileWritable(this.masterPath); if (!isWritable) { return { platform: 'master', success: false, message: `Failed to sync with Master template: Permission denied. Could not make file writable: ${this.masterPath}` }; } // Create directory if it doesn't exist (should be handled by ensureFileWritable, but just to be safe) const dir = path.dirname(this.masterPath); await fs.mkdir(dir, { recursive: true }); // Write the template to the Master template file console.error(`Writing template to Master file ${this.masterPath}`); await fs.writeFile(this.masterPath, templateContent, 'utf-8'); // Update last sync time this.lastSyncTime = now; return { platform: 'master', success: true, message: `Successfully updated Master template at ${this.masterPath}` }; } catch (err) { console.error('Error syncing with Master template:', err); return { platform: 'master', success: false, message: `Failed to sync with Master template: ${err instanceof Error ? err.message : String(err)}` }; } } } /** * Platform synchronization manager */ export class PlatformService { private syncers: Map<PlatformType, PlatformSyncer> = new Map(); private initialized = false; private configuredPlatforms: Set<PlatformType> = new Set(); private syncQueue: { platform: PlatformType; content: string }[] = []; private isSyncing = false; /** * Initialize the platform service */ async initialize(): Promise<void> { if (this.initialized) return; // Log the configuration paths console.error('Initializing platform service with paths:'); console.error(`Master template path: ${config.paths?.masterTemplate}`); console.error(`Claude projects path: ${config.paths?.claudeProjectsPath}`); console.error(`Windsurf memory path: ${config.paths?.windsurfMemoryPath}`); // Set up default syncers this.configuredPlatforms.add('master'); this.syncers.set('master', new MasterSyncer(config.paths?.masterTemplate)); this.configuredPlatforms.add('claude-code'); this.syncers.set('claude-code', new ClaudeCodeSyncer(config.paths?.claudeProjectsPath)); console.error('ClaudeCodeSyncer initialized - ready to sync CLAUDE.md files'); console.error(`Using Claude projects path from config: ${config.paths?.claudeProjectsPath}`); this.configuredPlatforms.add('windsurf'); this.syncers.set('windsurf', new WindsurfSyncer(config.paths?.windsurfMemoryPath)); console.error(`WindsurfSyncer initialized with path from config: ${config.paths?.windsurfMemoryPath}`); // Optionally set up Claude Web syncer if configured try { const config = await this.loadConfig(); if (config.claudeWeb && config.claudeWeb.enabled) { this.configuredPlatforms.add('claude-web'); this.syncers.set('claude-web', new ClaudeWebSyncer({ email: config.claudeWeb.email, headless: config.claudeWeb.headless })); console.error('Claude Web sync enabled'); } } catch (err) { console.error('Error loading config for Claude Web sync:', err); // Continue without Claude Web sync } this.initialized = true; // Start the sync queue processor this.processSyncQueue(); } /** * Load configuration from environment or config file */ private async loadConfig(): Promise<any> { // Default config const config = { claudeWeb: { enabled: false, headless: true, email: undefined as string | undefined } }; // Try to load from environment variables if (process.env.CLAUDE_WEB_SYNC_ENABLED === 'true') { config.claudeWeb.enabled = true; config.claudeWeb.headless = process.env.CLAUDE_WEB_HEADLESS !== 'false'; if (process.env.CLAUDE_WEB_EMAIL) { config.claudeWeb.email = process.env.CLAUDE_WEB_EMAIL; } } // Could also try to load from a config file here return config; } /** * Add a sync task to the queue */ private queueSync(platform: PlatformType, content: string): void { this.syncQueue.push({ platform, content }); if (!this.isSyncing) { this.processSyncQueue(); } } /** * Process the sync queue in background */ private async processSyncQueue(): Promise<void> { if (this.isSyncing || this.syncQueue.length === 0) { return; } this.isSyncing = true; while (this.syncQueue.length > 0) { const task = this.syncQueue.shift(); if (!task) continue; const { platform, content } = task; const syncer = this.syncers.get(platform); if (syncer) { try { console.error(`Processing queued sync for ${platform}`); await syncer.sync(content); } catch (err) { console.error(`Error processing queued sync for ${platform}:`, err); } } } this.isSyncing = false; } /** * Set the syncer for a specific platform */ setSyncer(platform: PlatformType, syncer: PlatformSyncer): void { this.syncers.set(platform, syncer); this.configuredPlatforms.add(platform); } /** * Disable a specific platform */ disablePlatform(platform: PlatformType): void { this.configuredPlatforms.delete(platform); } /** * Enable a specific platform */ enablePlatform(platform: PlatformType): void { if (this.syncers.has(platform)) { this.configuredPlatforms.add(platform); } } /** * Sync template with all platforms */ async syncAll(templateContent?: string): Promise<SyncStatus[]> { if (!this.initialized) await this.initialize(); // Get template content if not provided if (!templateContent) { const template = await templateService.getTemplate(); templateContent = generateTemplate(template); } const results: SyncStatus[] = []; console.error(`Syncing with all configured platforms (${this.configuredPlatforms.size}): ${Array.from(this.configuredPlatforms).join(', ')}`); for (const platform of this.configuredPlatforms) { const syncer = this.syncers.get(platform); if (syncer) { console.error(`Syncing with platform: ${platform}`); try { const result = await syncer.sync(templateContent); results.push(result); console.error(`Sync result for ${platform}: ${result.success ? 'Success' : 'Failure'} - ${result.message}`); } catch (err) { console.error(`Error syncing with ${platform}:`, err); results.push({ platform, success: false, message: `Error syncing with ${platform}: ${err instanceof Error ? err.message : String(err)}` }); } } else { console.error(`No syncer configured for platform: ${platform}`); results.push({ platform, success: false, message: `No syncer configured for platform: ${platform}` }); } } // Update memory cache after successful sync (if available) if (memoryCacheService && memoryCacheService.updateCacheAfterSync) { try { console.error('Updating memory cache after sync...'); await memoryCacheService.updateCacheAfterSync(); console.error('Memory cache updated successfully'); } catch (err) { console.error('Error updating memory cache:', err); // This is non-critical, so we don't add it to results } } return results; } /** * Sync template with a specific platform */ async syncPlatform(platform: PlatformType): Promise<SyncStatus> { if (!this.initialized) await this.initialize(); if (!this.configuredPlatforms.has(platform)) { return { platform, success: false, message: `Platform ${platform} is disabled` }; } const template = templateService.getTemplate(); const templateContent = generateTemplate(template); const syncer = this.syncers.get(platform); if (!syncer) { return { platform, success: false, message: `No syncer configured for platform: ${platform}` }; } return await syncer.sync(templateContent); } /** * Get configured platforms */ getPlatforms(): PlatformType[] { return Array.from(this.configuredPlatforms); } /** * Get available platforms (including disabled) */ getAvailablePlatforms(): PlatformType[] { return Array.from(this.syncers.keys()); } } // Export singleton instance export const platformService = new PlatformService();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jktfe/myAImemory-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server