cntx-ui 2.0.11 → 2.0.13

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cntx-ui",
3
3
  "type": "module",
4
- "version": "2.0.11",
4
+ "version": "2.0.13",
5
5
  "description": "File context management tool with web UI and MCP server for AI development workflows - bundle project files for LLM consumption",
6
6
  "keywords": [
7
7
  "ai-development",
@@ -50,6 +50,10 @@
50
50
  "test:local": "npm pack && npm install -g ./cntx-ui-2.0.0.tgz"
51
51
  },
52
52
  "dependencies": {
53
+ "glob": "^8.1.0",
54
+ "tree-sitter": "^0.21.1",
55
+ "tree-sitter-javascript": "^0.23.1",
56
+ "tree-sitter-typescript": "^0.23.2",
53
57
  "ws": "^8.13.0"
54
58
  }
55
59
  }
package/server.js CHANGED
@@ -5,6 +5,7 @@ import { WebSocketServer } from 'ws';
5
5
  import { fileURLToPath } from 'url';
6
6
  import path from 'path';
7
7
  import { startMCPTransport } from './lib/mcp-transport.js';
8
+ import SemanticSplitter from './lib/semantic-splitter.js';
8
9
  import { homedir } from 'os';
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -51,6 +52,16 @@ export class CntxServer {
51
52
  userIgnorePatterns: [], // User-added ignore patterns
52
53
  disabledSystemPatterns: [] // System patterns the user disabled
53
54
  };
55
+
56
+ // Semantic splitting (parallel to bundle system)
57
+ this.semanticSplitter = new SemanticSplitter({
58
+ maxChunkSize: 2000,
59
+ includeContext: true,
60
+ groupRelated: true,
61
+ minFunctionSize: 50
62
+ });
63
+ this.semanticCache = null;
64
+ this.lastSemanticAnalysis = null;
54
65
  }
55
66
 
56
67
  init() {
@@ -225,12 +236,12 @@ export class CntxServer {
225
236
  '**/.git/**',
226
237
  '**/.svn/**',
227
238
  '**/.hg/**',
228
-
239
+
229
240
  // Dependencies
230
241
  '**/node_modules/**',
231
242
  '**/vendor/**',
232
243
  '**/.pnp/**',
233
-
244
+
234
245
  // Build outputs
235
246
  '**/dist/**',
236
247
  '**/build/**',
@@ -238,18 +249,18 @@ export class CntxServer {
238
249
  '**/.next/**',
239
250
  '**/.nuxt/**',
240
251
  '**/target/**',
241
-
252
+
242
253
  // Package files
243
254
  '**/*.tgz',
244
255
  '**/*.tar.gz',
245
256
  '**/*.zip',
246
257
  '**/*.rar',
247
258
  '**/*.7z',
248
-
259
+
249
260
  // Logs
250
261
  '**/*.log',
251
262
  '**/logs/**',
252
-
263
+
253
264
  // Cache directories
254
265
  '**/.cache/**',
255
266
  '**/.parcel-cache/**',
@@ -257,30 +268,30 @@ export class CntxServer {
257
268
  '**/coverage/**',
258
269
  '**/.pytest_cache/**',
259
270
  '**/__pycache__/**',
260
-
271
+
261
272
  // IDE/Editor files
262
273
  '**/.vscode/**',
263
274
  '**/.idea/**',
264
275
  '**/*.swp',
265
276
  '**/*.swo',
266
277
  '**/*~',
267
-
278
+
268
279
  // OS files
269
280
  '**/.DS_Store',
270
281
  '**/Thumbs.db',
271
282
  '**/desktop.ini',
272
-
283
+
273
284
  // Environment files
274
285
  '**/.env',
275
286
  '**/.env.local',
276
287
  '**/.env.*.local',
277
-
288
+
278
289
  // Lock files
279
290
  '**/package-lock.json',
280
291
  '**/yarn.lock',
281
292
  '**/pnpm-lock.yaml',
282
293
  '**/Cargo.lock',
283
-
294
+
284
295
  // cntx-ui specific
285
296
  '**/.cntx/**'
286
297
  ];
@@ -591,7 +602,7 @@ Add your specific project rules and preferences below:
591
602
 
592
603
  generateClaudeMdTemplate(projectInfo) {
593
604
  const { name, description, type } = projectInfo;
594
-
605
+
595
606
  let template = `# ${name}
596
607
 
597
608
  ${description ? `${description}\n\n` : ''}## Project Structure
@@ -802,6 +813,7 @@ This project uses cntx-ui for bundle management and AI context organization.
802
813
  if (!this.shouldIgnoreFile(fullPath)) {
803
814
  if (!this.isQuietMode) console.log(`File ${eventType}: ${filename}`);
804
815
  this.markBundlesChanged(filename.replace(/\\\\/g, '/'));
816
+ this.invalidateSemanticCache(); // Invalidate semantic cache on file changes
805
817
  this.broadcastUpdate();
806
818
  }
807
819
  }
@@ -903,7 +915,7 @@ This project uses cntx-ui for bundle management and AI context organization.
903
915
  // Bundle overview section
904
916
  const filesByType = this.categorizeFiles(files);
905
917
  const entryPoints = this.identifyEntryPoints(files);
906
-
918
+
907
919
  xml += ` <cntx:overview>
908
920
  <cntx:purpose>${this.escapeXml(this.getBundlePurpose(bundleName))}</cntx:purpose>
909
921
  <cntx:file-types>
@@ -949,7 +961,7 @@ This project uses cntx-ui for bundle management and AI context organization.
949
961
  // Then organize by file type
950
962
  Object.entries(filesByType).forEach(([type, typeFiles]) => {
951
963
  if (type === 'entry-points') return; // Already handled above
952
-
964
+
953
965
  const remainingFiles = typeFiles.filter(file => !entryPoints.includes(file));
954
966
  if (remainingFiles.length > 0) {
955
967
  xml += ` <cntx:group type="${type}" description="${this.getTypeDescription(type)}">
@@ -983,28 +995,28 @@ This project uses cntx-ui for bundle management and AI context organization.
983
995
  files.forEach(file => {
984
996
  const ext = extname(file).toLowerCase();
985
997
  const basename = file.toLowerCase();
986
-
987
- if (basename.includes('component') || file.includes('/components/') ||
988
- ext === '.jsx' || ext === '.tsx' && !basename.includes('test')) {
998
+
999
+ if (basename.includes('component') || file.includes('/components/') ||
1000
+ ext === '.jsx' || ext === '.tsx' && !basename.includes('test')) {
989
1001
  categories.components.push(file);
990
1002
  } else if (basename.includes('hook') || file.includes('/hooks/')) {
991
1003
  categories.hooks.push(file);
992
- } else if (basename.includes('util') || file.includes('/utils/') ||
993
- basename.includes('helper') || file.includes('/lib/')) {
1004
+ } else if (basename.includes('util') || file.includes('/utils/') ||
1005
+ basename.includes('helper') || file.includes('/lib/')) {
994
1006
  categories.utilities.push(file);
995
- } else if (ext === '.json' || basename.includes('config') ||
996
- ext === '.yaml' || ext === '.yml' || ext === '.toml') {
1007
+ } else if (ext === '.json' || basename.includes('config') ||
1008
+ ext === '.yaml' || ext === '.yml' || ext === '.toml') {
997
1009
  categories.configuration.push(file);
998
1010
  } else if (ext === '.css' || ext === '.scss' || ext === '.less') {
999
1011
  categories.styles.push(file);
1000
- } else if (basename.includes('type') || ext === '.d.ts' ||
1001
- file.includes('/types/')) {
1012
+ } else if (basename.includes('type') || ext === '.d.ts' ||
1013
+ file.includes('/types/')) {
1002
1014
  categories.types.push(file);
1003
- } else if (basename.includes('test') || basename.includes('spec') ||
1004
- file.includes('/test/') || file.includes('/__tests__/')) {
1015
+ } else if (basename.includes('test') || basename.includes('spec') ||
1016
+ file.includes('/test/') || file.includes('/__tests__/')) {
1005
1017
  categories.tests.push(file);
1006
- } else if (ext === '.md' || basename.includes('readme') ||
1007
- basename.includes('doc')) {
1018
+ } else if (ext === '.md' || basename.includes('readme') ||
1019
+ basename.includes('doc')) {
1008
1020
  categories.documentation.push(file);
1009
1021
  } else {
1010
1022
  categories.other.push(file);
@@ -1023,16 +1035,16 @@ This project uses cntx-ui for bundle management and AI context organization.
1023
1035
 
1024
1036
  identifyEntryPoints(files) {
1025
1037
  const entryPoints = [];
1026
-
1038
+
1027
1039
  files.forEach(file => {
1028
1040
  const basename = file.toLowerCase();
1029
-
1041
+
1030
1042
  // Common entry point patterns
1031
- if (basename.includes('main.') || basename.includes('index.') ||
1032
- basename.includes('app.') || basename === 'server.js' ||
1033
- file.endsWith('/App.tsx') || file.endsWith('/App.jsx') ||
1034
- file.endsWith('/main.tsx') || file.endsWith('/main.js') ||
1035
- file.endsWith('/index.tsx') || file.endsWith('/index.js')) {
1043
+ if (basename.includes('main.') || basename.includes('index.') ||
1044
+ basename.includes('app.') || basename === 'server.js' ||
1045
+ file.endsWith('/App.tsx') || file.endsWith('/App.jsx') ||
1046
+ file.endsWith('/main.tsx') || file.endsWith('/main.js') ||
1047
+ file.endsWith('/index.tsx') || file.endsWith('/index.js')) {
1036
1048
  entryPoints.push(file);
1037
1049
  }
1038
1050
  });
@@ -1083,11 +1095,11 @@ This project uses cntx-ui for bundle management and AI context organization.
1083
1095
  try {
1084
1096
  const stat = statSync(fullPath);
1085
1097
  const content = readFileSync(fullPath, 'utf8');
1086
-
1098
+
1087
1099
  // Add role indicator for certain files
1088
1100
  const role = this.getFileRole(file);
1089
1101
  const roleAttr = role ? ` role="${role}"` : '';
1090
-
1102
+
1091
1103
  fileXml = ` <cntx:file path="${file}" ext="${extname(file)}"${roleAttr}>
1092
1104
  `;
1093
1105
  fileXml += ` <cntx:meta size="${stat.size}" modified="${stat.mtime.toISOString()}" lines="${content.split('\n').length}" />
@@ -1105,18 +1117,17 @@ This project uses cntx-ui for bundle management and AI context organization.
1105
1117
 
1106
1118
  getFileRole(file) {
1107
1119
  const basename = file.toLowerCase();
1108
-
1120
+
1109
1121
  if (basename.includes('main.') || basename.includes('index.')) return 'entry-point';
1110
1122
  if (basename.includes('app.')) return 'main-component';
1111
1123
  if (file === 'server.js') return 'server-entry';
1112
1124
  if (basename.includes('config')) return 'configuration';
1113
1125
  if (basename.includes('package.json')) return 'package-config';
1114
1126
  if (basename.includes('readme')) return 'documentation';
1115
-
1127
+
1116
1128
  return null;
1117
1129
  }
1118
1130
 
1119
-
1120
1131
  escapeXml(text) {
1121
1132
  return String(text)
1122
1133
  .replace(/&/g, '&amp;')
@@ -1276,6 +1287,8 @@ This project uses cntx-ui for bundle management and AI context organization.
1276
1287
  }
1277
1288
 
1278
1289
  // API Routes
1290
+ console.log('🔍 Processing API request:', url.pathname);
1291
+
1279
1292
  if (url.pathname === '/api/bundles') {
1280
1293
  res.writeHead(200, { 'Content-Type': 'application/json' });
1281
1294
  const bundleData = Array.from(this.bundles.entries()).map(([name, bundle]) => ({
@@ -1289,6 +1302,72 @@ This project uses cntx-ui for bundle management and AI context organization.
1289
1302
  }));
1290
1303
  res.end(JSON.stringify(bundleData));
1291
1304
 
1305
+ } else if (url.pathname === '/api/semantic-chunks') {
1306
+ console.log('🔍 Semantic chunks route matched! URL:', url.pathname);
1307
+ this.getSemanticAnalysis()
1308
+ .then(analysis => {
1309
+ console.log('✅ Semantic analysis successful');
1310
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1311
+ res.end(JSON.stringify(analysis));
1312
+ })
1313
+ .catch(error => {
1314
+ console.error('❌ Semantic analysis failed:', error.message);
1315
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1316
+ res.end(JSON.stringify({ error: error.message }));
1317
+ });
1318
+
1319
+ } else if (url.pathname === '/api/semantic-chunks/export') {
1320
+ if (req.method === 'POST') {
1321
+ let body = '';
1322
+ req.on('data', chunk => body += chunk);
1323
+ req.on('end', () => {
1324
+ try {
1325
+ const { chunkName } = JSON.parse(body);
1326
+ this.exportSemanticChunk(chunkName)
1327
+ .then(xmlContent => {
1328
+ res.writeHead(200, { 'Content-Type': 'application/xml' });
1329
+ res.end(xmlContent);
1330
+ })
1331
+ .catch(error => {
1332
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1333
+ res.end(JSON.stringify({ error: error.message }));
1334
+ });
1335
+ } catch (error) {
1336
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1337
+ res.end(JSON.stringify({ error: error.message }));
1338
+ }
1339
+ });
1340
+ } else {
1341
+ res.writeHead(405);
1342
+ res.end('Method not allowed');
1343
+ }
1344
+
1345
+ } else if (url.pathname === '/api/bundles-from-chunk') {
1346
+ if (req.method === 'POST') {
1347
+ let body = '';
1348
+ req.on('data', chunk => body += chunk);
1349
+ req.on('end', () => {
1350
+ try {
1351
+ const { chunkName, files } = JSON.parse(body);
1352
+ this.createBundleFromChunk(chunkName, files)
1353
+ .then(() => {
1354
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1355
+ res.end(JSON.stringify({ success: true }));
1356
+ })
1357
+ .catch(error => {
1358
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1359
+ res.end(JSON.stringify({ error: error.message }));
1360
+ });
1361
+ } catch (error) {
1362
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1363
+ res.end(JSON.stringify({ error: error.message }));
1364
+ }
1365
+ });
1366
+ } else {
1367
+ res.writeHead(405);
1368
+ res.end('Method not allowed');
1369
+ }
1370
+
1292
1371
  } else if (url.pathname.startsWith('/api/bundles/')) {
1293
1372
  const bundleName = url.pathname.split('/').pop();
1294
1373
  const bundle = this.bundles.get(bundleName);
@@ -1590,11 +1669,11 @@ This project uses cntx-ui for bundle management and AI context organization.
1590
1669
  } else if (url.pathname.startsWith('/api/bundle-categories/')) {
1591
1670
  const bundleName = url.pathname.split('/').pop();
1592
1671
  const bundle = this.bundles.get(bundleName);
1593
-
1672
+
1594
1673
  if (bundle) {
1595
1674
  const filesByType = this.categorizeFiles(bundle.files);
1596
1675
  const entryPoints = this.identifyEntryPoints(bundle.files);
1597
-
1676
+
1598
1677
  res.writeHead(200, { 'Content-Type': 'application/json' });
1599
1678
  res.end(JSON.stringify({
1600
1679
  purpose: this.getBundlePurpose(bundleName),
@@ -1637,6 +1716,36 @@ This project uses cntx-ui for bundle management and AI context organization.
1637
1716
  });
1638
1717
  }
1639
1718
 
1719
+ } else if (url.pathname === '/api/mcp-status') {
1720
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1721
+
1722
+ // Simple check - MCP is available if we can find package.json
1723
+ let isAccessible = true;
1724
+ let testResult = 'available';
1725
+
1726
+ // Check if package.json exists using existing imports
1727
+ try {
1728
+ const packagePath = join(this.CWD, 'package.json');
1729
+ if (existsSync(packagePath)) {
1730
+ testResult = 'local_package_found';
1731
+ } else {
1732
+ testResult = 'using_global_npx';
1733
+ }
1734
+ } catch (error) {
1735
+ testResult = 'check_failed';
1736
+ }
1737
+
1738
+ const mcpStatus = {
1739
+ running: isAccessible,
1740
+ accessible: isAccessible,
1741
+ testResult: testResult,
1742
+ command: 'npx cntx-ui mcp',
1743
+ workingDirectory: this.CWD,
1744
+ lastChecked: new Date().toISOString(),
1745
+ trackingEnabled: this.mcpServerStarted || false
1746
+ };
1747
+ res.end(JSON.stringify(mcpStatus, null, 2));
1748
+
1640
1749
  } else if (url.pathname === '/api/status') {
1641
1750
  res.writeHead(200, { 'Content-Type': 'application/json' });
1642
1751
  const statusInfo = {
@@ -1712,19 +1821,150 @@ This project uses cntx-ui for bundle management and AI context organization.
1712
1821
  this.watchers.forEach(watcher => watcher.close());
1713
1822
  this.saveBundleStates();
1714
1823
  }
1824
+
1825
+ // Semantic Chunking Methods
1826
+ async getSemanticAnalysis() {
1827
+ // Force refresh always for now (TODO: implement proper cache invalidation)
1828
+ this.semanticCache = null // Clear cache
1829
+ const shouldRefresh = true
1830
+
1831
+ console.log('🔍 Cache check - shouldRefresh:', shouldRefresh, 'lastAnalysis:', this.lastSemanticAnalysis, 'now:', Date.now());
1832
+
1833
+ if (shouldRefresh) {
1834
+ try {
1835
+ // Auto-discover JavaScript/TypeScript files in the entire project
1836
+ const patterns = ['**/*.{js,jsx,ts,tsx,mjs}'];
1837
+
1838
+ // Load bundle configuration for chunk grouping
1839
+ let bundleConfig = null;
1840
+ if (existsSync(this.CONFIG_FILE)) {
1841
+ bundleConfig = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
1842
+ }
1843
+
1844
+ this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, patterns, bundleConfig);
1845
+ this.lastSemanticAnalysis = Date.now();
1846
+
1847
+ // Debug logging
1848
+ console.log('🔍 Semantic analysis complete. Sample chunk keys:',
1849
+ this.semanticCache.chunks.length > 0 ? Object.keys(this.semanticCache.chunks[0]) : 'No chunks');
1850
+ if (this.semanticCache.chunks.length > 0) {
1851
+ console.log('🔍 Sample chunk businessDomains:', this.semanticCache.chunks[0].businessDomains);
1852
+ }
1853
+ } catch (error) {
1854
+ console.error('Semantic analysis failed:', error.message);
1855
+ throw new Error(`Semantic analysis failed: ${error.message}`);
1856
+ }
1857
+ }
1858
+
1859
+ return this.semanticCache;
1860
+ }
1861
+
1862
+ async exportSemanticChunk(chunkName) {
1863
+ const analysis = await this.getSemanticAnalysis();
1864
+ const chunk = analysis.chunks.find(c => c.name === chunkName);
1865
+
1866
+ if (!chunk) {
1867
+ throw new Error(`Chunk "${chunkName}" not found`);
1868
+ }
1869
+
1870
+ // Generate XML content for the chunk files
1871
+ let xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n`;
1872
+ xmlContent += `<codebase_context semantic_chunk="${chunkName}">\n`;
1873
+ xmlContent += ` <chunk_info>\n`;
1874
+ xmlContent += ` <name>${chunkName}</name>\n`;
1875
+ xmlContent += ` <purpose>${chunk.purpose || 'No description'}</purpose>\n`;
1876
+ xmlContent += ` <file_count>${chunk.files.length}</file_count>\n`;
1877
+ xmlContent += ` <size>${chunk.size} bytes</size>\n`;
1878
+ xmlContent += ` <complexity>${chunk.complexity?.level || 'unknown'}</complexity>\n`;
1879
+ xmlContent += ` <tags>${(chunk.tags || []).join(', ')}</tags>\n`;
1880
+ xmlContent += ` </chunk_info>\n\n`;
1881
+
1882
+ // Add each function in the chunk
1883
+ if (chunk.functions) {
1884
+ for (const func of chunk.functions) {
1885
+ xmlContent += ` <function name="${func.name}" file="${func.filePath}">\n`;
1886
+ xmlContent += ` <signature>${func.signature}</signature>\n`;
1887
+ xmlContent += ` <type>${func.type}</type>\n`;
1888
+ xmlContent += ` <lines>${func.startLine}-${func.endLine}</lines>\n`;
1889
+
1890
+ if (func.context.imports.length > 0) {
1891
+ xmlContent += ` <imports>${func.context.imports.join(', ')}</imports>\n`;
1892
+ }
1893
+
1894
+ xmlContent += ` <code>\n`;
1895
+ xmlContent += func.code
1896
+ .split('\n')
1897
+ .map((line, i) => `${(func.startLine + i).toString().padStart(3)} ${line}`)
1898
+ .join('\n');
1899
+ xmlContent += `\n </code>\n`;
1900
+ xmlContent += ` </function>\n\n`;
1901
+ }
1902
+ } else {
1903
+ // Fallback for file-based chunks
1904
+ for (const filePath of chunk.files || []) {
1905
+ const fullPath = join(this.CWD, filePath);
1906
+ if (existsSync(fullPath)) {
1907
+ try {
1908
+ const content = readFileSync(fullPath, 'utf8');
1909
+ xmlContent += ` <file path="${filePath}">\n`;
1910
+ xmlContent += content
1911
+ .split('\n')
1912
+ .map((line, i) => `${(i + 1).toString().padStart(3)} ${line}`)
1913
+ .join('\n');
1914
+ xmlContent += `\n </file>\n\n`;
1915
+ } catch (error) {
1916
+ console.warn(`Could not read file ${filePath}:`, error.message);
1917
+ }
1918
+ }
1919
+ }
1920
+ }
1921
+
1922
+ xmlContent += `</codebase_context>`;
1923
+ return xmlContent;
1924
+ }
1925
+
1926
+ async createBundleFromChunk(chunkName, files) {
1927
+ // Load current config
1928
+ let config = {};
1929
+ if (existsSync(this.CONFIG_FILE)) {
1930
+ config = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
1931
+ }
1932
+
1933
+ if (!config.bundles) {
1934
+ config.bundles = {};
1935
+ }
1936
+
1937
+ // Create bundle with the chunk name and files
1938
+ const bundleName = chunkName.toLowerCase().replace(/[-\s]+/g, '-');
1939
+ config.bundles[bundleName] = files;
1940
+
1941
+ // Save config
1942
+ writeFileSync(this.CONFIG_FILE, JSON.stringify(config, null, 2));
1943
+
1944
+ // Reload bundles
1945
+ this.loadConfig();
1946
+ this.generateAllBundles();
1947
+ this.saveBundleStates();
1948
+ this.broadcastUpdate();
1949
+ }
1950
+
1951
+ invalidateSemanticCache() {
1952
+ this.semanticCache = null;
1953
+ this.lastSemanticAnalysis = null;
1954
+ }
1715
1955
  }
1716
1956
 
1717
1957
  export function startServer(options = {}) {
1718
1958
  const server = new CntxServer(options.cwd, { quiet: options.quiet });
1719
1959
  server.init();
1720
-
1960
+
1721
1961
  if (options.withMcp) {
1722
1962
  server.mcpServerStarted = true;
1723
1963
  if (!server.isQuietMode) {
1724
1964
  console.log('🔗 MCP server tracking enabled - use /api/status to check MCP configuration');
1725
1965
  }
1726
1966
  }
1727
-
1967
+
1728
1968
  return server.startServer(options.port);
1729
1969
  }
1730
1970
 
@@ -1900,17 +2140,17 @@ export function setupMCP(cwd = process.cwd(), options = {}) {
1900
2140
  // Write updated config
1901
2141
  try {
1902
2142
  writeFileSync(configFile, JSON.stringify(config, null, 2));
1903
-
2143
+
1904
2144
  if (!isQuiet) {
1905
2145
  console.log(`✅ Added MCP server: ${serverName}`);
1906
2146
  console.log('📋 Your Claude Desktop config now includes:');
1907
-
2147
+
1908
2148
  Object.keys(config.mcpServers).forEach(name => {
1909
2149
  if (name.startsWith('cntx-ui-')) {
1910
2150
  console.log(` • ${name}: ${config.mcpServers[name].cwd}`);
1911
2151
  }
1912
2152
  });
1913
-
2153
+
1914
2154
  console.log('🔄 Please restart Claude Desktop to use the updated configuration');
1915
2155
  }
1916
2156
  } catch (error) {