cntx-ui 2.0.15 → 3.0.1

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 (32) hide show
  1. package/README.md +40 -344
  2. package/bin/cntx-ui-mcp.sh +3 -0
  3. package/bin/cntx-ui.js +2 -1
  4. package/lib/agent-runtime.js +161 -1340
  5. package/lib/agent-tools.js +9 -7
  6. package/lib/api-router.js +262 -79
  7. package/lib/bundle-manager.js +172 -407
  8. package/lib/configuration-manager.js +94 -59
  9. package/lib/database-manager.js +397 -0
  10. package/lib/file-system-manager.js +17 -0
  11. package/lib/heuristics-manager.js +119 -17
  12. package/lib/mcp-server.js +125 -55
  13. package/lib/semantic-splitter.js +222 -481
  14. package/lib/simple-vector-store.js +69 -300
  15. package/package.json +18 -31
  16. package/server.js +151 -73
  17. package/templates/TOOLS.md +41 -0
  18. package/templates/activities/activities/create-project-bundles/README.md +4 -3
  19. package/templates/activities/activities/create-project-bundles/notes.md +15 -19
  20. package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
  21. package/templates/activities/activities.json +1 -1
  22. package/templates/agent-config.yaml +0 -13
  23. package/templates/agent-instructions.md +22 -6
  24. package/templates/agent-rules/capabilities/bundle-system.md +1 -1
  25. package/templates/agent-rules/project-specific/architecture.md +1 -1
  26. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  27. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  28. package/web/dist/index.html +2 -2
  29. package/mcp-config-example.json +0 -9
  30. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
  31. package/web/dist/assets/index-dF3qg-y_.js +0 -2486
  32. package/web/dist/assets/index-h5FGSg_P.css +0 -1
@@ -3,8 +3,10 @@
3
3
  * Built on top of existing cntx-ui infrastructure
4
4
  */
5
5
 
6
- import { readFileSync, existsSync } from 'fs';
7
- import { join, relative } from 'path';
6
+ import { readFileSync, existsSync, statSync } from 'fs';
7
+ import { join, relative, extname } from 'path';
8
+ import fs from 'fs';
9
+ import path from 'path';
8
10
  import { exec } from 'child_process';
9
11
  import { promisify } from 'util';
10
12
 
@@ -106,8 +108,8 @@ export class AgentTools {
106
108
  // Apply semantic search if vector store is available
107
109
  if (this.cntxServer.vectorStoreInitialized) {
108
110
  try {
109
- const searchResults = await this.cntxServer.vectorStore.search(query, maxResults * 2);
110
- const chunkIds = searchResults.map(r => r.metadata?.chunkId).filter(Boolean);
111
+ const searchResults = await this.cntxServer.vectorStore.search(query, { limit: maxResults * 2 });
112
+ const chunkIds = searchResults.map(r => r.id || r.chunkId).filter(Boolean);
111
113
  chunks = chunks.filter(c => chunkIds.includes(c.id));
112
114
  } catch (error) {
113
115
  // Fall back to text-based search
@@ -318,7 +320,7 @@ export class AgentTools {
318
320
  getFileSize(filePath) {
319
321
  try {
320
322
  const fullPath = join(this.cntxServer.CWD, filePath);
321
- const stats = require('fs').statSync(fullPath);
323
+ const stats = statSync(fullPath);
322
324
  return stats.size;
323
325
  } catch {
324
326
  return 0;
@@ -326,7 +328,7 @@ export class AgentTools {
326
328
  }
327
329
 
328
330
  getFileType(filePath) {
329
- const ext = require('path').extname(filePath).toLowerCase();
331
+ const ext = extname(filePath).toLowerCase();
330
332
  const typeMap = {
331
333
  '.js': 'javascript',
332
334
  '.jsx': 'javascript',
@@ -345,7 +347,7 @@ export class AgentTools {
345
347
  }
346
348
 
347
349
  getMimeType(filePath) {
348
- const ext = require('path').extname(filePath).toLowerCase();
350
+ const ext = path.extname(filePath).toLowerCase();
349
351
  const mimeTypes = {
350
352
  '.js': 'application/javascript',
351
353
  '.jsx': 'application/javascript',
package/lib/api-router.js CHANGED
@@ -4,9 +4,12 @@
4
4
  */
5
5
 
6
6
  import { parse } from 'url';
7
+ import fs from 'fs';
8
+ import path from 'path';
7
9
 
8
10
  export default class APIRouter {
9
- constructor(configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore, activityManager) {
11
+ constructor(cntxServer, configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore, activityManager) {
12
+ this.cntxServer = cntxServer;
10
13
  this.configManager = configManager;
11
14
  this.bundleManager = bundleManager;
12
15
  this.fileSystemManager = fileSystemManager;
@@ -19,6 +22,12 @@ export default class APIRouter {
19
22
  const method = req.method;
20
23
  const pathname = url.pathname;
21
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
+
22
31
  try {
23
32
  // Route to appropriate handler
24
33
  if (pathname === '/api/bundles' && method === 'GET') {
@@ -164,6 +173,15 @@ export default class APIRouter {
164
173
  return await this.handlePostVectorDbSearch(req, res);
165
174
  }
166
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
+
167
185
  if (pathname === '/api/vector-db/search-by-type' && method === 'POST') {
168
186
  return await this.handlePostVectorDbSearchByType(req, res);
169
187
  }
@@ -176,6 +194,11 @@ export default class APIRouter {
176
194
  return await this.handleGetActivities(req, res);
177
195
  }
178
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
+
179
202
  if (pathname.startsWith('/api/activities/') && pathname.endsWith('/execute') && method === 'POST') {
180
203
  const activityId = pathname.split('/')[3];
181
204
  return await this.handlePostActivityExecute(req, res, activityId);
@@ -190,6 +213,22 @@ export default class APIRouter {
190
213
  return await this.handleOpenFile(req, res);
191
214
  }
192
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
+
193
232
  // If no route matches, return 404
194
233
  res.writeHead(404, { 'Content-Type': 'application/json' });
195
234
  res.end(JSON.stringify({ error: 'API endpoint not found' }));
@@ -204,20 +243,13 @@ export default class APIRouter {
204
243
  // === Bundle Operations ===
205
244
 
206
245
  async handleGetBundles(req, res, url) {
207
- const bundles = this.configManager.getBundles();
208
- const bundleData = Array.from(bundles.entries()).map(([name, bundle]) => ({
209
- name,
210
- changed: bundle.changed,
211
- fileCount: bundle.files.length,
212
- contentPreview: bundle.content.substring(0, 200) + (bundle.content.length > 200 ? '...' : ''),
213
- files: bundle.files,
214
- patterns: bundle.patterns,
215
- size: bundle.size,
216
- generated: bundle.generated
217
- }));
218
-
219
- res.writeHead(200, { 'Content-Type': 'application/json' });
220
- res.end(JSON.stringify(bundleData));
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
+ }
221
253
  }
222
254
 
223
255
  async handlePostBundles(req, res) {
@@ -243,7 +275,7 @@ export default class APIRouter {
243
275
  }
244
276
  // Ensure we're working with relative paths
245
277
  const relativeAddFileName = fileName.startsWith('/') ?
246
- require('path').relative(this.configManager.CWD, fileName) : fileName;
278
+ path.relative(this.configManager.CWD, fileName) : fileName;
247
279
  if (!bundle.files.includes(relativeAddFileName)) {
248
280
  bundle.files.push(relativeAddFileName);
249
281
  bundle.changed = true;
@@ -257,7 +289,7 @@ export default class APIRouter {
257
289
  }
258
290
  // Ensure we're working with relative paths for both search and removal
259
291
  const relativeRemoveFileName = fileName.startsWith('/') ?
260
- require('path').relative(this.configManager.CWD, fileName) : fileName;
292
+ path.relative(this.configManager.CWD, fileName) : fileName;
261
293
  const removeIndex = bundle.files.indexOf(relativeRemoveFileName);
262
294
  if (removeIndex > -1) {
263
295
  bundle.files.splice(removeIndex, 1);
@@ -273,7 +305,7 @@ export default class APIRouter {
273
305
  fileNames.forEach(file => {
274
306
  // Ensure we're working with relative paths
275
307
  const relativeFile = file.startsWith('/') ?
276
- require('path').relative(this.configManager.CWD, file) : file;
308
+ path.relative(this.configManager.CWD, file) : file;
277
309
  if (!bundle.files.includes(relativeFile)) {
278
310
  bundle.files.push(relativeFile);
279
311
  }
@@ -289,7 +321,7 @@ export default class APIRouter {
289
321
  fileNames.forEach(file => {
290
322
  // Ensure we're working with relative paths
291
323
  const relativeFile = file.startsWith('/') ?
292
- require('path').relative(this.configManager.CWD, file) : file;
324
+ path.relative(this.configManager.CWD, file) : file;
293
325
  const index = bundle.files.indexOf(relativeFile);
294
326
  if (index > -1) {
295
327
  bundle.files.splice(index, 1);
@@ -378,9 +410,8 @@ export default class APIRouter {
378
410
  editor: this.configManager.getEditor()
379
411
  };
380
412
 
381
- bundles.forEach((bundle, name) => {
382
- config.bundles[name] = bundle.patterns;
383
- });
413
+ // Note: bundles are now managed separately in bundle-states.json
414
+ // config.json only contains non-bundle settings
384
415
 
385
416
  res.writeHead(200, { 'Content-Type': 'application/json' });
386
417
  res.end(JSON.stringify(config));
@@ -570,12 +601,14 @@ export default class APIRouter {
570
601
  const { content } = JSON.parse(body);
571
602
 
572
603
  // Save content and reload patterns
573
- this.configManager.saveIgnoreFile ? this.configManager.saveIgnoreFile(content) : null;
574
- this.configManager.loadIgnorePatterns();
575
- this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
604
+ const success = this.configManager.saveCntxignore(content);
605
+ if (!success) {
606
+ return this.sendError(res, 500, 'Failed to save .cntxignore');
607
+ }
576
608
 
577
- res.writeHead(200, { 'Content-Type': 'application/json' });
578
- res.end(JSON.stringify({ success: true }));
609
+ this.fileSystemManager.setIgnorePatterns(this.configManager.ignorePatterns);
610
+
611
+ this.sendResponse(res, 200, { success: true });
579
612
  }
580
613
 
581
614
  async handleGetGitignore(req, res) {
@@ -624,37 +657,20 @@ export default class APIRouter {
624
657
  name: chunk.name,
625
658
  code: chunk.code,
626
659
  semanticType: chunk.subtype || chunk.type || 'unknown',
627
- businessDomain: chunk.tags || [],
628
- technicalPatterns: chunk.tags || [],
660
+ businessDomain: chunk.businessDomain || [],
661
+ technicalPatterns: chunk.technicalPatterns || [],
629
662
  purpose: chunk.purpose || '',
630
663
  filePath: chunk.filePath,
631
664
  files: chunk.filePath ? [chunk.filePath] : [],
632
665
  size: chunk.size || 0,
633
666
  complexity: chunk.complexity || 0,
667
+ tags: chunk.tags || [],
634
668
  startLine: chunk.startLine,
635
669
  isExported: chunk.isExported,
636
670
  isAsync: chunk.isAsync,
637
- bundles: chunk.bundles || [],
638
- embedding: chunk.embedding,
639
- // Also include nested metadata format that VectorVisualization expects
640
- metadata: {
641
- content: chunk.code || '',
642
- semanticType: chunk.subtype || chunk.type || 'unknown',
643
- businessDomain: chunk.tags || [],
644
- technicalPatterns: chunk.tags || [],
645
- purpose: chunk.purpose || '',
646
- files: chunk.filePath ? [chunk.filePath] : [],
647
- size: chunk.size || 0,
648
- complexity: chunk.complexity || 0
649
- }
671
+ bundles: chunk.bundles || []
650
672
  }));
651
673
 
652
- // console.log('📊 Transformed chunks sample:', chunks[0] ? {
653
- // id: chunks[0].id,
654
- // semanticType: chunks[0].semanticType,
655
- // hasMetadata: !!chunks[0].metadata
656
- // } : 'No chunks');
657
-
658
674
  this.sendResponse(res, 200, {
659
675
  summary: {
660
676
  totalFiles: analysis?.summary?.totalFiles || analysis?.fileCount || 0,
@@ -703,17 +719,78 @@ export default class APIRouter {
703
719
  }));
704
720
  }
705
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
+
706
738
  async handleGetMcpStatus(req, res) {
739
+ const isRunning = this.cntxServer.mcpServerStarted || false;
707
740
  const status = {
708
- enabled: this.mcpServerStarted || false,
741
+ enabled: isRunning,
742
+ running: isRunning,
709
743
  available: true,
710
- message: 'MCP server integration available'
744
+ message: isRunning ? 'MCP server is running' : 'MCP server integration available'
711
745
  };
712
746
 
713
747
  res.writeHead(200, { 'Content-Type': 'application/json' });
714
748
  res.end(JSON.stringify(status));
715
749
  }
716
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
+
717
794
  async handleGetStatus(req, res) {
718
795
  const bundles = this.configManager.getBundles();
719
796
  const bundleStats = Array.from(bundles.entries()).map(([name, bundle]) => ({
@@ -742,50 +819,55 @@ export default class APIRouter {
742
819
 
743
820
  // === Vector Database Operations ===
744
821
 
745
- async handleGetVectorDbStatus(req, res) {
822
+ async handlePostVectorDbSearch(req, res) {
746
823
  try {
824
+ const body = await this.getRequestBody(req);
825
+ const { query, limit = 10 } = JSON.parse(body);
826
+
747
827
  // Initialize vector store if needed
748
- if (!this.vectorStore.embedder) {
828
+ if (!this.vectorStore.initialized) {
749
829
  await this.vectorStore.init();
750
830
  }
751
831
 
752
- const stats = await this.vectorStore.getStats();
753
- this.sendResponse(res, 200, { stats });
832
+ const results = await this.vectorStore.search(query, { limit });
833
+ this.sendResponse(res, 200, results);
754
834
  } catch (error) {
755
835
  this.sendError(res, 500, error.message);
756
836
  }
757
837
  }
758
838
 
759
- async handlePostVectorDbRebuild(req, res) {
839
+ async handleGetVectorDbStatus(req, res) {
760
840
  try {
761
- await this.vectorStore.clear();
762
- const analysis = await this.semanticAnalysisManager.getSemanticAnalysis();
763
-
764
- if (analysis && analysis.chunks) {
765
- await this.vectorStore.storePrecomputedChunks(analysis.chunks);
766
- }
767
-
768
- const stats = await this.vectorStore.getStats();
769
- res.writeHead(200, { 'Content-Type': 'application/json' });
770
- res.end(JSON.stringify({ success: true, stats }));
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
+ });
771
849
  } catch (error) {
772
- res.writeHead(500, { 'Content-Type': 'application/json' });
773
- res.end(JSON.stringify({ error: error.message }));
850
+ this.sendError(res, 500, error.message);
774
851
  }
775
852
  }
776
853
 
777
- async handlePostVectorDbSearch(req, res) {
854
+ async handlePostVectorDbRebuild(req, res) {
778
855
  try {
779
- const body = await this.getRequestBody(req);
780
- const { query, limit = 10 } = JSON.parse(body);
781
-
782
- // Initialize vector store if needed
783
- if (!this.vectorStore.embedder) {
784
- await this.vectorStore.init();
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);
785
864
  }
786
865
 
787
- const results = await this.vectorStore.findSimilar(query, { limit });
788
- this.sendResponse(res, 200, results);
866
+ const info = this.configManager.dbManager.getInfo();
867
+ this.sendResponse(res, 200, {
868
+ success: true,
869
+ embeddingCount: info.embeddingCount
870
+ });
789
871
  } catch (error) {
790
872
  this.sendError(res, 500, error.message);
791
873
  }
@@ -829,13 +911,21 @@ export default class APIRouter {
829
911
 
830
912
  async handleGetActivities(req, res) {
831
913
  try {
832
- console.log('API: /api/activities called');
833
914
  const activities = await this.activityManager.loadActivities();
834
- console.log('API: Loaded activities:', activities.length);
835
915
  res.writeHead(200, { 'Content-Type': 'application/json' });
836
916
  res.end(JSON.stringify(activities));
837
917
  } catch (error) {
838
- console.log('API: Error loading activities:', error.message);
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) {
839
929
  res.writeHead(500, { 'Content-Type': 'application/json' });
840
930
  res.end(JSON.stringify({ error: error.message }));
841
931
  }
@@ -975,4 +1065,97 @@ export default class APIRouter {
975
1065
  req.on('error', reject);
976
1066
  });
977
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
+ }
978
1161
  }