@weppy/roblox-mcp 2.7.5 → 2.7.7
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/CHANGELOG.md +23 -0
- package/README.md +25 -5
- package/{plugins/weppy-roblox-mcp/dist → dist}/index.js +68 -68
- package/package.json +5 -5
- package/{plugins/weppy-roblox-mcp/roblox-plugin → roblox-plugin}/WeppyRobloxMCP.rbxm +0 -0
- package/.claude-plugin/marketplace.json +0 -43
- package/CODE_OF_CONDUCT.md +0 -29
- package/COMMERCIAL-LICENSE.md +0 -13
- package/CONTRIBUTING.md +0 -36
- package/SECURITY.md +0 -28
- package/SUPPORT.md +0 -25
- package/TRADEMARKS.md +0 -18
- package/glama.json +0 -7
- package/install.ps1 +0 -964
- package/install.sh +0 -939
- package/llms-full.txt +0 -13
- package/llms.txt +0 -69
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +0 -28
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogDetailPage-D6Tqz7ut.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogDetailPage-DglsIYkW.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogPage-65B3_w0_.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ChangelogPage-CNxAGfwG.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConfirmModal-Cpk7SbKb.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConnectionPage-B-IN5LsC.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ConnectionPage-CNtjimlm.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/GameChangeDetail-C1XtdYwk.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/GameChangeDetail-DM3mWsFX.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/InfoLabel-B_fEbHa7.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/OverviewPage-B4O0bv4R.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/OverviewPage-Dsfl-NRT.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/PlaytestPage-BHLRKn8U.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/PlaytestPage-DjjsIkke.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SettingsPage-DmIKC_O1.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SettingsPage-Du8-FZAO.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/StatusBadge-C2zYt5iE.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/StatusBadge-DRdnq30k.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SyncPage-CW_0kNpZ.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/SyncPage-Dm7Ni3j_.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/Tabs-876h0_zB.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/Tabs-BsTVkBUh.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TierComparison-DGh9vLz0.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TierComparison-poRtDe46.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ToolsPage-Bt9vYA7u.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ToolsPage-D77yJ9jZ.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/TooltipText-DX5jnyNF.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/UiStudioPage-YtdlkQzT.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/UiStudioPage-eSinjpOX.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/WhatsNewPage--uCu0xCm.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/WhatsNewPage-Lxgj0StO.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/index-BPIBy2lU.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/index-CX4MHzNt.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/sample-requests-CwDMfktX.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/sample-requests-CygerZZ_.css +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/ui-studio-sample-DrNTD6yi.png +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/assets/useLiveUptime-ElD9lDzh.js +0 -0
- /package/{plugins/weppy-roblox-mcp/dashboard → dashboard}/dist/index.html +0 -0
- /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
|