cntx-ui 2.0.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +51 -339
  2. package/VISION.md +110 -0
  3. package/bin/cntx-ui-mcp.sh +3 -0
  4. package/bin/cntx-ui.js +138 -55
  5. package/lib/agent-runtime.js +301 -0
  6. package/lib/agent-tools.js +370 -0
  7. package/lib/api-router.js +1161 -0
  8. package/lib/bundle-manager.js +236 -0
  9. package/lib/configuration-manager.js +760 -0
  10. package/lib/database-manager.js +397 -0
  11. package/lib/file-system-manager.js +489 -0
  12. package/lib/heuristics-manager.js +527 -0
  13. package/lib/mcp-server.js +1125 -2
  14. package/lib/semantic-splitter.js +225 -491
  15. package/lib/simple-vector-store.js +98 -0
  16. package/lib/websocket-manager.js +470 -0
  17. package/package.json +19 -25
  18. package/server.js +742 -1935
  19. package/templates/TOOLS.md +41 -0
  20. package/templates/activities/README.md +67 -0
  21. package/templates/activities/activities/create-project-bundles/README.md +84 -0
  22. package/templates/activities/activities/create-project-bundles/notes.md +98 -0
  23. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  24. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  25. package/templates/activities/activities.json +219 -0
  26. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  27. package/templates/activities/lib/create-activity.mdc +63 -0
  28. package/templates/activities/lib/generate-tasks.mdc +64 -0
  29. package/templates/activities/lib/process-task-list.mdc +52 -0
  30. package/templates/agent-config.yaml +65 -0
  31. package/templates/agent-instructions.md +234 -0
  32. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  33. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  34. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  35. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  36. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  37. package/templates/agent-rules/core/response-formatting.md +120 -0
  38. package/templates/agent-rules/project-specific/architecture.md +145 -0
  39. package/templates/config.json +76 -0
  40. package/templates/hidden-files.json +14 -0
  41. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  42. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  43. package/web/dist/cntx-ui.svg +18 -0
  44. package/web/dist/index.html +25 -8
  45. package/lib/semantic-integration.js +0 -441
  46. package/mcp-config-example.json +0 -9
  47. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  48. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  49. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,1161 @@
1
+ /**
2
+ * API Router for cntx-ui
3
+ * Handles all HTTP API endpoints and request routing
4
+ */
5
+
6
+ import { parse } from 'url';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+
10
+ export default class APIRouter {
11
+ constructor(cntxServer, configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore, activityManager) {
12
+ this.cntxServer = cntxServer;
13
+ this.configManager = configManager;
14
+ this.bundleManager = bundleManager;
15
+ this.fileSystemManager = fileSystemManager;
16
+ this.semanticAnalysisManager = semanticAnalysisManager;
17
+ this.vectorStore = vectorStore;
18
+ this.activityManager = activityManager;
19
+ }
20
+
21
+ async handleRequest(req, res, url) {
22
+ const method = req.method;
23
+ const pathname = url.pathname;
24
+
25
+ // DEBUG: Log all incoming API requests
26
+ console.log('[API REQUEST]', method, pathname);
27
+ if (pathname.includes('database')) {
28
+ console.log('[DATABASE] Route requested:', pathname, method);
29
+ }
30
+
31
+ try {
32
+ // Route to appropriate handler
33
+ if (pathname === '/api/bundles' && method === 'GET') {
34
+ return await this.handleGetBundles(req, res, url);
35
+ }
36
+
37
+ if (pathname === '/api/bundles' && method === 'POST') {
38
+ return await this.handlePostBundles(req, res);
39
+ }
40
+
41
+ if (pathname.startsWith('/api/bundles/') && method === 'GET') {
42
+ const bundleName = pathname.split('/')[3];
43
+ return await this.handleGetBundle(req, res, bundleName);
44
+ }
45
+
46
+ if (pathname.startsWith('/api/regenerate/') && (method === 'GET' || method === 'POST')) {
47
+ const bundleName = pathname.split('/')[3];
48
+ return await this.handleRegenerateBundle(req, res, bundleName);
49
+ }
50
+
51
+ if (pathname === '/api/bundles-from-chunk' && method === 'POST') {
52
+ return await this.handleCreateBundlesFromChunk(req, res);
53
+ }
54
+
55
+ if (pathname === '/api/bundle-visibility-stats' && method === 'GET') {
56
+ return await this.handleBundleVisibilityStats(req, res);
57
+ }
58
+
59
+ if (pathname.startsWith('/api/bundle-categories/') && method === 'GET') {
60
+ const bundleName = pathname.split('/')[3];
61
+ return await this.handleBundleCategories(req, res, bundleName);
62
+ }
63
+
64
+ if (pathname === '/api/config' && method === 'GET') {
65
+ return await this.handleGetConfig(req, res);
66
+ }
67
+
68
+ if (pathname === '/api/config' && method === 'POST') {
69
+ return await this.handlePostConfig(req, res);
70
+ }
71
+
72
+ if (pathname === '/api/files' && method === 'GET') {
73
+ return await this.handleGetFiles(req, res);
74
+ }
75
+
76
+ if (pathname === '/api/cursor-rules' && method === 'GET') {
77
+ return await this.handleGetCursorRules(req, res);
78
+ }
79
+
80
+ if (pathname === '/api/cursor-rules' && method === 'POST') {
81
+ return await this.handlePostCursorRules(req, res);
82
+ }
83
+
84
+ if (pathname === '/api/cursor-rules/templates' && method === 'GET') {
85
+ return await this.handleGetCursorRulesTemplates(req, res);
86
+ }
87
+
88
+ if (pathname === '/api/claude-md' && method === 'GET') {
89
+ return await this.handleGetClaudeMd(req, res);
90
+ }
91
+
92
+ if (pathname === '/api/claude-md' && method === 'POST') {
93
+ return await this.handlePostClaudeMd(req, res);
94
+ }
95
+
96
+ if (pathname === '/api/heuristics/config' && method === 'GET') {
97
+ return await this.handleGetHeuristicsConfig(req, res);
98
+ }
99
+
100
+ if (pathname === '/api/heuristics/config' && method === 'PUT') {
101
+ return await this.handlePutHeuristicsConfig(req, res);
102
+ }
103
+
104
+ if (pathname === '/api/test-pattern' && method === 'POST') {
105
+ return await this.handleTestPattern(req, res);
106
+ }
107
+
108
+ if (pathname === '/api/hidden-files' && method === 'GET') {
109
+ return await this.handleGetHiddenFiles(req, res);
110
+ }
111
+
112
+ if (pathname === '/api/hidden-files' && method === 'POST') {
113
+ return await this.handlePostHiddenFiles(req, res);
114
+ }
115
+
116
+ if (pathname === '/api/files-with-visibility' && method === 'GET') {
117
+ return await this.handleGetFilesWithVisibility(req, res, url);
118
+ }
119
+
120
+ if (pathname === '/api/ignore-patterns' && method === 'GET') {
121
+ return await this.handleGetIgnorePatterns(req, res);
122
+ }
123
+
124
+ if (pathname === '/api/ignore-patterns' && method === 'POST') {
125
+ return await this.handlePostIgnorePatterns(req, res);
126
+ }
127
+
128
+ if (pathname === '/api/reset-hidden-files' && method === 'POST') {
129
+ return await this.handleResetHiddenFiles(req, res);
130
+ }
131
+
132
+ if (pathname === '/api/semantic-chunks' && method === 'GET') {
133
+ return await this.handleGetSemanticChunks(req, res, url);
134
+ }
135
+
136
+ if (pathname === '/api/semantic-chunks/export' && method === 'POST') {
137
+ return await this.handleExportSemanticChunk(req, res);
138
+ }
139
+
140
+ if (pathname === '/api/mcp-status' && method === 'GET') {
141
+ return await this.handleGetMcpStatus(req, res);
142
+ }
143
+
144
+ if (pathname === '/api/cntxignore' && method === 'GET') {
145
+ return await this.handleGetCntxignore(req, res);
146
+ }
147
+
148
+ if (pathname === '/api/cntxignore' && method === 'POST') {
149
+ return await this.handlePostCntxignore(req, res);
150
+ }
151
+
152
+ if (pathname === '/api/gitignore' && method === 'GET') {
153
+ return await this.handleGetGitignore(req, res);
154
+ }
155
+
156
+ if (pathname === '/api/gitignore' && method === 'POST') {
157
+ return await this.handlePostGitignore(req, res);
158
+ }
159
+
160
+ if (pathname === '/api/status' && method === 'GET') {
161
+ return await this.handleGetStatus(req, res);
162
+ }
163
+
164
+ if (pathname === '/api/vector-db/status' && method === 'GET') {
165
+ return await this.handleGetVectorDbStatus(req, res);
166
+ }
167
+
168
+ if (pathname === '/api/vector-db/rebuild' && method === 'POST') {
169
+ return await this.handlePostVectorDbRebuild(req, res);
170
+ }
171
+
172
+ if (pathname === '/api/vector-db/search' && method === 'POST') {
173
+ return await this.handlePostVectorDbSearch(req, res);
174
+ }
175
+
176
+ if (pathname === '/api/vector-db/network' && method === 'GET') {
177
+ return await this.handleGetVectorDbNetwork(req, res);
178
+ }
179
+
180
+ if (pathname === '/api/semantic-search' && method === 'POST') {
181
+ return await this.handlePostSemanticSearch(req, res);
182
+ }
183
+
184
+
185
+ if (pathname === '/api/vector-db/search-by-type' && method === 'POST') {
186
+ return await this.handlePostVectorDbSearchByType(req, res);
187
+ }
188
+
189
+ if (pathname === '/api/vector-db/search-by-domain' && method === 'POST') {
190
+ return await this.handlePostVectorDbSearchByDomain(req, res);
191
+ }
192
+
193
+ if (pathname === '/api/activities' && method === 'GET') {
194
+ return await this.handleGetActivities(req, res);
195
+ }
196
+
197
+ if (pathname.startsWith('/api/activities/') && pathname.endsWith('/reasoning') && method === 'GET') {
198
+ const activityId = pathname.split('/')[3];
199
+ return await this.handleGetActivityReasoning(req, res, activityId);
200
+ }
201
+
202
+ if (pathname.startsWith('/api/activities/') && pathname.endsWith('/execute') && method === 'POST') {
203
+ const activityId = pathname.split('/')[3];
204
+ return await this.handlePostActivityExecute(req, res, activityId);
205
+ }
206
+
207
+ if (pathname.startsWith('/api/activities/') && pathname.endsWith('/stop') && method === 'POST') {
208
+ const activityId = pathname.split('/')[3];
209
+ return await this.handlePostActivityStop(req, res, activityId);
210
+ }
211
+
212
+ if (pathname === '/api/open-file' && method === 'POST') {
213
+ return await this.handleOpenFile(req, res);
214
+ }
215
+
216
+ if (pathname === '/api/bundle-sync-status' && method === 'GET') {
217
+ return await this.handleGetBundleSyncStatus(req, res);
218
+ }
219
+
220
+ if (pathname === '/api/bundle-sync-status' && method === 'POST') {
221
+ return await this.handlePostBundleSyncStatus(req, res);
222
+ }
223
+
224
+ if (pathname === '/api/database/info' && method === 'GET') {
225
+ return await this.handleGetDatabaseInfo(req, res);
226
+ }
227
+
228
+ if (pathname === '/api/database/query' && method === 'POST') {
229
+ return await this.handlePostDatabaseQuery(req, res);
230
+ }
231
+
232
+ // If no route matches, return 404
233
+ res.writeHead(404, { 'Content-Type': 'application/json' });
234
+ res.end(JSON.stringify({ error: 'API endpoint not found' }));
235
+
236
+ } catch (error) {
237
+ console.error('API Error:', error);
238
+ res.writeHead(500, { 'Content-Type': 'application/json' });
239
+ res.end(JSON.stringify({ error: error.message }));
240
+ }
241
+ }
242
+
243
+ // === Bundle Operations ===
244
+
245
+ async handleGetBundles(req, res, url) {
246
+ try {
247
+ const bundleInfo = this.bundleManager.getAllBundleInfo();
248
+ res.writeHead(200, { 'Content-Type': 'application/json' });
249
+ res.end(JSON.stringify(bundleInfo));
250
+ } catch (error) {
251
+ this.sendError(res, 500, error.message);
252
+ }
253
+ }
254
+
255
+ async handlePostBundles(req, res) {
256
+ try {
257
+ const body = JSON.parse(await this.getRequestBody(req));
258
+ const { action, bundleName, fileName, fileNames } = body;
259
+
260
+ if (!action || !bundleName) {
261
+ return this.sendError(res, 400, 'Missing required fields: action and bundleName');
262
+ }
263
+
264
+ const bundles = this.configManager.getBundles();
265
+ const bundle = bundles.get(bundleName);
266
+
267
+ if (!bundle) {
268
+ return this.sendError(res, 404, `Bundle "${bundleName}" not found`);
269
+ }
270
+
271
+ switch (action) {
272
+ case 'add-file':
273
+ if (!fileName) {
274
+ return this.sendError(res, 400, 'Missing fileName for add-file action');
275
+ }
276
+ // Ensure we're working with relative paths
277
+ const relativeAddFileName = fileName.startsWith('/') ?
278
+ path.relative(this.configManager.CWD, fileName) : fileName;
279
+ if (!bundle.files.includes(relativeAddFileName)) {
280
+ bundle.files.push(relativeAddFileName);
281
+ bundle.changed = true;
282
+ this.configManager.saveBundleStates();
283
+ }
284
+ break;
285
+
286
+ case 'remove-file':
287
+ if (!fileName) {
288
+ return this.sendError(res, 400, 'Missing fileName for remove-file action');
289
+ }
290
+ // Ensure we're working with relative paths for both search and removal
291
+ const relativeRemoveFileName = fileName.startsWith('/') ?
292
+ path.relative(this.configManager.CWD, fileName) : fileName;
293
+ const removeIndex = bundle.files.indexOf(relativeRemoveFileName);
294
+ if (removeIndex > -1) {
295
+ bundle.files.splice(removeIndex, 1);
296
+ bundle.changed = true;
297
+ this.configManager.saveBundleStates();
298
+ }
299
+ break;
300
+
301
+ case 'bulk-add-files':
302
+ if (!fileNames || !Array.isArray(fileNames)) {
303
+ return this.sendError(res, 400, 'Missing fileNames array for bulk-add-files action');
304
+ }
305
+ fileNames.forEach(file => {
306
+ // Ensure we're working with relative paths
307
+ const relativeFile = file.startsWith('/') ?
308
+ path.relative(this.configManager.CWD, file) : file;
309
+ if (!bundle.files.includes(relativeFile)) {
310
+ bundle.files.push(relativeFile);
311
+ }
312
+ });
313
+ bundle.changed = true;
314
+ this.configManager.saveBundleStates();
315
+ break;
316
+
317
+ case 'bulk-remove-files':
318
+ if (!fileNames || !Array.isArray(fileNames)) {
319
+ return this.sendError(res, 400, 'Missing fileNames array for bulk-remove-files action');
320
+ }
321
+ fileNames.forEach(file => {
322
+ // Ensure we're working with relative paths
323
+ const relativeFile = file.startsWith('/') ?
324
+ path.relative(this.configManager.CWD, file) : file;
325
+ const index = bundle.files.indexOf(relativeFile);
326
+ if (index > -1) {
327
+ bundle.files.splice(index, 1);
328
+ }
329
+ });
330
+ bundle.changed = true;
331
+ this.configManager.saveBundleStates();
332
+ break;
333
+
334
+ default:
335
+ return this.sendError(res, 400, `Unknown action: ${action}`);
336
+ }
337
+
338
+ this.sendResponse(res, 200, { success: true, message: `Action ${action} completed successfully` });
339
+ } catch (error) {
340
+ console.error('handlePostBundles error:', error);
341
+ this.sendError(res, 500, error.message);
342
+ }
343
+ }
344
+
345
+ async handleGetBundle(req, res, bundleName) {
346
+ const content = this.bundleManager.getBundleContent(bundleName);
347
+ if (!content) {
348
+ res.writeHead(404, { 'Content-Type': 'application/json' });
349
+ res.end(JSON.stringify({ error: 'Bundle not found' }));
350
+ return;
351
+ }
352
+
353
+ res.writeHead(200, { 'Content-Type': 'application/xml' });
354
+ res.end(content);
355
+ }
356
+
357
+ async handleRegenerateBundle(req, res, bundleName) {
358
+ try {
359
+ await this.bundleManager.regenerateBundle(bundleName);
360
+ this.sendResponse(res, 200, { success: true, message: `Bundle ${bundleName} regenerated` });
361
+ } catch (error) {
362
+ this.sendError(res, 500, error.message);
363
+ }
364
+ }
365
+
366
+ async handleCreateBundlesFromChunk(req, res) {
367
+ const body = await this.getRequestBody(req);
368
+ const { chunkName, files } = JSON.parse(body);
369
+
370
+ const bundleName = this.configManager.createBundleFromChunk(chunkName, files);
371
+ await this.bundleManager.regenerateBundle(bundleName);
372
+
373
+ res.writeHead(200, { 'Content-Type': 'application/json' });
374
+ res.end(JSON.stringify({ success: true, bundleName }));
375
+ }
376
+
377
+ async handleBundleVisibilityStats(req, res) {
378
+ const bundles = this.configManager.getBundles();
379
+ const stats = {};
380
+
381
+ for (const [bundleName] of bundles) {
382
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
383
+ stats[bundleName] = {
384
+ total: files.length,
385
+ included: files.filter(f => f.included).length,
386
+ hidden: files.filter(f => f.hidden).length,
387
+ matching: files.filter(f => f.matchesPattern).length
388
+ };
389
+ }
390
+
391
+ res.writeHead(200, { 'Content-Type': 'application/json' });
392
+ res.end(JSON.stringify(stats));
393
+ }
394
+
395
+ async handleBundleCategories(req, res, bundleName) {
396
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
397
+ const includedFiles = files.filter(f => f.included).map(f => f.fullPath);
398
+ const categories = this.fileSystemManager.categorizeFiles(includedFiles);
399
+
400
+ res.writeHead(200, { 'Content-Type': 'application/json' });
401
+ res.end(JSON.stringify(categories));
402
+ }
403
+
404
+ // === Configuration Endpoints ===
405
+
406
+ async handleGetConfig(req, res) {
407
+ const bundles = this.configManager.getBundles();
408
+ const config = {
409
+ bundles: {},
410
+ editor: this.configManager.getEditor()
411
+ };
412
+
413
+ // Note: bundles are now managed separately in bundle-states.json
414
+ // config.json only contains non-bundle settings
415
+
416
+ res.writeHead(200, { 'Content-Type': 'application/json' });
417
+ res.end(JSON.stringify(config));
418
+ }
419
+
420
+ async handlePostConfig(req, res) {
421
+ const body = await this.getRequestBody(req);
422
+ const config = JSON.parse(body);
423
+
424
+ this.configManager.saveConfig(config);
425
+ this.configManager.loadConfig();
426
+ await this.bundleManager.generateAllBundles();
427
+
428
+ res.writeHead(200, { 'Content-Type': 'application/json' });
429
+ res.end(JSON.stringify({ success: true }));
430
+ }
431
+
432
+ async handleGetCursorRules(req, res) {
433
+ const content = this.configManager.loadCursorRules();
434
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
435
+ res.end(content);
436
+ }
437
+
438
+ async handlePostCursorRules(req, res) {
439
+ const body = await this.getRequestBody(req);
440
+ const { content } = JSON.parse(body);
441
+
442
+ this.configManager.saveCursorRules(content);
443
+ res.writeHead(200, { 'Content-Type': 'application/json' });
444
+ res.end(JSON.stringify({ success: true }));
445
+ }
446
+
447
+ async handleGetCursorRulesTemplates(req, res) {
448
+ const templates = {
449
+ react: this.configManager.generateCursorRulesTemplate({ projectType: 'react', name: 'React Project' }),
450
+ vue: this.configManager.generateCursorRulesTemplate({ projectType: 'vue', name: 'Vue Project' }),
451
+ angular: this.configManager.generateCursorRulesTemplate({ projectType: 'angular', name: 'Angular Project' }),
452
+ 'node-backend': this.configManager.generateCursorRulesTemplate({ projectType: 'node-backend', name: 'Node.js Backend' }),
453
+ javascript: this.configManager.generateCursorRulesTemplate({ projectType: 'javascript', name: 'JavaScript Project' })
454
+ };
455
+
456
+ res.writeHead(200, { 'Content-Type': 'application/json' });
457
+ res.end(JSON.stringify(templates));
458
+ }
459
+
460
+ async handleGetClaudeMd(req, res) {
461
+ const content = this.configManager.loadClaudeMd();
462
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
463
+ res.end(content);
464
+ }
465
+
466
+ async handlePostClaudeMd(req, res) {
467
+ const body = await this.getRequestBody(req);
468
+ const { content } = JSON.parse(body);
469
+
470
+ this.configManager.saveClaudeMd(content);
471
+ res.writeHead(200, { 'Content-Type': 'application/json' });
472
+ res.end(JSON.stringify({ success: true }));
473
+ }
474
+
475
+ async handleGetHeuristicsConfig(req, res) {
476
+ const config = this.configManager.loadHeuristicsConfig();
477
+ res.writeHead(200, { 'Content-Type': 'application/json' });
478
+ res.end(JSON.stringify(config));
479
+ }
480
+
481
+ async handlePutHeuristicsConfig(req, res) {
482
+ const body = await this.getRequestBody(req);
483
+ const config = JSON.parse(body);
484
+
485
+ this.configManager.saveHeuristicsConfig(config);
486
+ res.writeHead(200, { 'Content-Type': 'application/json' });
487
+ res.end(JSON.stringify({ success: true }));
488
+ }
489
+
490
+ // === File Operations ===
491
+
492
+ async handleGetFiles(req, res) {
493
+ const fileTree = this.fileSystemManager.getFileTree();
494
+ res.writeHead(200, { 'Content-Type': 'application/json' });
495
+ res.end(JSON.stringify(fileTree));
496
+ }
497
+
498
+ async handleGetFilesWithVisibility(req, res, url) {
499
+ const bundleName = url.searchParams.get('bundle');
500
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
501
+
502
+ res.writeHead(200, { 'Content-Type': 'application/json' });
503
+ res.end(JSON.stringify(files));
504
+ }
505
+
506
+ async handleGetHiddenFiles(req, res) {
507
+ const hiddenConfig = this.configManager.getHiddenFilesConfig();
508
+ const stats = {
509
+ globalHidden: hiddenConfig.globalHidden.length,
510
+ bundleSpecificTotal: Object.values(hiddenConfig.bundleSpecific).reduce((sum, arr) => sum + arr.length, 0),
511
+ userIgnorePatterns: hiddenConfig.userIgnorePatterns.length,
512
+ disabledSystemPatterns: hiddenConfig.disabledSystemPatterns.length,
513
+ config: hiddenConfig
514
+ };
515
+
516
+ res.writeHead(200, { 'Content-Type': 'application/json' });
517
+ res.end(JSON.stringify(stats));
518
+ }
519
+
520
+ async handlePostHiddenFiles(req, res) {
521
+ const body = await this.getRequestBody(req);
522
+ const data = JSON.parse(body);
523
+
524
+ if (data.action === 'toggle') {
525
+ this.configManager.toggleFileVisibility(data.filePath, data.bundleName, data.forceHide);
526
+ } else if (data.action === 'bulk-toggle') {
527
+ this.configManager.bulkToggleFileVisibility(data.filePaths, data.bundleName, data.forceHide);
528
+ }
529
+
530
+ res.writeHead(200, { 'Content-Type': 'application/json' });
531
+ res.end(JSON.stringify({ success: true }));
532
+ }
533
+
534
+ async handleGetIgnorePatterns(req, res) {
535
+ const patterns = {
536
+ system: this.fileSystemManager.ignorePatterns.filter(p => !this.configManager.getHiddenFilesConfig().userIgnorePatterns.some(up => up.pattern === p)),
537
+ user: this.configManager.getHiddenFilesConfig().userIgnorePatterns,
538
+ disabled: this.configManager.getHiddenFilesConfig().disabledSystemPatterns
539
+ };
540
+
541
+ res.writeHead(200, { 'Content-Type': 'application/json' });
542
+ res.end(JSON.stringify(patterns));
543
+ }
544
+
545
+ async handlePostIgnorePatterns(req, res) {
546
+ const body = await this.getRequestBody(req);
547
+ const data = JSON.parse(body);
548
+
549
+ if (data.action === 'add') {
550
+ this.configManager.addUserIgnorePattern(data.pattern);
551
+ } else if (data.action === 'remove') {
552
+ this.configManager.removeUserIgnorePattern(data.pattern);
553
+ } else if (data.action === 'toggle-system') {
554
+ this.configManager.toggleSystemIgnorePattern(data.pattern);
555
+ }
556
+
557
+ // Reload patterns and regenerate bundles
558
+ this.configManager.loadIgnorePatterns();
559
+ this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
560
+ await this.bundleManager.generateAllBundles();
561
+
562
+ res.writeHead(200, { 'Content-Type': 'application/json' });
563
+ res.end(JSON.stringify({ success: true }));
564
+ }
565
+
566
+ async handleResetHiddenFiles(req, res) {
567
+ const body = await this.getRequestBody(req);
568
+ const { scope, bundleName } = JSON.parse(body);
569
+
570
+ const hiddenConfig = this.configManager.getHiddenFilesConfig();
571
+
572
+ if (scope === 'global') {
573
+ hiddenConfig.globalHidden = [];
574
+ } else if (scope === 'bundle' && bundleName) {
575
+ delete hiddenConfig.bundleSpecific[bundleName];
576
+ } else if (scope === 'all') {
577
+ hiddenConfig.globalHidden = [];
578
+ hiddenConfig.bundleSpecific = {};
579
+ }
580
+
581
+ this.configManager.saveHiddenFilesConfig();
582
+ await this.bundleManager.generateAllBundles();
583
+
584
+ res.writeHead(200, { 'Content-Type': 'application/json' });
585
+ res.end(JSON.stringify({ success: true }));
586
+ }
587
+
588
+ async handleGetCntxignore(req, res) {
589
+ try {
590
+ const content = this.configManager.loadCntxignore();
591
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
592
+ res.end(content);
593
+ } catch (error) {
594
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
595
+ res.end('');
596
+ }
597
+ }
598
+
599
+ async handlePostCntxignore(req, res) {
600
+ const body = await this.getRequestBody(req);
601
+ const { content } = JSON.parse(body);
602
+
603
+ // Save content and reload patterns
604
+ const success = this.configManager.saveCntxignore(content);
605
+ if (!success) {
606
+ return this.sendError(res, 500, 'Failed to save .cntxignore');
607
+ }
608
+
609
+ this.fileSystemManager.setIgnorePatterns(this.configManager.ignorePatterns);
610
+
611
+ this.sendResponse(res, 200, { success: true });
612
+ }
613
+
614
+ async handleGetGitignore(req, res) {
615
+ try {
616
+ const content = this.configManager.loadGitignore();
617
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
618
+ res.end(content);
619
+ } catch (error) {
620
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
621
+ res.end('');
622
+ }
623
+ }
624
+
625
+ async handlePostGitignore(req, res) {
626
+ const body = await this.getRequestBody(req);
627
+ const { content } = JSON.parse(body);
628
+
629
+ this.configManager.saveGitignore(content);
630
+
631
+ res.writeHead(200, { 'Content-Type': 'application/json' });
632
+ res.end(JSON.stringify({ success: true }));
633
+ }
634
+
635
+ // === Semantic Analysis ===
636
+
637
+ async handleGetSemanticChunks(req, res, url) {
638
+ const forceRefresh = url.searchParams.get('refresh') === 'true';
639
+
640
+ try {
641
+ const analysis = forceRefresh
642
+ ? await this.semanticAnalysisManager.refreshSemanticAnalysis()
643
+ : await this.semanticAnalysisManager.getSemanticAnalysis();
644
+
645
+ const rawChunks = analysis?.chunks || [];
646
+ // console.log('📊 Raw chunks sample:', rawChunks[0] ? {
647
+ // name: rawChunks[0].name,
648
+ // type: rawChunks[0].type,
649
+ // subtype: rawChunks[0].subtype,
650
+ // purpose: rawChunks[0].purpose,
651
+ // hasCode: !!rawChunks[0].code
652
+ // } : 'No chunks');
653
+
654
+ // Transform chunks to match VectorVisualization expectations
655
+ const chunks = rawChunks.map(chunk => ({
656
+ id: chunk.name || chunk.id || `chunk-${Math.random()}`,
657
+ name: chunk.name,
658
+ code: chunk.code,
659
+ semanticType: chunk.subtype || chunk.type || 'unknown',
660
+ businessDomain: chunk.businessDomain || [],
661
+ technicalPatterns: chunk.technicalPatterns || [],
662
+ purpose: chunk.purpose || '',
663
+ filePath: chunk.filePath,
664
+ files: chunk.filePath ? [chunk.filePath] : [],
665
+ size: chunk.size || 0,
666
+ complexity: chunk.complexity || 0,
667
+ tags: chunk.tags || [],
668
+ startLine: chunk.startLine,
669
+ isExported: chunk.isExported,
670
+ isAsync: chunk.isAsync,
671
+ bundles: chunk.bundles || []
672
+ }));
673
+
674
+ this.sendResponse(res, 200, {
675
+ summary: {
676
+ totalFiles: analysis?.summary?.totalFiles || analysis?.fileCount || 0,
677
+ totalFunctions: rawChunks.filter(c => c.type === 'function_chunk').length,
678
+ totalChunks: rawChunks.length,
679
+ averageChunkSize: rawChunks.length > 0 ? Math.round(rawChunks.reduce((sum, c) => sum + (c.size || 0), 0) / rawChunks.length) : 0
680
+ },
681
+ chunks: chunks,
682
+ lastSemanticAnalysis: this.semanticAnalysisManager.lastSemanticAnalysis
683
+ });
684
+ } catch (error) {
685
+ this.sendError(res, 500, error.message);
686
+ }
687
+ }
688
+
689
+ async handleExportSemanticChunk(req, res) {
690
+ const body = await this.getRequestBody(req);
691
+ const { chunkName } = JSON.parse(body);
692
+
693
+ try {
694
+ const content = await this.semanticAnalysisManager.exportSemanticChunk(chunkName);
695
+ res.writeHead(200, { 'Content-Type': 'application/xml' });
696
+ res.end(content);
697
+ } catch (error) {
698
+ res.writeHead(500, { 'Content-Type': 'application/json' });
699
+ res.end(JSON.stringify({ error: error.message }));
700
+ }
701
+ }
702
+
703
+ // === Utilities ===
704
+
705
+ async handleTestPattern(req, res) {
706
+ const body = await this.getRequestBody(req);
707
+ const { pattern } = JSON.parse(body);
708
+
709
+ const allFiles = this.fileSystemManager.getAllFiles();
710
+ const matchingFiles = allFiles.filter(file =>
711
+ this.fileSystemManager.matchesPattern(file, pattern)
712
+ ).map(file => this.fileSystemManager.relativePath(file));
713
+
714
+ res.writeHead(200, { 'Content-Type': 'application/json' });
715
+ res.end(JSON.stringify({
716
+ pattern,
717
+ matchCount: matchingFiles.length,
718
+ matches: matchingFiles.slice(0, 100) // Limit to first 100 matches
719
+ }));
720
+ }
721
+
722
+ async handlePostSemanticSearch(req, res) {
723
+ try {
724
+ const body = await this.getRequestBody(req);
725
+ const { query, limit = 20 } = JSON.parse(body);
726
+
727
+ if (!query) {
728
+ return this.sendError(res, 400, 'Search query is required');
729
+ }
730
+
731
+ const results = await this.vectorStore.search(query, { limit });
732
+ this.sendResponse(res, 200, { results });
733
+ } catch (error) {
734
+ this.sendError(res, 500, error.message);
735
+ }
736
+ }
737
+
738
+ async handleGetMcpStatus(req, res) {
739
+ const isRunning = this.cntxServer.mcpServerStarted || false;
740
+ const status = {
741
+ enabled: isRunning,
742
+ running: isRunning,
743
+ available: true,
744
+ message: isRunning ? 'MCP server is running' : 'MCP server integration available'
745
+ };
746
+
747
+ res.writeHead(200, { 'Content-Type': 'application/json' });
748
+ res.end(JSON.stringify(status));
749
+ }
750
+
751
+ async handleGetVectorDbNetwork(req, res) {
752
+ try {
753
+ // 1. Get all chunks and embeddings
754
+ const chunks = this.configManager.dbManager.db.prepare('SELECT * FROM semantic_chunks').all();
755
+ const embeddings = this.configManager.dbManager.db.prepare('SELECT * FROM vector_embeddings').all();
756
+
757
+ const nodes = chunks.map(c => this.configManager.dbManager.mapChunkRow(c));
758
+ const edges = [];
759
+ const threshold = 0.7; // High threshold for clean network
760
+
761
+ // 2. Perform pairwise similarity on backend (efficient for small/medium repos)
762
+ // Limit to top 100 most complex chunks to keep graph readable
763
+ const topNodes = nodes.sort((a, b) => (b.complexity?.score || 0) - (a.complexity?.score || 0)).slice(0, 100);
764
+
765
+ const embeddingMap = new Map();
766
+ embeddings.forEach(e => {
767
+ embeddingMap.set(e.chunk_id, new Float32Array(e.embedding.buffer, e.embedding.byteOffset, e.embedding.byteLength / 4));
768
+ });
769
+
770
+ for (let i = 0; i < topNodes.length; i++) {
771
+ for (let j = i + 1; j < topNodes.length; j++) {
772
+ const vecA = embeddingMap.get(topNodes[i].id);
773
+ const vecB = embeddingMap.get(topNodes[j].id);
774
+
775
+ if (vecA && vecB) {
776
+ const similarity = this.vectorStore.cosineSimilarity(vecA, vecB);
777
+ if (similarity >= threshold) {
778
+ edges.push({
779
+ source: topNodes[i].id,
780
+ target: topNodes[j].id,
781
+ similarity
782
+ });
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ this.sendResponse(res, 200, { nodes: topNodes, edges });
789
+ } catch (error) {
790
+ this.sendError(res, 500, error.message);
791
+ }
792
+ }
793
+
794
+ async handleGetStatus(req, res) {
795
+ const bundles = this.configManager.getBundles();
796
+ const bundleStats = Array.from(bundles.entries()).map(([name, bundle]) => ({
797
+ name,
798
+ fileCount: bundle.files.length,
799
+ size: bundle.size,
800
+ changed: bundle.changed,
801
+ generated: bundle.generated
802
+ }));
803
+
804
+ const status = {
805
+ uptime: process.uptime(),
806
+ memory: process.memoryUsage(),
807
+ bundles: bundleStats,
808
+ scanning: this.bundleManager._isScanning || false,
809
+ totalFiles: this.fileSystemManager.getAllFiles().length,
810
+ mcp: {
811
+ enabled: this.mcpServerStarted || false,
812
+ available: true
813
+ }
814
+ };
815
+
816
+ res.writeHead(200, { 'Content-Type': 'application/json' });
817
+ res.end(JSON.stringify(status));
818
+ }
819
+
820
+ // === Vector Database Operations ===
821
+
822
+ async handlePostVectorDbSearch(req, res) {
823
+ try {
824
+ const body = await this.getRequestBody(req);
825
+ const { query, limit = 10 } = JSON.parse(body);
826
+
827
+ // Initialize vector store if needed
828
+ if (!this.vectorStore.initialized) {
829
+ await this.vectorStore.init();
830
+ }
831
+
832
+ const results = await this.vectorStore.search(query, { limit });
833
+ this.sendResponse(res, 200, results);
834
+ } catch (error) {
835
+ this.sendError(res, 500, error.message);
836
+ }
837
+ }
838
+
839
+ async handleGetVectorDbStatus(req, res) {
840
+ try {
841
+ const info = this.configManager.dbManager.getInfo();
842
+ this.sendResponse(res, 200, {
843
+ stats: {
844
+ totalChunks: info.chunkCount,
845
+ embeddingCount: info.embeddingCount,
846
+ modelName: this.vectorStore.modelName
847
+ }
848
+ });
849
+ } catch (error) {
850
+ this.sendError(res, 500, error.message);
851
+ }
852
+ }
853
+
854
+ async handlePostVectorDbRebuild(req, res) {
855
+ try {
856
+ console.log('🔄 Rebuilding vector database...');
857
+ // 1. Get all chunks from SQLite
858
+ const chunks = this.configManager.dbManager.db.prepare('SELECT * FROM semantic_chunks').all()
859
+ .map(row => this.configManager.dbManager.mapChunkRow(row));
860
+
861
+ // 2. Generate/Persist embeddings for every chunk
862
+ for (const chunk of chunks) {
863
+ await this.vectorStore.upsertChunk(chunk);
864
+ }
865
+
866
+ const info = this.configManager.dbManager.getInfo();
867
+ this.sendResponse(res, 200, {
868
+ success: true,
869
+ embeddingCount: info.embeddingCount
870
+ });
871
+ } catch (error) {
872
+ this.sendError(res, 500, error.message);
873
+ }
874
+ }
875
+
876
+ async handlePostVectorDbSearchByType(req, res) {
877
+ try {
878
+ const body = await this.getRequestBody(req);
879
+ const { type, limit = 10 } = JSON.parse(body);
880
+
881
+ // Initialize vector store if needed
882
+ if (!this.vectorStore.embedder) {
883
+ await this.vectorStore.init();
884
+ }
885
+
886
+ const results = await this.vectorStore.findByType(type, limit);
887
+ this.sendResponse(res, 200, results);
888
+ } catch (error) {
889
+ this.sendError(res, 500, error.message);
890
+ }
891
+ }
892
+
893
+ async handlePostVectorDbSearchByDomain(req, res) {
894
+ try {
895
+ const body = await this.getRequestBody(req);
896
+ const { domain, limit = 10 } = JSON.parse(body);
897
+
898
+ // Initialize vector store if needed
899
+ if (!this.vectorStore.embedder) {
900
+ await this.vectorStore.init();
901
+ }
902
+
903
+ const results = await this.vectorStore.findByDomain(domain, limit);
904
+ this.sendResponse(res, 200, results);
905
+ } catch (error) {
906
+ this.sendError(res, 500, error.message);
907
+ }
908
+ }
909
+
910
+ // === Activities ===
911
+
912
+ async handleGetActivities(req, res) {
913
+ try {
914
+ const activities = await this.activityManager.loadActivities();
915
+ res.writeHead(200, { 'Content-Type': 'application/json' });
916
+ res.end(JSON.stringify(activities));
917
+ } catch (error) {
918
+ res.writeHead(500, { 'Content-Type': 'application/json' });
919
+ res.end(JSON.stringify({ error: error.message }));
920
+ }
921
+ }
922
+
923
+ async handleGetActivityReasoning(req, res, activityId) {
924
+ try {
925
+ const history = this.configManager.dbManager.getSessionHistory(activityId);
926
+ res.writeHead(200, { 'Content-Type': 'application/json' });
927
+ res.end(JSON.stringify({ history }));
928
+ } catch (error) {
929
+ res.writeHead(500, { 'Content-Type': 'application/json' });
930
+ res.end(JSON.stringify({ error: error.message }));
931
+ }
932
+ }
933
+
934
+ async handlePostActivityExecute(req, res, activityId) {
935
+ try {
936
+ const result = await this.activityManager.executeActivity(activityId);
937
+ res.writeHead(200, { 'Content-Type': 'application/json' });
938
+ res.end(JSON.stringify(result));
939
+ } catch (error) {
940
+ res.writeHead(500, { 'Content-Type': 'application/json' });
941
+ res.end(JSON.stringify({ error: error.message }));
942
+ }
943
+ }
944
+
945
+ async handlePostActivityStop(req, res, activityId) {
946
+ try {
947
+ const result = await this.activityManager.stopActivity(activityId);
948
+ res.writeHead(200, { 'Content-Type': 'application/json' });
949
+ res.end(JSON.stringify(result));
950
+ } catch (error) {
951
+ res.writeHead(500, { 'Content-Type': 'application/json' });
952
+ res.end(JSON.stringify({ error: error.message }));
953
+ }
954
+ }
955
+
956
+ async handleOpenFile(req, res) {
957
+ try {
958
+ const body = await this.getRequestBody(req);
959
+ const { filePath, line, column } = JSON.parse(body);
960
+
961
+ if (!filePath) {
962
+ return this.sendError(res, 400, 'Missing filePath parameter');
963
+ }
964
+
965
+ const { spawn } = await import('child_process');
966
+ const path = await import('path');
967
+ const fs = await import('fs');
968
+
969
+ const fullPath = path.resolve(this.configManager.CWD, filePath);
970
+
971
+ if (!fs.existsSync(fullPath)) {
972
+ return this.sendError(res, 404, 'File not found');
973
+ }
974
+
975
+ let editorCommand = this.configManager.getEditor();
976
+ let command, args;
977
+
978
+ // Helper to add line/column if supported
979
+ const addLineColumn = (file, line, column) => {
980
+ if (line && column) return `${file}:${line}:${column}`;
981
+ if (line) return `${file}:${line}`;
982
+ return file;
983
+ };
984
+
985
+ if (editorCommand === 'system') {
986
+ switch (process.platform) {
987
+ case 'darwin':
988
+ command = 'open';
989
+ args = [fullPath];
990
+ break;
991
+ case 'win32':
992
+ command = 'start';
993
+ args = [fullPath];
994
+ break;
995
+ default:
996
+ command = 'xdg-open';
997
+ args = [fullPath];
998
+ break;
999
+ }
1000
+ } else if (editorCommand.startsWith('code')) {
1001
+ // VS Code: try direct file:line format
1002
+ command = 'code';
1003
+ if (line) {
1004
+ args = [`${fullPath}:${line}`];
1005
+ } else {
1006
+ args = [fullPath];
1007
+ }
1008
+ } else if (editorCommand.startsWith('subl')) {
1009
+ // Sublime Text supports file:line[:column]
1010
+ command = 'subl';
1011
+ args = [addLineColumn(fullPath, line, column)];
1012
+ } else {
1013
+ // Custom editor: just append file path, ignore line/column
1014
+ command = editorCommand.split(' ')[0];
1015
+ args = [...editorCommand.split(' ').slice(1), fullPath];
1016
+ }
1017
+
1018
+ const child = spawn(command, args, {
1019
+ detached: true,
1020
+ stdio: 'ignore',
1021
+ shell: false
1022
+ });
1023
+
1024
+ child.unref();
1025
+
1026
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1027
+ res.end(JSON.stringify({ success: true, message: `File opened with ${command}` }));
1028
+
1029
+ } catch (error) {
1030
+ console.error('Failed to open file:', error);
1031
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1032
+ res.end(JSON.stringify({
1033
+ error: 'Failed to open file in editor. Please check your editor configuration.',
1034
+ details: error.message
1035
+ }));
1036
+ }
1037
+ }
1038
+
1039
+ // === Utility Methods ===
1040
+
1041
+ sendResponse(res, statusCode, data, contentType = 'application/json') {
1042
+ res.setHeader('Content-Type', contentType);
1043
+ res.statusCode = statusCode;
1044
+
1045
+ if (typeof data === 'string') {
1046
+ res.end(data);
1047
+ } else {
1048
+ res.end(JSON.stringify(data));
1049
+ }
1050
+ }
1051
+
1052
+ sendError(res, statusCode, message) {
1053
+ this.sendResponse(res, statusCode, { error: message });
1054
+ }
1055
+
1056
+ async getRequestBody(req) {
1057
+ return new Promise((resolve, reject) => {
1058
+ let body = '';
1059
+ req.on('data', chunk => {
1060
+ body += chunk.toString();
1061
+ });
1062
+ req.on('end', () => {
1063
+ resolve(body);
1064
+ });
1065
+ req.on('error', reject);
1066
+ });
1067
+ }
1068
+
1069
+ async handleGetBundleSyncStatus(req, res) {
1070
+ const bundleStatesPath = path.join(this.configManager.CNTX_DIR, 'bundle-states.json');
1071
+
1072
+ // Since bundles are now only stored in bundle-states.json, we just check if the file exists and is valid
1073
+ let bundleStates = [];
1074
+ let bundleStatesExists = fs.existsSync(bundleStatesPath);
1075
+
1076
+ if (bundleStatesExists) {
1077
+ try {
1078
+ bundleStates = JSON.parse(fs.readFileSync(bundleStatesPath, 'utf8'));
1079
+ } catch (error) {
1080
+ bundleStatesExists = false;
1081
+ }
1082
+ }
1083
+
1084
+ const details = {
1085
+ inSync: bundleStatesExists && bundleStates.length > 0,
1086
+ bundleCount: bundleStates.length,
1087
+ bundleNames: bundleStates.map(b => b.name),
1088
+ hasValidBundleFile: bundleStatesExists,
1089
+ message: bundleStatesExists
1090
+ ? `Found ${bundleStates.length} bundles in bundle-states.json`
1091
+ : 'bundle-states.json file not found or invalid'
1092
+ };
1093
+
1094
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1095
+ res.end(JSON.stringify(details));
1096
+ }
1097
+
1098
+ async handlePostBundleSyncStatus(req, res) {
1099
+ const bundleStatesPath = path.join(this.configManager.CNTX_DIR, 'bundle-states.json');
1100
+
1101
+ if (!fs.existsSync(bundleStatesPath)) {
1102
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1103
+ res.end(JSON.stringify({ message: 'bundle-states.json not found' }));
1104
+ return;
1105
+ }
1106
+
1107
+ try {
1108
+ // Validate and reload bundle states to ensure they're in sync with the file
1109
+ const bundleStates = JSON.parse(fs.readFileSync(bundleStatesPath, 'utf8'));
1110
+
1111
+ // Reload bundle states in configuration manager
1112
+ this.configManager.loadBundleStates();
1113
+
1114
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1115
+ res.end(JSON.stringify({
1116
+ message: 'Bundle states reloaded successfully',
1117
+ bundleCount: bundleStates.length,
1118
+ bundles: bundleStates.map(b => b.name)
1119
+ }));
1120
+ } catch (error) {
1121
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1122
+ res.end(JSON.stringify({
1123
+ message: 'Failed to reload bundle states',
1124
+ error: error.message
1125
+ }));
1126
+ }
1127
+ }
1128
+
1129
+ // === Database API Handlers ===
1130
+
1131
+ async handleGetDatabaseInfo(req, res) {
1132
+ try {
1133
+ const info = this.configManager.dbManager.getInfo();
1134
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1135
+ res.end(JSON.stringify(info));
1136
+ } catch (error) {
1137
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1138
+ res.end(JSON.stringify({ error: error.message }));
1139
+ }
1140
+ }
1141
+
1142
+ async handlePostDatabaseQuery(req, res) {
1143
+ try {
1144
+ const body = await this.getRequestBody(req);
1145
+ const { query } = JSON.parse(body);
1146
+
1147
+ if (!query || typeof query !== 'string') {
1148
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1149
+ res.end(JSON.stringify({ error: 'Query is required' }));
1150
+ return;
1151
+ }
1152
+
1153
+ const results = this.configManager.dbManager.query(query);
1154
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1155
+ res.end(JSON.stringify({ results }));
1156
+ } catch (error) {
1157
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1158
+ res.end(JSON.stringify({ error: error.message }));
1159
+ }
1160
+ }
1161
+ }