vg-coder-cli 2.0.7 → 2.0.9

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,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  "vg-coder": "./bin/vg-coder.js"
9
9
  },
10
10
  "scripts": {
11
+ "install:local": "npm install -g .",
11
12
  "start": "node src/index.js",
12
13
  "test": "jest",
13
14
  "test:watch": "jest --watch",
@@ -106,6 +106,7 @@ class FileScanner {
106
106
 
107
107
  const node = {
108
108
  path: dirPath,
109
+ relativePath: relativePath,
109
110
  name: name,
110
111
  size: stats.size,
111
112
  type: stats.isDirectory() ? 'directory' : 'file',
@@ -148,47 +149,6 @@ class FileScanner {
148
149
  return node;
149
150
  }
150
151
 
151
- /**
152
- * Get regex for build directories and hidden files to ignore
153
- */
154
- getBuildIgnoreRegex() {
155
- // Create regex pattern for directories to ignore
156
- const ignorePatterns = [
157
- // Hidden directories (starting with .)
158
- '^\\..*',
159
-
160
- // Build directories
161
- '^build$',
162
- '^target$',
163
- '^dist$',
164
- '^out$',
165
- '^bin$',
166
-
167
- // Dependencies
168
- '^node_modules$',
169
- '^vendor$',
170
-
171
- // Temporary files
172
- '^tmp$',
173
- '^temp$',
174
-
175
- // Logs
176
- '^logs$',
177
- '^log$',
178
-
179
- // Coverage reports
180
- '^coverage$'
181
- ];
182
-
183
- // Combine all patterns into one regex
184
- const combinedPattern = ignorePatterns.join('|');
185
- return new RegExp(`(${combinedPattern})`);
186
- }
187
-
188
-
189
-
190
-
191
-
192
152
  /**
193
153
  * Trích xuất danh sách files từ tree
194
154
  */
@@ -381,69 +341,8 @@ class FileScanner {
381
341
  * Tạo nội dung kết hợp từ tất cả files
382
342
  */
383
343
  async createCombinedContent(files, options = {}) {
384
- const {
385
- includeStats = true,
386
- includeTree = true,
387
- headerTemplate = this.getDefaultHeaderTemplate(),
388
- separatorTemplate = this.getDefaultSeparatorTemplate()
389
- } = options;
390
-
391
- let content = '';
392
-
393
- // Header với thông tin project
394
- if (includeStats) {
395
- content += this.generateProjectHeader(files);
396
- content += '\n\n';
397
- }
398
-
399
- // Cấu trúc thư mục
400
- if (includeTree) {
401
- content += this.generateTreeStructure(files);
402
- content += '\n\n';
403
- }
404
-
405
- // Nội dung từng file
406
- for (let i = 0; i < files.length; i++) {
407
- const file = files[i];
408
-
409
- // Header cho file
410
- content += headerTemplate
411
- .replace('{path}', file.relativePath)
412
- .replace('{name}', file.name)
413
- .replace('{extension}', file.extension || '')
414
- .replace('{size}', file.size)
415
- .replace('{lines}', file.lines);
416
-
417
- content += '\n';
418
- content += file.content;
419
- content += '\n';
420
-
421
- // Separator giữa các files
422
- if (i < files.length - 1) {
423
- content += separatorTemplate;
424
- content += '\n';
425
- }
426
- }
427
-
428
- return content;
429
- }
430
-
431
- /**
432
- * Template header mặc định cho file
433
- */
434
- getDefaultHeaderTemplate() {
435
- return `
436
- ================================================================================
437
- File: {path}
438
- Size: {size} bytes | Lines: {lines}
439
- ================================================================================`;
440
- }
441
-
442
- /**
443
- * Template separator mặc định
444
- */
445
- getDefaultSeparatorTemplate() {
446
- return '\n\n';
344
+ // ... (unchanged)
345
+ return this.createCombinedContentForAI(files, options);
447
346
  }
448
347
 
449
348
  /**
@@ -29,8 +29,11 @@ class ApiServer {
29
29
  */
30
30
  setupMiddleware() {
31
31
  this.app.use(cors());
32
- this.app.use(bodyParser.json());
33
- this.app.use(bodyParser.urlencoded({ extended: true }));
32
+ this.app.use(bodyParser.json({ limit: '50mb' })); // Increase limit for large file lists
33
+ this.app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
34
+
35
+ // Serve static files from views directory (CSS, JS)
36
+ this.app.use(express.static(path.join(__dirname, 'views')));
34
37
 
35
38
  // Request logging
36
39
  this.app.use((req, res, next) => {
@@ -60,7 +63,7 @@ class ApiServer {
60
63
  // Analyze endpoint - returns project.txt file
61
64
  this.app.post('/api/analyze', async (req, res) => {
62
65
  try {
63
- const { path: projectPath, options = {} } = req.body;
66
+ const { path: projectPath, options = {}, specificFiles } = req.body;
64
67
 
65
68
  if (!projectPath) {
66
69
  return res.status(400).json({
@@ -78,6 +81,9 @@ class ApiServer {
78
81
  }
79
82
 
80
83
  console.log(chalk.yellow(`Analyzing project: ${resolvedPath}`));
84
+ if (specificFiles) {
85
+ console.log(chalk.yellow(`Filtering for ${specificFiles.length} specific files`));
86
+ }
81
87
 
82
88
  // Detect project type
83
89
  const detector = new ProjectDetector(resolvedPath);
@@ -92,8 +98,15 @@ class ApiServer {
92
98
  const scanner = new FileScanner(resolvedPath, scannerOptions);
93
99
  const scanResult = await scanner.scanProject();
94
100
 
101
+ let filesToProcess = scanResult.files;
102
+
103
+ // Filter specific files if requested
104
+ if (specificFiles && Array.isArray(specificFiles) && specificFiles.length > 0) {
105
+ filesToProcess = filesToProcess.filter(file => specificFiles.includes(file.relativePath));
106
+ }
107
+
95
108
  // Create AI-friendly content
96
- const aiContent = await scanner.createCombinedContentForAI(scanResult.files, {
109
+ const aiContent = await scanner.createCombinedContentForAI(filesToProcess, {
97
110
  includeStats: true,
98
111
  includeTree: true,
99
112
  preserveLineNumbers: true
@@ -104,7 +117,7 @@ class ApiServer {
104
117
  res.setHeader('Content-Disposition', 'attachment; filename="project.txt"');
105
118
  res.send(aiContent);
106
119
 
107
- console.log(chalk.green(`✓ Analysis completed: ${scanResult.files.length} files`));
120
+ console.log(chalk.green(`✓ Analysis completed: ${filesToProcess.length} files`));
108
121
 
109
122
  } catch (error) {
110
123
  console.error(chalk.red('Error during analysis:'), error);
@@ -178,6 +191,50 @@ class ApiServer {
178
191
  }
179
192
  });
180
193
 
194
+ // Structure endpoint
195
+ this.app.get('/api/structure', async (req, res) => {
196
+ try {
197
+ const projectPath = req.query.path || '.';
198
+ const resolvedPath = path.resolve(projectPath);
199
+
200
+ // Validate path
201
+ if (!await fs.pathExists(resolvedPath)) {
202
+ return res.status(404).json({
203
+ error: `Project path does not exist: ${projectPath}`
204
+ });
205
+ }
206
+
207
+ console.log(chalk.yellow(`Analyzing structure for: ${resolvedPath}`));
208
+
209
+ // 1. Scan files
210
+ const scanner = new FileScanner(resolvedPath);
211
+ const scanResult = await scanner.scanProject();
212
+
213
+ // 2. Tokenize tree
214
+ const tokenManager = new TokenManager();
215
+ const enrichedTree = tokenManager.analyzeTree(scanResult.tree, scanResult.files);
216
+
217
+ tokenManager.cleanup();
218
+
219
+ // 3. Return result
220
+ res.json({
221
+ path: resolvedPath,
222
+ totalFiles: scanResult.files.length,
223
+ rootTokens: enrichedTree.tokens,
224
+ structure: enrichedTree
225
+ });
226
+
227
+ console.log(chalk.green(`✓ Structure analysis completed: ${scanResult.files.length} files`));
228
+
229
+ } catch (error) {
230
+ console.error(chalk.red('Error getting structure:'), error);
231
+ res.status(500).json({
232
+ error: 'Failed to get structure',
233
+ message: error.message
234
+ });
235
+ }
236
+ });
237
+
181
238
  // Clean endpoint
182
239
  this.app.delete('/api/clean', async (req, res) => {
183
240
  try {
@@ -282,23 +339,10 @@ class ApiServer {
282
339
  console.log(chalk.green(`\n🚀 VG Coder API Server started!`));
283
340
  console.log(chalk.blue(`📡 Listening on: http://localhost:${this.port}`));
284
341
  console.log(chalk.cyan(`\n🎨 Dashboard: http://localhost:${this.port}`));
285
- console.log(chalk.yellow(`\n📚 Available endpoints:`));
286
- console.log(` GET /health - Health check`);
287
- console.log(` POST /api/analyze - Analyze project (returns project.txt)`);
288
- console.log(` GET /api/info?path=. - Get project info`);
289
- console.log(` DELETE /api/clean - Clean output directory`);
290
- console.log(` POST /api/execute - Execute bash script`);
291
- console.log(chalk.gray(`\n💡 Press Ctrl+C to stop the server\n`));
292
342
  resolve();
293
343
  });
294
344
 
295
345
  this.server.on('error', (err) => {
296
- if (err.code === 'EADDRINUSE') {
297
- console.error(chalk.red(`\n❌ Port ${this.port} is already in use!`));
298
- console.log(chalk.yellow(`Try using a different port with: vg start -p <port>\n`));
299
- } else {
300
- console.error(chalk.red('\n❌ Server error:'), err.message);
301
- }
302
346
  reject(err);
303
347
  });
304
348
  });
@@ -0,0 +1,122 @@
1
+ /* Tree View Styles */
2
+ .tree-container {
3
+ margin-top: 20px;
4
+ background: var(--ios-input-bg);
5
+ border-radius: 12px;
6
+ overflow: hidden;
7
+ border: 1px solid rgba(0,0,0,0.05);
8
+ }
9
+
10
+ .tree-header {
11
+ padding: 12px 16px;
12
+ background: rgba(0,0,0,0.03);
13
+ border-bottom: 1px solid rgba(0,0,0,0.05);
14
+ font-weight: 600;
15
+ display: flex;
16
+ justify-content: space-between;
17
+ align-items: center;
18
+ color: var(--text-primary);
19
+ }
20
+
21
+ .tree-content {
22
+ padding: 10px;
23
+ font-family: 'SF Mono', 'Menlo', monospace;
24
+ font-size: 13px;
25
+ overflow-x: auto;
26
+ }
27
+
28
+ .tree-ul {
29
+ list-style: none;
30
+ padding-left: 20px;
31
+ margin: 0;
32
+ border-left: 1px solid var(--ios-separator);
33
+ }
34
+
35
+ /* Root level shouldn't have border */
36
+ .tree-content > .tree-ul {
37
+ border-left: none;
38
+ padding-left: 0;
39
+ }
40
+
41
+ .tree-li {
42
+ margin: 4px 0;
43
+ position: relative;
44
+ }
45
+
46
+ .tree-item-row {
47
+ display: flex;
48
+ align-items: center;
49
+ padding: 4px 8px;
50
+ border-radius: 6px;
51
+ cursor: pointer;
52
+ transition: background 0.1s;
53
+ }
54
+
55
+ .tree-item-row:hover {
56
+ background: rgba(0,0,0,0.05);
57
+ }
58
+
59
+ /* Checkbox Style */
60
+ .tree-checkbox {
61
+ margin-right: 8px;
62
+ cursor: pointer;
63
+ width: 16px;
64
+ height: 16px;
65
+ accent-color: var(--ios-blue);
66
+ }
67
+
68
+ .tree-icon {
69
+ margin-right: 8px;
70
+ font-size: 14px;
71
+ width: 16px;
72
+ text-align: center;
73
+ }
74
+
75
+ .tree-name {
76
+ flex: 1;
77
+ white-space: nowrap;
78
+ overflow: hidden;
79
+ text-overflow: ellipsis;
80
+ margin-right: 10px;
81
+ color: var(--text-primary);
82
+ }
83
+
84
+ /* Token Badges */
85
+ .token-badge {
86
+ font-size: 11px;
87
+ padding: 2px 6px;
88
+ border-radius: 4px;
89
+ font-weight: 600;
90
+ min-width: 40px;
91
+ text-align: center;
92
+ }
93
+
94
+ .token-low { background: rgba(52, 199, 89, 0.15); color: var(--ios-green); } /* < 2k */
95
+ .token-med { background: rgba(255, 149, 0, 0.15); color: #FF9500; } /* 2k - 5k */
96
+ .token-high { background: rgba(255, 59, 48, 0.15); color: var(--ios-red); } /* > 5k */
97
+
98
+ /* Folder toggle states */
99
+ .tree-li > .tree-ul {
100
+ display: block; /* Default expanded */
101
+ }
102
+
103
+ .tree-li.collapsed > .tree-ul {
104
+ display: none;
105
+ }
106
+
107
+ .arrow {
108
+ display: inline-block;
109
+ width: 12px;
110
+ font-size: 10px;
111
+ color: var(--text-secondary);
112
+ transition: transform 0.2s;
113
+ margin-right: 4px;
114
+ }
115
+
116
+ .tree-li.collapsed > .tree-item-row .arrow {
117
+ transform: rotate(-90deg);
118
+ }
119
+
120
+ .tree-li:not(.has-children) .arrow {
121
+ visibility: hidden;
122
+ }