@weppy/roblox-mcp 2.7.5 → 2.7.6

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +25 -5
  3. package/{plugins/weppy-roblox-mcp/dist → dist}/index.js +68 -68
  4. package/package.json +5 -5
  5. package/{plugins/weppy-roblox-mcp/roblox-plugin → roblox-plugin}/WeppyRobloxMCP.rbxm +0 -0
  6. package/.claude-plugin/marketplace.json +0 -43
  7. package/CODE_OF_CONDUCT.md +0 -29
  8. package/COMMERCIAL-LICENSE.md +0 -13
  9. package/CONTRIBUTING.md +0 -36
  10. package/SECURITY.md +0 -28
  11. package/SUPPORT.md +0 -25
  12. package/TRADEMARKS.md +0 -18
  13. package/glama.json +0 -7
  14. package/install.ps1 +0 -964
  15. package/install.sh +0 -939
  16. package/llms-full.txt +0 -13
  17. package/llms.txt +0 -69
  18. package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +0 -28
  19. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogDetailPage-D6Tqz7ut.css +0 -0
  20. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogDetailPage-DglsIYkW.js +0 -0
  21. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogPage-65B3_w0_.js +0 -0
  22. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogPage-CNxAGfwG.css +0 -0
  23. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConfirmModal-Cpk7SbKb.js +0 -0
  24. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConnectionPage-B-IN5LsC.js +0 -0
  25. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConnectionPage-CNtjimlm.css +0 -0
  26. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/GameChangeDetail-C1XtdYwk.css +0 -0
  27. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/GameChangeDetail-DM3mWsFX.js +0 -0
  28. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/InfoLabel-B_fEbHa7.js +0 -0
  29. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/OverviewPage-B4O0bv4R.js +0 -0
  30. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/OverviewPage-Dsfl-NRT.css +0 -0
  31. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/PlaytestPage-BHLRKn8U.js +0 -0
  32. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/PlaytestPage-DjjsIkke.css +0 -0
  33. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SettingsPage-DmIKC_O1.js +0 -0
  34. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SettingsPage-Du8-FZAO.css +0 -0
  35. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/StatusBadge-C2zYt5iE.css +0 -0
  36. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/StatusBadge-DRdnq30k.js +0 -0
  37. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SyncPage-CW_0kNpZ.js +0 -0
  38. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SyncPage-Dm7Ni3j_.css +0 -0
  39. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/Tabs-876h0_zB.css +0 -0
  40. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/Tabs-BsTVkBUh.js +0 -0
  41. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TierComparison-DGh9vLz0.css +0 -0
  42. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TierComparison-poRtDe46.js +0 -0
  43. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ToolsPage-Bt9vYA7u.css +0 -0
  44. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ToolsPage-D77yJ9jZ.js +0 -0
  45. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TooltipText-DX5jnyNF.js +0 -0
  46. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/UiStudioPage-YtdlkQzT.js +0 -0
  47. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/UiStudioPage-eSinjpOX.css +0 -0
  48. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/WhatsNewPage--uCu0xCm.js +0 -0
  49. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/WhatsNewPage-Lxgj0StO.css +0 -0
  50. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/index-BPIBy2lU.js +0 -0
  51. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/index-CX4MHzNt.css +0 -0
  52. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/sample-requests-CwDMfktX.js +0 -0
  53. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/sample-requests-CygerZZ_.css +0 -0
  54. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ui-studio-sample-DrNTD6yi.png +0 -0
  55. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/useLiveUptime-ElD9lDzh.js +0 -0
  56. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/index.html +0 -0
  57. /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/weppy-icon.png +0 -0
package/install.ps1 DELETED
@@ -1,964 +0,0 @@
1
- #
2
- # WEPPY — One-line install script (Windows PowerShell)
3
- #
4
- # Usage:
5
- # irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
6
- #
7
- # Interactive 2 steps:
8
- # [1/2] Setup — install Roblox Studio Plugin via npx
9
- # [2/2] Register MCP with AI apps (user selection)
10
- #
11
-
12
- $ErrorActionPreference = "Stop"
13
- $script:InstallLogPath = Join-Path ([System.IO.Path]::GetTempPath()) ("weppy-install-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date))
14
- $script:TranscriptStarted = $false
15
- $script:NpmCommandPath = $null
16
-
17
- # ── Utilities ──
18
- function Write-Step($step, $msg) { Write-Host "`n[$step] $msg" -ForegroundColor Cyan -NoNewline; Write-Host "" }
19
- function Write-Ok($msg) { Write-Host " ✓ $msg" -ForegroundColor Green }
20
- function Write-Warn($msg) { Write-Host " ⚠ $msg" -ForegroundColor Yellow }
21
- function Write-Fail($msg) { Write-Host " ✗ $msg" -ForegroundColor Red }
22
- function Write-Info($msg) { Write-Host " [INFO] $msg" -ForegroundColor Blue }
23
- function Stop-InstallTranscript() {
24
- if ($script:TranscriptStarted) {
25
- try { Stop-Transcript | Out-Null } catch {}
26
- $script:TranscriptStarted = $false
27
- }
28
- }
29
- function Pause-OnFailureIfInteractive() {
30
- if ($Host.Name -match 'ConsoleHost|Visual Studio Code Host') {
31
- try { Read-Host "Press Enter to exit" | Out-Null } catch {}
32
- }
33
- }
34
- function Abort-Install($msg) { throw $msg }
35
-
36
- try {
37
- Start-Transcript -Path $script:InstallLogPath -Force | Out-Null
38
- $script:TranscriptStarted = $true
39
- } catch {}
40
-
41
- trap {
42
- $message = if ($_.Exception) { $_.Exception.Message } else { $_.ToString() }
43
- Write-Fail "Installation failed: $message"
44
- Write-Host " Log saved to: $script:InstallLogPath" -ForegroundColor Yellow
45
- Pause-OnFailureIfInteractive
46
- Stop-InstallTranscript
47
- exit 1
48
- }
49
-
50
- function Confirm-Action($prompt) {
51
- if ($env:CI -eq 'true') {
52
- Write-Host "$prompt (Y/n): Y"
53
- return $true
54
- }
55
- $reply = Read-Host "$prompt (Y/n)"
56
- if ([string]::IsNullOrWhiteSpace($reply)) { $reply = "Y" }
57
- return $reply -match '^[Yy]'
58
- }
59
-
60
- function Resolve-NpmCommand() {
61
- if ($script:NpmCommandPath) {
62
- return $script:NpmCommandPath
63
- }
64
-
65
- $npmCommand = Get-Command npm.cmd -ErrorAction SilentlyContinue
66
- if (-not $npmCommand) {
67
- Abort-Install "npm.cmd not found. Check Node.js installation."
68
- }
69
-
70
- $script:NpmCommandPath = $npmCommand.Source
71
- return $script:NpmCommandPath
72
- }
73
-
74
- function Resolve-OptionalCliCommand($commandName) {
75
- $resolvedCommand = Get-Command $commandName -ErrorAction SilentlyContinue
76
- if ($resolvedCommand) {
77
- return $resolvedCommand.Source
78
- }
79
-
80
- $candidatePaths = @()
81
-
82
- if ($env:APPDATA) {
83
- $appDataNpmDir = Join-Path $env:APPDATA 'npm'
84
- $candidatePaths += (Join-Path $appDataNpmDir "$commandName.cmd")
85
- $candidatePaths += (Join-Path $appDataNpmDir $commandName)
86
- }
87
-
88
- foreach ($candidatePath in $candidatePaths) {
89
- if ([string]::IsNullOrWhiteSpace($candidatePath)) {
90
- continue
91
- }
92
-
93
- if (Test-Path $candidatePath) {
94
- return $candidatePath
95
- }
96
- }
97
-
98
- return $null
99
- }
100
-
101
- function Test-McpJsonConfigured($configPath) {
102
- if (-not (Test-Path $configPath)) {
103
- return $false
104
- }
105
-
106
- try {
107
- $config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
108
- return $null -ne $config.mcpServers.'weppy-roblox-mcp'
109
- }
110
- catch {
111
- return $false
112
- }
113
- }
114
-
115
- # Require an explicit `@<tag>` so the installer can upgrade legacy bare
116
- # entries (`@weppy/roblox-mcp`) — those reuse npx cache and trap users on
117
- # outdated versions. Tagged entries (`@latest`, `@2.6.4`, …) are preserved.
118
- function Test-WeppyPackageSpec($value) {
119
- if ([string]::IsNullOrWhiteSpace($value)) {
120
- return $false
121
- }
122
- return $value -match '^@weppy/roblox-mcp@.+$'
123
- }
124
-
125
- # Add the MCP server under the canonical `mcpServers` wrapper in the Antigravity
126
- # config and strip any legacy flat key left over from earlier versions.
127
- function Add-AntigravityMcpConfig($configPath) {
128
- $parentDir = Split-Path $configPath -Parent
129
- if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
130
- $env:MCP_CONFIG_PATH = $configPath
131
- try {
132
- node --input-type=commonjs -e @"
133
- const fs = require('fs');
134
- const configPath = process.env.MCP_CONFIG_PATH;
135
- let config = {};
136
- try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
137
- if (!config || typeof config !== 'object' || Array.isArray(config)) {
138
- config = {};
139
- }
140
- const mcpServers = config.mcpServers;
141
- if (mcpServers !== undefined && (typeof mcpServers !== 'object' || mcpServers === null || Array.isArray(mcpServers))) {
142
- throw new Error('Antigravity mcpServers must be an object');
143
- }
144
- const next = { ...config };
145
- delete next['weppy-roblox-mcp'];
146
- next.mcpServers = {
147
- ...(mcpServers || {}),
148
- 'weppy-roblox-mcp': { command: 'npx', args: ['-y', '@weppy/roblox-mcp@latest'] }
149
- };
150
- config = next;
151
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
152
- "@
153
- } finally {
154
- Remove-Item Env:\MCP_CONFIG_PATH -ErrorAction SilentlyContinue
155
- }
156
- }
157
-
158
- function Test-AntigravityMcpConfigured($configPath) {
159
- if (-not (Test-Path $configPath)) {
160
- return $false
161
- }
162
-
163
- try {
164
- $config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
165
- $hasLegacyFlatKey = $config.PSObject.Properties.Name -contains 'weppy-roblox-mcp'
166
- $server = $config.mcpServers.'weppy-roblox-mcp'
167
- # Accept args[1] as the weppy package regardless of whether a version tag is appended.
168
- $hasCanonicalArgs = ($server.args -is [System.Array]) -and ($server.args.Count -eq 2) -and ($server.args[0] -eq '-y') -and (Test-WeppyPackageSpec $server.args[1])
169
- return ($server.command -eq 'npx') -and $hasCanonicalArgs -and (-not $hasLegacyFlatKey)
170
- }
171
- catch {
172
- return $false
173
- }
174
- }
175
-
176
- function Test-CodexConfigConfigured($configPath) {
177
- if (-not (Test-Path $configPath)) {
178
- return $false
179
- }
180
-
181
- $env:MCP_CODEX_CONFIG_PATH = $configPath
182
- try {
183
- @'
184
- const fs = require('fs');
185
-
186
- const configPath = process.env.MCP_CODEX_CONFIG_PATH;
187
- const serverName = 'weppy-roblox-mcp';
188
- const expectedCommand = 'npx';
189
- // Require an explicit `@<tag>` so the installer can upgrade legacy bare entries.
190
- const packageSpecPattern = /^@weppy\/roblox-mcp@.+$/;
191
- const headerPattern = new RegExp(
192
- '^\\s*\\[\\s*mcp_servers\\.' + serverName.replace(/[.*+?^${}()|[\]\\\\]/g, '\\$&') + '\\s*\\]\\s*(?:#.*)?$'
193
- );
194
-
195
- function stripCommentOutsideStrings(line) {
196
- let inSingle = false;
197
- let inDouble = false;
198
- let escaped = false;
199
-
200
- for (let index = 0; index < line.length; index += 1) {
201
- const char = line[index];
202
-
203
- if (char === '"' && !inSingle && !escaped) {
204
- inDouble = !inDouble;
205
- } else if (char === "'" && !inDouble && !escaped) {
206
- inSingle = !inSingle;
207
- } else if (char === '#' && !inSingle && !inDouble) {
208
- return line.slice(0, index).trimEnd();
209
- }
210
-
211
- escaped = char === '\\' && !escaped;
212
- if (char !== '\\') {
213
- escaped = false;
214
- }
215
- }
216
-
217
- return line.trimEnd();
218
- }
219
-
220
- function countTripleQuoteToggles(line, quote) {
221
- let count = 0;
222
- let inSingle = false;
223
- let inDouble = false;
224
- let escaped = false;
225
-
226
- for (let index = 0; index < line.length; index += 1) {
227
- const char = line[index] ?? '';
228
- const nextThree = line.slice(index, index + 3);
229
- const isOutsideStrings = !inSingle && !inDouble;
230
-
231
- if (isOutsideStrings && nextThree === quote.repeat(3)) {
232
- count += 1;
233
- index += 2;
234
- escaped = false;
235
- continue;
236
- }
237
-
238
- if (char === '"' && !inSingle && !escaped) {
239
- inDouble = !inDouble;
240
- } else if (char === "'" && !inDouble && !escaped) {
241
- inSingle = !inSingle;
242
- } else if (char === '#' && !inSingle && !inDouble) {
243
- break;
244
- }
245
-
246
- escaped = char === '\\' && !escaped;
247
- if (char !== '\\') {
248
- escaped = false;
249
- }
250
- }
251
-
252
- return count;
253
- }
254
-
255
- function advanceTripleQuoteState(line, state) {
256
- const next = { ...state };
257
- const tripleDoubleCount = countTripleQuoteToggles(line, '"');
258
- const tripleSingleCount = countTripleQuoteToggles(line, "'");
259
-
260
- if (!next.inTripleSingle && tripleDoubleCount % 2 === 1) {
261
- next.inTripleDouble = !next.inTripleDouble;
262
- }
263
-
264
- if (!next.inTripleDouble && tripleSingleCount % 2 === 1) {
265
- next.inTripleSingle = !next.inTripleSingle;
266
- }
267
-
268
- return next;
269
- }
270
-
271
- function isTomlTableHeaderLine(line) {
272
- const normalized = stripCommentOutsideStrings(line).trim();
273
-
274
- if (normalized.length === 0) {
275
- return false;
276
- }
277
-
278
- return /^\[\[.*\]\]$/.test(normalized) || /^\[.*\]$/.test(normalized);
279
- }
280
-
281
- function findAllCodexBlocks(source) {
282
- const lines = source.split('\n');
283
- const blocks = [];
284
- let activeLines = null;
285
- let state = {
286
- inTripleDouble: false,
287
- inTripleSingle: false,
288
- };
289
-
290
- for (const line of lines) {
291
- const isHeaderCandidate = !state.inTripleDouble && !state.inTripleSingle && isTomlTableHeaderLine(line);
292
- const isCodexHeader = isHeaderCandidate && headerPattern.test(line);
293
-
294
- if (isCodexHeader) {
295
- if (activeLines !== null) {
296
- blocks.push(activeLines.join('\n').trim());
297
- }
298
- activeLines = [line];
299
- } else if (activeLines !== null && isHeaderCandidate) {
300
- blocks.push(activeLines.join('\n').trim());
301
- activeLines = null;
302
- } else if (activeLines !== null) {
303
- activeLines.push(line);
304
- }
305
-
306
- state = advanceTripleQuoteState(line, state);
307
- }
308
-
309
- if (activeLines !== null) {
310
- blocks.push(activeLines.join('\n').trim());
311
- }
312
-
313
- return blocks;
314
- }
315
-
316
- function parseStringAssignment(value, key) {
317
- const match = new RegExp('^\\s*' + key + '\\s*=\\s*(["\'])([^"\']+)\\1\\s*$').exec(value);
318
- return match ? match[2] : null;
319
- }
320
-
321
- function parseTomlStringArray(value) {
322
- const match = /^\s*args\s*=\s*\[(.*)\]\s*$/ms.exec(value.trim());
323
-
324
- if (match === null) {
325
- return null;
326
- }
327
-
328
- const body = match[1] ?? '';
329
- const values = [];
330
- let cursor = 0;
331
- let expectValue = true;
332
-
333
- while (cursor < body.length) {
334
- while (cursor < body.length && /\s/.test(body[cursor] ?? '')) {
335
- cursor += 1;
336
- }
337
-
338
- if (cursor >= body.length) {
339
- break;
340
- }
341
-
342
- if (!expectValue) {
343
- if (body[cursor] !== ',') {
344
- return null;
345
- }
346
- cursor += 1;
347
- expectValue = true;
348
- continue;
349
- }
350
-
351
- const quote = body[cursor];
352
- if (quote !== '"' && quote !== "'") {
353
- return null;
354
- }
355
-
356
- cursor += 1;
357
- let token = '';
358
- let escaped = false;
359
-
360
- while (cursor < body.length) {
361
- const char = body[cursor] ?? '';
362
-
363
- if (char === quote && !escaped) {
364
- cursor += 1;
365
- values.push(token);
366
- break;
367
- }
368
-
369
- token += char;
370
- escaped = char === '\\' && !escaped;
371
- if (char !== '\\') {
372
- escaped = false;
373
- }
374
- cursor += 1;
375
- }
376
-
377
- if (cursor > body.length) {
378
- return null;
379
- }
380
-
381
- expectValue = false;
382
- }
383
-
384
- const leftover = body.slice(cursor).trim();
385
- if (leftover === ',') {
386
- return values;
387
- }
388
-
389
- return leftover.length === 0 ? values : null;
390
- }
391
-
392
- function collectArrayLines(lines, startIndex) {
393
- const collected = [stripCommentOutsideStrings(lines[startIndex] ?? '')];
394
- let bracketDepth = 0;
395
- let inSingle = false;
396
- let inDouble = false;
397
- let escaped = false;
398
-
399
- for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex += 1) {
400
- const sanitized = stripCommentOutsideStrings(lines[lineIndex] ?? '');
401
- if (lineIndex !== startIndex) {
402
- collected.push(sanitized);
403
- }
404
-
405
- for (let index = 0; index < sanitized.length; index += 1) {
406
- const char = sanitized[index] ?? '';
407
-
408
- if (char === '"' && !inSingle && !escaped) {
409
- inDouble = !inDouble;
410
- } else if (char === "'" && !inDouble && !escaped) {
411
- inSingle = !inSingle;
412
- } else if (!inSingle && !inDouble) {
413
- if (char === '[') {
414
- bracketDepth += 1;
415
- } else if (char === ']') {
416
- bracketDepth -= 1;
417
- }
418
- }
419
-
420
- escaped = char === '\\' && !escaped;
421
- if (char !== '\\') {
422
- escaped = false;
423
- }
424
- }
425
-
426
- if (bracketDepth <= 0) {
427
- return {
428
- nextIndex: lineIndex,
429
- text: collected.join('\n'),
430
- };
431
- }
432
- }
433
-
434
- return null;
435
- }
436
-
437
- function parseCodexBlock(blockContent) {
438
- const lines = blockContent.split('\n');
439
- let command = null;
440
- let args = null;
441
- let hasConflict = false;
442
- let inTripleDouble = false;
443
- let inTripleSingle = false;
444
-
445
- for (let index = 1; index < lines.length; index += 1) {
446
- const line = lines[index] ?? '';
447
- const sanitized = stripCommentOutsideStrings(line);
448
- const trimmed = sanitized.trim();
449
-
450
- if (inTripleDouble) {
451
- if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
452
- inTripleDouble = false;
453
- }
454
- continue;
455
- }
456
-
457
- if (inTripleSingle) {
458
- if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
459
- inTripleSingle = false;
460
- }
461
- continue;
462
- }
463
-
464
- if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
465
- inTripleDouble = true;
466
- continue;
467
- }
468
-
469
- if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
470
- inTripleSingle = true;
471
- continue;
472
- }
473
-
474
- if (trimmed.length === 0) {
475
- continue;
476
- }
477
-
478
- if (/^command\s*=/.test(trimmed)) {
479
- const parsedCommand = parseStringAssignment(trimmed, 'command');
480
- if (command !== null || parsedCommand === null) {
481
- hasConflict = true;
482
- } else {
483
- command = parsedCommand;
484
- }
485
- continue;
486
- }
487
-
488
- if (/^args\s*=/.test(trimmed)) {
489
- const collected = collectArrayLines(lines, index);
490
- const parsedArgs = collected === null ? null : parseTomlStringArray(collected.text);
491
-
492
- if (args !== null || parsedArgs === null || collected === null) {
493
- hasConflict = true;
494
- } else {
495
- args = parsedArgs;
496
- index = collected.nextIndex;
497
- }
498
- }
499
- }
500
-
501
- return {
502
- args,
503
- command,
504
- hasConflict,
505
- };
506
- }
507
-
508
- function isStructurallySafe(source) {
509
- let bracketDepth = 0;
510
- let braceDepth = 0;
511
- let inSingle = false;
512
- let inDouble = false;
513
- let escaped = false;
514
- let tripleState = {
515
- inTripleDouble: false,
516
- inTripleSingle: false,
517
- };
518
-
519
- for (const line of source.split('\n')) {
520
- tripleState = advanceTripleQuoteState(line, tripleState);
521
-
522
- for (let index = 0; index < line.length; index += 1) {
523
- const char = line[index] ?? '';
524
-
525
- if (!inSingle && !inDouble && char === '#') {
526
- break;
527
- }
528
-
529
- if (char === '"' && !inSingle && !escaped) {
530
- inDouble = !inDouble;
531
- } else if (char === "'" && !inDouble && !escaped) {
532
- inSingle = !inSingle;
533
- } else if (!inSingle && !inDouble) {
534
- if (char === '[') {
535
- bracketDepth += 1;
536
- } else if (char === ']') {
537
- bracketDepth -= 1;
538
- if (bracketDepth < 0) {
539
- return false;
540
- }
541
- } else if (char === '{') {
542
- braceDepth += 1;
543
- } else if (char === '}') {
544
- braceDepth -= 1;
545
- if (braceDepth < 0) {
546
- return false;
547
- }
548
- }
549
- }
550
-
551
- escaped = char === '\\' && !escaped;
552
- if (char !== '\\') {
553
- escaped = false;
554
- }
555
- }
556
- }
557
-
558
- return (
559
- !tripleState.inTripleDouble &&
560
- !tripleState.inTripleSingle &&
561
- bracketDepth === 0 &&
562
- braceDepth === 0 &&
563
- !inSingle &&
564
- !inDouble
565
- );
566
- }
567
-
568
- try {
569
- const source = fs.readFileSync(configPath, 'utf8');
570
- if (!isStructurallySafe(source)) {
571
- process.exit(1);
572
- }
573
-
574
- const blocks = findAllCodexBlocks(source);
575
- if (blocks.length !== 1) {
576
- process.exit(1);
577
- }
578
-
579
- const parsed = parseCodexBlock(blocks[0]);
580
- const isConfigured =
581
- !parsed.hasConflict &&
582
- parsed.command === expectedCommand &&
583
- Array.isArray(parsed.args) &&
584
- parsed.args.length === 2 &&
585
- parsed.args[0] === '-y' &&
586
- typeof parsed.args[1] === 'string' &&
587
- packageSpecPattern.test(parsed.args[1]);
588
-
589
- process.exit(isConfigured ? 0 : 1);
590
- } catch {
591
- process.exit(1);
592
- }
593
- '@ | node --input-type=commonjs
594
- return $LASTEXITCODE -eq 0
595
- }
596
- finally {
597
- Remove-Item Env:\MCP_CODEX_CONFIG_PATH -ErrorAction SilentlyContinue
598
- }
599
- }
600
-
601
- # Add MCP server to JSON config file (PowerShell 5.1 compatible — edits JSON via node)
602
- function Add-McpToConfig($configPath) {
603
- $parentDir = Split-Path $configPath -Parent
604
- if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
605
- $env:MCP_CONFIG_PATH = $configPath
606
- try {
607
- node --input-type=commonjs -e @"
608
- const fs = require('fs');
609
- const configPath = process.env.MCP_CONFIG_PATH;
610
- let config = {};
611
- try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
612
- if (!config.mcpServers) config.mcpServers = {};
613
- config.mcpServers['weppy-roblox-mcp'] = { command: 'npx', args: ['-y', '@weppy/roblox-mcp@latest'] };
614
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
615
- "@
616
- } finally {
617
- Remove-Item Env:\MCP_CONFIG_PATH -ErrorAction SilentlyContinue
618
- }
619
- }
620
-
621
- # ── Header ──
622
- Write-Host ""
623
- Write-Host "WEPPY Installer" -ForegroundColor White -BackgroundColor DarkCyan
624
- Write-Host "AI-powered Roblox Studio integration" -ForegroundColor DarkGray
625
- Write-Host ("=" * 40)
626
-
627
- # ── Node.js check ──
628
- try {
629
- $nodeVersion = (node -v) -replace 'v', ''
630
- $majorVersion = [int]($nodeVersion.Split('.')[0])
631
- if ($majorVersion -lt 18) {
632
- Abort-Install "Node.js 18 or higher required (current: v$nodeVersion). Upgrade: https://nodejs.org"
633
- }
634
- Write-Ok "Node.js v$nodeVersion detected"
635
- }
636
- catch {
637
- Abort-Install "Node.js is not installed. Install Node.js 18+: https://nodejs.org"
638
- }
639
-
640
- # ═══════════════════════════════════
641
- # [1/2] Setup — Roblox Studio Plugin
642
- # ═══════════════════════════════════
643
- Write-Step "1/2" "Setup Roblox Studio Plugin"
644
-
645
- if (Confirm-Action " Run npx -y @weppy/roblox-mcp@latest --setup?") {
646
- try {
647
- $npmCommandPath = Resolve-NpmCommand
648
- $npmDir = Split-Path $npmCommandPath -Parent
649
- $npxPath = Join-Path $npmDir "npx.cmd"
650
- if (-not (Test-Path $npxPath)) {
651
- $npxPath = "npx"
652
- }
653
- $setupWorkingDir = Join-Path ([System.IO.Path]::GetTempPath()) ("weppy-setup-" + [System.Guid]::NewGuid().ToString("N"))
654
- $previousLocation = Get-Location
655
-
656
- try {
657
- New-Item -ItemType Directory -Path $setupWorkingDir -Force | Out-Null
658
- Set-Location $setupWorkingDir
659
- # Isolate stdin with an empty pipe so the stdio MCP server does not inherit
660
- # the interactive pwsh terminal stdin (which would cause it to hang under irm|iex).
661
- # The @latest tag forces npx to resolve from the registry instead of
662
- # reusing an older version pinned in the npm cache.
663
- $null | & $npxPath -y "@weppy/roblox-mcp@latest" --setup
664
- if ($LASTEXITCODE -ne 0) {
665
- Write-Warn "Setup encountered a warning (non-blocking)"
666
- } else {
667
- Write-Ok "Setup complete"
668
- }
669
- }
670
- finally {
671
- Set-Location $previousLocation
672
- Remove-Item $setupWorkingDir -Recurse -Force -ErrorAction SilentlyContinue
673
- }
674
- }
675
- catch {
676
- Write-Warn "Setup encountered a warning: $_"
677
- }
678
- }
679
- else {
680
- Write-Warn "Setup skipped"
681
- }
682
-
683
- # ═══════════════════════════════════
684
- # [2/2] Register MCP with AI apps
685
- # ═══════════════════════════════════
686
- Write-Step "2/2" "Register MCP with AI apps"
687
- Write-Host " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity"
688
-
689
- $detectedNames = @()
690
- $detectedTypes = @()
691
- $notDetected = @()
692
-
693
- $claudeProjectConfig = Join-Path (Get-Location).Path '.mcp.json'
694
- $claudeGlobalConfig = Join-Path $env:USERPROFILE '.claude\mcp.json'
695
- $claudeCodeCliCommand = Resolve-OptionalCliCommand 'claude'
696
-
697
- # `claude mcp add` stores entries under ~/.claude.json or in local/user scope,
698
- # so prefer `claude mcp list` as the source of truth when the CLI is available
699
- # (the JSON path checks remain as a fallback). The entry counts as configured
700
- # only when its args carry an explicit `@<tag>` — legacy bare entries fall
701
- # through and get re-registered with the canonical `@latest` form.
702
- function Test-ClaudeCliConfigured($cliCommand) {
703
- if (-not $cliCommand) { return $false }
704
- try {
705
- $listOutput = & $cliCommand mcp list 2>$null
706
- if ($LASTEXITCODE -ne 0) { return $false }
707
- $line = $listOutput | Select-String -Pattern '^weppy-roblox-mcp:' | Select-Object -First 1
708
- if (-not $line) { return $false }
709
- return ($line.Line -match '@weppy/roblox-mcp@')
710
- } catch {
711
- return $false
712
- }
713
- }
714
-
715
- $claudeCodeConfigured = (Test-ClaudeCliConfigured $claudeCodeCliCommand) `
716
- -or (Test-McpJsonConfigured $claudeProjectConfig) `
717
- -or (Test-McpJsonConfigured $claudeGlobalConfig)
718
-
719
- if ($claudeCodeConfigured) {
720
- $detectedNames += 'Claude Code (configured)'
721
- $detectedTypes += 'claude-code'
722
- }
723
- elseif ($claudeCodeCliCommand) {
724
- $detectedNames += 'Claude Code (CLI)'
725
- $detectedTypes += 'claude-code'
726
- }
727
- else {
728
- $notDetected += 'Claude Code (not found)'
729
- }
730
-
731
- # Claude Desktop
732
- $claudeDesktopConfig = Join-Path $env:APPDATA "Claude\claude_desktop_config.json"
733
- if (Test-Path $claudeDesktopConfig) {
734
- $detectedNames += "Claude Desktop"
735
- $detectedTypes += "claude-desktop"
736
- }
737
- else {
738
- $notDetected += "Claude Desktop (config not found)"
739
- }
740
-
741
- # Cursor (detect only when mcp.json or binary exists)
742
- $cursorDir = Join-Path $env:USERPROFILE ".cursor"
743
- if ((Test-Path (Join-Path $cursorDir "mcp.json")) -or (Get-Command cursor -ErrorAction SilentlyContinue)) {
744
- $detectedNames += "Cursor"
745
- $detectedTypes += "cursor"
746
- }
747
- else {
748
- $notDetected += "Cursor (not found)"
749
- }
750
-
751
- $codexConfig = Join-Path $env:USERPROFILE '.codex\config.toml'
752
- $codexConfigured = Test-CodexConfigConfigured $codexConfig
753
- $codexCliCommand = Resolve-OptionalCliCommand 'codex'
754
-
755
- if ($codexConfigured) {
756
- $detectedNames += 'Codex CLI/App (configured)'
757
- $detectedTypes += 'codex-cli'
758
- }
759
- elseif ($codexCliCommand) {
760
- $detectedNames += 'Codex CLI/App'
761
- $detectedTypes += 'codex-cli'
762
- }
763
- else {
764
- $notDetected += 'Codex CLI/App (not found)'
765
- }
766
-
767
- # Gemini CLI
768
- $geminiConfig = Join-Path $env:USERPROFILE '.gemini\settings.json'
769
- $geminiConfigured = Test-McpJsonConfigured $geminiConfig
770
- $geminiCliCommand = Resolve-OptionalCliCommand 'gemini'
771
-
772
- if ($geminiConfigured) {
773
- $detectedNames += 'Gemini CLI (configured)'
774
- $detectedTypes += "gemini-cli"
775
- }
776
- elseif ($geminiCliCommand) {
777
- $detectedNames += "Gemini CLI"
778
- $detectedTypes += "gemini-cli"
779
- }
780
- else {
781
- $notDetected += "Gemini CLI (not found)"
782
- }
783
-
784
- # Antigravity (unofficial path, auto-register if found)
785
- $antigravityConfig = Join-Path $env:USERPROFILE '.gemini\antigravity\mcp_config.json'
786
- $antigravityConfigured = Test-AntigravityMcpConfigured $antigravityConfig
787
- $antigravityDirExists = Test-Path (Join-Path $env:USERPROFILE '.gemini\antigravity')
788
-
789
- if ($antigravityConfigured) {
790
- $detectedNames += 'Antigravity (configured)'
791
- $detectedTypes += 'antigravity'
792
- }
793
- elseif ((Test-Path $antigravityConfig) -or $antigravityDirExists) {
794
- $detectedNames += 'Antigravity'
795
- $detectedTypes += 'antigravity'
796
- }
797
- else {
798
- $notDetected += 'Antigravity (not found)'
799
- }
800
-
801
- if ($detectedNames.Count -eq 0) {
802
- Write-Warn "No AI apps detected"
803
- Write-Info "Register MCP server manually: npx -y @weppy/roblox-mcp@latest"
804
- }
805
- else {
806
- Write-Host ""
807
- Write-Host " Detected:" -ForegroundColor White
808
- for ($i = 0; $i -lt $detectedNames.Count; $i++) {
809
- Write-Host " $($i + 1). $($detectedNames[$i])" -ForegroundColor Green
810
- }
811
-
812
- if ($notDetected.Count -gt 0) {
813
- Write-Host ""
814
- Write-Host " Not detected:" -ForegroundColor DarkGray
815
- foreach ($item in $notDetected) {
816
- Write-Host " - $item" -ForegroundColor DarkGray
817
- }
818
- }
819
-
820
- Write-Host ""
821
- if ($env:CI -eq 'true') {
822
- Write-Host " Select apps to register (comma-separated, 'a' for all, 'n' to skip): a"
823
- $selection = 'a'
824
- } else {
825
- $selection = Read-Host " Select apps to register (comma-separated, 'a' for all, 'n' to skip)"
826
- }
827
- if ([string]::IsNullOrWhiteSpace($selection)) { $selection = "n" }
828
-
829
- $selectedIndices = @()
830
- switch -Regex ($selection) {
831
- '^[Nn]$' { Write-Warn "MCP registration skipped" }
832
- '^[Aa]$' { $selectedIndices = 0..($detectedNames.Count - 1) }
833
- default {
834
- foreach ($part in ($selection -split ',')) {
835
- $part = $part.Trim()
836
- if ($part -match '^\d+$') {
837
- $idx = [int]$part - 1
838
- if ($idx -ge 0 -and $idx -lt $detectedNames.Count) {
839
- $selectedIndices += $idx
840
- }
841
- }
842
- }
843
- }
844
- }
845
-
846
- foreach ($idx in $selectedIndices) {
847
- $appType = $detectedTypes[$idx]
848
- $appName = $detectedNames[$idx]
849
-
850
- try {
851
- switch ($appType) {
852
- "claude-code" {
853
- if ($claudeCodeConfigured) {
854
- Write-Ok "Already configured: $appName"
855
- }
856
- elseif ($claudeCodeCliCommand) {
857
- $claudeStderrFile = Join-Path ([System.IO.Path]::GetTempPath()) ("weppy-claude-{0}.err" -f ([System.Guid]::NewGuid().ToString("N")))
858
- try {
859
- # Best-effort remove any legacy bare entry so the subsequent add can
860
- # install the canonical `@latest` form.
861
- try { & $claudeCodeCliCommand mcp remove weppy-roblox-mcp *> $null } catch {}
862
- & $claudeCodeCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest" 2> $claudeStderrFile
863
- $claudeExit = $LASTEXITCODE
864
- if ($claudeExit -eq 0) {
865
- Write-Ok "Registered: $appName"
866
- }
867
- else {
868
- $stderrContent = if (Test-Path $claudeStderrFile) { Get-Content $claudeStderrFile -Raw } else { '' }
869
- if ($stderrContent -match '(?i)already exists') {
870
- # Already registered in another scope — not a failure
871
- Write-Ok "Already configured: $appName"
872
- }
873
- else {
874
- Write-Fail "Failed: $appName (exit=$claudeExit)"
875
- Write-Host " CLI: $claudeCodeCliCommand" -ForegroundColor DarkGray
876
- if ($stderrContent) {
877
- Write-Host " stderr:" -ForegroundColor DarkGray
878
- $stderrContent.TrimEnd("`r","`n").Split("`n") | ForEach-Object {
879
- Write-Host " $($_.TrimEnd())" -ForegroundColor DarkGray
880
- }
881
- }
882
- # Fall back to writing the JSON config directly when the CLI fails for other reasons
883
- Add-McpToConfig $claudeGlobalConfig
884
- Write-Ok "Registered via fallback JSON: $appName"
885
- }
886
- }
887
- }
888
- finally {
889
- Remove-Item $claudeStderrFile -ErrorAction SilentlyContinue
890
- }
891
- }
892
- else {
893
- Write-Fail "Failed: $appName (CLI/config unavailable)"
894
- }
895
- }
896
- "claude-desktop" {
897
- Add-McpToConfig $claudeDesktopConfig
898
- Write-Ok "Registered: $appName"
899
- }
900
- "cursor" {
901
- $cursorConfig = Join-Path $env:USERPROFILE ".cursor\mcp.json"
902
- Add-McpToConfig $cursorConfig
903
- Write-Ok "Registered: $appName"
904
- }
905
- "codex-cli" {
906
- if ($codexConfigured) {
907
- Write-Ok "Already configured: $appName"
908
- }
909
- elseif ($codexCliCommand) {
910
- try { & $codexCliCommand mcp remove weppy-roblox-mcp *> $null } catch {}
911
- & $codexCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest"
912
- if ($LASTEXITCODE -ne 0) {
913
- throw 'codex mcp add failed'
914
- }
915
- Write-Ok "Registered: $appName"
916
- }
917
- else {
918
- Write-Fail "Failed: $appName (CLI/config unavailable)"
919
- }
920
- }
921
- "gemini-cli" {
922
- if ($geminiConfigured) {
923
- Write-Ok "Already configured: $appName"
924
- }
925
- else {
926
- Add-McpToConfig $geminiConfig
927
- Write-Ok "Registered: $appName"
928
- }
929
- }
930
- "antigravity" {
931
- if ($antigravityConfigured) {
932
- Write-Ok "Already configured: $appName"
933
- }
934
- else {
935
- Add-AntigravityMcpConfig $antigravityConfig
936
- Write-Ok "Registered: $appName"
937
- }
938
- }
939
- }
940
- }
941
- catch {
942
- Write-Fail "Failed: $appName ($_)"
943
- }
944
- }
945
- }
946
-
947
- # ═══════════════════════════════════
948
- # Installation summary
949
- # ═══════════════════════════════════
950
- Write-Host ""
951
- Write-Host ("=" * 40)
952
- Write-Host "Installation complete!" -ForegroundColor Green
953
- Write-Host ""
954
- Write-Host " Next steps:"
955
- Write-Host " 1. Restart Roblox Studio"
956
- Write-Host " 2. Look for the WEPPY button in the Plugins tab"
957
- Write-Host " 3. Click Connect and start building with AI!"
958
- Write-Host ""
959
- Write-Host " Auto registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity"
960
- Write-Host ""
961
- Write-Host " Docs: https://weppyai.com/en/install" -ForegroundColor DarkGray
962
- Write-Host ""
963
-
964
- Stop-InstallTranscript