cntx-ui 2.0.12 → 2.0.15
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 +137 -55
- package/lib/agent-runtime.js +1480 -0
- package/lib/agent-tools.js +368 -0
- package/lib/api-router.js +978 -0
- package/lib/bundle-manager.js +471 -0
- package/lib/configuration-manager.js +725 -0
- package/lib/file-system-manager.js +472 -0
- package/lib/function-level-chunker.js +406 -0
- package/lib/heuristics-manager.js +425 -0
- package/lib/mcp-server.js +1054 -1
- package/lib/semantic-splitter.js +588 -0
- package/lib/simple-vector-store.js +329 -0
- package/lib/treesitter-semantic-chunker.js +1485 -0
- package/lib/websocket-manager.js +470 -0
- package/package.json +14 -3
- package/server.js +673 -1704
- package/templates/activities/README.md +67 -0
- package/templates/activities/activities/create-project-bundles/README.md +83 -0
- package/templates/activities/activities/create-project-bundles/notes.md +102 -0
- package/templates/activities/activities/create-project-bundles/progress.md +63 -0
- package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
- package/templates/activities/activities.json +219 -0
- package/templates/activities/lib/.markdownlint.jsonc +18 -0
- package/templates/activities/lib/create-activity.mdc +63 -0
- package/templates/activities/lib/generate-tasks.mdc +64 -0
- package/templates/activities/lib/process-task-list.mdc +52 -0
- package/templates/agent-config.yaml +78 -0
- package/templates/agent-instructions.md +218 -0
- package/templates/agent-rules/capabilities/activities-system.md +147 -0
- package/templates/agent-rules/capabilities/bundle-system.md +131 -0
- package/templates/agent-rules/capabilities/vector-search.md +135 -0
- package/templates/agent-rules/core/codebase-navigation.md +91 -0
- package/templates/agent-rules/core/performance-hierarchy.md +48 -0
- package/templates/agent-rules/core/response-formatting.md +120 -0
- package/templates/agent-rules/project-specific/architecture.md +145 -0
- package/templates/config.json +76 -0
- package/templates/hidden-files.json +14 -0
- package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
- package/web/dist/assets/index-dF3qg-y_.js +2486 -0
- package/web/dist/assets/index-h5FGSg_P.css +1 -0
- package/web/dist/cntx-ui.svg +18 -0
- package/web/dist/index.html +25 -8
- package/web/dist/assets/index-8Kli5657.js +0 -541
- package/web/dist/assets/index-C-Ldi33E.css +0 -1
- package/web/dist/vite.svg +0 -1
package/server.js
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Refactored cntx-ui Server
|
|
3
|
+
* Lean orchestration layer using modular architecture
|
|
4
|
+
*/
|
|
5
|
+
|
|
3
6
|
import { createServer } from 'http';
|
|
4
|
-
import {
|
|
7
|
+
import { join, dirname } from 'path';
|
|
5
8
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, cpSync } from 'fs';
|
|
10
|
+
import * as fs from 'fs';
|
|
8
11
|
import { homedir } from 'os';
|
|
9
12
|
|
|
13
|
+
// Import our modular components
|
|
14
|
+
import ConfigurationManager from './lib/configuration-manager.js';
|
|
15
|
+
import FileSystemManager from './lib/file-system-manager.js';
|
|
16
|
+
import BundleManager from './lib/bundle-manager.js';
|
|
17
|
+
import APIRouter from './lib/api-router.js';
|
|
18
|
+
import WebSocketManager from './lib/websocket-manager.js';
|
|
19
|
+
|
|
20
|
+
// Import existing lib modules
|
|
21
|
+
import { startMCPTransport } from './lib/mcp-transport.js';
|
|
22
|
+
import SemanticSplitter from './lib/semantic-splitter.js';
|
|
23
|
+
import SimpleVectorStore from './lib/simple-vector-store.js';
|
|
24
|
+
import { MCPServer } from './lib/mcp-server.js';
|
|
25
|
+
|
|
10
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
27
|
|
|
28
|
+
// Utility function for content types
|
|
12
29
|
function getContentType(filePath) {
|
|
13
|
-
const ext =
|
|
30
|
+
const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
|
|
14
31
|
const contentTypes = {
|
|
15
32
|
'.html': 'text/html',
|
|
16
33
|
'.js': 'application/javascript',
|
|
@@ -29,1895 +46,847 @@ export class CntxServer {
|
|
|
29
46
|
constructor(cwd = process.cwd(), options = {}) {
|
|
30
47
|
this.CWD = cwd;
|
|
31
48
|
this.CNTX_DIR = join(cwd, '.cntx');
|
|
32
|
-
this.
|
|
33
|
-
this.CONFIG_FILE = join(this.CNTX_DIR, 'config.json');
|
|
34
|
-
this.BUNDLES_FILE = join(this.CNTX_DIR, 'bundles.json');
|
|
35
|
-
this.HIDDEN_FILES_CONFIG = join(this.CNTX_DIR, 'hidden-files.json');
|
|
36
|
-
this.IGNORE_FILE = join(cwd, '.cntxignore');
|
|
37
|
-
this.CURSOR_RULES_FILE = join(cwd, '.cursorrules');
|
|
38
|
-
this.CLAUDE_MD_FILE = join(cwd, 'CLAUDE.md');
|
|
39
|
-
|
|
40
|
-
this.bundles = new Map();
|
|
41
|
-
this.ignorePatterns = [];
|
|
42
|
-
this.watchers = [];
|
|
43
|
-
this.clients = new Set();
|
|
44
|
-
this.isScanning = false;
|
|
45
|
-
this.mcpServer = null;
|
|
49
|
+
this.verbose = options.verbose || false;
|
|
46
50
|
this.mcpServerStarted = false;
|
|
51
|
+
this.mcpServer = null;
|
|
52
|
+
this.initMessages = []; // Track initialization messages
|
|
53
|
+
|
|
54
|
+
// Initialize modular components
|
|
55
|
+
this.configManager = new ConfigurationManager(cwd, { verbose: this.verbose });
|
|
56
|
+
this.fileSystemManager = new FileSystemManager(cwd, { verbose: this.verbose });
|
|
57
|
+
this.bundleManager = new BundleManager(this.configManager, this.fileSystemManager, this.verbose);
|
|
58
|
+
this.webSocketManager = new WebSocketManager(this.bundleManager, this.configManager, { verbose: this.verbose });
|
|
59
|
+
|
|
60
|
+
// Initialize semantic analysis components
|
|
61
|
+
this.semanticSplitter = new SemanticSplitter({
|
|
62
|
+
maxChunkSize: 2000,
|
|
63
|
+
includeContext: true,
|
|
64
|
+
groupRelated: true,
|
|
65
|
+
minFunctionSize: 50
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.vectorStore = new SimpleVectorStore({
|
|
69
|
+
modelName: 'Xenova/all-MiniLM-L6-v2',
|
|
70
|
+
collectionName: 'code-chunks'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.semanticCache = null;
|
|
74
|
+
this.lastSemanticAnalysis = null;
|
|
75
|
+
this.vectorStoreInitialized = false;
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
// Create semantic analysis manager object for API router
|
|
78
|
+
this.semanticAnalysisManager = {
|
|
79
|
+
getSemanticAnalysis: () => this.getSemanticAnalysis(),
|
|
80
|
+
refreshSemanticAnalysis: () => this.refreshSemanticAnalysis(),
|
|
81
|
+
exportSemanticChunk: (chunkName) => this.exportSemanticChunk(chunkName),
|
|
82
|
+
lastSemanticAnalysis: this.lastSemanticAnalysis
|
|
53
83
|
};
|
|
54
|
-
}
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.startWatching();
|
|
63
|
-
this.generateAllBundles();
|
|
64
|
-
}
|
|
85
|
+
// Create activity manager placeholder
|
|
86
|
+
this.activityManager = {
|
|
87
|
+
loadActivities: () => this.loadActivities(),
|
|
88
|
+
executeActivity: (id) => this.executeActivity(id),
|
|
89
|
+
stopActivity: (id) => this.stopActivity(id)
|
|
90
|
+
};
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
files: [],
|
|
76
|
-
content: '',
|
|
77
|
-
changed: false,
|
|
78
|
-
lastGenerated: null,
|
|
79
|
-
size: 0
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
92
|
+
// Initialize API router with all managers
|
|
93
|
+
this.apiRouter = new APIRouter(
|
|
94
|
+
this.configManager,
|
|
95
|
+
this.bundleManager,
|
|
96
|
+
this.fileSystemManager,
|
|
97
|
+
this.semanticAnalysisManager,
|
|
98
|
+
this.vectorStore,
|
|
99
|
+
this.activityManager
|
|
100
|
+
);
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
content: '',
|
|
89
|
-
changed: false,
|
|
90
|
-
lastGenerated: null,
|
|
91
|
-
size: 0
|
|
92
|
-
});
|
|
93
|
-
}
|
|
102
|
+
// Add references for cross-module communication
|
|
103
|
+
this.bundleManager.fileSystemManager = this.fileSystemManager;
|
|
104
|
+
this.bundleManager.webSocketManager = this.webSocketManager;
|
|
105
|
+
this.apiRouter.mcpServerStarted = this.mcpServerStarted;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
// Progress bar utility
|
|
109
|
+
async showProgressBar(message, minTime = 500) {
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
112
|
+
let frameIndex = 0;
|
|
113
|
+
|
|
114
|
+
const interval = setInterval(() => {
|
|
115
|
+
process.stdout.write(`\r${frames[frameIndex]} ${message}`);
|
|
116
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
117
|
+
}, 80);
|
|
118
|
+
|
|
119
|
+
return () => {
|
|
120
|
+
clearInterval(interval);
|
|
121
|
+
const elapsed = Date.now() - startTime;
|
|
122
|
+
const remaining = Math.max(0, minTime - elapsed);
|
|
123
|
+
|
|
124
|
+
if (remaining > 0) {
|
|
125
|
+
return new Promise(resolve => setTimeout(resolve, remaining));
|
|
103
126
|
}
|
|
104
|
-
|
|
127
|
+
return Promise.resolve();
|
|
128
|
+
};
|
|
105
129
|
}
|
|
106
130
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!this.isQuietMode) console.error('Failed to save hidden files config:', e.message);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
131
|
+
// Single progress bar for initialization
|
|
132
|
+
async showInitProgress(steps) {
|
|
133
|
+
const totalSteps = steps.length;
|
|
134
|
+
let currentStep = 0;
|
|
114
135
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
const updateProgress = (stepName, completed = false) => {
|
|
137
|
+
const progress = Math.round((currentStep / totalSteps) * 100);
|
|
138
|
+
const barLength = 30;
|
|
139
|
+
const filledLength = Math.round((progress / 100) * barLength);
|
|
140
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
120
141
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
142
|
+
// Clear the line and show progress
|
|
143
|
+
process.stdout.write(`\r[${bar}] ${progress}% - ${stepName}${' '.repeat(20)}`);
|
|
144
|
+
};
|
|
125
145
|
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
// Initialize progress bar
|
|
147
|
+
updateProgress(steps[0]);
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.hiddenFilesConfig.bundleSpecific[bundleName] = [];
|
|
134
|
-
}
|
|
149
|
+
return {
|
|
150
|
+
next: (stepName, minTime = 800) => {
|
|
151
|
+
return new Promise(async (resolve) => {
|
|
152
|
+
const startTime = Date.now();
|
|
135
153
|
|
|
136
|
-
|
|
137
|
-
|
|
154
|
+
// Move to next step
|
|
155
|
+
currentStep++;
|
|
138
156
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
|
|
143
|
-
} else {
|
|
144
|
-
bundleHidden.push(filePath);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
// Force to specific state
|
|
148
|
-
if (forceHide && !isCurrentlyHidden) {
|
|
149
|
-
bundleHidden.push(filePath);
|
|
150
|
-
} else if (!forceHide && isCurrentlyHidden) {
|
|
151
|
-
this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
// Global hiding
|
|
156
|
-
const isCurrentlyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
|
|
157
|
-
|
|
158
|
-
if (forceHide === null) {
|
|
159
|
-
// Toggle current state
|
|
160
|
-
if (isCurrentlyHidden) {
|
|
161
|
-
this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
|
|
162
|
-
} else {
|
|
163
|
-
this.hiddenFilesConfig.globalHidden.push(filePath);
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
// Force to specific state
|
|
167
|
-
if (forceHide && !isCurrentlyHidden) {
|
|
168
|
-
this.hiddenFilesConfig.globalHidden.push(filePath);
|
|
169
|
-
} else if (!forceHide && isCurrentlyHidden) {
|
|
170
|
-
this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
157
|
+
if (currentStep < totalSteps) {
|
|
158
|
+
updateProgress(steps[currentStep]);
|
|
159
|
+
}
|
|
174
160
|
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
// Add random delay between 200-800ms on top of minimum time
|
|
162
|
+
const randomDelay = Math.floor(Math.random() * 600) + 200;
|
|
163
|
+
const totalDelay = minTime + randomDelay;
|
|
177
164
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
165
|
+
// Wait minimum time + random delay
|
|
166
|
+
const elapsed = Date.now() - startTime;
|
|
167
|
+
const remaining = Math.max(0, totalDelay - elapsed);
|
|
168
|
+
if (remaining > 0) {
|
|
169
|
+
await new Promise(resolve => setTimeout(resolve, remaining));
|
|
170
|
+
}
|
|
183
171
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
172
|
+
resolve();
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
complete: () => {
|
|
176
|
+
const progress = 100;
|
|
177
|
+
const barLength = 30;
|
|
178
|
+
const bar = '█'.repeat(barLength);
|
|
179
|
+
process.stdout.write(`\r[${bar}] ${progress}% - Complete${' '.repeat(20)}\n`);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
193
182
|
}
|
|
194
183
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
this.
|
|
199
|
-
this.saveHiddenFilesConfig();
|
|
200
|
-
this.loadIgnorePatterns();
|
|
201
|
-
this.generateAllBundles();
|
|
202
|
-
return true;
|
|
184
|
+
// Helper method to add initialization messages
|
|
185
|
+
addInitMessage(message) {
|
|
186
|
+
if (this.verbose) {
|
|
187
|
+
this.initMessages.push(message);
|
|
203
188
|
}
|
|
204
|
-
return false;
|
|
205
189
|
}
|
|
206
190
|
|
|
207
|
-
|
|
208
|
-
const index = this.hiddenFilesConfig.disabledSystemPatterns.indexOf(pattern);
|
|
209
|
-
if (index > -1) {
|
|
210
|
-
// Re-enable the pattern
|
|
211
|
-
this.hiddenFilesConfig.disabledSystemPatterns.splice(index, 1);
|
|
212
|
-
} else {
|
|
213
|
-
// Disable the pattern
|
|
214
|
-
this.hiddenFilesConfig.disabledSystemPatterns.push(pattern);
|
|
215
|
-
}
|
|
191
|
+
// === Initialization ===
|
|
216
192
|
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
this.generateAllBundles();
|
|
220
|
-
}
|
|
193
|
+
async init(options = {}) {
|
|
194
|
+
if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
|
|
221
195
|
|
|
222
|
-
|
|
223
|
-
const systemPatterns = [
|
|
224
|
-
// Version control
|
|
225
|
-
'**/.git/**',
|
|
226
|
-
'**/.svn/**',
|
|
227
|
-
'**/.hg/**',
|
|
228
|
-
|
|
229
|
-
// Dependencies
|
|
230
|
-
'**/node_modules/**',
|
|
231
|
-
'**/vendor/**',
|
|
232
|
-
'**/.pnp/**',
|
|
233
|
-
|
|
234
|
-
// Build outputs
|
|
235
|
-
'**/dist/**',
|
|
236
|
-
'**/build/**',
|
|
237
|
-
'**/out/**',
|
|
238
|
-
'**/.next/**',
|
|
239
|
-
'**/.nuxt/**',
|
|
240
|
-
'**/target/**',
|
|
241
|
-
|
|
242
|
-
// Package files
|
|
243
|
-
'**/*.tgz',
|
|
244
|
-
'**/*.tar.gz',
|
|
245
|
-
'**/*.zip',
|
|
246
|
-
'**/*.rar',
|
|
247
|
-
'**/*.7z',
|
|
248
|
-
|
|
249
|
-
// Logs
|
|
250
|
-
'**/*.log',
|
|
251
|
-
'**/logs/**',
|
|
252
|
-
|
|
253
|
-
// Cache directories
|
|
254
|
-
'**/.cache/**',
|
|
255
|
-
'**/.parcel-cache/**',
|
|
256
|
-
'**/.nyc_output/**',
|
|
257
|
-
'**/coverage/**',
|
|
258
|
-
'**/.pytest_cache/**',
|
|
259
|
-
'**/__pycache__/**',
|
|
260
|
-
|
|
261
|
-
// IDE/Editor files
|
|
262
|
-
'**/.vscode/**',
|
|
263
|
-
'**/.idea/**',
|
|
264
|
-
'**/*.swp',
|
|
265
|
-
'**/*.swo',
|
|
266
|
-
'**/*~',
|
|
267
|
-
|
|
268
|
-
// OS files
|
|
269
|
-
'**/.DS_Store',
|
|
270
|
-
'**/Thumbs.db',
|
|
271
|
-
'**/desktop.ini',
|
|
272
|
-
|
|
273
|
-
// Environment files
|
|
274
|
-
'**/.env',
|
|
275
|
-
'**/.env.local',
|
|
276
|
-
'**/.env.*.local',
|
|
277
|
-
|
|
278
|
-
// Lock files
|
|
279
|
-
'**/package-lock.json',
|
|
280
|
-
'**/yarn.lock',
|
|
281
|
-
'**/pnpm-lock.yaml',
|
|
282
|
-
'**/Cargo.lock',
|
|
283
|
-
|
|
284
|
-
// cntx-ui specific
|
|
285
|
-
'**/.cntx/**'
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
// Read from .cntxignore file
|
|
289
|
-
let filePatterns = [];
|
|
290
|
-
if (existsSync(this.IGNORE_FILE)) {
|
|
291
|
-
filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
|
|
292
|
-
.split('\n')
|
|
293
|
-
.map(line => line.trim())
|
|
294
|
-
.filter(line => line && !line.startsWith('#'));
|
|
295
|
-
}
|
|
196
|
+
const { skipFileWatcher = false, skipBundleGeneration = false } = options;
|
|
296
197
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
...systemPatterns.filter(pattern =>
|
|
301
|
-
!this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
|
|
302
|
-
),
|
|
303
|
-
// File patterns
|
|
304
|
-
...filePatterns.filter(pattern =>
|
|
305
|
-
!systemPatterns.includes(pattern) &&
|
|
306
|
-
!this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
|
|
307
|
-
),
|
|
308
|
-
// User-added patterns
|
|
309
|
-
...this.hiddenFilesConfig.userIgnorePatterns
|
|
310
|
-
];
|
|
311
|
-
|
|
312
|
-
// Update .cntxignore file with current patterns
|
|
313
|
-
const allPatterns = [
|
|
314
|
-
'# System patterns',
|
|
315
|
-
...systemPatterns.map(pattern =>
|
|
316
|
-
this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
|
|
317
|
-
? `# ${pattern}`
|
|
318
|
-
: pattern
|
|
319
|
-
),
|
|
320
|
-
'',
|
|
321
|
-
'# User patterns',
|
|
322
|
-
...this.hiddenFilesConfig.userIgnorePatterns,
|
|
323
|
-
'',
|
|
324
|
-
'# File-specific patterns (edit manually)',
|
|
325
|
-
...filePatterns.filter(pattern =>
|
|
326
|
-
!systemPatterns.includes(pattern) &&
|
|
327
|
-
!this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
|
|
328
|
-
)
|
|
329
|
-
];
|
|
330
|
-
|
|
331
|
-
writeFileSync(this.IGNORE_FILE, allPatterns.join('\n'));
|
|
332
|
-
}
|
|
198
|
+
const steps = skipFileWatcher
|
|
199
|
+
? ['Loading configuration', 'Loading semantic cache']
|
|
200
|
+
: ['Loading configuration', 'Setting up file watcher', 'Loading semantic cache', 'Starting file watcher', 'Generating bundles'];
|
|
333
201
|
|
|
334
|
-
|
|
335
|
-
if (existsSync(this.BUNDLES_FILE)) {
|
|
336
|
-
try {
|
|
337
|
-
const savedBundles = JSON.parse(readFileSync(this.BUNDLES_FILE, 'utf8'));
|
|
338
|
-
Object.entries(savedBundles).forEach(([name, data]) => {
|
|
339
|
-
if (this.bundles.has(name)) {
|
|
340
|
-
const bundle = this.bundles.get(name);
|
|
341
|
-
bundle.content = data.content || '';
|
|
342
|
-
bundle.lastGenerated = data.lastGenerated;
|
|
343
|
-
bundle.size = data.size || 0;
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
} catch (e) {
|
|
347
|
-
if (!this.isQuietMode) console.warn('Could not load bundle states:', e.message);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
202
|
+
const progress = await this.showInitProgress(steps);
|
|
351
203
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
size: bundle.size
|
|
359
|
-
};
|
|
360
|
-
});
|
|
361
|
-
writeFileSync(this.BUNDLES_FILE, JSON.stringify(bundleStates, null, 2));
|
|
362
|
-
}
|
|
204
|
+
// Step 1: Loading configuration
|
|
205
|
+
this.configManager.loadConfig();
|
|
206
|
+
this.configManager.loadHiddenFilesConfig();
|
|
207
|
+
this.configManager.loadIgnorePatterns();
|
|
208
|
+
this.configManager.loadBundleStates();
|
|
209
|
+
await progress.next(steps[0], 800);
|
|
363
210
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
211
|
+
if (!skipFileWatcher) {
|
|
212
|
+
// Step 2: Setting up file watcher
|
|
213
|
+
this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
|
|
214
|
+
await progress.next(steps[1], 400);
|
|
368
215
|
}
|
|
369
|
-
return this.getDefaultCursorRules();
|
|
370
|
-
}
|
|
371
216
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
217
|
+
// Step 3: Loading semantic cache
|
|
218
|
+
const cacheData = this.configManager.loadSemanticCache();
|
|
219
|
+
if (cacheData) {
|
|
220
|
+
this.semanticCache = cacheData.analysis;
|
|
221
|
+
this.lastSemanticAnalysis = cacheData.timestamp;
|
|
222
|
+
}
|
|
223
|
+
await progress.next(skipFileWatcher ? steps[1] : steps[2], 800);
|
|
376
224
|
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// Use defaults
|
|
225
|
+
if (!skipFileWatcher) {
|
|
226
|
+
// Step 4: Starting file watcher
|
|
227
|
+
this.startWatching();
|
|
228
|
+
await progress.next(steps[3], 600);
|
|
229
|
+
|
|
230
|
+
// Step 5: Generating bundles
|
|
231
|
+
if (!skipBundleGeneration) {
|
|
232
|
+
this.bundleManager.generateAllBundles();
|
|
233
|
+
await progress.next(steps[4], 1200);
|
|
387
234
|
}
|
|
388
235
|
}
|
|
389
236
|
|
|
390
|
-
|
|
237
|
+
// Complete progress bar
|
|
238
|
+
progress.complete();
|
|
391
239
|
}
|
|
392
240
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (deps.react || deps['@types/react']) return 'react';
|
|
397
|
-
if (deps.vue || deps['@vue/cli']) return 'vue';
|
|
398
|
-
if (deps.angular || deps['@angular/core']) return 'angular';
|
|
399
|
-
if (deps.express || deps.fastify || deps.koa) return 'node';
|
|
400
|
-
if (deps.next || deps.nuxt || deps.gatsby) return 'fullstack';
|
|
401
|
-
if (deps.typescript || deps['@types/node']) return 'typescript';
|
|
402
|
-
if (pkg.type === 'module' || deps.vite || deps.webpack) return 'modern-js';
|
|
241
|
+
// Display initialization summary
|
|
242
|
+
displayInitSummary() {
|
|
243
|
+
const summary = [];
|
|
403
244
|
|
|
404
|
-
|
|
405
|
-
|
|
245
|
+
// Add semantic cache info
|
|
246
|
+
if (this.semanticCache) {
|
|
247
|
+
summary.push(`Loaded semantic cache (${this.semanticCache.chunks.length} chunks with embeddings)`);
|
|
248
|
+
}
|
|
406
249
|
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
## Project Context
|
|
414
|
-
- **Project**: ${projectInfo.name}
|
|
415
|
-
- **Type**: React Application
|
|
416
|
-
- **Description**: ${projectInfo.description}
|
|
417
|
-
|
|
418
|
-
## Development Guidelines
|
|
419
|
-
|
|
420
|
-
### Code Style
|
|
421
|
-
- Use TypeScript for all new components
|
|
422
|
-
- Prefer functional components with hooks
|
|
423
|
-
- Use Tailwind CSS for styling
|
|
424
|
-
- Follow React best practices and hooks rules
|
|
425
|
-
|
|
426
|
-
### File Organization
|
|
427
|
-
- Components in \`src/components/\`
|
|
428
|
-
- Custom hooks in \`src/hooks/\`
|
|
429
|
-
- Utilities in \`src/lib/\`
|
|
430
|
-
- Types in \`src/types/\`
|
|
431
|
-
|
|
432
|
-
### Naming Conventions
|
|
433
|
-
- PascalCase for components
|
|
434
|
-
- camelCase for functions and variables
|
|
435
|
-
- kebab-case for files and folders
|
|
436
|
-
- Use descriptive, meaningful names
|
|
437
|
-
|
|
438
|
-
### Bundle Context
|
|
439
|
-
This project uses cntx-ui for file bundling. Current bundles: ${bundlesList}
|
|
440
|
-
- **ui**: React components and styles
|
|
441
|
-
- **api**: API routes and utilities
|
|
442
|
-
- **config**: Configuration files
|
|
443
|
-
- **docs**: Documentation
|
|
444
|
-
|
|
445
|
-
### AI Assistant Instructions
|
|
446
|
-
- When suggesting code changes, consider the current bundle structure
|
|
447
|
-
- Prioritize TypeScript and modern React patterns
|
|
448
|
-
- Suggest Tailwind classes for styling
|
|
449
|
-
- Keep components focused and reusable
|
|
450
|
-
- Always include proper TypeScript types
|
|
451
|
-
- Consider bundle organization when suggesting file locations
|
|
452
|
-
|
|
453
|
-
## Custom Rules
|
|
454
|
-
Add your specific project rules and preferences below:
|
|
455
|
-
|
|
456
|
-
### Team Preferences
|
|
457
|
-
- [Add team coding standards]
|
|
458
|
-
- [Add preferred libraries/frameworks]
|
|
459
|
-
- [Add project-specific guidelines]
|
|
460
|
-
|
|
461
|
-
### Architecture Notes
|
|
462
|
-
- [Document key architectural decisions]
|
|
463
|
-
- [Note important patterns to follow]
|
|
464
|
-
- [List critical dependencies]
|
|
465
|
-
`,
|
|
466
|
-
|
|
467
|
-
node: `# ${projectInfo.name} - Node.js Project Rules
|
|
468
|
-
|
|
469
|
-
## Project Context
|
|
470
|
-
- **Project**: ${projectInfo.name}
|
|
471
|
-
- **Type**: Node.js Backend
|
|
472
|
-
- **Description**: ${projectInfo.description}
|
|
473
|
-
|
|
474
|
-
## Development Guidelines
|
|
475
|
-
|
|
476
|
-
### Code Style
|
|
477
|
-
- Use ES modules (import/export)
|
|
478
|
-
- TypeScript preferred for type safety
|
|
479
|
-
- Follow Node.js best practices
|
|
480
|
-
- Use async/await over promises
|
|
481
|
-
|
|
482
|
-
### File Organization
|
|
483
|
-
- Routes in \`src/routes/\`
|
|
484
|
-
- Middleware in \`src/middleware/\`
|
|
485
|
-
- Models in \`src/models/\`
|
|
486
|
-
- Utilities in \`src/utils/\`
|
|
487
|
-
|
|
488
|
-
### Bundle Context
|
|
489
|
-
This project uses cntx-ui for file bundling. Current bundles: ${bundlesList}
|
|
490
|
-
- **api**: Core API logic and routes
|
|
491
|
-
- **config**: Environment and configuration
|
|
492
|
-
- **docs**: API documentation
|
|
493
|
-
|
|
494
|
-
### AI Assistant Instructions
|
|
495
|
-
- Focus on scalable backend architecture
|
|
496
|
-
- Suggest proper error handling
|
|
497
|
-
- Consider security best practices
|
|
498
|
-
- Optimize for performance and maintainability
|
|
499
|
-
- Consider bundle organization when suggesting file locations
|
|
500
|
-
|
|
501
|
-
## Custom Rules
|
|
502
|
-
Add your specific project rules and preferences below:
|
|
503
|
-
|
|
504
|
-
### Team Preferences
|
|
505
|
-
- [Add team coding standards]
|
|
506
|
-
- [Add preferred libraries/frameworks]
|
|
507
|
-
- [Add project-specific guidelines]
|
|
508
|
-
|
|
509
|
-
### Architecture Notes
|
|
510
|
-
- [Document key architectural decisions]
|
|
511
|
-
- [Note important patterns to follow]
|
|
512
|
-
- [List critical dependencies]
|
|
513
|
-
`,
|
|
514
|
-
|
|
515
|
-
general: `# ${projectInfo.name} - Project Rules
|
|
516
|
-
|
|
517
|
-
## Project Context
|
|
518
|
-
- **Project**: ${projectInfo.name}
|
|
519
|
-
- **Description**: ${projectInfo.description}
|
|
520
|
-
|
|
521
|
-
## Development Guidelines
|
|
522
|
-
|
|
523
|
-
### Code Quality
|
|
524
|
-
- Write clean, readable code
|
|
525
|
-
- Follow consistent naming conventions
|
|
526
|
-
- Add comments for complex logic
|
|
527
|
-
- Maintain proper file organization
|
|
528
|
-
|
|
529
|
-
### Bundle Management
|
|
530
|
-
This project uses cntx-ui for intelligent file bundling. Current bundles: ${bundlesList}
|
|
531
|
-
- **master**: Complete project overview
|
|
532
|
-
- **config**: Configuration and setup files
|
|
533
|
-
- **docs**: Documentation and README files
|
|
534
|
-
|
|
535
|
-
### AI Assistant Instructions
|
|
536
|
-
- When helping with code, consider the project structure
|
|
537
|
-
- Suggest improvements for maintainability
|
|
538
|
-
- Follow established patterns in the codebase
|
|
539
|
-
- Help optimize bundle configurations when needed
|
|
540
|
-
- Consider bundle organization when suggesting file locations
|
|
541
|
-
|
|
542
|
-
## Custom Rules
|
|
543
|
-
Add your specific project rules and preferences below:
|
|
544
|
-
|
|
545
|
-
### Team Preferences
|
|
546
|
-
- [Add team coding standards]
|
|
547
|
-
- [Add preferred libraries/frameworks]
|
|
548
|
-
- [Add project-specific guidelines]
|
|
549
|
-
|
|
550
|
-
### Architecture Notes
|
|
551
|
-
- [Document key architectural decisions]
|
|
552
|
-
- [Note important patterns to follow]
|
|
553
|
-
- [List critical dependencies]
|
|
554
|
-
`
|
|
555
|
-
};
|
|
250
|
+
// Add ignore patterns info
|
|
251
|
+
const ignorePatterns = this.configManager.getIgnorePatterns();
|
|
252
|
+
if (ignorePatterns.length > 0) {
|
|
253
|
+
summary.push(`Loaded ${ignorePatterns.length} ignore patterns`);
|
|
254
|
+
}
|
|
556
255
|
|
|
557
|
-
|
|
558
|
-
|
|
256
|
+
// Add bundle info
|
|
257
|
+
const bundles = this.bundleManager.getAllBundleInfo();
|
|
258
|
+
if (bundles.length > 0) {
|
|
259
|
+
summary.push(`Generated ${bundles.length} bundles`);
|
|
260
|
+
}
|
|
559
261
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
262
|
+
// Add file watcher info
|
|
263
|
+
summary.push('File watcher started');
|
|
264
|
+
summary.push('WebSocket server initialized');
|
|
563
265
|
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
|
|
266
|
+
// Display summary
|
|
267
|
+
if (summary.length > 0) {
|
|
268
|
+
console.log('Initialization complete:');
|
|
269
|
+
summary.forEach(msg => console.log(` • ${msg}`));
|
|
270
|
+
console.log('');
|
|
567
271
|
}
|
|
568
|
-
return this.getDefaultClaudeMd();
|
|
569
272
|
}
|
|
570
273
|
|
|
571
|
-
|
|
572
|
-
// Get project info for context
|
|
573
|
-
let projectInfo = { name: 'unknown', description: '', type: 'general' };
|
|
574
|
-
const pkgPath = join(this.CWD, 'package.json');
|
|
274
|
+
// === File Watching ===
|
|
575
275
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
name: pkg.name || 'unknown',
|
|
581
|
-
description: pkg.description || '',
|
|
582
|
-
type: this.detectProjectType(pkg)
|
|
583
|
-
};
|
|
584
|
-
} catch (e) {
|
|
585
|
-
// Use defaults if package.json is invalid
|
|
276
|
+
startWatching() {
|
|
277
|
+
this.fileSystemManager.startWatching(async (eventType, filename) => {
|
|
278
|
+
if (this.verbose) {
|
|
279
|
+
console.log(`📁 File ${eventType}: ${filename}`);
|
|
586
280
|
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return this.generateClaudeMdTemplate(projectInfo);
|
|
590
|
-
}
|
|
591
281
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
282
|
+
// Skip processing files in .cntx directory to prevent infinite loops
|
|
283
|
+
if (filename.startsWith('.cntx/')) {
|
|
284
|
+
if (this.verbose) {
|
|
285
|
+
console.log(`📁 Skipping .cntx file: ${filename}`);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
596
289
|
|
|
597
|
-
|
|
290
|
+
// Mark affected bundles as changed
|
|
291
|
+
this.bundleManager.markBundlesChanged(filename);
|
|
598
292
|
|
|
599
|
-
|
|
293
|
+
// Invalidate semantic cache if needed
|
|
294
|
+
this.invalidateSemanticCache();
|
|
600
295
|
|
|
601
|
-
|
|
296
|
+
// Notify WebSocket clients
|
|
297
|
+
this.webSocketManager.onFileChanged(filename, eventType);
|
|
602
298
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
template += `- **${bundleName}**: ${bundle.files.length} files\n`;
|
|
299
|
+
// Automatically regenerate affected bundles after a short delay
|
|
300
|
+
setTimeout(async () => {
|
|
301
|
+
await this.regenerateChangedBundles(filename);
|
|
302
|
+
}, 1000); // 1 second delay to batch multiple rapid changes
|
|
608
303
|
});
|
|
304
|
+
}
|
|
609
305
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
- Use TypeScript for type safety
|
|
615
|
-
- Write meaningful commit messages
|
|
616
|
-
- Test changes thoroughly
|
|
306
|
+
async regenerateChangedBundles(filename) {
|
|
307
|
+
try {
|
|
308
|
+
const bundles = this.configManager.getBundles();
|
|
309
|
+
const affectedBundles = [];
|
|
617
310
|
|
|
618
|
-
|
|
311
|
+
// Find which bundles are affected by this file
|
|
312
|
+
bundles.forEach((bundle, name) => {
|
|
313
|
+
const matchesBundle = bundle.patterns.some(pattern =>
|
|
314
|
+
this.fileSystemManager.matchesPattern(filename, pattern)
|
|
315
|
+
);
|
|
619
316
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (type === 'react') {
|
|
626
|
-
template += `
|
|
627
|
-
### React Specific
|
|
317
|
+
if (matchesBundle && bundle.changed) {
|
|
318
|
+
affectedBundles.push(name);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
628
321
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
322
|
+
// Regenerate each affected bundle
|
|
323
|
+
for (const bundleName of affectedBundles) {
|
|
324
|
+
if (this.verbose) {
|
|
325
|
+
console.log(`🔄 Auto-regenerating bundle: ${bundleName}`);
|
|
326
|
+
}
|
|
327
|
+
await this.bundleManager.regenerateBundle(bundleName);
|
|
328
|
+
}
|
|
636
329
|
|
|
637
|
-
|
|
638
|
-
-
|
|
639
|
-
- Proper error handling
|
|
640
|
-
`;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Failed to auto-regenerate bundles:', error.message);
|
|
641
332
|
}
|
|
642
|
-
|
|
643
|
-
return template;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
saveClaudeMd(content) {
|
|
647
|
-
writeFileSync(this.CLAUDE_MD_FILE, content, 'utf8');
|
|
648
333
|
}
|
|
649
334
|
|
|
650
|
-
|
|
651
|
-
const relativePath = relative(this.CWD, filePath).replace(/\\\\/g, '/');
|
|
335
|
+
// === HTTP Server ===
|
|
652
336
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (relativePath.startsWith('.git/')) return true;
|
|
656
|
-
if (relativePath.startsWith('.cntx/')) return true;
|
|
337
|
+
async handleRequest(req, res) {
|
|
338
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
657
339
|
|
|
658
|
-
|
|
659
|
-
|
|
340
|
+
// Add CORS headers for all requests
|
|
341
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
342
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
343
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
660
344
|
|
|
661
|
-
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
|
|
345
|
+
// Handle preflight requests
|
|
346
|
+
if (req.method === 'OPTIONS') {
|
|
347
|
+
res.writeHead(200);
|
|
348
|
+
res.end();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
665
351
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
.
|
|
669
|
-
|
|
352
|
+
try {
|
|
353
|
+
// Handle API routes
|
|
354
|
+
if (url.pathname.startsWith('/api/')) {
|
|
355
|
+
return await this.apiRouter.handleRequest(req, res, url);
|
|
356
|
+
}
|
|
670
357
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
regexPattern = regexPattern.replace(/DOUBLESTAR/g, '.*');
|
|
358
|
+
// Handle static files
|
|
359
|
+
return this.handleStaticFile(req, res, url);
|
|
674
360
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (!this.isQuietMode) console.log(`Regex error for pattern "${pattern}": ${e.message}`);
|
|
680
|
-
return false;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Request handling error:', error);
|
|
363
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
364
|
+
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
681
365
|
}
|
|
682
366
|
}
|
|
683
367
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
'node_modules',
|
|
688
|
-
'.git',
|
|
689
|
-
'.svn',
|
|
690
|
-
'.hg',
|
|
691
|
-
'vendor',
|
|
692
|
-
'__pycache__',
|
|
693
|
-
'.pytest_cache',
|
|
694
|
-
'.venv',
|
|
695
|
-
'venv',
|
|
696
|
-
'env',
|
|
697
|
-
'.env',
|
|
698
|
-
'dist',
|
|
699
|
-
'build',
|
|
700
|
-
'out',
|
|
701
|
-
'.next',
|
|
702
|
-
'.nuxt',
|
|
703
|
-
'coverage',
|
|
704
|
-
'.nyc_output',
|
|
705
|
-
'.cache',
|
|
706
|
-
'.parcel-cache',
|
|
707
|
-
'.vercel',
|
|
708
|
-
'.netlify',
|
|
709
|
-
'tmp',
|
|
710
|
-
'temp',
|
|
711
|
-
'.tmp',
|
|
712
|
-
'.temp',
|
|
713
|
-
'logs',
|
|
714
|
-
'*.egg-info',
|
|
715
|
-
'.cntx'
|
|
716
|
-
];
|
|
717
|
-
|
|
718
|
-
if (badDirectories.includes(itemName)) {
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
368
|
+
handleStaticFile(req, res, url) {
|
|
369
|
+
const webDir = join(__dirname, 'web', 'dist');
|
|
370
|
+
let filePath = join(webDir, url.pathname === '/' ? 'index.html' : url.pathname);
|
|
721
371
|
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
'.DS_Store', '.Thumbs.db', 'desktop.ini',
|
|
728
|
-
// Editor files
|
|
729
|
-
'.vscode', '.idea', '*.swp', '*.swo', '*~',
|
|
730
|
-
// Media files (large and useless for AI)
|
|
731
|
-
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg',
|
|
732
|
-
'.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv',
|
|
733
|
-
'.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma',
|
|
734
|
-
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
735
|
-
'.zip', '.tar', '.gz', '.rar', '.7z', '.bz2',
|
|
736
|
-
'.exe', '.dll', '.so', '.dylib', '.app', '.dmg', '.pkg',
|
|
737
|
-
// Cache/temp files
|
|
738
|
-
'.cache', '.tmp', '.temp', '.lock',
|
|
739
|
-
// Compiled files
|
|
740
|
-
'.pyc', '.pyo', '.class', '.o', '.obj', '.a', '.lib'
|
|
741
|
-
];
|
|
742
|
-
|
|
743
|
-
const fileExt = extname(itemName).toLowerCase();
|
|
744
|
-
if (badExtensions.includes(fileExt)) {
|
|
745
|
-
return true;
|
|
372
|
+
// Security check - ensure path is within web directory
|
|
373
|
+
if (!filePath.startsWith(webDir)) {
|
|
374
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
375
|
+
res.end('Forbidden');
|
|
376
|
+
return;
|
|
746
377
|
}
|
|
747
378
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
/pnpm-lock\.yaml$/,
|
|
758
|
-
/Thumbs\.db$/,
|
|
759
|
-
/\.DS_Store$/
|
|
760
|
-
];
|
|
761
|
-
|
|
762
|
-
if (badFilePatterns.some(pattern => pattern.test(itemName))) {
|
|
763
|
-
return true;
|
|
379
|
+
if (!existsSync(filePath)) {
|
|
380
|
+
// For SPA routing, serve index.html for non-API routes
|
|
381
|
+
if (!url.pathname.startsWith('/api/')) {
|
|
382
|
+
filePath = join(webDir, 'index.html');
|
|
383
|
+
} else {
|
|
384
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
385
|
+
res.end('Not Found');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
764
388
|
}
|
|
765
389
|
|
|
766
|
-
// PATH-BASED IGNORES (from your .cntxignore)
|
|
767
|
-
return this.ignorePatterns.some(pattern => this.matchesPattern(fullPath, pattern));
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
getAllFiles(dir = this.CWD, files = []) {
|
|
771
390
|
try {
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
const fullPath = join(dir, item);
|
|
775
|
-
const relativePath = relative(this.CWD, fullPath).replace(/\\\\/g, '/');
|
|
391
|
+
const content = readFileSync(filePath);
|
|
392
|
+
const contentType = getContentType(filePath);
|
|
776
393
|
|
|
777
|
-
|
|
778
|
-
|
|
394
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
395
|
+
res.end(content);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
398
|
+
res.end('Error reading file');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
779
401
|
|
|
780
|
-
|
|
781
|
-
continue; // Don't even log it, just skip
|
|
782
|
-
}
|
|
402
|
+
// === Semantic Analysis (Legacy methods for compatibility) ===
|
|
783
403
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
404
|
+
async getSemanticAnalysis() {
|
|
405
|
+
// First, try to load from cache
|
|
406
|
+
if (!this.semanticCache) {
|
|
407
|
+
const cacheData = this.configManager.loadSemanticCache();
|
|
408
|
+
if (cacheData) {
|
|
409
|
+
this.semanticCache = cacheData.analysis;
|
|
410
|
+
this.lastSemanticAnalysis = cacheData.timestamp;
|
|
411
|
+
return cacheData.analysis;
|
|
790
412
|
}
|
|
791
|
-
} catch (e) {
|
|
792
|
-
// Skip directories we can't read
|
|
793
413
|
}
|
|
794
414
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
startWatching() {
|
|
799
|
-
const watcher = watch(this.CWD, { recursive: true }, (eventType, filename) => {
|
|
800
|
-
if (filename && !this.isScanning) {
|
|
801
|
-
const fullPath = join(this.CWD, filename);
|
|
802
|
-
if (!this.shouldIgnoreFile(fullPath)) {
|
|
803
|
-
if (!this.isQuietMode) console.log(`File ${eventType}: ${filename}`);
|
|
804
|
-
this.markBundlesChanged(filename.replace(/\\\\/g, '/'));
|
|
805
|
-
this.broadcastUpdate();
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
this.watchers.push(watcher);
|
|
810
|
-
}
|
|
415
|
+
// Check if we need to refresh the semantic analysis
|
|
416
|
+
const shouldRefresh = !this.semanticCache || !this.lastSemanticAnalysis;
|
|
811
417
|
|
|
812
|
-
|
|
813
|
-
const allFiles = this.getAllFiles();
|
|
814
|
-
const fileData = allFiles.map(file => {
|
|
815
|
-
const fullPath = join(this.CWD, file);
|
|
418
|
+
if (shouldRefresh) {
|
|
816
419
|
try {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
path: file,
|
|
820
|
-
size: stat.size,
|
|
821
|
-
modified: stat.mtime
|
|
822
|
-
};
|
|
823
|
-
} catch (e) {
|
|
824
|
-
return {
|
|
825
|
-
path: file,
|
|
826
|
-
size: 0,
|
|
827
|
-
modified: new Date()
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
return fileData;
|
|
832
|
-
}
|
|
420
|
+
// Auto-discover JavaScript/TypeScript files in the entire project
|
|
421
|
+
const patterns = ['**/*.{js,jsx,ts,tsx,mjs}'];
|
|
833
422
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
});
|
|
840
|
-
}
|
|
423
|
+
// Load bundle configuration for chunk grouping
|
|
424
|
+
let bundleConfig = null;
|
|
425
|
+
if (existsSync(this.configManager.CONFIG_FILE)) {
|
|
426
|
+
bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
|
|
427
|
+
}
|
|
841
428
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
if (!this.isQuietMode) console.log('Scanning files and generating bundles...');
|
|
429
|
+
this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, patterns, bundleConfig);
|
|
430
|
+
this.lastSemanticAnalysis = Date.now();
|
|
845
431
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
});
|
|
432
|
+
// Only enhance chunks with embeddings if they don't already have them
|
|
433
|
+
await this.enhanceSemanticChunksIfNeeded(this.semanticCache);
|
|
849
434
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (!this.isQuietMode) console.log('Bundle generation complete');
|
|
853
|
-
}
|
|
435
|
+
// Save to disk cache
|
|
436
|
+
this.configManager.saveSemanticCache(this.semanticCache);
|
|
854
437
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
438
|
+
console.log('🔍 Semantic analysis complete');
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.error('Semantic analysis failed:', error.message);
|
|
441
|
+
throw new Error(`Semantic analysis failed: ${error.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
858
444
|
|
|
859
|
-
|
|
860
|
-
|
|
445
|
+
return this.semanticCache;
|
|
446
|
+
}
|
|
861
447
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
|
|
865
|
-
);
|
|
448
|
+
async refreshSemanticAnalysis() {
|
|
449
|
+
console.log('🔄 Forcing semantic analysis refresh...');
|
|
866
450
|
|
|
867
|
-
//
|
|
868
|
-
|
|
451
|
+
// Clear memory cache
|
|
452
|
+
this.semanticCache = null;
|
|
453
|
+
this.lastSemanticAnalysis = null;
|
|
869
454
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
bundle.changed = false;
|
|
873
|
-
bundle.lastGenerated = new Date().toISOString();
|
|
874
|
-
bundle.size = Buffer.byteLength(bundle.content, 'utf8');
|
|
455
|
+
// Remove disk cache file
|
|
456
|
+
this.configManager.invalidateSemanticCache();
|
|
875
457
|
|
|
876
|
-
|
|
458
|
+
return this.getSemanticAnalysis();
|
|
877
459
|
}
|
|
878
460
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
<cntx:bundle xmlns:cntx="https://cntx.dev/schema" name="${bundleName}" generated="${new Date().toISOString()}">
|
|
882
|
-
`;
|
|
461
|
+
async enhanceSemanticChunksIfNeeded(analysis) {
|
|
462
|
+
if (!analysis || !analysis.chunks) return;
|
|
883
463
|
|
|
884
|
-
|
|
885
|
-
const pkgPath = join(this.CWD, 'package.json');
|
|
886
|
-
if (existsSync(pkgPath)) {
|
|
887
|
-
try {
|
|
888
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
889
|
-
xml += ` <cntx:project>
|
|
890
|
-
<cntx:name>${this.escapeXml(pkg.name || 'unknown')}</cntx:name>
|
|
891
|
-
<cntx:version>${pkg.version || '0.0.0'}</cntx:version>
|
|
892
|
-
`;
|
|
893
|
-
if (pkg.description) xml += ` <cntx:description>${this.escapeXml(pkg.description)}</cntx:description>
|
|
894
|
-
`;
|
|
895
|
-
xml += ` </cntx:project>
|
|
896
|
-
`;
|
|
897
|
-
} catch (e) {
|
|
898
|
-
xml += ` <cntx:project><cntx:error>Could not parse package.json</cntx:error></cntx:project>
|
|
899
|
-
`;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Bundle overview section
|
|
904
|
-
const filesByType = this.categorizeFiles(files);
|
|
905
|
-
const entryPoints = this.identifyEntryPoints(files);
|
|
906
|
-
|
|
907
|
-
xml += ` <cntx:overview>
|
|
908
|
-
<cntx:purpose>${this.escapeXml(this.getBundlePurpose(bundleName))}</cntx:purpose>
|
|
909
|
-
<cntx:file-types>
|
|
910
|
-
`;
|
|
464
|
+
const chunksNeedingEmbeddings = analysis.chunks.filter(chunk => !chunk.embedding);
|
|
911
465
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
xml += ` </cntx:file-types>
|
|
918
|
-
`;
|
|
919
|
-
|
|
920
|
-
if (entryPoints.length > 0) {
|
|
921
|
-
xml += ` <cntx:entry-points>
|
|
922
|
-
`;
|
|
923
|
-
entryPoints.forEach(file => {
|
|
924
|
-
xml += ` <cntx:file>${file}</cntx:file>
|
|
925
|
-
`;
|
|
926
|
-
});
|
|
927
|
-
xml += ` </cntx:entry-points>
|
|
928
|
-
`;
|
|
466
|
+
if (chunksNeedingEmbeddings.length === 0) {
|
|
467
|
+
console.log('✅ All chunks already have embeddings');
|
|
468
|
+
return;
|
|
929
469
|
}
|
|
930
470
|
|
|
931
|
-
|
|
932
|
-
`;
|
|
471
|
+
console.log(`🔧 Enhancing ${chunksNeedingEmbeddings.length} chunks with embeddings...`);
|
|
933
472
|
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
// Entry points first
|
|
939
|
-
if (entryPoints.length > 0) {
|
|
940
|
-
xml += ` <cntx:group type="entry-points" description="Main entry files for this bundle">
|
|
941
|
-
`;
|
|
942
|
-
entryPoints.forEach(file => {
|
|
943
|
-
xml += this.generateFileXML(file);
|
|
944
|
-
});
|
|
945
|
-
xml += ` </cntx:group>
|
|
946
|
-
`;
|
|
473
|
+
// Initialize vector store if needed
|
|
474
|
+
if (!this.vectorStoreInitialized) {
|
|
475
|
+
await this.vectorStore.init();
|
|
476
|
+
this.vectorStoreInitialized = true;
|
|
947
477
|
}
|
|
948
478
|
|
|
949
|
-
//
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
`;
|
|
957
|
-
remainingFiles.forEach(file => {
|
|
958
|
-
xml += this.generateFileXML(file);
|
|
959
|
-
});
|
|
960
|
-
xml += ` </cntx:group>
|
|
961
|
-
`;
|
|
479
|
+
// Add embeddings to chunks that need them
|
|
480
|
+
for (const chunk of chunksNeedingEmbeddings) {
|
|
481
|
+
try {
|
|
482
|
+
const content = this.getChunkContentForEmbedding(chunk);
|
|
483
|
+
chunk.embedding = await this.vectorStore.generateEmbedding(content);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error(`Failed to generate embedding for chunk ${chunk.id}:`, error.message);
|
|
962
486
|
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
xml += ` </cntx:files>
|
|
966
|
-
</cntx:bundle>`;
|
|
967
|
-
return xml;
|
|
487
|
+
}
|
|
968
488
|
}
|
|
969
489
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
'components': [],
|
|
973
|
-
'hooks': [],
|
|
974
|
-
'utilities': [],
|
|
975
|
-
'configuration': [],
|
|
976
|
-
'styles': [],
|
|
977
|
-
'types': [],
|
|
978
|
-
'tests': [],
|
|
979
|
-
'documentation': [],
|
|
980
|
-
'other': []
|
|
981
|
-
};
|
|
490
|
+
getChunkContentForEmbedding(chunk) {
|
|
491
|
+
let content = chunk.content || '';
|
|
982
492
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
if (basename.includes('component') || file.includes('/components/') ||
|
|
988
|
-
ext === '.jsx' || ext === '.tsx' && !basename.includes('test')) {
|
|
989
|
-
categories.components.push(file);
|
|
990
|
-
} else if (basename.includes('hook') || file.includes('/hooks/')) {
|
|
991
|
-
categories.hooks.push(file);
|
|
992
|
-
} else if (basename.includes('util') || file.includes('/utils/') ||
|
|
993
|
-
basename.includes('helper') || file.includes('/lib/')) {
|
|
994
|
-
categories.utilities.push(file);
|
|
995
|
-
} else if (ext === '.json' || basename.includes('config') ||
|
|
996
|
-
ext === '.yaml' || ext === '.yml' || ext === '.toml') {
|
|
997
|
-
categories.configuration.push(file);
|
|
998
|
-
} else if (ext === '.css' || ext === '.scss' || ext === '.less') {
|
|
999
|
-
categories.styles.push(file);
|
|
1000
|
-
} else if (basename.includes('type') || ext === '.d.ts' ||
|
|
1001
|
-
file.includes('/types/')) {
|
|
1002
|
-
categories.types.push(file);
|
|
1003
|
-
} else if (basename.includes('test') || basename.includes('spec') ||
|
|
1004
|
-
file.includes('/test/') || file.includes('/__tests__/')) {
|
|
1005
|
-
categories.tests.push(file);
|
|
1006
|
-
} else if (ext === '.md' || basename.includes('readme') ||
|
|
1007
|
-
basename.includes('doc')) {
|
|
1008
|
-
categories.documentation.push(file);
|
|
1009
|
-
} else {
|
|
1010
|
-
categories.other.push(file);
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
493
|
+
if (chunk.businessDomains?.length > 0) {
|
|
494
|
+
content += ' ' + chunk.businessDomains.join(' ');
|
|
495
|
+
}
|
|
1013
496
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
delete categories[key];
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
497
|
+
if (chunk.technicalPatterns?.length > 0) {
|
|
498
|
+
content += ' ' + chunk.technicalPatterns.join(' ');
|
|
499
|
+
}
|
|
1020
500
|
|
|
1021
|
-
return
|
|
501
|
+
return content.trim();
|
|
1022
502
|
}
|
|
1023
503
|
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
files.forEach(file => {
|
|
1028
|
-
const basename = file.toLowerCase();
|
|
1029
|
-
|
|
1030
|
-
// 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')) {
|
|
1036
|
-
entryPoints.push(file);
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
return entryPoints;
|
|
1041
|
-
}
|
|
504
|
+
async exportSemanticChunk(chunkName) {
|
|
505
|
+
const analysis = await this.getSemanticAnalysis();
|
|
506
|
+
const chunk = analysis.chunks.find(c => c.name === chunkName || c.id === chunkName);
|
|
1042
507
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
'frontend': 'User interface components, pages, and client-side logic',
|
|
1047
|
-
'backend': 'Server-side logic, APIs, and backend services',
|
|
1048
|
-
'api': 'API endpoints, routes, and server communication logic',
|
|
1049
|
-
'server': 'Main server application and core backend functionality',
|
|
1050
|
-
'components': 'Reusable UI components and interface elements',
|
|
1051
|
-
'ui-components': 'User interface components and design system elements',
|
|
1052
|
-
'config': 'Configuration files, settings, and environment setup',
|
|
1053
|
-
'docs': 'Documentation, README files, and project guides',
|
|
1054
|
-
'utils': 'Utility functions, helpers, and shared libraries',
|
|
1055
|
-
'types': 'TypeScript type definitions and interfaces',
|
|
1056
|
-
'tests': 'Test files, test utilities, and testing configuration'
|
|
1057
|
-
};
|
|
508
|
+
if (!chunk) {
|
|
509
|
+
throw new Error(`Chunk "${chunkName}" not found`);
|
|
510
|
+
}
|
|
1058
511
|
|
|
1059
|
-
return
|
|
512
|
+
return this.bundleManager.generateFileXML(chunk.filePath);
|
|
1060
513
|
}
|
|
1061
514
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
'hooks': 'Custom React hooks and state management',
|
|
1066
|
-
'utilities': 'Helper functions, utilities, and shared libraries',
|
|
1067
|
-
'configuration': 'Configuration files, settings, and build configs',
|
|
1068
|
-
'styles': 'CSS, SCSS, and styling files',
|
|
1069
|
-
'types': 'TypeScript type definitions and interfaces',
|
|
1070
|
-
'tests': 'Test files and testing utilities',
|
|
1071
|
-
'documentation': 'README files, docs, and guides',
|
|
1072
|
-
'other': 'Additional project files'
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
return descriptions[type] || `Files categorized as ${type}`;
|
|
515
|
+
invalidateSemanticCache() {
|
|
516
|
+
this.semanticCache = null;
|
|
517
|
+
this.lastSemanticAnalysis = null;
|
|
1076
518
|
}
|
|
1077
519
|
|
|
1078
|
-
|
|
1079
|
-
const fullPath = join(this.CWD, file);
|
|
1080
|
-
let fileXml = ` <cntx:file path="${file}" ext="${extname(file)}">
|
|
1081
|
-
`;
|
|
520
|
+
// === Activity Management (Placeholder) ===
|
|
1082
521
|
|
|
522
|
+
async loadActivities() {
|
|
1083
523
|
try {
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
// Add role indicator for certain files
|
|
1088
|
-
const role = this.getFileRole(file);
|
|
1089
|
-
const roleAttr = role ? ` role="${role}"` : '';
|
|
1090
|
-
|
|
1091
|
-
fileXml = ` <cntx:file path="${file}" ext="${extname(file)}"${roleAttr}>
|
|
1092
|
-
`;
|
|
1093
|
-
fileXml += ` <cntx:meta size="${stat.size}" modified="${stat.mtime.toISOString()}" lines="${content.split('\n').length}" />
|
|
1094
|
-
<cntx:content><![CDATA[${content}]]></cntx:content>
|
|
1095
|
-
`;
|
|
1096
|
-
} catch (e) {
|
|
1097
|
-
fileXml += ` <cntx:error>Could not read file: ${e.message}</cntx:error>
|
|
1098
|
-
`;
|
|
1099
|
-
}
|
|
524
|
+
const activitiesPath = join(this.CWD, '.cntx', 'activities');
|
|
525
|
+
const activitiesJsonPath = join(activitiesPath, 'activities.json');
|
|
1100
526
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
527
|
+
console.log('DEBUG: Looking for activities at:', activitiesJsonPath);
|
|
528
|
+
console.log('DEBUG: File exists:', fs.existsSync(activitiesJsonPath));
|
|
529
|
+
console.log('DEBUG: CWD is:', this.CWD);
|
|
1105
530
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
531
|
+
if (!fs.existsSync(activitiesJsonPath)) {
|
|
532
|
+
console.log('Activities file not found, returning empty array');
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const activitiesData = JSON.parse(fs.readFileSync(activitiesJsonPath, 'utf8'));
|
|
537
|
+
|
|
538
|
+
return activitiesData.map((activity, index) => {
|
|
539
|
+
// Extract the actual directory name from the references field
|
|
540
|
+
let activityId = activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
541
|
+
if (activity.references && activity.references.length > 0) {
|
|
542
|
+
// Extract directory name from path like ".cntx/activities/activities/refactor-js-to-ts/README.md"
|
|
543
|
+
const refPath = activity.references[0];
|
|
544
|
+
const pathParts = refPath.split('/');
|
|
545
|
+
if (pathParts.length >= 4) {
|
|
546
|
+
activityId = pathParts[3]; // activities/activities/{this-part}/README.md
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const activityDir = join(activitiesPath, 'activities', activityId);
|
|
550
|
+
|
|
551
|
+
// Load markdown files
|
|
552
|
+
const files = {
|
|
553
|
+
readme: this.loadMarkdownFile(join(activityDir, 'README.md')),
|
|
554
|
+
progress: this.loadMarkdownFile(join(activityDir, 'progress.md')),
|
|
555
|
+
tasks: this.loadMarkdownFile(join(activityDir, 'tasks.md')),
|
|
556
|
+
notes: this.loadMarkdownFile(join(activityDir, 'notes.md'))
|
|
557
|
+
};
|
|
1118
558
|
|
|
559
|
+
// Calculate progress from progress.md file
|
|
560
|
+
const progress = this.parseProgressFromMarkdown(files.progress);
|
|
1119
561
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
562
|
+
return {
|
|
563
|
+
id: activityId,
|
|
564
|
+
name: activity.title,
|
|
565
|
+
description: activity.description,
|
|
566
|
+
status: activity.status === 'todo' ? 'pending' : activity.status,
|
|
567
|
+
priority: activity.tags?.includes('high') ? 'high' : activity.tags?.includes('low') ? 'low' : 'medium',
|
|
568
|
+
progress,
|
|
569
|
+
updatedAt: new Date().toISOString(),
|
|
570
|
+
category: activity.tags?.[0] || 'general',
|
|
571
|
+
files,
|
|
572
|
+
tags: activity.tags
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
} catch (error) {
|
|
576
|
+
console.error('Failed to load activities:', error);
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
1127
579
|
}
|
|
1128
580
|
|
|
1129
|
-
|
|
581
|
+
loadMarkdownFile(filePath) {
|
|
1130
582
|
try {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}
|
|
1137
|
-
} catch (e) {
|
|
1138
|
-
return {
|
|
1139
|
-
size: 0,
|
|
1140
|
-
mtime: new Date()
|
|
1141
|
-
};
|
|
583
|
+
if (fs.existsSync(filePath)) {
|
|
584
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
585
|
+
}
|
|
586
|
+
return 'No content available';
|
|
587
|
+
} catch (error) {
|
|
588
|
+
return `Error loading file: ${error.message}`;
|
|
1142
589
|
}
|
|
1143
590
|
}
|
|
1144
591
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const fileStats = this.getFileStats(filePath);
|
|
1150
|
-
const isGloballyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
|
|
1151
|
-
const bundleHidden = bundleName ? this.isFileHidden(filePath, bundleName) : false;
|
|
1152
|
-
|
|
1153
|
-
// Determine which bundles this file appears in
|
|
1154
|
-
const inBundles = [];
|
|
1155
|
-
this.bundles.forEach((bundle, name) => {
|
|
1156
|
-
const matchesPattern = bundle.patterns.some(pattern => this.matchesPattern(filePath, pattern));
|
|
1157
|
-
const notHidden = !this.isFileHidden(filePath, name);
|
|
1158
|
-
if (matchesPattern && notHidden) {
|
|
1159
|
-
inBundles.push(name);
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
return {
|
|
1164
|
-
path: filePath,
|
|
1165
|
-
size: fileStats.size,
|
|
1166
|
-
modified: fileStats.mtime,
|
|
1167
|
-
visible: !isGloballyHidden && !bundleHidden,
|
|
1168
|
-
globallyHidden: isGloballyHidden,
|
|
1169
|
-
bundleHidden: bundleHidden,
|
|
1170
|
-
inBundles: inBundles,
|
|
1171
|
-
matchesIgnorePattern: this.shouldIgnoreFile(join(this.CWD, filePath))
|
|
1172
|
-
};
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
startServer(port = 3333) {
|
|
1177
|
-
const server = createServer((req, res) => {
|
|
1178
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
1179
|
-
|
|
1180
|
-
// CORS headers for ALL requests - MUST be first
|
|
1181
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1182
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
1183
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
1184
|
-
res.setHeader('Access-Control-Max-Age', '86400');
|
|
1185
|
-
|
|
1186
|
-
// Handle preflight OPTIONS requests
|
|
1187
|
-
if (req.method === 'OPTIONS') {
|
|
1188
|
-
res.writeHead(200);
|
|
1189
|
-
res.end();
|
|
1190
|
-
return;
|
|
592
|
+
parseProgressFromMarkdown(progressContent) {
|
|
593
|
+
try {
|
|
594
|
+
if (!progressContent || progressContent === 'No content available') {
|
|
595
|
+
return 0;
|
|
1191
596
|
}
|
|
1192
597
|
|
|
1193
|
-
//
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
if (url.pathname === '/') {
|
|
1198
|
-
// Serve index.html for root
|
|
1199
|
-
const indexPath = path.join(webDistPath, 'index.html');
|
|
1200
|
-
if (existsSync(indexPath)) {
|
|
1201
|
-
try {
|
|
1202
|
-
const content = readFileSync(indexPath, 'utf8');
|
|
1203
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1204
|
-
res.end(content);
|
|
1205
|
-
return;
|
|
1206
|
-
} catch (e) {
|
|
1207
|
-
if (!this.isQuietMode) console.error('Error serving index.html:', e);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Fallback if no web interface built
|
|
1212
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1213
|
-
res.end(`
|
|
1214
|
-
<!DOCTYPE html>
|
|
1215
|
-
<html>
|
|
1216
|
-
<head>
|
|
1217
|
-
<title>cntx-ui Server</title>
|
|
1218
|
-
<style>
|
|
1219
|
-
body { font-family: system-ui, sans-serif; margin: 40px; }
|
|
1220
|
-
.container { max-width: 600px; }
|
|
1221
|
-
.api-link { background: #f5f5f5; padding: 10px; border-radius: 5px; margin: 10px 0; }
|
|
1222
|
-
code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; }
|
|
1223
|
-
</style>
|
|
1224
|
-
</head>
|
|
1225
|
-
<body>
|
|
1226
|
-
<div class="container">
|
|
1227
|
-
<h1>🚀 cntx-ui Server Running</h1>
|
|
1228
|
-
<p>Your cntx-ui server is running successfully!</p>
|
|
1229
|
-
|
|
1230
|
-
<h2>Available APIs:</h2>
|
|
1231
|
-
<div class="api-link">
|
|
1232
|
-
<strong>Bundles:</strong> <a href="/api/bundles">/api/bundles</a>
|
|
1233
|
-
</div>
|
|
1234
|
-
<div class="api-link">
|
|
1235
|
-
<strong>Configuration:</strong> <a href="/api/config">/api/config</a>
|
|
1236
|
-
</div>
|
|
1237
|
-
<div class="api-link">
|
|
1238
|
-
<strong>Files:</strong> <a href="/api/files">/api/files</a>
|
|
1239
|
-
</div>
|
|
1240
|
-
<div class="api-link">
|
|
1241
|
-
<strong>Status:</strong> <a href="/api/status">/api/status</a>
|
|
1242
|
-
</div>
|
|
1243
|
-
|
|
1244
|
-
<h2>Web Interface:</h2>
|
|
1245
|
-
<p>The web interface is not available because it wasn't built when this package was published.</p>
|
|
1246
|
-
<p>To enable the web interface, the package maintainer needs to run:</p>
|
|
1247
|
-
<pre><code>cd web && npm install && npm run build</code></pre>
|
|
1248
|
-
|
|
1249
|
-
<h2>CLI Usage:</h2>
|
|
1250
|
-
<p>You can still use all CLI commands:</p>
|
|
1251
|
-
<ul>
|
|
1252
|
-
<li><code>cntx-ui status</code> - Check current status</li>
|
|
1253
|
-
<li><code>cntx-ui bundle master</code> - Generate specific bundle</li>
|
|
1254
|
-
<li><code>cntx-ui init</code> - Initialize configuration</li>
|
|
1255
|
-
</ul>
|
|
1256
|
-
</div>
|
|
1257
|
-
</body>
|
|
1258
|
-
</html>
|
|
1259
|
-
`);
|
|
1260
|
-
return;
|
|
1261
|
-
} else {
|
|
1262
|
-
// Serve other static assets
|
|
1263
|
-
const filePath = path.join(webDistPath, url.pathname);
|
|
1264
|
-
if (existsSync(filePath)) {
|
|
1265
|
-
try {
|
|
1266
|
-
const content = readFileSync(filePath);
|
|
1267
|
-
const contentType = getContentType(filePath);
|
|
1268
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
1269
|
-
res.end(content);
|
|
1270
|
-
return;
|
|
1271
|
-
} catch (e) {
|
|
1272
|
-
if (!this.isQuietMode) console.error('Error serving static file:', e);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
598
|
+
// Look for "Overall Completion: XX%" pattern
|
|
599
|
+
const overallMatch = progressContent.match(/(?:Overall Completion|Progress):\s*(\d+)%/i);
|
|
600
|
+
if (overallMatch) {
|
|
601
|
+
return parseInt(overallMatch[1], 10);
|
|
1276
602
|
}
|
|
1277
603
|
|
|
1278
|
-
//
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
files: bundle.files,
|
|
1287
|
-
lastGenerated: bundle.lastGenerated,
|
|
1288
|
-
size: bundle.size
|
|
1289
|
-
}));
|
|
1290
|
-
res.end(JSON.stringify(bundleData));
|
|
1291
|
-
|
|
1292
|
-
} else if (url.pathname.startsWith('/api/bundles/')) {
|
|
1293
|
-
const bundleName = url.pathname.split('/').pop();
|
|
1294
|
-
const bundle = this.bundles.get(bundleName);
|
|
1295
|
-
if (bundle) {
|
|
1296
|
-
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
|
1297
|
-
res.end(bundle.content);
|
|
1298
|
-
} else {
|
|
1299
|
-
res.writeHead(404);
|
|
1300
|
-
res.end('Bundle not found');
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
} else if (url.pathname.startsWith('/api/regenerate/')) {
|
|
1304
|
-
const bundleName = url.pathname.split('/').pop();
|
|
1305
|
-
if (this.bundles.has(bundleName)) {
|
|
1306
|
-
this.generateBundle(bundleName);
|
|
1307
|
-
this.saveBundleStates();
|
|
1308
|
-
this.broadcastUpdate();
|
|
1309
|
-
res.writeHead(200);
|
|
1310
|
-
res.end('OK');
|
|
1311
|
-
} else {
|
|
1312
|
-
res.writeHead(404);
|
|
1313
|
-
res.end('Bundle not found');
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
} else if (url.pathname === '/api/files') {
|
|
1317
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1318
|
-
const fileTree = this.getFileTree();
|
|
1319
|
-
res.end(JSON.stringify(fileTree));
|
|
1320
|
-
|
|
1321
|
-
} else if (url.pathname === '/api/config') {
|
|
1322
|
-
if (req.method === 'GET') {
|
|
1323
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1324
|
-
if (existsSync(this.CONFIG_FILE)) {
|
|
1325
|
-
const config = readFileSync(this.CONFIG_FILE, 'utf8');
|
|
1326
|
-
res.end(config);
|
|
1327
|
-
} else {
|
|
1328
|
-
const defaultConfig = {
|
|
1329
|
-
bundles: {
|
|
1330
|
-
master: ['**/*'],
|
|
1331
|
-
api: ['src/api.js'],
|
|
1332
|
-
ui: ['src/component.jsx', 'src/*.jsx'],
|
|
1333
|
-
config: ['package.json', 'package-lock.json', '*.config.*'],
|
|
1334
|
-
docs: ['README.md', '*.md']
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
res.end(JSON.stringify(defaultConfig));
|
|
1338
|
-
}
|
|
1339
|
-
} else if (req.method === 'POST') {
|
|
1340
|
-
let body = '';
|
|
1341
|
-
req.on('data', chunk => body += chunk);
|
|
1342
|
-
req.on('end', () => {
|
|
1343
|
-
try {
|
|
1344
|
-
if (!this.isQuietMode) console.log('🔍 Received config save request');
|
|
1345
|
-
const config = JSON.parse(body);
|
|
1346
|
-
if (!this.isQuietMode) console.log('📝 Config to save:', JSON.stringify(config, null, 2));
|
|
1347
|
-
|
|
1348
|
-
// Ensure .cntx directory exists
|
|
1349
|
-
if (!existsSync(this.CNTX_DIR)) {
|
|
1350
|
-
if (!this.isQuietMode) console.log('📁 Creating .cntx directory...');
|
|
1351
|
-
mkdirSync(this.CNTX_DIR, { recursive: true });
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
// Write config file
|
|
1355
|
-
if (!this.isQuietMode) console.log('💾 Writing config to:', this.CONFIG_FILE);
|
|
1356
|
-
writeFileSync(this.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
1357
|
-
if (!this.isQuietMode) console.log('✅ Config file written successfully');
|
|
1358
|
-
|
|
1359
|
-
// Reload configuration
|
|
1360
|
-
this.loadConfig();
|
|
1361
|
-
this.generateAllBundles();
|
|
1362
|
-
this.broadcastUpdate();
|
|
1363
|
-
|
|
1364
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1365
|
-
res.end('OK');
|
|
1366
|
-
if (!this.isQuietMode) console.log('✅ Config save response sent');
|
|
1367
|
-
|
|
1368
|
-
} catch (e) {
|
|
1369
|
-
if (!this.isQuietMode) console.error('❌ Config save error:', e);
|
|
1370
|
-
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
1371
|
-
res.end(`Error: ${e.message}`);
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
|
|
1375
|
-
req.on('error', (err) => {
|
|
1376
|
-
if (!this.isQuietMode) console.error('❌ Request error:', err);
|
|
1377
|
-
if (!res.headersSent) {
|
|
1378
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
1379
|
-
res.end('Internal Server Error');
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
} else if (url.pathname === '/api/cursor-rules') {
|
|
1385
|
-
if (req.method === 'GET') {
|
|
1386
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1387
|
-
const rules = this.loadCursorRules();
|
|
1388
|
-
res.end(rules);
|
|
1389
|
-
} else if (req.method === 'POST') {
|
|
1390
|
-
let body = '';
|
|
1391
|
-
req.on('data', chunk => body += chunk);
|
|
1392
|
-
req.on('end', () => {
|
|
1393
|
-
try {
|
|
1394
|
-
const { content } = JSON.parse(body);
|
|
1395
|
-
this.saveCursorRules(content);
|
|
1396
|
-
res.writeHead(200);
|
|
1397
|
-
res.end('OK');
|
|
1398
|
-
} catch (e) {
|
|
1399
|
-
res.writeHead(400);
|
|
1400
|
-
res.end('Invalid request');
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
604
|
+
// Fallback: count completed tasks vs total tasks in checkbox format
|
|
605
|
+
const taskMatches = progressContent.match(/- \[([x✓✅\s])\]/gi);
|
|
606
|
+
if (taskMatches && taskMatches.length > 0) {
|
|
607
|
+
const completedTasks = taskMatches.filter(match =>
|
|
608
|
+
match.includes('[x]') || match.includes('[✓]') || match.includes('[✅]')
|
|
609
|
+
).length;
|
|
610
|
+
return Math.round((completedTasks / taskMatches.length) * 100);
|
|
611
|
+
}
|
|
1404
612
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
};
|
|
1412
|
-
res.end(JSON.stringify(templates));
|
|
1413
|
-
|
|
1414
|
-
} else if (url.pathname === '/api/claude-md') {
|
|
1415
|
-
if (req.method === 'GET') {
|
|
1416
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1417
|
-
const claudeMd = this.loadClaudeMd();
|
|
1418
|
-
res.end(claudeMd);
|
|
1419
|
-
} else if (req.method === 'POST') {
|
|
1420
|
-
let body = '';
|
|
1421
|
-
req.on('data', chunk => body += chunk);
|
|
1422
|
-
req.on('end', () => {
|
|
1423
|
-
try {
|
|
1424
|
-
const { content } = JSON.parse(body);
|
|
1425
|
-
this.saveClaudeMd(content);
|
|
1426
|
-
res.writeHead(200);
|
|
1427
|
-
res.end('OK');
|
|
1428
|
-
} catch (e) {
|
|
1429
|
-
res.writeHead(400);
|
|
1430
|
-
res.end('Invalid request');
|
|
1431
|
-
}
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
613
|
+
return 0;
|
|
614
|
+
} catch (error) {
|
|
615
|
+
console.error('Error parsing progress:', error);
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
1434
619
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
req.on('end', () => {
|
|
1440
|
-
try {
|
|
1441
|
-
const { pattern } = JSON.parse(body);
|
|
1442
|
-
const allFiles = this.getAllFiles();
|
|
1443
|
-
const matchingFiles = allFiles.filter(file =>
|
|
1444
|
-
this.matchesPattern(file, pattern)
|
|
1445
|
-
);
|
|
1446
|
-
|
|
1447
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1448
|
-
res.end(JSON.stringify(matchingFiles));
|
|
1449
|
-
} catch (e) {
|
|
1450
|
-
res.writeHead(400);
|
|
1451
|
-
res.end('Invalid request');
|
|
1452
|
-
}
|
|
1453
|
-
});
|
|
1454
|
-
} else {
|
|
1455
|
-
res.writeHead(405);
|
|
1456
|
-
res.end('Method not allowed');
|
|
1457
|
-
}
|
|
620
|
+
async executeActivity(activityId) {
|
|
621
|
+
// Placeholder - would execute specific activity
|
|
622
|
+
return { success: false, message: 'Activity execution not implemented' };
|
|
623
|
+
}
|
|
1458
624
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
totalFiles: this.getAllFiles().length,
|
|
1464
|
-
globallyHidden: this.hiddenFilesConfig.globalHidden.length,
|
|
1465
|
-
bundleSpecificHidden: this.hiddenFilesConfig.bundleSpecific,
|
|
1466
|
-
ignorePatterns: {
|
|
1467
|
-
system: [
|
|
1468
|
-
{ pattern: '**/.git/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.git/**') },
|
|
1469
|
-
{ pattern: '**/node_modules/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/node_modules/**') },
|
|
1470
|
-
{ pattern: '**/.cntx/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.cntx/**') }
|
|
1471
|
-
],
|
|
1472
|
-
user: this.hiddenFilesConfig.userIgnorePatterns,
|
|
1473
|
-
disabled: this.hiddenFilesConfig.disabledSystemPatterns
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
res.end(JSON.stringify(stats));
|
|
1477
|
-
} else if (req.method === 'POST') {
|
|
1478
|
-
let body = '';
|
|
1479
|
-
req.on('data', chunk => body += chunk);
|
|
1480
|
-
req.on('end', () => {
|
|
1481
|
-
try {
|
|
1482
|
-
const { action, filePath, filePaths, bundleName, forceHide } = JSON.parse(body);
|
|
1483
|
-
|
|
1484
|
-
if (action === 'toggle' && filePath) {
|
|
1485
|
-
this.toggleFileVisibility(filePath, bundleName, forceHide);
|
|
1486
|
-
} else if (action === 'bulk-toggle' && filePaths) {
|
|
1487
|
-
this.bulkToggleFileVisibility(filePaths, bundleName, forceHide);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
this.generateAllBundles();
|
|
1491
|
-
this.broadcastUpdate();
|
|
1492
|
-
|
|
1493
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1494
|
-
res.end(JSON.stringify({ success: true }));
|
|
1495
|
-
} catch (e) {
|
|
1496
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1497
|
-
res.end(JSON.stringify({ error: e.message }));
|
|
1498
|
-
}
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
625
|
+
async stopActivity(activityId) {
|
|
626
|
+
// Placeholder - would stop running activity
|
|
627
|
+
return { success: false, message: 'Activity stopping not implemented' };
|
|
628
|
+
}
|
|
1501
629
|
|
|
1502
|
-
|
|
1503
|
-
const bundleName = url.searchParams.get('bundle');
|
|
1504
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1505
|
-
const files = this.getFileListWithVisibility(bundleName);
|
|
1506
|
-
res.end(JSON.stringify(files));
|
|
1507
|
-
|
|
1508
|
-
} else if (url.pathname === '/api/ignore-patterns') {
|
|
1509
|
-
if (req.method === 'GET') {
|
|
1510
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1511
|
-
|
|
1512
|
-
// Read file patterns
|
|
1513
|
-
let filePatterns = [];
|
|
1514
|
-
if (existsSync(this.IGNORE_FILE)) {
|
|
1515
|
-
filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
|
|
1516
|
-
.split('\n')
|
|
1517
|
-
.map(line => line.trim())
|
|
1518
|
-
.filter(line => line && !line.startsWith('#'));
|
|
1519
|
-
}
|
|
630
|
+
// === MCP Server Integration ===
|
|
1520
631
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
active: !this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
|
|
1527
|
-
})),
|
|
1528
|
-
user: this.hiddenFilesConfig.userIgnorePatterns.map(pattern => ({ pattern, active: true })),
|
|
1529
|
-
file: filePatterns.filter(pattern =>
|
|
1530
|
-
!systemPatterns.includes(pattern) &&
|
|
1531
|
-
!this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
|
|
1532
|
-
).map(pattern => ({ pattern, active: true }))
|
|
1533
|
-
};
|
|
1534
|
-
res.end(JSON.stringify(patterns));
|
|
1535
|
-
|
|
1536
|
-
} else if (req.method === 'POST') {
|
|
1537
|
-
let body = '';
|
|
1538
|
-
req.on('data', chunk => body += chunk);
|
|
1539
|
-
req.on('end', () => {
|
|
1540
|
-
try {
|
|
1541
|
-
const { action, pattern } = JSON.parse(body);
|
|
1542
|
-
let success = false;
|
|
1543
|
-
|
|
1544
|
-
switch (action) {
|
|
1545
|
-
case 'add':
|
|
1546
|
-
success = this.addUserIgnorePattern(pattern);
|
|
1547
|
-
break;
|
|
1548
|
-
case 'remove':
|
|
1549
|
-
success = this.removeUserIgnorePattern(pattern);
|
|
1550
|
-
break;
|
|
1551
|
-
case 'toggle-system':
|
|
1552
|
-
this.toggleSystemIgnorePattern(pattern);
|
|
1553
|
-
success = true;
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
this.broadcastUpdate();
|
|
1558
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1559
|
-
res.end(JSON.stringify({ success }));
|
|
1560
|
-
} catch (e) {
|
|
1561
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1562
|
-
res.end(JSON.stringify({ error: e.message }));
|
|
1563
|
-
}
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
632
|
+
startMCPServer() {
|
|
633
|
+
if (!this.mcpServer) {
|
|
634
|
+
this.mcpServer = new MCPServer(this);
|
|
635
|
+
this.mcpServerStarted = true;
|
|
636
|
+
this.apiRouter.mcpServerStarted = true;
|
|
1566
637
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const allFiles = this.getAllFiles();
|
|
1573
|
-
const matchingFiles = allFiles.filter(file =>
|
|
1574
|
-
bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
|
|
1575
|
-
);
|
|
1576
|
-
|
|
1577
|
-
const visibleFiles = matchingFiles.filter(file => !this.isFileHidden(file, bundleName));
|
|
1578
|
-
const hiddenFiles = matchingFiles.length - visibleFiles.length;
|
|
1579
|
-
|
|
1580
|
-
stats[bundleName] = {
|
|
1581
|
-
total: matchingFiles.length,
|
|
1582
|
-
visible: visibleFiles.length,
|
|
1583
|
-
hidden: hiddenFiles,
|
|
1584
|
-
patterns: bundle.patterns
|
|
1585
|
-
};
|
|
1586
|
-
});
|
|
638
|
+
if (this.verbose) {
|
|
639
|
+
console.log('🔗 MCP server started');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
1587
643
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
} else if (url.pathname.startsWith('/api/bundle-categories/')) {
|
|
1591
|
-
const bundleName = url.pathname.split('/').pop();
|
|
1592
|
-
const bundle = this.bundles.get(bundleName);
|
|
1593
|
-
|
|
1594
|
-
if (bundle) {
|
|
1595
|
-
const filesByType = this.categorizeFiles(bundle.files);
|
|
1596
|
-
const entryPoints = this.identifyEntryPoints(bundle.files);
|
|
1597
|
-
|
|
1598
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1599
|
-
res.end(JSON.stringify({
|
|
1600
|
-
purpose: this.getBundlePurpose(bundleName),
|
|
1601
|
-
filesByType,
|
|
1602
|
-
entryPoints,
|
|
1603
|
-
totalFiles: bundle.files.length
|
|
1604
|
-
}));
|
|
1605
|
-
} else {
|
|
1606
|
-
res.writeHead(404);
|
|
1607
|
-
res.end('Bundle not found');
|
|
1608
|
-
}
|
|
644
|
+
// === Server Lifecycle ===
|
|
1609
645
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
req.on('end', () => {
|
|
1615
|
-
try {
|
|
1616
|
-
const { scope, bundleName } = JSON.parse(body);
|
|
1617
|
-
|
|
1618
|
-
if (scope === 'global') {
|
|
1619
|
-
this.hiddenFilesConfig.globalHidden = [];
|
|
1620
|
-
} else if (scope === 'bundle' && bundleName) {
|
|
1621
|
-
delete this.hiddenFilesConfig.bundleSpecific[bundleName];
|
|
1622
|
-
} else if (scope === 'all') {
|
|
1623
|
-
this.hiddenFilesConfig.globalHidden = [];
|
|
1624
|
-
this.hiddenFilesConfig.bundleSpecific = {};
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
this.saveHiddenFilesConfig();
|
|
1628
|
-
this.generateAllBundles();
|
|
1629
|
-
this.broadcastUpdate();
|
|
1630
|
-
|
|
1631
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1632
|
-
res.end(JSON.stringify({ success: true }));
|
|
1633
|
-
} catch (e) {
|
|
1634
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1635
|
-
res.end(JSON.stringify({ error: e.message }));
|
|
1636
|
-
}
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
646
|
+
async listen(port = 3333, host = 'localhost') {
|
|
647
|
+
const server = createServer((req, res) => {
|
|
648
|
+
this.handleRequest(req, res);
|
|
649
|
+
});
|
|
1639
650
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
const statusInfo = {
|
|
1643
|
-
server: {
|
|
1644
|
-
version: '2.0.8',
|
|
1645
|
-
workingDirectory: this.CWD,
|
|
1646
|
-
startTime: new Date().toISOString(),
|
|
1647
|
-
isScanning: this.isScanning
|
|
1648
|
-
},
|
|
1649
|
-
bundles: {
|
|
1650
|
-
count: this.bundles.size,
|
|
1651
|
-
names: Array.from(this.bundles.keys()),
|
|
1652
|
-
totalFiles: Array.from(this.bundles.values()).reduce((sum, bundle) => sum + bundle.files.length, 0)
|
|
1653
|
-
},
|
|
1654
|
-
mcp: {
|
|
1655
|
-
available: true,
|
|
1656
|
-
serverStarted: this.mcpServerStarted,
|
|
1657
|
-
command: 'npx cntx-ui mcp',
|
|
1658
|
-
setupScript: './examples/claude-mcp-setup.sh'
|
|
1659
|
-
},
|
|
1660
|
-
files: {
|
|
1661
|
-
total: this.getAllFiles().length,
|
|
1662
|
-
hiddenGlobally: this.hiddenFilesConfig.globalHidden.length,
|
|
1663
|
-
ignorePatterns: this.ignorePatterns.length
|
|
1664
|
-
}
|
|
1665
|
-
};
|
|
1666
|
-
res.end(JSON.stringify(statusInfo, null, 2));
|
|
651
|
+
// Initialize WebSocket server
|
|
652
|
+
this.webSocketManager.initialize(server);
|
|
1667
653
|
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}
|
|
1672
|
-
|
|
654
|
+
// Start server and show progress
|
|
655
|
+
server.listen(port, host, () => {
|
|
656
|
+
console.log('');
|
|
657
|
+
console.log(`🌐 Server running at http://${host}:${port}`);
|
|
658
|
+
console.log(`📊 Serving ${this.bundleManager.getAllBundleInfo().length} bundles from your project`);
|
|
659
|
+
console.log('');
|
|
1673
660
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
this.clients.add(ws);
|
|
1677
|
-
ws.on('close', () => this.clients.delete(ws));
|
|
1678
|
-
this.sendUpdate(ws);
|
|
661
|
+
// Display initialization summary
|
|
662
|
+
this.displayInitSummary();
|
|
1679
663
|
});
|
|
1680
664
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
665
|
+
// Handle graceful shutdown
|
|
666
|
+
process.on('SIGINT', () => {
|
|
667
|
+
console.log('\n🛑 Shutting down server...');
|
|
668
|
+
this.webSocketManager.close();
|
|
669
|
+
this.fileSystemManager.destroy();
|
|
670
|
+
server.close(() => {
|
|
671
|
+
console.log('✅ Server stopped');
|
|
672
|
+
process.exit(0);
|
|
673
|
+
});
|
|
1687
674
|
});
|
|
1688
675
|
|
|
1689
676
|
return server;
|
|
1690
677
|
}
|
|
678
|
+
}
|
|
1691
679
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}));
|
|
1707
|
-
client.send(JSON.stringify(bundleData));
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
680
|
+
// Export function for CLI compatibility
|
|
681
|
+
export async function startServer(options = {}) {
|
|
682
|
+
const server = new CntxServer(options.cwd, { verbose: options.verbose });
|
|
683
|
+
|
|
684
|
+
// Show ASCII art first
|
|
685
|
+
const asciiArt = `
|
|
686
|
+
██████ ███ ██ ████████ ██ ██ ██ ██ ██
|
|
687
|
+
██ ████ ██ ██ ██ ██ ██ ██ ██
|
|
688
|
+
██ ██ ██ ██ ██ ███ █████ ██ ██ ██
|
|
689
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
690
|
+
██████ ██ ████ ██ ██ ██ ██████ ██
|
|
691
|
+
`;
|
|
692
|
+
console.log(asciiArt);
|
|
693
|
+
console.log(''); // Add blank line after art
|
|
1710
694
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
this.saveBundleStates();
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
695
|
+
// Now start initialization with progress bar
|
|
696
|
+
await server.init();
|
|
1716
697
|
|
|
1717
|
-
export function startServer(options = {}) {
|
|
1718
|
-
const server = new CntxServer(options.cwd, { quiet: options.quiet });
|
|
1719
|
-
server.init();
|
|
1720
|
-
|
|
1721
698
|
if (options.withMcp) {
|
|
1722
|
-
server.
|
|
1723
|
-
if (!server.isQuietMode) {
|
|
1724
|
-
console.log('🔗 MCP server tracking enabled - use /api/status to check MCP configuration');
|
|
1725
|
-
}
|
|
699
|
+
server.startMCPServer();
|
|
1726
700
|
}
|
|
1727
|
-
|
|
1728
|
-
return server.startServer(options.port);
|
|
1729
|
-
}
|
|
1730
701
|
|
|
1731
|
-
|
|
1732
|
-
const server = new CntxServer(options.cwd, { quiet: true });
|
|
1733
|
-
server.init();
|
|
1734
|
-
startMCPTransport(server);
|
|
1735
|
-
return server;
|
|
702
|
+
return await server.listen(options.port, options.host);
|
|
1736
703
|
}
|
|
1737
704
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
server.
|
|
1741
|
-
server.
|
|
1742
|
-
server.
|
|
705
|
+
// CLI Functions for backward compatibility
|
|
706
|
+
export async function startMCPServer(options = {}) {
|
|
707
|
+
const server = new CntxServer(options.cwd, { verbose: true });
|
|
708
|
+
await server.init();
|
|
709
|
+
server.startMCPServer();
|
|
710
|
+
|
|
711
|
+
// For MCP mode, we don't start the web server, just keep the process alive
|
|
712
|
+
console.log('🔗 MCP server running on stdio...');
|
|
1743
713
|
}
|
|
1744
714
|
|
|
1745
|
-
export function
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
console.log('🚀 Starting initConfig...');
|
|
1749
|
-
console.log('📂 Working directory:', cwd);
|
|
1750
|
-
}
|
|
715
|
+
export async function generateBundle(bundleName = 'master') {
|
|
716
|
+
const server = new CntxServer(process.cwd(), { verbose: true });
|
|
717
|
+
await server.init({ skipFileWatcher: true });
|
|
1751
718
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
719
|
+
await server.bundleManager.regenerateBundle(bundleName);
|
|
720
|
+
const bundleInfo = server.bundleManager.getBundleInfo(bundleName);
|
|
721
|
+
|
|
722
|
+
if (!bundleInfo) {
|
|
723
|
+
throw new Error(`Bundle '${bundleName}' not found`);
|
|
1756
724
|
}
|
|
1757
725
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
master: ['**/*']
|
|
1761
|
-
}
|
|
1762
|
-
};
|
|
726
|
+
return bundleInfo;
|
|
727
|
+
}
|
|
1763
728
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
if (!existsSync(server.CNTX_DIR)) {
|
|
1768
|
-
if (!isQuiet) console.log('📁 Creating .cntx directory...');
|
|
1769
|
-
mkdirSync(server.CNTX_DIR, { recursive: true });
|
|
1770
|
-
if (!isQuiet) console.log('✅ .cntx directory created');
|
|
1771
|
-
} else {
|
|
1772
|
-
if (!isQuiet) console.log('✅ .cntx directory already exists');
|
|
1773
|
-
}
|
|
729
|
+
export async function initConfig() {
|
|
730
|
+
const server = new CntxServer(process.cwd(), { verbose: false });
|
|
731
|
+
const templateDir = join(__dirname, 'templates');
|
|
1774
732
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
}
|
|
733
|
+
// Initialize directory structure
|
|
734
|
+
if (!existsSync(server.CNTX_DIR)) {
|
|
735
|
+
mkdirSync(server.CNTX_DIR, { recursive: true });
|
|
736
|
+
console.log('📁 Created .cntx directory');
|
|
737
|
+
}
|
|
1781
738
|
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
739
|
+
// Initialize basic configuration
|
|
740
|
+
server.configManager.loadConfig();
|
|
741
|
+
server.configManager.saveConfig({
|
|
742
|
+
bundles: {
|
|
743
|
+
master: ['**/*']
|
|
1787
744
|
}
|
|
745
|
+
});
|
|
1788
746
|
|
|
1789
|
-
|
|
1790
|
-
if (!isQuiet) console.log('✅ writeFileSync completed');
|
|
1791
|
-
|
|
1792
|
-
// Verify file was created
|
|
1793
|
-
if (!isQuiet) {
|
|
1794
|
-
console.log('🔍 Checking if config.json exists...');
|
|
1795
|
-
const configExists = existsSync(server.CONFIG_FILE);
|
|
1796
|
-
console.log('Config exists?', configExists);
|
|
747
|
+
console.log('⚙️ Basic configuration initialized');
|
|
1797
748
|
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
console.log('❌ Config file was NOT created');
|
|
1804
|
-
}
|
|
749
|
+
// Copy agent configuration files
|
|
750
|
+
const agentFiles = [
|
|
751
|
+
'agent-config.yaml',
|
|
752
|
+
'agent-instructions.md'
|
|
753
|
+
];
|
|
1805
754
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
755
|
+
for (const file of agentFiles) {
|
|
756
|
+
const sourcePath = join(templateDir, file);
|
|
757
|
+
const destPath = join(server.CNTX_DIR, file);
|
|
758
|
+
|
|
759
|
+
if (existsSync(sourcePath) && !existsSync(destPath)) {
|
|
760
|
+
copyFileSync(sourcePath, destPath);
|
|
761
|
+
console.log(`📄 Created ${file}`);
|
|
1810
762
|
}
|
|
763
|
+
}
|
|
1811
764
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
765
|
+
// Copy agent-rules directory structure
|
|
766
|
+
const agentRulesSource = join(templateDir, 'agent-rules');
|
|
767
|
+
const agentRulesDest = join(server.CNTX_DIR, 'agent-rules');
|
|
768
|
+
|
|
769
|
+
if (existsSync(agentRulesSource) && !existsSync(agentRulesDest)) {
|
|
770
|
+
cpSync(agentRulesSource, agentRulesDest, { recursive: true });
|
|
771
|
+
console.log('📁 Created agent-rules directory with templates');
|
|
1818
772
|
}
|
|
1819
773
|
|
|
1820
|
-
//
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
const cursorRules = server.getDefaultCursorRules();
|
|
1825
|
-
server.saveCursorRules(cursorRules);
|
|
1826
|
-
if (!isQuiet) console.log(`📋 Created ${relative(cwd, server.CURSOR_RULES_FILE)} with project-specific rules`);
|
|
1827
|
-
}
|
|
1828
|
-
} catch (error) {
|
|
1829
|
-
if (!isQuiet) console.error('❌ Error creating cursor rules:', error);
|
|
774
|
+
// Copy activities framework
|
|
775
|
+
const activitiesDir = join(server.CNTX_DIR, 'activities');
|
|
776
|
+
if (!existsSync(activitiesDir)) {
|
|
777
|
+
mkdirSync(activitiesDir, { recursive: true });
|
|
1830
778
|
}
|
|
1831
779
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
console.log('
|
|
1839
|
-
console.log(' Follow the setup guide to create your first bundles');
|
|
1840
|
-
console.log('');
|
|
1841
|
-
console.log('💡 The web interface handles everything - no manual file editing needed!');
|
|
780
|
+
// Copy activities README
|
|
781
|
+
const activitiesReadmeSource = join(templateDir, 'activities', 'README.md');
|
|
782
|
+
const activitiesReadmeDest = join(activitiesDir, 'README.md');
|
|
783
|
+
|
|
784
|
+
if (existsSync(activitiesReadmeSource) && !existsSync(activitiesReadmeDest)) {
|
|
785
|
+
copyFileSync(activitiesReadmeSource, activitiesReadmeDest);
|
|
786
|
+
console.log('📄 Created activities/README.md');
|
|
1842
787
|
}
|
|
788
|
+
|
|
789
|
+
// Copy activities lib directory (MDC templates)
|
|
790
|
+
const activitiesLibSource = join(templateDir, 'activities', 'lib');
|
|
791
|
+
const activitiesLibDest = join(activitiesDir, 'lib');
|
|
792
|
+
|
|
793
|
+
if (existsSync(activitiesLibSource) && !existsSync(activitiesLibDest)) {
|
|
794
|
+
cpSync(activitiesLibSource, activitiesLibDest, { recursive: true });
|
|
795
|
+
console.log('📁 Created activities/lib with MDC templates');
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Copy activities.json from templates
|
|
799
|
+
const activitiesJsonPath = join(activitiesDir, 'activities.json');
|
|
800
|
+
const templateActivitiesJsonPath = join(templateDir, 'activities', 'activities.json');
|
|
801
|
+
if (!existsSync(activitiesJsonPath) && existsSync(templateActivitiesJsonPath)) {
|
|
802
|
+
copyFileSync(templateActivitiesJsonPath, activitiesJsonPath);
|
|
803
|
+
console.log('📄 Created activities.json with bundle example activity');
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Copy example activity from templates
|
|
807
|
+
const activitiesDestDir = join(activitiesDir, 'activities');
|
|
808
|
+
const templateActivitiesDir = join(templateDir, 'activities', 'activities');
|
|
809
|
+
if (!existsSync(activitiesDestDir) && existsSync(templateActivitiesDir)) {
|
|
810
|
+
cpSync(templateActivitiesDir, activitiesDestDir, { recursive: true });
|
|
811
|
+
console.log('📁 Created example activity with templates');
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
console.log('');
|
|
815
|
+
console.log('🎉 cntx-ui initialized with full scaffolding!');
|
|
816
|
+
console.log('');
|
|
817
|
+
console.log('Next steps:');
|
|
818
|
+
console.log(' 1️⃣ Start the server: cntx-ui watch');
|
|
819
|
+
console.log(' 2️⃣ Open web UI: http://localhost:3333');
|
|
820
|
+
console.log(' 3️⃣ Read .cntx/agent-instructions.md for AI integration');
|
|
821
|
+
console.log(' 4️⃣ Explore .cntx/activities/README.md for project management');
|
|
822
|
+
console.log('');
|
|
823
|
+
console.log('💡 Pro tip: Use "cntx-ui status" to see your project overview');
|
|
1843
824
|
}
|
|
1844
825
|
|
|
1845
|
-
export function getStatus(
|
|
1846
|
-
const server = new CntxServer(cwd, {
|
|
1847
|
-
server.init();
|
|
826
|
+
export async function getStatus() {
|
|
827
|
+
const server = new CntxServer(process.cwd(), { verbose: true });
|
|
828
|
+
await server.init({ skipFileWatcher: true });
|
|
1848
829
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
console.log(`📦 Bundles configured: ${server.bundles.size}`);
|
|
1852
|
-
server.bundles.forEach((bundle, name) => {
|
|
1853
|
-
const status = bundle.changed ? '🔄 CHANGED' : '✅ SYNCED';
|
|
1854
|
-
console.log(` ${name}: ${bundle.files.length} files ${status}`);
|
|
1855
|
-
});
|
|
830
|
+
const bundles = server.bundleManager.getAllBundleInfo();
|
|
831
|
+
const totalFiles = server.fileSystemManager.getAllFiles().length;
|
|
1856
832
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
}
|
|
833
|
+
console.log('📊 cntx-ui Status');
|
|
834
|
+
console.log('================');
|
|
835
|
+
console.log(`Total files: ${totalFiles}`);
|
|
836
|
+
console.log(`Bundles: ${bundles.length}`);
|
|
837
|
+
|
|
838
|
+
bundles.forEach(bundle => {
|
|
839
|
+
console.log(` • ${bundle.name}: ${bundle.fileCount} files (${Math.round(bundle.size / 1024)}KB)`);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
return {
|
|
843
|
+
totalFiles,
|
|
844
|
+
bundles: bundles.length,
|
|
845
|
+
bundleDetails: bundles
|
|
846
|
+
};
|
|
1860
847
|
}
|
|
1861
848
|
|
|
1862
|
-
export function setupMCP(
|
|
1863
|
-
const
|
|
1864
|
-
const
|
|
1865
|
-
const projectName = basename(projectDir);
|
|
1866
|
-
const configFile = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
849
|
+
export function setupMCP() {
|
|
850
|
+
const configPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
851
|
+
const projectPath = process.cwd();
|
|
1867
852
|
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
}
|
|
853
|
+
console.log('🔧 Setting up MCP integration...');
|
|
854
|
+
console.log(`Project: ${projectPath}`);
|
|
855
|
+
console.log(`Claude config: ${configPath}`);
|
|
1872
856
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
857
|
+
try {
|
|
858
|
+
let config = {};
|
|
859
|
+
if (existsSync(configPath)) {
|
|
860
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
861
|
+
}
|
|
1878
862
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
if (existsSync(configFile)) {
|
|
1882
|
-
try {
|
|
1883
|
-
const configContent = readFileSync(configFile, 'utf8');
|
|
1884
|
-
config = JSON.parse(configContent);
|
|
1885
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
1886
|
-
} catch (error) {
|
|
1887
|
-
if (!isQuiet) console.warn('⚠️ Could not parse existing config, creating new one');
|
|
1888
|
-
config = { mcpServers: {} };
|
|
863
|
+
if (!config.mcpServers) {
|
|
864
|
+
config.mcpServers = {};
|
|
1889
865
|
}
|
|
1890
|
-
}
|
|
1891
866
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
cwd: projectDir
|
|
1898
|
-
};
|
|
867
|
+
config.mcpServers['cntx-ui'] = {
|
|
868
|
+
command: 'node',
|
|
869
|
+
args: [join(projectPath, 'bin', 'cntx-ui.js'), 'mcp'],
|
|
870
|
+
env: {}
|
|
871
|
+
};
|
|
1899
872
|
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
writeFileSync(
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
console.log('📋 Your Claude Desktop config now includes:');
|
|
1907
|
-
|
|
1908
|
-
Object.keys(config.mcpServers).forEach(name => {
|
|
1909
|
-
if (name.startsWith('cntx-ui-')) {
|
|
1910
|
-
console.log(` • ${name}: ${config.mcpServers[name].cwd}`);
|
|
1911
|
-
}
|
|
1912
|
-
});
|
|
1913
|
-
|
|
1914
|
-
console.log('🔄 Please restart Claude Desktop to use the updated configuration');
|
|
1915
|
-
}
|
|
873
|
+
// Ensure directory exists
|
|
874
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
875
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
876
|
+
|
|
877
|
+
console.log('✅ MCP integration configured');
|
|
878
|
+
console.log('💡 Restart Claude Desktop to apply changes');
|
|
1916
879
|
} catch (error) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
console.error('💡 Make sure Claude Desktop is not running and try again');
|
|
1920
|
-
}
|
|
1921
|
-
throw error;
|
|
880
|
+
console.error('❌ Failed to setup MCP:', error.message);
|
|
881
|
+
console.log('💡 You may need to manually add the configuration to Claude Desktop');
|
|
1922
882
|
}
|
|
1923
883
|
}
|
|
884
|
+
|
|
885
|
+
// Auto-start server when run directly
|
|
886
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
887
|
+
if (isMainModule) {
|
|
888
|
+
console.log('🚀 Starting cntx-ui server...');
|
|
889
|
+
const server = new CntxServer();
|
|
890
|
+
server.init();
|
|
891
|
+
server.listen(3333, 'localhost');
|
|
892
|
+
}
|