UNPKG

create-ai-chat-context-experimental

Version:

Phase 2: TypeScript rewrite - AI Chat Context & Memory System with conversation extraction and AICF format support (powered by aicf-core v2.1.0).

376 lines 15.8 kB
/** * This file is part of create-ai-chat-context-experimental. * Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). * See LICENSE file for details. */ /** * Watcher Command * Phase 3: CLI Integration - October 2025 * Phase 5.5: Multi-Claude Support - October 2025 * * Background watcher for automatic checkpoint processing and multi-Claude consolidation */ import { join } from 'path'; import chalk from 'chalk'; import { WatcherManager } from '../utils/WatcherManager.js'; import { WatcherLogger } from '../utils/WatcherLogger.js'; import { MultiClaudeConsolidationService } from '../services/MultiClaudeConsolidationService.js'; import { WatcherConfigManager } from '../core/WatcherConfigManager.js'; import { AugmentCacheWriter } from '../writers/AugmentCacheWriter.js'; import { ClaudeCacheWriter } from '../writers/ClaudeCacheWriter.js'; import { CacheConsolidationAgent } from '../agents/CacheConsolidationAgent.js'; import { MemoryDropoffAgent } from '../agents/MemoryDropoffAgent.js'; import { SessionConsolidationAgent } from '../agents/SessionConsolidationAgent.js'; import { DaemonManager } from '../utils/DaemonManager.js'; /** * Watch directory for checkpoint files and process them automatically */ export class WatcherCommand { interval; watchDir; verbose; _daemon; _foreground; manager; logger; consolidationService; configManager; augmentCacheWriter; claudeCacheWriter; cacheConsolidationAgent; sessionConsolidationAgent; memoryDropoffAgent; isRunning = false; enabledPlatforms = []; cwd; constructor(options = {}) { this.cwd = options.cwd || process.cwd(); this.interval = parseInt(options.interval || '300000', 10); this.watchDir = options.dir || join(this.cwd, './checkpoints'); this.verbose = options.verbose || false; this._daemon = options.daemon || false; this._foreground = options.foreground !== false; // Default to foreground this.manager = new WatcherManager({ pidFile: join(this.cwd, '.watcher.pid'), logFile: join(this.cwd, '.watcher.log'), verbose: this.verbose, }); this.logger = new WatcherLogger({ verbose: this.verbose, logLevel: this.verbose ? 'debug' : 'info', }); this.configManager = new WatcherConfigManager(); this.consolidationService = new MultiClaudeConsolidationService({ verbose: this.verbose, enableCli: options.claudeCli ?? true, enableDesktop: options.claudeDesktop ?? true, }); this.augmentCacheWriter = new AugmentCacheWriter(this.cwd); this.claudeCacheWriter = new ClaudeCacheWriter(this.cwd); this.cacheConsolidationAgent = new CacheConsolidationAgent(this.cwd); this.sessionConsolidationAgent = new SessionConsolidationAgent(this.cwd); this.memoryDropoffAgent = new MemoryDropoffAgent(this.cwd); // Determine which platforms to enable this.enabledPlatforms = this.determinePlatforms(options); } /** * Determine which platforms to enable based on CLI options or config */ determinePlatforms(options) { const selectedPlatforms = []; // Check if any platform flags were explicitly set const hasExplicitFlags = options.augment !== undefined || options.warp !== undefined || options.claudeDesktop !== undefined || options.claudeCli !== undefined || options.copilot !== undefined || options.chatgpt !== undefined; if (hasExplicitFlags) { // Use only explicitly enabled platforms if (options.augment) selectedPlatforms.push('augment'); if (options.warp) selectedPlatforms.push('warp'); if (options.claudeDesktop) selectedPlatforms.push('claude-desktop'); if (options.claudeCli) selectedPlatforms.push('claude-cli'); if (options.copilot) selectedPlatforms.push('copilot'); if (options.chatgpt) selectedPlatforms.push('chatgpt'); } else { // Use all enabled platforms from config (or defaults if config not found) const configResult = this.configManager.loadSync(); if (configResult.ok) { return this.configManager.getEnabledPlatforms(); } // Default: enable Augment if no config found selectedPlatforms.push('augment'); } return selectedPlatforms; } /** * Start the watcher */ async start() { // Check if daemon is already running const daemonManager = new DaemonManager(this.cwd); const statusResult = daemonManager.getStatus(); if (statusResult.ok && statusResult.value.running) { console.log(); console.log(chalk.red('❌ Watcher already running')); console.log(); console.log(chalk.yellow('Status:'), chalk.green('Running ✓')); console.log(chalk.yellow('PID:'), statusResult.value.pid); console.log(chalk.yellow('Uptime:'), statusResult.value.uptime); console.log(); console.log(chalk.dim('To stop the watcher, run:')); console.log(chalk.cyan(' aice stop')); console.log(); console.log(chalk.dim('To check status, run:')); console.log(chalk.cyan(' aice status')); console.log(); process.exit(1); } this.isRunning = true; // Initialize manager const initResult = this.manager.initialize(); if (!initResult.ok) { console.error(chalk.red('❌ Failed to initialize watcher:'), initResult.error); process.exit(1); } // Write PID file const pidResult = daemonManager.writePidFile(process.pid); if (!pidResult.ok) { console.error(chalk.red('❌ Failed to write PID file:'), pidResult.error.message); process.exit(1); } this.logger.info('Watcher started', { watchDir: this.watchDir, interval: this.interval, verbose: this.verbose, platforms: this.enabledPlatforms, }); console.log(chalk.cyan('\n🔍 Starting watcher...\n')); console.log(chalk.gray(` Watch Directory: ${this.watchDir}`)); console.log(chalk.gray(` Check Interval: ${this.interval}ms`)); console.log(chalk.gray(` Verbose: ${this.verbose ? 'enabled' : 'disabled'}`)); console.log(chalk.gray(` Mode: ${this._daemon ? 'daemon' : this._foreground ? 'foreground' : 'auto'}`)); console.log(chalk.gray(` PID File: ${this.manager.getPidFilePath()}`)); console.log(chalk.gray(` Log File: ${this.manager.getLogFilePath()}`)); // Show enabled platforms if (this.enabledPlatforms.length > 0) { console.log(chalk.cyan('\n 🤖 Enabled Platforms:')); this.enabledPlatforms.forEach((platform) => { const platformEmoji = this.getPlatformEmoji(platform); console.log(chalk.gray(` ${platformEmoji} ${platform}`)); }); } else { console.log(chalk.yellow('\n ⚠️ No platforms enabled')); } // Show multi-Claude status only if Claude platforms are enabled const claudePlatformsEnabled = this.enabledPlatforms.some((p) => ['claude-desktop', 'claude-cli'].includes(p)); if (claudePlatformsEnabled) { const availableSources = this.consolidationService.getAvailableSources(); if (availableSources.length > 0) { console.log(chalk.cyan('\n 📚 Multi-Claude Support Enabled')); console.log(chalk.gray(` Available Sources: ${availableSources.join(', ')}`)); } } console.log(chalk.gray('\n Press Ctrl+C to stop\n')); // Handle graceful shutdown process.on('SIGINT', () => { this.stop(); }); // Start watching this.watch(); } /** * Get emoji for platform */ getPlatformEmoji(platform) { const emojis = { augment: '🔧', warp: '⚡', 'claude-desktop': '🖥️', 'claude-cli': '💻', copilot: '🤖', chatgpt: '🌐', }; return emojis[platform]; } /** * Stop the watcher */ stop() { this.isRunning = false; const status = this.manager.getStatus(); this.logger.info('Watcher stopped', { uptime: status.uptime, processedCount: status.processedCount, errorCount: status.errorCount, }); const cleanupResult = this.manager.cleanup(); if (!cleanupResult.ok) { console.error(chalk.red('❌ Failed to cleanup watcher:'), cleanupResult.error); } // Clean up PID file const daemonManager = new DaemonManager(this.cwd); const deletePidResult = daemonManager.deletePidFile(); if (!deletePidResult.ok) { console.error(chalk.red('❌ Failed to delete PID file:'), deletePidResult.error.message); } console.log(chalk.yellow('\n\n🛑 Watcher stopped\n')); console.log(chalk.gray(` Processed: ${status.processedCount}`)); console.log(chalk.gray(` Errors: ${status.errorCount}`)); console.log(chalk.gray(` Uptime: ${status.uptime ? Math.round(status.uptime / 1000) : 0}s\n`)); process.exit(0); } /** * Watch directory for checkpoint files */ watch() { const checkInterval = setInterval(() => { if (!this.isRunning) { clearInterval(checkInterval); return; } try { this.checkForCheckpoints(); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); this.logger.error('Watcher error', { error: errorMsg }); console.error(chalk.red('❌ Watcher error:'), errorMsg); this.manager.recordError(errorMsg); } }, this.interval); } /** * Check for new checkpoint files and multi-Claude messages */ checkForCheckpoints() { // Phase 6: Cache-First Pipeline // 1. Write LLM data to cache this.writeLLMDataToCache(); // 2. Consolidate cache chunks into individual conversation files this.consolidateCacheChunks(); // 3. Consolidate individual files into session files (Phase 6.5) this.consolidateSessionFiles(); // 4. Run memory dropoff (Phase 7) - compress sessions by age this.runMemoryDropoff(); } /** * Write LLM data to cache (Augment and Claude) */ writeLLMDataToCache() { // Write Augment data to cache if (this.enabledPlatforms.includes('augment')) { this.augmentCacheWriter.write().then((result) => { if (result.ok) { this.logger.debug('Augment cache written', { newChunks: result.value.newChunksWritten, skipped: result.value.chunksSkipped, }); } else { this.logger.error('Failed to write Augment cache', { error: result.error.message }); } }); } // Write Claude data to cache if (this.enabledPlatforms.includes('claude-cli') || this.enabledPlatforms.includes('claude-desktop')) { this.claudeCacheWriter.write().then((result) => { if (result.ok) { this.logger.debug('Claude cache written', { newChunks: result.value.newChunksWritten, skipped: result.value.chunksSkipped, }); } else { this.logger.error('Failed to write Claude cache', { error: result.error.message }); } }); } } /** * Consolidate cache chunks into .aicf and .ai files */ consolidateCacheChunks() { this.cacheConsolidationAgent.consolidate().then((result) => { if (result.ok) { this.logger.info('Cache consolidation complete', { processed: result.value.totalChunksProcessed, consolidated: result.value.chunksConsolidated, duplicated: result.value.chunksDuplicated, filesWritten: result.value.filesWritten, }); if (this.verbose) { console.log(chalk.green(`✅ Consolidated ${result.value.chunksConsolidated} chunks`)); } } else { this.logger.error('Cache consolidation failed', { error: result.error.message }); } }); } /** * Consolidate individual conversation files into session files (Phase 6.5) * Groups conversations by date and creates clean, AI-readable session files */ consolidateSessionFiles() { this.sessionConsolidationAgent.consolidate().then((result) => { if (result.ok) { const stats = result.value; this.logger.info('Session consolidation complete', { totalFiles: stats.totalFiles, totalConversations: stats.totalConversations, sessionsCreated: stats.sessionsCreated, uniqueConversations: stats.uniqueConversations, duplicatesRemoved: stats.duplicatesRemoved, storageReduction: stats.storageReduction, tokenReduction: stats.tokenReduction, }); if (this.verbose && stats.sessionsCreated > 0) { console.log(chalk.cyan(`📋 Consolidated ${stats.totalConversations} conversations into ${stats.sessionsCreated} sessions`)); console.log(chalk.gray(` Storage: ${stats.storageReduction}, Tokens: ${stats.tokenReduction}`)); } } else { this.logger.error('Session consolidation failed', { error: result.error.message }); } }); } /** * Run memory dropoff agent (Phase 7) * Move and compress conversations by age */ runMemoryDropoff() { this.memoryDropoffAgent.dropoff().then((result) => { if (result.ok) { const stats = result.value; this.logger.info('Memory dropoff complete', { sessions: stats.sessionFiles, medium: stats.mediumFiles, old: stats.oldFiles, archive: stats.archiveFiles, movedToMedium: stats.movedToMedium, movedToOld: stats.movedToOld, movedToArchive: stats.movedToArchive, compressed: stats.compressed, }); if (this.verbose && stats.compressed > 0) { console.log(chalk.cyan(`🗜️ Compressed ${stats.compressed} conversations`)); } } else { this.logger.error('Memory dropoff failed', { error: result.error.message }); } }); } } //# sourceMappingURL=WatcherCommand.js.map