feat: Replace DB component tools with filesystem-based user-components/

Components are now .vue files in user-components/<folder>/ parsed at runtime.
Replaces 6 DB MCP tools with 2 (list_fs_components, load_fs_component).
Adds vue-parser, fs-components API, and file watcher for live reload.
This commit is contained in:
2026-02-18 10:24:05 -06:00
parent e9451b2a47
commit d27da30494
13 changed files with 597 additions and 19 deletions

View File

@@ -0,0 +1,67 @@
/**
* Components Handler
* Watches user-components/ for .vue and .json file changes
* and broadcasts notifications via the sync server.
*/
import { watch, mkdirSync, existsSync, type FSWatcher } from 'fs'
import { join } from 'path'
import { USER_COMPONENTS_DIR } from '../../config'
let componentsWatcher: FSWatcher | null = null
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const DEBOUNCE_MS = 400
export function setupComponentsWatcher(workingDir: string, broadcast: (message: string, filter?: (ws: any) => boolean) => void) {
const componentsDir = join(workingDir, USER_COMPONENTS_DIR)
// Auto-create directory if it doesn't exist
if (!existsSync(componentsDir)) {
try {
mkdirSync(componentsDir, { recursive: true })
} catch (e: any) {
console.error(`[Components] Failed to create ${componentsDir}: ${e.message}`)
return
}
}
try {
componentsWatcher = watch(componentsDir, { recursive: true }, (_, filename) => {
if (!filename) return
// Only watch .vue and .json files
if (!filename.endsWith('.vue') && !filename.endsWith('.json')) return
// Debounce
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
// Extract folder name from path (first segment)
const folder = filename.split(/[/\\]/)[0] || ''
const file = filename.split(/[/\\]/).pop() || filename
console.log(`[Components] Change: ${filename}`)
broadcast(JSON.stringify({
type: 'component-change',
folder,
file,
timestamp: Date.now()
}))
}, DEBOUNCE_MS)
})
console.log(`[Components] Watching ${componentsDir}`)
} catch (e: any) {
console.error(`[Components] Watch failed: ${e.message}`)
}
}
export function cleanupComponentsWatcher() {
if (componentsWatcher) {
componentsWatcher.close()
componentsWatcher = null
}
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
}