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.
@@ -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
- console.log('\nšŸ‘‹ Shutting down cntx-ui...');
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';
@@ -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 results = await this.vectorStore.search(query, { limit });
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) {
@@ -6,8 +6,10 @@ import { readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
7
  export class MCPServer {
8
8
  cntxServer;
9
- constructor(cntxServer) {
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: '3.0.0' }
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(`Failed to process ${filePath}: ${error.message}`);
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
- const tree = parser.parse(content);
116
- const root = tree.rootNode;
117
- const ext = extname(relativePath).toLowerCase();
118
- const elements = {
119
- functions: [],
120
- types: [],
121
- imports: []
122
- };
123
- if (['.js', '.jsx', '.ts', '.tsx', '.rs'].includes(ext)) {
124
- elements.imports = this.extractImports(root, content, relativePath);
125
- // Traverse AST for functions and types
126
- this.traverse(root, content, relativePath, elements);
127
- }
128
- else if (ext === '.json') {
129
- this.extractJsonStructures(root, content, relativePath, elements);
130
- }
131
- else if (ext === '.css' || ext === '.scss') {
132
- this.extractCssStructures(root, content, relativePath, elements);
133
- }
134
- else if (ext === '.html') {
135
- this.extractHtmlStructures(root, content, relativePath, elements);
136
- }
137
- else if (ext === '.sql') {
138
- this.extractSqlStructures(root, content, relativePath, elements);
139
- }
140
- else if (ext === '.md') {
141
- this.extractMarkdownStructures(root, content, relativePath, elements);
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
- else if (ext === '.toml') {
144
- this.extractTomlStructures(root, content, relativePath, elements);
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
- console.log(`šŸ¤– Initializing local RAG engine (${this.modelName})...`);
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
- console.log('āœ… Local RAG engine ready');
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
- console.log(`šŸš€ cntx-ui server running at http://${host}:${port}`);
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
- console.log('šŸ“ Created .cntx directory');
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
- console.log('šŸ“„ Created .mcp.json for agent auto-discovery');
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
- console.log(`šŸ’” Suggested bundle: ${d.name} (${d.dir}/**)`);
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
- console.log('šŸ“„ Created .cntxignore with smart defaults');
364
+ server.log('šŸ“„ Created .cntxignore with smart defaults');
334
365
  }
335
- console.log('āš™ļø Basic configuration initialized');
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
- console.log(`šŸ“„ Created ${file}`);
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
- console.log('šŸ“ Created agent-rules directory with templates');
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
- console.log('šŸ“Š cntx-ui Status');
391
- console.log('================');
392
- console.log(`Total files: ${totalFiles}`);
393
- console.log(`Bundles: ${bundlesWithCounts.length}`);
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
- console.log(` • ${bundle.name}: ${bundle.fileCount} files${sizeStr}`);
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
- console.log('šŸ”§ Setting up MCP integration...');
408
- console.log(`Project: ${projectPath}`);
409
- console.log(`Claude config: ${configPath}`);
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
- console.log('āœ… MCP integration configured');
427
- console.log('šŸ’” Restart Claude Desktop to apply changes');
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
- console.log('šŸ’” You may need to manually add the configuration to Claude Desktop');
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.3",
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": {