vg-coder-cli 2.0.16 → 2.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.16",
3
+ "version": "2.0.19",
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": {
@@ -51,11 +51,8 @@ class ApiServer {
51
51
 
52
52
  setupSocketIO() {
53
53
  this.io.on('connection', (socket) => {
54
- // Nhận event init với termId
55
54
  socket.on('terminal:init', (data) => {
56
- if (!data || !data.termId) {
57
- return;
58
- }
55
+ if (!data || !data.termId) return;
59
56
  const { termId, cols, rows } = data;
60
57
  terminalManager.createTerminal(socket, termId, cols, rows, this.workingDir);
61
58
  });
@@ -97,62 +94,159 @@ class ApiServer {
97
94
  }
98
95
  });
99
96
 
100
- this.app.get('/api/git/diff', async (req, res) => {
101
- try {
102
- // 1. Lấy diff của các file đã track (modified, deleted, staged)
103
- const { stdout: diffStdout } = await execAsync('git diff HEAD', {
104
- cwd: this.workingDir,
105
- maxBuffer: 20 * 1024 * 1024
106
- });
97
+ // --- GIT API START ---
107
98
 
108
- // 2. Lấy danh sách untracked files (files mới tạo chưa add)
109
- const { stdout: untrackedStdout } = await execAsync('git ls-files --others --exclude-standard', {
110
- cwd: this.workingDir,
111
- maxBuffer: 20 * 1024 * 1024
112
- });
99
+ // Get Git Status
100
+ this.app.get('/api/git/status', async (req, res) => {
101
+ try {
102
+ // FIX: Added -u flag to show individual files in untracked directories
103
+ const { stdout } = await execAsync('git status --porcelain -u', { cwd: this.workingDir });
104
+
105
+ const staged = [];
106
+ const unstaged = [];
107
+ const untracked = [];
108
+
109
+ const lines = stdout.split('\n').filter(l => l.trim());
110
+
111
+ lines.forEach(line => {
112
+ const x = line[0];
113
+ const y = line[1];
114
+ const path = line.substring(3);
115
+
116
+ if (x !== ' ' && x !== '?') {
117
+ staged.push({ path, status: x });
118
+ }
113
119
 
114
- let combinedDiff = diffStdout || '';
115
- const untrackedFiles = untrackedStdout.split('\n').filter(f => f.trim());
116
-
117
- // 3. Tạo diff giả lập cho untracked files để hiển thị trên UI
118
- if (untrackedFiles.length > 0) {
119
- if (combinedDiff && !combinedDiff.endsWith('\n')) combinedDiff += '\n';
120
-
121
- for (const file of untrackedFiles) {
122
- try {
123
- const filePath = path.join(this.workingDir, file);
124
- const stat = await fs.stat(filePath);
125
- if (stat.isDirectory()) continue;
126
-
127
- const content = await fs.readFile(filePath, 'utf8');
128
-
129
- // Tạo header giống git diff
130
- combinedDiff += `diff --git a/${file} b/${file}\n`;
131
- combinedDiff += `new file mode 100644\n`;
132
- combinedDiff += `--- /dev/null\n`;
133
- combinedDiff += `+++ b/${file}\n`;
134
-
135
- if (content.length > 0) {
136
- const lines = content.split('\n');
137
- combinedDiff += `@@ -0,0 +1,${lines.length} @@\n`;
138
- lines.forEach(line => {
139
- combinedDiff += `+${line}\n`;
140
- });
120
+ if (y !== ' ') {
121
+ if (x === '?' && y === '?') {
122
+ untracked.push({ path, status: 'U' });
123
+ } else {
124
+ unstaged.push({ path, status: y });
141
125
  }
142
- combinedDiff += `\n`;
143
- } catch (err) {
144
- // Bỏ qua nếu lỗi đọc file (vd: binary)
145
126
  }
127
+ });
128
+
129
+ const changes = [...unstaged, ...untracked];
130
+ res.json({ staged, changes });
131
+ } catch (error) {
132
+ console.error(chalk.red('❌ [GIT STATUS] Error:'), error.message);
133
+ res.status(500).json({ error: error.message });
134
+ }
135
+ });
136
+
137
+ // Git Stage
138
+ this.app.post('/api/git/stage', async (req, res) => {
139
+ try {
140
+ const { files } = req.body;
141
+ const target = files.includes('*') ? '.' : files.map(f => `"${f}"`).join(' ');
142
+ await execAsync(`git add ${target}`, { cwd: this.workingDir });
143
+ res.json({ success: true });
144
+ } catch (error) {
145
+ res.status(500).json({ error: error.message });
146
+ }
147
+ });
148
+
149
+ // Git Unstage
150
+ this.app.post('/api/git/unstage', async (req, res) => {
151
+ try {
152
+ const { files } = req.body;
153
+ const target = files.includes('*') ? '' : files.map(f => `"${f}"`).join(' ');
154
+ await execAsync(`git reset HEAD ${target}`, { cwd: this.workingDir });
155
+ res.json({ success: true });
156
+ } catch (error) {
157
+ res.status(500).json({ error: error.message });
158
+ }
159
+ });
160
+
161
+ // Git Discard (NEW)
162
+ this.app.post('/api/git/discard', async (req, res) => {
163
+ try {
164
+ const { files } = req.body; // array or '*'
165
+
166
+ // Discard All
167
+ if (files.includes('*')) {
168
+ // Restore tracked files
169
+ try { await execAsync('git restore .', { cwd: this.workingDir }); } catch (e) {}
170
+ // Clean untracked files
171
+ try { await execAsync('git clean -fd', { cwd: this.workingDir }); } catch (e) {}
172
+ } else {
173
+ // Discard specific files
174
+ for (const file of files) {
175
+ // Try restore (for tracked modified/deleted)
176
+ try { await execAsync(`git restore "${file}"`, { cwd: this.workingDir }); } catch (e) {}
177
+ // Try clean (for untracked)
178
+ try { await execAsync(`git clean -f "${file}"`, { cwd: this.workingDir }); } catch (e) {}
179
+ }
180
+ }
181
+ res.json({ success: true });
182
+ } catch (error) {
183
+ console.error('Discard error:', error);
184
+ res.status(500).json({ error: error.message });
185
+ }
186
+ });
187
+
188
+ // Git Commit
189
+ this.app.post('/api/git/commit', async (req, res) => {
190
+ try {
191
+ const { message } = req.body;
192
+ if (!message) throw new Error('Commit message is required');
193
+ const safeMessage = message.replace(/"/g, '\\"');
194
+ await execAsync(`git commit -m "${safeMessage}"`, { cwd: this.workingDir });
195
+ res.json({ success: true });
196
+ } catch (error) {
197
+ res.status(500).json({ error: error.message });
198
+ }
199
+ });
200
+
201
+ // Get Diff
202
+ this.app.get('/api/git/diff', async (req, res) => {
203
+ try {
204
+ const file = req.query.file;
205
+ const type = req.query.type || 'working';
206
+
207
+ let cmd = '';
208
+
209
+ if (type === 'staged') {
210
+ cmd = file ? `git diff --cached -- "${file}"` : `git diff --cached`;
211
+ } else {
212
+ if (file) {
213
+ // Check if directory to avoid EISDIR (Safety Check)
214
+ try {
215
+ const filePath = path.join(this.workingDir, file);
216
+ if (await fs.pathExists(filePath)) {
217
+ const stat = await fs.stat(filePath);
218
+ if (stat.isDirectory()) {
219
+ return res.json({ diff: '' });
220
+ }
221
+ }
222
+ } catch (e) {
223
+ // Ignore stat errors
224
+ }
225
+
226
+ // Check untracked
227
+ const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: this.workingDir });
228
+ if (isUntracked.trim()) {
229
+ const content = await fs.readFile(path.join(this.workingDir, file), 'utf8');
230
+ let fakeDiff = `diff --git a/${file} b/${file}\nnew file mode 100644\n--- /dev/null\n+++ b/${file}\n@@ -0,0 +1,${content.split('\n').length} @@\n`;
231
+ content.split('\n').forEach(l => fakeDiff += `+${l}\n`);
232
+ return res.json({ diff: fakeDiff });
233
+ }
234
+ cmd = `git diff -- "${file}"`;
235
+ } else {
236
+ cmd = `git diff`;
146
237
  }
147
238
  }
148
239
 
149
- res.json({ diff: combinedDiff });
240
+ const { stdout } = await execAsync(cmd, { cwd: this.workingDir, maxBuffer: 20 * 1024 * 1024 });
241
+ res.json({ diff: stdout });
150
242
  } catch (error) {
151
- console.error(chalk.red('❌ [GIT] Error:'), error.message);
243
+ console.error(chalk.red('❌ [GIT DIFF] Error:'), error.message);
152
244
  res.json({ diff: '', error: error.message });
153
245
  }
154
246
  });
155
247
 
248
+ // --- GIT API END ---
249
+
156
250
  this.app.post('/api/analyze', async (req, res) => {
157
251
  const { path: projectPath, options = {}, specificFiles } = req.body;
158
252
  if (!projectPath) return res.status(400).json({ error: 'Missing path' });
@@ -1,4 +1,4 @@
1
- /* --- LAYOUT HEADER --- */
1
+ /* --- CONTAINER & LAYOUT --- */
2
2
  .git-toggle-group {
3
3
  display: flex;
4
4
  align-items: center;
@@ -42,24 +42,6 @@
42
42
  justify-content: center;
43
43
  }
44
44
 
45
- /* --- CONTAINER CHÍNH --- */
46
- .right-panel {
47
- position: relative;
48
- /* Quan trọng */
49
- }
50
-
51
- .d2h-file-list {
52
- position: fixed;
53
- top: 0;
54
- left: 0;
55
- z-index: 99;
56
- background-color: #0d1117;
57
- overflow: scroll;
58
- height: calc(100vh - 46px);
59
- width: 446px;
60
- top: 46px;
61
- }
62
-
63
45
  .git-view-container {
64
46
  position: absolute;
65
47
  top: 50px;
@@ -67,89 +49,275 @@
67
49
  right: 0;
68
50
  bottom: 0;
69
51
  z-index: 9999;
70
- /* Z-index cao nhất */
71
52
  background: #0d1117;
72
- /* Nền đen GitHub */
73
53
  display: none;
74
- flex-direction: column;
75
- overflow: scroll;
54
+ flex-direction: row;
55
+ overflow: hidden;
76
56
  }
77
57
 
78
58
  .git-view-container.active {
79
59
  display: flex;
80
60
  }
81
61
 
82
- /* --- DEBUG / SAFETY NET CSS --- */
83
- /* Ép toàn bộ text trong vùng code phải có màu sáng */
84
- .d2h-wrapper * {
85
- box-sizing: border-box;
62
+ /* --- SIDEBAR --- */
63
+ .git-sidebar {
64
+ width: 300px;
65
+ min-width: 250px;
66
+ background: #0d1117;
67
+ border-right: 1px solid #30363d;
68
+ display: flex;
69
+ flex-direction: column;
70
+ overflow-y: auto;
71
+ color: #c9d1d9;
72
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
73
+ user-select: none;
86
74
  }
87
75
 
88
- /* Force text color */
89
- .d2h-code-line-ctn,
90
- .d2h-code-line,
91
- .hljs {
92
- color: #e6edf3 !important;
93
- /* Trắng xám */
94
- background: transparent !important;
76
+ /* --- COMMIT SECTION --- */
77
+ .git-commit-section {
78
+ padding: 10px;
79
+ border-bottom: 1px solid #30363d;
80
+ background: #0d1117;
95
81
  }
96
82
 
97
- /* Force line number color */
98
- .d2h-code-side-linenumber {
99
- color: #6e7681 !important;
100
- background: #0d1117 !important;
101
- border-color: #30363d !important;
83
+ .git-commit-input {
84
+ width: 100%;
85
+ background: #161b22;
86
+ border: 1px solid #30363d;
87
+ color: #c9d1d9;
88
+ padding: 6px 8px;
89
+ border-radius: 4px;
90
+ font-family: inherit;
91
+ font-size: 13px;
92
+ resize: vertical;
93
+ min-height: 32px;
94
+ margin-bottom: 8px;
102
95
  }
103
96
 
104
- /* Fix background diff */
105
- .d2h-ins {
106
- background-color: rgba(46, 160, 67, 0.15) !important;
97
+ .git-commit-input:focus {
98
+ outline: 1px solid #58a6ff;
99
+ border-color: #58a6ff;
107
100
  }
108
101
 
109
- .d2h-del {
110
- background-color: rgba(248, 81, 73, 0.15) !important;
102
+ .git-commit-btn {
103
+ width: 100%;
104
+ background: #0078d4;
105
+ color: white;
106
+ border: none;
107
+ padding: 6px 12px;
108
+ border-radius: 2px;
109
+ cursor: pointer;
110
+ font-size: 13px;
111
+ font-weight: 500;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ gap: 6px;
111
116
  }
112
117
 
113
- /* Layout */
114
- .d2h-files-wrapper {
115
- flex: 1;
116
- overflow: auto;
117
- background: #0d1117;
118
+ .git-commit-btn:hover {
119
+ background: #026ec1;
118
120
  }
119
121
 
120
- .d2h-file-list-wrapper {
121
- width: 250px;
122
- flex-shrink: 0;
123
- background: #0d1117;
124
- border-right: 1px solid #30363d;
125
- display: flex;
126
- flex-direction: column;
122
+ .git-commit-btn:disabled {
123
+ background: #30363d;
124
+ color: #8b949e;
125
+ cursor: not-allowed;
127
126
  }
128
127
 
129
- .d2h-file-list-header,
130
- .d2h-file-header {
131
- background: #161b22 !important;
132
- border-bottom: 1px solid #30363d !important;
133
- color: #e6edf3 !important;
128
+ /* --- SECTIONS & TREE --- */
129
+ .git-section {
130
+ margin-bottom: 0;
131
+ }
132
+
133
+ .git-section-header {
134
+ padding: 8px 16px;
135
+ font-size: 11px;
136
+ font-weight: bold;
137
+ text-transform: uppercase;
138
+ color: #8b949e;
139
+ background: #161b22;
140
+ display: flex;
141
+ justify-content: space-between;
142
+ align-items: center;
143
+ cursor: pointer;
134
144
  position: sticky;
135
145
  top: 0;
136
146
  z-index: 10;
147
+ border-bottom: 1px solid #21262d;
148
+ }
149
+
150
+ .git-section-header:hover {
151
+ color: #c9d1d9;
152
+ }
153
+
154
+ .git-badge {
155
+ background: #30363d;
156
+ color: #c9d1d9;
157
+ padding: 2px 6px;
158
+ border-radius: 10px;
159
+ font-size: 10px;
160
+ min-width: 18px;
161
+ text-align: center;
162
+ }
163
+
164
+ .git-tree-root {
165
+ list-style: none;
166
+ padding: 0;
167
+ margin: 0;
137
168
  }
138
169
 
139
- .d2h-file-list a {
140
- color: #8b949e !important;
141
- text-decoration: none;
142
- display: block;
143
- padding: 5px 10px;
170
+ .git-tree-node {
171
+ list-style: none;
144
172
  }
145
173
 
146
- .d2h-file-list a:hover {
147
- color: #58a6ff !important;
174
+ .git-tree-content {
175
+ display: flex;
176
+ align-items: center;
177
+ padding: 4px 8px;
178
+ cursor: pointer;
179
+ font-size: 13px;
180
+ height: 24px;
181
+ color: #c9d1d9;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .git-tree-content:hover {
148
186
  background: #161b22;
149
187
  }
150
188
 
151
- /* Ẩn rác */
152
- .d2h-file-switch,
153
- .d2h-tag {
154
- display: none !important;
155
- }
189
+ .git-tree-content.selected {
190
+ background: #30363d;
191
+ color: #fff;
192
+ }
193
+
194
+ .git-indent-guide {
195
+ display: inline-block;
196
+ width: 16px;
197
+ height: 100%;
198
+ flex-shrink: 0;
199
+ }
200
+
201
+ .git-arrow {
202
+ width: 16px;
203
+ text-align: center;
204
+ font-size: 10px;
205
+ color: #8b949e;
206
+ transition: transform 0.15s;
207
+ display: inline-block;
208
+ flex-shrink: 0;
209
+ }
210
+
211
+ .git-tree-node.collapsed > .git-tree-content > .git-arrow {
212
+ transform: rotate(-90deg);
213
+ }
214
+
215
+ .git-tree-node.collapsed > ul {
216
+ display: none;
217
+ }
218
+
219
+ .git-icon {
220
+ margin-right: 6px;
221
+ font-weight: bold;
222
+ width: 16px;
223
+ text-align: center;
224
+ flex-shrink: 0;
225
+ font-size: 14px;
226
+ }
227
+
228
+ /* Status Colors */
229
+ .git-status-M { color: #d29922; }
230
+ .git-status-A, .git-status-U { color: #3fb950; }
231
+ .git-status-D { color: #f85149; text-decoration: line-through; }
232
+ .git-status-R { color: #d29922; }
233
+
234
+ .git-label {
235
+ flex: 1;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .git-dir-label {
242
+ color: #c9d1d9;
243
+ }
244
+
245
+ .git-file-label {
246
+ color: #8b949e;
247
+ }
248
+ .git-tree-content:hover .git-file-label,
249
+ .git-tree-content.selected .git-file-label {
250
+ color: #e6edf3;
251
+ }
252
+
253
+ /* ACTIONS */
254
+ .git-actions {
255
+ display: none;
256
+ margin-left: 6px;
257
+ flex-shrink: 0;
258
+ }
259
+
260
+ .git-tree-content:hover .git-actions {
261
+ display: flex;
262
+ gap: 4px;
263
+ }
264
+
265
+ .git-btn-action {
266
+ background: transparent;
267
+ border: none;
268
+ color: #c9d1d9;
269
+ cursor: pointer;
270
+ padding: 0 4px;
271
+ border-radius: 4px;
272
+ font-size: 14px;
273
+ line-height: 1;
274
+ }
275
+
276
+ .git-btn-action:hover {
277
+ background: #30363d;
278
+ color: #fff;
279
+ }
280
+
281
+ /* Destructive Action (Discard) */
282
+ .git-btn-action.destructive:hover {
283
+ background: #f85149;
284
+ color: #fff;
285
+ }
286
+
287
+ /* --- DIFF AREA --- */
288
+ .git-diff-area {
289
+ flex: 1;
290
+ overflow: auto;
291
+ background: #0d1117;
292
+ position: relative;
293
+ display: flex;
294
+ flex-direction: column;
295
+ }
296
+
297
+ .git-empty-state {
298
+ display: flex;
299
+ flex-direction: column;
300
+ align-items: center;
301
+ justify-content: center;
302
+ height: 100%;
303
+ color: #8b949e;
304
+ }
305
+
306
+ /* Diff2Html Overrides */
307
+ .d2h-wrapper * { box-sizing: border-box; }
308
+ .d2h-file-list-wrapper { display: none; }
309
+ .d2h-wrapper { margin: 0; }
310
+ .d2h-file-header { display: none; }
311
+ .d2h-code-line-ctn, .d2h-code-line, .hljs {
312
+ color: #e6edf3 !important;
313
+ background: transparent !important;
314
+ font-family: Menlo, Monaco, Consolas, monospace;
315
+ font-size: 12px;
316
+ }
317
+ .d2h-code-side-linenumber {
318
+ background: #0d1117 !important;
319
+ border-color: #30363d !important;
320
+ color: #6e7681 !important;
321
+ }
322
+ .d2h-ins { background-color: rgba(46, 160, 67, 0.15) !important; }
323
+ .d2h-del { background-color: rgba(248, 81, 73, 0.15) !important; }