@weppy/roblox-mcp 2.2.2 → 2.3.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.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.github/workflows/install-test.yml +108 -0
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +14 -10
  5. package/docs/en/installation/README.md +4 -4
  6. package/docs/en/installation/roblox-explorer.md +13 -3
  7. package/docs/en/installation/roblox-plugin.md +42 -31
  8. package/docs/en/pro-upgrade.md +26 -12
  9. package/docs/en/sync/luau-lsp.md +41 -0
  10. package/docs/en/sync/overview.md +4 -1
  11. package/docs/es/README.md +13 -7
  12. package/docs/es/installation/README.md +4 -4
  13. package/docs/es/pro-upgrade.md +26 -12
  14. package/docs/es/sync/luau-lsp.md +41 -0
  15. package/docs/es/sync/overview.md +4 -1
  16. package/docs/id/README.md +12 -6
  17. package/docs/id/installation/README.md +4 -4
  18. package/docs/id/pro-upgrade.md +26 -12
  19. package/docs/id/sync/luau-lsp.md +41 -0
  20. package/docs/id/sync/overview.md +4 -1
  21. package/docs/installer/assets/index-Bz0amd7x.js +63 -0
  22. package/docs/installer/assets/index-ei4lRUa6.css +1 -0
  23. package/docs/installer/index.html +14 -0
  24. package/docs/installer/manifest.webmanifest +15 -0
  25. package/docs/installer/sw.js +7 -0
  26. package/docs/installer/wrox-icon.png +0 -0
  27. package/docs/ja/README.md +14 -10
  28. package/docs/ja/installation/README.md +3 -3
  29. package/docs/ja/pro-upgrade.md +26 -12
  30. package/docs/ja/sync/luau-lsp.md +41 -0
  31. package/docs/ja/sync/overview.md +4 -1
  32. package/docs/ko/README.md +14 -10
  33. package/docs/ko/installation/README.md +4 -4
  34. package/docs/ko/installation/roblox-explorer.md +13 -3
  35. package/docs/ko/installation/roblox-plugin.md +42 -31
  36. package/docs/ko/pro-upgrade.md +26 -12
  37. package/docs/ko/sync/luau-lsp.md +41 -0
  38. package/docs/ko/sync/overview.md +4 -1
  39. package/docs/pt-br/README.md +13 -7
  40. package/docs/pt-br/installation/README.md +4 -4
  41. package/docs/pt-br/pro-upgrade.md +26 -12
  42. package/docs/pt-br/sync/luau-lsp.md +41 -0
  43. package/docs/pt-br/sync/overview.md +4 -1
  44. package/docs/troubleshooting.md +1 -1
  45. package/install.ps1 +468 -98
  46. package/install.sh +461 -71
  47. package/llms-full.txt +14 -2
  48. package/llms.txt +10 -4
  49. package/package.json +1 -1
  50. package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
  51. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-D7eMrarv.js → ChangelogDetailPage-ITTDURna.js} +1 -1
  52. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogPage-DFCCRyyK.js → ChangelogPage-DjVot-60.js} +1 -1
  53. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-BmRJ2JXZ.js → ConfirmModal-B1q8BGeA.js} +1 -1
  54. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-9bG71eB1.css +1 -0
  55. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-D4y36l03.js +1 -0
  56. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-CCDWZLC9.js → InfoLabel-COMRAIq0.js} +1 -1
  57. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-BHpt3LI2.js → OverviewPage-DojgIxVT.js} +1 -1
  58. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PlaytestPage-CNwwI5Ro.js → PlaytestPage-CkEvf6XW.js} +1 -1
  59. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PropertyDiff-r9iLxfi8.js +6 -0
  60. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SettingsPage-CPqQYZPN.js → SettingsPage-CEs6Ob3d.js} +1 -1
  61. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-C8VKAPpk.js → StatusBadge-DHhSWmAT.js} +1 -1
  62. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-ifjnfsNg.js +4 -0
  63. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierComparison-7ofkPwj3.js → TierComparison-C29caZ6C.js} +1 -1
  64. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierPromoProgress-SnRUjAPh.js → TierPromoProgress-BdEtTxkK.js} +1 -1
  65. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ToolsPage-CrdNh3D9.js → ToolsPage-BOIC0ngW.js} +1 -1
  66. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-CQYn3Wfp.js +129 -0
  67. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-BnXeLpOw.js → useLiveUptime-Dco8Aiiz.js} +1 -1
  68. package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +1 -1
  69. package/plugins/weppy-roblox-mcp/dist/index.js +86 -83
  70. package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
  71. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-CN3LYLAT.css +0 -1
  72. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-CiaCY026.js +0 -1
  73. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PropertyDiff-DIplDn-J.js +0 -6
  74. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-DTSKbpio.js +0 -4
  75. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-DGGmfli1.js +0 -129
package/install.sh CHANGED
@@ -5,10 +5,9 @@
5
5
  # Usage:
6
6
  # curl -fsSL https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.sh | bash
7
7
  #
8
- # Interactive 3 steps:
9
- # [1/3] MCP server install (npm)
10
- # [2/3] Roblox Studio Plugin install (.rbxm)
11
- # [3/3] Register MCP with AI apps (user selection)
8
+ # Interactive 2 steps:
9
+ # [1/2] Setup install Roblox Studio Plugin via npx
10
+ # [2/2] Register MCP with AI apps (user selection)
12
11
  #
13
12
 
14
13
  set -euo pipefail
@@ -74,6 +73,10 @@ trap 'handle_install_error "${LINENO}" "$BASH_COMMAND"' ERR
74
73
  confirm() {
75
74
  local prompt="$1"
76
75
  local reply
76
+ if [ "${CI:-}" = "true" ]; then
77
+ printf "%s (Y/n): Y\n" "$prompt"
78
+ return 0
79
+ fi
77
80
  printf "%s (Y/n): " "$prompt"
78
81
  read -r reply </dev/tty
79
82
  reply="${reply:-Y}"
@@ -117,7 +120,7 @@ try {
117
120
  ' >/dev/null 2>&1
118
121
  }
119
122
 
120
- # Add MCP server to Antigravity config (flat top-level keys, no mcpServers wrapper)
123
+ # Antigravity 설정에 canonical mcpServers 래퍼로 MCP 서버를 추가하고 legacy flat key를 정리
121
124
  add_antigravity_mcp_config() {
122
125
  local config_path="$1"
123
126
  local parent_dir
@@ -128,7 +131,20 @@ const fs = require("fs");
128
131
  const configPath = process.env.MCP_CONFIG_PATH;
129
132
  let config = {};
130
133
  try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
131
- config["weppy-roblox-mcp"] = { command: "npx", args: ["-y", "@weppy/roblox-mcp"] };
134
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
135
+ config = {};
136
+ }
137
+ const mcpServers = config.mcpServers;
138
+ if (mcpServers !== undefined && (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers))) {
139
+ throw new Error("Antigravity mcpServers must be an object");
140
+ }
141
+ const next = { ...config };
142
+ delete next["weppy-roblox-mcp"];
143
+ next.mcpServers = {
144
+ ...(mcpServers || {}),
145
+ "weppy-roblox-mcp": { command: "npx", args: ["-y", "@weppy/roblox-mcp"] }
146
+ };
147
+ config = next;
132
148
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
133
149
  '
134
150
  }
@@ -141,9 +157,24 @@ is_antigravity_mcp_configured() {
141
157
  MCP_CONFIG_PATH="$config_path" node -e '
142
158
  const fs = require("fs");
143
159
  const configPath = process.env.MCP_CONFIG_PATH;
160
+ function isJsonObject(value) {
161
+ return typeof value === "object" && value !== null && !Array.isArray(value);
162
+ }
163
+ function hasExpectedCommandShape(value) {
164
+ return (
165
+ isJsonObject(value) &&
166
+ value.command === "npx" &&
167
+ Array.isArray(value.args) &&
168
+ value.args.length === 2 &&
169
+ value.args[0] === "-y" &&
170
+ value.args[1] === "@weppy/roblox-mcp"
171
+ );
172
+ }
144
173
  try {
145
174
  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
146
- process.exit(config?.["weppy-roblox-mcp"] ? 0 : 1);
175
+ const canonical = config?.mcpServers?.["weppy-roblox-mcp"];
176
+ const hasLegacyFlatKey = Object.prototype.hasOwnProperty.call(config, "weppy-roblox-mcp");
177
+ process.exit(hasExpectedCommandShape(canonical) && !hasLegacyFlatKey ? 0 : 1);
147
178
  } catch {
148
179
  process.exit(1);
149
180
  }
@@ -154,7 +185,414 @@ is_codex_config_configured() {
154
185
  local config_path="$1"
155
186
 
156
187
  [ -f "$config_path" ] || return 1
157
- grep -Eq '^[[:space:]]*\[mcp_servers\.weppy-roblox-mcp\][[:space:]]*$' "$config_path"
188
+ MCP_CODEX_CONFIG_PATH="$config_path" node <<'NODE' >/dev/null 2>&1
189
+ const fs = require("fs");
190
+
191
+ const configPath = process.env.MCP_CODEX_CONFIG_PATH;
192
+ const serverName = "weppy-roblox-mcp";
193
+ const expectedCommand = "npx";
194
+ const expectedArgs = ["-y", "@weppy/roblox-mcp"];
195
+ const headerPattern = new RegExp(
196
+ "^\\s*\\[\\s*mcp_servers\\." + serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*\\]\\s*(?:#.*)?$",
197
+ );
198
+
199
+ function stripCommentOutsideStrings(line) {
200
+ let inSingle = false;
201
+ let inDouble = false;
202
+ let escaped = false;
203
+
204
+ for (let index = 0; index < line.length; index += 1) {
205
+ const char = line[index];
206
+
207
+ if (char === '"' && !inSingle && !escaped) {
208
+ inDouble = !inDouble;
209
+ } else if (char === "'" && !inDouble && !escaped) {
210
+ inSingle = !inSingle;
211
+ } else if (char === "#" && !inSingle && !inDouble) {
212
+ return line.slice(0, index).trimEnd();
213
+ }
214
+
215
+ escaped = char === "\\" && !escaped;
216
+ if (char !== "\\") {
217
+ escaped = false;
218
+ }
219
+ }
220
+
221
+ return line.trimEnd();
222
+ }
223
+
224
+ function countTripleQuoteToggles(line, quote) {
225
+ let count = 0;
226
+ let inSingle = false;
227
+ let inDouble = false;
228
+ let escaped = false;
229
+
230
+ for (let index = 0; index < line.length; index += 1) {
231
+ const char = line[index] ?? "";
232
+ const nextThree = line.slice(index, index + 3);
233
+ const isOutsideStrings = !inSingle && !inDouble;
234
+
235
+ if (isOutsideStrings && nextThree === quote.repeat(3)) {
236
+ count += 1;
237
+ index += 2;
238
+ escaped = false;
239
+ continue;
240
+ }
241
+
242
+ if (char === '"' && !inSingle && !escaped) {
243
+ inDouble = !inDouble;
244
+ } else if (char === "'" && !inDouble && !escaped) {
245
+ inSingle = !inSingle;
246
+ } else if (char === "#" && !inSingle && !inDouble) {
247
+ break;
248
+ }
249
+
250
+ escaped = char === "\\" && !escaped;
251
+ if (char !== "\\") {
252
+ escaped = false;
253
+ }
254
+ }
255
+
256
+ return count;
257
+ }
258
+
259
+ function advanceTripleQuoteState(line, state) {
260
+ const next = { ...state };
261
+ const tripleDoubleCount = countTripleQuoteToggles(line, '"');
262
+ const tripleSingleCount = countTripleQuoteToggles(line, "'");
263
+
264
+ if (!next.inTripleSingle && tripleDoubleCount % 2 === 1) {
265
+ next.inTripleDouble = !next.inTripleDouble;
266
+ }
267
+
268
+ if (!next.inTripleDouble && tripleSingleCount % 2 === 1) {
269
+ next.inTripleSingle = !next.inTripleSingle;
270
+ }
271
+
272
+ return next;
273
+ }
274
+
275
+ function isTomlTableHeaderLine(line) {
276
+ const normalized = stripCommentOutsideStrings(line).trim();
277
+
278
+ if (normalized.length === 0) {
279
+ return false;
280
+ }
281
+
282
+ return /^\[\[.*\]\]$/.test(normalized) || /^\[.*\]$/.test(normalized);
283
+ }
284
+
285
+ function findAllCodexBlocks(source) {
286
+ const lines = source.split("\n");
287
+ const blocks = [];
288
+ let activeLines = null;
289
+ let state = {
290
+ inTripleDouble: false,
291
+ inTripleSingle: false,
292
+ };
293
+
294
+ for (const line of lines) {
295
+ const isHeaderCandidate = !state.inTripleDouble && !state.inTripleSingle && isTomlTableHeaderLine(line);
296
+ const isCodexHeader = isHeaderCandidate && headerPattern.test(line);
297
+
298
+ if (isCodexHeader) {
299
+ if (activeLines !== null) {
300
+ blocks.push(activeLines.join("\n").trim());
301
+ }
302
+ activeLines = [line];
303
+ } else if (activeLines !== null && isHeaderCandidate) {
304
+ blocks.push(activeLines.join("\n").trim());
305
+ activeLines = null;
306
+ } else if (activeLines !== null) {
307
+ activeLines.push(line);
308
+ }
309
+
310
+ state = advanceTripleQuoteState(line, state);
311
+ }
312
+
313
+ if (activeLines !== null) {
314
+ blocks.push(activeLines.join("\n").trim());
315
+ }
316
+
317
+ return blocks;
318
+ }
319
+
320
+ function parseStringAssignment(value, key) {
321
+ const match = new RegExp("^\\s*" + key + "\\s*=\\s*([\"'])([^\"']+)\\1\\s*$").exec(value);
322
+ return match ? match[2] : null;
323
+ }
324
+
325
+ function parseTomlStringArray(value) {
326
+ const match = /^\s*args\s*=\s*\[(.*)\]\s*$/ms.exec(value.trim());
327
+
328
+ if (match === null) {
329
+ return null;
330
+ }
331
+
332
+ const body = match[1] ?? "";
333
+ const values = [];
334
+ let cursor = 0;
335
+ let expectValue = true;
336
+
337
+ while (cursor < body.length) {
338
+ while (cursor < body.length && /\s/.test(body[cursor] ?? "")) {
339
+ cursor += 1;
340
+ }
341
+
342
+ if (cursor >= body.length) {
343
+ break;
344
+ }
345
+
346
+ if (!expectValue) {
347
+ if (body[cursor] !== ",") {
348
+ return null;
349
+ }
350
+ cursor += 1;
351
+ expectValue = true;
352
+ continue;
353
+ }
354
+
355
+ const quote = body[cursor];
356
+ if (quote !== '"' && quote !== "'") {
357
+ return null;
358
+ }
359
+
360
+ cursor += 1;
361
+ let token = "";
362
+ let escaped = false;
363
+
364
+ while (cursor < body.length) {
365
+ const char = body[cursor] ?? "";
366
+
367
+ if (char === quote && !escaped) {
368
+ cursor += 1;
369
+ values.push(token);
370
+ break;
371
+ }
372
+
373
+ token += char;
374
+ escaped = char === "\\" && !escaped;
375
+ if (char !== "\\") {
376
+ escaped = false;
377
+ }
378
+ cursor += 1;
379
+ }
380
+
381
+ if (cursor > body.length) {
382
+ return null;
383
+ }
384
+
385
+ expectValue = false;
386
+ }
387
+
388
+ const leftover = body.slice(cursor).trim();
389
+ if (leftover === ",") {
390
+ return values;
391
+ }
392
+
393
+ return leftover.length === 0 ? values : null;
394
+ }
395
+
396
+ function collectArrayLines(lines, startIndex) {
397
+ const collected = [stripCommentOutsideStrings(lines[startIndex] ?? "")];
398
+ let bracketDepth = 0;
399
+ let inSingle = false;
400
+ let inDouble = false;
401
+ let escaped = false;
402
+
403
+ for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex += 1) {
404
+ const sanitized = stripCommentOutsideStrings(lines[lineIndex] ?? "");
405
+ if (lineIndex !== startIndex) {
406
+ collected.push(sanitized);
407
+ }
408
+
409
+ for (let index = 0; index < sanitized.length; index += 1) {
410
+ const char = sanitized[index] ?? "";
411
+
412
+ if (char === '"' && !inSingle && !escaped) {
413
+ inDouble = !inDouble;
414
+ } else if (char === "'" && !inDouble && !escaped) {
415
+ inSingle = !inSingle;
416
+ } else if (!inSingle && !inDouble) {
417
+ if (char === "[") {
418
+ bracketDepth += 1;
419
+ } else if (char === "]") {
420
+ bracketDepth -= 1;
421
+ }
422
+ }
423
+
424
+ escaped = char === "\\" && !escaped;
425
+ if (char !== "\\") {
426
+ escaped = false;
427
+ }
428
+ }
429
+
430
+ if (bracketDepth <= 0) {
431
+ return {
432
+ nextIndex: lineIndex,
433
+ text: collected.join("\n"),
434
+ };
435
+ }
436
+ }
437
+
438
+ return null;
439
+ }
440
+
441
+ function parseCodexBlock(blockContent) {
442
+ const lines = blockContent.split("\n");
443
+ let command = null;
444
+ let args = null;
445
+ let hasConflict = false;
446
+ let inTripleDouble = false;
447
+ let inTripleSingle = false;
448
+
449
+ for (let index = 1; index < lines.length; index += 1) {
450
+ const line = lines[index] ?? "";
451
+ const sanitized = stripCommentOutsideStrings(line);
452
+ const trimmed = sanitized.trim();
453
+
454
+ if (inTripleDouble) {
455
+ if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
456
+ inTripleDouble = false;
457
+ }
458
+ continue;
459
+ }
460
+
461
+ if (inTripleSingle) {
462
+ if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
463
+ inTripleSingle = false;
464
+ }
465
+ continue;
466
+ }
467
+
468
+ if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
469
+ inTripleDouble = true;
470
+ continue;
471
+ }
472
+
473
+ if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
474
+ inTripleSingle = true;
475
+ continue;
476
+ }
477
+
478
+ if (trimmed.length === 0) {
479
+ continue;
480
+ }
481
+
482
+ if (/^command\s*=/.test(trimmed)) {
483
+ const parsedCommand = parseStringAssignment(trimmed, "command");
484
+ if (command !== null || parsedCommand === null) {
485
+ hasConflict = true;
486
+ } else {
487
+ command = parsedCommand;
488
+ }
489
+ continue;
490
+ }
491
+
492
+ if (/^args\s*=/.test(trimmed)) {
493
+ const collected = collectArrayLines(lines, index);
494
+ const parsedArgs = collected === null ? null : parseTomlStringArray(collected.text);
495
+
496
+ if (args !== null || parsedArgs === null || collected === null) {
497
+ hasConflict = true;
498
+ } else {
499
+ args = parsedArgs;
500
+ index = collected.nextIndex;
501
+ }
502
+ }
503
+ }
504
+
505
+ return {
506
+ args,
507
+ command,
508
+ hasConflict,
509
+ };
510
+ }
511
+
512
+ function isStructurallySafe(source) {
513
+ let bracketDepth = 0;
514
+ let braceDepth = 0;
515
+ let inSingle = false;
516
+ let inDouble = false;
517
+ let escaped = false;
518
+ let tripleState = {
519
+ inTripleDouble: false,
520
+ inTripleSingle: false,
521
+ };
522
+
523
+ for (const line of source.split("\n")) {
524
+ tripleState = advanceTripleQuoteState(line, tripleState);
525
+
526
+ for (let index = 0; index < line.length; index += 1) {
527
+ const char = line[index] ?? "";
528
+
529
+ if (!inSingle && !inDouble && char === "#") {
530
+ break;
531
+ }
532
+
533
+ if (char === '"' && !inSingle && !escaped) {
534
+ inDouble = !inDouble;
535
+ } else if (char === "'" && !inDouble && !escaped) {
536
+ inSingle = !inSingle;
537
+ } else if (!inSingle && !inDouble) {
538
+ if (char === "[") {
539
+ bracketDepth += 1;
540
+ } else if (char === "]") {
541
+ bracketDepth -= 1;
542
+ if (bracketDepth < 0) {
543
+ return false;
544
+ }
545
+ } else if (char === "{") {
546
+ braceDepth += 1;
547
+ } else if (char === "}") {
548
+ braceDepth -= 1;
549
+ if (braceDepth < 0) {
550
+ return false;
551
+ }
552
+ }
553
+ }
554
+
555
+ escaped = char === "\\" && !escaped;
556
+ if (char !== "\\") {
557
+ escaped = false;
558
+ }
559
+ }
560
+ }
561
+
562
+ return (
563
+ !tripleState.inTripleDouble &&
564
+ !tripleState.inTripleSingle &&
565
+ bracketDepth === 0 &&
566
+ braceDepth === 0 &&
567
+ !inSingle &&
568
+ !inDouble
569
+ );
570
+ }
571
+
572
+ try {
573
+ const source = fs.readFileSync(configPath, "utf8");
574
+ if (!isStructurallySafe(source)) {
575
+ process.exit(1);
576
+ }
577
+
578
+ const blocks = findAllCodexBlocks(source);
579
+ if (blocks.length !== 1) {
580
+ process.exit(1);
581
+ }
582
+
583
+ const parsed = parseCodexBlock(blocks[0]);
584
+ const isConfigured =
585
+ !parsed.hasConflict &&
586
+ parsed.command === expectedCommand &&
587
+ Array.isArray(parsed.args) &&
588
+ parsed.args.length === expectedArgs.length &&
589
+ parsed.args.every((entry, index) => entry === expectedArgs[index]);
590
+
591
+ process.exit(isConfigured ? 0 : 1);
592
+ } catch {
593
+ process.exit(1);
594
+ }
595
+ NODE
158
596
  }
159
597
 
160
598
  resolve_optional_cli_command() {
@@ -175,16 +613,6 @@ resolve_optional_cli_command() {
175
613
  return 1
176
614
  }
177
615
 
178
- is_lfs_pointer() {
179
- local file_path="$1"
180
-
181
- if [ ! -f "$file_path" ]; then
182
- return 1
183
- fi
184
-
185
- grep -q "git-lfs.github.com/spec/v1" "$file_path"
186
- }
187
-
188
616
  # ── Header ──
189
617
  # shellcheck disable=SC2059
190
618
  printf "\n${BOLD}WROX Installer${NC}\n"
@@ -208,67 +636,24 @@ fi
208
636
  success "Node.js $(node -v) detected"
209
637
 
210
638
  # ═══════════════════════════════════
211
- # [1/3] MCP server install
639
+ # [1/2] Setup Roblox Studio Plugin
212
640
  # ═══════════════════════════════════
213
- step "1/3" "Install @weppy/roblox-mcp via npm"
641
+ step "1/2" "Setup Roblox Studio Plugin"
214
642
 
215
- if confirm " Run npm install -g @weppy/roblox-mcp?"; then
216
- if npm install -g @weppy/roblox-mcp; then
217
- success "Installed @weppy/roblox-mcp"
643
+ if confirm " Run npx -y @weppy/roblox-mcp --setup?"; then
644
+ if npx -y @weppy/roblox-mcp --setup; then
645
+ success "Setup complete"
218
646
  else
219
- fail "npm install failed"
220
- exit 1
647
+ warn "Setup encountered a warning (non-blocking)"
221
648
  fi
222
649
  else
223
- warn "MCP server install skipped"
650
+ warn "Setup skipped"
224
651
  fi
225
652
 
226
653
  # ═══════════════════════════════════
227
- # [2/3] Roblox Studio Plugin install
654
+ # [2/2] Register MCP with AI apps
228
655
  # ═══════════════════════════════════
229
- step "2/3" "Install Roblox Studio Plugin"
230
-
231
- PLUGINS_DIR="$HOME/Documents/Roblox/Plugins"
232
- PLUGIN_NAME="WeppyRobloxMCP.rbxm"
233
-
234
- # Search for .rbxm in npm global prefix
235
- NPM_PREFIX=$(npm prefix -g 2>/dev/null)
236
- BUNDLED_PLUGIN=""
237
-
238
- # Search for .rbxm in npm global path
239
- for search_dir in \
240
- "${NPM_PREFIX}/lib/node_modules/@weppy/roblox-mcp/plugins/weppy-roblox-mcp/roblox-plugin" \
241
- "${NPM_PREFIX}/lib/node_modules/@weppy/roblox-mcp/roblox-plugin"; do
242
- if [ -f "${search_dir}/${PLUGIN_NAME}" ]; then
243
- BUNDLED_PLUGIN="${search_dir}/${PLUGIN_NAME}"
244
- break
245
- fi
246
- done
247
-
248
- if [ -n "$BUNDLED_PLUGIN" ]; then
249
- if is_lfs_pointer "$BUNDLED_PLUGIN"; then
250
- fail "Bundled plugin payload is invalid (Git LFS pointer detected)"
251
- info "Install the plugin from the GitHub release ZIP instead"
252
- exit 1
253
- fi
254
-
255
- printf " → %s/%s\n" "$PLUGINS_DIR" "$PLUGIN_NAME"
256
- if confirm " Copy plugin to Roblox Plugins folder?"; then
257
- mkdir -p "$PLUGINS_DIR"
258
- cp "$BUNDLED_PLUGIN" "$PLUGINS_DIR/$PLUGIN_NAME"
259
- success "Plugin installed → $PLUGINS_DIR/$PLUGIN_NAME"
260
- else
261
- warn "Plugin install skipped"
262
- fi
263
- else
264
- warn "Bundled plugin file not found"
265
- info "Will be installed automatically on first MCP server run"
266
- fi
267
-
268
- # ═══════════════════════════════════
269
- # [3/3] Register MCP with AI apps
270
- # ═══════════════════════════════════
271
- step "3/3" "Register MCP with AI apps"
656
+ step "2/2" "Register MCP with AI apps"
272
657
  printf " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity\n"
273
658
 
274
659
  MCP_COMMAND='npx -y @weppy/roblox-mcp'
@@ -376,7 +761,12 @@ else
376
761
  # shellcheck disable=SC2059
377
762
  printf "\n Select apps to register ${DIM}(comma-separated, 'a' for all, 'n' to skip)${NC}\n"
378
763
  printf " → "
379
- read -r selection </dev/tty
764
+ if [ "${CI:-}" = "true" ]; then
765
+ selection="a"
766
+ printf "a\n"
767
+ else
768
+ read -r selection </dev/tty
769
+ fi
380
770
  selection="${selection:-n}"
381
771
 
382
772
  # Parse selection
package/llms-full.txt CHANGED
@@ -21,9 +21,21 @@ Roblox Studio Plugin ← Executes commands inside Studio
21
21
 
22
22
  When an AI app says "Create a blue part", the MCP server converts this request, and the Roblox Studio plugin actually creates the part.
23
23
 
24
- ## One-Line Install (Recommended)
24
+ ## Web Installer (Recommended)
25
25
 
26
- Installs the MCP server, Roblox Studio plugin, and registers with your AI apps in one step:
26
+ Open the installer page in Chrome, Edge, or Arc for one-click setup:
27
+
28
+ https://hope1026.github.io/weppy-roblox-mcp/installer/
29
+
30
+ It writes the supported AI app config for `npx -y @weppy/roblox-mcp` first.
31
+
32
+ After you reopen the AI app, the first MCP run fetches the package through `npx` and auto-installs the Roblox Studio plugin.
33
+
34
+ For Safari or Firefox, use the installer page as a guide or continue with the manual install below.
35
+
36
+ ## One-Line Install (Fallback)
37
+
38
+ Advanced users can install everything in one command:
27
39
 
28
40
  ```bash
29
41
  # macOS / Linux
package/llms.txt CHANGED
@@ -21,7 +21,11 @@ terrain, and more — inside a live Studio session.
21
21
 
22
22
  ## Installation
23
23
 
24
- **One-line install** (installs MCP server, Roblox Studio plugin, and registers with AI apps):
24
+ **Web installer** (recommended one-click setup for Chrome, Edge, and Arc):
25
+
26
+ https://hope1026.github.io/weppy-roblox-mcp/installer/
27
+
28
+ **One-line install** (advanced fallback):
25
29
 
26
30
  ```bash
27
31
  # macOS / Linux
@@ -31,8 +35,10 @@ curl -fsSL https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/inst
31
35
  irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
32
36
  ```
33
37
 
34
- Or manually:
35
- 1. Install Roblox Studio plugin (bridge between Studio and AI)
38
+ Safari / Firefox users should follow the installer page guide or open the installation docs.
39
+
40
+ Manual alternative:
41
+ 1. Install Roblox Studio plugin via the installation guide
36
42
  2. Register MCP server: `npx -y @weppy/roblox-mcp`
37
43
 
38
44
  ## Tiers
@@ -49,7 +55,7 @@ Or manually:
49
55
  - WROX Dashboard guide: https://github.com/hope1026/weppy-roblox-mcp/blob/main/docs/en/dashboard/overview.md
50
56
  - Troubleshooting: https://github.com/hope1026/weppy-roblox-mcp/blob/main/docs/troubleshooting.md
51
57
  - Compatibility: https://github.com/hope1026/weppy-roblox-mcp/blob/main/docs/compatibility.md
52
- - Pro upgrade: https://github.com/hope1026/weppy-roblox-mcp/blob/main/docs/en/pro-upgrade.md
58
+ - Pro upgrade: https://weppy-web.pages.dev/en/plans
53
59
 
54
60
  ## Requirements
55
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weppy/roblox-mcp",
3
- "version": "2.2.2",
3
+ "version": "2.3.1",
4
4
  "description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
5
5
  "main": "plugins/weppy-roblox-mcp/dist/index.js",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weppy-roblox-mcp",
3
3
  "description": "MCP server for Roblox Studio integration - AI-powered game development with specialized agents and skills",
4
- "version": "2.2.2",
4
+ "version": "2.3.1",
5
5
  "author": {
6
6
  "name": "hope1026"
7
7
  },