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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "1.0.15",
3
+ "version": "1.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": {
@@ -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(result);
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 &quot;src/test.js&quot;)&#10;cat <<'EOF' > src/test.js&#10;console.log('Hello World');&#10;EOF"></textarea>
381
385
  </div>
382
- <button class="btn" onclick="testExecute()">
383
- <span>▶️</span>
384
- <span>Execute Script</span>
385
- </button>
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