Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

vg-coder-cli

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vg-coder-cli - npm Package Compare versions

Comparing version
1.0.10
to
1.0.11
+320
src/server/api-server.js
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const path = require('path');
const fs = require('fs-extra');
const chalk = require('chalk');
const packageJson = require('../../package.json');
const ProjectDetector = require('../detectors/project-detector');
const FileScanner = require('../scanner/file-scanner');
const TokenManager = require('../tokenizer/token-manager');
const BashExecutor = require('../utils/bash-executor');
/**
* API Server for VG Coder CLI
*/
class ApiServer {
constructor(port = 6868) {
this.port = port;
this.app = express();
this.server = null;
this.workingDir = process.cwd(); // Track working directory
this.setupMiddleware();
this.setupRoutes();
}
/**
* Setup Express middleware
*/
setupMiddleware() {
this.app.use(cors());
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
// Request logging
this.app.use((req, res, next) => {
console.log(chalk.blue(`[${new Date().toISOString()}] ${req.method} ${req.path}`));
next();
});
}
/**
* Setup API routes
*/
setupRoutes() {
// Dashboard - serve HTML interface
this.app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'views', 'dashboard.html'));
});
// Health check
this.app.get('/health', (req, res) => {
res.json({
status: 'ok',
version: packageJson.version,
timestamp: new Date().toISOString()
});
});
// Analyze endpoint - returns project.txt file
this.app.post('/api/analyze', async (req, res) => {
try {
const { path: projectPath, options = {} } = req.body;
if (!projectPath) {
return res.status(400).json({
error: 'Missing required field: path'
});
}
const resolvedPath = path.resolve(projectPath);
// Validate project path
if (!await fs.pathExists(resolvedPath)) {
return res.status(404).json({
error: `Project path does not exist: ${projectPath}`
});
}
console.log(chalk.yellow(`Analyzing project: ${resolvedPath}`));
// Detect project type
const detector = new ProjectDetector(resolvedPath);
const projectInfo = await detector.detectAll();
// Scan files
const scannerOptions = {
maxTokens: parseInt(options.maxTokens || 8000),
extensions: options.extensions ? options.extensions.split(',').map(ext => ext.trim()) : undefined,
includeHidden: options.includeHidden || false
};
const scanner = new FileScanner(resolvedPath, scannerOptions);
const scanResult = await scanner.scanProject();
// Create AI-friendly content
const aiContent = await scanner.createCombinedContentForAI(scanResult.files, {
includeStats: true,
includeTree: true,
preserveLineNumbers: true
});
// Set response headers for file download
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="project.txt"');
res.send(aiContent);
console.log(chalk.green(`✓ Analysis completed: ${scanResult.files.length} files`));
} catch (error) {
console.error(chalk.red('Error during analysis:'), error);
res.status(500).json({
error: 'Analysis failed',
message: error.message
});
}
});
// Info endpoint
this.app.get('/api/info', async (req, res) => {
try {
const projectPath = req.query.path;
if (!projectPath) {
return res.status(400).json({
error: 'Missing required query parameter: path'
});
}
const resolvedPath = path.resolve(projectPath);
if (!await fs.pathExists(resolvedPath)) {
return res.status(404).json({
error: `Project path does not exist: ${projectPath}`
});
}
// Detect project
const detector = new ProjectDetector(resolvedPath);
const projectInfo = await detector.detectAll();
// Quick scan
const scanner = new FileScanner(resolvedPath);
const scanResult = await scanner.scanProject();
// Token analysis
const tokenManager = new TokenManager();
const tokenAnalysis = tokenManager.analyzeFiles(scanResult.files);
tokenManager.cleanup();
const extensions = [...new Set(scanResult.files.map(f => f.extension))].filter(Boolean);
res.json({
path: resolvedPath,
primaryType: projectInfo.primary,
detectedTechnologies: projectInfo.detected,
stats: {
totalFiles: scanResult.stats.processedFiles,
totalSize: scanResult.files.reduce((sum, f) => sum + f.size, 0),
totalLines: scanResult.files.reduce((sum, f) => sum + f.lines, 0),
extensions: extensions
},
tokens: {
total: tokenAnalysis.summary.totalTokens,
averagePerFile: tokenAnalysis.summary.averageTokensPerFile,
filesExceedingLimit: tokenAnalysis.summary.filesExceedingLimit,
estimatedChunks: tokenAnalysis.summary.estimatedChunks
}
});
console.log(chalk.green(`✓ Info retrieved for: ${resolvedPath}`));
} catch (error) {
console.error(chalk.red('Error getting info:'), error);
res.status(500).json({
error: 'Failed to get project info',
message: error.message
});
}
});
// Clean endpoint
this.app.delete('/api/clean', async (req, res) => {
try {
const { output } = req.body;
if (!output) {
return res.status(400).json({
error: 'Missing required field: output'
});
}
const outputPath = path.resolve(output);
if (await fs.pathExists(outputPath)) {
await fs.remove(outputPath);
res.json({
success: true,
message: `Cleaned: ${outputPath}`
});
console.log(chalk.green(`✓ Cleaned: ${outputPath}`));
} else {
res.json({
success: true,
message: 'Output directory does not exist'
});
}
} catch (error) {
console.error(chalk.red('Error cleaning:'), error);
res.status(500).json({
error: 'Failed to clean',
message: error.message
});
}
});
// Execute bash script endpoint
this.app.post('/api/execute', async (req, res) => {
try {
const { bash } = req.body;
if (!bash) {
return res.status(400).json({
error: 'Missing required field: bash'
});
}
console.log(chalk.yellow(`Executing bash script (${bash.length} chars)...`));
// Create executor with working directory
const executor = new BashExecutor(this.workingDir);
// Execute script (validates syntax first, then executes)
const result = await executor.execute(bash);
if (result.success) {
console.log(chalk.green(`✓ Bash execution completed in ${result.executionTime}ms`));
res.json(result);
} else {
console.log(chalk.red(`✗ Bash execution failed: ${result.error || 'Exit code ' + result.exitCode}`));
res.status(400).json(result);
}
} catch (error) {
console.error(chalk.red('Error executing bash:'), error);
res.status(500).json({
error: 'Execution failed',
message: error.message
});
}
});
// 404 handler
this.app.use((req, res) => {
res.status(404).json({
error: 'Not found',
message: `Route ${req.method} ${req.path} not found`
});
});
// Error handler
this.app.use((err, req, res, next) => {
console.error(chalk.red('Server error:'), err);
res.status(500).json({
error: 'Internal server error',
message: err.message
});
});
}
/**
* Start the server
*/
async start() {
return new Promise((resolve, reject) => {
this.server = this.app.listen(this.port, () => {
console.log(chalk.green(`\n🚀 VG Coder API Server started!`));
console.log(chalk.blue(`📡 Listening on: http://localhost:${this.port}`));
console.log(chalk.cyan(`\n🎨 Dashboard: http://localhost:${this.port}`));
console.log(chalk.yellow(`\n📚 Available endpoints:`));
console.log(` GET /health - Health check`);
console.log(` POST /api/analyze - Analyze project (returns project.txt)`);
console.log(` GET /api/info?path=. - Get project info`);
console.log(` DELETE /api/clean - Clean output directory`);
console.log(` POST /api/execute - Execute bash script`);
console.log(chalk.gray(`\n💡 Press Ctrl+C to stop the server\n`));
resolve();
});
this.server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(chalk.red(`\n❌ Port ${this.port} is already in use!`));
console.log(chalk.yellow(`Try using a different port with: vg start -p <port>\n`));
} else {
console.error(chalk.red('\n❌ Server error:'), err.message);
}
reject(err);
});
});
}
/**
* Stop the server
*/
async stop() {
return new Promise((resolve) => {
if (this.server) {
this.server.close(() => {
console.log(chalk.yellow('\n👋 Server stopped\n'));
resolve();
});
} else {
resolve();
}
});
}
}
module.exports = ApiServer;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VG Coder API Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.header h1 {
color: #667eea;
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 1.1em;
}
.status {
display: inline-block;
padding: 8px 16px;
background: #10b981;
color: white;
border-radius: 20px;
font-size: 0.9em;
margin-top: 10px;
}
.endpoints {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 20px;
}
.endpoint-card {
background: white;
border-radius: 12px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
transition: transform 0.3s ease;
}
.endpoint-card:hover {
transform: translateY(-5px);
}
.endpoint-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.method {
padding: 6px 12px;
border-radius: 6px;
font-weight: bold;
font-size: 0.85em;
}
.method.get { background: #3b82f6; color: white; }
.method.post { background: #10b981; color: white; }
.method.delete { background: #ef4444; color: white; }
.endpoint-path {
font-family: 'Courier New', monospace;
color: #333;
font-size: 1.1em;
}
.endpoint-desc {
color: #666;
margin-bottom: 15px;
line-height: 1.6;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: 500;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 2px solid #e5e7eb;
border-radius: 6px;
font-size: 0.95em;
font-family: 'Courier New', monospace;
}
.form-group textarea {
min-height: 120px;
resize: vertical;
}
.btn {
background: #667eea;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 1em;
font-weight: 600;
transition: background 0.3s ease;
}
.btn:hover {
background: #5568d3;
}
.btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.response {
margin-top: 15px;
padding: 15px;
border-radius: 6px;
background: #f9fafb;
border-left: 4px solid #667eea;
display: none;
}
.response.show {
display: block;
}
.response.success {
border-left-color: #10b981;
background: #f0fdf4;
}
.response.error {
border-left-color: #ef4444;
background: #fef2f2;
}
.response pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 0.9em;
white-space: pre-wrap;
word-wrap: break-word;
}
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 VG Coder API Dashboard</h1>
<p>Test your API endpoints directly from the browser</p>
<span class="status" id="status">● Server Running</span>
</div>
<div class="endpoints">
<!-- Health Check -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<span class="endpoint-path">/health</span>
</div>
<p class="endpoint-desc">Check server health status</p>
<button class="btn" onclick="testHealth()">Test Health Check</button>
<div class="response" id="health-response"></div>
</div>
<!-- Analyze -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method post">POST</span>
<span class="endpoint-path">/api/analyze</span>
</div>
<p class="endpoint-desc">Analyze project and download project.txt</p>
<div class="form-group">
<label>Project Path:</label>
<input type="text" id="analyze-path" value="." placeholder=".">
</div>
<div class="form-group">
<label>Max Tokens:</label>
<input type="number" id="analyze-tokens" value="8000" placeholder="8000">
</div>
<button class="btn" onclick="testAnalyze()">Analyze & Download</button>
<div class="response" id="analyze-response"></div>
</div>
<!-- Info -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<span class="endpoint-path">/api/info</span>
</div>
<p class="endpoint-desc">Get project information and statistics</p>
<div class="form-group">
<label>Project Path:</label>
<input type="text" id="info-path" value="." placeholder=".">
</div>
<button class="btn" onclick="testInfo()">Get Info</button>
<div class="response" id="info-response"></div>
</div>
<!-- Execute Bash -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method post">POST</span>
<span class="endpoint-path">/api/execute</span>
</div>
<p class="endpoint-desc">Execute bash script with syntax validation</p>
<div class="form-group">
<label>Bash Script:</label>
<textarea id="execute-bash" placeholder="echo 'Hello World'&#10;date"></textarea>
</div>
<button class="btn" onclick="testExecute()">Execute Script</button>
<div class="response" id="execute-response"></div>
</div>
<!-- Clean -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method delete">DELETE</span>
<span class="endpoint-path">/api/clean</span>
</div>
<p class="endpoint-desc">Clean output directory</p>
<div class="form-group">
<label>Output Path:</label>
<input type="text" id="clean-output" value="./vg-output" placeholder="./vg-output">
</div>
<button class="btn" onclick="testClean()">Clean Directory</button>
<div class="response" id="clean-response"></div>
</div>
</div>
</div>
<script>
const API_BASE = window.location.origin;
function showResponse(elementId, data, isError = false) {
const el = document.getElementById(elementId);
el.className = 'response show ' + (isError ? 'error' : 'success');
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
}
function showLoading(button) {
button.disabled = true;
button.innerHTML = '<span class="loading"></span> Loading...';
}
function resetButton(button, text) {
button.disabled = false;
button.textContent = text;
}
async function testHealth() {
const btn = event.target;
showLoading(btn);
try {
const res = await fetch(`${API_BASE}/health`);
const data = await res.json();
showResponse('health-response', data);
} catch (err) {
showResponse('health-response', { error: err.message }, true);
}
resetButton(btn, 'Test Health Check');
}
async function testAnalyze() {
const btn = event.target;
const path = document.getElementById('analyze-path').value;
const maxTokens = document.getElementById('analyze-tokens').value;
showLoading(btn);
try {
const res = await fetch(`${API_BASE}/api/analyze`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
path,
options: { maxTokens: parseInt(maxTokens) }
})
});
if (res.ok) {
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'project.txt';
a.click();
showResponse('analyze-response', { success: true, message: 'File downloaded!' });
} else {
const data = await res.json();
showResponse('analyze-response', data, true);
}
} catch (err) {
showResponse('analyze-response', { error: err.message }, true);
}
resetButton(btn, 'Analyze & Download');
}
async function testInfo() {
const btn = event.target;
const path = document.getElementById('info-path').value;
showLoading(btn);
try {
const res = await fetch(`${API_BASE}/api/info?path=${encodeURIComponent(path)}`);
const data = await res.json();
showResponse('info-response', data, !res.ok);
} catch (err) {
showResponse('info-response', { error: err.message }, true);
}
resetButton(btn, 'Get Info');
}
async function testExecute() {
const btn = event.target;
const bash = document.getElementById('execute-bash').value;
if (!bash.trim()) {
showResponse('execute-response', { error: 'Bash script is required' }, true);
return;
}
showLoading(btn);
try {
const res = await fetch(`${API_BASE}/api/execute`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bash })
});
const data = await res.json();
showResponse('execute-response', data, !res.ok || !data.success);
} catch (err) {
showResponse('execute-response', { error: err.message }, true);
}
resetButton(btn, 'Execute Script');
}
async function testClean() {
const btn = event.target;
const output = document.getElementById('clean-output').value;
showLoading(btn);
try {
const res = await fetch(`${API_BASE}/api/clean`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ output })
});
const data = await res.json();
showResponse('clean-response', data, !res.ok);
} catch (err) {
showResponse('clean-response', { error: err.message }, true);
}
resetButton(btn, 'Clean Directory');
}
// Check server status periodically
setInterval(async () => {
try {
const res = await fetch(`${API_BASE}/health`);
if (res.ok) {
document.getElementById('status').textContent = '● Server Running';
document.getElementById('status').style.background = '#10b981';
}
} catch {
document.getElementById('status').textContent = '● Server Offline';
document.getElementById('status').style.background = '#ef4444';
}
}, 5000);
</script>
</body>
</html>
const { exec } = require('child_process');
const path = require('path');
const fs = require('fs-extra');
const { promisify } = require('util');
const execAsync = promisify(exec);
/**
* Bash Script Executor
* Validates and executes bash scripts safely
*/
class BashExecutor {
constructor(workingDir) {
this.workingDir = workingDir || process.cwd();
this.tempDir = path.join(this.workingDir, '.vg', 'temp-execute');
}
/**
* Ensure temp directory exists
*/
async ensureTempDir() {
await fs.ensureDir(this.tempDir);
}
/**
* Cleanup temp directory
*/
async cleanup() {
try {
if (await fs.pathExists(this.tempDir)) {
await fs.remove(this.tempDir);
}
} catch (error) {
console.error('Cleanup error:', error.message);
}
}
/**
* Validate bash script syntax
* @param {string} bashScript - The bash script to validate
* @returns {Promise<{valid: boolean, error: string|null}>}
*/
async validateSyntax(bashScript) {
await this.ensureTempDir();
const scriptPath = path.join(this.tempDir, 'validate.sh');
try {
// Write script to temp file
await fs.writeFile(scriptPath, bashScript, 'utf8');
// Validate syntax using bash -n
await execAsync(`bash -n "${scriptPath}"`);
return { valid: true, error: null };
} catch (error) {
return {
valid: false,
error: error.stderr || error.message
};
}
}
/**
* Execute bash script in working directory
* @param {string} bashScript - The bash script to execute
* @returns {Promise<{success: boolean, stdout: string, stderr: string, exitCode: number}>}
*/
async execute(bashScript) {
const startTime = Date.now();
try {
// First validate syntax
const validation = await this.validateSyntax(bashScript);
if (!validation.valid) {
return {
success: false,
error: 'Syntax validation failed',
details: validation.error,
executionTime: Date.now() - startTime
};
}
// Execute script in working directory
const result = await execAsync(bashScript, {
cwd: this.workingDir,
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
shell: '/bin/bash'
});
// Cleanup temp directory
await this.cleanup();
return {
success: true,
stdout: result.stdout || '',
stderr: result.stderr || '',
exitCode: 0,
executionTime: Date.now() - startTime
};
} catch (error) {
// Cleanup even on error
await this.cleanup();
// Check if it's an execution error (not syntax)
if (error.code !== undefined) {
return {
success: false,
stdout: error.stdout || '',
stderr: error.stderr || error.message,
exitCode: error.code || 1,
executionTime: Date.now() - startTime
};
}
// Unknown error
return {
success: false,
error: 'Execution failed',
details: error.message,
executionTime: Date.now() - startTime
};
}
}
}
module.exports = BashExecutor;
+5
-2
{
"name": "vg-coder-cli",
"version": "1.0.10",
"version": "1.0.11",
"description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",

@@ -55,3 +55,6 @@ "main": "src/index.js",

"chalk": "^4.1.2",
"ora": "^5.4.1"
"ora": "^5.4.1",
"express": "^4.18.2",
"cors": "^2.8.5",
"body-parser": "^1.20.2"
},

@@ -58,0 +61,0 @@ "devDependencies": {

+273
-104
# VG Coder CLI
🚀 **CLI tool để phân tích dự án, nối file mã nguồn, đếm token và xuất HTML** với syntax highlighting và copy functionality.
🚀 **Powerful CLI tool & API Server** để phân tích dự án, nối file mã nguồn, đếm token, xuất HTML và thực thi bash scripts qua REST API.
## ✨ Tính năng
- 🔍 **Phát hiện loại dự án**: Tự động nhận diện Angular, Spring Boot, React, Vue, Node.js, Python, Java, .NET.
- 📁 **Xử lý `.gitignore`**: Tuân thủ chuẩn Git với multi-level ignore rules.
- 🛡️ **Hỗ trợ `.vgignore`**: Có độ ưu tiên cao hơn `.gitignore`, với cú pháp giống hệt.
- 📜 **Bỏ qua file mặc định**: Tự động bỏ qua các thư mục phổ biến như `node_modules`, `dist`, `.git`, `build`, `target` và các file cấu hình IDE.
- 📄 **Scan và nối file**: Quét toàn bộ dự án và nối các file mã nguồn lại với nhau.
- 🧮 **Đếm token**: Sử dụng `tiktoken` để đếm token chính xác cho các mô hình AI.
- ✂️ **Chia nhỏ nội dung**: Chia nội dung thông minh thành các chunk nhỏ hơn mà vẫn giữ cấu trúc file.
- 🌐 **Xuất HTML**: Tạo báo cáo HTML tương tác với syntax highlighting và các nút bấm sao chép.
- 📋 **Sao chép vào Clipboard**: Chế độ `--clipboard-only` giúp sao chép toàn bộ mã nguồn đã xử lý vào clipboard, không cần tạo file.
- 🤖 **Tối ưu cho AI**: Xuất file `combined.txt` với định dạng thân thiện cho các mô hình AI và cung cấp mẫu script để hướng dẫn AI.
- 🌳 **Cây thư mục**: Hiển thị và cho phép sao chép cấu trúc cây thư mục của dự án trong giao diện HTML.
- 🔍 **Tìm kiếm tích hợp**: Giao diện HTML đi kèm chức năng tìm kiếm nội dung trực tiếp trong code.
### 📊 Code Analysis & Export
- 🔍 **Phát hiện loại dự án**: Tự động nhận diện Angular, Spring Boot, React, Vue, Node.js, Python, Java, .NET
- 📁 **Xử lý `.gitignore`**: Tuân thủ chuẩn Git với multi-level ignore rules
- 🛡️ **Hỗ trợ `.vgignore`**: Có độ ưu tiên cao hơn `.gitignore`, với cú pháp giống hệt
- 📜 **Bỏ qua file mặc định**: Tự động bỏ qua `node_modules`, `dist`, `.git`, `build`, `target`
- 📄 **Scan và nối file**: Quét toàn bộ dự án và nối các file mã nguồn
- 🧮 **Đếm token**: Sử dụng `tiktoken` để đếm token chính xác cho AI models
- ✂️ **Chia nhỏ nội dung**: Chia nội dung thông minh thành chunks nhỏ hơn
- 🌐 **Xuất HTML**: Tạo báo cáo HTML tương tác với syntax highlighting
- 📋 **Sao chép vào Clipboard**: Chế độ `-c` sao chép toàn bộ code vào clipboard
- 🤖 **Tối ưu cho AI**: Xuất file `combined.txt` với định dạng thân thiện cho AI
### 🚀 API Server (NEW!)
- 🌐 **REST API Server**: Khởi động server với `vg start`
- 🎨 **Beautiful Dashboard**: Tự động mở web UI để test API
- 📡 **5 API Endpoints**:
- `GET /health` - Health check
- `POST /api/analyze` - Phân tích dự án, download project.txt
- `GET /api/info` - Lấy thông tin dự án (JSON)
- `POST /api/execute` - **Thực thi bash scripts** với validation
- `DELETE /api/clean` - Xóa output directory
- ⚡ **Real-time Status**: Dashboard hiển thị server status live
- 🔒 **Syntax Validation**: Validate bash syntax trước khi execute
- 🧹 **Auto Cleanup**: Tự động dọn dẹp temp files
## 📦 Cài đặt
### Cài đặt từ NPM (Recommended)
### Từ NPM (Recommended)
```bash
# Cài đặt global
# Global install
npm install -g vg-coder-cli
# Hoặc cài đặt local
# Local install
npm install vg-coder-cli
```
### Cài đặt từ source
### Từ Source
```bash
# Clone repository
git clone <repository-url>
git clone https://github.com/tinhthanh/vg-coder-cli.git
cd vg-coder-cli
# Cài đặt dependencies
npm install
# Cấp quyền thực thi (nếu cần)
chmod +x bin/vg-coder.js
```

@@ -46,76 +52,130 @@

### Phân tích dự án và xuất HTML
### CLI Commands
#### 1. Phân tích dự án
```bash
# Nếu cài global
vg-coder analyze
# Phân tích và xuất HTML
vg analyze
vg a # Alias rút gọn
# Nếu cài local
npx vg-coder analyze
# Với options
vg analyze /path/to/project --max-tokens 8192 --output ./my-output
# Phân tích dự án khác
vg-coder analyze /path/to/project
# Copy vào clipboard (không tạo file)
vg analyze -c
vg analyze --clipboard
```
# Với options tùy chỉnh
vg-coder analyze /path/to/project --max-tokens 8192 --output ./my-output --theme monokai
#### 2. Xem thông tin dự án
```bash
vg info
vg info /path/to/project
```
### Sao chép nhanh vào Clipboard (Không tạo file)
Chế độ này rất hữu ích để nhanh chóng đưa toàn bộ ngữ cảnh dự án vào các công cụ AI.
#### 3. Xóa output
```bash
# Phân tích và sao chép toàn bộ code vào clipboard
vg-coder analyze --clipboard-only
vg clean
vg clean --output ./my-output
```
# Hoặc dùng alias ngắn gọn
vg-coder analyze --clipboard
#### 4. **Khởi động API Server** 🆕
```bash
# Start server (mặc định port 6868)
vg start
vg s # Alias rút gọn
# Custom port
vg start -p 8080
# Browser tự động mở dashboard tại http://localhost:6868
```
### Xem thông tin dự án
### API Endpoints
#### Health Check
```bash
# Thông tin dự án hiện tại
vg-coder info
GET http://localhost:6868/health
```
# Thông tin dự án khác
vg-coder info /path/to/project
**Response:**
```json
{
"status": "ok",
"version": "1.0.10",
"timestamp": "2025-11-24T15:00:00.000Z"
}
```
### Xóa output
#### Analyze Project
```bash
# Xóa output mặc định
vg-coder clean
POST http://localhost:6868/api/analyze
Content-Type: application/json
# Xóa output tùy chỉnh
vg-coder clean --output ./my-output
{
"path": ".",
"options": {
"maxTokens": 8000
}
}
```
## 📜 Trợ giúp (Help)
**Response:** Downloads `project.txt` file
Bạn có thể xem tất cả các lệnh và tùy chọn có sẵn bằng cách sử dụng cờ `--help` hoặc `-h`.
### Trợ giúp chung
Để xem danh sách các lệnh chính:
#### Get Project Info
```bash
vg-coder --help
GET http://localhost:6868/api/info?path=.
```
**Output (ví dụ):**
**Response:**
```json
{
"path": "/path/to/project",
"primaryType": "nodejs",
"stats": {
"totalFiles": 42,
"totalSize": 123456,
"totalLines": 5000
},
"tokens": {
"total": 15000,
"averagePerFile": 357
}
}
```
Usage: vg-coder [command] [options]
CLI tool để phân tích dự án, nối file mã nguồn, đếm token và xuất HTML
#### Execute Bash Script 🆕
```bash
POST http://localhost:6868/api/execute
Content-Type: application/json
Options:
-V, --version output the version number
-h, --help display help for command
{
"bash": "mkdir -p $(dirname \"src/test.js\")\ncat <<'EOF' > src/test.js\nconsole.log('Hello');\nEOF"
}
```
Commands:
analyze [path] Phân tích dự án và tạo output HTML
info [path] Hiển thị thông tin về dự án
clean Xóa thư mục output
help [command] display help for command
**Response:**
```json
{
"success": true,
"stdout": "",
"stderr": "",
"exitCode": 0,
"executionTime": 15
}
```
### Trợ giúp cho lệnh cụ thể
Để xem chi tiết các tùy chọn cho một lệnh cụ thể (ví dụ: `analyze`):
**Features:**
- ✅ Syntax validation trong `.vg/temp-execute`
- ✅ Execute tại working directory
- ✅ Auto cleanup temp files
- ✅ Return stdout/stderr/exitCode
#### Clean Output
```bash
vg-coder analyze --help
DELETE http://localhost:6868/api/clean
Content-Type: application/json
{
"output": "./vg-output"
}
```

@@ -125,31 +185,61 @@

### CLI Options
| Option | Mô tả | Default |
|--------|-------|---------|
| `--max-tokens <number>` | Số token tối đa mỗi chunk | 8000 |
| `--model <model>` | Model AI để đếm token | gpt-4 |
| `--output <path>` | Thư mục output | ./vg-output |
| `--extensions <list>` | Danh sách extensions (comma-separated) | Tự động phát hiện |
| `--include-hidden` | Bao gồm file ẩn (bị bỏ qua mặc định) | false |
| `--no-structure` | Không ưu tiên giữ cấu trúc file khi chia chunk | false |
| `-o, --output <path>` | Thư mục output | ./vg-output |
| `-m, --max-tokens <number>` | Số token tối đa mỗi chunk | 8000 |
| `-t, --model <model>` | Model AI để đếm token | gpt-4 |
| `--extensions <list>` | Extensions (comma-separated) | Auto-detect |
| `--include-hidden` | Bao gồm file ẩn | false |
| `--no-structure` | Không giữ cấu trúc file | false |
| `--theme <theme>` | Theme cho syntax highlighting | github |
| `--clipboard-only` | Sao chép nội dung vào clipboard thay vì tạo file output. | false |
| `--clipboard` | Alias cho `--clipboard-only` | false |
| `-c, --clipboard` | Copy vào clipboard | false |
| `--save-txt` | Lưu vào vg-projects.txt | false |
### Server Options
## 🤖 Tối ưu cho AI (AI Optimization)
| Option | Mô tả | Default |
|--------|-------|---------|
| `-p, --port <port>` | Port cho server | 6868 |
### File `combined.txt`
Công cụ tạo ra file `combined.txt` được định dạng đặc biệt để dễ dàng đưa vào các mô hình ngôn ngữ lớn. Mỗi file được phân tách rõ ràng bằng một header duy nhất, giúp AI nhận biết và xử lý chính xác từng file.
## 🎨 Dashboard UI
**Ví dụ định dạng:**
```
// ===== FILE: src/index.js =====
... nội dung file index.js ...
Khi chạy `vg start`, browser tự động mở dashboard với:
// ===== FILE: src/utils.js =====
... nội dung file utils.js ...
- 🎯 **Interactive Forms** cho tất cả endpoints
- 🎨 **Beautiful Gradient UI** (purple to violet)
- 📊 **Real-time Server Status** (green/red indicator)
- 💻 **Syntax Highlighting** cho responses
- ⚡ **Loading States** cho async operations
- 📥 **Auto Download** cho analyze endpoint
## 🤖 Tích hợp AI
### System Prompt cho AI
Xem file [SYSTEM_PROMPT.md](SYSTEM_PROMPT.md) để biết cách tích hợp với AI.
**Command Prefixes:**
- `/ask` - Q&A mode (Markdown response)
- `/plan` - Planning mode (Checklist + bash)
- `/fix` - Bug fix mode (Analysis + solution)
- `/code` - Code generation (Bash script only)
### Bash Script Format
Khi AI generate code với `/code`, format chuẩn:
```bash
mkdir -p $(dirname "path/to/file.ext")
cat <<'EOF' > path/to/file.ext
... file content ...
EOF
```
### Mẫu Script Hướng Dẫn AI
Trang `combined.html` có sẵn một mẫu hướng dẫn (prompt template) để yêu cầu AI trả về các thay đổi dưới dạng script shell. Điều này giúp tự động hóa việc áp dụng các thay đổi do AI đề xuất một cách an toàn và có thể kiểm soát.
**Quy tắc:**
- ✅ Luôn có `mkdir -p $(dirname "...")` trước mỗi file
- ✅ Sử dụng `<<'EOF'` (có quotes) để tránh expansion
- ✅ Chỉ include files có thay đổi
- ✅ Ghi đè hoàn toàn file content

@@ -160,12 +250,12 @@ ## 📁 Cấu trúc Output

vg-output/
├── index.html # Trang chính với navigation và cây thư mục
├── combined.html # Tất cả code trong một file HTML, có chức năng tìm kiếm
├── combined.txt # Tất cả code trong một file text, tối ưu cho AI
├── chunks/ # Các chunk riêng biệt (nếu nội dung lớn)
├── index.html # Trang chính với navigation
├── combined.html # Tất cả code, có search
├── combined.txt # Text format, tối ưu cho AI
├── chunks/ # Chunks riêng biệt
│ ├── chunk-1.html
│ └── ...
└── assets/ # CSS, JS cho trang HTML
└── assets/ # CSS, JS
```
## 🎯 Các loại dự án được hỗ trợ
## 🎯 Dự án được hỗ trợ

@@ -178,16 +268,74 @@ - **Frontend**: Angular, React, Vue.js, Svelte

## 🛡️ Quy tắc bỏ qua file (Ignoring Files)
## 🛡️ Quy tắc bỏ qua file
Công cụ tuân thủ các quy tắc bỏ qua file theo thứ tự ưu tiên sau:
1. **`.vgignore`**: Các quy tắc trong file này có độ ưu tiên cao nhất.
2. **`.gitignore`**: Các quy tắc trong file `.gitignore` sẽ được áp dụng.
3. **Quy tắc mặc định**: Nếu không có các file trên, công cụ sẽ tự động bỏ qua các thư mục và file phổ biến như `node_modules`, `.git`, `dist`, `build`, `target`, các file log, và các thư mục cấu hình của IDE (`.vscode`, `.idea`).
Thứ tự ưu tiên:
1. **`.vgignore`** - Cao nhất
2. **`.gitignore`** - Trung bình
3. **Default rules** - Thấp nhất (node_modules, .git, dist, build, target)
## 📝 Examples
### Example 1: Analyze và Copy
```bash
# Analyze project và copy vào clipboard
vg a . -c
# Paste vào AI tool (Claude, ChatGPT, etc.)
```
### Example 2: API Server Workflow
```bash
# 1. Start server
vg start
# 2. Browser mở dashboard tự động
# 3. Test endpoints trực tiếp trên UI
# 4. Hoặc dùng Postman/curl
# 5. Execute bash script từ AI
curl -X POST http://localhost:6868/api/execute \
-H "Content-Type: application/json" \
-d '{"bash": "mkdir -p src && echo \"test\" > src/file.js"}'
```
### Example 3: AI Integration
```bash
# 1. Analyze project
vg a . -c
# 2. Paste vào AI với prompt:
# "/code Thêm authentication vào project này"
# 3. AI trả về bash script
# 4. Copy bash script
# 5. Execute qua API
curl -X POST http://localhost:6868/api/execute \
-H "Content-Type: application/json" \
-d '{"bash": "..."}'
```
## 🔧 Development
```bash
# Install dependencies
npm install
# Run tests
npm test
# Run in dev mode
npm run dev
# Build and publish
npm run push
```
## 🤝 Đóng góp
1. Fork repository
2. Tạo feature branch (`git checkout -b feature/amazing-feature`)
3. Commit changes (`git commit -m 'Add amazing feature'`)
4. Push to branch (`git push origin feature/amazing-feature`)
5. Tạo Pull Request
1. Fork repository
2. Create feature branch (`git checkout -b feature/amazing-feature`)
3. Commit changes (`git commit -m 'Add amazing feature'`)
4. Push to branch (`git push origin feature/amazing-feature`)
5. Create Pull Request

@@ -197,1 +345,22 @@ ## 📄 License

MIT License - xem file [LICENSE](LICENSE) để biết thêm chi tiết.
## 🔗 Links
- **GitHub**: https://github.com/tinhthanh/vg-coder-cli
- **NPM**: https://www.npmjs.com/package/vg-coder-cli
- **Issues**: https://github.com/tinhthanh/vg-coder-cli/issues
## 📊 Version History
### v1.0.10 (Latest)
- ✨ Added API Server with REST endpoints
- 🎨 Beautiful dashboard UI with auto-open browser
- ⚡ Bash script execution with validation
- 🔧 Shortened commands: `vg`, `a`, `-c`, `s`
- 📝 System prompt documentation
### v1.0.9
- 🚀 Initial release
- 📊 Code analysis and token counting
- 🌐 HTML export with syntax highlighting
- 📋 Clipboard integration

@@ -13,2 +13,3 @@ const { Command } = require('commander');

const ClipboardManager = require('./utils/clipboard');
const ApiServer = require('./server/api-server');

@@ -62,2 +63,10 @@ /**

.action(this.handleClean.bind(this));
// Start server command
this.program
.command('start')
.alias('s')
.description('Khởi động API server')
.option('-p, --port <port>', 'Port cho server', '6868')
.action(this.handleStart.bind(this));
}

@@ -364,2 +373,50 @@

/**
* Handle start command
*/
async handleStart(options) {
try {
const port = parseInt(options.port);
const server = new ApiServer(port);
await server.start();
// Auto-open browser to dashboard
const dashboardUrl = `http://localhost:${port}`;
const { exec } = require('child_process');
const platform = process.platform;
let openCommand;
if (platform === 'darwin') {
openCommand = `open ${dashboardUrl}`;
} else if (platform === 'win32') {
openCommand = `start ${dashboardUrl}`;
} else {
openCommand = `xdg-open ${dashboardUrl}`;
}
exec(openCommand, (error) => {
if (error) {
console.log(chalk.yellow(`\n💡 Open your browser manually: ${dashboardUrl}`));
} else {
console.log(chalk.green(`\n✓ Dashboard opened in browser`));
}
});
// Handle graceful shutdown
const shutdown = async () => {
console.log(chalk.yellow('\n\nShutting down server...'));
await server.stop();
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
} catch (error) {
console.error(chalk.red('\n❌ Failed to start server:'), error.message);
process.exit(1);
}
}
/**
* Run CLI

@@ -366,0 +423,0 @@ */