UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

203 lines 5.48 kB
/** * Watch Service * * Monitors file system for changes and triggers workflow processing. * Uses chokidar for efficient file watching with debouncing. */ import chokidar from 'chokidar'; import * as path from 'path'; /** * File watching service with debouncing */ export class WatchService { watcher = null; callbacks = []; debounceTimers = new Map(); debounceMs = 500; stats; isRunning = false; constructor() { this.stats = { filesWatched: 0, eventsProcessed: 0, errors: 0, startTime: new Date() }; } /** * Start watching files */ async start(patterns, config) { if (this.isRunning) { throw new Error('Watch service is already running'); } this.debounceMs = config.debounce; // Configure chokidar this.watcher = chokidar.watch(patterns, { ignored: config.ignorePatterns || [], persistent: true, ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 } }); // Set up event handlers this.watcher.on('add', (path) => this.handleEvent('add', path)); this.watcher.on('change', (path) => this.handleEvent('change', path)); this.watcher.on('unlink', (path) => this.handleEvent('unlink', path)); this.watcher.on('ready', () => { const watched = this.watcher?.getWatched() || {}; this.stats.filesWatched = Object.values(watched).reduce((sum, files) => sum + files.length, 0); }); this.watcher.on('error', (error) => { this.stats.errors++; console.error('Watch error:', error); }); this.isRunning = true; // Wait for ready await new Promise((resolve) => { if (this.watcher) { this.watcher.on('ready', () => resolve()); } else { resolve(); } }); } /** * Stop watching */ async stop() { if (!this.isRunning) { return; } // Clear pending debounce timers for (const timer of this.debounceTimers.values()) { clearTimeout(timer); } this.debounceTimers.clear(); // Close watcher if (this.watcher) { await this.watcher.close(); this.watcher = null; } this.isRunning = false; } /** * Register callback for file changes */ onFileChange(callback) { this.callbacks.push(callback); } /** * Remove callback */ removeCallback(callback) { const index = this.callbacks.indexOf(callback); if (index > -1) { this.callbacks.splice(index, 1); } } /** * Set debounce time */ debounce(ms) { if (ms < 0) { throw new Error('Debounce time must be >= 0'); } this.debounceMs = ms; } /** * Get watch statistics */ getStats() { return { ...this.stats }; } /** * Check if service is running */ running() { return this.isRunning; } /** * Get list of watched files */ getWatchedFiles() { if (!this.watcher) { return []; } const watched = this.watcher.getWatched(); const files = []; for (const [dir, fileList] of Object.entries(watched)) { for (const file of fileList) { files.push(path.join(dir, file)); } } return files; } // Private methods handleEvent(type, path) { // Clear existing timer for this path const existingTimer = this.debounceTimers.get(path); if (existingTimer) { clearTimeout(existingTimer); } // Set new timer const timer = setTimeout(() => { this.processEvent(type, path); this.debounceTimers.delete(path); }, this.debounceMs); this.debounceTimers.set(path, timer); } async processEvent(type, path) { const event = { type, path, timestamp: new Date() }; this.stats.eventsProcessed++; this.stats.lastEvent = event.timestamp; // Call all registered callbacks for (const callback of this.callbacks) { try { await callback(event); } catch (error) { this.stats.errors++; console.error(`Error processing ${path}:`, error); } } } /** * Add pattern to watch */ addPattern(pattern) { if (!this.watcher) { throw new Error('Watch service is not running'); } this.watcher.add(pattern); } /** * Remove pattern from watch */ removePattern(pattern) { if (!this.watcher) { throw new Error('Watch service is not running'); } this.watcher.unwatch(pattern); } /** * Reset statistics */ resetStats() { this.stats = { filesWatched: this.stats.filesWatched, eventsProcessed: 0, errors: 0, startTime: new Date(), lastEvent: undefined }; } } //# sourceMappingURL=watch-service.js.map