cntx-ui 3.1.3 ā 3.1.6
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 +7 -2
- package/dist/lib/api-router.js +106 -2
- package/dist/lib/database-manager.js +48 -0
- package/dist/lib/mcp-server.js +4 -2
- package/dist/lib/semantic-splitter.js +45 -32
- package/dist/lib/simple-vector-store.js +12 -2
- package/dist/server.js +55 -23
- package/package.json +2 -1
- package/web/dist/assets/index-CZ-DWr8c.css +1 -0
- package/web/dist/assets/index-DAunWqbX.js +344 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-8QXMnXVq.js +0 -1968
- package/web/dist/assets/index-DJi03HLz.css +0 -1
package/dist/bin/cntx-ui.js
CHANGED
|
@@ -14,7 +14,12 @@ const command = args[0] || 'help';
|
|
|
14
14
|
const isVerbose = args.includes('--verbose');
|
|
15
15
|
// Graceful shutdown
|
|
16
16
|
process.on('SIGINT', () => {
|
|
17
|
-
|
|
17
|
+
if (command === 'mcp') {
|
|
18
|
+
process.stderr.write('\nš Shutting down cntx-ui...\n');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log('\nš Shutting down cntx-ui...');
|
|
22
|
+
}
|
|
18
23
|
process.exit(0);
|
|
19
24
|
});
|
|
20
25
|
async function main() {
|
|
@@ -32,7 +37,7 @@ async function main() {
|
|
|
32
37
|
await initConfig();
|
|
33
38
|
break;
|
|
34
39
|
case 'mcp':
|
|
35
|
-
await startServer({ withMcp: true, skipFileWatcher: true });
|
|
40
|
+
await startServer({ withMcp: true, skipFileWatcher: true, isMcp: true });
|
|
36
41
|
break;
|
|
37
42
|
case 'bundle':
|
|
38
43
|
const bundleName = args[1] || 'master';
|
package/dist/lib/api-router.js
CHANGED
|
@@ -81,6 +81,9 @@ export default class APIRouter {
|
|
|
81
81
|
if (pathname === '/api/vector-db/search' && method === 'POST') {
|
|
82
82
|
return await this.handlePostVectorDbSearch(req, res);
|
|
83
83
|
}
|
|
84
|
+
if (pathname === '/api/vector-db/projection' && method === 'GET') {
|
|
85
|
+
return await this.handleGetVectorDbProjection(req, res);
|
|
86
|
+
}
|
|
84
87
|
if (pathname === '/api/vector-db/network' && method === 'GET') {
|
|
85
88
|
return await this.handleGetVectorDbNetwork(req, res);
|
|
86
89
|
}
|
|
@@ -283,8 +286,12 @@ export default class APIRouter {
|
|
|
283
286
|
}
|
|
284
287
|
async handlePostSemanticSearch(req, res) {
|
|
285
288
|
const body = await this.getRequestBody(req);
|
|
286
|
-
const { query, limit = 20 } = JSON.parse(body);
|
|
287
|
-
const
|
|
289
|
+
const { query, question, limit = 20 } = JSON.parse(body);
|
|
290
|
+
const searchTerm = query || question;
|
|
291
|
+
if (!searchTerm) {
|
|
292
|
+
return this.sendError(res, 400, 'Missing search term (query or question)');
|
|
293
|
+
}
|
|
294
|
+
const results = await this.vectorStore.search(searchTerm, { limit });
|
|
288
295
|
this.sendResponse(res, 200, { results });
|
|
289
296
|
}
|
|
290
297
|
async handleGetVectorDbStatus(req, res) {
|
|
@@ -311,6 +318,103 @@ export default class APIRouter {
|
|
|
311
318
|
const results = await this.vectorStore.search(query, { limit });
|
|
312
319
|
this.sendResponse(res, 200, results);
|
|
313
320
|
}
|
|
321
|
+
async handleGetVectorDbProjection(req, res) {
|
|
322
|
+
const dbManager = this.configManager.dbManager;
|
|
323
|
+
const embeddingCountRow = dbManager.db.prepare('SELECT COUNT(*) as count FROM vector_embeddings').get();
|
|
324
|
+
const currentEmbeddingCount = embeddingCountRow.count;
|
|
325
|
+
if (currentEmbeddingCount === 0) {
|
|
326
|
+
return this.sendResponse(res, 200, {
|
|
327
|
+
points: [],
|
|
328
|
+
meta: { totalPoints: 0, embeddingCount: 0, computedAt: null, cached: false }
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// Check cache freshness
|
|
332
|
+
const cachedCount = dbManager.getProjectionEmbeddingCount();
|
|
333
|
+
if (cachedCount === currentEmbeddingCount) {
|
|
334
|
+
const cached = dbManager.getProjections();
|
|
335
|
+
if (cached) {
|
|
336
|
+
// Join with chunk metadata
|
|
337
|
+
const chunkMap = new Map();
|
|
338
|
+
const chunks = dbManager.db.prepare('SELECT * FROM semantic_chunks').all();
|
|
339
|
+
for (const c of chunks) {
|
|
340
|
+
const mapped = dbManager.mapChunkRow(c);
|
|
341
|
+
chunkMap.set(mapped.id, mapped);
|
|
342
|
+
}
|
|
343
|
+
const points = cached.map(p => {
|
|
344
|
+
const chunk = chunkMap.get(p.chunkId);
|
|
345
|
+
return {
|
|
346
|
+
id: p.chunkId,
|
|
347
|
+
x: p.x,
|
|
348
|
+
y: p.y,
|
|
349
|
+
name: chunk?.name || 'unknown',
|
|
350
|
+
filePath: chunk?.filePath || '',
|
|
351
|
+
purpose: chunk?.purpose || 'unknown',
|
|
352
|
+
semanticType: chunk?.subtype || chunk?.type || 'unknown',
|
|
353
|
+
complexity: chunk?.complexity?.score || 0,
|
|
354
|
+
directory: chunk?.filePath ? chunk.filePath.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
return this.sendResponse(res, 200, {
|
|
358
|
+
points,
|
|
359
|
+
meta: { totalPoints: points.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: true }
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Compute fresh UMAP projection
|
|
364
|
+
const rows = dbManager.db.prepare(`
|
|
365
|
+
SELECT ve.chunk_id, ve.embedding, sc.name, sc.file_path, sc.type, sc.subtype, sc.complexity_score, sc.purpose
|
|
366
|
+
FROM vector_embeddings ve
|
|
367
|
+
JOIN semantic_chunks sc ON ve.chunk_id = sc.id
|
|
368
|
+
`).all();
|
|
369
|
+
if (rows.length < 2) {
|
|
370
|
+
return this.sendResponse(res, 200, {
|
|
371
|
+
points: rows.map((r) => ({
|
|
372
|
+
id: r.chunk_id, x: 0, y: 0,
|
|
373
|
+
name: r.name, filePath: r.file_path, purpose: r.purpose || 'unknown',
|
|
374
|
+
semanticType: r.subtype || r.type || 'unknown', complexity: r.complexity_score || 0,
|
|
375
|
+
directory: r.file_path ? r.file_path.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
376
|
+
})),
|
|
377
|
+
meta: { totalPoints: rows.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: false }
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// Extract embeddings as number[][]
|
|
381
|
+
const embeddings = rows.map((r) => {
|
|
382
|
+
const buf = r.embedding;
|
|
383
|
+
const floats = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
384
|
+
return Array.from(floats);
|
|
385
|
+
});
|
|
386
|
+
// Run UMAP
|
|
387
|
+
const { UMAP } = await import('umap-js');
|
|
388
|
+
const umap = new UMAP({
|
|
389
|
+
nNeighbors: Math.min(15, rows.length - 1),
|
|
390
|
+
minDist: 0.1,
|
|
391
|
+
nComponents: 2
|
|
392
|
+
});
|
|
393
|
+
const projected = umap.fit(embeddings);
|
|
394
|
+
// Save to cache
|
|
395
|
+
const projections = rows.map((r, i) => ({
|
|
396
|
+
chunkId: r.chunk_id,
|
|
397
|
+
x: projected[i][0],
|
|
398
|
+
y: projected[i][1]
|
|
399
|
+
}));
|
|
400
|
+
dbManager.saveProjections(projections, currentEmbeddingCount);
|
|
401
|
+
// Build response
|
|
402
|
+
const points = rows.map((r, i) => ({
|
|
403
|
+
id: r.chunk_id,
|
|
404
|
+
x: projected[i][0],
|
|
405
|
+
y: projected[i][1],
|
|
406
|
+
name: r.name,
|
|
407
|
+
filePath: r.file_path,
|
|
408
|
+
purpose: r.purpose || 'unknown',
|
|
409
|
+
semanticType: r.subtype || r.type || 'unknown',
|
|
410
|
+
complexity: r.complexity_score || 0,
|
|
411
|
+
directory: r.file_path ? r.file_path.split('/').slice(0, -1).join('/') || '.' : '.'
|
|
412
|
+
}));
|
|
413
|
+
this.sendResponse(res, 200, {
|
|
414
|
+
points,
|
|
415
|
+
meta: { totalPoints: points.length, embeddingCount: currentEmbeddingCount, computedAt: new Date().toISOString(), cached: false }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
314
418
|
async handleGetVectorDbNetwork(req, res) {
|
|
315
419
|
const chunks = this.configManager.dbManager.db.prepare('SELECT * FROM semantic_chunks').all();
|
|
316
420
|
const embeddings = this.configManager.dbManager.db.prepare('SELECT * FROM vector_embeddings').all();
|
|
@@ -79,6 +79,16 @@ export default class DatabaseManager {
|
|
|
79
79
|
FOREIGN KEY(session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
+
-- UMAP Projection Cache
|
|
83
|
+
CREATE TABLE IF NOT EXISTS umap_projections (
|
|
84
|
+
chunk_id TEXT PRIMARY KEY,
|
|
85
|
+
x REAL NOT NULL,
|
|
86
|
+
y REAL NOT NULL,
|
|
87
|
+
computed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
embedding_count INTEGER NOT NULL,
|
|
89
|
+
FOREIGN KEY(chunk_id) REFERENCES semantic_chunks(id) ON DELETE CASCADE
|
|
90
|
+
);
|
|
91
|
+
|
|
82
92
|
CREATE INDEX IF NOT EXISTS idx_bundles_changed ON bundles(changed);
|
|
83
93
|
CREATE INDEX IF NOT EXISTS idx_chunks_file ON semantic_chunks(file_path);
|
|
84
94
|
CREATE INDEX IF NOT EXISTS idx_chunks_purpose ON semantic_chunks(purpose);
|
|
@@ -265,6 +275,44 @@ export default class DatabaseManager {
|
|
|
265
275
|
return [];
|
|
266
276
|
}
|
|
267
277
|
}
|
|
278
|
+
// UMAP Projection Cache
|
|
279
|
+
saveProjections(projections, embeddingCount) {
|
|
280
|
+
const transaction = this.db.transaction(() => {
|
|
281
|
+
this.db.prepare('DELETE FROM umap_projections').run();
|
|
282
|
+
const stmt = this.db.prepare('INSERT INTO umap_projections (chunk_id, x, y, embedding_count) VALUES (?, ?, ?, ?)');
|
|
283
|
+
for (const p of projections) {
|
|
284
|
+
stmt.run(p.chunkId, p.x, p.y, embeddingCount);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
transaction();
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
console.error('Failed to save projections:', error.message);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
getProjections() {
|
|
297
|
+
try {
|
|
298
|
+
const rows = this.db.prepare('SELECT chunk_id, x, y, embedding_count FROM umap_projections').all();
|
|
299
|
+
if (rows.length === 0)
|
|
300
|
+
return null;
|
|
301
|
+
return rows.map(r => ({ chunkId: r.chunk_id, x: r.x, y: r.y, embeddingCount: r.embedding_count }));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getProjectionEmbeddingCount() {
|
|
308
|
+
try {
|
|
309
|
+
const row = this.db.prepare('SELECT embedding_count FROM umap_projections LIMIT 1').get();
|
|
310
|
+
return row?.embedding_count ?? 0;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
268
316
|
// Close database connection
|
|
269
317
|
close() {
|
|
270
318
|
if (this.db) {
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -6,8 +6,10 @@ import { readFileSync } from 'fs';
|
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
export class MCPServer {
|
|
8
8
|
cntxServer;
|
|
9
|
-
|
|
9
|
+
version;
|
|
10
|
+
constructor(cntxServer, version = '3.0.0') {
|
|
10
11
|
this.cntxServer = cntxServer;
|
|
12
|
+
this.version = version;
|
|
11
13
|
// Listen for MCP requests on stdin
|
|
12
14
|
process.stdin.on('data', (data) => {
|
|
13
15
|
this.handleInput(data.toString());
|
|
@@ -36,7 +38,7 @@ export class MCPServer {
|
|
|
36
38
|
resources: {},
|
|
37
39
|
prompts: {}
|
|
38
40
|
},
|
|
39
|
-
serverInfo: { name: 'cntx-ui', version:
|
|
41
|
+
serverInfo: { name: 'cntx-ui', version: this.version }
|
|
40
42
|
}));
|
|
41
43
|
case 'tools/list':
|
|
42
44
|
return this.sendResponse(this.handleListTools(id));
|
|
@@ -28,6 +28,7 @@ export default class SemanticSplitter {
|
|
|
28
28
|
includeContext: true, // Include imports/types needed
|
|
29
29
|
minFunctionSize: 40, // Skip tiny functions
|
|
30
30
|
minStructureSize: 20, // Skip tiny structures
|
|
31
|
+
verbose: options.verbose || false,
|
|
31
32
|
...options
|
|
32
33
|
};
|
|
33
34
|
// Initialize tree-sitter parsers
|
|
@@ -88,7 +89,10 @@ export default class SemanticSplitter {
|
|
|
88
89
|
allChunks.push(...fileChunks);
|
|
89
90
|
}
|
|
90
91
|
catch (error) {
|
|
91
|
-
console.warn(
|
|
92
|
+
console.warn(`ā ļø Failed to process ${filePath}: ${error.message}`);
|
|
93
|
+
if (this.options.verbose) {
|
|
94
|
+
console.error(error.stack);
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
console.log(`š§© Created ${allChunks.length} semantic chunks across project`);
|
|
@@ -112,39 +116,48 @@ export default class SemanticSplitter {
|
|
|
112
116
|
return [];
|
|
113
117
|
}
|
|
114
118
|
const parser = this.getParser(relativePath);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
119
|
+
try {
|
|
120
|
+
const tree = parser.parse(content);
|
|
121
|
+
const root = tree.rootNode;
|
|
122
|
+
const ext = extname(relativePath).toLowerCase();
|
|
123
|
+
const elements = {
|
|
124
|
+
functions: [],
|
|
125
|
+
types: [],
|
|
126
|
+
imports: []
|
|
127
|
+
};
|
|
128
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.rs'].includes(ext)) {
|
|
129
|
+
elements.imports = this.extractImports(root, content, relativePath);
|
|
130
|
+
// Traverse AST for functions and types
|
|
131
|
+
this.traverse(root, content, relativePath, elements);
|
|
132
|
+
}
|
|
133
|
+
else if (ext === '.json') {
|
|
134
|
+
this.extractJsonStructures(root, content, relativePath, elements);
|
|
135
|
+
}
|
|
136
|
+
else if (ext === '.css' || ext === '.scss') {
|
|
137
|
+
this.extractCssStructures(root, content, relativePath, elements);
|
|
138
|
+
}
|
|
139
|
+
else if (ext === '.html') {
|
|
140
|
+
this.extractHtmlStructures(root, content, relativePath, elements);
|
|
141
|
+
}
|
|
142
|
+
else if (ext === '.sql') {
|
|
143
|
+
this.extractSqlStructures(root, content, relativePath, elements);
|
|
144
|
+
}
|
|
145
|
+
else if (ext === '.md') {
|
|
146
|
+
this.extractMarkdownStructures(root, content, relativePath, elements);
|
|
147
|
+
}
|
|
148
|
+
else if (ext === '.toml') {
|
|
149
|
+
this.extractTomlStructures(root, content, relativePath, elements);
|
|
150
|
+
}
|
|
151
|
+
// Create chunks from elements
|
|
152
|
+
return this.createChunks(elements, content, relativePath);
|
|
142
153
|
}
|
|
143
|
-
|
|
144
|
-
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.warn(`ā ļø Parser failed for ${relativePath}: ${error.message}`);
|
|
156
|
+
if (this.options.verbose) {
|
|
157
|
+
console.error(error.stack);
|
|
158
|
+
}
|
|
159
|
+
return [];
|
|
145
160
|
}
|
|
146
|
-
// Create chunks from elements
|
|
147
|
-
return this.createChunks(elements, content, relativePath);
|
|
148
161
|
}
|
|
149
162
|
traverse(node, content, filePath, elements) {
|
|
150
163
|
// Detect Function Declarations (JS/TS)
|
|
@@ -9,19 +9,29 @@ export default class SimpleVectorStore {
|
|
|
9
9
|
modelName;
|
|
10
10
|
pipe;
|
|
11
11
|
initialized;
|
|
12
|
+
isMcp;
|
|
12
13
|
constructor(databaseManager, options = {}) {
|
|
13
14
|
this.db = databaseManager;
|
|
14
15
|
this.modelName = options.modelName || 'Xenova/all-MiniLM-L6-v2';
|
|
15
16
|
this.pipe = null;
|
|
16
17
|
this.initialized = false;
|
|
18
|
+
this.isMcp = options.isMcp || false;
|
|
19
|
+
}
|
|
20
|
+
log(message) {
|
|
21
|
+
if (this.isMcp) {
|
|
22
|
+
process.stderr.write(message + '\n');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.log(message);
|
|
26
|
+
}
|
|
17
27
|
}
|
|
18
28
|
async init() {
|
|
19
29
|
if (this.initialized)
|
|
20
30
|
return;
|
|
21
|
-
|
|
31
|
+
this.log(`š¤ Initializing local RAG engine (${this.modelName})...`);
|
|
22
32
|
this.pipe = await pipeline('feature-extraction', this.modelName);
|
|
23
33
|
this.initialized = true;
|
|
24
|
-
|
|
34
|
+
this.log('ā
Local RAG engine ready');
|
|
25
35
|
}
|
|
26
36
|
async generateEmbedding(text) {
|
|
27
37
|
await this.init();
|
package/dist/server.js
CHANGED
|
@@ -38,7 +38,9 @@ function getProjectName(cwd) {
|
|
|
38
38
|
export class CntxServer {
|
|
39
39
|
CWD;
|
|
40
40
|
CNTX_DIR;
|
|
41
|
+
version;
|
|
41
42
|
verbose;
|
|
43
|
+
isMcp;
|
|
42
44
|
mcpServerStarted;
|
|
43
45
|
mcpServer;
|
|
44
46
|
initMessages;
|
|
@@ -61,9 +63,24 @@ export class CntxServer {
|
|
|
61
63
|
this.CWD = cwd;
|
|
62
64
|
this.CNTX_DIR = join(cwd, '.cntx');
|
|
63
65
|
this.verbose = options.verbose || false;
|
|
66
|
+
this.isMcp = options.isMcp || false;
|
|
64
67
|
this.mcpServerStarted = false;
|
|
65
68
|
this.mcpServer = null;
|
|
66
69
|
this.initMessages = [];
|
|
70
|
+
// Read package version
|
|
71
|
+
try {
|
|
72
|
+
let pkgDir = __dirname;
|
|
73
|
+
let pkgPath = join(pkgDir, 'package.json');
|
|
74
|
+
if (!existsSync(pkgPath)) {
|
|
75
|
+
pkgDir = join(__dirname, '..');
|
|
76
|
+
pkgPath = join(pkgDir, 'package.json');
|
|
77
|
+
}
|
|
78
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
79
|
+
this.version = pkg.version;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
this.version = '3.1.5';
|
|
83
|
+
}
|
|
67
84
|
// Ensure directory exists
|
|
68
85
|
if (!existsSync(this.CNTX_DIR))
|
|
69
86
|
mkdirSync(this.CNTX_DIR, { recursive: true });
|
|
@@ -78,10 +95,12 @@ export class CntxServer {
|
|
|
78
95
|
this.semanticSplitter = new SemanticSplitter({
|
|
79
96
|
maxChunkSize: 2000,
|
|
80
97
|
includeContext: true,
|
|
81
|
-
minFunctionSize: 50
|
|
98
|
+
minFunctionSize: 50,
|
|
99
|
+
verbose: this.verbose
|
|
82
100
|
});
|
|
83
101
|
this.vectorStore = new SimpleVectorStore(this.databaseManager, {
|
|
84
|
-
modelName: 'Xenova/all-MiniLM-L6-v2'
|
|
102
|
+
modelName: 'Xenova/all-MiniLM-L6-v2',
|
|
103
|
+
isMcp: this.isMcp
|
|
85
104
|
});
|
|
86
105
|
this.semanticCache = null;
|
|
87
106
|
this.lastSemanticAnalysis = null;
|
|
@@ -96,6 +115,14 @@ export class CntxServer {
|
|
|
96
115
|
this.bundleManager.fileSystemManager = this.fileSystemManager;
|
|
97
116
|
this.bundleManager.webSocketManager = this.webSocketManager;
|
|
98
117
|
}
|
|
118
|
+
log(message) {
|
|
119
|
+
if (this.isMcp) {
|
|
120
|
+
process.stderr.write(message + '\n');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
99
126
|
async init(options = {}) {
|
|
100
127
|
const { skipFileWatcher = false, skipBundleGeneration = false } = options;
|
|
101
128
|
// Load configs
|
|
@@ -190,11 +217,15 @@ export class CntxServer {
|
|
|
190
217
|
}
|
|
191
218
|
startMCPServer() {
|
|
192
219
|
if (!this.mcpServer) {
|
|
193
|
-
this.mcpServer = new MCPServer(this);
|
|
220
|
+
this.mcpServer = new MCPServer(this, this.version);
|
|
194
221
|
this.mcpServerStarted = true;
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
async listen(port = 3333, host = 'localhost') {
|
|
225
|
+
if (this.isMcp) {
|
|
226
|
+
this.log('Mode: MCP (stdio) - Skipping HTTP server start');
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
198
229
|
const server = createServer((req, res) => {
|
|
199
230
|
const url = parse(req.url || '/', true);
|
|
200
231
|
// Serve static files from web/dist
|
|
@@ -207,7 +238,7 @@ export class CntxServer {
|
|
|
207
238
|
this.webSocketManager.initialize(server);
|
|
208
239
|
return new Promise((resolve) => {
|
|
209
240
|
server.listen(port, host, () => {
|
|
210
|
-
|
|
241
|
+
this.log(`š cntx-ui server running at http://${host}:${port}`);
|
|
211
242
|
resolve(server);
|
|
212
243
|
});
|
|
213
244
|
});
|
|
@@ -264,7 +295,7 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
264
295
|
// 1. Initialize directory structure
|
|
265
296
|
if (!existsSync(server.CNTX_DIR)) {
|
|
266
297
|
mkdirSync(server.CNTX_DIR, { recursive: true });
|
|
267
|
-
|
|
298
|
+
server.log('š Created .cntx directory');
|
|
268
299
|
}
|
|
269
300
|
// 2. Create .mcp.json for Claude Code discovery
|
|
270
301
|
const mcpConfigPath = join(cwd, '.mcp.json');
|
|
@@ -278,7 +309,7 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
278
309
|
}
|
|
279
310
|
};
|
|
280
311
|
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
|
|
281
|
-
|
|
312
|
+
server.log('š Created .mcp.json for agent auto-discovery');
|
|
282
313
|
// 3. Initialize basic configuration with better defaults and auto-suggestions
|
|
283
314
|
server.configManager.loadConfig();
|
|
284
315
|
const suggestedBundles = {
|
|
@@ -296,7 +327,7 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
296
327
|
commonDirs.forEach(d => {
|
|
297
328
|
if (existsSync(join(cwd, d.dir))) {
|
|
298
329
|
suggestedBundles[d.name] = [`${d.dir}/**`];
|
|
299
|
-
|
|
330
|
+
server.log(`š” Suggested bundle: ${d.name} (${d.dir}/**)`);
|
|
300
331
|
}
|
|
301
332
|
});
|
|
302
333
|
server.configManager.saveConfig({
|
|
@@ -330,9 +361,9 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
330
361
|
.mcp.json
|
|
331
362
|
`;
|
|
332
363
|
writeFileSync(ignorePath, defaultIgnore, 'utf8');
|
|
333
|
-
|
|
364
|
+
server.log('š Created .cntxignore with smart defaults');
|
|
334
365
|
}
|
|
335
|
-
|
|
366
|
+
server.log('āļø Basic configuration initialized');
|
|
336
367
|
let templateDir = join(__dirname, 'templates');
|
|
337
368
|
if (!existsSync(templateDir)) {
|
|
338
369
|
// Fallback for dist/ context
|
|
@@ -356,7 +387,7 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
356
387
|
else {
|
|
357
388
|
copyFileSync(sourcePath, destPath);
|
|
358
389
|
}
|
|
359
|
-
|
|
390
|
+
server.log(`š Created ${file}`);
|
|
360
391
|
}
|
|
361
392
|
}
|
|
362
393
|
// Copy agent-rules directory structure
|
|
@@ -364,7 +395,7 @@ export async function initConfig(cwd = process.cwd()) {
|
|
|
364
395
|
const agentRulesDest = join(server.CNTX_DIR, 'agent-rules');
|
|
365
396
|
if (existsSync(agentRulesSource) && !existsSync(agentRulesDest)) {
|
|
366
397
|
cpSync(agentRulesSource, agentRulesDest, { recursive: true });
|
|
367
|
-
|
|
398
|
+
server.log('š Created agent-rules directory with templates');
|
|
368
399
|
}
|
|
369
400
|
return server.initMessages;
|
|
370
401
|
}
|
|
@@ -387,13 +418,13 @@ export async function getStatus() {
|
|
|
387
418
|
}
|
|
388
419
|
return bundle;
|
|
389
420
|
}));
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
421
|
+
server.log('š cntx-ui Status');
|
|
422
|
+
server.log('================');
|
|
423
|
+
server.log(`Total files: ${totalFiles}`);
|
|
424
|
+
server.log(`Bundles: ${bundlesWithCounts.length}`);
|
|
394
425
|
bundlesWithCounts.forEach(bundle => {
|
|
395
426
|
const sizeStr = bundle.size > 0 ? ` (${Math.round(bundle.size / 1024)}KB)` : '';
|
|
396
|
-
|
|
427
|
+
server.log(` ⢠${bundle.name}: ${bundle.fileCount} files${sizeStr}`);
|
|
397
428
|
});
|
|
398
429
|
return {
|
|
399
430
|
totalFiles,
|
|
@@ -404,9 +435,10 @@ export async function getStatus() {
|
|
|
404
435
|
export function setupMCP() {
|
|
405
436
|
const configPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
406
437
|
const projectPath = process.cwd();
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
438
|
+
const server = new CntxServer(projectPath);
|
|
439
|
+
server.log('š§ Setting up MCP integration...');
|
|
440
|
+
server.log(`Project: ${projectPath}`);
|
|
441
|
+
server.log(`Claude config: ${configPath}`);
|
|
410
442
|
try {
|
|
411
443
|
let config = {};
|
|
412
444
|
if (existsSync(configPath)) {
|
|
@@ -423,19 +455,19 @@ export function setupMCP() {
|
|
|
423
455
|
// Ensure directory exists
|
|
424
456
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
425
457
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
426
|
-
|
|
427
|
-
|
|
458
|
+
server.log('ā
MCP integration configured');
|
|
459
|
+
server.log('š” Restart Claude Desktop to apply changes');
|
|
428
460
|
}
|
|
429
461
|
catch (error) {
|
|
430
462
|
console.error('ā Failed to setup MCP:', error.message);
|
|
431
|
-
|
|
463
|
+
server.log('š” You may need to manually add the configuration to Claude Desktop');
|
|
432
464
|
}
|
|
433
465
|
}
|
|
434
466
|
// Auto-start server when run directly
|
|
435
467
|
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
436
468
|
if (isMainModule) {
|
|
437
|
-
console.log('š Starting cntx-ui server...');
|
|
438
469
|
const server = new CntxServer();
|
|
470
|
+
server.log('š Starting cntx-ui server...');
|
|
439
471
|
server.init();
|
|
440
472
|
server.listen(3333, 'localhost');
|
|
441
473
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cntx-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.6",
|
|
5
5
|
"description": "Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"repository-intelligence",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"tree-sitter-sql": "^0.1.0",
|
|
60
60
|
"tree-sitter-toml": "^0.5.1",
|
|
61
61
|
"tree-sitter-typescript": "^0.23.2",
|
|
62
|
+
"umap-js": "^1.4.0",
|
|
62
63
|
"ws": "^8.13.0"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|