vg-coder-cli 2.0.16 → 2.0.17

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.17",
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,144 @@ 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
+ const { stdout } = await execAsync('git status --porcelain', { cwd: this.workingDir });
103
+
104
+ const staged = [];
105
+ const unstaged = [];
106
+ const untracked = [];
107
+
108
+ const lines = stdout.split('\n').filter(l => l.trim());
109
+
110
+ lines.forEach(line => {
111
+ const x = line[0];
112
+ const y = line[1];
113
+ const path = line.substring(3);
114
+
115
+ if (x !== ' ' && x !== '?') {
116
+ staged.push({ path, status: x });
117
+ }
113
118
 
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
- });
119
+ if (y !== ' ') {
120
+ if (x === '?' && y === '?') {
121
+ untracked.push({ path, status: 'U' });
122
+ } else {
123
+ unstaged.push({ path, status: y });
141
124
  }
142
- combinedDiff += `\n`;
143
- } catch (err) {
144
- // Bỏ qua nếu lỗi đọc file (vd: binary)
125
+ }
126
+ });
127
+
128
+ const changes = [...unstaged, ...untracked];
129
+ res.json({ staged, changes });
130
+ } catch (error) {
131
+ console.error(chalk.red('❌ [GIT STATUS] Error:'), error.message);
132
+ res.status(500).json({ error: error.message });
133
+ }
134
+ });
135
+
136
+ // Git Stage
137
+ this.app.post('/api/git/stage', async (req, res) => {
138
+ try {
139
+ const { files } = req.body;
140
+ const target = files.includes('*') ? '.' : files.map(f => `"${f}"`).join(' ');
141
+ await execAsync(`git add ${target}`, { cwd: this.workingDir });
142
+ res.json({ success: true });
143
+ } catch (error) {
144
+ res.status(500).json({ error: error.message });
145
+ }
146
+ });
147
+
148
+ // Git Unstage
149
+ this.app.post('/api/git/unstage', async (req, res) => {
150
+ try {
151
+ const { files } = req.body;
152
+ const target = files.includes('*') ? '' : files.map(f => `"${f}"`).join(' ');
153
+ await execAsync(`git reset HEAD ${target}`, { cwd: this.workingDir });
154
+ res.json({ success: true });
155
+ } catch (error) {
156
+ res.status(500).json({ error: error.message });
157
+ }
158
+ });
159
+
160
+ // Git Discard (NEW)
161
+ this.app.post('/api/git/discard', async (req, res) => {
162
+ try {
163
+ const { files } = req.body; // array or '*'
164
+
165
+ // Discard All
166
+ if (files.includes('*')) {
167
+ // Restore tracked files
168
+ try { await execAsync('git restore .', { cwd: this.workingDir }); } catch (e) {}
169
+ // Clean untracked files
170
+ try { await execAsync('git clean -fd', { cwd: this.workingDir }); } catch (e) {}
171
+ } else {
172
+ // Discard specific files
173
+ for (const file of files) {
174
+ // Try restore (for tracked modified/deleted)
175
+ try { await execAsync(`git restore "${file}"`, { cwd: this.workingDir }); } catch (e) {}
176
+ // Try clean (for untracked)
177
+ try { await execAsync(`git clean -f "${file}"`, { cwd: this.workingDir }); } catch (e) {}
145
178
  }
146
179
  }
180
+ res.json({ success: true });
181
+ } catch (error) {
182
+ console.error('Discard error:', error);
183
+ res.status(500).json({ error: error.message });
184
+ }
185
+ });
186
+
187
+ // Git Commit
188
+ this.app.post('/api/git/commit', async (req, res) => {
189
+ try {
190
+ const { message } = req.body;
191
+ if (!message) throw new Error('Commit message is required');
192
+ const safeMessage = message.replace(/"/g, '\\"');
193
+ await execAsync(`git commit -m "${safeMessage}"`, { cwd: this.workingDir });
194
+ res.json({ success: true });
195
+ } catch (error) {
196
+ res.status(500).json({ error: error.message });
147
197
  }
198
+ });
148
199
 
149
- res.json({ diff: combinedDiff });
200
+ // Get Diff
201
+ this.app.get('/api/git/diff', async (req, res) => {
202
+ try {
203
+ const file = req.query.file;
204
+ const type = req.query.type || 'working';
205
+
206
+ let cmd = '';
207
+
208
+ if (type === 'staged') {
209
+ cmd = file ? `git diff --cached -- "${file}"` : `git diff --cached`;
210
+ } else {
211
+ if (file) {
212
+ const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: this.workingDir });
213
+ if (isUntracked.trim()) {
214
+ const content = await fs.readFile(path.join(this.workingDir, file), 'utf8');
215
+ 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`;
216
+ content.split('\n').forEach(l => fakeDiff += `+${l}\n`);
217
+ return res.json({ diff: fakeDiff });
218
+ }
219
+ cmd = `git diff -- "${file}"`;
220
+ } else {
221
+ cmd = `git diff`;
222
+ }
223
+ }
224
+
225
+ const { stdout } = await execAsync(cmd, { cwd: this.workingDir, maxBuffer: 20 * 1024 * 1024 });
226
+ res.json({ diff: stdout });
150
227
  } catch (error) {
151
- console.error(chalk.red('❌ [GIT] Error:'), error.message);
228
+ console.error(chalk.red('❌ [GIT DIFF] Error:'), error.message);
152
229
  res.json({ diff: '', error: error.message });
153
230
  }
154
231
  });
155
232
 
233
+ // --- GIT API END ---
234
+
156
235
  this.app.post('/api/analyze', async (req, res) => {
157
236
  const { path: projectPath, options = {}, specificFiles } = req.body;
158
237
  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; }