ropilot 0.1.22 → 0.1.24
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/lib/setup.js +41 -159
- package/package.json +1 -1
package/lib/setup.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Setup and initialization for Ropilot CLI
|
|
3
|
-
*
|
|
4
|
-
* Handles:
|
|
5
|
-
* - First-time setup
|
|
6
|
-
* - Downloading system prompts
|
|
7
|
-
* - Creating project configs
|
|
8
|
-
* - Installing Studio plugin
|
|
9
3
|
*/
|
|
10
4
|
|
|
11
5
|
import { existsSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from 'fs';
|
|
@@ -46,13 +40,11 @@ function isWSL() {
|
|
|
46
40
|
* Get Windows username when running in WSL (tries multiple methods)
|
|
47
41
|
*/
|
|
48
42
|
function getWindowsUsername() {
|
|
49
|
-
// Method 1: cmd.exe
|
|
50
43
|
try {
|
|
51
44
|
const result = execSync('cmd.exe /c echo %USERNAME%', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
52
45
|
if (result && !result.includes('%')) return result;
|
|
53
46
|
} catch {}
|
|
54
47
|
|
|
55
|
-
// Method 2: whoami.exe (returns DOMAIN\user)
|
|
56
48
|
try {
|
|
57
49
|
const result = execSync('/mnt/c/Windows/System32/whoami.exe', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
58
50
|
if (result && result.includes('\\')) {
|
|
@@ -60,7 +52,6 @@ function getWindowsUsername() {
|
|
|
60
52
|
}
|
|
61
53
|
} catch {}
|
|
62
54
|
|
|
63
|
-
// Method 3: powershell
|
|
64
55
|
try {
|
|
65
56
|
const result = execSync('powershell.exe -NoProfile -Command "$env:USERNAME"', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
66
57
|
if (result) return result;
|
|
@@ -86,20 +77,16 @@ function getValidWindowsUsers() {
|
|
|
86
77
|
|
|
87
78
|
/**
|
|
88
79
|
* Get Roblox plugins folder path based on OS (with WSL detection)
|
|
89
|
-
* Returns { path, needsPrompt, users } - needsPrompt true if user selection needed
|
|
90
80
|
*/
|
|
91
81
|
function getPluginsFolder() {
|
|
92
82
|
const os = platform();
|
|
93
83
|
|
|
94
|
-
// Check for WSL first - need to use Windows path
|
|
95
84
|
if (os === 'linux' && isWSL()) {
|
|
96
|
-
// Try to auto-detect Windows username
|
|
97
85
|
const windowsUser = getWindowsUsername();
|
|
98
86
|
if (windowsUser && existsSync(join('/mnt/c/Users', windowsUser))) {
|
|
99
87
|
return { path: join('/mnt/c/Users', windowsUser, 'AppData', 'Local', 'Roblox', 'Plugins') };
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
// Fallback: scan /mnt/c/Users
|
|
103
90
|
const validUsers = getValidWindowsUsers();
|
|
104
91
|
if (validUsers.length === 1) {
|
|
105
92
|
return { path: join('/mnt/c/Users', validUsers[0], 'AppData', 'Local', 'Roblox', 'Plugins') };
|
|
@@ -115,7 +102,6 @@ function getPluginsFolder() {
|
|
|
115
102
|
} else if (os === 'darwin') {
|
|
116
103
|
return { path: join(homedir(), 'Documents', 'Roblox', 'Plugins') };
|
|
117
104
|
} else {
|
|
118
|
-
// Linux without WSL - Roblox doesn't officially support Linux
|
|
119
105
|
return { path: null };
|
|
120
106
|
}
|
|
121
107
|
}
|
|
@@ -141,33 +127,26 @@ function prompt(question) {
|
|
|
141
127
|
* Install Studio plugin to Roblox plugins folder
|
|
142
128
|
*/
|
|
143
129
|
async function installPlugin() {
|
|
144
|
-
console.log('Installing Studio plugin...');
|
|
145
|
-
|
|
146
130
|
try {
|
|
147
|
-
// Check plugin version
|
|
148
131
|
const versionResponse = await fetch(PLUGIN_VERSION_URL);
|
|
149
132
|
if (!versionResponse.ok) {
|
|
150
133
|
throw new Error(`Failed to fetch plugin version: ${versionResponse.status}`);
|
|
151
134
|
}
|
|
152
135
|
const versionInfo = await versionResponse.json();
|
|
153
|
-
console.log(` Plugin version: ${versionInfo.version}`);
|
|
154
136
|
|
|
155
|
-
// Download plugin
|
|
156
137
|
const pluginResponse = await fetch(PLUGIN_DOWNLOAD_URL);
|
|
157
138
|
if (!pluginResponse.ok) {
|
|
158
139
|
throw new Error(`Failed to download plugin: ${pluginResponse.status}`);
|
|
159
140
|
}
|
|
160
141
|
const pluginSource = await pluginResponse.text();
|
|
161
142
|
|
|
162
|
-
// Get plugins folder
|
|
163
143
|
let folderResult = getPluginsFolder();
|
|
164
144
|
let pluginsFolder = folderResult.path;
|
|
165
145
|
|
|
166
|
-
// If WSL detected multiple users, prompt for selection
|
|
167
146
|
if (folderResult.needsPrompt && folderResult.users) {
|
|
168
|
-
console.log('
|
|
169
|
-
folderResult.users.forEach((u, i) => console.log(`
|
|
170
|
-
const choice = await prompt(`
|
|
147
|
+
console.log('Multiple Windows users detected:');
|
|
148
|
+
folderResult.users.forEach((u, i) => console.log(` ${i + 1}. ${u}`));
|
|
149
|
+
const choice = await prompt(`Select user (1-${folderResult.users.length}): `);
|
|
171
150
|
|
|
172
151
|
let selectedUser;
|
|
173
152
|
const num = parseInt(choice);
|
|
@@ -184,81 +163,63 @@ async function installPlugin() {
|
|
|
184
163
|
}
|
|
185
164
|
}
|
|
186
165
|
|
|
187
|
-
// If still no folder and running in WSL, prompt for username directly
|
|
188
166
|
if (!pluginsFolder && isWSL()) {
|
|
189
|
-
const username = await prompt('
|
|
190
|
-
if (username
|
|
191
|
-
pluginsFolder = join('/mnt/c/Users', username, 'AppData', 'Local', 'Roblox', 'Plugins');
|
|
192
|
-
} else if (username) {
|
|
193
|
-
// Trust user input even if path doesn't exist yet
|
|
167
|
+
const username = await prompt('Enter your Windows username: ');
|
|
168
|
+
if (username) {
|
|
194
169
|
pluginsFolder = join('/mnt/c/Users', username, 'AppData', 'Local', 'Roblox', 'Plugins');
|
|
195
170
|
}
|
|
196
171
|
}
|
|
197
172
|
|
|
198
173
|
if (!pluginsFolder) {
|
|
199
|
-
console.log('
|
|
200
|
-
console.log('
|
|
174
|
+
console.log('Could not find Roblox plugins folder');
|
|
175
|
+
console.log('Download manually: https://ropilot.ai/plugin/StudioPlugin.lua');
|
|
201
176
|
return false;
|
|
202
177
|
}
|
|
203
178
|
|
|
204
|
-
// Create plugins folder if it doesn't exist
|
|
205
179
|
mkdirSync(pluginsFolder, { recursive: true });
|
|
206
180
|
|
|
207
|
-
// Write plugin file
|
|
208
181
|
const pluginPath = join(pluginsFolder, 'RopilotPlugin.lua');
|
|
209
182
|
writeFileSync(pluginPath, pluginSource);
|
|
210
183
|
|
|
211
|
-
console.log(`
|
|
184
|
+
console.log(`Plugin v${versionInfo.version} installed`);
|
|
212
185
|
|
|
213
|
-
// Save version to config
|
|
214
186
|
const config = loadGlobalConfig();
|
|
215
187
|
config.pluginVersion = versionInfo.version;
|
|
216
188
|
saveGlobalConfig(config);
|
|
217
189
|
|
|
218
190
|
return true;
|
|
219
191
|
} catch (err) {
|
|
220
|
-
console.error(`
|
|
221
|
-
console.log('
|
|
192
|
+
console.error(`Plugin install failed: ${err.message}`);
|
|
193
|
+
console.log('Download manually: https://ropilot.ai/plugin/StudioPlugin.lua');
|
|
222
194
|
return false;
|
|
223
195
|
}
|
|
224
196
|
}
|
|
225
197
|
|
|
226
|
-
|
|
227
198
|
/**
|
|
228
199
|
* Download prompts from edge server
|
|
229
200
|
*/
|
|
230
201
|
async function downloadPrompts(apiKey) {
|
|
231
|
-
console.log('Downloading latest agent package...');
|
|
232
|
-
|
|
233
202
|
try {
|
|
234
|
-
// Fetch from edge server
|
|
235
203
|
const response = await fetch(`${EDGE_URL}/package`);
|
|
236
204
|
if (!response.ok) {
|
|
237
205
|
throw new Error(`Server returned ${response.status}`);
|
|
238
206
|
}
|
|
239
207
|
|
|
240
208
|
const pkg = await response.json();
|
|
241
|
-
console.log(`Package version: ${pkg.version}`);
|
|
242
209
|
|
|
243
|
-
// Ensure prompts directory exists
|
|
244
210
|
mkdirSync(PROMPTS_DIR, { recursive: true });
|
|
245
|
-
|
|
246
|
-
// Write package info
|
|
247
211
|
writeFileSync(join(PROMPTS_DIR, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
248
212
|
|
|
249
|
-
// Update config with version info
|
|
250
213
|
const config = loadGlobalConfig();
|
|
251
214
|
config.promptsVersion = pkg.version;
|
|
252
215
|
config.lastUpdated = new Date().toISOString();
|
|
253
216
|
saveGlobalConfig(config);
|
|
254
217
|
|
|
255
|
-
console.log(
|
|
218
|
+
console.log(`Agent package v${pkg.version} downloaded`);
|
|
256
219
|
return pkg;
|
|
257
220
|
} catch (err) {
|
|
258
|
-
console.error('Failed to download
|
|
259
|
-
console.log('Using bundled defaults...');
|
|
221
|
+
console.error('Failed to download package:', err.message);
|
|
260
222
|
|
|
261
|
-
// Fallback to bundled defaults
|
|
262
223
|
const prompts = getDefaultPrompts();
|
|
263
224
|
mkdirSync(PROMPTS_DIR, { recursive: true });
|
|
264
225
|
for (const [name, content] of Object.entries(prompts)) {
|
|
@@ -275,57 +236,12 @@ async function downloadPrompts(apiKey) {
|
|
|
275
236
|
}
|
|
276
237
|
|
|
277
238
|
/**
|
|
278
|
-
* Get default prompts (bundled)
|
|
239
|
+
* Get default prompts (bundled fallback)
|
|
279
240
|
*/
|
|
280
241
|
function getDefaultPrompts() {
|
|
281
242
|
return {
|
|
282
|
-
'CLAUDE.md':
|
|
283
|
-
|
|
284
|
-
You are connected to Ropilot, an AI assistant for Roblox Studio development.
|
|
285
|
-
|
|
286
|
-
## Available Tools
|
|
287
|
-
|
|
288
|
-
Ropilot provides MCP tools for interacting with Roblox Studio:
|
|
289
|
-
|
|
290
|
-
### Reading & Inspection
|
|
291
|
-
- \`ropilot_listchildren\` - List children of an instance
|
|
292
|
-
- \`ropilot_getproperties\` - Get properties of an instance
|
|
293
|
-
- \`ropilot_search\` - Search for instances by name
|
|
294
|
-
- \`ropilot_searchbyclass\` - Search for instances by class name
|
|
295
|
-
|
|
296
|
-
### Playtesting
|
|
297
|
-
- \`ropilot_test_start_playtest\` - Start a playtest session
|
|
298
|
-
- \`ropilot_test_stop_playtest\` - Stop the current playtest
|
|
299
|
-
- \`ropilot_test_run_client_lua\` - Run Lua code on the client
|
|
300
|
-
- \`ropilot_test_run_server_lua\` - Run Lua code on the server
|
|
301
|
-
|
|
302
|
-
### Data Management
|
|
303
|
-
- \`ropilot_reset_data\` - Reset player data in DataStore
|
|
304
|
-
|
|
305
|
-
## Context Targeting
|
|
306
|
-
|
|
307
|
-
Commands can target different contexts:
|
|
308
|
-
- \`edit\` - Edit mode (default)
|
|
309
|
-
- \`server\` - Server context during playtest
|
|
310
|
-
- \`client\` - Client context during playtest
|
|
311
|
-
|
|
312
|
-
Use the \`target_context\` parameter to specify which context should execute the command.
|
|
313
|
-
|
|
314
|
-
## Best Practices
|
|
315
|
-
|
|
316
|
-
1. **Read before modify** - Always inspect the current state before making changes
|
|
317
|
-
2. **Use specific paths** - Reference instances by their full path (e.g., "Workspace.Map.Building")
|
|
318
|
-
3. **Delegate playtesting** - Use the roblox-tester subagent for playtest operations
|
|
319
|
-
4. **Test incrementally** - Start playtests to verify changes work correctly
|
|
320
|
-
`,
|
|
321
|
-
|
|
322
|
-
'subagents.json': JSON.stringify({
|
|
323
|
-
"roblox-tester": {
|
|
324
|
-
"description": "Fast subagent for executing Roblox playtest steps",
|
|
325
|
-
"tools": ["ropilot_test_*", "ropilot_get_*", "ropilot_execute_lua"],
|
|
326
|
-
"prompt": "You are a Roblox playtest executor. Run the specified test steps and report results concisely."
|
|
327
|
-
}
|
|
328
|
-
}, null, 2)
|
|
243
|
+
'CLAUDE.md': `@import ROPILOT.md\n`,
|
|
244
|
+
'ROPILOT.md': `# Ropilot\n\nRoblox Studio AI assistant. Use ropilot_* MCP tools to interact with Studio.\n`
|
|
329
245
|
};
|
|
330
246
|
}
|
|
331
247
|
|
|
@@ -334,60 +250,61 @@ Use the \`target_context\` parameter to specify which context should execute the
|
|
|
334
250
|
*/
|
|
335
251
|
function setupProjectPrompts(pkg) {
|
|
336
252
|
const files = pkg.files || {};
|
|
253
|
+
const preserveFiles = ['CLAUDE.md'];
|
|
254
|
+
|
|
255
|
+
let created = 0, updated = 0, skipped = 0;
|
|
337
256
|
|
|
338
257
|
for (const [filePath, content] of Object.entries(files)) {
|
|
339
258
|
const fullPath = filePath;
|
|
340
259
|
const dir = dirname(fullPath);
|
|
341
260
|
|
|
342
|
-
// Create directory if needed
|
|
343
261
|
if (dir && dir !== '.') {
|
|
344
262
|
mkdirSync(dir, { recursive: true });
|
|
345
263
|
}
|
|
346
264
|
|
|
347
|
-
// Check if file exists
|
|
348
265
|
if (existsSync(fullPath)) {
|
|
349
|
-
console.log(` Exists: ${fullPath}`);
|
|
350
|
-
|
|
351
|
-
// For CLAUDE.md, append @import line if not already present
|
|
352
266
|
if (fullPath === 'CLAUDE.md') {
|
|
353
267
|
const existing = readFileSync(fullPath, 'utf-8');
|
|
354
268
|
if (!existing.includes('@import ROPILOT.md')) {
|
|
355
269
|
writeFileSync(fullPath, existing.trimEnd() + '\n\n@import ROPILOT.md\n');
|
|
356
|
-
|
|
270
|
+
updated++;
|
|
271
|
+
} else {
|
|
272
|
+
skipped++;
|
|
357
273
|
}
|
|
274
|
+
} else if (preserveFiles.includes(fullPath)) {
|
|
275
|
+
skipped++;
|
|
276
|
+
} else {
|
|
277
|
+
writeFileSync(fullPath, content);
|
|
278
|
+
updated++;
|
|
358
279
|
}
|
|
359
280
|
} else {
|
|
360
281
|
writeFileSync(fullPath, content);
|
|
361
|
-
|
|
282
|
+
created++;
|
|
362
283
|
}
|
|
363
284
|
}
|
|
285
|
+
|
|
286
|
+
console.log(`Project files: ${created} created, ${updated} updated, ${skipped} unchanged`);
|
|
364
287
|
}
|
|
365
288
|
|
|
366
289
|
/**
|
|
367
290
|
* Initialize Ropilot in current project
|
|
368
291
|
*/
|
|
369
292
|
export async function init(providedApiKey = null) {
|
|
370
|
-
console.log('');
|
|
371
|
-
console.log('Welcome to Ropilot!');
|
|
372
|
-
console.log('===================');
|
|
373
|
-
console.log('');
|
|
293
|
+
console.log('\nRopilot Setup\n');
|
|
374
294
|
|
|
375
|
-
// Always prompt for API key (per-project config)
|
|
376
295
|
const existingKey = loadProjectConfig()?.apiKey;
|
|
377
296
|
const defaultHint = existingKey ? ` [${existingKey.slice(0, 20)}...]` : '';
|
|
378
297
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
298
|
+
if (!existingKey) {
|
|
299
|
+
console.log('Get your API key at: https://ropilot.ai\n');
|
|
300
|
+
}
|
|
382
301
|
|
|
383
302
|
let apiKey = providedApiKey;
|
|
384
303
|
if (!apiKey) {
|
|
385
|
-
apiKey = await prompt(`
|
|
304
|
+
apiKey = await prompt(`API key${defaultHint}: `);
|
|
386
305
|
|
|
387
|
-
// If empty and existing key, use existing
|
|
388
306
|
if (!apiKey && existingKey) {
|
|
389
307
|
apiKey = existingKey;
|
|
390
|
-
console.log(`Using existing key: ${apiKey.slice(0, 20)}...`);
|
|
391
308
|
}
|
|
392
309
|
}
|
|
393
310
|
|
|
@@ -396,45 +313,26 @@ export async function init(providedApiKey = null) {
|
|
|
396
313
|
process.exit(1);
|
|
397
314
|
}
|
|
398
315
|
|
|
399
|
-
// Save to project config (.ropilot.json)
|
|
400
316
|
setProjectApiKey(apiKey);
|
|
401
|
-
console.log('API key saved to .ropilot.json');
|
|
402
|
-
console.log('');
|
|
403
|
-
|
|
317
|
+
console.log('API key saved to .ropilot.json\n');
|
|
404
318
|
|
|
405
|
-
// Install Studio plugin
|
|
406
319
|
await installPlugin();
|
|
407
|
-
console.log('');
|
|
408
|
-
|
|
409
|
-
// Download prompts
|
|
410
320
|
const pkg = await downloadPrompts(apiKey);
|
|
411
|
-
console.log('');
|
|
412
|
-
|
|
413
|
-
// Set up project files (includes .mcp.json from package)
|
|
414
|
-
console.log('Setting up project files...');
|
|
415
321
|
setupProjectPrompts(pkg);
|
|
416
|
-
console.log('');
|
|
417
322
|
|
|
418
|
-
|
|
419
|
-
console.log('Setup complete!');
|
|
420
|
-
console.log('');
|
|
323
|
+
console.log('\nSetup complete!\n');
|
|
421
324
|
console.log('Next steps:');
|
|
422
|
-
console.log('1. Open
|
|
423
|
-
console.log('2.
|
|
424
|
-
console.log('3.
|
|
425
|
-
console.log('');
|
|
325
|
+
console.log('1. Open Studio → Plugins tab → Click "Ropilot AI"');
|
|
326
|
+
console.log('2. Enter your API key and click Connect');
|
|
327
|
+
console.log('3. Start Claude Code or Cursor in this directory\n');
|
|
426
328
|
}
|
|
427
329
|
|
|
428
330
|
/**
|
|
429
331
|
* Update prompts to latest version
|
|
430
332
|
*/
|
|
431
333
|
export async function update() {
|
|
432
|
-
console.log('');
|
|
433
|
-
console.log('Updating Ropilot...');
|
|
434
|
-
console.log('===================');
|
|
435
|
-
console.log('');
|
|
334
|
+
console.log('\nUpdating Ropilot...\n');
|
|
436
335
|
|
|
437
|
-
// Check project config first, then global
|
|
438
336
|
const projectConfig = loadProjectConfig();
|
|
439
337
|
const apiKey = projectConfig?.apiKey || getApiKey();
|
|
440
338
|
|
|
@@ -443,25 +341,9 @@ export async function update() {
|
|
|
443
341
|
process.exit(1);
|
|
444
342
|
}
|
|
445
343
|
|
|
446
|
-
const source = projectConfig?.apiKey ? '.ropilot.json' : '~/.ropilot/config.json';
|
|
447
|
-
console.log(`Using API key from ${source}: ${apiKey.slice(0, 20)}...`);
|
|
448
|
-
console.log('');
|
|
449
|
-
|
|
450
|
-
// Re-install Studio plugin
|
|
451
344
|
await installPlugin();
|
|
452
|
-
console.log('');
|
|
453
|
-
|
|
454
|
-
// Re-download prompts
|
|
455
345
|
const pkg = await downloadPrompts(apiKey);
|
|
456
|
-
console.log('');
|
|
457
|
-
|
|
458
|
-
// Re-write project files
|
|
459
|
-
console.log('Updating project files...');
|
|
460
346
|
setupProjectPrompts(pkg);
|
|
461
|
-
console.log('');
|
|
462
347
|
|
|
463
|
-
console.log('
|
|
464
|
-
console.log('');
|
|
465
|
-
console.log('Note: Check the Studio Plugins tab to verify the updated plugin is loaded.');
|
|
466
|
-
console.log('');
|
|
348
|
+
console.log('\nUpdate complete! Restart Studio to reload the plugin.\n');
|
|
467
349
|
}
|