vg-coder-cli 2.0.24 → 2.0.26
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/.vg/tree-state.json +1 -7
- package/package.json +1 -1
- package/src/index.js +36 -0
- package/src/server/api-server.js +241 -29
- package/src/server/project-manager.js +353 -0
- package/src/server/terminal-manager.js +130 -3
- package/src/server/views/css/iframe.css +1 -0
- package/src/server/views/css/terminal.css +88 -0
- package/src/server/views/dashboard.css +391 -16
- package/src/server/views/dashboard.html +150 -19
- package/src/server/views/js/features/commands.js +230 -0
- package/src/server/views/js/features/iframe-manager.js +6 -2
- package/src/server/views/js/features/project-switcher.js +153 -0
- package/src/server/views/js/features/terminal.js +280 -8
- package/src/server/views/js/main.js +65 -0
- package/src/server/views/js/utils/log-utils.js +164 -0
- package/src/server/views/js/utils/smart-copy-engine.js +283 -0
- package/src/server/views/vg-coder/controller.js +376 -2
- package/vetgo-auto/chrome/src/controller.ts +75 -1
- package/vetgo-auto/chrome/src/utils/ai-domains.ts +33 -0
- package/vetgo-auto/chrome/src/utils/injector-script.ts +251 -0
- package/vetgo-auto/vg-coder.zip +0 -0
- package/vg-coder-cli-2.0.26.tgz +0 -0
- package/vg-coder-cli-2.0.23.tgz +0 -0
- package/vg-coder-cli-2.0.24.tgz +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saved Commands Feature
|
|
3
|
+
* Manages saved terminal commands that can be quickly executed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let savedCommands = [];
|
|
7
|
+
let editingCommandId = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize saved commands on page load
|
|
11
|
+
*/
|
|
12
|
+
export async function initSavedCommands() {
|
|
13
|
+
await loadSavedCommands();
|
|
14
|
+
renderCommands();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load saved commands from backend
|
|
19
|
+
*/
|
|
20
|
+
async function loadSavedCommands() {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch('/api/commands/load');
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
savedCommands = data.commands || [];
|
|
25
|
+
renderCommands(); // Re-render after loading
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to load commands:', error);
|
|
28
|
+
savedCommands = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Save commands to backend (debounced)
|
|
34
|
+
*/
|
|
35
|
+
let saveTimeout = null;
|
|
36
|
+
async function saveCommands() {
|
|
37
|
+
clearTimeout(saveTimeout);
|
|
38
|
+
saveTimeout = setTimeout(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch('/api/commands/save', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ commands: savedCommands })
|
|
44
|
+
});
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
if (data.success) {
|
|
47
|
+
console.log(`✓ Saved ${data.count} commands`);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Failed to save commands:', error);
|
|
51
|
+
}
|
|
52
|
+
}, 500);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render commands in UI
|
|
57
|
+
*/
|
|
58
|
+
function renderCommands() {
|
|
59
|
+
const container = document.getElementById('commands-list');
|
|
60
|
+
const emptyState = document.getElementById('commands-empty-state');
|
|
61
|
+
|
|
62
|
+
if (!container) return;
|
|
63
|
+
|
|
64
|
+
if (savedCommands.length === 0) {
|
|
65
|
+
container.innerHTML = '';
|
|
66
|
+
if (emptyState) emptyState.style.display = 'block';
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (emptyState) emptyState.style.display = 'none';
|
|
71
|
+
|
|
72
|
+
container.innerHTML = savedCommands.map(cmd => `
|
|
73
|
+
<div class="command-card" onclick="window.runSavedCommand('${cmd.id}')">
|
|
74
|
+
<div class="command-card-main">
|
|
75
|
+
<span class="command-icon">${cmd.icon}</span>
|
|
76
|
+
<div class="command-info">
|
|
77
|
+
<div class="command-name">${escapeHtml(cmd.name)}</div>
|
|
78
|
+
<div class="command-text">${escapeHtml(cmd.command)}</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="command-card-actions">
|
|
82
|
+
<button class="command-action-btn" onclick="window.editCommand('${cmd.id}'); event.stopPropagation();" title="Edit">
|
|
83
|
+
✏️
|
|
84
|
+
</button>
|
|
85
|
+
<button class="command-action-btn" onclick="window.deleteCommand('${cmd.id}'); event.stopPropagation();" title="Delete">
|
|
86
|
+
🗑️
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
`).join('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Open add command modal
|
|
95
|
+
*/
|
|
96
|
+
function openAddCommandModal() {
|
|
97
|
+
editingCommandId = null;
|
|
98
|
+
document.getElementById('modal-title').textContent = 'Add Command';
|
|
99
|
+
document.getElementById('command-icon').value = '🚀';
|
|
100
|
+
document.getElementById('command-name').value = '';
|
|
101
|
+
document.getElementById('command-text').value = '';
|
|
102
|
+
document.getElementById('command-modal').style.display = 'flex';
|
|
103
|
+
document.getElementById('command-name').focus();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Open edit command modal
|
|
108
|
+
*/
|
|
109
|
+
function editCommand(id) {
|
|
110
|
+
const command = savedCommands.find(c => c.id === id);
|
|
111
|
+
if (!command) return;
|
|
112
|
+
|
|
113
|
+
editingCommandId = id;
|
|
114
|
+
document.getElementById('modal-title').textContent = 'Edit Command';
|
|
115
|
+
document.getElementById('command-icon').value = command.icon;
|
|
116
|
+
document.getElementById('command-name').value = command.name;
|
|
117
|
+
document.getElementById('command-text').value = command.command;
|
|
118
|
+
document.getElementById('command-modal').style.display = 'flex';
|
|
119
|
+
document.getElementById('command-name').focus();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Close command modal
|
|
124
|
+
*/
|
|
125
|
+
function closeCommandModal() {
|
|
126
|
+
document.getElementById('command-modal').style.display = 'none';
|
|
127
|
+
editingCommandId = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handle command form submit
|
|
132
|
+
*/
|
|
133
|
+
function handleCommandFormSubmit(event) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
|
|
136
|
+
const icon = document.getElementById('command-icon').value.trim();
|
|
137
|
+
const name = document.getElementById('command-name').value.trim();
|
|
138
|
+
const command = document.getElementById('command-text').value.trim();
|
|
139
|
+
|
|
140
|
+
if (!icon || !name || !command) return;
|
|
141
|
+
|
|
142
|
+
if (editingCommandId) {
|
|
143
|
+
// Edit existing
|
|
144
|
+
const index = savedCommands.findIndex(c => c.id === editingCommandId);
|
|
145
|
+
if (index !== -1) {
|
|
146
|
+
savedCommands[index] = { ...savedCommands[index], icon, name, command };
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Add new
|
|
150
|
+
savedCommands.push({
|
|
151
|
+
id: 'cmd_' + Date.now(),
|
|
152
|
+
icon,
|
|
153
|
+
name,
|
|
154
|
+
command
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
saveCommands();
|
|
159
|
+
renderCommands();
|
|
160
|
+
closeCommandModal();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Delete command with confirmation
|
|
165
|
+
*/
|
|
166
|
+
function deleteCommand(id) {
|
|
167
|
+
const command = savedCommands.find(c => c.id === id);
|
|
168
|
+
if (!command) return;
|
|
169
|
+
|
|
170
|
+
if (confirm(`Delete command "${command.name}"?`)) {
|
|
171
|
+
savedCommands = savedCommands.filter(c => c.id !== id);
|
|
172
|
+
saveCommands();
|
|
173
|
+
renderCommands();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run saved command - open terminal and copy command to clipboard
|
|
179
|
+
*/
|
|
180
|
+
function runSavedCommand(id) {
|
|
181
|
+
const command = savedCommands.find(c => c.id === id);
|
|
182
|
+
if (!command) return;
|
|
183
|
+
|
|
184
|
+
// Create new terminal
|
|
185
|
+
if (typeof window.createNewTerminal === 'function') {
|
|
186
|
+
window.createNewTerminal();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Copy command to clipboard
|
|
190
|
+
navigator.clipboard.writeText(command.command).then(() => {
|
|
191
|
+
// Show toast notification
|
|
192
|
+
if (typeof window.showToast === 'function') {
|
|
193
|
+
window.showToast(`📋 Copied: ${command.command}`, 'success');
|
|
194
|
+
}
|
|
195
|
+
}).catch(err => {
|
|
196
|
+
console.error('Failed to copy command:', err);
|
|
197
|
+
if (typeof window.showToast === 'function') {
|
|
198
|
+
window.showToast('Failed to copy command', 'error');
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Escape HTML to prevent XSS
|
|
205
|
+
*/
|
|
206
|
+
function escapeHtml(text) {
|
|
207
|
+
const div = document.createElement('div');
|
|
208
|
+
div.textContent = text;
|
|
209
|
+
return div.innerHTML;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Global exports for HTML onclick
|
|
213
|
+
window.openAddCommandModal = openAddCommandModal;
|
|
214
|
+
window.editCommand = editCommand;
|
|
215
|
+
window.closeCommandModal = closeCommandModal;
|
|
216
|
+
window.deleteCommand = deleteCommand;
|
|
217
|
+
window.runSavedCommand = runSavedCommand;
|
|
218
|
+
window.loadSavedCommands = loadSavedCommands; // Export for project switching
|
|
219
|
+
|
|
220
|
+
// Setup form submit handler
|
|
221
|
+
if (typeof document !== 'undefined') {
|
|
222
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
223
|
+
const form = document.getElementById('command-form');
|
|
224
|
+
if (form) {
|
|
225
|
+
form.addEventListener('submit', handleCommandFormSubmit);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export { openAddCommandModal, editCommand, deleteCommand, runSavedCommand, loadSavedCommands };
|
|
@@ -37,13 +37,17 @@ export function initIframeManager() {
|
|
|
37
37
|
const updateProvider = (providerId) => {
|
|
38
38
|
const provider = AI_PROVIDERS.find(p => p.id === providerId) || AI_PROVIDERS[0];
|
|
39
39
|
|
|
40
|
+
// Add VG Coder context parameter to prevent nested iframe injection
|
|
41
|
+
const urlWithParam = new URL(provider.url);
|
|
42
|
+
urlWithParam.searchParams.set('vg_coder_context', 'true');
|
|
43
|
+
|
|
40
44
|
// Reset iframe source to trigger reload
|
|
41
45
|
iframe.src = 'about:blank';
|
|
42
46
|
setTimeout(() => {
|
|
43
|
-
iframe.src =
|
|
47
|
+
iframe.src = urlWithParam.toString();
|
|
44
48
|
}, 50);
|
|
45
49
|
|
|
46
|
-
// Update placeholder link
|
|
50
|
+
// Update placeholder link (without the parameter for direct access)
|
|
47
51
|
if (placeholderLink) {
|
|
48
52
|
placeholderLink.href = provider.url;
|
|
49
53
|
placeholderLink.textContent = `Mở ${provider.name} tab mới ↗`;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Project Switcher - Multi-project management UI
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize project switcher
|
|
5
|
+
*/
|
|
6
|
+
export async function initProjectSwitcher() {
|
|
7
|
+
// Load initial project list
|
|
8
|
+
await loadProjects();
|
|
9
|
+
|
|
10
|
+
// Poll for project updates every 5 seconds
|
|
11
|
+
setInterval(loadProjects, 5000);
|
|
12
|
+
|
|
13
|
+
// Listen for socket events
|
|
14
|
+
setupSocketListeners();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load and display all projects
|
|
19
|
+
*/
|
|
20
|
+
async function loadProjects() {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch('/api/projects');
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
|
|
25
|
+
updateProjectSelector(data.projects, data.activeProjectId);
|
|
26
|
+
updateProjectCount(data.totalProjects);
|
|
27
|
+
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Failed to load projects:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update project selector dropdown
|
|
35
|
+
*/
|
|
36
|
+
function updateProjectSelector(projects, activeProjectId) {
|
|
37
|
+
const selector = document.getElementById('project-selector');
|
|
38
|
+
if (!selector) return;
|
|
39
|
+
|
|
40
|
+
// Clear existing options
|
|
41
|
+
selector.innerHTML = '';
|
|
42
|
+
|
|
43
|
+
// Add projects
|
|
44
|
+
projects.forEach(project => {
|
|
45
|
+
const option = document.createElement('option');
|
|
46
|
+
option.value = project.id;
|
|
47
|
+
option.textContent = project.name;
|
|
48
|
+
option.selected = project.id === activeProjectId;
|
|
49
|
+
selector.appendChild(option);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update project count badge
|
|
55
|
+
*/
|
|
56
|
+
function updateProjectCount(count) {
|
|
57
|
+
const badge = document.getElementById('project-count');
|
|
58
|
+
if (!badge) return;
|
|
59
|
+
|
|
60
|
+
badge.textContent = `${count} project${count !== 1 ? 's' : ''}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Switch to a different project
|
|
65
|
+
*/
|
|
66
|
+
export async function switchProject(projectId) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch('/api/projects/switch', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ projectId })
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
|
|
76
|
+
if (data.success) {
|
|
77
|
+
// Emit custom event for other components to react
|
|
78
|
+
const event = new CustomEvent('project-switched', {
|
|
79
|
+
detail: {
|
|
80
|
+
projectId,
|
|
81
|
+
projectName: data.project.name,
|
|
82
|
+
project: data.project
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
window.dispatchEvent(event);
|
|
86
|
+
|
|
87
|
+
// Show toast
|
|
88
|
+
showToast(`Switched to: ${data.project.name}`, 'success');
|
|
89
|
+
} else {
|
|
90
|
+
showToast('Failed to switch project', 'error');
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to switch project:', error);
|
|
94
|
+
showToast('Error switching project', 'error');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Setup socket listeners for real-time updates
|
|
100
|
+
*/
|
|
101
|
+
function setupSocketListeners() {
|
|
102
|
+
if (typeof io === 'undefined') return;
|
|
103
|
+
|
|
104
|
+
const socket = io();
|
|
105
|
+
|
|
106
|
+
// New project registered
|
|
107
|
+
socket.on('project:registered', (data) => {
|
|
108
|
+
console.log('New project registered:', data);
|
|
109
|
+
loadProjects();
|
|
110
|
+
showToast(`New project joined: ${data.name}`, 'info');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Project switched
|
|
114
|
+
socket.on('project:switched', (data) => {
|
|
115
|
+
console.log('Project switched:', data);
|
|
116
|
+
loadProjects();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Project removed
|
|
120
|
+
socket.on('project:removed', (data) => {
|
|
121
|
+
console.log('Project removed:', data);
|
|
122
|
+
loadProjects();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Show toast notification
|
|
128
|
+
*/
|
|
129
|
+
function showToast(message, type = 'info') {
|
|
130
|
+
const toast = document.getElementById('toast');
|
|
131
|
+
if (!toast) return;
|
|
132
|
+
|
|
133
|
+
toast.textContent = message;
|
|
134
|
+
toast.className = 'toast show';
|
|
135
|
+
|
|
136
|
+
if (type === 'success') {
|
|
137
|
+
toast.style.background = '#28a745';
|
|
138
|
+
} else if (type === 'error') {
|
|
139
|
+
toast.style.background = '#dc3545';
|
|
140
|
+
} else if (type === 'warning') {
|
|
141
|
+
toast.style.background = '#ffc107';
|
|
142
|
+
toast.style.color = '#000';
|
|
143
|
+
} else {
|
|
144
|
+
toast.style.background = '#007bff';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
toast.classList.remove('show');
|
|
149
|
+
}, 3000);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Export for global access
|
|
153
|
+
window.switchProject = switchProject;
|