mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-19 20:17:31 +00:00
REST API & CRUD: - Add REST API routes for profiles, cliproxy, accounts - Integrate React Query with data fetching hooks - Create API client wrapper with TypeScript types - Add shadcn/ui components (dialog, input, label, table) - Build profiles table with edit/delete actions - Create profile form dialog with zod validation - Mount QueryClientProvider in App Real-time Sync & WebSocket: - Implement file watcher with chokidar for config changes - Create WebSocket server with broadcast capability - Add client auto-reconnect with exponential backoff - Integrate React Query cache invalidation on WS messages - Add connection status indicator in header - Implement heartbeat keepalive (30s interval) - Add graceful cleanup on server shutdown - Show toast notifications for external config changes Dependencies: - Add @tanstack/react-query, react-table, react-hook-form, zod - Add chokidar for file watching
76 lines
1.7 KiB
TypeScript
76 lines
1.7 KiB
TypeScript
/**
|
|
* File Watcher (Phase 04)
|
|
*
|
|
* Watches ~/.ccs/ directory for config file changes using chokidar.
|
|
* Broadcasts changes to WebSocket clients for real-time sync.
|
|
*/
|
|
|
|
import chokidar, { FSWatcher } from 'chokidar';
|
|
import * as path from 'path';
|
|
import { getCcsDir } from '../utils/config-manager';
|
|
|
|
export interface FileChangeEvent {
|
|
type: 'config-changed' | 'settings-changed' | 'profiles-changed';
|
|
path: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
export type FileChangeCallback = (event: FileChangeEvent) => void;
|
|
|
|
export function createFileWatcher(onChange: FileChangeCallback): FSWatcher {
|
|
const ccsDir = getCcsDir();
|
|
|
|
const watcher = chokidar.watch(
|
|
[
|
|
path.join(ccsDir, 'config.json'),
|
|
path.join(ccsDir, '*.settings.json'),
|
|
path.join(ccsDir, 'profiles.json'),
|
|
],
|
|
{
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
awaitWriteFinish: {
|
|
stabilityThreshold: 100,
|
|
pollInterval: 50,
|
|
},
|
|
}
|
|
);
|
|
|
|
watcher.on('change', (filePath) => {
|
|
const basename = path.basename(filePath);
|
|
let type: FileChangeEvent['type'];
|
|
|
|
if (basename === 'config.json') {
|
|
type = 'config-changed';
|
|
} else if (basename === 'profiles.json') {
|
|
type = 'profiles-changed';
|
|
} else {
|
|
type = 'settings-changed';
|
|
}
|
|
|
|
onChange({
|
|
type,
|
|
path: filePath,
|
|
timestamp: Date.now(),
|
|
});
|
|
});
|
|
|
|
watcher.on('add', (filePath) => {
|
|
onChange({
|
|
type: 'config-changed',
|
|
path: filePath,
|
|
timestamp: Date.now(),
|
|
});
|
|
});
|
|
|
|
watcher.on('unlink', (filePath) => {
|
|
onChange({
|
|
type: 'config-changed',
|
|
path: filePath,
|
|
timestamp: Date.now(),
|
|
});
|
|
});
|
|
|
|
return watcher;
|
|
}
|