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.
Files changed (2) hide show
  1. package/lib/setup.js +41 -159
  2. 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(' Multiple Windows users detected:');
169
- folderResult.users.forEach((u, i) => console.log(` ${i + 1}. ${u}`));
170
- const choice = await prompt(` Enter number (1-${folderResult.users.length}) or username: `);
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(' Enter your Windows username: ');
190
- if (username && existsSync(join('/mnt/c/Users', 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(' Could not determine Roblox plugins folder');
200
- console.log(' Manual install: Download from https://ropilot.ai/plugin/StudioPlugin.lua');
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(` Installed to: ${pluginPath}`);
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(` Failed to install plugin: ${err.message}`);
221
- console.log(' You can manually download from: https://ropilot.ai/plugin/StudioPlugin.lua');
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('Agent package downloaded successfully!');
218
+ console.log(`Agent package v${pkg.version} downloaded`);
256
219
  return pkg;
257
220
  } catch (err) {
258
- console.error('Failed to download from server:', err.message);
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': `# Ropilot - AI-Powered Roblox Development
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
- console.log(` Updated: ${fullPath} (added @import ROPILOT.md)`);
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
- console.log(` Created: ${fullPath}`);
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
- console.log('To use Ropilot, you need an API key.');
380
- console.log('Get one at: https://ropilot.ai');
381
- console.log('');
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(`Enter your API key${defaultHint}: `);
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
- // Done!
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 Roblox Studio and check the Plugins tab for "Ropilot AI"');
423
- console.log('2. Click Ropilot AI → Enter your API key Connect');
424
- console.log('3. Run Claude Code, Cursor, or any MCP-compatible AI tool in this directory');
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('Update complete!');
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ropilot",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "AI-powered Roblox development assistant - MCP CLI",
5
5
  "author": "whut",
6
6
  "license": "MIT",