@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.github/workflows/install-test.yml +108 -0
- package/CHANGELOG.md +31 -0
- package/README.md +14 -10
- package/docs/en/installation/README.md +4 -4
- package/docs/en/installation/roblox-explorer.md +13 -3
- package/docs/en/installation/roblox-plugin.md +42 -31
- package/docs/en/pro-upgrade.md +26 -12
- package/docs/en/sync/luau-lsp.md +41 -0
- package/docs/en/sync/overview.md +4 -1
- package/docs/es/README.md +13 -7
- package/docs/es/installation/README.md +4 -4
- package/docs/es/pro-upgrade.md +26 -12
- package/docs/es/sync/luau-lsp.md +41 -0
- package/docs/es/sync/overview.md +4 -1
- package/docs/id/README.md +12 -6
- package/docs/id/installation/README.md +4 -4
- package/docs/id/pro-upgrade.md +26 -12
- package/docs/id/sync/luau-lsp.md +41 -0
- package/docs/id/sync/overview.md +4 -1
- package/docs/installer/assets/index-Bz0amd7x.js +63 -0
- package/docs/installer/assets/index-ei4lRUa6.css +1 -0
- package/docs/installer/index.html +14 -0
- package/docs/installer/manifest.webmanifest +15 -0
- package/docs/installer/sw.js +7 -0
- package/docs/installer/wrox-icon.png +0 -0
- package/docs/ja/README.md +14 -10
- package/docs/ja/installation/README.md +3 -3
- package/docs/ja/pro-upgrade.md +26 -12
- package/docs/ja/sync/luau-lsp.md +41 -0
- package/docs/ja/sync/overview.md +4 -1
- package/docs/ko/README.md +14 -10
- package/docs/ko/installation/README.md +4 -4
- package/docs/ko/installation/roblox-explorer.md +13 -3
- package/docs/ko/installation/roblox-plugin.md +42 -31
- package/docs/ko/pro-upgrade.md +26 -12
- package/docs/ko/sync/luau-lsp.md +41 -0
- package/docs/ko/sync/overview.md +4 -1
- package/docs/pt-br/README.md +13 -7
- package/docs/pt-br/installation/README.md +4 -4
- package/docs/pt-br/pro-upgrade.md +26 -12
- package/docs/pt-br/sync/luau-lsp.md +41 -0
- package/docs/pt-br/sync/overview.md +4 -1
- package/docs/troubleshooting.md +1 -1
- package/install.ps1 +468 -98
- package/install.sh +461 -71
- package/llms-full.txt +14 -2
- package/llms.txt +10 -4
- package/package.json +1 -1
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-D7eMrarv.js → ChangelogDetailPage-ITTDURna.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogPage-DFCCRyyK.js → ChangelogPage-DjVot-60.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-BmRJ2JXZ.js → ConfirmModal-B1q8BGeA.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-9bG71eB1.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-D4y36l03.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-CCDWZLC9.js → InfoLabel-COMRAIq0.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-BHpt3LI2.js → OverviewPage-DojgIxVT.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PlaytestPage-CNwwI5Ro.js → PlaytestPage-CkEvf6XW.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PropertyDiff-r9iLxfi8.js +6 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SettingsPage-CPqQYZPN.js → SettingsPage-CEs6Ob3d.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-C8VKAPpk.js → StatusBadge-DHhSWmAT.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-ifjnfsNg.js +4 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierComparison-7ofkPwj3.js → TierComparison-C29caZ6C.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierPromoProgress-SnRUjAPh.js → TierPromoProgress-BdEtTxkK.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ToolsPage-CrdNh3D9.js → ToolsPage-BOIC0ngW.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-CQYn3Wfp.js +129 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-BnXeLpOw.js → useLiveUptime-Dco8Aiiz.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +1 -1
- package/plugins/weppy-roblox-mcp/dist/index.js +86 -83
- package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-CN3LYLAT.css +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ConnectionPage-CiaCY026.js +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PropertyDiff-DIplDn-J.js +0 -6
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-DTSKbpio.js +0 -4
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-DGGmfli1.js +0 -129
package/install.ps1
CHANGED
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
# Usage:
|
|
5
5
|
# irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
|
|
6
6
|
#
|
|
7
|
-
# Interactive
|
|
8
|
-
# [1/
|
|
9
|
-
# [2/
|
|
10
|
-
# [3/3] Register MCP with AI apps (user selection)
|
|
7
|
+
# Interactive 2 steps:
|
|
8
|
+
# [1/2] Setup — install Roblox Studio Plugin via npx
|
|
9
|
+
# [2/2] Register MCP with AI apps (user selection)
|
|
11
10
|
#
|
|
12
11
|
|
|
13
12
|
$ErrorActionPreference = "Stop"
|
|
14
13
|
$script:InstallLogPath = Join-Path ([System.IO.Path]::GetTempPath()) ("wrox-install-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date))
|
|
15
14
|
$script:TranscriptStarted = $false
|
|
16
15
|
$script:NpmCommandPath = $null
|
|
17
|
-
$script:NpmGlobalPrefix = $null
|
|
18
16
|
|
|
19
17
|
# ── Utilities ──
|
|
20
18
|
function Write-Step($step, $msg) { Write-Host "`n[$step] $msg" -ForegroundColor Cyan -NoNewline; Write-Host "" }
|
|
@@ -50,6 +48,10 @@ trap {
|
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
function Confirm-Action($prompt) {
|
|
51
|
+
if ($env:CI -eq 'true') {
|
|
52
|
+
Write-Host "$prompt (Y/n): Y"
|
|
53
|
+
return $true
|
|
54
|
+
}
|
|
53
55
|
$reply = Read-Host "$prompt (Y/n)"
|
|
54
56
|
if ([string]::IsNullOrWhiteSpace($reply)) { $reply = "Y" }
|
|
55
57
|
return $reply -match '^[Yy]'
|
|
@@ -69,30 +71,6 @@ function Resolve-NpmCommand() {
|
|
|
69
71
|
return $script:NpmCommandPath
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
function Get-NpmGlobalPrefix() {
|
|
73
|
-
if ($script:NpmGlobalPrefix) {
|
|
74
|
-
return $script:NpmGlobalPrefix
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
$script:NpmGlobalPrefix = (Invoke-Npm prefix -g 2>$null | Out-String).Trim()
|
|
78
|
-
return $script:NpmGlobalPrefix
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function Invoke-Npm {
|
|
82
|
-
param(
|
|
83
|
-
[Parameter(ValueFromRemainingArguments = $true)]
|
|
84
|
-
[string[]]$Args
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
$npmCommandPath = Resolve-NpmCommand
|
|
88
|
-
$output = & $npmCommandPath @Args
|
|
89
|
-
if ($LASTEXITCODE -ne 0) {
|
|
90
|
-
throw "npm $($Args -join ' ') failed with exit code $LASTEXITCODE"
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return $output
|
|
94
|
-
}
|
|
95
|
-
|
|
96
74
|
function Resolve-OptionalCliCommand($commandName) {
|
|
97
75
|
$resolvedCommand = Get-Command $commandName -ErrorAction SilentlyContinue
|
|
98
76
|
if ($resolvedCommand) {
|
|
@@ -100,11 +78,6 @@ function Resolve-OptionalCliCommand($commandName) {
|
|
|
100
78
|
}
|
|
101
79
|
|
|
102
80
|
$candidatePaths = @()
|
|
103
|
-
$npmPrefix = Get-NpmGlobalPrefix
|
|
104
|
-
if ($npmPrefix) {
|
|
105
|
-
$candidatePaths += (Join-Path $npmPrefix "$commandName.cmd")
|
|
106
|
-
$candidatePaths += (Join-Path $npmPrefix $commandName)
|
|
107
|
-
}
|
|
108
81
|
|
|
109
82
|
if ($env:APPDATA) {
|
|
110
83
|
$appDataNpmDir = Join-Path $env:APPDATA 'npm'
|
|
@@ -139,7 +112,7 @@ function Test-McpJsonConfigured($configPath) {
|
|
|
139
112
|
}
|
|
140
113
|
}
|
|
141
114
|
|
|
142
|
-
#
|
|
115
|
+
# Antigravity 설정에 canonical mcpServers 래퍼로 MCP 서버를 추가하고 legacy flat key를 정리
|
|
143
116
|
function Add-AntigravityMcpConfig($configPath) {
|
|
144
117
|
$parentDir = Split-Path $configPath -Parent
|
|
145
118
|
if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
|
|
@@ -150,7 +123,20 @@ const fs = require('fs');
|
|
|
150
123
|
const configPath = process.env.MCP_CONFIG_PATH;
|
|
151
124
|
let config = {};
|
|
152
125
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
|
|
153
|
-
config
|
|
126
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
127
|
+
config = {};
|
|
128
|
+
}
|
|
129
|
+
const mcpServers = config.mcpServers;
|
|
130
|
+
if (mcpServers !== undefined && (typeof mcpServers !== 'object' || mcpServers === null || Array.isArray(mcpServers))) {
|
|
131
|
+
throw new Error('Antigravity mcpServers must be an object');
|
|
132
|
+
}
|
|
133
|
+
const next = { ...config };
|
|
134
|
+
delete next['weppy-roblox-mcp'];
|
|
135
|
+
next.mcpServers = {
|
|
136
|
+
...(mcpServers || {}),
|
|
137
|
+
'weppy-roblox-mcp': { command: 'npx', args: ['-y', '@weppy/roblox-mcp'] }
|
|
138
|
+
};
|
|
139
|
+
config = next;
|
|
154
140
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
155
141
|
"@
|
|
156
142
|
} finally {
|
|
@@ -165,7 +151,10 @@ function Test-AntigravityMcpConfigured($configPath) {
|
|
|
165
151
|
|
|
166
152
|
try {
|
|
167
153
|
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
|
|
168
|
-
|
|
154
|
+
$hasLegacyFlatKey = $config.PSObject.Properties.Name -contains 'weppy-roblox-mcp'
|
|
155
|
+
$server = $config.mcpServers.'weppy-roblox-mcp'
|
|
156
|
+
$hasCanonicalArgs = ($server.args -is [System.Array]) -and ($server.args.Count -eq 2) -and ($server.args[0] -eq '-y') -and ($server.args[1] -eq '@weppy/roblox-mcp')
|
|
157
|
+
return ($server.command -eq 'npx') -and $hasCanonicalArgs -and (-not $hasLegacyFlatKey)
|
|
169
158
|
}
|
|
170
159
|
catch {
|
|
171
160
|
return $false
|
|
@@ -177,7 +166,421 @@ function Test-CodexConfigConfigured($configPath) {
|
|
|
177
166
|
return $false
|
|
178
167
|
}
|
|
179
168
|
|
|
180
|
-
|
|
169
|
+
$env:MCP_CODEX_CONFIG_PATH = $configPath
|
|
170
|
+
try {
|
|
171
|
+
node -e @'
|
|
172
|
+
const fs = require('fs');
|
|
173
|
+
|
|
174
|
+
const configPath = process.env.MCP_CODEX_CONFIG_PATH;
|
|
175
|
+
const serverName = 'weppy-roblox-mcp';
|
|
176
|
+
const expectedCommand = 'npx';
|
|
177
|
+
const expectedArgs = ['-y', '@weppy/roblox-mcp'];
|
|
178
|
+
const headerPattern = new RegExp(
|
|
179
|
+
'^\\s*\\[\\s*mcp_servers\\.' + serverName.replace(/[.*+?^${}()|[\]\\\\]/g, '\\$&') + '\\s*\\]\\s*(?:#.*)?$'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
function stripCommentOutsideStrings(line) {
|
|
183
|
+
let inSingle = false;
|
|
184
|
+
let inDouble = false;
|
|
185
|
+
let escaped = false;
|
|
186
|
+
|
|
187
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
188
|
+
const char = line[index];
|
|
189
|
+
|
|
190
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
191
|
+
inDouble = !inDouble;
|
|
192
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
193
|
+
inSingle = !inSingle;
|
|
194
|
+
} else if (char === '#' && !inSingle && !inDouble) {
|
|
195
|
+
return line.slice(0, index).trimEnd();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
escaped = char === '\\' && !escaped;
|
|
199
|
+
if (char !== '\\') {
|
|
200
|
+
escaped = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return line.trimEnd();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function countTripleQuoteToggles(line, quote) {
|
|
208
|
+
let count = 0;
|
|
209
|
+
let inSingle = false;
|
|
210
|
+
let inDouble = false;
|
|
211
|
+
let escaped = false;
|
|
212
|
+
|
|
213
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
214
|
+
const char = line[index] ?? '';
|
|
215
|
+
const nextThree = line.slice(index, index + 3);
|
|
216
|
+
const isOutsideStrings = !inSingle && !inDouble;
|
|
217
|
+
|
|
218
|
+
if (isOutsideStrings && nextThree === quote.repeat(3)) {
|
|
219
|
+
count += 1;
|
|
220
|
+
index += 2;
|
|
221
|
+
escaped = false;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
226
|
+
inDouble = !inDouble;
|
|
227
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
228
|
+
inSingle = !inSingle;
|
|
229
|
+
} else if (char === '#' && !inSingle && !inDouble) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
escaped = char === '\\' && !escaped;
|
|
234
|
+
if (char !== '\\') {
|
|
235
|
+
escaped = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return count;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function advanceTripleQuoteState(line, state) {
|
|
243
|
+
const next = { ...state };
|
|
244
|
+
const tripleDoubleCount = countTripleQuoteToggles(line, '"');
|
|
245
|
+
const tripleSingleCount = countTripleQuoteToggles(line, "'");
|
|
246
|
+
|
|
247
|
+
if (!next.inTripleSingle && tripleDoubleCount % 2 === 1) {
|
|
248
|
+
next.inTripleDouble = !next.inTripleDouble;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!next.inTripleDouble && tripleSingleCount % 2 === 1) {
|
|
252
|
+
next.inTripleSingle = !next.inTripleSingle;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return next;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function isTomlTableHeaderLine(line) {
|
|
259
|
+
const normalized = stripCommentOutsideStrings(line).trim();
|
|
260
|
+
|
|
261
|
+
if (normalized.length === 0) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return /^\[\[.*\]\]$/.test(normalized) || /^\[.*\]$/.test(normalized);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function findAllCodexBlocks(source) {
|
|
269
|
+
const lines = source.split('\n');
|
|
270
|
+
const blocks = [];
|
|
271
|
+
let activeLines = null;
|
|
272
|
+
let state = {
|
|
273
|
+
inTripleDouble: false,
|
|
274
|
+
inTripleSingle: false,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
for (const line of lines) {
|
|
278
|
+
const isHeaderCandidate = !state.inTripleDouble && !state.inTripleSingle && isTomlTableHeaderLine(line);
|
|
279
|
+
const isCodexHeader = isHeaderCandidate && headerPattern.test(line);
|
|
280
|
+
|
|
281
|
+
if (isCodexHeader) {
|
|
282
|
+
if (activeLines !== null) {
|
|
283
|
+
blocks.push(activeLines.join('\n').trim());
|
|
284
|
+
}
|
|
285
|
+
activeLines = [line];
|
|
286
|
+
} else if (activeLines !== null && isHeaderCandidate) {
|
|
287
|
+
blocks.push(activeLines.join('\n').trim());
|
|
288
|
+
activeLines = null;
|
|
289
|
+
} else if (activeLines !== null) {
|
|
290
|
+
activeLines.push(line);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
state = advanceTripleQuoteState(line, state);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (activeLines !== null) {
|
|
297
|
+
blocks.push(activeLines.join('\n').trim());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return blocks;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseStringAssignment(value, key) {
|
|
304
|
+
const match = new RegExp('^\\s*' + key + '\\s*=\\s*(["\\'])([^"\\']+)\\1\\s*$').exec(value);
|
|
305
|
+
return match ? match[2] : null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function parseTomlStringArray(value) {
|
|
309
|
+
const match = /^\s*args\s*=\s*\[(.*)\]\s*$/ms.exec(value.trim());
|
|
310
|
+
|
|
311
|
+
if (match === null) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const body = match[1] ?? '';
|
|
316
|
+
const values = [];
|
|
317
|
+
let cursor = 0;
|
|
318
|
+
let expectValue = true;
|
|
319
|
+
|
|
320
|
+
while (cursor < body.length) {
|
|
321
|
+
while (cursor < body.length && /\s/.test(body[cursor] ?? '')) {
|
|
322
|
+
cursor += 1;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (cursor >= body.length) {
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!expectValue) {
|
|
330
|
+
if (body[cursor] !== ',') {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
cursor += 1;
|
|
334
|
+
expectValue = true;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const quote = body[cursor];
|
|
339
|
+
if (quote !== '"' && quote !== "'") {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
cursor += 1;
|
|
344
|
+
let token = '';
|
|
345
|
+
let escaped = false;
|
|
346
|
+
|
|
347
|
+
while (cursor < body.length) {
|
|
348
|
+
const char = body[cursor] ?? '';
|
|
349
|
+
|
|
350
|
+
if (char === quote && !escaped) {
|
|
351
|
+
cursor += 1;
|
|
352
|
+
values.push(token);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
token += char;
|
|
357
|
+
escaped = char === '\\' && !escaped;
|
|
358
|
+
if (char !== '\\') {
|
|
359
|
+
escaped = false;
|
|
360
|
+
}
|
|
361
|
+
cursor += 1;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (cursor > body.length) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
expectValue = false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const leftover = body.slice(cursor).trim();
|
|
372
|
+
if (leftover === ',') {
|
|
373
|
+
return values;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return leftover.length === 0 ? values : null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function collectArrayLines(lines, startIndex) {
|
|
380
|
+
const collected = [stripCommentOutsideStrings(lines[startIndex] ?? '')];
|
|
381
|
+
let bracketDepth = 0;
|
|
382
|
+
let inSingle = false;
|
|
383
|
+
let inDouble = false;
|
|
384
|
+
let escaped = false;
|
|
385
|
+
|
|
386
|
+
for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex += 1) {
|
|
387
|
+
const sanitized = stripCommentOutsideStrings(lines[lineIndex] ?? '');
|
|
388
|
+
if (lineIndex !== startIndex) {
|
|
389
|
+
collected.push(sanitized);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (let index = 0; index < sanitized.length; index += 1) {
|
|
393
|
+
const char = sanitized[index] ?? '';
|
|
394
|
+
|
|
395
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
396
|
+
inDouble = !inDouble;
|
|
397
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
398
|
+
inSingle = !inSingle;
|
|
399
|
+
} else if (!inSingle && !inDouble) {
|
|
400
|
+
if (char === '[') {
|
|
401
|
+
bracketDepth += 1;
|
|
402
|
+
} else if (char === ']') {
|
|
403
|
+
bracketDepth -= 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
escaped = char === '\\' && !escaped;
|
|
408
|
+
if (char !== '\\') {
|
|
409
|
+
escaped = false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (bracketDepth <= 0) {
|
|
414
|
+
return {
|
|
415
|
+
nextIndex: lineIndex,
|
|
416
|
+
text: collected.join('\n'),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function parseCodexBlock(blockContent) {
|
|
425
|
+
const lines = blockContent.split('\n');
|
|
426
|
+
let command = null;
|
|
427
|
+
let args = null;
|
|
428
|
+
let hasConflict = false;
|
|
429
|
+
let inTripleDouble = false;
|
|
430
|
+
let inTripleSingle = false;
|
|
431
|
+
|
|
432
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
433
|
+
const line = lines[index] ?? '';
|
|
434
|
+
const sanitized = stripCommentOutsideStrings(line);
|
|
435
|
+
const trimmed = sanitized.trim();
|
|
436
|
+
|
|
437
|
+
if (inTripleDouble) {
|
|
438
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
439
|
+
inTripleDouble = false;
|
|
440
|
+
}
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (inTripleSingle) {
|
|
445
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
446
|
+
inTripleSingle = false;
|
|
447
|
+
}
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
452
|
+
inTripleDouble = true;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
457
|
+
inTripleSingle = true;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (trimmed.length === 0) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (/^command\s*=/.test(trimmed)) {
|
|
466
|
+
const parsedCommand = parseStringAssignment(trimmed, 'command');
|
|
467
|
+
if (command !== null || parsedCommand === null) {
|
|
468
|
+
hasConflict = true;
|
|
469
|
+
} else {
|
|
470
|
+
command = parsedCommand;
|
|
471
|
+
}
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (/^args\s*=/.test(trimmed)) {
|
|
476
|
+
const collected = collectArrayLines(lines, index);
|
|
477
|
+
const parsedArgs = collected === null ? null : parseTomlStringArray(collected.text);
|
|
478
|
+
|
|
479
|
+
if (args !== null || parsedArgs === null || collected === null) {
|
|
480
|
+
hasConflict = true;
|
|
481
|
+
} else {
|
|
482
|
+
args = parsedArgs;
|
|
483
|
+
index = collected.nextIndex;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
args,
|
|
490
|
+
command,
|
|
491
|
+
hasConflict,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function isStructurallySafe(source) {
|
|
496
|
+
let bracketDepth = 0;
|
|
497
|
+
let braceDepth = 0;
|
|
498
|
+
let inSingle = false;
|
|
499
|
+
let inDouble = false;
|
|
500
|
+
let escaped = false;
|
|
501
|
+
let tripleState = {
|
|
502
|
+
inTripleDouble: false,
|
|
503
|
+
inTripleSingle: false,
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
for (const line of source.split('\n')) {
|
|
507
|
+
tripleState = advanceTripleQuoteState(line, tripleState);
|
|
508
|
+
|
|
509
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
510
|
+
const char = line[index] ?? '';
|
|
511
|
+
|
|
512
|
+
if (!inSingle && !inDouble && char === '#') {
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
517
|
+
inDouble = !inDouble;
|
|
518
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
519
|
+
inSingle = !inSingle;
|
|
520
|
+
} else if (!inSingle && !inDouble) {
|
|
521
|
+
if (char === '[') {
|
|
522
|
+
bracketDepth += 1;
|
|
523
|
+
} else if (char === ']') {
|
|
524
|
+
bracketDepth -= 1;
|
|
525
|
+
if (bracketDepth < 0) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
} else if (char === '{') {
|
|
529
|
+
braceDepth += 1;
|
|
530
|
+
} else if (char === '}') {
|
|
531
|
+
braceDepth -= 1;
|
|
532
|
+
if (braceDepth < 0) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
escaped = char === '\\' && !escaped;
|
|
539
|
+
if (char !== '\\') {
|
|
540
|
+
escaped = false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
!tripleState.inTripleDouble &&
|
|
547
|
+
!tripleState.inTripleSingle &&
|
|
548
|
+
bracketDepth === 0 &&
|
|
549
|
+
braceDepth === 0 &&
|
|
550
|
+
!inSingle &&
|
|
551
|
+
!inDouble
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const source = fs.readFileSync(configPath, 'utf8');
|
|
557
|
+
if (!isStructurallySafe(source)) {
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const blocks = findAllCodexBlocks(source);
|
|
562
|
+
if (blocks.length !== 1) {
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const parsed = parseCodexBlock(blocks[0]);
|
|
567
|
+
const isConfigured =
|
|
568
|
+
!parsed.hasConflict &&
|
|
569
|
+
parsed.command === expectedCommand &&
|
|
570
|
+
Array.isArray(parsed.args) &&
|
|
571
|
+
parsed.args.length === expectedArgs.length &&
|
|
572
|
+
parsed.args.every((entry, index) => entry === expectedArgs[index]);
|
|
573
|
+
|
|
574
|
+
process.exit(isConfigured ? 0 : 1);
|
|
575
|
+
} catch {
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
'@
|
|
579
|
+
return $LASTEXITCODE -eq 0
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
Remove-Item Env:\MCP_CODEX_CONFIG_PATH -ErrorAction SilentlyContinue
|
|
583
|
+
}
|
|
181
584
|
}
|
|
182
585
|
|
|
183
586
|
# Add MCP server to JSON config file (PowerShell 5.1 compatible — edits JSON via node)
|
|
@@ -200,11 +603,6 @@ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
|
200
603
|
}
|
|
201
604
|
}
|
|
202
605
|
|
|
203
|
-
function Test-LfsPointer($filePath) {
|
|
204
|
-
if (-not (Test-Path $filePath)) { return $false }
|
|
205
|
-
return Select-String -Path $filePath -Pattern "git-lfs.github.com/spec/v1" -Quiet
|
|
206
|
-
}
|
|
207
|
-
|
|
208
606
|
# ── Header ──
|
|
209
607
|
Write-Host ""
|
|
210
608
|
Write-Host "WROX Installer" -ForegroundColor White -BackgroundColor DarkCyan
|
|
@@ -225,71 +623,37 @@ catch {
|
|
|
225
623
|
}
|
|
226
624
|
|
|
227
625
|
# ═══════════════════════════════════
|
|
228
|
-
# [1/
|
|
626
|
+
# [1/2] Setup — Roblox Studio Plugin
|
|
229
627
|
# ═══════════════════════════════════
|
|
230
|
-
Write-Step "1/
|
|
628
|
+
Write-Step "1/2" "Setup Roblox Studio Plugin"
|
|
231
629
|
|
|
232
|
-
if (Confirm-Action " Run
|
|
630
|
+
if (Confirm-Action " Run npx -y @weppy/roblox-mcp --setup?") {
|
|
233
631
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
# ═══════════════════════════════════
|
|
246
|
-
# [2/3] Roblox Studio Plugin install
|
|
247
|
-
# ═══════════════════════════════════
|
|
248
|
-
Write-Step "2/3" "Install Roblox Studio Plugin"
|
|
249
|
-
|
|
250
|
-
$pluginsDir = Join-Path $env:LOCALAPPDATA "Roblox\Plugins"
|
|
251
|
-
$pluginName = "WeppyRobloxMCP.rbxm"
|
|
252
|
-
|
|
253
|
-
# Search for .rbxm in npm global path
|
|
254
|
-
$npmPrefix = Get-NpmGlobalPrefix
|
|
255
|
-
$bundledPlugin = $null
|
|
256
|
-
$searchPaths = @(
|
|
257
|
-
(Join-Path $npmPrefix "node_modules\@weppy\roblox-mcp\plugins\weppy-roblox-mcp\roblox-plugin\$pluginName"),
|
|
258
|
-
(Join-Path $npmPrefix "node_modules\@weppy\roblox-mcp\roblox-plugin\$pluginName")
|
|
259
|
-
)
|
|
260
|
-
foreach ($p in $searchPaths) {
|
|
261
|
-
if (Test-Path $p) {
|
|
262
|
-
$bundledPlugin = $p
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if ($bundledPlugin) {
|
|
268
|
-
if (Test-LfsPointer $bundledPlugin) {
|
|
269
|
-
Abort-Install "Bundled plugin payload is invalid (Git LFS pointer detected). Install the plugin from the GitHub release ZIP instead."
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
Write-Host " → $pluginsDir\$pluginName"
|
|
273
|
-
if (Confirm-Action " Copy plugin to Roblox Plugins folder?") {
|
|
274
|
-
if (-not (Test-Path $pluginsDir)) {
|
|
275
|
-
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
|
|
632
|
+
$npmCommandPath = Resolve-NpmCommand
|
|
633
|
+
$npmDir = Split-Path $npmCommandPath -Parent
|
|
634
|
+
$npxPath = Join-Path $npmDir "npx.cmd"
|
|
635
|
+
if (-not (Test-Path $npxPath)) {
|
|
636
|
+
$npxPath = "npx"
|
|
637
|
+
}
|
|
638
|
+
& $npxPath -y "@weppy/roblox-mcp" --setup
|
|
639
|
+
if ($LASTEXITCODE -ne 0) {
|
|
640
|
+
Write-Warn "Setup encountered a warning (non-blocking)"
|
|
641
|
+
} else {
|
|
642
|
+
Write-Ok "Setup complete"
|
|
276
643
|
}
|
|
277
|
-
Copy-Item $bundledPlugin -Destination (Join-Path $pluginsDir $pluginName) -Force
|
|
278
|
-
Write-Ok "Plugin installed → $pluginsDir\$pluginName"
|
|
279
644
|
}
|
|
280
|
-
|
|
281
|
-
Write-Warn "
|
|
645
|
+
catch {
|
|
646
|
+
Write-Warn "Setup encountered a warning: $_"
|
|
282
647
|
}
|
|
283
648
|
}
|
|
284
649
|
else {
|
|
285
|
-
Write-Warn "
|
|
286
|
-
Write-Info "Will be installed automatically on first MCP server run"
|
|
650
|
+
Write-Warn "Setup skipped"
|
|
287
651
|
}
|
|
288
652
|
|
|
289
653
|
# ═══════════════════════════════════
|
|
290
|
-
# [
|
|
654
|
+
# [2/2] Register MCP with AI apps
|
|
291
655
|
# ═══════════════════════════════════
|
|
292
|
-
Write-Step "
|
|
656
|
+
Write-Step "2/2" "Register MCP with AI apps"
|
|
293
657
|
Write-Host " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity"
|
|
294
658
|
|
|
295
659
|
$detectedNames = @()
|
|
@@ -403,7 +767,12 @@ else {
|
|
|
403
767
|
}
|
|
404
768
|
|
|
405
769
|
Write-Host ""
|
|
406
|
-
$
|
|
770
|
+
if ($env:CI -eq 'true') {
|
|
771
|
+
Write-Host " Select apps to register (comma-separated, 'a' for all, 'n' to skip): a"
|
|
772
|
+
$selection = 'a'
|
|
773
|
+
} else {
|
|
774
|
+
$selection = Read-Host " Select apps to register (comma-separated, 'a' for all, 'n' to skip)"
|
|
775
|
+
}
|
|
407
776
|
if ([string]::IsNullOrWhiteSpace($selection)) { $selection = "n" }
|
|
408
777
|
|
|
409
778
|
$selectedIndices = @()
|
|
@@ -436,7 +805,8 @@ else {
|
|
|
436
805
|
elseif ($claudeCodeCliCommand) {
|
|
437
806
|
& $claudeCodeCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp"
|
|
438
807
|
if ($LASTEXITCODE -ne 0) {
|
|
439
|
-
|
|
808
|
+
# CLI 실패 시 (Windows에서 -- 파싱 문제 등) JSON config에 직접 쓰기로 폴백
|
|
809
|
+
Add-McpToConfig $claudeGlobalConfig
|
|
440
810
|
}
|
|
441
811
|
Write-Ok "Registered: $appName"
|
|
442
812
|
}
|