vg-coder-cli 1.0.6 → 1.0.8

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 CHANGED
@@ -4,14 +4,18 @@
4
4
 
5
5
  ## ✨ Tính năng
6
6
 
7
- - 🔍 **Phát hiện loại dự án**: Tự động nhận diện Angular, Spring Boot, React, Vue, Node.js, Python, Java, .NET
8
- - 📁 **Xử lý .gitignore**: Tuân thủ chuẩn Git với multi-level ignore rules
9
- - 📄 **Scan nối file**: Quét toàn bộ dự án nối file nguồn
10
- - 🧮 **Đếm token**: Sử dụng tiktoken để đếm token chính xác cho AI models
11
- - ✂️ **Chia nhỏ nội dung**: Smart chunking với preserve structure
12
- - 🌐 **Xuất HTML**: Tạo HTML với syntax highlighting copy buttons
13
- - 🎨 **Syntax Highlighting**: Hỗ trợ nhiều ngôn ngữ lập trình
14
- - 📋 **Copy to Clipboard**: Dễ dàng copy code với một click
7
+ - 🔍 **Phát hiện loại dự án**: Tự động nhận diện Angular, Spring Boot, React, Vue, Node.js, Python, Java, .NET.
8
+ - 📁 **Xử lý `.gitignore`**: Tuân thủ chuẩn Git với multi-level ignore rules.
9
+ - 🛡️ **Hỗ trợ `.vgignore`**: độ ưu tiên cao hơn `.gitignore`, với pháp giống hệt.
10
+ - 📜 **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.
11
+ - 📄 **Scan nối file**: Quét toàn bộ dự án và nối các file mã nguồn lại với nhau.
12
+ - 🧮 **Đếm token**: Sử dụng `tiktoken` để đếm token chính xác cho các mô hình AI.
13
+ - ✂️ **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.
14
+ - 🌐 **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.
15
+ - 📋 **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.
16
+ - 🤖 **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.
17
+ - 🌳 **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.
18
+ - 🔍 **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.
15
19
 
16
20
  ## 📦 Cài đặt
17
21
 
@@ -39,7 +43,7 @@ chmod +x bin/vg-coder.js
39
43
 
40
44
  ## 🚀 Sử dụng
41
45
 
42
- ### Phân tích dự án
46
+ ### Phân tích dự án và xuất HTML
43
47
  ```bash
44
48
  # Nếu cài global
45
49
  vg-coder analyze
@@ -54,6 +58,16 @@ vg-coder analyze /path/to/project
54
58
  vg-coder analyze /path/to/project --max-tokens 8192 --output ./my-output --theme monokai
55
59
  ```
56
60
 
61
+ ### Sao chép nhanh vào Clipboard (Không tạo file)
62
+ 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.
63
+ ```bash
64
+ # Phân tích và sao chép toàn bộ code vào clipboard
65
+ vg-coder analyze --clipboard-only
66
+
67
+ # Hoặc dùng alias ngắn gọn
68
+ vg-coder analyze --clipboard
69
+ ```
70
+
57
71
  ### Xem thông tin dự án
58
72
  ```bash
59
73
  # Thông tin dự án hiện tại
@@ -66,10 +80,43 @@ vg-coder info /path/to/project
66
80
  ### Xóa output
67
81
  ```bash
68
82
  # Xóa output mặc định
69
- node src/index.js clean
83
+ vg-coder clean
70
84
 
71
85
  # Xóa output tùy chỉnh
72
- node src/index.js clean --output ./my-output
86
+ vg-coder clean --output ./my-output
87
+ ```
88
+
89
+ ## 📜 Trợ giúp (Help)
90
+
91
+ 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`.
92
+
93
+ ### Trợ giúp chung
94
+ Để xem danh sách các lệnh chính:
95
+ ```bash
96
+ vg-coder --help
97
+ ```
98
+
99
+ **Output (ví dụ):**
100
+ ```
101
+ Usage: vg-coder [command] [options]
102
+
103
+ CLI tool để phân tích dự án, nối file mã nguồn, đếm token và xuất HTML
104
+
105
+ Options:
106
+ -V, --version output the version number
107
+ -h, --help display help for command
108
+
109
+ Commands:
110
+ analyze [path] Phân tích dự án và tạo output HTML
111
+ info [path] Hiển thị thông tin về dự án
112
+ clean Xóa thư mục output
113
+ help [command] display help for command
114
+ ```
115
+
116
+ ### Trợ giúp cho lệnh cụ thể
117
+ Để xem chi tiết các tùy chọn cho một lệnh cụ thể (ví dụ: `analyze`):
118
+ ```bash
119
+ vg-coder analyze --help
73
120
  ```
74
121
 
75
122
  ## ⚙️ Options
@@ -79,68 +126,42 @@ node src/index.js clean --output ./my-output
79
126
  | `--max-tokens <number>` | Số token tối đa mỗi chunk | 8000 |
80
127
  | `--model <model>` | Model AI để đếm token | gpt-4 |
81
128
  | `--output <path>` | Thư mục output | ./vg-output |
82
- | `--extensions <list>` | Danh sách extensions (comma-separated) | Auto-detect |
83
- | `--include-hidden` | Bao gồm file ẩn | false |
84
- | `--no-structure` | Không ưu tiên giữ cấu trúc file | false |
129
+ | `--extensions <list>` | Danh sách extensions (comma-separated) | Tự động phát hiện |
130
+ | `--include-hidden` | Bao gồm file ẩn (bị bỏ qua mặc định) | false |
131
+ | `--no-structure` | Không ưu tiên giữ cấu trúc file khi chia chunk | false |
85
132
  | `--theme <theme>` | Theme cho syntax highlighting | github |
133
+ | `--clipboard-only` | Sao chép nội dung vào clipboard thay vì tạo file output. | false |
134
+ | `--clipboard` | Alias cho `--clipboard-only` | false |
86
135
 
87
- ## 📋 Ví dụ chi tiết
88
136
 
89
- ### Phân tích dự án Angular
90
- ```bash
91
- node src/index.js analyze ./my-angular-app --max-tokens 6000 --theme monokai
92
- ```
137
+ ## 🤖 Tối ưu cho AI (AI Optimization)
93
138
 
94
- ### Phân tích với extensions tùy chỉnh
95
- ```bash
96
- node src/index.js analyze ./my-project --extensions "js,ts,vue,css" --include-hidden
97
- ```
139
+ ### File `combined.txt`
140
+ 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.
98
141
 
99
- ### Xem thông tin chi tiết
100
- ```bash
101
- node src/index.js info ./my-project
102
- # Output:
103
- # 📁 Project Information:
104
- # Path: /path/to/my-project
105
- # Primary Type: nodejs
106
- #
107
- # Detected Technologies:
108
- # nodejs: high confidence
109
- #
110
- # 📊 File Statistics:
111
- # Total Files: 25
112
- # Total Size: 156.7 KB
113
- # Total Lines: 4,523
114
- # Extensions: js, ts, json, md
142
+ **Ví dụ định dạng:**
115
143
  ```
144
+ // ===== FILE: src/index.js =====
145
+ ... nội dung file index.js ...
116
146
 
117
- ## 🧪 Testing
118
-
119
- ```bash
120
- # Chạy tất cả tests
121
- npm test
122
-
123
- # Chạy tests với coverage
124
- npm run test:coverage
125
-
126
- # Chạy specific test
127
- npm test -- --testNamePattern="project detection"
147
+ // ===== FILE: src/utils.js =====
148
+ ... nội dung file utils.js ...
128
149
  ```
129
150
 
151
+ ### Mẫu Script Hướng Dẫn AI
152
+ 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.
153
+
130
154
  ## 📁 Cấu trúc Output
131
155
 
132
156
  ```
133
157
  vg-output/
134
- ├── index.html # Trang chính với navigation
135
- ├── combined.html # Tất cả code trong một file
136
- ├── chunks/ # Các chunk riêng biệt
158
+ ├── index.html # Trang chính với navigation và cây thư mục
159
+ ├── combined.html # Tất cả code trong một file HTML, có chức năng tìm kiếm
160
+ ├── combined.txt # Tất cả code trong một file text, tối ưu cho AI
161
+ ├── chunks/ # Các chunk riêng biệt (nếu nội dung lớn)
137
162
  │ ├── chunk-1.html
138
- │ ├── chunk-2.html
139
163
  │ └── ...
140
- └── assets/ # CSS, JS, themes
141
- ├── styles.css
142
- ├── scripts.js
143
- └── highlight.css
164
+ └── assets/ # CSS, JS cho trang HTML
144
165
  ```
145
166
 
146
167
  ## 🎯 Các loại dự án được hỗ trợ
@@ -151,28 +172,20 @@ vg-output/
151
172
  - **Languages**: JavaScript, TypeScript, Java, Python, C#, Go, Rust
152
173
  - **Config**: JSON, YAML, XML, TOML
153
174
 
154
- ## 🔧 Tùy chỉnh
155
-
156
- ### Extensions mặc định
157
- Tool tự động detect extensions phù hợp với loại dự án:
158
- - **Web**: .js, .jsx, .ts, .tsx, .vue, .html, .css, .scss
159
- - **Backend**: .java, .py, .cs, .go, .rs, .php
160
- - **Config**: .json, .yaml, .xml, .toml, .env
175
+ ## 🛡️ Quy tắc bỏ qua file (Ignoring Files)
161
176
 
162
- ### Themes sẵn
163
- - github (default)
164
- - monokai
165
- - atom-one-dark
166
- - vs2015
167
- - rainbow
177
+ Công cụ tuân thủ các quy tắc bỏ qua file theo thứ tự ưu tiên sau:
178
+ 1. **`.vgignore`**: Các quy tắc trong file này có độ ưu tiên cao nhất.
179
+ 2. **`.gitignore`**: Các quy tắc trong file `.gitignore` sẽ được áp dụng.
180
+ 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`).
168
181
 
169
182
  ## 🤝 Đóng góp
170
183
 
171
- 1. Fork repository
172
- 2. Tạo feature branch (`git checkout -b feature/amazing-feature`)
173
- 3. Commit changes (`git commit -m 'Add amazing feature'`)
174
- 4. Push to branch (`git push origin feature/amazing-feature`)
175
- 5. Tạo Pull Request
184
+ 1. Fork repository
185
+ 2. Tạo feature branch (`git checkout -b feature/amazing-feature`)
186
+ 3. Commit changes (`git commit -m 'Add amazing feature'`)
187
+ 4. Push to branch (`git push origin feature/amazing-feature`)
188
+ 5. Tạo Pull Request
176
189
 
177
190
  ## 📄 License
178
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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": {
@@ -39,11 +39,15 @@ class HtmlExporter {
39
39
 
40
40
  // Tạo combined view
41
41
  await this.createCombinedPage(chunks, metadata);
42
-
42
+
43
+ // Tạo combined.txt cho AI tools
44
+ await this.createCombinedTxtFile(chunks, metadata);
45
+
43
46
  return {
44
47
  indexPath: path.join(this.outputPath, 'index.html'),
45
48
  chunksPath: path.join(this.outputPath, 'chunks'),
46
49
  combinedPath: path.join(this.outputPath, 'combined.html'),
50
+ combinedTxtPath: path.join(this.outputPath, 'combined.txt'),
47
51
  totalFiles: chunks.length
48
52
  };
49
53
  }
@@ -250,6 +254,126 @@ Nếu file chưa tồn tại, script sẽ tự tạo file và thư mục cha.</c
250
254
  await fs.writeFile(path.join(this.outputPath, 'combined.html'), html);
251
255
  }
252
256
 
257
+ /**
258
+ * Tạo combined.txt file cho AI tools với formatting tối ưu
259
+ */
260
+ async createCombinedTxtFile(chunks, metadata) {
261
+ // Sử dụng files gốc nếu có, nếu không thì dùng chunks
262
+ const files = metadata.files;
263
+
264
+ if (files && files.length > 0) {
265
+ // Tạo content từ files gốc với formatting AI-friendly
266
+ let content = '';
267
+
268
+ // Minimal header cho AI context
269
+ content += `// VG Coder Analysis - ${metadata.projectInfo?.primary || 'Unknown'} Project\n`;
270
+ content += `// Files: ${files.length} | Generated: ${new Date().toISOString()}\n\n`;
271
+
272
+ // Nội dung từng file với boundaries rõ ràng
273
+ for (let i = 0; i < files.length; i++) {
274
+ const file = files[i];
275
+
276
+ // File boundary marker
277
+ content += `// ===== FILE: ${file.relativePath} =====\n`;
278
+
279
+ // Nội dung file nguyên bản
280
+ content += file.content;
281
+
282
+ // Đảm bảo file kết thúc bằng newline
283
+ if (!file.content.endsWith('\n')) {
284
+ content += '\n';
285
+ }
286
+
287
+ // Separator giữa các files
288
+ if (i < files.length - 1) {
289
+ content += '\n';
290
+ }
291
+ }
292
+
293
+ await fs.writeFile(path.join(this.outputPath, 'combined.txt'), content, 'utf8');
294
+ } else {
295
+ // Fallback: sử dụng chunks (legacy)
296
+ let content = '';
297
+
298
+ // Minimal header cho AI context
299
+ content += `// VG Coder Analysis - ${metadata.projectInfo?.primary || 'Unknown'} Project\n`;
300
+ content += `// Files: ${chunks.length} | Generated: ${new Date().toISOString()}\n\n`;
301
+
302
+ // Parse chunks để extract file content với boundaries rõ ràng
303
+ for (let i = 0; i < chunks.length; i++) {
304
+ const chunk = chunks[i];
305
+
306
+ // Extract file content từ chunk, bỏ qua headers và separators
307
+ const cleanContent = this.extractCleanContent(chunk.content);
308
+
309
+ if (cleanContent.trim()) {
310
+ content += cleanContent;
311
+
312
+ // Đảm bảo kết thúc bằng newline
313
+ if (!cleanContent.endsWith('\n')) {
314
+ content += '\n';
315
+ }
316
+
317
+ // Separator giữa các chunks (minimal)
318
+ if (i < chunks.length - 1) {
319
+ content += '\n';
320
+ }
321
+ }
322
+ }
323
+
324
+ await fs.writeFile(path.join(this.outputPath, 'combined.txt'), content, 'utf8');
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Extract clean content từ chunk, loại bỏ headers và formatting
330
+ */
331
+ extractCleanContent(chunkContent) {
332
+ const lines = chunkContent.split('\n');
333
+ let cleanLines = [];
334
+ let inFileContent = false;
335
+ let currentFilePath = '';
336
+
337
+ for (let i = 0; i < lines.length; i++) {
338
+ const line = lines[i];
339
+
340
+ // Detect file header
341
+ if (line.includes('================================================================================')) {
342
+ if (i + 1 < lines.length && lines[i + 1].startsWith('File: ')) {
343
+ // Start of new file
344
+ currentFilePath = lines[i + 1].replace('File: ', '').trim();
345
+ cleanLines.push(`// ===== FILE: ${currentFilePath} =====`);
346
+ inFileContent = false;
347
+ i += 2; // Skip header lines
348
+ continue;
349
+ }
350
+ }
351
+
352
+ // Skip project headers and structure
353
+ if (line.startsWith('# Project Analysis Report') ||
354
+ line.startsWith('Generated:') ||
355
+ line.startsWith('Project Path:') ||
356
+ line.startsWith('## Statistics') ||
357
+ line.startsWith('## Project Structure') ||
358
+ line.startsWith('- Total') ||
359
+ line.startsWith('- Extensions:') ||
360
+ line.startsWith('```') ||
361
+ line.startsWith('├──') ||
362
+ line.startsWith('└──') ||
363
+ line.trim() === '') {
364
+ continue;
365
+ }
366
+
367
+ // Add actual file content
368
+ if (currentFilePath && !line.includes('================================================================================')) {
369
+ cleanLines.push(line);
370
+ inFileContent = true;
371
+ }
372
+ }
373
+
374
+ return cleanLines.join('\n');
375
+ }
376
+
253
377
  /**
254
378
  * Copy static assets
255
379
  */
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ const ProjectDetector = require('./detectors/project-detector');
9
9
  const FileScanner = require('./scanner/file-scanner');
10
10
  const TokenManager = require('./tokenizer/token-manager');
11
11
  const HtmlExporter = require('./exporter/html-exporter');
12
+ const ClipboardManager = require('./utils/clipboard');
12
13
 
13
14
  /**
14
15
  * Main CLI Application
@@ -39,6 +40,8 @@ class VGCoderCLI {
39
40
  .option('--include-hidden', 'Bao gồm file ẩn')
40
41
  .option('--no-structure', 'Không ưu tiên giữ cấu trúc file')
41
42
  .option('--theme <theme>', 'Theme cho syntax highlighting', 'github')
43
+ .option('--clipboard-only', 'Copy content to clipboard without creating files')
44
+ .option('--clipboard', 'Alias for --clipboard-only')
42
45
  .action(this.handleAnalyze.bind(this));
43
46
 
44
47
  // Info command
@@ -60,17 +63,25 @@ class VGCoderCLI {
60
63
  */
61
64
  async handleAnalyze(projectPath, options) {
62
65
  const spinner = ora('Initializing analysis...').start();
63
-
66
+
64
67
  try {
65
68
  // Resolve project path
66
69
  projectPath = path.resolve(projectPath || process.cwd());
67
- const outputPath = path.resolve(options.output);
70
+
71
+ // Check if clipboard-only mode
72
+ const clipboardMode = options.clipboardOnly || options.clipboard;
73
+ const outputPath = clipboardMode ? null : path.resolve(options.output || './vg-output');
68
74
 
69
75
  // Validate project path
70
76
  if (!await fs.pathExists(projectPath)) {
71
77
  throw new Error(`Project path does not exist: ${projectPath}`);
72
78
  }
73
79
 
80
+ // Validate output path for non-clipboard mode
81
+ if (!clipboardMode && !outputPath) {
82
+ throw new Error('Output path is required for non-clipboard mode');
83
+ }
84
+
74
85
  spinner.text = 'Detecting project type...';
75
86
 
76
87
  // Detect project type
@@ -123,10 +134,42 @@ class VGCoderCLI {
123
134
  console.log(`Estimated Chunks: ${tokenAnalysis.summary.estimatedChunks}`);
124
135
 
125
136
  spinner.text = 'Creating content chunks...';
126
-
127
- // Create combined content
137
+
138
+ if (clipboardMode) {
139
+ // Clipboard mode: create AI-friendly content and copy to clipboard
140
+ spinner.text = 'Creating AI-friendly content...';
141
+
142
+ const aiContent = await scanner.createCombinedContentForAI(scanResult.files, {
143
+ includeStats: false,
144
+ includeTree: false,
145
+ preserveLineNumbers: true
146
+ });
147
+
148
+ spinner.text = 'Copying to clipboard...';
149
+
150
+ await ClipboardManager.copyToClipboard(aiContent);
151
+ const contentInfo = ClipboardManager.getContentInfo(aiContent);
152
+
153
+ // Cleanup
154
+ tokenManager.cleanup();
155
+
156
+ spinner.succeed('Content copied to clipboard successfully!');
157
+
158
+ console.log(chalk.green('\n📋 Clipboard Content:'));
159
+ console.log(`Files: ${chalk.cyan(scanResult.files.length)}`);
160
+ console.log(`Lines: ${chalk.cyan(contentInfo.lines.toLocaleString())}`);
161
+ console.log(`Characters: ${chalk.cyan(contentInfo.characters.toLocaleString())}`);
162
+ console.log(`Size: ${chalk.cyan(contentInfo.size)}`);
163
+
164
+ console.log(chalk.blue('\n💡 Ready for AI analysis!'));
165
+ console.log('Content is now in your clipboard and ready to paste into AI tools.');
166
+
167
+ return; // Exit early for clipboard mode
168
+ }
169
+
170
+ // Normal mode: create HTML output
128
171
  const combinedContent = await scanner.createCombinedContent(scanResult.files);
129
-
172
+
130
173
  // Chunk content
131
174
  const chunks = await tokenManager.chunkContent(combinedContent, {
132
175
  projectType: projectInfo.primary,
@@ -153,7 +196,8 @@ class VGCoderCLI {
153
196
  projectInfo: projectInfo,
154
197
  scanStats: scanResult.stats,
155
198
  tokenStats: tokenAnalysis.summary,
156
- directoryStructure: treeStructure
199
+ directoryStructure: treeStructure,
200
+ files: scanResult.files // Thêm files gốc để tạo combined.txt
157
201
  });
158
202
 
159
203
  // Cleanup
@@ -164,6 +208,9 @@ class VGCoderCLI {
164
208
  console.log(chalk.green('\n✅ Output Generated:'));
165
209
  console.log(`Index: ${chalk.cyan(exportResult.indexPath)}`);
166
210
  console.log(`Combined: ${chalk.cyan(exportResult.combinedPath)}`);
211
+ if (exportResult.combinedTxtPath) {
212
+ console.log(`Combined.txt: ${chalk.cyan(exportResult.combinedTxtPath)}`);
213
+ }
167
214
  console.log(`Chunks: ${chalk.cyan(exportResult.chunksPath)}`);
168
215
  console.log(`Total Files: ${exportResult.totalFiles}`);
169
216
 
@@ -446,6 +446,54 @@ Size: {size} bytes | Lines: {lines}
446
446
  return '\n\n';
447
447
  }
448
448
 
449
+ /**
450
+ * Tạo nội dung kết hợp cho AI tools với formatting chính xác
451
+ */
452
+ async createCombinedContentForAI(files, options = {}) {
453
+ const {
454
+ includeStats = false,
455
+ includeTree = false,
456
+ preserveLineNumbers = true
457
+ } = options;
458
+
459
+ let content = '';
460
+
461
+ // Header với thông tin project (tùy chọn)
462
+ if (includeStats) {
463
+ content += this.generateProjectHeader(files);
464
+ content += '\n\n';
465
+ }
466
+
467
+ // Cấu trúc thư mục (tùy chọn)
468
+ if (includeTree) {
469
+ content += this.generateTreeStructure(files);
470
+ content += '\n\n';
471
+ }
472
+
473
+ // Nội dung từng file với formatting chính xác
474
+ for (let i = 0; i < files.length; i++) {
475
+ const file = files[i];
476
+
477
+ // File boundary marker - không ảnh hưởng line numbering
478
+ content += `// ===== FILE: ${file.relativePath} =====\n`;
479
+
480
+ // Nội dung file nguyên bản
481
+ content += file.content;
482
+
483
+ // Đảm bảo file kết thúc bằng newline
484
+ if (!file.content.endsWith('\n')) {
485
+ content += '\n';
486
+ }
487
+
488
+ // Separator giữa các files
489
+ if (i < files.length - 1) {
490
+ content += '\n';
491
+ }
492
+ }
493
+
494
+ return content;
495
+ }
496
+
449
497
  /**
450
498
  * Tạo header thông tin project
451
499
  */
@@ -0,0 +1,170 @@
1
+ const { spawn } = require('child_process');
2
+ const os = require('os');
3
+
4
+ /**
5
+ * Cross-platform clipboard utility
6
+ */
7
+ class ClipboardManager {
8
+ /**
9
+ * Copy text to clipboard
10
+ */
11
+ static async copyToClipboard(text) {
12
+ const platform = os.platform();
13
+
14
+ try {
15
+ switch (platform) {
16
+ case 'darwin': // macOS
17
+ return await this.copyMacOS(text);
18
+ case 'win32': // Windows
19
+ return await this.copyWindows(text);
20
+ case 'linux': // Linux
21
+ return await this.copyLinux(text);
22
+ default:
23
+ throw new Error(`Unsupported platform: ${platform}`);
24
+ }
25
+ } catch (error) {
26
+ throw new Error(`Failed to copy to clipboard: ${error.message}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Copy to clipboard on macOS
32
+ */
33
+ static async copyMacOS(text) {
34
+ return new Promise((resolve, reject) => {
35
+ const pbcopy = spawn('pbcopy');
36
+
37
+ pbcopy.stdin.write(text);
38
+ pbcopy.stdin.end();
39
+
40
+ pbcopy.on('close', (code) => {
41
+ if (code === 0) {
42
+ resolve();
43
+ } else {
44
+ reject(new Error(`pbcopy exited with code ${code}`));
45
+ }
46
+ });
47
+
48
+ pbcopy.on('error', (error) => {
49
+ reject(error);
50
+ });
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Copy to clipboard on Windows
56
+ */
57
+ static async copyWindows(text) {
58
+ return new Promise((resolve, reject) => {
59
+ const clip = spawn('clip');
60
+
61
+ clip.stdin.write(text);
62
+ clip.stdin.end();
63
+
64
+ clip.on('close', (code) => {
65
+ if (code === 0) {
66
+ resolve();
67
+ } else {
68
+ reject(new Error(`clip exited with code ${code}`));
69
+ }
70
+ });
71
+
72
+ clip.on('error', (error) => {
73
+ reject(error);
74
+ });
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Copy to clipboard on Linux
80
+ */
81
+ static async copyLinux(text) {
82
+ // Try xclip first, then xsel as fallback
83
+ try {
84
+ return await this.copyLinuxXclip(text);
85
+ } catch (error) {
86
+ try {
87
+ return await this.copyLinuxXsel(text);
88
+ } catch (xselError) {
89
+ throw new Error('Neither xclip nor xsel is available. Please install one of them.');
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Copy using xclip
96
+ */
97
+ static async copyLinuxXclip(text) {
98
+ return new Promise((resolve, reject) => {
99
+ const xclip = spawn('xclip', ['-selection', 'clipboard']);
100
+
101
+ xclip.stdin.write(text);
102
+ xclip.stdin.end();
103
+
104
+ xclip.on('close', (code) => {
105
+ if (code === 0) {
106
+ resolve();
107
+ } else {
108
+ reject(new Error(`xclip exited with code ${code}`));
109
+ }
110
+ });
111
+
112
+ xclip.on('error', (error) => {
113
+ reject(error);
114
+ });
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Copy using xsel
120
+ */
121
+ static async copyLinuxXsel(text) {
122
+ return new Promise((resolve, reject) => {
123
+ const xsel = spawn('xsel', ['--clipboard', '--input']);
124
+
125
+ xsel.stdin.write(text);
126
+ xsel.stdin.end();
127
+
128
+ xsel.on('close', (code) => {
129
+ if (code === 0) {
130
+ resolve();
131
+ } else {
132
+ reject(new Error(`xsel exited with code ${code}`));
133
+ }
134
+ });
135
+
136
+ xsel.on('error', (error) => {
137
+ reject(error);
138
+ });
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Get clipboard content size info
144
+ */
145
+ static getContentInfo(text) {
146
+ const lines = text.split('\n').length;
147
+ const chars = text.length;
148
+ const bytes = Buffer.byteLength(text, 'utf8');
149
+
150
+ return {
151
+ lines,
152
+ characters: chars,
153
+ bytes,
154
+ size: this.formatBytes(bytes)
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Format bytes to human readable
160
+ */
161
+ static formatBytes(bytes) {
162
+ if (bytes === 0) return '0 Bytes';
163
+ const k = 1024;
164
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
165
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
166
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
167
+ }
168
+ }
169
+
170
+ module.exports = ClipboardManager;
Binary file
@@ -1 +0,0 @@
1
- {"name": "test-large", "version": "1.0.0"}
@@ -1 +0,0 @@
1
- {"name": "test-small", "version": "1.0.0"}
@@ -1 +0,0 @@
1
- console.log("Hello World");
Binary file
Binary file
Binary file
Binary file
Binary file