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