cntx-ui 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cntx-ui",
3
3
  "type": "module",
4
- "version": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "description": "Minimal file bundling and tagging tool for AI development",
6
6
  "keywords": [
7
7
  "ai",
package/server.js CHANGED
@@ -29,6 +29,7 @@ export class CntxServer {
29
29
  this.CNTX_DIR = join(cwd, '.cntx');
30
30
  this.CONFIG_FILE = join(this.CNTX_DIR, 'config.json');
31
31
  this.BUNDLES_FILE = join(this.CNTX_DIR, 'bundles.json');
32
+ this.HIDDEN_FILES_CONFIG = join(this.CNTX_DIR, 'hidden-files.json');
32
33
  this.IGNORE_FILE = join(cwd, '.cntxignore');
33
34
  this.CURSOR_RULES_FILE = join(cwd, '.cursorrules');
34
35
 
@@ -37,11 +38,19 @@ export class CntxServer {
37
38
  this.watchers = [];
38
39
  this.clients = new Set();
39
40
  this.isScanning = false;
41
+
42
+ this.hiddenFilesConfig = {
43
+ globalHidden: [], // Files hidden across all bundles
44
+ bundleSpecific: {}, // Files hidden per bundle: { bundleName: [filePaths] }
45
+ userIgnorePatterns: [], // User-added ignore patterns
46
+ disabledSystemPatterns: [] // System patterns the user disabled
47
+ };
40
48
  }
41
49
 
42
50
  init() {
43
51
  if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
44
52
  this.loadConfig();
53
+ this.loadHiddenFilesConfig();
45
54
  this.loadIgnorePatterns();
46
55
  this.loadBundleStates();
47
56
  this.startWatching();
@@ -75,21 +84,183 @@ export class CntxServer {
75
84
  }
76
85
  }
77
86
 
87
+ loadHiddenFilesConfig() {
88
+ if (existsSync(this.HIDDEN_FILES_CONFIG)) {
89
+ try {
90
+ const config = JSON.parse(readFileSync(this.HIDDEN_FILES_CONFIG, 'utf8'));
91
+ this.hiddenFilesConfig = { ...this.hiddenFilesConfig, ...config };
92
+ } catch (e) {
93
+ console.warn('Could not load hidden files config:', e.message);
94
+ }
95
+ }
96
+ }
97
+
98
+ saveHiddenFilesConfig() {
99
+ try {
100
+ writeFileSync(this.HIDDEN_FILES_CONFIG, JSON.stringify(this.hiddenFilesConfig, null, 2));
101
+ } catch (e) {
102
+ console.error('Failed to save hidden files config:', e.message);
103
+ }
104
+ }
105
+
106
+ isFileHidden(filePath, bundleName = null) {
107
+ // Check global hidden files
108
+ if (this.hiddenFilesConfig.globalHidden.includes(filePath)) {
109
+ return true;
110
+ }
111
+
112
+ // Check bundle-specific hidden files
113
+ if (bundleName && this.hiddenFilesConfig.bundleSpecific[bundleName]) {
114
+ return this.hiddenFilesConfig.bundleSpecific[bundleName].includes(filePath);
115
+ }
116
+
117
+ return false;
118
+ }
119
+
120
+ toggleFileVisibility(filePath, bundleName = null, forceHide = null) {
121
+ if (bundleName) {
122
+ // Bundle-specific hiding
123
+ if (!this.hiddenFilesConfig.bundleSpecific[bundleName]) {
124
+ this.hiddenFilesConfig.bundleSpecific[bundleName] = [];
125
+ }
126
+
127
+ const bundleHidden = this.hiddenFilesConfig.bundleSpecific[bundleName];
128
+ const isCurrentlyHidden = bundleHidden.includes(filePath);
129
+
130
+ if (forceHide === null) {
131
+ // Toggle current state
132
+ if (isCurrentlyHidden) {
133
+ this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
134
+ } else {
135
+ bundleHidden.push(filePath);
136
+ }
137
+ } else {
138
+ // Force to specific state
139
+ if (forceHide && !isCurrentlyHidden) {
140
+ bundleHidden.push(filePath);
141
+ } else if (!forceHide && isCurrentlyHidden) {
142
+ this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
143
+ }
144
+ }
145
+ } else {
146
+ // Global hiding
147
+ const isCurrentlyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
148
+
149
+ if (forceHide === null) {
150
+ // Toggle current state
151
+ if (isCurrentlyHidden) {
152
+ this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
153
+ } else {
154
+ this.hiddenFilesConfig.globalHidden.push(filePath);
155
+ }
156
+ } else {
157
+ // Force to specific state
158
+ if (forceHide && !isCurrentlyHidden) {
159
+ this.hiddenFilesConfig.globalHidden.push(filePath);
160
+ } else if (!forceHide && isCurrentlyHidden) {
161
+ this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
162
+ }
163
+ }
164
+ }
165
+
166
+ this.saveHiddenFilesConfig();
167
+ }
168
+
169
+ bulkToggleFileVisibility(filePaths, bundleName = null, forceHide = null) {
170
+ filePaths.forEach(filePath => {
171
+ this.toggleFileVisibility(filePath, bundleName, forceHide);
172
+ });
173
+ }
174
+
175
+ addUserIgnorePattern(pattern) {
176
+ if (!this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)) {
177
+ this.hiddenFilesConfig.userIgnorePatterns.push(pattern);
178
+ this.saveHiddenFilesConfig();
179
+ this.loadIgnorePatterns();
180
+ this.generateAllBundles();
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+
186
+ removeUserIgnorePattern(pattern) {
187
+ const index = this.hiddenFilesConfig.userIgnorePatterns.indexOf(pattern);
188
+ if (index > -1) {
189
+ this.hiddenFilesConfig.userIgnorePatterns.splice(index, 1);
190
+ this.saveHiddenFilesConfig();
191
+ this.loadIgnorePatterns();
192
+ this.generateAllBundles();
193
+ return true;
194
+ }
195
+ return false;
196
+ }
197
+
198
+ toggleSystemIgnorePattern(pattern) {
199
+ const index = this.hiddenFilesConfig.disabledSystemPatterns.indexOf(pattern);
200
+ if (index > -1) {
201
+ // Re-enable the pattern
202
+ this.hiddenFilesConfig.disabledSystemPatterns.splice(index, 1);
203
+ } else {
204
+ // Disable the pattern
205
+ this.hiddenFilesConfig.disabledSystemPatterns.push(pattern);
206
+ }
207
+
208
+ this.saveHiddenFilesConfig();
209
+ this.loadIgnorePatterns();
210
+ this.generateAllBundles();
211
+ }
212
+
78
213
  loadIgnorePatterns() {
214
+ const systemPatterns = [
215
+ '**/.git/**',
216
+ '**/node_modules/**',
217
+ '**/.cntx/**'
218
+ ];
219
+
220
+ // Read from .cntxignore file
221
+ let filePatterns = [];
79
222
  if (existsSync(this.IGNORE_FILE)) {
80
- this.ignorePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
223
+ filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
81
224
  .split('\n')
82
225
  .map(line => line.trim())
83
226
  .filter(line => line && !line.startsWith('#'));
84
- } else {
85
- this.ignorePatterns = [
86
- // Keep minimal patterns since we handle most stuff in shouldIgnoreAnything
87
- '**/.git/**',
88
- '**/node_modules/**',
89
- '**/.cntx/**'
90
- ];
91
- writeFileSync(this.IGNORE_FILE, this.ignorePatterns.join('\n'));
92
227
  }
228
+
229
+ // Combine all patterns
230
+ this.ignorePatterns = [
231
+ // System patterns (filtered by disabled list)
232
+ ...systemPatterns.filter(pattern =>
233
+ !this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
234
+ ),
235
+ // File patterns
236
+ ...filePatterns.filter(pattern =>
237
+ !systemPatterns.includes(pattern) &&
238
+ !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
239
+ ),
240
+ // User-added patterns
241
+ ...this.hiddenFilesConfig.userIgnorePatterns
242
+ ];
243
+
244
+ // Update .cntxignore file with current patterns
245
+ const allPatterns = [
246
+ '# System patterns',
247
+ ...systemPatterns.map(pattern =>
248
+ this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
249
+ ? `# ${pattern}`
250
+ : pattern
251
+ ),
252
+ '',
253
+ '# User patterns',
254
+ ...this.hiddenFilesConfig.userIgnorePatterns,
255
+ '',
256
+ '# File-specific patterns (edit manually)',
257
+ ...filePatterns.filter(pattern =>
258
+ !systemPatterns.includes(pattern) &&
259
+ !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
260
+ )
261
+ ];
262
+
263
+ writeFileSync(this.IGNORE_FILE, allPatterns.join('\n'));
93
264
  }
94
265
 
95
266
  loadBundleStates() {
@@ -534,10 +705,15 @@ Add your specific project rules and preferences below:
534
705
  console.log(`Generating bundle: ${name}`);
535
706
  const allFiles = this.getAllFiles();
536
707
 
537
- bundle.files = allFiles.filter(file =>
708
+ // Filter files by bundle patterns
709
+ let bundleFiles = allFiles.filter(file =>
538
710
  bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
539
711
  );
540
712
 
713
+ // Remove hidden files
714
+ bundleFiles = bundleFiles.filter(file => !this.isFileHidden(file, name));
715
+
716
+ bundle.files = bundleFiles;
541
717
  bundle.content = this.generateBundleXML(name, bundle.files);
542
718
  bundle.changed = false;
543
719
  bundle.lastGenerated = new Date().toISOString();
@@ -606,6 +782,53 @@ Add your specific project rules and preferences below:
606
782
  .replace(/'/g, ''');
607
783
  }
608
784
 
785
+ getFileStats(filePath) {
786
+ try {
787
+ const fullPath = join(this.CWD, filePath);
788
+ const stat = statSync(fullPath);
789
+ return {
790
+ size: stat.size,
791
+ mtime: stat.mtime
792
+ };
793
+ } catch (e) {
794
+ return {
795
+ size: 0,
796
+ mtime: new Date()
797
+ };
798
+ }
799
+ }
800
+
801
+ getFileListWithVisibility(bundleName = null) {
802
+ const allFiles = this.getAllFiles();
803
+
804
+ return allFiles.map(filePath => {
805
+ const fileStats = this.getFileStats(filePath);
806
+ const isGloballyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
807
+ const bundleHidden = bundleName ? this.isFileHidden(filePath, bundleName) : false;
808
+
809
+ // Determine which bundles this file appears in
810
+ const inBundles = [];
811
+ this.bundles.forEach((bundle, name) => {
812
+ const matchesPattern = bundle.patterns.some(pattern => this.matchesPattern(filePath, pattern));
813
+ const notHidden = !this.isFileHidden(filePath, name);
814
+ if (matchesPattern && notHidden) {
815
+ inBundles.push(name);
816
+ }
817
+ });
818
+
819
+ return {
820
+ path: filePath,
821
+ size: fileStats.size,
822
+ modified: fileStats.mtime,
823
+ visible: !isGloballyHidden && !bundleHidden,
824
+ globallyHidden: isGloballyHidden,
825
+ bundleHidden: bundleHidden,
826
+ inBundles: inBundles,
827
+ matchesIgnorePattern: this.shouldIgnoreFile(join(this.CWD, filePath))
828
+ };
829
+ });
830
+ }
831
+
609
832
  startServer(port = 3333) {
610
833
  const server = createServer((req, res) => {
611
834
  const url = new URL(req.url, `http://localhost:${port}`);
@@ -705,7 +928,7 @@ Add your specific project rules and preferences below:
705
928
  }
706
929
  }
707
930
 
708
- // API Routes (existing code continues here...)
931
+ // API Routes
709
932
  if (url.pathname === '/api/bundles') {
710
933
  res.writeHead(200, { 'Content-Type': 'application/json' });
711
934
  const bundleData = Array.from(this.bundles.entries()).map(([name, bundle]) => ({
@@ -865,6 +1088,167 @@ Add your specific project rules and preferences below:
865
1088
  res.end('Method not allowed');
866
1089
  }
867
1090
 
1091
+ } else if (url.pathname === '/api/hidden-files') {
1092
+ if (req.method === 'GET') {
1093
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1094
+ const stats = {
1095
+ totalFiles: this.getAllFiles().length,
1096
+ globallyHidden: this.hiddenFilesConfig.globalHidden.length,
1097
+ bundleSpecificHidden: this.hiddenFilesConfig.bundleSpecific,
1098
+ ignorePatterns: {
1099
+ system: [
1100
+ { pattern: '**/.git/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.git/**') },
1101
+ { pattern: '**/node_modules/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/node_modules/**') },
1102
+ { pattern: '**/.cntx/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.cntx/**') }
1103
+ ],
1104
+ user: this.hiddenFilesConfig.userIgnorePatterns,
1105
+ disabled: this.hiddenFilesConfig.disabledSystemPatterns
1106
+ }
1107
+ };
1108
+ res.end(JSON.stringify(stats));
1109
+ } else if (req.method === 'POST') {
1110
+ let body = '';
1111
+ req.on('data', chunk => body += chunk);
1112
+ req.on('end', () => {
1113
+ try {
1114
+ const { action, filePath, filePaths, bundleName, forceHide } = JSON.parse(body);
1115
+
1116
+ if (action === 'toggle' && filePath) {
1117
+ this.toggleFileVisibility(filePath, bundleName, forceHide);
1118
+ } else if (action === 'bulk-toggle' && filePaths) {
1119
+ this.bulkToggleFileVisibility(filePaths, bundleName, forceHide);
1120
+ }
1121
+
1122
+ this.generateAllBundles();
1123
+ this.broadcastUpdate();
1124
+
1125
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1126
+ res.end(JSON.stringify({ success: true }));
1127
+ } catch (e) {
1128
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1129
+ res.end(JSON.stringify({ error: e.message }));
1130
+ }
1131
+ });
1132
+ }
1133
+
1134
+ } else if (url.pathname === '/api/files-with-visibility') {
1135
+ const bundleName = url.searchParams.get('bundle');
1136
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1137
+ const files = this.getFileListWithVisibility(bundleName);
1138
+ res.end(JSON.stringify(files));
1139
+
1140
+ } else if (url.pathname === '/api/ignore-patterns') {
1141
+ if (req.method === 'GET') {
1142
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1143
+
1144
+ // Read file patterns
1145
+ let filePatterns = [];
1146
+ if (existsSync(this.IGNORE_FILE)) {
1147
+ filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
1148
+ .split('\n')
1149
+ .map(line => line.trim())
1150
+ .filter(line => line && !line.startsWith('#'));
1151
+ }
1152
+
1153
+ const systemPatterns = ['**/.git/**', '**/node_modules/**', '**/.cntx/**'];
1154
+
1155
+ const patterns = {
1156
+ system: systemPatterns.map(pattern => ({
1157
+ pattern,
1158
+ active: !this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
1159
+ })),
1160
+ user: this.hiddenFilesConfig.userIgnorePatterns.map(pattern => ({ pattern, active: true })),
1161
+ file: filePatterns.filter(pattern =>
1162
+ !systemPatterns.includes(pattern) &&
1163
+ !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
1164
+ ).map(pattern => ({ pattern, active: true }))
1165
+ };
1166
+ res.end(JSON.stringify(patterns));
1167
+
1168
+ } else if (req.method === 'POST') {
1169
+ let body = '';
1170
+ req.on('data', chunk => body += chunk);
1171
+ req.on('end', () => {
1172
+ try {
1173
+ const { action, pattern } = JSON.parse(body);
1174
+ let success = false;
1175
+
1176
+ switch (action) {
1177
+ case 'add':
1178
+ success = this.addUserIgnorePattern(pattern);
1179
+ break;
1180
+ case 'remove':
1181
+ success = this.removeUserIgnorePattern(pattern);
1182
+ break;
1183
+ case 'toggle-system':
1184
+ this.toggleSystemIgnorePattern(pattern);
1185
+ success = true;
1186
+ break;
1187
+ }
1188
+
1189
+ this.broadcastUpdate();
1190
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1191
+ res.end(JSON.stringify({ success }));
1192
+ } catch (e) {
1193
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1194
+ res.end(JSON.stringify({ error: e.message }));
1195
+ }
1196
+ });
1197
+ }
1198
+
1199
+ } else if (url.pathname === '/api/bundle-visibility-stats') {
1200
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1201
+ const stats = {};
1202
+
1203
+ this.bundles.forEach((bundle, bundleName) => {
1204
+ const allFiles = this.getAllFiles();
1205
+ const matchingFiles = allFiles.filter(file =>
1206
+ bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
1207
+ );
1208
+
1209
+ const visibleFiles = matchingFiles.filter(file => !this.isFileHidden(file, bundleName));
1210
+ const hiddenFiles = matchingFiles.length - visibleFiles.length;
1211
+
1212
+ stats[bundleName] = {
1213
+ total: matchingFiles.length,
1214
+ visible: visibleFiles.length,
1215
+ hidden: hiddenFiles,
1216
+ patterns: bundle.patterns
1217
+ };
1218
+ });
1219
+
1220
+ res.end(JSON.stringify(stats));
1221
+
1222
+ } else if (url.pathname === '/api/reset-hidden-files') {
1223
+ if (req.method === 'POST') {
1224
+ let body = '';
1225
+ req.on('data', chunk => body += chunk);
1226
+ req.on('end', () => {
1227
+ try {
1228
+ const { scope, bundleName } = JSON.parse(body);
1229
+
1230
+ if (scope === 'global') {
1231
+ this.hiddenFilesConfig.globalHidden = [];
1232
+ } else if (scope === 'bundle' && bundleName) {
1233
+ delete this.hiddenFilesConfig.bundleSpecific[bundleName];
1234
+ } else if (scope === 'all') {
1235
+ this.hiddenFilesConfig.globalHidden = [];
1236
+ this.hiddenFilesConfig.bundleSpecific = {};
1237
+ }
1238
+
1239
+ this.saveHiddenFilesConfig();
1240
+ this.generateAllBundles();
1241
+ this.broadcastUpdate();
1242
+
1243
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1244
+ res.end(JSON.stringify({ success: true }));
1245
+ } catch (e) {
1246
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1247
+ res.end(JSON.stringify({ error: e.message }));
1248
+ }
1249
+ });
1250
+ }
1251
+
868
1252
  } else {
869
1253
  res.writeHead(404);
870
1254
  res.end('Not found');