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 +2 -1
- package/src/scanner/file-scanner.js +3 -104
- package/src/server/api-server.js +62 -18
- package/src/server/views/css/structure.css +122 -0
- package/src/server/views/dashboard.css +488 -0
- package/src/server/views/dashboard.html +83 -892
- package/src/server/views/dashboard.js +457 -0
- package/src/server/views/js/api.js +118 -0
- package/src/server/views/js/config.js +120 -0
- package/src/server/views/js/features/structure.js +221 -0
- package/src/server/views/js/handlers.js +182 -0
- package/src/server/views/js/main.js +72 -0
- package/src/server/views/js/utils.js +82 -0
- package/src/tokenizer/token-manager.js +52 -2
- package/vg-coder-cli-2.0.8.tgz +0 -0
- package/vg-coder-cli-2.0.9.tgz +0 -0
- package/vg-coder-cli-1.0.17.tgz +0 -0
- package/vg-coder-cli-2.0.4.tgz +0 -0
- package/vg-coder-cli-2.0.5.tgz +0 -0
- package/vg-coder-cli-2.0.6.tgz +0 -0
- package/vg-coder-cli-2.0.7.tgz +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vg-coder-cli",
|
|
3
|
-
"version": "2.0.
|
|
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
|
-
|
|
385
|
-
|
|
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
|
/**
|
package/src/server/api-server.js
CHANGED
|
@@ -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(
|
|
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: ${
|
|
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
|
+
}
|