vg-coder-cli 1.0.15 → 1.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
package/src/server/api-server.js
CHANGED
|
@@ -237,8 +237,13 @@ class ApiServer {
|
|
|
237
237
|
console.log(chalk.green(`✓ Bash execution completed in ${result.executionTime}ms`));
|
|
238
238
|
res.json(result);
|
|
239
239
|
} else {
|
|
240
|
+
// Check if it's a syntax error
|
|
241
|
+
const isSyntaxError = result.error === 'Syntax validation failed';
|
|
240
242
|
console.log(chalk.red(`✗ Bash execution failed: ${result.error || 'Exit code ' + result.exitCode}`));
|
|
241
|
-
res.status(400).json(
|
|
243
|
+
res.status(400).json({
|
|
244
|
+
...result,
|
|
245
|
+
syntaxError: isSyntaxError
|
|
246
|
+
});
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
} catch (error) {
|
|
@@ -363,6 +363,10 @@
|
|
|
363
363
|
<span id="analyze-copy-icon">📋</span>
|
|
364
364
|
<span id="analyze-copy-text">Copy to Clipboard</span>
|
|
365
365
|
</button>
|
|
366
|
+
<button class="btn btn-copy" onclick="copyAnalyzeAsFile()">
|
|
367
|
+
<span id="analyze-file-icon">📄</span>
|
|
368
|
+
<span id="analyze-file-text">Copy as File</span>
|
|
369
|
+
</button>
|
|
366
370
|
</div>
|
|
367
371
|
<div class="response" id="analyze-response"></div>
|
|
368
372
|
</div>
|
|
@@ -379,10 +383,16 @@
|
|
|
379
383
|
<textarea id="execute-bash"
|
|
380
384
|
placeholder="mkdir -p $(dirname "src/test.js") cat <<'EOF' > src/test.js console.log('Hello World'); EOF"></textarea>
|
|
381
385
|
</div>
|
|
382
|
-
<
|
|
383
|
-
<
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
<div class="btn-group">
|
|
387
|
+
<button class="btn" onclick="testExecute()">
|
|
388
|
+
<span>▶️</span>
|
|
389
|
+
<span>Execute Script</span>
|
|
390
|
+
</button>
|
|
391
|
+
<button class="btn" onclick="executeFromClipboard()">
|
|
392
|
+
<span>📋</span>
|
|
393
|
+
<span>Execute from Clipboard</span>
|
|
394
|
+
</button>
|
|
395
|
+
</div>
|
|
386
396
|
<div class="response" id="execute-response"></div>
|
|
387
397
|
</div>
|
|
388
398
|
</div>
|
|
@@ -672,6 +682,75 @@ API sẽ:
|
|
|
672
682
|
}
|
|
673
683
|
}
|
|
674
684
|
|
|
685
|
+
async function copyAsFile(filename, content) {
|
|
686
|
+
const blob = new Blob([content], {
|
|
687
|
+
type: "application/octet-stream"
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// ClipboardItem cần type key phải trùng blob.type
|
|
691
|
+
const item = new ClipboardItem(
|
|
692
|
+
{ [blob.type]: blob },
|
|
693
|
+
{
|
|
694
|
+
// Không phải chuẩn, nhưng Chrome hỗ trợ unofficial metadata
|
|
695
|
+
type: "application/octet-stream",
|
|
696
|
+
presentationStyle: "attachment",
|
|
697
|
+
name: filename
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
await navigator.clipboard.write([item]);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function copyAnalyzeAsFile() {
|
|
705
|
+
const copyBtn = event.target.closest('.btn-copy');
|
|
706
|
+
const copyIcon = document.getElementById('analyze-file-icon');
|
|
707
|
+
const copyText = document.getElementById('analyze-file-text');
|
|
708
|
+
|
|
709
|
+
if (!lastAnalyzeResult) {
|
|
710
|
+
// Fetch if not already analyzed
|
|
711
|
+
const path = document.getElementById('analyze-path').value;
|
|
712
|
+
showLoading(copyBtn, '<span id="analyze-file-icon">📄</span><span id="analyze-file-text">Copy as File</span>');
|
|
713
|
+
|
|
714
|
+
try {
|
|
715
|
+
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
716
|
+
method: 'POST',
|
|
717
|
+
headers: { 'Content-Type': 'application/json' },
|
|
718
|
+
body: JSON.stringify({ path })
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
if (res.ok) {
|
|
722
|
+
lastAnalyzeResult = await res.text();
|
|
723
|
+
} else {
|
|
724
|
+
showToast('❌ Lỗi analyze!', 'error');
|
|
725
|
+
resetButton(copyBtn);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
} catch (err) {
|
|
729
|
+
showToast('❌ Lỗi: ' + err.message, 'error');
|
|
730
|
+
resetButton(copyBtn);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
resetButton(copyBtn);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
await copyAsFile("project.txt", lastAnalyzeResult);
|
|
738
|
+
|
|
739
|
+
copyBtn.classList.add('copied');
|
|
740
|
+
copyIcon.textContent = '✓';
|
|
741
|
+
copyText.textContent = 'Copied!';
|
|
742
|
+
showToast('✅ Đã copy project.txt như file!', 'success');
|
|
743
|
+
|
|
744
|
+
setTimeout(() => {
|
|
745
|
+
copyBtn.classList.remove('copied');
|
|
746
|
+
copyIcon.textContent = '📄';
|
|
747
|
+
copyText.textContent = 'Copy as File';
|
|
748
|
+
}, 2000);
|
|
749
|
+
} catch (err) {
|
|
750
|
+
showToast('❌ Lỗi copy: ' + err.message, 'error');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
675
754
|
async function testExecute() {
|
|
676
755
|
const btn = event.target.closest('.btn');
|
|
677
756
|
const bash = document.getElementById('execute-bash').value;
|
|
@@ -703,6 +782,65 @@ API sẽ:
|
|
|
703
782
|
resetButton(btn);
|
|
704
783
|
}
|
|
705
784
|
|
|
785
|
+
async function executeFromClipboard() {
|
|
786
|
+
const btn = event.target.closest('.btn');
|
|
787
|
+
|
|
788
|
+
showLoading(btn, '<span>📋</span><span>Execute from Clipboard</span>');
|
|
789
|
+
|
|
790
|
+
try {
|
|
791
|
+
// Read from clipboard
|
|
792
|
+
const clipboardText = await navigator.clipboard.readText();
|
|
793
|
+
|
|
794
|
+
// Check if clipboard is empty
|
|
795
|
+
if (!clipboardText || !clipboardText.trim()) {
|
|
796
|
+
showToast('⚠️ Clipboard trống! Vui lòng copy bash script trước.', 'error');
|
|
797
|
+
showResponse('execute-response', {
|
|
798
|
+
error: 'Clipboard is empty',
|
|
799
|
+
message: 'Please copy a bash script to clipboard first'
|
|
800
|
+
}, true);
|
|
801
|
+
resetButton(btn);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Display clipboard content in textarea for reference
|
|
806
|
+
document.getElementById('execute-bash').value = clipboardText;
|
|
807
|
+
|
|
808
|
+
// Execute the script (API will validate syntax first)
|
|
809
|
+
const res = await fetch(`${API_BASE}/api/execute`, {
|
|
810
|
+
method: 'POST',
|
|
811
|
+
headers: { 'Content-Type': 'application/json' },
|
|
812
|
+
body: JSON.stringify({ bash: clipboardText })
|
|
813
|
+
});
|
|
814
|
+
const data = await res.json();
|
|
815
|
+
showResponse('execute-response', data, !res.ok || !data.success);
|
|
816
|
+
|
|
817
|
+
if (data.success) {
|
|
818
|
+
showToast('✅ Thực thi từ clipboard thành công!', 'success');
|
|
819
|
+
} else {
|
|
820
|
+
// Check if it's a syntax error
|
|
821
|
+
if (data.syntaxError) {
|
|
822
|
+
showToast('❌ Lỗi syntax! Kiểm tra bash script.', 'error');
|
|
823
|
+
} else {
|
|
824
|
+
showToast('❌ Thực thi thất bại!', 'error');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
} catch (err) {
|
|
828
|
+
// Handle clipboard permission errors
|
|
829
|
+
if (err.name === 'NotAllowedError') {
|
|
830
|
+
showToast('❌ Không có quyền truy cập clipboard!', 'error');
|
|
831
|
+
showResponse('execute-response', {
|
|
832
|
+
error: 'Clipboard permission denied',
|
|
833
|
+
message: 'Please allow clipboard access in your browser settings'
|
|
834
|
+
}, true);
|
|
835
|
+
} else {
|
|
836
|
+
showResponse('execute-response', { error: err.message }, true);
|
|
837
|
+
showToast('❌ Lỗi: ' + err.message, 'error');
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
resetButton(btn);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
|
|
706
844
|
function showToast(message, type = 'success') {
|
|
707
845
|
const toast = document.getElementById('toast');
|
|
708
846
|
toast.textContent = message;
|
|
Binary file
|