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.
@@ -0,0 +1,389 @@
1
+ const { encoding_for_model, get_encoding } = require('tiktoken');
2
+
3
+ /**
4
+ * Quản lý token counting và chunking
5
+ */
6
+ class TokenManager {
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ model: options.model || 'gpt-4',
10
+ maxTokens: options.maxTokens || 8000,
11
+ chunkOverlap: options.chunkOverlap || 200,
12
+ preserveStructure: options.preserveStructure !== false,
13
+ ...options
14
+ };
15
+
16
+ this.encoding = null;
17
+ this.initializeEncoding();
18
+ }
19
+
20
+ /**
21
+ * Khởi tạo encoding
22
+ */
23
+ initializeEncoding() {
24
+ try {
25
+ // Thử sử dụng encoding cho model cụ thể
26
+ this.encoding = encoding_for_model(this.options.model);
27
+ } catch (error) {
28
+ try {
29
+ // Fallback to cl100k_base (GPT-4, GPT-3.5-turbo)
30
+ this.encoding = get_encoding('cl100k_base');
31
+ } catch (fallbackError) {
32
+ // Fallback to p50k_base (GPT-3)
33
+ this.encoding = get_encoding('p50k_base');
34
+ }
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Đếm tokens trong text
40
+ */
41
+ countTokens(text) {
42
+ if (!text || typeof text !== 'string') {
43
+ return 0;
44
+ }
45
+
46
+ try {
47
+ const tokens = this.encoding.encode(text);
48
+ return tokens.length;
49
+ } catch (error) {
50
+ // Fallback: estimate based on characters (rough approximation)
51
+ return Math.ceil(text.length / 4);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Phân tích token usage cho một file
57
+ */
58
+ analyzeFile(file) {
59
+ const headerTokens = this.countTokens(this.generateFileHeader(file));
60
+ const contentTokens = this.countTokens(file.content);
61
+ const totalTokens = headerTokens + contentTokens;
62
+
63
+ return {
64
+ file: file.relativePath,
65
+ headerTokens,
66
+ contentTokens,
67
+ totalTokens,
68
+ exceedsLimit: totalTokens > this.options.maxTokens,
69
+ chunksNeeded: Math.ceil(totalTokens / this.options.maxTokens)
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Phân tích token usage cho tất cả files
75
+ */
76
+ analyzeFiles(files) {
77
+ const analyses = files.map(file => this.analyzeFile(file));
78
+
79
+ const totalTokens = analyses.reduce((sum, analysis) => sum + analysis.totalTokens, 0);
80
+ const filesExceedingLimit = analyses.filter(analysis => analysis.exceedsLimit);
81
+ const totalChunks = analyses.reduce((sum, analysis) => sum + analysis.chunksNeeded, 0);
82
+
83
+ return {
84
+ files: analyses,
85
+ summary: {
86
+ totalFiles: files.length,
87
+ totalTokens,
88
+ averageTokensPerFile: Math.round(totalTokens / files.length),
89
+ filesExceedingLimit: filesExceedingLimit.length,
90
+ totalChunks,
91
+ estimatedChunks: Math.ceil(totalTokens / this.options.maxTokens)
92
+ }
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Chia nhỏ content thành chunks
98
+ */
99
+ async chunkContent(content, metadata = {}) {
100
+ const totalTokens = this.countTokens(content);
101
+
102
+ if (totalTokens <= this.options.maxTokens) {
103
+ return [{
104
+ content,
105
+ tokens: totalTokens,
106
+ chunkIndex: 0,
107
+ totalChunks: 1,
108
+ metadata
109
+ }];
110
+ }
111
+
112
+ return this.options.preserveStructure
113
+ ? await this.chunkByStructure(content, metadata)
114
+ : await this.chunkByTokens(content, metadata);
115
+ }
116
+
117
+ /**
118
+ * Chia nhỏ theo cấu trúc (ưu tiên giữ nguyên files)
119
+ */
120
+ async chunkByStructure(content, metadata = {}) {
121
+ const chunks = [];
122
+ const lines = content.split('\n');
123
+ let currentChunk = '';
124
+ let currentTokens = 0;
125
+ let chunkIndex = 0;
126
+
127
+ // Tìm file boundaries
128
+ const fileBoundaries = this.findFileBoundaries(lines);
129
+
130
+ for (let i = 0; i < fileBoundaries.length; i++) {
131
+ const boundary = fileBoundaries[i];
132
+ const fileContent = lines.slice(boundary.start, boundary.end).join('\n');
133
+ const fileTokens = this.countTokens(fileContent);
134
+
135
+ // Nếu file này làm chunk vượt quá limit
136
+ if (currentTokens + fileTokens > this.options.maxTokens && currentChunk) {
137
+ // Lưu chunk hiện tại
138
+ chunks.push({
139
+ content: currentChunk.trim(),
140
+ tokens: currentTokens,
141
+ chunkIndex: chunkIndex++,
142
+ totalChunks: 0, // Sẽ update sau
143
+ metadata: { ...metadata, type: 'structure' }
144
+ });
145
+
146
+ currentChunk = '';
147
+ currentTokens = 0;
148
+ }
149
+
150
+ // Nếu file quá lớn, chia nhỏ file này
151
+ if (fileTokens > this.options.maxTokens) {
152
+ const fileChunks = await this.chunkLargeFile(fileContent, boundary.filePath);
153
+ chunks.push(...fileChunks.map(chunk => ({
154
+ ...chunk,
155
+ chunkIndex: chunkIndex++,
156
+ metadata: { ...metadata, ...chunk.metadata, type: 'large-file' }
157
+ })));
158
+ } else {
159
+ currentChunk += fileContent + '\n';
160
+ currentTokens += fileTokens;
161
+ }
162
+ }
163
+
164
+ // Thêm chunk cuối cùng
165
+ if (currentChunk.trim()) {
166
+ chunks.push({
167
+ content: currentChunk.trim(),
168
+ tokens: currentTokens,
169
+ chunkIndex: chunkIndex++,
170
+ totalChunks: 0,
171
+ metadata: { ...metadata, type: 'structure' }
172
+ });
173
+ }
174
+
175
+ // Update totalChunks
176
+ chunks.forEach(chunk => {
177
+ chunk.totalChunks = chunks.length;
178
+ });
179
+
180
+ return chunks;
181
+ }
182
+
183
+ /**
184
+ * Chia nhỏ theo tokens (simple splitting)
185
+ */
186
+ async chunkByTokens(content, metadata = {}) {
187
+ const chunks = [];
188
+ const lines = content.split('\n');
189
+ let currentChunk = '';
190
+ let currentTokens = 0;
191
+ let chunkIndex = 0;
192
+
193
+ for (const line of lines) {
194
+ const lineTokens = this.countTokens(line + '\n');
195
+
196
+ if (currentTokens + lineTokens > this.options.maxTokens && currentChunk) {
197
+ // Lưu chunk hiện tại
198
+ chunks.push({
199
+ content: currentChunk.trim(),
200
+ tokens: currentTokens,
201
+ chunkIndex: chunkIndex++,
202
+ totalChunks: 0,
203
+ metadata: { ...metadata, type: 'token-based' }
204
+ });
205
+
206
+ // Bắt đầu chunk mới với overlap
207
+ if (this.options.chunkOverlap > 0) {
208
+ const overlapLines = this.getOverlapLines(currentChunk, this.options.chunkOverlap);
209
+ currentChunk = overlapLines;
210
+ currentTokens = this.countTokens(overlapLines);
211
+ } else {
212
+ currentChunk = '';
213
+ currentTokens = 0;
214
+ }
215
+ }
216
+
217
+ currentChunk += line + '\n';
218
+ currentTokens += lineTokens;
219
+ }
220
+
221
+ // Thêm chunk cuối cùng
222
+ if (currentChunk.trim()) {
223
+ chunks.push({
224
+ content: currentChunk.trim(),
225
+ tokens: currentTokens,
226
+ chunkIndex: chunkIndex++,
227
+ totalChunks: 0,
228
+ metadata: { ...metadata, type: 'token-based' }
229
+ });
230
+ }
231
+
232
+ // Update totalChunks
233
+ chunks.forEach(chunk => {
234
+ chunk.totalChunks = chunks.length;
235
+ });
236
+
237
+ return chunks;
238
+ }
239
+
240
+ /**
241
+ * Chia nhỏ file lớn
242
+ */
243
+ async chunkLargeFile(fileContent, filePath) {
244
+ const lines = fileContent.split('\n');
245
+ const chunks = [];
246
+ let currentChunk = '';
247
+ let currentTokens = 0;
248
+ let chunkIndex = 0;
249
+
250
+ // Thêm header cho file
251
+ const header = `\n=== Large File: ${filePath} (Part {part}) ===\n`;
252
+
253
+ for (const line of lines) {
254
+ const lineTokens = this.countTokens(line + '\n');
255
+
256
+ if (currentTokens + lineTokens > this.options.maxTokens - 100 && currentChunk) { // Reserve 100 tokens for header
257
+ const partHeader = header.replace('{part}', (chunkIndex + 1).toString());
258
+ chunks.push({
259
+ content: partHeader + currentChunk.trim(),
260
+ tokens: this.countTokens(partHeader + currentChunk.trim()),
261
+ chunkIndex: chunkIndex++,
262
+ totalChunks: 0,
263
+ metadata: {
264
+ filePath,
265
+ partNumber: chunkIndex + 1,
266
+ type: 'large-file-part'
267
+ }
268
+ });
269
+
270
+ currentChunk = '';
271
+ currentTokens = 0;
272
+ }
273
+
274
+ currentChunk += line + '\n';
275
+ currentTokens += lineTokens;
276
+ }
277
+
278
+ // Thêm chunk cuối cùng
279
+ if (currentChunk.trim()) {
280
+ const partHeader = header.replace('{part}', (chunkIndex + 1).toString());
281
+ chunks.push({
282
+ content: partHeader + currentChunk.trim(),
283
+ tokens: this.countTokens(partHeader + currentChunk.trim()),
284
+ chunkIndex: chunkIndex++,
285
+ totalChunks: 0,
286
+ metadata: {
287
+ filePath,
288
+ partNumber: chunkIndex + 1,
289
+ type: 'large-file-part'
290
+ }
291
+ });
292
+ }
293
+
294
+ // Update totalChunks
295
+ chunks.forEach(chunk => {
296
+ chunk.totalChunks = chunks.length;
297
+ });
298
+
299
+ return chunks;
300
+ }
301
+
302
+ /**
303
+ * Tìm file boundaries trong content
304
+ */
305
+ findFileBoundaries(lines) {
306
+ const boundaries = [];
307
+ let currentStart = 0;
308
+ let currentFilePath = null;
309
+
310
+ for (let i = 0; i < lines.length; i++) {
311
+ const line = lines[i];
312
+
313
+ // Tìm file header pattern
314
+ if (line.includes('================================================================================')) {
315
+ if (i + 1 < lines.length && lines[i + 1].startsWith('File: ')) {
316
+ // Lưu boundary trước đó
317
+ if (currentFilePath) {
318
+ boundaries.push({
319
+ start: currentStart,
320
+ end: i,
321
+ filePath: currentFilePath
322
+ });
323
+ }
324
+
325
+ // Bắt đầu file mới
326
+ currentStart = i;
327
+ currentFilePath = lines[i + 1].replace('File: ', '').trim();
328
+ }
329
+ }
330
+ }
331
+
332
+ // Thêm boundary cuối cùng
333
+ if (currentFilePath) {
334
+ boundaries.push({
335
+ start: currentStart,
336
+ end: lines.length,
337
+ filePath: currentFilePath
338
+ });
339
+ }
340
+
341
+ return boundaries;
342
+ }
343
+
344
+ /**
345
+ * Lấy overlap lines
346
+ */
347
+ getOverlapLines(content, maxTokens) {
348
+ const lines = content.split('\n');
349
+ let overlapContent = '';
350
+ let tokens = 0;
351
+
352
+ // Lấy từ cuối lên
353
+ for (let i = lines.length - 1; i >= 0; i--) {
354
+ const line = lines[i] + '\n';
355
+ const lineTokens = this.countTokens(line);
356
+
357
+ if (tokens + lineTokens > maxTokens) {
358
+ break;
359
+ }
360
+
361
+ overlapContent = line + overlapContent;
362
+ tokens += lineTokens;
363
+ }
364
+
365
+ return overlapContent;
366
+ }
367
+
368
+ /**
369
+ * Tạo file header
370
+ */
371
+ generateFileHeader(file) {
372
+ return `
373
+ ================================================================================
374
+ File: ${file.relativePath}
375
+ Size: ${file.size} bytes | Lines: ${file.lines}
376
+ ================================================================================`;
377
+ }
378
+
379
+ /**
380
+ * Cleanup encoding
381
+ */
382
+ cleanup() {
383
+ if (this.encoding) {
384
+ this.encoding.free();
385
+ }
386
+ }
387
+ }
388
+
389
+ module.exports = TokenManager;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Utility helpers
3
+ */
4
+
5
+ /**
6
+ * Format bytes to human readable string
7
+ */
8
+ function formatBytes(bytes) {
9
+ if (bytes === 0) return '0 Bytes';
10
+ const k = 1024;
11
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
12
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
13
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
14
+ }
15
+
16
+ /**
17
+ * Format number with commas
18
+ */
19
+ function formatNumber(num) {
20
+ return num.toLocaleString();
21
+ }
22
+
23
+ /**
24
+ * Get file extension from filename
25
+ */
26
+ function getFileExtension(filename) {
27
+ return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2);
28
+ }
29
+
30
+ /**
31
+ * Check if path is hidden (starts with dot)
32
+ */
33
+ function isHidden(filePath) {
34
+ return filePath.split('/').some(part => part.startsWith('.'));
35
+ }
36
+
37
+ /**
38
+ * Sanitize filename for safe usage
39
+ */
40
+ function sanitizeFilename(filename) {
41
+ return filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
42
+ }
43
+
44
+ /**
45
+ * Deep merge objects
46
+ */
47
+ function deepMerge(target, source) {
48
+ const output = Object.assign({}, target);
49
+ if (isObject(target) && isObject(source)) {
50
+ Object.keys(source).forEach(key => {
51
+ if (isObject(source[key])) {
52
+ if (!(key in target))
53
+ Object.assign(output, { [key]: source[key] });
54
+ else
55
+ output[key] = deepMerge(target[key], source[key]);
56
+ } else {
57
+ Object.assign(output, { [key]: source[key] });
58
+ }
59
+ });
60
+ }
61
+ return output;
62
+ }
63
+
64
+ /**
65
+ * Check if value is object
66
+ */
67
+ function isObject(item) {
68
+ return item && typeof item === 'object' && !Array.isArray(item);
69
+ }
70
+
71
+ /**
72
+ * Debounce function
73
+ */
74
+ function debounce(func, wait, immediate) {
75
+ let timeout;
76
+ return function executedFunction(...args) {
77
+ const later = () => {
78
+ timeout = null;
79
+ if (!immediate) func(...args);
80
+ };
81
+ const callNow = immediate && !timeout;
82
+ clearTimeout(timeout);
83
+ timeout = setTimeout(later, wait);
84
+ if (callNow) func(...args);
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Throttle function
90
+ */
91
+ function throttle(func, limit) {
92
+ let inThrottle;
93
+ return function(...args) {
94
+ if (!inThrottle) {
95
+ func.apply(this, args);
96
+ inThrottle = true;
97
+ setTimeout(() => inThrottle = false, limit);
98
+ }
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Generate unique ID
104
+ */
105
+ function generateId() {
106
+ return Math.random().toString(36).substr(2, 9);
107
+ }
108
+
109
+ /**
110
+ * Sleep for specified milliseconds
111
+ */
112
+ function sleep(ms) {
113
+ return new Promise(resolve => setTimeout(resolve, ms));
114
+ }
115
+
116
+ module.exports = {
117
+ formatBytes,
118
+ formatNumber,
119
+ getFileExtension,
120
+ isHidden,
121
+ sanitizeFilename,
122
+ deepMerge,
123
+ isObject,
124
+ debounce,
125
+ throttle,
126
+ generateId,
127
+ sleep
128
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "test-project",
3
+ "version": "1.0.0",
4
+ "description": "A test project for VG Coder CLI",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "start": "node src/index.js",
8
+ "test": "jest"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.0",
12
+ "lodash": "^4.17.21"
13
+ },
14
+ "devDependencies": {
15
+ "jest": "^29.0.0",
16
+ "nodemon": "^3.0.0"
17
+ },
18
+ "keywords": ["test", "demo", "vg-coder"],
19
+ "author": "VG Coder",
20
+ "license": "MIT"
21
+ }
@@ -0,0 +1,129 @@
1
+ const express = require('express');
2
+ const _ = require('lodash');
3
+ const User = require('../models/User');
4
+ const { validateUser, validateEmail } = require('../utils/validation');
5
+
6
+ const router = express.Router();
7
+
8
+ // Get all users
9
+ router.get('/', async (req, res) => {
10
+ try {
11
+ const { page = 1, limit = 10, search } = req.query;
12
+ const offset = (page - 1) * limit;
13
+
14
+ let users = await User.findAll({
15
+ limit: parseInt(limit),
16
+ offset: parseInt(offset),
17
+ where: search ? {
18
+ name: { [Op.iLike]: `%${search}%` }
19
+ } : {}
20
+ });
21
+
22
+ users = users.map(user => _.omit(user.toJSON(), ['password']));
23
+
24
+ res.json({
25
+ users,
26
+ pagination: {
27
+ page: parseInt(page),
28
+ limit: parseInt(limit),
29
+ total: await User.count()
30
+ }
31
+ });
32
+ } catch (error) {
33
+ res.status(500).json({ error: error.message });
34
+ }
35
+ });
36
+
37
+ // Get user by ID
38
+ router.get('/:id', async (req, res) => {
39
+ try {
40
+ const { id } = req.params;
41
+ const user = await User.findByPk(id);
42
+
43
+ if (!user) {
44
+ return res.status(404).json({ error: 'User not found' });
45
+ }
46
+
47
+ res.json(_.omit(user.toJSON(), ['password']));
48
+ } catch (error) {
49
+ res.status(500).json({ error: error.message });
50
+ }
51
+ });
52
+
53
+ // Create new user
54
+ router.post('/', async (req, res) => {
55
+ try {
56
+ const userData = req.body;
57
+
58
+ // Validate input
59
+ const validation = validateUser(userData);
60
+ if (!validation.isValid) {
61
+ return res.status(400).json({
62
+ error: 'Validation failed',
63
+ details: validation.errors
64
+ });
65
+ }
66
+
67
+ // Check if email already exists
68
+ const existingUser = await User.findOne({
69
+ where: { email: userData.email }
70
+ });
71
+
72
+ if (existingUser) {
73
+ return res.status(409).json({
74
+ error: 'Email already exists'
75
+ });
76
+ }
77
+
78
+ const user = await User.create(userData);
79
+ res.status(201).json(_.omit(user.toJSON(), ['password']));
80
+ } catch (error) {
81
+ res.status(500).json({ error: error.message });
82
+ }
83
+ });
84
+
85
+ // Update user
86
+ router.put('/:id', async (req, res) => {
87
+ try {
88
+ const { id } = req.params;
89
+ const updateData = req.body;
90
+
91
+ const user = await User.findByPk(id);
92
+ if (!user) {
93
+ return res.status(404).json({ error: 'User not found' });
94
+ }
95
+
96
+ // Validate update data
97
+ const validation = validateUser(updateData, true);
98
+ if (!validation.isValid) {
99
+ return res.status(400).json({
100
+ error: 'Validation failed',
101
+ details: validation.errors
102
+ });
103
+ }
104
+
105
+ await user.update(updateData);
106
+ res.json(_.omit(user.toJSON(), ['password']));
107
+ } catch (error) {
108
+ res.status(500).json({ error: error.message });
109
+ }
110
+ });
111
+
112
+ // Delete user
113
+ router.delete('/:id', async (req, res) => {
114
+ try {
115
+ const { id } = req.params;
116
+ const user = await User.findByPk(id);
117
+
118
+ if (!user) {
119
+ return res.status(404).json({ error: 'User not found' });
120
+ }
121
+
122
+ await user.destroy();
123
+ res.status(204).send();
124
+ } catch (error) {
125
+ res.status(500).json({ error: error.message });
126
+ }
127
+ });
128
+
129
+ module.exports = router;
@@ -0,0 +1,46 @@
1
+ const express = require('express');
2
+ const _ = require('lodash');
3
+ const userController = require('./controllers/userController');
4
+ const authMiddleware = require('./middleware/auth');
5
+
6
+ const app = express();
7
+ const PORT = process.env.PORT || 3000;
8
+
9
+ // Middleware
10
+ app.use(express.json());
11
+ app.use(express.urlencoded({ extended: true }));
12
+
13
+ // Routes
14
+ app.get('/', (req, res) => {
15
+ res.json({
16
+ message: 'Welcome to Test API',
17
+ version: '1.0.0',
18
+ timestamp: new Date().toISOString()
19
+ });
20
+ });
21
+
22
+ app.use('/api/users', authMiddleware, userController);
23
+
24
+ // Error handling middleware
25
+ app.use((err, req, res, next) => {
26
+ console.error(err.stack);
27
+ res.status(500).json({
28
+ error: 'Something went wrong!',
29
+ message: err.message
30
+ });
31
+ });
32
+
33
+ // 404 handler
34
+ app.use('*', (req, res) => {
35
+ res.status(404).json({
36
+ error: 'Route not found',
37
+ path: req.originalUrl
38
+ });
39
+ });
40
+
41
+ app.listen(PORT, () => {
42
+ console.log(`Server is running on port ${PORT}`);
43
+ console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
44
+ });
45
+
46
+ module.exports = app;