vg-coder-cli 1.0.0
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/README.md +179 -0
- package/bin/vg-coder.js +11 -0
- package/package.json +64 -0
- package/src/detectors/project-detector.js +333 -0
- package/src/exporter/html-exporter.js +1026 -0
- package/src/ignore/ignore-manager.js +298 -0
- package/src/index.js +282 -0
- package/src/scanner/file-scanner.js +592 -0
- package/src/tokenizer/token-manager.js +389 -0
- package/src/utils/helpers.js +128 -0
- package/test-project/package.json +21 -0
- package/test-project/src/controllers/userController.js +129 -0
- package/test-project/src/index.js +46 -0
- package/test-project/src/middleware/auth.js +142 -0
- package/test-project/styles/main.css +287 -0
- package/vg-coder-cli-1.0.0.tgz +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ignore = require('ignore');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Quản lý ignore patterns theo chuẩn Git
|
|
7
|
+
*/
|
|
8
|
+
class IgnoreManager {
|
|
9
|
+
constructor(projectPath) {
|
|
10
|
+
this.projectPath = projectPath;
|
|
11
|
+
this.ignoreInstances = new Map(); // Cache ignore instances theo thư mục
|
|
12
|
+
this.defaultIgnores = this.getDefaultIgnores();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lấy danh sách ignore patterns mặc định
|
|
17
|
+
*/
|
|
18
|
+
getDefaultIgnores() {
|
|
19
|
+
return [
|
|
20
|
+
// Node.js
|
|
21
|
+
'node_modules/',
|
|
22
|
+
'npm-debug.log*',
|
|
23
|
+
'yarn-debug.log*',
|
|
24
|
+
'yarn-error.log*',
|
|
25
|
+
'.npm',
|
|
26
|
+
'.yarn/',
|
|
27
|
+
|
|
28
|
+
// Build outputs
|
|
29
|
+
'dist/',
|
|
30
|
+
'build/',
|
|
31
|
+
'out/',
|
|
32
|
+
'target/',
|
|
33
|
+
'bin/',
|
|
34
|
+
'obj/',
|
|
35
|
+
|
|
36
|
+
// IDE và Editor
|
|
37
|
+
'.vscode/',
|
|
38
|
+
'.idea/',
|
|
39
|
+
'*.swp',
|
|
40
|
+
'*.swo',
|
|
41
|
+
'*~',
|
|
42
|
+
'.DS_Store',
|
|
43
|
+
'Thumbs.db',
|
|
44
|
+
|
|
45
|
+
// Logs
|
|
46
|
+
'logs/',
|
|
47
|
+
'*.log',
|
|
48
|
+
|
|
49
|
+
// Environment files
|
|
50
|
+
'.env',
|
|
51
|
+
'.env.local',
|
|
52
|
+
'.env.development.local',
|
|
53
|
+
'.env.test.local',
|
|
54
|
+
'.env.production.local',
|
|
55
|
+
|
|
56
|
+
// Cache directories
|
|
57
|
+
'.cache/',
|
|
58
|
+
'.parcel-cache/',
|
|
59
|
+
'.next/',
|
|
60
|
+
'.nuxt/',
|
|
61
|
+
|
|
62
|
+
// Coverage reports
|
|
63
|
+
'coverage/',
|
|
64
|
+
'*.lcov',
|
|
65
|
+
|
|
66
|
+
// Dependency directories
|
|
67
|
+
'bower_components/',
|
|
68
|
+
'jspm_packages/',
|
|
69
|
+
|
|
70
|
+
// Java
|
|
71
|
+
'*.class',
|
|
72
|
+
'*.jar',
|
|
73
|
+
'*.war',
|
|
74
|
+
'*.ear',
|
|
75
|
+
'.gradle/',
|
|
76
|
+
|
|
77
|
+
// Python
|
|
78
|
+
'__pycache__/',
|
|
79
|
+
'*.py[cod]',
|
|
80
|
+
'*$py.class',
|
|
81
|
+
'*.so',
|
|
82
|
+
'.Python',
|
|
83
|
+
'env/',
|
|
84
|
+
'venv/',
|
|
85
|
+
'.venv/',
|
|
86
|
+
'pip-log.txt',
|
|
87
|
+
'pip-delete-this-directory.txt',
|
|
88
|
+
|
|
89
|
+
// .NET
|
|
90
|
+
'[Bb]in/',
|
|
91
|
+
'[Oo]bj/',
|
|
92
|
+
'*.user',
|
|
93
|
+
'*.suo',
|
|
94
|
+
'*.userosscache',
|
|
95
|
+
'*.sln.docstates',
|
|
96
|
+
|
|
97
|
+
// Temporary files
|
|
98
|
+
'*.tmp',
|
|
99
|
+
'*.temp',
|
|
100
|
+
'*.bak',
|
|
101
|
+
'*.backup',
|
|
102
|
+
|
|
103
|
+
// OS generated files
|
|
104
|
+
'.DS_Store?',
|
|
105
|
+
'ehthumbs.db',
|
|
106
|
+
'Icon?',
|
|
107
|
+
|
|
108
|
+
// VG Coder output
|
|
109
|
+
'vg-output/'
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Lấy ignore instance cho một thư mục cụ thể
|
|
115
|
+
*/
|
|
116
|
+
async getIgnoreInstance(dirPath) {
|
|
117
|
+
const relativePath = path.relative(this.projectPath, dirPath);
|
|
118
|
+
const cacheKey = relativePath || '.';
|
|
119
|
+
|
|
120
|
+
if (this.ignoreInstances.has(cacheKey)) {
|
|
121
|
+
return this.ignoreInstances.get(cacheKey);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ig = ignore();
|
|
125
|
+
|
|
126
|
+
// Thêm default ignores
|
|
127
|
+
ig.add(this.defaultIgnores);
|
|
128
|
+
|
|
129
|
+
// Đọc .gitignore từ root đến thư mục hiện tại
|
|
130
|
+
const pathParts = relativePath ? relativePath.split(path.sep) : [];
|
|
131
|
+
let currentPath = this.projectPath;
|
|
132
|
+
|
|
133
|
+
// Đọc .gitignore từ root
|
|
134
|
+
await this.addGitignoreFromPath(ig, currentPath);
|
|
135
|
+
|
|
136
|
+
// Đọc .gitignore từ các thư mục con theo thứ tự
|
|
137
|
+
for (const part of pathParts) {
|
|
138
|
+
currentPath = path.join(currentPath, part);
|
|
139
|
+
await this.addGitignoreFromPath(ig, currentPath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.ignoreInstances.set(cacheKey, ig);
|
|
143
|
+
return ig;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Thêm patterns từ .gitignore file
|
|
148
|
+
*/
|
|
149
|
+
async addGitignoreFromPath(ig, dirPath) {
|
|
150
|
+
const gitignorePath = path.join(dirPath, '.gitignore');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
154
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
155
|
+
const patterns = this.parseGitignoreContent(content, dirPath);
|
|
156
|
+
ig.add(patterns);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// Ignore errors reading .gitignore files
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Parse nội dung .gitignore
|
|
165
|
+
*/
|
|
166
|
+
parseGitignoreContent(content, basePath) {
|
|
167
|
+
const lines = content.split('\n');
|
|
168
|
+
const patterns = [];
|
|
169
|
+
|
|
170
|
+
for (let line of lines) {
|
|
171
|
+
line = line.trim();
|
|
172
|
+
|
|
173
|
+
// Skip empty lines và comments
|
|
174
|
+
if (!line || line.startsWith('#')) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Xử lý relative path từ basePath
|
|
179
|
+
const relativePath = path.relative(this.projectPath, basePath);
|
|
180
|
+
if (relativePath && !line.startsWith('/')) {
|
|
181
|
+
// Nếu pattern không bắt đầu bằng /, thêm relative path
|
|
182
|
+
line = path.posix.join(relativePath, line);
|
|
183
|
+
} else if (line.startsWith('/')) {
|
|
184
|
+
// Remove leading slash cho absolute patterns
|
|
185
|
+
line = line.substring(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
patterns.push(line);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return patterns;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Kiểm tra xem một file/thư mục có bị ignore không
|
|
196
|
+
*/
|
|
197
|
+
async shouldIgnore(filePath) {
|
|
198
|
+
try {
|
|
199
|
+
const absolutePath = path.resolve(this.projectPath, filePath);
|
|
200
|
+
const relativePath = path.relative(this.projectPath, absolutePath);
|
|
201
|
+
|
|
202
|
+
// Không ignore nếu file nằm ngoài project
|
|
203
|
+
if (relativePath.startsWith('..')) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const dirPath = path.dirname(absolutePath);
|
|
208
|
+
const ig = await this.getIgnoreInstance(dirPath);
|
|
209
|
+
|
|
210
|
+
return ig.ignores(relativePath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Lọc danh sách files/directories
|
|
218
|
+
*/
|
|
219
|
+
async filterIgnored(items) {
|
|
220
|
+
const results = [];
|
|
221
|
+
|
|
222
|
+
for (const item of items) {
|
|
223
|
+
const shouldIgnore = await this.shouldIgnore(item);
|
|
224
|
+
if (!shouldIgnore) {
|
|
225
|
+
results.push(item);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Lấy tất cả patterns đang được áp dụng cho một thư mục
|
|
234
|
+
*/
|
|
235
|
+
async getAppliedPatterns(dirPath = this.projectPath) {
|
|
236
|
+
const ig = await this.getIgnoreInstance(dirPath);
|
|
237
|
+
return {
|
|
238
|
+
default: this.defaultIgnores,
|
|
239
|
+
gitignore: ig._rules.filter(rule => !this.defaultIgnores.includes(rule.origin))
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Kiểm tra xem có .gitignore file nào không
|
|
245
|
+
*/
|
|
246
|
+
async hasGitignoreFiles() {
|
|
247
|
+
const gitignoreFiles = [];
|
|
248
|
+
|
|
249
|
+
const checkGitignore = async (dirPath) => {
|
|
250
|
+
const gitignorePath = path.join(dirPath, '.gitignore');
|
|
251
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
252
|
+
gitignoreFiles.push(path.relative(this.projectPath, gitignorePath));
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Kiểm tra root
|
|
257
|
+
await checkGitignore(this.projectPath);
|
|
258
|
+
|
|
259
|
+
// Kiểm tra subdirectories (chỉ 2 levels để tránh quá chậm)
|
|
260
|
+
try {
|
|
261
|
+
const items = await fs.readdir(this.projectPath);
|
|
262
|
+
for (const item of items) {
|
|
263
|
+
const itemPath = path.join(this.projectPath, item);
|
|
264
|
+
const stat = await fs.stat(itemPath);
|
|
265
|
+
if (stat.isDirectory()) {
|
|
266
|
+
await checkGitignore(itemPath);
|
|
267
|
+
|
|
268
|
+
// Level 2
|
|
269
|
+
try {
|
|
270
|
+
const subItems = await fs.readdir(itemPath);
|
|
271
|
+
for (const subItem of subItems) {
|
|
272
|
+
const subItemPath = path.join(itemPath, subItem);
|
|
273
|
+
const subStat = await fs.stat(subItemPath);
|
|
274
|
+
if (subStat.isDirectory()) {
|
|
275
|
+
await checkGitignore(subItemPath);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
// Ignore errors
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Ignore errors
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return gitignoreFiles;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Clear cache
|
|
292
|
+
*/
|
|
293
|
+
clearCache() {
|
|
294
|
+
this.ignoreInstances.clear();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports = IgnoreManager;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
|
|
7
|
+
const ProjectDetector = require('./detectors/project-detector');
|
|
8
|
+
const FileScanner = require('./scanner/file-scanner');
|
|
9
|
+
const TokenManager = require('./tokenizer/token-manager');
|
|
10
|
+
const HtmlExporter = require('./exporter/html-exporter');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main CLI Application
|
|
14
|
+
*/
|
|
15
|
+
class VGCoderCLI {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.program = new Command();
|
|
18
|
+
this.setupCommands();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Setup CLI commands
|
|
23
|
+
*/
|
|
24
|
+
setupCommands() {
|
|
25
|
+
this.program
|
|
26
|
+
.name('vg-coder')
|
|
27
|
+
.description('CLI tool để phân tích dự án, nối file mã nguồn, đếm token và xuất HTML')
|
|
28
|
+
.version('1.0.0');
|
|
29
|
+
|
|
30
|
+
// Analyze command
|
|
31
|
+
this.program
|
|
32
|
+
.command('analyze [path]')
|
|
33
|
+
.description('Phân tích dự án và tạo output HTML')
|
|
34
|
+
.option('-o, --output <path>', 'Thư mục output', './vg-output')
|
|
35
|
+
.option('-m, --max-tokens <number>', 'Số token tối đa mỗi chunk', '8000')
|
|
36
|
+
.option('-t, --model <model>', 'Model AI để đếm token', 'gpt-4')
|
|
37
|
+
.option('--extensions <extensions>', 'Danh sách extensions (comma-separated)')
|
|
38
|
+
.option('--include-hidden', 'Bao gồm file ẩn')
|
|
39
|
+
.option('--no-structure', 'Không ưu tiên giữ cấu trúc file')
|
|
40
|
+
.option('--theme <theme>', 'Theme cho syntax highlighting', 'github')
|
|
41
|
+
.action(this.handleAnalyze.bind(this));
|
|
42
|
+
|
|
43
|
+
// Info command
|
|
44
|
+
this.program
|
|
45
|
+
.command('info [path]')
|
|
46
|
+
.description('Hiển thị thông tin về dự án')
|
|
47
|
+
.action(this.handleInfo.bind(this));
|
|
48
|
+
|
|
49
|
+
// Clean command
|
|
50
|
+
this.program
|
|
51
|
+
.command('clean')
|
|
52
|
+
.description('Xóa thư mục output')
|
|
53
|
+
.option('-o, --output <path>', 'Thư mục output', './vg-output')
|
|
54
|
+
.action(this.handleClean.bind(this));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle analyze command
|
|
59
|
+
*/
|
|
60
|
+
async handleAnalyze(projectPath, options) {
|
|
61
|
+
const spinner = ora('Initializing analysis...').start();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Resolve project path
|
|
65
|
+
projectPath = path.resolve(projectPath || process.cwd());
|
|
66
|
+
const outputPath = path.resolve(options.output);
|
|
67
|
+
|
|
68
|
+
// Validate project path
|
|
69
|
+
if (!await fs.pathExists(projectPath)) {
|
|
70
|
+
throw new Error(`Project path does not exist: ${projectPath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
spinner.text = 'Detecting project type...';
|
|
74
|
+
|
|
75
|
+
// Detect project type
|
|
76
|
+
const detector = new ProjectDetector(projectPath);
|
|
77
|
+
const projectInfo = await detector.detectAll();
|
|
78
|
+
|
|
79
|
+
console.log(chalk.blue('\n📁 Project Detection:'));
|
|
80
|
+
console.log(`Primary Type: ${chalk.green(projectInfo.primary)}`);
|
|
81
|
+
if (Object.keys(projectInfo.detected).length > 1) {
|
|
82
|
+
console.log(`Other Types: ${Object.keys(projectInfo.detected).filter(t => t !== projectInfo.primary).join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
spinner.text = 'Scanning files...';
|
|
86
|
+
|
|
87
|
+
// Scan files
|
|
88
|
+
const scannerOptions = {
|
|
89
|
+
maxTokens: parseInt(options.maxTokens),
|
|
90
|
+
extensions: options.extensions ? options.extensions.split(',').map(ext => ext.trim()) : undefined,
|
|
91
|
+
includeHidden: options.includeHidden
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const scanner = new FileScanner(projectPath, scannerOptions);
|
|
95
|
+
const scanResult = await scanner.scanProject();
|
|
96
|
+
|
|
97
|
+
console.log(chalk.blue('\n📊 Scan Results:'));
|
|
98
|
+
console.log(`Files Found: ${scanResult.stats.totalFiles}`);
|
|
99
|
+
console.log(`Files Processed: ${scanResult.stats.processedFiles}`);
|
|
100
|
+
console.log(`Scan Time: ${scanResult.stats.scanTime}ms`);
|
|
101
|
+
|
|
102
|
+
// Hiển thị cấu trúc thư mục được scan
|
|
103
|
+
console.log(chalk.blue('\n📁 Directory Structure:'));
|
|
104
|
+
const treeStructure = scanner.renderProjectTree(scanResult.tree);
|
|
105
|
+
console.log(treeStructure);
|
|
106
|
+
|
|
107
|
+
spinner.text = 'Analyzing tokens...';
|
|
108
|
+
|
|
109
|
+
// Analyze tokens
|
|
110
|
+
const tokenManager = new TokenManager({
|
|
111
|
+
model: options.model,
|
|
112
|
+
maxTokens: parseInt(options.maxTokens),
|
|
113
|
+
preserveStructure: options.structure !== false
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const tokenAnalysis = tokenManager.analyzeFiles(scanResult.files);
|
|
117
|
+
|
|
118
|
+
console.log(chalk.blue('\n🧮 Token Analysis:'));
|
|
119
|
+
console.log(`Total Tokens: ${chalk.yellow(tokenAnalysis.summary.totalTokens.toLocaleString())}`);
|
|
120
|
+
console.log(`Average Tokens/File: ${tokenAnalysis.summary.averageTokensPerFile.toLocaleString()}`);
|
|
121
|
+
console.log(`Files Exceeding Limit: ${tokenAnalysis.summary.filesExceedingLimit}`);
|
|
122
|
+
console.log(`Estimated Chunks: ${tokenAnalysis.summary.estimatedChunks}`);
|
|
123
|
+
|
|
124
|
+
spinner.text = 'Creating content chunks...';
|
|
125
|
+
|
|
126
|
+
// Create combined content
|
|
127
|
+
const combinedContent = await scanner.createCombinedContent(scanResult.files);
|
|
128
|
+
|
|
129
|
+
// Chunk content
|
|
130
|
+
const chunks = await tokenManager.chunkContent(combinedContent, {
|
|
131
|
+
projectType: projectInfo.primary,
|
|
132
|
+
projectPath: projectPath,
|
|
133
|
+
totalFiles: scanResult.files.length
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log(chalk.blue('\n✂️ Chunking Results:'));
|
|
137
|
+
console.log(`Total Chunks: ${chunks.length}`);
|
|
138
|
+
chunks.forEach((chunk, index) => {
|
|
139
|
+
console.log(` Chunk ${index + 1}: ${chunk.tokens.toLocaleString()} tokens (${chunk.metadata?.type || 'unknown'})`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
spinner.text = 'Generating HTML output...';
|
|
143
|
+
|
|
144
|
+
// Export HTML
|
|
145
|
+
const exporter = new HtmlExporter(outputPath, {
|
|
146
|
+
theme: options.theme,
|
|
147
|
+
title: `VG Coder Analysis - ${path.basename(projectPath)}`
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const exportResult = await exporter.exportChunks(chunks, {
|
|
151
|
+
projectType: projectInfo.primary,
|
|
152
|
+
projectInfo: projectInfo,
|
|
153
|
+
scanStats: scanResult.stats,
|
|
154
|
+
tokenStats: tokenAnalysis.summary,
|
|
155
|
+
directoryStructure: treeStructure
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Cleanup
|
|
159
|
+
tokenManager.cleanup();
|
|
160
|
+
|
|
161
|
+
spinner.succeed('Analysis completed successfully!');
|
|
162
|
+
|
|
163
|
+
console.log(chalk.green('\n✅ Output Generated:'));
|
|
164
|
+
console.log(`Index: ${chalk.cyan(exportResult.indexPath)}`);
|
|
165
|
+
console.log(`Combined: ${chalk.cyan(exportResult.combinedPath)}`);
|
|
166
|
+
console.log(`Chunks: ${chalk.cyan(exportResult.chunksPath)}`);
|
|
167
|
+
console.log(`Total Files: ${exportResult.totalFiles}`);
|
|
168
|
+
|
|
169
|
+
console.log(chalk.blue('\n🌐 Open in browser:'));
|
|
170
|
+
console.log(`file://${exportResult.indexPath}`);
|
|
171
|
+
|
|
172
|
+
} catch (error) {
|
|
173
|
+
spinner.fail('Analysis failed');
|
|
174
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handle info command
|
|
181
|
+
*/
|
|
182
|
+
async handleInfo(projectPath, options) {
|
|
183
|
+
const spinner = ora('Gathering project information...').start();
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Resolve project path
|
|
187
|
+
projectPath = path.resolve(projectPath || process.cwd());
|
|
188
|
+
|
|
189
|
+
if (!await fs.pathExists(projectPath)) {
|
|
190
|
+
throw new Error(`Project path does not exist: ${projectPath}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Detect project
|
|
194
|
+
const detector = new ProjectDetector(projectPath);
|
|
195
|
+
const projectInfo = await detector.detectAll();
|
|
196
|
+
|
|
197
|
+
// Quick scan
|
|
198
|
+
const scanner = new FileScanner(projectPath);
|
|
199
|
+
const scanResult = await scanner.scanProject();
|
|
200
|
+
|
|
201
|
+
// Token analysis
|
|
202
|
+
const tokenManager = new TokenManager();
|
|
203
|
+
const tokenAnalysis = tokenManager.analyzeFiles(scanResult.files);
|
|
204
|
+
|
|
205
|
+
spinner.succeed('Information gathered');
|
|
206
|
+
|
|
207
|
+
console.log(chalk.blue('\n📁 Project Information:'));
|
|
208
|
+
console.log(`Path: ${chalk.cyan(projectPath)}`);
|
|
209
|
+
console.log(`Primary Type: ${chalk.green(projectInfo.primary)}`);
|
|
210
|
+
|
|
211
|
+
if (Object.keys(projectInfo.detected).length > 0) {
|
|
212
|
+
console.log('\nDetected Technologies:');
|
|
213
|
+
Object.entries(projectInfo.detected).forEach(([type, info]) => {
|
|
214
|
+
console.log(` ${chalk.yellow(type)}: ${info.confidence} confidence`);
|
|
215
|
+
if (info.version) {
|
|
216
|
+
console.log(` Version: ${info.version}`);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(chalk.blue('\n📊 File Statistics:'));
|
|
222
|
+
console.log(`Total Files: ${scanResult.stats.processedFiles}`);
|
|
223
|
+
console.log(`Total Size: ${scanner.formatBytes(scanResult.files.reduce((sum, f) => sum + f.size, 0))}`);
|
|
224
|
+
console.log(`Total Lines: ${scanResult.files.reduce((sum, f) => sum + f.lines, 0).toLocaleString()}`);
|
|
225
|
+
|
|
226
|
+
const extensions = [...new Set(scanResult.files.map(f => f.extension))].filter(Boolean);
|
|
227
|
+
console.log(`Extensions: ${extensions.join(', ')}`);
|
|
228
|
+
|
|
229
|
+
console.log(chalk.blue('\n🧮 Token Statistics:'));
|
|
230
|
+
console.log(`Total Tokens: ${tokenAnalysis.summary.totalTokens.toLocaleString()}`);
|
|
231
|
+
console.log(`Average Tokens/File: ${tokenAnalysis.summary.averageTokensPerFile.toLocaleString()}`);
|
|
232
|
+
console.log(`Files Exceeding 8K Limit: ${tokenAnalysis.summary.filesExceedingLimit}`);
|
|
233
|
+
console.log(`Estimated Chunks (8K): ${tokenAnalysis.summary.estimatedChunks}`);
|
|
234
|
+
|
|
235
|
+
tokenManager.cleanup();
|
|
236
|
+
|
|
237
|
+
} catch (error) {
|
|
238
|
+
spinner.fail('Failed to gather information');
|
|
239
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Handle clean command
|
|
246
|
+
*/
|
|
247
|
+
async handleClean(options) {
|
|
248
|
+
const spinner = ora('Cleaning output directory...').start();
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const outputPath = path.resolve(options.output);
|
|
252
|
+
|
|
253
|
+
if (await fs.pathExists(outputPath)) {
|
|
254
|
+
await fs.remove(outputPath);
|
|
255
|
+
spinner.succeed(`Cleaned: ${outputPath}`);
|
|
256
|
+
} else {
|
|
257
|
+
spinner.succeed('Output directory does not exist');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
} catch (error) {
|
|
261
|
+
spinner.fail('Failed to clean');
|
|
262
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Run CLI
|
|
269
|
+
*/
|
|
270
|
+
run() {
|
|
271
|
+
this.program.parse();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Export for testing
|
|
276
|
+
module.exports = VGCoderCLI;
|
|
277
|
+
|
|
278
|
+
// Run if called directly
|
|
279
|
+
if (require.main === module) {
|
|
280
|
+
const cli = new VGCoderCLI();
|
|
281
|
+
cli.run();
|
|
282
|
+
}
|