ropilot 0.1.2 → 0.1.4
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/README.md +1 -1
- package/lib/config.js +1 -1
- package/lib/proxy.js +54 -18
- package/lib/setup.js +161 -18
- package/package.json +1 -1
package/README.md
CHANGED
package/lib/config.js
CHANGED
|
@@ -14,7 +14,7 @@ const GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, 'config.json');
|
|
|
14
14
|
const PROJECT_CONFIG_FILE = '.ropilot.json';
|
|
15
15
|
|
|
16
16
|
// Edge server base URL
|
|
17
|
-
export const EDGE_URL = 'https://ropilot.ai/
|
|
17
|
+
export const EDGE_URL = 'https://ropilot.ai/edge';
|
|
18
18
|
|
|
19
19
|
// Default config
|
|
20
20
|
const DEFAULT_CONFIG = {
|
package/lib/proxy.js
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createInterface } from 'readline';
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { getApiKey, EDGE_URL, loadGlobalConfig, saveGlobalConfig } from './config.js';
|
|
13
|
+
|
|
14
|
+
// Plugin version check URL (served from ropilot.ai main server)
|
|
15
|
+
const PLUGIN_VERSION_URL = 'https://ropilot.ai/plugin/version';
|
|
16
|
+
const PLUGIN_DOWNLOAD_URL = 'https://ropilot.ai/plugin/StudioPlugin.lua';
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Send JSON-RPC response to stdout
|
|
@@ -54,23 +61,11 @@ async function forwardToEdge(apiKey, request) {
|
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
/**
|
|
57
|
-
* Handle MCP initialize request
|
|
64
|
+
* Handle MCP initialize request - forward to edge to get instructions
|
|
58
65
|
*/
|
|
59
|
-
function handleInitialize(request) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
id: request.id,
|
|
63
|
-
result: {
|
|
64
|
-
protocolVersion: '2024-11-05',
|
|
65
|
-
capabilities: {
|
|
66
|
-
tools: {}
|
|
67
|
-
},
|
|
68
|
-
serverInfo: {
|
|
69
|
-
name: 'ropilot',
|
|
70
|
-
version: '0.1.0'
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
66
|
+
async function handleInitialize(apiKey, request) {
|
|
67
|
+
const edgeResponse = await forwardToEdge(apiKey, request);
|
|
68
|
+
return edgeResponse;
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
/**
|
|
@@ -97,7 +92,7 @@ async function processRequest(apiKey, request) {
|
|
|
97
92
|
|
|
98
93
|
switch (method) {
|
|
99
94
|
case 'initialize':
|
|
100
|
-
return handleInitialize(request);
|
|
95
|
+
return await handleInitialize(apiKey, request);
|
|
101
96
|
|
|
102
97
|
case 'initialized':
|
|
103
98
|
// Notification, no response needed
|
|
@@ -115,6 +110,44 @@ async function processRequest(apiKey, request) {
|
|
|
115
110
|
}
|
|
116
111
|
}
|
|
117
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Check for plugin updates (runs in background, writes to stderr)
|
|
115
|
+
*/
|
|
116
|
+
async function checkPluginUpdate() {
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(PLUGIN_VERSION_URL);
|
|
119
|
+
if (!response.ok) return;
|
|
120
|
+
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
const latestVersion = data.version;
|
|
123
|
+
const downloadUrl = data.download_url || PLUGIN_DOWNLOAD_URL;
|
|
124
|
+
|
|
125
|
+
// Check if we've already notified about this version
|
|
126
|
+
const config = loadGlobalConfig();
|
|
127
|
+
const lastNotifiedVersion = config.lastPluginVersionNotified;
|
|
128
|
+
|
|
129
|
+
if (latestVersion && latestVersion !== lastNotifiedVersion) {
|
|
130
|
+
// Check if we have a stored plugin version
|
|
131
|
+
const storedVersion = config.pluginVersion;
|
|
132
|
+
|
|
133
|
+
if (!storedVersion || storedVersion !== latestVersion) {
|
|
134
|
+
// New version available - notify via stderr (doesn't interfere with MCP protocol)
|
|
135
|
+
console.error('');
|
|
136
|
+
console.error(`[Ropilot] Plugin update available: v${latestVersion}`);
|
|
137
|
+
console.error(`[Ropilot] Download: ${downloadUrl}`);
|
|
138
|
+
console.error(`[Ropilot] Install to Roblox Studio plugins folder and restart Studio.`);
|
|
139
|
+
console.error('');
|
|
140
|
+
|
|
141
|
+
// Save that we notified about this version
|
|
142
|
+
config.lastPluginVersionNotified = latestVersion;
|
|
143
|
+
saveGlobalConfig(config);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// Silently ignore update check errors - not critical
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
118
151
|
/**
|
|
119
152
|
* Run the stdio MCP server
|
|
120
153
|
*/
|
|
@@ -127,6 +160,9 @@ export async function serve() {
|
|
|
127
160
|
process.exit(1);
|
|
128
161
|
}
|
|
129
162
|
|
|
163
|
+
// Check for plugin updates in background (non-blocking)
|
|
164
|
+
checkPluginUpdate();
|
|
165
|
+
|
|
130
166
|
// Read JSON-RPC messages from stdin
|
|
131
167
|
let buffer = '';
|
|
132
168
|
let contentLength = -1;
|
package/lib/setup.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* - First-time setup
|
|
6
6
|
* - Downloading system prompts
|
|
7
7
|
* - Creating project configs
|
|
8
|
+
* - Installing Studio plugin
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
|
|
11
|
+
import { existsSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from 'fs';
|
|
11
12
|
import { join, dirname } from 'path';
|
|
12
|
-
import { homedir } from 'os';
|
|
13
|
+
import { homedir, platform } from 'os';
|
|
13
14
|
import { createInterface } from 'readline';
|
|
14
15
|
import {
|
|
15
16
|
loadGlobalConfig,
|
|
@@ -22,6 +23,65 @@ import {
|
|
|
22
23
|
// Prompts storage directory
|
|
23
24
|
const PROMPTS_DIR = join(homedir(), '.ropilot', 'prompts');
|
|
24
25
|
|
|
26
|
+
// Plugin URLs
|
|
27
|
+
const PLUGIN_VERSION_URL = 'https://ropilot.ai/plugin/version';
|
|
28
|
+
const PLUGIN_DOWNLOAD_URL = 'https://ropilot.ai/plugin/StudioPlugin.lua';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if running in WSL
|
|
32
|
+
*/
|
|
33
|
+
function isWSL() {
|
|
34
|
+
try {
|
|
35
|
+
const release = readFileSync('/proc/version', 'utf-8').toLowerCase();
|
|
36
|
+
return release.includes('microsoft') || release.includes('wsl');
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get Windows username when running in WSL
|
|
44
|
+
*/
|
|
45
|
+
function getWindowsUsername() {
|
|
46
|
+
try {
|
|
47
|
+
const { execSync } = require('child_process');
|
|
48
|
+
const username = execSync('cmd.exe /c echo %USERNAME%', { encoding: 'utf-8' }).trim();
|
|
49
|
+
if (username && !username.includes('%')) {
|
|
50
|
+
return username;
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get Roblox plugins folder path based on OS (with WSL detection)
|
|
58
|
+
*/
|
|
59
|
+
function getPluginsFolder() {
|
|
60
|
+
const os = platform();
|
|
61
|
+
|
|
62
|
+
// Check for WSL first - need to use Windows path
|
|
63
|
+
if (os === 'linux' && isWSL()) {
|
|
64
|
+
const windowsUser = getWindowsUsername();
|
|
65
|
+
if (windowsUser) {
|
|
66
|
+
const pluginsPath = join('/mnt/c/Users', windowsUser, 'AppData', 'Local', 'Roblox', 'Plugins');
|
|
67
|
+
if (existsSync(dirname(pluginsPath)) || existsSync('/mnt/c/Users/' + windowsUser)) {
|
|
68
|
+
return pluginsPath;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log(' Warning: Could not detect Windows username for WSL plugin install');
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (os === 'win32') {
|
|
76
|
+
return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'Roblox', 'Plugins');
|
|
77
|
+
} else if (os === 'darwin') {
|
|
78
|
+
return join(homedir(), 'Documents', 'Roblox', 'Plugins');
|
|
79
|
+
} else {
|
|
80
|
+
// Linux without WSL - Roblox doesn't officially support Linux
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
25
85
|
/**
|
|
26
86
|
* Prompt for user input
|
|
27
87
|
*/
|
|
@@ -39,6 +99,95 @@ function prompt(question) {
|
|
|
39
99
|
});
|
|
40
100
|
}
|
|
41
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Install Studio plugin to Roblox plugins folder
|
|
104
|
+
*/
|
|
105
|
+
async function installPlugin() {
|
|
106
|
+
console.log('Installing Studio plugin...');
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Check plugin version
|
|
110
|
+
const versionResponse = await fetch(PLUGIN_VERSION_URL);
|
|
111
|
+
if (!versionResponse.ok) {
|
|
112
|
+
throw new Error(`Failed to fetch plugin version: ${versionResponse.status}`);
|
|
113
|
+
}
|
|
114
|
+
const versionInfo = await versionResponse.json();
|
|
115
|
+
console.log(` Plugin version: ${versionInfo.version}`);
|
|
116
|
+
|
|
117
|
+
// Download plugin
|
|
118
|
+
const pluginResponse = await fetch(PLUGIN_DOWNLOAD_URL);
|
|
119
|
+
if (!pluginResponse.ok) {
|
|
120
|
+
throw new Error(`Failed to download plugin: ${pluginResponse.status}`);
|
|
121
|
+
}
|
|
122
|
+
const pluginSource = await pluginResponse.text();
|
|
123
|
+
|
|
124
|
+
// Get plugins folder
|
|
125
|
+
const pluginsFolder = getPluginsFolder();
|
|
126
|
+
|
|
127
|
+
if (!pluginsFolder) {
|
|
128
|
+
console.log(' Could not determine Roblox plugins folder');
|
|
129
|
+
console.log(' Manual install: Download from https://ropilot.ai/plugin/StudioPlugin.lua');
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create plugins folder if it doesn't exist
|
|
134
|
+
mkdirSync(pluginsFolder, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Write plugin file
|
|
137
|
+
const pluginPath = join(pluginsFolder, 'RopilotPlugin.lua');
|
|
138
|
+
writeFileSync(pluginPath, pluginSource);
|
|
139
|
+
|
|
140
|
+
console.log(` Installed to: ${pluginPath}`);
|
|
141
|
+
|
|
142
|
+
// Save version to config
|
|
143
|
+
const config = loadGlobalConfig();
|
|
144
|
+
config.pluginVersion = versionInfo.version;
|
|
145
|
+
saveGlobalConfig(config);
|
|
146
|
+
|
|
147
|
+
return true;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(` Failed to install plugin: ${err.message}`);
|
|
150
|
+
console.log(' You can manually download from: https://ropilot.ai/plugin/StudioPlugin.lua');
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create .mcp.json for Claude Code compatibility
|
|
157
|
+
*/
|
|
158
|
+
function createMcpConfig() {
|
|
159
|
+
const mcpConfig = {
|
|
160
|
+
mcpServers: {
|
|
161
|
+
ropilot: {
|
|
162
|
+
command: "npx",
|
|
163
|
+
args: ["ropilot"]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const mcpPath = '.mcp.json';
|
|
169
|
+
|
|
170
|
+
if (existsSync(mcpPath)) {
|
|
171
|
+
// Merge with existing config
|
|
172
|
+
try {
|
|
173
|
+
const existing = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
174
|
+
if (!existing.mcpServers?.ropilot) {
|
|
175
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
176
|
+
existing.mcpServers.ropilot = mcpConfig.mcpServers.ropilot;
|
|
177
|
+
writeFileSync(mcpPath, JSON.stringify(existing, null, 2));
|
|
178
|
+
console.log(` Updated: ${mcpPath} (added ropilot server)`);
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` Exists: ${mcpPath} (ropilot already configured)`);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(` Failed to update ${mcpPath}: ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
|
|
187
|
+
console.log(` Created: ${mcpPath}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
42
191
|
/**
|
|
43
192
|
* Download prompts from edge server
|
|
44
193
|
*/
|
|
@@ -209,34 +358,28 @@ export async function init(providedApiKey = null) {
|
|
|
209
358
|
console.log('');
|
|
210
359
|
}
|
|
211
360
|
|
|
361
|
+
// Install Studio plugin
|
|
362
|
+
console.log('');
|
|
363
|
+
await installPlugin();
|
|
364
|
+
console.log('');
|
|
365
|
+
|
|
212
366
|
// Download prompts
|
|
213
367
|
const pkg = await downloadPrompts(apiKey);
|
|
214
368
|
console.log('');
|
|
215
369
|
|
|
216
|
-
// Set up project
|
|
370
|
+
// Set up project files
|
|
217
371
|
console.log('Setting up project files...');
|
|
218
372
|
setupProjectPrompts(pkg);
|
|
373
|
+
createMcpConfig();
|
|
219
374
|
console.log('');
|
|
220
375
|
|
|
221
376
|
// Done!
|
|
222
377
|
console.log('Setup complete!');
|
|
223
378
|
console.log('');
|
|
224
379
|
console.log('Next steps:');
|
|
225
|
-
console.log('1.
|
|
226
|
-
console.log('2.
|
|
227
|
-
console.log('');
|
|
228
|
-
console.log(' For Claude Desktop, add to claude_desktop_config.json:');
|
|
229
|
-
console.log(' {');
|
|
230
|
-
console.log(' "mcpServers": {');
|
|
231
|
-
console.log(' "ropilot": {');
|
|
232
|
-
console.log(' "command": "npx",');
|
|
233
|
-
console.log(' "args": ["ropilot"]');
|
|
234
|
-
console.log(' }');
|
|
235
|
-
console.log(' }');
|
|
236
|
-
console.log(' }');
|
|
237
|
-
console.log('');
|
|
238
|
-
console.log(' Or use HTTP directly:');
|
|
239
|
-
console.log(` ${EDGE_URL}/mcp/${apiKey}/message`);
|
|
380
|
+
console.log('1. Restart Roblox Studio to load the plugin');
|
|
381
|
+
console.log('2. In Studio: Plugins tab → Ropilot → Settings → Enter your API key');
|
|
382
|
+
console.log('3. Run Claude Code in this directory - it will auto-detect .mcp.json');
|
|
240
383
|
console.log('');
|
|
241
384
|
}
|
|
242
385
|
|