cntx-ui 3.0.5 → 3.0.7

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/bin/cntx-ui.js CHANGED
@@ -3,19 +3,19 @@
3
3
  import { readFileSync } from 'fs';
4
4
  import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
- import { startServer, startMCPServer, generateBundle, initConfig, getStatus, setupMCP } from '../server.js';
6
+ import { autoInitAndStart, startMCPServer, generateBundle, initConfig, getStatus } from '../server.js';
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
9
  const packagePath = join(__dirname, '..', 'package.json');
10
10
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
11
11
 
12
12
  const args = process.argv.slice(2);
13
- const command = args[0] || 'help';
13
+ const command = args[0] || 'start';
14
14
  const isVerbose = args.includes('--verbose');
15
15
 
16
16
  // Graceful shutdown
17
17
  process.on('SIGINT', () => {
18
- console.log('\n👋 Shutting down cntx-ui...');
18
+ console.log('\n Shutting down cntx-ui...');
19
19
  process.exit(0);
20
20
  });
21
21
 
@@ -25,55 +25,30 @@ function showHelp() {
25
25
  ${packageJson.description}
26
26
 
27
27
  Usage:
28
- cntx-ui <command> [options]
28
+ cntx-ui [command] [options]
29
29
 
30
30
  Commands:
31
+ (default) Auto-init if needed, then start web server
31
32
  init Initialize configuration in current directory
32
- watch [port] Start web server (default port: 3333)
33
33
  mcp Start MCP server (stdio transport)
34
34
  bundle [name] Generate specific bundle (default: master)
35
35
  status Show current project status
36
- setup-mcp Add this project to Claude Desktop MCP config
37
36
 
38
37
  Options:
39
38
  --verbose Enable detailed logging
40
- --with-mcp Start web server with MCP status tracking
41
39
  --version, -v Show version number
42
40
  --help, -h Show this help message
43
41
 
44
42
  Examples:
43
+ cntx-ui Start server (auto-inits if needed)
45
44
  cntx-ui init Initialize a new project
46
- cntx-ui watch Start web server on port 3333
47
- cntx-ui watch 8080 Start web server on port 8080
48
- cntx-ui watch --verbose Start with detailed logs
49
- cntx-ui watch --with-mcp Start with MCP integration
45
+ cntx-ui mcp Start MCP server on stdio
50
46
  cntx-ui bundle api Generate 'api' bundle
51
47
  cntx-ui status Show project status
52
- cntx-ui setup-mcp Configure Claude Desktop integration
53
48
 
54
49
  MCP Integration:
55
- The MCP server provides AI-accessible bundle management for Claude Desktop
56
- and other MCP-compatible clients. Use 'setup-mcp' to configure automatic
57
- integration with Claude Desktop.
58
-
59
- Agent Collaboration:
60
- To get an external agent up to speed with your project, use this prompt:
61
-
62
- "I'm working in a project that uses cntx-ui for file organization and AI
63
- collaboration. Please read these files to understand the project structure
64
- and help me with activities:
65
-
66
- @.cntx/agent-instructions.md
67
- @.cntx/activities/README.md
68
- @.cntx/activities/activities.json
69
-
70
- After reading those, please also examine:
71
- @.cntx/activities/lib/create-activity.mdc
72
- @.cntx/activities/lib/generate-tasks.mdc
73
- @.cntx/activities/lib/process-task-list.mdc
74
-
75
- These files contain the complete workflow for creating and managing
76
- activities with agent assistance."
50
+ Running 'cntx-ui init' creates a .mcp.json file so Claude Code
51
+ can auto-discover the MCP server. Run 'cntx-ui mcp' for stdio mode.
77
52
 
78
53
  Repository: ${packageJson.repository.url}
79
54
  Author: ${packageJson.author}
@@ -95,17 +70,13 @@ async function main() {
95
70
  return showHelp();
96
71
  }
97
72
 
98
- // Handle default command (watch if no command provided and no flags)
99
- const actualCommand = command === 'help' ? 'watch' : command;
100
-
101
73
  try {
102
- switch (actualCommand) {
74
+ switch (command) {
75
+ case 'start':
103
76
  case 'watch':
104
77
  case 'w':
105
78
  const port = parseInt(args[1]) || 3333;
106
- // Enable MCP status tracking by default for the web dashboard
107
- const withMcp = !args.includes('--no-mcp');
108
- await startServer({ port, withMcp, verbose: isVerbose });
79
+ await autoInitAndStart({ port, verbose: isVerbose });
109
80
  break;
110
81
 
111
82
  case 'mcp':
@@ -118,9 +89,9 @@ async function main() {
118
89
  const bundleName = args[1] || 'master';
119
90
  try {
120
91
  await generateBundle(bundleName);
121
- console.log(`✅ Bundle '${bundleName}' generated successfully`);
92
+ console.log(`Bundle '${bundleName}' generated successfully`);
122
93
  } catch (error) {
123
- console.error(`❌ Failed to generate bundle '${bundleName}': ${error.message}`);
94
+ console.error(`Failed to generate bundle '${bundleName}': ${error.message}`);
124
95
  process.exit(1);
125
96
  }
126
97
  break;
@@ -135,17 +106,13 @@ async function main() {
135
106
  await getStatus();
136
107
  break;
137
108
 
138
- case 'setup-mcp':
139
- setupMCP();
140
- break;
141
-
142
109
  default:
143
- console.error(`❌ Unknown command: ${command}`);
110
+ console.error(`Unknown command: ${command}`);
144
111
  console.log('Run "cntx-ui --help" for usage information.');
145
112
  process.exit(1);
146
113
  }
147
114
  } catch (error) {
148
- console.error(`❌ Error: ${error.message}`);
115
+ console.error(`Error: ${error.message}`);
149
116
  if (isVerbose) {
150
117
  console.error(error.stack);
151
118
  }
@@ -281,6 +281,76 @@ This agent is **stateful**. All interactions in this directory are logged to a p
281
281
  return { strategy: 'TBD', description: 'Ready to plan' };
282
282
  }
283
283
 
284
+ /**
285
+ * Discussion Mode: Engage in architectural discussion about the codebase
286
+ */
287
+ async discussAndPlan(userInput, context = {}) {
288
+ try {
289
+ await this.logInteraction('user', userInput);
290
+
291
+ const overview = await this.getCodebaseOverview();
292
+ const searchResults = await this.cntxServer.vectorStore.search(userInput, { limit: 5 });
293
+
294
+ const response = {
295
+ input: userInput,
296
+ context,
297
+ overview,
298
+ relevantCode: searchResults.map(r => ({
299
+ name: r.name,
300
+ filePath: r.filePath,
301
+ purpose: r.purpose,
302
+ type: r.type
303
+ })),
304
+ suggestions: [
305
+ 'Review the relevant code sections listed above.',
306
+ 'Consider how changes would affect dependent bundles.',
307
+ 'Use semantic search to explore related patterns.'
308
+ ]
309
+ };
310
+
311
+ await this.logInteraction('agent', `Discussion: ${userInput}`, { response });
312
+
313
+ return response;
314
+ } catch (error) {
315
+ throw new Error(`Discussion failed: ${error.message}`);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Organize Mode: Setup and maintenance of project organization
321
+ */
322
+ async organizeProject(options = {}) {
323
+ const { activity = 'detect', autoDetect = true, force = false } = options;
324
+
325
+ try {
326
+ await this.logInteraction('agent', `Organizing project: ${activity}`);
327
+
328
+ const overview = await this.getCodebaseOverview();
329
+ const bundles = await this.analyzeBundles('all');
330
+
331
+ const response = {
332
+ activity,
333
+ overview,
334
+ bundles: bundles.map(b => ({
335
+ name: b.name,
336
+ fileCount: b.fileCount,
337
+ purpose: b.purpose
338
+ })),
339
+ recommendations: [
340
+ 'Review bundle organization for coverage gaps.',
341
+ 'Check .cntxignore for missing exclusion patterns.',
342
+ 'Run semantic analysis to identify uncategorized code.'
343
+ ]
344
+ };
345
+
346
+ await this.logInteraction('agent', `Organization complete for activity: ${activity}`, { response });
347
+
348
+ return response;
349
+ } catch (error) {
350
+ throw new Error(`Organization failed: ${error.message}`);
351
+ }
352
+ }
353
+
284
354
  async generateContextualAnswer(question, results, includeCode) {
285
355
  let response = `Based on the codebase analysis:\n\n`;
286
356
  if (results.chunks.length > 0) {
package/lib/api-router.js CHANGED
@@ -8,26 +8,19 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
 
10
10
  export default class APIRouter {
11
- constructor(cntxServer, configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore, activityManager) {
11
+ constructor(cntxServer, configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore) {
12
12
  this.cntxServer = cntxServer;
13
13
  this.configManager = configManager;
14
14
  this.bundleManager = bundleManager;
15
15
  this.fileSystemManager = fileSystemManager;
16
16
  this.semanticAnalysisManager = semanticAnalysisManager;
17
17
  this.vectorStore = vectorStore;
18
- this.activityManager = activityManager;
19
18
  }
20
19
 
21
20
  async handleRequest(req, res, url) {
22
21
  const method = req.method;
23
22
  const pathname = url.pathname;
24
23
 
25
- // DEBUG: Log all incoming API requests
26
- console.log('[API REQUEST]', method, pathname);
27
- if (pathname.includes('database')) {
28
- console.log('[DATABASE] Route requested:', pathname, method);
29
- }
30
-
31
24
  try {
32
25
  // Route to appropriate handler
33
26
  if (pathname === '/api/bundles' && method === 'GET') {
@@ -73,26 +66,6 @@ export default class APIRouter {
73
66
  return await this.handleGetFiles(req, res);
74
67
  }
75
68
 
76
- if (pathname === '/api/cursor-rules' && method === 'GET') {
77
- return await this.handleGetCursorRules(req, res);
78
- }
79
-
80
- if (pathname === '/api/cursor-rules' && method === 'POST') {
81
- return await this.handlePostCursorRules(req, res);
82
- }
83
-
84
- if (pathname === '/api/cursor-rules/templates' && method === 'GET') {
85
- return await this.handleGetCursorRulesTemplates(req, res);
86
- }
87
-
88
- if (pathname === '/api/claude-md' && method === 'GET') {
89
- return await this.handleGetClaudeMd(req, res);
90
- }
91
-
92
- if (pathname === '/api/claude-md' && method === 'POST') {
93
- return await this.handlePostClaudeMd(req, res);
94
- }
95
-
96
69
  if (pathname === '/api/heuristics/config' && method === 'GET') {
97
70
  return await this.handleGetHeuristicsConfig(req, res);
98
71
  }
@@ -190,25 +163,6 @@ export default class APIRouter {
190
163
  return await this.handlePostVectorDbSearchByDomain(req, res);
191
164
  }
192
165
 
193
- if (pathname === '/api/activities' && method === 'GET') {
194
- return await this.handleGetActivities(req, res);
195
- }
196
-
197
- if (pathname.startsWith('/api/activities/') && pathname.endsWith('/reasoning') && method === 'GET') {
198
- const activityId = pathname.split('/')[3];
199
- return await this.handleGetActivityReasoning(req, res, activityId);
200
- }
201
-
202
- if (pathname.startsWith('/api/activities/') && pathname.endsWith('/execute') && method === 'POST') {
203
- const activityId = pathname.split('/')[3];
204
- return await this.handlePostActivityExecute(req, res, activityId);
205
- }
206
-
207
- if (pathname.startsWith('/api/activities/') && pathname.endsWith('/stop') && method === 'POST') {
208
- const activityId = pathname.split('/')[3];
209
- return await this.handlePostActivityStop(req, res, activityId);
210
- }
211
-
212
166
  if (pathname === '/api/open-file' && method === 'POST') {
213
167
  return await this.handleOpenFile(req, res);
214
168
  }
@@ -429,49 +383,6 @@ export default class APIRouter {
429
383
  res.end(JSON.stringify({ success: true }));
430
384
  }
431
385
 
432
- async handleGetCursorRules(req, res) {
433
- const content = this.configManager.loadCursorRules();
434
- res.writeHead(200, { 'Content-Type': 'text/plain' });
435
- res.end(content);
436
- }
437
-
438
- async handlePostCursorRules(req, res) {
439
- const body = await this.getRequestBody(req);
440
- const { content } = JSON.parse(body);
441
-
442
- this.configManager.saveCursorRules(content);
443
- res.writeHead(200, { 'Content-Type': 'application/json' });
444
- res.end(JSON.stringify({ success: true }));
445
- }
446
-
447
- async handleGetCursorRulesTemplates(req, res) {
448
- const templates = {
449
- react: this.configManager.generateCursorRulesTemplate({ projectType: 'react', name: 'React Project' }),
450
- vue: this.configManager.generateCursorRulesTemplate({ projectType: 'vue', name: 'Vue Project' }),
451
- angular: this.configManager.generateCursorRulesTemplate({ projectType: 'angular', name: 'Angular Project' }),
452
- 'node-backend': this.configManager.generateCursorRulesTemplate({ projectType: 'node-backend', name: 'Node.js Backend' }),
453
- javascript: this.configManager.generateCursorRulesTemplate({ projectType: 'javascript', name: 'JavaScript Project' })
454
- };
455
-
456
- res.writeHead(200, { 'Content-Type': 'application/json' });
457
- res.end(JSON.stringify(templates));
458
- }
459
-
460
- async handleGetClaudeMd(req, res) {
461
- const content = this.configManager.loadClaudeMd();
462
- res.writeHead(200, { 'Content-Type': 'text/plain' });
463
- res.end(content);
464
- }
465
-
466
- async handlePostClaudeMd(req, res) {
467
- const body = await this.getRequestBody(req);
468
- const { content } = JSON.parse(body);
469
-
470
- this.configManager.saveClaudeMd(content);
471
- res.writeHead(200, { 'Content-Type': 'application/json' });
472
- res.end(JSON.stringify({ success: true }));
473
- }
474
-
475
386
  async handleGetHeuristicsConfig(req, res) {
476
387
  const config = this.configManager.loadHeuristicsConfig();
477
388
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -907,52 +818,6 @@ export default class APIRouter {
907
818
  }
908
819
  }
909
820
 
910
- // === Activities ===
911
-
912
- async handleGetActivities(req, res) {
913
- try {
914
- const activities = await this.activityManager.loadActivities();
915
- res.writeHead(200, { 'Content-Type': 'application/json' });
916
- res.end(JSON.stringify(activities));
917
- } catch (error) {
918
- res.writeHead(500, { 'Content-Type': 'application/json' });
919
- res.end(JSON.stringify({ error: error.message }));
920
- }
921
- }
922
-
923
- async handleGetActivityReasoning(req, res, activityId) {
924
- try {
925
- const history = this.configManager.dbManager.getSessionHistory(activityId);
926
- res.writeHead(200, { 'Content-Type': 'application/json' });
927
- res.end(JSON.stringify({ history }));
928
- } catch (error) {
929
- res.writeHead(500, { 'Content-Type': 'application/json' });
930
- res.end(JSON.stringify({ error: error.message }));
931
- }
932
- }
933
-
934
- async handlePostActivityExecute(req, res, activityId) {
935
- try {
936
- const result = await this.activityManager.executeActivity(activityId);
937
- res.writeHead(200, { 'Content-Type': 'application/json' });
938
- res.end(JSON.stringify(result));
939
- } catch (error) {
940
- res.writeHead(500, { 'Content-Type': 'application/json' });
941
- res.end(JSON.stringify({ error: error.message }));
942
- }
943
- }
944
-
945
- async handlePostActivityStop(req, res, activityId) {
946
- try {
947
- const result = await this.activityManager.stopActivity(activityId);
948
- res.writeHead(200, { 'Content-Type': 'application/json' });
949
- res.end(JSON.stringify(result));
950
- } catch (error) {
951
- res.writeHead(500, { 'Content-Type': 'application/json' });
952
- res.end(JSON.stringify({ error: error.message }));
953
- }
954
- }
955
-
956
821
  async handleOpenFile(req, res) {
957
822
  try {
958
823
  const body = await this.getRequestBody(req);
package/lib/mcp-server.js CHANGED
@@ -578,36 +578,6 @@ export class MCPServer {
578
578
  required: ['path', 'content']
579
579
  }
580
580
  },
581
- {
582
- name: 'manage_activities',
583
- description: 'CRUD operations for project activities',
584
- inputSchema: {
585
- type: 'object',
586
- properties: {
587
- action: {
588
- type: 'string',
589
- enum: ['list', 'get', 'create', 'update', 'delete'],
590
- description: 'Action to perform on activities'
591
- },
592
- activityId: {
593
- type: 'string',
594
- description: 'Activity ID (required for get, update, delete)'
595
- },
596
- activity: {
597
- type: 'object',
598
- description: 'Activity data (required for create, update)',
599
- properties: {
600
- title: { type: 'string' },
601
- description: { type: 'string' },
602
- status: { type: 'string', enum: ['todo', 'in_progress', 'completed', 'blocked'] },
603
- tags: { type: 'array', items: { type: 'string' } },
604
- tasks: { type: 'array', items: { type: 'object' } }
605
- }
606
- }
607
- },
608
- required: ['action']
609
- }
610
- }
611
581
  ];
612
582
 
613
583
  return this.createSuccessResponse(id, { tools });
@@ -676,9 +646,6 @@ export class MCPServer {
676
646
  case 'write_file':
677
647
  return this.toolWriteFile(args, id);
678
648
 
679
- case 'manage_activities':
680
- return this.toolManageActivities(args, id);
681
-
682
649
  default:
683
650
  return this.createErrorResponse(id, -32602, 'Unknown tool');
684
651
  }
@@ -1369,119 +1336,6 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
1369
1336
  }
1370
1337
  }
1371
1338
 
1372
- async toolManageActivities(args, id) {
1373
- const { action, activityId, activity } = args;
1374
-
1375
- if (!action) {
1376
- return this.createErrorResponse(id, -32602, 'Action is required');
1377
- }
1378
-
1379
- try {
1380
- const activitiesPath = join(this.cntxServer.CWD, '.cntx', 'activities');
1381
- const activitiesJsonPath = join(activitiesPath, 'activities.json');
1382
-
1383
- let activities = [];
1384
- if (existsSync(activitiesJsonPath)) {
1385
- activities = JSON.parse(readFileSync(activitiesJsonPath, 'utf8'));
1386
- }
1387
-
1388
- let result;
1389
-
1390
- switch (action) {
1391
- case 'list':
1392
- result = { activities: activities.map(a => ({
1393
- id: a.title.toLowerCase().replace(/[^a-z0-9]/g, '-'),
1394
- title: a.title,
1395
- description: a.description,
1396
- status: a.status,
1397
- tags: a.tags
1398
- })) };
1399
- break;
1400
-
1401
- case 'get':
1402
- if (!activityId) {
1403
- return this.createErrorResponse(id, -32602, 'Activity ID is required for get action');
1404
- }
1405
- const found = activities.find(a =>
1406
- a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1407
- );
1408
- if (!found) {
1409
- return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1410
- }
1411
-
1412
- // Load markdown files
1413
- const activityDir = join(activitiesPath, 'activities', activityId);
1414
- const files = {};
1415
- ['README.md', 'progress.md', 'tasks.md', 'notes.md'].forEach(file => {
1416
- const filePath = join(activityDir, file);
1417
- files[file.replace('.md', '')] = existsSync(filePath)
1418
- ? readFileSync(filePath, 'utf8')
1419
- : 'No content available';
1420
- });
1421
-
1422
- result = { ...found, files };
1423
- break;
1424
-
1425
- case 'create':
1426
- if (!activity || !activity.title) {
1427
- return this.createErrorResponse(id, -32602, 'Activity with title is required for create action');
1428
- }
1429
- activities.push({
1430
- title: activity.title,
1431
- description: activity.description || '',
1432
- status: activity.status || 'todo',
1433
- tags: activity.tags || ['general'],
1434
- tasks: activity.tasks || []
1435
- });
1436
- writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1437
- result = { created: true, activityId: activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-') };
1438
- break;
1439
-
1440
- case 'update':
1441
- if (!activityId || !activity) {
1442
- return this.createErrorResponse(id, -32602, 'Activity ID and activity data are required for update action');
1443
- }
1444
- const updateIndex = activities.findIndex(a =>
1445
- a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1446
- );
1447
- if (updateIndex === -1) {
1448
- return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1449
- }
1450
- activities[updateIndex] = { ...activities[updateIndex], ...activity };
1451
- writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1452
- result = { updated: true };
1453
- break;
1454
-
1455
- case 'delete':
1456
- if (!activityId) {
1457
- return this.createErrorResponse(id, -32602, 'Activity ID is required for delete action');
1458
- }
1459
- const deleteIndex = activities.findIndex(a =>
1460
- a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1461
- );
1462
- if (deleteIndex === -1) {
1463
- return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1464
- }
1465
- activities.splice(deleteIndex, 1);
1466
- writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1467
- result = { deleted: true };
1468
- break;
1469
-
1470
- default:
1471
- return this.createErrorResponse(id, -32602, `Unknown action: ${action}`);
1472
- }
1473
-
1474
- return this.createSuccessResponse(id, {
1475
- content: [{
1476
- type: 'text',
1477
- text: JSON.stringify(result, null, 2)
1478
- }]
1479
- });
1480
- } catch (error) {
1481
- return this.createErrorResponse(id, -32603, 'Failed to manage activities', error.message);
1482
- }
1483
- }
1484
-
1485
1339
  // Helper methods
1486
1340
  getMimeType(filePath) {
1487
1341
  const ext = filePath.split('.').pop()?.toLowerCase();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cntx-ui",
3
3
  "type": "module",
4
- "version": "3.0.5",
4
+ "version": "3.0.7",
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",