vg-coder-cli 2.0.6 → 2.0.8

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.
@@ -7,673 +7,113 @@
7
7
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
8
8
  <meta name="apple-mobile-web-app-capable" content="yes">
9
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
- <title>VG Coder CLI</title>
11
- <style>
12
- :root {
13
- --ios-bg: #F2F2F7;
14
- --ios-card: #FFFFFF;
15
- --ios-blue: #007AFF;
16
- --ios-green: #34C759;
17
- --ios-red: #FF3B30;
18
- --ios-text-primary: #000000;
19
- --ios-text-secondary: #8E8E93;
20
- --ios-separator: #C6C6C8;
21
- --ios-input-bg: #E5E5EA;
22
- --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
23
- }
24
-
25
- * {
26
- margin: 0;
27
- padding: 0;
28
- box-sizing: border-box;
29
- -webkit-tap-highlight-color: transparent;
30
- }
31
-
32
- body {
33
- font-family: var(--font-stack);
34
- background-color: var(--ios-bg);
35
- color: var(--ios-text-primary);
36
- min-height: 100vh;
37
- padding-bottom: 40px;
38
- font-size: 17px;
39
- line-height: 1.5;
40
- -webkit-font-smoothing: antialiased;
41
- }
42
-
43
- /* Glass Header */
44
- .header-nav {
45
- position: sticky;
46
- top: 0;
47
- z-index: 100;
48
- background: rgba(255, 255, 255, 0.85);
49
- backdrop-filter: blur(20px);
50
- -webkit-backdrop-filter: blur(20px);
51
- border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
52
- padding: 15px 20px;
53
- display: flex;
54
- justify-content: space-between;
55
- align-items: center;
56
- padding-top: max(15px, env(safe-area-inset-top));
57
- }
58
-
59
- .app-title {
60
- font-weight: 700;
61
- font-size: 1.2rem;
62
- letter-spacing: -0.5px;
63
- }
64
-
65
- .status-badge {
66
- font-size: 0.75rem;
67
- font-weight: 600;
68
- padding: 4px 10px;
69
- border-radius: 20px;
70
- background: var(--ios-green);
71
- color: white;
72
- box-shadow: 0 2px 4px rgba(52, 199, 89, 0.2);
73
- transition: all 0.3s ease;
74
- }
75
-
76
- .container {
77
- max-width: 800px;
78
- margin: 0 auto;
79
- padding: 20px;
80
- padding-left: max(20px, env(safe-area-inset-left));
81
- padding-right: max(20px, env(safe-area-inset-right));
82
- }
83
-
84
- /* iOS Cards */
85
- .card {
86
- background: var(--ios-card);
87
- border-radius: 20px;
88
- padding: 24px;
89
- margin-bottom: 24px;
90
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
91
- position: relative;
92
- overflow: hidden;
93
- }
94
-
95
- .card-header {
96
- display: flex;
97
- align-items: center;
98
- margin-bottom: 16px;
99
- gap: 12px;
100
- }
101
-
102
- .card-icon {
103
- width: 40px;
104
- height: 40px;
105
- border-radius: 12px;
106
- background: var(--ios-bg);
107
- display: flex;
108
- align-items: center;
109
- justify-content: center;
110
- font-size: 1.2rem;
111
- color: var(--ios-blue);
112
- }
113
-
114
- .card-title {
115
- font-size: 1.1rem;
116
- font-weight: 700;
117
- letter-spacing: -0.3px;
118
- }
119
-
120
- .card-desc {
121
- color: var(--ios-text-secondary);
122
- font-size: 0.9rem;
123
- margin-bottom: 20px;
124
- }
125
-
126
- /* Forms */
127
- .form-group {
128
- margin-bottom: 20px;
129
- }
130
-
131
- .form-label {
132
- display: block;
133
- font-size: 0.85rem;
134
- font-weight: 600;
135
- color: var(--ios-text-secondary);
136
- margin-bottom: 8px;
137
- text-transform: uppercase;
138
- letter-spacing: 0.5px;
139
- }
140
-
141
- input[type="text"],
142
- textarea {
143
- width: 100%;
144
- background: var(--ios-input-bg);
145
- border: none;
146
- border-radius: 12px;
147
- padding: 14px 16px;
148
- font-size: 1rem;
149
- font-family: inherit;
150
- color: var(--ios-text-primary);
151
- transition: all 0.2s;
152
- -webkit-appearance: none;
153
- }
154
-
155
- textarea {
156
- min-height: 120px;
157
- resize: vertical;
158
- line-height: 1.4;
159
- }
160
-
161
- input:focus,
162
- textarea:focus {
163
- outline: none;
164
- background: #D1D1D6;
165
- }
166
-
167
- /* Buttons */
168
- .btn-group {
169
- display: grid;
170
- grid-template-columns: 1fr 1fr;
171
- gap: 12px;
172
- }
173
-
174
- .btn {
175
- border: none;
176
- background: var(--ios-blue);
177
- color: white;
178
- padding: 14px;
179
- border-radius: 14px;
180
- font-size: 1rem;
181
- font-weight: 600;
182
- cursor: pointer;
183
- transition: transform 0.1s, opacity 0.2s;
184
- display: flex;
185
- align-items: center;
186
- justify-content: center;
187
- gap: 8px;
188
- -webkit-appearance: none;
189
- }
190
-
191
- .btn:active {
192
- transform: scale(0.98);
193
- opacity: 0.8;
194
- }
195
-
196
- .btn-secondary {
197
- background: rgba(0, 122, 255, 0.15);
198
- color: var(--ios-blue);
199
- }
200
-
201
- /* Full width button */
202
- .btn-full {
203
- grid-column: 1 / -1;
204
- }
205
-
206
- .btn-copy {
207
- background: #E5E5EA;
208
- color: black;
209
- }
210
-
211
- .btn-copy.copied {
212
- background: var(--ios-green);
213
- color: white;
214
- }
215
-
216
- /* System Prompt Accordion */
217
- .prompt-accordion {
218
- cursor: pointer;
219
- user-select: none;
220
- }
221
-
222
- .prompt-header {
223
- display: flex;
224
- justify-content: space-between;
225
- align-items: center;
226
- padding: 4px 0;
227
- }
228
-
229
- .chevron {
230
- color: var(--ios-text-secondary);
231
- transition: transform 0.3s ease;
232
- font-size: 0.8rem;
233
- }
234
-
235
- .prompt-content {
236
- max-height: 0;
237
- overflow: hidden;
238
- transition: max-height 0.4s cubic-bezier(0.65, 0, 0.35, 1);
239
- opacity: 0;
240
- }
241
-
242
- .prompt-content.open {
243
- max-height: 500px;
244
- opacity: 1;
245
- margin-top: 15px;
246
- }
247
-
248
- .code-block {
249
- background: #2c2c2e;
250
- color: #fff;
251
- padding: 15px;
252
- border-radius: 12px;
253
- font-family: 'SF Mono', 'Menlo', monospace;
254
- font-size: 0.85rem;
255
- overflow-x: auto;
256
- white-space: pre-wrap;
257
- margin-bottom: 15px;
258
- border: 1px solid rgba(0, 0, 0, 0.1);
259
- }
260
-
261
- /* Response Area */
262
- .response {
263
- margin-top: 20px;
264
- background: #F9F9F9;
265
- border-radius: 12px;
266
- padding: 15px;
267
- border-left: 5px solid var(--ios-text-secondary);
268
- display: none;
269
- animation: slideDown 0.3s ease;
270
- }
271
-
272
- .response.show {
273
- display: block;
274
- }
275
-
276
- .response.success {
277
- border-left-color: var(--ios-green);
278
- background: #F0FFF4;
279
- }
280
-
281
- .response.error {
282
- border-left-color: var(--ios-red);
283
- background: #FFF5F5;
284
- }
285
-
286
- .response pre {
287
- font-family: 'SF Mono', 'Menlo', monospace;
288
- font-size: 0.85rem;
289
- white-space: pre-wrap;
290
- word-break: break-all;
291
- color: #333;
292
- }
293
-
294
- /* Toast */
295
- .toast {
296
- position: fixed;
297
- top: 20px;
298
- left: 50%;
299
- transform: translateX(-50%) translateY(-100px);
300
- background: rgba(0, 0, 0, 0.8);
301
- backdrop-filter: blur(10px);
302
- color: white;
303
- padding: 12px 24px;
304
- border-radius: 50px;
305
- font-weight: 600;
306
- font-size: 0.95rem;
307
- z-index: 1000;
308
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
309
- transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
310
- display: flex;
311
- align-items: center;
312
- gap: 8px;
313
- white-space: nowrap;
314
- }
315
-
316
- .toast.show {
317
- transform: translateX(-50%) translateY(0);
318
- }
319
-
320
- .loading-spinner {
321
- width: 18px;
322
- height: 18px;
323
- border: 2px solid rgba(255, 255, 255, 0.3);
324
- border-radius: 50%;
325
- border-top-color: white;
326
- animation: spin 0.8s linear infinite;
327
- }
328
-
329
- @keyframes spin {
330
- to {
331
- transform: rotate(360deg);
332
- }
333
- }
334
-
335
- @keyframes slideDown {
336
- from {
337
- opacity: 0;
338
- transform: translateY(-10px);
339
- }
340
-
341
- to {
342
- opacity: 1;
343
- transform: translateY(0);
344
- }
345
- }
346
- </style>
10
+ <title>VG Coder API Dashboard</title>
11
+ <link rel="stylesheet" href="/dashboard.css">
12
+ <script>
13
+ // Pre-load theme to prevent flash
14
+ (function() {
15
+ const savedTheme = localStorage.getItem('theme') || 'light';
16
+ document.documentElement.setAttribute('data-theme', savedTheme);
17
+ })();
18
+ </script>
347
19
  </head>
348
20
 
349
21
  <body>
350
- <!-- Glass Header -->
351
- <nav class="header-nav">
352
- <div class="app-title">VG Coder</div>
353
- <div id="status" class="status-badge">● Offline</div>
354
- </nav>
355
-
356
22
  <div class="container">
357
-
358
- <!-- System Prompt Card -->
359
- <div class="card prompt-accordion">
360
- <div class="card-header prompt-header" onclick="toggleSystemPrompt()">
361
- <div style="display: flex; align-items: center; gap: 12px;">
362
- <div class="card-icon" style="background: rgba(255, 59, 48, 0.1); color: var(--ios-red);">⚡</div>
363
- <div class="card-title">System Prompt</div>
364
- </div>
365
- <div class="chevron" id="toggle-icon">▼</div>
366
- </div>
367
- <div class="prompt-content" id="system-prompt-content">
368
- <div class="code-block" id="prompt-text"></div>
369
- <button class="btn btn-secondary btn-full" onclick="copySystemPrompt()">
370
- <span id="copy-icon">📋</span>
371
- <span id="copy-text">Copy Prompt</span>
372
- </button>
23
+ <div class="header">
24
+ <div class="header-content">
25
+ <span class="status" id="status">● Server Starting...</span>
26
+ <div style="height: 10px;"></div>
27
+ <h1>VG Coder</h1>
28
+ <p>API Dashboard & Automation</p>
373
29
  </div>
30
+ <button class="theme-toggle" id="theme-toggle" title="Toggle Dark Mode">
31
+ <span id="theme-icon">🌙</span>
32
+ </button>
374
33
  </div>
375
34
 
376
- <!-- Analyze Card -->
377
- <div class="card">
378
- <div class="card-header">
379
- <div class="card-icon">📊</div>
380
- <div>
381
- <div class="card-title">Project Analyze</div>
35
+ <!-- System Prompt Section -->
36
+ <div class="system-prompt-card">
37
+ <div class="system-prompt-header" onclick="toggleSystemPrompt()">
38
+ <div class="header-title-group">
39
+ <button class="btn-icon-head" onclick="copySystemPromptFromHeader(event)" title="Copy System Prompt">
40
+ 📋
41
+ </button>
42
+ <h2>System Prompt</h2>
382
43
  </div>
44
+ <span class="toggle-icon" id="toggle-icon">▼</span>
383
45
  </div>
384
- <div class="card-desc">Phân tích dự án và lấy source code</div>
385
-
386
- <div class="form-group">
387
- <label class="form-label">Project Path</label>
388
- <input type="text" id="analyze-path" value="." placeholder="/path/to/project">
389
- </div>
390
-
391
- <div class="btn-group">
392
- <button class="btn" onclick="testAnalyze()">
393
- <span>📥</span> Download
394
- </button>
395
- <button class="btn btn-secondary" onclick="copyAnalyzeResult()">
396
- <span id="analyze-copy-icon">📋</span> Copy
46
+ <div class="system-prompt-content" id="system-prompt-content">
47
+ <div class="prompt-text" id="prompt-text"></div>
48
+ <button class="btn btn-copy" onclick="copySystemPrompt()">
49
+ <span id="copy-icon">📋</span>
50
+ <span id="copy-text">Copy System Prompt</span>
397
51
  </button>
398
52
  </div>
399
- <div class="response" id="analyze-response"></div>
400
53
  </div>
401
54
 
402
- <!-- Execute Card -->
403
- <div class="card">
404
- <div class="card-header">
405
- <div class="card-icon" style="background: rgba(52, 199, 89, 0.1); color: var(--ios-green);">🚀</div>
406
- <div>
407
- <div class="card-title">Execute Script</div>
55
+ <div class="endpoints">
56
+ <!-- Analyze -->
57
+ <div class="endpoint-card">
58
+ <div class="endpoint-header">
59
+ <!-- Left side: Method + Path -->
60
+ <div class="endpoint-title-group">
61
+ <span class="method post">POST</span>
62
+ <span class="endpoint-path">/api/analyze</span>
63
+ </div>
64
+ <!-- Right side: Download Icon -->
65
+ <button class="btn-icon-head" onclick="testAnalyze()" title="Download Project Source">
66
+ 📥
67
+ </button>
408
68
  </div>
409
- </div>
410
- <div class="card-desc">Thực thi bash script an toàn</div>
411
-
412
- <div class="form-group">
413
- <label class="form-label">Bash Script</label>
414
- <textarea id="execute-bash" placeholder="mkdir -p src..."></textarea>
69
+ <p class="endpoint-desc">Phân tích dự án và lấy toàn bộ source code.</p>
70
+ <div class="form-group">
71
+ <label>Path</label>
72
+ <input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
73
+ </div>
74
+ <div class="btn-group">
75
+ <!-- Big Download button removed as it's now in the header -->
76
+ <button class="btn btn-copy" onclick="copyAnalyzeResult()">
77
+ <span id="analyze-copy-icon">📋</span>
78
+ <span id="analyze-copy-text">Copy Text</span>
79
+ </button>
80
+ </div>
81
+ <div class="response" id="analyze-response"></div>
415
82
  </div>
416
83
 
417
- <div class="btn-group">
418
- <button class="btn" style="background: var(--ios-green);" onclick="testExecute()">
419
- <span>▶️</span> Execute
420
- </button>
421
- <button class="btn btn-secondary" onclick="executeFromClipboard()">
422
- <span>📋</span> Paste & Run
423
- </button>
84
+ <!-- Execute Bash -->
85
+ <div class="endpoint-card">
86
+ <div class="endpoint-header">
87
+ <div class="endpoint-title-group">
88
+ <span class="method post">POST</span>
89
+ <span class="endpoint-path">/api/execute</span>
90
+ </div>
91
+ <!-- Could add an execute icon here later if needed -->
92
+ </div>
93
+ <p class="endpoint-desc">Thực thi bash script với syntax validation.</p>
94
+ <div class="form-group">
95
+ <label>Bash Script</label>
96
+ <textarea id="execute-bash"
97
+ placeholder="mkdir -p src/test&#10;echo 'Hello' > src/test/hello.txt"></textarea>
98
+ </div>
99
+ <div class="btn-group">
100
+ <button class="btn" onclick="testExecute()">
101
+ <span>▶️</span>
102
+ <span>Run Script</span>
103
+ </button>
104
+ <button class="btn" onclick="executeFromClipboard()">
105
+ <span>📋</span>
106
+ <span>Paste & Run</span>
107
+ </button>
108
+ </div>
109
+ <div class="response" id="execute-response"></div>
424
110
  </div>
425
- <div class="response" id="execute-response"></div>
426
111
  </div>
427
112
  </div>
428
113
 
429
- <!-- iOS Toast -->
430
114
  <div class="toast" id="toast"></div>
431
115
 
432
- <script>
433
- const API_BASE = window.location.origin;
434
- let lastAnalyzeResult = null;
435
-
436
- const SYSTEM_PROMPT = `# VG Coder AI System Prompt
437
-
438
- ## Command Prefixes
439
- ### /ask - Q&A Mode
440
- ### /plan - Planning Mode
441
- ### /fix - Bug Fix Mode
442
- ### /code - Code Generation Mode
443
-
444
- ## ⚠️ QUY TẮC BẮT BUỘC /code
445
-
446
- 1. **Format Script Chuẩn:**
447
- \`\`\`bash
448
- mkdir -p $(dirname "path/to/file.ext")
449
- cat <<'EOF' > path/to/file.ext
450
- ... content ...
451
- EOF
452
- \`\`\`
453
-
454
- 2. **Quy tắc:**
455
- - Luôn có mkdir -p
456
- - Dùng <<'EOF' (có quotes)
457
- - Chỉ include files thay đổi
458
- `;
459
-
460
- document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
461
-
462
- function toggleSystemPrompt() {
463
- const content = document.getElementById('system-prompt-content');
464
- const icon = document.getElementById('toggle-icon');
465
- content.classList.toggle('open');
466
- icon.style.transform = content.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
467
- }
468
-
469
- function showResponse(elementId, data, isError = false) {
470
- const el = document.getElementById(elementId);
471
- el.className = 'response show ' + (isError ? 'error' : 'success');
472
-
473
- // Format nice JSON
474
- if (typeof data === 'object') {
475
- if (data.stdout) data.stdout = data.stdout.trim();
476
- if (data.stderr) data.stderr = data.stderr.trim();
477
- }
478
-
479
- el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
480
- }
481
-
482
- function showLoading(button, originalText) {
483
- button.disabled = true;
484
- button.innerHTML = '<div class="loading-spinner"></div>';
485
- button.dataset.originalText = originalText;
486
- }
487
-
488
- function resetButton(button) {
489
- button.disabled = false;
490
- button.innerHTML = button.dataset.originalText;
491
- }
492
-
493
- function showToast(message, type = 'success') {
494
- const toast = document.getElementById('toast');
495
- let icon = type === 'success' ? '✅' : (type === 'error' ? '❌' : 'ℹ️');
496
- toast.innerHTML = `${icon} ${message}`;
497
- toast.classList.add('show');
498
- setTimeout(() => toast.classList.remove('show'), 3000);
499
- }
500
-
501
- // --- ROBUST COPY FUNCTION (MODERN + FALLBACK) ---
502
- function fallbackCopyTextToClipboard(text) {
503
- var textArea = document.createElement("textarea");
504
- textArea.value = text;
505
-
506
- // Ensure textarea is not visible but part of DOM
507
- textArea.style.position = "fixed";
508
- textArea.style.left = "-9999px";
509
- textArea.style.top = "0";
510
- document.body.appendChild(textArea);
511
-
512
- textArea.focus();
513
- textArea.select();
514
-
515
- try {
516
- var successful = document.execCommand('copy');
517
- return successful;
518
- } catch (err) {
519
- console.error('Fallback: Oops, unable to copy', err);
520
- return false;
521
- } finally {
522
- document.body.removeChild(textArea);
523
- }
524
- }
525
-
526
- async function copyToClipboard(text, btnElement, successIconId, successTextId) {
527
- if (!text) return showToast('Nothing to copy', 'error');
528
-
529
- const updateUI = () => {
530
- if (btnElement) {
531
- btnElement.classList.add('copied');
532
- const icon = document.getElementById(successIconId);
533
- const label = document.getElementById(successTextId);
534
-
535
- const originalIcon = icon.textContent;
536
- const originalLabel = label.textContent;
537
-
538
- icon.textContent = '✓';
539
- label.textContent = 'Copied';
540
-
541
- setTimeout(() => {
542
- btnElement.classList.remove('copied');
543
- icon.textContent = originalIcon;
544
- label.textContent = originalLabel;
545
- }, 2000);
546
- }
547
- showToast('Copied to clipboard!');
548
- };
549
-
550
- // Try Modern API first
551
- if (navigator.clipboard && navigator.clipboard.writeText) {
552
- try {
553
- await navigator.clipboard.writeText(text);
554
- updateUI();
555
- } catch (err) {
556
- console.warn('Modern copy failed, trying fallback', err);
557
- // Try fallback if modern API fails
558
- if (fallbackCopyTextToClipboard(text)) {
559
- updateUI();
560
- } else {
561
- showToast('Failed to copy', 'error');
562
- }
563
- }
564
- } else {
565
- // Use fallback immediately if API doesn't exist
566
- if (fallbackCopyTextToClipboard(text)) {
567
- updateUI();
568
- } else {
569
- showToast('Failed to copy', 'error');
570
- }
571
- }
572
- }
573
-
574
- // --- Logic Handlers ---
575
-
576
- function copySystemPrompt() {
577
- copyToClipboard(SYSTEM_PROMPT, event.target.closest('.btn'), 'copy-icon', 'copy-text');
578
- }
579
-
580
- async function testAnalyze() {
581
- const btn = event.target.closest('.btn');
582
- const path = document.getElementById('analyze-path').value;
583
- const originalHTML = btn.innerHTML;
584
-
585
- showLoading(btn, originalHTML);
586
- try {
587
- const res = await fetch(`${API_BASE}/api/analyze`, {
588
- method: 'POST',
589
- headers: { 'Content-Type': 'application/json' },
590
- body: JSON.stringify({ path })
591
- });
592
-
593
- if (res.ok) {
594
- const text = await res.text();
595
- lastAnalyzeResult = text;
596
-
597
- // Trigger download
598
- const blob = new Blob([text], { type: 'text/plain' });
599
- const url = window.URL.createObjectURL(blob);
600
- const a = document.createElement('a');
601
- a.href = url;
602
- a.download = 'project.txt';
603
- a.click();
604
-
605
- showResponse('analyze-response', { success: true, files: text.split('===== FILE:').length - 1 });
606
- showToast('Analysis completed');
607
- } else {
608
- const data = await res.json();
609
- showResponse('analyze-response', data, true);
610
- showToast('Analysis failed', 'error');
611
- }
612
- } catch (err) {
613
- showResponse('analyze-response', { error: err.message }, true);
614
- showToast('Connection error', 'error');
615
- }
616
- resetButton(btn);
617
- }
618
-
619
- async function copyAnalyzeResult() {
620
- const btn = event.target.closest('.btn');
621
- if (!lastAnalyzeResult) {
622
- showToast('Please analyze/download first', 'error');
623
- return;
624
- }
625
- copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
626
- }
627
-
628
- async function testExecute() {
629
- const btn = event.target.closest('.btn');
630
- const bash = document.getElementById('execute-bash').value;
631
- if (!bash.trim()) return showToast('Script is empty', 'error');
632
-
633
- showLoading(btn, btn.innerHTML);
634
- try {
635
- const res = await fetch(`${API_BASE}/api/execute`, {
636
- method: 'POST',
637
- headers: { 'Content-Type': 'application/json' },
638
- body: JSON.stringify({ bash })
639
- });
640
- const data = await res.json();
641
- showResponse('execute-response', data, !data.success);
642
- showToast(data.success ? 'Executed successfully' : 'Execution failed', data.success ? 'success' : 'error');
643
- } catch (err) {
644
- showResponse('execute-response', { error: err.message }, true);
645
- }
646
- resetButton(btn);
647
- }
648
-
649
- async function executeFromClipboard() {
650
- try {
651
- const text = await navigator.clipboard.readText();
652
- if (!text.trim()) return showToast('Clipboard is empty', 'error');
653
-
654
- document.getElementById('execute-bash').value = text;
655
- showToast('Pasted from clipboard');
656
- } catch (err) {
657
- showToast('Clipboard permission denied', 'error');
658
- }
659
- }
660
-
661
- // Init Check
662
- (async () => {
663
- const statusBadge = document.getElementById('status');
664
- try {
665
- const res = await fetch(`${API_BASE}/health`);
666
- if (res.ok) {
667
- statusBadge.textContent = '● Online';
668
- statusBadge.style.backgroundColor = 'var(--ios-green)';
669
- }
670
- } catch {
671
- statusBadge.textContent = '● Offline';
672
- statusBadge.style.backgroundColor = 'var(--ios-text-secondary)';
673
- }
674
- })();
675
-
676
- </script>
116
+ <script type="module" src="/js/main.js"></script>
677
117
  </body>
678
118
 
679
- </html>
119
+ </html>