@adiholick/nvstudio-mcp 2.0.0 → 2.0.1

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/dist/index.js CHANGED
@@ -27,7 +27,7 @@ function backupScript(target, code) {
27
27
  // 2. Inisialisasi server MCP
28
28
  const server = new index_js_1.Server({
29
29
  name: "nvstudio-mcp",
30
- version: "2.0.0",
30
+ version: "2.0.1",
31
31
  }, {
32
32
  capabilities: {
33
33
  tools: {},
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@adiholick/nvstudio-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Local Bridge Server MCP for Roblox Studio",
5
5
  "main": "dist/index.js",
6
- "bin": "dist/index.js",
6
+ "bin": {
7
+ "nvstudio-mcp": "./dist/index.js"
8
+ },
7
9
  "files": [
8
10
  "dist",
9
11
  "public",
package/public/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>nvstudio-mcp | Dashboard</title>
6
+ <title>nvstudio-mcp | Bridge Dashboard</title>
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
@@ -11,23 +11,29 @@
11
11
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
12
12
 
13
13
  :root {
14
- --bg: #0a0a0a;
15
- --surface: #111111;
16
- --surface2: #1a1a1a;
17
- --surface3: #222222;
18
- --border: #2a2a2a;
19
- --border2: #333333;
20
- --text: #f0f0f0;
21
- --text-muted: #888888;
22
- --text-dim: #555555;
23
- --white: #ffffff;
24
- --accent: #ffffff;
25
- --task: #a8a8a8;
26
- --response: #d0d0d0;
27
- --error: #ff5555;
28
- --system: #888888;
29
- --success: #888888;
30
- --sidebar-w: 220px;
14
+ --bg: #000000;
15
+ --surface: #0a0a0a;
16
+ --surface-glass: rgba(10, 10, 10, 0.7);
17
+ --surface-card: #121212;
18
+ --surface-hover: #1c1c1c;
19
+ --border: rgba(255, 255, 255, 0.08);
20
+ --border-highlight: rgba(255, 255, 255, 0.15);
21
+
22
+ --text: #f4f4f5;
23
+ --text-muted: #a1a1aa;
24
+ --text-dim: #71717a;
25
+
26
+ --accent: #3b82f6;
27
+ --accent-glow: rgba(59, 130, 246, 0.2);
28
+ --success: #10b981;
29
+ --success-glow: rgba(16, 185, 129, 0.25);
30
+ --error: #ef4444;
31
+ --error-glow: rgba(239, 68, 68, 0.2);
32
+ --task: #8b5cf6;
33
+ --response: #14b8a6;
34
+ --system: #a1a1aa;
35
+
36
+ --sidebar-w: 260px;
31
37
  }
32
38
 
33
39
  html, body {
@@ -37,421 +43,437 @@
37
43
  font-family: 'Inter', sans-serif;
38
44
  font-size: 14px;
39
45
  overflow: hidden;
46
+ -webkit-font-smoothing: antialiased;
40
47
  }
41
48
 
42
49
  /* ── LAYOUT ── */
43
50
  .layout {
44
51
  display: flex;
45
52
  height: 100vh;
53
+ background: radial-gradient(circle at 50% 0%, rgba(20, 20, 25, 1) 0%, rgba(0, 0, 0, 1) 100%);
46
54
  }
47
55
 
48
56
  /* ── SIDEBAR ── */
49
57
  .sidebar {
50
58
  width: var(--sidebar-w);
51
59
  min-width: var(--sidebar-w);
52
- background: var(--surface);
60
+ background: var(--surface-glass);
61
+ backdrop-filter: blur(12px);
53
62
  border-right: 1px solid var(--border);
54
63
  display: flex;
55
64
  flex-direction: column;
56
65
  overflow: hidden;
66
+ z-index: 10;
57
67
  }
58
68
 
59
69
  .sidebar-logo {
60
- padding: 24px 20px 20px;
70
+ padding: 24px;
61
71
  border-bottom: 1px solid var(--border);
62
72
  }
63
73
  .sidebar-logo .logo-mark {
64
74
  display: flex;
65
75
  align-items: center;
66
- gap: 10px;
76
+ gap: 14px;
67
77
  }
68
78
  .sidebar-logo .logo-icon {
69
- width: 32px; height: 32px;
70
- background: var(--white);
71
- border-radius: 8px;
79
+ width: 38px; height: 38px;
80
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
81
+ border-radius: 10px;
72
82
  display: flex; align-items: center; justify-content: center;
73
83
  flex-shrink: 0;
84
+ box-shadow: 0 4px 12px var(--accent-glow);
74
85
  }
75
- .sidebar-logo .logo-icon svg { color: #000; }
86
+ .sidebar-logo .logo-icon svg { color: #fff; width: 22px; height: 22px; }
87
+
88
+ .logo-text-wrapper { display: flex; flex-direction: column; }
76
89
  .sidebar-logo .logo-name {
77
- font-size: 13px;
90
+ font-size: 14px;
78
91
  font-weight: 700;
79
- color: var(--white);
92
+ color: var(--text);
80
93
  letter-spacing: 0.02em;
81
94
  }
82
95
  .sidebar-logo .logo-sub {
83
- font-size: 10px;
84
- color: var(--text-dim);
85
- margin-top: 2px;
86
- letter-spacing: 0.08em;
96
+ font-size: 11px;
97
+ color: var(--accent);
98
+ font-weight: 600;
99
+ margin-top: 3px;
100
+ letter-spacing: 0.05em;
87
101
  text-transform: uppercase;
88
102
  }
89
103
 
90
104
  /* Status Indicator */
91
105
  .sidebar-status {
92
- padding: 14px 20px;
106
+ padding: 18px 24px;
93
107
  border-bottom: 1px solid var(--border);
94
108
  display: flex;
95
109
  align-items: center;
96
- gap: 8px;
110
+ gap: 12px;
111
+ background: rgba(0, 0, 0, 0.2);
112
+ }
113
+ .status-indicator-ring {
114
+ position: relative;
115
+ width: 10px; height: 10px;
116
+ display: flex; align-items: center; justify-content: center;
97
117
  }
98
118
  .status-dot {
99
- width: 7px; height: 7px;
119
+ width: 10px; height: 10px;
100
120
  border-radius: 50%;
101
- background: var(--text-dim);
102
- flex-shrink: 0;
103
- transition: background 0.4s;
121
+ z-index: 2;
122
+ transition: all 0.3s ease;
123
+ }
124
+ .status-dot.connected { background: var(--success); box-shadow: 0 0 10px var(--success); }
125
+ .status-dot.waiting { background: var(--error); box-shadow: 0 0 10px var(--error); }
126
+
127
+ .status-ping {
128
+ position: absolute;
129
+ width: 100%; height: 100%;
130
+ border-radius: 50%;
131
+ background: inherit;
132
+ animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
133
+ opacity: 0;
134
+ z-index: 1;
104
135
  }
105
- .status-dot.connected { background: var(--white); box-shadow: 0 0 8px rgba(255,255,255,0.4); }
106
- .status-dot.waiting { background: #777; animation: pulse-dot 1.5s infinite; }
107
- .status-label {
108
- font-size: 11px;
109
- color: var(--text-muted);
110
- font-weight: 500;
111
- letter-spacing: 0.06em;
112
- text-transform: uppercase;
136
+ .status-dot.connected + .status-ping { background: var(--success); opacity: 0.5; }
137
+
138
+ @keyframes ping {
139
+ 75%, 100% { transform: scale(2.5); opacity: 0; }
113
140
  }
114
141
 
115
- @keyframes pulse-dot {
116
- 0%, 100% { opacity: 1; }
117
- 50% { opacity: 0.3; }
142
+ .status-label {
143
+ font-size: 13px;
144
+ font-weight: 500;
145
+ color: var(--text);
118
146
  }
119
147
 
120
- /* Nav */
148
+ /* Navigation */
121
149
  .sidebar-nav {
122
150
  flex: 1;
123
- padding: 12px 0;
151
+ padding: 20px 12px;
124
152
  overflow-y: auto;
125
153
  }
126
154
  .nav-group-label {
127
- font-size: 10px;
155
+ font-size: 11px;
128
156
  font-weight: 600;
129
157
  color: var(--text-dim);
130
- letter-spacing: 0.1em;
131
158
  text-transform: uppercase;
132
- padding: 8px 20px 4px;
159
+ letter-spacing: 0.1em;
160
+ margin: 0 12px 8px;
133
161
  }
134
162
  .nav-item {
135
163
  display: flex;
136
164
  align-items: center;
137
165
  gap: 10px;
138
- padding: 9px 20px;
139
- cursor: pointer;
166
+ padding: 10px 12px;
167
+ border-radius: 8px;
140
168
  color: var(--text-muted);
141
- font-size: 13px;
169
+ cursor: pointer;
170
+ transition: all 0.2s;
171
+ margin-bottom: 4px;
142
172
  font-weight: 500;
143
- transition: color 0.15s, background 0.15s;
144
- border-left: 2px solid transparent;
145
- user-select: none;
146
173
  }
147
- .nav-item:hover { color: var(--text); background: var(--surface2); }
148
- .nav-item.active { color: var(--white); border-left-color: var(--white); background: var(--surface2); }
149
- .nav-item svg { width: 15px; height: 15px; flex-shrink: 0; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
174
+ .nav-item:hover {
175
+ background: var(--surface-hover);
176
+ color: var(--text);
177
+ }
178
+ .nav-item.active {
179
+ background: rgba(59, 130, 246, 0.1);
180
+ color: var(--accent);
181
+ border-left: 2px solid var(--accent);
182
+ }
183
+ .nav-item svg { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2; fill: none; }
150
184
 
151
185
  .sidebar-footer {
152
- padding: 16px 20px;
186
+ padding: 20px 24px;
153
187
  border-top: 1px solid var(--border);
154
- }
155
- .sidebar-footer .version {
156
- font-size: 11px;
188
+ display: flex;
189
+ justify-content: space-between;
190
+ align-items: center;
191
+ font-size: 12px;
157
192
  color: var(--text-dim);
158
- font-family: 'JetBrains Mono', monospace;
193
+ }
194
+ .badge-v2 {
195
+ background: rgba(59, 130, 246, 0.15);
196
+ color: var(--accent);
197
+ border: 1px solid rgba(59, 130, 246, 0.3);
198
+ padding: 3px 8px;
199
+ border-radius: 4px;
200
+ font-weight: 600;
201
+ font-size: 10px;
202
+ letter-spacing: 0.05em;
159
203
  }
160
204
 
161
- /* ── MAIN ── */
205
+ /* ── MAIN CONTENT ── */
162
206
  .main {
163
207
  flex: 1;
164
208
  display: flex;
165
209
  flex-direction: column;
166
210
  overflow: hidden;
167
- background: var(--bg);
211
+ position: relative;
168
212
  }
169
213
 
170
- /* Topbar */
171
214
  .topbar {
172
- height: 56px;
215
+ height: 70px;
216
+ padding: 0 32px;
217
+ border-bottom: 1px solid var(--border);
173
218
  display: flex;
174
219
  align-items: center;
175
220
  justify-content: space-between;
176
- padding: 0 28px;
177
- border-bottom: 1px solid var(--border);
178
- flex-shrink: 0;
221
+ background: rgba(0, 0, 0, 0.4);
222
+ backdrop-filter: blur(10px);
223
+ z-index: 5;
179
224
  }
180
225
  .page-title {
181
- font-size: 15px;
226
+ font-size: 18px;
182
227
  font-weight: 600;
183
- color: var(--white);
184
- }
185
- .topbar-right {
228
+ color: var(--text);
186
229
  display: flex;
187
230
  align-items: center;
188
231
  gap: 12px;
189
232
  }
233
+
234
+ .topbar-right { display: flex; align-items: center; gap: 16px; }
190
235
  .topbar-badge {
191
- font-size: 11px;
236
+ font-size: 12px;
192
237
  font-weight: 500;
193
- font-family: 'JetBrains Mono', monospace;
194
238
  color: var(--text-muted);
195
- background: var(--surface2);
239
+ background: var(--surface-card);
196
240
  border: 1px solid var(--border);
197
241
  padding: 4px 10px;
198
242
  border-radius: 20px;
199
243
  }
200
244
  .clear-btn {
201
- font-size: 11px;
202
- font-weight: 500;
203
- color: var(--text-dim);
204
- background: none;
245
+ background: transparent;
205
246
  border: 1px solid var(--border);
247
+ color: var(--text-muted);
248
+ font-family: inherit;
249
+ font-size: 12px;
250
+ font-weight: 500;
206
251
  padding: 5px 12px;
207
252
  border-radius: 6px;
208
253
  cursor: pointer;
209
- transition: color 0.15s, border-color 0.15s;
254
+ transition: all 0.2s;
255
+ }
256
+ .clear-btn:hover {
257
+ background: var(--surface-hover);
258
+ color: var(--text);
259
+ border-color: var(--text-dim);
210
260
  }
211
- .clear-btn:hover { color: var(--white); border-color: var(--border2); }
212
261
 
213
- /* Content */
214
262
  .content {
215
263
  flex: 1;
216
- overflow: hidden;
217
264
  position: relative;
265
+ overflow: hidden;
218
266
  }
219
-
220
- /* ── PAGES ── */
221
267
  .page {
222
- display: none;
223
- height: 100%;
268
+ position: absolute;
269
+ inset: 0;
270
+ padding: 32px;
224
271
  overflow-y: auto;
225
- padding: 24px 28px;
226
- flex-direction: column;
227
- gap: 20px;
272
+ opacity: 0;
273
+ pointer-events: none;
274
+ transition: opacity 0.3s ease;
275
+ }
276
+ .page.active {
277
+ opacity: 1;
278
+ pointer-events: auto;
228
279
  }
229
- .page.active { display: flex; }
230
280
 
231
281
  /* ── ACTIVITY LOG PAGE ── */
232
- #page-activity {
233
- padding: 0;
234
- }
235
- #page-activity .log-toolbar {
282
+ .log-toolbar {
236
283
  display: flex;
237
- align-items: center;
238
284
  justify-content: space-between;
239
- padding: 14px 28px;
285
+ align-items: center;
286
+ padding: 16px 32px;
287
+ background: var(--surface-glass);
240
288
  border-bottom: 1px solid var(--border);
241
- flex-shrink: 0;
242
- gap: 12px;
243
- }
244
- .filter-btns {
245
- display: flex;
246
- gap: 4px;
247
289
  }
290
+ .filter-btns { display: flex; gap: 8px; }
248
291
  .filter-btn {
249
- font-size: 11px;
250
- font-weight: 500;
251
- padding: 5px 12px;
252
- border-radius: 5px;
292
+ background: var(--surface-card);
253
293
  border: 1px solid var(--border);
254
- background: none;
255
294
  color: var(--text-muted);
295
+ font-family: inherit;
296
+ font-size: 12px;
297
+ padding: 6px 14px;
298
+ border-radius: 6px;
256
299
  cursor: pointer;
257
- transition: all 0.15s;
300
+ transition: all 0.2s;
301
+ font-weight: 500;
302
+ }
303
+ .filter-btn:hover { background: var(--surface-hover); color: var(--text); }
304
+ .filter-btn.active {
305
+ background: var(--text);
306
+ color: var(--bg);
307
+ border-color: var(--text);
258
308
  }
259
- .filter-btn:hover { color: var(--white); border-color: var(--border2); }
260
- .filter-btn.active { background: var(--white); color: #000; border-color: var(--white); }
261
309
 
262
310
  .scroll-lock-btn {
263
- display: flex;
264
- align-items: center;
265
- gap: 6px;
266
- font-size: 11px;
267
- color: var(--text-dim);
268
- background: none;
269
- border: 1px solid var(--border);
270
- padding: 5px 10px;
271
- border-radius: 5px;
272
- cursor: pointer;
273
- transition: all 0.15s;
311
+ display: flex; align-items: center; gap: 6px;
312
+ background: transparent; border: none;
313
+ color: var(--text-muted); font-family: inherit; font-size: 12px; cursor: pointer; transition: 0.2s;
274
314
  }
275
- .scroll-lock-btn:hover { color: var(--text-muted); }
276
- .scroll-lock-btn.locked { color: var(--white); border-color: var(--border2); }
277
- .scroll-lock-btn svg { width: 12px; height: 12px; }
315
+ .scroll-lock-btn:hover { color: var(--text); }
316
+ .scroll-lock-btn svg { width: 14px; height: 14px; opacity: 0.5; }
317
+ .scroll-lock-btn.locked svg { opacity: 1; stroke: var(--accent); }
318
+ .scroll-lock-btn.locked { color: var(--accent); }
278
319
 
279
320
  .log-list {
280
321
  flex: 1;
281
322
  overflow-y: auto;
282
- padding: 0;
283
- }
284
- .log-entry {
323
+ padding: 16px 32px;
285
324
  display: flex;
286
- align-items: flex-start;
287
- gap: 0;
288
- padding: 11px 28px;
289
- border-bottom: 1px solid var(--border);
290
- font-family: 'JetBrains Mono', monospace;
291
- font-size: 12px;
292
- line-height: 1.6;
293
- transition: background 0.1s;
294
- }
295
- .log-entry:hover { background: var(--surface2); }
296
- .log-entry-time {
297
- color: var(--text-dim);
298
- min-width: 80px;
299
- flex-shrink: 0;
300
- margin-right: 16px;
301
- }
302
- .log-entry-badge {
303
- display: inline-flex;
304
- align-items: center;
305
- font-size: 9px;
306
- font-weight: 700;
307
- letter-spacing: 0.08em;
308
- text-transform: uppercase;
309
- padding: 2px 6px;
310
- border-radius: 3px;
311
- margin-right: 12px;
312
- flex-shrink: 0;
313
- min-width: 60px;
314
- justify-content: center;
315
- font-family: 'Inter', sans-serif;
325
+ flex-direction: column;
326
+ gap: 8px;
327
+ height: calc(100vh - 140px); /* 70px topbar + ~70px toolbar */
316
328
  }
317
- .badge-task { background: rgba(255,255,255,0.08); color: var(--task); }
318
- .badge-response { background: rgba(255,255,255,0.06); color: var(--response); }
319
- .badge-error { background: rgba(255,85,85,0.12); color: var(--error); }
320
- .badge-system { background: rgba(255,255,255,0.04); color: var(--system); }
321
- .log-entry-msg { color: var(--text); word-break: break-word; }
322
-
329
+
323
330
  .log-empty {
324
331
  display: flex;
325
332
  flex-direction: column;
326
333
  align-items: center;
327
334
  justify-content: center;
328
335
  height: 100%;
336
+ color: var(--text-dim);
337
+ gap: 16px;
338
+ }
339
+ .log-empty svg { width: 48px; height: 48px; opacity: 0.2; stroke: currentColor; stroke-width: 1.5; fill: none; }
340
+ .log-empty p { font-size: 14px; font-weight: 500; }
341
+
342
+ .log-entry {
343
+ display: flex;
329
344
  gap: 12px;
345
+ padding: 12px 16px;
346
+ background: rgba(255, 255, 255, 0.02);
347
+ border: 1px solid var(--border);
348
+ border-radius: 8px;
349
+ font-family: 'JetBrains Mono', monospace;
350
+ font-size: 13px;
351
+ line-height: 1.5;
352
+ transition: transform 0.2s, background 0.2s;
353
+ }
354
+ .log-entry:hover {
355
+ background: rgba(255, 255, 255, 0.04);
356
+ border-color: var(--border-highlight);
357
+ }
358
+ .log-entry-time {
330
359
  color: var(--text-dim);
360
+ font-size: 12px;
361
+ flex-shrink: 0;
362
+ white-space: nowrap;
331
363
  }
332
- .log-empty svg { width: 40px; height: 40px; stroke: var(--text-dim); fill: none; stroke-width: 1; stroke-linecap: round; stroke-linejoin: round; }
333
- .log-empty p { font-size: 13px; }
364
+ .log-entry-badge {
365
+ font-size: 10px; font-weight: 700;
366
+ padding: 2px 8px; border-radius: 4px;
367
+ height: fit-content; flex-shrink: 0;
368
+ }
369
+
370
+ .badge-task { color: var(--task); background: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); }
371
+ .badge-response { color: var(--response); background: rgba(20, 184, 166, 0.15); border: 1px solid rgba(20, 184, 166, 0.3); }
372
+ .badge-error { color: var(--error); background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.3); }
373
+ .badge-system { color: var(--system); background: rgba(161, 161, 170, 0.15); border: 1px solid rgba(161, 161, 170, 0.3); }
374
+
375
+ .log-entry-msg { color: var(--text-primary); word-break: break-word; }
334
376
 
335
377
  /* ── STATS PAGE ── */
336
378
  .stats-grid {
337
379
  display: grid;
338
- grid-template-columns: repeat(2, 1fr);
339
- gap: 16px;
380
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
381
+ gap: 20px;
382
+ max-width: 1000px;
340
383
  }
341
384
  .stat-card {
342
- background: var(--surface);
385
+ background: var(--surface-card);
343
386
  border: 1px solid var(--border);
344
- border-radius: 10px;
345
- padding: 22px 24px;
346
- display: flex;
347
- flex-direction: column;
348
- gap: 8px;
349
- }
350
- .stat-card.full { grid-column: 1 / -1; }
351
- .stat-card-label {
352
- font-size: 11px;
353
- font-weight: 600;
354
- color: var(--text-dim);
355
- letter-spacing: 0.08em;
356
- text-transform: uppercase;
357
- }
358
- .stat-card-value {
359
- font-size: 36px;
360
- font-weight: 700;
361
- color: var(--white);
362
- font-family: 'JetBrains Mono', monospace;
363
- line-height: 1;
364
- }
365
- .stat-card-sub {
366
- font-size: 12px;
367
- color: var(--text-muted);
368
- }
369
-
370
- .progress-bar-wrap {
371
- height: 4px;
372
- background: var(--surface3);
373
- border-radius: 2px;
387
+ border-radius: 12px;
388
+ padding: 24px;
389
+ position: relative;
374
390
  overflow: hidden;
375
- margin-top: 4px;
376
391
  }
377
- .progress-bar {
378
- height: 100%;
379
- background: var(--white);
380
- border-radius: 2px;
381
- transition: width 0.6s ease;
382
- min-width: 2px;
392
+ .stat-card::before {
393
+ content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 2px;
394
+ background: linear-gradient(90deg, transparent, var(--border-highlight), transparent);
395
+ opacity: 0; transition: opacity 0.3s;
383
396
  }
397
+ .stat-card:hover::before { opacity: 1; }
398
+ .stat-card.full { grid-column: 1 / -1; }
399
+
400
+ .stat-card-label { font-size: 13px; font-weight: 500; color: var(--text-muted); margin-bottom: 8px; }
401
+ .stat-card-value { font-size: 32px; font-weight: 700; color: var(--text); letter-spacing: -0.02em; margin-bottom: 4px; }
402
+ .stat-card-sub { font-size: 12px; color: var(--text-dim); }
403
+
404
+ .progress-bar-wrap { height: 4px; background: rgba(255,255,255,0.05); border-radius: 2px; margin-top: 16px; overflow: hidden; }
405
+ .progress-bar { height: 100%; background: var(--success); border-radius: 2px; transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
384
406
  .progress-bar.error { background: var(--error); }
385
407
 
386
- /* Session Info card */
387
- .info-list { display: flex; flex-direction: column; gap: 12px; }
388
- .info-row {
389
- display: flex;
390
- justify-content: space-between;
391
- align-items: center;
392
- font-size: 13px;
393
- }
394
- .info-row-label { color: var(--text-muted); }
395
- .info-row-value {
396
- font-family: 'JetBrains Mono', monospace;
397
- font-size: 12px;
398
- color: var(--white);
399
- background: var(--surface2);
400
- padding: 3px 8px;
401
- border-radius: 4px;
402
- }
403
-
408
+ .info-list { display: flex; flex-direction: column; gap: 16px; margin-top: 20px; }
409
+ .info-row { display: flex; justify-content: space-between; align-items: center; padding-bottom: 16px; border-bottom: 1px dashed var(--border); }
410
+ .info-row:last-child { border-bottom: none; padding-bottom: 0; }
411
+ .info-row-label { font-size: 13px; color: var(--text-muted); }
412
+ .info-row-value { font-size: 14px; font-weight: 600; color: var(--text); font-family: 'JetBrains Mono', monospace; }
413
+
404
414
  /* ── ABOUT PAGE ── */
405
- .about-section { max-width: 580px; }
415
+ .about-section { max-width: 680px; }
406
416
  .about-hero {
407
- background: var(--surface);
417
+ background: linear-gradient(145deg, var(--surface-card) 0%, rgba(10,10,10,1) 100%);
408
418
  border: 1px solid var(--border);
409
- border-radius: 12px;
410
- padding: 32px;
419
+ border-radius: 16px;
420
+ padding: 40px;
411
421
  display: flex;
412
422
  flex-direction: column;
413
- gap: 12px;
414
- margin-bottom: 20px;
423
+ gap: 16px;
424
+ margin-bottom: 24px;
425
+ position: relative;
426
+ overflow: hidden;
427
+ }
428
+ .about-hero::after {
429
+ content: ''; position: absolute; top: -50%; right: -20%; width: 400px; height: 400px;
430
+ background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
431
+ pointer-events: none;
415
432
  }
416
- .about-hero h2 { font-size: 22px; color: var(--white); }
417
- .about-hero p { font-size: 13px; color: var(--text-muted); line-height: 1.7; }
418
- .about-links { display: flex; gap: 10px; margin-top: 4px; }
433
+ .about-hero h2 { font-size: 28px; font-weight: 700; color: var(--text); letter-spacing: -0.02em; display: flex; align-items: center; gap: 12px;}
434
+ .v2-chip { font-size: 12px; background: var(--accent); color: white; padding: 2px 8px; border-radius: 12px; vertical-align: middle;}
435
+ .about-hero p { font-size: 15px; color: var(--text-muted); line-height: 1.6; }
436
+
437
+ .about-links { display: flex; gap: 12px; margin-top: 8px; }
419
438
  .about-link {
420
- font-size: 12px;
421
- font-weight: 500;
422
- color: var(--text-muted);
423
- background: var(--surface2);
424
- border: 1px solid var(--border);
425
- padding: 7px 14px;
426
- border-radius: 6px;
427
- text-decoration: none;
428
- transition: all 0.15s;
429
- display: inline-flex;
430
- align-items: center;
431
- gap: 6px;
439
+ font-size: 13px; font-weight: 600; color: var(--text);
440
+ background: rgba(255,255,255,0.05); border: 1px solid var(--border);
441
+ padding: 10px 18px; border-radius: 8px;
442
+ text-decoration: none; transition: all 0.2s;
443
+ display: inline-flex; align-items: center; gap: 8px;
432
444
  }
433
- .about-link:hover { color: var(--white); border-color: var(--border2); }
445
+ .about-link:hover { background: rgba(255,255,255,0.1); border-color: var(--border-highlight); transform: translateY(-1px); }
434
446
 
435
447
  .about-card {
436
- background: var(--surface);
448
+ background: var(--surface-card);
437
449
  border: 1px solid var(--border);
438
- border-radius: 10px;
439
- padding: 20px 24px;
440
- margin-bottom: 16px;
450
+ border-radius: 12px;
451
+ padding: 24px 32px;
452
+ margin-bottom: 20px;
453
+ box-shadow: 0 4px 20px rgba(0,0,0,0.2);
454
+ }
455
+ .about-card h3 { font-size: 16px; font-weight: 600; color: var(--text); margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 12px;}
456
+ .about-card ul { display: flex; flex-direction: column; gap: 12px; padding-left: 20px; }
457
+ .about-card li { font-size: 14px; color: var(--text-muted); line-height: 1.6; }
458
+ .about-card li strong { color: var(--text); }
459
+ .about-card code {
460
+ background: rgba(255,255,255,0.1);
461
+ color: var(--accent);
462
+ padding: 2px 6px;
463
+ border-radius: 4px;
464
+ font-family: 'JetBrains Mono', monospace;
465
+ font-size: 12px;
441
466
  }
442
- .about-card h3 { font-size: 13px; font-weight: 600; color: var(--white); margin-bottom: 10px; }
443
- .about-card ul { display: flex; flex-direction: column; gap: 7px; padding-left: 16px; }
444
- .about-card li { font-size: 13px; color: var(--text-muted); line-height: 1.5; }
445
467
 
446
468
  /* Scrollbar */
447
- ::-webkit-scrollbar { width: 4px; height: 4px; }
469
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
448
470
  ::-webkit-scrollbar-track { background: transparent; }
449
- ::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }
450
- ::-webkit-scrollbar-thumb:hover { background: #555; }
471
+ ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
472
+ ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
451
473
 
452
474
  /* Transitions */
453
- .log-entry { animation: fadeIn 0.2s ease; }
454
- @keyframes fadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
475
+ .log-entry { animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
476
+ @keyframes slideIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
455
477
  </style>
456
478
  </head>
457
479
  <body>
@@ -462,20 +484,23 @@
462
484
  <div class="sidebar-logo">
463
485
  <div class="logo-mark">
464
486
  <div class="logo-icon">
465
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
487
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
466
488
  <path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>
467
489
  </svg>
468
490
  </div>
469
- <div>
491
+ <div class="logo-text-wrapper">
470
492
  <div class="logo-name">nvstudio-mcp</div>
471
- <div class="logo-sub">Bridge Server</div>
493
+ <div class="logo-sub">Zero-Delay Engine</div>
472
494
  </div>
473
495
  </div>
474
496
  </div>
475
497
 
476
498
  <div class="sidebar-status">
477
- <div class="status-dot waiting" id="status-dot"></div>
478
- <span class="status-label" id="status-label">Waiting</span>
499
+ <div class="status-indicator-ring">
500
+ <div class="status-dot waiting" id="status-dot"></div>
501
+ <div class="status-ping"></div>
502
+ </div>
503
+ <span class="status-label" id="status-label">Menunggu Studio...</span>
479
504
  </div>
480
505
 
481
506
  <nav class="sidebar-nav">
@@ -489,15 +514,16 @@
489
514
  Statistics
490
515
  </div>
491
516
 
492
- <div class="nav-group-label" style="margin-top:12px;">Info</div>
517
+ <div class="nav-group-label" style="margin-top:20px;">Info</div>
493
518
  <div class="nav-item" data-page="about">
494
519
  <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
495
- About
520
+ About v2.0
496
521
  </div>
497
522
  </nav>
498
523
 
499
524
  <div class="sidebar-footer">
500
- <div class="version" id="pkg-version">v1.0.2</div>
525
+ <span class="badge-v2" id="pkg-version">v2.0.1</span>
526
+ <span style="font-size:10px; opacity:0.5;">Model Context Protocol</span>
501
527
  </div>
502
528
  </aside>
503
529
 
@@ -517,10 +543,10 @@
517
543
  <div class="page active" id="page-activity" style="padding:0; gap:0;">
518
544
  <div class="log-toolbar">
519
545
  <div class="filter-btns">
520
- <button class="filter-btn active" data-filter="all">All</button>
521
- <button class="filter-btn" data-filter="task">Task</button>
546
+ <button class="filter-btn active" data-filter="all">All Logs</button>
547
+ <button class="filter-btn" data-filter="task">Task / Patch</button>
522
548
  <button class="filter-btn" data-filter="response">Response</button>
523
- <button class="filter-btn" data-filter="error">Error</button>
549
+ <button class="filter-btn" data-filter="error">Error / Validate</button>
524
550
  <button class="filter-btn" data-filter="system">System</button>
525
551
  </div>
526
552
  <button class="scroll-lock-btn locked" id="scroll-lock-btn">
@@ -556,33 +582,37 @@
556
582
  <div class="stat-card">
557
583
  <div class="stat-card-label">Succeeded</div>
558
584
  <div class="stat-card-value" id="stat-success">0</div>
559
- <div class="stat-card-sub">Perintah berhasil</div>
585
+ <div class="stat-card-sub">Perintah berhasil dieksekusi</div>
560
586
  </div>
561
587
  <div class="stat-card">
562
588
  <div class="stat-card-label">Failed</div>
563
589
  <div class="stat-card-value" id="stat-error">0</div>
564
- <div class="stat-card-sub">Perintah gagal</div>
590
+ <div class="stat-card-sub">Perintah gagal atau error sintaks</div>
565
591
  <div class="progress-bar-wrap">
566
592
  <div class="progress-bar error" id="bar-error" style="width:0%"></div>
567
593
  </div>
568
594
  </div>
569
595
  <div class="stat-card full">
570
- <div class="stat-card-label">Session Info</div>
596
+ <div class="stat-card-label">Session Diagnostics</div>
571
597
  <div class="info-list">
572
598
  <div class="info-row">
573
- <span class="info-row-label">Session dimulai</span>
574
- <span class="info-row-value" id="info-session">—</span>
599
+ <span class="info-row-label">Mode Polling</span>
600
+ <span class="info-row-value" style="color:var(--accent);">Zero-Delay Long-Polling (&lt;50ms)</span>
601
+ </div>
602
+ <div class="info-row">
603
+ <span class="info-row-label">Luau Pre-Validation</span>
604
+ <span class="info-row-value" style="color:var(--success);">Active ✓</span>
575
605
  </div>
576
606
  <div class="info-row">
577
- <span class="info-row-label">Port aktif</span>
607
+ <span class="info-row-label">Port Server</span>
578
608
  <span class="info-row-value">3055</span>
579
609
  </div>
580
610
  <div class="info-row">
581
- <span class="info-row-label">Studio status</span>
611
+ <span class="info-row-label">Studio Connection</span>
582
612
  <span class="info-row-value" id="info-studio">Disconnected</span>
583
613
  </div>
584
614
  <div class="info-row">
585
- <span class="info-row-label">Uptime</span>
615
+ <span class="info-row-label">Session Uptime</span>
586
616
  <span class="info-row-value" id="info-uptime">—</span>
587
617
  </div>
588
618
  </div>
@@ -594,38 +624,37 @@
594
624
  <div class="page" id="page-about">
595
625
  <div class="about-section">
596
626
  <div class="about-hero">
597
- <h2>nvstudio-mcp</h2>
598
- <p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude) secara langsung ke dalam <strong>Roblox Studio</strong>. AI Anda dapat membaca, menulis, dan mengeksekusi script layaknya Co-Pilot sejati.</p>
627
+ <h2>nvstudio-mcp <span class="v2-chip">v2.0.0</span></h2>
628
+ <p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
599
629
  <div class="about-links">
600
630
  <a class="about-link" href="https://github.com/Adiholick/nvstudio-mcp" target="_blank">
601
- <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
631
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
602
632
  GitHub
603
633
  </a>
604
634
  <a class="about-link" href="https://www.npmjs.com/package/@adiholick/nvstudio-mcp" target="_blank">
605
- <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm7.315 5.876H2.17v12.248H12v-9.6h-4.685V5.876zm9.515 0h-5.145v12.248h5.145V5.876zm-7.315 4.8H9.87v4.8H9.515v-4.8z"/></svg>
635
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm7.315 5.876H2.17v12.248H12v-9.6h-4.685V5.876zm9.515 0h-5.145v12.248h5.145V5.876zm-7.315 4.8H9.87v4.8H9.515v-4.8z"/></svg>
606
636
  NPM Package
607
637
  </a>
608
638
  </div>
609
639
  </div>
610
640
 
611
641
  <div class="about-card">
612
- <h3>Cara Kerja</h3>
642
+ <h3>⚡ Arsitektur v2.0 Baru</h3>
613
643
  <ul>
614
- <li>Plugin Roblox Studio melakukan polling ke server MCP Bridge ini setiap detik.</li>
615
- <li>AI Agent mengirimkan perintah melalui tool <code>call_mcp_tool</code>.</li>
616
- <li>Bridge meneruskan perintah ke Studio dan mengembalikan hasilnya ke AI.</li>
617
- <li>Dashboard ini menampilkan semua aktivitas secara real-time via WebSocket.</li>
644
+ <li><strong>Zero-Delay Long-Polling:</strong> Plugin Studio kini menunggu task secara realtime hingga 20 detik tanpa putus. Memangkas latensi drastis menjadi &lt; 50ms (dibandingkan polling 1-detik di v1).</li>
645
+ <li><strong>Luau Pre-Commit Validator:</strong> Mencegah engine Roblox crash dengan memvalidasi sintaks Luau <i>sebelum</i> kode dikirim ke Studio.</li>
646
+ <li><strong>Partial Script Patching:</strong> AI kini menggunakan metode <code>patch_script_source</code> (pengeditan parsial per baris) yang jauh lebih cepat dan menghemat ribuan token.</li>
618
647
  </ul>
619
648
  </div>
620
649
 
621
650
  <div class="about-card">
622
- <h3>Commands yang Tersedia</h3>
651
+ <h3>🛠️ Commands MCP Unggulan</h3>
623
652
  <ul>
624
- <li><code>get_children</code> — Daftar children dari instance</li>
625
- <li><code>get_script_source</code> — Baca source code script</li>
626
- <li><code>update_script_source</code> — Tulis/perbarui source code</li>
627
- <li><code>rollback_script</code> — Rollback ke versi sebelumnya</li>
628
- <li><code>get_logs</code> — Ambil log output Studio</li>
653
+ <li><code>patch_script_source</code> — Mengedit bagian spesifik dari script (super cepat).</li>
654
+ <li><code>execute_luau</code> — Menjalankan script Luau secara realtime di Studio.</li>
655
+ <li><code>insert_asset</code> — Memasukkan model 3D/asset dari library Roblox.</li>
656
+ <li><code>generate_terrain</code> — Men-generate atau memanipulasi terrain voxel.</li>
657
+ <li><code>get_children</code> / <code>get_script_source</code> — Inspeksi hirarki dan source code.</li>
629
658
  </ul>
630
659
  </div>
631
660
  </div>
@@ -654,7 +683,7 @@
654
683
  const pages = document.querySelectorAll('.page');
655
684
  const topbarTitle = document.getElementById('topbar-title');
656
685
  const clearBtn = document.getElementById('clear-btn');
657
- const pageTitles = { activity: 'Activity Log', stats: 'Statistics', about: 'About' };
686
+ const pageTitles = { activity: 'Activity Log', stats: 'Session Diagnostics', about: 'About v2.0' };
658
687
 
659
688
  navItems.forEach(item => {
660
689
  item.addEventListener('click', () => {
@@ -701,13 +730,11 @@
701
730
 
702
731
  if (filtered.length === 0) {
703
732
  logEmpty.style.display = 'flex';
704
- // Remove all entries but keep empty
705
733
  [...logList.children].forEach(c => { if (c !== logEmpty) c.remove(); });
706
734
  return;
707
735
  }
708
736
  logEmpty.style.display = 'none';
709
737
 
710
- // Full re-render
711
738
  const frag = document.createDocumentFragment();
712
739
  filtered.forEach(log => {
713
740
  const el = createLogEl(log);
@@ -718,17 +745,25 @@
718
745
  logEmpty.style.display = 'none';
719
746
  logList.appendChild(frag);
720
747
 
721
- if (autoScroll) logList.scrollTop = 0; // newest on top
748
+ if (autoScroll) logList.scrollTop = 0;
722
749
  }
723
750
 
724
751
  function createLogEl(log) {
725
752
  const el = document.createElement('div');
726
753
  el.className = 'log-entry';
727
754
  el.dataset.type = log.type;
728
- const badges = { task: 'TASK', response: 'RESP', error: 'ERROR', system: 'SYS' };
755
+
756
+ let displayType = log.type.toUpperCase();
757
+ // Custom badges for v2 features based on message content
758
+ if (log.type === 'task' && log.message.includes('patch_script_source')) {
759
+ displayType = 'PATCH';
760
+ } else if (log.type === 'error' && log.message.includes('Syntax Error')) {
761
+ displayType = 'SYNTAX';
762
+ }
763
+
729
764
  el.innerHTML = `
730
765
  <span class="log-entry-time">${log.time}</span>
731
- <span class="log-entry-badge badge-${log.type}">${badges[log.type] || log.type}</span>
766
+ <span class="log-entry-badge badge-${log.type}">${displayType}</span>
732
767
  <span class="log-entry-msg">${escapeHtml(log.message)}</span>
733
768
  `;
734
769
  return el;
@@ -740,7 +775,7 @@
740
775
 
741
776
  function prependLog(log) {
742
777
  allLogs.unshift(log);
743
- if (allLogs.length > 500) allLogs.pop();
778
+ if (allLogs.length > 1000) allLogs.pop(); // Keep max 1000 logs in memory
744
779
  logCount.textContent = `${allLogs.length} event${allLogs.length !== 1 ? 's' : ''}`;
745
780
 
746
781
  if (activeFilter !== 'all' && log.type !== activeFilter) return;
@@ -763,14 +798,22 @@
763
798
  const rate = s.totalTasks > 0 ? Math.round((s.successCount / s.totalTasks) * 100) : null;
764
799
  document.getElementById('stat-rate').textContent = rate !== null ? rate + '%' : '—';
765
800
  document.getElementById('bar-success').style.width = (rate || 0) + '%';
801
+
766
802
  const errRate = s.totalTasks > 0 ? Math.round((s.errorCount / s.totalTasks) * 100) : 0;
767
803
  document.getElementById('bar-error').style.width = errRate + '%';
768
804
 
769
805
  if (s.sessionStart) {
770
806
  sessionStartTime = new Date(s.sessionStart);
771
- document.getElementById('info-session').textContent = sessionStartTime.toLocaleString('id-ID');
772
807
  }
773
- document.getElementById('info-studio').textContent = s.studioConnected ? 'Connected ✓' : 'Disconnected';
808
+
809
+ const statusEl = document.getElementById('info-studio');
810
+ if (s.studioConnected) {
811
+ statusEl.textContent = 'Connected ✓';
812
+ statusEl.style.color = 'var(--success)';
813
+ } else {
814
+ statusEl.textContent = 'Disconnected';
815
+ statusEl.style.color = 'var(--text-muted)';
816
+ }
774
817
  }
775
818
 
776
819
  /* ── Uptime ── */
@@ -786,8 +829,16 @@
786
829
  /* ── Studio Status ── */
787
830
  function setStudioStatus(connected) {
788
831
  statusDot.className = 'status-dot ' + (connected ? 'connected' : 'waiting');
789
- statusLabel.textContent = connected ? 'Studio Connected' : 'Waiting';
790
- document.getElementById('info-studio').textContent = connected ? 'Connected ✓' : 'Disconnected';
832
+ statusLabel.textContent = connected ? 'Studio Connected' : 'Menunggu Studio...';
833
+
834
+ const statusEl = document.getElementById('info-studio');
835
+ if (connected) {
836
+ statusEl.textContent = 'Connected ✓';
837
+ statusEl.style.color = 'var(--success)';
838
+ } else {
839
+ statusEl.textContent = 'Disconnected';
840
+ statusEl.style.color = 'var(--text-muted)';
841
+ }
791
842
  }
792
843
 
793
844
  /* ── Socket Events ── */